Compare commits

..

782 Commits

Author SHA1 Message Date
Jan Čermák dfa8bf679d Disable version check and publish 2026-03-18 12:22:28 +01:00
Jan Čermák d0cd84b35e Revert changes to the build_base job 2026-03-18 12:15:49 +01:00
Jan Čermák cebc35901f Remove redundant default "context: ." 2026-03-17 17:45:01 +01:00
Jan Čermák def8dc202d Fix indent for continued packages 2026-03-17 17:23:06 +01:00
Jan Čermák 9e4fcac98a Pin builder actions to release hash 2026-03-17 17:22:17 +01:00
Jan Čermák c0b581c924 Merge remote-tracking branch 'origin/dev' into gha-builder 2026-03-17 16:16:55 +01:00
Jan Čermák cf3ad71c8f Add io.hass.machine labels to machine images 2026-03-17 16:09:16 +01:00
Joost Lekkerkerker 447d616097 Add select for SmartThings RVC sound mode (#164519) 2026-03-17 15:57:59 +01:00
Norbert Rittel d3102e718d Consistenly sentence-case "API token" in habitica (#165369)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-17 14:30:39 +00:00
Josef Zweck 69ee49735a Remove support for homeassistant.update_entity from mold_indicator (#165797) 2026-03-17 15:26:22 +01:00
Daniel Hjelseth Høyer 35a99dd4a4 Fix Tibber update token (#164295)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2026-03-17 15:11:51 +01:00
Ariel Ebersberger 51c3397be8 Refactor wemo integration to use async service action handlers (#165794)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-17 15:07:00 +01:00
Brett Adams 57f0fd2ed2 Tesla Fleet: fix malformed energy live response handling (#165101) 2026-03-17 15:04:35 +01:00
Erik Montnemery fa7a216afe Use return value from target_entities directly in condition tests (#165791) 2026-03-17 14:55:17 +01:00
Josef Zweck 20f4426e1d Fix mold_indicator sensor update (#158996) 2026-03-17 14:28:50 +01:00
Erik Montnemery ba30563772 Deduplicate tests testing triggers in mode last (#165789) 2026-03-17 14:28:10 +01:00
A. Gideonse b807c104a3 Add button platform to Indevolt integration (#165283) 2026-03-17 13:59:18 +01:00
epenet 9e6abb719a Add fixture for Kerui/Tuya video doorbell (#165786) 2026-03-17 13:57:28 +01:00
jvmahon ed2083a60d Limit color temperature to maximum Matter MIREDs value (#163892)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-17 12:49:18 +00:00
Kornel 94db0d5eab Handle timeout in HKDevice.async_update (#162071)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-03-17 12:42:43 +00:00
dckiller51 06eed998b9 Add platform attribute to Xbox sensors (#161661) 2026-03-17 12:40:42 +00:00
Andrej Walilko fb5c2f2566 Add shuffle service and enqueue support to jellyfin media player (#161632)
Co-authored-by: Andrej Walilko <awalilko@liquidweb.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-17 12:17:55 +00:00
Dominik 4f7d065230 Fix fritz target selector for dial and set_guest_wifi_password (#165396) 2026-03-17 13:15:51 +01:00
Erwin Douna d034df9b93 Add Portainer request timeout (#165785) 2026-03-17 12:58:55 +01:00
Erik Montnemery 6c9fc7c7a1 Deduplicate tests testing triggers in mode first (#165779)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-17 12:44:21 +01:00
Leon Grave ba58ef23d8 Add reauthentication-flow to freshr (#165545)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-17 12:22:54 +01:00
johanzander 0a0fa96ac1 Add silver quality scale for growatt_server (#165500) 2026-03-17 12:18:11 +01:00
Erik Montnemery 9cc7ef75b0 Move cover.trigger.CoverDomainSpec to cover.models (#165774) 2026-03-17 12:11:11 +01:00
Carlos Sánchez López 2e0d6d2bbf Add fixture for Tuya wg2 alarm panel (Duosmart C30) (#165701)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-03-17 11:34:53 +01:00
Artur Pragacz bafef2065f Rework user-given entity name logic (#162763) 2026-03-17 11:09:20 +01:00
Erik Montnemery fdfe87de4c Move condition/trigger test helpers to test.components.common (#165777) 2026-03-17 11:08:38 +01:00
epenet 933d123db3 Move xiaomi_miio coordinator to separate module (#165766) 2026-03-17 11:04:31 +01:00
Brett Adams 1f9946a1b8 Fix sensor reset handling in Tesla Fleet (#165744) 2026-03-17 10:59:35 +01:00
Josef Zweck 403e30b56e Add upload progress tracking to hassio (#165664) 2026-03-17 10:47:58 +01:00
Robert Resch e4524d9b68 Run split tests in the same stage with mypy (#165738) 2026-03-17 10:45:55 +01:00
Ariel Ebersberger 738100c897 Fix wemo tests for Python 3.14.3 (#165768) 2026-03-17 10:40:54 +01:00
Erik Montnemery 67356de21b Deduplicate tests testing triggers in mode any (#165772) 2026-03-17 10:27:12 +01:00
Robert Resch 80c5bd1843 Bump pyOpenSSL to 26.0.0 (#165770) 2026-03-17 10:13:37 +01:00
Erik Montnemery 492883de57 Add cover conditions (#165661) 2026-03-17 10:11:16 +01:00
Samuel Xiao 45f1247237 Switchbot Cloud: Add new supported device(Standing Fan) (#165755) 2026-03-17 09:59:18 +01:00
Ville Skyttä 0e76d927cf Switch to actions/attest for build provenance (#165350) 2026-03-17 09:57:48 +01:00
Erik Montnemery 4769a769e0 Use return value from target_entities directly in all trigger tests (#165761) 2026-03-17 09:55:08 +01:00
J. Nick Koston f2d62049ec Fix ESPHome cold/warm white brightness applied twice (#165405) 2026-03-17 09:24:21 +01:00
dependabot[bot] 751b2638ce Bump sigstore/cosign-installer from 4.0.0 to 4.1.0 (#165758)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-17 09:23:42 +01:00
Joakim Plate 120d3ee85a Add support for aqua contour/precise line of gardena products (#165326) 2026-03-17 08:32:17 +01:00
Joost Lekkerkerker 2d273a86ba Add more connection info to SmartThings (#165472) 2026-03-17 08:30:57 +01:00
Erik Montnemery 9bbd9d8bcd Deduplicate trigger tests checking labs flag (#165760)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-17 08:19:10 +01:00
mettolen 5ff2cac077 Set parallel updates for Huum integration (#165749) 2026-03-17 07:54:37 +01:00
mettolen 74b0d058ec Fix issues in Huum unit test (#165753) 2026-03-17 07:54:01 +01:00
mettolen 29f96e3f9c Move _async_abort_entries_match before the try block in Huum (#165752) 2026-03-17 07:47:47 +01:00
Mike Degatano 39b44445ec Use aiohasupervisor for all calls from hassio/coordinator (#164413)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-03-17 01:06:56 +01:00
Robert Resch 589622c05a Fix pterodactyl tests (#165745) 2026-03-16 23:44:26 +01:00
Brett Adams 6abe576ec9 Platinum quality for Teslemetry (#165727) 2026-03-16 22:31:17 +00:00
Robert Resch 75978d8837 Fix demo tests for Python 3.14.3 (#165724) 2026-03-16 22:52:04 +01:00
Robert Resch a2da13a0b3 Fix kitchen_sink tests for Python 3.14.3 (#165730) 2026-03-16 22:45:36 +01:00
Robert Resch ce081d7e71 Fix local_file tests for Python 3.14.3 (#165731) 2026-03-16 22:45:15 +01:00
Robert Resch 037e123e11 Fix media_player tests for Python 3.14.3 (#165732) 2026-03-16 22:44:52 +01:00
Robert Resch 592b7e5594 Fix wake_on_lan tests for Python 3.14.3 (#165733) 2026-03-16 22:44:23 +01:00
Cyril MARIN a963eed3a7 Add bearer token as optional setting to Ollama (#165325)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-16 22:14:33 +01:00
Devin Slick 2042f2e2bd Add Lojack integration (#162047)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-16 22:09:10 +01:00
mettolen 3580fab26e Initialize quality scale for Huum integration (#164902) 2026-03-16 22:08:43 +01:00
Matt Zimmerman 1817522107 Clean up SmartTub integration and tests (#165517)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-16 22:06:23 +01:00
Matt Zimmerman 98a9ce3a64 Add quality scale file for SmartTub integration (#162376)
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 21:48:09 +01:00
johanzander 163bfb0fdd Add SPH inverter support to Growatt Server integration (#165314)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 21:46:48 +01:00
Jeff Terrace 66f04c702c Update onvif parsers library to latest parsing multiple (#165571)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-16 21:40:37 +01:00
Khole 41c497c49e Hive: Fix bug in config flow for authentication and device registration (#165061) 2026-03-16 21:07:34 +01:00
Ludovic BOUÉ c25a664365 Fix Matter firmware update detection when version strings are identical (#165509) 2026-03-16 21:07:03 +01:00
Raj Laud 3dec70abce Add AC charger sensor support to victron_ble (#165497)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 20:59:30 +01:00
Robert Resch 3c2f696a23 Improve type hints for pilight (#165719) 2026-03-16 20:55:04 +01:00
Nathan Spencer 54745dc1f2 Remove stale devices at setup in Whisker (#165721) 2026-03-16 20:54:02 +01:00
Raj Laud e4345c72d9 Fix SmartLithium 8-cell support in victron_ble (#165496)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 20:49:43 +01:00
J. Diego Rodríguez Royo 7acb253ae2 Add bread baking and dough proving programs to Home Connect (#165717) 2026-03-16 20:47:20 +01:00
J. Diego Rodríguez Royo 812c63eeb7 Bump aiohomeconnect to 0.32.0 (#165716) 2026-03-16 20:46:22 +01:00
Erwin Douna 7f13731035 Start orphaned entries in normal mode only (#164815)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-16 20:45:33 +01:00
Christian Lackas 879178e8a2 Add light support for HmIP-MP3P (Combination Signalling Device) (#162825) 2026-03-16 20:43:36 +01:00
Brett Adams 4d8cedb061 Add dynamic device discovery for Teslemetry (#162143)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Erwin Douna <e.douna@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-16 20:31:05 +01:00
Christian Lackas e9f0d8a550 vicare: Remove heating type config, defaulting to auto-detection (#165649) 2026-03-16 20:26:02 +01:00
Joost Lekkerkerker c5a04deb28 Add integration type to Orvibo (#165706) 2026-03-16 20:04:59 +01:00
Bouwe Westerdijk f2a205e8d7 Improve Plugwise DataUpdateCoordinator (#165715)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-16 20:04:55 +01:00
prana-dev-official 254aa30ad8 Add sensor platform to prana (#165632) 2026-03-16 20:03:36 +01:00
J. Diego Rodríguez Royo de4025634a Add start selected program action to Home Connect (#165362)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-16 20:03:20 +01:00
Artur Pragacz db4af890f4 Use standard syrupy serialisation for registries in homekit controller (#165693) 2026-03-16 18:17:22 +00:00
cdheiser 501c8fecec Bump pylutron to 0.4.0 and maintain switch compatibility (#165592) 2026-03-16 19:13:23 +01:00
Andres Ruiz 03edee1335 Enable support for multiple Waterfurnace devices (#162692)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-16 19:04:14 +01:00
Nathan Spencer 00b0da7d26 Add auto device removal handling to Whisker (#165709) 2026-03-16 18:01:37 +00:00
J. Nick Koston bf23fc5887 Fix choppy HomeKit camera audio with SRTP audio proxy (#165185) 2026-03-16 07:36:08 -10:00
Artur Pragacz 6f746c4375 Add common entity_entry_as_dict util to diagnostics (#165692) 2026-03-16 18:16:13 +01:00
Nathan Spencer e7c3a62569 Add dynamic devices support for Whisker (#165704) 2026-03-16 18:11:10 +01:00
Joost Lekkerkerker b1578a0c8c Add hassfest check to make sure new integrations have an integration type (#164001) 2026-03-16 18:10:30 +01:00
Martin Ecker 56b4d2c015 Add correct speed fan mapping for Z-Wave GE/Jasco Enbrighten ZWA4013 (#164500) 2026-03-16 17:53:58 +01:00
Erwin Douna d5ee99c450 Proxmox re-use sanitize UserID (#164303) 2026-03-16 17:50:51 +01:00
hanwg 7d2a305996 Suggest chat_id for subentry flow for Telegram bot (#165515)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-16 17:33:00 +01:00
Josef Zweck 6945418805 Refactor mold_indicator sensor (#165696) 2026-03-16 17:10:05 +01:00
Ariel Ebersberger ccecbcb389 Refactor condition helpers (#165662) 2026-03-16 16:57:53 +01:00
epenet 8bb51c0662 Move meteo_france coordinators to separate module (#164558)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-16 16:27:54 +01:00
Raj Laud f66edf6b86 Bump victron-ble-ha-parser to 0.6.1 (#165473)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 15:27:06 +00:00
Joost Lekkerkerker 70e469366b Finish test coverage in TRMNL (#165611) 2026-03-16 16:18:20 +01:00
epenet 4a9ba865be Fix HVACMode mappings in Tuya climate (#165691) 2026-03-16 16:15:12 +01:00
Denis Shulyaka 0167182e2e Add support for service tier for OpenAI integration (#165379)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-16 15:38:29 +01:00
Ariel Ebersberger 11411a880d Refactor trigger helpers (#165455)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-16 15:26:57 +01:00
J. Diego Rodríguez Royo ce47abe1d3 Add climate entity for air conditioner to Home Connect (#155981)
Co-authored-by: Joostlek <joostlek@outlook.com>
Co-authored-by: emontnemery <erik@montnemery.com>
2026-03-16 15:19:57 +01:00
epenet b58513c19a Use TuyaCoverAction enum in Tuya cover (#165690) 2026-03-16 15:08:49 +01:00
epenet 4e1dab6d8b Migrate remaining vacuum wrappers to Tuya library (#165688) 2026-03-16 15:06:03 +01:00
epenet 5ae8e1c319 Migrate remaining climate wrappers to Tuya library (#165687) 2026-03-16 15:03:15 +01:00
epenet 17bf6ca591 Migrate remaining alarm control panel wrappers to Tuya library (#165686) 2026-03-16 14:59:10 +01:00
epenet 256d30c38d Migrate remaining fan wrappers to Tuya library (#165685) 2026-03-16 14:56:26 +01:00
Jan Čermák 5d182394c2 Update zizmor to v1.23.1 (#165467) 2026-03-16 14:30:13 +01:00
epenet 011e6863d8 Bump tuya-device-handlers to 0.0.13 (#165684) 2026-03-16 14:11:26 +01:00
Anis Kadri b902b590b1 Add UniFi Access binary sensors (#165569)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-16 14:03:46 +01:00
peteS-UK 960666e15b Improve discovery flow for Squeezebox (#153958) 2026-03-16 13:50:33 +01:00
Mike Degatano 1fb59c9f11 Remove code notary related unsupported reasons (#165417)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-03-16 13:45:58 +01:00
Mike Degatano 332bf95e16 Bump aiohasupervisor to 0.4.1 (#165489)
Co-authored-by: Stefan Agner <stefan@agner.ch>
2026-03-16 13:11:48 +01:00
Joost Lekkerkerker e35fc8267e Fix typing in nsw_fuel_station (#165679) 2026-03-16 12:41:53 +01:00
Joost Lekkerkerker f8b4ffc0d7 Fix translation placeholders in Assist pipeline (#165676) 2026-03-16 12:37:47 +01:00
Mike Degatano 003ee5a699 Remove aiohasupervisor from pyproject.toml (#165512) 2026-03-16 11:56:10 +01:00
epenet c91d805174 Use external library wrapper in Tuya vacuum (#165673) 2026-03-16 11:52:34 +01:00
epenet c478d19ae3 Use external library wrapper in Tuya climate (#165672) 2026-03-16 11:46:59 +01:00
Samuel Xiao 09169b0f06 Switchbot Cloud: Fixed Circulator Fan on start error (#165241)
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-03-16 11:45:21 +01:00
epenet aa1dbee315 Use external library wrapper in Tuya cover (#165656) 2026-03-16 11:37:18 +01:00
TimL daf89e5673 Bump Pysmlight to 0.3.0 (#165658) 2026-03-16 11:35:25 +01:00
Joshua Monta 85dc81c147 Update uhoo IQS to silver (#165665) 2026-03-16 11:31:53 +01:00
epenet 5acf24cb53 Use external library wrapper in Tuya alarm control panel (#165671) 2026-03-16 11:30:51 +01:00
Martin Hjelmare 79829a311c Fix emulated_kasa tests for Python 3.14.3 (#165667) 2026-03-16 11:19:06 +01:00
Martin Hjelmare ce2c62ae28 Fix numato tests for Python 3.14.3 (#165669) 2026-03-16 11:17:29 +01:00
Martin Hjelmare 1cda3f47d6 Fix valve tests for Python 3.14.3 (#165668) 2026-03-16 11:16:27 +01:00
Nathan Spencer e254716615 Remove deprecated entity creation code for Litter-Robot 4 devices (#165636) 2026-03-16 10:40:31 +01:00
epenet 1d410f4cbd Use external library wrapper in Tuya humidifer (#165654) 2026-03-16 10:39:54 +01:00
epenet 6616793e2b Use external library wrapper in Tuya light (#165653) 2026-03-16 10:39:43 +01:00
Joost Lekkerkerker 6766961327 Finish TRMNL docs (#165612) 2026-03-16 10:38:11 +01:00
Denis Shulyaka dd6fc11d28 Bump python-telegram-bot to 22.6 (#165508)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-16 10:34:54 +01:00
Simone Chemelli cb5b8b212c Bump aiocomelit to 2.0.1 (#165663) 2026-03-16 10:32:55 +01:00
epenet 66b96d096e Use external library wrapper in Tuya event (#165655) 2026-03-16 10:32:31 +01:00
epenet e86160de36 Use external library wrapper in Tuya fan (#165464) 2026-03-16 10:24:00 +01:00
Simone Chemelli 7617007edd Update IQS to silver for Fritz (#162280) 2026-03-16 10:19:35 +01:00
epenet 3e065b31b3 Simplify Prana entity descriptions (#165660)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-16 10:12:16 +01:00
Simone Chemelli 5f909a6f3a Fix wifi switch status and add 100% coverage for Fritz (#164696) 2026-03-16 10:05:42 +01:00
Jan Bouwhuis 6117a20ec6 Fix MQTT device tracker overrides via JSON state attributes without reset (#165529) 2026-03-16 10:03:35 +01:00
Simone Chemelli 93bc05bb3f Fix switch set for Vodafone Station (#165273) 2026-03-16 10:00:52 +01:00
Thomas Kadauke e7397ccaa7 fix: Increase WebSocket message size limit to 16MB in Hass.io ingress proxy (#164442)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 09:48:06 +01:00
Joshua Monta 91a43873a2 feat: implement reauthentication requirement (#165641) 2026-03-16 09:03:01 +01:00
Ludovic BOUÉ 469e06fb8c Add Matter certified Silabs fan example to fixtures (#165622) 2026-03-16 09:02:23 +01:00
Joost Lekkerkerker bac370e775 Add diagnostics to Zinvolt (#165623) 2026-03-16 08:22:46 +01:00
Mick Vleeshouwer 1a9da26286 Update Overkiz test fixtures and diagnostics to use more realistic fixture (#165615) 2026-03-16 08:21:01 +01:00
Lukas f795707c53 Pooldose bump python-pooldose to 0.8.6 (#165616) 2026-03-16 07:27:57 +01:00
Allen Porter 9ad1356e4b Upgrade ical dependency to 13.2.2. (#165642) 2026-03-16 07:09:25 +01:00
J. Nick Koston 0f70d5fd39 Bump aioesphomeapi to 44.5.2 (#165644) 2026-03-16 07:07:49 +01:00
Josh Gustafson f4c6724953 Add icons for Arcam sensors (#165637) 2026-03-16 06:56:30 +01:00
Sab44 82432d9ee7 Bump librehardwaremonitor-api to version 1.11.1 (#165629) 2026-03-16 00:10:51 +00:00
Joost Lekkerkerker 8db07f3ceb Add API key url to step description in TRMNL (#165614) 2026-03-15 23:41:22 +01:00
Joost Lekkerkerker 2fe9d1ef86 Add reconfigure flow to TRMNL (#165594) 2026-03-15 21:52:40 +01:00
David Bishop cbb1f3726c Move coordinator tests and migrate test data to JSON fixtures (#165503)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 20:53:45 +01:00
Josh Gustafson beb122bb1a Add binary sensor platform to Arcam FMJ (#165272)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 20:08:05 +01:00
J. Nick Koston 8d6099b055 Bump ulid-transform to 2.0.2 (#165585) 2026-03-15 20:07:19 +01:00
J. Nick Koston 7ebe11c0e6 Bump habluetooth to 5.10.2 (#165591) 2026-03-15 20:07:01 +01:00
Andrew Jackson 12b14b46c0 Bump aiomealie to 1.2.2 (#165610) 2026-03-15 19:56:48 +01:00
tronikos cc45201f2d Redact utility account id in Opower diagnostics (#165145) 2026-03-15 11:56:36 -07:00
Erwin Douna a433a163a3 Migrate unique ID of Portainer integration (#165123) 2026-03-15 18:00:41 +01:00
Joost Lekkerkerker 7fd86145d1 Add 2 more sensors to TRMNL (#165604) 2026-03-15 17:13:35 +01:00
Joost Lekkerkerker f244af590e Handle action exceptions in TRMNL (#165607) 2026-03-15 17:00:14 +01:00
Joost Lekkerkerker 9a7dd98d89 Change initiate flow button text for TRMNL (#165606) 2026-03-15 16:46:57 +01:00
Joost Lekkerkerker 6c4beba465 Bump trmnl to 0.1.1 (#165605) 2026-03-15 16:45:43 +01:00
Joost Lekkerkerker 3a46beec76 Add dynamic device handling to TRMNL (#165548) 2026-03-15 16:43:52 +01:00
Josef Zweck d7c2dfc4d4 Add backup progress callback to onedrive integrations (#165217) 2026-03-15 16:31:04 +01:00
Joost Lekkerkerker 4efbafb003 Add TRMNL time platform (#165537)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-15 14:29:20 +01:00
Simone Chemelli 1b10db28f1 Add 100% coverage of coordinator for Fritz (#164074) 2026-03-15 12:16:42 +01:00
Joost Lekkerkerker 1e988fbb04 Remove stateclass from timestamp entity in Intellifire (#165403) 2026-03-15 11:39:57 +01:00
J. Nick Koston 9ab577aad4 Bump fnv-hash-fast to 2.0.0 (#165586) 2026-03-15 09:55:54 +01:00
Olivier R. ed53469eb6 Fix KeyError 'api_domain' in Freebox zeroconf discovery (#165288)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-15 09:07:28 +01:00
Andres Ruiz 56aa96a00c Add re-auth flow for Waterfurnace (#165406) 2026-03-15 07:09:35 +01:00
Anis Kadri 99c6cdbe44 Bump py-unifi-access to 1.1.0 (#165576) 2026-03-15 06:58:27 +01:00
J. Diego Rodríguez Royo 1fd30b73e7 Add fan speed percentage to service schema (#165557) 2026-03-15 06:57:38 +01:00
Joost Lekkerkerker 14aace0c00 Add stale device handling to TRMNL (#165550) 2026-03-15 06:56:05 +01:00
Joost Lekkerkerker 6eed18623b Add reauthentication to TRMNL (#165546) 2026-03-15 06:54:26 +01:00
Joost Lekkerkerker 66ca7d5782 Add switch platform to TRMNL (#165539) 2026-03-15 06:49:09 +01:00
Joost Lekkerkerker a7436cbdc3 Add diagnostics to TRMNL (#165544) 2026-03-15 06:48:13 +01:00
Joost Lekkerkerker 5e57b0272d Add diagnostics to Chess.com (#165563) 2026-03-15 06:47:37 +01:00
Raphael Hehl e16b6ab026 Add emergency switch platform for UniFi Access integration (#165536)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-03-14 20:43:12 +01:00
Joost Lekkerkerker e21fb14b9a Discover Aeotec hub for SmartThings (#165469) 2026-03-14 19:56:53 +01:00
Simone Chemelli 8e099a874b Bump aioamazondevices to 13.0.1 (#165476) 2026-03-14 19:46:02 +01:00
Åke Strandberg a5302a6219 Fix missing code for Miele dishwasher (#165553) 2026-03-14 19:45:47 +01:00
Nathan Spencer f761ac5b49 Add coordinator exception translations and mark entity/exception-translations rules as done (#165551) 2026-03-14 19:27:11 +01:00
Josh Gustafson 6988e73ddc Add sensor platform to Arcam FMJ (#165271)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-14 18:18:17 +01:00
Norbert Rittel a88374557b Make "Power-on behavior" in zha consistent with matter and tuya (#165549) 2026-03-14 18:04:55 +01:00
Nathan Spencer f2456b2c3a Add reconfiguration flow to Whisker (#165513) 2026-03-14 17:30:29 +01:00
Raphael Hehl c1a525b7aa Add unifi_access to Ubiquiti brand and regenerate integrations.json (#165538)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-03-14 17:09:16 +01:00
Joost Lekkerkerker 9d2febd24e Add TRMNL integration (#165499) 2026-03-14 16:17:19 +01:00
Raphael Hehl 54f96bcc33 Add event platform for UniFi Access integration (#165531)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-03-14 14:12:50 +01:00
Manu 5582d83f7b Remove duplicate sensor entity description for monitor port in Uptime Kuma integration (#165479) 2026-03-14 14:05:48 +01:00
Joost Lekkerkerker 2832456bcd Add binary sensor for cooktop in SmartThings (#165481) 2026-03-14 14:05:24 +01:00
Norbert Rittel 070c5821e4 Make start_up_current_level in zha consistent with matter (#165504) 2026-03-14 13:58:01 +01:00
Lukas 07caa8ed2d Bump python-pooldose to 0.8.5 (#165507) 2026-03-14 13:57:20 +01:00
Kevin Stillhammer b02f447e4d Bump pywaze to 1.2.0 (#165526) 2026-03-14 13:56:15 +01:00
Nathan Spencer 4fbb22e861 Update Whisker quality scale docs rules (#165510) 2026-03-14 11:38:29 +01:00
hanwg 45199a341f Pass web session to download files for Telegram bot (#165424) 2026-03-14 09:57:39 +01:00
Jan-Philipp Benecke de5f42d7a0 Add progress reporting to WebDAV upload (#165398) 2026-03-14 08:35:47 +01:00
Artur Pragacz 4459dce73a Reorder code to group intent errors (#165431) 2026-03-13 18:58:19 -05:00
Artur Pragacz a465905467 Remove speech parameter from service intent handler (#165225) 2026-03-13 18:57:16 -05:00
Raphael Hehl a47faa3ced Add UniFi Access integration (#165404)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-03-14 00:00:18 +01:00
Josh 7276403ab9 Allow deleting UniFi client devices (#165505) 2026-03-13 23:06:58 +01:00
Raj Laud 018717af4f Fix victron_ble warning sensor using duplicate alarm translation key (#165502)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-13 22:23:54 +01:00
Norbert Rittel 274c2b8092 Shorten "Power-on behavior" name in matter to be consistent (#165490) 2026-03-13 21:22:49 +01:00
David Bishop bfe15a55c9 Add entity-unavailable and log-when-unavailable (#165486)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 20:20:55 +00:00
dvdinth 54ad67b810 Bump pyintelliclima dependency for IntelliClima integration (#165478)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2026-03-13 20:16:27 +00:00
Nathan Spencer 4d2732df6f Add diagnostics to Whisker (#165487) 2026-03-13 20:38:57 +01:00
Andres Ruiz 2be3291d8e Update brand name for Subaru integration (#165485) 2026-03-13 20:26:44 +01:00
Joost Lekkerkerker 4326cb96ea Add zigbee address to SmartThings devices (#165474)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-13 20:14:58 +01:00
Norbert Rittel 278894d4b4 Make "power-on behavior" states more consistent in tuya (#165344) 2026-03-13 18:53:32 +00:00
Ariel Ebersberger eb17367229 Add DomainSpec to trigger and condition helpers (#165392) 2026-03-13 19:50:19 +01:00
Mike Degatano d96191723f Improve error handling when addon unavailable for install/update (#165352) 2026-03-13 19:28:19 +01:00
mcisk b6c7b2952e Add autoskope integration (#146772)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-13 19:19:00 +01:00
David Bishop 356de12bce Add parallel-updates and action-exceptions for Whisker (#165433)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:33:42 +01:00
epenet 57c49d0c48 Fix missing Tuya climate preset_mode (#165460) 2026-03-13 17:49:10 +01:00
Joost Lekkerkerker af22b5fdbb Bump pySmartThings to 3.7.0 (#165468) 2026-03-13 17:12:15 +01:00
Joost Lekkerkerker 9c710961f0 Add Matter fixtures to SmartThings (#165466) 2026-03-13 17:09:38 +01:00
epenet 2a2da83173 Use external library wrapper in Tuya binary_sensor (#165465) 2026-03-13 17:05:52 +01:00
jvmahon 00a52245e3 Add Matter start-up Power-on level entity (#164775) 2026-03-13 17:04:12 +01:00
TheJulianJES adb30e1ec1 Hide ZWA-2 adapter in Zigbee serial port selector (#155526) 2026-03-13 16:56:12 +01:00
TheJulianJES 34a7fcf8d3 Bump ZHA to 1.0.2 (#165423) 2026-03-13 16:15:51 +01:00
prana-dev-official 95a57a2984 Add fan platform for Prana Integration (#163379)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-13 16:05:37 +01:00
epenet 7f39cc0aeb Bump tuya-device-handlers to 0.0.12 (#165462) 2026-03-13 15:58:12 +01:00
Robin Lintermann 6962288e85 Add spring status sensor entity (#164332) 2026-03-13 14:29:37 +01:00
Eli Sand fab4355cc8 Enhance generic_thermostat with min/max run time and cooldown time (#136298) 2026-03-13 14:22:33 +01:00
Robin Lintermann e39d84e8fc Bump pysmarlaapi to 1.0.2 (#165454) 2026-03-13 12:46:09 +01:00
Christian Lackas 35f597223a Add DHW operating mode select entity to ViCare integration (#163832) 2026-03-13 12:44:24 +01:00
Galorhallen 9d61c8336d Update govee local api to 2.4.0 (#165418) 2026-03-13 12:43:41 +01:00
Robert Resch 6fd3603b7b Bump orjson to 3.11.7 (#165443) 2026-03-13 12:34:13 +01:00
epenet 49ac5c42ee Add base entity to arcam_fmj (#165447) 2026-03-13 12:27:52 +01:00
epenet df0db5853c Fix device name in arcam_fmj (#165448) 2026-03-13 12:25:52 +01:00
dependabot[bot] 7afc5b777c Bump docker/metadata-action from 5.10.0 to 6.0.0 (#165438)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 12:25:35 +01:00
dependabot[bot] 595aeea8cc Bump github/codeql-action from 4.32.4 to 4.32.6 (#165436)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 12:22:09 +01:00
dependabot[bot] 02abba02d1 Bump docker/setup-buildx-action from 3.12.0 to 4.0.0 (#165437)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 12:21:54 +01:00
dependabot[bot] 4ca1ad96f1 Bump docker/build-push-action from 6.19.2 to 7.0.0 (#165435)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-13 12:21:20 +01:00
Erik Montnemery 9f3beba97a Fix vera test opening sockets (#165439) 2026-03-13 11:00:17 +01:00
johanzander 9f86006328 Update Growatt quality scale: add config flow data descriptions (#165426)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:46:14 +01:00
Erik Montnemery 4ac651d0b4 Add occupancy triggers (#165374) 2026-03-13 08:41:48 +01:00
J. Nick Koston 9e54abbcb5 Handle OAuth token request exceptions in Yale setup (#165430) 2026-03-13 08:19:24 +01:00
Erik Montnemery d5915c8811 Add motion triggers (#165373) 2026-03-13 07:54:51 +01:00
Erik Montnemery 0c2887df9e Fix numerical entity trigger schema (#165411) 2026-03-13 07:32:43 +01:00
Zach Feldman 3767bac850 August oauth2 exception migration (#165397)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-03-12 17:28:08 -10:00
J. Nick Koston 9d962d3815 Add missing ON_OFF support and target_temperature_step to ESPHome water heater (#165427)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-12 16:10:29 -10:00
Bram Kragten 786fd40ae8 Update frontend to 20260312.0 (#165420) 2026-03-12 23:07:04 +01:00
Joakim Plate 5ec65dbd58 Remove use of media player internals in arcam (#165359) 2026-03-12 21:55:39 +00:00
Josef Zweck 35878bb203 Bump onedrive-personal-sdk to 0.1.7 (#165401) 2026-03-12 21:59:40 +01:00
Arie Catsman e14d88ff55 Bump pyenphase to 2.4.6 (#165402) 2026-03-12 20:06:49 +00:00
Erwin Douna d04efbfe48 Add platinum badge to Portainer (#165048)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
2026-03-12 19:30:31 +01:00
AlCalzone 3f35cd5cd2 Remove Z-Wave Installer panel (#165388)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: AlCalzone <17641229+AlCalzone@users.noreply.github.com>
2026-03-12 17:30:28 +01:00
AlCalzone 86ffd58665 Instruct AI to add type annotations to tests (#165386)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-12 17:10:30 +01:00
prana-dev-official 6206392b28 Bump prana-local-api to 0.12.0 (#165394) 2026-03-12 17:05:26 +01:00
dvdinth b7c36c707f Add IntelliClima Sensor platform (#163901)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-12 16:33:34 +01:00
Joakim Sørensen 973c32b99d Add latency results if available to the support package (#165377) 2026-03-12 10:44:08 +01:00
Erik Montnemery 951775bea6 Add window triggers (#165230) 2026-03-12 10:18:42 +01:00
Artur Pragacz 0f2dbdf4f4 Fix logging of unavailable entities in entity call (#165370) 2026-03-12 09:53:30 +01:00
Jan-Philipp Benecke 443ff7efe1 Bump aiowebdav2 to 0.6.2 (#165353) 2026-03-12 08:17:41 +01:00
Jeef 0ee6b954df Bump intellifire4py to 4.4.0 (#165356) 2026-03-12 08:15:48 +01:00
Norbert Rittel 5681acf0e1 Sentence-case "API token" and "username/password" in growatt (#165368) 2026-03-12 07:49:35 +01:00
Andres Ruiz a94458b8bc Bump waterfurnace version v1.6.2 (#165348) 2026-03-12 07:49:12 +01:00
Josef Zweck f3c38ba2d3 Add "cleaning_up" stage to backup (#165349) 2026-03-12 07:28:17 +01:00
Jan Bouwhuis c1acd1d860 Allow an MQTT entity to show as a group (#152270)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-03-11 22:25:28 +01:00
chli1 f4748aa63d fix #163316: FRITZ!SmartHome integration not showing boost status on … (#164574) 2026-03-11 22:19:43 +01:00
Brett Adams 31f4f618cc Fix duplicate energy remaining sensors in Tessie (#165102) 2026-03-11 21:39:35 +01:00
Oluwatobi Mustapha 30aec4d2ab Migrate OAuth helper token request exception handling in Google Sheets (#165000)
Signed-off-by: Oluwatobi Mustapha <oluwatobimustapha539@gmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 20:33:26 +01:00
AlCalzone 335abd7002 Support new Z-Wave JS "Opening state" notification variable (#165236) 2026-03-11 20:13:54 +01:00
Joakim Sørensen 3b3f0e9240 Bump hass-nabucasa from 1.15.0 to 2.0.0 (#165335) 2026-03-11 20:02:28 +01:00
Simone Chemelli 49586d1519 Fix dnd switch status for Alexa Devices (#164953) 2026-03-11 19:21:51 +01:00
Erwin Douna c63ded3522 Add Swarm stack to Portainer (#164991) 2026-03-11 18:14:05 +01:00
Josef Zweck 2eb65ab314 Buffer backup upload progress events (#165249)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-11 17:29:35 +01:00
ams2990 402a37b435 Change light.toggle service call to invoke LightEntity.async_toggle (#156196)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-03-11 17:17:10 +01:00
Erik Montnemery aa66e8ef0c Improve humidity triggers (#165323) 2026-03-11 17:11:27 +01:00
noambav f1a1e284b7 Add support for Fish Audio s2-pro model (#165269) 2026-03-11 17:07:56 +01:00
hanwg 08594f4e0c Update migration message for Telegram bot (#165299) 2026-03-11 17:04:16 +01:00
Joakim Plate 8d810588f8 Move secondary zone of arcam to sub-device (#165336) 2026-03-11 16:57:47 +01:00
Sid 70faad15d5 Add binary_sensor to eheimdigital (#165035)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 16:21:16 +01:00
TheJulianJES d447843687 Bump python-otbr-api to 2.9.0 (#165298) 2026-03-11 16:15:35 +01:00
Steve Easley 83b64e29fa Bump pyjvcprojector to 2.0.3 (#165327) 2026-03-11 16:13:26 +01:00
tronikos 4558a10e05 Improve test coverage in Opower to make it silver (#165124) 2026-03-11 15:56:31 +01:00
johanzander 5ad9e81082 Add reauthentication flow to growatt_server (silver quality scale) (#164993)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:51:25 +01:00
cdheiser ba00a14772 Fix flakiness in lutron tests and isolate platforms per test file (#165328) 2026-03-11 15:08:00 +01:00
J. Diego Rodríguez Royo 49f4d07eeb Add fan entity for air conditioner to Home Connect (#155983)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-11 14:29:01 +01:00
Dan Raper 5d271a0d30 Bump ohme to 1.7.0 (#165318) 2026-03-11 12:49:07 +01:00
Joakim Plate 474b683d3c Update gardena to 2.1.0 (#165322) 2026-03-11 12:48:24 +01:00
Erik Montnemery d37106a360 Add gate triggers (#165228) 2026-03-11 10:59:53 +01:00
epenet e115c90719 Reduce internal testing in arcam_fmj tests (#165315) 2026-03-11 10:14:24 +01:00
epenet 6ad3adf0c3 Remove duplicate fixture in arcam_fmj tests (#165312) 2026-03-11 09:51:51 +01:00
dependabot[bot] 2a8d59be4c Bump docker/login-action from 3.7.0 to 4.0.0 (#165302) 2026-03-11 09:16:34 +01:00
dependabot[bot] 6e6e35bc3b Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#165304) 2026-03-11 09:15:36 +01:00
epenet 795b4c8414 Fix incorrect type annotations in tests (#165305) 2026-03-11 08:38:58 +01:00
Luke Lashley 16389dc18e Bump python-roborock to 4.20.0 (#165292) 2026-03-11 08:21:28 +01:00
Erik Montnemery e7a1c8d001 Remove triggers binary_sensor.occupancy_cleared and occupancy_detected (#165181) 2026-03-11 07:37:40 +01:00
Luke Lashley 4efb10dae1 Remove an extra roborock trait from updating (#165297) 2026-03-11 02:31:10 +01:00
Erik Montnemery f163576e78 Fail more tests when pytest_socket.SocketBlockedError is raised (#155398)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-11 00:00:00 +01:00
Erik Montnemery cad8f97e97 Prevent network access in telegram_bot tests (#165284) 2026-03-10 21:53:35 +01:00
Jeef 4ae6099d84 Add local/cloud option to Intellifire (#162739)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 21:11:57 +01:00
epenet 60dc88fa15 Move NUT coordinator to separate module (#164848)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 09:13:00 -10:00
Josh Gustafson 2d2c6d676d Address Arcam FMJ post-merge feedback (#165277)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 20:09:54 +01:00
Matthias Alphart f3879335ab KNX: add config for unit_of_measurement for yaml sensor entities (#165082) 2026-03-10 19:27:59 +01:00
Matthias Alphart 11bc00038e KNX: add config for device_class and unit_of_measurement for yaml number entities (#165083) 2026-03-10 19:27:48 +01:00
David Bonnes 6845e8b880 Extend RESET_SYSTEM action to all Evohome controller types (#164459) 2026-03-10 19:27:35 +01:00
cdheiser 5741016931 Bump pylutron version to 0.3.0 (#164707)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-10 18:10:28 +01:00
WardZhou 6cbc4e7f62 Add support for Thread Integration to Display Icons for Aeotec SmartThings TBRs (#165275) 2026-03-10 18:07:50 +01:00
Joost Lekkerkerker 4064df0114 Create reset HEPA filter button for main component in SmartThings (#165262) 2026-03-10 18:00:55 +01:00
Troels Schwarz-Linnet 789f850691 Implement 2 new sensors in pyvicare (#164523) 2026-03-10 17:59:36 +01:00
Abílio Costa efca71852b Implement exception-translations for whirlpool integration (#165017) 2026-03-10 17:56:59 +01:00
A. Gideonse 1967e9f309 Add reconfiguration flow to Indevolt integration (#165132) 2026-03-10 17:43:19 +01:00
Artur Pragacz 6ac0c163aa Improve group entities (#160860) 2026-03-10 17:34:52 +01:00
Norbert Rittel bbe20fd698 Improve descriptions of bond actions (#164744) 2026-03-10 17:08:23 +01:00
hanwg f576743340 Fix proxy settings not applied for Telegram bot (#165240) 2026-03-10 16:42:46 +01:00
John O'Nolan 3b4a1fba5f Update Ghost integration quality scale to gold (#165215) 2026-03-10 16:25:15 +01:00
Artur Pragacz 1677a9bfa6 Add clean area intent for vacuum (#165182) 2026-03-10 16:24:18 +01:00
Jordan Harvey 0d9c458705 Anglian Water: Add last meter reading processed sensor (#159144)
Co-authored-by: Ariel Ebersberger <31776703+justanotherariel@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-03-10 16:18:11 +01:00
epenet 57026a862d Ensure actions have name and description translations (#158243)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-10 16:06:51 +01:00
Josh Gustafson fd05be4c52 Refactor Arcam FMJ to use coordinator pattern (#165232)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 15:37:09 +01:00
Dave Love b1f038849e Add Midea Smart Inverter Window AC to Matter Fan Only mode list (#165170) 2026-03-10 15:28:09 +01:00
Ariel Ebersberger b46c9ccc65 Influxdb: Add reconfigure flow (#165186) 2026-03-10 15:06:31 +01:00
epenet 80601426cf Move spotify coordinator to separate module (#164927) 2026-03-10 15:01:04 +01:00
Michael 9519bd2428 Add turned off and turned on triggers to input boolean (#158824)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-03-10 14:26:15 +01:00
Manu be0b7f06a8 Bump pyrate-limiter to 4.0.2, PSNAWP to 3.0.3, python-roborock to 4.17.2 (#164133)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-03-10 13:54:37 +01:00
Joost Lekkerkerker d30c6de168 Add another air purifier fixture to SmartThings (#165261) 2026-03-10 12:30:12 +01:00
Jan Čermák 6febd78e00 Move labels back to top 2026-03-10 09:44:05 +01:00
Sab44 0fa666518e Dynamically add new devices to Libre Hardware Monitor (#165250) 2026-03-10 09:19:50 +01:00
Jan Čermák 8ec6e36d3f Remove default BUILD_FROM in main Dockerfile for now 2026-03-10 09:17:56 +01:00
Jan Čermák a04fc6b260 Mark autogenerated files 2026-03-10 09:17:08 +01:00
Josef Zweck cf454a1fa3 Bump onedrive-personal-sdk to 0.1.6 (#165219) 2026-03-10 09:13:07 +01:00
Jan Čermák 5c307fbb23 Remove dynamic labels from dockerfiles 2026-03-10 08:55:56 +01:00
Panda-NZ a36733c4dc Add ambient temperature range controls to ToGrill integration (#165235) 2026-03-09 23:40:30 +01:00
Bram Kragten bf846e0756 Validate reorder is only used when multiple is true (#165216) 2026-03-09 22:32:02 +01:00
Erik Montnemery c037dad093 Add humidity triggers (#165197) 2026-03-09 20:34:26 +01:00
Erik Montnemery ce11e66e1f Add cover triggers (#165188)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-09 19:37:36 +01:00
David Bishop f38ca7b04a Add unique_id to Whisker (Litter-Robot) config entries (#164766)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-09 19:35:34 +01:00
Tor André Roland 01200ef0a8 Optimizations to Adax local device control (#162109)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-09 19:29:43 +01:00
mettolen c5e0c78cbc Minor Saunum integration improvements (#164705) 2026-03-09 19:22:27 +01:00
g4bri3lDev 7681caa936 Add diagnostics to OpenDisplay integration (#165222) 2026-03-09 19:05:52 +01:00
Bram Kragten 230a2ff045 Add reorder support to area selector (#165211) 2026-03-09 17:40:34 +01:00
A. Gideonse 9d828502a3 Fix code owner for indevolt integration (#165214) 2026-03-09 17:40:00 +01:00
Samuel Xiao 28088a7e1a Switchbot Cloud: Compatible with new device types (#165191) 2026-03-09 17:12:39 +01:00
epenet 9e8171fb77 Improve test coverage in Tuya light (#164954) 2026-03-09 17:11:26 +01:00
John O'Nolan 1660d3b28a Add stale device removal to Ghost integration (#165134) 2026-03-09 17:10:13 +01:00
Josef Zweck 2ef81a54a5 Allow backups to report the upload progress (#163608)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-09 17:12:49 +02:00
Samuel Xiao ce6154839e Switchbot Cloud: Fixed light mode settings error (#164723) 2026-03-09 15:50:02 +01:00
Erik Montnemery a25300b8e1 Fix import in cover (#165199) 2026-03-09 15:27:12 +01:00
Leon Grave 6fa8e71b21 Add freshr integration, based on pyfreshr (#164538)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-09 15:26:03 +01:00
tronikos c983978a10 Remove type: ignore in Android TV Remote (#165126) 2026-03-09 14:42:51 +01:00
Joost Lekkerkerker 68b8b6b675 Add fixture for Air Purifier to SmartThings (#165187) 2026-03-09 14:21:34 +01:00
Martin Hjelmare ee4d313b10 Fix update tests for Python 3.14.3 (#165196) 2026-03-09 14:21:18 +01:00
Erik Montnemery 5e665093c9 Revert "Add number.changed trigger" (#165193) 2026-03-09 13:55:08 +01:00
A. Gideonse 9a5f509ab9 Fix missing Gen-2 sensor for the Indevolt integration (#165133) 2026-03-09 13:49:54 +01:00
Erik Montnemery 8d0cd5edaa Remove some climate and humidifier triggers (#165192) 2026-03-09 13:37:31 +01:00
epenet 71726272f5 Speed up SmartThings tests (#165184)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 13:25:14 +01:00
epenet 9c6c27ab56 Avoid duplicate id/label in smartthings device fixtures (#165190) 2026-03-09 12:40:11 +01:00
Joost Lekkerkerker db20cf8161 Rename SmartThings devices to maintain uniqueness (#165189) 2026-03-09 12:16:07 +01:00
John O'Nolan 59b6270157 Add reconfigure flow to Ghost integration (#165131) 2026-03-09 11:57:40 +01:00
epenet a65ba01bbe Mark climate type hints as mandatory (#164982)
Co-authored-by: Robert Resch <robert@resch.dev>
2026-03-09 11:50:42 +01:00
Erik Montnemery a5d0350560 Add garage_door triggers (#165144)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 11:42:09 +01:00
Shai Ungar 368993556f Bump pyseventeentrack to 1.1.2 (#165089)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 10:38:48 +01:00
Daniel Shneyder 23ea17eaef Bump kaiterra-async-client to 1.1.0 (#165166) 2026-03-09 09:59:55 +01:00
g4bri3lDev 6ace93e45b Bump py-opendisplay to 5.5.0 (#165138)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 09:29:57 +01:00
epenet 237a0ae03f Improve type hints in ecobee climate (#165178) 2026-03-09 09:16:43 +01:00
epenet 6067be6f49 Improve type hints in lightwave climate (#165179) 2026-03-09 09:16:29 +01:00
J. Nick Koston a35c3d5de5 Bump yalexs-ble to 3.3.0 (#165168) 2026-03-08 16:39:30 -10:00
J. Nick Koston e9c3634cb6 Bump habluetooth to 5.9.1 and bleak-retry-connector to 4.6.0 (#165022) 2026-03-08 16:16:53 -10:00
J. Nick Koston 2ba4544180 Bump yalexs-ble to 3.2.8 (#165018) 2026-03-09 03:07:49 +01:00
Artur Pragacz 5235ce7ae4 Lower ssdp discovery timeout log severity in Onkyo (#165156) 2026-03-09 02:19:42 +01:00
Oscar 56b601e577 Add basic auth support to remote_calendar (#158075) 2026-03-08 16:52:58 -07:00
Justin Boyd f01a0586cb Bump airtouch5py to 0.4.0 (#161640)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-03-08 21:47:06 +01:00
Erwin Douna ca641a097b Fix forced VERIFY_SSL in Portainer (#165079) 2026-03-08 13:19:45 +01:00
Åke Strandberg df2f9d9ef8 Add missing code for Miele dryer (#165122) 2026-03-08 13:18:54 +01:00
Bouwe Westerdijk 501301f4e0 Bump plugwise to v1.11.3 (#165053) 2026-03-08 13:15:44 +01:00
Joakim Plate 89231a1a29 Update pychromecast to 14.0.10 (#165069) 2026-03-08 13:14:34 +01:00
John O'Nolan fe11a6d38f Add diagnostics to Ghost integration (#165130) 2026-03-08 13:03:57 +01:00
Artur Pragacz 3154c3c962 Make restore state resilient to extra_restore_state_data errors (#165086) 2026-03-08 10:39:53 +01:00
mettolen 5031323dea Add description strings to Huum integration (#165094) 2026-03-08 10:24:15 +01:00
Henning Kerstan 017a9e6938 Bump enocean-async to 0.4.2 (#165084) 2026-03-08 09:02:51 +00:00
tronikos 9e974ab30e Add diagnostics in Opower (#165113) 2026-03-08 09:14:15 +01:00
Norbert Rittel 30c0d6792a Make spelling of "auto-empty dock" consistent in roborock (#165117) 2026-03-08 09:12:56 +01:00
Erwin Douna 9ffb9aa824 Bump pyportainer to 1.0.33 (#165080) 2026-03-08 08:33:33 +01:00
A. Gideonse 9ad71711da Add diagnostics to Indevolt integration (#165096) 2026-03-08 08:32:18 +01:00
Steve Easley ef83165159 Bump jvc_projector dependency to 2.0.2 (#165099) 2026-03-08 08:29:53 +01:00
Jordan Harvey f0108c1175 Bump pyanglianwater to 3.1.1 (#165097) 2026-03-08 08:28:06 +01:00
Richard Kroegel 802aa991a9 Remove broken BMW & Mini integrations (#165075) 2026-03-08 00:00:03 +00:00
Sab44 f055c6c7fd Add quality scale exemptions for discovery in Libre Hardware Monitor (#165085) 2026-03-07 23:29:07 +01:00
Joel Hawksley 2a8b045f43 Update weatherkit to fetch hourly data for 7 days (#164494) 2026-03-07 19:08:13 +00:00
Erik Montnemery 281f439bc9 Add trigger door.closed (#165057) 2026-03-07 13:18:46 +00:00
Erik Montnemery 71b420b433 Add trigger door.opened (#164728) 2026-03-07 12:59:09 +01:00
J. Nick Koston 2f02d0f0dc Bump bleak-esphome to 3.7.1 (#165025) 2026-03-07 11:27:59 +00:00
Allen Porter 37cb3cbd50 Bump pyrainbird to 6.1.1 (#165030) 2026-03-07 11:27:28 +00:00
AlCalzone beec21c4a9 Fix cover state updates for legacy Multilevel Switch based Z-Wave covers (#165003) 2026-03-07 12:16:30 +01:00
Pete Sage 642f603ea2 Add binary_sensors for Rehlko load shedding (#164984) 2026-03-07 11:59:44 +01:00
Abílio Costa a3d8d76678 Simplify AGENTS.md (#164894) 2026-03-07 06:27:44 +01:00
J. Nick Koston c25feaa62b Bump aioesphomeapi to 44.3.1 (#165023) 2026-03-06 19:02:18 -10:00
Glenn Waters 50bde6fccd Hunter Douglas Powerview: Fix missing class in hierarchy. (#164264)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-06 21:16:38 +01:00
Karl Beecken 1b7398c271 Bump teltasync to 0.2.0 (#164995) 2026-03-06 21:16:19 +01:00
Sid 7e4b8e802e Add support for the reeflexUV+e to eheimdigital (#163656) 2026-03-06 20:28:39 +01:00
Joost Lekkerkerker 4bcea27151 Bump spotifyaio to 2.0.2 (#164114)
Co-authored-by: Robert Resch <robert@resch.dev>
2026-03-06 20:28:04 +01:00
konsulten ffca43027f Add reconfigure flow for systemnexa2 (#164361) 2026-03-06 20:23:17 +01:00
Joshua Leaper 01e94ca5b2 Update ness_alarm scan interval to 5 secs (#164835) 2026-03-06 20:12:35 +01:00
Petro31 b8ea6b4162 Update template light test framework (#164688) 2026-03-06 20:12:10 +01:00
epenet 1471cb93bc Move smart_meter_texas coordinator to separate module (#164926)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:11:38 +01:00
Erwin Douna 2f7ac2b439 Migrate Smartthings OAuth exceptions (#164939)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-06 20:10:41 +01:00
epenet 0accb403be Move WattTime coordinator to separate module (#164726)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:10:14 +01:00
epenet f49a323faf Move wolflink coordinator to separate module (#164929)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-06 20:08:29 +01:00
TimL 21d303dbbc Fix button entity creation for devices with more than two radios (#164699) 2026-03-06 20:07:56 +01:00
Antonio Mello c080a460a2 Fix IntesisHome outdoor_temp not reported when value is 0.0 (#164703)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:07:11 +01:00
epenet 75d675f299 Move AirVisual coordinator to separate module (#164738)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:06:18 +01:00
epenet a7e7d01b7a Move launch_library coordinator to separate module (#164747)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:05:42 +01:00
epenet 8a0569e279 Move AirVisual Pro coordinator to separate module (#164742)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-06 20:05:30 +01:00
epenet e8279bd20f Move LED BLE coordinator to separate module (#164749)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:04:51 +01:00
epenet 852dbf8986 Move peco coordinator to separate module (#164851)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:04:34 +01:00
hanwg 6f0eb1d07a Upgrade IQS to gold for Telegram bot (#164911) 2026-03-06 20:04:01 +01:00
epenet 6f68d91593 Move DataUpdateCoordinator to coordinator module in tesla_wall_connector (#164937)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 20:01:16 +01:00
epenet ffc17b6e91 Move whois coordinator to separate module (#164936)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-06 20:00:18 +01:00
epenet 0d04d79844 Move DataUpdateCoordinator to separate module in reolink (#164914)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:59:56 +01:00
epenet f57884cb95 Move kraken API wrapper class to coordinator module (#164942)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 19:54:20 +01:00
Manu 3a83fe5c72 Change setpoint step size in IronOS integration (#164979) 2026-03-06 19:38:26 +01:00
Willem-Jan van Rootselaar 973feb71c1 Bump python-bsblan to 5.1.2 (#164963) 2026-03-06 19:37:55 +01:00
epenet ecee23fc7a Move pi_hole coordinator to separate module (#164869)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-06 19:36:52 +01:00
epenet 442d2282dc Improve type hints in maxcube climate (#164978) 2026-03-06 18:10:51 +01:00
Robert Resch 8853d3e17d Add lawn mower started_returning trigger (#164834) 2026-03-06 18:08:28 +01:00
epenet 6d1e387911 Improve type hints in airtouch4 climate (#164977) 2026-03-06 18:05:27 +01:00
epenet 13fe135e7f Improve type hints in nexia climate (#164976) 2026-03-06 18:04:56 +01:00
epenet 618687ea05 Improve type hints in nuheat climate (#164975) 2026-03-06 18:04:24 +01:00
epenet 8b545a6e76 Improve type hints in oem climate (#164974) 2026-03-06 18:04:07 +01:00
epenet 42fa13200d Improve type hints in proliphix climate (#164972) 2026-03-06 18:03:39 +01:00
epenet d56e944a86 Improve type hints in schluter climate (#164970) 2026-03-06 18:03:17 +01:00
epenet fb357390ce Remove disabled Tfiac integration (#164966) 2026-03-06 18:00:42 +01:00
Shay Levy 702450e209 Bump aioswitcher to 6.1.1 (#164981) 2026-03-06 17:54:38 +01:00
g4bri3lDev bbe45e0759 Add OpenDisplay integration (#164048)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-03-06 16:23:09 +01:00
epenet 92902c7aa1 Improve type hints in smarttub climate (#164968) 2026-03-06 16:07:41 +01:00
epenet 5d92dd7760 Use shorthand attributes in zhong_hong climate (#164964) 2026-03-06 16:00:14 +01:00
Joost Lekkerkerker 0ab62dabde Create Chess.com integration (#164960) 2026-03-06 15:55:59 +01:00
Sean O'Keeffe fc68828c78 more programs for Miele steam ovens (#164768)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-06 15:15:43 +01:00
Sab44 7644036592 Add diagnostics to Libre Hardware Monitor (#164958)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-06 15:15:18 +01:00
epenet f19068f7de Mark device_info type hint as mandatory (#164951) 2026-03-06 15:15:05 +01:00
Robin Lintermann 13d2211755 Add sensor entity for total swing time (#164334) 2026-03-06 15:10:06 +01:00
epenet 87e63591d1 Use shorthand attributes in heatmiser climate (#164957) 2026-03-06 15:00:51 +01:00
epenet fc02bbcdd0 Improve type hints in coolmaster climate (#164956) 2026-03-06 15:00:13 +01:00
Simone Chemelli 388d619604 Bump aiovodafone to 3.1.3 (#164955) 2026-03-06 14:59:51 +01:00
Daniel Hjelseth Høyer 3777acff95 Fix energy unit in Homevolt (#164959)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2026-03-06 14:58:44 +01:00
Jamie Magee e0fd6784cf Test aladdin_connect stale device cleanup (#164119) 2026-03-06 13:03:09 +01:00
epenet 305463d882 Move DataUpdateCoordinator to coordinator module in nsw_fuel_station (#164940)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:25:07 +01:00
Erwin Douna de16edc55b Replace assert in Proxmox coordinator (#164892) 2026-03-06 11:16:14 +01:00
Erwin Douna bd6438937b Adjust read-only parallel updates for Portainer (#164890) 2026-03-06 11:14:58 +01:00
Erwin Douna 45e453791e Update Proxmox code owners (#164941) 2026-03-06 11:11:06 +01:00
epenet 152137a3a2 Move DataUpdateCoordinator to separate module in simplisafe (#164917)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 11:10:31 +01:00
epenet e059c51b1d Move wiz coordinator to separate module (#164931)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:21:07 +01:00
epenet 9ef66a3a90 Move supla coordinator to separate module (#164928)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 10:20:42 +01:00
Petro31 494f8c32d5 Fix 'this' variable in template options flow (#164866) 2026-03-06 09:39:42 +01:00
dependabot[bot] 51f90a328b Bump actions/attest-build-provenance from 3.2.0 to 4.1.0 (#164909)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-06 09:38:33 +01:00
epenet b7bdb7b32a Move DataUpdateCoordinator to separate module in subaru (#164918)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 09:09:03 +01:00
epenet 76c8bae098 Use typed coordinator in powerwall (#164887)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 08:35:09 +01:00
Erwin Douna 59a75e74fe Bump proxmoxer 2.3.0 (#164884) 2026-03-06 08:34:45 +01:00
Christopher Fenner a4af1ce5f8 Translate device name in Season integration (#164882) 2026-03-06 08:33:20 +01:00
Erwin Douna 30ea0b4923 Proxmoxve add parallel updates (#164889) 2026-03-06 08:32:36 +01:00
Erwin Douna fb889dd524 Optimize init proxmox (#164891) 2026-03-06 08:32:18 +01:00
epenet 31055c5cde Move DataUpdateCoordinator to separate module in recollect_waste (#164913)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:31:15 +01:00
epenet a264e5949f Move DataUpdateCoordinator to separate module in senz (#164916)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 08:30:29 +01:00
Colin 84260ac3f7 Use shared aiohttp session in openevse (#164552) 2026-03-06 07:49:53 +01:00
epenet f50a35877d Move RDW DataUpdateCoordinator to separate module (#164910)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 07:47:08 +01:00
Luke Lashley 6bc94a318a Pass in Base Url during Roborock reauth (#164903) 2026-03-05 20:24:59 -08:00
Blake Messer b0904917ca Fix Rain Bird controllers updated by Rain Bird 2.x (#163915) 2026-03-05 19:37:15 -08:00
Michael 536cfc4c67 Add number.changed trigger (#163984) 2026-03-05 21:36:39 +01:00
Erwin Douna 27b647fa36 Add backoff/max retries in Portainer API (#164805)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-03-05 21:26:22 +01:00
Michael 16fb2dfa91 Add domain driven triggers to schedule helper (#159325) 2026-03-05 21:26:05 +01:00
Josef Zweck 664b75e060 Bump onedrive-personal-sdk to 0.1.5 (#164880) 2026-03-05 20:19:19 +00:00
Erik Montnemery 1cd302eb17 Fix flaky bang_olufsen tests (#164868) 2026-03-05 21:18:10 +01:00
Dan Carroll 8da86796d2 Bump pyeconet to 0.2.2 (#164859) 2026-03-05 20:17:57 +00:00
Denis Shulyaka 33c0edc994 Add GPT-5.4 support to OpenAI conversation (#164883) 2026-03-05 20:16:53 +00:00
epenet 3e8833da54 Refactor Tuya wrappers to use generics (#164587) 2026-03-05 19:22:48 +01:00
Michael Hansen 3858d557b3 Add missing parameters from handle REST API (#164687)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
2026-03-05 11:48:57 -06:00
Renat Sibgatulin 0923bed4b6 Add zeroconf support for air-Q (#164727)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 17:55:34 +01:00
Marc Mueller 9b8432eac3 Fix volvo test RuntimeWarning (#164845) 2026-03-05 17:51:12 +01:00
Tucker Kern 5232c05702 Ensure Snapcast client has a valid current group before accessing group attributes. (#164683) 2026-03-05 17:50:31 +01:00
Erik Montnemery e5f77801a7 Unconditionally set up base platform integrations (#164863) 2026-03-05 17:30:34 +01:00
Erik Montnemery bc138b3485 Fix incomplete device info in laundrify sensor (#164824)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-05 17:08:31 +01:00
Andrew Jackson ae90c5fa92 Update Mastodon quality scale to gold (#164842) 2026-03-05 16:50:45 +01:00
Matthias Alphart 2fce45abe1 Fix KNX sensor default attributes for energy and volume DPTs (#164838)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 16:48:47 +01:00
karwosts e4417f7b00 Add unique_id to demo water_heater (#164857) 2026-03-05 16:40:17 +01:00
Ariel Ebersberger b57c7f8a95 Fix ffmpeg fixture (#164860) 2026-03-05 16:37:43 +01:00
Henning Kerstan 0618460d73 Replace enocean library (#164272)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 16:30:06 +01:00
epenet 92dd045772 Move Mullvad VPN coordinator to separate module (#164750)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 16:20:58 +01:00
Michael Hansen fc723e1a42 Add missing features to Wyoming conversation agent (#164278) 2026-03-05 15:56:21 +01:00
Joshua Monta 5907356309 Add new influenza index sensor to Uhoo (#164710) 2026-03-05 15:37:22 +01:00
J. Diego Rodríguez Royo 1c221b4714 Bump aiohomeconnect to 0.30.0 (#164846)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-05 15:34:12 +01:00
Retha Runolfsson 05d57167d2 Add support for switchbot keypad vision (#160484)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-05 14:54:07 +01:00
epenet 69a98dd53e Move nuheat coordinator to separate module (#164833)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 14:16:55 +01:00
John O'Nolan 3c7dd93c7f Add reauthentication flow to Ghost integration (Silver) (#164847) 2026-03-05 14:16:03 +01:00
reneboer 1327712be4 Add sensor charging settings mode (#164455)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-03-05 13:24:23 +01:00
epenet 933e57ba6a Simplify Netgear entity initialisation (#164837) 2026-03-05 13:17:19 +01:00
John O'Nolan 77d54aadc6 Fix Ghost config flow using wrong field name for site UUID (#164836) 2026-03-05 12:46:59 +01:00
Jan Čermák 36cb3e21fe Merge remote-tracking branch 'origin/dev' into gha-builder 2026-03-05 12:17:11 +01:00
Jan Čermák f645b232f9 Fix container-(username|password) -> container-registry-(username|password) 2026-03-05 12:14:36 +01:00
Jan Čermák e8454d9b2c Use updated build-image action inputs, sort alphabetically 2026-03-05 12:10:43 +01:00
Andreas Jakl 5fe2ab93ff Add device tracker to NRGkick integration (#164804)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-05 12:00:30 +01:00
Glenn de Haan 0e4698eb99 Add device class to active_liter_lpm sensor (#164809) 2026-03-05 11:50:37 +01:00
epenet 698c5eca00 Migrate remaining netgear coordinators to separate module (#164826) 2026-03-05 11:49:28 +01:00
Raphael Hehl c7776057b7 Enforce SSRF redirect protection only for connector allowed_protocol_schema_set (#164769)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-03-05 11:45:05 +01:00
Jan Čermák 02ae9b2f71 Generate machine dockerfiles using hassfest script 2026-03-05 11:22:12 +01:00
Erik Montnemery e87c677cc4 Improve homee tests (#164820) 2026-03-05 11:15:50 +01:00
Erik Montnemery c3858a0841 Improve tuya diagnostic tests (#164819) 2026-03-05 11:13:01 +01:00
Michael 42bc5c3a5f Add remote.turned_on and remote.turned_off triggers (#164535)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-03-05 10:52:29 +01:00
epenet 76bc58da2c Add base NetgearDataCoordinator to netgear (#164816) 2026-03-05 10:52:12 +01:00
epenet fc8719ce35 Remove caio from licenses exception list (#164806) 2026-03-05 10:18:08 +01:00
dependabot[bot] 60a4a97d9c Bump dawidd6/action-download-artifact from 14 to 16 (#164790)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-05 10:16:23 +01:00
Erwin Douna 284721e1df Bump pyportainer 1.0.32 (#164803) 2026-03-05 09:06:46 +01:00
Norbert Rittel bfa707d79e Use common string for "host" in devialet config flow (#164798) 2026-03-05 08:32:46 +01:00
Norbert Rittel 633e2e7469 Use common state for "medium" in smartthings (#164799) 2026-03-05 08:32:35 +01:00
dependabot[bot] ad1c6846e7 Bump actions/upload-artifact from 6.0.0 to 7.0.0 (#164791) 2026-03-05 07:29:59 +01:00
Erwin Douna f75140b626 Add const to Portainer for endpoint up (#164746) 2026-03-05 00:38:59 +01:00
rappenze f83757da7c Use unique fibaro_id in test fixtures (#164763) 2026-03-04 22:04:38 +00:00
Norbert Rittel ca338c98f3 Clarify description of vacuum.clean_area action (#164764) 2026-03-04 21:57:59 +00:00
Ian Foster 18a8afb017 Update keyboard_remote dependencies (#164755) 2026-03-04 19:47:17 +01:00
Jan Čermák f6f7390063 Restore build context also in build_python 2026-03-04 18:26:21 +01:00
Jan Čermák bfa1fd7f1b Use new home-assistant/builder actions for image builds
This PR completely drops usage of the builder action in favor of new actions
introduced in home-assistant/builder#273. This results in faster builds with
better caching options and simple local builds using Docker BuildKit.

The image dependency chain currently still uses per-arch builds but once
docker-base and docker repositories start publishing multi-arch images, we can
simplify the action a bit further.

The idea to use composite actions comes from #162245 and this PR fully predates
it. There is minor difference that the files generated twice in per-arch builds
are now generated and archived by the init job.
2026-03-04 18:05:12 +01:00
Italo Lombardi 0136e9c7eb ISS integration: better entity handling (#159050)
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-03-04 17:46:48 +01:00
Erik Montnemery d88c736016 Add is_closed state attribute to cover (#164739) 2026-03-04 16:54:06 +01:00
Robert Resch 780dc178a1 Use Python version file in CI for setting the default python version (#164751) 2026-03-04 16:53:31 +01:00
Petro31 b7ba945dfc Fix this variable preview issue with template entities from the UI (#164740) 2026-03-04 16:01:41 +01:00
Magnus Øverli 01de7052af Add deprecation timeline to flexit_bacnet fireplace switch (#164450) 2026-03-04 15:47:40 +01:00
Allen Porter 3fe6a31ee9 Improve Roborock device info creation and enhance device registration for disabled or failed devices. (#164553) 2026-03-04 15:45:51 +01:00
rappenze 95570643ec Fix handling of several thermostat QuickApp's in fibaro (#164344)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 15:40:49 +01:00
starkillerOG e3210b0ab9 Fix Reolink entity unique_id migration when unique_id already exists (#164667) 2026-03-04 15:12:26 +01:00
Artur Pragacz 2edabf903a Add backup integration to recovery mode (#164734) 2026-03-04 14:33:28 +01:00
Stefan Agner 0e4e703b64 Ignore transient empty segments in Matter vacuum (#164737)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-04 14:24:28 +01:00
tobiaswaldvogel 88624f5179 Use jog up/down in motionblinds if no tilt position is available (#164694)
Signed-off-by: Tobias Waldvogel <tobias.waldvogel@gmail.com>
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
2026-03-04 13:27:47 +01:00
Erwin Douna 4a5fdfc0ec Bump pyportainer 1.0.31 (#164733) 2026-03-04 13:26:10 +01:00
Bram Kragten c6e91afae4 Update frontend to 20260304.0 (#164736) 2026-03-04 13:25:57 +01:00
Kamil Breguła db5e7e4521 Refactor AWS S3 tests (#164098)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 13:13:43 +01:00
Joakim Plate 25489c224b Restore handling of is active input for chromecast (#164735) 2026-03-04 13:10:10 +01:00
Tom c4f64598a0 Add informative errors to Proxmox VE buttons (#164417) 2026-03-04 12:48:17 +01:00
starkillerOG 59e579cf5a Bump reolink-aio to 0.19.1 (#164732) 2026-03-04 12:46:38 +01:00
epenet 831c28cf2c Migrate netgear to use runtime_data (#164718) 2026-03-04 11:37:05 +01:00
Erik Montnemery be1affc6ba Pin exact Python version in .python-version (#164722) 2026-03-04 11:21:44 +01:00
J. Diego Rodríguez Royo 94a25b5688 Improve mobile_app notify.notify with not connected targets (#161855) 2026-03-04 11:11:02 +01:00
AlCalzone 382940d661 Support Z-Wave Hoppe eHandle tilt sensor (#164689) 2026-03-04 11:00:24 +01:00
Brett Adams b8e1c0cf2c Fix teslemetry time_of_use service tariff double-wrapping (#164702)
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 09:59:52 +01:00
TheJulianJES 0d23d8dc09 Bump ZHA to 1.0.1 (#164709) 2026-03-04 09:57:07 +01:00
dependabot[bot] b750de1e3e Bump actions/ai-inference from 2.0.6 to 2.0.7 (#164713)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-04 09:45:00 +01:00
hanwg 7d7e8e0bde Add support for http webhook for Telegram bot (#162690) 2026-03-04 09:18:02 +01:00
Joost Lekkerkerker d6f355355f Add cleaning type select to SmartThings (#164472)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-03-04 07:18:18 +01:00
Simone Chemelli 5dad64e54c Bump aioamazondevices to 13.0.0 (#164618) 2026-03-03 22:16:07 +00:00
Robert Resch c311ff0464 Fix wheels building by using arch dependent requirements_all file (#164675) 2026-03-03 21:55:59 +01:00
Dave T c45675a01f Add additional diagnostic sensors to aurora_abb_powerone PV inverter (#164622) 2026-03-03 21:34:44 +01:00
erikbadman 9d92141812 Add support for active power limit in Kostal Plenticore (#164674) 2026-03-03 21:33:54 +01:00
Robin Lintermann 501b973a98 Add send diagnostics button to smarla (#164335) 2026-03-03 21:31:31 +01:00
Kamil Breguła fd4d8137da Change reconfiguration-flow status to 'todo' in WebDAV (#164637)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 21:23:24 +01:00
Miguel Angel Nubla 33881c1912 Fix infinite loop in esphome assist_satellite (#163097)
Co-authored-by: Artur Pragacz <artur@pragacz.com>
2026-03-03 20:44:36 +01:00
Robin Lintermann 9bdb03dbe8 Set device classes and measurement units for Smarla (#164682) 2026-03-03 18:36:02 +00:00
epenet d2178ba458 Cleanup deprecated tuya entities (#164657) 2026-03-03 19:31:09 +01:00
Abílio Costa 06cdf3c5d2 Add PR review Claude skill (#164626) 2026-03-03 18:21:51 +00:00
r2xj 84c994ab80 Add support for samsungce.lamp as light entity and when not under main component (#164448)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-03 18:29:36 +01:00
Abílio Costa 1d5913d7a5 Simplify copilot-instructions.md script to use file refs (#164686) 2026-03-03 17:17:25 +00:00
epenet 05acba37c7 Remove deprecated YAML import from nederlandse_spoorwegen (#164662) 2026-03-03 17:59:29 +01:00
Samuel Xiao 7496406156 Bumb switchbot api to v2.11.0 (#164663) 2026-03-03 17:59:03 +01:00
epenet 543f2b1396 Improve type hints in meteoclimatic (#164651) 2026-03-03 17:57:54 +01:00
epenet 3df2bbda80 Bump tuya-device-handlers to 0.0.11 (#164586) 2026-03-03 17:57:36 +01:00
epenet b661d37a86 Move mutesync coordinator to separate module (#164600) 2026-03-03 17:57:11 +01:00
Ariel Ebersberger 2102babc6d Influxdb repair issue follow up (#164684) 2026-03-03 17:57:09 +01:00
epenet f3a1cab582 Migrate motionblinds_ble to runtime_data (#164601) 2026-03-03 17:56:54 +01:00
epenet 03c9ce25c8 Simplify access to motioneye client (#164599) 2026-03-03 17:56:16 +01:00
Christian Lackas 8fcabcec16 Fix HomematicIP heating group availability with unreachable members (#162571) 2026-03-03 17:34:14 +01:00
Michael Hansen 2a33096074 Bump intents to 2026.3.3 (#164676) 2026-03-03 17:26:44 +01:00
Ariel Ebersberger 14a9eada09 Add repair issue after importing influxdb yaml config (#164145)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 16:33:25 +01:00
tobiaswaldvogel 4a00f78e90 Add missing cover entity features to motion_blinds (#164673)
Signed-off-by: Tobias Waldvogel <tobias.waldvogel@gmail.com>
2026-03-03 16:30:55 +01:00
starkillerOG abef46864e Fix key error in Reolink DHCP if still setting up (#164619) 2026-03-03 16:12:30 +01:00
Willem-Jan van Rootselaar 73b28f1ee2 Bump python-bsblan to 5.1.1 (#164591) 2026-03-03 15:56:07 +01:00
epenet 7379d41393 Migrate met_eireann to runtime_data (#164607) 2026-03-03 15:55:12 +01:00
epenet 89acb02519 Migrate monoprice to runtime_data (#164604) 2026-03-03 15:54:48 +01:00
Paul Tarjan e343e90da2 Fix Reolink camera updates persisting in UI (#161149)
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-03 15:40:32 +01:00
Daniel Schneider e9a576494b Bump ring-doorbell to 0.9.14 (#158074)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-03 15:36:26 +01:00
TimL 4e047b56d8 Bump pysmlight to v0.2.16 (#164665)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-03-03 14:47:54 +01:00
epenet a1e95c483d Migrate metoffice to runtime_data (#164606) 2026-03-03 14:19:57 +01:00
Andreas Jakl 9cb6e02c5f Add binary sensor platform and tests to NRGkick integration (#164629)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-03-03 13:55:10 +01:00
epenet 2c75e3289a Improve device_info type hints in mobile_app (#164655) 2026-03-03 13:40:56 +01:00
reneboer 348012a6b8 Bump renault-api to 0.5.6 (#164664) 2026-03-03 12:52:41 +01:00
Michael e0db00e089 Allow the creation of multi-domain triggers (#164628) 2026-03-03 12:52:27 +01:00
Thomas Pfeiffer b2280198d9 Add equalizer switch for Cambridge Audio devices (#162956) 2026-03-03 12:51:24 +01:00
Artur Pragacz 9cc4a3e427 Trigger recovery mode on registry major version downgrade (#164340) 2026-03-03 11:46:32 +01:00
Raman Gupta f94a075641 Decouple Vizio apps coordinator from config entry (#163923)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-03-03 11:22:41 +01:00
hanwg f1856e6ef6 Update subentry description for Telegram bot (#164642)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 11:21:01 +01:00
mettolen ed35bafa6c Bump pysaunum to 0.6.0 (#164530) 2026-03-03 11:18:02 +01:00
Manu 66e16d728b Bump python-xbox to 0.2.0 (#164616) 2026-03-03 11:10:14 +01:00
Matthias Alphart a806efa7e2 Update knx-frontend to 2026.3.2.183756 (#164623) 2026-03-03 11:08:20 +01:00
Norman Yee ad4b4bd221 Enhance GV5140 test to assert temperature and humidity sensors (#164644) 2026-03-03 11:05:32 +01:00
David Recordon c9c9a149b6 Bump pylutron-caseta to 0.27.0 (#164614) 2026-03-03 11:03:12 +01:00
epenet 0f9fdfe2de Fix invalid device registry identifiers in eafm (#164654)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-03 11:02:59 +01:00
Abílio Costa a76b63912d Add Ubisys virtual integration (#164314) 2026-03-03 10:00:57 +00:00
Joshua Monta bc03e13d38 Bump uhooapi to 1.2.8 (#164648) 2026-03-03 10:59:32 +01:00
Colin 450aa9757d Bump python-openevse-http to 0.2.5 (#164641) 2026-03-03 10:54:58 +01:00
Tom Matheussen 158389a4f2 Remove deprecated YAML import from Satel Integra (#164469) 2026-03-03 10:24:23 +01:00
Raman Gupta 95e89d5ef1 Redact zwave_js dsk key from diagnostics (#164636)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 10:01:35 +01:00
dependabot[bot] e107b8e5cd Bump actions/download-artifact from 7.0.0 to 8.0.0 (#164647)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-03 08:34:36 +01:00
epenet f875b43ede Remove unnecessary suppress in importlib helper (#164323) 2026-03-03 01:00:32 +01:00
Jeff Terrace 6242ef78c4 Move ONVIF event parsing into a module outside core (#164550)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-03-02 12:18:05 -10:00
Abílio Costa 3c342c0768 Add infrared platform to ESPHome (#162346)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 22:00:47 +00:00
Norman Yee 5dba5fc79d Add Govee H5140 CO2 monitor support to govee_ble (#164365)
Co-authored-by: J. Nick Koston <nick@koston.org>
2026-03-02 20:12:48 +00:00
James 713b7cf36d Check Daikin zone temp keys before represent (#164297)
Co-authored-by: barneyonline <barneyonline@users.noreply.github.com>
2026-03-02 19:48:39 +00:00
Bram Kragten cb016b014b Update frontend to 20260302.0 (#164612) 2026-03-02 18:53:01 +01:00
Michael Hansen afb4523f63 Add device_id and satellite_id to conversation HTTP/websocket APIs (#164414) 2026-03-02 17:01:51 +01:00
Alex Brown 05ad4986ac Fix Matter clear lock user (#164493) 2026-03-02 16:28:49 +01:00
epenet 42dbd5f98f Migrate moat to runtime_data (#164605) 2026-03-02 16:14:25 +01:00
epenet f58a514ce7 Migrate monzo to runtime_data (#164603) 2026-03-02 16:14:10 +01:00
Artur Pragacz 8fb384a5e1 Raise on vacuum area mapping not configured (#164595) 2026-03-02 15:36:48 +01:00
Samuel Xiao c24302b5ce Switchbot Cloud: Fixed Smart Radiator Thermostat off line (#162714)
Co-authored-by: Ariel Ebersberger <ariel@ebersberger.io>
2026-03-02 14:44:34 +01:00
Jan-Philipp Benecke 999ad9b642 Bump aiotankerkoenig to 0.5.1 (#164590) 2026-03-02 14:44:29 +01:00
Pierre Sassoulas 36d6b4dafe Use clearer number notation for very small and very large literals (#164521) 2026-03-02 14:06:19 +01:00
Norbert Rittel 06870a2e25 Replace "the lock" with "a lock" in matter action descriptions (#164585) 2026-03-02 12:56:45 +01:00
willemstuursma 85eba2bb15 Bump DSMR parser to 1.5.0 (#164484) 2026-03-02 12:52:37 +01:00
Joost Lekkerkerker 5dd6dcc215 Add select for SmartThings Water spray level (#164520) 2026-03-02 12:17:31 +01:00
epenet 8bf894a514 Migrate microbees to runtime_data (#164564) 2026-03-02 12:04:34 +01:00
epenet d3c67f2ae1 Migrate medcom_ble to runtime_data (#164557) 2026-03-02 12:03:35 +01:00
epenet b60a282b60 Move motioneye coordinator to separate module (#164568) 2026-03-02 11:57:19 +01:00
epenet 0da1d40a19 Migrate meteoclimatic to runtime_data (#164559) 2026-03-02 11:50:46 +01:00
Robert Resch aa3be915a0 Bump aiogithubapi to 26.0.0 (#164579) 2026-03-02 11:49:32 +01:00
Manu 0d97bfbc59 Bump pyloadapi to 2.0.0 (#164495) 2026-03-02 11:47:13 +01:00
epenet fe830337c9 Migrate modem_callerid to runtime_data (#164566) 2026-03-02 11:45:58 +01:00
epenet 5210b7d847 Migrate moehlenhoff_alpha2 to runtime_data (#164571) 2026-03-02 11:45:10 +01:00
Mike Ryan 2f7ed4040b Bump python-fullykiosk from 0.0.14 to 0.0.15 (#164511) 2026-03-02 11:42:56 +01:00
Simone Chemelli 6376ba93a7 Bump aioamazondevices to 12.0.2 (#164518) 2026-03-02 11:37:39 +01:00
J. Nick Koston fd3a1cc9f4 Bump yalexs-ble to 3.2.7 (#164555) 2026-03-02 11:36:05 +01:00
epenet 208013ab76 Move metoffice coordinators to separate module (#164562)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 11:31:57 +01:00
Alex Brown 770b3f910e Fix Matter lock credential slot iteration bound (#164478)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 10:56:03 +01:00
Norbert Rittel 5dce4a8eda Change one remaining string from "Overseerr" to "Seerr" (#164569) 2026-03-02 10:22:49 +01:00
Jan-Philipp Benecke 6fcc9da948 Fix large WebDAV backup metadata download (#164563)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-02 10:17:18 +01:00
epenet bf93580ff9 Migrate modern_forms to runtime_data (#164570) 2026-03-02 10:10:03 +01:00
Jan-Philipp Benecke 0c2fe045d5 Bump aiowebdav2 to 0.6.1 (#164560) 2026-03-02 10:09:33 +01:00
Joost Lekkerkerker e14a3a6b0e Fix SmartThings EHS power (#164395) 2026-03-02 08:35:37 +01:00
Joost Lekkerkerker e032740e90 Add time platform to SmartThings (#164451) 2026-03-02 08:34:53 +01:00
Joost Lekkerkerker 78ad1e102d Add binary sensor for full dust bag in SmartThings (#164457) 2026-03-02 08:34:19 +01:00
Joost Lekkerkerker 4f97cc7b68 Add sound detection sensitivity select to SmartThings (#164466) 2026-03-02 08:33:47 +01:00
dependabot[bot] df8f135532 Bump github/codeql-action from 4.32.3 to 4.32.4 (#164554) 2026-03-02 07:30:23 +01:00
J. Nick Koston 0066801b0f Bump yarl to 1.23.0 (#164542) 2026-03-02 07:22:37 +01:00
Joost Lekkerkerker 0aa66ed6cb Add select for SmartThings driving mode (#164522) 2026-03-01 19:11:58 +01:00
HadiAyache 6903463f14 Fix AccuWeather daily forecast crash when humidity average is missing (#163968)
Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-01 17:19:15 +01:00
Brett Adams a473010fee Update Tessie quality scale to silver (#164104)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-01 16:53:39 +01:00
Robin Lintermann ddf7a783a8 Bump smarla quality scale to silver (#164325) 2026-03-01 11:52:11 +01:00
Joost Lekkerkerker 513e4d52fe Add button to reset HEPA filter to SmartThings (#164464) 2026-03-01 07:33:10 +01:00
Klaas Schoute 17bb14e260 Update error handling messages for Powerfox Local integration (#164465) 2026-03-01 07:32:36 +01:00
Brett Adams cd1258464b Fix OAuth token type narrowing in Teslemetry (#164505) 2026-03-01 07:31:34 +01:00
Allen Porter d3f5e0e6d7 Update nest access token error handling to use specific OAuth2 token request exceptions (#164506) 2026-03-01 07:26:07 +01:00
Joost Lekkerkerker e124829364 Rename Overseerr integration to Seerr (#164060) 2026-02-28 23:07:31 +01:00
Jan Bouwhuis 87b83dcc1b Remove the MQTT object_id option after 6 months of deprecation (#164460)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-28 20:12:23 +01:00
Erik Montnemery be9b47539d Revert "Remove unnecessary volume_up/volume_down overrides from frontier_silicon media player" (#164463) 2026-02-28 20:11:52 +01:00
Joost Lekkerkerker be6ddc314c Add sound detection switch to SmartThings (#164470) 2026-02-28 20:11:13 +01:00
David Bonnes c6f8a7b7e4 Harden test of an invalid service call for Evohome (#164458) 2026-02-28 20:10:11 +01:00
Joost Lekkerkerker 53da5612e9 Add fan speed to SmartThings vacuum (#164452)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-28 20:09:43 +01:00
Michael Davie 6cc56b76f9 Bump env-canada to 0.13.2 (#164480)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 20:08:17 +01:00
Tom Matheussen 03cb65d555 Require user code to be set when toggling Satel Integra switches (#164483) 2026-02-28 20:06:56 +01:00
Abílio Costa 73dd024933 Add merged PR count sensor to Github integration (#164405) 2026-02-28 15:13:17 +01:00
Barry vd. Heuvel 1c8c92bf8f Bump weheat to 2026.2.28 (#164456) 2026-02-28 14:40:58 +01:00
Khole 7e041a6759 Hive - Bump pyhive-integration to v1.0.8 (#164453) 2026-02-28 12:32:37 +00:00
Alex Brown ee05f14530 Add Matter lock user and credential management services (#161936)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-28 10:43:09 +01:00
Simone Chemelli f0ba5178b7 Fix RpcSensorDescription for Shelly (#150719) 2026-02-28 09:28:53 +01:00
Denis Shulyaka df51ac932b Improve Anthropic service exceptions (#164418)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-28 09:20:17 +01:00
Paulus Schoutsen e96b5f2eb1 Remove unnecessary volume_up/volume_down overrides from mpd media player (#164428)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 09:16:53 +01:00
Paulus Schoutsen 4e59c89327 Remove unnecessary volume_up/volume_down overrides from bluesound media player (#164426)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:57:53 +01:00
Paulus Schoutsen 15676021a9 Remove unnecessary volume_up/volume_down overrides from demo media player (#164424)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:57:30 +01:00
Paulus Schoutsen d3197a0d1e Remove unnecessary volume_up/volume_down overrides from aquostv media player (#164431)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:56:09 +01:00
Paulus Schoutsen 35692b335c Remove unnecessary volume_up/volume_down overrides from frontier_silicon media player (#164430)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:49:47 +01:00
Paulus Schoutsen cc5c810501 Remove unnecessary volume_up/volume_down overrides from NADtcp media player (#164434)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:47:08 +01:00
Paulus Schoutsen f2681f2dc8 Remove unnecessary volume_up/volume_down overrides from monoprice media player (#164429)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-28 08:45:43 +01:00
Brett Adams fe0a22c790 Complete strict typing for Teslemetry integration (#164416) 2026-02-28 08:33:45 +01:00
Norman Yee 186ab50458 Bump govee-ble to 1.2.0 (#164438) 2026-02-28 08:24:38 +01:00
mettolen b524c40176 Remove error translation placeholders from Airobot (#164436) 2026-02-28 06:18:19 +01:00
Klaas Schoute 642864959a Update translatable exceptions for Powerfox integration (#164322) 2026-02-28 01:57:02 +00:00
Franck Nijhof 7ef6c34149 Reject relative paths in SFTP storage backup location config flow (#164408) 2026-02-27 19:25:04 -05:00
Franck Nijhof 5b32e42b8c Add aioclient_mock to ssdp tests to prevent real HTTP requests (#164403) 2026-02-27 19:24:13 -05:00
Franck Nijhof 1be8b8e525 Add discovery mocks to tplink init tests (#164386) 2026-02-27 19:23:47 -05:00
Franck Nijhof 3fae15c430 Fix fixture ordering in esphome dashboard tests (#164367) 2026-02-27 19:23:13 -05:00
Franck Nijhof c7e78568d0 Enable real sockets in default_config setup test (#164366) 2026-02-27 19:22:29 -05:00
Stefan Agner 492b542136 Fix Matter vacuum crash on nullable ServiceArea location info (#164411) 2026-02-28 00:11:32 +01:00
Franck Nijhof 0f4852d8c2 Enable sockets for http integration tests (#164404) 2026-02-27 22:22:15 +01:00
nopoz 737c0c1823 Google Cast: detect state and attributes when device is doing active non-media casting (#160819)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-02-27 22:07:09 +01:00
Petro31 5fadcb01e9 Fix int vs float template sensor issue (#164339) 2026-02-27 22:06:37 +01:00
TheJulianJES 2b4f46a739 Fix ZHA update entities not working after reload (#164290) 2026-02-27 22:04:51 +01:00
Franck Nijhof 44fe37da1f Mock ConnectionContextBuilder in homematicip_cloud tests (#164356) 2026-02-27 22:00:37 +01:00
Joost Lekkerkerker abd4e89577 Sync SmartThings vacuum fixture (#164360) 2026-02-27 21:43:30 +01:00
Franck Nijhof 033798835a Refactor adguard tests to use proper fixtures for mocking (#164402)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-27 21:34:10 +01:00
Franck Nijhof 83c77957c1 Add missing mock fixtures to telegram_bot polling init test (#164398) 2026-02-27 21:29:10 +01:00
dependabot[bot] b1bc1dc102 Bump actions/dependency-review-action from 4.8.2 to 4.8.3 (#164296)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-27 21:21:15 +01:00
Jason Hunter 40b8a2c380 Remove Duke Energy (#164282)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-02-27 20:19:03 +00:00
Glenn de Haan fb23a6fbf8 Add HDFury audio offset numbers (#164315) 2026-02-27 21:02:34 +01:00
Joost Lekkerkerker faad3de02c Bump pySmartThings to 3.6.0 (#164397) 2026-02-27 21:00:33 +01:00
Franck Nijhof 5f30f532e5 Mock async_setup_entry in unifiprotect reauth tests (#164375) 2026-02-27 20:53:52 +01:00
Franck Nijhof 667e8c4d38 Mock async_setup_entry in jvc_projector config flow tests (#164401) 2026-02-27 20:53:38 +01:00
Franck Nijhof 74240ecd26 Mock async_setup_entry in lametric DHCP discovery test (#164400) 2026-02-27 20:50:11 +01:00
Franck Nijhof c81ee53265 Mock TodoistAPIAsync in todoist failed coordinator update test (#164390) 2026-02-27 20:49:02 +01:00
Franck Nijhof 8835f1d5e6 Mock async_setup_entry in youless config flow test (#164399) 2026-02-27 20:46:48 +01:00
Franck Nijhof 2ca84182d8 Patch discovery in elkm1 invalid auth and reconfigure tests (#164396) 2026-02-27 20:46:45 +01:00
Franck Nijhof 3f0d1bc071 Mock PyMochad controller in mochad tests (#164394) 2026-02-27 20:43:08 +01:00
Franck Nijhof 350f462bdf Prevent real setup during DHCP discovery test in fully_kiosk tests (#164342) 2026-02-27 20:42:32 +01:00
Franck Nijhof 2f98e68ed8 Mock async_setup_entry in arcam_fmj config flow tests (#164351) 2026-02-27 20:42:12 +01:00
Franck Nijhof 5b7fac94e5 Mock async_setup_entry in ccm15 config flow tests (#164352) 2026-02-27 20:42:02 +01:00
Franck Nijhof c32ce3da5c Add missing rest_api fixture in samsungtv setup test (#164353) 2026-02-27 20:41:39 +01:00
Franck Nijhof 0e1d1fbaed Fix fixture ordering in jvc_projector integration setup (#164354) 2026-02-27 20:41:17 +01:00
Franck Nijhof 57d7f364f4 Mock async_setup_entry in wilight SSDP flow test (#164393) 2026-02-27 20:40:35 +01:00
Franck Nijhof 7cc5777b47 Fix fixture ordering in madVR tests to ensure proper mocking (#164350) 2026-02-27 20:38:42 +01:00
Franck Nijhof 5e3f23b6a2 Fix mock target for Met Office config flow error test (#164391) 2026-02-27 20:37:24 +01:00
Franck Nijhof 6873a40407 Mock async_setup_entry in forked_daapd config flow tests (#164370) 2026-02-27 20:36:33 +01:00
Franck Nijhof ddaa2fb293 Mock async_setup_entry in daikin config flow tests (#164371) 2026-02-27 20:36:23 +01:00
Franck Nijhof 53b6223459 Mock async_setup_entry in emulated_roku config flow tests (#164368) 2026-02-27 20:35:50 +01:00
Franck Nijhof 7329cfb927 Mock async_setup_entry in home_connect migration tests (#164357) 2026-02-27 20:33:54 +01:00
Franck Nijhof 44b80dde0c Mock async_setup_entry in radarr config flow tests (#164359) 2026-02-27 20:33:19 +01:00
Joost Lekkerkerker 8c125e4e4f Add do not disturb switch to SmartThings (#164364) 2026-02-27 20:31:56 +01:00
Franck Nijhof 227a258382 Add missing client mocks to tplink_omada service tests (#164389) 2026-02-27 20:30:54 +01:00
Franck Nijhof addc2a6766 Mock async_setup_entry in speedtestdotnet config flow test (#164387) 2026-02-27 20:30:47 +01:00
Franck Nijhof 97bcea9727 Mock async_setup_entry in tautulli config flow tests (#164388) 2026-02-27 20:30:38 +01:00
Franck Nijhof 4f05c807b0 Mock async_setup_entry in panasonic_viera config flow tests (#164385) 2026-02-27 20:30:25 +01:00
Franck Nijhof 177a918c26 Mock async_setup_entry in onvif DHCP host update test (#164384) 2026-02-27 20:30:15 +01:00
Franck Nijhof 9705770c6c Remove unnecessary config entry from velux validation error test (#164383) 2026-02-27 20:30:12 +01:00
Franck Nijhof 7309351165 Mock async_setup_entry in lunatone config flow tests (#164382) 2026-02-27 20:29:22 +01:00
Franck Nijhof d0401de70d Mock HMConnection in homematic notify tests (#164381) 2026-02-27 20:29:14 +01:00
Franck Nijhof 6b89359a73 Mock async_setup_entry in sharkiq setup test (#164380) 2026-02-27 20:27:40 +01:00
Franck Nijhof b31bafab99 Mock async_setup_entry in roku options flow test (#164377) 2026-02-27 20:27:13 +01:00
Franck Nijhof 84c556bb63 Mock setup and client in sma config flow tests (#164374) 2026-02-27 20:26:59 +01:00
Franck Nijhof 225ea02d9a Fix axis setup failure test to mock at correct layer (#164373) 2026-02-27 20:26:46 +01:00
Franck Nijhof ebd1cc994c Add missing mock_transmission_client to transmission init tests (#164369) 2026-02-27 20:26:33 +01:00
Franck Nijhof 9ec22ba158 Mock async_setup_entry in kostal_plenticore reconfigure test (#164372) 2026-02-27 20:26:18 +01:00
Paulus Schoutsen 2ff85d2134 Add missing volume supported features to dunehd (#164343)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:50:42 +01:00
reneboer 3eb7f04510 Add tests for Megane e-Tech (#164358)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-27 19:47:22 +01:00
Kamil Breguła 54613ac8d9 Add mik-laj as codeowner to WLED (#164349)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
2026-02-27 18:31:37 +01:00
Joost Lekkerkerker 044522a8ab Add state for washing mop in SmartThings (#164348) 2026-02-27 18:26:20 +01:00
Willem-Jan van Rootselaar 19bf41496a Set entity_registry_enabled_default to False for total energy sensor (#164197) 2026-02-27 18:03:17 +01:00
Johnny Willemsen a7efba098d Update state labels to use common keys in indevolt (#164308) 2026-02-27 17:57:02 +01:00
Arie Catsman 042ad3b759 Add missing production ct data, total-consumption and new CT to enphase_envoy (#164270) 2026-02-27 17:43:46 +01:00
Franck Nijhof 4270e4c793 Mock firmware data during reauth flow init in airos tests (#164341) 2026-02-27 17:21:22 +01:00
Erwin Douna cb11c22e76 SMA add data descriptions (#164331) 2026-02-27 16:34:45 +01:00
Norbert Rittel c6e23fec93 Replace "service" with "action" in evohome exception string (#164333) 2026-02-27 16:32:15 +01:00
epenet 553cecb397 Ensure future is marked as retrieved in frontend storage (#164320) 2026-02-27 15:51:34 +02:00
Erwin Douna bb7d5897d1 Portainer redact CONF_HOST in diagnostics (#164301) 2026-02-27 13:54:12 +01:00
7eaves 3e050ebe59 Bump PySwitchBot to 1.1.0 (#164298) 2026-02-27 13:11:14 +01:00
Ye Zhiling 856a9e695a Pass encoding to AtomicWriter in write_utf8_file_atomic (#164015) 2026-02-27 11:40:58 +01:00
Artur Pragacz 1944a8bd3a Remove vacuum area mapping not configured issue (#164259) 2026-02-27 11:20:46 +01:00
epenet 3f11af8084 Drop single-use service name constants in bsblan (#164311) 2026-02-27 10:59:02 +01:00
David Bonnes 46a87cd9dd Migrate evohome's zone services to entity-level services (#164105)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-27 10:16:35 +01:00
Erwin Douna f8a657cf01 Proxmox expand data descriptions (#164304) 2026-02-27 09:59:43 +01:00
Norbert Rittel 75ed7b2fa2 Improve descriptions of schlage actions (#164299) 2026-02-27 08:46:08 +01:00
hanwg e63e54820c Remove redundant exception messages from Telegram bot (#164289) 2026-02-27 08:19:10 +01:00
Kamil Breguła 37d2c946e8 Add diagnostics platform to AWS S3 (#164118)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-02-27 08:16:58 +01:00
James e8a35ea69d Handle missing Daikin zone temperature keys (#164170)
Co-authored-by: barneyonline <barneyonline@users.noreply.github.com>
2026-02-26 22:15:55 +00:00
Erwin Douna 28b950c64a Simplify entity init in Proxmox (#164265) 2026-02-26 21:26:29 +01:00
Denis Shulyaka e7cf6cbe72 Create reauth flow for Anthropic for auth errors during conversation (#164267)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-26 21:16:11 +01:00
Raphael Hehl 5ad71453b8 Bump uiprotect to version 10.2.2 (#164269)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-02-26 21:12:30 +01:00
Andrew Grimberg ab9c8093c3 Add services for managing Schlage door codes (#151014)
Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-02-26 20:54:57 +01:00
peteS-UK 51acdeb563 Add config flow support to Orvibo legacy integration (#155115)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-26 19:59:13 +01:00
Johnny Willemsen bf60d57cc2 Update state labels to use common keys in compit (#164261) 2026-02-26 18:56:11 +01:00
Kamil Breguła d94f15b985 Update IQS for AWS S3 (#164117)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
2026-02-26 18:54:04 +01:00
Erwin Douna 8a621e6570 Remove kw arg for Portainer (#164260) 2026-02-26 18:43:56 +01:00
Bram Kragten dd44b15b7b Update frontend to 20260226.0 (#164262) 2026-02-26 18:42:48 +01:00
epenet 23ec28bbbf Simplify portainer entity initialisation (#164256) 2026-02-26 17:00:35 +01:00
epenet 7a6a479b53 Rename local constants in device_automation test (#164143) 2026-02-26 16:41:02 +01:00
epenet f9ffaad7f1 Drop single-use service name constants in abode (#164146) 2026-02-26 16:40:43 +01:00
epenet d4aa52ecc3 Drop single-use service name constants in alarmdecoder (#164150) 2026-02-26 16:40:28 +01:00
epenet 1b5eea5fae Drop single-use service name constants in amberelectric (#164152) 2026-02-26 16:40:13 +01:00
epenet 39dce8eb31 Drop single-use service name constants in androidtv (#164153) 2026-02-26 16:39:53 +01:00
epenet b651e62c7f Drop single-use service name constants in advantage_air (#164148) 2026-02-26 16:39:32 +01:00
Denis Shulyaka 1e807dc9da Update reasoning options for gpt-5.3-codex (#164179) 2026-02-26 16:39:04 +01:00
epenet cba69e7e69 Drop single-use service name constants in agent_dvr (#164149) 2026-02-26 16:37:58 +01:00
Denis Shulyaka 802a7aafec Disable code interpreter with minimal reasoning for OpenAI (#164254) 2026-02-26 16:37:31 +01:00
Joost Lekkerkerker db5e7b3e3b Remove invalid color mode from philips_js (#164204) 2026-02-26 16:33:35 +01:00
Erwin Douna 75798bfb5e Fix stack devices merging with container devices in Portainer (#164135)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-26 16:14:50 +01:00
Kevin Stillhammer 06a25de0d5 Remove redundant DEFAULT_TIME_DELTA in waze_travel_time (#164227) 2026-02-26 15:43:16 +01:00
AlCalzone 892da4a03e Rename "Z-Wave Supervisor app" to "Z-Wave JS app" (#164147) 2026-02-26 15:38:03 +01:00
epenet 91e8e3da7a Use constants in default_config tests (#164144) 2026-02-26 15:31:48 +01:00
Kevin Stillhammer 144b8768a1 Add time_delta option to waze_travel_time (#161803) 2026-02-26 14:28:05 +01:00
Brett Adams cb6d86f86d Add energy price calendar platform to Teslemetry (#145848)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-26 13:42:45 +01:00
epenet 422007577e Use constant in diagnostics test (#164139) 2026-02-26 12:58:21 +01:00
Norbert Rittel 7c2904bf48 Replace "add-ons" with "apps" in backup issues (#164129) 2026-02-26 12:57:09 +01:00
epenet 3240fd7fc8 Drop single-use service name constants in amcrest (#164156) 2026-02-26 12:54:21 +01:00
epenet 7dc2dff4e7 Drop single-use service name constants in alexa_devices (#164151) 2026-02-26 12:53:18 +01:00
Abílio Costa 7e8de9bb9c Add infrared entity integration (#162251)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-02-26 11:45:21 +00:00
Luca Angemi 9eff12605c Add minimum state duration variable to history_stats (#151643) 2026-02-26 11:21:37 +01:00
Erik Montnemery 784ac85759 Require full coverage for backup platforms (#164137) 2026-02-26 11:16:32 +01:00
Amit Finkelstein 31f7961437 Add HassOS "mount_reload" action (#155996)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-02-26 08:04:58 +01:00
mettolen eaae64fa12 Remove error translation placeholders from Saunum (#164121) 2026-02-26 07:44:19 +01:00
Paulus Schoutsen 88b276f3a4 Simplify Anthropic integration name (#164124)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-26 07:43:44 +01:00
Kamil Breguła f5c996e243 Add support for S3 prefix in AWS S3 integration (#162836)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
2026-02-26 07:39:50 +01:00
Michael Hansen 4863df00a1 Avoid invalid cache future state (#164081) 2026-02-25 22:36:53 -05:00
Maciej Bieniek 9fadfecf14 Bump accuweather to 5.1.0 (#164034) 2026-02-26 02:00:10 +01:00
Liquidmasl dae7f73f53 Sonarr post merge changes (#164112) 2026-02-26 01:57:14 +01:00
Jamie Magee c46d0382c3 Add diagnostics to aladdin_connect for easier troubleshooting (#164110) 2026-02-26 00:17:38 +01:00
Artur Pragacz c21e9cb24c Fix Matter vacuum clean area status check (#164108) 2026-02-25 23:49:14 +01:00
David Bonnes 928732af40 Clean up evohome constants (#164102) 2026-02-25 20:23:17 +00:00
Franck Nijhof 51dc6d7c26 Bump version to 2026.4.0dev0 (#164101) 2026-02-25 21:08:17 +01:00
Przemko92 02972579aa Add Compit fan (#164049)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-25 20:52:01 +01:00
2748 changed files with 165630 additions and 82337 deletions
@@ -0,0 +1,46 @@
---
name: github-pr-reviewer
description: Review a GitHub pull request and provide feedback comments. Use when the user says "review the current PR" or asks to review a specific PR.
---
# Review GitHub Pull Request
## Preparation:
- Check if the local commit matches the last one in the PR. If not, checkout the PR locally using 'gh pr checkout'.
- CRITICAL: If 'gh pr checkout' fails for ANY reason, you MUST immediately STOP.
- Do NOT attempt any workarounds.
- Do NOT proceed with the review.
- ALERT about the failure and WAIT for instructions.
- This is a hard requirement - no exceptions.
## Follow these steps:
1. Use 'gh pr view' to get the PR details and description.
2. Use 'gh pr diff' to see all the changes in the PR.
3. Analyze the code changes for:
- Code quality and style consistency
- Potential bugs or issues
- Performance implications
- Security concerns
- Test coverage
- Documentation updates if needed
4. Ensure any existing review comments have been addressed.
5. Generate constructive review comments in the CONSOLE. DO NOT POST TO GITHUB YOURSELF.
## IMPORTANT:
- Just review. DO NOT make any changes
- Be constructive and specific in your comments
- Suggest improvements where appropriate
- Only provide review feedback in the CONSOLE. DO NOT ACT ON GITHUB.
- No need to run tests or linters, just review the code changes.
- No need to highlight things that are already good.
## Output format:
- 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
```
+1
View File
@@ -34,6 +34,7 @@ base_platforms: &base_platforms
- homeassistant/components/humidifier/**
- homeassistant/components/image/**
- homeassistant/components/image_processing/**
- homeassistant/components/infrared/**
- homeassistant/components/lawn_mower/**
- homeassistant/components/light/**
- homeassistant/components/lock/**
+1
View File
@@ -16,6 +16,7 @@ Dockerfile.dev linguist-language=Dockerfile
CODEOWNERS linguist-generated=true
Dockerfile linguist-generated=true
homeassistant/generated/*.py linguist-generated=true
machine/* linguist-generated=true
mypy.ini linguist-generated=true
requirements.txt linguist-generated=true
requirements_all.txt linguist-generated=true
File diff suppressed because it is too large Load Diff
+323 -378
View File
@@ -10,7 +10,6 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: "3.14.2"
PIP_TIMEOUT: 60
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"
@@ -36,16 +35,17 @@ jobs:
channel: ${{ steps.version.outputs.channel }}
publish: ${{ steps.version.outputs.publish }}
architectures: ${{ env.ARCHITECTURES }}
base_image_version: ${{ env.BASE_IMAGE_VERSION }}
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
- name: Get information
id: info
@@ -57,10 +57,10 @@ jobs:
with:
type: ${{ env.BUILD_TYPE }}
- name: Verify version
uses: home-assistant/actions/helpers/verify-version@master # zizmor: ignore[unpinned-uses]
with:
ignore-dev: true
# - name: Verify version
# uses: home-assistant/actions/helpers/verify-version@master # zizmor: ignore[unpinned-uses]
# with:
# ignore-dev: true
- name: Fail if translations files are checked in
run: |
@@ -73,14 +73,14 @@ jobs:
- name: Download Translations
run: python3 -m script.translations download
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} # zizmor: ignore[secrets-outside-env]
- name: Archive translations
shell: bash
run: find ./homeassistant/components/*/translations -name "*.json" | tar zcvf translations.tar.gz -T -
- name: Upload translations
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: translations
path: translations.tar.gz
@@ -101,7 +101,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
include:
- arch: amd64
os: ubuntu-latest
os: ubuntu-24.04
- arch: aarch64
os: ubuntu-24.04-arm
steps:
@@ -112,7 +112,7 @@ jobs:
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@5c98f0b039f36ef966fdb7dfa9779262785ecb05 # v14
uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
@@ -123,7 +123,7 @@ jobs:
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@5c98f0b039f36ef966fdb7dfa9779262785ecb05 # v14
uses: dawidd6/action-download-artifact@2536c51d3d126276eb39f74d6bc9c72ac6ef30d3 # v16
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: OHF-Voice/intents-package
@@ -132,11 +132,11 @@ jobs:
workflow_conclusion: success
name: package
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
- name: Adjust nightly version
if: needs.init.outputs.channel == 'dev'
@@ -182,7 +182,7 @@ jobs:
fi
- name: Download translations
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: translations
@@ -196,77 +196,20 @@ jobs:
run: |
echo "${GITHUB_SHA};${GITHUB_REF};${GITHUB_EVENT_NAME};${GITHUB_ACTOR}" > rootfs/OFFICIAL_IMAGE
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: "v2.5.3"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Build variables
id: vars
shell: bash
env:
ARCH: ${{ matrix.arch }}
run: |
echo "base_image=ghcr.io/home-assistant/${ARCH}-homeassistant-base:${BASE_IMAGE_VERSION}" >> "$GITHUB_OUTPUT"
echo "cache_image=ghcr.io/home-assistant/${ARCH}-homeassistant:latest" >> "$GITHUB_OUTPUT"
echo "created=$(date --rfc-3339=seconds --utc)" >> "$GITHUB_OUTPUT"
- name: Verify base image signature
env:
BASE_IMAGE: ${{ steps.vars.outputs.base_image }}
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "https://github.com/home-assistant/docker/.*" \
"${BASE_IMAGE}"
- name: Verify cache image signature
id: cache
continue-on-error: true
env:
CACHE_IMAGE: ${{ steps.vars.outputs.cache_image }}
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "https://github.com/home-assistant/core/.*" \
"${CACHE_IMAGE}"
- name: Build base image
id: build
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
with:
context: .
file: ./Dockerfile
platforms: ${{ steps.vars.outputs.platform }}
push: true
cache-from: ${{ steps.cache.outcome == 'success' && steps.vars.outputs.cache_image || '' }}
arch: ${{ matrix.arch }}
build-args: |
BUILD_FROM=${{ steps.vars.outputs.base_image }}
tags: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
outputs: type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
labels: |
io.hass.arch=${{ matrix.arch }}
io.hass.version=${{ needs.init.outputs.version }}
org.opencontainers.image.created=${{ steps.vars.outputs.created }}
org.opencontainers.image.version=${{ needs.init.outputs.version }}
- name: Sign image
env:
ARCH: ${{ matrix.arch }}
VERSION: ${{ needs.init.outputs.version }}
DIGEST: ${{ steps.build.outputs.digest }}
run: |
cosign sign --yes "ghcr.io/home-assistant/${ARCH}-homeassistant:${VERSION}@${DIGEST}"
BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
cache-gha: false
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
cosign-base-identity: "https://github.com/home-assistant/docker/.*"
cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ needs.init.outputs.base_image_version }}
image: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant
image-tags: ${{ needs.init.outputs.version }}
push: true
version: ${{ needs.init.outputs.version }}
build_machine:
name: Build ${{ matrix.machine }} machine core image
@@ -315,308 +258,310 @@ jobs:
with:
persist-credentials: false
- name: Set build additional args
- name: Compute extra tags
id: tags
shell: bash
env:
VERSION: ${{ needs.init.outputs.version }}
run: |
# Create general tags
if [[ "${VERSION}" =~ d ]]; then
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV
echo "extra_tags=dev" >> "$GITHUB_OUTPUT"
elif [[ "${VERSION}" =~ b ]]; then
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV
echo "extra_tags=beta" >> "$GITHUB_OUTPUT"
else
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
echo "extra_tags=stable" >> "$GITHUB_OUTPUT"
fi
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
- name: Build machine image
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image
uses: home-assistant/builder@6cb4fd3d1338b6e22d0958a4bcb53e0965ea63b4 # 2026.02.1
with:
image: ${{ matrix.arch }}
args: |
$BUILD_ARGS \
--target /data/machine \
--cosign \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
publish_ha:
name: Publish version files
environment: ${{ needs.init.outputs.channel }}
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_machine"]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master # zizmor: ignore[unpinned-uses]
with:
name: ${{ secrets.GIT_NAME }}
email: ${{ secrets.GIT_EMAIL }}
token: ${{ secrets.GIT_TOKEN }}
- name: Update version file
uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
with:
key: "homeassistant[]"
key-description: "Home Assistant Core"
version: ${{ needs.init.outputs.version }}
channel: ${{ needs.init.outputs.channel }}
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
- name: Update version file (stable -> beta)
if: needs.init.outputs.channel == 'stable'
uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
with:
key: "homeassistant[]"
key-description: "Home Assistant Core"
version: ${{ needs.init.outputs.version }}
channel: beta
exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
publish_container:
name: Publish meta container for ${{ matrix.registry }}
environment: ${{ needs.init.outputs.channel }}
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ubuntu-latest
permissions:
contents: read # To check out the repository
packages: write # To push to GHCR
id-token: write # For cosign signing
strategy:
fail-fast: false
matrix:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: "v2.5.3"
- name: Login to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Verify architecture image signatures
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
VERSION: ${{ needs.init.outputs.version }}
run: |
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
for arch in $ARCHS; do
echo "Verifying ${arch} image signature..."
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp https://github.com/home-assistant/core/.* \
"ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"
done
echo "✓ All images verified successfully"
# Generate all Docker tags based on version string
# Version format: YYYY.MM.PATCH, YYYY.MM.PATCHbN (beta), or YYYY.MM.PATCH.devYYYYMMDDHHMM (dev)
# Examples:
# 2025.12.1 (stable) -> tags: 2025.12.1, 2025.12, stable, latest, beta, rc
# 2025.12.0b3 (beta) -> tags: 2025.12.0b3, beta, rc
# 2025.12.0.dev202511250240 -> tags: 2025.12.0.dev202511250240, dev
- name: Generate Docker metadata
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
with:
images: ${{ matrix.registry }}/home-assistant
sep-tags: ","
tags: |
type=raw,value=${{ needs.init.outputs.version }},priority=9999
type=raw,value=dev,enable=${{ contains(needs.init.outputs.version, 'd') }}
type=raw,value=beta,enable=${{ !contains(needs.init.outputs.version, 'd') }}
type=raw,value=rc,enable=${{ !contains(needs.init.outputs.version, 'd') }}
type=raw,value=stable,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
type=raw,value=latest,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.init.outputs.version }},enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.7.1
- name: Copy architecture images to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
VERSION: ${{ needs.init.outputs.version }}
run: |
# Use imagetools to copy image blobs directly between registries
# This preserves provenance/attestations and seems to be much faster than pull/push
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
for arch in $ARCHS; do
echo "Copying ${arch} image to DockerHub..."
for attempt in 1 2 3; do
if docker buildx imagetools create \
--tag "docker.io/homeassistant/${arch}-homeassistant:${VERSION}" \
"ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"; then
break
fi
echo "Attempt ${attempt} failed, retrying in 10 seconds..."
sleep 10
if [ "${attempt}" -eq 3 ]; then
echo "Failed after 3 attempts"
exit 1
fi
done
cosign sign --yes "docker.io/homeassistant/${arch}-homeassistant:${VERSION}"
done
- name: Create and push multi-arch manifests
shell: bash
env:
ARCHITECTURES: ${{ needs.init.outputs.architectures }}
REGISTRY: ${{ matrix.registry }}
VERSION: ${{ needs.init.outputs.version }}
META_TAGS: ${{ steps.meta.outputs.tags }}
run: |
# Build list of architecture images dynamically
ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
ARCH_IMAGES=()
for arch in $ARCHS; do
ARCH_IMAGES+=("${REGISTRY}/${arch}-homeassistant:${VERSION}")
done
# Build list of all tags for single manifest creation
# Note: Using sep-tags=',' in metadata-action for easier parsing
TAG_ARGS=()
IFS=',' read -ra TAGS <<< "${META_TAGS}"
for tag in "${TAGS[@]}"; do
TAG_ARGS+=("--tag" "${tag}")
done
# Create manifest with ALL tags in a single operation (much faster!)
echo "Creating multi-arch manifest with tags: ${TAGS[*]}"
docker buildx imagetools create "${TAG_ARGS[@]}" "${ARCH_IMAGES[@]}"
# Sign each tag separately (signing requires individual tag names)
echo "Signing all tags..."
for tag in "${TAGS[@]}"; do
echo "Signing ${tag}"
cosign sign --yes "${tag}"
done
echo "All manifests created and signed successfully"
build_python:
name: Build PyPi package
environment: ${{ needs.init.outputs.channel }}
needs: ["init", "build_base"]
runs-on: ubuntu-latest
permissions:
contents: read # To check out the repository
id-token: write # For PyPI trusted publishing
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
- name: Download translations
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: translations
- name: Extract translations
run: |
tar xvf translations.tar.gz
rm translations.tar.gz
- name: Build package
shell: bash
run: |
# Remove dist, build, and homeassistant.egg-info
# when build locally for testing!
pip install build
python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
with:
skip-existing: true
hassfest-image:
name: Build and test hassfest image
runs-on: ubuntu-latest
permissions:
contents: read # To check out the repository
packages: write # To push to GHCR
attestations: write # For build provenance attestation
id-token: write # For build provenance attestation
needs: ["init"]
if: github.repository_owner == 'home-assistant'
env:
HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
load: true
tags: ${{ env.HASSFEST_IMAGE_TAG }}
- name: Run hassfest against core
run: docker run --rm -v "${GITHUB_WORKSPACE}":/github/workspace "${HASSFEST_IMAGE_TAG}" --core-path=/github/workspace
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
arch: ${{ matrix.arch }}
build-args: |
BUILD_FROM=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
cache-gha: false
container-registry-password: ${{ secrets.GITHUB_TOKEN }}
context: machine/
cosign-base-identity: "https://github.com/home-assistant/core/.*"
cosign-base-verify: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
file: machine/${{ matrix.machine }}
image: ghcr.io/home-assistant/${{ matrix.machine }}-homeassistant
image-tags: |
${{ needs.init.outputs.version }}
${{ steps.tags.outputs.extra_tags }}
push: true
tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
version: ${{ needs.init.outputs.version }}
# publish_ha:
# name: Publish version files
# environment: ${{ needs.init.outputs.channel }}
# if: github.repository_owner == 'home-assistant'
# needs: ["init", "build_machine"]
# runs-on: ubuntu-latest
# permissions:
# contents: read
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
#
# - name: Initialize git
# uses: home-assistant/actions/helpers/git-init@master # zizmor: ignore[unpinned-uses]
# with:
# name: ${{ secrets.GIT_NAME }}
# email: ${{ secrets.GIT_EMAIL }}
# token: ${{ secrets.GIT_TOKEN }}
#
# - name: Update version file
# uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
# with:
# key: "homeassistant[]"
# key-description: "Home Assistant Core"
# version: ${{ needs.init.outputs.version }}
# channel: ${{ needs.init.outputs.channel }}
# exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
#
# - name: Update version file (stable -> beta)
# if: needs.init.outputs.channel == 'stable'
# uses: home-assistant/actions/helpers/version-push@master # zizmor: ignore[unpinned-uses]
# with:
# key: "homeassistant[]"
# key-description: "Home Assistant Core"
# version: ${{ needs.init.outputs.version }}
# channel: beta
# exclude-list: '["odroid-xu","qemuarm","qemux86","raspberrypi","raspberrypi2","raspberrypi3","raspberrypi4","tinker"]'
#
# publish_container:
# name: Publish meta container for ${{ matrix.registry }}
# environment: ${{ needs.init.outputs.channel }}
# if: github.repository_owner == 'home-assistant'
# needs: ["init", "build_base"]
# runs-on: ubuntu-latest
# permissions:
# contents: read # To check out the repository
# packages: write # To push to GHCR
# id-token: write # For cosign signing
# strategy:
# fail-fast: false
# matrix:
# registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
# steps:
# - name: Install Cosign
# uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
# with:
# cosign-release: "v2.5.3"
#
# - name: Login to DockerHub
# if: matrix.registry == 'docker.io/homeassistant'
# uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
# with:
# username: ${{ secrets.DOCKERHUB_USERNAME }}
# password: ${{ secrets.DOCKERHUB_TOKEN }}
#
# - name: Login to GitHub Container Registry
# uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Verify architecture image signatures
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# for arch in $ARCHS; do
# echo "Verifying ${arch} image signature..."
# cosign verify \
# --certificate-oidc-issuer https://token.actions.githubusercontent.com \
# --certificate-identity-regexp https://github.com/home-assistant/core/.* \
# "ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"
# done
# echo "✓ All images verified successfully"
#
# # Generate all Docker tags based on version string
# # Version format: YYYY.MM.PATCH, YYYY.MM.PATCHbN (beta), or YYYY.MM.PATCH.devYYYYMMDDHHMM (dev)
# # Examples:
# # 2025.12.1 (stable) -> tags: 2025.12.1, 2025.12, stable, latest, beta, rc
# # 2025.12.0b3 (beta) -> tags: 2025.12.0b3, beta, rc
# # 2025.12.0.dev202511250240 -> tags: 2025.12.0.dev202511250240, dev
# - name: Generate Docker metadata
# id: meta
# uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
# with:
# images: ${{ matrix.registry }}/home-assistant
# sep-tags: ","
# tags: |
# type=raw,value=${{ needs.init.outputs.version }},priority=9999
# type=raw,value=dev,enable=${{ contains(needs.init.outputs.version, 'd') }}
# type=raw,value=beta,enable=${{ !contains(needs.init.outputs.version, 'd') }}
# type=raw,value=rc,enable=${{ !contains(needs.init.outputs.version, 'd') }}
# type=raw,value=stable,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
# type=raw,value=latest,enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
# type=semver,pattern={{major}}.{{minor}},value=${{ needs.init.outputs.version }},enable=${{ !contains(needs.init.outputs.version, 'd') && !contains(needs.init.outputs.version, 'b') }}
#
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v3.7.1
#
# - name: Copy architecture images to DockerHub
# if: matrix.registry == 'docker.io/homeassistant'
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# VERSION: ${{ needs.init.outputs.version }}
# run: |
# # Use imagetools to copy image blobs directly between registries
# # This preserves provenance/attestations and seems to be much faster than pull/push
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# for arch in $ARCHS; do
# echo "Copying ${arch} image to DockerHub..."
# for attempt in 1 2 3; do
# if docker buildx imagetools create \
# --tag "docker.io/homeassistant/${arch}-homeassistant:${VERSION}" \
# "ghcr.io/home-assistant/${arch}-homeassistant:${VERSION}"; then
# break
# fi
# echo "Attempt ${attempt} failed, retrying in 10 seconds..."
# sleep 10
# if [ "${attempt}" -eq 3 ]; then
# echo "Failed after 3 attempts"
# exit 1
# fi
# done
# cosign sign --yes "docker.io/homeassistant/${arch}-homeassistant:${VERSION}"
# done
#
# - name: Create and push multi-arch manifests
# shell: bash
# env:
# ARCHITECTURES: ${{ needs.init.outputs.architectures }}
# REGISTRY: ${{ matrix.registry }}
# VERSION: ${{ needs.init.outputs.version }}
# META_TAGS: ${{ steps.meta.outputs.tags }}
# run: |
# # Build list of architecture images dynamically
# ARCHS=$(echo "${ARCHITECTURES}" | jq -r '.[]')
# ARCH_IMAGES=()
# for arch in $ARCHS; do
# ARCH_IMAGES+=("${REGISTRY}/${arch}-homeassistant:${VERSION}")
# done
#
# # Build list of all tags for single manifest creation
# # Note: Using sep-tags=',' in metadata-action for easier parsing
# TAG_ARGS=()
# IFS=',' read -ra TAGS <<< "${META_TAGS}"
# for tag in "${TAGS[@]}"; do
# TAG_ARGS+=("--tag" "${tag}")
# done
#
# # Create manifest with ALL tags in a single operation (much faster!)
# echo "Creating multi-arch manifest with tags: ${TAGS[*]}"
# docker buildx imagetools create "${TAG_ARGS[@]}" "${ARCH_IMAGES[@]}"
#
# # Sign each tag separately (signing requires individual tag names)
# echo "Signing all tags..."
# for tag in "${TAGS[@]}"; do
# echo "Signing ${tag}"
# cosign sign --yes "${tag}"
# done
#
# echo "All manifests created and signed successfully"
#
# build_python:
# name: Build PyPi package
# environment: ${{ needs.init.outputs.channel }}
# needs: ["init", "build_base"]
# runs-on: ubuntu-latest
# permissions:
# contents: read # To check out the repository
# id-token: write # For PyPI trusted publishing
# if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
# steps:
# - name: Checkout the repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
#
# - name: Set up Python
# uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
# with:
# python-version-file: ".python-version"
#
# - name: Download translations
# uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
# with:
# name: translations
#
# - name: Extract translations
# run: |
# tar xvf translations.tar.gz
# rm translations.tar.gz
#
# - name: Build package
# shell: bash
# run: |
# # Remove dist, build, and homeassistant.egg-info
# # when build locally for testing!
# pip install build
# python -m build
#
# - name: Upload package to PyPI
# uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
# with:
# skip-existing: true
#
# hassfest-image:
# name: Build and test hassfest image
# runs-on: ubuntu-latest
# permissions:
# contents: read # To check out the repository
# packages: write # To push to GHCR
# attestations: write # For build provenance attestation
# id-token: write # For build provenance attestation
# needs: ["init"]
# if: github.repository_owner == 'home-assistant'
# env:
# HASSFEST_IMAGE_NAME: ghcr.io/home-assistant/hassfest
# HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
# steps:
# - name: Checkout repository
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# with:
# persist-credentials: false
#
# - name: Login to GitHub Container Registry
# uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
# with:
# registry: ghcr.io
# username: ${{ github.repository_owner }}
# password: ${{ secrets.GITHUB_TOKEN }}
#
# - name: Build Docker image
# uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
# with:
# context: . # So action will not pull the repository again
# file: ./script/hassfest/docker/Dockerfile
# load: true
# tags: ${{ env.HASSFEST_IMAGE_TAG }}
#
# - name: Run hassfest against core
# run: docker run --rm -v "${GITHUB_WORKSPACE}":/github/workspace "${HASSFEST_IMAGE_TAG}" --core-path=/github/workspace
#
# - name: Push Docker image
# if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
# id: push
# uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
# with:
# context: . # So action will not pull the repository again
# file: ./script/hassfest/docker/Dockerfile
# push: true
# tags: ${{ env.HASSFEST_IMAGE_TAG }},${{ env.HASSFEST_IMAGE_NAME }}:latest
#
# - name: Generate artifact attestation
# if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
# uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
# with:
# subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
# subject-digest: ${{ steps.push.outputs.digest }}
# push-to-registry: true
+51 -51
View File
@@ -40,9 +40,8 @@ env:
CACHE_VERSION: 3
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.3"
DEFAULT_PYTHON: "3.14.2"
ALL_PYTHON_VERSIONS: "['3.14.2']"
HA_SHORT_VERSION: "2026.4"
ADDITIONAL_PYTHON_VERSIONS: "[]"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support
@@ -166,6 +165,11 @@ jobs:
tests_glob=""
lint_only=""
skip_coverage=""
default_python=$(cat .python-version)
all_python_versions=$(jq -cn \
--arg default_python "${default_python}" \
--argjson additional_python_versions "${ADDITIONAL_PYTHON_VERSIONS}" \
'[$default_python] + $additional_python_versions')
if [[ "${INTEGRATION_CHANGES}" != "[]" ]];
then
@@ -235,8 +239,8 @@ jobs:
echo "mariadb_groups=${mariadb_groups}" >> $GITHUB_OUTPUT
echo "postgresql_groups: ${postgresql_groups}"
echo "postgresql_groups=${postgresql_groups}" >> $GITHUB_OUTPUT
echo "python_versions: ${ALL_PYTHON_VERSIONS}"
echo "python_versions=${ALL_PYTHON_VERSIONS}" >> $GITHUB_OUTPUT
echo "python_versions: ${all_python_versions}"
echo "python_versions=${all_python_versions}" >> $GITHUB_OUTPUT
echo "test_full_suite: ${test_full_suite}"
echo "test_full_suite=${test_full_suite}" >> $GITHUB_OUTPUT
echo "integrations_glob: ${integrations_glob}"
@@ -452,7 +456,7 @@ jobs:
python --version
uv pip freeze >> pip_freeze.txt
- name: Upload pip_freeze artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pip-freeze-${{ matrix.python-version }}
path: pip_freeze.txt
@@ -503,13 +507,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -540,13 +544,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -576,11 +580,11 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Run gen_copilot_instructions.py
run: |
@@ -605,7 +609,7 @@ jobs:
with:
persist-credentials: false
- name: Dependency review
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
with:
license-check: false # We use our own license audit checks
@@ -653,7 +657,7 @@ jobs:
. venv/bin/activate
python -m script.licenses extract --output-file=licenses-${PYTHON_VERSION}.json
- name: Upload licenses
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: licenses-${{ github.run_number }}-${{ matrix.python-version }}
path: licenses-${{ matrix.python-version }}.json
@@ -682,13 +686,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -735,13 +739,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -786,11 +790,11 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Generate partial mypy restore key
id: generate-mypy-key
@@ -798,7 +802,7 @@ jobs:
mypy_version=$(cat requirements_test.txt | grep 'mypy.*=' | cut -d '=' -f 3)
echo "version=${mypy_version}" >> $GITHUB_OUTPUT
echo "key=mypy-${MYPY_CACHE_VERSION}-${mypy_version}-${HA_SHORT_VERSION}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -848,10 +852,6 @@ jobs:
needs:
- info
- base
- gen-requirements-all
- hassfest
- prek
- mypy
steps:
- name: Restore apt cache
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
@@ -879,13 +879,13 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Restore full Python ${{ env.DEFAULT_PYTHON }} virtual environment
- name: Restore full Python virtual environment
id: cache-venv
uses: actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
@@ -901,7 +901,7 @@ jobs:
. venv/bin/activate
python -m script.split_tests ${TEST_GROUP_COUNT} tests
- name: Upload pytest_buckets
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pytest_buckets
path: pytest_buckets.txt
@@ -978,7 +978,7 @@ jobs:
run: |
echo "::add-matcher::.github/workflows/matchers/pytest-slow.json"
- name: Download pytest_buckets
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: pytest_buckets
- name: Compile English translations
@@ -1020,14 +1020,14 @@ jobs:
2>&1 | tee pytest-${PYTHON_VERSION}-${TEST_GROUP}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-full.conclusion == 'failure'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
path: pytest-*.txt
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
@@ -1040,7 +1040,7 @@ jobs:
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-results-full-${{ matrix.python-version }}-${{ matrix.group }}
path: junit.xml
@@ -1177,7 +1177,7 @@ jobs:
2>&1 | tee pytest-${PYTHON_VERSION}-${mariadb}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.mariadb }}
@@ -1185,7 +1185,7 @@ jobs:
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.mariadb }}
@@ -1199,7 +1199,7 @@ jobs:
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-results-mariadb-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.mariadb }}
@@ -1338,7 +1338,7 @@ jobs:
2>&1 | tee pytest-${PYTHON_VERSION}-${postgresql}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.postgresql }}
@@ -1346,7 +1346,7 @@ jobs:
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.postgresql }}
@@ -1360,7 +1360,7 @@ jobs:
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-results-postgres-${{ matrix.python-version }}-${{
steps.pytest-partial.outputs.postgresql }}
@@ -1387,7 +1387,7 @@ jobs:
with:
persist-credentials: false
- name: Download all coverage artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
pattern: coverage-*
- name: Upload coverage to Codecov
@@ -1396,7 +1396,7 @@ jobs:
with:
fail_ci_if_error: true
flags: full-suite
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env]
pytest-partial:
name: Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
@@ -1514,14 +1514,14 @@ jobs:
2>&1 | tee pytest-${PYTHON_VERSION}-${TEST_GROUP}.txt
- name: Upload pytest output
if: success() || failure() && steps.pytest-partial.conclusion == 'failure'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: pytest-${{ github.run_number }}-${{ matrix.python-version }}-${{ matrix.group }}
path: pytest-*.txt
overwrite: true
- name: Upload coverage artifact
if: needs.info.outputs.skip_coverage != 'true'
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-${{ matrix.python-version }}-${{ matrix.group }}
path: coverage.xml
@@ -1534,7 +1534,7 @@ jobs:
mv "junit.xml-tmp" "junit.xml"
- name: Upload test results artifact
if: needs.info.outputs.skip_coverage != 'true' && !cancelled()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-results-partial-${{ matrix.python-version }}-${{ matrix.group }}
path: junit.xml
@@ -1558,7 +1558,7 @@ jobs:
with:
persist-credentials: false
- name: Download all coverage artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
pattern: coverage-*
- name: Upload coverage to Codecov
@@ -1566,7 +1566,7 @@ jobs:
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }} # zizmor: ignore[secrets-outside-env]
upload-test-results:
name: Upload test results to Codecov
@@ -1587,7 +1587,7 @@ jobs:
&& needs.info.outputs.skip_coverage != 'true' && !cancelled()
steps:
- name: Download all coverage artifacts
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
pattern: test-results-*
- name: Upload test results to Codecov
+2 -2
View File
@@ -28,11 +28,11 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
uses: github/codeql-action/init@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3
uses: github/codeql-action/analyze@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
with:
category: "/language:python"
@@ -236,7 +236,7 @@ jobs:
- name: Detect duplicates using AI
id: ai_detection
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/ai-inference@a380166897b5408b8fb7dddd148142794cb5624a # v2.0.6
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
with:
model: openai/gpt-4o
system-prompt: |
@@ -62,7 +62,7 @@ jobs:
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@a380166897b5408b8fb7dddd148142794cb5624a # v2.0.6
uses: actions/ai-inference@e09e65981758de8b2fdab13c2bfb7c7d5493b0b6 # v2.0.7
with:
model: openai/gpt-4o-mini
system-prompt: |
+2 -2
View File
@@ -58,8 +58,8 @@ jobs:
# v1.7.0
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
with:
app_id: ${{ secrets.ISSUE_TRIAGE_APP_ID }}
private_key: ${{ secrets.ISSUE_TRIAGE_APP_PEM }}
app_id: ${{ secrets.ISSUE_TRIAGE_APP_ID }} # zizmor: ignore[secrets-outside-env]
private_key: ${{ secrets.ISSUE_TRIAGE_APP_PEM }} # zizmor: ignore[secrets-outside-env]
# The 90 day stale policy for issues
# Used for:
+3 -6
View File
@@ -15,9 +15,6 @@ concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
env:
DEFAULT_PYTHON: "3.14.2"
jobs:
upload:
name: Upload
@@ -29,13 +26,13 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
- name: Upload Translations
env:
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }} # zizmor: ignore[secrets-outside-env]
run: |
python3 -m script.translations upload
+12 -15
View File
@@ -16,9 +16,6 @@ on:
- "requirements.txt"
- "script/gen_requirements_all.py"
env:
DEFAULT_PYTHON: "3.14.2"
permissions: {}
concurrency:
@@ -36,11 +33,11 @@ jobs:
with:
persist-credentials: false
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
- name: Set up Python
id: python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version-file: ".python-version"
check-latest: true
- name: Create Python virtual environment
@@ -77,7 +74,7 @@ jobs:
) > .env_file
- name: Upload env_file
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: env_file
path: ./.env_file
@@ -85,7 +82,7 @@ jobs:
overwrite: true
- name: Upload requirements_diff
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: requirements_diff
path: ./requirements_diff.txt
@@ -97,7 +94,7 @@ jobs:
python -m script.gen_requirements_all ci
- name: Upload requirements_all_wheels
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: requirements_all_wheels
path: ./requirements_all_wheels_*.txt
@@ -124,12 +121,12 @@ jobs:
persist-credentials: false
- name: Download env_file
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: env_file
- name: Download requirements_diff
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: requirements_diff
@@ -145,7 +142,7 @@ jobs:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-key: ${{ secrets.WHEELS_KEY }} # zizmor: ignore[secrets-outside-env]
env-file: true
apk: "libffi-dev;openssl-dev;yaml-dev;nasm;zlib-ng-dev"
skip-binary: aiohttp;multidict;propcache;yarl;SQLAlchemy
@@ -175,17 +172,17 @@ jobs:
persist-credentials: false
- name: Download env_file
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: env_file
- name: Download requirements_diff
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: requirements_diff
- name: Download requirements_all_wheels
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3 # v8.0.0
with:
name: requirements_all_wheels
@@ -203,7 +200,7 @@ jobs:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
wheels-key: ${{ secrets.WHEELS_KEY }} # zizmor: ignore[secrets-outside-env]
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
+1 -1
View File
@@ -18,7 +18,7 @@ repos:
exclude_types: [csv, json, html]
exclude: ^tests/fixtures/|homeassistant/generated/|tests/components/.*/snapshots/
- repo: https://github.com/zizmorcore/zizmor-pre-commit
rev: v1.22.0
rev: v1.23.1
hooks:
- id: zizmor
args:
+1 -1
View File
@@ -1 +1 @@
3.14
3.14.2
+5 -1
View File
@@ -123,7 +123,6 @@ homeassistant.components.blueprint.*
homeassistant.components.bluesound.*
homeassistant.components.bluetooth.*
homeassistant.components.bluetooth_adapters.*
homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.*
homeassistant.components.bosch_alarm.*
homeassistant.components.braviatv.*
@@ -213,6 +212,7 @@ homeassistant.components.flexit_bacnet.*
homeassistant.components.flux_led.*
homeassistant.components.folder_watcher.*
homeassistant.components.forecast_solar.*
homeassistant.components.freshr.*
homeassistant.components.fritz.*
homeassistant.components.fritzbox.*
homeassistant.components.fritzbox_callmonitor.*
@@ -289,6 +289,7 @@ homeassistant.components.imgw_pib.*
homeassistant.components.immich.*
homeassistant.components.incomfort.*
homeassistant.components.inels.*
homeassistant.components.infrared.*
homeassistant.components.input_button.*
homeassistant.components.input_select.*
homeassistant.components.input_text.*
@@ -341,6 +342,7 @@ homeassistant.components.lookin.*
homeassistant.components.lovelace.*
homeassistant.components.luftdaten.*
homeassistant.components.lunatone.*
homeassistant.components.lutron.*
homeassistant.components.madvr.*
homeassistant.components.manual.*
homeassistant.components.mastodon.*
@@ -544,6 +546,7 @@ homeassistant.components.tcp.*
homeassistant.components.technove.*
homeassistant.components.tedee.*
homeassistant.components.telegram_bot.*
homeassistant.components.teslemetry.*
homeassistant.components.text.*
homeassistant.components.thethingsnetwork.*
homeassistant.components.threshold.*
@@ -567,6 +570,7 @@ homeassistant.components.trafikverket_train.*
homeassistant.components.trafikverket_weatherstation.*
homeassistant.components.transmission.*
homeassistant.components.trend.*
homeassistant.components.trmnl.*
homeassistant.components.tts.*
homeassistant.components.twentemilieu.*
homeassistant.components.unifi.*
+8 -311
View File
@@ -4,325 +4,22 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
## Code Review Guidelines
**When reviewing code, do NOT comment on:**
- **Missing imports** - We use static analysis tooling to catch that
- **Code formatting** - We have ruff as a formatting tool that will catch those if needed (unless specifically instructed otherwise in these instructions)
**Git commit practices during review:**
- **Do NOT amend, squash, or rebase commits after review has started** - Reviewers need to see what changed since their last review
## Python Requirements
- **Compatibility**: Python 3.13+
- **Language Features**: Use the newest features when possible:
- Pattern matching
- Type hints
- f-strings (preferred over `%` or `.format()`)
- Dataclasses
- Walrus operator
### Strict Typing (Platinum)
- **Comprehensive Type Hints**: Add type hints to all functions, methods, and variables
- **Custom Config Entry Types**: When using runtime_data:
```python
type MyIntegrationConfigEntry = ConfigEntry[MyClient]
```
- **Library Requirements**: Include `py.typed` file for PEP-561 compliance
## Code Quality Standards
- **Formatting**: Ruff
- **Linting**: PyLint and Ruff
- **Type Checking**: MyPy
- **Lint/Type/Format Fixes**: Always prefer addressing the underlying issue (e.g., import the typed source, update shared stubs, align with Ruff expectations, or correct formatting at the source) before disabling a rule, adding `# type: ignore`, or skipping a formatter. Treat suppressions and `noqa` comments as a last resort once no compliant fix exists
- **Testing**: pytest with plain functions and fixtures
- **Language**: American English for all code, comments, and documentation (use sentence case, including titles)
### Writing Style Guidelines
- **Tone**: Friendly and informative
- **Perspective**: Use second-person ("you" and "your") for user-facing messages
- **Inclusivity**: Use objective, non-discriminatory language
- **Clarity**: Write for non-native English speakers
- **Formatting in Messages**:
- Use backticks for: file paths, filenames, variable names, field entries
- Use sentence case for titles and messages (capitalize only the first word and proper nouns)
- Avoid abbreviations when possible
### Documentation Standards
- **File Headers**: Short and concise
```python
"""Integration for Peblar EV chargers."""
```
- **Method/Function Docstrings**: Required for all
```python
async def async_setup_entry(hass: HomeAssistant, entry: PeblarConfigEntry) -> bool:
"""Set up Peblar from a config entry."""
```
- **Comment Style**:
- Use clear, descriptive comments
- Explain the "why" not just the "what"
- Keep code block lines under 80 characters when possible
- Use progressive disclosure (simple explanation first, complex details later)
## Async Programming
- All external I/O operations must be async
- **Best Practices**:
- Avoid sleeping in loops
- Avoid awaiting in loops - use `gather` instead
- No blocking calls
- Group executor jobs when possible - switching between event loop and executor is expensive
### Blocking Operations
- **Use Executor**: For blocking I/O operations
```python
result = await hass.async_add_executor_job(blocking_function, args)
```
- **Never Block Event Loop**: Avoid file operations, `time.sleep()`, blocking HTTP calls
- **Replace with Async**: Use `asyncio.sleep()` instead of `time.sleep()`
### Thread Safety
- **@callback Decorator**: For event loop safe functions
```python
@callback
def async_update_callback(self, event):
"""Safe to run in event loop."""
self.async_write_ha_state()
```
- **Sync APIs from Threads**: Use sync versions when calling from non-event loop threads
- **Registry Changes**: Must be done in event loop thread
### Error Handling
- **Exception Types**: Choose most specific exception available
- `ServiceValidationError`: User input errors (preferred over `ValueError`)
- `HomeAssistantError`: Device communication failures
- `ConfigEntryNotReady`: Temporary setup issues (device offline)
- `ConfigEntryAuthFailed`: Authentication problems
- `ConfigEntryError`: Permanent setup issues
- **Try/Catch Best Practices**:
- Only wrap code that can throw exceptions
- Keep try blocks minimal - process data after the try/catch
- **Avoid bare exceptions** except in specific cases:
- ❌ Generally not allowed: `except:` or `except Exception:`
- ✅ Allowed in config flows to ensure robustness
- ✅ Allowed in functions/methods that run in background tasks
- Bad pattern:
```python
try:
data = await device.get_data() # Can throw
# ❌ Don't process data inside try block
processed = data.get("value", 0) * 100
self._attr_native_value = processed
except DeviceError:
_LOGGER.error("Failed to get data")
```
- Good pattern:
```python
try:
data = await device.get_data() # Can throw
except DeviceError:
_LOGGER.error("Failed to get data")
return
# ✅ Process data outside try block
processed = data.get("value", 0) * 100
self._attr_native_value = processed
```
- **Bare Exception Usage**:
```python
# ❌ Not allowed in regular code
try:
data = await device.get_data()
except Exception: # Too broad
_LOGGER.error("Failed")
# ✅ Allowed in config flow for robustness
async def async_step_user(self, user_input=None):
try:
await self._test_connection(user_input)
except Exception: # Allowed here
errors["base"] = "unknown"
# ✅ Allowed in background tasks
async def _background_refresh():
try:
await coordinator.async_refresh()
except Exception: # Allowed in task
_LOGGER.exception("Unexpected error in background task")
```
- **Setup Failure Patterns**:
```python
try:
await device.async_setup()
except (asyncio.TimeoutError, TimeoutException) as ex:
raise ConfigEntryNotReady(f"Timeout connecting to {device.host}") from ex
except AuthFailed as ex:
raise ConfigEntryAuthFailed(f"Credentials expired for {device.name}") from ex
```
### Logging
- **Format Guidelines**:
- No periods at end of messages
- No integration names/domains (added automatically)
- No sensitive data (keys, tokens, passwords)
- Use debug level for non-user-facing messages
- **Use Lazy Logging**:
```python
_LOGGER.debug("This is a log message with %s", variable)
```
### Unavailability Logging
- **Log Once**: When device/service becomes unavailable (info level)
- **Log Recovery**: When device/service comes back online
- **Implementation Pattern**:
```python
_unavailable_logged: bool = False
if not self._unavailable_logged:
_LOGGER.info("The sensor is unavailable: %s", ex)
self._unavailable_logged = True
# On recovery:
if self._unavailable_logged:
_LOGGER.info("The sensor is back online")
self._unavailable_logged = False
```
## Development Commands
### Environment
- **Local development (non-container)**: Activate the project venv before running commands: `source .venv/bin/activate`
- **Dev container**: No activation needed, the environment is pre-configured
.vscode/tasks.json contains useful commands used for development.
### Code Quality & Linting
- **Run all linters on all files**: `prek run --all-files`
- **Run linters on staged files only**: `prek run`
- **PyLint on everything** (slow): `pylint homeassistant`
- **PyLint on specific folder**: `pylint homeassistant/components/my_integration`
- **MyPy type checking (whole project)**: `mypy homeassistant/`
- **MyPy on specific integration**: `mypy homeassistant/components/my_integration`
## Python Syntax Notes
### Testing
- **Quick test of changed files**: `pytest --timeout=10 --picked`
- **Update test snapshots**: Add `--snapshot-update` to pytest command
- ⚠️ Omit test results after using `--snapshot-update`
- Always run tests again without the flag to verify snapshots
- **Full test suite** (AVOID - very slow): `pytest ./tests`
- Python 3.14 explicitly allows `except TypeA, TypeB:` without parentheses.
### Dependencies & Requirements
- **Update generated files after dependency changes**: `python -m script.gen_requirements_all`
- **Install all Python requirements**:
```bash
uv pip install -r requirements_all.txt -r requirements.txt -r requirements_test.txt
```
- **Install test requirements only**:
```bash
uv pip install -r requirements_test_all.txt -r requirements.txt
```
## Testing
### Translations
- **Update translations after strings.json changes**:
```bash
python -m script.translations develop --all
```
When writing or modifying tests, ensure all test function parameters have type annotations.
Prefer concrete types (for example, `HomeAssistant`, `MockConfigEntry`, etc.) over `Any`.
### Project Validation
- **Run hassfest** (checks project structure and updates generated files):
```bash
python -m script.hassfest
```
## Good practices
## Common Anti-Patterns & Best Practices
### ❌ **Avoid These Patterns**
```python
# Blocking operations in event loop
data = requests.get(url) # ❌ Blocks event loop
time.sleep(5) # ❌ Blocks event loop
# Reusing BleakClient instances
self.client = BleakClient(address)
await self.client.connect()
# Later...
await self.client.connect() # ❌ Don't reuse
# Hardcoded strings in code
self._attr_name = "Temperature Sensor" # ❌ Not translatable
# Missing error handling
data = await self.api.get_data() # ❌ No exception handling
# Storing sensitive data in diagnostics
return {"api_key": entry.data[CONF_API_KEY]} # ❌ Exposes secrets
# Accessing hass.data directly in tests
coordinator = hass.data[DOMAIN][entry.entry_id] # ❌ Don't access hass.data
# User-configurable polling intervals
# In config flow
vol.Optional("scan_interval", default=60): cv.positive_int # ❌ Not allowed
# In coordinator
update_interval = timedelta(minutes=entry.data.get("scan_interval", 1)) # ❌ Not allowed
# User-configurable config entry names (non-helper integrations)
vol.Optional("name", default="My Device"): cv.string # ❌ Not allowed in regular integrations
# Too much code in try block
try:
response = await client.get_data() # Can throw
# ❌ Data processing should be outside try block
temperature = response["temperature"] / 10
humidity = response["humidity"]
self._attr_native_value = temperature
except ClientError:
_LOGGER.error("Failed to fetch data")
# Bare exceptions in regular code
try:
value = await sensor.read_value()
except Exception: # ❌ Too broad - catch specific exceptions
_LOGGER.error("Failed to read sensor")
```
### ✅ **Use These Patterns Instead**
```python
# Async operations with executor
data = await hass.async_add_executor_job(requests.get, url)
await asyncio.sleep(5) # ✅ Non-blocking
# Fresh BleakClient instances
client = BleakClient(address) # ✅ New instance each time
await client.connect()
# Translatable entity names
_attr_translation_key = "temperature_sensor" # ✅ Translatable
# Proper error handling
try:
data = await self.api.get_data()
except ApiException as err:
raise UpdateFailed(f"API error: {err}") from err
# Redacted diagnostics data
return async_redact_data(data, {"api_key", "password"}) # ✅ Safe
# Test through proper integration setup and fixtures
@pytest.fixture
async def init_integration(hass, mock_config_entry, mock_api):
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id) # ✅ Proper setup
# Integration-determined polling intervals (not user-configurable)
SCAN_INTERVAL = timedelta(minutes=5) # ✅ Common pattern: constant in const.py
class MyCoordinator(DataUpdateCoordinator[MyData]):
def __init__(self, hass: HomeAssistant, client: MyClient, config_entry: ConfigEntry) -> None:
# ✅ Integration determines interval based on device capabilities, connection type, etc.
interval = timedelta(minutes=1) if client.is_local else SCAN_INTERVAL
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=interval,
config_entry=config_entry, # ✅ Pass config_entry - it's accepted and recommended
)
```
Integrations with Platinum or Gold level in the Integration Quality Scale reflect a high standard of code quality and maintainability. When looking for examples of something, these are good places to start. The level is indicated in the manifest.json of the integration.
Generated
+38 -13
View File
@@ -186,6 +186,8 @@ build.json @home-assistant/supervisor
/tests/components/auth/ @home-assistant/core
/homeassistant/components/automation/ @home-assistant/core
/tests/components/automation/ @home-assistant/core
/homeassistant/components/autoskope/ @mcisk
/tests/components/autoskope/ @mcisk
/homeassistant/components/avea/ @pattyland
/homeassistant/components/awair/ @ahayworth @ricohageman
/tests/components/awair/ @ahayworth @ricohageman
@@ -234,8 +236,6 @@ build.json @home-assistant/supervisor
/tests/components/bluetooth/ @bdraco
/homeassistant/components/bluetooth_adapters/ @bdraco
/tests/components/bluetooth_adapters/ @bdraco
/homeassistant/components/bmw_connected_drive/ @gerard33 @rikroe
/tests/components/bmw_connected_drive/ @gerard33 @rikroe
/homeassistant/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
/tests/components/bond/ @bdraco @prystupa @joshs85 @marciogranzotto
/homeassistant/components/bosch_alarm/ @mag1024 @sanjay900
@@ -281,6 +281,8 @@ build.json @home-assistant/supervisor
/tests/components/cert_expiry/ @jjlawren
/homeassistant/components/chacon_dio/ @cnico
/tests/components/chacon_dio/ @cnico
/homeassistant/components/chess_com/ @joostlek
/tests/components/chess_com/ @joostlek
/homeassistant/components/cisco_ios/ @fbradyirl
/homeassistant/components/cisco_mobility_express/ @fbradyirl
/homeassistant/components/cisco_webex_teams/ @fbradyirl
@@ -383,6 +385,8 @@ build.json @home-assistant/supervisor
/tests/components/dlna_dms/ @chishm
/homeassistant/components/dnsip/ @gjohansson-ST
/tests/components/dnsip/ @gjohansson-ST
/homeassistant/components/door/ @home-assistant/core
/tests/components/door/ @home-assistant/core
/homeassistant/components/doorbird/ @oblogic7 @bdraco @flacjacket
/tests/components/doorbird/ @oblogic7 @bdraco @flacjacket
/homeassistant/components/dormakaba_dkey/ @emontnemery
@@ -401,8 +405,6 @@ build.json @home-assistant/supervisor
/tests/components/dsmr_reader/ @sorted-bits @glodenox @erwindouna
/homeassistant/components/duckdns/ @tr4nt0r
/tests/components/duckdns/ @tr4nt0r
/homeassistant/components/duke_energy/ @hunterjm
/tests/components/duke_energy/ @hunterjm
/homeassistant/components/duotecno/ @cereal2nd
/tests/components/duotecno/ @cereal2nd
/homeassistant/components/dwd_weather_warnings/ @runningman84 @stephan192
@@ -551,6 +553,8 @@ build.json @home-assistant/supervisor
/tests/components/freebox/ @hacf-fr @Quentame
/homeassistant/components/freedompro/ @stefano055415
/tests/components/freedompro/ @stefano055415
/homeassistant/components/freshr/ @SierraNL
/tests/components/freshr/ @SierraNL
/homeassistant/components/fressnapf_tracker/ @eifinger
/tests/components/fressnapf_tracker/ @eifinger
/homeassistant/components/fritz/ @AaronDavidSchneider @chemelli74 @mib1185
@@ -569,10 +573,14 @@ build.json @home-assistant/supervisor
/tests/components/fully_kiosk/ @cgarwood
/homeassistant/components/fyta/ @dontinelli
/tests/components/fyta/ @dontinelli
/homeassistant/components/garage_door/ @home-assistant/core
/tests/components/garage_door/ @home-assistant/core
/homeassistant/components/garages_amsterdam/ @klaasnicolaas
/tests/components/garages_amsterdam/ @klaasnicolaas
/homeassistant/components/gardena_bluetooth/ @elupus
/tests/components/gardena_bluetooth/ @elupus
/homeassistant/components/gate/ @home-assistant/core
/tests/components/gate/ @home-assistant/core
/homeassistant/components/gdacs/ @exxamalte
/tests/components/gdacs/ @exxamalte
/homeassistant/components/generic/ @davet2001
@@ -739,6 +747,8 @@ build.json @home-assistant/supervisor
/tests/components/huisbaasje/ @dennisschroer
/homeassistant/components/humidifier/ @home-assistant/core @Shulyaka
/tests/components/humidifier/ @home-assistant/core @Shulyaka
/homeassistant/components/humidity/ @home-assistant/core
/tests/components/humidity/ @home-assistant/core
/homeassistant/components/hunterdouglas_powerview/ @bdraco @kingy444 @trullock
/tests/components/hunterdouglas_powerview/ @bdraco @kingy444 @trullock
/homeassistant/components/husqvarna_automower/ @Thomas55555
@@ -788,12 +798,14 @@ build.json @home-assistant/supervisor
/tests/components/improv_ble/ @emontnemery
/homeassistant/components/incomfort/ @jbouwh
/tests/components/incomfort/ @jbouwh
/homeassistant/components/indevolt/ @xirtnl
/tests/components/indevolt/ @xirtnl
/homeassistant/components/indevolt/ @xirt
/tests/components/indevolt/ @xirt
/homeassistant/components/inels/ @epdevlab
/tests/components/inels/ @epdevlab
/homeassistant/components/influxdb/ @mdegat01 @Robbie1221
/tests/components/influxdb/ @mdegat01 @Robbie1221
/homeassistant/components/infrared/ @home-assistant/core
/tests/components/infrared/ @home-assistant/core
/homeassistant/components/inkbird/ @bdraco
/tests/components/inkbird/ @bdraco
/homeassistant/components/input_boolean/ @home-assistant/core
@@ -962,6 +974,8 @@ build.json @home-assistant/supervisor
/tests/components/logbook/ @home-assistant/core
/homeassistant/components/logger/ @home-assistant/core
/tests/components/logger/ @home-assistant/core
/homeassistant/components/lojack/ @devinslick
/tests/components/lojack/ @devinslick
/homeassistant/components/london_underground/ @jpbede
/tests/components/london_underground/ @jpbede
/homeassistant/components/lookin/ @ANMalko @bdraco
@@ -1061,6 +1075,8 @@ build.json @home-assistant/supervisor
/tests/components/moon/ @fabaff @frenck
/homeassistant/components/mopeka/ @bdraco
/tests/components/mopeka/ @bdraco
/homeassistant/components/motion/ @home-assistant/core
/tests/components/motion/ @home-assistant/core
/homeassistant/components/motion_blinds/ @starkillerOG
/tests/components/motion_blinds/ @starkillerOG
/homeassistant/components/motionblinds_ble/ @LennP @jerrybboy
@@ -1174,6 +1190,8 @@ build.json @home-assistant/supervisor
/tests/components/nzbget/ @chriscla
/homeassistant/components/obihai/ @dshokouhi @ejpenney
/tests/components/obihai/ @dshokouhi @ejpenney
/homeassistant/components/occupancy/ @home-assistant/core
/tests/components/occupancy/ @home-assistant/core
/homeassistant/components/octoprint/ @rfleming71
/tests/components/octoprint/ @rfleming71
/homeassistant/components/ohmconnect/ @robbiet480
@@ -1200,6 +1218,8 @@ build.json @home-assistant/supervisor
/tests/components/open_meteo/ @frenck
/homeassistant/components/open_router/ @joostlek
/tests/components/open_router/ @joostlek
/homeassistant/components/opendisplay/ @g4bri3lDev
/tests/components/opendisplay/ @g4bri3lDev
/homeassistant/components/openerz/ @misialq
/tests/components/openerz/ @misialq
/homeassistant/components/openevse/ @c00w @firstof9
@@ -1305,8 +1325,8 @@ build.json @home-assistant/supervisor
/tests/components/prosegur/ @dgomes
/homeassistant/components/proximity/ @mib1185
/tests/components/proximity/ @mib1185
/homeassistant/components/proxmoxve/ @jhollowe @Corbeno @erwindouna
/tests/components/proxmoxve/ @jhollowe @Corbeno @erwindouna
/homeassistant/components/proxmoxve/ @Corbeno @erwindouna @CoMPaTech
/tests/components/proxmoxve/ @Corbeno @erwindouna @CoMPaTech
/homeassistant/components/ps4/ @ktnrg45
/tests/components/ps4/ @ktnrg45
/homeassistant/components/pterodactyl/ @elmurato
@@ -1650,8 +1670,8 @@ build.json @home-assistant/supervisor
/tests/components/system_bridge/ @timmo001
/homeassistant/components/systemmonitor/ @gjohansson-ST
/tests/components/systemmonitor/ @gjohansson-ST
/homeassistant/components/systemnexa2/ @konsulten @slangstrom
/tests/components/systemnexa2/ @konsulten @slangstrom
/homeassistant/components/systemnexa2/ @konsulten
/tests/components/systemnexa2/ @konsulten
/homeassistant/components/tado/ @erwindouna
/tests/components/tado/ @erwindouna
/homeassistant/components/tag/ @home-assistant/core
@@ -1691,7 +1711,6 @@ build.json @home-assistant/supervisor
/tests/components/tessie/ @Bre77
/homeassistant/components/text/ @home-assistant/core
/tests/components/text/ @home-assistant/core
/homeassistant/components/tfiac/ @fredrike @mellado
/homeassistant/components/thermobeacon/ @bdraco
/tests/components/thermobeacon/ @bdraco
/homeassistant/components/thermopro/ @bdraco @h3ss
@@ -1753,6 +1772,8 @@ build.json @home-assistant/supervisor
/tests/components/trend/ @jpbede
/homeassistant/components/triggercmd/ @rvmey
/tests/components/triggercmd/ @rvmey
/homeassistant/components/trmnl/ @joostlek
/tests/components/trmnl/ @joostlek
/homeassistant/components/tts/ @home-assistant/core
/tests/components/tts/ @home-assistant/core
/homeassistant/components/tuya/ @Tuya @zlinoliver
@@ -1769,6 +1790,8 @@ build.json @home-assistant/supervisor
/tests/components/ukraine_alarm/ @PaulAnnekov
/homeassistant/components/unifi/ @Kane610
/tests/components/unifi/ @Kane610
/homeassistant/components/unifi_access/ @imhotep @RaHehl
/tests/components/unifi_access/ @imhotep @RaHehl
/homeassistant/components/unifi_direct/ @tofuSCHNITZEL
/homeassistant/components/unifiled/ @florisvdk
/homeassistant/components/unifiprotect/ @RaHehl
@@ -1894,13 +1917,15 @@ build.json @home-assistant/supervisor
/tests/components/wiffi/ @mampfes
/homeassistant/components/wilight/ @leofig-rj
/tests/components/wilight/ @leofig-rj
/homeassistant/components/window/ @home-assistant/core
/tests/components/window/ @home-assistant/core
/homeassistant/components/wirelesstag/ @sergeymaysak
/homeassistant/components/withings/ @joostlek
/tests/components/withings/ @joostlek
/homeassistant/components/wiz/ @sbidy @arturpragacz
/tests/components/wiz/ @sbidy @arturpragacz
/homeassistant/components/wled/ @frenck
/tests/components/wled/ @frenck
/homeassistant/components/wled/ @frenck @mik-laj
/tests/components/wled/ @frenck @mik-laj
/homeassistant/components/wmspro/ @mback2k
/tests/components/wmspro/ @mback2k
/homeassistant/components/wolflink/ @adamkrol93 @mtielen
Generated
-1
View File
@@ -10,7 +10,6 @@ LABEL \
org.opencontainers.image.description="Open-source home automation platform running on Python 3" \
org.opencontainers.image.documentation="https://www.home-assistant.io/docs/" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://github.com/home-assistant/core" \
org.opencontainers.image.title="Home Assistant" \
org.opencontainers.image.url="https://www.home-assistant.io/"
+2
View File
@@ -10,6 +10,7 @@ coverage:
target: auto
threshold: 1
paths:
- homeassistant/components/*/backup.py
- homeassistant/components/*/config_flow.py
- homeassistant/components/*/device_action.py
- homeassistant/components/*/device_condition.py
@@ -28,6 +29,7 @@ coverage:
target: 100
threshold: 0
paths:
- homeassistant/components/*/backup.py
- homeassistant/components/*/config_flow.py
- homeassistant/components/*/device_action.py
- homeassistant/components/*/device_condition.py
+12
View File
@@ -236,6 +236,18 @@ DEFAULT_INTEGRATIONS = {
"input_text",
"schedule",
"timer",
#
# Base platforms:
*BASE_PLATFORMS,
#
# Integrations providing triggers and conditions for base platforms:
"door",
"garage_door",
"gate",
"humidity",
"motion",
"occupancy",
"window",
}
DEFAULT_INTEGRATIONS_RECOVERY_MODE = {
# These integrations are set up if recovery mode is activated.
+8 -1
View File
@@ -1,5 +1,12 @@
{
"domain": "ubiquiti",
"name": "Ubiquiti",
"integrations": ["airos", "unifi", "unifi_direct", "unifiled", "unifiprotect"]
"integrations": [
"airos",
"unifi",
"unifi_access",
"unifi_direct",
"unifiled",
"unifiprotect"
]
}
+3 -10
View File
@@ -12,10 +12,6 @@ from homeassistant.helpers.dispatcher import dispatcher_send
from .const import DOMAIN, DOMAIN_DATA, LOGGER
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
ATTR_SETTING = "setting"
ATTR_VALUE = "value"
@@ -75,16 +71,13 @@ def async_setup_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
hass.services.async_register(
DOMAIN, SERVICE_SETTINGS, _change_setting, schema=CHANGE_SETTING_SCHEMA
DOMAIN, "change_setting", _change_setting, schema=CHANGE_SETTING_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_CAPTURE_IMAGE, _capture_image, schema=CAPTURE_IMAGE_SCHEMA
DOMAIN, "capture_image", _capture_image, schema=CAPTURE_IMAGE_SCHEMA
)
hass.services.async_register(
DOMAIN,
SERVICE_TRIGGER_AUTOMATION,
_trigger_automation,
schema=AUTOMATION_SCHEMA,
DOMAIN, "trigger_automation", _trigger_automation, schema=AUTOMATION_SCHEMA
)
+34 -6
View File
@@ -168,29 +168,57 @@ class LocalAdaxDevice(CoordinatorEntity[AdaxLocalCoordinator], ClimateEntity):
if hvac_mode == HVACMode.HEAT:
temperature = self._attr_target_temperature or self._attr_min_temp
await self._adax_data_handler.set_target_temperature(temperature)
self._attr_target_temperature = temperature
self._attr_icon = "mdi:radiator"
elif hvac_mode == HVACMode.OFF:
await self._adax_data_handler.set_target_temperature(0)
self._attr_icon = "mdi:radiator-off"
else:
# Ignore unsupported HVAC modes to avoid desynchronizing entity state
# from the physical device.
return
self._attr_hvac_mode = hvac_mode
self.async_write_ha_state()
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
await self._adax_data_handler.set_target_temperature(temperature)
if self._attr_hvac_mode == HVACMode.HEAT:
await self._adax_data_handler.set_target_temperature(temperature)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._attr_target_temperature = temperature
self.async_write_ha_state()
def _update_hvac_attributes(self) -> None:
"""Update hvac mode and temperatures from coordinator data.
The coordinator reports a target temperature of 0 when the heater is
turned off. In that case, only the hvac mode and icon are updated and
the previous non-zero target temperature is preserved. When the
reported target temperature is non-zero, the stored target temperature
is updated to match the coordinator value.
"""
if data := self.coordinator.data:
self._attr_current_temperature = data["current_temperature"]
self._attr_available = self._attr_current_temperature is not None
if (target_temp := data["target_temperature"]) == 0:
self._attr_hvac_mode = HVACMode.OFF
self._attr_icon = "mdi:radiator-off"
if target_temp == 0:
if self._attr_target_temperature is None:
self._attr_target_temperature = self._attr_min_temp
else:
self._attr_hvac_mode = HVACMode.HEAT
self._attr_icon = "mdi:radiator"
self._attr_target_temperature = target_temp
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._update_hvac_attributes()
super()._handle_coordinator_update()
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self._update_hvac_attributes()
@@ -10,8 +10,6 @@ from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN
ADVANTAGE_AIR_SERVICE_SET_TIME_TO = "set_time_to"
@callback
def async_setup_services(hass: HomeAssistant) -> None:
@@ -20,7 +18,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
ADVANTAGE_AIR_SERVICE_SET_TIME_TO,
"set_time_to",
entity_domain=SENSOR_DOMAIN,
schema={vol.Required("minutes"): cv.positive_int},
func="set_time_to",
+5 -11
View File
@@ -8,18 +8,12 @@ from homeassistant.helpers import service
from .const import DOMAIN
_DEV_EN_ALT = "enable_alerts"
_DEV_DS_ALT = "disable_alerts"
_DEV_EN_REC = "start_recording"
_DEV_DS_REC = "stop_recording"
_DEV_SNAP = "snapshot"
CAMERA_SERVICES = {
_DEV_EN_ALT: "async_enable_alerts",
_DEV_DS_ALT: "async_disable_alerts",
_DEV_EN_REC: "async_start_recording",
_DEV_DS_REC: "async_stop_recording",
_DEV_SNAP: "async_snapshot",
"enable_alerts": "async_enable_alerts",
"disable_alerts": "async_disable_alerts",
"start_recording": "async_start_recording",
"stop_recording": "async_stop_recording",
"snapshot": "async_snapshot",
}
@@ -18,6 +18,10 @@ from homeassistant.helpers.schema_config_entry_flow import (
SchemaOptionsFlowHandler,
)
from homeassistant.helpers.selector import BooleanSelector
from homeassistant.helpers.service_info.zeroconf import (
ATTR_PROPERTIES_ID,
ZeroconfServiceInfo,
)
from .const import CONF_CLIP_NEGATIVE, CONF_RETURN_AVERAGE, DOMAIN
@@ -46,6 +50,9 @@ class AirQConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_discovered_host: str
_discovered_name: str
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
@@ -90,6 +97,58 @@ class AirQConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery of an air-Q device."""
self._discovered_host = discovery_info.host
self._discovered_name = discovery_info.properties.get("devicename", "air-Q")
device_id = discovery_info.properties.get(ATTR_PROPERTIES_ID)
if not device_id:
return self.async_abort(reason="incomplete_discovery")
await self.async_set_unique_id(device_id)
self._abort_if_unique_id_configured(
updates={CONF_IP_ADDRESS: self._discovered_host},
reload_on_update=True,
)
self.context["title_placeholders"] = {"name": self._discovered_name}
return await self.async_step_discovery_confirm()
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle user confirmation of a discovered air-Q device."""
errors: dict[str, str] = {}
if user_input is not None:
session = async_get_clientsession(self.hass)
airq = AirQ(self._discovered_host, user_input[CONF_PASSWORD], session)
try:
await airq.validate()
except ClientConnectionError:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
else:
return self.async_create_entry(
title=self._discovered_name,
data={
CONF_IP_ADDRESS: self._discovered_host,
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
return self.async_show_form(
step_id="discovery_confirm",
data_schema=vol.Schema({vol.Required(CONF_PASSWORD): str}),
description_placeholders={"name": self._discovered_name},
errors=errors,
)
@staticmethod
@callback
def async_get_options_flow(
+9 -1
View File
@@ -7,5 +7,13 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioairq"],
"requirements": ["aioairq==0.4.7"]
"requirements": ["aioairq==0.4.7"],
"zeroconf": [
{
"properties": {
"device": "air-q"
},
"type": "_http._tcp.local."
}
]
}
+10 -1
View File
@@ -1,14 +1,23 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"incomplete_discovery": "The discovered air-Q device did not provide a device ID. Ensure the firmware is up to date."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_input": "[%key:common::config_flow::error::invalid_host%]"
},
"flow_title": "{name}",
"step": {
"discovery_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"description": "Do you want to set up **{name}**?",
"title": "Set up air-Q"
},
"user": {
"data": {
"ip_address": "[%key:common::config_flow::data::ip%]",
@@ -117,23 +117,23 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update()
@property
def current_temperature(self):
def current_temperature(self) -> int:
"""Return the current temperature."""
return self._unit.Temperature
@property
def fan_mode(self):
def fan_mode(self) -> str:
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._ac_number].AcFanSpeed]
@property
def fan_modes(self):
def fan_modes(self) -> list[str]:
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsForAc(self._ac_number)
return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds]
@property
def hvac_mode(self):
def hvac_mode(self) -> HVACMode:
"""Return hvac target hvac state."""
is_off = self._unit.PowerState == "Off"
if is_off:
@@ -236,17 +236,17 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint
@property
def current_temperature(self):
def current_temperature(self) -> int:
"""Return the current temperature."""
return self._unit.Temperature
@property
def target_temperature(self):
def target_temperature(self) -> int:
"""Return the temperature we are trying to reach."""
return self._unit.TargetSetpoint
@property
def hvac_mode(self):
def hvac_mode(self) -> HVACMode:
"""Return hvac target hvac state."""
# there are other power states that aren't 'on' but still count as on (eg. 'Turbo')
is_off = self._unit.PowerState == "Off"
@@ -272,12 +272,12 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
self.async_write_ha_state()
@property
def fan_mode(self):
def fan_mode(self) -> str:
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._unit.BelongsToAc].AcFanSpeed]
@property
def fan_modes(self):
def fan_modes(self) -> list[str]:
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsByGroup(
self._group_number
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["airtouch5py"],
"requirements": ["airtouch5py==0.3.0"]
"requirements": ["airtouch5py==0.4.0"]
}
+7 -43
View File
@@ -7,13 +7,7 @@ from datetime import timedelta
from math import ceil
from typing import Any
from pyairvisual.cloud_api import (
CloudAPI,
InvalidKeyError,
KeyExpiredError,
UnauthorizedError,
)
from pyairvisual.errors import AirVisualError
from pyairvisual.cloud_api import CloudAPI
from homeassistant.components import automation
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
@@ -28,14 +22,12 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import (
aiohttp_client,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
CONF_CITY,
@@ -47,8 +39,7 @@ from .const import (
INTEGRATION_TYPE_NODE_PRO,
LOGGER,
)
type AirVisualConfigEntry = ConfigEntry[DataUpdateCoordinator]
from .coordinator import AirVisualConfigEntry, AirVisualDataUpdateCoordinator
# We use a raw string for the airvisual_pro domain (instead of importing the actual
# constant) so that we can avoid listing it as a dependency:
@@ -85,8 +76,8 @@ def async_get_cloud_api_update_interval(
@callback
def async_get_cloud_coordinators_by_api_key(
hass: HomeAssistant, api_key: str
) -> list[DataUpdateCoordinator]:
"""Get all DataUpdateCoordinator objects related to a particular API key."""
) -> list[AirVisualDataUpdateCoordinator]:
"""Get all AirVisualDataUpdateCoordinator objects related to a particular API key."""
return [
entry.runtime_data
for entry in hass.config_entries.async_entries(DOMAIN)
@@ -180,38 +171,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirVisualConfigEntry) ->
websession = aiohttp_client.async_get_clientsession(hass)
cloud_api = CloudAPI(entry.data[CONF_API_KEY], session=websession)
async def async_update_data() -> dict[str, Any]:
"""Get new data from the API."""
if CONF_CITY in entry.data:
api_coro = cloud_api.air_quality.city(
entry.data[CONF_CITY],
entry.data[CONF_STATE],
entry.data[CONF_COUNTRY],
)
else:
api_coro = cloud_api.air_quality.nearest_city(
entry.data[CONF_LATITUDE],
entry.data[CONF_LONGITUDE],
)
try:
return await api_coro
except (InvalidKeyError, KeyExpiredError, UnauthorizedError) as ex:
raise ConfigEntryAuthFailed from ex
except AirVisualError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err
coordinator = DataUpdateCoordinator(
coordinator = AirVisualDataUpdateCoordinator(
hass,
LOGGER,
config_entry=entry,
entry,
cloud_api,
name=async_get_geography_id(entry.data),
# We give a placeholder update interval in order to create the coordinator;
# then, below, we use the coordinator's presence (along with any other
# coordinators using the same API key) to calculate an actual, leveled
# update interval:
update_interval=timedelta(minutes=5),
update_method=async_update_data,
)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
@@ -0,0 +1,72 @@
"""Define an AirVisual data coordinator."""
from __future__ import annotations
from datetime import timedelta
from typing import Any
from pyairvisual.cloud_api import (
CloudAPI,
InvalidKeyError,
KeyExpiredError,
UnauthorizedError,
)
from pyairvisual.errors import AirVisualError
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_COUNTRY, CONF_LATITUDE, CONF_LONGITUDE, CONF_STATE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_CITY, LOGGER
type AirVisualConfigEntry = ConfigEntry[AirVisualDataUpdateCoordinator]
class AirVisualDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Class to manage fetching AirVisual data."""
config_entry: AirVisualConfigEntry
def __init__(
self,
hass: HomeAssistant,
entry: AirVisualConfigEntry,
cloud_api: CloudAPI,
name: str,
) -> None:
"""Initialize the coordinator."""
self._cloud_api = cloud_api
super().__init__(
hass,
LOGGER,
config_entry=entry,
name=name,
# We give a placeholder update interval in order to create the coordinator;
# then, in async_setup_entry, we use the coordinator's presence (along with
# any other coordinators using the same API key) to calculate an actual,
# leveled update interval:
update_interval=timedelta(minutes=5),
)
async def _async_update_data(self) -> dict[str, Any]:
"""Get new data from the API."""
if CONF_CITY in self.config_entry.data:
api_coro = self._cloud_api.air_quality.city(
self.config_entry.data[CONF_CITY],
self.config_entry.data[CONF_STATE],
self.config_entry.data[CONF_COUNTRY],
)
else:
api_coro = self._cloud_api.air_quality.nearest_city(
self.config_entry.data[CONF_LATITUDE],
self.config_entry.data[CONF_LONGITUDE],
)
try:
return await api_coro
except (InvalidKeyError, KeyExpiredError, UnauthorizedError) as ex:
raise ConfigEntryAuthFailed from ex
except AirVisualError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err
@@ -15,8 +15,8 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from . import AirVisualConfigEntry
from .const import CONF_CITY
from .coordinator import AirVisualConfigEntry
CONF_COORDINATES = "coordinates"
CONF_TITLE = "title"
+5 -9
View File
@@ -2,29 +2,25 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import AirVisualDataUpdateCoordinator
class AirVisualEntity(CoordinatorEntity):
class AirVisualEntity(CoordinatorEntity[AirVisualDataUpdateCoordinator]):
"""Define a generic AirVisual entity."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
coordinator: AirVisualDataUpdateCoordinator,
description: EntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_extra_state_attributes = {}
self._entry = entry
self.entity_description = description
async def async_added_to_hass(self) -> None:
+8 -10
View File
@@ -8,7 +8,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_LATITUDE,
ATTR_LONGITUDE,
@@ -24,10 +23,9 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from . import AirVisualConfigEntry
from .const import CONF_CITY
from .coordinator import AirVisualConfigEntry, AirVisualDataUpdateCoordinator
from .entity import AirVisualEntity
ATTR_CITY = "city"
@@ -113,7 +111,7 @@ async def async_setup_entry(
"""Set up AirVisual sensors based on a config entry."""
coordinator = entry.runtime_data
async_add_entities(
AirVisualGeographySensor(coordinator, entry, description, locale)
AirVisualGeographySensor(coordinator, description, locale)
for locale in GEOGRAPHY_SENSOR_LOCALES
for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
)
@@ -124,14 +122,14 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
def __init__(
self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
coordinator: AirVisualDataUpdateCoordinator,
description: SensorEntityDescription,
locale: str,
) -> None:
"""Initialize."""
super().__init__(coordinator, entry, description)
super().__init__(coordinator, description)
entry = coordinator.config_entry
self._attr_extra_state_attributes.update(
{
ATTR_CITY: entry.data.get(CONF_CITY),
@@ -182,16 +180,16 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
#
# We use any coordinates in the config entry and, in the case of a geography by
# name, we fall back to the latitude longitude provided in the coordinator data:
latitude = self._entry.data.get(
latitude = self.coordinator.config_entry.data.get(
CONF_LATITUDE,
self.coordinator.data["location"]["coordinates"][1],
)
longitude = self._entry.data.get(
longitude = self.coordinator.config_entry.data.get(
CONF_LONGITUDE,
self.coordinator.data["location"]["coordinates"][0],
)
if self._entry.options[CONF_SHOW_ON_MAP]:
if self.coordinator.config_entry.options[CONF_SHOW_ON_MAP]:
self._attr_extra_state_attributes[ATTR_LATITUDE] = latitude
self._attr_extra_state_attributes[ATTR_LONGITUDE] = longitude
self._attr_extra_state_attributes.pop("lati", None)
@@ -4,18 +4,9 @@ from __future__ import annotations
import asyncio
from contextlib import suppress
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
from pyairvisual.node import (
InvalidAuthenticationError,
NodeConnectionError,
NodeProError,
NodeSamba,
)
from pyairvisual.node import NodeProError, NodeSamba
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_IP_ADDRESS,
CONF_PASSWORD,
@@ -23,25 +14,16 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from homeassistant.exceptions import ConfigEntryNotReady
from .const import LOGGER
from .coordinator import (
AirVisualProConfigEntry,
AirVisualProCoordinator,
AirVisualProData,
)
PLATFORMS = [Platform.SENSOR]
UPDATE_INTERVAL = timedelta(minutes=1)
type AirVisualProConfigEntry = ConfigEntry[AirVisualProData]
@dataclass
class AirVisualProData:
"""Define a data class."""
coordinator: DataUpdateCoordinator
node: NodeSamba
async def async_setup_entry(
hass: HomeAssistant, entry: AirVisualProConfigEntry
@@ -54,48 +36,15 @@ async def async_setup_entry(
except NodeProError as err:
raise ConfigEntryNotReady from err
reload_task: asyncio.Task | None = None
async def async_get_data() -> dict[str, Any]:
"""Get data from the device."""
try:
data = await node.async_get_latest_measurements()
data["history"] = {}
if data["settings"].get("follow_mode") == "device":
history = await node.async_get_history(include_trends=False)
data["history"] = history.get("measurements", [])[-1]
except InvalidAuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid Samba password") from err
except NodeConnectionError as err:
nonlocal reload_task
if not reload_task:
reload_task = hass.async_create_task(
hass.config_entries.async_reload(entry.entry_id)
)
raise UpdateFailed(f"Connection to Pro unit lost: {err}") from err
except NodeProError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err
return data
coordinator = DataUpdateCoordinator(
hass,
LOGGER,
config_entry=entry,
name="Node/Pro data",
update_interval=UPDATE_INTERVAL,
update_method=async_get_data,
)
coordinator = AirVisualProCoordinator(hass, entry, node)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = AirVisualProData(coordinator=coordinator, node=node)
async def async_shutdown(_: Event) -> None:
"""Define an event handler to disconnect from the websocket."""
nonlocal reload_task
if reload_task:
if coordinator.reload_task:
with suppress(asyncio.CancelledError):
reload_task.cancel()
coordinator.reload_task.cancel()
await node.async_disconnect()
entry.async_on_unload(
@@ -0,0 +1,79 @@
"""DataUpdateCoordinator for the AirVisual Pro integration."""
from __future__ import annotations
import asyncio
from dataclasses import dataclass
from datetime import timedelta
from typing import Any
from pyairvisual.node import (
InvalidAuthenticationError,
NodeConnectionError,
NodeProError,
NodeSamba,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import LOGGER
UPDATE_INTERVAL = timedelta(minutes=1)
@dataclass
class AirVisualProData:
"""Define a data class."""
coordinator: AirVisualProCoordinator
node: NodeSamba
type AirVisualProConfigEntry = ConfigEntry[AirVisualProData]
class AirVisualProCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Coordinator for AirVisual Pro data."""
config_entry: AirVisualProConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: AirVisualProConfigEntry,
node: NodeSamba,
) -> None:
"""Initialize."""
super().__init__(
hass,
LOGGER,
config_entry=config_entry,
name="Node/Pro data",
update_interval=UPDATE_INTERVAL,
)
self._node = node
self.reload_task: asyncio.Task[bool] | None = None
async def _async_update_data(self) -> dict[str, Any]:
"""Get data from the device."""
try:
data = await self._node.async_get_latest_measurements()
data["history"] = {}
if data["settings"].get("follow_mode") == "device":
history = await self._node.async_get_history(include_trends=False)
data["history"] = history.get("measurements", [])[-1]
except InvalidAuthenticationError as err:
raise ConfigEntryAuthFailed("Invalid Samba password") from err
except NodeConnectionError as err:
if self.reload_task is None:
self.reload_task = self.hass.async_create_task(
self.hass.config_entries.async_reload(self.config_entry.entry_id)
)
raise UpdateFailed(f"Connection to Pro unit lost: {err}") from err
except NodeProError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err
return data
@@ -8,7 +8,7 @@ from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from . import AirVisualProConfigEntry
from .coordinator import AirVisualProConfigEntry
CONF_MAC_ADDRESS = "mac_address"
CONF_SERIAL_NUMBER = "serial_number"
@@ -4,19 +4,17 @@ from __future__ import annotations
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import AirVisualProCoordinator
class AirVisualProEntity(CoordinatorEntity):
class AirVisualProEntity(CoordinatorEntity[AirVisualProCoordinator]):
"""Define a generic AirVisual Pro entity."""
def __init__(
self, coordinator: DataUpdateCoordinator, description: EntityDescription
self, coordinator: AirVisualProCoordinator, description: EntityDescription
) -> None:
"""Initialize."""
super().__init__(coordinator)
@@ -22,7 +22,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AirVisualProConfigEntry
from .coordinator import AirVisualProConfigEntry
from .entity import AirVisualProEntity
@@ -0,0 +1,32 @@
"""Diagnostics support for Aladdin Connect."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from .coordinator import AladdinConnectConfigEntry
TO_REDACT = {"access_token", "refresh_token"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
"config_entry": async_redact_data(config_entry.as_dict(), TO_REDACT),
"doors": {
uid: {
"device_id": coordinator.data.device_id,
"door_number": coordinator.data.door_number,
"name": coordinator.data.name,
"status": coordinator.data.status,
"link_status": coordinator.data.link_status,
"battery_level": coordinator.data.battery_level,
}
for uid, coordinator in config_entry.runtime_data.items()
},
}
@@ -45,7 +45,7 @@ rules:
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery: done
discovery-update-info:
status: exempt
@@ -66,9 +66,7 @@ rules:
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo
stale-devices:
status: todo
comment: We can automatically remove removed devices
stale-devices: done
# Platinum
async-dependency: todo
@@ -2,6 +2,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.condition import (
Condition,
EntityStateConditionBase,
@@ -43,7 +44,7 @@ def make_entity_state_required_features_condition(
class CustomCondition(EntityStateRequiredFeaturesCondition):
"""Condition for entity state changes."""
_domain = domain
_domain_specs = {domain: DomainSpec()}
_states = {to_state}
_required_features = required_features
@@ -2,6 +2,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.automation import DomainSpec
from homeassistant.helpers.entity import get_supported_features
from homeassistant.helpers.trigger import (
EntityTargetStateTriggerBase,
@@ -44,7 +45,7 @@ def make_entity_state_trigger_required_features(
class CustomTrigger(EntityStateTriggerRequiredFeatures):
"""Trigger for entity state changes."""
_domain = domain
_domain_specs = {domain: DomainSpec()}
_to_states = {to_state}
_required_features = required_features
@@ -13,9 +13,6 @@ from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN
SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
ATTR_KEYPRESS = "keypress"
@@ -26,7 +23,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ALARM_TOGGLE_CHIME,
"alarm_toggle_chime",
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
schema={
vol.Required(ATTR_CODE): cv.string,
@@ -37,7 +34,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ALARM_KEYPRESS,
"alarm_keypress",
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
schema={
vol.Required(ATTR_KEYPRESS): cv.string,
@@ -16,9 +16,6 @@ from .coordinator import AmazonConfigEntry
ATTR_TEXT_COMMAND = "text_command"
ATTR_SOUND = "sound"
ATTR_INFO_SKILL = "info_skill"
SERVICE_TEXT_COMMAND = "send_text_command"
SERVICE_SOUND_NOTIFICATION = "send_sound"
SERVICE_INFO_SKILL = "send_info_skill"
SCHEMA_SOUND_SERVICE = vol.Schema(
{
@@ -128,17 +125,17 @@ def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the services for the Amazon Devices integration."""
for service_name, method, schema in (
(
SERVICE_SOUND_NOTIFICATION,
"send_sound",
async_send_sound_notification,
SCHEMA_SOUND_SERVICE,
),
(
SERVICE_TEXT_COMMAND,
"send_text_command",
async_send_text_command,
SCHEMA_CUSTOM_COMMAND,
),
(
SERVICE_INFO_SKILL,
"send_info_skill",
async_send_info_skill,
SCHEMA_INFO_SKILL,
),
@@ -16,8 +16,6 @@ ATTRIBUTION = "Data provided by Amber Electric"
LOGGER = logging.getLogger(__package__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
SERVICE_GET_FORECASTS = "get_forecasts"
GENERAL_CHANNEL = "general"
CONTROLLED_LOAD_CHANNEL = "controlled_load"
FEED_IN_CHANNEL = "feed_in"
@@ -22,7 +22,6 @@ from .const import (
DOMAIN,
FEED_IN_CHANNEL,
GENERAL_CHANNEL,
SERVICE_GET_FORECASTS,
)
from .coordinator import AmberConfigEntry
from .helpers import format_cents_to_dollars, normalize_descriptor
@@ -101,7 +100,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
hass.services.async_register(
DOMAIN,
SERVICE_GET_FORECASTS,
"get_forecasts",
handle_get_forecasts,
GET_FORECASTS_SCHEMA,
supports_response=SupportsResponse.ONLY,
+11 -23
View File
@@ -49,18 +49,6 @@ SCAN_INTERVAL = timedelta(seconds=15)
STREAM_SOURCE_LIST = ["snapshot", "mjpeg", "rtsp"]
_SRV_EN_REC = "enable_recording"
_SRV_DS_REC = "disable_recording"
_SRV_EN_AUD = "enable_audio"
_SRV_DS_AUD = "disable_audio"
_SRV_EN_MOT_REC = "enable_motion_recording"
_SRV_DS_MOT_REC = "disable_motion_recording"
_SRV_GOTO = "goto_preset"
_SRV_CBW = "set_color_bw"
_SRV_TOUR_ON = "start_tour"
_SRV_TOUR_OFF = "stop_tour"
_SRV_PTZ_CTRL = "ptz_control"
_ATTR_PTZ_TT = "travel_time"
_ATTR_PTZ_MOV = "movement"
_MOV = [
@@ -103,17 +91,17 @@ _SRV_PTZ_SCHEMA = _SRV_SCHEMA.extend(
)
CAMERA_SERVICES = {
_SRV_EN_REC: (_SRV_SCHEMA, "async_enable_recording", ()),
_SRV_DS_REC: (_SRV_SCHEMA, "async_disable_recording", ()),
_SRV_EN_AUD: (_SRV_SCHEMA, "async_enable_audio", ()),
_SRV_DS_AUD: (_SRV_SCHEMA, "async_disable_audio", ()),
_SRV_EN_MOT_REC: (_SRV_SCHEMA, "async_enable_motion_recording", ()),
_SRV_DS_MOT_REC: (_SRV_SCHEMA, "async_disable_motion_recording", ()),
_SRV_GOTO: (_SRV_GOTO_SCHEMA, "async_goto_preset", (_ATTR_PRESET,)),
_SRV_CBW: (_SRV_CBW_SCHEMA, "async_set_color_bw", (_ATTR_COLOR_BW,)),
_SRV_TOUR_ON: (_SRV_SCHEMA, "async_start_tour", ()),
_SRV_TOUR_OFF: (_SRV_SCHEMA, "async_stop_tour", ()),
_SRV_PTZ_CTRL: (
"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),
@@ -338,6 +338,7 @@ class Analytics:
hass = self._hass
supervisor_info = None
addons_info: dict[str, Any] | None = None
operating_system_info: dict[str, Any] = {}
if self._data.uuid is None:
@@ -347,6 +348,7 @@ class Analytics:
if self.supervisor:
supervisor_info = hassio.get_supervisor_info(hass)
operating_system_info = hassio.get_os_info(hass) or {}
addons_info = hassio.get_addons_info(hass) or {}
system_info = await async_get_system_info(hass)
integrations = []
@@ -419,13 +421,10 @@ class Analytics:
integrations.append(integration.domain)
if supervisor_info is not None:
if addons_info is not None:
supervisor_client = hassio.get_supervisor_client(hass)
installed_addons = await asyncio.gather(
*(
supervisor_client.addons.addon_info(addon[ATTR_SLUG])
for addon in supervisor_info[ATTR_ADDONS]
)
*(supervisor_client.addons.addon_info(slug) for slug in addons_info)
)
addons.extend(
{
@@ -36,7 +36,7 @@ from .const import (
SIGNAL_CONFIG_ENTITY,
)
from .entity import AndroidTVEntity, adb_decorator
from .services import ATTR_ADB_RESPONSE, ATTR_HDMI_INPUT, SERVICE_LEARN_SENDEVENT
from .services import ATTR_ADB_RESPONSE, ATTR_HDMI_INPUT
_LOGGER = logging.getLogger(__name__)
@@ -271,7 +271,7 @@ class ADBDevice(AndroidTVEntity, MediaPlayerEntity):
self.async_write_ha_state()
msg = (
f"Output from service '{SERVICE_LEARN_SENDEVENT}' from"
f"Output from service 'learn_sendevent' from"
f" {self.entity_id}: '{output}'"
)
persistent_notification.async_create(
@@ -16,11 +16,6 @@ ATTR_DEVICE_PATH = "device_path"
ATTR_HDMI_INPUT = "hdmi_input"
ATTR_LOCAL_PATH = "local_path"
SERVICE_ADB_COMMAND = "adb_command"
SERVICE_DOWNLOAD = "download"
SERVICE_LEARN_SENDEVENT = "learn_sendevent"
SERVICE_UPLOAD = "upload"
@callback
def async_setup_services(hass: HomeAssistant) -> None:
@@ -29,7 +24,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ADB_COMMAND,
"adb_command",
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={vol.Required(ATTR_COMMAND): cv.string},
func="adb_command",
@@ -37,7 +32,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_LEARN_SENDEVENT,
"learn_sendevent",
entity_domain=MEDIA_PLAYER_DOMAIN,
schema=None,
func="learn_sendevent",
@@ -45,7 +40,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_DOWNLOAD,
"download",
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={
vol.Required(ATTR_DEVICE_PATH): cv.string,
@@ -56,7 +51,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_UPLOAD,
"upload",
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={
vol.Required(ATTR_DEVICE_PATH): cv.string,
@@ -27,4 +27,4 @@ def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRem
def get_enable_ime(entry: AndroidTVRemoteConfigEntry) -> bool:
"""Get value of enable_ime option or its default value."""
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE) # type: ignore[no-any-return]
return bool(entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE))
@@ -4,6 +4,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from datetime import datetime
from enum import StrEnum
from pyanglianwater.meter import SmartMeter
@@ -32,13 +33,14 @@ class AnglianWaterSensor(StrEnum):
YESTERDAY_WATER_COST = "yesterday_water_cost"
YESTERDAY_SEWERAGE_COST = "yesterday_sewerage_cost"
LATEST_READING = "latest_reading"
LAST_UPDATED = "last_updated"
@dataclass(frozen=True, kw_only=True)
class AnglianWaterSensorEntityDescription(SensorEntityDescription):
"""Describes AnglianWater sensor entity."""
value_fn: Callable[[SmartMeter], float]
value_fn: Callable[[SmartMeter], float | datetime | None]
ENTITY_DESCRIPTIONS: tuple[AnglianWaterSensorEntityDescription, ...] = (
@@ -76,6 +78,13 @@ ENTITY_DESCRIPTIONS: tuple[AnglianWaterSensorEntityDescription, ...] = (
translation_key=AnglianWaterSensor.YESTERDAY_SEWERAGE_COST,
entity_category=EntityCategory.DIAGNOSTIC,
),
AnglianWaterSensorEntityDescription(
key=AnglianWaterSensor.LAST_UPDATED,
device_class=SensorDeviceClass.TIMESTAMP,
value_fn=lambda entity: entity.last_updated,
translation_key=AnglianWaterSensor.LAST_UPDATED,
entity_category=EntityCategory.DIAGNOSTIC,
),
)
@@ -112,6 +121,6 @@ class AnglianWaterSensorEntity(AnglianWaterEntity, SensorEntity):
self.entity_description = description
@property
def native_value(self) -> float | None:
def native_value(self) -> float | datetime | None:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.smart_meter)
@@ -34,6 +34,9 @@
},
"entity": {
"sensor": {
"last_updated": {
"name": "Last meter reading processed"
},
"latest_reading": {
"name": "Latest reading"
},
+5 -7
View File
@@ -400,8 +400,8 @@ def _convert_content(
# If there is only one text block, simplify the content to a string
messages[-1]["content"] = messages[-1]["content"][0]["text"]
else:
# Note: We don't pass SystemContent here as its passed to the API as the prompt
raise TypeError(f"Unexpected content type: {type(content)}")
# Note: We don't pass SystemContent here as it's passed to the API as the prompt
raise HomeAssistantError("Unexpected content type in chat log")
return messages, container_id
@@ -442,8 +442,8 @@ 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:
raise TypeError("Expected a stream of messages")
if stream is None or not hasattr(stream, "__aiter__"):
raise HomeAssistantError("Expected a stream of messages")
current_tool_block: ToolUseBlockParam | ServerToolUseBlockParam | None = None
current_tool_args: str
@@ -456,8 +456,6 @@ async def _transform_stream( # noqa: C901 - This is complex, but better to have
LOGGER.debug("Received response: %s", response)
if isinstance(response, RawMessageStartEvent):
if response.message.role != "assistant":
raise ValueError("Unexpected message role")
input_usage = response.message.usage
first_block = True
elif isinstance(response, RawContentBlockStartEvent):
@@ -666,7 +664,7 @@ class AnthropicBaseLLMEntity(Entity):
system = chat_log.content[0]
if not isinstance(system, conversation.SystemContent):
raise TypeError("First message must be a system message")
raise HomeAssistantError("First message must be a system message")
# System prompt with caching enabled
system_prompt: list[TextBlockParam] = [
@@ -31,10 +31,7 @@ rules:
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: todo
comment: |
Reevaluate exceptions for entity services.
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
@@ -117,6 +117,7 @@ class SharpAquosTVDevice(MediaPlayerEntity):
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.PLAY
)
_attr_volume_step = 2 / 60
def __init__(
self, name: str, remote: sharp_aquos_rc.TV, power_on_enabled: bool = False
@@ -161,22 +162,6 @@ class SharpAquosTVDevice(MediaPlayerEntity):
"""Turn off tvplayer."""
self._remote.power(0)
@_retry
def volume_up(self) -> None:
"""Volume up the media player."""
if self.volume_level is None:
_LOGGER.debug("Unknown volume in volume_up")
return
self._remote.volume(int(self.volume_level * 60) + 2)
@_retry
def volume_down(self) -> None:
"""Volume down media player."""
if self.volume_level is None:
_LOGGER.debug("Unknown volume in volume_down")
return
self._remote.volume(int(self.volume_level * 60) - 2)
@_retry
def set_volume_level(self, volume: float) -> None:
"""Set Volume media player."""
+32 -18
View File
@@ -8,46 +8,55 @@ from typing import Any
from arcam.fmj import ConnectionFailed
from arcam.fmj.client import Client
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_send
from .const import (
DEFAULT_SCAN_INTERVAL,
SIGNAL_CLIENT_DATA,
SIGNAL_CLIENT_STARTED,
SIGNAL_CLIENT_STOPPED,
)
type ArcamFmjConfigEntry = ConfigEntry[Client]
from .const import DEFAULT_SCAN_INTERVAL
from .coordinator import ArcamFmjConfigEntry, ArcamFmjCoordinator, ArcamFmjRuntimeData
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.MEDIA_PLAYER]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.MEDIA_PLAYER, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ArcamFmjConfigEntry) -> bool:
"""Set up config entry."""
entry.runtime_data = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
client = Client(entry.data[CONF_HOST], entry.data[CONF_PORT])
coordinators: dict[int, ArcamFmjCoordinator] = {}
for zone in (1, 2):
coordinator = ArcamFmjCoordinator(hass, entry, client, zone)
coordinators[zone] = coordinator
entry.runtime_data = ArcamFmjRuntimeData(client, coordinators)
entry.async_create_background_task(
hass, _run_client(hass, entry.runtime_data, DEFAULT_SCAN_INTERVAL), "arcam_fmj"
hass,
_run_client(hass, entry.runtime_data, DEFAULT_SCAN_INTERVAL),
"arcam_fmj",
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ArcamFmjConfigEntry) -> bool:
"""Cleanup before removing config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def _run_client(hass: HomeAssistant, client: Client, interval: float) -> None:
async def _run_client(
hass: HomeAssistant,
runtime_data: ArcamFmjRuntimeData,
interval: float,
) -> None:
client = runtime_data.client
coordinators = runtime_data.coordinators
def _listen(_: Any) -> None:
async_dispatcher_send(hass, SIGNAL_CLIENT_DATA, client.host)
for coordinator in coordinators.values():
coordinator.async_notify_data_updated()
while True:
try:
@@ -55,16 +64,21 @@ async def _run_client(hass: HomeAssistant, client: Client, interval: float) -> N
await client.start()
_LOGGER.debug("Client connected %s", client.host)
async_dispatcher_send(hass, SIGNAL_CLIENT_STARTED, client.host)
try:
for coordinator in coordinators.values():
await coordinator.state.start()
with client.listen(_listen):
for coordinator in coordinators.values():
coordinator.async_notify_connected()
await client.process()
finally:
await client.stop()
_LOGGER.debug("Client disconnected %s", client.host)
async_dispatcher_send(hass, SIGNAL_CLIENT_STOPPED, client.host)
for coordinator in coordinators.values():
coordinator.async_notify_disconnected()
except ConnectionFailed:
await asyncio.sleep(interval)
@@ -0,0 +1,68 @@
"""Arcam binary sensors for incoming stream info."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from arcam.fmj.state import State
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ArcamFmjConfigEntry
from .entity import ArcamFmjEntity
@dataclass(frozen=True, kw_only=True)
class ArcamFmjBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes an Arcam FMJ binary sensor entity."""
value_fn: Callable[[State], bool | None]
BINARY_SENSORS: tuple[ArcamFmjBinarySensorEntityDescription, ...] = (
ArcamFmjBinarySensorEntityDescription(
key="incoming_video_interlaced",
translation_key="incoming_video_interlaced",
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=lambda state: (
vp.interlaced
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ArcamFmjConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Arcam FMJ binary sensors from a config entry."""
coordinators = config_entry.runtime_data.coordinators
entities: list[ArcamFmjBinarySensorEntity] = []
for coordinator in coordinators.values():
entities.extend(
ArcamFmjBinarySensorEntity(coordinator, description)
for description in BINARY_SENSORS
)
async_add_entities(entities)
class ArcamFmjBinarySensorEntity(ArcamFmjEntity, BinarySensorEntity):
"""Representation of an Arcam FMJ binary sensor."""
entity_description: ArcamFmjBinarySensorEntityDescription
@property
def is_on(self) -> bool | None:
"""Return the binary sensor value."""
return self.entity_description.value_fn(self.coordinator.state)
@@ -2,10 +2,6 @@
DOMAIN = "arcam_fmj"
SIGNAL_CLIENT_STARTED = "arcam.client_started"
SIGNAL_CLIENT_STOPPED = "arcam.client_stopped"
SIGNAL_CLIENT_DATA = "arcam.client_data"
EVENT_TURN_ON = "arcam_fmj.turn_on"
DEFAULT_PORT = 50000
@@ -0,0 +1,97 @@
"""Coordinator for Arcam FMJ integration."""
from __future__ import annotations
from dataclasses import dataclass
import logging
from arcam.fmj import ConnectionFailed
from arcam.fmj.client import Client
from arcam.fmj.state import State
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@dataclass
class ArcamFmjRuntimeData:
"""Runtime data for Arcam FMJ integration."""
client: Client
coordinators: dict[int, ArcamFmjCoordinator]
type ArcamFmjConfigEntry = ConfigEntry[ArcamFmjRuntimeData]
class ArcamFmjCoordinator(DataUpdateCoordinator[None]):
"""Coordinator for a single Arcam FMJ zone."""
config_entry: ArcamFmjConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ArcamFmjConfigEntry,
client: Client,
zone: int,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=f"Arcam FMJ zone {zone}",
)
self.client = client
self.state = State(client, zone)
self.last_update_success = False
name = config_entry.title
unique_id = config_entry.unique_id or config_entry.entry_id
unique_id_device = unique_id
if zone != 1:
unique_id_device += f"-{zone}"
name += f" Zone {zone}"
self.device_info = DeviceInfo(
identifiers={(DOMAIN, unique_id_device)},
manufacturer="Arcam",
model="Arcam FMJ AVR",
name=name,
)
self.zone_unique_id = f"{unique_id}-{zone}"
if zone != 1:
self.device_info["via_device"] = (DOMAIN, unique_id)
async def _async_update_data(self) -> None:
"""Fetch data for manual refresh."""
try:
await self.state.update()
except ConnectionFailed as err:
raise UpdateFailed(
f"Connection failed during update for zone {self.state.zn}"
) from err
@callback
def async_notify_data_updated(self) -> None:
"""Notify that new data has been received from the device."""
self.async_set_updated_data(None)
@callback
def async_notify_connected(self) -> None:
"""Handle client connected."""
self.hass.async_create_task(self.async_refresh())
@callback
def async_notify_disconnected(self) -> None:
"""Handle client disconnected."""
self.last_update_success = False
self.async_update_listeners()
@@ -0,0 +1,28 @@
"""Base entity for Arcam FMJ integration."""
from __future__ import annotations
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .coordinator import ArcamFmjCoordinator
class ArcamFmjEntity(CoordinatorEntity[ArcamFmjCoordinator]):
"""Base entity for Arcam FMJ."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: ArcamFmjCoordinator,
description: EntityDescription | None = None,
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._attr_device_info = coordinator.device_info
self._attr_entity_registry_enabled_default = coordinator.state.zn == 1
self._attr_unique_id = coordinator.zone_unique_id
if description is not None:
self._attr_unique_id = f"{self._attr_unique_id}-{description.key}"
self.entity_description = description
@@ -0,0 +1,35 @@
{
"entity": {
"binary_sensor": {
"incoming_video_interlaced": {
"default": "mdi:reorder-horizontal"
}
},
"sensor": {
"incoming_audio_config": {
"default": "mdi:surround-sound"
},
"incoming_audio_format": {
"default": "mdi:dolby"
},
"incoming_audio_sample_rate": {
"default": "mdi:waveform"
},
"incoming_video_aspect_ratio": {
"default": "mdi:aspect-ratio"
},
"incoming_video_colorspace": {
"default": "mdi:palette"
},
"incoming_video_horizontal_resolution": {
"default": "mdi:arrow-expand-horizontal"
},
"incoming_video_refresh_rate": {
"default": "mdi:animation"
},
"incoming_video_vertical_resolution": {
"default": "mdi:arrow-expand-vertical"
}
}
}
}
@@ -8,7 +8,6 @@ import logging
from typing import Any
from arcam.fmj import ConnectionFailed, SourceCodes
from arcam.fmj.state import State
from homeassistant.components.media_player import (
BrowseError,
@@ -20,20 +19,13 @@ from homeassistant.components.media_player import (
MediaType,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import ArcamFmjConfigEntry
from .const import (
DOMAIN,
EVENT_TURN_ON,
SIGNAL_CLIENT_DATA,
SIGNAL_CLIENT_STARTED,
SIGNAL_CLIENT_STOPPED,
)
from .const import EVENT_TURN_ON
from .coordinator import ArcamFmjConfigEntry, ArcamFmjCoordinator
from .entity import ArcamFmjEntity
_LOGGER = logging.getLogger(__name__)
@@ -44,19 +36,10 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the configuration entry."""
client = config_entry.runtime_data
coordinators = config_entry.runtime_data.coordinators
async_add_entities(
[
ArcamFmj(
config_entry.title,
State(client, zone),
config_entry.unique_id or config_entry.entry_id,
)
for zone in (1, 2)
],
True,
[ArcamFmj(coordinators[zone]) for zone in (1, 2)],
)
@@ -77,21 +60,13 @@ def convert_exception[**_P, _R](
return _convert_exception
class ArcamFmj(MediaPlayerEntity):
class ArcamFmj(ArcamFmjEntity, MediaPlayerEntity):
"""Representation of a media device."""
_attr_should_poll = False
_attr_has_entity_name = True
def __init__(
self,
device_name: str,
state: State,
uuid: str,
) -> None:
def __init__(self, coordinator: ArcamFmjCoordinator) -> None:
"""Initialize device."""
self._state = state
self._attr_name = f"Zone {state.zn}"
super().__init__(coordinator)
self._state = coordinator.state
self._attr_supported_features = (
MediaPlayerEntityFeature.SELECT_SOURCE
| MediaPlayerEntityFeature.PLAY_MEDIA
@@ -102,18 +77,8 @@ class ArcamFmj(MediaPlayerEntity):
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.TURN_ON
)
if state.zn == 1:
if self._state.zn == 1:
self._attr_supported_features |= MediaPlayerEntityFeature.SELECT_SOUND_MODE
self._attr_unique_id = f"{uuid}-{state.zn}"
self._attr_entity_registry_enabled_default = state.zn == 1
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, uuid),
},
manufacturer="Arcam",
model="Arcam FMJ AVR",
name=device_name,
)
@property
def state(self) -> MediaPlayerState:
@@ -122,49 +87,6 @@ class ArcamFmj(MediaPlayerEntity):
return MediaPlayerState.ON
return MediaPlayerState.OFF
async def async_added_to_hass(self) -> None:
"""Once registered, add listener for events."""
await self._state.start()
try:
await self._state.update()
except ConnectionFailed as connection:
_LOGGER.debug("Connection lost during addition: %s", connection)
@callback
def _data(host: str) -> None:
if host == self._state.client.host:
self.async_write_ha_state()
@callback
def _started(host: str) -> None:
if host == self._state.client.host:
self.async_schedule_update_ha_state(force_refresh=True)
@callback
def _stopped(host: str) -> None:
if host == self._state.client.host:
self.async_schedule_update_ha_state(force_refresh=True)
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_CLIENT_DATA, _data)
)
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_CLIENT_STARTED, _started)
)
self.async_on_remove(
async_dispatcher_connect(self.hass, SIGNAL_CLIENT_STOPPED, _stopped)
)
async def async_update(self) -> None:
"""Force update of state."""
_LOGGER.debug("Update state %s", self.name)
try:
await self._state.update()
except ConnectionFailed as connection:
_LOGGER.debug("Connection lost during update: %s", connection)
@convert_exception
async def async_mute_volume(self, mute: bool) -> None:
"""Send mute command."""
@@ -0,0 +1,162 @@
"""Arcam sensors for incoming stream info."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from arcam.fmj import IncomingVideoAspectRatio, IncomingVideoColorspace
from arcam.fmj.state import IncomingAudioConfig, IncomingAudioFormat, State
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import EntityCategory, UnitOfFrequency
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import ArcamFmjConfigEntry
from .entity import ArcamFmjEntity
@dataclass(frozen=True, kw_only=True)
class ArcamFmjSensorEntityDescription(SensorEntityDescription):
"""Describes an Arcam FMJ sensor entity."""
value_fn: Callable[[State], int | float | str | None]
SENSORS: tuple[ArcamFmjSensorEntityDescription, ...] = (
ArcamFmjSensorEntityDescription(
key="incoming_video_horizontal_resolution",
translation_key="incoming_video_horizontal_resolution",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="px",
suggested_display_precision=0,
value_fn=lambda state: (
vp.horizontal_resolution
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
ArcamFmjSensorEntityDescription(
key="incoming_video_vertical_resolution",
translation_key="incoming_video_vertical_resolution",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement="px",
suggested_display_precision=0,
value_fn=lambda state: (
vp.vertical_resolution
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
ArcamFmjSensorEntityDescription(
key="incoming_video_refresh_rate",
translation_key="incoming_video_refresh_rate",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.FREQUENCY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
suggested_display_precision=0,
value_fn=lambda state: (
vp.refresh_rate
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
ArcamFmjSensorEntityDescription(
key="incoming_video_aspect_ratio",
translation_key="incoming_video_aspect_ratio",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingVideoAspectRatio],
value_fn=lambda state: (
vp.aspect_ratio.name.lower()
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
ArcamFmjSensorEntityDescription(
key="incoming_video_colorspace",
translation_key="incoming_video_colorspace",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
options=[member.name.lower() for member in IncomingVideoColorspace],
value_fn=lambda state: (
vp.colorspace.name.lower()
if (vp := state.get_incoming_video_parameters()) is not None
else None
),
),
ArcamFmjSensorEntityDescription(
key="incoming_audio_format",
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
),
),
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
),
),
ArcamFmjSensorEntityDescription(
key="incoming_audio_sample_rate",
translation_key="incoming_audio_sample_rate",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.FREQUENCY,
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
suggested_display_precision=0,
value_fn=lambda state: (
None
if (sample_rate := state.get_incoming_audio_sample_rate()) == 0
else sample_rate
),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ArcamFmjConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Arcam FMJ sensors from a config entry."""
coordinators = config_entry.runtime_data.coordinators
entities: list[ArcamFmjSensorEntity] = []
for coordinator in coordinators.values():
entities.extend(
ArcamFmjSensorEntity(coordinator, description) for description in SENSORS
)
async_add_entities(entities)
class ArcamFmjSensorEntity(ArcamFmjEntity, SensorEntity):
"""Representation of an Arcam FMJ sensor."""
entity_description: ArcamFmjSensorEntityDescription
@property
def native_value(self) -> int | float | str | None:
"""Return the sensor value."""
return self.entity_description.value_fn(self.coordinator.state)
@@ -23,5 +23,121 @@
"trigger_type": {
"turn_on": "{entity_name} was requested to turn on"
}
},
"entity": {
"binary_sensor": {
"incoming_video_interlaced": {
"name": "Incoming video interlaced"
}
},
"sensor": {
"incoming_audio_config": {
"name": "Incoming audio configuration",
"state": {
"auro_10_1": "Auro 10.1",
"auro_11_1": "Auro 11.1",
"auro_13_1": "Auro 13.1",
"auro_2_2_2": "Auro 2.2.2",
"auro_5_0": "Auro 5.0",
"auro_5_1": "Auro 5.1",
"auro_8_0": "Auro 8.0",
"auro_9_1": "Auro 9.1",
"auro_quad": "Auro quad",
"dual_mono": "Dual mono",
"dual_mono_lfe": "Dual mono + LFE",
"mono": "Mono",
"mono_lfe": "Mono + LFE",
"stereo_center": "Stereo center",
"stereo_center_lfe": "Stereo center + LFE",
"stereo_center_surr_lr": "Stereo center surround L/R",
"stereo_center_surr_lr_back_lr": "Stereo center surround L/R back L/R",
"stereo_center_surr_lr_back_lr_lfe": "Stereo center surround L/R back L/R + LFE",
"stereo_center_surr_lr_back_matrix": "Stereo center surround L/R back matrix",
"stereo_center_surr_lr_back_matrix_lfe": "Stereo center surround L/R back matrix + LFE",
"stereo_center_surr_lr_back_mono": "Stereo center surround L/R back mono",
"stereo_center_surr_lr_back_mono_lfe": "Stereo center surround L/R back mono + LFE",
"stereo_center_surr_lr_lfe": "Stereo center surround L/R + LFE",
"stereo_center_surr_mono": "Stereo center surround mono",
"stereo_center_surr_mono_lfe": "Stereo center surround mono + LFE",
"stereo_downmix": "Stereo downmix",
"stereo_downmix_lfe": "Stereo downmix + LFE",
"stereo_lfe": "Stereo + LFE",
"stereo_only": "Stereo only",
"stereo_only_lo_ro": "Stereo only Lo/Ro",
"stereo_only_lo_ro_lfe": "Stereo only Lo/Ro + LFE",
"stereo_surr_lr": "Stereo surround L/R",
"stereo_surr_lr_back_lr": "Stereo surround L/R back L/R",
"stereo_surr_lr_back_lr_lfe": "Stereo surround L/R back L/R + LFE",
"stereo_surr_lr_back_matrix": "Stereo surround L/R back matrix",
"stereo_surr_lr_back_matrix_lfe": "Stereo surround L/R back matrix + LFE",
"stereo_surr_lr_back_mono": "Stereo surround L/R back mono",
"stereo_surr_lr_back_mono_lfe": "Stereo surround L/R back mono + LFE",
"stereo_surr_lr_lfe": "Stereo surround L/R + LFE",
"stereo_surr_mono": "Stereo surround mono",
"stereo_surr_mono_lfe": "Stereo surround mono + LFE",
"undetected": "Undetected",
"unknown": "Unknown"
}
},
"incoming_audio_format": {
"name": "Incoming audio format",
"state": {
"analogue_direct": "Analogue direct",
"auro_3d": "Auro-3D",
"dolby_atmos": "Dolby Atmos",
"dolby_digital": "Dolby Digital",
"dolby_digital_ex": "Dolby Digital EX",
"dolby_digital_plus": "Dolby Digital Plus",
"dolby_digital_surround": "Dolby Digital Surround",
"dolby_digital_true_hd": "Dolby TrueHD",
"dts": "DTS",
"dts_96_24": "DTS 96/24",
"dts_core": "DTS Core",
"dts_es_discrete": "DTS-ES Discrete",
"dts_es_discrete_96_24": "DTS-ES Discrete 96/24",
"dts_es_matrix": "DTS-ES Matrix",
"dts_es_matrix_96_24": "DTS-ES Matrix 96/24",
"dts_hd_high_res_audio": "DTS-HD High Resolution Audio",
"dts_hd_master_audio": "DTS-HD Master Audio",
"dts_low_bit_rate": "DTS Low Bit Rate",
"dts_x": "DTS:X",
"imax_enhanced": "IMAX Enhanced",
"pcm": "PCM",
"pcm_zero": "PCM zero",
"undetected": "Undetected",
"unsupported": "Unsupported"
}
},
"incoming_audio_sample_rate": {
"name": "Incoming audio sample rate"
},
"incoming_video_aspect_ratio": {
"name": "Incoming video aspect ratio",
"state": {
"aspect_16_9": "16:9",
"aspect_4_3": "4:3",
"undefined": "Undefined"
}
},
"incoming_video_colorspace": {
"name": "Incoming video colorspace",
"state": {
"dolby_vision": "Dolby Vision",
"hdr10": "HDR10",
"hdr10_plus": "HDR10+",
"hlg": "HLG",
"normal": "Normal"
}
},
"incoming_video_horizontal_resolution": {
"name": "Incoming video horizontal resolution"
},
"incoming_video_refresh_rate": {
"name": "Incoming video refresh rate"
},
"incoming_video_vertical_resolution": {
"name": "Incoming video vertical resolution"
}
}
}
}
@@ -78,19 +78,13 @@ class AssistPipelineSelect(SelectEntity, restore_state.RestoreEntity):
index: int = 0,
) -> None:
"""Initialize a pipeline selector."""
if index < 1:
# Keep compatibility
key_suffix = ""
placeholder = ""
else:
key_suffix = f"_{index + 1}"
placeholder = f" {index + 1}"
self.entity_description = replace(
self.entity_description,
key=f"pipeline{key_suffix}",
translation_placeholders={"index": placeholder},
)
if index >= 1:
self.entity_description = replace(
self.entity_description,
key=f"pipeline_{index + 1}",
translation_key="pipeline_n",
translation_placeholders={"index": str(index + 1)},
)
self._domain = domain
self._unique_id_prefix = unique_id_prefix
@@ -7,11 +7,17 @@
},
"select": {
"pipeline": {
"name": "Assistant{index}",
"name": "Assistant",
"state": {
"preferred": "Preferred"
}
},
"pipeline_n": {
"name": "Assistant {index}",
"state": {
"preferred": "[%key:component::assist_pipeline::entity::select::pipeline::state::preferred%]"
}
},
"vad_sensitivity": {
"name": "Finished speaking detection",
"state": {
@@ -30,5 +30,5 @@
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.2.8"]
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.3.0"]
}
@@ -61,7 +61,13 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]):
frequency = self.client.measure(4)
i_leak_dcdc = self.client.measure(6)
i_leak_inverter = self.client.measure(7)
power_in_1 = self.client.measure(8)
power_in_2 = self.client.measure(9)
temperature_c = self.client.measure(21)
voltage_in_1 = self.client.measure(23)
current_in_1 = self.client.measure(25)
voltage_in_2 = self.client.measure(26)
current_in_2 = self.client.measure(27)
r_iso = self.client.measure(30)
energy_wh = self.client.cumulated_energy(5)
[alarm, *_] = self.client.alarms()
@@ -87,7 +93,13 @@ class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]):
data["grid_frequency"] = round(frequency, 1)
data["i_leak_dcdc"] = i_leak_dcdc
data["i_leak_inverter"] = i_leak_inverter
data["power_in_1"] = round(power_in_1, 1)
data["power_in_2"] = round(power_in_2, 1)
data["temp"] = round(temperature_c, 1)
data["voltage_in_1"] = round(voltage_in_1, 1)
data["current_in_1"] = round(current_in_1, 1)
data["voltage_in_2"] = round(voltage_in_2, 1)
data["current_in_2"] = round(current_in_2, 1)
data["r_iso"] = r_iso
data["totalenergy"] = round(energy_wh / 1000, 2)
data["alarm"] = alarm
@@ -68,6 +68,7 @@ SENSOR_TYPES = [
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfFrequency.HERTZ,
state_class=SensorStateClass.MEASUREMENT,
translation_key="grid_frequency",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@@ -88,6 +89,60 @@ SENSOR_TYPES = [
translation_key="i_leak_inverter",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="power_in_1",
device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
translation_key="power_in_1",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="power_in_2",
device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfPower.WATT,
state_class=SensorStateClass.MEASUREMENT,
translation_key="power_in_2",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="voltage_in_1",
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
translation_key="voltage_in_1",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="current_in_1",
device_class=SensorDeviceClass.CURRENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="current_in_1",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="voltage_in_2",
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
state_class=SensorStateClass.MEASUREMENT,
translation_key="voltage_in_2",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="current_in_2",
device_class=SensorDeviceClass.CURRENT,
entity_category=EntityCategory.DIAGNOSTIC,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
state_class=SensorStateClass.MEASUREMENT,
translation_key="current_in_2",
entity_registry_enabled_default=False,
),
SensorEntityDescription(
key="alarm",
device_class=SensorDeviceClass.ENUM,
@@ -24,9 +24,18 @@
"alarm": {
"name": "Alarm status"
},
"current_in_1": {
"name": "String 1 current"
},
"current_in_2": {
"name": "String 2 current"
},
"grid_current": {
"name": "Grid current"
},
"grid_frequency": {
"name": "Grid frequency"
},
"grid_voltage": {
"name": "Grid voltage"
},
@@ -36,6 +45,12 @@
"i_leak_inverter": {
"name": "Inverter leak current"
},
"power_in_1": {
"name": "String 1 power"
},
"power_in_2": {
"name": "String 2 power"
},
"power_output": {
"name": "Power output"
},
@@ -44,6 +59,12 @@
},
"total_energy": {
"name": "Total energy"
},
"voltage_in_1": {
"name": "String 1 voltage"
},
"voltage_in_2": {
"name": "String 2 voltage"
}
}
}
@@ -121,6 +121,7 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
"alarm_control_panel",
"assist_satellite",
"climate",
"cover",
"device_tracker",
"fan",
"humidifier",
@@ -137,24 +138,33 @@ _EXPERIMENTAL_CONDITION_PLATFORMS = {
_EXPERIMENTAL_TRIGGER_PLATFORMS = {
"alarm_control_panel",
"assist_satellite",
"binary_sensor",
"button",
"climate",
"cover",
"device_tracker",
"door",
"fan",
"garage_door",
"gate",
"humidifier",
"humidity",
"input_boolean",
"lawn_mower",
"light",
"lock",
"media_player",
"motion",
"occupancy",
"person",
"remote",
"scene",
"schedule",
"siren",
"switch",
"text",
"update",
"vacuum",
"window",
}
@@ -0,0 +1,53 @@
"""The Autoskope integration."""
from __future__ import annotations
import aiohttp
from autoskope_client.api import AutoskopeApi
from autoskope_client.models import CannotConnect, InvalidAuth
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DEFAULT_HOST
from .coordinator import AutoskopeConfigEntry, AutoskopeDataUpdateCoordinator
PLATFORMS: list[Platform] = [Platform.DEVICE_TRACKER]
async def async_setup_entry(hass: HomeAssistant, entry: AutoskopeConfigEntry) -> bool:
"""Set up Autoskope from a config entry."""
session = async_create_clientsession(hass, cookie_jar=aiohttp.CookieJar())
api = AutoskopeApi(
host=entry.data.get(CONF_HOST, DEFAULT_HOST),
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=session,
)
try:
await api.connect()
except InvalidAuth as err:
# Raise ConfigEntryError until reauth flow is implemented (then ConfigEntryAuthFailed)
raise ConfigEntryError(
"Authentication failed, please check credentials"
) from err
except CannotConnect as err:
raise ConfigEntryNotReady("Could not connect to Autoskope API") from err
coordinator = AutoskopeDataUpdateCoordinator(hass, api, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: AutoskopeConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -0,0 +1,89 @@
"""Config flow for the Autoskope integration."""
from __future__ import annotations
from typing import Any
from autoskope_client.api import AutoskopeApi
from autoskope_client.models import CannotConnect, InvalidAuth
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.data_entry_flow import section
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from .const import DEFAULT_HOST, DOMAIN, SECTION_ADVANCED_SETTINGS
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Schema(
{
vol.Required(CONF_HOST, default=DEFAULT_HOST): TextSelector(
TextSelectorConfig(type=TextSelectorType.URL)
),
}
),
{"collapsed": True},
),
}
)
class AutoskopeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Autoskope."""
VERSION = 1
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
if user_input is not None:
username = user_input[CONF_USERNAME].lower()
host = user_input[SECTION_ADVANCED_SETTINGS][CONF_HOST].lower()
try:
cv.url(host)
except vol.Invalid:
errors["base"] = "invalid_url"
if not errors:
await self.async_set_unique_id(f"{username}@{host}")
self._abort_if_unique_id_configured()
try:
async with AutoskopeApi(
host=host,
username=username,
password=user_input[CONF_PASSWORD],
):
pass
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
else:
return self.async_create_entry(
title=f"Autoskope ({username})",
data={
CONF_USERNAME: username,
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_HOST: host,
},
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
@@ -0,0 +1,9 @@
"""Constants for the Autoskope integration."""
from datetime import timedelta
DOMAIN = "autoskope"
DEFAULT_HOST = "https://portal.autoskope.de"
SECTION_ADVANCED_SETTINGS = "advanced_settings"
UPDATE_INTERVAL = timedelta(seconds=60)
@@ -0,0 +1,60 @@
"""Data update coordinator for the Autoskope integration."""
from __future__ import annotations
import logging
from autoskope_client.api import AutoskopeApi
from autoskope_client.models import CannotConnect, InvalidAuth, Vehicle
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, UPDATE_INTERVAL
_LOGGER = logging.getLogger(__name__)
type AutoskopeConfigEntry = ConfigEntry[AutoskopeDataUpdateCoordinator]
class AutoskopeDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Vehicle]]):
"""Class to manage fetching Autoskope data."""
config_entry: AutoskopeConfigEntry
def __init__(
self, hass: HomeAssistant, api: AutoskopeApi, entry: AutoskopeConfigEntry
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=UPDATE_INTERVAL,
config_entry=entry,
)
self.api = api
async def _async_update_data(self) -> dict[str, Vehicle]:
"""Fetch data from API endpoint."""
try:
vehicles = await self.api.get_vehicles()
return {vehicle.id: vehicle for vehicle in vehicles}
except InvalidAuth:
# Attempt to re-authenticate using stored credentials
try:
await self.api.authenticate()
# Retry the request after successful re-authentication
vehicles = await self.api.get_vehicles()
return {vehicle.id: vehicle for vehicle in vehicles}
except InvalidAuth as reauth_err:
raise ConfigEntryAuthFailed(
f"Authentication failed: {reauth_err}"
) from reauth_err
except CannotConnect as err:
raise UpdateFailed(f"Error communicating with API: {err}") from err
@@ -0,0 +1,145 @@
"""Support for Autoskope device tracking."""
from __future__ import annotations
from autoskope_client.constants import MANUFACTURER
from autoskope_client.models import Vehicle
from homeassistant.components.device_tracker import SourceType, TrackerEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import AutoskopeConfigEntry, AutoskopeDataUpdateCoordinator
PARALLEL_UPDATES = 0
async def async_setup_entry(
hass: HomeAssistant,
entry: AutoskopeConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Autoskope device tracker entities."""
coordinator: AutoskopeDataUpdateCoordinator = entry.runtime_data
tracked_vehicles: set[str] = set()
@callback
def update_entities() -> None:
"""Update entities based on coordinator data."""
current_vehicles = set(coordinator.data.keys())
vehicles_to_add = current_vehicles - tracked_vehicles
if vehicles_to_add:
new_entities = [
AutoskopeDeviceTracker(coordinator, vehicle_id)
for vehicle_id in vehicles_to_add
]
tracked_vehicles.update(vehicles_to_add)
async_add_entities(new_entities)
entry.async_on_unload(coordinator.async_add_listener(update_entities))
update_entities()
class AutoskopeDeviceTracker(
CoordinatorEntity[AutoskopeDataUpdateCoordinator], TrackerEntity
):
"""Representation of an Autoskope tracked device."""
_attr_has_entity_name = True
_attr_name: str | None = None
def __init__(
self, coordinator: AutoskopeDataUpdateCoordinator, vehicle_id: str
) -> None:
"""Initialize the TrackerEntity."""
super().__init__(coordinator)
self._vehicle_id = vehicle_id
self._attr_unique_id = vehicle_id
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if (
self._vehicle_id in self.coordinator.data
and (device_entry := self.device_entry) is not None
and device_entry.name != self._vehicle_data.name
):
device_registry = dr.async_get(self.hass)
device_registry.async_update_device(
device_entry.id, name=self._vehicle_data.name
)
super()._handle_coordinator_update()
@property
def device_info(self) -> DeviceInfo:
"""Return device info for the vehicle."""
vehicle = self.coordinator.data[self._vehicle_id]
return DeviceInfo(
identifiers={(DOMAIN, str(vehicle.id))},
name=vehicle.name,
manufacturer=MANUFACTURER,
model=vehicle.model,
serial_number=vehicle.imei,
)
@property
def available(self) -> bool:
"""Return if entity is available."""
return (
super().available
and self.coordinator.data is not None
and self._vehicle_id in self.coordinator.data
)
@property
def _vehicle_data(self) -> Vehicle:
"""Return the vehicle data for the current entity."""
return self.coordinator.data[self._vehicle_id]
@property
def latitude(self) -> float | None:
"""Return latitude value of the device."""
if (vehicle := self._vehicle_data) and vehicle.position:
return float(vehicle.position.latitude)
return None
@property
def longitude(self) -> float | None:
"""Return longitude value of the device."""
if (vehicle := self._vehicle_data) and vehicle.position:
return float(vehicle.position.longitude)
return None
@property
def source_type(self) -> SourceType:
"""Return the source type of the device."""
return SourceType.GPS
@property
def location_accuracy(self) -> float:
"""Return the location accuracy of the device in meters."""
if (vehicle := self._vehicle_data) and vehicle.gps_quality:
if vehicle.gps_quality > 0:
# HDOP to estimated accuracy in meters
# HDOP of 1-2 = good (5-10m), 2-5 = moderate (10-25m), >5 = poor (>25m)
return float(max(5, int(vehicle.gps_quality * 5.0)))
return 0.0
@property
def icon(self) -> str:
"""Return the icon based on the vehicle's activity."""
if self._vehicle_id not in self.coordinator.data:
return "mdi:car-clock"
vehicle = self._vehicle_data
if vehicle.position:
if vehicle.position.park_mode:
return "mdi:car-brake-parking"
if vehicle.position.speed > 5: # Moving threshold: 5 km/h
return "mdi:car-arrow-right"
return "mdi:car"
return "mdi:car-clock"
@@ -0,0 +1,11 @@
{
"domain": "autoskope",
"name": "Autoskope",
"codeowners": ["@mcisk"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/autoskope",
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["autoskope_client==1.4.1"]
}
@@ -0,0 +1,88 @@
# + in comment indicates requirement for quality scale
# - in comment indicates issue to be fixed, not impacting quality scale
rules:
# Bronze
action-setup:
status: exempt
comment: |
Integration does not provide custom services.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
Integration does not provide custom services.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup: done
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
Integration does not provide custom services.
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: todo
parallel-updates: done
reauthentication-flow:
status: todo
comment: |
Reauthentication flow removed for initial PR, will be added in follow-up.
test-coverage: done
# Gold
devices: done
diagnostics: todo
discovery-update-info:
status: exempt
comment: |
Integration does not use discovery. Autoskope devices use NB-IoT/LTE-M (via IoT SIMs) and LoRaWAN.
discovery:
status: exempt
comment: |
Integration does not use discovery. Autoskope devices use NB-IoT/LTE-M (via IoT SIMs) and LoRaWAN.
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: done
entity-category: done
entity-device-class: done
entity-disabled-by-default:
status: exempt
comment: |
Only one entity type (device_tracker) is created, making this not applicable.
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: todo
comment: |
Reconfiguration flow removed for initial PR, will be added in follow-up.
repair-issues: todo
stale-devices: done
# Platinum
async-dependency: done
inject-websession: done
strict-typing:
status: todo
comment: |
Integration needs to be added to .strict-typing file for full compliance.
@@ -0,0 +1,52 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_url": "Invalid URL",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"user": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"data_description": {
"password": "The password for your Autoskope account.",
"username": "The username for your Autoskope account."
},
"description": "Enter your Autoskope credentials.",
"sections": {
"advanced_settings": {
"data": {
"host": "API endpoint"
},
"data_description": {
"host": "The URL of your Autoskope API endpoint. Only change this if you use a white-label portal."
},
"name": "Advanced settings"
}
},
"title": "Connect to Autoskope"
}
}
},
"issues": {
"cannot_connect": {
"description": "Home Assistant could not connect to the Autoskope API at {host}. Please check the connection details and ensure the API endpoint is reachable.\n\nError: {error}",
"title": "Failed to connect to Autoskope"
},
"invalid_auth": {
"description": "Authentication with Autoskope failed for user {username}. Please re-authenticate the integration with the correct password.",
"title": "Invalid Autoskope authentication"
},
"low_battery": {
"description": "The battery voltage for vehicle {vehicle_name} ({vehicle_id}) is low ({value}V). Consider checking or replacing the battery.",
"title": "Low vehicle battery ({vehicle_name})"
}
}
}
+29 -12
View File
@@ -14,12 +14,13 @@ from homeassistant.components.backup import (
BackupAgent,
BackupAgentError,
BackupNotFound,
OnProgressCallback,
suggested_filename,
)
from homeassistant.core import HomeAssistant, callback
from . import S3ConfigEntry
from .const import CONF_BUCKET, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
from .const import CONF_BUCKET, CONF_PREFIX, DATA_BACKUP_AGENT_LISTENERS, DOMAIN
from .helpers import async_list_backups_from_s3
_LOGGER = logging.getLogger(__name__)
@@ -100,6 +101,13 @@ class S3BackupAgent(BackupAgent):
self.unique_id = entry.entry_id
self._backup_cache: dict[str, AgentBackup] = {}
self._cache_expiration = time()
self._prefix: str = entry.data.get(CONF_PREFIX, "")
def _with_prefix(self, key: str) -> str:
"""Add prefix to a key if configured."""
if not self._prefix:
return key
return f"{self._prefix}/{key}"
@handle_boto_errors
async def async_download_backup(
@@ -115,7 +123,9 @@ class S3BackupAgent(BackupAgent):
backup = await self._find_backup_by_id(backup_id)
tar_filename, _ = suggested_filenames(backup)
response = await self._client.get_object(Bucket=self._bucket, Key=tar_filename)
response = await self._client.get_object(
Bucket=self._bucket, Key=self._with_prefix(tar_filename)
)
return response["Body"].iter_chunks()
async def async_upload_backup(
@@ -123,6 +133,7 @@ class S3BackupAgent(BackupAgent):
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup.
@@ -142,7 +153,7 @@ class S3BackupAgent(BackupAgent):
metadata_content = json.dumps(backup.as_dict())
await self._client.put_object(
Bucket=self._bucket,
Key=metadata_filename,
Key=self._with_prefix(metadata_filename),
Body=metadata_content,
)
except BotoCoreError as err:
@@ -169,7 +180,7 @@ class S3BackupAgent(BackupAgent):
await self._client.put_object(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
Body=bytes(file_data),
)
@@ -186,7 +197,7 @@ class S3BackupAgent(BackupAgent):
_LOGGER.debug("Starting multipart upload for %s", tar_filename)
multipart_upload = await self._client.create_multipart_upload(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
)
upload_id = multipart_upload["UploadId"]
try:
@@ -216,7 +227,7 @@ class S3BackupAgent(BackupAgent):
)
part = await cast(Any, self._client).upload_part(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
PartNumber=part_number,
UploadId=upload_id,
Body=part_data.tobytes(),
@@ -244,7 +255,7 @@ class S3BackupAgent(BackupAgent):
)
part = await cast(Any, self._client).upload_part(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
PartNumber=part_number,
UploadId=upload_id,
Body=remaining_data.tobytes(),
@@ -253,7 +264,7 @@ class S3BackupAgent(BackupAgent):
await cast(Any, self._client).complete_multipart_upload(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
UploadId=upload_id,
MultipartUpload={"Parts": parts},
)
@@ -262,7 +273,7 @@ class S3BackupAgent(BackupAgent):
try:
await self._client.abort_multipart_upload(
Bucket=self._bucket,
Key=tar_filename,
Key=self._with_prefix(tar_filename),
UploadId=upload_id,
)
except BotoCoreError:
@@ -283,8 +294,12 @@ class S3BackupAgent(BackupAgent):
tar_filename, metadata_filename = suggested_filenames(backup)
# Delete both the backup file and its metadata file
await self._client.delete_object(Bucket=self._bucket, Key=tar_filename)
await self._client.delete_object(Bucket=self._bucket, Key=metadata_filename)
await self._client.delete_object(
Bucket=self._bucket, Key=self._with_prefix(tar_filename)
)
await self._client.delete_object(
Bucket=self._bucket, Key=self._with_prefix(metadata_filename)
)
# Reset cache after successful deletion
self._cache_expiration = time()
@@ -317,7 +332,9 @@ class S3BackupAgent(BackupAgent):
if time() <= self._cache_expiration:
return self._backup_cache
backups_list = await async_list_backups_from_s3(self._client, self._bucket)
backups_list = await async_list_backups_from_s3(
self._client, self._bucket, self._prefix
)
self._backup_cache = {b.backup_id: b for b in backups_list}
self._cache_expiration = time() + CACHE_TTL
+25 -9
View File
@@ -22,6 +22,7 @@ from .const import (
CONF_ACCESS_KEY_ID,
CONF_BUCKET,
CONF_ENDPOINT_URL,
CONF_PREFIX,
CONF_SECRET_ACCESS_KEY,
DEFAULT_ENDPOINT_URL,
DESCRIPTION_AWS_S3_DOCS_URL,
@@ -39,6 +40,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
vol.Required(CONF_ENDPOINT_URL, default=DEFAULT_ENDPOINT_URL): TextSelector(
config=TextSelectorConfig(type=TextSelectorType.URL)
),
vol.Optional(CONF_PREFIX, default=""): cv.string,
}
)
@@ -53,12 +55,17 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
self._async_abort_entries_match(
{
CONF_BUCKET: user_input[CONF_BUCKET],
CONF_ENDPOINT_URL: user_input[CONF_ENDPOINT_URL],
}
)
normalized_prefix = user_input.get(CONF_PREFIX, "").strip("/")
# Check for existing entries, treating missing prefix as empty
for entry in self._async_current_entries(include_ignore=False):
entry_prefix = (entry.data.get(CONF_PREFIX) or "").strip("/")
if (
entry.data.get(CONF_BUCKET) == user_input[CONF_BUCKET]
and entry.data.get(CONF_ENDPOINT_URL)
== user_input[CONF_ENDPOINT_URL]
and entry_prefix == normalized_prefix
):
return self.async_abort(reason="already_configured")
hostname = urlparse(user_input[CONF_ENDPOINT_URL]).hostname
if not hostname or not hostname.endswith(AWS_DOMAIN):
@@ -83,9 +90,18 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
except ConnectionError:
errors[CONF_ENDPOINT_URL] = "cannot_connect"
else:
return self.async_create_entry(
title=user_input[CONF_BUCKET], data=user_input
)
data = dict(user_input)
if not normalized_prefix:
# Do not persist empty optional values
data.pop(CONF_PREFIX, None)
else:
data[CONF_PREFIX] = normalized_prefix
title = user_input[CONF_BUCKET]
if normalized_prefix:
title = f"{title} - {normalized_prefix}"
return self.async_create_entry(title=title, data=data)
return self.async_show_form(
step_id="user",
+1
View File
@@ -11,6 +11,7 @@ CONF_ACCESS_KEY_ID = "access_key_id"
CONF_SECRET_ACCESS_KEY = "secret_access_key"
CONF_ENDPOINT_URL = "endpoint_url"
CONF_BUCKET = "bucket"
CONF_PREFIX = "prefix"
AWS_DOMAIN = "amazonaws.com"
DEFAULT_ENDPOINT_URL = f"https://s3.eu-central-1.{AWS_DOMAIN}/"
@@ -13,7 +13,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_BUCKET, DOMAIN
from .const import CONF_BUCKET, CONF_PREFIX, DOMAIN
from .helpers import async_list_backups_from_s3
SCAN_INTERVAL = timedelta(hours=6)
@@ -53,11 +53,14 @@ class S3DataUpdateCoordinator(DataUpdateCoordinator[SensorData]):
)
self.client = client
self._bucket: str = entry.data[CONF_BUCKET]
self._prefix: str = entry.data.get(CONF_PREFIX, "")
async def _async_update_data(self) -> SensorData:
"""Fetch data from AWS S3."""
try:
backups = await async_list_backups_from_s3(self.client, self._bucket)
backups = await async_list_backups_from_s3(
self.client, self._bucket, self._prefix
)
except BotoCoreError as error:
raise UpdateFailed(
translation_domain=DOMAIN,
@@ -0,0 +1,55 @@
"""Diagnostics support for AWS S3."""
from __future__ import annotations
import dataclasses
from typing import Any
from homeassistant.components.backup import (
DATA_MANAGER as BACKUP_DATA_MANAGER,
BackupManager,
)
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from .const import (
CONF_ACCESS_KEY_ID,
CONF_BUCKET,
CONF_PREFIX,
CONF_SECRET_ACCESS_KEY,
DOMAIN,
)
from .coordinator import S3ConfigEntry
from .helpers import async_list_backups_from_s3
TO_REDACT = (CONF_ACCESS_KEY_ID, CONF_SECRET_ACCESS_KEY)
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: S3ConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data
backup_manager: BackupManager = hass.data[BACKUP_DATA_MANAGER]
backups = await async_list_backups_from_s3(
coordinator.client,
bucket=entry.data[CONF_BUCKET],
prefix=entry.data.get(CONF_PREFIX, ""),
)
data = {
"coordinator_data": dataclasses.asdict(coordinator.data),
"config": {
**entry.data,
**entry.options,
},
"backup_agents": [
{"name": agent.name}
for agent in backup_manager.backup_agents.values()
if agent.domain == DOMAIN
],
"backup": [backup.as_dict() for backup in backups],
}
return async_redact_data(data, TO_REDACT)
+7 -1
View File
@@ -17,11 +17,17 @@ _LOGGER = logging.getLogger(__name__)
async def async_list_backups_from_s3(
client: S3Client,
bucket: str,
prefix: str,
) -> list[AgentBackup]:
"""List backups from an S3 bucket by reading metadata files."""
paginator = client.get_paginator("list_objects_v2")
metadata_files: list[dict[str, Any]] = []
async for page in paginator.paginate(Bucket=bucket):
list_kwargs: dict[str, Any] = {"Bucket": bucket}
if prefix:
list_kwargs["Prefix"] = prefix + "/"
async for page in paginator.paginate(**list_kwargs):
metadata_files.extend(
obj
for obj in page.get("Contents", [])
@@ -23,7 +23,9 @@ rules:
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
unique-config-entry:
status: exempt
comment: Hassfest does not recognize the duplicate prevention logic. Duplicate entries are prevented by checking bucket, endpoint URL, and prefix in the config flow.
# Silver
action-exceptions:
@@ -36,14 +38,14 @@ rules:
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: todo
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
test-coverage: done
# Gold
devices: done
diagnostics: todo
diagnostics: done
discovery-update-info:
status: exempt
comment: S3 is a cloud service that is not discovered on the network.
@@ -15,12 +15,14 @@
"access_key_id": "Access key ID",
"bucket": "Bucket name",
"endpoint_url": "Endpoint URL",
"prefix": "Prefix",
"secret_access_key": "Secret access key"
},
"data_description": {
"access_key_id": "Access key ID to connect to AWS S3 API",
"bucket": "Bucket must already exist and be writable by the provided credentials.",
"endpoint_url": "Endpoint URL provided to [Boto3 Session]({boto3_docs_url}). Region-specific [AWS S3 endpoints]({aws_s3_docs_url}) are available in their docs.",
"prefix": "Folder or prefix to store backups in, for example `backups`",
"secret_access_key": "Secret access key to connect to AWS S3 API"
},
"title": "Add AWS S3 bucket"
@@ -16,6 +16,7 @@ from homeassistant.components.backup import (
BackupAgent,
BackupAgentError,
BackupNotFound,
OnProgressCallback,
suggested_filename,
)
from homeassistant.core import HomeAssistant, callback
@@ -129,6 +130,7 @@ class AzureStorageBackupAgent(BackupAgent):
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup."""
@@ -17,6 +17,7 @@ from homeassistant.components.backup import (
BackupAgent,
BackupAgentError,
BackupNotFound,
OnProgressCallback,
suggested_filename,
)
from homeassistant.core import HomeAssistant, callback
@@ -230,6 +231,7 @@ class BackblazeBackupAgent(BackupAgent):
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup to Backblaze B2.
@@ -17,6 +17,7 @@ from .agent import (
BackupAgentError,
BackupAgentPlatformProtocol,
LocalBackupAgent,
OnProgressCallback,
)
from .config import BackupConfig, CreateBackupParametersDict
from .const import DATA_MANAGER, DOMAIN
@@ -41,6 +42,7 @@ from .manager import (
RestoreBackupEvent,
RestoreBackupStage,
RestoreBackupState,
UploadBackupEvent,
WrittenBackup,
)
from .models import AddonInfo, AgentBackup, BackupNotFound, Folder
@@ -72,9 +74,11 @@ __all__ = [
"LocalBackupAgent",
"ManagerBackup",
"NewBackup",
"OnProgressCallback",
"RestoreBackupEvent",
"RestoreBackupStage",
"RestoreBackupState",
"UploadBackupEvent",
"WrittenBackup",
"async_get_manager",
"suggested_filename",
+9
View File
@@ -14,6 +14,13 @@ from homeassistant.core import HomeAssistant, callback
from .models import AgentBackup, BackupAgentError
class OnProgressCallback(Protocol):
"""Protocol for on_progress callback."""
def __call__(self, *, bytes_uploaded: int, **kwargs: Any) -> None:
"""Report upload progress."""
class BackupAgentUnreachableError(BackupAgentError):
"""Raised when the agent can't reach its API."""
@@ -53,12 +60,14 @@ class BackupAgent(abc.ABC):
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup.
:param open_stream: A function returning an async iterator that yields bytes.
:param backup: Metadata about the backup that should be uploaded.
:param on_progress: A callback to report the number of uploaded bytes.
"""
@abc.abstractmethod
+2 -1
View File
@@ -11,7 +11,7 @@ from typing import Any
from homeassistant.core import HomeAssistant
from homeassistant.helpers.hassio import is_hassio
from .agent import BackupAgent, LocalBackupAgent
from .agent import BackupAgent, LocalBackupAgent, OnProgressCallback
from .const import DOMAIN, LOGGER
from .models import AgentBackup, BackupNotFound
from .util import read_backup, suggested_filename
@@ -73,6 +73,7 @@ class CoreLocalBackupAgent(LocalBackupAgent):
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup."""
+66 -4
View File
@@ -32,6 +32,7 @@ from homeassistant.helpers import (
issue_registry as ir,
start,
)
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.json import json_bytes
from homeassistant.util import dt as dt_util, json as json_util
from homeassistant.util.async_iterator import AsyncIteratorReader
@@ -78,6 +79,8 @@ from .util import (
validate_password_stream,
)
UPLOAD_PROGRESS_DEBOUNCE_SECONDS = 1
@dataclass(frozen=True, kw_only=True, slots=True)
class NewBackup:
@@ -141,6 +144,7 @@ class CreateBackupStage(StrEnum):
ADDONS = "addons"
AWAIT_ADDON_RESTARTS = "await_addon_restarts"
DOCKER_CONFIG = "docker_config"
CLEANING_UP = "cleaning_up"
FINISHING_FILE = "finishing_file"
FOLDERS = "folders"
HOME_ASSISTANT = "home_assistant"
@@ -252,6 +256,15 @@ class BlockedEvent(ManagerStateEvent):
manager_state: BackupManagerState = BackupManagerState.BLOCKED
@dataclass(frozen=True, kw_only=True, slots=True)
class UploadBackupEvent(ManagerStateEvent):
"""Backup agent upload progress event."""
agent_id: str
uploaded_bytes: int
total_bytes: int
class BackupPlatformProtocol(Protocol):
"""Define the format that backup platforms can have."""
@@ -579,9 +592,50 @@ class BackupManager:
_backup = replace(
backup, protected=should_encrypt, size=streamer.size()
)
await self.backup_agents[agent_id].async_upload_backup(
agent = self.backup_agents[agent_id]
latest_uploaded_bytes = 0
@callback
def _emit_upload_progress() -> None:
"""Emit the latest upload progress event."""
self.async_on_backup_event(
UploadBackupEvent(
manager_state=self.state,
agent_id=agent_id,
uploaded_bytes=latest_uploaded_bytes,
total_bytes=_backup.size,
)
)
upload_progress_debouncer: Debouncer[None] = Debouncer(
self.hass,
LOGGER,
cooldown=UPLOAD_PROGRESS_DEBOUNCE_SECONDS,
immediate=True,
function=_emit_upload_progress,
)
@callback
def on_upload_progress(*, bytes_uploaded: int, **kwargs: Any) -> None:
"""Handle upload progress."""
nonlocal latest_uploaded_bytes
latest_uploaded_bytes = bytes_uploaded
upload_progress_debouncer.async_schedule_call()
await agent.async_upload_backup(
open_stream=open_stream_func,
backup=_backup,
on_progress=on_upload_progress,
)
upload_progress_debouncer.async_cancel()
self.async_on_backup_event(
UploadBackupEvent(
manager_state=self.state,
agent_id=agent_id,
uploaded_bytes=_backup.size,
total_bytes=_backup.size,
)
)
if streamer:
await streamer.wait()
@@ -1237,6 +1291,13 @@ class BackupManager:
)
# delete old backups more numerous than copies
# try this regardless of agent errors above
self.async_on_backup_event(
CreateBackupEvent(
reason=None,
stage=CreateBackupStage.CLEANING_UP,
state=CreateBackupState.IN_PROGRESS,
)
)
await delete_backups_exceeding_configured_count(self)
finally:
@@ -1374,9 +1435,10 @@ class BackupManager:
"""Forward event to subscribers."""
if (current_state := self.state) != (new_state := event.manager_state):
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
self.last_event = event
if not isinstance(event, (BlockedEvent, IdleEvent)):
self.last_action_event = event
if not isinstance(event, UploadBackupEvent):
self.last_event = event
if not isinstance(event, (BlockedEvent, IdleEvent)):
self.last_action_event = event
for subscription in self._backup_event_subscriptions:
subscription(event)
@@ -174,13 +174,5 @@
"on": "mdi:window-open"
}
}
},
"triggers": {
"occupancy_cleared": {
"trigger": "mdi:home-outline"
},
"occupancy_detected": {
"trigger": "mdi:home"
}
}
}
@@ -1,8 +1,4 @@
{
"common": {
"trigger_behavior_description_occupancy": "The behavior of the targeted occupancy sensors to trigger on.",
"trigger_behavior_name": "Behavior"
},
"device_automation": {
"condition_type": {
"is_bat_low": "{entity_name} battery is low",
@@ -321,36 +317,5 @@
}
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"title": "Binary sensor",
"triggers": {
"occupancy_cleared": {
"description": "Triggers after one or more occupancy sensors stop detecting occupancy.",
"fields": {
"behavior": {
"description": "[%key:component::binary_sensor::common::trigger_behavior_description_occupancy%]",
"name": "[%key:component::binary_sensor::common::trigger_behavior_name%]"
}
},
"name": "Occupancy cleared"
},
"occupancy_detected": {
"description": "Triggers after one or more occupancy sensors start detecting occupancy.",
"fields": {
"behavior": {
"description": "[%key:component::binary_sensor::common::trigger_behavior_description_occupancy%]",
"name": "[%key:component::binary_sensor::common::trigger_behavior_name%]"
}
},
"name": "Occupancy detected"
}
}
"title": "Binary sensor"
}
@@ -1,67 +0,0 @@
"""Provides triggers for binary sensors."""
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity import get_device_class
from homeassistant.helpers.trigger import EntityTargetStateTriggerBase, Trigger
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from . import DOMAIN, BinarySensorDeviceClass
def get_device_class_or_undefined(
hass: HomeAssistant, entity_id: str
) -> str | None | UndefinedType:
"""Get the device class of an entity or UNDEFINED if not found."""
try:
return get_device_class(hass, entity_id)
except HomeAssistantError:
return UNDEFINED
class BinarySensorOnOffTrigger(EntityTargetStateTriggerBase):
"""Class for binary sensor on/off triggers."""
_device_class: BinarySensorDeviceClass | None
_domain: str = DOMAIN
def entity_filter(self, entities: set[str]) -> set[str]:
"""Filter entities of this domain."""
entities = super().entity_filter(entities)
return {
entity_id
for entity_id in entities
if get_device_class_or_undefined(self._hass, entity_id)
== self._device_class
}
def make_binary_sensor_trigger(
device_class: BinarySensorDeviceClass | None,
to_state: str,
) -> type[BinarySensorOnOffTrigger]:
"""Create an entity state trigger class."""
class CustomTrigger(BinarySensorOnOffTrigger):
"""Trigger for entity state changes."""
_device_class = device_class
_to_states = {to_state}
return CustomTrigger
TRIGGERS: dict[str, type[Trigger]] = {
"occupancy_detected": make_binary_sensor_trigger(
BinarySensorDeviceClass.OCCUPANCY, STATE_ON
),
"occupancy_cleared": make_binary_sensor_trigger(
BinarySensorDeviceClass.OCCUPANCY, STATE_OFF
),
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for binary sensors."""
return TRIGGERS
+6 -6
View File
@@ -190,7 +190,7 @@ class BitcoinSensor(SensorEntity):
elif sensor_type == "miners_revenue_usd":
self._attr_native_value = f"{stats.miners_revenue_usd:.0f}"
elif sensor_type == "btc_mined":
self._attr_native_value = str(stats.btc_mined * 0.00000001)
self._attr_native_value = str(stats.btc_mined * 1e-8)
elif sensor_type == "trade_volume_usd":
self._attr_native_value = f"{stats.trade_volume_usd:.1f}"
elif sensor_type == "difficulty":
@@ -208,13 +208,13 @@ class BitcoinSensor(SensorEntity):
elif sensor_type == "blocks_size":
self._attr_native_value = f"{stats.blocks_size:.1f}"
elif sensor_type == "total_fees_btc":
self._attr_native_value = f"{stats.total_fees_btc * 0.00000001:.2f}"
self._attr_native_value = f"{stats.total_fees_btc * 1e-8:.2f}"
elif sensor_type == "total_btc_sent":
self._attr_native_value = f"{stats.total_btc_sent * 0.00000001:.2f}"
self._attr_native_value = f"{stats.total_btc_sent * 1e-8:.2f}"
elif sensor_type == "estimated_btc_sent":
self._attr_native_value = f"{stats.estimated_btc_sent * 0.00000001:.2f}"
self._attr_native_value = f"{stats.estimated_btc_sent * 1e-8:.2f}"
elif sensor_type == "total_btc":
self._attr_native_value = f"{stats.total_btc * 0.00000001:.2f}"
self._attr_native_value = f"{stats.total_btc * 1e-8:.2f}"
elif sensor_type == "total_blocks":
self._attr_native_value = f"{stats.total_blocks:.0f}"
elif sensor_type == "next_retarget":
@@ -222,7 +222,7 @@ class BitcoinSensor(SensorEntity):
elif sensor_type == "estimated_transaction_volume_usd":
self._attr_native_value = f"{stats.estimated_transaction_volume_usd:.2f}"
elif sensor_type == "miners_revenue_btc":
self._attr_native_value = f"{stats.miners_revenue_btc * 0.00000001:.1f}"
self._attr_native_value = f"{stats.miners_revenue_btc * 1e-8:.1f}"
elif sensor_type == "market_price_usd":
self._attr_native_value = f"{stats.market_price_usd:.2f}"

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