Compare commits

...

620 Commits

Author SHA1 Message Date
Franck Nijhof b10582b0a9 2026.5.0 (#169484) 2026-05-06 17:22:09 +02:00
Franck Nijhof b193d951d7 Bump version to 2026.5.0 2026-05-06 15:01:09 +00:00
Franck Nijhof 4cd0d9dcec Bump version to 2026.5.0b4 2026-05-06 13:27:18 +00:00
Daniel Hjelseth Høyer 32f65b2e11 Bump pyTibber to 0.37.4 (#169907) 2026-05-06 13:27:09 +00:00
Erik Montnemery 8c79d1e44b Remove _get_tracked_value method from EntityConditionBase (#169906) 2026-05-06 13:27:07 +00:00
Erik Montnemery 8d53f7a520 Exclude incompatible humidifier entities from humidifier automations (#169905) 2026-05-06 13:27:05 +00:00
Erik Montnemery cc83ee88fb Exclude incompatible water_heater entities from water_heater automations (#169904) 2026-05-06 13:27:03 +00:00
Erik Montnemery 0c5b02eff3 Exclude incompatible climate entities from climate automations (#169903) 2026-05-06 13:27:02 +00:00
Erik Montnemery 9da9f8fd50 Unload scripts and conditions created by template entities (#169366) 2026-05-06 13:27:00 +00:00
Franck Nijhof d70ffcd3e9 Bump version to 2026.5.0b3 2026-05-06 11:16:10 +00:00
Erik Montnemery 3e26d0dfe3 Exclude incompatible entities from temperature automations (#169901) 2026-05-06 11:15:56 +00:00
Erik Montnemery eab9747b32 Exclude incompatible entities from humidity automations (#169898) 2026-05-06 11:15:54 +00:00
Erik Montnemery 9e955d8294 Add media_player volume condition (#169897) 2026-05-06 11:15:52 +00:00
Bram Kragten f08cd01ff8 Update frontend to 20260429.3 (#169893) 2026-05-06 11:10:49 +00:00
Erik Montnemery eabaf3b0fe Add media_player muted conditions (#169892) 2026-05-06 11:10:47 +00:00
Tom Matheussen 65ca790d15 Bump satel_integra to 1.3.1 (#169889) 2026-05-06 11:10:45 +00:00
Joost Lekkerkerker d177944f7a Fix Zinvolt select options (#169886) 2026-05-06 11:10:43 +00:00
Erik Montnemery 7f186f4430 Add media_player volume triggers (#169885) 2026-05-06 11:10:41 +00:00
Erik Montnemery 4f4f4642a7 Add method _should_include to EntityConditionBase (#169884)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-06 11:10:39 +00:00
Erik Montnemery 12e443cd31 Improve entity trigger tests (#169881) 2026-05-06 11:10:37 +00:00
Erik Montnemery 22a7daabe7 Add method _should_include to EntityTriggerBase (#169837) 2026-05-06 11:10:35 +00:00
Erik Montnemery c139e99abd Improve condition test helper docstrings (#169871) 2026-05-06 11:09:06 +00:00
Erik Montnemery 2bfdb96a3f Improve trigger test helper docstrings (#169869) 2026-05-06 11:09:04 +00:00
puddly 4b24ca924b Bump serialx to 1.7.0 (#169867) 2026-05-06 11:09:02 +00:00
Michael Hansen 1d3d714e4f Bump intents to 2026.5.5 (#169855) 2026-05-06 11:09:00 +00:00
Erik Montnemery ffae6eda8a Validate yaml matches implementation in automation options_supported tests (#169798) 2026-05-06 11:05:41 +00:00
Erik Montnemery 4dd996b728 Add trigger media_player.unmuted (#169797) 2026-05-06 11:05:40 +00:00
Erik Montnemery afad1e8dac Improve mobile_app device tracker tests (#169724) 2026-05-06 11:05:38 +00:00
Manu 8e41933251 Record notification from legacy notify action in Mobile App (#169749) 2026-05-06 11:00:21 +00:00
Erik Montnemery c581eaad53 Add trigger timer.time_remaining (#169763) 2026-05-06 10:58:59 +00:00
Ludovic BOUÉ 3050e79d06 Expose SET_SPEED for all fans via PercentSetting in Matter (#169696)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-05-06 08:55:15 +00:00
Andres Ruiz 0e8ecd1065 Catch additional errors as potentially retryable errors during energy data updates (#169646) 2026-05-06 08:55:13 +00:00
Paulus Schoutsen 94732139f4 Bump version to 2026.5.0b2 2026-05-05 10:29:37 -04:00
Denis Shulyaka c5e08b2409 Return the requested format for OpenAI TTS (#169839)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-05 10:29:30 -04:00
Joost Lekkerkerker c12e1b5f4a Add Zunzunbee Zigbee brand (#169838) 2026-05-05 10:29:29 -04:00
Joost Lekkerkerker 6cfedb55e6 Add Sensereo matter brand (#169836) 2026-05-05 10:29:27 -04:00
Åke Strandberg af4cb9530b Add missing code for miele washing machine (#169795) 2026-05-05 10:29:26 -04:00
Matthias Alphart 58e97e7d5f Update xknxproject to 3.9.0 (#169775) 2026-05-05 10:29:25 -04:00
Daniel Hjelseth Høyer 2945b51617 Bump pyTibber to 0.37.3 (#169762) 2026-05-05 10:29:24 -04:00
Keilin Bickar 9d0e2df627 bump sense-energy to 0.14.1 (#169761) 2026-05-05 10:29:23 -04:00
Steve Syrell 643ae080db Bump Insteon-panel to 0.6.2 (#169757) 2026-05-05 10:29:22 -04:00
G Johansson a7eaa51179 Fix config flow validation in Nord Pool (#169751) 2026-05-05 10:29:21 -04:00
Petro31 e15852ff38 Fix uptime template sensor (#169743) 2026-05-05 10:29:20 -04:00
Diogo Gomes f6dec34136 Bump pytrydan to 1.0.0 (#169742) 2026-05-05 10:29:19 -04:00
Raj Laud 53905fbc49 Bump victron-ble-ha-parser to 0.7.0 (#169736)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-05 10:29:17 -04:00
Thomas D 8218ff0fe8 Add missing initialization charging power status option to Volvo (#169727) 2026-05-05 10:29:16 -04:00
kernelpanic85 663f7e3e6b Add Celsius and Fahrenheit to Smartthings UNITS mapping (#169686) 2026-05-05 10:29:15 -04:00
Nathan Spencer 4dfa2b8b88 Limit power status binary sensor to non-LR5 devices (#169659) 2026-05-05 10:29:14 -04:00
Nathan Spencer f828b165b1 Bump pylitterbot to 2025.4.0 (#169652) 2026-05-05 10:29:13 -04:00
shbatm c56c506648 Add precipitation device class to WeatherFlow Cloud accumulation sensors (#169638)
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 10:29:12 -04:00
Artur Pragacz 8e5bf2a35f Fix async_unload teardown race in scripts (#169562) 2026-05-05 10:29:10 -04:00
Erik Montnemery 4d575e69a4 Improve template reload (#169480) 2026-05-05 10:29:09 -04:00
Christian Lackas 4f78bbccc0 Use all_devices in ViCare diagnostics for completeness (#169429) 2026-05-05 10:29:08 -04:00
Erik Montnemery 2d66ebe54a Add trigger media_player.muted (#156736) 2026-05-05 10:29:07 -04:00
Paulus Schoutsen a3e1209778 Bump version to 2026.5.0b1 2026-05-04 12:44:42 -04:00
Paul Bottein 7c44a0b88d Update frontend to 20260429.2 (#169748) 2026-05-04 12:44:23 -04:00
Manu 126058e0fa Bump bring-api to 1.1.2 (#169729) 2026-05-04 12:44:22 -04:00
Thomas D 28742822cb Ignore location FORBIDDEN response for the Volvo integration (#169713) 2026-05-04 12:44:21 -04:00
karwosts 179d370c2a Use uptime device_class for Uptime sensor (#169692) 2026-05-04 12:44:20 -04:00
Allen Porter 2d8f3691cf Update Nest doorbell event to use standard DoorbellEventType.RING (#169691) 2026-05-04 12:44:19 -04:00
Tom ce4fc9e880 Improve ProxmoxVE config flow preparing bug fixing (#169682)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-05-04 12:44:18 -04:00
Ronald van der Meer 9e357e7e5a Bump python-duco-client to 0.3.10 (#169677) 2026-05-04 12:44:17 -04:00
OMEGA_RAZER ed35b23e62 Updated prowlpy to 1.1.5 (#169671) 2026-05-04 12:44:17 -04:00
Tom Matheussen 191d2d1f12 Bump satel_integra to 1.3.0 (#169668) 2026-05-04 12:44:16 -04:00
SeifEddineMezned b165d8251f Fix grammar in mqtt/strings.json: "Minimal one" → "At least one" (#169666) 2026-05-04 12:44:15 -04:00
Midori Kochiya 5e8886aeb7 Fix M1S-T500 update error (#169651) 2026-05-04 12:44:14 -04:00
Michael bdb66635f8 Pass None config entry to schluter coordinator (#169621) 2026-05-04 12:44:13 -04:00
Michael 5ba6e348da Fix detection of CPU temperature sensor support on olde FRITZ!Box models (#169620) 2026-05-04 12:44:12 -04:00
Petro31 ed52b0ce80 Change vacuum template config names for clean area (#169599)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
2026-05-04 12:44:11 -04:00
Jan-Philipp Benecke 33ee3d6967 Decrease WebDAV client timeout (#169591) 2026-05-04 12:44:10 -04:00
tronikos f36676c32c Bump opower to 0.18.2 (#169588) 2026-05-04 12:44:09 -04:00
Ronald van der Meer 77beddb1e7 Fix Duco unknown node type not re-evaluated after becoming known (#169579) 2026-05-04 12:42:31 -04:00
SeifEddineMezned 1677e410b3 Fix possessive apostrophe errors in mqtt/strings.json (#169576)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2026-05-04 12:38:37 -04:00
SeifEddineMezned 1be09347cd Fix grammar and clarity in samsungtv/strings.json (#169574) 2026-05-04 12:38:36 -04:00
Simone Chemelli c30ac2c0f3 Bump pyuptimerobot to 25.0.0 (#169572) 2026-05-04 12:37:45 -04:00
Shay Levy 145c7435a5 Bump aioshelly to 13.25.0 (#169569) 2026-05-04 12:36:21 -04:00
Paul Bottein 60f3b3bcc0 Update frontend to 20260429.1 (#169565) 2026-05-04 12:36:20 -04:00
Dan Raper 03e6d3bd30 Bump ohme to 1.9.0 (#169556) 2026-05-04 12:36:19 -04:00
Abílio Costa ee4d150e13 Use the correct schema for triggers/conditions "for" option (#169539) 2026-05-04 12:35:29 -04:00
bkobus-bbx 148603a10e Bump blebox_uniapi to 2.5.2 (#169534) 2026-05-04 12:33:13 -04:00
Erik Montnemery 1dbd933d3c Enable duration support in all entity conditions (#169532)
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-04 12:32:30 -04:00
Matthias Alphart f7ee7423fe Update knx-frontend to 2026.4.30.60856 (#169529) 2026-05-04 12:26:31 -04:00
Tomer 6322f1e37a Victron GX: Bug fix: parent device is mapped to the wrong device (#169525)
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 12:26:30 -04:00
Manu 0d8c7fbb9d Fix: Migrate also device entries to subentry in GitHub integration (#169523) 2026-05-04 12:26:29 -04:00
Boris Bolshem 70e30b02a4 Fix KeyError in telegram_bot media group download debug log (#169519) 2026-05-04 12:26:28 -04:00
Simone Chemelli ebd21ea9b2 Fix uptime sensor for Synology DSM (#169512) 2026-05-04 12:26:27 -04:00
Erik Montnemery 9aa092cd34 Correct wake_on_lan entity behavior when entity_id changes (#169486)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-04 12:26:26 -04:00
TheJulianJES b274fe85b7 Re-interview ZHA device on websocket reconfigure (#169483) 2026-05-04 12:26:25 -04:00
Erik Montnemery 777c36998c Remove scripts from DATA_SCRIPTS on unload (#169415) 2026-05-04 12:26:24 -04:00
Kurt Chrisford a3977428f9 Implement current setpoint method in actron air integration (#169358) 2026-05-04 12:26:23 -04:00
Simone Chemelli 2d626c263c Storage problem management for Comelit Serial Bridge (#169297) 2026-05-04 12:26:22 -04:00
Jeef d1461f2e68 Bump weatherflow4py to 1.5.4 (#168994) 2026-05-04 12:26:21 -04:00
bkobus-bbx 3b778d2cc7 fix: incorrect position inversion for blebox gateBox cover (#168893) 2026-05-04 12:26:20 -04:00
Yuval Weiss 67b7d17a2f Add Broadlink infrared emitter support (#168889) 2026-05-04 12:26:18 -04:00
Tomer 1afeadc342 Victron GX: bug fix for missing translation key (#168461)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-05-04 12:26:17 -04:00
jftkcs f6aa4e2092 Fix reasoning summary handling for OpenAI o-models (#168093)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Denis Shulyaka <Shulyaka@gmail.com>
2026-05-04 12:26:16 -04:00
Khole 3b00c5bb96 Check device registration before completing Hive reauth flow (#168035)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-05-04 12:26:15 -04:00
Franck Nijhof ef7eed579b Bump version to 2026.5.0b0 2026-04-29 16:40:46 +00:00
Franck Nijhof 568a0085fe Bump version to 2026.5.0 2026-04-29 15:50:10 +00:00
Joakim Plate f5363db26f Move finish watering to sensor (#169476)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 17:34:38 +02:00
Erik Montnemery 3be1aa5441 Include errors in script trace when continue_on_error is set (#168676) 2026-04-29 17:30:47 +02:00
Paul Bottein 7dbffb7375 Update frontend to 20260429.0 (#169475) 2026-04-29 17:21:40 +02:00
A. Gideonse 07c4025d47 Add indevolt binary sensor platform (#169375)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-29 16:19:17 +01:00
Franck Nijhof 3e3e425aa5 Bump Fumis integration to platinum quality scale (#169443) 2026-04-29 17:14:50 +02:00
Erik Montnemery 162a4fc385 Use automation behavior selector in triggers and conditions (#169438) 2026-04-29 17:10:50 +02:00
Manu ef6fd92079 Add notify entities to Mobile app integration (#168510)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-29 17:06:13 +02:00
Erik Montnemery 4ad71a070a Improve timer icons (#169474) 2026-04-29 17:02:22 +02:00
Erik Montnemery f33ad12f5e Correct entity_id change for automations (#169470) 2026-04-29 16:30:02 +02:00
Erik Montnemery da7fbb0dd6 Correct entity_id change for scripts (#169472) 2026-04-29 16:29:25 +02:00
Abílio Costa 81137345a3 Extract triggers/conditions/services for non-primary entities (#169441) 2026-04-29 15:28:09 +01:00
Erik Montnemery d3e77d4195 Add timer triggers (#169450) 2026-04-29 16:27:52 +02:00
renovate[bot] ce977e90a5 Update cryptography to 47.0.0 (#169465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 16:22:16 +02:00
Martin Hjelmare 2871b87344 Revert "Include indirect automation references in device view (#167719)" (#169471) 2026-04-29 15:15:50 +01:00
renovate[bot] d82ce1e22d Update ruff (#169473)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 16:11:22 +02:00
Guido Schmitz b8bb2e0090 Use uptime sensor class in devolo Home Network (#169469) 2026-04-29 16:10:39 +02:00
Erik Montnemery 1b81cfe3ca Make it always optional to specify trigger and condition options (#169467) 2026-04-29 15:06:05 +01:00
renovate[bot] 0a3f0d90c3 Update url-normalize to 3.0.0 (#169466)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 15:48:05 +02:00
renovate[bot] 84d566a02c Update pyOpenSSL to 26.1.0 (#169464)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 15:47:52 +02:00
renovate[bot] 0e0d54e4b6 Update uv to 0.11.8 (#169463)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 15:47:39 +02:00
epenet 5b05061def Fix plex sensor test broken by Python 3.14.3 asyncio changes (#169448) 2026-04-29 15:22:34 +02:00
renovate[bot] e0bf76769a Update ruff (#169461)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 15:17:04 +02:00
renovate[bot] 63868bc169 Migrate Renovate config (#169462)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-29 15:16:26 +02:00
Bram Kragten b8b7169371 Add automation behavior selector (#166484)
Co-authored-by: Erik <erik@montnemery.com>
2026-04-29 15:10:47 +02:00
Maciej Bieniek 1cc778954f Use new UPTIME sensor class in Brother (#169457) 2026-04-29 14:54:55 +02:00
Maciej Bieniek 3ba3ecdef3 Use new UPTIME sensor class in NAM (#169458) 2026-04-29 14:54:14 +02:00
Ronald van der Meer 5c57fc6e14 Fix Duco HTTPS polling performance by lowering SCAN_INTERVAL to 10 seconds (#169453) 2026-04-29 14:25:04 +02:00
epenet 2da440043a Fix Sonos group regroup race when entity is not yet registered (#169445) 2026-04-29 14:13:59 +02:00
epenet 4f34725e53 Fix flaky portainer test_device_registry (#169456)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:11:07 +02:00
epenet d03bec2f44 Fix race in devolo Home Network device tracker device lookup (#169454)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:08:00 +02:00
epenet 57c37fc10c Fix race in Ping device tracker device lookup (#169432)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:02:37 +02:00
Simone Chemelli fd98594143 Use defaults for device class UPTIME in Fritz (#169149) 2026-04-29 12:34:25 +01:00
Robert Svensson 894547abed Add Axis doorbell event platform (#169422) 2026-04-29 12:29:58 +01:00
Luis Miranda b48060674c Add OMIE integration (#150399)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: abmantis <amfcalt@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-29 12:10:22 +01:00
vturekhanov 6f2aa7852a Fix availability state for bridged Matter devices (#165078)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-04-29 13:05:15 +02:00
Abílio Costa 9d53645468 Remove LLM test instruction (#169442) 2026-04-29 12:58:47 +02:00
Simone Chemelli a3f1c067f7 Fix host connections for Fritz (#169434) 2026-04-29 12:57:05 +02:00
Tomer cef97973d0 Victron GX device_tracker optional attributes (#168646)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 12:56:13 +02:00
TheJulianJES 7bb297a3fc Bump ZHA to 1.3.0 (#169433) 2026-04-29 12:51:18 +02:00
Maciej Bieniek 7e2b8e1a48 Bump aioshelly to 13.24.2 (#169440) 2026-04-29 12:50:44 +02:00
Franck Nijhof 013c5e7f7c Add diagnostics to Fumis integration (#169437) 2026-04-29 12:38:09 +02:00
Abílio Costa 7cb1d5b8ab Allow targeting non-primary entities in conditions (#169291) 2026-04-29 12:25:26 +02:00
Paulus Schoutsen 57d9e8ea6f Filter history API responses by per-entity read permissions (#169236)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:16:49 +02:00
Andrew Ng 32743fcf8d Fix Acaia battery sensor going unavailable on first-session disconnect (#169420)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-04-29 12:12:44 +02:00
Simone Chemelli f4637db26d Add routine management to Alexa Devices (#166291) 2026-04-29 11:45:03 +02:00
Erik Montnemery b4bfe6b80b Rename timer last_action to last_transition (#169430) 2026-04-29 11:35:36 +02:00
Andrej Walilko 278f25ec6e Redact sensitive api creds before logging message in websocket api (#169326)
Co-authored-by: Andrej Walilko <awalilko@liquidweb.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-04-29 11:15:05 +02:00
Robert Resch 39d3bc3e53 Bump deebot-client to 18.2.0 (#169003) 2026-04-29 11:13:14 +02:00
Yabing Yi bb41a2df9f Fix logbook spam by including image domain in ALWAYS_CONTINUOUS_DOMAINS (#169240)
Co-authored-by: Claude Code <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-04-29 10:58:13 +02:00
Petar Petrov 284242b90e Copy unit_of_measurement onto energy inverted power sensor (#169427) 2026-04-29 10:56:08 +02:00
Erik Montnemery a95c216983 Unload scripts created by websocket command execute_script (#169368)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-04-29 10:24:24 +02:00
Simone Chemelli d41a3ae0cd Use defaults for device class UPTIME in Shelly (#169148) 2026-04-29 10:12:18 +02:00
J. Nick Koston 0dfbe3ef84 Expose async_clear_advertisement_history in the bluetooth API (#169191) 2026-04-29 10:11:27 +02:00
Franck Nijhof 71fc725d75 Extract state template functions into a state Jinja2 extension (#169034) 2026-04-29 10:03:38 +02:00
Shay Levy d41c9aee52 Bump aioshelly to 13.24.1 (#169426)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-04-29 10:53:38 +03:00
epenet 8091f511b8 Reject manifest dependencies on core integrations in hassfest (#169425) 2026-04-29 09:52:46 +02:00
Franck Nijhof a7baedc22b Add error and alert sensors to Fumis integration (#169307) 2026-04-29 09:51:22 +02:00
Franck Nijhof 05bfb3a52e Add number platform to Fumis integration (#169100) 2026-04-29 09:39:15 +02:00
Robert Resch 2a5b95ba4d Require hass in Template (#169292)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 09:26:32 +02:00
Steve Easley 3dd972cc7a Fix jvcprojector entities going unavailable on transient command errors (#168985) 2026-04-29 09:21:53 +02:00
Marc Mueller acd9dd218a Protect CI cache save against cancellation (#168310) 2026-04-29 09:20:37 +02:00
J. Diego Rodríguez Royo 6552cf8f7a Keep options values when chaging or starting program on Home Connect (#168575) 2026-04-29 09:19:41 +02:00
Artur Pragacz e4e4785225 Clean up entity_service_call tests (#169170)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-29 09:17:45 +02:00
G Johansson d531ce8d1d Use async_on_create_entry in bayesian (#169218)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 09:14:58 +02:00
Stefan Agner 0224928655 Bump python-otbr-api to 2.10.0 (#169370)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-04-29 09:10:26 +02:00
Erik Montnemery 05121b89c6 Unload scripts and conditions created by automations (#169362) 2026-04-29 09:03:34 +02:00
Tomer 326895f0a1 Victron GX: Platinum quality scale (#169070) 2026-04-29 09:02:39 +02:00
Brett Adams 45121eddf1 Use new console pages for vehicles and energy sites in Teslemetry (#168865) 2026-04-29 09:00:39 +02:00
Konrad Strack 5e4f8f8bff Fix missing hue.activate_scene actions (#168859) 2026-04-29 08:59:04 +02:00
Klaas Schoute b9bbe36af0 Remove name field from Forecast.solar config flow (#169165) 2026-04-29 08:58:22 +02:00
epenet b56cdb9106 Fix flaky unifi device_tracker entity race on setup (#169359) 2026-04-29 08:55:10 +02:00
epenet e975496145 Fix flaky test_tasks_logged_that_block_stage_2 with Python 3.14.3 (#169424)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:54:21 +02:00
Simone Chemelli cdeb550b87 Use new UPTIME sensor class for Synology DSM (#169090) 2026-04-29 08:52:53 +02:00
Erik Montnemery 62082bdf14 Use modern condition API in script helper (#169355) 2026-04-29 08:51:57 +02:00
ibrahim amous 891efeb9cb Use enumerate instead of range(len()) in Duco fan speed list (#169392) 2026-04-29 08:51:26 +02:00
Daniel Hjelseth Høyer dc8abff6b9 Improve data updating for Tibber (#168065)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-04-29 08:42:47 +02:00
epenet aa7474839b Fix watts coordinator interrupting fast polling on hub update (#169365)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:39:58 +02:00
Robert Svensson 06a96712f6 Bump axis to v69 (#169408) 2026-04-29 08:27:43 +02:00
Kurt Chrisford 97be8f485a Add DRY HVAC mode support to Actron Air based on hardware capabilities (#169132) 2026-04-29 08:26:03 +02:00
Florent Thoumie a9c23ff445 iaqualink: add reconfigure flow (#169412) 2026-04-29 08:24:57 +02:00
Brett Adams cd92cb1258 Filter out "Unknown" part_name from Teslemetry energy device model (#169413)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:23:52 +02:00
Erik Montnemery c3f01b3a23 Unload scripts created by wake_on_lan switch (#169367) 2026-04-29 08:22:00 +02:00
Erik Montnemery 4b232be04a Unload scripts created by intent_script (#169363) 2026-04-29 08:21:26 +02:00
Robert Svensson cd5e21d3ac Allow Axis websocket event usage if supported (#169409) 2026-04-29 08:19:19 +02:00
Abílio Costa 84d5085f3b Add path-specific custom instructions to copilot gen script (#169402) 2026-04-29 08:19:10 +02:00
Erik Montnemery 44e94a82f1 Add last_action state attribute to timers (#168282) 2026-04-29 08:16:19 +02:00
Christian Lackas fe0da5c34f Bump PyViCare to 2.60.1 (#169401) 2026-04-29 08:14:39 +02:00
epenet c0200084ec Fix flaky test_alexa_config_expose_entity_prefs with Python 3.14.3 (#169421)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 08:13:14 +02:00
Simone Chemelli ef63ab5def Use new UPTIME sensor class for Vodafone Station (#169077)
Co-authored-by: Copilot <copilot@github.com>
2026-04-29 08:12:05 +02:00
Michael 3683607820 Deprecate firmware update button in FRITZ!Box Tools (#168117) 2026-04-29 08:10:29 +02:00
epenet 4c70fef2da Fix flaky mcp_server tests with Python 3.14.3 (#169385) 2026-04-28 22:13:06 -07:00
Nicolas Mowen d956af095e Add ability to filter GetLiveContext tool (#168457)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-28 22:08:29 -07:00
Brett Adams ea34fe4107 Bump Tesla Fleet API to 1.4.7 (#169411) 2026-04-29 01:25:04 +02:00
oxidworks e1c81c9b9e Reword country_not_configured repair description (#168357) 2026-04-29 00:55:48 +02:00
Mike Degatano 4ea0e6b240 Require admin for supervisor event publishing and addon options info (#169325)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Stefan Agner <stefan@agner.ch>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-28 18:51:16 -04:00
TheJulianJES 0ae5a19602 Handle ZHA dynamic entity add/remove events (#169341) 2026-04-28 23:48:57 +02:00
Raphael Hehl 80c7e47c42 Migrate UniFi Network discovery from SSDP to unifi_discovery (#168122)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-28 23:17:36 +02:00
Paul Bottein dfe4085189 Add fan platform to Novy Cooker Hood (#169380) 2026-04-28 16:51:46 -04:00
Paul Bottein 65a12b48e7 Add Novy Cooker Hood integration (#169194) 2026-04-28 16:24:19 -04:00
Joost Lekkerkerker cd639b829c Add battery mode select to Zinvolt (#169397) 2026-04-28 21:16:23 +02:00
Paulus Schoutsen ea5b633574 Bump rf-protocols to 2.2.0 (#169400)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-28 21:11:02 +02:00
Paulus Schoutsen 2f2413c979 Enforce per-entity permissions in calendar HTTP and WS APIs (#169235)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-28 15:06:49 -04:00
Ludovic BOUÉ 799bcb0f88 Fix Matter electrical sensors wrongly categorized as diagnostic (#169208)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-04-28 20:56:26 +02:00
Erik Montnemery d3cf5d9aab Add duration support to cover conditions (#169346) 2026-04-28 20:50:41 +02:00
puddly d2fddf129d Include matching integrations in scanned ports WS API (#169387) 2026-04-28 14:50:31 -04:00
AlCalzone d19c2506bf Discover Fibaro FGMS001 v2.8 as a motion sensor for Z-Wave (#169276) 2026-04-28 20:45:12 +02:00
Øyvind Matheson Wergeland 8fd3d0bb44 Fix nobo_hub KeyError when a zone or component is removed (#169378) 2026-04-28 20:30:53 +02:00
Ludovic BOUÉ d62f136c58 Add child lock entity for Eve Matter devices (#169391) 2026-04-28 19:55:37 +02:00
Ludovic BOUÉ 86e8b9df9b Add temporary mute button for Heiman smoke detector (#169311)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-04-28 18:19:22 +01:00
epenet aa5e942528 Fix flaky gardena_bluetooth test_timeout_manufacturer_data (#169389) 2026-04-28 19:15:43 +02:00
epenet 6636e67af6 Fix flaky cloud TTS and picotts streaming tests (#169376)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 18:38:03 +02:00
Raphael Hehl 30f310fc24 Add UniFi Protect relay output switches via public API (#169201)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-28 18:34:58 +02:00
Paulus Schoutsen de4e1c444e Restrict homematic.set_install_mode service to admins (#169203)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
2026-04-28 17:00:13 +01:00
Raphael Hehl eaf72106f8 Add siren platform to UniFi Protect integration (#169216)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 17:50:02 +02:00
A. Gideonse b90a074fb4 Bump indevolt-api to 1.6.4 (#169377) 2026-04-28 16:12:09 +01:00
epenet 3aea7f0695 Fix flaky stream test_stream_retries (#169372)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 15:41:21 +02:00
epenet ba8b1b2daf Fix flaky google calendar tests (#169371)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-28 15:21:38 +02:00
Erik Montnemery 5ff1c15df3 Unload scripts created by script entities (#169364) 2026-04-28 15:02:09 +02:00
Zoltán Farkasdi 0280d921e5 netatmo: add battery sensor for doortags (#168202) 2026-04-28 14:22:26 +02:00
Petro31 955e8362e4 Add template number device_class (#168438) 2026-04-28 14:12:18 +02:00
Ariel Ebersberger 1fc0b620c0 Fix flaky template test (#169361) 2026-04-28 14:09:07 +02:00
puddly ab08153d62 Expose more port metadata when listing serial ports (#169336) 2026-04-28 07:57:13 -04:00
Robert Resch b47b7fa58c User camera unique id in go2rtc if available (#168603)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 13:39:55 +02:00
Kurt Chrisford c50676dee9 Bump actron-neo-api requirement to version 0.5.6 (#169357) 2026-04-28 13:17:10 +02:00
Erik Montnemery 96bd991bb8 Use modern condition API in condition helper tests (#169353) 2026-04-28 13:04:21 +02:00
epenet 7e2a7b9393 Fix shelly device_trigger tests (#169305)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 13:50:02 +03:00
Erik Montnemery eb2217cfa6 Use modern condition API in condition tests (#169354) 2026-04-28 12:04:29 +02:00
Matrix b2269b3dba Bump yolink-api to 0.6.5 (#169350) 2026-04-28 11:59:23 +02:00
Abílio Costa eb85d7cd98 Allow targeting non-primary entities in triggers (#168857) 2026-04-28 11:49:20 +02:00
Robert Resch 6663717d59 Require local_only to be a boolean on webhook (#169296)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 11:40:49 +02:00
Mike Degatano 33e5a96a57 Require admin on APIs to create/delete config entries from Supervisor discovery (#169340) 2026-04-28 11:38:39 +02:00
Mike Degatano 7cb4d5ca9c Require admin for addon panel register and delete (#169329) 2026-04-28 11:38:29 +02:00
epenet 7dacd0080b Fix otbr flaky config flow tests in Python 3.14.3 (#169348)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:22:15 +02:00
Franck Nijhof 7f44fe031c Update gotailwind to v0.4.0 (#169316) 2026-04-28 10:54:05 +02:00
epenet 9656aaa6bd Fix Withings via_device race causing flaky test_devices (#169347)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 10:52:59 +02:00
Leonardo Rivera bf4b865e83 Fix OneDrive upload service to report all missing files at once (#169221) 2026-04-28 10:31:08 +02:00
Erik Montnemery 73dcc2f5a8 Add missing call to ConditionChecker.async_setup in async_from_config (#169055) 2026-04-28 10:30:11 +02:00
Ronald van der Meer fa0cf37e2c Fix Duco diagnostics crash on connection error (#169322) 2026-04-28 10:25:18 +02:00
epenet fa6c6ee4fc Fix bang olufsen flaky tests in Python 3.14.3 (#169345)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 09:23:31 +02:00
Erik Montnemery 4eb000d863 Add state tracking to EntityConditionBase (#169030) 2026-04-28 09:14:46 +02:00
Artur Pragacz d3809dd4cb Improve error handling for vacuum clean_area (#168177) 2026-04-28 09:14:17 +02:00
karwosts 2f3a6243f7 Restore state_class to derivative sensor (#163557) 2026-04-28 09:09:21 +02:00
epenet f36799d139 Refactor timezone handling in mqtt datetime (#169342)
Co-authored-by: Copilot <copilot@github.com>
2026-04-28 09:08:49 +02:00
Erik Montnemery 8d5f83e5f1 Add explicit test of conditions.async_conditions_from_config (#169042) 2026-04-28 08:24:43 +02:00
A. Gideonse 308cb686d2 Refactor indevolt integration to remove magic number constants (#169337)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-28 07:51:04 +02:00
frantzju 63d4f4d03d Bump freebox-api to 1.3.1 (#169335) 2026-04-28 01:08:57 +02:00
A. Gideonse d8a4b36381 Bump indevolt-api to 1.6.3 (#169338) 2026-04-28 00:22:31 +02:00
Florent Thoumie c048af2e4e Update to iaqualink 0.7.0 (#169330) 2026-04-27 22:27:38 +01:00
Manu 1a25864890 Bump aiontfy to 0.8.5 (#169327) 2026-04-27 23:13:14 +02:00
Thomas55555 d1922189aa Bump aioautomower to 2.7.4 (#169331) 2026-04-27 23:02:15 +02:00
Miko Stern 7594ead857 Add departure_delay sensor to Israel Rail (#169035) 2026-04-27 20:19:14 +01:00
puddly 8ce14877a4 Register a stub transport for ESPHome serial proxies via USB (#169308) 2026-04-27 15:12:07 -04:00
A. Gideonse 2fb0de3cdb Add actions to Indevolt integration (#163578)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-27 19:46:28 +01:00
Stefan Agner 3e77a4bfb2 Remove unused X-Hass constants (#169324) 2026-04-27 20:35:52 +02:00
puddly 4b9dd68fe7 Remove unnecessary title assertion from Yellow integration tests (#169320) 2026-04-27 20:22:33 +02:00
Stefan Agner f21ed9054b Remove hassio onboarding bypass for backup endpoints (#169299)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 13:37:06 -04:00
puddly 53e4d6c8fc Migrate ZHA to SerialPortSelector (#169099) 2026-04-27 13:03:54 -04:00
MohamedBarrak3 6a67c0faf7 Fix grammatical error in synology_dsm missing_data error message (#169306) 2026-04-27 18:17:25 +02:00
epenet d590f4f0b5 Fix more Shelly tests for Python 3.14.3 (#169304) 2026-04-27 18:16:43 +02:00
epenet 45a6134209 Wait for background tasks in shelly tests (#169301) 2026-04-27 18:40:49 +03:00
Paulus Schoutsen 6893d2b13d Migrate remaining top-level async_register_command handlers to decorators (#169227)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 17:29:24 +02:00
Paulus Schoutsen 370babf542 Require admin for diagnostics download and application credentials list (#169234) 2026-04-27 17:24:28 +02:00
epenet 6c89ecb98b Improve subscription handling in shelly tests (#169293)
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 18:17:22 +03:00
Simone Chemelli cc1eaa72a6 Bump aiocomelit to 2.0.3 (#169300) 2026-04-27 17:12:00 +02:00
Paulus Schoutsen 21a3c5b0ed Require admin for logger.set_level and logger.set_default_level services (#169232)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:03:03 +02:00
Paulus Schoutsen 080eb6af84 Require admin for backup.create and backup.create_automatic services (#169228)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-27 17:01:30 +02:00
Paulus Schoutsen 663538c492 Require admin for cloud preference and support package endpoints (#169205) 2026-04-27 16:59:24 +02:00
Paulus Schoutsen d119bbe4ef Require admin for configurator.configure service (#169230)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 16:56:47 +02:00
Ronald van der Meer 0eb204508c Bump python-duco-client to 0.3.9 (#169174) 2026-04-27 15:58:03 +02:00
Peter Grauvogel ad836b48b0 Fix Green Planet Energy timestamp sensors (#166153)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-04-27 15:41:31 +02:00
epenet 9cc9f240e7 Raise ConfigEntryError on decora_wifi authentication error (#169285) 2026-04-27 15:19:29 +02:00
epenet 91d5c080de Add missing mock in izone tests (#169287) 2026-04-27 15:18:12 +02:00
epenet 9390bf3414 Fix incorrect CONF_ID in twinkly tests (#169284) 2026-04-27 15:16:35 +02:00
Matthias Alphart a5b65766db Fix flaky KNX test (#169288) 2026-04-27 15:11:57 +02:00
epenet 9321ff504c Fix missing mock in switcher_kis config flow test (#169286)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-27 16:07:58 +03:00
Robert Resch 8a22e84db0 Verify local_only webhook on MockRequest (#169271)
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 14:49:48 +02:00
Paulus Schoutsen 642206699d Require admin for management WebSocket commands (#169209)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:34:15 -04:00
epenet 5d98f467fb Migrate esphome to use HassKey (#168873)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 14:16:29 +02:00
Paulus Schoutsen 54727a6f20 Require admin access for hassio Supervisor services (#169226)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:06:40 -04:00
Michael ed371bc644 Deprecate yaml import in Shopping List (#169084) 2026-04-27 13:04:11 +01:00
Manu 3cc6cc9519 Set integration type helper in OTP integration (#169012) 2026-04-27 12:52:02 +02:00
Denis Shulyaka 2053e61a80 Refactor anthropic stream processing (#168980) 2026-04-27 12:51:48 +02:00
Allen Porter 3673a80a37 Fix Google Generative AI token usage statistics tracing (#169222) 2026-04-27 12:48:32 +02:00
wollew 0cc531e333 Remove unused line of code in Velux integration (#168987) 2026-04-27 12:43:50 +02:00
Øyvind Matheson Wergeland 12280dbe63 Add sensor platform tests for nobo_hub (#169008) 2026-04-27 12:43:09 +02:00
Maciej Bieniek 84a5ba26d3 Remove unused name string from AccuWeather strings (#169195) 2026-04-27 12:38:28 +02:00
epenet 6ec4466ad7 Remove deprecated action from google generative AI (#169255) 2026-04-27 12:36:51 +02:00
epenet cb62562f5b Migrate waze_travel_time to use HassKey for shared semaphore (#169264)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 12:36:37 +02:00
Franck Nijhof a381a3a741 Update vehicle to 3.0.0 (#169127) 2026-04-27 12:24:48 +02:00
G Johansson 6902504087 Bump holidays lib to 0.95 (#169199) 2026-04-27 11:59:27 +02:00
Manu 64f2fa42fc Add integration type service to HTML5 Push notifications (#169015) 2026-04-27 11:56:37 +02:00
Franck Nijhof 64c9a76fc8 Add binary sensor platform to Fumis integration (#169032)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-27 11:53:25 +02:00
Franck Nijhof e9fc6b3e74 Merge branch 'master' into dev 2026-04-27 09:50:23 +00:00
epenet 605eea6274 Migrate yeelight to use HassKey for shared custom effects (#169268)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 11:31:35 +02:00
Paulus Schoutsen 86af61d7b5 Generalize radio_frequency test fixtures in components conftest (#169098)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-27 11:25:25 +02:00
epenet 45978f41cd Use runtime_data in xiaomi_aqara (#169262) 2026-04-27 11:18:32 +02:00
epenet 4d4e45854f Use HassKey in wemo (#169261) 2026-04-27 11:09:39 +02:00
epenet 43fa4f2646 Use runtime_data in yeelight (#169260) 2026-04-27 10:52:32 +02:00
epenet 5cedb0b726 Apply Tuya device quirks on device registration (#168897)
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 10:51:40 +02:00
epenet e3de695b99 Fix openai_conversation tests broken by Python 3.14.3 asyncio changes (#169259) 2026-04-27 10:27:01 +02:00
epenet e684490219 Fix google_generative_ai_conversation tests broken by Python 3.14.3 asyncio changes (#169258) 2026-04-27 10:20:49 +02:00
Paulus Schoutsen 70947c612c Require admin for fritz.set_guest_wifi_password service (#169233)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 08:55:58 +02:00
epenet 07c144841f Cleanup deprecated SEND_PIN service in blink (#169253) 2026-04-27 08:50:51 +02:00
G Johansson 392c46c028 Remove unique id in dnsip (#169211)
Co-authored-by: Copilot <copilot@github.com>
2026-04-27 08:47:24 +02:00
Paulus Schoutsen 5af3b361c8 Enforce admin policy for assist_satellite ask_question service (#169231)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 06:41:22 +02:00
Paulus Schoutsen 040c960ced Require admin for deCONZ services (#169207)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:33:42 -04:00
Paulus Schoutsen f2787115d0 Restrict file.read_file service to admin users (#169204) 2026-04-26 20:33:11 -04:00
Paulus Schoutsen 2dd1632fc3 Restrict Insteon link management services to admins (#169206)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 20:32:44 -04:00
Manu ed1aefc643 Bump pyloadapi to 2.1.0 (#169215) 2026-04-26 22:11:21 +02:00
Maciej Bieniek f1bbe4204b Use the carbon monoxide device class in the Airly integration (#169193)
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 17:53:01 +02:00
Luke Lashley 929379799c Add cleaning route for Q7 vacuums (#169177) 2026-04-26 08:42:15 -07:00
Daniel Feinberg d20d1df382 Add reset buttons for dock strainer and maintenance brush consumables in Roborock (#167395)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-26 08:41:18 -07:00
Raphael Hehl b5a1b592e9 Bump uiprotect to 10.4.1 (#169192)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-04-26 09:44:41 -05:00
Raphael Hehl 6384e6b38d Add alarm control panel platform to UniFi Protect (#169158)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-26 08:45:08 -05:00
Klaas Schoute cd98577eb7 Update easyEnergy integration to v3.0.0 (#169162) 2026-04-26 14:23:59 +02:00
Kurt Chrisford fa9a336725 Bump actron-neo-api to 0.5.5 (#169176) 2026-04-26 14:17:24 +02:00
mithomas aa0199b442 Add LG Netcast service to send remote control commands (#168649) 2026-04-26 14:14:16 +02:00
Richard Kroegel a506be4be0 Remove TARGET_TEMPERATURE_RANGE from eurotronic climate (#169182) 2026-04-26 14:09:11 +02:00
Maciej Bieniek e78a79a29e Remove name from Airly config flow (#169145)
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 13:38:50 +02:00
Paul Bottein eed4acc745 Add radio_frequency platform to Broadlink (#169128)
Co-authored-by: Claude <noreply@anthropic.com>
2026-04-26 11:40:47 +02:00
Tom Matheussen f1fcca2c75 Bump satel_integra to 1.2.2 (#169180) 2026-04-26 11:19:05 +02:00
javicalle 8673694f6f Try to fix RFLink tests broken by Python 3.14.3 asyncio changes (#169157) 2026-04-26 10:23:56 +02:00
Gustav Åkerström e8e9914ef5 Template vacuum segments (#167805)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-26 03:33:41 +02:00
Jan Bouwhuis 77c7225750 Fix None is not and allowed Unit of Measurement during MQTT Device setup via the UI (#169173) 2026-04-26 00:47:34 +02:00
Jan Bouwhuis 595f041143 Add MQTT datetime platform (#169091) 2026-04-26 00:45:11 +02:00
Franck Nijhof 2c4f598c06 Add button platform to Fumis integration (#169095) 2026-04-26 00:35:48 +02:00
Michael 1bf77e095d Migrate refoss to use entry.runtime_data (#169105) 2026-04-26 00:18:35 +02:00
Andres Ruiz d832abc5fc Add climate entity to Waterfurnace (#168729) 2026-04-26 00:17:12 +02:00
Jordi e7dae028ba Bump aioaquacell to 1.0.0 (#169166) 2026-04-26 00:15:01 +02:00
EnjoyingM e19d0e75c3 Wolflink: Fixing Codeowner (#169171) 2026-04-26 00:14:11 +02:00
Samuel Xiao 306fc529f2 Switchbot_BLE: bump PySwitchbot to 2.2.0 (#169119) 2026-04-26 00:01:14 +02:00
Marc Mueller c1894eda83 Detect .start entry point files in hassfest check (#169135) 2026-04-25 23:55:47 +02:00
mayerwin e9ca9254df Preserve sub-meter GPS accuracy in mobile_app webhooks (#169144)
Co-authored-by: mayerwin <2272127+mayerwin@users.noreply.github.com>
2026-04-25 23:51:56 +02:00
Ronald van der Meer 9ccc2e7473 Add temperature sensor to Duco integration (#169021) 2026-04-25 23:49:17 +02:00
Raphael Hehl d1bdd6eeeb Upgrade UniFi Network integration quality scale to Silver (#168736)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-25 23:44:37 +02:00
Franck Nijhof 8e3070afe1 Add reconfiguration flow to PVOutput (#169123) 2026-04-25 17:19:11 -04:00
Maciej Bieniek c48502afda Remove name from AccuWeather config flow (#169142)
Co-authored-by: Copilot <copilot@github.com>
2026-04-25 23:12:14 +02:00
Øyvind Matheson Wergeland 77df31fa83 Add climate platform tests for nobo_hub (#169010) 2026-04-25 23:11:44 +02:00
Denis Shulyaka f06cd25f4a Add GPT-5.5 support (#169112) 2026-04-25 23:11:19 +02:00
Matthias Alphart 19ebb1da2a Update knx-frontend to 2026.4.25.155016: Add notes to UI expose (#169154) 2026-04-25 23:05:44 +02:00
Christian Lackas f225d8162b homematicip_cloud: migrate entity unique IDs to stable format (#166580)
Co-authored-by: Christian Lackas <9592452+lackas@users.noreply.github.com>
2026-04-25 23:01:00 +02:00
Mika 759ac2eacd Add battery storage data sensors to SolarEdge integration (#161722)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: it-rec <19797875+it-rec@users.noreply.github.com>
2026-04-25 23:00:49 +02:00
Raphael Hehl b474a42844 unifiprotect: bump uiprotect to 10.4.0 (#169146)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-25 12:39:40 -05:00
shbatm db76773727 Standardize ISY994 sensor units and device classes (#169017)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-25 05:44:41 -05:00
Franck Nijhof 48b650c486 Modernize RDW config flow tests (#169129) 2026-04-25 11:03:15 +02:00
Franck Nijhof 9e1c02262e Set parallel updates for Hydrawise platforms (#169101)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-25 10:47:33 +02:00
Andrew Jackson 5a79dd9d99 Bump aiomealie to 1.2.4 (#169125) 2026-04-25 10:42:16 +02:00
Franck Nijhof c3f66f9e90 Set parallel updates to 0 for Forecast.Solar (#169126) 2026-04-25 10:27:57 +02:00
Joakim Plate 77fd120cd5 Protect update coordinator callbacks (#169122) 2026-04-25 10:15:33 +02:00
Joakim Plate 1978c9772a Filter unknown values from arcam enum (#169124) 2026-04-25 10:14:10 +02:00
Franck Nijhof 6862b808ae Update fumis to v0.4.0 (#169097) 2026-04-25 09:30:44 +02:00
Manu 757deb3a1c Add reconfiguration flow to Notifications for Android TV / Fire TV (#169111) 2026-04-25 09:29:38 +02:00
Franck Nijhof 54e3c3fc9b Extract common entity base class for RDW (#169118) 2026-04-25 09:26:28 +02:00
Michael e509c9b78a Migrate onvif to use entry.runtime_data (#169106) 2026-04-25 09:23:48 +02:00
A. Gideonse 2eb9f69d1e Bump indevolt-api to 1.4.3 (#169103) 2026-04-25 03:05:49 +02:00
Franck Nijhof 2278423758 Upgrade Elgato quality scale to platinum (#169102) 2026-04-24 23:10:46 +01:00
Franck Nijhof 4625176606 Upgrade Twente Milieu quality scale to platinum (#169104) 2026-04-24 23:29:52 +02:00
Tom Wilkie e7aa672133 Register MAC address connections on Synology DSM hub device (#169085)
Signed-off-by: Tom Wilkie <tom.wilkie@gmail.com>
Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2026-04-24 22:28:22 +02:00
Miko Stern 99185bf9a4 Bump israel-rail-api to 0.1.5 (#169094) 2026-04-24 21:11:46 +02:00
Franck Nijhof b5e66bbcd0 2026.4.4 (#169092) 2026-04-24 20:37:21 +02:00
Abílio Costa 2deb364ab0 Reinforce Python 3.14 exceptions Agent instructions (#169089) 2026-04-24 20:21:07 +02:00
Raphael Hehl 822b97d096 Upgrade unifi_access quality scale to platinum (#168204)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-24 20:20:39 +02:00
Simone Chemelli 1e0dc86eea Use new UPTIME sensor class for Shelly (#169088) 2026-04-24 20:20:00 +02:00
A. Gideonse ca70abe240 Refactor button platform to use indevolt-api 1.4.2 (#169063)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 19:11:01 +01:00
Franck Nijhof f479b0ad6a Bump version to 2026.4.4 2026-04-24 18:08:48 +00:00
Bram Kragten 458b5fe8bf Update frontend to 20260325.8 (#169076) 2026-04-24 18:07:57 +00:00
Robert Resch 9621307cb0 Validate local_only user for signed requests (#169066) 2026-04-24 18:06:48 +00:00
Maciej Bieniek 4507f9a8d8 Bump aiotractive to 1.0.3 (#169059) 2026-04-24 18:06:47 +00:00
Maciej Bieniek 19dd68b7fc Slow down Tractive API polling to avoid 429 too many requests (#169057)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 18:06:45 +00:00
Daniel Hjelseth Høyer 245b9ed4c0 Update Tibber library, 0.37.2 (#169027) 2026-04-24 18:06:44 +00:00
Robert Resch 838feef660 Validate local_only user property during ws auth phase (#168812) 2026-04-24 18:06:42 +00:00
Jan Bouwhuis ca4d36db1a Cancel and await idle_start future if the task was canceled after an IMAP connection was lost (#168662)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-04-24 18:06:41 +00:00
Joakim Plate 39b690b22c Correct state/device class for water in gardena (#168637) 2026-04-24 18:06:39 +00:00
Allen Porter ed560f0ba7 Add Roborock fan speed validation and error handling (#168623) 2026-04-24 18:06:38 +00:00
Nils Ove Erstad 0db50acb89 Fix MQTT JSON light restoring None color_mode on startup (#168608)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2026-04-24 18:06:36 +00:00
Ariel Ebersberger 446d89aee2 Disable rflink tests broken by Python 3.14.3 asyncio changes (#169074)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 19:06:23 +01:00
Ariel Ebersberger 6fe1862d15 Disable dsmr tests broken by Python 3.14.3 asyncio changes (#169064)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 19:06:14 +01:00
Ariel Ebersberger 4f5d0a7305 Disable plex tests broken by Python 3.14.3 asyncio changes (#169069)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 19:06:11 +01:00
Simone Chemelli f84bf99105 Bump aioamazondevices to 13.4.3 (#168536) 2026-04-24 18:04:30 +00:00
Khole 7fad242ad0 Hive - Bump pyhive-integration to 1.0.9 (#168489) 2026-04-24 18:04:29 +00:00
MohamedBarrak3 fcd6f78f35 Fix case-sensitive MIME type check in Google Generative AI TTS (#168458) 2026-04-24 18:04:27 +00:00
Raj Laud 056ff957e8 Fix Victron BLE false reauth on unrecognised advertisement mode bytes (#168209)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-24 18:04:26 +00:00
albaintor 9cf95404cf Fixed Kodi Media Browsing (#165819) 2026-04-24 18:04:24 +00:00
Ariel Ebersberger 4d8acfa61c Disable knx tests broken by Python 3.14.3 asyncio changes (#169079)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 18:54:21 +01:00
Maciej Bieniek 9369a5dc93 Slow down Tractive API polling to avoid 429 too many requests (#169057)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 19:51:07 +02:00
WardZhou 8b2afb4e66 Add a dynamic sensitivity slider for Matter sensors (#167710)
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2026-04-24 18:50:23 +01:00
Bram Kragten a53d3ea9eb Update frontend to 20260325.8 (#169076) 2026-04-24 19:50:11 +02:00
Maciej Bieniek e422c08d4e Support media player for Shelly Wall Display (#168494)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 19:49:31 +02:00
Michael 599fe252ef Fix feedreader tests broken by Python 3.14.3 asyncio changes (#169080) 2026-04-24 18:49:10 +02:00
Erik Montnemery aad93fd577 Add tests asserting condition features (#168881) 2026-04-24 18:41:39 +02:00
Stefan Agner a19aebed16 Keep add-on update entity in progress across post-install refresh (#168756)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:09:25 +02:00
mnaggatz c9d8257465 Return None for Velux cover position when unknown (#168566) 2026-04-24 17:42:30 +02:00
shbatm 5ee6a2181f Fix Flume sensor units and device classes (#169013) 2026-04-24 10:27:37 -05:00
Simone Chemelli ec18e0c6d4 Add uptime device class to the sensor platform (#164266)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 16:18:17 +01:00
Paulus Schoutsen c4426b9476 Add radio_frequency platform to ESPHome (#168448)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 10:48:06 -04:00
Paulus Schoutsen c4fd458d03 Add Honeywell String Lights integration (#168450) 2026-04-24 10:43:56 -04:00
Robert Resch dd71d6cd50 Validate local_only user for signed requests (#169066) 2026-04-24 16:27:15 +02:00
Erik Montnemery 7d494f687e Adjust compound conditions (#169054)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-04-24 16:21:13 +02:00
Paulus Schoutsen 45adc3d477 Bump rf-protocols to 2.1.0 (#169062) 2026-04-24 09:53:41 -04:00
Tomer 59766bb249 Victron GX: quality scale adjustments (#168988)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 15:45:31 +02:00
Martin d849b12bc7 Add distance device class to Ecowitt lightning distance sensors (#168995) 2026-04-24 15:41:51 +02:00
Manu 8cd2d397d1 Add data descriptions to config flow in OTP integration (#168989) 2026-04-24 15:41:18 +02:00
Jan Bouwhuis 8580a6436d Add MQTT date platform (#168998) 2026-04-24 15:36:21 +02:00
A. Gideonse 7b3b1e34fa Bump indevolt-api to 1.4.2 (#169061) 2026-04-24 15:24:45 +02:00
Maciej Bieniek bb9520856f Bump aiotractive to 1.0.3 (#169059) 2026-04-24 15:14:17 +02:00
Paulus Schoutsen 032dce20b1 Bump aioesphomeapi to 44.21.0 (#169056) 2026-04-24 08:14:12 -05:00
Erik Montnemery a92277b7fa Add method Script.unload (#169036) 2026-04-24 15:12:17 +02:00
Willem-Jan van Rootselaar 10d78d280a Add multiple heating system circuit support to BSBlan (#165992)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 15:12:09 +02:00
Renat Sibgatulin cf1faf3a20 Refactor AirQ config flow tests (#169053) 2026-04-24 15:11:29 +02:00
Ariel Ebersberger ccd82e6b8b Disable sonos tests broken by Python 3.14.3 asyncio changes (#169046)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 14:59:58 +02:00
Erik Montnemery db01b8e421 Migrate async_conditions_from_config to ConditionChecker (#169033) 2026-04-24 14:28:10 +02:00
A. Gideonse bf36c3d193 Bump indevolt-api to 1.4.0 (#169050) 2026-04-24 13:20:19 +02:00
Paulus Schoutsen dd2a90a31f Add radio_frequency entity integration (#168447)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: balloob <1444314+balloob@users.noreply.github.com>
2026-04-24 06:37:28 -04:00
Mattie eb42804871 Add binary sensor platform to Qube heat pump (#166611)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-24 12:30:49 +02:00
Erik Montnemery 6b5bbede52 Update websocket_api.handle_test_condition to use modern condition API (#169029) 2026-04-24 12:25:39 +02:00
Marc Mueller 28c3ca37b9 Refactor pylint plugins to use match statements (#168894) 2026-04-24 12:25:16 +02:00
Franck Nijhof 76376d6b26 Add pylint plugin to detect IP-based unique IDs in config entries (#168822)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: frenck <195327+frenck@users.noreply.github.com>
2026-04-24 10:59:59 +02:00
Raphael Hehl dbb750a583 Add AV1 support for HLS fallback (#161492)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-24 10:07:26 +02:00
Trendafil Gechev aec8d00c95 Add WLED segment freeze support (#168424) 2026-04-24 10:06:49 +02:00
A. Gideonse 39fbd2ccbd Bump indevolt-api to 1.3.1 (#168986) 2026-04-24 09:58:59 +02:00
Denis Shulyaka 1942f12e55 Refactor Anthropic model args (#169014) 2026-04-24 09:58:16 +02:00
Manu eb825796f9 Remove name from config flow of Notifications for Android TV /Fire TV (#169024) 2026-04-24 09:47:03 +02:00
Maciej Bieniek ac6e425748 Add tilt and rotation binary sensors for Shelly Cury (#169002)
Co-authored-by: Copilot <copilot@github.com>
2026-04-24 09:45:13 +02:00
Øyvind Matheson Wergeland cf092c63c0 Declare PARALLEL_UPDATES = 0 for nobo_hub platforms (#169011) 2026-04-24 09:44:54 +02:00
Daniel Hjelseth Høyer 4d8f3dfaf7 Update Tibber library, 0.37.2 (#169027) 2026-04-24 09:44:43 +02:00
Erik Montnemery ed7f2b1810 Migrate compound conditions to ConditionChecker (#169028) 2026-04-24 09:44:28 +02:00
Raphael Hehl 3ff2b4424f Bump uiprotect to 10.3.1 (#169031) 2026-04-24 09:44:08 +02:00
Franck Nijhof f8e6137d28 Update fumis to v0.3.0 (#168984) 2026-04-24 09:24:05 +02:00
Abílio Costa 6a57382eff Allow extracting non-primary entities in websocket command (#168860)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-24 09:13:51 +02:00
Erik Montnemery cebe4aa685 Refactor condition API (#168815)
Co-authored-by: Artur Pragacz <artur@pragacz.com>
2026-04-24 07:46:34 +02:00
Ronald van der Meer 32b9a21294 Bump python-duco-client to 0.3.6 (#169020) 2026-04-24 05:18:55 +02:00
Tomer 7de684d47b Victron GX: Add reconfiguration flow (#168997)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 23:35:30 +02:00
Samuel Xiao 5a9bb972d0 Add sensor description for Lock state in Switchbot Cloud (#168607)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-23 23:34:47 +02:00
Øyvind Matheson Wergeland e1a73fbeed Add select platform tests for nobo_hub (#168738)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:21:26 +02:00
Tom Matheussen 20a88eb21e Add entity availability to Satel Integra (#168476)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 23:20:00 +02:00
Raphael Hehl 0bb678cacf Bump uiprotect to 10.3.0 (#168992)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-23 22:18:18 +01:00
Harvey 0e817c5c90 Bump HueBLE to 2.2.2 (#167677) 2026-04-23 22:48:44 +02:00
fender4645 e5cd1e2830 Tessie: log warning instead of raising UpdateFailed for missing energy history (#168068)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 22:47:15 +02:00
kostavelikov b4c8452a5a Add open (unlatch) support to Homee locks (#168532) 2026-04-23 22:32:51 +02:00
Tomer 86ffb9eccb Victron GX: Add exception translations (#168762)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 22:30:47 +02:00
epenet 7bf3e75bc8 Fix invalid notification/event handling in Tuya tests (#168854)
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 22:14:20 +02:00
Tomer 5394c764b4 Victron GX: Add strict typing (#168907)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-23 22:11:33 +02:00
Tomer 1cd34e8477 Victron GX stale devices (#168706)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-23 22:06:13 +02:00
Willem-Jan van Rootselaar 0122b2811a Bump python-bsblan to 5.2.0 (#168892) 2026-04-23 22:05:58 +02:00
Artur Pragacz 3f2bc45686 Migrate to entity services in monoprice (#168972) 2026-04-23 22:05:20 +02:00
Franck Nijhof 4612a72cd2 Add reconfiguration flow to Fumis integration (#168759)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 21:53:40 +02:00
Michael 8448ace289 Migrate shopping_list to use entry.runtime_data (#168911) 2026-04-23 21:30:11 +02:00
Ariel Ebersberger 19fd6e2036 Fix b&o race conditions for Python 3.14.3 (#168885) 2026-04-23 21:26:54 +02:00
c0ffeeca7 94ca503f71 Media browser: apply sentence-style capitalization (#168971) 2026-04-23 21:24:25 +02:00
Artur Pragacz fbf30e64a0 Migrate to entity services in amcrest (#168974) 2026-04-23 21:23:43 +02:00
Jan Bouwhuis 49022b69b0 Add MQTT time platform (#168898) 2026-04-23 21:12:28 +02:00
Mick Vleeshouwer 13105bd0b7 Migrate cover platform to entity descriptions in Overkiz (#141330)
Co-authored-by: Copilot <copilot@github.com>
2026-04-23 19:58:17 +02:00
epenet 438c1e9c3d Remove leftover hass.data[DOMAIN] usage from insteon (#168880)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 15:22:44 +02:00
Christophe Gagnier b0ecc2f36a Add reconfigure flow to TechnoVE integration (#168466)
Co-authored-by: Moustachauve <2206577+Moustachauve@users.noreply.github.com>
2026-04-23 15:09:35 +02:00
Ronald van der Meer 19f19e00f6 Add UCRH sensor support and warn on unknown node types in Duco (#168758) 2026-04-23 15:02:36 +02:00
Raphael Hehl 95ec39ac1a unifi_access: add direction attribute to access events (#168853)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: home-assistant[bot] <78085893+home-assistant[bot]@users.noreply.github.com>
2026-04-23 14:44:57 +02:00
Matthias Alphart c6b4594e7a Update knx-frontend to 2026.4.22.141111 (#168837) 2026-04-23 14:44:24 +02:00
Joost Lekkerkerker cf0b5c6e51 Migrate GitHub to subentries (#160564)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-04-23 14:23:47 +02:00
epenet 187fcd10b3 Add async_panel_exists helper to frontend and use it across integrations (#168884) 2026-04-23 14:14:45 +03:00
TheJulianJES ed1cba02ae Migrate Matter integration to use runtime_data (#168862) 2026-04-23 13:03:08 +02:00
epenet b213eb23c8 Reduce context switching in Tuya initialisation (#168830)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-23 12:48:15 +02:00
renovate[bot] 30d362dc8e Update uv to 0.11.7 (#168864)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-23 10:43:51 +02:00
Erik Montnemery 67c818c7a8 Add comment to trigger base class (#168882) 2026-04-23 10:42:07 +02:00
epenet 5927f50bd2 Use runtime_data in Huawei LTE (#168876)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 09:48:45 +02:00
epenet 66d7afa442 Migrate flux_led to use HassKey for FLUX_LED_DISCOVERY (#168872)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:56:20 +02:00
epenet 51fcdaff7a Migrate slimproto to use runtime_data (#168869)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:55:37 +02:00
Raphael Hehl 67baec27cf unifi_access: add missing WebSocket handlers for remote_view and device_update events (#168850)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-23 08:50:09 +02:00
epenet d45941d648 Migrate kraken to use runtime_data (#168870)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:24:56 +02:00
Raphael Hehl a338d04441 unifi_access: bump py-unifi-access to 1.3.0 (#168851)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-23 08:24:41 +02:00
epenet 69eca62446 Clean up leftover hass.data[DOMAIN] usage in keenetic_ndms2 (#168871)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 08:20:48 +02:00
Franck Nijhof 507b5f1bbf Add pylint plugin to detect polling interval fields in config flows (#168849) 2026-04-22 23:41:43 +02:00
A. Gideonse ee8a15b368 Fix incorrect sensor definition for Indevolt Gen-1 devices (#168835)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
2026-04-22 22:03:13 +02:00
Erik Montnemery 7f92d88606 Replace climate-control device with thermostat in climate translations (#161419) 2026-04-22 21:02:54 +02:00
epenet cc1c5e788f Revert Tuya camera quirk changes (#168820) 2026-04-22 20:54:49 +02:00
epenet 1159946391 Bump tuya-device-handlers to 0.0.18 (#168821) 2026-04-22 20:53:37 +02:00
Erik Montnemery 46208c034e Add tests asserting air_quality condition features (#168731) 2026-04-22 20:42:42 +02:00
puddly abdd132bdc Register optimized ESPHome serial proxy transport with serialx (#168817) 2026-04-22 13:16:56 -04:00
Denis Shulyaka 1b71ef2a60 Add gpt-image-2 model support for OpenAI (#168826) 2026-04-22 18:13:04 +01:00
Abílio Costa f0445a792d Add dummy Claude skill instruction for testing (#168829) 2026-04-22 18:35:24 +02:00
Abílio Costa 24e3842319 Rename Claude's integration skill (#168825) 2026-04-22 17:04:49 +01:00
epenet 54aae2c7de Ensure Tuya (stale) device is removed before adding new (#168721) 2026-04-22 16:58:00 +01:00
epenet ea3e8cf9b0 Add tests for Tuya dynamic add/remove device (#168824) 2026-04-22 16:13:56 +01:00
Abílio Costa a16f6f965e Improve claude gh pr review summary + business logic lib note (#168819) 2026-04-22 16:05:28 +01:00
Manu d772320f06 Record notifications sent via ntfy.publish action in ntfy integration (#166352) 2026-04-22 17:01:31 +02:00
Michael Hansen 8a74b41db5 Add audio processing settings to speech-to-text entities (#167246) 2026-04-22 08:43:21 -05:00
Raphael Hehl fddc6aaf38 Add entity translations to UniFi integration (#168739)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-04-22 15:40:35 +02:00
Franck Nijhof fab59d7a13 Add pylint plugin to enforce entry.runtime_data over hass.data[DOMAIN] (#168760) 2026-04-22 15:31:58 +02:00
Robert Resch 1345356bdc Validate local_only user property during ws auth phase (#168812) 2026-04-22 14:07:47 +02:00
Shay Levy be07fed774 Remove unused hass.data[DOMAIN] in LG webOS TV (#168813) 2026-04-22 13:58:44 +02:00
Franck Nijhof 1fec38ef28 2026.4.3 (#168451) 2026-04-17 22:08:45 +02:00
Franck Nijhof 9c4b6951ef Ran hassfest manually 2026-04-17 19:45:38 +00:00
Franck Nijhof 7d2f303035 Bump version to 2026.4.3 2026-04-17 19:35:02 +00:00
Åke Strandberg c61c09fba3 Add cleaning codes for MIele steam oven combo (#168418) 2026-04-17 19:33:51 +00:00
Tom Matheussen 83c807d01c Update satel-integra to 1.2.1 (#168416) 2026-04-17 19:33:50 +00:00
Jan Bouwhuis 1b0386ddfc Fix disabled discovered MQTT entities cleaned up (#168382) 2026-04-17 19:33:48 +00:00
Jamin 0af6a85049 Fix VOIP blocking call in event loop (#168331) 2026-04-17 19:27:38 +00:00
Maciej Bieniek 7bd0bc9c8a Bump imgw-pib to 2.1.0 (#168319) 2026-04-17 19:27:36 +00:00
Christopher Fenner b200930fd4 Bump PyViCare to v2.59.0 (#168254) 2026-04-17 19:27:35 +00:00
johanzander 5c046a3750 Fix unit of measurement for SPH power sensors in growatt_server (#168251)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 19:27:34 +00:00
renovate[bot] c1a013d718 Update uv to 0.11.6 (#168237)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-17 19:26:34 +00:00
renovate[bot] f1f6cdae2a Update Pillow to 12.2.0 (#168234)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-04-17 19:25:04 +00:00
Daniel Hjelseth Høyer f98de4618a Bump pyTibber to 0.37.1 (#168208) 2026-04-17 19:25:02 +00:00
Daniel Hjelseth Høyer 6b2033b060 Handle Tibber async_get_client failing (#168207)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2026-04-17 19:25:01 +00:00
Arie Catsman d9dc2bbae4 Bump pyenphase from 2.4.6 to 2.4.8 (#168190) 2026-04-17 19:24:59 +00:00
Marcel van der Veldt 82a1884085 Fix Wyoming satellite memory leak on disconnect (#168152) 2026-04-17 19:24:58 +00:00
Nathan Spencer c46f6721bc Bump pylitterbot to 2025.3.2 (#168146) 2026-04-17 19:24:57 +00:00
Simone Chemelli ed3ff38d30 Bump aioamazondevices to 13.4.1 (#168121) 2026-04-17 19:24:55 +00:00
Simone Chemelli fd3e12a85f Bump aioamazondevices to 13.4.0 (#167984) 2026-04-17 19:24:54 +00:00
Retha Runolfsson 1e0a0b70f4 Fix Switchbot Keypad Vision doorbell detection (#168098)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
2026-04-17 19:21:51 +00:00
AlCalzone 2598dde7aa Update Z-Wave cover moving state based on current position and cover capabilities (#168096) 2026-04-17 19:21:49 +00:00
Tom Matheussen 9af7fe22bd Bump satel-integra to 1.1.1 (#168091) 2026-04-17 19:21:48 +00:00
Tom Matheussen 546eef2eee Bump satel_integra to 1.1.0 (#167353) 2026-04-17 19:21:46 +00:00
Retha Runolfsson d65b7ce2f3 Bump PySwitchbot to 2.0.1 (#168090) 2026-04-17 17:29:23 +00:00
gerculanum b09671a409 Fix missing kWh unit for dlq ADD_ELE energy sensor (#168026)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-17 17:29:22 +00:00
J. Nick Koston 412771465d Fix ESPHome cold/warm white color temperature read-back (#167972) 2026-04-17 17:29:21 +00:00
Michael 3a72bc23b9 Don't create cpu temperature sensor when not supported in FRITZ!Box Tools (#167905) 2026-04-17 17:29:20 +00:00
Colin b3d7ba5ce5 Fix openevse charging_current and charging_power units (#167863) 2026-04-17 17:29:18 +00:00
Aidan Timson 4e7b6838eb Fix device_class removal in template binary sensors (#167775) 2026-04-17 17:29:17 +00:00
Kevin O'Brien 437f5ef66c Fix Proxmox VE storage usage percentage crash on missing used_fraction (#167136)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-17 17:29:15 +00:00
Franck Nijhof f886b03e14 2026.4.2 (#167939) 2026-04-11 20:41:23 +02:00
Willem-Jan van Rootselaar 190ee49e3a Bump python-bsblan to version 5.1.4 (#167987) 2026-04-11 18:00:37 +00:00
Erwin Douna f7c5a51f46 Portainer fix fetching swarm stacks (#167979) 2026-04-11 17:56:13 +00:00
tronikos e4e9c22016 Bump opower to 0.18.1 (#167967) 2026-04-11 17:56:12 +00:00
Norbert Rittel f2df848e3f Fix spelling of "Shut down" button label in proxmoxve (#167059) 2026-04-11 17:56:10 +00:00
Franck Nijhof cdce98faaf Update cryptography to 46.0.7 (#167960) 2026-04-11 08:12:40 +00:00
Martin Hjelmare fde103cdfd Fix tibber price sensor first state update (#167938)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-11 08:02:37 +00:00
Daniel Hjelseth Høyer fcd6c6e335 Improve Tibber price coordinator (#166175)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-11 08:02:36 +00:00
Franck Nijhof 8f2cec26e3 Merge branch 'master' into rc 2026-04-10 22:45:55 +02:00
Franck Nijhof 05463cde99 Bump version to 2026.4.2 2026-04-10 20:41:40 +00:00
On Freund a948799a6e Bump pyrisco to 0.6.8 (#167924) 2026-04-10 20:41:20 +00:00
Bram Kragten 624fab064a Update frontend to 20260325.7 (#167922) 2026-04-10 20:41:19 +00:00
Nathan Spencer a331cb7199 Bump pylitterbot to 2025.2.1 (#167921) 2026-04-10 20:41:17 +00:00
Thomas D 7d6eaf40a6 Fix light on action for qbus integration (#167917) 2026-04-10 20:41:16 +00:00
panosmz 1ae9e7c87d Bump oasatelematics to 0.4 (#167911) 2026-04-10 20:41:14 +00:00
Thomas D 6bcfc32d48 Bump qbusmqttapi to 1.4.3 (#167909) 2026-04-10 20:41:13 +00:00
Joost Lekkerkerker 0b5f85bdb9 Bump zinvolt to 0.4.3 (#167908) 2026-04-10 20:41:12 +00:00
Maikel Punie d153eee822 Bump velbusaio to 2026.4.0 (#167868) 2026-04-10 20:41:10 +00:00
TheJulianJES afcc2113ce Bump ZHA to 1.1.2 (#167849) 2026-04-10 20:40:12 +00:00
J. Diego Rodríguez Royo ae5bd63993 Fix service.yaml values for Home Connect (#167847) 2026-04-10 20:38:38 +00:00
Simone Chemelli 78107c478d Fix stale devices removal for Alexa devices (#167837) 2026-04-10 20:38:36 +00:00
Joost Lekkerkerker 84490ef0bb Support Chess.com accounts with no name (#167824) 2026-04-10 20:38:34 +00:00
Raj Laud 887e14638b Fix Victron BLE storage errors caused by non-serializable value_fn callable in sensor entity description (#167819)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 20:38:32 +00:00
Raj Laud 818bde1d5e Fix Victron BLE false reauth triggered by unknown enum bitmask combinations (#167809)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 20:38:31 +00:00
Benjamin Hudgens 83da18b761 Revert "Fix Ring snapshots" - #164337 (#167790) 2026-04-10 20:38:29 +00:00
Maciej Bieniek bd904caea1 Bump aiotractive to 1.0.2 (#167783) 2026-04-10 20:38:28 +00:00
Michael 500f030eaa Set proper state for the internet_access switches in FRITZ!Box Tools (#167767) 2026-04-10 20:38:26 +00:00
wollew ce755f5f8f Bump pyvlx to 0.2.33 (#167764) 2026-04-10 20:38:25 +00:00
Nick Haghiri fb766d164b Improve error logging for Backblaze B2 upload failures (#167721) 2026-04-10 20:38:23 +00:00
Tom 394670e33f Fix ProxmoxVE migration causing reauthentication (#167624) 2026-04-10 20:38:21 +00:00
G Johansson f79285f9ab Bump holidays to 0.94 (#167604) 2026-04-10 20:38:20 +00:00
Erik Montnemery a422611ada Fix securetar size calculation when encrypting backup (#167602) 2026-04-10 20:38:18 +00:00
Erik Montnemery 4c34dcd560 Bump securetar to 2026.4.0 (#167600) 2026-04-10 20:38:17 +00:00
Maciej Bieniek 1aca993c12 Fix Tractive switch availability (#167599) 2026-04-10 20:38:15 +00:00
Leo Periou a8cc099b66 fix EWS deviceType problem (#167597) 2026-04-10 20:38:13 +00:00
Artur Pragacz c56d67c02f Set up condition and trigger helpers in check config script (#167589)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 20:38:11 +00:00
Jan Čermák 0ce98cfb34 Remove homeassistant/actions/helpers/info from builder workflow (#167573) 2026-04-10 20:35:03 +00:00
Jan Bouwhuis 4a13ab9aff Bump incomfort-client to v0.7.0 (#167546) 2026-04-10 20:28:23 +00:00
Fabian Neundorf dc65646d8b Bump python-picnic-api2 to 1.3.4 (#167539)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-04-10 20:28:22 +00:00
Nelson Osacky 39fbdad775 Add missing Miele dishwasher program ID 201 (#167536)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:28:20 +00:00
Jordan Harvey b4f6a43a14 Bump pynintendoparental to 2.3.4 (#167510) 2026-04-10 20:28:19 +00:00
Nick Haghiri e5ff7a9944 Handle BadRequest exception in Backblaze B2 config flow and setup (#167482) 2026-04-10 20:28:18 +00:00
Nick Haghiri ca9945f750 Bump b2sdk to 2.10.4 (#167481) 2026-04-10 20:28:16 +00:00
Andrea Turri b028e2a6ae Miele - fix core temperature reading (#167476) 2026-04-10 20:28:15 +00:00
Allen Porter 6f4aca495b Update roborock services to raise ServiceNotSupported for new devices that don't yet support it (#167470)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 20:28:14 +00:00
Jamie Magee a892b5364d Fix nzbget positional argument mismatch in NZBGetAPI calls (#167456) 2026-04-10 20:28:12 +00:00
Steve Easley f57e682a98 Bump jvcprojector dependency to pyjvcprojector 2.0.5 (#167450) 2026-04-10 20:28:11 +00:00
Nils Ove Erstad 3493517b6d Fix missing color_mode initialization in MQTT JSON light schema (#167429) 2026-04-10 20:28:09 +00:00
Jordan Harvey b5842b8484 Fix handling of missing period statistics in Anglian Water coordinator (#167427)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 20:28:08 +00:00
Willem-Jan van Rootselaar 3333b8d019 Fix setup without dhw (#167423) 2026-04-10 20:28:07 +00:00
Simone Chemelli 745860553c Bump aiocomelit to 2.0.2 (#167414) 2026-04-10 20:26:02 +00:00
Shai Ungar 7188a09a59 Use dedicated session for seventeentrack to preserve login cookies (#167394)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 20:26:01 +00:00
Robert Svensson 96a9b89412 Bump axis to v68 to improve MQTT event resiliance (#167373) 2026-04-10 20:25:59 +00:00
Tom 586d7ab526 Improve ProxmoxVE permissions handling (#167370) 2026-04-10 20:25:58 +00:00
Joost Lekkerkerker 5f2fe4ffd4 Bump aiohue to 4.8.1 (#167369) 2026-04-10 20:25:56 +00:00
Simone Chemelli 040192c103 Align and cleanup tests data for Fritz (#167363) 2026-04-10 20:25:55 +00:00
Jordan Harvey e85430105e Bump cryptography to 46.0.6 (#167330)
Co-authored-by: Robert Resch <robert@resch.dev>
2026-04-10 20:22:31 +00:00
cdheiser 5c7c0a6e83 Bump pylutron to 0.4.1 (#167324) 2026-04-10 20:21:53 +00:00
007hacky007 c7bd673d01 Bump afsapi to 0.3.1 (#167321) 2026-04-10 20:21:51 +00:00
MarkGodwin 6d3a93df81 Update to tplink-omada-client 1.5.7 (#167313) 2026-04-10 20:21:50 +00:00
Raj Laud 6a934b5fe3 Fix victron ble reauth flow title (#167307)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 20:21:48 +00:00
Daniel Hjelseth Høyer d644348dc8 Bump pyTibber to 0.37.0 (#167283) 2026-04-10 20:21:47 +00:00
Ludovic BOUÉ dbfde9266c Add Hisense AC (0x138C/0x0101) to Matter dry and fan mode device lists (#167282)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-04-10 20:21:45 +00:00
Simone Chemelli ed0b68ec4a Allow force alarm actions for Comelit (#167202) 2026-04-10 20:21:44 +00:00
Patrick c32d523f63 Bump starlink-grpc-core to 1.2.5 (#167195) 2026-04-10 20:21:42 +00:00
Marco Sousa 98a4e27e35 Bump aiopvpc to 4.3.1 (#167189) 2026-04-10 20:21:41 +00:00
Alex Merkel fb1365e9a4 [LG Soundbar] Fix incorrect state for some models (#167094) 2026-04-10 20:21:39 +00:00
Willem-Jan van Rootselaar 850b034a5f Include port in BSB-LAN configuration URL when non-default (#166480) 2026-04-10 20:19:09 +00:00
Samuel Xiao b880876e0e Switchbot Cloud: Enable Webhook for Bot (#165647) 2026-04-10 20:13:56 +00:00
Jeef ab601e5717 Prevent the intellifire client from polling independently of its coordinator (#165341)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-10 20:13:54 +00:00
Matt Philips 7eda592c72 Improve handling of disconnected meters with Rainforest Automation Eagle-200 (#161185)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-10 20:13:53 +00:00
Franck Nijhof b981ece163 Pin actions/helpers/info to fix release build (#167327) 2026-04-03 20:58:54 +00:00
Franck Nijhof 7ea931fdc8 2026.4.1 (#167310) 2026-04-03 22:10:54 +02:00
Franck Nijhof f3038a20af Bump version to 2026.4.1 2026-04-03 16:05:08 +00:00
Pete Sage de234c7190 Sonos alarm switch entities may not be created when speaker offline initially (#167303) 2026-04-03 16:01:17 +00:00
Pete Sage 399681984f Bump soco to 0.30.15 (#167299) 2026-04-03 16:01:16 +00:00
Joost Lekkerkerker 5ca14ca7d7 Bump Zinvolt to 0.4.1 (#167296) 2026-04-03 16:01:15 +00:00
Joost Lekkerkerker ac53cfa85a Make sure we take all Zinvolt battery units in account (#167294) 2026-04-03 16:01:13 +00:00
Ludovic BOUÉ 02f1a9c3a9 Fix Matter water heater off mode (#167286)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-04-03 16:01:12 +00:00
Bram Kragten f93fdceac9 Update frontend to 20260325.6 (#167285) 2026-04-03 16:01:11 +00:00
Ludovic BOUÉ 711a89f7b8 Fix to allow Matter Fan percent setting to be null when FanMode is Auto (#167279)
Co-authored-by: Ludovic BOUÉ <132135057+lboue@users.noreply.github.com>
2026-04-03 16:01:09 +00:00
Norbert Rittel 19e58c554e Improve Assist satellite action naming consistency (#167278) 2026-04-03 16:01:08 +00:00
Joost Lekkerkerker feb6c2bfe6 Bump zinvolt to 0.4.0 (#167276) 2026-04-03 16:01:07 +00:00
Norbert Rittel 6bb91422ff Improve Media player action naming consistency (#167274)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-03 16:01:06 +00:00
Andrew Jackson 3bd699285b Remove Transmission port forward sensor (#167269) 2026-04-03 16:00:17 +00:00
dotlambda 6d10305197 Bump psutil to 7.2.2 (#167263) 2026-04-03 15:57:57 +00:00
Joakim Plate 42a9c8488d Update arcam to 1.8.3 (#167249) 2026-04-03 15:57:56 +00:00
Norbert Rittel c6c273559e Improve Recorder action naming consistency (#167244) 2026-04-03 15:57:55 +00:00
Pete Sage f7394ce302 Fix Sonos reporting wrong state when media title is whitespace (#167223) 2026-04-03 15:57:53 +00:00
G Johansson 175dec6f1a Bump holiday library to 0.93 (#167217) 2026-04-03 15:57:52 +00:00
G Johansson d137761cb5 Fix SMHI (#167212) 2026-04-03 15:57:50 +00:00
Simone Chemelli 8055cbc58d Migrate image unique_id for Fritz (#167209) 2026-04-03 15:57:49 +00:00
Joost Lekkerkerker c9dff27590 Remove not implemented supported feature from Wiim (#167205) 2026-04-03 15:57:48 +00:00
Mike Degatano c913a858b6 Wrap hassio import in is_hassio check in get_system_info helper (#167111) 2026-04-03 15:57:46 +00:00
Joost Lekkerkerker 4ed33a804e Bump pySmartThings to 3.7.3 (#167075) 2026-04-03 15:57:45 +00:00
Kevin O'Brien 8bf5674826 Fix Proxmox VE backup status sensor false positive due to case mismatch (#167069)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-03 15:57:43 +00:00
Manu b8a0b0083b Fix websocket calling async_release_notes in update component although unavailable (#167067) 2026-04-03 15:57:42 +00:00
Bram Kragten a57c101b5e Fix select condition state selector (#167064) 2026-04-03 15:57:41 +00:00
Brett Adams 957b8c1c52 Fix Tesla Fleet OAuth scope refresh during reauth (#166920) 2026-04-03 15:57:40 +00:00
Brett Adams bb002d051b Fix Tesla Fleet charge current scope handling (#166919) 2026-04-03 15:57:38 +00:00
LTek 2b2fd4ac92 Fix Ring snapshots (#164337)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-04-03 15:57:37 +00:00
Jan Bouwhuis f4c270629b Fix tuya energy sensor units (#160392) 2026-04-03 15:57:35 +00:00
1427 changed files with 60339 additions and 15414 deletions
+5 -4
View File
@@ -27,12 +27,13 @@ description: Reviews GitHub pull requests and provides feedback comments. This i
- No need to highlight things that are already good.
## Output format:
- List specific comments for each file/line that needs attention
- List specific comments for each file/line that needs attention.
- In the end, summarize with an overall assessment (approve, request changes, or comment) and bullet point list of changes suggested, if any.
- Example output:
```
Overall assessment: request changes.
- [CRITICAL] Memory leak in homeassistant/components/sensor/my_sensor.py:143
- [PROBLEM] Inefficient algorithm in homeassistant/helpers/data_processing.py:87
- [SUGGESTION] Improve variable naming in homeassistant/helpers/config_validation.py:45
- [CRITICAL] sensor.py:143 - Memory leak
- [PROBLEM] data_processing.py:87 - Inefficient algorithm
- [SUGGESTION] test_init.py:45 - Improve x variable name
```
- Make sure to include the file and line number when possible in the bullet points.
@@ -1,5 +1,5 @@
---
name: Home Assistant Integration knowledge
name: ha-integration-knowledge
description: Everything you need to know to build, test and review Home Assistant Integrations. If you're looking at an integration, you must use this as your primary reference.
---
@@ -14,6 +14,7 @@ description: Everything you need to know to build, test and review Home Assistan
- Do NOT allow users to set config entry names in config flows. Names are automatically generated or can be customized later in UI. Exception: helper integrations may allow custom names.
- For entity actions and entity services, avoid requesting redundant defensive checks for fields already enforced by Home Assistant validation schemas and entity filters; only request extra guards when values bypass validation or are transformed unsafely.
- When validation guarantees a key is present, prefer direct dictionary indexing (`data["key"]`) over `.get("key")` so invalid assumptions fail fast.
- Integrations should be thin wrappers. Protocol parsing, device state machines, or other domain logic belong in a separate PyPI library, not in the integration itself. If unsure, ask before inlining.
The following platforms have extra guidelines:
- **Diagnostics**: [`platform-diagnostics.md`](platform-diagnostics.md) for diagnostic data collection
+1
View File
@@ -36,6 +36,7 @@ base_platforms: &base_platforms
- homeassistant/components/image_processing/**
- homeassistant/components/infrared/**
- homeassistant/components/lawn_mower/**
- homeassistant/components/radio_frequency/**
- homeassistant/components/light/**
- homeassistant/components/lock/**
- homeassistant/components/media_player/**
+2 -7
View File
@@ -5,7 +5,7 @@
# Copilot code review instructions
- Start review comments with a short, one-sentence summary of the suggested fix.
- Do not add comments about code style, formatting or linting issues.
- Do not comment on code style, formatting or linting issues.
# GitHub Copilot & Claude Code Instructions
@@ -21,7 +21,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
## Python Syntax Notes
- Python 3.14 explicitly allows `except TypeA, TypeB:` without parentheses.
- Python 3.14 explicitly allows `except TypeA, TypeB:` without parentheses. Never flag this as an issue since Home Assistant officially supports Python 3.14.
## Testing
@@ -34,8 +34,3 @@ Integrations with Platinum or Gold level in the Integration Quality Scale reflec
When reviewing entity actions, do not suggest extra defensive checks for input fields that are already validated by Home Assistant's service/action schemas and entity selection filters. Suggest additional guards only when data bypasses those validators or is transformed into a less-safe form.
When validation guarantees a dict key exists, prefer direct key access (`data["key"]`) instead of `.get("key")` so contract violations are surfaced instead of silently masked.
# Skills
- Home Assistant Integration knowledge: .claude/skills/integrations/SKILL.md
@@ -0,0 +1,46 @@
---
applyTo: "homeassistant/components/**, tests/components/**"
excludeAgent: "cloud-agent"
---
<!-- Automatically generated by gen_copilot_instructions.py, do not edit -->
## File Locations
- **Integration code**: `./homeassistant/components/<integration_domain>/`
- **Integration tests**: `./tests/components/<integration_domain>/`
## General guidelines
- When looking for examples, prefer integrations with the platinum or gold quality scale level first.
- Polling intervals are NOT user-configurable. Never add scan_interval, update_interval, or polling frequency options to config flows or config entries.
- Do NOT allow users to set config entry names in config flows. Names are automatically generated or can be customized later in UI. Exception: helper integrations may allow custom names.
- For entity actions and entity services, avoid requesting redundant defensive checks for fields already enforced by Home Assistant validation schemas and entity filters; only request extra guards when values bypass validation or are transformed unsafely.
- When validation guarantees a key is present, prefer direct dictionary indexing (`data["key"]`) over `.get("key")` so invalid assumptions fail fast.
- Integrations should be thin wrappers. Protocol parsing, device state machines, or other domain logic belong in a separate PyPI library, not in the integration itself. If unsure, ask before inlining.
The following platforms have extra guidelines:
- **Diagnostics**: [`platform-diagnostics.md`](platform-diagnostics.md) for diagnostic data collection
- **Repairs**: [`platform-repairs.md`](platform-repairs.md) for user-actionable repair issues
## Integration Quality Scale
- When validating the quality scale rules, check them at https://developers.home-assistant.io/docs/core/integration-quality-scale/rules
- When implementing or reviewing an integration, always consider the quality scale rules, since they promote best practices.
Template scale file: `./script/scaffold/templates/integration/integration/quality_scale.yaml`
### How Rules Apply
1. **Check `manifest.json`**: Look for `"quality_scale"` key to determine integration level
2. **Bronze Rules**: Always required for any integration with quality scale
3. **Higher Tier Rules**: Only apply if integration targets that tier or higher
4. **Rule Status**: Check `quality_scale.yaml` in integration folder for:
- `done`: Rule implemented
- `exempt`: Rule doesn't apply (with reason in comment)
- `todo`: Rule needs implementation
## Testing Requirements
- Tests should avoid interacting or mocking internal integration details. For more info, see https://developers.home-assistant.io/docs/development_testing/#writing-tests-for-integrations
+3 -2
View File
@@ -6,7 +6,7 @@
"pep621",
"pip_requirements",
"pre-commit",
"regex",
"custom.regex",
"homeassistant-manifest"
],
@@ -27,8 +27,9 @@
]
},
"regexManagers": [
"customManagers": [
{
"customType": "regex",
"description": "Update ruff required-version in pyproject.toml",
"managerFilePatterns": ["/^pyproject\\.toml$/"],
"matchStrings": ["required-version = \">=(?<currentValue>[\\d.]+)\""],
+29 -3
View File
@@ -366,7 +366,7 @@ jobs:
echo "key=uv-${UV_CACHE_VERSION}-${uv_version}-${HA_SHORT_VERSION}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore base Python virtual environment
id: cache-venv
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: venv
key: >-
@@ -374,7 +374,8 @@ jobs:
needs.info.outputs.python_cache_key }}
- name: Restore uv wheel cache
if: steps.cache-venv.outputs.cache-hit != 'true'
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
id: cache-uv
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ env.UV_CACHE_DIR }}
key: >-
@@ -398,6 +399,7 @@ jobs:
if: |
steps.cache-venv.outputs.cache-hit != 'true'
|| steps.cache-apt-check.outputs.cache-hit != 'true'
id: install-os-deps
timeout-minutes: 10
env:
APT_CACHE_HIT: ${{ steps.cache-apt-check.outputs.cache-hit }}
@@ -431,7 +433,10 @@ jobs:
sudo chmod -R 755 ${APT_CACHE_BASE}
fi
- name: Save apt cache
if: steps.cache-apt-check.outputs.cache-hit != 'true'
if: |
always()
&& steps.cache-apt-check.outputs.cache-hit != 'true'
&& steps.install-os-deps.outcome == 'success'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
@@ -441,6 +446,7 @@ jobs:
${{ runner.os }}-${{ runner.arch }}-${{ needs.info.outputs.apt_cache_key }}
- name: Create Python virtual environment
if: steps.cache-venv.outputs.cache-hit != 'true'
id: create-venv
run: |
python -m venv venv
. venv/bin/activate
@@ -471,6 +477,26 @@ jobs:
- name: Check dirty
run: |
./script/check_dirty
- name: Save uv wheel cache
if: |
(success() && steps.cache-venv.outputs.cache-hit != 'true')
|| (always()
&& steps.create-venv.outcome == 'success'
&& steps.cache-uv.outputs.cache-matched-key == '')
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ env.UV_CACHE_DIR }}
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
steps.generate-uv-key.outputs.key }}
- name: Save base Python virtual environment
if: always() && steps.create-venv.outcome == 'success'
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: venv
key: >-
${{ runner.os }}-${{ runner.arch }}-${{ steps.python.outputs.python-version }}-${{
needs.info.outputs.python_cache_key }}
hassfest:
name: Check hassfest
+1 -1
View File
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.10
rev: v0.15.12
hooks:
- id: ruff-check
args:
+1
View File
@@ -599,6 +599,7 @@ homeassistant.components.vallox.*
homeassistant.components.valve.*
homeassistant.components.velbus.*
homeassistant.components.velux.*
homeassistant.components.victron_gx.*
homeassistant.components.vivotek.*
homeassistant.components.vlc_telnet.*
homeassistant.components.vodafone_station.*
+1 -1
View File
@@ -12,7 +12,7 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
## Python Syntax Notes
- Python 3.14 explicitly allows `except TypeA, TypeB:` without parentheses.
- Python 3.14 explicitly allows `except TypeA, TypeB:` without parentheses. Never flag this as an issue since Home Assistant officially supports Python 3.14.
## Testing
Generated
+12 -4
View File
@@ -758,6 +758,8 @@ CLAUDE.md @home-assistant/core
/tests/components/homewizard/ @DCSBL
/homeassistant/components/honeywell/ @rdfurman @mkmer
/tests/components/honeywell/ @rdfurman @mkmer
/homeassistant/components/honeywell_string_lights/ @balloob
/tests/components/honeywell_string_lights/ @balloob
/homeassistant/components/hr_energy_qube/ @MattieGit
/tests/components/hr_energy_qube/ @MattieGit
/homeassistant/components/html5/ @alexyao2015 @tr4nt0r
@@ -849,8 +851,8 @@ CLAUDE.md @home-assistant/core
/tests/components/input_select/ @home-assistant/core
/homeassistant/components/input_text/ @home-assistant/core
/tests/components/input_text/ @home-assistant/core
/homeassistant/components/insteon/ @teharris1
/tests/components/insteon/ @teharris1
/homeassistant/components/insteon/ @teharris1 @ssyrell
/tests/components/insteon/ @teharris1 @ssyrell
/homeassistant/components/integration/ @dgomes
/tests/components/integration/ @dgomes
/homeassistant/components/intelliclima/ @dvdinth
@@ -1201,6 +1203,8 @@ CLAUDE.md @home-assistant/core
/tests/components/notify_events/ @matrozov @papajojo
/homeassistant/components/notion/ @bachya
/tests/components/notion/ @bachya
/homeassistant/components/novy_cooker_hood/ @piitaya
/tests/components/novy_cooker_hood/ @piitaya
/homeassistant/components/nrgkick/ @andijakl
/tests/components/nrgkick/ @andijakl
/homeassistant/components/nsw_fuel_station/ @nickw444
@@ -1237,6 +1241,8 @@ CLAUDE.md @home-assistant/core
/homeassistant/components/ollama/ @synesthesiam
/tests/components/ollama/ @synesthesiam
/homeassistant/components/ombi/ @larssont
/homeassistant/components/omie/ @luuuis
/tests/components/omie/ @luuuis
/homeassistant/components/onboarding/ @home-assistant/core
/tests/components/onboarding/ @home-assistant/core
/homeassistant/components/ondilo_ico/ @JeromeHXP
@@ -1415,6 +1421,8 @@ CLAUDE.md @home-assistant/core
/tests/components/radarr/ @tkdrob
/homeassistant/components/radio_browser/ @frenck
/tests/components/radio_browser/ @frenck
/homeassistant/components/radio_frequency/ @home-assistant/core
/tests/components/radio_frequency/ @home-assistant/core
/homeassistant/components/radiotherm/ @vinnyfuria
/tests/components/radiotherm/ @vinnyfuria
/homeassistant/components/rainbird/ @konikvranik @allenporter
@@ -1983,8 +1991,8 @@ CLAUDE.md @home-assistant/core
/tests/components/wled/ @frenck @mik-laj
/homeassistant/components/wmspro/ @mback2k
/tests/components/wmspro/ @mback2k
/homeassistant/components/wolflink/ @adamkrol93 @mtielen
/tests/components/wolflink/ @adamkrol93 @mtielen
/homeassistant/components/wolflink/ @adamkrol93 @EnjoyingM
/tests/components/wolflink/ @adamkrol93 @EnjoyingM
/homeassistant/components/workday/ @fabaff @gjohansson-ST
/tests/components/workday/ @fabaff @gjohansson-ST
/homeassistant/components/worldclock/ @fabaff
+16 -1
View File
@@ -2,7 +2,8 @@
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Iterable
from typing import TYPE_CHECKING
import voluptuous as vol
@@ -13,6 +14,9 @@ from .models import PermissionLookup
from .types import PolicyType
from .util import test_all
if TYPE_CHECKING:
from ..models import User
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
__all__ = [
@@ -22,10 +26,21 @@ __all__ = [
"PermissionLookup",
"PolicyPermissions",
"PolicyType",
"filter_entity_ids_by_permission",
"merge_policies",
]
def filter_entity_ids_by_permission(
user: User, entity_ids: Iterable[str], key: str
) -> list[str]:
"""Filter entity IDs to those the user can access for the given policy key."""
if user.is_admin or user.permissions.access_all_entities(key):
return list(entity_ids)
check_entity = user.permissions.check_entity
return [entity_id for entity_id in entity_ids if check_entity(entity_id, key)]
class AbstractPermissions:
"""Default permissions class."""
+1 -1
View File
@@ -1,5 +1,5 @@
{
"domain": "honeywell",
"name": "Honeywell",
"integrations": ["lyric", "evohome", "honeywell"]
"integrations": ["lyric", "evohome", "honeywell", "honeywell_string_lights"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"domain": "sensereo",
"name": "Sensereo",
"iot_standards": ["matter"]
}
+5
View File
@@ -0,0 +1,5 @@
{
"domain": "zunzunbee",
"name": "Zunzunbee",
"iot_standards": ["zigbee"]
}
+1 -1
View File
@@ -143,4 +143,4 @@ class AcaiaRestoreSensor(AcaiaEntity, RestoreSensor):
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available or self._restored_data is not None
return super().available or self.native_value is not None
@@ -4,7 +4,7 @@ from __future__ import annotations
from asyncio import timeout
from collections.abc import Mapping
from typing import Any
from typing import TYPE_CHECKING, Any
from accuweather import AccuWeather, ApiError, InvalidApiKeyError, RequestsExceededError
from aiohttp import ClientError
@@ -12,7 +12,7 @@ from aiohttp.client_exceptions import ClientConnectorError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -55,8 +55,11 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
)
self._abort_if_unique_id_configured()
if TYPE_CHECKING:
assert accuweather.location_name is not None
return self.async_create_entry(
title=user_input[CONF_NAME], data=user_input
title=accuweather.location_name, data=user_input
)
return self.async_show_form(
@@ -70,9 +73,6 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
}
),
errors=errors,
@@ -64,7 +64,7 @@ class AccuWeatherObservationDataUpdateCoordinator(
"""Initialize."""
self.accuweather = accuweather
self.location_key = accuweather.location_key
name = config_entry.data[CONF_NAME]
name = config_entry.data.get(CONF_NAME) or config_entry.title
if TYPE_CHECKING:
assert self.location_key is not None
@@ -122,7 +122,7 @@ class AccuWeatherForecastDataUpdateCoordinator(
self.accuweather = accuweather
self.location_key = accuweather.location_key
self._fetch_method = fetch_method
name = config_entry.data[CONF_NAME]
name = config_entry.data.get(CONF_NAME) or config_entry.title
if TYPE_CHECKING:
assert self.location_key is not None
@@ -25,8 +25,7 @@
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"name": "[%key:common::config_flow::data::name%]"
"longitude": "[%key:common::config_flow::data::longitude%]"
},
"data_description": {
"api_key": "API key generated in the AccuWeather APIs portal."
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/acer_projector",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["serialx==1.4.1"]
"requirements": ["serialx==1.7.0"]
}
+26 -3
View File
@@ -38,6 +38,7 @@ HVAC_MODE_MAPPING_ACTRONAIR_TO_HA = {
"HEAT": HVACMode.HEAT,
"FAN": HVACMode.FAN_ONLY,
"AUTO": HVACMode.AUTO,
"DRY": HVACMode.DRY,
"OFF": HVACMode.OFF,
}
HVAC_MODE_MAPPING_HA_TO_ACTRONAIR = {
@@ -79,7 +80,6 @@ class ActronAirClimateEntity(ClimateEntity):
)
_attr_name = None
_attr_fan_modes = list(FAN_MODE_MAPPING_ACTRONAIR_TO_HA.values())
_attr_hvac_modes = list(HVAC_MODE_MAPPING_ACTRONAIR_TO_HA.values())
class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
@@ -93,6 +93,17 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
super().__init__(coordinator)
self._attr_unique_id = self._serial_number
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of supported HVAC modes."""
modes = [
HVAC_MODE_MAPPING_ACTRONAIR_TO_HA[mode]
for mode in self._status.user_aircon_settings.supported_modes
if mode in HVAC_MODE_MAPPING_ACTRONAIR_TO_HA
]
modes.append(HVACMode.OFF)
return modes
@property
def min_temp(self) -> float:
"""Return the minimum temperature that can be set."""
@@ -136,7 +147,7 @@ class ActronSystemClimate(ActronAirAcEntity, ActronAirClimateEntity):
@property
def target_temperature(self) -> float:
"""Return the target temperature."""
return self._status.user_aircon_settings.temperature_setpoint_cool_c
return self._status.user_aircon_settings.current_setpoint
@actron_air_command
async def async_set_fan_mode(self, fan_mode: str) -> None:
@@ -179,6 +190,18 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
super().__init__(coordinator, zone)
self._attr_unique_id: str = self._zone_identifier
@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of supported HVAC modes."""
status = self.coordinator.data
modes = [
HVAC_MODE_MAPPING_ACTRONAIR_TO_HA[mode]
for mode in status.user_aircon_settings.supported_modes
if mode in HVAC_MODE_MAPPING_ACTRONAIR_TO_HA
]
modes.append(HVACMode.OFF)
return modes
@property
def min_temp(self) -> float:
"""Return the minimum temperature that can be set."""
@@ -216,7 +239,7 @@ class ActronZoneClimate(ActronAirZoneEntity, ActronAirClimateEntity):
@property
def target_temperature(self) -> float | None:
"""Return the target temperature."""
return self._zone.temperature_setpoint_cool_c
return self._zone.current_setpoint
@actron_air_command
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
@@ -13,5 +13,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["actron-neo-api==0.5.3"]
"requirements": ["actron-neo-api==0.5.6"]
}
@@ -25,7 +25,7 @@ async def async_get_media_source(hass: HomeAssistant) -> MediaSource:
hass.data[DATA_MEDIA_SOURCE] = source = local_source.LocalSource(
hass,
DOMAIN,
"AI Generated Images",
"AI generated images",
{IMAGE_DIR: str(media_dir)},
f"/{DOMAIN}",
)
@@ -36,9 +36,7 @@ def _make_detected_condition(
) -> type[Condition]:
"""Create a detected condition for a binary sensor device class."""
return make_entity_state_condition(
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)},
STATE_ON,
support_duration=True,
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)}, STATE_ON
)
@@ -47,9 +45,7 @@ def _make_cleared_condition(
) -> type[Condition]:
"""Create a cleared condition for a binary sensor device class."""
return make_entity_state_condition(
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)},
STATE_OFF,
support_duration=True,
{BINARY_SENSOR_DOMAIN: DomainSpec(device_class=device_class)}, STATE_OFF
)
@@ -4,11 +4,14 @@
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
automation_behavior:
mode: condition
.condition_for: &condition_for
required: true
default: 00:00:00
selector:
duration:
# --- Unit lists for multi-unit pollutants ---
@@ -249,11 +252,7 @@
.condition_binary_common: &condition_binary_common
fields:
behavior: *condition_behavior
for:
required: true
default: 00:00:00
selector:
duration:
for: *condition_for
is_gas_detected:
<<: *condition_binary_common
@@ -285,6 +284,7 @@ is_co_value:
target: *target_co_sensor
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -299,6 +299,7 @@ is_ozone_value:
target: *target_ozone
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -313,6 +314,7 @@ is_voc_value:
target: *target_voc
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -327,6 +329,7 @@ is_voc_ratio_value:
target: *target_voc_ratio
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -341,6 +344,7 @@ is_no_value:
target: *target_no
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -355,6 +359,7 @@ is_no2_value:
target: *target_no2
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -369,6 +374,7 @@ is_so2_value:
target: *target_so2
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -385,6 +391,7 @@ is_co2_value:
target: *target_co2
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -397,6 +404,7 @@ is_pm1_value:
target: *target_pm1
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -409,6 +417,7 @@ is_pm25_value:
target: *target_pm25
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -421,6 +430,7 @@ is_pm4_value:
target: *target_pm4
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -433,6 +443,7 @@ is_pm10_value:
target: *target_pm10
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -445,6 +456,7 @@ is_n2o_value:
target: *target_n2o
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
@@ -14,6 +14,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -50,6 +53,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -86,6 +92,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -98,6 +107,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -110,6 +122,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -122,6 +137,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -134,6 +152,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -146,6 +167,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -158,6 +182,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -170,6 +197,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -206,6 +236,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -218,6 +251,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -230,6 +266,9 @@
"behavior": {
"name": "[%key:component::air_quality::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::air_quality::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::air_quality::common::condition_threshold_name%]"
}
@@ -237,21 +276,6 @@
"name": "Volatile organic compounds value"
}
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"title": "Air Quality",
"triggers": {
"co2_changed": {
@@ -3,12 +3,8 @@
required: true
default: any
selector:
select:
translation_key: trigger_behavior
options:
- first
- last
- any
automation_behavior:
mode: trigger
for: &trigger_for
required: true
default: 00:00:00
+9 -12
View File
@@ -12,11 +12,11 @@ from airly.exceptions import AirlyError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_USE_NEAREST, DOMAIN, NO_AIRLY_SENSORS
from .const import CONF_USE_NEAREST, DEFAULT_NAME, DOMAIN, NO_AIRLY_SENSORS
DESCRIPTION_PLACEHOLDERS = {
"developer_registration_url": "https://developer.airly.eu/register",
@@ -45,16 +45,16 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
try:
location_point_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
user_input[CONF_API_KEY],
user_input[CONF_LATITUDE],
user_input[CONF_LONGITUDE],
)
if not location_point_valid:
location_nearest_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
user_input[CONF_API_KEY],
user_input[CONF_LATITUDE],
user_input[CONF_LONGITUDE],
use_nearest=True,
)
except AirlyError as err:
@@ -68,7 +68,7 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="wrong_location")
use_nearest = True
return self.async_create_entry(
title=user_input[CONF_NAME],
title=DEFAULT_NAME,
data={**user_input, CONF_USE_NEAREST: use_nearest},
)
@@ -83,9 +83,6 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
vol.Optional(
CONF_LONGITUDE, default=self.hass.config.longitude
): cv.longitude,
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
}
),
errors=errors,
+2
View File
@@ -37,3 +37,5 @@ MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
URL = "https://airly.org/map/#{latitude},{longitude}"
DEFAULT_NAME: Final = "Airly"
+2 -2
View File
@@ -127,7 +127,7 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
),
AirlySensorEntityDescription(
key=ATTR_API_CO,
translation_key="co",
device_class=SensorDeviceClass.CO,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
@@ -178,7 +178,7 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Airly sensor entities based on a config entry."""
name = entry.data[CONF_NAME]
name = entry.data.get(CONF_NAME) or entry.title
coordinator = entry.runtime_data
+1 -5
View File
@@ -13,8 +13,7 @@
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"latitude": "[%key:common::config_flow::data::latitude%]",
"longitude": "[%key:common::config_flow::data::longitude%]",
"name": "[%key:common::config_flow::data::name%]"
"longitude": "[%key:common::config_flow::data::longitude%]"
},
"description": "To generate API key go to {developer_registration_url}"
}
@@ -24,9 +23,6 @@
"sensor": {
"caqi": {
"name": "Common air quality index"
},
"co": {
"name": "[%key:component::sensor::entity_component::carbon_monoxide::name%]"
}
}
},
@@ -36,6 +36,8 @@ class AirTouch5ConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unexpected exception")
errors = {"base": "cannot_connect"}
else:
# Uses the host/IP value from CONF_HOST as unique ID, which is no longer allowed
# pylint: disable-next=hass-unique-id-ip-based
await self.async_set_unique_id(user_input[CONF_HOST])
self._abort_if_unique_id_configured()
return self.async_create_entry(
@@ -4,7 +4,6 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.condition import (
ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR,
Condition,
EntityStateConditionBase,
make_entity_state_condition,
@@ -26,7 +25,6 @@ class EntityStateRequiredFeaturesCondition(EntityStateConditionBase):
"""State condition."""
_required_features: int
_schema = ENTITY_STATE_CONDITION_SCHEMA_ANY_ALL_FOR
def entity_filter(self, entities: set[str]) -> set[str]:
"""Filter entities of this domain with the required features."""
@@ -84,11 +82,9 @@ CONDITIONS: dict[str, type[Condition]] = {
AlarmControlPanelState.ARMED_VACATION,
AlarmControlPanelEntityFeature.ARM_VACATION,
),
"is_disarmed": make_entity_state_condition(
DOMAIN, AlarmControlPanelState.DISARMED, support_duration=True
),
"is_disarmed": make_entity_state_condition(DOMAIN, AlarmControlPanelState.DISARMED),
"is_triggered": make_entity_state_condition(
DOMAIN, AlarmControlPanelState.TRIGGERED, support_duration=True
DOMAIN, AlarmControlPanelState.TRIGGERED
),
}
@@ -1,22 +1,14 @@
.condition_common: &condition_common
target: &condition_common_target
target:
entity:
domain: alarm_control_panel
fields: &condition_common_fields
behavior: &condition_common_behavior
behavior:
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
.condition_common_for: &condition_common_for
target: *condition_common_target
fields: &condition_common_for_fields
behavior: *condition_common_behavior
automation_behavior:
mode: condition
for:
required: true
default: 00:00:00
@@ -26,7 +18,7 @@
is_armed: *condition_common
is_armed_away:
fields: *condition_common_for_fields
fields: *condition_common_fields
target:
entity:
domain: alarm_control_panel
@@ -34,7 +26,7 @@ is_armed_away:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_AWAY
is_armed_home:
fields: *condition_common_for_fields
fields: *condition_common_fields
target:
entity:
domain: alarm_control_panel
@@ -42,7 +34,7 @@ is_armed_home:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_HOME
is_armed_night:
fields: *condition_common_for_fields
fields: *condition_common_fields
target:
entity:
domain: alarm_control_panel
@@ -50,13 +42,13 @@ is_armed_night:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_NIGHT
is_armed_vacation:
fields: *condition_common_for_fields
fields: *condition_common_fields
target:
entity:
domain: alarm_control_panel
supported_features:
- alarm_control_panel.AlarmControlPanelEntityFeature.ARM_VACATION
is_disarmed: *condition_common_for
is_disarmed: *condition_common
is_triggered: *condition_common_for
is_triggered: *condition_common
@@ -11,6 +11,9 @@
"fields": {
"behavior": {
"name": "[%key:component::alarm_control_panel::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::alarm_control_panel::common::condition_for_name%]"
}
},
"name": "Alarm is armed"
@@ -160,21 +163,6 @@
"message": "Arming requires a code but none was given for {entity_id}."
}
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"alarm_arm_away": {
"description": "Arms an alarm in the away mode.",
@@ -7,12 +7,8 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
translation_key: trigger_behavior
automation_behavior:
mode: trigger
for:
required: true
default: 00:00:00
@@ -11,6 +11,7 @@ from .services import async_setup_services
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.BUTTON,
Platform.NOTIFY,
Platform.SENSOR,
Platform.SWITCH,
@@ -0,0 +1,55 @@
"""Support for buttons."""
from homeassistant.components.button import ButtonEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import slugify
from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator
from .entity import AmazonServiceEntity
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: AmazonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up button entities for Alexa Devices."""
coordinator = entry.runtime_data
known_routines: set[str] = set()
def _check_routines() -> None:
current_routines = set(coordinator.api.routines)
new_routines = current_routines - known_routines
if new_routines:
known_routines.update(new_routines)
async_add_entities(
AmazonRoutineButton(coordinator, routine) for routine in new_routines
)
_check_routines()
entry.async_on_unload(coordinator.async_add_listener(_check_routines))
class AmazonRoutineButton(AmazonServiceEntity, ButtonEntity):
"""Button entity for Alexa routine."""
_attr_has_entity_name = True
def __init__(self, coordinator: AmazonDevicesCoordinator, routine: str) -> None:
"""Initialize the routine button entity."""
self._coordinator = coordinator
self._routine = routine
super().__init__(
coordinator,
EntityDescription(key=slugify(routine), name=routine),
)
async def async_press(self) -> None:
"""Handle button press action."""
await self._coordinator.api.call_routine(self._routine)
@@ -12,12 +12,13 @@ from aioamazondevices.structures import AmazonDevice
from aiohttp import ClientSession
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.util import slugify
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
@@ -64,6 +65,13 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
for identifier_domain, identifier in device.identifiers
if identifier_domain == DOMAIN
}
self.previous_routines: set[str] = {
routine.unique_id
for routine in er.async_entries_for_config_entry(
er.async_get(hass), entry.entry_id
)
if routine.domain == Platform.BUTTON
}
async def _async_update_data(self) -> dict[str, AmazonDevice]:
"""Update device data."""
@@ -92,8 +100,13 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
current_devices = set(data.keys())
if stale_devices := self.previous_devices - current_devices:
await self._async_remove_device_stale(stale_devices)
self.previous_devices = current_devices
current_routines = {slugify(routine) for routine in self.api.routines}
if stale_routines := self.previous_routines - current_routines:
await self._async_remove_routine_stale(stale_routines)
self.previous_routines = current_routines
return data
async def _async_remove_device_stale(
@@ -116,3 +129,23 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
device_id=device.id,
remove_config_entry_id=self.config_entry.entry_id,
)
async def _async_remove_routine_stale(
self,
stale_routines: set[str],
) -> None:
"""Remove stale routine."""
entity_registry = er.async_get(self.hass)
for routine in stale_routines:
_LOGGER.debug(
"Detected change in routines: routine %s removed",
routine,
)
entity_id = entity_registry.async_get_entity_id(
Platform.BUTTON,
DOMAIN,
f"{slugify(self.config_entry.unique_id)}-{slugify(routine)}",
)
if entity_id:
entity_registry.async_remove(entity_id)
@@ -2,9 +2,10 @@
from aioamazondevices.structures import AmazonDevice
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify
from .const import DOMAIN
from .coordinator import AmazonDevicesCoordinator
@@ -50,3 +51,32 @@ class AmazonEntity(CoordinatorEntity[AmazonDevicesCoordinator]):
and self._serial_num in self.coordinator.data
and self.device.online
)
class AmazonServiceEntity(CoordinatorEntity[AmazonDevicesCoordinator]):
"""Defines Alexa Devices entity for service device."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AmazonDevicesCoordinator,
description: EntityDescription,
) -> None:
"""Initialize the service entity."""
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, service_device_id(coordinator))},
manufacturer="Amazon",
entry_type=DeviceEntryType.SERVICE,
)
self.entity_description = description
self._attr_unique_id = (
f"{slugify(coordinator.config_entry.unique_id)}-{description.key}"
)
def service_device_id(coordinator: AmazonDevicesCoordinator) -> str:
"""Return service device id."""
return slugify(f"{coordinator.config_entry.unique_id}_service_device")
+1 -2
View File
@@ -39,7 +39,6 @@ from homeassistant.helpers.typing import ConfigType
from .binary_sensor import BINARY_SENSOR_KEYS, BINARY_SENSORS, check_binary_sensors
from .camera import STREAM_SOURCE_LIST
from .const import (
CAMERAS,
COMM_RETRIES,
COMM_TIMEOUT,
DATA_AMCREST,
@@ -359,7 +358,7 @@ def _start_event_monitor(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Amcrest IP Camera component."""
hass.data.setdefault(DATA_AMCREST, {DEVICES: {}, CAMERAS: []})
hass.data.setdefault(DATA_AMCREST, {DEVICES: {}})
for device in config[DOMAIN]:
name: str = device[CONF_NAME]
+10 -74
View File
@@ -12,13 +12,11 @@ import aiohttp
from aiohttp import web
from amcrest import AmcrestError
from haffmpeg.camera import CameraMjpeg
import voluptuous as vol
from homeassistant.components.camera import Camera, CameraEntityFeature
from homeassistant.components.ffmpeg import FFmpegManager, get_ffmpeg_manager
from homeassistant.const import ATTR_ENTITY_ID, CONF_NAME, STATE_OFF, STATE_ON
from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream,
async_aiohttp_proxy_web,
@@ -29,11 +27,13 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import (
ATTR_COLOR_BW,
CAMERA_WEB_SESSION_TIMEOUT,
CAMERAS,
CBW,
COMM_TIMEOUT,
DATA_AMCREST,
DEVICES,
MOV,
RESOLUTION_TO_STREAM,
SERVICE_UPDATE,
SNAPSHOT_TIMEOUT,
@@ -49,65 +49,11 @@ SCAN_INTERVAL = timedelta(seconds=15)
STREAM_SOURCE_LIST = ["snapshot", "mjpeg", "rtsp"]
_ATTR_PTZ_TT = "travel_time"
_ATTR_PTZ_MOV = "movement"
_MOV = [
"zoom_out",
"zoom_in",
"right",
"left",
"up",
"down",
"right_down",
"right_up",
"left_down",
"left_up",
]
_ZOOM_ACTIONS = ["ZoomWide", "ZoomTele"]
_MOVE_1_ACTIONS = ["Right", "Left", "Up", "Down"]
_MOVE_2_ACTIONS = ["RightDown", "RightUp", "LeftDown", "LeftUp"]
_ACTION = _ZOOM_ACTIONS + _MOVE_1_ACTIONS + _MOVE_2_ACTIONS
_DEFAULT_TT = 0.2
_ATTR_PRESET = "preset"
_ATTR_COLOR_BW = "color_bw"
_CBW_COLOR = "color"
_CBW_AUTO = "auto"
_CBW_BW = "bw"
_CBW = [_CBW_COLOR, _CBW_AUTO, _CBW_BW]
_SRV_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids})
_SRV_GOTO_SCHEMA = _SRV_SCHEMA.extend(
{vol.Required(_ATTR_PRESET): vol.All(vol.Coerce(int), vol.Range(min=1))}
)
_SRV_CBW_SCHEMA = _SRV_SCHEMA.extend({vol.Required(_ATTR_COLOR_BW): vol.In(_CBW)})
_SRV_PTZ_SCHEMA = _SRV_SCHEMA.extend(
{
vol.Required(_ATTR_PTZ_MOV): vol.In(_MOV),
vol.Optional(_ATTR_PTZ_TT, default=_DEFAULT_TT): cv.small_float,
}
)
CAMERA_SERVICES = {
"enable_recording": (_SRV_SCHEMA, "async_enable_recording", ()),
"disable_recording": (_SRV_SCHEMA, "async_disable_recording", ()),
"enable_audio": (_SRV_SCHEMA, "async_enable_audio", ()),
"disable_audio": (_SRV_SCHEMA, "async_disable_audio", ()),
"enable_motion_recording": (_SRV_SCHEMA, "async_enable_motion_recording", ()),
"disable_motion_recording": (_SRV_SCHEMA, "async_disable_motion_recording", ()),
"goto_preset": (_SRV_GOTO_SCHEMA, "async_goto_preset", (_ATTR_PRESET,)),
"set_color_bw": (_SRV_CBW_SCHEMA, "async_set_color_bw", (_ATTR_COLOR_BW,)),
"start_tour": (_SRV_SCHEMA, "async_start_tour", ()),
"stop_tour": (_SRV_SCHEMA, "async_stop_tour", ()),
"ptz_control": (
_SRV_PTZ_SCHEMA,
"async_ptz_control",
(_ATTR_PTZ_MOV, _ATTR_PTZ_TT),
),
}
_BOOL_TO_STATE = {True: STATE_ON, False: STATE_OFF}
@@ -275,7 +221,7 @@ class AmcrestCam(Camera):
self._motion_recording_enabled
)
if self._color_bw is not None:
attr[_ATTR_COLOR_BW] = self._color_bw
attr[ATTR_COLOR_BW] = self._color_bw
return attr
@property
@@ -322,15 +268,7 @@ class AmcrestCam(Camera):
self.async_schedule_update_ha_state(True)
async def async_added_to_hass(self) -> None:
"""Subscribe to signals and add camera to list."""
self._unsub_dispatcher.extend(
async_dispatcher_connect(
self.hass,
service_signal(service, self.entity_id),
getattr(self, callback_name),
)
for service, (_, callback_name, _) in CAMERA_SERVICES.items()
)
"""Subscribe to signals."""
self._unsub_dispatcher.append(
async_dispatcher_connect(
self.hass,
@@ -338,11 +276,9 @@ class AmcrestCam(Camera):
self.async_on_demand_update,
)
)
self.hass.data[DATA_AMCREST][CAMERAS].append(self.entity_id)
async def async_will_remove_from_hass(self) -> None:
"""Remove camera from list and disconnect from signals."""
self.hass.data[DATA_AMCREST][CAMERAS].remove(self.entity_id)
"""Disconnect from signals."""
for unsub_dispatcher in self._unsub_dispatcher:
unsub_dispatcher()
@@ -456,7 +392,7 @@ class AmcrestCam(Camera):
async def async_ptz_control(self, movement: str, travel_time: float) -> None:
"""Move or zoom camera in specified direction."""
code = _ACTION[_MOV.index(movement)]
code = _ACTION[MOV.index(movement)]
kwargs = {"code": code, "arg1": 0, "arg2": 0, "arg3": 0}
if code in _MOVE_1_ACTIONS:
@@ -613,10 +549,10 @@ class AmcrestCam(Camera):
)
async def _async_get_color_mode(self) -> str:
return _CBW[await self._api.async_day_night_color]
return CBW[await self._api.async_day_night_color]
async def _async_set_color_mode(self, cbw: str) -> None:
await self._api.async_set_day_night_color(_CBW.index(cbw), channel=0)
await self._api.async_set_day_night_color(CBW.index(cbw), channel=0)
async def _async_set_color_bw(self, cbw: str) -> None:
"""Set camera color mode."""
+15 -1
View File
@@ -2,7 +2,6 @@
DOMAIN = "amcrest"
DATA_AMCREST = DOMAIN
CAMERAS = "cameras"
DEVICES = "devices"
BINARY_SENSOR_SCAN_INTERVAL_SECS = 5
@@ -17,3 +16,18 @@ SERVICE_UPDATE = "update"
RESOLUTION_LIST = {"high": 0, "low": 1}
RESOLUTION_TO_STREAM = {0: "Main", 1: "Extra"}
ATTR_COLOR_BW = "color_bw"
CBW = ["color", "auto", "bw"]
MOV = [
"zoom_out",
"zoom_in",
"right",
"left",
"up",
"down",
"right_down",
"right_up",
"left_down",
"left_up",
]
+57 -52
View File
@@ -1,62 +1,67 @@
"""Support for Amcrest IP cameras."""
"""Services for Amcrest IP cameras."""
from __future__ import annotations
from homeassistant.auth.models import User
from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import Unauthorized, UnknownUser
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.service import async_extract_entity_ids
import voluptuous as vol
from .camera import CAMERA_SERVICES
from .const import CAMERAS, DATA_AMCREST, DOMAIN
from .helpers import service_signal
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, service
from .const import ATTR_COLOR_BW, CBW, DOMAIN, MOV
_ATTR_PRESET = "preset"
_ATTR_PTZ_MOV = "movement"
_ATTR_PTZ_TT = "travel_time"
_DEFAULT_TT = 0.2
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the Amcrest IP Camera services."""
for service_name, func in (
("enable_recording", "async_enable_recording"),
("disable_recording", "async_disable_recording"),
("enable_audio", "async_enable_audio"),
("disable_audio", "async_disable_audio"),
("enable_motion_recording", "async_enable_motion_recording"),
("disable_motion_recording", "async_disable_motion_recording"),
("start_tour", "async_start_tour"),
("stop_tour", "async_stop_tour"),
):
service.async_register_platform_entity_service(
hass,
DOMAIN,
service_name,
entity_domain=CAMERA_DOMAIN,
schema=None,
func=func,
)
def have_permission(user: User | None, entity_id: str) -> bool:
return not user or user.permissions.check_entity(entity_id, POLICY_CONTROL)
async def async_extract_from_service(call: ServiceCall) -> list[str]:
if call.context.user_id:
user = await hass.auth.async_get_user(call.context.user_id)
if user is None:
raise UnknownUser(context=call.context)
else:
user = None
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
# Return all entity_ids user has permission to control.
return [
entity_id
for entity_id in hass.data[DATA_AMCREST][CAMERAS]
if have_permission(user, entity_id)
]
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
return []
call_ids = await async_extract_entity_ids(call)
entity_ids = []
for entity_id in hass.data[DATA_AMCREST][CAMERAS]:
if entity_id not in call_ids:
continue
if not have_permission(user, entity_id):
raise Unauthorized(
context=call.context, entity_id=entity_id, permission=POLICY_CONTROL
)
entity_ids.append(entity_id)
return entity_ids
async def async_service_handler(call: ServiceCall) -> None:
args = [call.data[arg] for arg in CAMERA_SERVICES[call.service][2]]
for entity_id in await async_extract_from_service(call):
async_dispatcher_send(hass, service_signal(call.service, entity_id), *args)
for service, params in CAMERA_SERVICES.items():
hass.services.async_register(DOMAIN, service, async_service_handler, params[0])
service.async_register_platform_entity_service(
hass,
DOMAIN,
"goto_preset",
entity_domain=CAMERA_DOMAIN,
schema={vol.Required(_ATTR_PRESET): vol.All(vol.Coerce(int), vol.Range(min=1))},
func="async_goto_preset",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
"set_color_bw",
entity_domain=CAMERA_DOMAIN,
schema={vol.Required(ATTR_COLOR_BW): vol.In(CBW)},
func="async_set_color_bw",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
"ptz_control",
entity_domain=CAMERA_DOMAIN,
schema={
vol.Required(_ATTR_PTZ_MOV): vol.In(MOV),
vol.Optional(_ATTR_PTZ_TT, default=_DEFAULT_TT): cv.small_float,
},
func="async_ptz_control",
)
+411 -228
View File
@@ -1,7 +1,8 @@
"""Base entity for Anthropic."""
import base64
from collections.abc import AsyncGenerator, Callable, Iterable
from collections import deque
from collections.abc import AsyncIterator, Callable, Iterable
from dataclasses import dataclass, field
from datetime import UTC, datetime
import json
@@ -20,18 +21,22 @@ from anthropic.types import (
CitationWebSearchResultLocationParam,
CodeExecutionTool20250825Param,
CodeExecutionToolResultBlock,
CodeExecutionToolResultBlockContent,
CodeExecutionToolResultBlockParamContentParam,
Container,
ContentBlock,
ContentBlockParam,
DocumentBlockParam,
ImageBlockParam,
InputJSONDelta,
JSONOutputFormatParam,
Message,
MessageDeltaUsage,
MessageParam,
MessageStreamEvent,
ModelInfo,
OutputConfigParam,
RawContentBlockDelta,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
@@ -68,18 +73,30 @@ from anthropic.types import (
WebSearchTool20250305Param,
WebSearchTool20260209Param,
WebSearchToolResultBlock,
WebSearchToolResultBlockContent,
WebSearchToolResultBlockParamContentParam,
)
from anthropic.types.bash_code_execution_tool_result_block import (
Content as BashCodeExecutionToolResultBlockContent,
)
from anthropic.types.bash_code_execution_tool_result_block_param import (
Content as BashCodeExecutionToolResultBlockParamContentParam,
)
from anthropic.types.message_create_params import MessageCreateParamsStreaming
from anthropic.types.raw_message_delta_event import Delta
from anthropic.types.text_editor_code_execution_tool_result_block import (
Content as TextEditorCodeExecutionToolResultBlockContent,
)
from anthropic.types.text_editor_code_execution_tool_result_block_param import (
Content as TextEditorCodeExecutionToolResultBlockParamContentParam,
)
from anthropic.types.tool_search_tool_result_block import (
Content as ToolSearchToolResultBlockContent,
)
from anthropic.types.tool_search_tool_result_block_param import (
Content as ToolSearchToolResultBlockParamContentParam,
)
from anthropic.types.tool_use_block import Caller
import voluptuous as vol
from voluptuous_openapi import convert
@@ -91,7 +108,7 @@ from homeassistant.helpers import device_registry as dr, llm
from homeassistant.helpers.json import json_dumps
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import slugify
from homeassistant.util.json import JsonObjectType
from homeassistant.util.json import JsonArrayType, JsonObjectType
from .const import (
CONF_CHAT_MODEL,
@@ -445,13 +462,7 @@ def _convert_content( # noqa: C901
return messages, container_id
async def _transform_stream( # noqa: C901 - This is complex, but better to have it in one place
chat_log: conversation.ChatLog,
stream: AsyncStream[MessageStreamEvent],
output_tool: str | None = None,
) -> AsyncGenerator[
conversation.AssistantContentDeltaDict | conversation.ToolResultContentDeltaDict
]:
class AnthropicDeltaStream:
"""Transform the response stream into HA format.
A typical stream of responses might look something like the following:
@@ -481,201 +492,376 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
Each message could contain multiple blocks of the same type.
"""
if stream is None or not hasattr(stream, "__aiter__"):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unexpected_stream_object"
def __init__(
self,
chat_log: conversation.ChatLog,
stream: AsyncStream[MessageStreamEvent],
output_tool: str | None = None,
) -> None:
"""Initialize the delta stream."""
self._chat_log: conversation.ChatLog = chat_log
self._stream: AsyncStream[MessageStreamEvent] = stream
self._output_tool: str | None = output_tool
self._buffer: deque[
conversation.AssistantContentDeltaDict
| conversation.ToolResultContentDeltaDict
] = deque()
self._stream_iterator: AsyncIterator[MessageStreamEvent] | None = None
self._current_tool_block: ToolUseBlockParam | ServerToolUseBlockParam | None = (
None
)
self._current_tool_args: str = ""
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
self._input_usage: Usage | None = None
self._first_block: bool = True
current_tool_block: ToolUseBlockParam | ServerToolUseBlockParam | None = None
current_tool_args: str
content_details = ContentDetails()
content_details.add_citation_detail()
input_usage: Usage | None = None
first_block: bool = True
def __aiter__(
self,
) -> AsyncIterator[
conversation.AssistantContentDeltaDict | conversation.ToolResultContentDeltaDict
]:
"""Initialize the stream and return the async iterator."""
if self._stream is None or not hasattr(self._stream, "__aiter__"):
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="unexpected_stream_object"
)
if self._stream_iterator is None:
self._stream_iterator = self._stream.__aiter__()
return self
async for response in stream:
LOGGER.debug("Received response: %s", response)
async def __anext__(
self,
) -> (
conversation.AssistantContentDeltaDict | conversation.ToolResultContentDeltaDict
):
"""Get the next item from the stream."""
while True:
if self._buffer:
return self._buffer.popleft()
if isinstance(response, RawMessageStartEvent):
input_usage = response.message.usage
first_block = True
elif isinstance(response, RawContentBlockStartEvent):
if isinstance(response.content_block, ToolUseBlock):
current_tool_block = ToolUseBlockParam(
type="tool_use",
id=response.content_block.id,
name=response.content_block.name,
input=response.content_block.input or {},
)
current_tool_args = ""
if response.content_block.name == output_tool:
if first_block or content_details.has_content():
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
content_details.add_citation_detail()
yield {"role": "assistant"}
first_block = False
elif isinstance(response.content_block, TextBlock):
if ( # Do not start a new assistant content just for citations, concatenate consecutive blocks with citations instead.
first_block
or (
not content_details.has_citations()
and response.content_block.citations is None
and content_details.has_content()
)
):
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
yield {"role": "assistant"}
first_block = False
content_details.add_citation_detail()
if response.content_block.text:
content_details.citation_details[-1].length += len(
response.content_block.text
)
yield {"content": response.content_block.text}
elif isinstance(response.content_block, ThinkingBlock):
if first_block or content_details.thinking_signature:
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
content_details.add_citation_detail()
yield {"role": "assistant"}
first_block = False
elif isinstance(response.content_block, RedactedThinkingBlock):
LOGGER.debug(
"Some of Claudes internal reasoning has been automatically "
"encrypted for safety reasons. This doesnt affect the quality of "
"responses"
)
if first_block or content_details.redacted_thinking:
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
content_details.add_citation_detail()
yield {"role": "assistant"}
first_block = False
content_details.redacted_thinking = response.content_block.data
elif isinstance(response.content_block, ServerToolUseBlock):
current_tool_block = ServerToolUseBlockParam(
type="server_tool_use",
id=response.content_block.id,
name=response.content_block.name,
input=response.content_block.input or {},
)
current_tool_args = ""
elif isinstance(
response.content_block,
(
WebSearchToolResultBlock,
CodeExecutionToolResultBlock,
BashCodeExecutionToolResultBlock,
TextEditorCodeExecutionToolResultBlock,
ToolSearchToolResultBlock,
),
):
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
content_details.add_citation_detail()
yield {
"role": "tool_result",
"tool_call_id": response.content_block.tool_use_id,
"tool_name": response.content_block.type.removesuffix(
"_tool_result"
),
"tool_result": {
"content": cast(
JsonObjectType, response.content_block.to_dict()["content"]
)
}
if isinstance(response.content_block.content, list)
else cast(JsonObjectType, response.content_block.content.to_dict()),
response = await self._stream_iterator.__anext__() # type: ignore[union-attr]
LOGGER.debug("Received response: %s", response)
self.on_message_stream_event(response)
def on_message_stream_event(self, event: MessageStreamEvent) -> None:
"""Handle MessageStreamEvent."""
if isinstance(event, RawMessageStartEvent):
self.on_message_start_event(event.message)
return
if isinstance(event, RawContentBlockStartEvent):
self.on_content_block_start_event(event.content_block, event.index)
return
if isinstance(event, RawContentBlockDeltaEvent):
self.on_content_block_delta_event(event.delta)
return
if isinstance(event, RawContentBlockStopEvent):
self.on_content_block_stop_event(event.index)
return
if isinstance(event, RawMessageDeltaEvent):
self.on_message_delta_event(event.delta, event.usage)
return
if isinstance(event, RawMessageStopEvent):
self.on_message_stop_event()
return
LOGGER.debug("Unhandled event type: %s", event.type) # type: ignore[unreachable] # pragma: no cover - All types are handled but we want to verify that
def on_message_start_event(self, message: Message) -> None:
"""Handle RawMessageStartEvent."""
self._input_usage = message.usage
self._first_block = True
def on_content_block_start_event(
self, content_block: ContentBlock, index: int
) -> None:
"""Handle RawContentBlockStartEvent."""
if isinstance(content_block, ToolUseBlock):
self.on_tool_use_block(
content_block.id,
content_block.input,
content_block.name,
content_block.caller,
)
return
if isinstance(content_block, TextBlock):
self.on_text_block(content_block.text, content_block.citations)
return
if isinstance(content_block, ThinkingBlock):
self.on_thinking_block(content_block.thinking, content_block.signature)
return
if isinstance(content_block, RedactedThinkingBlock):
self.on_redacted_thinking_block(content_block.data)
return
if isinstance(content_block, ServerToolUseBlock):
self.on_server_tool_use_block(
content_block.id,
content_block.name,
content_block.input,
content_block.caller,
)
return
if isinstance(
content_block,
(
WebSearchToolResultBlock,
CodeExecutionToolResultBlock,
BashCodeExecutionToolResultBlock,
TextEditorCodeExecutionToolResultBlock,
ToolSearchToolResultBlock,
),
):
self.on_server_tool_result_block(
content_block.tool_use_id,
content_block.type,
content_block.content,
content_block.caller if hasattr(content_block, "caller") else None,
)
return
LOGGER.debug("Unhandled content block type: %s", content_block.type)
def on_tool_use_block(
self, id: str, input: dict[str, Any], name: str, caller: Caller | None
) -> None:
"""Handle ToolUseBlock."""
self._current_tool_block = ToolUseBlockParam(
type="tool_use",
id=id,
name=name,
input=input,
)
self._current_tool_args = ""
if name == self._output_tool:
if self._first_block or self._content_details.has_content():
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
self._buffer.append({"role": "assistant"})
self._first_block = False
def on_text_block(self, text: str, citations: list[TextCitation] | None) -> None:
"""Handle TextBlock."""
if ( # Do not start a new assistant content just for citations, concatenate consecutive blocks with citations instead.
self._first_block
or (
not self._content_details.has_citations()
and citations is None
and self._content_details.has_content()
)
):
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._buffer.append({"role": "assistant"})
self._first_block = False
self._content_details.add_citation_detail()
if text:
self._content_details.citation_details[-1].length += len(text)
self._buffer.append({"content": text})
def on_thinking_block(self, thinking: str, signature: str) -> None:
"""Handle ThinkingBlock."""
if self._first_block or self._content_details.thinking_signature:
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
self._buffer.append({"role": "assistant"})
self._first_block = False
def on_redacted_thinking_block(self, data: str) -> None:
"""Handle RedactedThinkingBlock."""
LOGGER.debug(
"Some of Claudes internal reasoning has been automatically "
"encrypted for safety reasons. This doesnt affect the quality of "
"responses"
)
if self._first_block or self._content_details.redacted_thinking:
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
self._buffer.append({"role": "assistant"})
self._first_block = False
self._content_details.redacted_thinking = data
def on_server_tool_use_block(
self,
id: str,
name: Literal[
"web_search",
"web_fetch",
"code_execution",
"bash_code_execution",
"text_editor_code_execution",
"tool_search_tool_regex",
"tool_search_tool_bm25",
],
input: dict[str, Any],
caller: Caller | None,
) -> None:
"""Handle ServerToolUseBlock."""
self._current_tool_block = ServerToolUseBlockParam(
type="server_tool_use",
id=id,
name=name,
input=input,
)
self._current_tool_args = ""
def on_server_tool_result_block(
self,
tool_use_id: str,
tool_name: Literal[
"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
| CodeExecutionToolResultBlockContent
| BashCodeExecutionToolResultBlockContent
| TextEditorCodeExecutionToolResultBlockContent
| ToolSearchToolResultBlockContent,
caller: Caller | None,
) -> None:
"""Handle various server tool result blocks."""
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
self._buffer.append(
{
"role": "tool_result",
"tool_call_id": tool_use_id,
"tool_name": tool_name.removesuffix("_tool_result"),
"tool_result": {
"content": cast(JsonArrayType, [x.to_dict() for x in content])
}
first_block = True
elif isinstance(response, RawContentBlockDeltaEvent):
if isinstance(response.delta, InputJSONDelta):
if (
current_tool_block is not None
and current_tool_block["name"] == output_tool
):
content_details.citation_details[-1].length += len(
response.delta.partial_json
)
yield {"content": response.delta.partial_json}
else:
current_tool_args += response.delta.partial_json
elif isinstance(response.delta, TextDelta):
if response.delta.text:
content_details.citation_details[-1].length += len(
response.delta.text
)
yield {"content": response.delta.text}
elif isinstance(response.delta, ThinkingDelta):
if response.delta.thinking:
yield {"thinking_content": response.delta.thinking}
elif isinstance(response.delta, SignatureDelta):
content_details.thinking_signature = response.delta.signature
elif isinstance(response.delta, CitationsDelta):
content_details.add_citation(response.delta.citation)
elif isinstance(response, RawContentBlockStopEvent):
if current_tool_block is not None:
if current_tool_block["name"] == output_tool:
current_tool_block = None
continue
tool_args = json.loads(current_tool_args) if current_tool_args else {}
current_tool_block["input"] |= tool_args
yield {
if isinstance(content, list)
else cast(JsonObjectType, content.to_dict()),
}
)
self._first_block = True
def on_content_block_delta_event(self, delta: RawContentBlockDelta) -> None:
"""Handle RawContentBlockDeltaEvent."""
if isinstance(delta, InputJSONDelta):
self.on_input_json_delta(delta.partial_json)
return
if isinstance(delta, TextDelta):
self.on_text_delta(delta.text)
return
if isinstance(delta, ThinkingDelta):
self.on_thinking_delta(delta.thinking)
return
if isinstance(delta, SignatureDelta):
self.on_signature_delta(delta.signature)
return
if isinstance(delta, CitationsDelta):
self.on_citations_delta(delta.citation)
return
LOGGER.debug("Unhandled content delta type: %s", delta.type) # type: ignore[unreachable] # pragma: no cover - All types are handled but we want to verify that
def on_input_json_delta(self, partial_json: str) -> None:
"""Handle InputJSONDelta."""
if (
self._current_tool_block is not None
and self._current_tool_block["name"] == self._output_tool
):
self._content_details.citation_details[-1].length += len(partial_json)
self._buffer.append({"content": partial_json})
else:
self._current_tool_args += partial_json
def on_text_delta(self, text: str) -> None:
"""Handle TextDelta."""
if text:
self._content_details.citation_details[-1].length += len(text)
self._buffer.append({"content": text})
def on_thinking_delta(self, thinking: str) -> None:
"""Handle ThinkingDelta."""
if thinking:
self._buffer.append({"thinking_content": thinking})
def on_signature_delta(self, signature: str) -> None:
"""Handle SignatureDelta."""
self._content_details.thinking_signature = signature
def on_citations_delta(self, citation: TextCitation) -> None:
"""Handle CitationsDelta."""
self._content_details.add_citation(citation)
def on_content_block_stop_event(self, index: int) -> None:
"""Handle RawContentBlockStopEvent."""
if self._current_tool_block is not None:
if self._current_tool_block["name"] == self._output_tool:
self._current_tool_block = None
return
tool_args = (
json.loads(self._current_tool_args) if self._current_tool_args else {}
)
self._current_tool_block["input"] |= tool_args
self._buffer.append(
{
"tool_calls": [
llm.ToolInput(
id=current_tool_block["id"],
tool_name=current_tool_block["name"],
tool_args=current_tool_block["input"],
external=current_tool_block["type"] == "server_tool_use",
id=self._current_tool_block["id"],
tool_name=self._current_tool_block["name"],
tool_args=self._current_tool_block["input"],
external=self._current_tool_block["type"]
== "server_tool_use",
)
]
}
current_tool_block = None
elif isinstance(response, RawMessageDeltaEvent):
if (usage := response.usage) is not None:
chat_log.async_trace(_create_token_stats(input_usage, usage))
content_details.container = response.delta.container
if response.delta.stop_reason == "refusal":
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="api_refusal"
)
elif isinstance(response, RawMessageStopEvent):
if content_details:
content_details.delete_empty()
yield {"native": content_details}
content_details = ContentDetails()
content_details.add_citation_detail()
)
self._current_tool_block = None
def on_message_delta_event(self, delta: Delta, usage: MessageDeltaUsage) -> None:
"""Handle RawMessageDeltaEvent."""
self._chat_log.async_trace(self._create_token_stats(self._input_usage, usage))
self._content_details.container = delta.container
if delta.stop_reason == "refusal":
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="api_refusal"
)
def _create_token_stats(
input_usage: Usage | None, response_usage: MessageDeltaUsage
) -> dict[str, Any]:
"""Create token stats for conversation agent tracing."""
input_tokens = 0
cached_input_tokens = 0
if input_usage:
input_tokens = input_usage.input_tokens
cached_input_tokens = input_usage.cache_creation_input_tokens or 0
output_tokens = response_usage.output_tokens
return {
"stats": {
"input_tokens": input_tokens,
"cached_input_tokens": cached_input_tokens,
"output_tokens": output_tokens,
def on_message_stop_event(self) -> None:
"""Handle RawMessageStopEvent."""
if self._content_details:
self._content_details.delete_empty()
self._buffer.append({"native": self._content_details})
self._content_details = ContentDetails()
self._content_details.add_citation_detail()
def _create_token_stats(
self, input_usage: Usage | None, response_usage: MessageDeltaUsage
) -> dict[str, Any]:
"""Create token stats for conversation agent tracing."""
input_tokens = 0
cached_input_tokens = 0
if input_usage:
input_tokens = input_usage.input_tokens
cached_input_tokens = input_usage.cache_creation_input_tokens or 0
output_tokens = response_usage.output_tokens
return {
"stats": {
"input_tokens": input_tokens,
"cached_input_tokens": cached_input_tokens,
"output_tokens": output_tokens,
}
}
}
class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
@@ -703,15 +889,14 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
entry_type=dr.DeviceEntryType.SERVICE,
)
async def _async_handle_chat_log( # noqa: C901
async def _get_model_args( # noqa: C901
self,
chat_log: conversation.ChatLog,
structure_name: str | None = None,
structure: vol.Schema | None = None,
max_iterations: int = MAX_TOOL_ITERATIONS,
) -> None:
"""Generate an answer for the chat log."""
options = self.subentry.data
) -> tuple[MessageCreateParamsStreaming, str | None]:
"""Get the model arguments."""
options: dict[str, Any] = DEFAULT | self.subentry.data
preloaded_tools = [
"HassTurnOn",
@@ -729,21 +914,18 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
messages, container_id = _convert_content(chat_log.content[1:])
model = options.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
model = options[CONF_CHAT_MODEL]
model_args = MessageCreateParamsStreaming(
model=model,
messages=messages,
max_tokens=options.get(CONF_MAX_TOKENS, DEFAULT[CONF_MAX_TOKENS]),
max_tokens=options[CONF_MAX_TOKENS],
system=system.content,
stream=True,
container=container_id,
)
if (
options.get(CONF_PROMPT_CACHING, DEFAULT[CONF_PROMPT_CACHING])
== PromptCaching.PROMPT
):
if options[CONF_PROMPT_CACHING] == PromptCaching.PROMPT:
model_args["system"] = [
{
"type": "text",
@@ -751,19 +933,14 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
"cache_control": {"type": "ephemeral"},
}
]
elif (
options.get(CONF_PROMPT_CACHING, DEFAULT[CONF_PROMPT_CACHING])
== PromptCaching.AUTOMATIC
):
elif options[CONF_PROMPT_CACHING] == PromptCaching.AUTOMATIC:
model_args["cache_control"] = {"type": "ephemeral"}
if (
self.model_info.capabilities
and self.model_info.capabilities.thinking.types.adaptive.supported
):
thinking_effort = options.get(
CONF_THINKING_EFFORT, DEFAULT[CONF_THINKING_EFFORT]
)
thinking_effort = options[CONF_THINKING_EFFORT]
if thinking_effort != "none":
model_args["thinking"] = ThinkingConfigAdaptiveParam(
type="adaptive", display="summarized"
@@ -772,9 +949,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
else:
thinking_budget = options.get(
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
)
thinking_budget = options[CONF_THINKING_BUDGET]
if (
self.model_info.capabilities
and self.model_info.capabilities.thinking.types.enabled.supported
@@ -791,9 +966,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
and self.model_info.capabilities.effort.supported
):
model_args["output_config"] = OutputConfigParam(
effort=options.get(
CONF_THINKING_EFFORT, DEFAULT[CONF_THINKING_EFFORT]
)
effort=options[CONF_THINKING_EFFORT]
)
tools: list[ToolUnionParam] = []
@@ -803,12 +976,12 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
for tool in chat_log.llm_api.tools
]
if options.get(CONF_CODE_EXECUTION):
if options[CONF_CODE_EXECUTION]:
# The `web_search_20260209` tool automatically enables `code_execution_20260120` tool
if (
not self.model_info.capabilities
or not self.model_info.capabilities.code_execution.supported
or not options.get(CONF_WEB_SEARCH)
or not options[CONF_WEB_SEARCH]
):
tools.append(
CodeExecutionTool20250825Param(
@@ -817,26 +990,26 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
),
)
if options.get(CONF_WEB_SEARCH):
if options[CONF_WEB_SEARCH]:
if (
not self.model_info.capabilities
or not self.model_info.capabilities.code_execution.supported
or not options.get(CONF_CODE_EXECUTION)
or not options[CONF_CODE_EXECUTION]
):
web_search: WebSearchTool20250305Param | WebSearchTool20260209Param = (
WebSearchTool20250305Param(
name="web_search",
type="web_search_20250305",
max_uses=options.get(CONF_WEB_SEARCH_MAX_USES),
max_uses=options[CONF_WEB_SEARCH_MAX_USES],
)
)
else:
web_search = WebSearchTool20260209Param(
name="web_search",
type="web_search_20260209",
max_uses=options.get(CONF_WEB_SEARCH_MAX_USES),
max_uses=options[CONF_WEB_SEARCH_MAX_USES],
)
if options.get(CONF_WEB_SEARCH_USER_LOCATION):
if options[CONF_WEB_SEARCH_USER_LOCATION]:
web_search["user_location"] = {
"type": "approximate",
"city": options.get(CONF_WEB_SEARCH_CITY, ""),
@@ -937,10 +1110,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
preloaded_tools.append(structure_name)
if tools:
if (
options.get(CONF_TOOL_SEARCH, DEFAULT[CONF_TOOL_SEARCH])
and len(tools) > len(preloaded_tools) + 1
):
if options[CONF_TOOL_SEARCH] and len(tools) > len(preloaded_tools) + 1:
for tool in tools:
if not tool["name"].endswith(tuple(preloaded_tools)):
tool["defer_loading"] = True
@@ -953,6 +1123,19 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
model_args["tools"] = tools
return model_args, structure_name
async def _async_handle_chat_log(
self,
chat_log: conversation.ChatLog,
structure_name: str | None = None,
structure: vol.Schema | None = None,
max_iterations: int = MAX_TOOL_ITERATIONS,
) -> None:
"""Generate an answer for the chat log."""
model_args, structure_name = await self._get_model_args(
chat_log, structure_name, structure
)
coordinator = self.entry.runtime_data
client = coordinator.client
@@ -966,7 +1149,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
content
async for content in chat_log.async_add_delta_content_stream(
self.entity_id,
_transform_stream(
AnthropicDeltaStream(
chat_log,
stream,
output_tool=structure_name or None,
@@ -974,7 +1157,7 @@ class AnthropicBaseLLMEntity(CoordinatorEntity[AnthropicCoordinator]):
)
]
)
messages.extend(new_messages)
cast(list[MessageParam], model_args["messages"]).extend(new_messages)
except anthropic.AuthenticationError as err:
# Trigger coordinator to confirm the auth failure and trigger the reauth flow.
await coordinator.async_request_refresh()
@@ -155,7 +155,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.data[DATA_COMPONENT] = storage_collection
collection.DictStorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
storage_collection,
DOMAIN,
DOMAIN,
CREATE_FIELDS,
UPDATE_FIELDS,
admin_only=True,
).async_setup(hass)
websocket_api.async_register_command(hass, handle_integration_list)
@@ -341,6 +346,7 @@ async def handle_integration_list(
vol.Required("config_entry_id"): str,
}
)
@websocket_api.require_admin
@websocket_api.async_response
async def handle_config_entry(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
+1 -1
View File
@@ -28,7 +28,7 @@ class AquacellEntity(CoordinatorEntity[AquacellCoordinator]):
self._attr_unique_id = f"{softener_key}-{entity_key}"
self._attr_device_info = DeviceInfo(
name=self.softener.name,
hw_version=self.softener.fwVersion,
hw_version=self.softener.diagnostics.fw_version,
identifiers={(DOMAIN, str(softener_key))},
manufacturer=self.softener.brand,
model=self.softener.ssn,
@@ -8,5 +8,5 @@
"integration_type": "device",
"iot_class": "cloud_polling",
"loggers": ["aioaquacell"],
"requirements": ["aioaquacell==0.2.0"]
"requirements": ["aioaquacell==1.0.0"]
}
+7 -7
View File
@@ -38,39 +38,39 @@ SENSORS: tuple[SoftenerSensorEntityDescription, ...] = (
translation_key="salt_left_side_percentage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda softener: softener.salt.leftPercent,
value_fn=lambda softener: softener.salt.left_percent,
),
SoftenerSensorEntityDescription(
key="salt_right_side_percentage",
translation_key="salt_right_side_percentage",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda softener: softener.salt.rightPercent,
value_fn=lambda softener: softener.salt.right_percent,
),
SoftenerSensorEntityDescription(
key="salt_left_side_time_remaining",
translation_key="salt_left_side_time_remaining",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.DAYS,
value_fn=lambda softener: softener.salt.leftDays,
value_fn=lambda softener: softener.salt.left_days,
),
SoftenerSensorEntityDescription(
key="salt_right_side_time_remaining",
translation_key="salt_right_side_time_remaining",
device_class=SensorDeviceClass.DURATION,
native_unit_of_measurement=UnitOfTime.DAYS,
value_fn=lambda softener: softener.salt.rightDays,
value_fn=lambda softener: softener.salt.right_days,
),
SoftenerSensorEntityDescription(
key="battery",
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
value_fn=lambda softener: softener.battery,
value_fn=lambda softener: softener.diagnostics.battery,
),
SoftenerSensorEntityDescription(
key="wi_fi_strength",
translation_key="wi_fi_strength",
value_fn=lambda softener: softener.wifiLevel,
value_fn=lambda softener: softener.diagnostics.wifi_level,
device_class=SensorDeviceClass.ENUM,
options=[
"high",
@@ -82,7 +82,7 @@ SENSORS: tuple[SoftenerSensorEntityDescription, ...] = (
key="last_update",
translation_key="last_update",
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda softener: softener.lastUpdate,
value_fn=lambda softener: softener.diagnostics.last_update,
),
)
+29 -18
View File
@@ -4,8 +4,9 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from arcam.fmj import IncomingVideoAspectRatio, IncomingVideoColorspace
from arcam.fmj import IncomingVideoAspectRatio, IncomingVideoColorspace, IntOrTypeEnum
from arcam.fmj.state import IncomingAudioConfig, IncomingAudioFormat, State
from homeassistant.components.sensor import (
@@ -21,6 +22,25 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ArcamFmjConfigEntry
from .entity import ArcamFmjEntity
_LOGGER = logging.getLogger(__name__)
def _enum_options(value: type[IntOrTypeEnum]) -> list[str]:
return [
member.name.lower() for member in value if not member.name.startswith("CODE_")
]
def _enum_value(value: IntOrTypeEnum | None) -> str | None:
if value is None:
return None
if value.name.startswith("CODE_"):
_LOGGER.debug("Undefined enum value %s ignored", value)
return None
return value.name.lower()
@dataclass(frozen=True, kw_only=True)
class ArcamFmjSensorEntityDescription(SensorEntityDescription):
@@ -75,9 +95,9 @@ SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
translation_key="incoming_video_aspect_ratio",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingVideoAspectRatio],
options=_enum_options(IncomingVideoAspectRatio),
value_fn=lambda state: (
vp.aspect_ratio.name.lower()
_enum_value(vp.aspect_ratio)
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
@@ -87,11 +107,10 @@ SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
translation_key="incoming_video_colorspace",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingVideoColorspace],
options=_enum_options(IncomingVideoColorspace),
value_fn=lambda state: (
vp.colorspace.name.lower()
_enum_value(vp.colorspace)
if (vp := state.get_incoming_video_parameters()) is not None
and vp.colorspace is not None
else None
),
),
@@ -100,24 +119,16 @@ SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
translation_key="incoming_audio_format",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingAudioFormat],
value_fn=lambda state: (
result.name.lower()
if (result := state.get_incoming_audio_format()[0]) is not None
else None
),
options=_enum_options(IncomingAudioFormat),
value_fn=lambda state: _enum_value(state.get_incoming_audio_format()[0]),
),
ArcamFmjSensorEntityDescription(
key="incoming_audio_config",
translation_key="incoming_audio_config",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingAudioConfig],
value_fn=lambda state: (
result.name.lower()
if (result := state.get_incoming_audio_format()[1]) is not None
else None
),
options=_enum_options(IncomingAudioConfig),
value_fn=lambda state: _enum_value(state.get_incoming_audio_format()[1]),
),
ArcamFmjSensorEntityDescription(
key="incoming_audio_sample_rate",
@@ -945,7 +945,10 @@ class PipelineRun:
try:
# Transcribe audio stream
stt_vad: VoiceCommandSegmenter | None = None
if self.audio_settings.is_vad_enabled:
if (
self.audio_settings.is_vad_enabled
and self.stt_provider.audio_processing.requires_external_vad
):
stt_vad = VoiceCommandSegmenter(
silence_seconds=self.audio_settings.silence_seconds
)
@@ -13,11 +13,12 @@ from hassil.util import (
)
import voluptuous as vol
from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL
from homeassistant.components.http import StaticPathConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
from homeassistant.exceptions import HomeAssistantError
from homeassistant.exceptions import HomeAssistantError, Unauthorized, UnknownUser
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -103,6 +104,22 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def handle_ask_question(call: ServiceCall) -> dict[str, Any]:
"""Handle a Show View service call."""
satellite_entity_id: str = call.data[ATTR_ENTITY_ID]
if call.context.user_id:
user = await hass.auth.async_get_user(call.context.user_id)
if user is None:
raise UnknownUser(
context=call.context,
permission=POLICY_CONTROL,
user_id=call.context.user_id,
)
if not user.permissions.check_entity(satellite_entity_id, POLICY_CONTROL):
raise Unauthorized(
context=call.context,
permission=POLICY_CONTROL,
user_id=call.context.user_id,
perm_category=CAT_ENTITIES,
)
satellite_entity: AssistSatelliteEntity | None = component.get_entity(
satellite_entity_id
)
@@ -7,17 +7,13 @@ from .const import DOMAIN
from .entity import AssistSatelliteState
CONDITIONS: dict[str, type[Condition]] = {
"is_idle": make_entity_state_condition(
DOMAIN, AssistSatelliteState.IDLE, support_duration=True
),
"is_listening": make_entity_state_condition(
DOMAIN, AssistSatelliteState.LISTENING, support_duration=True
),
"is_idle": make_entity_state_condition(DOMAIN, AssistSatelliteState.IDLE),
"is_listening": make_entity_state_condition(DOMAIN, AssistSatelliteState.LISTENING),
"is_processing": make_entity_state_condition(
DOMAIN, AssistSatelliteState.PROCESSING, support_duration=True
DOMAIN, AssistSatelliteState.PROCESSING
),
"is_responding": make_entity_state_condition(
DOMAIN, AssistSatelliteState.RESPONDING, support_duration=True
DOMAIN, AssistSatelliteState.RESPONDING
),
}
@@ -7,11 +7,8 @@
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
automation_behavior:
mode: condition
for:
required: true
default: 00:00:00
@@ -72,19 +72,6 @@
"id": "Answer ID",
"sentences": "Sentences"
}
},
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
@@ -7,12 +7,8 @@
required: true
default: any
selector:
select:
options:
- first
- last
- any
translation_key: trigger_behavior
automation_behavior:
mode: trigger
for:
required: true
default: 00:00:00
@@ -165,6 +165,7 @@ async def websocket_set_wake_words(
vol.Required("entity_id"): cv.entity_domain(DOMAIN),
}
)
@websocket_api.require_admin
@websocket_api.async_response
async def websocket_test_connection(
hass: HomeAssistant,
+18 -25
View File
@@ -15,24 +15,6 @@ from homeassistant.data_entry_flow import FlowContext
from homeassistant.helpers import config_validation as cv
from homeassistant.util.hass_dict import HassKey
WS_TYPE_SETUP_MFA = "auth/setup_mfa"
SCHEMA_WS_SETUP_MFA = vol.All(
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{
vol.Required("type"): WS_TYPE_SETUP_MFA,
vol.Exclusive("mfa_module_id", "module_or_flow_id"): str,
vol.Exclusive("flow_id", "module_or_flow_id"): str,
vol.Optional("user_input"): object,
}
),
cv.has_at_least_one_key("mfa_module_id", "flow_id"),
)
WS_TYPE_DEPOSE_MFA = "auth/depose_mfa"
SCHEMA_WS_DEPOSE_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
{vol.Required("type"): WS_TYPE_DEPOSE_MFA, vol.Required("mfa_module_id"): str}
)
DATA_SETUP_FLOW_MGR: HassKey[MfaFlowManager] = HassKey("auth_mfa_setup_flow_manager")
_LOGGER = logging.getLogger(__name__)
@@ -73,16 +55,24 @@ def async_setup(hass: HomeAssistant) -> None:
"""Init mfa setup flow manager."""
hass.data[DATA_SETUP_FLOW_MGR] = MfaFlowManager(hass)
websocket_api.async_register_command(
hass, WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA
)
websocket_api.async_register_command(
hass, WS_TYPE_DEPOSE_MFA, websocket_depose_mfa, SCHEMA_WS_DEPOSE_MFA
)
websocket_api.async_register_command(hass, websocket_setup_mfa)
websocket_api.async_register_command(hass, websocket_depose_mfa)
@callback
@websocket_api.websocket_command(
vol.All(
vol.Schema(
{
vol.Required("type"): "auth/setup_mfa",
vol.Exclusive("mfa_module_id", "module_or_flow_id"): str,
vol.Exclusive("flow_id", "module_or_flow_id"): str,
vol.Optional("user_input"): object,
}
),
cv.has_at_least_one_key("mfa_module_id", "flow_id"),
)
)
@websocket_api.ws_require_user(allow_system_user=False)
def websocket_setup_mfa(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
@@ -121,6 +111,9 @@ def websocket_setup_mfa(
@callback
@websocket_api.websocket_command(
{vol.Required("type"): "auth/depose_mfa", vol.Required("mfa_module_id"): str}
)
@websocket_api.ws_require_user(allow_system_user=False)
def websocket_depose_mfa(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
@@ -4,10 +4,10 @@ from __future__ import annotations
from abc import ABC, abstractmethod
import asyncio
from collections.abc import Callable, Mapping
from collections.abc import Callable
from dataclasses import dataclass
import logging
from typing import Any, Protocol, cast
from typing import Any, cast
from propcache.api import cached_property
import voluptuous as vol
@@ -194,6 +194,7 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"switch",
"temperature",
"text",
"timer",
"todo",
"update",
"vacuum",
@@ -229,14 +230,11 @@ def is_disabled_experimental_trigger(hass: HomeAssistant, platform: str) -> bool
)
class IfAction(Protocol):
class IfAction(condition_helper.ConditionsChecker):
"""Define the format of if_action."""
config: list[ConfigType]
def __call__(self, variables: Mapping[str, Any] | None = None) -> bool:
"""AND all conditions."""
def is_on(hass: HomeAssistant, entity_id: str) -> bool:
"""Return true if specified automation entity_id is on.
@@ -835,7 +833,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
if (
not skip_condition
and self._condition is not None
and not self._condition(variables)
and not self._condition.async_check(variables=variables)
):
self._logger.debug(
"Conditions not met, aborting automation. Condition summary: %s",
@@ -903,7 +901,15 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity):
async def async_will_remove_from_hass(self) -> None:
"""Remove listeners when removing automation from Home Assistant."""
await super().async_will_remove_from_hass()
await self._async_disable()
if self.registry_entry and self.registry_entry.entity_id != self.entity_id:
# Entity ID change, do not unload the script or conditions as they will
# be reused.
await self._async_disable()
return
await self._async_disable(stop_actions=False)
await self.action_script.async_unload()
if self._condition is not None:
self._condition.async_unload()
async def _async_enable_automation(self, event: Event) -> None:
"""Start automation on startup."""
@@ -1276,6 +1282,7 @@ async def _async_process_if(
@websocket_api.websocket_command({"type": "automation/config", "entity_id": str})
@websocket_api.require_admin
def websocket_config(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
+7 -1
View File
@@ -18,4 +18,10 @@ DEFAULT_STREAM_PROFILE = "No stream profile"
DEFAULT_TRIGGER_TIME = 0
DEFAULT_VIDEO_SOURCE = "No video source"
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CAMERA, Platform.LIGHT, Platform.SWITCH]
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.CAMERA,
Platform.EVENT,
Platform.LIGHT,
Platform.SWITCH,
]
+62
View File
@@ -0,0 +1,62 @@
"""Support for Axis event entities."""
from __future__ import annotations
from dataclasses import dataclass
from axis.models.event import Event, EventTopic
from homeassistant.components.event import (
DoorbellEventType,
EventDeviceClass,
EventEntity,
EventEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AxisConfigEntry
from .entity import AxisEventDescription, AxisEventEntity
DOORBELL_CONFIG = ("I8116-E", "0")
@dataclass(frozen=True, kw_only=True)
class AxisEventPlatformDescription(AxisEventDescription, EventEntityDescription):
"""Axis event entity description."""
ENTITY_DESCRIPTIONS = (
AxisEventPlatformDescription(
key="Doorbell",
device_class=EventDeviceClass.DOORBELL,
event_types=[DoorbellEventType.RING],
event_topic=EventTopic.PORT_INPUT,
name_fn=lambda _hub, _event: "Doorbell",
supported_fn=lambda hub, event: (hub.config.model, event.id) == DOORBELL_CONFIG,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AxisConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up an Axis event platform."""
config_entry.runtime_data.entity_loader.register_platform(
async_add_entities, AxisEvent, ENTITY_DESCRIPTIONS
)
class AxisEvent(AxisEventEntity, EventEntity):
"""Representation of an Axis event entity."""
entity_description: AxisEventPlatformDescription
@callback
def async_event_callback(self, event: Event) -> None:
"""Handle Axis event updates."""
if event.is_tripped:
self._trigger_event(DoorbellEventType.RING)
self.async_write_ha_state()
+1
View File
@@ -36,6 +36,7 @@ async def get_axis_api(
username=config[CONF_USERNAME],
password=config[CONF_PASSWORD],
web_proto=config.get(CONF_PROTOCOL, "http"),
websocket_enabled=True,
)
)
+1 -1
View File
@@ -29,7 +29,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["axis"],
"requirements": ["axis==68"],
"requirements": ["axis==69"],
"ssdp": [
{
"manufacturer": "AXIS"
+6 -3
View File
@@ -2,6 +2,7 @@
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.hassio import is_hassio
from homeassistant.helpers.service import async_register_admin_service
from .const import DATA_MANAGER, DOMAIN
@@ -30,7 +31,9 @@ async def _async_handle_create_automatic_service(call: ServiceCall) -> None:
def async_setup_services(hass: HomeAssistant) -> None:
"""Register services."""
if not is_hassio(hass):
hass.services.async_register(DOMAIN, "create", _async_handle_create_service)
hass.services.async_register(
DOMAIN, "create_automatic", _async_handle_create_automatic_service
async_register_admin_service(
hass, DOMAIN, "create", _async_handle_create_service
)
async_register_admin_service(
hass, DOMAIN, "create_automatic", _async_handle_create_automatic_service
)
@@ -21,8 +21,9 @@ from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.ssl import get_default_context
from .const import DOMAIN
from .const import DOMAIN, MANUFACTURER, BeoModel
from .services import async_setup_services
from .util import get_remotes
from .websocket import BeoWebsocket
@@ -58,15 +59,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: BeoConfigEntry) -> bool:
# Remove casts to str
assert entry.unique_id
# Create device now as BeoWebsocket needs a device for debug logging, firing events etc.
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, entry.unique_id)},
name=entry.title,
model=entry.data[CONF_MODEL],
)
client = MozartClient(host=entry.data[CONF_HOST], ssl_context=get_default_context())
# Check API and WebSocket connection
@@ -83,6 +75,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: BeoConfigEntry) -> bool:
await client.close_api_client()
raise ConfigEntryNotReady(f"Unable to connect to {entry.title}") from error
# Create device now as BeoWebsocket needs a device for debug logging, firing events etc.
device_registry = dr.async_get(hass)
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, entry.unique_id)},
model=entry.data[CONF_MODEL],
)
# Create devices for paired Beoremote One remotes
for remote in await get_remotes(client):
device_registry.async_get_or_create(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, f"{remote.serial_number}_{entry.unique_id}")},
name=f"{BeoModel.BEOREMOTE_ONE}-{remote.serial_number}-{entry.unique_id}",
model=BeoModel.BEOREMOTE_ONE,
serial_number=remote.serial_number,
sw_version=remote.app_version,
manufacturer=MANUFACTURER,
via_device=(DOMAIN, entry.unique_id),
)
websocket = BeoWebsocket(hass, entry, client)
# Add the websocket and API client
@@ -52,6 +52,7 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
_beolink_jid = ""
_client: MozartClient
_friendly_name = ""
_host = ""
_model = ""
_name = ""
@@ -111,6 +112,7 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
)
self._beolink_jid = beolink_self.jid
self._friendly_name = beolink_self.friendly_name
self._serial_number = get_serial_number_from_jid(beolink_self.jid)
await self.async_set_unique_id(self._serial_number)
@@ -149,6 +151,7 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="invalid_address")
self._model = discovery_info.hostname[:-16].replace("-", " ")
self._friendly_name = discovery_info.properties[ATTR_FRIENDLY_NAME]
self._serial_number = discovery_info.properties[ATTR_SERIAL_NUMBER]
self._beolink_jid = f"{discovery_info.properties[ATTR_TYPE_NUMBER]}.{discovery_info.properties[ATTR_ITEM_NUMBER]}.{self._serial_number}@products.bang-olufsen.com"
@@ -164,16 +167,13 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
async def _create_entry(self) -> ConfigFlowResult:
"""Create the config entry for a discovered or manually configured Bang & Olufsen device."""
# Ensure that created entities have a unique and easily identifiable id and not a "friendly name"
self._name = f"{self._model}-{self._serial_number}"
return self.async_create_entry(
title=self._name,
title=self._friendly_name,
data=EntryData(
host=self._host,
jid=self._beolink_jid,
model=self._model,
name=self._name,
name=self._friendly_name,
),
)
@@ -20,7 +20,6 @@ from .const import (
CONNECTION_STATUS,
DEVICE_BUTTON_EVENTS,
DOMAIN,
MANUFACTURER,
BeoModel,
WebsocketNotification,
)
@@ -142,12 +141,6 @@ class BeoRemoteKeyEvent(BeoEvent):
self._attr_unique_id = f"{remote.serial_number}_{self._unique_id}_{key_type}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{remote.serial_number}_{self._unique_id}")},
name=f"{BeoModel.BEOREMOTE_ONE}-{remote.serial_number}-{self._unique_id}",
model=BeoModel.BEOREMOTE_ONE,
serial_number=remote.serial_number,
sw_version=remote.app_version,
manufacturer=MANUFACTURER,
via_device=(DOMAIN, self._unique_id),
)
# Make the native key name Home Assistant compatible
@@ -115,7 +115,7 @@ class BeoSensorRemoteBatteryLevel(BeoSensor):
f"{remote.serial_number}_{self._unique_id}_remote_battery_level"
)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{remote.serial_number}_{self._unique_id}")}
identifiers={(DOMAIN, f"{remote.serial_number}_{self._unique_id}")},
)
self._attr_native_value = remote.battery_level
self._remote = remote
+15 -5
View File
@@ -30,19 +30,29 @@ BATTERY_PERCENTAGE_DOMAIN_SPECS = {
CONDITIONS: dict[str, type[Condition]] = {
"is_low": make_entity_state_condition(
BATTERY_DOMAIN_SPECS, STATE_ON, support_duration=True
BATTERY_DOMAIN_SPECS,
STATE_ON,
primary_entities_only=False,
),
"is_not_low": make_entity_state_condition(
BATTERY_DOMAIN_SPECS, STATE_OFF, support_duration=True
BATTERY_DOMAIN_SPECS,
STATE_OFF,
primary_entities_only=False,
),
"is_charging": make_entity_state_condition(
BATTERY_CHARGING_DOMAIN_SPECS, STATE_ON, support_duration=True
BATTERY_CHARGING_DOMAIN_SPECS,
STATE_ON,
primary_entities_only=False,
),
"is_not_charging": make_entity_state_condition(
BATTERY_CHARGING_DOMAIN_SPECS, STATE_OFF, support_duration=True
BATTERY_CHARGING_DOMAIN_SPECS,
STATE_OFF,
primary_entities_only=False,
),
"is_level": make_entity_numerical_condition(
BATTERY_PERCENTAGE_DOMAIN_SPECS, PERCENTAGE
BATTERY_PERCENTAGE_DOMAIN_SPECS,
PERCENTAGE,
primary_entities_only=False,
),
}
@@ -3,16 +3,14 @@
entity:
- domain: binary_sensor
device_class: battery
primary_entities_only: false
fields:
behavior: &condition_behavior
required: true
default: any
selector:
select:
translation_key: condition_behavior
options:
- all
- any
automation_behavior:
mode: condition
for: &condition_for
required: true
default: 00:00:00
@@ -42,6 +40,7 @@ is_charging:
entity:
- domain: binary_sensor
device_class: battery_charging
primary_entities_only: false
fields:
behavior: *condition_behavior
for: *condition_for
@@ -51,6 +50,7 @@ is_not_charging:
entity:
- domain: binary_sensor
device_class: battery_charging
primary_entities_only: false
fields:
behavior: *condition_behavior
for: *condition_for
@@ -60,8 +60,10 @@ is_level:
entity:
- domain: sensor
device_class: battery
primary_entities_only: false
fields:
behavior: *condition_behavior
for: *condition_for
threshold:
required: true
selector:
+3 -15
View File
@@ -26,6 +26,9 @@
"behavior": {
"name": "[%key:component::battery::common::condition_behavior_name%]"
},
"for": {
"name": "[%key:component::battery::common::condition_for_name%]"
},
"threshold": {
"name": "[%key:component::battery::common::condition_threshold_name%]"
}
@@ -69,21 +72,6 @@
"name": "Battery is not low"
}
},
"selector": {
"condition_behavior": {
"options": {
"all": "All",
"any": "Any"
}
},
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"title": "Battery",
"triggers": {
"level_changed": {
+14 -6
View File
@@ -32,19 +32,27 @@ BATTERY_PERCENTAGE_DOMAIN_SPECS: dict[str, DomainSpec] = {
}
TRIGGERS: dict[str, type[Trigger]] = {
"low": make_entity_target_state_trigger(BATTERY_LOW_DOMAIN_SPECS, STATE_ON),
"not_low": make_entity_target_state_trigger(BATTERY_LOW_DOMAIN_SPECS, STATE_OFF),
"low": make_entity_target_state_trigger(
BATTERY_LOW_DOMAIN_SPECS, STATE_ON, primary_entities_only=False
),
"not_low": make_entity_target_state_trigger(
BATTERY_LOW_DOMAIN_SPECS, STATE_OFF, primary_entities_only=False
),
"started_charging": make_entity_target_state_trigger(
BATTERY_CHARGING_DOMAIN_SPECS, STATE_ON
BATTERY_CHARGING_DOMAIN_SPECS, STATE_ON, primary_entities_only=False
),
"stopped_charging": make_entity_target_state_trigger(
BATTERY_CHARGING_DOMAIN_SPECS, STATE_OFF
BATTERY_CHARGING_DOMAIN_SPECS, STATE_OFF, primary_entities_only=False
),
"level_changed": make_entity_numerical_state_changed_trigger(
BATTERY_PERCENTAGE_DOMAIN_SPECS, valid_unit="%"
BATTERY_PERCENTAGE_DOMAIN_SPECS,
valid_unit="%",
primary_entities_only=False,
),
"level_crossed_threshold": make_entity_numerical_state_crossed_threshold_trigger(
BATTERY_PERCENTAGE_DOMAIN_SPECS, valid_unit="%"
BATTERY_PERCENTAGE_DOMAIN_SPECS,
valid_unit="%",
primary_entities_only=False,
),
}
@@ -3,12 +3,8 @@
required: true
default: any
selector:
select:
translation_key: trigger_behavior
options:
- first
- last
- any
automation_behavior:
mode: trigger
for: &trigger_for
required: true
default: 00:00:00
@@ -33,16 +29,19 @@
entity:
- domain: binary_sensor
device_class: battery
primary_entities_only: false
.trigger_target_charging: &trigger_target_charging
entity:
- domain: binary_sensor
device_class: battery_charging
primary_entities_only: false
.trigger_target_percentage: &trigger_target_percentage
entity:
- domain: sensor
device_class: battery
primary_entities_only: false
low:
fields:
@@ -33,11 +33,13 @@ from homeassistant.components.update import DOMAIN as UPDATE_DOMAIN
from homeassistant.components.weather import DOMAIN as WEATHER_DOMAIN
from homeassistant.components.zone import DOMAIN as ZONE_DOMAIN
from homeassistant.config_entries import (
SOURCE_USER,
ConfigEntry,
ConfigFlowResult,
ConfigSubentry,
ConfigSubentryData,
ConfigSubentryFlow,
FlowType,
SubentryFlowContext,
SubentryFlowResult,
)
from homeassistant.const import (
@@ -62,7 +64,6 @@ from homeassistant.helpers.schema_config_entry_flow import (
from .binary_sensor import above_greater_than_below, no_overlapping
from .const import (
CONF_OBSERVATIONS,
CONF_P_GIVEN_F,
CONF_P_GIVEN_T,
CONF_PRIOR,
@@ -373,26 +374,6 @@ def _validate_observation_subentry(
return user_input
async def _validate_subentry_from_config_entry(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
# Standard behavior is to merge the result with the options.
# In this case, we want to add a subentry so we update the options directly.
observations: list[dict[str, Any]] = handler.options.setdefault(
CONF_OBSERVATIONS, []
)
if handler.parent_handler.cur_step is not None:
user_input[CONF_PLATFORM] = handler.parent_handler.cur_step["step_id"]
user_input = _validate_observation_subentry(
user_input[CONF_PLATFORM],
user_input,
other_subentries=handler.options[CONF_OBSERVATIONS],
)
observations.append(user_input)
return {}
async def _get_description_placeholders(
handler: SchemaCommonFlowHandler,
) -> dict[str, str]:
@@ -420,48 +401,12 @@ async def _get_description_placeholders(
}
async def _get_observation_menu_options(handler: SchemaCommonFlowHandler) -> list[str]:
"""Return the menu options for the observation selector."""
options = [typ.value for typ in ObservationTypes]
if handler.options.get(CONF_OBSERVATIONS):
options.append("finish")
return options
CONFIG_FLOW: dict[str, SchemaFlowMenuStep | SchemaFlowFormStep] = {
str(USER): SchemaFlowFormStep(
CONFIG_SCHEMA,
validate_user_input=_validate_user,
next_step=str(OBSERVATION_SELECTOR),
description_placeholders=_get_description_placeholders,
),
str(OBSERVATION_SELECTOR): SchemaFlowMenuStep(
_get_observation_menu_options,
),
str(ObservationTypes.STATE): SchemaFlowFormStep(
STATE_SUBSCHEMA,
next_step=str(OBSERVATION_SELECTOR),
validate_user_input=_validate_subentry_from_config_entry,
# Prevent the name of the bayesian sensor from being used as the suggested
# name of the observations
suggested_values=None,
description_placeholders=_get_description_placeholders,
),
str(ObservationTypes.NUMERIC_STATE): SchemaFlowFormStep(
NUMERIC_STATE_SUBSCHEMA,
next_step=str(OBSERVATION_SELECTOR),
validate_user_input=_validate_subentry_from_config_entry,
suggested_values=None,
description_placeholders=_get_description_placeholders,
),
str(ObservationTypes.TEMPLATE): SchemaFlowFormStep(
TEMPLATE_SUBSCHEMA,
next_step=str(OBSERVATION_SELECTOR),
validate_user_input=_validate_subentry_from_config_entry,
suggested_values=None,
description_placeholders=_get_description_placeholders,
),
"finish": SchemaFlowFormStep(),
)
}
@@ -497,27 +442,17 @@ class BayesianConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
name: str = options[CONF_NAME]
return name
@callback
def async_create_entry(
self,
data: Mapping[str, Any],
**kwargs: Any,
) -> ConfigFlowResult:
"""Finish config flow and create a config entry."""
data = dict(data)
observations = data.pop(CONF_OBSERVATIONS)
subentries: list[ConfigSubentryData] = [
ConfigSubentryData(
data=observation,
title=observation[CONF_NAME],
subentry_type="observation",
unique_id=None,
)
for observation in observations
]
self.async_config_flow_finished(data)
return super().async_create_entry(data=data, subentries=subentries, **kwargs)
async def async_on_create_entry(self, result: ConfigFlowResult) -> ConfigFlowResult:
"""Start subentry flow when config entry has been created."""
subentry_result = await self.hass.config_entries.subentries.async_init(
(result["result"].entry_id, "observation"),
context=SubentryFlowContext(source=SOURCE_USER),
)
result["next_flow"] = (
FlowType.CONFIG_SUBENTRIES_FLOW,
subentry_result["flow_id"],
)
return result
class ObservationSubentryFlowHandler(ConfigSubentryFlow):
+3 -1
View File
@@ -85,7 +85,9 @@ class BleBoxCoverEntity(BleBoxEntity[blebox_uniapi.cover.Cover], CoverEntity):
if position == -1: # possible for shutterBox
return None
return None if position is None else 100 - position
if position is None:
return None
return 100 - position if self._feature.is_position_inverted else position
@property
def current_cover_tilt_position(self) -> int | None:
@@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["blebox_uniapi"],
"requirements": ["blebox-uniapi==2.5.1"],
"requirements": ["blebox-uniapi==2.5.2"],
"zeroconf": ["_bbxsrv._tcp.local."]
}
@@ -21,9 +21,6 @@
"save_video": {
"service": "mdi:file-video"
},
"send_pin": {
"service": "mdi:two-factor-authentication"
},
"trigger_camera": {
"service": "mdi:image-refresh"
}
+3 -49
View File
@@ -5,15 +5,9 @@ from __future__ import annotations
import voluptuous as vol
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.const import (
ATTR_CONFIG_ENTRY_ID,
CONF_FILE_PATH,
CONF_FILENAME,
CONF_PIN,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, issue_registry as ir, service
from homeassistant.const import CONF_FILE_PATH, CONF_FILENAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN
@@ -23,50 +17,10 @@ SERVICE_SAVE_VIDEO = "save_video"
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
# Deprecated
SERVICE_SEND_PIN = "send_pin"
SERVICE_SEND_PIN_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_PIN): cv.string,
}
)
async def _send_pin(call: ServiceCall) -> None:
"""Call blink to send new pin."""
# Create repair issue to inform user about service removal
ir.async_create_issue(
call.hass,
DOMAIN,
"service_send_pin_deprecation",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.ERROR,
breaks_in_ha_version="2026.5.0",
translation_key="service_send_pin_deprecation",
translation_placeholders={"service_name": f"{DOMAIN}.{SERVICE_SEND_PIN}"},
)
# Service has been removed - raise exception
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_removed",
translation_placeholders={"service_name": f"{DOMAIN}.{SERVICE_SEND_PIN}"},
)
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Blink integration."""
hass.services.async_register(
DOMAIN,
SERVICE_SEND_PIN,
_send_pin,
schema=SERVICE_SEND_PIN_SCHEMA,
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
@@ -35,15 +35,3 @@ save_recent_clips:
example: "/tmp"
selector:
text:
send_pin:
fields:
config_entry_id:
required: true
selector:
config_entry:
integration: blink
pin:
example: "abc123"
selector:
text:
@@ -82,9 +82,6 @@
},
"not_loaded": {
"message": "{target} is not loaded."
},
"service_removed": {
"message": "The service {service_name} has been removed and is no longer needed. Home Assistant will automatically prompt for reauthentication when required."
}
},
"issues": {
@@ -98,10 +95,6 @@
}
},
"title": "Blink update service is being removed"
},
"service_send_pin_deprecation": {
"description": "The service {service_name} has been removed and is no longer needed. When a new two-factor authentication code is required, Home Assistant will automatically prompt you to reauthenticate through the integration configuration. Please remove any automations or scripts that call this service.",
"title": "Blink send PIN service has been removed"
}
},
"options": {
@@ -140,20 +133,6 @@
},
"name": "Save video"
},
"send_pin": {
"description": "Sends a new PIN to Blink for 2FA.",
"fields": {
"config_entry_id": {
"description": "The Blink integration ID.",
"name": "Integration ID"
},
"pin": {
"description": "PIN received from Blink. Leave empty if you only received a verification email.",
"name": "PIN"
}
},
"name": "Send PIN"
},
"trigger_camera": {
"description": "Requests camera to take new image.",
"name": "Trigger camera"
@@ -58,6 +58,7 @@ from .api import (
async_address_present,
async_ble_device_from_address,
async_clear_address_from_match_history,
async_clear_advertisement_history,
async_current_scanners,
async_discovered_service_info,
async_get_advertisement_callback,
@@ -116,6 +117,7 @@ __all__ = [
"async_address_present",
"async_ble_device_from_address",
"async_clear_address_from_match_history",
"async_clear_advertisement_history",
"async_current_scanners",
"async_discovered_service_info",
"async_get_advertisement_callback",
+13
View File
@@ -207,6 +207,19 @@ def async_clear_address_from_match_history(hass: HomeAssistant, address: str) ->
_get_manager(hass).async_clear_address_from_match_history(address)
@hass_callback
def async_clear_advertisement_history(hass: HomeAssistant, address: str) -> None:
"""Clear cached advertisement history for a device.
Causes the next advertisement from this address to be treated as new
data, bypassing the change-detection guard in the Bluetooth manager.
Intended for devices that emit static advertisements as a wake-up
signal, for example, devices that require an active GATT connection
to read sensor data and whose advertisement payload never changes.
"""
_get_manager(hass).async_clear_advertisement_history(address)
@hass_callback
def async_register_scanner(
hass: HomeAssistant,
+1 -1
View File
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["bring_api"],
"quality_scale": "platinum",
"requirements": ["bring-api==1.1.1"]
"requirements": ["bring-api==1.1.2"]
}
@@ -1,4 +1,5 @@
"""The Broadlink integration."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -34,6 +34,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink climate entities."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
if device.api.type in DOMAINS_AND_TYPES[Platform.CLIMATE]:
@@ -6,7 +6,9 @@ DOMAIN = "broadlink"
DOMAINS_AND_TYPES = {
Platform.CLIMATE: {"HYS"},
Platform.INFRARED: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
Platform.LIGHT: {"LB1", "LB2"},
Platform.RADIO_FREQUENCY: {"RM4PRO", "RMPRO"},
Platform.REMOTE: {"RM4MINI", "RM4PRO", "RMMINI", "RMMINIB", "RMPRO"},
Platform.SELECT: {"HYS"},
Platform.SENSOR: {
@@ -133,6 +133,8 @@ class BroadlinkDevice[_ApiT: blk.Device = blk.Device]:
await coordinator.async_config_entry_first_refresh()
self.update_manager = update_manager
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
self.hass.data[DOMAIN].devices[config.entry_id] = self
self.reset_jobs.append(config.add_update_listener(self.async_update))
@@ -0,0 +1,69 @@
"""Infrared platform for Broadlink remotes."""
from __future__ import annotations
from typing import TYPE_CHECKING
from broadlink.exceptions import BroadlinkException
from broadlink.remote import pulses_to_data as _bl_pulses_to_data
from homeassistant.components.infrared import InfraredCommand, InfraredEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .entity import BroadlinkEntity
if TYPE_CHECKING:
from .device import BroadlinkDevice
PARALLEL_UPDATES = 1
def _timings_to_broadlink_packet(timings: list[int]) -> bytes:
"""Convert signed microsecond timings to a Broadlink IR packet.
Positive values are pulse (high) durations; negative values are space
(low) durations. The Broadlink library's encoder expects absolute
durations.
"""
pulses = [abs(t) for t in timings]
return _bl_pulses_to_data(pulses)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Broadlink infrared entity."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkInfraredEntity(device)])
class BroadlinkInfraredEntity(BroadlinkEntity, InfraredEntity):
"""Broadlink infrared transmitter entity."""
_attr_has_entity_name = True
_attr_translation_key = "infrared_emitter"
def __init__(self, device: BroadlinkDevice) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = f"{device.unique_id}-emitter"
async def async_send_command(self, command: InfraredCommand) -> None:
"""Send an IR command via the Broadlink device."""
packet = _timings_to_broadlink_packet(command.get_raw_timings())
try:
await self._device.async_request(self._device.api.send_data, packet)
except (BroadlinkException, OSError) as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="send_command_failed",
translation_placeholders={"error": str(err)},
) from err
@@ -32,6 +32,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink light."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
lights = []
@@ -0,0 +1,132 @@
"""Radio Frequency platform for Broadlink."""
from __future__ import annotations
import logging
from broadlink.exceptions import BroadlinkException
from rf_protocols import RadioFrequencyCommand
from homeassistant.components.radio_frequency import RadioFrequencyTransmitterEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import DOMAIN
from .device import BroadlinkDevice
from .entity import BroadlinkEntity
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
_TICK_US = 32.84
_RF_433_TYPE_BYTE = 0xB2
_RF_315_TYPE_BYTE = 0xB4
_RF_433_RANGE = (433_050_000, 434_790_000)
_RF_315_RANGE = (314_950_000, 315_250_000)
SUPPORTED_FREQUENCY_RANGES: list[tuple[int, int]] = [_RF_433_RANGE, _RF_315_RANGE]
def _type_byte_for_frequency(frequency: int) -> int:
"""Return the Broadlink RF type byte for a given carrier frequency."""
if _RF_433_RANGE[0] <= frequency <= _RF_433_RANGE[1]:
return _RF_433_TYPE_BYTE
if _RF_315_RANGE[0] <= frequency <= _RF_315_RANGE[1]:
return _RF_315_TYPE_BYTE
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="frequency_not_supported",
translation_placeholders={"frequency": f"{frequency / 1_000_000:g}"},
)
def encode_rf_packet(
*,
type_byte: int,
repeat_count: int,
timings_us: list[int],
) -> bytes:
"""Encode raw OOK timings as a Broadlink RF pulse-length packet.
The layout is::
byte 0 type byte (0xB2 for 433 MHz, 0xB4 for 315 MHz)
byte 1 repeat count (additional transmissions after the first)
bytes 2..3 payload length (little-endian), counted from byte 4
bytes 4..N-1 pulses: 1 byte when ticks < 256, otherwise
0x00 followed by a 2-byte big-endian tick count
Each pulse is expressed as multiples of 32.84 µs ticks, which is the
timing resolution of the Broadlink RF front-end.
"""
buf = bytearray([type_byte, repeat_count, 0, 0])
for duration in timings_us:
ticks = round(abs(duration) / _TICK_US)
div, mod = divmod(ticks, 256)
if div:
buf.append(0x00)
buf.append(div)
buf.append(mod)
payload_len = len(buf) - 4
buf[2] = payload_len & 0xFF
buf[3] = (payload_len >> 8) & 0xFF
return bytes(buf)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Broadlink radio frequency transmitter."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device: BroadlinkDevice = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkRadioFrequency(device)])
class BroadlinkRadioFrequency(BroadlinkEntity, RadioFrequencyTransmitterEntity):
"""Representation of a Broadlink RF transmitter."""
_attr_has_entity_name = True
_attr_name = None
def __init__(self, device: BroadlinkDevice) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = device.unique_id
@property
def supported_frequency_ranges(self) -> list[tuple[int, int]]:
"""Return the Broadlink-supported narrow RF bands."""
return SUPPORTED_FREQUENCY_RANGES
async def async_send_command(self, command: RadioFrequencyCommand) -> None:
"""Encode an OOK command and transmit it via the Broadlink device."""
type_byte = _type_byte_for_frequency(command.frequency)
packet = encode_rf_packet(
type_byte=type_byte,
repeat_count=command.repeat_count,
timings_us=command.get_raw_timings(),
)
_LOGGER.debug(
"Transmitting RF packet: %d bytes on %d Hz (repeat=%d)",
len(packet),
command.frequency,
command.repeat_count,
)
device = self._device
try:
await device.async_request(device.api.send_data, packet)
except (BroadlinkException, OSError) as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="transmit_failed",
translation_placeholders={"error": str(err)},
) from err
@@ -95,6 +95,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up a Broadlink remote."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
remote = BroadlinkRemote(
device,
@@ -31,6 +31,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink select."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkDayOfWeek(device)])
@@ -108,6 +108,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink sensor."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
sensor_data = device.update_manager.coordinator.data
sensors = [
@@ -49,6 +49,11 @@
}
},
"entity": {
"infrared": {
"infrared_emitter": {
"name": "IR emitter"
}
},
"select": {
"day_of_week": {
"name": "Day of week",
@@ -77,5 +82,16 @@
"name": "Total consumption"
}
}
},
"exceptions": {
"frequency_not_supported": {
"message": "Broadlink devices cannot transmit on {frequency} MHz"
},
"send_command_failed": {
"message": "Failed to send IR command: {error}"
},
"transmit_failed": {
"message": "Failed to transmit RF command: {error}"
}
}
}
@@ -1,4 +1,5 @@
"""Support for Broadlink switches."""
# pylint: disable=hass-use-runtime-data # Uses legacy hass.data[DOMAIN] pattern
from __future__ import annotations
@@ -22,6 +22,8 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Broadlink time."""
# Uses legacy hass.data[DOMAIN] pattern
# pylint: disable-next=hass-use-runtime-data
device = hass.data[DOMAIN].devices[config_entry.entry_id]
async_add_entities([BroadlinkTime(device)])
+1 -2
View File
@@ -293,9 +293,8 @@ SENSOR_TYPES: tuple[BrotherSensorEntityDescription, ...] = (
),
BrotherSensorEntityDescription(
key="uptime",
translation_key="last_restart",
entity_registry_enabled_default=False,
device_class=SensorDeviceClass.TIMESTAMP,
device_class=SensorDeviceClass.UPTIME,
entity_category=EntityCategory.DIAGNOSTIC,
value=lambda data: data.uptime,
),
@@ -151,9 +151,6 @@
"laser_remaining_life": {
"name": "Laser remaining lifetime"
},
"last_restart": {
"name": "Last restart"
},
"magenta_drum_page_counter": {
"name": "Magenta drum page counter",
"unit_of_measurement": "[%key:component::brother::entity::sensor::page_counter::unit_of_measurement%]"

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