Compare commits

..

301 Commits

Author SHA1 Message Date
Joostlek ff43e12449 Merge branch 'dev' into electrolux
# Conflicts:
#	requirements_test_all.txt
2026-05-21 14:22:34 +02:00
Markus Tuominen 654408cc76 Set _attr_has_entity_name on sonos SonosFavoritesEntity (#171678) 2026-05-21 13:42:21 +02:00
Max Michels 1f814faad8 Replace duplicate constants with homeassistant.const imports (#171702) 2026-05-21 13:36:14 +02:00
Markus Tuominen 6e00eecfcd Set _attr_has_entity_name on lunatone LunatoneLineBroadcastLight (#171682) 2026-05-21 13:19:42 +02:00
Robert Resch 8c8620c511 Add check requirements yanked and CVE check (#171641)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-05-21 12:54:15 +02:00
Wendelin cca8825ca5 Add comment optional attribute to automation items (#171091) 2026-05-21 12:52:54 +02:00
Max Michels 92fbcc29a5 Replace duplicate constants with homeassistant.const imports (#171700) 2026-05-21 12:51:19 +02:00
Shay Levy 1c28833f39 Fix LG WebOS TV translation placeholders mismatches (#171696) 2026-05-21 13:33:36 +03:00
Christian Lackas cfdef77222 homematicip_cloud: migrate entity names to has_entity_name (#169273) 2026-05-21 12:29:43 +02:00
epenet 49720475da Bump renault-api to 0.5.10 (#171692) 2026-05-21 12:16:29 +02:00
Markus Tuominen 7967b84cc6 Set _attr_has_entity_name on omie OMIEPriceSensor (#171671) 2026-05-21 12:14:03 +02:00
Markus Tuominen c715557813 Set _attr_has_entity_name on smartthings SmartThingsScene (#171672) 2026-05-21 12:10:06 +02:00
Markus Tuominen 79e5330782 Set _attr_has_entity_name on ekeybionyx EkeyEvent (#171668) 2026-05-21 12:03:01 +02:00
Max Michels 5210ca64b1 Replace duplicate constants with homeassistant.const imports (#171669) 2026-05-21 12:02:12 +02:00
Markus Tuominen 65283e3d77 Set _attr_has_entity_name on fitbit battery sensors (#171670) 2026-05-21 12:01:27 +02:00
mhuiskes 427cb9f8db Remove unnecessary intermediate variables in zeversolar diagnostics (#171691) 2026-05-21 11:55:34 +02:00
Erik Montnemery a09e042d42 Add test of FlowHandler show_advanced_options property (#171681) 2026-05-21 11:47:42 +02:00
Shay Levy 072e9b51a2 Fix Shelly translation placeholders mismatches (#171685) 2026-05-21 11:47:20 +02:00
Erik Montnemery b96342c4f3 Remove use of advanced mode from the knx integration (#171674) 2026-05-21 11:26:22 +02:00
Erik Montnemery 56eae8c808 Fix min value for music_assistant.get_library offset (#171664) 2026-05-21 10:25:08 +02:00
Erik Montnemery 9fbdf86104 Rename advanced options section to additional options in opendisplay service actions (#171452) 2026-05-21 10:18:31 +02:00
Jan Bouwhuis 8ff5da59c4 Fix hardcoded exception strings in incomfort (#171616) 2026-05-21 10:09:04 +02:00
Andres Ruiz 298f4f8ed0 Remove National Grid US virtual integration (#171204) 2026-05-21 09:53:59 +02:00
Willem-Jan van Rootselaar 6fdc0bb90b Fix bsblan set data error translation (#171529) 2026-05-21 09:51:54 +02:00
Mick Vleeshouwer 94c3ad2cb2 Bump pyOverkiz to 1.20.4 (#171626) 2026-05-21 09:50:54 +02:00
Martin Hjelmare d83d44648c Fix Home Connect exception translation placeholder mismatch (#171655) 2026-05-21 09:38:22 +02:00
Erik Montnemery 279b614b7c Remove advanced mode from music_assistant service actions (#171451) 2026-05-21 09:35:48 +02:00
Erik Montnemery 244dfe014a Remove advanced mode from mqtt service actions (#171448)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-21 09:33:10 +02:00
Markus Tuominen 6b379e50cf Add has-entity-name pylint quality scale checker (#171486) 2026-05-21 10:21:06 +03:00
epenet 1368cd15da Remove myself from samsungtv code-owners (#171654) 2026-05-21 08:54:59 +02:00
Franck Nijhof 8c8cc3acb9 Fix habitica ignoring zero values for interval and streak (#171468) 2026-05-21 08:06:08 +02:00
Franck Nijhof b0634bea35 Fix SmartThings crash when timestamp attribute is None (#171467) 2026-05-21 08:05:42 +02:00
Raphael Hehl 5ae31cad6f Fix unifiprotect exception translations (#171510) (#171619)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-21 08:04:21 +02:00
Brandon Rothweiler b45aaaa177 Update py-aosmith to 1.0.18 (#171647) 2026-05-21 07:42:07 +02:00
Jan-Philipp Benecke 6560496440 Add missing WebDAV exception translation (#171614) 2026-05-20 20:46:31 -04:00
Erwin Douna 489dda8efb SMA refactor to new pylint (#171630) 2026-05-20 20:45:39 -04:00
Alexey Masolov 30c942d139 Catch requests.Timeout and apply TIMEOUT constant across CalDAV integration (#171632)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 20:45:08 -04:00
On Freund c735e47e23 Bump pyrisco to 0.7.0 (#171644) 2026-05-20 20:42:29 -04:00
Robert Resch 3856405c72 Add aw check requirements async block check (#171642) 2026-05-21 01:28:32 +02:00
Robert Resch 323479ca44 Fix aw check requirements safe output (#171643) 2026-05-21 01:16:25 +02:00
Raphael Hehl c8bfe56975 Fix hardcoded exception strings in unifi_access (#171629)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-05-21 00:37:08 +02:00
A. Gideonse ab214b64f2 Implement final Indevolt exceptions translations (#171635) 2026-05-21 00:35:01 +02:00
Max Michels fea673d93a Replace duplicate constants with homeassistant.const imports (#171639) 2026-05-21 00:24:05 +02:00
Max Michels 5405151112 Replace duplicate constants with homeassistant.const imports (#171637) 2026-05-21 00:12:23 +02:00
Max Michels b3c210ef24 Replace duplicate constants with homeassistant.const imports (#171638) 2026-05-21 00:11:59 +02:00
Robert Resch 5f5d74cfbd Remove requirements_test_all file (#171530) 2026-05-20 23:54:31 +02:00
Josh Gustafson c188fdcc8b Clean up arcam_fmj config flow (#171161)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 22:58:10 +02:00
Michael Hansen a3b43fc19b Handle multiple intents in Wyoming conversation (#171615)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
2026-05-20 16:48:56 -04:00
Maciej Bieniek 894a68acb6 Fix media_image_hash and validate the MIME type in the Shelly media player (#171585) 2026-05-20 22:25:30 +02:00
Kamil Breguła 30bc3fc412 Bump wled to 0.23.0 and remove backoff exception (#171622) 2026-05-20 22:16:43 +02:00
Michael 3cc0cc38ab Add missing translation placeholders for SMA exceptions (#171625) 2026-05-20 21:31:44 +02:00
Michael 296caa90c1 Fix exception strings in FRITZ!Box tools (#171603) 2026-05-20 20:55:42 +02:00
A. Gideonse bb4c211fb6 Add DHCP discovery to Indevolt (#169597)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 20:53:23 +02:00
Nick Haghiri d4fa904386 Add invalid_auth exception translation key to backblaze_b2 (#171584)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 20:51:44 +02:00
Erik Montnemery db98f0b434 Remove advanced mode from homeassistant service actions (#171440)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 20:40:36 +02:00
Erwin Douna 7341ac91ee SMA add missing exceptions (#171550) 2026-05-20 20:32:19 +02:00
Jan Bouwhuis b2fb5df0fb Remove positional message strings when translation_key is set in mqtt (#171617) 2026-05-20 20:16:41 +02:00
Manu 265485a7d0 Fix positional message strings in exceptions in Notify for Android TV / Fire TV integration (#171581) 2026-05-20 20:00:30 +02:00
J. Nick Koston bf1b93fb66 Bump aioesphomeapi to 45.0.4 (#171601) 2026-05-20 12:54:08 -05:00
dontinelli be9d4bedfd Fix update error message key in solarlog (#171611) 2026-05-20 19:53:19 +02:00
Franck Nijhof e8ac982e83 Add pylint checker for exception translation validation (#171453)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-20 19:45:56 +02:00
Abílio Costa 6c8e5a8e98 Add common availability test helper for IR/RF integrations (#171610)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 13:05:37 -04:00
Paul Bottein e40a3e18db Send entity domain in template config flow preview (#171599) 2026-05-20 13:00:23 -04:00
Robert Resch cba05caadd Fix aw generation (#171609) 2026-05-20 12:56:57 -04:00
Denis Shulyaka ef3bc61e2b Remove stale temperature key from anthropic strings (#171612) 2026-05-20 12:56:33 -04:00
shbatm 3eff36eb9d Use CONF_CODE from homeassistant.const in isy994 (#171608)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 18:48:35 +02:00
Michael 8402a4d876 Fix hardcoded exception strings in tankerkoenig (#171607) 2026-05-20 18:24:32 +02:00
shbatm 6159516dc0 Bump pyisy to 3.6.1 and modernize TLS handling for isy994 (#170136)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 18:12:11 +02:00
Åke Strandberg 8fd3dcc7b1 Fix translation placeholder for Miele fan errors (#171592) 2026-05-20 18:07:24 +02:00
Robert Resch b724e52408 Reduce token usage by moving deterministic checks to python (#171466)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-20 17:59:20 +02:00
Abílio Costa 1654f7b0f7 Move duplicated infrared state tracking to common class (#170906) 2026-05-20 11:53:02 -04:00
Luke Lashley c8b23d52ba Bump python-roborock to 5.12.0 (#171112)
Co-authored-by: Robert Resch <robert@resch.dev>
2026-05-20 17:48:42 +02:00
Aidan Timson 75d2babe65 Fix swallowed exceptions in lyric climate action handlers (#171356)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 17:47:13 +02:00
Jens Timmerman 39ad57acfd Bump guntamatic to v1.8.0 (#171593) 2026-05-20 17:45:47 +02:00
TheJulianJES 162729a176 Fix ZHA blocking minor version downgrades (#171319) 2026-05-20 17:45:16 +02:00
Erik Montnemery 376d94e7e1 Remove advanced mode from scene service actions (#171454) 2026-05-20 17:38:12 +02:00
Chris c3223b29a4 fix: handle and translate OpenEVSE charger exceptions in number entities (#171368) 2026-05-20 17:36:35 +02:00
Erik Montnemery 073ee88a64 Remove advanced mode from motioneye service actions (#171446) 2026-05-20 17:35:52 +02:00
Manu ef8b4f2d7f Fix reminder time calculation to use timezone-aware dt_util in Habitica (#171557) 2026-05-20 17:34:53 +02:00
Josef Zweck 0df063b420 Fix string ref for tedee (#171548) 2026-05-20 17:30:35 +02:00
Manu 79fe415d6c Add exception translations to Notifications for Android TV / Fire TV (#171583) 2026-05-20 16:54:03 +02:00
J. Nick Koston 00e3a909a0 Bump bluetooth-data-tools to 1.29.11 (#170949)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2026-05-20 10:52:58 -04:00
Jan-Philipp Benecke a85fb79331 Remove duplicate constant in zha (#171586) 2026-05-20 16:25:02 +02:00
Ronald van der Meer 7f2f268fca Fix Duco VLV nodes not creating CO2 and humidity sensors (#171182) 2026-05-20 16:21:32 +02:00
Paulus Schoutsen 4c31a1737d Split BrowseMediaSource into root and source-specific classes (#170835)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-20 09:59:49 -04:00
Abílio Costa abd8d85225 Add validator subagents to github-pr-reviewer skill (#171370) 2026-05-20 14:48:29 +01:00
Jordan Harvey 626a1a5c87 Remove positional message strings when translation_key is set in nintendo_parental_controls (#171531) 2026-05-20 15:47:43 +02:00
Jarkko Pöyry 1b2e8ccc0f Avoid polling in wled integration (#161183)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-20 15:47:07 +02:00
Mike O'Driscoll 4da2cd465a casper_glow: fix missing translation for exception (#171534) 2026-05-20 15:47:01 +02:00
Thomas55555 e3c31a3482 Allow setting a custom laqi in Google Air Quality (#160681)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-05-20 15:41:45 +02:00
A. Gideonse a0b52e0f58 Bump indevolt-api to 1.8.1 (#171472) 2026-05-20 14:37:01 +01:00
Andrew Jackson 75dd509c7b Add translations to Mastodon exceptions (#171528) 2026-05-20 15:33:01 +02:00
Max Michels 6540ccd52a Replace duplicate constants with homeassistant.const imports in citybikes (#171478) 2026-05-20 15:18:12 +02:00
Ronald van der Meer a35ad41495 Fix untranslated config entry error in Duco (#171514) 2026-05-20 15:17:32 +02:00
Max Michels cedf5a5861 Replace duplicate constants with homeassistant.const imports hddtemp (#171517) 2026-05-20 15:15:55 +02:00
Alexey Masolov 16f4dc74bf Add TIMEOUT constant to CalDAV integration (#171463)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-20 15:08:42 +02:00
Franck Nijhof c5f22936e4 Use HA timezone for date in saj (#171450) 2026-05-20 14:57:34 +02:00
Jan Čermák aa23b3176c Bump base image to 2026.05.0 with Python 3.14.5, use 3.14.5 in CI (#171482) 2026-05-20 14:43:54 +02:00
Franck Nijhof a144bbab2b Fix Wyoming satellite crash when TTS is not configured (#171513) 2026-05-20 14:30:20 +02:00
Erik Montnemery 6a20b99252 Adjust device_registry.async_setup (#167653) 2026-05-20 14:28:10 +02:00
Franck Nijhof 8a12c06116 Fix PowerView cover crash when shade position is unavailable (#171471) 2026-05-20 13:50:49 +02:00
Alistair Francis 5dc057b36d husqvarna_automower_ble: Gracefully handle unreachable device (#171479)
Signed-off-by: Alistair Francis <alistair@alistair23.me>
2026-05-20 13:48:11 +02:00
Robert Resch 6d6f14a0aa Revert "Bump py-opendisplay to 7.0.0" (#171477) 2026-05-20 13:42:15 +02:00
dependabot[bot] 7bd81aeb9f Bump actions/ai-inference from 2.0.7 to 2.1.0 (#171475)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 13:06:59 +02:00
Sören 8dc29b5411 Bump avea to 1.8.0 (#171473) 2026-05-20 11:55:49 +01:00
Sören 1cabcf522e Fix Avea stale brightness restore (#171139) 2026-05-20 12:53:45 +02:00
Erik Montnemery ff8d244839 Remove advanced mode from tts service actions (#171462) 2026-05-20 12:11:30 +02:00
J. Nick Koston d1a5b0dbd3 Bump aiodns to 4.0.4 (#171420)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: frenck <195327+frenck@users.noreply.github.com>
2026-05-20 12:10:03 +02:00
Erik Montnemery 86bab0c0f6 Remove advanced mode from zwave_js service actions (#171465) 2026-05-20 11:57:05 +02:00
J. Nick Koston 7f320a5a41 Bump zeroconf to 0.149.7 (#171054)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
2026-05-20 11:45:24 +02:00
Erik Montnemery 309ce5545e Set breaks_in_ha_version on issue about not running core in venv or container (#171426) 2026-05-20 11:35:55 +02:00
Erik Montnemery 293d7851ba Remove advanced mode from webostv service actions (#171464) 2026-05-20 12:33:21 +03:00
Erik Montnemery eeb9270241 Rename advanced options section to additional options in light service actions (#171444) 2026-05-20 11:30:30 +02:00
Erik Montnemery b841a26aff Remove advanced mode from fan service actions (#171439) 2026-05-20 11:29:41 +02:00
Erik Montnemery 40f7a2f50f Remove advanced mode from climate service actions (#171437) 2026-05-20 11:29:00 +02:00
Willem-Jan van Rootselaar 15b230c4e7 Bump python-bsblan to version 6.0.1 (#171447) 2026-05-20 11:24:24 +02:00
Erik Montnemery 49a14112b7 Remove advanced mode from sharkiq service actions (#171456) 2026-05-20 11:23:23 +02:00
Erik Montnemery e59e631a87 Remove advanced mode from squeezebox service actions (#171457) 2026-05-20 11:22:58 +02:00
Erik Montnemery c1d0c9fb9f Rename advanced options section to additional options in kitchen_sink service actions (#171443) 2026-05-20 10:57:35 +02:00
Andrew Jackson ee7461ed9c Remove duplicate const in time_date (#171438) 2026-05-20 10:43:27 +02:00
AlCalzone 9757f8b574 Add support for Z-Wave credential management (#168360)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-05-20 10:41:02 +02:00
Thomas Bouron 5ee96a3616 Improve temperature unit handling in Tuya numbers and sensors (#170432) 2026-05-20 09:59:27 +02:00
Tomer 703ac31bd1 Use CONF_MODEL from homeassistant.const in victron_gx (#171434) 2026-05-20 09:56:23 +02:00
Erik Montnemery 52bf0b0ee0 Remove references to the removed toon.update service (#171435) 2026-05-20 09:55:45 +02:00
Robert Resch 29db335930 Remove advanced mode dependency from version config flow (#171215) 2026-05-20 09:50:16 +02:00
Denis Shulyaka 8257107462 Replace duplicate constants with homeassistant.const imports in humidifier (#171354)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-05-20 09:27:05 +02:00
Jan Bouwhuis c7618949da Remove deprecated advanced flags from MQTT service actions services.yaml (#171430) 2026-05-20 09:24:48 +02:00
Marcos A L M Macedo 592154bd27 Add fixture for Tuya INTELAR IR288 (#171412)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-05-20 09:11:11 +02:00
epenet 7919330ae0 Bump renault-api to 0.5.9 (#171428) 2026-05-20 09:10:44 +02:00
epenet 6ffe1bab9a Fix duplicate-const in Renault services (#171429) 2026-05-20 08:58:36 +02:00
Michael Heyman 91705ef821 Replace duplicate ATTR_TEMPERATURE constant with homeassistant.const (#171423)
Co-authored-by: Michael Heyman <michaelheyman@users.noreply.github.com>
2026-05-20 08:58:11 +02:00
J. Nick Koston e15797af14 Bump aiodiscover to 3.2.0 (#171401) 2026-05-20 08:43:10 +02:00
Russell VanderMey a966ce4586 Use homeassistant.const CONF_TOKEN for triggercmd (#171411) 2026-05-20 08:42:44 +02:00
Paulus Schoutsen ee2de6641f Use CONF_MODEL from homeassistant.const in marantz_infrared (#171415)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-20 08:42:11 +02:00
J. Nick Koston 5f85ae6f95 Bump dbus-fast to 5.0.0 (#171421) 2026-05-20 08:40:57 +02:00
Phil-Rad 275e0b3dd1 Add reconfigure flow to cert_expiry (#170888) 2026-05-20 08:39:32 +02:00
Erik Montnemery a9475683e1 Add new device tracker base entity BaseScannerEntity (#171063) 2026-05-20 08:09:28 +02:00
Petro31 d4e1a7075e Clean up legacy template entity code (#170016) 2026-05-20 07:44:32 +02:00
Adam Katic 2a3d75eb2b Add missing speedtestdotnet options flow translation (#171153) 2026-05-20 08:26:37 +03:00
Tsvi Mostovicz 9212d2300c Replace duplicate constants with homeassistant.const imports in jewish calendar (#171403) 2026-05-19 21:18:18 -04:00
J. Nick Koston 6836b27ba6 Bump yarl to 1.24.2 (#171407) 2026-05-19 21:17:58 -04:00
TimL 8656f52d7a Add buzzer action play_rtttl to SMLIGHT (#166665)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-19 23:24:49 +02:00
Paul Bottein 6a07ca93e9 Migrate Freebox to has_entity_name and key-based unique IDs (#169860) 2026-05-19 23:17:57 +02:00
Jan Bouwhuis 77990c8808 Remove duplicate constants for homeassistant integration (#171363) 2026-05-19 23:00:55 +02:00
Jan Rieger e4227ee1d4 Replace duplicate constant with homeassistant.const import in gpsd (#171355) 2026-05-19 22:59:48 +02:00
Denis Shulyaka 87aca7416f Replace duplicate constants with homeassistant.const imports in generic_hygrostat (#171358) 2026-05-19 22:59:00 +02:00
Glenn Waters 1ec6619a20 ElkM1 integration: Fix duplicate constants (#171364) 2026-05-19 22:57:15 +02:00
Denis Shulyaka 85013282e4 Explicitly set parallel-updates for Anthropic (#171387) 2026-05-19 22:32:40 +02:00
Chris ceab93ab83 refactor: remove redundant CONF_ID constant from OpenEVSE integration (#171366) 2026-05-19 22:14:54 +02:00
Tomas Kislan ac636ce54f Use homeassistant.const CONF_HOST and CONF_PORT in minio (#171395)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 22:13:59 +02:00
Jan-Philipp Benecke 3287b01ed1 Group Nuki executor jobs (#171391) 2026-05-19 22:06:46 +02:00
Jens Timmerman 3acc7d08b3 fix translations (#171157) 2026-05-19 22:04:31 +02:00
Jan-Philipp Benecke 16eb5dce63 Fix language handling in jewish_calendar tests (#171383) 2026-05-19 21:47:37 +02:00
Petro31 3fee05db71 Add entity_platform helper function to create issues when platform setup is not supported by integration (#171105)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 21:08:03 +02:00
Artur Pragacz f823ef639a Prefix area to entity ID (#170560) 2026-05-19 20:53:26 +02:00
mithomas da4263b95c Fix missing delay and repeat support in LG Netcast remote (#170324)
Co-authored-by: Copilot <copilot@github.com>
2026-05-19 20:49:43 +02:00
Robert Resch 29e2184163 Fix workflow run (#171367) 2026-05-19 20:49:10 +02:00
Robert Resch 816c3ff939 Adjust aw check requirements checks (#171389)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-19 20:48:53 +02:00
karwosts 2348ccc76e Use modern batteries for demo integration (#171376) 2026-05-19 19:23:11 +01:00
Manu 4202686a0d Remove duplicate constants in Mobile App integration (#171379) 2026-05-19 20:17:18 +02:00
dontinelli dd1437f5f2 Remove obsolete local const in slide_local (#171386) 2026-05-19 20:16:09 +02:00
Willem-Jan van Rootselaar 1a1c9d935c Remove duplicate constant in bsblan integration (#171385) 2026-05-19 19:48:17 +02:00
javicalle 4c0e7eb92d Migrate RFLink YAML configuration (ADR0007) (#161822)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-19 19:44:03 +02:00
Josh Gustafson d288645f0e Declare PARALLEL_UPDATES on arcam_fmj platforms (#171151)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:26:57 +02:00
Jan-Philipp Benecke 66aad8d3c5 Fix solaredge tests (#171378) 2026-05-19 18:38:10 +02:00
peteS-UK 89e15b9eae Remove unused ATTR_TIME from squeezebox const.py (#171374) 2026-05-19 18:29:03 +02:00
Nick Haghiri 489b831a4b Use homeassistant.const CONF_PREFIX in backblaze_b2 (#171365) 2026-05-19 18:24:56 +02:00
Manu f1854e1816 Remove duplicate constant in Notifications for Android TV / Fire TV integration (#171377) 2026-05-19 18:18:36 +02:00
Manu 8931ce561c Remove duplicate constant in ntfy integration (#171375) 2026-05-19 18:08:46 +02:00
Petro31 4d19cec214 Replace duplicate constants with homeassistant.const imports in template (#171349)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-19 18:08:20 +02:00
Manu e111678c40 Remove duplicate constant from HTML5 integration (#171373) 2026-05-19 18:07:07 +02:00
Jan Bouwhuis 69de70407b Remove duplicate constants for MQTT (#171359) 2026-05-19 18:05:16 +02:00
Andrew Jackson 64d17521a4 Remove duplicate const in Mastodon (#171357) 2026-05-19 16:38:11 +01:00
Erik Montnemery b52476a37e Remove use of advanced mode from the homekit integration (#171200) 2026-05-19 16:27:58 +02:00
Denis Shulyaka 58c906a2d1 Replace duplicate constants with homeassistant.const imports in anthropic (#171316) 2026-05-19 16:17:15 +02:00
Denis Shulyaka 3b2fa3f5b7 Replace duplicate constants with homeassistant.const imports in openai_conversation (#171348)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-19 16:15:12 +02:00
Paul Bottein 0dae4689cf Use CONF_CODE in Novy Cooker Hood (#171350)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-19 16:15:08 +02:00
Joost Lekkerkerker cd7fe836b0 Fix CI (#171351) 2026-05-19 16:07:27 +02:00
Robert Resch e3bae0dbda Use multistage workflow to run agentic workflow on forks (#171186)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 16:06:14 +02:00
Mika 7cf3cba27b Split SolarEdge power-flow attributes into sensor entities (#170457)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-19 15:42:36 +02:00
Tsvi Mostovicz de70d9ed82 Jewish Calendar: add a calendar entity (#145140)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
2026-05-19 15:40:26 +02:00
Onero-testdev eb0c1700b7 Add support for SwitchBot Lock Vision (Pro) and Lock Pro Wifi (#170470) 2026-05-19 15:36:33 +02:00
Franck Nijhof 6fa5fc77aa Add pylint checker for duplicate homeassistant.const definitions (#170848)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 15:10:13 +02:00
Åke Strandberg c705e8ff56 Cleanup miele API timeouts (#171172) 2026-05-19 14:19:01 +02:00
Jan Bouwhuis ee248b536e Fix subscription ID for restored subscriptions (#171130) 2026-05-19 14:08:36 +02:00
Åke Strandberg 7bfd11cf2e Add missing Miele Dishwasher codes (#171175) 2026-05-19 14:01:35 +02:00
iluvdata 2dae262135 Address future error in RepairFlow for Anthropic (#171156) 2026-05-19 14:53:18 +03:00
Aidan Timson 76a463dd50 Bump aiolyric to 2.1.1, Update OAuth URL for lyric (#171181) 2026-05-19 13:16:47 +02:00
epenet 0d83b1cbe8 Handle temperature unit mismatch in Tuya climate (#171183) 2026-05-19 13:16:02 +02:00
Artur Pragacz ae622a7cd4 Fix zwave_js fixture path resolution (#171196) 2026-05-19 13:11:43 +02:00
Przemko92 3f0af1e5b7 Add Compit switch (#164053) 2026-05-19 13:00:39 +02:00
G Johansson 742e63d02c Fix exception handling in command_line notify service (#170709)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-19 12:49:19 +02:00
nopoz 1042ec2964 Bump pyenvisalink to 4.9 (#171125) 2026-05-19 12:03:20 +02:00
Erik Montnemery f4fdd4d58f Adjust device tracker tests (#171178) 2026-05-19 11:52:12 +02:00
iluvdata 3963555b2f Add RepairsFlowResult pylint check (#171145) 2026-05-19 11:35:25 +02:00
Erwin Douna 4f8885b40d Downloader add proper exceptions (#170771)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-19 11:35:01 +02:00
Robert Resch 3f49877ff1 Use renovate to update go2rtc (#169508) 2026-05-19 10:10:17 +02:00
Erik Montnemery d2bb31d115 Remove useless input validation from cast options flow (#171171) 2026-05-19 10:09:17 +02:00
Denis Shulyaka f499dbf29f Add web fetch tool support for Anthropic (#167405)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-19 09:50:50 +02:00
Keith Roehrenbeck bc0e3dc3be Fix Apple TV keyboard focus binary_sensor missing on cold start (#170360) 2026-05-19 09:38:48 +02:00
Florent Thoumie fb6e6170bf Improve iaqualink 429 handling (#170231) 2026-05-19 09:24:09 +02:00
Mick Vleeshouwer 9e22711874 Fix controls for UpDownGarageDoor4T and additional 4T covers in Overkiz (#171144) 2026-05-19 09:22:31 +02:00
puddly 1982dd9085 Fix ZHA config entries using a URI without a port (#171164) 2026-05-19 09:14:48 +02:00
Adam Katic c32098decd Add quality scale for speedtestdotnet integration (#170782) 2026-05-19 10:06:06 +03:00
Erik Montnemery 2e87750d70 Remove use of advanced mode from the cast integration (#171090) 2026-05-19 08:26:35 +02:00
karwosts 55354770a8 Make energy electric sources nameable (#170658) 2026-05-19 09:22:42 +03:00
epenet d7b63a40db Rename Tuya fixtures (#171169) 2026-05-19 08:22:09 +02:00
Erik Montnemery c80d1ba003 Correct signature of mock class in test_recovery_from_dbus_restart (#171097) 2026-05-19 07:48:12 +02:00
Paulus Schoutsen e675423c3c add /local to no auth sig required urls (#171140)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
2026-05-19 06:55:47 +02:00
Marcos A L M Macedo 11cbf91563 Add total production sensor support for Tuya SPM02 devices (#171166) 2026-05-19 06:51:46 +02:00
yemua 4d5c36a3c1 Enable current/power/voltage sensors by default for Tuya electrical categories (#171098) 2026-05-19 06:45:58 +02:00
Carlos Sánchez López cc335a3bd9 Add number support for Tuya WG2 alarm panel (Duosmart C30) (#165836)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-05-19 06:38:12 +02:00
Josh Gustafson f764a32564 Use device name in arcam_fmj browse media root (#171160)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:49:22 -04:00
Josh Gustafson aeb7109708 Share arcam_fmj convert_exception decorator from entity module (#171162)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:01:28 -04:00
Josh Gustafson f75c205c08 Annotate parametrized arcam_fmj media_player test signatures (#171163)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 20:57:24 -04:00
Joost Lekkerkerker e20f4c8f6e Use subentry helper in Satel Integra (#167060)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-05-19 01:08:53 +02:00
Erik Montnemery 72f6c38e7d Remove use of advanced mode from the tasmota integration (#171093) 2026-05-19 00:43:08 +02:00
Erik Montnemery 40408def0f Don't set _attr_source_type in victron_gx device tracker entity (#171077) 2026-05-19 00:40:05 +02:00
Robert Resch 282737e3c4 Bump gh aw to 0.74.4 (#171137) 2026-05-18 23:02:56 +01:00
Josh Gustafson a1cc735337 Report unknown state in arcam_fmj when power state is unreported (#171149)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 23:01:51 +01:00
Mick Vleeshouwer b6f4551a76 Add light entity tests to Overkiz (#171102) 2026-05-18 23:46:15 +02:00
Phil-Rad f5d2aa9c12 Use runtime_data and validate connection at setup for dnsip (#169745) 2026-05-18 23:24:57 +02:00
Onero-testdev 612dbf2d44 Add SwitchBot Permanent Outdoor Light support (#170463)
Co-authored-by: Fan Kai <fankai@onero.com>
2026-05-18 23:22:56 +02:00
Maciej Bieniek f2691e4feb Change model to model ID in the Tractive DeviceInfo (#171147) 2026-05-18 23:12:24 +02:00
Thomas D f9654e15a6 Support stepper output in Qbus integration (#170772) 2026-05-18 22:44:16 +02:00
Michael 01dde25ffa Bump aioimmich to 0.14.1 (#171138) 2026-05-18 22:25:51 +02:00
Michael 34254c138f Fix handling of tracked devices on cleanup in FRITZ!Box Tools (#170574)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-05-18 22:03:43 +02:00
renovate[bot] 1076d65c9c Update syrupy to 5.2.0 (#171100)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-18 22:00:11 +02:00
Heikki Henriksen ad71e31bad prusalink: add sd_ready, farm_mode, and status_connect binary sensors (#169310)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:56:52 +02:00
Franck Nijhof 7608d5f99d Fix WeatherFlow websocket crash when data payload is None (#171037) 2026-05-18 15:43:42 -04:00
Erik Montnemery cafcbf8179 Improve bluetooth test fixture (#171061) 2026-05-18 21:17:50 +02:00
Erik Montnemery 852faa7f95 Fix docstring of cv.string (#171128) 2026-05-18 21:14:46 +02:00
renovate[bot] 5cf1e185f0 Update ruff (#171118)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2026-05-18 20:57:05 +02:00
Glenn Waters c4d25a5a26 ElkM1 integration: Deprecate Elk Setting sensors; replaced by time/number entities (#170041) 2026-05-18 20:56:29 +02:00
Maciej Bieniek 18f8e11865 Split Tractive entities into tracker-related and pet-related (#170256) 2026-05-18 20:55:05 +02:00
Kamil Breguła e8f3d357c4 Add group support to WLED main light (#169669)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-18 20:44:34 +02:00
Michael Hansen 1ad81697f7 Add chat log and response rendering to Wyoming conversation (#170433) 2026-05-18 20:43:33 +02:00
Arie Catsman f66652c729 Provide request retry option to overcome intermittant enphase_envoy failures (#168222)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-18 20:40:14 +02:00
Robert Resch c468ae77f3 Enable agentic library workflow on forks and users without write rightsA (#171123) 2026-05-18 20:20:36 +02:00
renovate[bot] 251d7e15d2 Update requests to 2.34.2 (#171119) 2026-05-18 20:18:53 +02:00
Sören d268f8b486 Restore Avea brightness on turn on (#171120) 2026-05-18 19:58:59 +02:00
Jamin 6f3dfab487 Voip runtime data (#170765)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-18 19:40:30 +02:00
Crocmagnon 8d8b9bb2e8 data grand lyon: split coordinators (#170662) 2026-05-18 19:30:55 +02:00
Franck Nijhof 8c9d659dcf Use HA timezone for date in recollect_waste (#171106) 2026-05-18 19:20:14 +02:00
Franck Nijhof f08adfe712 Use HA timezone for date in cookidoo (#171109) 2026-05-18 19:18:30 +02:00
renovate[bot] de29414b37 Update uv to 0.11.14 (#171099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-18 19:13:07 +02:00
Ludovic BOUÉ 01d9c2e810 Add siren platform support to Matter integration (#170031)
Co-authored-by: Ludovic BOUÉ <938089+lboue@users.noreply.github.com>
2026-05-18 18:45:28 +02:00
epenet 9b3b3eca6d Prioritize native Tuya unit of measurement (#170338) 2026-05-18 17:29:46 +02:00
Copilot 2e45ce36a7 Create agentic workflow to validate dependencies (#168855)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: edenhaus <26537646+edenhaus@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2026-05-18 16:59:49 +02:00
g4bri3lDev fe56ce6813 Bump py-opendisplay to 7.0.0 (#171088) 2026-05-18 16:50:51 +02:00
Erik Montnemery 8000b419ea Remove stale reference to advanced mode from MQTT tests (#171095) 2026-05-18 16:14:52 +02:00
Noah Husby f0a5ce747e Disallow session closure for Cambridge Audio (#171036) 2026-05-18 15:47:49 +02:00
aide 7da5b10b51 Add new integration for AiDot (#167272)
Co-authored-by: bryan <185078974@qq.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-18 14:57:25 +02:00
Mick Vleeshouwer 94b373641d Fix tilt and position support for VenetianBlind covers in Overkiz (#170974) 2026-05-18 14:51:26 +02:00
Pete Sage dfd241dd1a Add search to Sonos (#170891)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-18 14:39:12 +02:00
Klaas Schoute 27b161bf7c Add new params to actions of easyEnergy integration (#169225)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-18 14:36:01 +02:00
Josef Zweck f2362aa2a3 Bump pylamarzocco to 2.2.5 (#171083) 2026-05-18 14:16:04 +02:00
Matthias Alphart 90946c3e2f Fix swallowed exception in knx event_register action (#171010)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-18 14:06:25 +02:00
Franck Nijhof 318091689c Fix line length violations in new code since cleanup PRs (#171062) 2026-05-18 14:03:52 +02:00
Petro31 ee8c3ca864 Fix swallowed exceptions in template action handlers (#171080) 2026-05-18 13:55:59 +02:00
Jonathan Segev 5f6f300a20 Bump aiolyric to 2.1.0 (#171007)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-05-18 13:54:33 +02:00
Sören ad04aeced9 Fix Avea color state refresh (#171003) 2026-05-18 13:54:08 +02:00
Franck Nijhof bbb31f2910 Group sequential executor jobs in verisure config flow (#171081) 2026-05-18 13:47:58 +02:00
Martin Hjelmare 0ed81e426b Fix swallowed exceptions in VLC Telnet actions (#171071) 2026-05-18 13:42:12 +02:00
Mick Vleeshouwer 4582c56c1c Fix is_closed state and position for DynamicPergola covers in Overkiz (#170983) 2026-05-18 13:41:28 +02:00
Mick Vleeshouwer 9ce3e00e87 Fix is_closed state for DiscretePositionableGarageDoor in Overkiz (#170981) 2026-05-18 13:40:48 +02:00
Franck Nijhof bd2ea9a148 Group sequential executor jobs in roomba vacuum (#171078) 2026-05-18 13:39:00 +02:00
Paulus Schoutsen e34be91439 Bump dsmr-parser to 1.7.0 (#171082)
Co-authored-by: Claude <noreply@anthropic.com>
2026-05-18 13:36:32 +02:00
Franck Nijhof 3e5beb9aa3 Group sequential executor jobs in ezviz config flow (#171084) 2026-05-18 13:33:11 +02:00
Franck Nijhof ac5df83d1a Group sequential executor jobs in comfoconnect fan (#171085) 2026-05-18 13:30:49 +02:00
Franck Nijhof c9e014c5d8 Group sequential executor jobs in soma setup (#171087) 2026-05-18 13:29:42 +02:00
Franck Nijhof 1b7564dcdf Group sequential executor jobs in smappee config flow (#171086) 2026-05-18 13:29:00 +02:00
Paulus Schoutsen 71425dd19f Add buttons platform to Marantz IR Remote (PM6006) (#169627)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-18 12:46:09 +02:00
zhangluofeng eea08a0457 Add Xthings Cloud Switch (#170554) 2026-05-18 12:45:04 +02:00
Erik Montnemery 00132b4416 Remove source_type property from paj_gps device tracker entity (#171076) 2026-05-18 12:43:48 +02:00
Erik Montnemery 6b9efed899 Don't set _attr_source_type in nrgkick device tracker entity (#171075) 2026-05-18 12:43:39 +02:00
Erik Montnemery b0b6b46152 Remove source_type property from lojack device tracker entity (#171073) 2026-05-18 12:43:33 +02:00
Erik Montnemery 044ef25cb6 Remove source_type property from fressnapf_tracker device tracker entity (#171072) 2026-05-18 12:43:30 +02:00
bkobus-bbx b633fbcf07 Fix ValueError when turning on blebox light with brightness set to 0 (#170769)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-05-18 12:41:30 +02:00
Mick Vleeshouwer 7c9b6ad2a8 Fix controls for OpenCloseGate4T (rts:GateOpenerRTS4TComponent) in Overkiz (#170987) 2026-05-18 12:39:48 +02:00
Jan-Philipp Benecke 89d9fff1e9 Fix typo in lovelace action error message (#171074) 2026-05-18 13:38:27 +03:00
A. Gideonse e0af3dfa99 Add real-time control sensors to Indevolt (#170729)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-18 12:32:37 +02:00
renovate[bot] 4fb3ad102c Update cryptography to 48.0.0 (#170372) 2026-05-18 12:32:35 +02:00
Øyvind Matheson Wergeland dc2ab012fa End nobo_hub config flow tests in CREATE_ENTRY or ABORT (#170141) 2026-05-18 12:30:52 +02:00
Dougal Matthews 140fef6915 Add geo_location entity support to Prometheus exporter (#170721) 2026-05-18 12:27:41 +02:00
Franck Nijhof 822a567ca9 Return media_content_id as string in forked_daapd (#171059) 2026-05-18 12:26:45 +02:00
Sören aa8904b0cd Use config entry title for Avea light (#170978) 2026-05-18 12:26:09 +02:00
Franck Nijhof e9f9194b7b Fix swallowed exception in cast play_media for unsupported apps (#171064) 2026-05-18 12:22:22 +02:00
Jan-Philipp Benecke d0f4cba32c Reraise HomeAssistantError with translation in lovelace (#171053) 2026-05-18 11:53:22 +02:00
Erik Montnemery beba530a9a Remove source_type from autoskope device tracker entity (#171070) 2026-05-18 11:47:11 +02:00
AlCalzone 5d3fd5a487 Bump opensensemap-api to 0.4.1 (#171056) 2026-05-18 11:42:10 +02:00
Franck Nijhof bed6af2ef2 Fix swallowed exceptions in rest switch action handlers (#171069) 2026-05-18 11:38:06 +02:00
Mick Vleeshouwer 2b20b69928 Add tests for scene platform in Overkiz (#170993) 2026-05-18 11:35:33 +02:00
Jan Bouwhuis d5d50ac11a Set subscription identifier to allow matching duplicate payloads with overlapping subscriptions (#169604)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-18 11:27:05 +02:00
Mick Vleeshouwer ba5a62ec2a Replace redacted labels in test fixtures with meaningful names in overkiz (#170988) 2026-05-18 11:19:29 +02:00
Joakim Plate 88ca0faea0 Require service on fjaraskupan to detect it (#170363) 2026-05-18 11:00:05 +02:00
LG-ThinQ-Integration a333f31d44 Fix swallowed exceptions in lg_thinq action handlers (#171047)
Co-authored-by: YunseonPark-LGE <yunseon.park@lge.com>
2026-05-18 10:09:30 +02:00
James Nimmo 8854ad5765 Bump pyIntesishome to 1.8.8 (#171041) 2026-05-18 09:51:42 +02:00
Joost Lekkerkerker 7dfef5c82a Add icon translations to Electrolux (#170422) 2026-05-13 07:54:58 +02:00
Joostlek b75cd0f6a7 Merge branch 'dev' into electrolux 2026-05-12 16:50:46 +02:00
ferenc-fustos-electrolux 7859aba432 Add electrolux integration (#157176) 2026-05-12 12:40:40 +01:00
1418 changed files with 51013 additions and 17782 deletions
@@ -18,6 +18,13 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
4. Ensure any existing review comments have been addressed.
5. Generate constructive review comments in the CONSOLE. DO NOT POST TO GITHUB YOURSELF.
## Verification:
- After the review, run parallel subagents for each finding to double check it.
- Spawn up to a maximum of 10 parallel subagents at a time.
- Gather the results from the subagents and summarize them in the final review comments.
## IMPORTANT:
- Just review. DO NOT make any changes
- Be constructive and specific in your comments
+1 -2
View File
@@ -14,12 +14,11 @@ Dockerfile.dev linguist-language=Dockerfile
# Generated files
CODEOWNERS linguist-generated=true
Dockerfile linguist-generated=true
homeassistant/generated/*.py linguist-generated=true
machine/* linguist-generated=true
mypy.ini linguist-generated=true
requirements.txt linguist-generated=true
requirements_all.txt linguist-generated=true
requirements_test_all.txt linguist-generated=true
requirements_test_pre_commit.txt linguist-generated=true
script/hassfest/docker/Dockerfile linguist-generated=true
.github/workflows/*.lock.yml linguist-generated=true
+3
View File
@@ -11,3 +11,6 @@ updates:
- github_actions
cooldown:
default-days: 7
ignore:
# Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump.
- dependency-name: "github/gh-aw-actions/**"
+27
View File
@@ -6,6 +6,7 @@
"pep621",
"pip_requirements",
"pre-commit",
"dockerfile",
"custom.regex",
"homeassistant-manifest"
],
@@ -21,6 +22,10 @@
]
},
"dockerfile": {
"managerFilePatterns": ["/^Dockerfile$/"]
},
"homeassistant-manifest": {
"managerFilePatterns": [
"/^homeassistant/components/[^/]+/manifest\\.json$/"
@@ -35,6 +40,14 @@
"matchStrings": ["required-version = \">=(?<currentValue>[\\d.]+)\""],
"depNameTemplate": "ruff",
"datasourceTemplate": "pypi"
},
{
"customType": "regex",
"description": "Update go2rtc RECOMMENDED_VERSION in const.py alongside the Dockerfile pin",
"managerFilePatterns": ["/^homeassistant/components/go2rtc/const\\.py$/"],
"matchStrings": ["RECOMMENDED_VERSION = \"(?<currentValue>[\\d.]+)\""],
"depNameTemplate": "ghcr.io/alexxit/go2rtc",
"datasourceTemplate": "docker"
}
],
@@ -115,6 +128,7 @@
"standard-aifc",
"standard-telnetlib",
"ulid-transform",
"unidiff",
"url-normalize",
"xmltodict"
],
@@ -184,6 +198,13 @@
"enabled": true,
"labels": ["dependency"]
},
{
"description": "Docker allowlist (ghcr.io exposes no release timestamps so the global cooldown needs to be bypassed)",
"matchPackageNames": ["ghcr.io/alexxit/go2rtc"],
"enabled": true,
"minimumReleaseAge": null,
"labels": ["dependency"]
},
{
"description": "Group ruff pre-commit hook with its PyPI twin into one PR",
"matchPackageNames": ["astral-sh/ruff-pre-commit", "ruff"],
@@ -213,6 +234,12 @@
"matchPackageNames": ["pylint", "astroid"],
"groupName": "pylint",
"groupSlug": "pylint"
},
{
"description": "Group go2rtc Dockerfile pin with const.py RECOMMENDED_VERSION into one PR",
"matchPackageNames": ["ghcr.io/alexxit/go2rtc"],
"groupName": "go2rtc",
"groupSlug": "go2rtc"
}
]
}
+1 -1
View File
@@ -14,7 +14,7 @@ env:
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"
# Base image version from https://github.com/home-assistant/docker
BASE_IMAGE_VERSION: "2026.04.0"
BASE_IMAGE_VERSION: "2026.05.0"
ARCHITECTURES: '["amd64", "aarch64"]'
permissions: {}
@@ -0,0 +1,74 @@
name: Check requirements (deterministic)
# Stage 1 of the Check requirements pipeline.
#
# Runs the deterministic Python checks and uploads the structured
# results as an artifact. Stage 2 (the agentic workflow defined in
# `check-requirements.md`) consumes the artifact on completion.
# yamllint disable-line rule:truthy
on:
# Auto-trigger on PRs that touch tracked requirement files is disabled
# for now while we iterate — testing the workflow_run handoff to the
# agentic stage is hard with an auto-trigger. Re-enable once the chain
# has been validated end-to-end.
# pull_request:
# types: [opened, synchronize, reopened]
# paths:
# - "**/requirements*.txt"
# - "homeassistant/package_constraints.txt"
workflow_dispatch:
inputs:
pull_request_number:
description: "Pull request number to (re-)check"
required: true
type: number
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ inputs.pull_request_number || github.event.pull_request.number }}
cancel-in-progress: true
jobs:
deterministic:
name: Run deterministic requirement checks
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read # To fetch the PR diff via gh CLI
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: ".python-version"
check-latest: true
- name: Install script dependencies
run: pip install -r script/check_requirements/requirements.txt
- name: Collect PR diff
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ inputs.pull_request_number || github.event.pull_request.number }}
run: |
mkdir -p deterministic
gh pr diff "${PR_NUMBER}" > deterministic/pr.diff
- name: Run deterministic checks
env:
PR_NUMBER: ${{ inputs.pull_request_number || github.event.pull_request.number }}
run: |
python -m script.check_requirements \
--pr-number "${PR_NUMBER}" \
--diff deterministic/pr.diff \
--output deterministic/results.json
- name: Upload deterministic-results artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: check-requirements-deterministic
path: deterministic/results.json
if-no-files-found: error
retention-days: 7
File diff suppressed because it is too large Load Diff
+378
View File
@@ -0,0 +1,378 @@
---
on:
workflow_run:
workflows: ["Check requirements (deterministic)"]
types: [completed]
permissions:
contents: read
actions: read
issues: read
pull-requests: read
network:
allowed:
- python
tools:
web-fetch: {}
github:
toolsets: [default, actions]
min-integrity: unapproved
safe-outputs:
add-comment:
max: 1
target: "${{ needs.extract_pr_number.outputs.pr_number }}"
needs:
- extract_pr_number
jobs:
extract_pr_number:
if: github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
permissions:
actions: read
outputs:
pr_number: ${{ steps.extract.outputs.pr_number }}
steps:
- name: Download deterministic-results artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: check-requirements-deterministic
path: /tmp/deterministic
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Extract PR number from artifact
id: extract
run: |
PR=$(jq -r '.pr_number' /tmp/deterministic/results.json)
echo "pr_number=${PR}" >> "${GITHUB_OUTPUT}"
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_sha }}
cancel-in-progress: true
steps:
- name: Download deterministic-results artifact
if: github.event.workflow_run.conclusion == 'success'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: check-requirements-deterministic
path: /tmp/gh-aw/deterministic
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
post-steps:
- name: Verify agent produced an add_comment safe-output
if: always() && github.event.workflow_run.conclusion == 'success'
run: |
OUTPUT=/tmp/gh-aw/agent_output.json
if [ ! -f "${OUTPUT}" ]; then
echo "::error::Agent output file ${OUTPUT} is missing; the agent did not run to completion."
exit 1
fi
if ! grep -q '"add_comment"' "${OUTPUT}"; then
echo "::error::Agent did not emit an add_comment safe-output; no review comment was posted to the PR."
echo "Agent output:"
cat "${OUTPUT}"
exit 1
fi
description: >
Resolves the deterministic-stage artifact's NEEDS_AGENT checks for changed
Python package requirements on PRs targeting the core repo, then posts the
final review comment. Triggered by completion of the deterministic workflow.
Reads the uploaded artifact from disk, replaces placeholders for any check
whose status is `needs_agent`, and posts the merged comment using the PR
number recorded inside the artifact itself. Each check kind has a dedicated
instruction section below; if the artifact contains a check kind that does
not have a section here, the agent fails hard rather than guess.
---
# Check requirements (AW)
You are a code review assistant for the Home Assistant project. The
deterministic stage has already evaluated every check it can on its own
and produced an artifact containing the PR number, per-package check
results, and a pre-rendered comment with placeholders. **Your only job is
to read that artifact, resolve any `needs_agent` checks, and post the
final comment.**
## Step 1 — Read the deterministic-stage artifact
The deterministic stage uploaded its results to the runner at
`/tmp/gh-aw/deterministic/results.json`.
The JSON has this shape:
- `pr_number` — the PR being checked. The `add_comment` safe-output is
already targeted at this PR (a pre-job extracts `pr_number` from the
artifact and the workflow wires it into the safe-output config via
`needs.extract_pr_number.outputs.pr_number`), so **you do not need to
set `item_number` yourself** — just emit `add_comment` with the
rendered body.
- `needs_agent``true` iff any package's check needs resolution.
- `packages[]` — one entry per changed package. Each entry has:
- `name`, `old_version` (`null` for a newly added package; otherwise the
previous pin), `new_version`, `repo_url`, `publisher_kind`.
- `checks` — a dict keyed by **check kind** (string). Each value has a
`status` (`pass`, `warn`, `fail`, or `needs_agent`) and `details`.
- `rendered_comment` — the final PR comment body, already rendered. For
every check whose status is `needs_agent` it contains two placeholders
you must replace:
- `{{CHECK_CELL:<pkg-name>:<check-kind>}}` — one cell of the summary
table. Replace with exactly one of `✅`, `⚠️`, `❌`.
- `{{CHECK_DETAIL:<pkg-name>:<check-kind>}}` — the body of one bullet
in the package's `<details>` block. Replace with
`<icon> <one-line explanation>` (the bullet's leading
`- **<label>**:` is already rendered — replace only the placeholder).
You **must not** modify any other content in `rendered_comment`. Do not
re-evaluate checks that already have a deterministic status. Do not add
or remove packages.
## Step 2 — Resolve each `needs_agent` check
For each `package` in `packages`:
For each `(check_kind, result)` in `package.checks` where
`result.status == "needs_agent"`:
1. Look up `## Check kind: <check_kind>` in the **Check instructions**
section below.
2. **If no matching section exists**: emit a single `add_comment` whose
body is:
```
<!-- requirements-check -->
## Check requirements
❌ Internal error: the deterministic artifact contains a check kind
(`<check_kind>` on package `<pkg-name>`) that this workflow has no
instructions for. Update `.github/workflows/check-requirements.md`
to add a matching `## Check kind: <check_kind>` section, or remove
the kind from the deterministic stage.
```
Then stop. **Do not improvise** a verdict for an unknown check kind.
3. Otherwise, follow the instructions in that section. They tell you
which icon (✅/⚠️/❌) and one-line explanation to produce.
## Step 3 — Post the comment
1. Replace every `{{CHECK_CELL:…}}` and `{{CHECK_DETAIL:…}}` placeholder
in `rendered_comment` with the resolved value.
2. Emit the resulting markdown using `add_comment` — set `body` to the
merged `rendered_comment` verbatim (the leading
`<!-- requirements-check -->` marker must be preserved). The PR
target is already set by the workflow; do not pass `item_number`.
If the artifact's top-level `needs_agent` is `false` (no checks need
you), emit `rendered_comment` unchanged.
## Check instructions
### Check kind: `repo_public`
Verify that the package's source repository is publicly reachable.
1. Read `package.repo_url`.
2. Use the `web-fetch` tool to GET that URL.
3. Decide the verdict:
- HTTP 200, returns a public repository page → ✅
`<repo_url> is publicly accessible.`
- HTTP 4xx/5xx, or the response redirects to a login / sign-in page →
❌ `Source repository at <repo_url> is not publicly accessible.
Home Assistant requires all dependencies to have publicly available
source code.`
- Any other inconclusive result → ⚠️ with a one-line description.
If `repo_public` resolves to ❌ for a package, **also** mark that
package's `release_pipeline` and `async_blocking` cells/details as ``
(em dash) and explain `Skipped because the source repository is not
publicly accessible.` — neither check can be performed without a public
repo.
### Check kind: `pr_link`
Verify the PR description contains the right link for the change.
1. Fetch the PR body via the GitHub MCP tool, using the `pr_number`
field from the artifact.
2. Extract all URLs from the body.
3. For a **new package** (`package.old_version` is `null`):
- The PR body must contain a URL that points at `package.repo_url`
(any sub-path of the same `owner/repo` on the same host is
acceptable). A PyPI link is **not** sufficient.
- ✅ if such a URL is present.
- ❌ otherwise:
`PR description must link to the source repository at <repo_url>.
A PyPI page link is not sufficient.`
4. For a **version bump** (`package.old_version` is not `null`):
- The PR body must contain a URL on the same host as
`package.repo_url` that references **both** `package.old_version`
and `package.new_version` (e.g. a GitHub compare URL
`compare/vX...vY`, a release / changelog URL containing both
versions, etc.).
- ✅ if such a URL is present and the versions match the actual bump.
- ❌ otherwise:
`PR description should link to a changelog or compare URL on
<repo_url> that mentions both <old_version> and <new_version>.`
### Check kind: `release_pipeline`
Inspect the upstream project's release / publish CI pipeline.
For each package needing inspection, determine the source repository
host from `package.repo_url`, then apply the corresponding checklist.
#### GitHub repositories (`github.com`)
1. List workflows: `GET /repos/{owner}/{repo}/actions/workflows`.
2. Identify any workflow whose name or filename suggests publishing to
PyPI (`release`, `publish`, `pypi`, or `deploy`).
3. Fetch the workflow file and check:
- **Trigger sanity**: triggered by `push` to tags,
`release: published`, or `workflow_run` on a release job —
**not** solely `workflow_dispatch` with no environment-protection
guard.
- **OIDC / Trusted Publisher**: look for `id-token: write` and one of
`pypa/gh-action-pypi-publish`, `actions/attest-build-provenance`,
or `TWINE_PASSWORD` from a static `secrets.PYPI_TOKEN`.
- **No manual upload bypass**: no ungated `twine upload` or
`pip upload`.
4. Verdict:
- ✅ if OIDC + sane triggers + no bypass.
- ⚠️ if static token but version bump, or details unclear.
- ❌ if static token on a new package, or only-manual triggers with
no environment protection.
#### GitLab repositories (`gitlab.com` or self-hosted GitLab)
1. Resolve the project ID via
`GET https://gitlab.com/api/v4/projects/{url-encoded-namespace-and-name}`.
2. Fetch `.gitlab-ci.yml` via
`GET https://gitlab.com/api/v4/projects/{id}/repository/files/.gitlab-ci.yml/raw?ref=HEAD`.
3. Apply the same conceptual checks: tag-only / protected-branch
triggers, GitLab OIDC `id_tokens` or CI/CD protected `PYPI_TOKEN`, no
ungated `twine upload`. Same verdict rules as GitHub.
#### Other code hosting providers (Bitbucket, Codeberg, Gitea, Sourcehut, …)
1. Use `web-fetch` to retrieve any visible CI configuration
(`.circleci/config.yml`, `Jenkinsfile`, `azure-pipelines.yml`,
`bitbucket-pipelines.yml`, `.builds/*.yml`).
2. Apply the conceptual checks: automated triggers, CI-injected
credentials, no manual `twine upload`.
3. If no CI config can be retrieved: ⚠️ `Release pipeline could not be
inspected; hosting provider is not GitHub or GitLab.`
### Check kind: `async_blocking`
Verify whether the dependency performs blocking I/O inside async code
paths. Home Assistant runs on a single asyncio event loop, so a library
that exposes an `async` surface must not call blocking APIs from inside
its `async def` functions — that stalls the whole loop. A purely sync
library is fine: Home Assistant integrations are expected to wrap such
calls in an executor.
**Two modes — pick by inspecting `package.old_version`:**
- `old_version` is `null` → **new package**: review the *entire current
source tree*. Nothing about this dependency has been vetted before.
- `old_version` is a string → **version bump**: review only the *diff
between `old_version` and `new_version`*. The previous version was
already accepted, so blocking calls that were present in
`old_version` are not regressions; report only what `new_version`
introduces.
#### Step 1 — Decide whether the library exposes an async surface
Use the `github` MCP tool (for `github.com` repos) or `web-fetch`
(other hosts) on `package.repo_url`. Always inspect the tag /
ref matching `new_version` (e.g. `v{new_version}` or `{new_version}`).
- Locate the top-level package directory (usually named after the
import name, often equal or close to `package.name`).
- Check `pyproject.toml` / `setup.py` / `setup.cfg` / `README*` for
async indicators (`Framework :: AsyncIO` trove classifier, `asyncio`
/ `aiohttp` / `httpx` / `anyio` in dependencies, an async usage
example in the README).
- Grep the package source for `async def`. A handful of `async def`
entries in the public modules is enough to treat the library as
having an async surface.
If the library is **sync-only** (no `async def` in its public modules
and no async framework dependency) → ✅
`Sync-only library; Home Assistant integrations must wrap calls in an
executor.` *This verdict is the same in both modes.*
#### Step 2a — Mode: new package (`old_version` is `null`)
Inspect **every `async def` in the public modules** for blocking
patterns. Walk transitively into helpers the async functions call.
#### Step 2b — Mode: version bump (`old_version` is a string)
Fetch the diff between the two tags and review **only changed lines**:
- GitHub: `GET /repos/{owner}/{repo}/compare/{old_tag}...{new_tag}` via
the `github` MCP tool, or
`https://github.com/{owner}/{repo}/compare/{old_tag}...{new_tag}.diff`
via `web-fetch`. Try the common tag formats in order until one
resolves: `v{version}`, `{version}`, `release-{version}`.
- GitLab: `https://gitlab.com/{namespace}/{project}/-/compare/{old_tag}...{new_tag}.diff`.
- Other hosts: use the project's equivalent compare URL via
`web-fetch`.
If neither tag format resolves on the host, fall back to a full review
(Step 2a) and mention in the detail that the diff was unavailable.
When reviewing the diff, only flag blocking patterns that appear in
**added lines** *inside or reachable from* an `async def`. A blocking
call that existed in `old_version` and is unchanged is not a regression
for this bump.
#### Step 3 — Blocking patterns to look for
In both modes, the patterns to flag inside `async def` bodies are:
- Sync HTTP: `requests.`, `urllib.request`, `urllib3.` direct use,
`http.client.`, sync `httpx.Client(` / `httpx.get(` (NOT the
`AsyncClient`), `pycurl`.
- `time.sleep(` (must be `await asyncio.sleep(`).
- Sync sockets: bare `socket.socket` reads/writes, `ssl.wrap_socket`,
blocking `select.select`.
- File I/O: `open(` / `pathlib.Path.read_*` / `.write_*` for
non-trivial sizes (small one-shot reads during import are
acceptable; reads/writes on the request path are not — prefer
`aiofiles` / executor).
- Sync DB drivers used directly: `sqlite3`, `psycopg2`, `pymysql`,
`pymongo` (sync client), `redis.Redis` (sync client).
- `subprocess.run` / `subprocess.call` / `os.system` (must be
`asyncio.create_subprocess_*`).
A call that is clearly dispatched to an executor
(`run_in_executor`, `asyncio.to_thread`, `anyio.to_thread.run_sync`)
does NOT count as blocking.
#### Step 4 — Verdict
- ✅ — no offending blocking pattern in the surface being reviewed
(whole tree for a new package, added lines for a bump). For a bump,
phrase the detail as `No new blocking calls introduced in
{old_version} → {new_version}.`.
- ⚠️ — blocking calls exist only in sync helpers that the async API
does not call, or only on a clearly non-hot path (e.g. one-shot
setup before the event loop is running). Cite at least one
`<file>:<line>` and explain why it is not on the hot path.
- ❌ — a blocking call is reachable from an `async def` that is part
of the public API on the request / polling path (for a bump: the
call was introduced or moved onto the hot path by this version).
Cite the offending `<file>:<line>` as a clickable link on the repo
host so the contributor can jump to it.
## Notes
- Be constructive and helpful. Reference the inspected workflow / CI
file by URL where useful so the contributor can fix the issue.
- The dedup of the requirements-check comment is handled by gh-aw's
`add_comment` safe-output via the `<!-- requirements-check -->`
marker on the first line of `rendered_comment`.
- If the deterministic workflow concluded with a non-success status,
this workflow's `if:` guard on `Download deterministic-results
artifact` skipped the download. If you find no file at
`/tmp/gh-aw/deterministic/results.json`, emit nothing — the post-step
verification is also gated and will not complain.
@@ -236,7 +236,7 @@ jobs:
- 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@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
uses: actions/ai-inference@17ff458cb182449bbb2e43701fcd98f6af8f6570 # v2.1.0
with:
model: openai/gpt-4o
system-prompt: |
@@ -62,7 +62,7 @@ jobs:
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
uses: actions/ai-inference@17ff458cb182449bbb2e43701fcd98f6af8f6570 # v2.1.0
with:
model: openai/gpt-4o-mini
system-prompt: |
+3 -1
View File
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
rev: v0.15.13
hooks:
- id: ruff-check
args:
@@ -23,6 +23,7 @@ repos:
- id: zizmor
args:
- --pedantic
exclude: ^\.github/workflows/.*\.lock\.yml$
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
@@ -46,6 +47,7 @@ repos:
additional_dependencies:
- prettier@3.6.2
- prettier-plugin-sort-json@4.2.0
exclude: ^\.github/workflows/.*\.lock\.yml$
- repo: https://github.com/cdce8p/python-typing-update
rev: v0.6.0
hooks:
+1 -1
View File
@@ -1 +1 @@
3.14.4
3.14.5
+3 -3
View File
@@ -132,7 +132,7 @@
"problemMatcher": []
},
{
"label": "Install all Requirements",
"label": "Install all production Requirements",
"type": "shell",
"command": "uv pip install -r requirements_all.txt",
"group": {
@@ -146,9 +146,9 @@
"problemMatcher": []
},
{
"label": "Install all Test Requirements",
"label": "Install all (test & production) Requirements",
"type": "shell",
"command": "uv pip install -r requirements.txt -r requirements_test_all.txt",
"command": "uv pip install -r requirements_all.txt -r requirements_test.txt",
"group": {
"kind": "build",
"isDefault": true
+1
View File
@@ -1,5 +1,6 @@
ignore: |
tests/fixtures/core/config/yaml_errors/
.github/workflows/*.lock.yml
rules:
braces:
level: error
Generated
+6 -2
View File
@@ -68,6 +68,8 @@ CLAUDE.md @home-assistant/core
/tests/components/agent_dvr/ @ispysoftware
/homeassistant/components/ai_task/ @home-assistant/core
/tests/components/ai_task/ @home-assistant/core
/homeassistant/components/aidot/ @s1eedz @HongBryan
/tests/components/aidot/ @s1eedz @HongBryan
/homeassistant/components/air_quality/ @home-assistant/core
/tests/components/air_quality/ @home-assistant/core
/homeassistant/components/airgradient/ @airgradienthq @joostlek
@@ -464,6 +466,8 @@ CLAUDE.md @home-assistant/core
/tests/components/electrasmart/ @jafar-atili
/homeassistant/components/electric_kiwi/ @mikey0000
/tests/components/electric_kiwi/ @mikey0000
/homeassistant/components/electrolux/ @electrolux-oss
/tests/components/electrolux/ @electrolux-oss
/homeassistant/components/elevenlabs/ @sorgfresser
/tests/components/elevenlabs/ @sorgfresser
/homeassistant/components/elgato/ @frenck
@@ -1536,8 +1540,8 @@ CLAUDE.md @home-assistant/core
/homeassistant/components/saj/ @fredericvl
/homeassistant/components/samsung_infrared/ @lmaertin
/tests/components/samsung_infrared/ @lmaertin
/homeassistant/components/samsungtv/ @chemelli74 @epenet
/tests/components/samsungtv/ @chemelli74 @epenet
/homeassistant/components/samsungtv/ @chemelli74
/tests/components/samsungtv/ @chemelli74
/homeassistant/components/sanix/ @tomaszsluszniak
/tests/components/sanix/ @tomaszsluszniak
/homeassistant/components/satel_integra/ @Tommatheussen
+2 -2
View File
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile@sha256:2780b5c3bab67f1f76c781860de469442999ed1a0d7992a5efdf2cffc0e3d769
# Automatically generated by hassfest.
# Partly generated by hassfest.
#
# To update, run python3 -m script.hassfest -p docker
ARG BUILD_FROM
@@ -26,7 +26,7 @@ WORKDIR /usr/src
COPY rootfs /
# Add go2rtc binary
COPY --from=ghcr.io/alexxit/go2rtc@sha256:675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae /usr/local/bin/go2rtc /bin/go2rtc
COPY --from=ghcr.io/alexxit/go2rtc:1.9.14@sha256:675c318b23c06fd862a61d262240c9a63436b4050d177ffc68a32710d9e05bae /usr/local/bin/go2rtc /bin/go2rtc
## Setup Home Assistant Core dependencies
COPY --parents requirements.txt homeassistant/package_constraints.txt homeassistant/
+1
View File
@@ -19,6 +19,7 @@ from .hub import AdsHub
DEFAULT_NAME = "ADS select"
# pylint: disable-next=home-assistant-duplicate-const
CONF_OPTIONS = "options"
PLATFORM_SCHEMA = SELECT_PLATFORM_SCHEMA.extend(
@@ -0,0 +1,25 @@
"""The aidot integration."""
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import AidotConfigEntry, AidotDeviceManagerCoordinator
PLATFORMS: list[Platform] = [Platform.LIGHT]
async def async_setup_entry(hass: HomeAssistant, entry: AidotConfigEntry) -> bool:
"""Set up aidot from a config entry."""
coordinator = AidotDeviceManagerCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(coordinator.async_add_listener(lambda: None))
return True
async def async_unload_entry(hass: HomeAssistant, entry: AidotConfigEntry) -> bool:
"""Unload a config entry."""
await entry.runtime_data.async_cleanup()
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -0,0 +1,66 @@
"""Config flow for Aidot integration."""
from typing import Any
from aidot.client import AidotClient
from aidot.const import CONF_ID, DEFAULT_COUNTRY_CODE, SUPPORTED_COUNTRY_CODES
from aidot.exceptions import AidotUserOrPassIncorrect
from aiohttp import ClientError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_COUNTRY_CODE, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers import selector
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
DATA_SCHEMA = vol.Schema(
{
vol.Required(
CONF_COUNTRY_CODE,
default=DEFAULT_COUNTRY_CODE,
): selector.CountrySelector(
selector.CountrySelectorConfig(
countries=SUPPORTED_COUNTRY_CODES,
)
),
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
class AidotConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle aidot config flow."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
client = AidotClient(
session=async_get_clientsession(self.hass),
country_code=user_input[CONF_COUNTRY_CODE],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
)
try:
login_info = await client.async_post_login()
except AidotUserOrPassIncorrect:
errors["base"] = "invalid_auth"
except TimeoutError, ClientError:
errors["base"] = "cannot_connect"
if not errors:
await self.async_set_unique_id(login_info[CONF_ID])
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=f"{user_input[CONF_USERNAME]} {user_input[CONF_COUNTRY_CODE]}",
data=login_info,
)
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
+3
View File
@@ -0,0 +1,3 @@
"""Constants for the aidot integration."""
DOMAIN = "aidot"
@@ -0,0 +1,163 @@
"""Coordinator for Aidot."""
from datetime import timedelta
import logging
from aidot.client import AidotClient
from aidot.const import (
CONF_ACCESS_TOKEN,
CONF_AES_KEY,
CONF_DEVICE_LIST,
CONF_ID,
CONF_TYPE,
)
from aidot.device_client import DeviceClient, DeviceStatusData
from aidot.exceptions import AidotAuthFailed, AidotUserOrPassIncorrect
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
type AidotConfigEntry = ConfigEntry[AidotDeviceManagerCoordinator]
_LOGGER = logging.getLogger(__name__)
UPDATE_DEVICE_LIST_INTERVAL = timedelta(hours=6)
class AidotDeviceUpdateCoordinator(DataUpdateCoordinator[DeviceStatusData]):
"""Class to manage Aidot data."""
def __init__(
self,
hass: HomeAssistant,
config_entry: AidotConfigEntry,
device_client: DeviceClient,
) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=None,
)
self.device_client = device_client
async def _async_setup(self) -> None:
"""Set up the coordinator."""
self.device_client.on_status_update = self._handle_status_update
def _handle_status_update(self, status: DeviceStatusData) -> None:
"""Handle status callback."""
self.async_set_updated_data(status)
async def _async_update_data(self) -> DeviceStatusData:
"""Return current status."""
return self.device_client.status
class AidotDeviceManagerCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Aidot data."""
config_entry: AidotConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: AidotConfigEntry,
) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=UPDATE_DEVICE_LIST_INTERVAL,
)
self.client = AidotClient(
session=async_get_clientsession(hass),
token=config_entry.data,
)
self.client.set_token_fresh_cb(self.token_fresh_cb)
self.device_coordinators: dict[str, AidotDeviceUpdateCoordinator] = {}
async def _async_setup(self) -> None:
"""Set up the coordinator."""
try:
await self.async_auto_login()
except AidotUserOrPassIncorrect as error:
raise ConfigEntryError from error
async def _async_update_data(self) -> None:
"""Update data async."""
try:
data = await self.client.async_get_all_device()
except AidotAuthFailed as error:
raise ConfigEntryError from error
current_devices = {
device[CONF_ID]: device
for device in data[CONF_DEVICE_LIST]
if (
device[CONF_TYPE] == "light"
and CONF_AES_KEY in device
and device[CONF_AES_KEY][0] is not None
)
}
removed_ids = set(self.device_coordinators) - set(current_devices)
for dev_id in removed_ids:
coordinator = self.device_coordinators.pop(dev_id)
coordinator.device_client.on_status_update = None
if removed_ids:
self._purge_deleted_lists()
for dev_id, device in current_devices.items():
if dev_id not in self.device_coordinators:
device_client = self.client.get_device_client(device)
device_coordinator = AidotDeviceUpdateCoordinator(
self.hass, self.config_entry, device_client
)
await device_coordinator.async_config_entry_first_refresh()
self.device_coordinators[dev_id] = device_coordinator
async def async_cleanup(self) -> None:
"""Perform cleanup actions."""
for coordinator in self.device_coordinators.values():
coordinator.device_client.on_status_update = None
await self.client.async_cleanup()
def token_fresh_cb(self) -> None:
"""Update token."""
self.hass.config_entries.async_update_entry(
self.config_entry, data=self.client.login_info.copy()
)
async def async_auto_login(self) -> None:
"""Async auto login."""
if self.client.login_info.get(CONF_ACCESS_TOKEN) is None:
await self.client.async_post_login()
def _purge_deleted_lists(self) -> None:
"""Purge device entries of deleted lists."""
device_reg = dr.async_get(self.hass)
identifiers = {
(
DOMAIN,
device_coordinator.device_client.info.dev_id,
)
for device_coordinator in self.device_coordinators.values()
}
for device in dr.async_entries_for_config_entry(
device_reg, self.config_entry.entry_id
):
if not set(device.identifiers) & identifiers:
_LOGGER.debug("Removing obsolete device entry %s", device.name)
device_reg.async_update_device(
device.id, remove_config_entry_id=self.config_entry.entry_id
)
+122
View File
@@ -0,0 +1,122 @@
"""Support for Aidot lights."""
from typing import Any
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_COLOR_TEMP_KELVIN,
ATTR_RGBW_COLOR,
ColorMode,
LightEntity,
)
from homeassistant.core import HomeAssistant, callback
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 .const import DOMAIN
from .coordinator import AidotConfigEntry, AidotDeviceUpdateCoordinator
async def async_setup_entry(
hass: HomeAssistant,
entry: AidotConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Light."""
coordinator = entry.runtime_data
async_add_entities(
AidotLight(device_coordinator)
for device_coordinator in coordinator.device_coordinators.values()
)
class AidotLight(CoordinatorEntity[AidotDeviceUpdateCoordinator], LightEntity):
"""Representation of a Aidot Wi-Fi Light."""
_attr_has_entity_name = True
_attr_name = None
def __init__(self, coordinator: AidotDeviceUpdateCoordinator) -> None:
"""Initialize the light."""
super().__init__(coordinator)
self._attr_unique_id = coordinator.device_client.info.dev_id
if hasattr(coordinator.device_client.info, "cct_max"):
self._attr_max_color_temp_kelvin = coordinator.device_client.info.cct_max
if hasattr(coordinator.device_client.info, "cct_min"):
self._attr_min_color_temp_kelvin = coordinator.device_client.info.cct_min
model_id = coordinator.device_client.info.model_id
manufacturer = model_id.split(".")[0]
model = model_id[len(manufacturer) + 1 :]
mac = coordinator.device_client.info.mac
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)},
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer=manufacturer,
model=model,
name=coordinator.device_client.info.name,
hw_version=coordinator.device_client.info.hw_version,
)
if coordinator.device_client.info.enable_rgbw:
self._attr_color_mode = ColorMode.RGBW
self._attr_supported_color_modes = {ColorMode.RGBW, ColorMode.COLOR_TEMP}
elif coordinator.device_client.info.enable_cct:
self._attr_color_mode = ColorMode.COLOR_TEMP
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP}
else:
self._attr_color_mode = ColorMode.BRIGHTNESS
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._update_status()
def _update_status(self) -> None:
"""Update light status from coordinator data."""
self._attr_is_on = self.coordinator.data.on
self._attr_brightness = self.coordinator.data.dimming
self._attr_color_temp_kelvin = self.coordinator.data.cct
self._attr_rgbw_color = self.coordinator.data.rgbw
@property
def available(self) -> bool:
"""Return if entity is available."""
return super().available and self.coordinator.data.online
@callback
def _handle_coordinator_update(self) -> None:
"""Update."""
self._update_status()
super()._handle_coordinator_update()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on, applying brightness, color temperature, RGBW, or plain on."""
if ATTR_BRIGHTNESS in kwargs:
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
await self.coordinator.device_client.async_set_brightness(brightness)
self.coordinator.data.dimming = brightness
self._attr_brightness = brightness
elif ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs.get(ATTR_COLOR_TEMP_KELVIN)
await self.coordinator.device_client.async_set_cct(color_temp_kelvin)
self.coordinator.data.cct = color_temp_kelvin
self._attr_color_temp_kelvin = color_temp_kelvin
self._attr_color_mode = ColorMode.COLOR_TEMP
elif ATTR_RGBW_COLOR in kwargs:
rgbw_color = kwargs.get(ATTR_RGBW_COLOR)
await self.coordinator.device_client.async_set_rgbw(rgbw_color)
self.coordinator.data.rgbw = rgbw_color
self._attr_rgbw_color = rgbw_color
self._attr_color_mode = ColorMode.RGBW
else:
await self.coordinator.device_client.async_turn_on()
self.coordinator.data.on = True
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
await self.coordinator.device_client.async_turn_off()
self.coordinator.data.on = False
self._attr_is_on = False
self.async_write_ha_state()
@@ -0,0 +1,11 @@
{
"domain": "aidot",
"name": "AiDot",
"codeowners": ["@s1eedz", "@HongBryan"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aidot",
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["python-aidot==0.3.53"]
}
@@ -0,0 +1,67 @@
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: This integration does not register any 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: todo
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: This integration has no option flow.
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: todo
reauthentication-flow: todo
test-coverage: todo
# Gold
devices: done
diagnostics: todo
discovery-update-info: todo
discovery: todo
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: todo
entity-category: todo
entity-device-class: todo
entity-translations: done
exception-translations: todo
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo
entity-disabled-by-default: todo
# Platinum
async-dependency: done
inject-websession: todo
strict-typing: todo
@@ -0,0 +1,25 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"step": {
"user": {
"data": {
"country_code": "Country",
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"country_code": "The country selected by AiDot app when logging in",
"password": "Password for logging in through AiDot app",
"username": "Account logged in through AiDot app"
}
}
}
}
}
@@ -81,8 +81,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
) as err:
raise ConfigEntryAuthFailed from err
except AirOSKeyDataMissingError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError("key_data_missing") from err
except Exception as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise ConfigEntryError("unknown") from err
airos_class: type[AirOS8 | AirOS6] = (
@@ -91,6 +91,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
translation_placeholders={"error": repr(err)},
) from err
except CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -2,4 +2,5 @@
DOMAIN = "altruist"
# pylint: disable-next=home-assistant-duplicate-const
CONF_HOST = "host"
@@ -16,6 +16,8 @@ from .entity import AnthropicBaseLLMEntity
if TYPE_CHECKING:
from . import AnthropicConfigEntry
PARALLEL_UPDATES = 0
_LOGGER = logging.getLogger(__name__)
@@ -24,10 +24,10 @@ from homeassistant.const import (
CONF_API_KEY,
CONF_LLM_HASS_API,
CONF_NAME,
CONF_PROMPT,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import llm
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import config_validation as cv, llm
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.selector import (
NumberSelector,
@@ -44,12 +44,13 @@ from .const import (
CONF_CHAT_MODEL,
CONF_CODE_EXECUTION,
CONF_MAX_TOKENS,
CONF_PROMPT,
CONF_PROMPT_CACHING,
CONF_RECOMMENDED,
CONF_THINKING_BUDGET,
CONF_THINKING_EFFORT,
CONF_TOOL_SEARCH,
CONF_WEB_FETCH,
CONF_WEB_FETCH_MAX_USES,
CONF_WEB_SEARCH,
CONF_WEB_SEARCH_CITY,
CONF_WEB_SEARCH_COUNTRY,
@@ -452,11 +453,19 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
vol.Optional(
CONF_WEB_SEARCH_MAX_USES,
default=DEFAULT[CONF_WEB_SEARCH_MAX_USES],
): int,
): cv.positive_int,
vol.Optional(
CONF_WEB_SEARCH_USER_LOCATION,
default=DEFAULT[CONF_WEB_SEARCH_USER_LOCATION],
): bool,
vol.Optional(
CONF_WEB_FETCH,
default=DEFAULT[CONF_WEB_FETCH],
): bool,
vol.Optional(
CONF_WEB_FETCH_MAX_USES,
default=DEFAULT[CONF_WEB_FETCH_MAX_USES],
): cv.positive_int,
}
)
+4 -1
View File
@@ -10,7 +10,6 @@ DEFAULT_CONVERSATION_NAME = "Claude conversation"
DEFAULT_AI_TASK_NAME = "Claude AI Task"
CONF_RECOMMENDED = "recommended"
CONF_PROMPT = "prompt"
CONF_CHAT_MODEL = "chat_model"
CONF_CODE_EXECUTION = "code_execution"
CONF_MAX_TOKENS = "max_tokens"
@@ -18,6 +17,8 @@ CONF_PROMPT_CACHING = "prompt_caching"
CONF_THINKING_BUDGET = "thinking_budget"
CONF_THINKING_EFFORT = "thinking_effort"
CONF_TOOL_SEARCH = "tool_search"
CONF_WEB_FETCH = "web_fetch"
CONF_WEB_FETCH_MAX_USES = "web_fetch_max_uses"
CONF_WEB_SEARCH = "web_search"
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
CONF_WEB_SEARCH_MAX_USES = "web_search_max_uses"
@@ -45,6 +46,8 @@ DEFAULT = {
CONF_THINKING_BUDGET: MIN_THINKING_BUDGET,
CONF_THINKING_EFFORT: "low",
CONF_TOOL_SEARCH: False,
CONF_WEB_FETCH: False,
CONF_WEB_FETCH_MAX_USES: 5,
CONF_WEB_SEARCH: False,
CONF_WEB_SEARCH_USER_LOCATION: False,
CONF_WEB_SEARCH_MAX_USES: 5,
@@ -4,14 +4,16 @@ from typing import Literal
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigSubentry
from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL
from homeassistant.const import CONF_LLM_HASS_API, CONF_PROMPT, MATCH_ALL
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AnthropicConfigEntry
from .const import CONF_PROMPT, DOMAIN
from .const import DOMAIN
from .entity import AnthropicBaseLLMEntity
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
@@ -5,11 +5,10 @@ from typing import TYPE_CHECKING, Any
from anthropic import __title__, __version__
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_API_KEY
from homeassistant.const import CONF_API_KEY, CONF_PROMPT
from homeassistant.helpers import entity_registry as er
from .const import (
CONF_PROMPT,
CONF_WEB_SEARCH_CITY,
CONF_WEB_SEARCH_COUNTRY,
CONF_WEB_SEARCH_REGION,
+55 -17
View File
@@ -17,8 +17,6 @@ from anthropic.types import (
Base64PDFSourceParam,
BashCodeExecutionToolResultBlock,
CitationsDelta,
CitationsWebSearchResultLocation,
CitationWebSearchResultLocationParam,
CodeExecutionTool20250825Param,
CodeExecutionToolResultBlock,
CodeExecutionToolResultBlockContent,
@@ -70,6 +68,9 @@ from anthropic.types import (
ToolUseBlock,
ToolUseBlockParam,
Usage,
WebFetchTool20250910Param,
WebFetchTool20260209Param,
WebFetchToolResultBlock,
WebSearchTool20250305Param,
WebSearchTool20260209Param,
WebSearchToolResultBlock,
@@ -97,6 +98,12 @@ from anthropic.types.tool_search_tool_result_block_param import (
Content as ToolSearchToolResultBlockParamContentParam,
)
from anthropic.types.tool_use_block import Caller
from anthropic.types.web_fetch_tool_result_block import (
Content as WebFetchToolResultBlockContent,
)
from anthropic.types.web_fetch_tool_result_block_param import (
Content as WebFetchToolResultBlockParamContentParam,
)
import voluptuous as vol
from voluptuous_openapi import convert
@@ -118,6 +125,8 @@ from .const import (
CONF_THINKING_BUDGET,
CONF_THINKING_EFFORT,
CONF_TOOL_SEARCH,
CONF_WEB_FETCH,
CONF_WEB_FETCH_MAX_USES,
CONF_WEB_SEARCH,
CONF_WEB_SEARCH_CITY,
CONF_WEB_SEARCH_COUNTRY,
@@ -208,17 +217,9 @@ class ContentDetails:
"""Add a citation to the current detail."""
if not self.citation_details:
self.citation_details.append(CitationDetails())
citation_param: TextCitationParam | None = None
if isinstance(citation, CitationsWebSearchResultLocation):
citation_param = CitationWebSearchResultLocationParam(
type="web_search_result_location",
title=citation.title,
url=citation.url,
cited_text=citation.cited_text,
encrypted_index=citation.encrypted_index,
)
if citation_param:
self.citation_details[-1].citations.append(citation_param)
self.citation_details[-1].citations.append(
cast(TextCitationParam, citation.to_dict())
)
def delete_empty(self) -> None:
"""Delete empty citation details."""
@@ -289,6 +290,15 @@ def _convert_content( # noqa: C901
content.tool_result,
),
}
elif content.tool_name == "web_fetch":
tool_result_block = {
"type": "web_fetch_tool_result",
"tool_use_id": content.tool_call_id,
"content": cast(
WebFetchToolResultBlockParamContentParam,
content.tool_result,
),
}
else:
tool_result_block = {
"type": "tool_result",
@@ -415,6 +425,7 @@ def _convert_content( # noqa: C901
id=tool_call.id,
name=cast(
Literal[
"web_fetch",
"web_search",
"code_execution",
"bash_code_execution",
@@ -428,6 +439,7 @@ def _convert_content( # noqa: C901
if tool_call.external
and tool_call.tool_name
in [
"web_fetch",
"web_search",
"code_execution",
"bash_code_execution",
@@ -609,6 +621,7 @@ class AnthropicDeltaStream:
if isinstance(
content_block,
(
WebFetchToolResultBlock,
WebSearchToolResultBlock,
CodeExecutionToolResultBlock,
BashCodeExecutionToolResultBlock,
@@ -724,13 +737,15 @@ class AnthropicDeltaStream:
self,
tool_use_id: str,
tool_name: Literal[
"web_fetch_tool_result",
"web_search_tool_result",
"code_execution_tool_result",
"bash_code_execution_tool_result",
"text_editor_code_execution_tool_result",
"tool_search_tool_result",
],
content: WebSearchToolResultBlockContent
content: WebFetchToolResultBlockContent
| WebSearchToolResultBlockContent
| CodeExecutionToolResultBlockContent
| BashCodeExecutionToolResultBlockContent
| TextEditorCodeExecutionToolResultBlockContent
@@ -907,6 +922,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
"GetLiveContext",
"code_execution",
"web_search",
"web_fetch",
]
system = chat_log.content[0]
@@ -980,12 +996,12 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
]
if options[CONF_CODE_EXECUTION]:
# The `web_search_20260209` tool automatically enables
# `code_execution_20260120` tool
# The `web_search_20260209` and `web_fetch_20260209` tools
# automatically enable `code_execution_20260120` tool
if (
not self.model_info.capabilities
or not self.model_info.capabilities.code_execution.supported
or not options[CONF_WEB_SEARCH]
or (not options[CONF_WEB_SEARCH] and not options[CONF_WEB_FETCH])
):
tools.append(
CodeExecutionTool20250825Param(
@@ -1023,6 +1039,28 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
}
tools.append(web_search)
if options[CONF_WEB_FETCH]:
if (
not self.model_info.capabilities
or not self.model_info.capabilities.code_execution.supported
or not options[CONF_CODE_EXECUTION]
):
tools.append(
WebFetchTool20250910Param(
name="web_fetch",
type="web_fetch_20250910",
max_uses=options[CONF_WEB_FETCH_MAX_USES],
)
)
else:
tools.append(
WebFetchTool20260209Param(
name="web_fetch",
type="web_fetch_20260209",
max_uses=options[CONF_WEB_FETCH_MAX_USES],
)
)
# Handle attachments by adding them to the last user message
last_content = chat_log.content[-1]
if last_content.role == "user" and last_content.attachments:
@@ -38,10 +38,7 @@ rules:
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates:
status: exempt
comment: |
The API does not limit parallel updates.
parallel-updates: done
reauthentication-flow: done
test-coverage: done
# Gold
@@ -40,9 +40,11 @@ class ModelDeprecatedRepairFlow(RepairsFlow):
self._current_subentry_id = None
self._model_list_cache = None
async def async_step_init(self, user_input: dict[str, str]) -> RepairsFlowResult:
async def async_step_init(
self, user_input: dict[str, str] | None
) -> RepairsFlowResult:
"""Handle the steps of a fix flow."""
if user_input.get(CONF_CHAT_MODEL):
if user_input and user_input.get(CONF_CHAT_MODEL):
self._async_update_current_subentry(user_input)
target = await self._async_next_target()
@@ -51,13 +51,11 @@
"advanced": {
"data": {
"chat_model": "[%key:common::generic::model%]",
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]",
"temperature": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::temperature%]"
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data::prompt_caching%]"
},
"data_description": {
"chat_model": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::chat_model%]",
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]",
"temperature": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::temperature%]"
"prompt_caching": "[%key:component::anthropic::config_subentries::conversation::step::advanced::data_description::prompt_caching%]"
},
"title": "[%key:component::anthropic::config_subentries::conversation::step::advanced::title%]"
},
@@ -80,6 +78,8 @@
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data::thinking_effort%]",
"tool_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data::tool_search%]",
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data::user_location%]",
"web_fetch": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_fetch%]",
"web_fetch_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_fetch_max_uses%]",
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search%]",
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search_max_uses%]"
},
@@ -90,6 +90,8 @@
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::thinking_effort%]",
"tool_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::tool_search%]",
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::user_location%]",
"web_fetch": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_fetch%]",
"web_fetch_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_fetch_max_uses%]",
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search%]",
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search_max_uses%]"
},
@@ -116,13 +118,11 @@
"advanced": {
"data": {
"chat_model": "[%key:common::generic::model%]",
"prompt_caching": "Caching strategy",
"temperature": "Temperature"
"prompt_caching": "Caching strategy"
},
"data_description": {
"chat_model": "The model to serve the responses.",
"prompt_caching": "Optimize your API cost and response times based on your usage.",
"temperature": "Control the randomness of the response, trading off between creativity and coherence."
"prompt_caching": "Optimize your API cost and response times based on your usage."
},
"title": "Advanced settings"
},
@@ -149,6 +149,8 @@
"thinking_effort": "Thinking effort",
"tool_search": "Enable tool search tool",
"user_location": "Include home location",
"web_fetch": "Enable web fetch",
"web_fetch_max_uses": "Maximum web fetches",
"web_search": "Enable web search",
"web_search_max_uses": "Maximum web searches"
},
@@ -159,6 +161,8 @@
"thinking_effort": "Control how many tokens Claude uses when responding, trading off between response thoroughness and token efficiency",
"tool_search": "Enable dynamic tool discovery instead of preloading all tools into the context",
"user_location": "Localize search results based on home location",
"web_fetch": "The web fetch tool allows Claude to retrieve full content from specified web pages and PDF documents to augment Claude's context with live web content",
"web_fetch_max_uses": "Limit the number of web fetches performed per response",
"web_search": "The web search tool gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff",
"web_search_max_uses": "Limit the number of searches performed per response"
},
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aosmith",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["py-aosmith==1.0.17"]
"requirements": ["py-aosmith==1.0.18"]
}
@@ -5,7 +5,7 @@ from pyatv.interface import AppleTV, KeyboardListener
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_NAME
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -21,23 +21,33 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Load Apple TV binary sensor based on a config entry."""
# apple_tv config entries always have a unique id
manager = config_entry.runtime_data
cb: CALLBACK_TYPE
added = False
@callback
def setup_entities(atv: AppleTV) -> None:
nonlocal added
if added:
return
if atv.features.in_state(FeatureState.Available, FeatureName.TextFocusState):
assert config_entry.unique_id is not None
name: str = config_entry.data[CONF_NAME]
async_add_entities(
[AppleTVKeyboardFocused(name, config_entry.unique_id, manager)]
)
cb()
added = True
cb = async_dispatcher_connect(
hass, f"{SIGNAL_CONNECTED}_{config_entry.unique_id}", setup_entities
config_entry.async_on_unload(
async_dispatcher_connect(
hass, f"{SIGNAL_CONNECTED}_{config_entry.unique_id}", setup_entities
)
)
config_entry.async_on_unload(cb)
# The manager may have already connected (and dispatched SIGNAL_CONNECTED)
# before this platform was forwarded, in which case the signal above was
# missed; handle that case directly.
if manager.atv is not None:
setup_entities(manager.atv)
class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener):
@@ -16,6 +16,9 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ArcamFmjConfigEntry
from .entity import ArcamFmjEntity
# Read-only, coordinator-driven entities; no per-entity I/O to bound.
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class ArcamFmjBinarySensorEntityDescription(BinarySensorEntityDescription):
@@ -1,9 +1,11 @@
"""Config flow to configure the Arcam FMJ component."""
import socket
from typing import Any
from urllib.parse import urlparse
from arcam.fmj.client import Client, ConnectionFailed
from arcam.fmj import ConnectionFailed
from arcam.fmj.client import Client
from arcam.fmj.utils import get_uniqueid_from_host, get_uniqueid_from_udn
import voluptuous as vol
@@ -29,26 +31,19 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: host, CONF_PORT: port})
async def _async_check_and_create(self, host: str, port: int) -> ConfigFlowResult:
async def _async_try_connect(self, host: str, port: int) -> None:
"""Verify the device is reachable."""
client = Client(host, port)
try:
await client.start()
except ConnectionFailed:
return self.async_abort(reason="cannot_connect")
finally:
await client.stop()
return self.async_create_entry(
title=f"{DEFAULT_NAME} ({host})",
data={CONF_HOST: host, CONF_PORT: port},
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a discovered device."""
errors: dict[str, str] = {}
if user_input is not None:
uuid = await get_uniqueid_from_host(
async_get_clientsession(self.hass), user_input[CONF_HOST]
@@ -58,18 +53,36 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
user_input[CONF_HOST], user_input[CONF_PORT], uuid
)
return await self._async_check_and_create(
user_input[CONF_HOST], user_input[CONF_PORT]
)
try:
await self._async_try_connect(
user_input[CONF_HOST], user_input[CONF_PORT]
)
except socket.gaierror:
errors["base"] = "invalid_host"
except TimeoutError:
errors["base"] = "timeout_connect"
except ConnectionRefusedError:
errors["base"] = "connection_refused"
except ConnectionFailed, OSError:
errors["base"] = "cannot_connect"
else:
return self.async_create_entry(
title=f"{DEFAULT_NAME} ({user_input[CONF_HOST]})",
data={
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
},
)
fields = {
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
schema = vol.Schema(fields)
if user_input is not None:
schema = self.add_suggested_values_to_schema(schema, user_input)
return self.async_show_form(
step_id="user", data_schema=vol.Schema(fields), errors=errors
)
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
@@ -79,7 +92,10 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
self.context["title_placeholders"] = placeholders
if user_input is not None:
return await self._async_check_and_create(self.host, self.port)
return self.async_create_entry(
title=f"{DEFAULT_NAME} ({self.host})",
data={CONF_HOST: self.host, CONF_PORT: self.port},
)
return self.async_show_form(
step_id="confirm", description_placeholders=placeholders
@@ -97,6 +113,11 @@ class ArcamFmjFlowHandler(ConfigFlow, domain=DOMAIN):
await self._async_set_unique_id_and_update(host, port, uuid)
try:
await self._async_try_connect(host, port)
except ConnectionFailed, OSError:
return self.async_abort(reason="cannot_connect")
self.host = host
self.port = DEFAULT_PORT
self.port = port
return await self.async_step_confirm()
@@ -53,18 +53,19 @@ class ArcamFmjCoordinator(DataUpdateCoordinator[None]):
self.state = State(client, zone)
self.update_in_progress = False
name = config_entry.title
device_name = config_entry.title
unique_id = config_entry.unique_id or config_entry.entry_id
unique_id_device = unique_id
if zone != 1:
unique_id_device += f"-{zone}"
name += f" Zone {zone}"
device_name += f" Zone {zone}"
self.device_name = device_name
self.device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id_device)},
manufacturer="Arcam",
model="Arcam FMJ AVR",
name=name,
name=device_name,
)
self.zone_unique_id = f"{unique_id}-{zone}"
@@ -1,11 +1,36 @@
"""Base entity for Arcam FMJ integration."""
from collections.abc import Callable, Coroutine
import functools
from typing import Any
from arcam.fmj import ConnectionFailed
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import ArcamFmjCoordinator
def convert_exception[**_P, _R](
func: Callable[_P, Coroutine[Any, Any, _R]],
) -> Callable[_P, Coroutine[Any, Any, _R]]:
"""Convert a connection failure into a translated HomeAssistantError."""
@functools.wraps(func)
async def _convert_exception(*args: _P.args, **kwargs: _P.kwargs) -> _R:
try:
return await func(*args, **kwargs)
except ConnectionFailed as exception:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="connection_failed"
) from exception
return _convert_exception
class ArcamFmjEntity(CoordinatorEntity[ArcamFmjCoordinator]):
"""Base entity for Arcam FMJ."""
@@ -1,11 +1,9 @@
"""Arcam media player."""
from collections.abc import Callable, Coroutine
import functools
import logging
from typing import Any
from arcam.fmj import ConnectionFailed, SourceCodes
from arcam.fmj import SourceCodes
from homeassistant.components.media_player import (
BrowseError,
@@ -18,15 +16,19 @@ from homeassistant.components.media_player import (
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN, EVENT_TURN_ON
from .coordinator import ArcamFmjConfigEntry, ArcamFmjCoordinator
from .entity import ArcamFmjEntity
from .entity import ArcamFmjEntity, convert_exception
_LOGGER = logging.getLogger(__name__)
# arcam-fmj serializes commands on a single TCP writer at the library
# layer; serialize at HA's layer to match the device's contract.
PARALLEL_UPDATES = 1
async def async_setup_entry(
hass: HomeAssistant,
@@ -41,23 +43,6 @@ async def async_setup_entry(
)
def convert_exception[**_P, _R](
func: Callable[_P, Coroutine[Any, Any, _R]],
) -> Callable[_P, Coroutine[Any, Any, _R]]:
"""Return decorator to convert a connection error into a home assistant error."""
@functools.wraps(func)
async def _convert_exception(*args: _P.args, **kwargs: _P.kwargs) -> _R:
try:
return await func(*args, **kwargs)
except ConnectionFailed as exception:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="connection_failed"
) from exception
return _convert_exception
class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
"""Representation of a media device."""
@@ -79,11 +64,17 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE
@property
def state(self) -> MediaPlayerState:
"""Return the state of the device."""
if self._state.get_power():
return MediaPlayerState.ON
return MediaPlayerState.OFF
def state(self) -> MediaPlayerState | None:
"""Return the state of the device.
``None`` is returned (surfaced as ``unknown``) when the device has
not yet reported a power state; this is distinct from a real
powered-off state and must not be collapsed to ``OFF``.
"""
power = self._state.get_power()
if power is None:
return None
return MediaPlayerState.ON if power else MediaPlayerState.OFF
@convert_exception
async def async_mute_volume(self, mute: bool) -> None:
@@ -179,7 +170,7 @@ class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
]
return BrowseMedia(
title="Arcam FMJ Receiver",
title=self.coordinator.device_name,
media_class=MediaClass.DIRECTORY,
media_content_id="root",
media_content_type="library",
@@ -22,6 +22,9 @@ from .entity import ArcamFmjEntity
_LOGGER = logging.getLogger(__name__)
# Read-only, coordinator-driven entities; no per-entity I/O to bound.
PARALLEL_UPDATES = 0
def _enum_options(value: type[IntOrTypeEnum]) -> list[str]:
return [
@@ -5,6 +5,12 @@
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"connection_refused": "Host refused connection",
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]"
},
"flow_title": "{host}",
"step": {
"confirm": {
@@ -19,6 +19,8 @@ DEVICES = "devices"
MANUFACTURER = "ABB"
ATTR_DEVICE_NAME = "device_name"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_DEVICE_ID = "device_id"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL = "model"
ATTR_FIRMWARE = "firmware"
@@ -82,7 +82,7 @@ rules:
comment: |
This integration does not have any entities that should disabled by default.
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues:
@@ -3,7 +3,7 @@
from autoskope_client.constants import MANUFACTURER
from autoskope_client.models import Vehicle
from homeassistant.components.device_tracker import SourceType, TrackerEntity
from homeassistant.components.device_tracker import TrackerEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
@@ -113,11 +113,6 @@ class AutoskopeDeviceTracker(
return float(vehicle.position.longitude)
return None
@property
def source_type(self) -> SourceType:
"""Return the source type of the device."""
return SourceType.GPS
@property
def location_accuracy(self) -> float:
"""Return the location accuracy of the device in meters."""
@@ -67,7 +67,7 @@ rules:
comment: |
Only one entity type (device_tracker) is created, making this not applicable.
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow:
status: todo
+38 -8
View File
@@ -32,6 +32,7 @@ from .const import DOMAIN, INTEGRATION_TITLE, UNKNOWN_NAME
_LOGGER = logging.getLogger(__name__)
UPDATE_EXCEPTIONS = (BleakError, OSError, RuntimeError)
BREAKS_IN_HA_VERSION = "2026.12.0"
AVEA_MAX_BRIGHTNESS = 4095
def _normalize_name(name: str | None) -> str | None:
@@ -41,6 +42,16 @@ def _normalize_name(name: str | None) -> str | None:
return name
def _ha_brightness_to_avea(brightness: int) -> int:
"""Convert Home Assistant brightness to Avea brightness."""
return round((brightness / 255) * AVEA_MAX_BRIGHTNESS)
def _avea_brightness_to_ha(brightness: int) -> int:
"""Convert Avea brightness to Home Assistant brightness."""
return round(255 * (brightness / AVEA_MAX_BRIGHTNESS))
def _create_deprecated_yaml_issue(hass: HomeAssistant) -> None:
"""Create the deprecated YAML issue for Avea."""
ir.async_create_issue(
@@ -84,7 +95,9 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Avea light platform."""
async_add_entities([AveaLight(entry.runtime_data)], update_before_add=True)
async_add_entities(
[AveaLight(entry.runtime_data, entry.title)], update_before_add=True
)
def _discover_bulbs_for_import() -> list[dict[str, str]]:
@@ -169,20 +182,23 @@ class AveaLight(LightEntity):
_attr_color_mode = ColorMode.HS
_attr_supported_color_modes = {ColorMode.HS}
def __init__(self, light: avea.Bulb) -> None:
def __init__(self, light: avea.Bulb, entry_title: str) -> None:
"""Initialize an AveaLight."""
self._light = light
self._attr_name = light.name
self._attr_name = entry_title
self._attr_brightness = light.brightness
self._last_brightness = 255
def turn_on(self, **kwargs: Any) -> None:
"""Instruct the light to turn on."""
if not kwargs:
self._light.set_brightness(4095)
self._light.set_brightness(_ha_brightness_to_avea(self._last_brightness))
else:
if ATTR_BRIGHTNESS in kwargs:
bright = round((kwargs[ATTR_BRIGHTNESS] / 255) * 4095)
self._light.set_brightness(bright)
brightness = kwargs[ATTR_BRIGHTNESS]
if brightness:
self._last_brightness = brightness
self._light.set_brightness(_ha_brightness_to_avea(brightness))
if ATTR_HS_COLOR in kwargs:
rgb = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR])
self._light.set_rgb(rgb[0], rgb[1], rgb[2])
@@ -190,9 +206,23 @@ class AveaLight(LightEntity):
def turn_off(self, **kwargs: Any) -> None:
"""Instruct the light to turn off."""
self._light.set_brightness(0)
self._attr_is_on = False
self._attr_brightness = 0
def update(self) -> None:
"""Fetch new state data for this light."""
if (brightness := self._light.get_brightness()) is not None:
connected = self._light.connect()
try:
brightness = self._light.get_brightness()
rgb_color = self._light.get_rgb()
finally:
if connected:
self._light.disconnect()
if brightness is not None:
self._attr_is_on = brightness != 0
self._attr_brightness = round(255 * (brightness / 4095))
self._attr_brightness = _avea_brightness_to_ha(brightness)
if self._attr_brightness:
self._last_brightness = self._attr_brightness
self._attr_hs_color = color_util.color_RGB_to_hs(*rgb_color)
+1 -1
View File
@@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["avea"],
"requirements": ["avea==1.6.1"]
"requirements": ["avea==1.8.0"]
}
@@ -51,6 +51,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: S3ConfigEntry) -> bool:
translation_key="invalid_bucket_name",
) from err
except ValueError as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryError(
translation_domain=DOMAIN,
translation_key="invalid_endpoint_url",
+1
View File
@@ -11,6 +11,7 @@ CONF_ACCESS_KEY_ID = "access_key_id"
CONF_SECRET_ACCESS_KEY = "secret_access_key"
CONF_ENDPOINT_URL = "endpoint_url"
CONF_BUCKET = "bucket"
# pylint: disable-next=home-assistant-duplicate-const
CONF_PREFIX = "prefix"
AWS_DOMAIN = "amazonaws.com"
@@ -72,7 +72,7 @@ rules:
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -75,11 +75,13 @@ def handle_backup_errors[_R, **P](
err.message,
exc_info=True,
)
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Error during backup operation in {func.__name__}:"
f" Status {err.status_code}, message: {err.message}"
) from err
except ServiceRequestError as err:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Timeout during backup operation in {func.__name__}"
) from err
@@ -90,6 +92,7 @@ def handle_backup_errors[_R, **P](
err,
exc_info=True,
)
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupAgentError(
f"Error during backup operation in {func.__name__}: {err}"
) from err
@@ -118,6 +121,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Download a backup file."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
download_stream = await self._client.download_blob(blob.name)
return download_stream.chunks()
@@ -155,6 +159,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Delete a backup file."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
await self._client.delete_blob(blob.name)
@@ -181,6 +186,7 @@ class AzureStorageBackupAgent(BackupAgent):
"""Return a backup."""
blob = await self._find_blob_by_backup_id(backup_id)
if blob is None:
# pylint: disable-next=home-assistant-exception-not-translated
raise BackupNotFound(f"Backup {backup_id} not found")
return AgentBackup.from_dict(json.loads(blob.metadata["backup_metadata"]))
@@ -89,6 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BackblazeConfigEntry) ->
translation_key="cannot_connect",
) from err
except exception.MissingAccountData as err:
# pylint: disable-next=home-assistant-exception-translation-key-missing
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
@@ -20,12 +20,12 @@ from homeassistant.components.backup import (
OnProgressCallback,
suggested_filename,
)
from homeassistant.const import CONF_PREFIX
from homeassistant.core import HomeAssistant, callback
from homeassistant.util.async_iterator import AsyncIteratorReader
from . import BackblazeConfigEntry
from .const import (
CONF_PREFIX,
DATA_BACKUP_AGENT_LISTENERS,
DOMAIN,
METADATA_FILE_SUFFIX,
@@ -8,6 +8,7 @@ from b2sdk.v2 import exception
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PREFIX
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.selector import (
TextSelector,
@@ -22,7 +23,6 @@ from .const import (
CONF_APPLICATION_KEY,
CONF_BUCKET,
CONF_KEY_ID,
CONF_PREFIX,
DOMAIN,
)
@@ -10,7 +10,6 @@ DOMAIN: Final = "backblaze_b2"
CONF_KEY_ID = "key_id"
CONF_APPLICATION_KEY = "application_key"
CONF_BUCKET = "bucket"
CONF_PREFIX = "prefix"
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
f"{DOMAIN}.backup_agent_listeners"
@@ -96,7 +96,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not have entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -67,6 +67,9 @@
"cannot_connect": {
"message": "Cannot connect to endpoint"
},
"invalid_auth": {
"message": "Authentication failed using the provided key ID and application key."
},
"invalid_bucket_name": {
"message": "Bucket does not exist or is not writable by the provided credentials."
},
+8 -2
View File
@@ -80,7 +80,8 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
def _color_temp_to_native_scale(self, x: int) -> int:
"""Convert color temperature from Kelvin to native BleBox scale (0-255).
BleBox native scale is inverted relative to Kelvin: 0=warm (2700K), 255=cold (6500K).
BleBox native scale is inverted:
0=warm (2700K), 255=cold (6500K).
"""
scaled = (
(self._attr_max_color_temp_kelvin - x)
@@ -98,7 +99,8 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
def _color_temp_from_native_scale(self, x: int) -> int:
"""Convert color temperature from native BleBox scale (0-255) to Kelvin.
BleBox native scale is inverted relative to Kelvin: 0=warm (2700K), 255=cold (6500K).
BleBox native scale is inverted:
0=warm (2700K), 255=cold (6500K).
"""
scaled = self._attr_max_color_temp_kelvin - (x / 255) * (
self._attr_max_color_temp_kelvin - self._attr_min_color_temp_kelvin
@@ -201,6 +203,10 @@ class BleBoxLightEntity(BleBoxEntity[blebox_uniapi.light.Light], LightEntity):
else:
value = feature.apply_brightness(value, brightness)
if isinstance(value, (list, tuple)) and not any(value):
await self._feature.async_off()
return
try:
await self._feature.async_on(value)
except ValueError as exc:
+2
View File
@@ -169,6 +169,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
try:
await self._camera.save_recent_clips(output_dir=file_path)
except OSError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
str(err),
translation_domain=DOMAIN,
@@ -190,6 +191,7 @@ class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
try:
await self._camera.video_to_file(filename)
except OSError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise ServiceValidationError(
str(err),
translation_domain=DOMAIN,
@@ -16,6 +16,7 @@ CONF_DETAILS = "details"
CONF_PASSIVE = "passive"
# pylint: disable-next=home-assistant-duplicate-const
CONF_SOURCE: Final = "source"
CONF_SOURCE_DOMAIN: Final = "source_domain"
CONF_SOURCE_MODEL: Final = "source_model"
@@ -19,8 +19,8 @@
"bleak-retry-connector==4.6.0",
"bluetooth-adapters==2.1.0",
"bluetooth-auto-recovery==1.5.3",
"bluetooth-data-tools==1.28.4",
"dbus-fast==4.0.4",
"bluetooth-data-tools==1.29.11",
"dbus-fast==5.0.0",
"habluetooth==6.1.0"
]
}
@@ -89,7 +89,9 @@ class PassiveBluetoothDataUpdateCoordinator(
class PassiveBluetoothCoordinatorEntity[ # pylint: disable=home-assistant-enforce-class-module
_PassiveBluetoothDataUpdateCoordinatorT: PassiveBluetoothDataUpdateCoordinator = PassiveBluetoothDataUpdateCoordinator
_PassiveBluetoothDataUpdateCoordinatorT: (
PassiveBluetoothDataUpdateCoordinator
) = PassiveBluetoothDataUpdateCoordinator
](BaseCoordinatorEntity[_PassiveBluetoothDataUpdateCoordinatorT]):
"""A class for entities using DataUpdateCoordinator."""
@@ -94,8 +94,8 @@ def serialize_service_info(
"address": service_info.address,
"rssi": service_info.rssi,
"manufacturer_data": {
str(manufacturer_id): manufacturer_data.hex()
for manufacturer_id, manufacturer_data in service_info.manufacturer_data.items()
str(manufacturer_id): data.hex()
for manufacturer_id, data in service_info.manufacturer_data.items()
},
"service_data": {
service_uuid: service_data.hex()
@@ -6,6 +6,7 @@ from typing import Final
ATTR_CID: Final = "cid"
ATTR_MAC: Final = "macAddr"
ATTR_MANUFACTURER: Final = "Sony"
# pylint: disable-next=home-assistant-duplicate-const
ATTR_MODEL: Final = "model"
CONF_NICKNAME: Final = "nickname"
+1 -1
View File
@@ -183,8 +183,8 @@ class BSBLANClimate(BSBLanCircuitEntity, ClimateEntity):
try:
await self.coordinator.client.thermostat(**data, circuit=self._circuit)
except BSBLANError as err:
# pylint: disable-next=home-assistant-exception-message-with-translation
raise HomeAssistantError(
"An error occurred while updating the BSBLAN device",
translation_domain=DOMAIN,
translation_key="set_data_error",
) from err
@@ -8,7 +8,7 @@
"iot_class": "local_polling",
"loggers": ["bsblan"],
"quality_scale": "silver",
"requirements": ["python-bsblan==5.2.1"],
"requirements": ["python-bsblan==6.0.1"],
"zeroconf": [
{
"name": "bsb-lan*",
+1 -1
View File
@@ -8,6 +8,7 @@ from bsblan import BSBLANError, DaySchedule, DHWSchedule, TimeSlot
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
@@ -20,7 +21,6 @@ if TYPE_CHECKING:
LOGGER = logging.getLogger(__name__)
ATTR_DEVICE_ID = "device_id"
ATTR_MONDAY_SLOTS = "monday_slots"
ATTR_TUESDAY_SLOTS = "tuesday_slots"
ATTR_WEDNESDAY_SLOTS = "wednesday_slots"
@@ -151,7 +151,9 @@ def sensor_update_to_bluetooth_data_update(
device_key_to_bluetooth_entity_key(device_key): BINARY_SENSOR_DESCRIPTIONS[
description.device_class
]
for device_key, description in sensor_update.binary_entity_descriptions.items()
for device_key, description in (
sensor_update.binary_entity_descriptions.items()
)
if description.device_class
},
entity_data={
+5 -1
View File
@@ -17,6 +17,8 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import TIMEOUT
type CalDavConfigEntry = ConfigEntry[caldav.DAVClient]
_LOGGER = logging.getLogger(__name__)
@@ -32,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: CalDavConfigEntry) -> bo
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=30,
timeout=TIMEOUT,
)
try:
await hass.async_add_executor_job(client.principal)
@@ -43,6 +45,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: CalDavConfigEntry) -> bo
# on some other unexpected server response.
_LOGGER.warning("Unexpected CalDAV server response: %s", err)
return False
except requests.Timeout as err:
raise ConfigEntryNotReady("Timeout connecting to CalDAV server") from err
except requests.ConnectionError as err:
raise ConfigEntryNotReady("Connection error from CalDAV server") from err
except DAVError as err:
+4 -2
View File
@@ -52,14 +52,16 @@ async def async_get_calendars(
warned_calendars.add((url, comp))
if comp in ASSUMED_COMPONENTS:
_LOGGER.warning(
"CalDAV server does not report supported components for calendar %s, "
"CalDAV server does not report supported"
" components for calendar %s, "
"assuming it supports the requested component '%s'",
name or url,
comp,
)
else:
_LOGGER.warning(
"CalDAV server does not report supported components for calendar %s. "
"CalDAV server does not report supported"
" components for calendar %s. "
"Not assuming support for requested component '%s'",
name or url,
comp,
+8 -2
View File
@@ -38,6 +38,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import CalDavConfigEntry
from .api import async_get_calendars
from .const import TIMEOUT
from .coordinator import CalDavUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -91,7 +92,12 @@ async def async_setup_platform(
days = config[CONF_DAYS]
client = caldav.DAVClient(
url, None, username, password, ssl_verify_cert=config[CONF_VERIFY_SSL]
url,
None,
username,
password,
ssl_verify_cert=config[CONF_VERIFY_SSL],
timeout=TIMEOUT,
)
calendars = await async_get_calendars(hass, client, SUPPORTED_COMPONENT)
@@ -231,7 +237,7 @@ class WebDavCalendarEntity(CoordinatorEntity[CalDavUpdateCoordinator], CalendarE
await self.hass.async_add_executor_job(
partial(self.coordinator.calendar.add_event, **item_data),
)
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
@callback
@@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_URL, CONF_USERNAME, CONF_VERIFY_SSL
from homeassistant.helpers import config_validation as cv
from .const import DOMAIN
from .const import DOMAIN, TIMEOUT
_LOGGER = logging.getLogger(__name__)
@@ -65,6 +65,7 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
ssl_verify_cert=user_input[CONF_VERIFY_SSL],
timeout=TIMEOUT,
)
try:
await self.hass.async_add_executor_job(client.principal)
@@ -75,6 +76,9 @@ class CalDavConfigFlow(ConfigFlow, domain=DOMAIN):
# AuthorizationError can be raised if the url is incorrect or
# on some other unexpected server response.
return "cannot_connect"
except requests.Timeout as err:
_LOGGER.warning("Timeout connecting to CalDAV server: %s", err)
return "cannot_connect"
except requests.ConnectionError as err:
_LOGGER.warning("Connection Error connecting to CalDAV server: %s", err)
return "cannot_connect"
+1
View File
@@ -3,3 +3,4 @@
from typing import Final
DOMAIN: Final = "caldav"
TIMEOUT: Final = 30
+5 -5
View File
@@ -138,7 +138,7 @@ class WebDavTodoListEntity(TodoListEntity):
)
# refreshing async otherwise it would take too much time
self.hass.async_create_task(self.async_update_ha_state(force_refresh=True))
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
async def async_update_todo_item(self, item: TodoItem) -> None:
@@ -150,7 +150,7 @@ class WebDavTodoListEntity(TodoListEntity):
)
except NotFoundError as err:
raise HomeAssistantError(f"Could not find To-do item {uid}") from err
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV lookup error: {err}") from err
vtodo = todo.icalendar_component # type: ignore[attr-defined]
vtodo["SUMMARY"] = item.summary or ""
@@ -174,7 +174,7 @@ class WebDavTodoListEntity(TodoListEntity):
)
# refreshing async otherwise it would take too much time
self.hass.async_create_task(self.async_update_ha_state(force_refresh=True))
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV save error: {err}") from err
async def async_delete_todo_items(self, uids: list[str]) -> None:
@@ -188,14 +188,14 @@ class WebDavTodoListEntity(TodoListEntity):
items = await asyncio.gather(*tasks)
except NotFoundError as err:
raise HomeAssistantError("Could not find To-do item") from err
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV lookup error: {err}") from err
# Run serially as some CalDAV servers do not support concurrent modifications
for item in items:
try:
await self.hass.async_add_executor_job(item.delete)
except (requests.ConnectionError, DAVError) as err:
except (requests.ConnectionError, requests.Timeout, DAVError) as err:
raise HomeAssistantError(f"CalDAV delete error: {err}") from err
# refreshing async otherwise it would take too much time
self.hass.async_create_task(self.async_update_ha_state(force_refresh=True))
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
DOMAIN = "calendar"
DATA_COMPONENT: HassKey[EntityComponent[CalendarEntity]] = HassKey(DOMAIN)
# pylint: disable-next=home-assistant-duplicate-const
CONF_EVENT = "event"
@@ -31,7 +31,9 @@ async def async_setup_entry(
) -> bool:
"""Set up Cambridge Audio integration from a config entry."""
client = StreamMagicClient(entry.data[CONF_HOST], async_get_clientsession(hass))
client = StreamMagicClient(
entry.data[CONF_HOST], async_get_clientsession(hass), should_close_session=False
)
async def _connection_update_callback(
_client: StreamMagicClient, _callback_type: CallbackType
@@ -7,6 +7,7 @@ from homeassistant.const import CONF_ADDRESS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN
from .coordinator import CasperGlowConfigEntry, CasperGlowCoordinator
PLATFORMS: list[Platform] = [
@@ -24,7 +25,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: CasperGlowConfigEntry) -
ble_device = bluetooth.async_ble_device_from_address(hass, address.upper(), True)
if not ble_device:
raise ConfigEntryNotReady(
f"Could not find Casper Glow device with address {address}"
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={"address": address},
)
glow = CasperGlow(ble_device)
@@ -54,6 +54,9 @@
"exceptions": {
"communication_error": {
"message": "An error occurred while communicating with the Casper Glow: {error}"
},
"device_not_found": {
"message": "Could not find Casper Glow device with address {address}"
}
}
}
+40 -73
View File
@@ -8,7 +8,7 @@ from homeassistant.components import onboarding
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.const import CONF_UUID
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.data_entry_flow import SectionConfig, section
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
@@ -17,7 +17,7 @@ from .const import CONF_IGNORE_CEC, CONF_KNOWN_HOSTS, DOMAIN
if TYPE_CHECKING:
from . import CastConfigEntry
IGNORE_CEC_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string]))
CONF_MORE_OPTIONS = "more_options"
KNOWN_HOSTS_SCHEMA = vol.Schema(
{
vol.Optional(
@@ -27,7 +27,19 @@ KNOWN_HOSTS_SCHEMA = vol.Schema(
)
}
)
WANTED_UUID_SCHEMA = vol.Schema(vol.All(cv.ensure_list, [cv.string]))
OPTIONS_SCHEMA = KNOWN_HOSTS_SCHEMA.extend(
{
vol.Required(CONF_MORE_OPTIONS): section(
vol.Schema(
{
vol.Optional(CONF_UUID): str,
vol.Optional(CONF_IGNORE_CEC): str,
}
),
SectionConfig(collapsed=True),
)
}
)
class FlowHandler(ConfigFlow, domain=DOMAIN):
@@ -92,100 +104,55 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
class CastOptionsFlowHandler(OptionsFlow):
"""Handle Google Cast options."""
def __init__(self) -> None:
"""Initialize Google Cast options flow."""
self.updated_config: dict[str, Any] = {}
async def async_step_init(self, user_input: None = None) -> ConfigFlowResult:
"""Manage the Google Cast options."""
return await self.async_step_basic_options()
async def async_step_basic_options(
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the Google Cast options."""
errors: dict[str, str] = {}
if user_input is not None:
ignore_cec = _string_to_list(
user_input[CONF_MORE_OPTIONS].get(CONF_IGNORE_CEC, "")
)
known_hosts = _trim_items(user_input.get(CONF_KNOWN_HOSTS, []))
self.updated_config = dict(self.config_entry.data)
self.updated_config[CONF_KNOWN_HOSTS] = known_hosts
if self.show_advanced_options:
return await self.async_step_advanced_options()
wanted_uuid = _string_to_list(
user_input[CONF_MORE_OPTIONS].get(CONF_UUID, "")
)
updated_config = dict(self.config_entry.data)
updated_config[CONF_IGNORE_CEC] = ignore_cec
updated_config[CONF_KNOWN_HOSTS] = known_hosts
updated_config[CONF_UUID] = wanted_uuid
self.hass.config_entries.async_update_entry(
self.config_entry, data=self.updated_config
self.config_entry, data=updated_config
)
return self.async_create_entry(title="", data={})
return self.async_show_form(
step_id="basic_options",
data_schema=self.add_suggested_values_to_schema(
KNOWN_HOSTS_SCHEMA, self.config_entry.data
),
errors=errors,
last_step=not self.show_advanced_options,
)
async def async_step_advanced_options(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the Google Cast options."""
errors: dict[str, str] = {}
if user_input is not None:
bad_cec, ignore_cec = _string_to_list(
user_input.get(CONF_IGNORE_CEC, ""), IGNORE_CEC_SCHEMA
)
bad_uuid, wanted_uuid = _string_to_list(
user_input.get(CONF_UUID, ""), WANTED_UUID_SCHEMA
suggested: dict[str, Any] = {CONF_MORE_OPTIONS: {}}
if CONF_KNOWN_HOSTS in self.config_entry.data:
suggested[CONF_KNOWN_HOSTS] = self.config_entry.data[CONF_KNOWN_HOSTS]
for key in (CONF_UUID, CONF_IGNORE_CEC):
if key not in self.config_entry.data:
continue
suggested[CONF_MORE_OPTIONS][key] = _list_to_string(
self.config_entry.data[key]
)
if not bad_cec and not bad_uuid:
self.updated_config[CONF_IGNORE_CEC] = ignore_cec
self.updated_config[CONF_UUID] = wanted_uuid
self.hass.config_entries.async_update_entry(
self.config_entry, data=self.updated_config
)
return self.async_create_entry(title="", data={})
fields: dict[vol.Marker, type[str]] = {}
current_config = self.config_entry.data
suggested_value = _list_to_string(current_config.get(CONF_UUID))
_add_with_suggestion(fields, CONF_UUID, suggested_value)
suggested_value = _list_to_string(current_config.get(CONF_IGNORE_CEC))
_add_with_suggestion(fields, CONF_IGNORE_CEC, suggested_value)
return self.async_show_form(
step_id="advanced_options",
data_schema=vol.Schema(fields),
errors=errors,
step_id="init",
data_schema=self.add_suggested_values_to_schema(OPTIONS_SCHEMA, suggested),
last_step=True,
)
def _list_to_string(items):
def _list_to_string(items: list[str]) -> str:
comma_separated_string = ""
if items:
comma_separated_string = ",".join(items)
return comma_separated_string
def _string_to_list(string, schema):
invalid = False
items = [x.strip() for x in string.split(",") if x.strip()]
try:
items = schema(items)
except vol.Invalid:
invalid = True
return invalid, items
def _string_to_list(string: str) -> list[str]:
return [x.strip() for x in string.split(",") if x.strip()]
def _trim_items(items: list[str]) -> list[str]:
return [x.strip() for x in items if x.strip()]
def _add_with_suggestion(
fields: dict[vol.Marker, type[str]], key: str, suggested_value: str
) -> None:
fields[vol.Optional(key, description={"suggested_value": suggested_value})] = str
@@ -718,9 +718,12 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
await self.hass.async_add_executor_job(
self._quick_play, app_name, app_data
)
# pylint: disable-next=home-assistant-action-swallowed-exception
except NotImplementedError:
_LOGGER.error("App %s not supported", app_name)
except NotImplementedError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="app_not_supported",
translation_placeholders={"app_name": app_name},
) from err
return
# Try the cast platforms
@@ -769,6 +772,8 @@ class CastMediaPlayerEntity(CastDevice, MediaPlayerEntity):
media_id,
err,
)
# Fallback: if playlist parsing fails, forward the raw URL to the device
# pylint: disable-next=home-assistant-action-swallowed-exception
except PlaylistError as err:
_LOGGER.warning(
"[%s %s] Failed to parse playlist %s: %s",
+19 -12
View File
@@ -18,26 +18,33 @@
}
}
},
"exceptions": {
"app_not_supported": {
"message": "App {app_name} is not supported"
}
},
"options": {
"error": {
"invalid_known_hosts": "[%key:component::cast::config::error::invalid_known_hosts%]"
},
"step": {
"advanced_options": {
"data": {
"ignore_cec": "Ignore CEC",
"uuid": "Allowed UUIDs"
},
"description": "Allowed UUIDs - A comma-separated list of UUIDs of Cast devices to add to Home Assistant. Use only if you dont want to add all available cast devices.\nIgnore CEC - A comma-separated list of Chromecasts that should ignore CEC data for determining the active input. This will be passed to pychromecast.IGNORE_CEC.",
"title": "Advanced Google Cast configuration"
},
"basic_options": {
"init": {
"data": {
"known_hosts": "[%key:component::cast::config::step::config::data::known_hosts%]"
},
"data_description": {
"known_hosts": "[%key:component::cast::config::step::config::data_description::known_hosts%]"
},
"sections": {
"more_options": {
"data": {
"ignore_cec": "Ignore CEC",
"uuid": "Allowed UUIDs"
},
"data_description": {
"ignore_cec": "A comma-separated list of Chromecasts that should ignore CEC data for determining the active input. This will be passed to pychromecast.IGNORE_CEC.",
"uuid": "A comma-separated list of UUIDs of Cast devices to add to Home Assistant. Use only if you dont want to add all available cast devices."
},
"name": "More options"
}
},
"title": "[%key:component::cast::config::step::config::title%]"
}
}
@@ -95,3 +95,42 @@ class CertexpiryConfigFlow(ConfigFlow, domain=DOMAIN):
),
errors=self._errors,
)
async def async_step_reconfigure(
self,
user_input: Mapping[str, Any] | None = None,
) -> ConfigFlowResult:
"""Handle reconfiguration of an existing entry."""
self._errors = {}
reconfigure_entry = self._get_reconfigure_entry()
if user_input is not None:
host = user_input[CONF_HOST]
port = user_input.get(CONF_PORT, DEFAULT_PORT)
if (
host != reconfigure_entry.data[CONF_HOST]
or port != reconfigure_entry.data[CONF_PORT]
):
self._async_abort_entries_match({CONF_HOST: host, CONF_PORT: port})
if await self._test_connection(user_input):
return self.async_update_reload_and_abort(
reconfigure_entry,
data_updates={CONF_HOST: host, CONF_PORT: port},
unique_id=f"{host}:{port}",
)
return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(
vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_PORT, default=DEFAULT_PORT): int,
}
),
user_input or reconfigure_entry.data,
),
errors=self._errors,
)
@@ -2,7 +2,8 @@
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"import_failed": "Import from config failed"
"import_failed": "Import from config failed",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"connection_refused": "Connection refused when connecting to host",
@@ -11,6 +12,13 @@
"resolve_failed": "This host cannot be resolved"
},
"step": {
"reconfigure": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]"
},
"title": "Reconfigure the certificate to test"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
+2 -2
View File
@@ -17,6 +17,8 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
APPLICATION_NAME,
ATTR_LATITUDE,
ATTR_LONGITUDE,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
@@ -45,8 +47,6 @@ HA_USER_AGENT = (
)
ATTR_UID = "uid"
ATTR_LATITUDE = "latitude"
ATTR_LONGITUDE = "longitude"
ATTR_EMPTY_SLOTS = "empty_slots"
ATTR_FREE_EBIKES = "free_ebikes"
ATTR_TIMESTAMP = "timestamp"
@@ -29,6 +29,7 @@ BASE_API_URL = "https://rest.clicksend.com/v3"
HEADERS = {"Content-Type": CONTENT_TYPE_JSON}
# pylint: disable-next=home-assistant-duplicate-const
CONF_LANGUAGE = "language"
CONF_VOICE = "voice"
+22 -22
View File
@@ -32,28 +32,28 @@ set_temperature:
max: 250
step: 0.1
mode: box
target_temp_high:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
advanced: true
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
target_temp_low:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
advanced: true
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
temperature_range:
fields:
target_temp_high:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
target_temp_low:
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
selector:
number:
min: 0
max: 250
step: 0.1
mode: box
hvac_mode:
selector:
state:
@@ -373,7 +373,12 @@
"name": "Target temperature"
}
},
"name": "Set thermostat target temperature"
"name": "Set thermostat target temperature",
"sections": {
"temperature_range": {
"name": "Temperature range"
}
}
},
"toggle": {
"description": "Toggles a thermostat on/off.",
+5 -2
View File
@@ -220,8 +220,11 @@ class CloudClient(Interface):
)
if is_cloud_ice_servers_enabled:
if self._cloud_ice_servers_listener is None:
self._cloud_ice_servers_listener = await self.cloud.ice_servers.async_register_ice_servers_listener(
register_cloud_ice_server
ice_servers = self.cloud.ice_servers
self._cloud_ice_servers_listener = (
await ice_servers.async_register_ice_servers_listener(
register_cloud_ice_server
)
)
elif self._cloud_ice_servers_listener:
self._cloud_ice_servers_listener()
@@ -11,6 +11,7 @@ CONF_ACCESS_KEY_ID = "access_key_id"
CONF_SECRET_ACCESS_KEY = "secret_access_key"
CONF_ENDPOINT_URL = "endpoint_url"
CONF_BUCKET = "bucket"
# pylint: disable-next=home-assistant-duplicate-const
CONF_PREFIX = "prefix"
# R2 is S3-compatible. Endpoint should be like:
@@ -94,7 +94,7 @@ rules:
entity-translations:
status: exempt
comment: This integration does not have entities.
exception-translations: done
exception-translations: todo
icon-translations:
status: exempt
comment: This integration does not use icons.
@@ -6,4 +6,5 @@ ATTR_URL = "color_extract_url"
DOMAIN = "color_extractor"
DEFAULT_NAME = "Color extractor"
# pylint: disable-next=home-assistant-duplicate-const
SERVICE_TURN_ON = "turn_on"
@@ -65,6 +65,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
translation_placeholders={"error": repr(err)},
) from err
except aiocomelit_exceptions.CannotAuthenticate as err:
# pylint: disable-next=home-assistant-exception-placeholder-mismatch
raise InvalidAuth(
translation_domain=DOMAIN,
translation_key="cannot_authenticate",
+6 -7
View File
@@ -94,13 +94,12 @@ class ComfoConnectFan(FanEntity):
self._handle_mode_update,
)
)
await self.hass.async_add_executor_job(
self._ccb.comfoconnect.register_sensor, SENSOR_FAN_SPEED_MODE
)
# pylint: disable-next=home-assistant-sequential-executor-jobs
await self.hass.async_add_executor_job(
self._ccb.comfoconnect.register_sensor, SENSOR_OPERATING_MODE_BIS
)
def _register_sensors() -> None:
self._ccb.comfoconnect.register_sensor(SENSOR_FAN_SPEED_MODE)
self._ccb.comfoconnect.register_sensor(SENSOR_OPERATING_MODE_BIS)
await self.hass.async_add_executor_job(_register_sensors)
def _handle_speed_update(self, value: float) -> None:
"""Handle update callbacks."""
@@ -10,10 +10,11 @@ from homeassistant.components.notify import (
)
from homeassistant.const import CONF_COMMAND
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util.process import kill_subprocess
from .const import CONF_COMMAND_TIMEOUT, LOGGER
from .const import CONF_COMMAND_TIMEOUT, DOMAIN, LOGGER
from .utils import create_platform_yaml_not_supported_issue, render_template_args
_LOGGER = logging.getLogger(__name__)
@@ -66,9 +67,18 @@ class CommandLineNotificationService(BaseNotificationService):
proc.returncode,
command,
)
# pylint: disable-next=home-assistant-action-swallowed-exception
except subprocess.TimeoutExpired:
_LOGGER.error("Timeout for command: %s", command)
except subprocess.TimeoutExpired as err:
_LOGGER.debug("Timeout for command: %s", command)
kill_subprocess(proc)
except subprocess.SubprocessError:
_LOGGER.error("Error trying to exec command: %s", command)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="timeout_error",
translation_placeholders={"command": command},
) from err
except subprocess.SubprocessError as err:
_LOGGER.debug("Error trying to exec command: %s", command)
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="command_error",
translation_placeholders={"command": command, "error": str(err)},
) from err
@@ -1,8 +1,10 @@
{
"issues": {
"platform_yaml_not_supported": {
"description": "Platform YAML setup is not supported.\nChange from configuring it using the `{platform}:` key to using the `command_line:` key directly in configuration.yaml and restart Home Assistant to resolve the issue.\nTo see the detailed documentation, select Learn more.",
"title": "Platform YAML is not supported in Command Line"
"exceptions": {
"command_error": {
"message": "Error trying to execute command: {command}. Error: {error}"
},
"timeout_error": {
"message": "Timeout trying to execute command: {command}"
}
},
"services": {

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