Compare commits

..

422 Commits

Author SHA1 Message Date
Jan Bouwhuis
b2ff488519 Revert "Do not allow overriding users when uuid is duplicate (#149408)"
This reverts commit 08ea640629.
2025-08-06 09:30:01 +02:00
Philipp Waller
119d0a0170 Update knx-frontend to 2025.8.6.52906 (#150085) 2025-08-06 09:28:44 +02:00
Joakim Sørensen
69faf38e86 Bump hass-nabucasa from 0.111.0 to 0.111.1 (#150082) 2025-08-06 09:24:09 +02:00
puddly
d0ef1a1a8b Bump ZHA to 0.0.66 (#150081) 2025-08-06 09:22:07 +02:00
Retha Runolfsson
8f328810bf Bump pyswitchbot to 0.68.3 (#150080) 2025-08-05 19:20:37 -10:00
Pete Sage
4f1b75e3b4 Bump soco to 0.30.11 (#150072) 2025-08-05 22:56:27 +01:00
J. Nick Koston
445a7fc749 Bump yalexs to 8.11.1 (#150073) 2025-08-05 22:55:01 +01:00
Robert Svensson
977c0797aa Bump axis to v65 (#150065) 2025-08-05 11:36:48 -10:00
Ludovic BOUÉ
a24f027923 Add icon for esa_state in Matter integration (#149075) 2025-08-05 23:18:48 +02:00
Martin Hjelmare
7b45798e30 Remove matter vacuum battery level attribute (#150061) 2025-08-05 22:40:42 +02:00
Artur Pragacz
2b0cda0ad1 Adjust condition and trigger method names (#150060) 2025-08-05 19:46:03 +01:00
starkillerOG
12dca4b1bf Bump reolink-aio to 0.14.6 (#150055) 2025-08-05 18:58:22 +02:00
karwosts
8c509b11b2 Fix template sensor uom string (#150057) 2025-08-05 18:56:34 +02:00
Joost Lekkerkerker
991c9008bd Change AI task strings (#150051) 2025-08-05 16:35:41 +02:00
Martin Hjelmare
fe95f6e1c5 Improve downloader service (#150046)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-08-05 16:12:55 +02:00
Bram Kragten
37510aa316 Update frontend to 20250805.0 (#150049) 2025-08-05 16:01:47 +02:00
Marc Mueller
4e40e9bf74 Update mypy-dev to 1.18.0a4 (#150005) 2025-08-05 15:56:03 +02:00
Bouwe Westerdijk
70c9b1f095 Implement snapshot testing for Plugwise button platform (#149984) 2025-08-05 15:31:02 +02:00
dependabot[bot]
f714388130 Bump docker/login-action from 3.4.0 to 3.5.0 (#150034)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 15:25:58 +02:00
Joost Lekkerkerker
ffb2a693f4 Ignore vacuum entities that properly deprecate battery (#150043) 2025-08-05 15:22:21 +02:00
Andrew Jackson
9d8e253ad3 Default to zero quantity on new todo items in Mealie (#150047) 2025-08-05 15:15:08 +02:00
dependabot[bot]
31631cc882 Bump actions/ai-inference from 1.2.4 to 1.2.7 (#150038)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-05 14:40:01 +02:00
epenet
3a64357201 Fix Tuya fan speeds with numeric values (#149971) 2025-08-05 13:22:45 +02:00
Thomas55555
20fdec9e9c Reduce polling in Husqvarna Automower (#149255)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-08-05 12:56:27 +02:00
Nippey
064a63fe1f Add support for Tuya "Bresser 7-in-1 Weatherstation" (#149498) 2025-08-05 12:54:40 +02:00
epenet
803654223a Revert "Do not create Tuya fan entities without control" (#150032) 2025-08-05 12:23:06 +02:00
epenet
a6148b50cf Add Tuya snapshots tests for button and vacuum platform (#149968) 2025-08-05 11:21:05 +02:00
Ludovic BOUÉ
02a3c5be14 Matter pump setpoint CurrentLevel limit (#149689) 2025-08-05 11:19:03 +02:00
Paulus Schoutsen
08ea640629 Do not allow overriding users when uuid is duplicate (#149408) 2025-08-05 11:13:32 +02:00
Grzegorz M
7dd761c9c3 Bump icalendar from 6.1.0 to 6.3.1 for CalDav (#149990) 2025-08-05 11:09:03 +02:00
epenet
6b827dfc33 Do not create Tuya fan entities without control (#149976)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-05 09:52:29 +02:00
Robert Resch
67c19087dd Bump deebot-client to 13.6.0 (#149983) 2025-08-05 09:08:33 +02:00
epenet
55c7c2f730 Redact terminal_id in Tuya fixture files (#149957) 2025-08-05 09:06:15 +02:00
Matthias Alphart
afee936c3d Update knx-frontend to 2025.8.4.154919 (#149991) 2025-08-05 09:03:23 +02:00
Marc Mueller
ed2ced6c36 Fix zimi test RuntimeWarnings (#150017) 2025-08-05 08:55:54 +02:00
Martin Hjelmare
4c5cf028d7 Fix Z-Wave duplicate provisioned device (#150008) 2025-08-05 08:50:42 +02:00
Thomas55555
68faa897ad Bump aioautomower to 2.1.2 (#150003) 2025-08-05 08:48:47 +02:00
Artur Pragacz
53c9c42148 Use relative trigger keys (#149846) 2025-08-04 23:01:40 +01:00
Michael Hansen
d48cc03be7 Bump wyoming to 1.7.2 (#150007) 2025-08-04 23:36:24 +02:00
starkillerOG
28236aa023 Reolink disable entities by default (#149986) 2025-08-04 23:03:38 +02:00
Tom
bfae07135a Bump python-airos to 0.2.4 (#149885) 2025-08-04 22:35:47 +02:00
Thomas55555
99d580e371 Add reset cutting blade usage time to Husqvarna Automower (#149628) 2025-08-04 22:28:34 +02:00
Petro31
4d53450cbf Create battery_level deprecation repair for template vacuum platform (#149987)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-08-04 21:54:50 +02:00
epenet
1fbce01e26 Add initial support for Tuya wg2 category (#149676) 2025-08-04 21:30:43 +02:00
markhannon
a9621ac811 Add tests for Zimi entitites (#144292) 2025-08-04 20:41:05 +02:00
Marc Mueller
94f2118b19 Fix flaky history_stats test case (#149974) 2025-08-04 20:34:07 +02:00
Mike Degatano
73ca6b4900 Add translation strings for unsupported OS version (#149837) 2025-08-04 17:40:11 +02:00
Joakim Sørensen
31e647b5b0 Bump hass-nabucasa from 0.110.1 to 0.111.0 (#149977) 2025-08-04 16:59:07 +02:00
epenet
fac5b2c09c Add Tuya snapshots tests for camera platform (#149959) 2025-08-04 16:58:46 +02:00
Martin Hjelmare
ae48179e95 Bump zwave-js-server-python to 0.67.1 (#149972) 2025-08-04 15:58:57 +02:00
Willem-Jan van Rootselaar
88c9d5dbe3 Fix bsblan reauthentication (#149926) 2025-08-04 15:35:41 +02:00
hanwg
b76f47cd9f Add bot details to Telegram bot events (#148638) 2025-08-04 14:32:48 +02:00
hanwg
822e1ffc8d Minor UI improvements for Telegram bot actions (#149889) 2025-08-04 14:27:15 +02:00
Martin Hjelmare
1632e0aef6 Direct migrations with Z-Wave JS UI to docs (#149966) 2025-08-04 13:36:12 +02:00
Petro31
e2bc73f153 Fix optimistic covers (#149962) 2025-08-04 13:35:13 +02:00
Joakim Sørensen
46cfdddc80 Move to the new handler for migrate_paypal_agreement (#149934) 2025-08-04 13:29:11 +02:00
Joost Lekkerkerker
0bdf6757c4 Pass config entry to Remote Calendar coordinator (#149958) 2025-08-04 13:28:59 +02:00
Joost Lekkerkerker
312e590360 Pass config entry to Broadlink coordinator (#149949) 2025-08-04 13:27:51 +02:00
Joost Lekkerkerker
7a6aaf667b Pass config entry to hue coordinator (#149941) 2025-08-04 13:27:10 +02:00
Joost Lekkerkerker
33eaca24d6 Pass config entry to Simplisafe coordinator (#149943) 2025-08-04 13:21:29 +02:00
Joost Lekkerkerker
3d27d501b1 Pass config entry to Mill coordinator (#149942) 2025-08-04 13:20:30 +02:00
Joost Lekkerkerker
39b651e075 Pass config entry to Kraken coordinator (#149944) 2025-08-04 13:17:27 +02:00
Joost Lekkerkerker
a962777a2e Pass config entry to Meteo France coordinator (#149945) 2025-08-04 13:14:50 +02:00
Joost Lekkerkerker
594ce8f266 Pass config entry to Smarttub coordinator (#149946) 2025-08-04 12:58:46 +02:00
Joost Lekkerkerker
9f867f268c Pass config entry to Snoo coordinator (#149947) 2025-08-04 12:58:19 +02:00
Joost Lekkerkerker
9edd242734 Pass config entry to SMS coordinator (#149955) 2025-08-04 12:49:26 +02:00
Bouwe Westerdijk
93e11aa8bc Refresh plugwise test-fixtures (#149875) 2025-08-04 12:35:24 +02:00
Joakim Sørensen
c2b298283e Bump hass-nabucasa from 0.110.0 to 0.110.1 (#149956) 2025-08-04 12:32:01 +02:00
Joost Lekkerkerker
106c086e8b Pass config entry to Unifi coordinator (#149952) 2025-08-04 12:29:27 +02:00
Markus Adrario
cbf4130bff Add zeroconf flow to Homee (#149820)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-08-04 12:26:22 +02:00
Erik Montnemery
afffe0b08b Fix DeviceEntry.suggested_area deprecation warning (#149951) 2025-08-04 12:20:30 +02:00
Joost Lekkerkerker
c1ccfee7cc Pass config entry to AsusWRT coordinator (#149953) 2025-08-04 12:08:03 +02:00
epenet
8d8383e1c1 Add extra Tuya snapshots for dc and dj category (lights) (#149940) 2025-08-04 12:07:25 +02:00
Marc Mueller
f350a1a1fa Add hassfest check to help with future dependency updates (#149624) 2025-08-04 12:03:39 +02:00
epenet
fe2bd8d09e Add Tuya snapshots for ywcgq category (#149948) 2025-08-04 12:02:34 +02:00
Joost Lekkerkerker
cf14226b02 Pass config entry to Fronius coordinator (#149954) 2025-08-04 12:02:21 +02:00
Brett Adams
bd3fe1d4ad Fix credit sensor when there are no vehicles in Teslemetry (#149925) 2025-08-04 11:26:14 +02:00
Christopher Fenner
377ca04be8 Update sensor icons in Volvo integration (#149811) 2025-08-04 11:24:51 +02:00
epenet
5837f55205 Add extra Tuya snapshots for cz category (#149938) 2025-08-04 11:23:58 +02:00
andreimoraru
0766edb9c4 Bump yt-dlp to 2025.07.21 (#149916)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-08-04 11:15:38 +02:00
epenet
e62e3778f3 Add Tuya snapshots for hps category (#149936) 2025-08-04 11:14:11 +02:00
epenet
aa8e4c1c15 Add Tuya snapshots for sgbj, sp, wfcon and ywbj category (#149933) 2025-08-04 11:11:06 +02:00
Erik Montnemery
46ed8a73fc Bump automower-ble to 0.2.7 (#149928) 2025-08-04 11:09:18 +02:00
dependabot[bot]
83f22497ae Bump actions/ai-inference from 1.2.3 to 1.2.4 (#149929)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-04 11:09:02 +02:00
epenet
3dda1685dc Add Tuya snapshots for pc and pir category (#149931) 2025-08-04 11:08:43 +02:00
Ståle Storø Hauknes
6fa9d42401 Airthings ContextVar warning (#149930) 2025-08-04 11:05:32 +02:00
jvmahon
1a54d566f8 Apple vendor name update (#149845) 2025-08-04 10:26:11 +02:00
puddly
1a9cae0f89 Bump ZHA to 0.0.65 (#149922) 2025-08-04 10:17:25 +02:00
epenet
551dcaa169 Rename Tuya fixture files (#149927) 2025-08-04 10:08:03 +02:00
epenet
5467db065b Make Tuya complex type handling explicit (#149677) 2025-08-04 07:59:47 +02:00
J. Nick Koston
6a8d752e56 Bump aiodiscover to 2.7.1 (#149920) 2025-08-03 16:42:38 -10:00
J. Nick Koston
179a56628d Bump dbus-fast to 2.44.3 (#149921) 2025-08-03 16:42:11 -10:00
J. Nick Koston
b3f830773a Bump yalexs-ble to 3.1.2 (#149917) 2025-08-03 15:02:30 -10:00
Joost Lekkerkerker
084e06ec7d Bump python-open-router to 0.3.1 (#149873) 2025-08-03 21:46:40 +02:00
Maciej Bieniek
e0190afd3c Bump imgw_pib to version 1.5.2 (#149892) 2025-08-03 20:07:01 +02:00
Jan-Philipp Benecke
b9e16d54c4 Add jitter sensor to Ping integration (#149899) 2025-08-03 20:06:14 +02:00
Thomas55555
627785edc1 Fix options for error sensor in Husqvarna Automower (#149901) 2025-08-03 20:05:23 +02:00
Andrew Jackson
4318e29ce8 Bump aiomealie to 0.10.1 (#149890) 2025-08-03 14:18:13 +02:00
Martin Hjelmare
fea5c63bba Fix Z-Wave handling of driver ready event (#149879) 2025-08-03 11:23:01 +02:00
Åke Strandberg
b2349ac2bd Improve miele climate test coverage (#149859) 2025-08-03 11:19:08 +02:00
Marc Mueller
08f7b708a4 Update pytest warnings filter (#149839) 2025-08-03 09:25:17 +02:00
Martin Hjelmare
1236801b7d Fix Z-Wave config entry state conditions in listen task (#149841) 2025-08-02 23:07:16 +02:00
Thomas D
72d9dbf39d Add scopes in config flow auth request for Volvo integration (#149813) 2025-08-02 22:17:13 +02:00
Thomas D
755864f9f3 Add sensor platform to Qbus integration (#149389)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-08-02 20:01:58 +02:00
peteS-UK
fa476d4e34 Fix initialisation of Apps and Radios list for Squeezebox (#149834) 2025-08-02 20:01:02 +02:00
Manu
018197e41a Add notifiers to send direct messages to friends in PlayStation Network (#149844) 2025-08-02 19:55:45 +02:00
Brett Adams
7dd2b9e422 Make history coordinator more reliable in Tesla Fleet (#149854) 2025-08-02 19:54:19 +02:00
hahn-th
3e615fd373 Improve code quality for garage door modules in homematicip_cloud (#149856) 2025-08-02 19:51:08 +02:00
Oliver
c0bf167e10 Update denonavr to 1.1.2 (#149842) 2025-08-02 19:44:01 +02:00
Andrea Turri
45f6778ff4 Fix Miele hob translation keys (#149865) 2025-08-02 18:37:57 +02:00
Jamin
bddd4d621a Bump VoIP utils to 0.3.4 (#149786) 2025-08-01 20:37:45 +01:00
Norbert Rittel
b0e75e9ee4 Update reference for volatile_organic_compounds_parts in template (#149831) 2025-08-01 20:36:10 +01:00
Norbert Rittel
d45c03a795 Update reference for volatile_organic_compounds_parts in random (#149832) 2025-08-01 20:35:04 +01:00
Norbert Rittel
8562c8d32f Add translations for recently introduced device classes to scrape (#149822) 2025-08-01 20:34:31 +01:00
Norbert Rittel
ae42d71123 Add translations for recently introduced device classes to sql (#149821) 2025-08-01 20:33:47 +01:00
Alexandre CUER
9616c8cd7b Bump pyemoncms to 0.1.2 (#149825) 2025-08-01 20:04:16 +01:00
kizovinh
9394546668 Add EZVIZ battery camera power status and online status sensor (#146822) 2025-08-01 20:00:53 +01:00
Norbert Rittel
d43f21c2e2 Fix descriptions for template number fields (#149804) 2025-08-01 20:35:48 +02:00
Norbert Rittel
8d68fee9f8 Add translation for absolute_humidity device class to template (#149814) 2025-08-01 18:30:59 +01:00
Willem-Jan van Rootselaar
b4a4e218ec Add re-authentication to BSBLan (#146280)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-08-01 16:42:59 +02:00
Norbert Rittel
fb2d62d692 Add translation for absolute_humidity device class to mqtt (#149818) 2025-08-01 15:57:47 +02:00
Erik Montnemery
f538807d6e Make device suggested_area only influence new devices (#149758)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-08-01 14:54:58 +02:00
Joost Lekkerkerker
a08c3c9f44 Improve Tado binary sensor tests (#149807) 2025-08-01 14:38:12 +02:00
Joost Lekkerkerker
506431c75f Improve Tado water heater tests (#149806) 2025-08-01 14:38:02 +02:00
Joost Lekkerkerker
37579440e6 Improve Tado climate tests (#149808) 2025-08-01 14:37:12 +02:00
Joost Lekkerkerker
5ce2729dc2 Improve Tado sensor tests (#149809) 2025-08-01 14:36:57 +02:00
Joost Lekkerkerker
b5e4ae4a53 Improve Tado switch tests (#149810)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-01 14:36:37 +02:00
Norbert Rittel
3d4386ea6d Add translation for absolute_humidity device class to random (#149815) 2025-08-01 14:32:14 +02:00
Alexandre CUER
9f1cec893e emoncms - fix missing data descriptions (#149733) 2025-08-01 13:22:46 +02:00
starkillerOG
bc87140a6f Update after Motion Blinds tilt change (#149779) 2025-08-01 11:15:49 +02:00
Erik Montnemery
d77a3fca83 Exclude is_new from DeviceEntry snapshots (#149801) 2025-08-01 11:01:26 +02:00
Joakim Sørensen
924a86dfb6 Add nameservers to supervisor system health response (#149749) 2025-08-01 10:51:48 +02:00
Erik Montnemery
0d7608f7c5 Deprecate DeviceEntry.suggested_area (#149730) 2025-08-01 10:34:34 +02:00
Tom
22e054f4cd Add diagnostics to UISP AirOS (#149631) 2025-08-01 09:24:22 +02:00
epenet
8b53b26333 Fix tuya light supported color modes (#149793)
Co-authored-by: Erik <erik@montnemery.com>
2025-08-01 09:13:53 +02:00
Erik Montnemery
4d59e8cd80 Fix flaky velbus test (#149743) 2025-08-01 07:49:51 +02:00
Fabian Leutgeb
61396d92a5 Homekit valve duration characteristics (#149698)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-31 15:21:48 -10:00
Philippe Lafoucrière
c72c600de4 Fix bootstrap script path resolution (#149721) 2025-07-31 23:47:25 +01:00
J. Nick Koston
b86b0c10bd Bump aioesphomeapi to 37.2.2 (#149755) 2025-07-31 12:23:24 -10:00
starkillerOG
eb222f6c5d Bump motionblinds to 0.6.30 (#149764) 2025-08-01 01:09:20 +03:00
Manu
4b5fe424ed Hide configuration URL when Uptime Kuma is installed locally (#149781) 2025-08-01 01:07:56 +03:00
Nathan Spencer
61ca42e923 Bump pylitterbot to 2024.2.3 (#149763) 2025-07-31 21:04:23 +02:00
Copilot
21c1427abf Fix ZHA ContextVar deprecation by passing config_entry (#149748)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joostlek <7083755+joostlek@users.noreply.github.com>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: TheJulianJES <6409465+TheJulianJES@users.noreply.github.com>
2025-07-31 14:52:17 -04:00
karwosts
aa6b37bc7c Fix add_suggested_values_to_schema when the schema has sections (#149718)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-07-31 20:50:26 +02:00
Marc Mueller
bbc1466cfc Update rpds-py to 0.26.0 (#149753) 2025-07-31 17:51:10 +01:00
Bram Kragten
21a9799060 Update frontend to 20250731.0 (#149757) 2025-07-31 18:46:10 +02:00
Erik Montnemery
f7d54b46ec Improve test of FlowHandler.add_suggested_values_to_schema (#149759) 2025-07-31 17:55:15 +02:00
Erik Montnemery
6ad1b8dcb1 Fix kitchen_sink option flow (#149760) 2025-07-31 17:49:09 +02:00
Abílio Costa
5f6b1212a3 Remove data flow step_id deprecation note (#149714) 2025-07-31 16:04:09 +02:00
dependabot[bot]
58dc6a952e Bump home-assistant/wheels from 2025.03.0 to 2025.07.0 (#149741) 2025-07-31 15:35:55 +02:00
Petro31
59d8df142d Nitpick default translations for template integration (#149740) 2025-07-31 15:19:43 +02:00
Petro31
04fb86b4ba Fix unique_id in config validation for legacy weather platform (#149742) 2025-07-31 15:19:37 +02:00
Erik Montnemery
3d744f032f Make _EventDeviceRegistryUpdatedData_Remove JSON serializable (#149734) 2025-07-31 12:35:13 +02:00
J. Nick Koston
f7c8cdb3a7 Bump aioesphomeapi to 37.2.0 (#149732) 2025-07-31 12:10:23 +02:00
Copilot
3952544822 Fix ContextVar deprecation warning in homeassistant_hardware integration (#149687)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: joostlek <7083755+joostlek@users.noreply.github.com>
Co-authored-by: mib1185 <35783820+mib1185@users.noreply.github.com>
2025-07-31 12:06:04 +02:00
Erik Montnemery
42101dd432 Remove result from FlowResult (#149202) 2025-07-31 10:58:36 +02:00
L.
f7eacaa48d Bump xiaomi-ble to 1.2.0 (#149711) 2025-07-31 09:01:06 +02:00
johanzander
ad0db5c83a Update growattServer to version 1.7.1 (#149716) 2025-07-31 08:17:33 +02:00
J. Nick Koston
63216b77c2 Bump aioesphomeapi to 37.1.6 (#149715) 2025-07-30 13:54:18 -10:00
Åke Strandberg
7a55373b0b Fix bug when interpreting miele action response (#149710) 2025-07-31 01:07:12 +02:00
J. Nick Koston
f9e7459901 Fix ESPHome unnecessary probing on DHCP discovery (#149713) 2025-07-31 01:06:08 +02:00
starkillerOG
94dc2e2ea3 Bump reolink-aio to 0.14.5 (#149700) 2025-07-30 22:54:32 +01:00
Åke Strandberg
2cf144fb25 Add missing translations for miele dishwasher (#149702) 2025-07-30 22:45:05 +01:00
Jan Bouwhuis
f318766021 Fix inconsistent use of the term 'target' and a typo in MQTT translation strings (#149703) 2025-07-30 22:42:53 +01:00
Andrea Turri
ec7fb140ac Fix Miele induction hob empty state (#149706) 2025-07-30 22:38:11 +01:00
Petro31
2706c7d67d Add translations for all fields in template integration (#149692)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-07-30 22:30:05 +01:00
Roman Sivriver
b4e50902eb Fix typo in backup log message (#149705) 2025-07-30 22:29:26 +01:00
Åke Strandberg
1ead01bc9a Explicitly pass config_entry to miele coordinator (#149691) 2025-07-30 20:19:01 +02:00
puddly
389a1251a1 Bump ZHA to 0.0.64 (#149683)
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Co-authored-by: abmantis <amfcalt@gmail.com>
2025-07-30 18:59:41 +01:00
Manu
8d27ca1e21 Fix KeyError in friends coordinator (#149684) 2025-07-30 19:59:01 +02:00
Michael Hansen
a76af50c10 Bump intents to 2025.7.30 (#149678) 2025-07-30 19:57:59 +02:00
Renat Sibgatulin
09b91bd76a Clean airq tests (#149682) 2025-07-30 18:48:36 +01:00
Jan Bouwhuis
736d582d04 Fix translation string reference for MQTT climate subentry option (#149673) 2025-07-30 18:53:21 +02:00
Bram Kragten
8114df4219 Bump version to 2025.9.0 (#149680) 2025-07-30 18:36:20 +02:00
Joost Lekkerkerker
8193259e02 Revert "Add select for heating circuit to Tado zones" (#149670) 2025-07-30 17:06:55 +02:00
Petro31
6306baa3c9 Add config flow to template lock platform (#149449) 2025-07-30 17:04:39 +02:00
Petro31
d481a694f1 Add config flow to template vacuum platform (#149458) 2025-07-30 17:04:08 +02:00
Robert Resch
edca3fc0b7 Add matter to Third Reality (#149659) 2025-07-30 16:52:20 +02:00
Bram Kragten
daea76c2f1 Update frontend to 20250730.0 (#149672) 2025-07-30 16:51:10 +02:00
Petro31
160b61e0b9 Add config flow to template fan platform (#149446)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
2025-07-30 16:17:49 +02:00
epenet
fc900a632a Revert logging for unsupported Tuya devices (#149665) 2025-07-30 16:04:45 +02:00
Joost Lekkerkerker
1b58809655 Add AI Task to OpenRouter (#149275) 2025-07-30 16:01:44 +02:00
Åke Strandberg
223c34056d Add missing colons in miele messages (#149668) 2025-07-30 15:58:43 +02:00
Jeef
99ee56a4dd Add Precipitation sensors to Weatherflow Cloud (#149619)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-30 15:45:03 +02:00
lucasfijen
91be25a292 Add get recipes search service to Mealie integration (#149348)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-30 15:43:10 +02:00
Petro31
a21af78aa1 Add config flow to template light platform (#149448) 2025-07-30 15:27:43 +02:00
Manu
70cfdfa231 Remove unnecessary CONFIG_SCHEMA from Uptime Kuma integration (#149601) 2025-07-30 15:23:54 +02:00
Jan Bouwhuis
a5b075af68 Add climate support for MQTT subentries (#149451)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-07-30 15:20:23 +02:00
Manu
c4d4ef884e Add hassio discovery flow to Uptime Kuma (#148770) 2025-07-30 15:13:39 +02:00
Manu
ba4e7e50e0 Add friend tracking to PlayStation Network (#149546) 2025-07-30 15:10:30 +02:00
Alistair Francis
dd0b23afb0 husqvarna_automower_ble: Support battery percentage sensor (#146159)
Signed-off-by: Alistair Francis <alistair@alistair23.me>
2025-07-30 15:07:47 +02:00
Manu
779f0afcc4 Refactor Habitica button and switch functions to use habiticalib instance directly (#149602) 2025-07-30 15:07:22 +02:00
Manu
d8016f7f41 Remove stale devices in Uptime Kuma (#149605) 2025-07-30 15:06:59 +02:00
Avery
25169e9075 Bump datadogpy to 0.52.0 (#149596) 2025-07-30 15:06:38 +02:00
Samuel Xiao
260ca70785 Add Light platform to Switchbot cloud (#146382) 2025-07-30 15:03:13 +02:00
Samuel Xiao
69e3a5bc34 Add support for more switchbot cloud vacuum models (#146637) 2025-07-30 15:02:37 +02:00
Simone Chemelli
1a75a88c76 Add actions to Alexa Devices (#145645) 2025-07-30 14:52:31 +02:00
Petro31
6c2a662838 Add config flow to template cover platform (#149433) 2025-07-30 14:48:24 +02:00
Artur Pragacz
749fc318ca Validate selectors in the trigger helper (#149662) 2025-07-30 14:22:55 +02:00
epenet
828f979c78 Use Tuya device listener in binary sensor tests (#148890) 2025-07-30 13:43:07 +02:00
Åke Strandberg
1eb6d5fe32 Add action for set_program_oven to miele (#149620) 2025-07-30 13:35:24 +02:00
epenet
5930ac6425 Use translation_placeholders in tuya switch descriptions (#149664) 2025-07-30 13:27:24 +02:00
Simone Chemelli
15e45df8a7 Use async_create_clientsession in Alexa Devices (#149432) 2025-07-30 12:49:21 +02:00
karwosts
a79d2da9a3 Move group toggle descriptions to data_description (#149625)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-07-30 12:31:32 +02:00
Robert Resch
ac86f2e2ba Add Frient brand (#149654) 2025-07-30 12:21:27 +02:00
Norbert Rittel
03ee97d38f Clarify description of turn_away_mode_on.osoenergy action (#149655) 2025-07-30 12:16:40 +02:00
J. Nick Koston
06233b5134 Bump aioesphomeapi to 37.1.5 (#149656) 2025-07-30 12:16:16 +02:00
Petro31
9d66b19c03 Add assumed optimistic to template number entities (#148499) 2025-07-30 11:20:04 +02:00
Martin Hjelmare
bb6bcfdd01 Add Z-Wave controller firmware updates (#149623) 2025-07-30 11:07:41 +02:00
Marc Mueller
8e9e304608 Update lxml to 6.0.0 (#149640) 2025-07-30 10:38:42 +02:00
dependabot[bot]
6b641411a0 Bump github/codeql-action from 3.29.4 to 3.29.5 (#149648)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-30 10:33:09 +02:00
Norbert Rittel
6f8214bbb4 Fix spelling mistakes in abort message of leaone (#149653) 2025-07-29 22:22:35 -10:00
Marcel van der Veldt
f66e83f33e Add dynamic encryption key support to the ESPHome integration (#148746)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-29 21:54:00 -10:00
Robert Resch
2ee82e1d6f Remove battery attribute from Ecovacs vacuums (#149581) 2025-07-30 09:24:16 +02:00
Jan Bouwhuis
0dd1e0cabb Suppress exception stack trace when writing MQTT entity state if a ValueError occured (#149583) 2025-07-30 09:06:15 +02:00
Åke Strandberg
45ae34cc0e Strip leading and trailing whitespace in program names in miele action response (#149643) 2025-07-30 00:23:03 +02:00
hypnosiss
73e578b168 Bump pymysensors library version (#149632) 2025-07-29 22:29:53 +01:00
Arie Catsman
52ee5d53ee bump pyenphase to 2.2.3 (#149641) 2025-07-29 22:27:43 +01:00
Marc Mueller
62713b1371 Update pyblu to 2.0.4 (#149589) 2025-07-29 22:32:32 +02:00
Simone Chemelli
c4c4463c63 Update IQS for Alexa Devices (#149639) 2025-07-29 22:00:49 +02:00
Franck Nijhof
7e2fd6e47b Merge branch 'master' into dev 2025-07-29 18:52:18 +00:00
karwosts
9f45801409 Remove advanced mode from group all option. (#149626) 2025-07-29 20:03:27 +02:00
J. Nick Koston
aaec243bf4 Properly cleanup ONVIF events to prevent log flooding on setup errors (#149603) 2025-07-29 19:49:20 +02:00
Tom
b67e85e8da Introduce Ubiquiti UISP airOS (#148989)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-29 19:41:13 +02:00
J. Nick Koston
25407c0f4b Bump aiohttp to 3.12.15 (#149609) 2025-07-29 19:21:31 +02:00
Stefan Agner
09e7d8d1a5 Increase open file descriptor limit on startup (#148940)
Co-authored-by: Jan Čermák <sairon@sairon.cz>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-07-29 17:42:26 +02:00
Markus Adrario
ff7c125334 Upgrade Homee quality scale to silver (#149194) 2025-07-29 15:19:08 +02:00
Martin Hjelmare
3d6f868cbc Bump zwave-js-server-python to 0.67.0 (#149616) 2025-07-29 13:57:40 +02:00
Thomas D
378c3af9df Bump qbusmqttapi to 1.4.2 (#149622) 2025-07-29 13:51:32 +02:00
osohotwateriot
c7271d1af9 Add OSO Energy Custom Away Mode Service (#149612) 2025-07-29 13:50:31 +02:00
Klaas Schoute
87400c6a17 Bump odp-amsterdam to v6.1.2 (#149617) 2025-07-29 12:59:30 +02:00
Christopher Fenner
692a1119a6 Adjust suggested display precision on Volvo distance sensors (#149593) 2025-07-29 12:29:07 +02:00
Thomas55555
2e728eb7de Bump aioautomower to 2.1.1 (#149585) 2025-07-29 09:38:50 +02:00
Manu
45ec9c7dad Refactor coordinator setup in Iron OS (#149600) 2025-07-29 09:37:32 +02:00
Manu
62ee1fbc64 Remove unnecessary CONF_NAME usage in Habitica integration (#149595) 2025-07-29 08:55:32 +02:00
Jan-Philipp Benecke
3c1aa9d9de Make exceptions translatable in Tankerkoenig integration (#149611) 2025-07-29 08:52:42 +02:00
J. Nick Koston
bf568b22d7 Bump onvif-zeep-async to 4.0.2 (#149606) 2025-07-29 08:41:45 +02:00
Michael
596f6cd216 Add people and tags collections to Immich media source (#149340) 2025-07-28 23:21:04 +02:00
Åke Strandberg
cf05f1046d Add action to retrieve list of programs on miele appliance (#149307) 2025-07-28 22:19:51 +02:00
Thomas55555
7f9be420d2 Add details to Husqvarna Automower restricted reason sensor (#147678)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-07-28 19:54:54 +01:00
Abílio Costa
dda46e7e0b Use non-autospec mock in Reolink's remaining tests (#149565)
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
2025-07-28 18:38:06 +01:00
Michael
b1dd742a57 Move battery properties from legacy Ecovacs vacuum entity to separate entities (#149084) 2025-07-28 18:49:12 +02:00
Simone Chemelli
5af4290b77 Update IQS for Alexa Devices (#149440) 2025-07-28 18:33:39 +02:00
Petro31
8339516fb4 Add optimistic option to alarm control panel yaml (#149334) 2025-07-28 17:44:43 +02:00
Matrix
aa1314c1d5 Add YoLink YS6614 support. (#149153) 2025-07-28 17:43:20 +02:00
epenet
92ad922ddc Add fan mode support for Tuya air conditioner (aqoouq7x) (#149226) 2025-07-28 17:42:36 +02:00
epenet
e518e7beac Add service tests to Tuya select platform (#149156)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-28 17:42:18 +02:00
Thomas D
483d814a8f Add new Volvo integration (#142994)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-07-28 17:24:15 +02:00
Tom
8f795f021c Bump Plugwise to v1.7.8 preventing rogue KeyError (#149000) 2025-07-28 17:19:43 +02:00
Petro31
d823b574c0 Add optimistic option to light yaml (#149395) 2025-07-28 16:59:57 +02:00
Petro31
49bd15718c Add optimistic option to fan yaml (#149390) 2025-07-28 16:58:46 +02:00
Abílio Costa
d3f18c1678 Add quality scale to ring manifest (#149406) 2025-07-28 16:35:38 +02:00
Joost Lekkerkerker
5ef17c8588 Bump the required version of ruff to 0.12.1 (#149571) 2025-07-28 16:32:56 +02:00
Norbert Rittel
e8b8d31027 Make actions labels consistent for Template alarm control panel (#149574) 2025-07-28 16:31:13 +02:00
Manu
978ee3870c Add notify platform to PlayStation Network integration (#149557) 2025-07-28 16:18:57 +02:00
Petro31
b3862591ea Add optimism to vacuum platform (#149425) 2025-07-28 16:18:37 +02:00
Petro31
1895db0ddd Add optimistic option to switch yaml (#149402) 2025-07-28 16:17:39 +02:00
Petro31
ee2cf961f6 Add assumed optimistic functionality to lock platform (#149397) 2025-07-28 16:17:09 +02:00
Martin Hjelmare
9a364ec729 Fix Z-Wave removal of devices when connected to unknown controller (#149339) 2025-07-28 16:13:39 +02:00
starkillerOG
96529ec245 Add Reolink pre-recording entities (#149522)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 16:12:53 +02:00
David Knowles
8fc8220924 Teach Hydrawise to auto-add/remove devices (#149547)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-28 16:06:15 +02:00
osohotwateriot
386f709fd3 Osoenergy holiday mode services (#149430)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-28 16:00:22 +02:00
alvi kazi 🇧🇩
d088fccb88 VeSync: add support for LAP-V102S-WJP air purifier (#149102) 2025-07-28 15:51:07 +02:00
jennoian
2a5448835f Add Vacuum support to smartthings (#148724)
Co-authored-by: Joostlek <joostlek@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-28 15:37:37 +02:00
Avery
a71eecaaa4 Update datadog test logic (#149459)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-07-28 15:10:55 +02:00
hanwg
46d810b9f9 Better error handling when setting up config entry for Telegram bot (#149444) 2025-07-28 14:52:40 +02:00
Petro31
48c4240a5d Delete unused switch platform code (#149468) 2025-07-28 14:48:45 +02:00
wittypluck
bf05c23414 Update OpenWeatherMap config step description to clarify API key documentation (#146843) 2025-07-28 14:40:00 +02:00
Michael
db1e6a0d98 Add quality scale and set Silver for Tankerkoenig (#143418) 2025-07-28 14:34:27 +02:00
Assaf Inbal
4ad35e8421 Add charging binary sensor to ituran (#149562)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-28 13:18:43 +02:00
wollew
850e04d9aa Add binary sensor for rain detection for Velux windows that have them (#148275)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-28 13:15:59 +02:00
Manu
95c5a91f01 Refactor active session handling in PlaystationNetwork (#149559) 2025-07-28 13:13:08 +02:00
Petro31
140f56aeaa Add common translation strings (#149472)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-07-28 13:12:52 +02:00
Michael
40ce228c9c Add upload_file action to immich integration (#147295)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-07-28 13:12:16 +02:00
Abílio Costa
18c5437fe7 Revert "Make default title configurable in XMPP" (#149544) 2025-07-28 13:42:40 +03:00
Norbert Rittel
ebad1ff4cc Fix capitalization of "IP address" in goalzero (#149563) 2025-07-28 11:59:11 +02:00
Ludovic BOUÉ
a68e722c92 Matter MicrowaveOven device (#148219)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-07-28 11:33:03 +02:00
Joakim Sørensen
05935bbc01 Bump hass-nabucasa from 0.108.0 to 0.110.0 (#149560) 2025-07-28 11:17:26 +02:00
Assaf Inbal
c67636b4f6 Add support for EVs in ituran (#149484) 2025-07-28 10:35:52 +02:00
Franck Nijhof
777b3128bb 2025.7.4 (#149526) 2025-07-28 10:15:08 +02:00
Shai Ungar
ab6cd0eb41 Bump israel-rail to 0.1.3 (#149555) 2025-07-28 08:42:40 +02:00
Brett Adams
f35558413a Bump tesla-fleet-api to 1.2.3 (#149550) 2025-07-28 07:58:59 +02:00
Michael
e30d405625 Enable strict typing in Tankerkoenig (#149535) 2025-07-27 22:48:15 +02:00
Thomas55555
622cce03a1 Bump aioautomower to 2.1.0 (#149541) 2025-07-27 22:46:59 +02:00
Raphael Hehl
1fa9141ce1 Bump uiprotect to version 7.20.0 (#149533) 2025-07-27 21:52:53 +02:00
Norbert Rittel
a060f7486f Replace duplicated strings and fix "street name" in waze_travel_time (#149512) 2025-07-27 21:36:25 +03:00
Manu
dbb5730389 Increase trophy titles retrieval page size to 500 for PlayStation Network (#149528) 2025-07-27 21:35:01 +03:00
Michael
431b2aa1d5 Add data description strings to Tankerkoenig (#149519)
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-27 20:13:05 +02:00
Michael
c99d81a554 Set PARALLEL_UPDATES in Tankerkoenig platforms (#149518) 2025-07-27 20:02:24 +02:00
starkillerOG
ff4dc393cf Bump reolink-aio to 0.14.4 (#149521) 2025-07-27 20:00:50 +02:00
Franck Nijhof
d384bee576 Bump version to 2025.7.4 2025-07-27 17:35:19 +00:00
Manu
f0cb5d5480 Bump habiticalib to v0.4.1 (#149523) 2025-07-27 17:35:01 +00:00
jb101010-2
725799c73e Bump pysuezV2 to 2.0.7 (#149436) 2025-07-27 17:34:59 +00:00
Simone Chemelli
dc6d2e3e84 Bump aioamazondevices to 3.5.1 (#149385) 2025-07-27 17:34:09 +00:00
Alex Hermann
4a7d06a68a Update slixmpp to 1.10.0 (#149374) 2025-07-27 17:29:37 +00:00
Brett Adams
cd800da357 Update Tesla OAuth Server in Tesla Fleet (#149280) 2025-07-27 17:29:35 +00:00
Antoine Reversat
4c8ab8eb64 Add fan off mode to the supported fan modes to fujitsu_fglair (#149277) 2025-07-27 17:29:33 +00:00
Martin Hjelmare
60f4d29d60 Add Z-Wave USB migration confirm step (#149243) 2025-07-27 17:29:31 +00:00
Avi Miller
68b7d09476 Fix brightness_step and brightness_step_pct via lifx.set_state (#149217)
Signed-off-by: Avi Miller <me@dje.li>
2025-07-27 17:29:29 +00:00
jvmahon
c3eb6dea11 Fix Matter light get brightness (#149186) 2025-07-27 17:29:28 +00:00
David Knowles
f428ffde87 Bump pyschlage to 2025.7.2 (#149148) 2025-07-27 17:29:26 +00:00
hanwg
fa207860a0 Fix multiple webhook secrets for Telegram bot (#149103) 2025-07-27 17:29:24 +00:00
Allen Porter
959c3a8a99 Fix a bug in rainbird device migration that results in additional devices (#149078) 2025-07-27 17:29:23 +00:00
Marc Hörsken
254ccca4e5 Fix warning about failure to get action during setup phase (#148923) 2025-07-27 17:29:21 +00:00
AlCalzone
5b08724d81 Keep entities of dead Z-Wave devices available (#148611) 2025-07-27 17:29:20 +00:00
Marc Mueller
ea2b3b3ff3 Update ical + gcal-sync (#149413) 2025-07-27 19:22:01 +02:00
Alex Hermann
a33760bc1a Update slixmpp to 1.10.0 (#149374) 2025-07-27 19:18:00 +02:00
Manu
4ea7ad52b1 Bump habiticalib to v0.4.1 (#149523) 2025-07-27 19:09:13 +02:00
Manu
dac75d1902 Add update platform to Uptime Kuma (#148973) 2025-07-27 18:02:33 +02:00
petep0p
0e9ced3c00 Correct core Purpleair integration's RSSI sensor to use RSSI value rather than barometric pressure (#149418) 2025-07-27 07:13:31 -06:00
Norbert Rittel
22d0fbcbd2 Fix spelling of "its" in mqtt (#149517) 2025-07-27 14:39:21 +02:00
Abílio Costa
57b641b97d Use non-autospec mock in Reolink's media source, number, sensor and siren tests (#149396) 2025-07-27 12:43:48 +02:00
J. Nick Koston
27bd6d2e38 Bump aioesphomeapi to 37.1.2 (#149460) 2025-07-26 22:48:48 -10:00
Assaf Inbal
427e5d81df Bump pyituran to 0.1.5 (#149486) 2025-07-26 19:03:51 +03:00
Shay Levy
b6bd92ed19 Shelly entity device info code quality (#149477) 2025-07-26 17:08:08 +03:00
Florian von Garrel
7976729e76 Paperless-ngx: Retry setup on initialization error (#149476) 2025-07-26 14:19:33 +02:00
Shay Levy
5aa0d0dc81 Remove Shelly redundant device info assignment in Button class (#149469) 2025-07-26 14:32:51 +03:00
jb101010-2
e1501d7510 Bump pysuezV2 to 2.0.7 (#149436) 2025-07-26 13:38:38 +03:00
Norbert Rittel
be5109fddf Change spelling of "Favorite x" to intl. English in bang_olufsen (#149464) 2025-07-26 13:35:11 +03:00
Norbert Rittel
c5cf9b07b7 Replace HA alarm (control panel) states with references in risco (#149466) 2025-07-26 13:34:24 +03:00
Norbert Rittel
002b7c6789 Fix descriptions in home_connect.set_program_and_options action (#149462) 2025-07-26 09:47:26 +02:00
Paul Bottein
e017dc80a0 Allow to reorder members within a group (#149003) 2025-07-26 01:07:51 +02:00
Erik Montnemery
aab7381553 Add test of ConfigSubentryFlow._subentry_type (#147565) 2025-07-26 00:27:04 +02:00
Norbert Rittel
cbf4409db3 Fix inconsistent spelling of "Wi-Fi" in unifiprotect (#149311)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-07-25 09:51:01 -10:00
Raphael Hehl
56fb59e48e Unifiprotect refactor device description ID retrieval in tests (#149445) 2025-07-25 09:21:57 -10:00
rappenze
971bd56bee Add Z-Box Hub virtual integration (#146678) 2025-07-25 20:37:36 +02:00
Matt Zimmerman
b2710c1bce Add smarttub cover sensor (#139134)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-07-25 20:10:39 +02:00
Petro31
a069b59efc Transition template types from string to platform keys (#149434) 2025-07-25 19:55:40 +02:00
osohotwateriot
02eb1dd533 Bump pyosoenergyapi to 1.2.4 (#149439) 2025-07-25 19:30:58 +02:00
Thomas55555
b3130c7929 Bump aioautomower to 2.0.2 (#149441) 2025-07-25 19:29:40 +02:00
Norbert Rittel
aad1dbecb4 Fix spelling of "IP" and improve action descriptions in lcn (#149314) 2025-07-25 19:28:43 +02:00
jvmahon
65109ea000 Fix Matter light get brightness (#149186) 2025-07-25 19:09:58 +02:00
Marc Mueller
356ac74fa5 Update orjson to 3.11.1 (#149442) 2025-07-25 19:07:07 +02:00
Norbert Rittel
f3513f7f29 Add missing hyphen to "case-sensitive" in tplink (#149363) 2025-07-25 19:01:57 +02:00
Marc Mueller
4bbb94f43d Update coverage to 7.10.0 (#149412) 2025-07-25 15:05:20 +02:00
Jan Bouwhuis
c1fa721a57 Revert "Use OptionsFlowWithReload in mqtt" (#149431) 2025-07-25 15:03:44 +02:00
Norbert Rittel
e3ffb41650 Improve some option and state names in home_connect (#149373) 2025-07-25 13:52:01 +02:00
Shay Levy
123cce6d96 Add configuration URL and model details to Shelly sub device info (#149404) 2025-07-25 14:26:32 +03:00
Guido Schmitz
6920dec352 Rework devolo Home Control config flow (#147121) 2025-07-25 12:55:42 +02:00
Guido Schmitz
f7cc260336 Add quality scale for devolo Home Network (#131510)
Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>
2025-07-25 12:20:33 +02:00
osohotwateriot
b7da31a021 Bump pyosoenergyapi to 1.2.3 (#149422) 2025-07-25 12:15:42 +02:00
Kevin Stillhammer
95d4dc678c Add option traffic_mode in here_travel_time (#146676) 2025-07-25 12:14:36 +02:00
Álvaro Fernández Rojas
7e9da052ca Update aioairzone-cloud to v0.7.1 (#149388)
Signed-off-by: Álvaro Fernández Rojas <noltari@gmail.com>
2025-07-25 08:17:26 +02:00
Marc Mueller
59ece455d9 Update numpy to 2.3.2 (#149411) 2025-07-25 02:24:25 +02:00
Jake Martin
3ba144c8b2 Bump monzopy to 1.5.1 (#149410) 2025-07-25 02:38:48 +03:00
Bram Kragten
456f992b7e 2025.7.3 (#149024) 2025-07-22 10:30:09 +02:00
Franck Nijhof
0675e34c62 Bump version to 2025.7.3 2025-07-18 17:05:52 +00:00
Simone Chemelli
190c98f5a8 Bump aioamazondevices to 3.5.0 (#149011) 2025-07-18 17:04:01 +00:00
Jan Bouwhuis
c6bb26be89 Ignore MQTT sensor unit of measurement if it is an empty string (#149006) 2025-07-18 17:02:02 +00:00
J. Nick Koston
d57c5ffa8f Bump PySwitchbot to 0.68.2 (#148996) 2025-07-18 17:02:01 +00:00
Bram Kragten
68889e1790 Update frontend to 20250702.3 (#148994) 2025-07-18 17:02:00 +00:00
Joost Lekkerkerker
8fdc50a29f Pass Syncthru entry to coordinator (#148974) 2025-07-18 17:01:58 +00:00
Steven Looman
5656b4c20d Bump async-upnp-client to 0.45.0 (#148961) 2025-07-18 17:01:57 +00:00
Maciej Bieniek
b6edcc9422 Bump gios to version 6.1.2 (#148884) 2025-07-18 17:01:56 +00:00
Maciej Bieniek
7a3eb53453 Bump gios to version 6.1.1 (#148414) 2025-07-18 17:01:54 +00:00
Arie Catsman
11a2c73e8a Bump pyenphase to 2.2.2 (#148870) 2025-07-18 17:00:32 +00:00
Brett Adams
1644484c92 Fix button platform parent class in Teslemetry (#148863) 2025-07-18 17:00:30 +00:00
Pete Sage
8e0a89dc2f Add guard to prevent exception in Sonos Favorites (#148854) 2025-07-18 17:00:29 +00:00
Robert Resch
9e4b8df344 Use ffmpeg for generic cameras in go2rtc (#148818) 2025-07-18 17:00:28 +00:00
Brett Adams
69fdc1d269 Bump Tesla Fleet API to 1.2.2 (#148776) 2025-07-18 17:00:26 +00:00
Joost Lekkerkerker
56e0aa103d Bump pySmartThings to 3.2.8 (#148761) 2025-07-18 17:00:25 +00:00
Maciej Bieniek
caf0492009 Fix Shelly n_current sensor removal condition (#148740) 2025-07-18 17:00:24 +00:00
hahn-th
c6d0aad3d3 Handle connection issues after websocket reconnected in homematicip_cloud (#147731) 2025-07-18 17:00:22 +00:00
Franck Nijhof
1f59b735c6 2025.7.2 (#148725) 2025-07-14 13:12:29 +02:00
Franck Nijhof
87af9fc8ba Bump version to 2025.7.2 2025-07-14 10:30:35 +00:00
Simone Chemelli
691a0ca065 Bump aioamazondevices to 3.2.10 (#148709) 2025-07-14 10:27:45 +00:00
Shay Levy
80384b89a5 Bump aioshelly to 13.7.2 (#148706) 2025-07-14 10:25:24 +00:00
Jan Bouwhuis
f7672985ed Fix hide empty sections in mqtt subentry flows (#148692) 2025-07-14 10:25:23 +00:00
Christopher Fenner
d4374dbcc7 Bump PyViCare to 2.50.0 (#148679) 2025-07-14 10:25:22 +00:00
Brett Adams
c4ddcd64c8 Fix Charge Cable binary sensor in Teslemetry (#148675) 2025-07-14 10:25:21 +00:00
0xEF
c802430066 Bump nyt_games to 0.5.0 (#148654) 2025-07-14 10:25:20 +00:00
falconindy
649fbfc729 snoo: use correct value for right safety clip binary sensor (#148647) 2025-07-14 10:25:18 +00:00
Jan Bouwhuis
80c52ad8ea Fix - only enable AlexaModeController if at least one mode is offered (#148614) 2025-07-14 10:25:17 +00:00
Lưu Quang Vũ
150d4716fa Fix Google Cloud 504 Deadline Exceeded (#148589) 2025-07-14 10:25:16 +00:00
Bram Kragten
dc2736580f Update frontend to 20250702.2 (#148573) 2025-07-14 10:25:15 +00:00
J. Nick Koston
f1272ef513 Bump aiohttp to 3.12.14 (#148565) 2025-07-14 10:25:14 +00:00
Åke Strandberg
3c2fa023b4 Remove vg argument from miele auth flow (#148541) 2025-07-14 10:25:12 +00:00
Kristof Mariën
5cf5be8c9c Fix for Renson set Breeze fan speed (#148537) 2025-07-14 10:25:11 +00:00
Jan-Philipp Benecke
63b21fda1a Ensure response is fully read to prevent premature connection closure in rest command (#148532) 2025-07-14 10:25:10 +00:00
J. Diego Rodríguez Royo
d87379d083 Use the link to the issue instead of creating new issues at Home Connect (#148523) 2025-07-14 10:25:09 +00:00
J. Diego Rodríguez Royo
0990cef917 Add Home Connect resume command button when an appliance is paused (#148512) 2025-07-14 10:25:08 +00:00
Michael
962ad99c20 Add workaround for sub units without main device in AVM Fritz!SmartHome (#148507) 2025-07-14 10:25:07 +00:00
Michael
9c9836defd Bump aioimmich to 0.10.2 (#148503) 2025-07-14 10:25:06 +00:00
Jan Bouwhuis
e951fc401c Fix entity_id should be based on object_id the first time an entity is added (#148484) 2025-07-14 10:25:05 +00:00
Robert Resch
00e2a177a5 Revert "Deprecate hddtemp" (#148482) 2025-07-14 10:25:04 +00:00
Joakim Sørensen
b6d316c8f2 Bump hass-nabucasa from 0.105.0 to 0.106.0 (#148473) 2025-07-14 10:25:02 +00:00
Raphael Hehl
b8425de0d0 Bump uiprotect to version 7.14.2 (#148453) 2025-07-14 10:25:01 +00:00
Joost Lekkerkerker
d51a44acbc Bump pySmartThings to 3.2.7 (#148394) 2025-07-14 10:25:00 +00:00
Josef Zweck
435465e569 Bump pylamarzocco to 2.0.11 (#148386) 2025-07-14 10:24:59 +00:00
Josef Zweck
3b047859f9 Create own clientsession for lamarzocco (#148385) 2025-07-14 10:24:58 +00:00
Simone Chemelli
91cdf1a367 Bump aioamazondevices to 3.2.8 (#148365)
Co-authored-by: Joakim Plate <elupus@ecce.se>
2025-07-14 10:24:57 +00:00
Joakim Plate
2377b136f3 Handle binary coils with non default mappings in nibe heatpump (#148354) 2025-07-14 10:24:56 +00:00
Retha Runolfsson
186c4e7038 Bump pyswitchbot to 0.68.1 (#148335) 2025-07-14 10:24:55 +00:00
Samuel Xiao
d303a7d17e Fix Switchbot cloud plug mini current unit Issue (#148314) 2025-07-14 10:24:54 +00:00
jvits227
14f059c766 Add lamp states to smartthings selector (#148302)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-07-14 10:23:55 +00:00
Arie Catsman
4a10370932 Bump pyenphase to 2.2.1 (#148292) 2025-07-14 10:13:43 +00:00
J. Nick Koston
672ffa5984 Restore httpx compatibility for non-primitive REST query parameters (#148286) 2025-07-14 10:13:42 +00:00
Maciej Bieniek
3d3f2527cb Bump gios to version 6.1.0 (#148274) 2025-07-14 10:13:41 +00:00
Shay Levy
5c3b279f95 Bump aiowebostv to 0.7.4 (#148273) 2025-07-14 10:13:40 +00:00
starkillerOG
59bcf1167a bump motionblinds to 0.6.29 (#148265) 2025-07-14 10:13:39 +00:00
Mark Adkins
b4d789f8e2 Bump sharkiq to 1.1.1 (#148244) 2025-07-14 10:12:00 +00:00
Josef Zweck
f4ca56052b Bump pylamarzocco to 2.0.10 (#148233) 2025-07-14 10:11:59 +00:00
J. Nick Koston
74f9549431 Fix UTF-8 encoding for REST basic authentication (#148225) 2025-07-14 10:11:58 +00:00
J. Nick Koston
9650727515 Fix REST sensor charset handling to respect Content-Type header (#148223) 2025-07-14 10:11:57 +00:00
TimL
c965da6559 Bump pysmlight to v0.2.7 (#148101)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-07-14 10:09:19 +00:00
Sören Beye
9077965214 Squeezebox: Fix tracks not having thumbnails (#147187) 2025-07-14 10:09:18 +00:00
Sören Beye
2b7992e849 Squeezebox: Fix track selection in media browser (#147185) 2025-07-14 10:09:16 +00:00
Franck Nijhof
5d6b02f470 2025.7.1 (#148171) 2025-07-04 22:00:18 +02:00
Franck Nijhof
a274961593 Bump version to 2025.7.1 2025-07-04 19:22:41 +00:00
Michael Freeman
4e163c4591 Bump venstarcolortouch to 0.21 (#148152) 2025-07-04 19:21:33 +00:00
Marc Mueller
3ffec2a655 [ci] Fix typing issue with aiohttp and aiosignal (#148141) 2025-07-04 19:21:31 +00:00
Bram Kragten
c646658643 Update frontend to 20250702.1 (#148131) 2025-07-04 19:21:30 +00:00
Simone Chemelli
342b4c3442 Bump aioamazondevices to 3.2.3 (#148082) 2025-07-04 19:21:28 +00:00
Arie Catsman
eb58c10e5e Cancel enphase mac verification on unload. (#148072) 2025-07-04 19:21:27 +00:00
Arie Catsman
f42e7d982f Bump pyenphase to 2.2.0 (#148070) 2025-07-04 19:21:25 +00:00
hanwg
898ef43750 Fix Telegram bots using plain text parser failing to load on restart (#148050) 2025-07-04 19:21:24 +00:00
Joakim Sørensen
f806e6ba49 Bump hass-nabucasa from 0.104.0 to 0.105.0 (#148040) 2025-07-04 19:21:23 +00:00
Marcel van der Veldt
c23bfb1b39 Fix state being incorrectly reported in some situations on Music Assistant players (#147997) 2025-07-04 19:21:22 +00:00
Robert Svensson
a2ffe32b02 Bump aiounifi to v84 (#147987) 2025-07-04 19:21:21 +00:00
puddly
0f32b6331d Bump ZHA to 0.0.62 (#147966) 2025-07-04 19:21:19 +00:00
epenet
9a4959560e Fix missing port in samsungtv (#147962)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-07-04 19:21:18 +00:00
Thomas55555
41ab7b346c Set timeout for remote calendar (#147024) 2025-07-04 19:21:17 +00:00
952 changed files with 63153 additions and 7746 deletions

View File

@@ -190,7 +190,7 @@ jobs:
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to GitHub Container Registry
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -256,7 +256,7 @@ jobs:
fi
- name: Login to GitHub Container Registry
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -330,14 +330,14 @@ jobs:
- name: Login to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
if: matrix.registry == 'ghcr.io/home-assistant'
uses: docker/login-action@v3.4.0
uses: docker/login-action@v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -502,7 +502,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Login to GitHub Container Registry
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
uses: docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 4
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.8"
HA_SHORT_VERSION: "2025.9"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version

View File

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

View File

@@ -231,7 +231,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@v1.2.3
uses: actions/ai-inference@v1.2.7
with:
model: openai/gpt-4o
system-prompt: |

View File

@@ -57,7 +57,7 @@ jobs:
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@v1.2.3
uses: actions/ai-inference@v1.2.7
with:
model: openai/gpt-4o-mini
system-prompt: |

View File

@@ -159,7 +159,7 @@ jobs:
sed -i "/uv/d" requirements_diff.txt
- name: Build wheels
uses: home-assistant/wheels@2025.03.0
uses: home-assistant/wheels@2025.07.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
@@ -219,7 +219,7 @@ jobs:
sed -i "/uv/d" requirements_diff.txt
- name: Build wheels
uses: home-assistant/wheels@2025.03.0
uses: home-assistant/wheels@2025.07.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2

View File

@@ -53,6 +53,7 @@ homeassistant.components.air_quality.*
homeassistant.components.airgradient.*
homeassistant.components.airly.*
homeassistant.components.airnow.*
homeassistant.components.airos.*
homeassistant.components.airq.*
homeassistant.components.airthings.*
homeassistant.components.airthings_ble.*
@@ -501,6 +502,7 @@ homeassistant.components.tag.*
homeassistant.components.tailscale.*
homeassistant.components.tailwind.*
homeassistant.components.tami4.*
homeassistant.components.tankerkoenig.*
homeassistant.components.tautulli.*
homeassistant.components.tcp.*
homeassistant.components.technove.*
@@ -546,6 +548,7 @@ homeassistant.components.valve.*
homeassistant.components.velbus.*
homeassistant.components.vlc_telnet.*
homeassistant.components.vodafone_station.*
homeassistant.components.volvo.*
homeassistant.components.wake_on_lan.*
homeassistant.components.wake_word.*
homeassistant.components.wallbox.*

4
CODEOWNERS generated
View File

@@ -67,6 +67,8 @@ build.json @home-assistant/supervisor
/tests/components/airly/ @bieniu
/homeassistant/components/airnow/ @asymworks
/tests/components/airnow/ @asymworks
/homeassistant/components/airos/ @CoMPaTech
/tests/components/airos/ @CoMPaTech
/homeassistant/components/airq/ @Sibgatulin @dl2080
/tests/components/airq/ @Sibgatulin @dl2080
/homeassistant/components/airthings/ @danielhiversen @LaStrada
@@ -1706,6 +1708,8 @@ build.json @home-assistant/supervisor
/tests/components/voip/ @balloob @synesthesiam @jaminh
/homeassistant/components/volumio/ @OnFreund
/tests/components/volumio/ @OnFreund
/homeassistant/components/volvo/ @thomasddn
/tests/components/volvo/ @thomasddn
/homeassistant/components/volvooncall/ @molobrakos
/tests/components/volvooncall/ @molobrakos
/homeassistant/components/vulcan/ @Antoni-Czaplicki

View File

@@ -33,7 +33,10 @@ class AuthFlowContext(FlowContext, total=False):
redirect_uri: str
AuthFlowResult = FlowResult[AuthFlowContext, tuple[str, str]]
class AuthFlowResult(FlowResult[AuthFlowContext, tuple[str, str]], total=False):
"""Typed result dict for auth flow."""
result: Credentials # Only present if type is CREATE_ENTRY
@attr.s(slots=True)

View File

@@ -0,0 +1,5 @@
{
"domain": "frient",
"name": "Frient",
"iot_standards": ["zigbee"]
}

View File

@@ -1,5 +1,5 @@
{
"domain": "third_reality",
"name": "Third Reality",
"iot_standards": ["zigbee"]
"iot_standards": ["matter", "zigbee"]
}

View File

@@ -1,5 +1,5 @@
{
"domain": "ubiquiti",
"name": "Ubiquiti",
"integrations": ["unifi", "unifi_direct", "unifiled", "unifiprotect"]
"integrations": ["airos", "unifi", "unifi_direct", "unifiled", "unifiprotect"]
}

View File

@@ -0,0 +1,42 @@
"""The Ubiquiti airOS integration."""
from __future__ import annotations
from airos.airos8 import AirOS
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import AirOSConfigEntry, AirOSDataUpdateCoordinator
_PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Set up Ubiquiti airOS from a config entry."""
# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(hass, verify_ssl=False)
airos_device = AirOS(
host=entry.data[CONF_HOST],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
session=session,
)
coordinator = AirOSDataUpdateCoordinator(hass, entry, airos_device)
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: AirOSConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, _PLATFORMS)

View File

@@ -0,0 +1,82 @@
"""Config flow for the Ubiquiti airOS integration."""
from __future__ import annotations
import logging
from typing import Any
from airos.exceptions import (
AirOSConnectionAuthenticationError,
AirOSConnectionSetupError,
AirOSDataMissingError,
AirOSDeviceConnectionError,
AirOSKeyDataMissingError,
)
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import AirOS
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Required(CONF_USERNAME, default="ubnt"): str,
vol.Required(CONF_PASSWORD): str,
}
)
class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ubiquiti airOS."""
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:
# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(self.hass, verify_ssl=False)
airos_device = AirOS(
host=user_input[CONF_HOST],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
session=session,
)
try:
await airos_device.login()
airos_data = await airos_device.status()
except (
AirOSConnectionSetupError,
AirOSDeviceConnectionError,
):
errors["base"] = "cannot_connect"
except (AirOSConnectionAuthenticationError, AirOSDataMissingError):
errors["base"] = "invalid_auth"
except AirOSKeyDataMissingError:
errors["base"] = "key_data_missing"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(airos_data.derived.mac)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=airos_data.host.hostname, data=user_input
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)

View File

@@ -0,0 +1,9 @@
"""Constants for the Ubiquiti airOS integration."""
from datetime import timedelta
DOMAIN = "airos"
SCAN_INTERVAL = timedelta(minutes=1)
MANUFACTURER = "Ubiquiti"

View File

@@ -0,0 +1,70 @@
"""DataUpdateCoordinator for AirOS."""
from __future__ import annotations
import logging
from airos.airos8 import AirOS, AirOSData
from airos.exceptions import (
AirOSConnectionAuthenticationError,
AirOSConnectionSetupError,
AirOSDataMissingError,
AirOSDeviceConnectionError,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
type AirOSConfigEntry = ConfigEntry[AirOSDataUpdateCoordinator]
class AirOSDataUpdateCoordinator(DataUpdateCoordinator[AirOSData]):
"""Class to manage fetching AirOS data from single endpoint."""
config_entry: AirOSConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: AirOSConfigEntry, airos_device: AirOS
) -> None:
"""Initialize the coordinator."""
self.airos_device = airos_device
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
async def _async_update_data(self) -> AirOSData:
"""Fetch data from AirOS."""
try:
await self.airos_device.login()
return await self.airos_device.status()
except (AirOSConnectionAuthenticationError,) as err:
_LOGGER.exception("Error authenticating with airOS device")
raise ConfigEntryError(
translation_domain=DOMAIN, translation_key="invalid_auth"
) from err
except (
AirOSConnectionSetupError,
AirOSDeviceConnectionError,
TimeoutError,
) as err:
_LOGGER.error("Error connecting to airOS device: %s", err)
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="cannot_connect",
) from err
except (AirOSDataMissingError,) as err:
_LOGGER.error("Expected data not returned by airOS device: %s", err)
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="error_data_missing",
) from err

View File

@@ -0,0 +1,33 @@
"""Diagnostics support for airOS."""
from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_HOST, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from .coordinator import AirOSConfigEntry
IP_REDACT = ["addr", "ipaddr", "ip6addr", "lastip"] # IP related
HW_REDACT = ["apmac", "hwaddr", "mac"] # MAC address
TO_REDACT_HA = [CONF_HOST, CONF_PASSWORD]
TO_REDACT_AIROS = [
"hostname", # Prevent leaking device naming
"essid", # Network SSID
"lat", # GPS latitude to prevent exposing location data.
"lon", # GPS longitude to prevent exposing location data.
*HW_REDACT,
*IP_REDACT,
]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: AirOSConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
"entry_data": async_redact_data(entry.data, TO_REDACT_HA),
"data": async_redact_data(entry.runtime_data.data.to_dict(), TO_REDACT_AIROS),
}

View File

@@ -0,0 +1,36 @@
"""Generic AirOS Entity Class."""
from __future__ import annotations
from homeassistant.const import CONF_HOST
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER
from .coordinator import AirOSDataUpdateCoordinator
class AirOSEntity(CoordinatorEntity[AirOSDataUpdateCoordinator]):
"""Represent a AirOS Entity."""
_attr_has_entity_name = True
def __init__(self, coordinator: AirOSDataUpdateCoordinator) -> None:
"""Initialise the gateway."""
super().__init__(coordinator)
airos_data = self.coordinator.data
configuration_url: str | None = (
f"https://{coordinator.config_entry.data[CONF_HOST]}"
)
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, airos_data.derived.mac)},
configuration_url=configuration_url,
identifiers={(DOMAIN, str(airos_data.host.device_id))},
manufacturer=MANUFACTURER,
model=airos_data.host.devmodel,
name=airos_data.host.hostname,
sw_version=airos_data.host.fwversion,
)

View File

@@ -0,0 +1,10 @@
{
"domain": "airos",
"name": "Ubiquiti airOS",
"codeowners": ["@CoMPaTech"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airos",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["airos==0.2.4"]
}

View File

@@ -0,0 +1,72 @@
rules:
# Bronze
action-setup:
status: exempt
comment: airOS does not have actions
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: airOS does not have actions
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: local_polling without events
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: airOS does not have actions
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done
entity-unavailable: todo
integration-owner: done
log-when-unavailable: todo
parallel-updates: todo
reauthentication-flow: todo
test-coverage: done
# Gold
devices: done
diagnostics: done
discovery-update-info: todo
discovery: todo
docs-data-update: done
docs-examples: todo
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: todo
docs-troubleshooting: done
docs-use-cases: todo
dynamic-devices: todo
entity-category: done
entity-device-class: done
entity-disabled-by-default:
status: todo
comment: prepared binary_sensors will provide this
entity-translations: done
exception-translations: done
icon-translations:
status: exempt
comment: no (custom) icons used or envisioned
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo
# Platinum
async-dependency: done
inject-websession: done
strict-typing: done

View File

@@ -0,0 +1,145 @@
"""AirOS Sensor component for Home Assistant."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import logging
from airos.data import NetRole, WirelessMode
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS,
UnitOfDataRate,
UnitOfFrequency,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import AirOSConfigEntry, AirOSData, AirOSDataUpdateCoordinator
from .entity import AirOSEntity
_LOGGER = logging.getLogger(__name__)
WIRELESS_MODE_OPTIONS = [mode.value.replace("-", "_").lower() for mode in WirelessMode]
NETROLE_OPTIONS = [mode.value for mode in NetRole]
@dataclass(frozen=True, kw_only=True)
class AirOSSensorEntityDescription(SensorEntityDescription):
"""Describe an AirOS sensor."""
value_fn: Callable[[AirOSData], StateType]
SENSORS: tuple[AirOSSensorEntityDescription, ...] = (
AirOSSensorEntityDescription(
key="host_cpuload",
translation_key="host_cpuload",
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.host.cpuload,
entity_registry_enabled_default=False,
),
AirOSSensorEntityDescription(
key="host_netrole",
translation_key="host_netrole",
device_class=SensorDeviceClass.ENUM,
value_fn=lambda data: data.host.netrole.value,
options=NETROLE_OPTIONS,
),
AirOSSensorEntityDescription(
key="wireless_frequency",
translation_key="wireless_frequency",
native_unit_of_measurement=UnitOfFrequency.MEGAHERTZ,
device_class=SensorDeviceClass.FREQUENCY,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.frequency,
),
AirOSSensorEntityDescription(
key="wireless_essid",
translation_key="wireless_essid",
value_fn=lambda data: data.wireless.essid,
),
AirOSSensorEntityDescription(
key="wireless_antenna_gain",
translation_key="wireless_antenna_gain",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.antenna_gain,
),
AirOSSensorEntityDescription(
key="wireless_throughput_tx",
translation_key="wireless_throughput_tx",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.throughput.tx,
),
AirOSSensorEntityDescription(
key="wireless_throughput_rx",
translation_key="wireless_throughput_rx",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.throughput.rx,
),
AirOSSensorEntityDescription(
key="wireless_polling_dl_capacity",
translation_key="wireless_polling_dl_capacity",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.polling.dl_capacity,
),
AirOSSensorEntityDescription(
key="wireless_polling_ul_capacity",
translation_key="wireless_polling_ul_capacity",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.wireless.polling.ul_capacity,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AirOSConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the AirOS sensors from a config entry."""
coordinator = config_entry.runtime_data
async_add_entities(AirOSSensor(coordinator, description) for description in SENSORS)
class AirOSSensor(AirOSEntity, SensorEntity):
"""Representation of a Sensor."""
entity_description: AirOSSensorEntityDescription
def __init__(
self,
coordinator: AirOSDataUpdateCoordinator,
description: AirOSSensorEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = f"{coordinator.data.derived.mac}_{description.key}"
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.data)

View File

@@ -0,0 +1,80 @@
{
"config": {
"flow_title": "Ubiquiti airOS device",
"step": {
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"host": "IP address or hostname of the airOS device",
"username": "Administrator username for the airOS device, normally 'ubnt'",
"password": "Password configured through the UISP app or web interface"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"key_data_missing": "Expected data not returned from the device, check the documentation for supported devices",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"entity": {
"sensor": {
"host_cpuload": {
"name": "CPU load"
},
"host_netrole": {
"name": "Network role",
"state": {
"bridge": "Bridge",
"router": "Router"
}
},
"wireless_frequency": {
"name": "Wireless frequency"
},
"wireless_essid": {
"name": "Wireless SSID"
},
"wireless_antenna_gain": {
"name": "Antenna gain"
},
"wireless_throughput_tx": {
"name": "Throughput transmit (actual)"
},
"wireless_throughput_rx": {
"name": "Throughput receive (actual)"
},
"wireless_polling_dl_capacity": {
"name": "Download capacity"
},
"wireless_polling_ul_capacity": {
"name": "Upload capacity"
},
"wireless_remote_hostname": {
"name": "Remote hostname"
}
}
},
"exceptions": {
"invalid_auth": {
"message": "[%key:common::config_flow::error::invalid_auth%]"
},
"cannot_connect": {
"message": "[%key:common::config_flow::error::cannot_connect%]"
},
"key_data_missing": {
"message": "Key data not returned from device"
},
"error_data_missing": {
"message": "Data incomplete or missing"
}
}
}

View File

@@ -7,21 +7,18 @@ import logging
from airthings import Airthings
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_SECRET
from .coordinator import AirthingsDataUpdateCoordinator
from .coordinator import AirthingsConfigEntry, AirthingsDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.SENSOR]
SCAN_INTERVAL = timedelta(minutes=6)
type AirthingsConfigEntry = ConfigEntry[AirthingsDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) -> bool:
"""Set up Airthings from a config entry."""
@@ -31,7 +28,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) ->
async_get_clientsession(hass),
)
coordinator = AirthingsDataUpdateCoordinator(hass, airthings)
coordinator = AirthingsDataUpdateCoordinator(hass, airthings, entry)
await coordinator.async_config_entry_first_refresh()

View File

@@ -5,6 +5,7 @@ import logging
from airthings import Airthings, AirthingsDevice, AirthingsError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -13,15 +14,23 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=6)
type AirthingsConfigEntry = ConfigEntry[AirthingsDataUpdateCoordinator]
class AirthingsDataUpdateCoordinator(DataUpdateCoordinator[dict[str, AirthingsDevice]]):
"""Coordinator for Airthings data updates."""
def __init__(self, hass: HomeAssistant, airthings: Airthings) -> None:
def __init__(
self,
hass: HomeAssistant,
airthings: Airthings,
config_entry: AirthingsConfigEntry,
) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_method=self._update_method,
update_interval=SCAN_INTERVAL,

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.7.0"]
"requirements": ["aioairzone-cloud==0.7.1"]
}

View File

@@ -2,8 +2,12 @@
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client, config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN
from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator
from .services import async_setup_services
PLATFORMS = [
Platform.BINARY_SENSOR,
@@ -12,11 +16,20 @@ PLATFORMS = [
Platform.SWITCH,
]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Alexa Devices component."""
async_setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
"""Set up Alexa Devices platform."""
coordinator = AmazonDevicesCoordinator(hass, entry)
session = aiohttp_client.async_create_clientsession(hass)
coordinator = AmazonDevicesCoordinator(hass, entry, session)
await coordinator.async_config_entry_first_refresh()
@@ -29,8 +42,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bo
async def async_unload_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
"""Unload a config entry."""
coordinator = entry.runtime_data
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await coordinator.api.close()
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -17,6 +17,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import CountrySelector
@@ -33,18 +34,15 @@ STEP_REAUTH_DATA_SCHEMA = vol.Schema(
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
session = aiohttp_client.async_create_clientsession(hass)
api = AmazonEchoApi(
session,
data[CONF_COUNTRY],
data[CONF_USERNAME],
data[CONF_PASSWORD],
)
try:
data = await api.login_mode_interactive(data[CONF_CODE])
finally:
await api.close()
return data
return await api.login_mode_interactive(data[CONF_CODE])
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):

View File

@@ -8,6 +8,7 @@ from aioamazondevices.exceptions import (
CannotConnect,
CannotRetrieveData,
)
from aiohttp import ClientSession
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
@@ -31,6 +32,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
self,
hass: HomeAssistant,
entry: AmazonConfigEntry,
session: ClientSession,
) -> None:
"""Initialize the scanner."""
super().__init__(
@@ -41,6 +43,7 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
update_interval=timedelta(seconds=SCAN_INTERVAL),
)
self.api = AmazonEchoApi(
session,
entry.data[CONF_COUNTRY],
entry.data[CONF_USERNAME],
entry.data[CONF_PASSWORD],

View File

@@ -38,5 +38,13 @@
}
}
}
},
"services": {
"send_sound": {
"service": "mdi:cast-audio"
},
"send_text_command": {
"service": "mdi:microphone-message"
}
}
}

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "silver",
"requirements": ["aioamazondevices==3.5.1"]
"requirements": ["aioamazondevices==4.0.0"]
}

View File

@@ -48,17 +48,17 @@ rules:
comment: There are a ton of mac address ranges in use, but also by kindles which are not supported by this integration
docs-data-update: done
docs-examples: done
docs-known-limitations: todo
docs-known-limitations: done
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: todo
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: todo
entity-category: done
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: todo
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues:
@@ -70,5 +70,5 @@ rules:
# Platinum
async-dependency: done
inject-websession: todo
inject-websession: done
strict-typing: done

View File

@@ -0,0 +1,121 @@
"""Support for services."""
from aioamazondevices.sounds import SOUNDS_LIST
import voluptuous as vol
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_DEVICE_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv, device_registry as dr
from .const import DOMAIN
from .coordinator import AmazonConfigEntry
ATTR_TEXT_COMMAND = "text_command"
ATTR_SOUND = "sound"
ATTR_SOUND_VARIANT = "sound_variant"
SERVICE_TEXT_COMMAND = "send_text_command"
SERVICE_SOUND_NOTIFICATION = "send_sound"
SCHEMA_SOUND_SERVICE = vol.Schema(
{
vol.Required(ATTR_SOUND): cv.string,
vol.Required(ATTR_SOUND_VARIANT): cv.positive_int,
vol.Required(ATTR_DEVICE_ID): cv.string,
},
)
SCHEMA_CUSTOM_COMMAND = vol.Schema(
{
vol.Required(ATTR_TEXT_COMMAND): cv.string,
vol.Required(ATTR_DEVICE_ID): cv.string,
}
)
@callback
def async_get_entry_id_for_service_call(
call: ServiceCall,
) -> tuple[dr.DeviceEntry, AmazonConfigEntry]:
"""Get the entry ID related to a service call (by device ID)."""
device_registry = dr.async_get(call.hass)
device_id = call.data[ATTR_DEVICE_ID]
if (device_entry := device_registry.async_get(device_id)) is None:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_device_id",
translation_placeholders={"device_id": device_id},
)
for entry_id in device_entry.config_entries:
if (entry := call.hass.config_entries.async_get_entry(entry_id)) is None:
continue
if entry.domain == DOMAIN:
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="entry_not_loaded",
translation_placeholders={"entry": entry.title},
)
return (device_entry, entry)
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="config_entry_not_found",
translation_placeholders={"device_id": device_id},
)
async def _async_execute_action(call: ServiceCall, attribute: str) -> None:
"""Execute action on the device."""
device, config_entry = async_get_entry_id_for_service_call(call)
assert device.serial_number
value: str = call.data[attribute]
coordinator = config_entry.runtime_data
if attribute == ATTR_SOUND:
variant: int = call.data[ATTR_SOUND_VARIANT]
pad = "_" if variant > 10 else "_0"
file = f"{value}{pad}{variant!s}"
if value not in SOUNDS_LIST or variant > SOUNDS_LIST[value]:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_sound_value",
translation_placeholders={"sound": value, "variant": str(variant)},
)
await coordinator.api.call_alexa_sound(
coordinator.data[device.serial_number], file
)
elif attribute == ATTR_TEXT_COMMAND:
await coordinator.api.call_alexa_text_command(
coordinator.data[device.serial_number], value
)
async def async_send_sound_notification(call: ServiceCall) -> None:
"""Send a sound notification to a AmazonDevice."""
await _async_execute_action(call, ATTR_SOUND)
async def async_send_text_command(call: ServiceCall) -> None:
"""Send a custom command to a AmazonDevice."""
await _async_execute_action(call, ATTR_TEXT_COMMAND)
@callback
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,
async_send_sound_notification,
SCHEMA_SOUND_SERVICE,
),
(
SERVICE_TEXT_COMMAND,
async_send_text_command,
SCHEMA_CUSTOM_COMMAND,
),
):
hass.services.async_register(DOMAIN, service_name, method, schema=schema)

View File

@@ -0,0 +1,504 @@
send_text_command:
fields:
device_id:
required: true
selector:
device:
integration: alexa_devices
text_command:
required: true
example: "Play B.B.C. on TuneIn"
selector:
text:
send_sound:
fields:
device_id:
required: true
selector:
device:
integration: alexa_devices
sound_variant:
required: true
example: 1
default: 1
selector:
number:
min: 1
max: 50
sound:
required: true
example: amzn_sfx_doorbell_chime
default: amzn_sfx_doorbell_chime
selector:
select:
options:
- air_horn
- air_horns
- airboat
- airport
- aliens
- amzn_sfx_airplane_takeoff_whoosh
- amzn_sfx_army_march_clank_7x
- amzn_sfx_army_march_large_8x
- amzn_sfx_army_march_small_8x
- amzn_sfx_baby_big_cry
- amzn_sfx_baby_cry
- amzn_sfx_baby_fuss
- amzn_sfx_battle_group_clanks
- amzn_sfx_battle_man_grunts
- amzn_sfx_battle_men_grunts
- amzn_sfx_battle_men_horses
- amzn_sfx_battle_noisy_clanks
- amzn_sfx_battle_yells_men
- amzn_sfx_battle_yells_men_run
- amzn_sfx_bear_groan_roar
- amzn_sfx_bear_roar_grumble
- amzn_sfx_bear_roar_small
- amzn_sfx_beep_1x
- amzn_sfx_bell_med_chime
- amzn_sfx_bell_short_chime
- amzn_sfx_bell_timer
- amzn_sfx_bicycle_bell_ring
- amzn_sfx_bird_chickadee_chirp_1x
- amzn_sfx_bird_chickadee_chirps
- amzn_sfx_bird_forest
- amzn_sfx_bird_forest_short
- amzn_sfx_bird_robin_chirp_1x
- amzn_sfx_boing_long_1x
- amzn_sfx_boing_med_1x
- amzn_sfx_boing_short_1x
- amzn_sfx_bus_drive_past
- amzn_sfx_buzz_electronic
- amzn_sfx_buzzer_loud_alarm
- amzn_sfx_buzzer_small
- amzn_sfx_car_accelerate
- amzn_sfx_car_accelerate_noisy
- amzn_sfx_car_click_seatbelt
- amzn_sfx_car_close_door_1x
- amzn_sfx_car_drive_past
- amzn_sfx_car_honk_1x
- amzn_sfx_car_honk_2x
- amzn_sfx_car_honk_3x
- amzn_sfx_car_honk_long_1x
- amzn_sfx_car_into_driveway
- amzn_sfx_car_into_driveway_fast
- amzn_sfx_car_slam_door_1x
- amzn_sfx_car_undo_seatbelt
- amzn_sfx_cat_angry_meow_1x
- amzn_sfx_cat_angry_screech_1x
- amzn_sfx_cat_long_meow_1x
- amzn_sfx_cat_meow_1x
- amzn_sfx_cat_purr
- amzn_sfx_cat_purr_meow
- amzn_sfx_chicken_cluck
- amzn_sfx_church_bell_1x
- amzn_sfx_church_bells_ringing
- amzn_sfx_clear_throat_ahem
- amzn_sfx_clock_ticking
- amzn_sfx_clock_ticking_long
- amzn_sfx_copy_machine
- amzn_sfx_cough
- amzn_sfx_crow_caw_1x
- amzn_sfx_crowd_applause
- amzn_sfx_crowd_bar
- amzn_sfx_crowd_bar_rowdy
- amzn_sfx_crowd_boo
- amzn_sfx_crowd_cheer_med
- amzn_sfx_crowd_excited_cheer
- amzn_sfx_dog_med_bark_1x
- amzn_sfx_dog_med_bark_2x
- amzn_sfx_dog_med_bark_growl
- amzn_sfx_dog_med_growl_1x
- amzn_sfx_dog_med_woof_1x
- amzn_sfx_dog_small_bark_2x
- amzn_sfx_door_open
- amzn_sfx_door_shut
- amzn_sfx_doorbell
- amzn_sfx_doorbell_buzz
- amzn_sfx_doorbell_chime
- amzn_sfx_drinking_slurp
- amzn_sfx_drum_and_cymbal
- amzn_sfx_drum_comedy
- amzn_sfx_earthquake_rumble
- amzn_sfx_electric_guitar
- amzn_sfx_electronic_beep
- amzn_sfx_electronic_major_chord
- amzn_sfx_elephant
- amzn_sfx_elevator_bell_1x
- amzn_sfx_elevator_open_bell
- amzn_sfx_fairy_melodic_chimes
- amzn_sfx_fairy_sparkle_chimes
- amzn_sfx_faucet_drip
- amzn_sfx_faucet_running
- amzn_sfx_fireplace_crackle
- amzn_sfx_fireworks
- amzn_sfx_fireworks_firecrackers
- amzn_sfx_fireworks_launch
- amzn_sfx_fireworks_whistles
- amzn_sfx_food_frying
- amzn_sfx_footsteps
- amzn_sfx_footsteps_muffled
- amzn_sfx_ghost_spooky
- amzn_sfx_glass_on_table
- amzn_sfx_glasses_clink
- amzn_sfx_horse_gallop_4x
- amzn_sfx_horse_huff_whinny
- amzn_sfx_horse_neigh
- amzn_sfx_horse_neigh_low
- amzn_sfx_horse_whinny
- amzn_sfx_human_walking
- amzn_sfx_jar_on_table_1x
- amzn_sfx_kitchen_ambience
- amzn_sfx_large_crowd_cheer
- amzn_sfx_large_fire_crackling
- amzn_sfx_laughter
- amzn_sfx_laughter_giggle
- amzn_sfx_lightning_strike
- amzn_sfx_lion_roar
- amzn_sfx_magic_blast_1x
- amzn_sfx_monkey_calls_3x
- amzn_sfx_monkey_chimp
- amzn_sfx_monkeys_chatter
- amzn_sfx_motorcycle_accelerate
- amzn_sfx_motorcycle_engine_idle
- amzn_sfx_motorcycle_engine_rev
- amzn_sfx_musical_drone_intro
- amzn_sfx_oars_splashing_rowboat
- amzn_sfx_object_on_table_2x
- amzn_sfx_ocean_wave_1x
- amzn_sfx_ocean_wave_on_rocks_1x
- amzn_sfx_ocean_wave_surf
- amzn_sfx_people_walking
- amzn_sfx_person_running
- amzn_sfx_piano_note_1x
- amzn_sfx_punch
- amzn_sfx_rain
- amzn_sfx_rain_on_roof
- amzn_sfx_rain_thunder
- amzn_sfx_rat_squeak_2x
- amzn_sfx_rat_squeaks
- amzn_sfx_raven_caw_1x
- amzn_sfx_raven_caw_2x
- amzn_sfx_restaurant_ambience
- amzn_sfx_rooster_crow
- amzn_sfx_scifi_air_escaping
- amzn_sfx_scifi_alarm
- amzn_sfx_scifi_alien_voice
- amzn_sfx_scifi_boots_walking
- amzn_sfx_scifi_close_large_explosion
- amzn_sfx_scifi_door_open
- amzn_sfx_scifi_engines_on
- amzn_sfx_scifi_engines_on_large
- amzn_sfx_scifi_engines_on_short_burst
- amzn_sfx_scifi_explosion
- amzn_sfx_scifi_explosion_2x
- amzn_sfx_scifi_incoming_explosion
- amzn_sfx_scifi_laser_gun_battle
- amzn_sfx_scifi_laser_gun_fires
- amzn_sfx_scifi_laser_gun_fires_large
- amzn_sfx_scifi_long_explosion_1x
- amzn_sfx_scifi_missile
- amzn_sfx_scifi_motor_short_1x
- amzn_sfx_scifi_open_airlock
- amzn_sfx_scifi_radar_high_ping
- amzn_sfx_scifi_radar_low
- amzn_sfx_scifi_radar_medium
- amzn_sfx_scifi_run_away
- amzn_sfx_scifi_sheilds_up
- amzn_sfx_scifi_short_low_explosion
- amzn_sfx_scifi_small_whoosh_flyby
- amzn_sfx_scifi_small_zoom_flyby
- amzn_sfx_scifi_sonar_ping_3x
- amzn_sfx_scifi_sonar_ping_4x
- amzn_sfx_scifi_spaceship_flyby
- amzn_sfx_scifi_timer_beep
- amzn_sfx_scifi_zap_backwards
- amzn_sfx_scifi_zap_electric
- amzn_sfx_sheep_baa
- amzn_sfx_sheep_bleat
- amzn_sfx_silverware_clank
- amzn_sfx_sirens
- amzn_sfx_sleigh_bells
- amzn_sfx_small_stream
- amzn_sfx_sneeze
- amzn_sfx_stream
- amzn_sfx_strong_wind_desert
- amzn_sfx_strong_wind_whistling
- amzn_sfx_subway_leaving
- amzn_sfx_subway_passing
- amzn_sfx_subway_stopping
- amzn_sfx_swoosh_cartoon_fast
- amzn_sfx_swoosh_fast_1x
- amzn_sfx_swoosh_fast_6x
- amzn_sfx_test_tone
- amzn_sfx_thunder_rumble
- amzn_sfx_toilet_flush
- amzn_sfx_trumpet_bugle
- amzn_sfx_turkey_gobbling
- amzn_sfx_typing_medium
- amzn_sfx_typing_short
- amzn_sfx_typing_typewriter
- amzn_sfx_vacuum_off
- amzn_sfx_vacuum_on
- amzn_sfx_walking_in_mud
- amzn_sfx_walking_in_snow
- amzn_sfx_walking_on_grass
- amzn_sfx_water_dripping
- amzn_sfx_water_droplets
- amzn_sfx_wind_strong_gusting
- amzn_sfx_wind_whistling_desert
- amzn_sfx_wings_flap_4x
- amzn_sfx_wings_flap_fast
- amzn_sfx_wolf_howl
- amzn_sfx_wolf_young_howl
- amzn_sfx_wooden_door
- amzn_sfx_wooden_door_creaks_long
- amzn_sfx_wooden_door_creaks_multiple
- amzn_sfx_wooden_door_creaks_open
- amzn_ui_sfx_gameshow_bridge
- amzn_ui_sfx_gameshow_countdown_loop_32s_full
- amzn_ui_sfx_gameshow_countdown_loop_64s_full
- amzn_ui_sfx_gameshow_countdown_loop_64s_minimal
- amzn_ui_sfx_gameshow_intro
- amzn_ui_sfx_gameshow_negative_response
- amzn_ui_sfx_gameshow_neutral_response
- amzn_ui_sfx_gameshow_outro
- amzn_ui_sfx_gameshow_player1
- amzn_ui_sfx_gameshow_player2
- amzn_ui_sfx_gameshow_player3
- amzn_ui_sfx_gameshow_player4
- amzn_ui_sfx_gameshow_positive_response
- amzn_ui_sfx_gameshow_tally_negative
- amzn_ui_sfx_gameshow_tally_positive
- amzn_ui_sfx_gameshow_waiting_loop_30s
- anchor
- answering_machines
- arcs_sparks
- arrows_bows
- baby
- back_up_beeps
- bars_restaurants
- baseball
- basketball
- battles
- beeps_tones
- bell
- bikes
- billiards
- board_games
- body
- boing
- books
- bow_wash
- box
- break_shatter_smash
- breaks
- brooms_mops
- bullets
- buses
- buzz
- buzz_hums
- buzzers
- buzzers_pistols
- cables_metal
- camera
- cannons
- car_alarm
- car_alarms
- car_cell_phones
- carnivals_fairs
- cars
- casino
- casinos
- cellar
- chimes
- chimes_bells
- chorus
- christmas
- church_bells
- clock
- cloth
- concrete
- construction
- construction_factory
- crashes
- crowds
- debris
- dining_kitchens
- dinosaurs
- dripping
- drops
- electric
- electrical
- elevator
- evolution_monsters
- explosions
- factory
- falls
- fax_scanner_copier
- feedback_mics
- fight
- fire
- fire_extinguisher
- fireballs
- fireworks
- fishing_pole
- flags
- football
- footsteps
- futuristic
- futuristic_ship
- gameshow
- gear
- ghosts_demons
- giant_monster
- glass
- glasses_clink
- golf
- gorilla
- grenade_lanucher
- griffen
- gyms_locker_rooms
- handgun_loading
- handgun_shot
- handle
- hands
- heartbeats_ekg
- helicopter
- high_tech
- hit_punch_slap
- hits
- horns
- horror
- hot_tub_filling_up
- human
- human_vocals
- hygene # codespell:ignore
- ice_skating
- ignitions
- infantry
- intro
- jet
- juggling
- key_lock
- kids
- knocks
- lab_equip
- lacrosse
- lamps_lanterns
- leather
- liquid_suction
- locker_doors
- machine_gun
- magic_spells
- medium_large_explosions
- metal
- modern_rings
- money_coins
- motorcycles
- movement
- moves
- nature
- oar_boat
- pagers
- paintball
- paper
- parachute
- pay_phones
- phone_beeps
- pigmy_bats
- pills
- pour_water
- power_up_down
- printers
- prison
- public_space
- racquetball
- radios_static
- rain
- rc_airplane
- rc_car
- refrigerators_freezers
- regular
- respirator
- rifle
- roller_coaster
- rollerskates_rollerblades
- room_tones
- ropes_climbing
- rotary_rings
- rowboat_canoe
- rubber
- running
- sails
- sand_gravel
- screen_doors
- screens
- seats_stools
- servos
- shoes_boots
- shotgun
- shower
- sink_faucet
- sink_filling_water
- sink_run_and_off
- sink_water_splatter
- sirens
- skateboards
- ski
- skids_tires
- sled
- slides
- small_explosions
- snow
- snowmobile
- soldiers
- splash_water
- splashes_sprays
- sports_whistles
- squeaks
- squeaky
- stairs
- steam
- submarine_diesel
- swing_doors
- switches_levers
- swords
- tape
- tape_machine
- televisions_shows
- tennis_pingpong
- textile
- throw
- thunder
- ticks
- timer
- toilet_flush
- tone
- tones_noises
- toys
- tractors
- traffic
- train
- trucks_vans
- turnstiles
- typing
- umbrella
- underwater
- vampires
- various
- video_tunes
- volcano_earthquake
- watches
- water
- water_running
- werewolves
- winches_gears
- wind
- wood
- wood_boat
- woosh
- zap
- zippers
translation_key: sound

View File

@@ -4,7 +4,8 @@
"data_description_country": "The country where your Amazon account is registered.",
"data_description_username": "The email address of your Amazon account.",
"data_description_password": "The password of your Amazon account.",
"data_description_code": "The one-time password to log in to your account. Currently, only tokens from OTP applications are supported."
"data_description_code": "The one-time password to log in to your account. Currently, only tokens from OTP applications are supported.",
"device_id_description": "The ID of the device to send the command to."
},
"config": {
"flow_title": "{username}",
@@ -84,12 +85,532 @@
}
}
},
"services": {
"send_sound": {
"name": "Send sound",
"description": "Sends a sound to a device",
"fields": {
"device_id": {
"name": "Device",
"description": "[%key:component::alexa_devices::common::device_id_description%]"
},
"sound": {
"name": "Alexa Skill sound file",
"description": "The sound file to play."
},
"sound_variant": {
"name": "Sound variant",
"description": "The variant of the sound to play."
}
}
},
"send_text_command": {
"name": "Send text command",
"description": "Sends a text command to a device",
"fields": {
"text_command": {
"name": "Alexa text command",
"description": "The text command to send."
},
"device_id": {
"name": "Device",
"description": "[%key:component::alexa_devices::common::device_id_description%]"
}
}
}
},
"selector": {
"sound": {
"options": {
"air_horn": "Air Horn",
"air_horns": "Air Horns",
"airboat": "Airboat",
"airport": "Airport",
"aliens": "Aliens",
"amzn_sfx_airplane_takeoff_whoosh": "Airplane Takeoff Whoosh",
"amzn_sfx_army_march_clank_7x": "Army March Clank 7x",
"amzn_sfx_army_march_large_8x": "Army March Large 8x",
"amzn_sfx_army_march_small_8x": "Army March Small 8x",
"amzn_sfx_baby_big_cry": "Baby Big Cry",
"amzn_sfx_baby_cry": "Baby Cry",
"amzn_sfx_baby_fuss": "Baby Fuss",
"amzn_sfx_battle_group_clanks": "Battle Group Clanks",
"amzn_sfx_battle_man_grunts": "Battle Man Grunts",
"amzn_sfx_battle_men_grunts": "Battle Men Grunts",
"amzn_sfx_battle_men_horses": "Battle Men Horses",
"amzn_sfx_battle_noisy_clanks": "Battle Noisy Clanks",
"amzn_sfx_battle_yells_men": "Battle Yells Men",
"amzn_sfx_battle_yells_men_run": "Battle Yells Men Run",
"amzn_sfx_bear_groan_roar": "Bear Groan Roar",
"amzn_sfx_bear_roar_grumble": "Bear Roar Grumble",
"amzn_sfx_bear_roar_small": "Bear Roar Small",
"amzn_sfx_beep_1x": "Beep 1x",
"amzn_sfx_bell_med_chime": "Bell Med Chime",
"amzn_sfx_bell_short_chime": "Bell Short Chime",
"amzn_sfx_bell_timer": "Bell Timer",
"amzn_sfx_bicycle_bell_ring": "Bicycle Bell Ring",
"amzn_sfx_bird_chickadee_chirp_1x": "Bird Chickadee Chirp 1x",
"amzn_sfx_bird_chickadee_chirps": "Bird Chickadee Chirps",
"amzn_sfx_bird_forest": "Bird Forest",
"amzn_sfx_bird_forest_short": "Bird Forest Short",
"amzn_sfx_bird_robin_chirp_1x": "Bird Robin Chirp 1x",
"amzn_sfx_boing_long_1x": "Boing Long 1x",
"amzn_sfx_boing_med_1x": "Boing Med 1x",
"amzn_sfx_boing_short_1x": "Boing Short 1x",
"amzn_sfx_bus_drive_past": "Bus Drive Past",
"amzn_sfx_buzz_electronic": "Buzz Electronic",
"amzn_sfx_buzzer_loud_alarm": "Buzzer Loud Alarm",
"amzn_sfx_buzzer_small": "Buzzer Small",
"amzn_sfx_car_accelerate": "Car Accelerate",
"amzn_sfx_car_accelerate_noisy": "Car Accelerate Noisy",
"amzn_sfx_car_click_seatbelt": "Car Click Seatbelt",
"amzn_sfx_car_close_door_1x": "Car Close Door 1x",
"amzn_sfx_car_drive_past": "Car Drive Past",
"amzn_sfx_car_honk_1x": "Car Honk 1x",
"amzn_sfx_car_honk_2x": "Car Honk 2x",
"amzn_sfx_car_honk_3x": "Car Honk 3x",
"amzn_sfx_car_honk_long_1x": "Car Honk Long 1x",
"amzn_sfx_car_into_driveway": "Car Into Driveway",
"amzn_sfx_car_into_driveway_fast": "Car Into Driveway Fast",
"amzn_sfx_car_slam_door_1x": "Car Slam Door 1x",
"amzn_sfx_car_undo_seatbelt": "Car Undo Seatbelt",
"amzn_sfx_cat_angry_meow_1x": "Cat Angry Meow 1x",
"amzn_sfx_cat_angry_screech_1x": "Cat Angry Screech 1x",
"amzn_sfx_cat_long_meow_1x": "Cat Long Meow 1x",
"amzn_sfx_cat_meow_1x": "Cat Meow 1x",
"amzn_sfx_cat_purr": "Cat Purr",
"amzn_sfx_cat_purr_meow": "Cat Purr Meow",
"amzn_sfx_chicken_cluck": "Chicken Cluck",
"amzn_sfx_church_bell_1x": "Church Bell 1x",
"amzn_sfx_church_bells_ringing": "Church Bells Ringing",
"amzn_sfx_clear_throat_ahem": "Clear Throat Ahem",
"amzn_sfx_clock_ticking": "Clock Ticking",
"amzn_sfx_clock_ticking_long": "Clock Ticking Long",
"amzn_sfx_copy_machine": "Copy Machine",
"amzn_sfx_cough": "Cough",
"amzn_sfx_crow_caw_1x": "Crow Caw 1x",
"amzn_sfx_crowd_applause": "Crowd Applause",
"amzn_sfx_crowd_bar": "Crowd Bar",
"amzn_sfx_crowd_bar_rowdy": "Crowd Bar Rowdy",
"amzn_sfx_crowd_boo": "Crowd Boo",
"amzn_sfx_crowd_cheer_med": "Crowd Cheer Med",
"amzn_sfx_crowd_excited_cheer": "Crowd Excited Cheer",
"amzn_sfx_dog_med_bark_1x": "Dog Med Bark 1x",
"amzn_sfx_dog_med_bark_2x": "Dog Med Bark 2x",
"amzn_sfx_dog_med_bark_growl": "Dog Med Bark Growl",
"amzn_sfx_dog_med_growl_1x": "Dog Med Growl 1x",
"amzn_sfx_dog_med_woof_1x": "Dog Med Woof 1x",
"amzn_sfx_dog_small_bark_2x": "Dog Small Bark 2x",
"amzn_sfx_door_open": "Door Open",
"amzn_sfx_door_shut": "Door Shut",
"amzn_sfx_doorbell": "Doorbell",
"amzn_sfx_doorbell_buzz": "Doorbell Buzz",
"amzn_sfx_doorbell_chime": "Doorbell Chime",
"amzn_sfx_drinking_slurp": "Drinking Slurp",
"amzn_sfx_drum_and_cymbal": "Drum And Cymbal",
"amzn_sfx_drum_comedy": "Drum Comedy",
"amzn_sfx_earthquake_rumble": "Earthquake Rumble",
"amzn_sfx_electric_guitar": "Electric Guitar",
"amzn_sfx_electronic_beep": "Electronic Beep",
"amzn_sfx_electronic_major_chord": "Electronic Major Chord",
"amzn_sfx_elephant": "Elephant",
"amzn_sfx_elevator_bell_1x": "Elevator Bell 1x",
"amzn_sfx_elevator_open_bell": "Elevator Open Bell",
"amzn_sfx_fairy_melodic_chimes": "Fairy Melodic Chimes",
"amzn_sfx_fairy_sparkle_chimes": "Fairy Sparkle Chimes",
"amzn_sfx_faucet_drip": "Faucet Drip",
"amzn_sfx_faucet_running": "Faucet Running",
"amzn_sfx_fireplace_crackle": "Fireplace Crackle",
"amzn_sfx_fireworks": "Fireworks",
"amzn_sfx_fireworks_firecrackers": "Fireworks Firecrackers",
"amzn_sfx_fireworks_launch": "Fireworks Launch",
"amzn_sfx_fireworks_whistles": "Fireworks Whistles",
"amzn_sfx_food_frying": "Food Frying",
"amzn_sfx_footsteps": "Footsteps",
"amzn_sfx_footsteps_muffled": "Footsteps Muffled",
"amzn_sfx_ghost_spooky": "Ghost Spooky",
"amzn_sfx_glass_on_table": "Glass On Table",
"amzn_sfx_glasses_clink": "Glasses Clink",
"amzn_sfx_horse_gallop_4x": "Horse Gallop 4x",
"amzn_sfx_horse_huff_whinny": "Horse Huff Whinny",
"amzn_sfx_horse_neigh": "Horse Neigh",
"amzn_sfx_horse_neigh_low": "Horse Neigh Low",
"amzn_sfx_horse_whinny": "Horse Whinny",
"amzn_sfx_human_walking": "Human Walking",
"amzn_sfx_jar_on_table_1x": "Jar On Table 1x",
"amzn_sfx_kitchen_ambience": "Kitchen Ambience",
"amzn_sfx_large_crowd_cheer": "Large Crowd Cheer",
"amzn_sfx_large_fire_crackling": "Large Fire Crackling",
"amzn_sfx_laughter": "Laughter",
"amzn_sfx_laughter_giggle": "Laughter Giggle",
"amzn_sfx_lightning_strike": "Lightning Strike",
"amzn_sfx_lion_roar": "Lion Roar",
"amzn_sfx_magic_blast_1x": "Magic Blast 1x",
"amzn_sfx_monkey_calls_3x": "Monkey Calls 3x",
"amzn_sfx_monkey_chimp": "Monkey Chimp",
"amzn_sfx_monkeys_chatter": "Monkeys Chatter",
"amzn_sfx_motorcycle_accelerate": "Motorcycle Accelerate",
"amzn_sfx_motorcycle_engine_idle": "Motorcycle Engine Idle",
"amzn_sfx_motorcycle_engine_rev": "Motorcycle Engine Rev",
"amzn_sfx_musical_drone_intro": "Musical Drone Intro",
"amzn_sfx_oars_splashing_rowboat": "Oars Splashing Rowboat",
"amzn_sfx_object_on_table_2x": "Object On Table 2x",
"amzn_sfx_ocean_wave_1x": "Ocean Wave 1x",
"amzn_sfx_ocean_wave_on_rocks_1x": "Ocean Wave On Rocks 1x",
"amzn_sfx_ocean_wave_surf": "Ocean Wave Surf",
"amzn_sfx_people_walking": "People Walking",
"amzn_sfx_person_running": "Person Running",
"amzn_sfx_piano_note_1x": "Piano Note 1x",
"amzn_sfx_punch": "Punch",
"amzn_sfx_rain": "Rain",
"amzn_sfx_rain_on_roof": "Rain On Roof",
"amzn_sfx_rain_thunder": "Rain Thunder",
"amzn_sfx_rat_squeak_2x": "Rat Squeak 2x",
"amzn_sfx_rat_squeaks": "Rat Squeaks",
"amzn_sfx_raven_caw_1x": "Raven Caw 1x",
"amzn_sfx_raven_caw_2x": "Raven Caw 2x",
"amzn_sfx_restaurant_ambience": "Restaurant Ambience",
"amzn_sfx_rooster_crow": "Rooster Crow",
"amzn_sfx_scifi_air_escaping": "Scifi Air Escaping",
"amzn_sfx_scifi_alarm": "Scifi Alarm",
"amzn_sfx_scifi_alien_voice": "Scifi Alien Voice",
"amzn_sfx_scifi_boots_walking": "Scifi Boots Walking",
"amzn_sfx_scifi_close_large_explosion": "Scifi Close Large Explosion",
"amzn_sfx_scifi_door_open": "Scifi Door Open",
"amzn_sfx_scifi_engines_on": "Scifi Engines On",
"amzn_sfx_scifi_engines_on_large": "Scifi Engines On Large",
"amzn_sfx_scifi_engines_on_short_burst": "Scifi Engines On Short Burst",
"amzn_sfx_scifi_explosion": "Scifi Explosion",
"amzn_sfx_scifi_explosion_2x": "Scifi Explosion 2x",
"amzn_sfx_scifi_incoming_explosion": "Scifi Incoming Explosion",
"amzn_sfx_scifi_laser_gun_battle": "Scifi Laser Gun Battle",
"amzn_sfx_scifi_laser_gun_fires": "Scifi Laser Gun Fires",
"amzn_sfx_scifi_laser_gun_fires_large": "Scifi Laser Gun Fires Large",
"amzn_sfx_scifi_long_explosion_1x": "Scifi Long Explosion 1x",
"amzn_sfx_scifi_missile": "Scifi Missile",
"amzn_sfx_scifi_motor_short_1x": "Scifi Motor Short 1x",
"amzn_sfx_scifi_open_airlock": "Scifi Open Airlock",
"amzn_sfx_scifi_radar_high_ping": "Scifi Radar High Ping",
"amzn_sfx_scifi_radar_low": "Scifi Radar Low",
"amzn_sfx_scifi_radar_medium": "Scifi Radar Medium",
"amzn_sfx_scifi_run_away": "Scifi Run Away",
"amzn_sfx_scifi_sheilds_up": "Scifi Sheilds Up",
"amzn_sfx_scifi_short_low_explosion": "Scifi Short Low Explosion",
"amzn_sfx_scifi_small_whoosh_flyby": "Scifi Small Whoosh Flyby",
"amzn_sfx_scifi_small_zoom_flyby": "Scifi Small Zoom Flyby",
"amzn_sfx_scifi_sonar_ping_3x": "Scifi Sonar Ping 3x",
"amzn_sfx_scifi_sonar_ping_4x": "Scifi Sonar Ping 4x",
"amzn_sfx_scifi_spaceship_flyby": "Scifi Spaceship Flyby",
"amzn_sfx_scifi_timer_beep": "Scifi Timer Beep",
"amzn_sfx_scifi_zap_backwards": "Scifi Zap Backwards",
"amzn_sfx_scifi_zap_electric": "Scifi Zap Electric",
"amzn_sfx_sheep_baa": "Sheep Baa",
"amzn_sfx_sheep_bleat": "Sheep Bleat",
"amzn_sfx_silverware_clank": "Silverware Clank",
"amzn_sfx_sirens": "Sirens",
"amzn_sfx_sleigh_bells": "Sleigh Bells",
"amzn_sfx_small_stream": "Small Stream",
"amzn_sfx_sneeze": "Sneeze",
"amzn_sfx_stream": "Stream",
"amzn_sfx_strong_wind_desert": "Strong Wind Desert",
"amzn_sfx_strong_wind_whistling": "Strong Wind Whistling",
"amzn_sfx_subway_leaving": "Subway Leaving",
"amzn_sfx_subway_passing": "Subway Passing",
"amzn_sfx_subway_stopping": "Subway Stopping",
"amzn_sfx_swoosh_cartoon_fast": "Swoosh Cartoon Fast",
"amzn_sfx_swoosh_fast_1x": "Swoosh Fast 1x",
"amzn_sfx_swoosh_fast_6x": "Swoosh Fast 6x",
"amzn_sfx_test_tone": "Test Tone",
"amzn_sfx_thunder_rumble": "Thunder Rumble",
"amzn_sfx_toilet_flush": "Toilet Flush",
"amzn_sfx_trumpet_bugle": "Trumpet Bugle",
"amzn_sfx_turkey_gobbling": "Turkey Gobbling",
"amzn_sfx_typing_medium": "Typing Medium",
"amzn_sfx_typing_short": "Typing Short",
"amzn_sfx_typing_typewriter": "Typing Typewriter",
"amzn_sfx_vacuum_off": "Vacuum Off",
"amzn_sfx_vacuum_on": "Vacuum On",
"amzn_sfx_walking_in_mud": "Walking In Mud",
"amzn_sfx_walking_in_snow": "Walking In Snow",
"amzn_sfx_walking_on_grass": "Walking On Grass",
"amzn_sfx_water_dripping": "Water Dripping",
"amzn_sfx_water_droplets": "Water Droplets",
"amzn_sfx_wind_strong_gusting": "Wind Strong Gusting",
"amzn_sfx_wind_whistling_desert": "Wind Whistling Desert",
"amzn_sfx_wings_flap_4x": "Wings Flap 4x",
"amzn_sfx_wings_flap_fast": "Wings Flap Fast",
"amzn_sfx_wolf_howl": "Wolf Howl",
"amzn_sfx_wolf_young_howl": "Wolf Young Howl",
"amzn_sfx_wooden_door": "Wooden Door",
"amzn_sfx_wooden_door_creaks_long": "Wooden Door Creaks Long",
"amzn_sfx_wooden_door_creaks_multiple": "Wooden Door Creaks Multiple",
"amzn_sfx_wooden_door_creaks_open": "Wooden Door Creaks Open",
"amzn_ui_sfx_gameshow_bridge": "Gameshow Bridge",
"amzn_ui_sfx_gameshow_countdown_loop_32s_full": "Gameshow Countdown Loop 32s Full",
"amzn_ui_sfx_gameshow_countdown_loop_64s_full": "Gameshow Countdown Loop 64s Full",
"amzn_ui_sfx_gameshow_countdown_loop_64s_minimal": "Gameshow Countdown Loop 64s Minimal",
"amzn_ui_sfx_gameshow_intro": "Gameshow Intro",
"amzn_ui_sfx_gameshow_negative_response": "Gameshow Negative Response",
"amzn_ui_sfx_gameshow_neutral_response": "Gameshow Neutral Response",
"amzn_ui_sfx_gameshow_outro": "Gameshow Outro",
"amzn_ui_sfx_gameshow_player1": "Gameshow Player1",
"amzn_ui_sfx_gameshow_player2": "Gameshow Player2",
"amzn_ui_sfx_gameshow_player3": "Gameshow Player3",
"amzn_ui_sfx_gameshow_player4": "Gameshow Player4",
"amzn_ui_sfx_gameshow_positive_response": "Gameshow Positive Response",
"amzn_ui_sfx_gameshow_tally_negative": "Gameshow Tally Negative",
"amzn_ui_sfx_gameshow_tally_positive": "Gameshow Tally Positive",
"amzn_ui_sfx_gameshow_waiting_loop_30s": "Gameshow Waiting Loop 30s",
"anchor": "Anchor",
"answering_machines": "Answering Machines",
"arcs_sparks": "Arcs Sparks",
"arrows_bows": "Arrows Bows",
"baby": "Baby",
"back_up_beeps": "Back Up Beeps",
"bars_restaurants": "Bars Restaurants",
"baseball": "Baseball",
"basketball": "Basketball",
"battles": "Battles",
"beeps_tones": "Beeps Tones",
"bell": "Bell",
"bikes": "Bikes",
"billiards": "Billiards",
"board_games": "Board Games",
"body": "Body",
"boing": "Boing",
"books": "Books",
"bow_wash": "Bow Wash",
"box": "Box",
"break_shatter_smash": "Break Shatter Smash",
"breaks": "Breaks",
"brooms_mops": "Brooms Mops",
"bullets": "Bullets",
"buses": "Buses",
"buzz": "Buzz",
"buzz_hums": "Buzz Hums",
"buzzers": "Buzzers",
"buzzers_pistols": "Buzzers Pistols",
"cables_metal": "Cables Metal",
"camera": "Camera",
"cannons": "Cannons",
"car_alarm": "Car Alarm",
"car_alarms": "Car Alarms",
"car_cell_phones": "Car Cell Phones",
"carnivals_fairs": "Carnivals Fairs",
"cars": "Cars",
"casino": "Casino",
"casinos": "Casinos",
"cellar": "Cellar",
"chimes": "Chimes",
"chimes_bells": "Chimes Bells",
"chorus": "Chorus",
"christmas": "Christmas",
"church_bells": "Church Bells",
"clock": "Clock",
"cloth": "Cloth",
"concrete": "Concrete",
"construction": "Construction",
"construction_factory": "Construction Factory",
"crashes": "Crashes",
"crowds": "Crowds",
"debris": "Debris",
"dining_kitchens": "Dining Kitchens",
"dinosaurs": "Dinosaurs",
"dripping": "Dripping",
"drops": "Drops",
"electric": "Electric",
"electrical": "Electrical",
"elevator": "Elevator",
"evolution_monsters": "Evolution Monsters",
"explosions": "Explosions",
"factory": "Factory",
"falls": "Falls",
"fax_scanner_copier": "Fax Scanner Copier",
"feedback_mics": "Feedback Mics",
"fight": "Fight",
"fire": "Fire",
"fire_extinguisher": "Fire Extinguisher",
"fireballs": "Fireballs",
"fireworks": "Fireworks",
"fishing_pole": "Fishing Pole",
"flags": "Flags",
"football": "Football",
"footsteps": "Footsteps",
"futuristic": "Futuristic",
"futuristic_ship": "Futuristic Ship",
"gameshow": "Gameshow",
"gear": "Gear",
"ghosts_demons": "Ghosts Demons",
"giant_monster": "Giant Monster",
"glass": "Glass",
"glasses_clink": "Glasses Clink",
"golf": "Golf",
"gorilla": "Gorilla",
"grenade_lanucher": "Grenade Lanucher",
"griffen": "Griffen",
"gyms_locker_rooms": "Gyms Locker Rooms",
"handgun_loading": "Handgun Loading",
"handgun_shot": "Handgun Shot",
"handle": "Handle",
"hands": "Hands",
"heartbeats_ekg": "Heartbeats EKG",
"helicopter": "Helicopter",
"high_tech": "High Tech",
"hit_punch_slap": "Hit Punch Slap",
"hits": "Hits",
"horns": "Horns",
"horror": "Horror",
"hot_tub_filling_up": "Hot Tub Filling Up",
"human": "Human",
"human_vocals": "Human Vocals",
"hygene": "Hygene",
"ice_skating": "Ice Skating",
"ignitions": "Ignitions",
"infantry": "Infantry",
"intro": "Intro",
"jet": "Jet",
"juggling": "Juggling",
"key_lock": "Key Lock",
"kids": "Kids",
"knocks": "Knocks",
"lab_equip": "Lab Equip",
"lacrosse": "Lacrosse",
"lamps_lanterns": "Lamps Lanterns",
"leather": "Leather",
"liquid_suction": "Liquid Suction",
"locker_doors": "Locker Doors",
"machine_gun": "Machine Gun",
"magic_spells": "Magic Spells",
"medium_large_explosions": "Medium Large Explosions",
"metal": "Metal",
"modern_rings": "Modern Rings",
"money_coins": "Money Coins",
"motorcycles": "Motorcycles",
"movement": "Movement",
"moves": "Moves",
"nature": "Nature",
"oar_boat": "Oar Boat",
"pagers": "Pagers",
"paintball": "Paintball",
"paper": "Paper",
"parachute": "Parachute",
"pay_phones": "Pay Phones",
"phone_beeps": "Phone Beeps",
"pigmy_bats": "Pigmy Bats",
"pills": "Pills",
"pour_water": "Pour Water",
"power_up_down": "Power Up Down",
"printers": "Printers",
"prison": "Prison",
"public_space": "Public Space",
"racquetball": "Racquetball",
"radios_static": "Radios Static",
"rain": "Rain",
"rc_airplane": "RC Airplane",
"rc_car": "RC Car",
"refrigerators_freezers": "Refrigerators Freezers",
"regular": "Regular",
"respirator": "Respirator",
"rifle": "Rifle",
"roller_coaster": "Roller Coaster",
"rollerskates_rollerblades": "RollerSkates RollerBlades",
"room_tones": "Room Tones",
"ropes_climbing": "Ropes Climbing",
"rotary_rings": "Rotary Rings",
"rowboat_canoe": "Rowboat Canoe",
"rubber": "Rubber",
"running": "Running",
"sails": "Sails",
"sand_gravel": "Sand Gravel",
"screen_doors": "Screen Doors",
"screens": "Screens",
"seats_stools": "Seats Stools",
"servos": "Servos",
"shoes_boots": "Shoes Boots",
"shotgun": "Shotgun",
"shower": "Shower",
"sink_faucet": "Sink Faucet",
"sink_filling_water": "Sink Filling Water",
"sink_run_and_off": "Sink Run And Off",
"sink_water_splatter": "Sink Water Splatter",
"sirens": "Sirens",
"skateboards": "Skateboards",
"ski": "Ski",
"skids_tires": "Skids Tires",
"sled": "Sled",
"slides": "Slides",
"small_explosions": "Small Explosions",
"snow": "Snow",
"snowmobile": "Snowmobile",
"soldiers": "Soldiers",
"splash_water": "Splash Water",
"splashes_sprays": "Splashes Sprays",
"sports_whistles": "Sports Whistles",
"squeaks": "Squeaks",
"squeaky": "Squeaky",
"stairs": "Stairs",
"steam": "Steam",
"submarine_diesel": "Submarine Diesel",
"swing_doors": "Swing Doors",
"switches_levers": "Switches Levers",
"swords": "Swords",
"tape": "Tape",
"tape_machine": "Tape Machine",
"televisions_shows": "Televisions Shows",
"tennis_pingpong": "Tennis PingPong",
"textile": "Textile",
"throw": "Throw",
"thunder": "Thunder",
"ticks": "Ticks",
"timer": "Timer",
"toilet_flush": "Toilet Flush",
"tone": "Tone",
"tones_noises": "Tones Noises",
"toys": "Toys",
"tractors": "Tractors",
"traffic": "Traffic",
"train": "Train",
"trucks_vans": "Trucks Vans",
"turnstiles": "Turnstiles",
"typing": "Typing",
"umbrella": "Umbrella",
"underwater": "Underwater",
"vampires": "Vampires",
"various": "Various",
"video_tunes": "Video Tunes",
"volcano_earthquake": "Volcano Earthquake",
"watches": "Watches",
"water": "Water",
"water_running": "Water Running",
"werewolves": "Werewolves",
"winches_gears": "Winches Gears",
"wind": "Wind",
"wood": "Wood",
"wood_boat": "Wood Boat",
"woosh": "Woosh",
"zap": "Zap",
"zippers": "Zippers"
}
}
},
"exceptions": {
"cannot_connect_with_error": {
"message": "Error connecting: {error}"
},
"cannot_retrieve_data_with_error": {
"message": "Error retrieving data: {error}"
},
"device_serial_number_missing": {
"message": "Device serial number missing: {device_id}"
},
"invalid_device_id": {
"message": "Invalid device ID specified: {device_id}"
},
"invalid_sound_value": {
"message": "Invalid sound {sound} with variant {variant} specified"
},
"entry_not_loaded": {
"message": "Entry not loaded: {entry}"
}
}
}

View File

@@ -430,7 +430,6 @@ async def async_devices_payload(hass: HomeAssistant) -> dict:
"model": device.model,
"sw_version": device.sw_version,
"hw_version": device.hw_version,
"has_suggested_area": device.suggested_area is not None,
"has_configuration_url": device.configuration_url is not None,
"via_device": None,
}

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Callable, Mapping
from datetime import datetime, timedelta
import logging
from typing import Any
from typing import TYPE_CHECKING, Any
from pyasuswrt import AsusWrtError
@@ -40,6 +40,9 @@ from .const import (
SENSORS_CONNECTED_DEVICE,
)
if TYPE_CHECKING:
from . import AsusWrtConfigEntry
CONF_REQ_RELOAD = [CONF_DNSMASQ, CONF_INTERFACE, CONF_REQUIRE_IP]
SCAN_INTERVAL = timedelta(seconds=30)
@@ -52,10 +55,13 @@ _LOGGER = logging.getLogger(__name__)
class AsusWrtSensorDataHandler:
"""Data handler for AsusWrt sensor."""
def __init__(self, hass: HomeAssistant, api: AsusWrtBridge) -> None:
def __init__(
self, hass: HomeAssistant, api: AsusWrtBridge, entry: AsusWrtConfigEntry
) -> None:
"""Initialize a AsusWrt sensor data handler."""
self._hass = hass
self._api = api
self._entry = entry
self._connected_devices = 0
async def _get_connected_devices(self) -> dict[str, int]:
@@ -91,6 +97,7 @@ class AsusWrtSensorDataHandler:
update_method=method,
# Polling interval. Will only be polled if there are subscribers.
update_interval=SCAN_INTERVAL if should_poll else None,
config_entry=self._entry,
)
await coordinator.async_refresh()
@@ -321,7 +328,9 @@ class AsusWrtRouter:
if self._sensors_data_handler:
return
self._sensors_data_handler = AsusWrtSensorDataHandler(self.hass, self._api)
self._sensors_data_handler = AsusWrtSensorDataHandler(
self.hass, self._api, self._entry
)
self._sensors_data_handler.update_device_count(self._connected_devices)
sensors_types = await self._api.async_get_available_sensors()

View File

@@ -28,5 +28,5 @@
"documentation": "https://www.home-assistant.io/integrations/august",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==8.10.0", "yalexs-ble==3.1.0"]
"requirements": ["yalexs==8.11.1", "yalexs-ble==3.1.2"]
}

View File

@@ -268,7 +268,7 @@ class LoginFlowBaseView(HomeAssistantView):
result.pop("data")
result.pop("context")
result_obj: Credentials = result.pop("result")
result_obj = result.pop("result")
# Result can be None if credential was never linked to a user before.
user = await hass.auth.async_get_user_by_credentials(result_obj)
@@ -281,7 +281,8 @@ class LoginFlowBaseView(HomeAssistantView):
)
process_success_login(request)
result["result"] = self._store_result(client_id, result_obj)
# We overwrite the Credentials object with the string code to retrieve it.
result["result"] = self._store_result(client_id, result_obj) # type: ignore[typeddict-item]
return self.json(result)

View File

@@ -29,7 +29,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["axis"],
"requirements": ["axis==64"],
"requirements": ["axis==65"],
"ssdp": [
{
"manufacturer": "AXIS"

View File

@@ -1119,7 +1119,7 @@ class BackupManager:
)
if unavailable_agents:
LOGGER.warning(
"Backup agents %s are not available, will backupp to %s",
"Backup agents %s are not available, will backup to %s",
unavailable_agents,
available_agents,
)

View File

@@ -93,7 +93,7 @@
}
},
"preset1": {
"name": "Favourite 1",
"name": "Favorite 1",
"state_attributes": {
"event_type": {
"state": {
@@ -107,7 +107,7 @@
}
},
"preset2": {
"name": "Favourite 2",
"name": "Favorite 2",
"state_attributes": {
"event_type": {
"state": {
@@ -121,7 +121,7 @@
}
},
"preset3": {
"name": "Favourite 3",
"name": "Favorite 3",
"state_attributes": {
"event_type": {
"state": {
@@ -135,7 +135,7 @@
}
},
"preset4": {
"name": "Favourite 4",
"name": "Favorite 4",
"state_attributes": {
"event_type": {
"state": {

View File

@@ -6,7 +6,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling",
"requirements": ["pyblu==2.0.1"],
"requirements": ["pyblu==2.0.4"],
"zeroconf": [
{
"type": "_musc._tcp.local."

View File

@@ -20,7 +20,7 @@
"bluetooth-adapters==2.0.0",
"bluetooth-auto-recovery==1.5.2",
"bluetooth-data-tools==1.28.2",
"dbus-fast==2.44.2",
"dbus-fast==2.44.3",
"habluetooth==4.0.1"
]
}

View File

@@ -64,6 +64,7 @@ class BroadlinkUpdateManager(ABC, Generic[_ApiT]):
device.hass,
_LOGGER,
name=f"{device.name} ({device.api.model} at {device.api.host[0]})",
config_entry=device.config,
update_method=self.async_update,
update_interval=self.SCAN_INTERVAL,
)

View File

@@ -2,9 +2,10 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from bsblan import BSBLAN, BSBLANConfig, BSBLANError
from bsblan import BSBLAN, BSBLANAuthError, BSBLANConfig, BSBLANError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@@ -45,7 +46,7 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
self.username = user_input.get(CONF_USERNAME)
self.password = user_input.get(CONF_PASSWORD)
return await self._validate_and_create()
return await self._validate_and_create(user_input)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
@@ -128,14 +129,29 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
self.username = user_input.get(CONF_USERNAME)
self.password = user_input.get(CONF_PASSWORD)
return await self._validate_and_create(is_discovery=True)
return await self._validate_and_create(user_input, is_discovery=True)
async def _validate_and_create(
self, is_discovery: bool = False
self, user_input: dict[str, Any], is_discovery: bool = False
) -> ConfigFlowResult:
"""Validate device connection and create entry."""
try:
await self._get_bsblan_info(is_discovery=is_discovery)
await self._get_bsblan_info()
except BSBLANAuthError:
if is_discovery:
return self.async_show_form(
step_id="discovery_confirm",
data_schema=vol.Schema(
{
vol.Optional(CONF_PASSKEY): str,
vol.Optional(CONF_USERNAME): str,
vol.Optional(CONF_PASSWORD): str,
}
),
errors={"base": "invalid_auth"},
description_placeholders={"host": str(self.host)},
)
return self._show_setup_form({"base": "invalid_auth"}, user_input)
except BSBLANError:
if is_discovery:
return self.async_show_form(
@@ -154,18 +170,137 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
return self._async_create_entry()
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauth flow."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauth confirmation flow."""
existing_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
assert existing_entry
if user_input is None:
# Preserve existing values as defaults
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Optional(
CONF_PASSKEY,
default=existing_entry.data.get(
CONF_PASSKEY, vol.UNDEFINED
),
): str,
vol.Optional(
CONF_USERNAME,
default=existing_entry.data.get(
CONF_USERNAME, vol.UNDEFINED
),
): str,
vol.Optional(
CONF_PASSWORD,
default=vol.UNDEFINED,
): str,
}
),
)
# Combine existing data with the user's new input for validation.
# This correctly handles adding, changing, and clearing credentials.
config_data = existing_entry.data.copy()
config_data.update(user_input)
self.host = config_data[CONF_HOST]
self.port = config_data[CONF_PORT]
self.passkey = config_data.get(CONF_PASSKEY)
self.username = config_data.get(CONF_USERNAME)
self.password = config_data.get(CONF_PASSWORD)
try:
await self._get_bsblan_info(raise_on_progress=False, is_reauth=True)
except BSBLANAuthError:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Optional(
CONF_PASSKEY,
default=user_input.get(CONF_PASSKEY, vol.UNDEFINED),
): str,
vol.Optional(
CONF_USERNAME,
default=user_input.get(CONF_USERNAME, vol.UNDEFINED),
): str,
vol.Optional(
CONF_PASSWORD,
default=vol.UNDEFINED,
): str,
}
),
errors={"base": "invalid_auth"},
)
except BSBLANError:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Optional(
CONF_PASSKEY,
default=user_input.get(CONF_PASSKEY, vol.UNDEFINED),
): str,
vol.Optional(
CONF_USERNAME,
default=user_input.get(CONF_USERNAME, vol.UNDEFINED),
): str,
vol.Optional(
CONF_PASSWORD,
default=vol.UNDEFINED,
): str,
}
),
errors={"base": "cannot_connect"},
)
# Update only the fields that were provided by the user
return self.async_update_reload_and_abort(
existing_entry, data_updates=user_input, reason="reauth_successful"
)
@callback
def _show_setup_form(self, errors: dict | None = None) -> ConfigFlowResult:
def _show_setup_form(
self, errors: dict | None = None, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Show the setup form to the user."""
# Preserve user input if provided, otherwise use defaults
defaults = user_input or {}
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): str,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): int,
vol.Optional(CONF_PASSKEY): str,
vol.Optional(CONF_USERNAME): str,
vol.Optional(CONF_PASSWORD): str,
vol.Required(
CONF_HOST, default=defaults.get(CONF_HOST, vol.UNDEFINED)
): str,
vol.Optional(
CONF_PORT, default=defaults.get(CONF_PORT, DEFAULT_PORT)
): int,
vol.Optional(
CONF_PASSKEY, default=defaults.get(CONF_PASSKEY, vol.UNDEFINED)
): str,
vol.Optional(
CONF_USERNAME,
default=defaults.get(CONF_USERNAME, vol.UNDEFINED),
): str,
vol.Optional(
CONF_PASSWORD,
default=defaults.get(CONF_PASSWORD, vol.UNDEFINED),
): str,
}
),
errors=errors or {},
@@ -186,7 +321,9 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
)
async def _get_bsblan_info(
self, raise_on_progress: bool = True, is_discovery: bool = False
self,
raise_on_progress: bool = True,
is_reauth: bool = False,
) -> None:
"""Get device information from a BSBLAN device."""
config = BSBLANConfig(
@@ -209,11 +346,13 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
format_mac(self.mac), raise_on_progress=raise_on_progress
)
# Always allow updating host/port for both user and discovery flows
# This ensures connectivity is maintained when devices change IP addresses
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self.host,
CONF_PORT: self.port,
}
)
# Skip unique_id configuration check during reauth to prevent "already_configured" abort
if not is_reauth:
# Always allow updating host/port for both user and discovery flows
# This ensures connectivity is maintained when devices change IP addresses
self._abort_if_unique_id_configured(
updates={
CONF_HOST: self.host,
CONF_PORT: self.port,
}
)

View File

@@ -4,11 +4,19 @@ from dataclasses import dataclass
from datetime import timedelta
from random import randint
from bsblan import BSBLAN, BSBLANConnectionError, HotWaterState, Sensor, State
from bsblan import (
BSBLAN,
BSBLANAuthError,
BSBLANConnectionError,
HotWaterState,
Sensor,
State,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER, SCAN_INTERVAL
@@ -62,6 +70,10 @@ class BSBLanUpdateCoordinator(DataUpdateCoordinator[BSBLanCoordinatorData]):
state = await self.client.state()
sensor = await self.client.sensor()
dhw = await self.client.hot_water_state()
except BSBLANAuthError as err:
raise ConfigEntryAuthFailed(
"Authentication failed for BSB-Lan device"
) from err
except BSBLANConnectionError as err:
host = self.config_entry.data[CONF_HOST] if self.config_entry else "unknown"
raise UpdateFailed(

View File

@@ -33,14 +33,25 @@
"username": "[%key:component::bsblan::config::step::user::data_description::username%]",
"password": "[%key:component::bsblan::config::step::user::data_description::password%]"
}
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The BSB-Lan integration needs to re-authenticate with {name}",
"data": {
"passkey": "[%key:component::bsblan::config::step::user::data::passkey%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"exceptions": {

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/caldav",
"iot_class": "cloud_polling",
"loggers": ["caldav", "vobject"],
"requirements": ["caldav==1.6.0", "icalendar==6.1.0"]
"requirements": ["caldav==1.6.0", "icalendar==6.3.1"]
}

View File

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

View File

@@ -4,11 +4,13 @@ from __future__ import annotations
import asyncio
import logging
from typing import Any
from aiohttp.client_exceptions import ClientError
from hass_nabucasa import Cloud, cloud_api
from hass_nabucasa.payments_api import PaymentsApiError, SubscriptionInfo
from hass_nabucasa import (
Cloud,
MigratePaypalAgreementInfo,
PaymentsApiError,
SubscriptionInfo,
)
from .client import CloudClient
from .const import REQUEST_TIMEOUT
@@ -29,17 +31,17 @@ async def async_subscription_info(cloud: Cloud[CloudClient]) -> SubscriptionInfo
async def async_migrate_paypal_agreement(
cloud: Cloud[CloudClient],
) -> dict[str, Any] | None:
) -> MigratePaypalAgreementInfo | None:
"""Migrate a paypal agreement from legacy."""
try:
async with asyncio.timeout(REQUEST_TIMEOUT):
return await cloud_api.async_migrate_paypal_agreement(cloud)
return await cloud.payments.migrate_paypal_agreement()
except TimeoutError:
_LOGGER.error(
"A timeout of %s was reached while trying to start agreement migration",
REQUEST_TIMEOUT,
)
except ClientError as exception:
except PaymentsApiError as exception:
_LOGGER.error("Failed to start agreement migration - %s", exception)
return None

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/compensation",
"iot_class": "calculated",
"quality_scale": "legacy",
"requirements": ["numpy==2.3.0"]
"requirements": ["numpy==2.3.2"]
}

View File

@@ -146,8 +146,9 @@ def _prepare_config_flow_result_json(
return prepare_result_json(result)
data = result.copy()
entry: config_entries.ConfigEntry = data["result"]
data["result"] = entry.as_json_fragment
entry: config_entries.ConfigEntry = data["result"] # type: ignore[typeddict-item]
# We overwrite the ConfigEntry object with its json representation.
data["result"] = entry.as_json_fragment # type: ignore[typeddict-unknown-key]
data.pop("data")
data.pop("context")
return data

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.6.23"]
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.7.30"]
}

View File

@@ -75,7 +75,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: DatadogConfigEntry) -> b
prefix = options[CONF_PREFIX]
sample_rate = options[CONF_RATE]
statsd_client = DogStatsd(host=host, port=port, namespace=prefix)
statsd_client = DogStatsd(
host=host, port=port, namespace=prefix, disable_telemetry=True
)
entry.runtime_data = statsd_client
initialize(statsd_host=host, statsd_port=port)

View File

@@ -36,14 +36,14 @@ class DatadogConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle user config flow."""
errors: dict[str, str] = {}
if user_input:
self._async_abort_entries_match(
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)
# Validate connection to Datadog Agent
success = await validate_datadog_connection(
self.hass,
user_input,
)
self._async_abort_entries_match(
{CONF_HOST: user_input[CONF_HOST], CONF_PORT: user_input[CONF_PORT]}
)
if not success:
errors["base"] = "cannot_connect"
else:
@@ -58,7 +58,6 @@ class DatadogConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_RATE: user_input[CONF_RATE],
},
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
@@ -107,7 +106,26 @@ class DatadogOptionsFlowHandler(OptionsFlow):
options = self.config_entry.options
if user_input is None:
user_input = {}
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Required(
CONF_PREFIX,
default=options.get(
CONF_PREFIX, data.get(CONF_PREFIX, DEFAULT_PREFIX)
),
): str,
vol.Required(
CONF_RATE,
default=options.get(
CONF_RATE, data.get(CONF_RATE, DEFAULT_RATE)
),
): int,
}
),
errors={},
)
success = await validate_datadog_connection(
self.hass,

View File

@@ -4,7 +4,7 @@ DOMAIN = "datadog"
CONF_RATE = "rate"
DEFAULT_HOST = "localhost"
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8125
DEFAULT_PREFIX = "hass"
DEFAULT_RATE = 1

View File

@@ -7,5 +7,5 @@
"iot_class": "local_push",
"loggers": ["datadog"],
"quality_scale": "legacy",
"requirements": ["datadog==0.15.0"]
"requirements": ["datadog==0.52.0"]
}

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/denonavr",
"iot_class": "local_push",
"loggers": ["denonavr"],
"requirements": ["denonavr==1.1.1"],
"requirements": ["denonavr==1.1.2"],
"ssdp": [
{
"manufacturer": "Denon",

View File

@@ -61,7 +61,7 @@ class DeviceCondition(Condition):
self._hass = hass
@classmethod
async def async_validate_condition_config(
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate device condition config."""
@@ -69,7 +69,7 @@ class DeviceCondition(Condition):
hass, config, cv.DEVICE_CONDITION_SCHEMA, DeviceAutomationType.CONDITION
)
async def async_condition_from_config(self) -> condition.ConditionCheckerType:
async def async_get_checker(self) -> condition.ConditionCheckerType:
"""Test a device condition."""
platform = await async_get_device_automation_platform(
self._hass, self._config[CONF_DOMAIN], DeviceAutomationType.CONDITION

View File

@@ -7,45 +7,39 @@ from typing import Any
import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from . import configure_mydevolo
from .const import DOMAIN, SUPPORTED_MODEL_TYPES
from .exceptions import CredentialsInvalid, UuidChanged
DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
)
class DevoloHomeControlFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a devolo HomeControl config flow."""
VERSION = 1
_reauth_entry: ConfigEntry
def __init__(self) -> None:
"""Initialize devolo Home Control flow."""
self.data_schema = {
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by the user."""
if user_input is None:
return self._show_form(step_id="user")
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
return self._show_form(step_id="user", errors={"base": "invalid_auth"})
errors: dict[str, str] = {}
if user_input is not None:
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
@@ -61,42 +55,47 @@ class DevoloHomeControlFlowHandler(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by zeroconf."""
if user_input is None:
return self._show_form(step_id="zeroconf_confirm")
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
return self._show_form(
step_id="zeroconf_confirm", errors={"base": "invalid_auth"}
)
errors: dict[str, str] = {}
if user_input is not None:
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
errors["base"] = "invalid_auth"
return self.async_show_form(
step_id="zeroconf_confirm", data_schema=DATA_SCHEMA, errors=errors
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauthentication."""
self._reauth_entry = self._get_reauth_entry()
self.data_schema = {
vol.Required(CONF_USERNAME, default=entry_data[CONF_USERNAME]): str,
vol.Required(CONF_PASSWORD): str,
}
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initiated by reauthentication."""
if user_input is None:
return self._show_form(step_id="reauth_confirm")
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
return self._show_form(
step_id="reauth_confirm", errors={"base": "invalid_auth"}
)
except UuidChanged:
return self._show_form(
step_id="reauth_confirm", errors={"base": "reauth_failed"}
)
errors: dict[str, str] = {}
data_schema = vol.Schema(
{
vol.Required(CONF_USERNAME, default=self.init_data[CONF_USERNAME]): str,
vol.Required(CONF_PASSWORD): str,
}
)
if user_input is not None:
try:
return await self._connect_mydevolo(user_input)
except CredentialsInvalid:
errors["base"] = "invalid_auth"
except UuidChanged:
errors["base"] = "reauth_failed"
return self.async_show_form(
step_id="reauth_confirm", data_schema=data_schema, errors=errors
)
async def _connect_mydevolo(self, user_input: dict[str, Any]) -> ConfigFlowResult:
"""Connect to mydevolo."""
@@ -119,21 +118,11 @@ class DevoloHomeControlFlowHandler(ConfigFlow, domain=DOMAIN):
},
)
if self._reauth_entry.unique_id != uuid:
if self.unique_id != uuid:
# The old user and the new user are not the same. This could mess-up everything as all unique IDs might change.
raise UuidChanged
reauth_entry = self._get_reauth_entry()
return self.async_update_reload_and_abort(
self._reauth_entry, data=user_input, unique_id=uuid
)
@callback
def _show_form(
self, step_id: str, errors: dict[str, str] | None = None
) -> ConfigFlowResult:
"""Show the form to the user."""
return self.async_show_form(
step_id=step_id,
data_schema=vol.Schema(self.data_schema),
errors=errors if errors else {},
reauth_entry, data=user_input, unique_id=uuid
)

View File

@@ -8,6 +8,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["devolo_plc_api"],
"quality_scale": "silver",
"requirements": ["devolo-plc-api==1.5.1"],
"zeroconf": [
{

View File

@@ -0,0 +1,84 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
This integration does not provide additional actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration does not provide additional actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: |
Entities of this integration does not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
This integration does not provide additional actions.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: |
This integration does not have an options flow.
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: done
test-coverage: done
# Gold
devices: done
diagnostics: done
discovery-update-info: done
discovery: done
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: done
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow:
status: exempt
comment: |
A change of the IP address is covered by discovery-update-info and a change of the password is covered by reauthentication-flow. No other configuration options are available.
repair-issues:
status: exempt
comment: |
This integration doesn't have any cases where raising an issue is needed.
stale-devices:
status: todo
comment: |
The tracked devices could be own devices with a manual delete option as the API cannot distinguish between stale devices and devices that are not home.
# Platinum
async-dependency: done
inject-websession: done
strict-typing: done

View File

@@ -16,7 +16,7 @@
"quality_scale": "internal",
"requirements": [
"aiodhcpwatcher==1.2.0",
"aiodiscover==2.7.0",
"aiodiscover==2.7.1",
"cached-ipaddress==0.10.0"
]
}

View File

@@ -18,6 +18,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# If path is relative, we assume relative to Home Assistant config dir
if not os.path.isabs(download_path):
download_path = hass.config.path(download_path)
hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_DOWNLOAD_DIR: download_path}
)
if not await hass.async_add_executor_job(os.path.isdir, download_path):
_LOGGER.error(

View File

@@ -11,6 +11,7 @@ import requests
import voluptuous as vol
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.util import raise_if_invalid_filename, raise_if_invalid_path
@@ -34,24 +35,33 @@ def download_file(service: ServiceCall) -> None:
entry = service.hass.config_entries.async_loaded_entries(DOMAIN)[0]
download_path = entry.data[CONF_DOWNLOAD_DIR]
url: str = service.data[ATTR_URL]
subdir: str | None = service.data.get(ATTR_SUBDIR)
target_filename: str | None = service.data.get(ATTR_FILENAME)
overwrite: bool = service.data[ATTR_OVERWRITE]
if subdir:
# Check the path
try:
raise_if_invalid_path(subdir)
except ValueError as err:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="subdir_invalid",
translation_placeholders={"subdir": subdir},
) from err
if os.path.isabs(subdir):
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="subdir_not_relative",
translation_placeholders={"subdir": subdir},
)
def do_download() -> None:
"""Download the file."""
final_path = None
filename = target_filename
try:
url = service.data[ATTR_URL]
subdir = service.data.get(ATTR_SUBDIR)
filename = service.data.get(ATTR_FILENAME)
overwrite = service.data.get(ATTR_OVERWRITE)
if subdir:
# Check the path
raise_if_invalid_path(subdir)
final_path = None
req = requests.get(url, stream=True, timeout=10)
if req.status_code != HTTPStatus.OK:

View File

@@ -12,6 +12,14 @@
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
}
},
"exceptions": {
"subdir_invalid": {
"message": "Invalid subdirectory, got: {subdir}"
},
"subdir_not_relative": {
"message": "Subdirectory must be relative, got: {subdir}"
}
},
"services": {
"download_file": {
"name": "Download file",

View File

@@ -4,10 +4,12 @@ from collections.abc import Callable
from dataclasses import dataclass
from deebot_client.capabilities import CapabilityEvent
from deebot_client.events.base import Event
from deebot_client.events import Event
from deebot_client.events.water_info import MopAttachedEvent
from sucks import VacBot
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
@@ -16,7 +18,11 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import EcovacsConfigEntry
from .entity import EcovacsCapabilityEntityDescription, EcovacsDescriptionEntity
from .entity import (
EcovacsCapabilityEntityDescription,
EcovacsDescriptionEntity,
EcovacsLegacyEntity,
)
from .util import get_supported_entities
@@ -47,12 +53,23 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add entities for passed config_entry in HA."""
controller = config_entry.runtime_data
async_add_entities(
get_supported_entities(
config_entry.runtime_data, EcovacsBinarySensor, ENTITY_DESCRIPTIONS
)
)
legacy_entities = []
for device in controller.legacy_devices:
if not controller.legacy_entity_is_added(device, "battery_charging"):
controller.add_legacy_entity(device, "battery_charging")
legacy_entities.append(EcovacsLegacyBatteryChargingSensor(device))
if legacy_entities:
async_add_entities(legacy_entities)
class EcovacsBinarySensor[EventT: Event](
EcovacsDescriptionEntity[CapabilityEvent[EventT]],
@@ -71,3 +88,33 @@ class EcovacsBinarySensor[EventT: Event](
self.async_write_ha_state()
self._subscribe(self._capability.event, on_event)
class EcovacsLegacyBatteryChargingSensor(EcovacsLegacyEntity, BinarySensorEntity):
"""Legacy battery charging sensor."""
_attr_device_class = BinarySensorDeviceClass.BATTERY_CHARGING
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
device: VacBot,
) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = f"{device.vacuum['did']}_battery_charging"
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
self._event_listeners.append(
self.device.statusEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
@property
def is_on(self) -> bool | None:
"""Return true if the binary sensor is on."""
if self.device.charge_status is None:
return None
return bool(self.device.is_charging)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.11", "deebot-client==13.5.0"]
"requirements": ["py-sucks==0.9.11", "deebot-client==13.6.0"]
}

View File

@@ -37,6 +37,7 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.helpers.typing import StateType
from . import EcovacsConfigEntry
@@ -225,7 +226,7 @@ async def async_setup_entry(
async_add_entities(entities)
async def _add_legacy_entities() -> None:
async def _add_legacy_lifespan_entities() -> None:
entities = []
for device in controller.legacy_devices:
for description in LEGACY_LIFESPAN_SENSORS:
@@ -242,14 +243,21 @@ async def async_setup_entry(
async_add_entities(entities)
def _fire_ecovacs_legacy_lifespan_event(_: Any) -> None:
hass.create_task(_add_legacy_entities())
hass.create_task(_add_legacy_lifespan_entities())
legacy_entities = []
for device in controller.legacy_devices:
config_entry.async_on_unload(
device.lifespanEvents.subscribe(
_fire_ecovacs_legacy_lifespan_event
).unsubscribe
)
if not controller.legacy_entity_is_added(device, "battery_status"):
controller.add_legacy_entity(device, "battery_status")
legacy_entities.append(EcovacsLegacyBatterySensor(device))
if legacy_entities:
async_add_entities(legacy_entities)
class EcovacsSensor(
@@ -344,6 +352,44 @@ class EcovacsErrorSensor(
self._subscribe(self._capability.event, on_event)
class EcovacsLegacyBatterySensor(EcovacsLegacyEntity, SensorEntity):
"""Legacy battery sensor."""
_attr_native_unit_of_measurement = PERCENTAGE
_attr_device_class = SensorDeviceClass.BATTERY
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(
self,
device: VacBot,
) -> None:
"""Initialize the entity."""
super().__init__(device)
self._attr_unique_id = f"{device.vacuum['did']}_battery_status"
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
self._event_listeners.append(
self.device.batteryEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
@property
def native_value(self) -> StateType:
"""Return the value reported by the sensor."""
if (status := self.device.battery_status) is not None:
return status * 100 # type: ignore[no-any-return]
return None
@property
def icon(self) -> str | None:
"""Return the icon to use in the frontend, if any."""
return icon_for_battery_level(
battery_level=self.native_value, charging=self.device.is_charging
)
class EcovacsLegacyLifespanSensor(EcovacsLegacyEntity, SensorEntity):
"""Legacy Lifespan sensor."""

View File

@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any
from deebot_client.capabilities import Capabilities, DeviceType
from deebot_client.device import Device
from deebot_client.events import BatteryEvent, FanSpeedEvent, RoomsEvent, StateEvent
from deebot_client.events import FanSpeedEvent, RoomsEvent, StateEvent
from deebot_client.models import CleanAction, CleanMode, Room, State
import sucks
@@ -22,7 +22,6 @@ from homeassistant.core import HomeAssistant, SupportsResponse
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.icon import icon_for_battery_level
from homeassistant.util import slugify
from . import EcovacsConfigEntry
@@ -71,8 +70,7 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
_attr_fan_speed_list = [sucks.FAN_SPEED_NORMAL, sucks.FAN_SPEED_HIGH]
_attr_supported_features = (
VacuumEntityFeature.BATTERY
| VacuumEntityFeature.RETURN_HOME
VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.CLEAN_SPOT
| VacuumEntityFeature.STOP
| VacuumEntityFeature.START
@@ -89,11 +87,6 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
lambda _: self.schedule_update_ha_state()
)
)
self._event_listeners.append(
self.device.batteryEvents.subscribe(
lambda _: self.schedule_update_ha_state()
)
)
self._event_listeners.append(
self.device.lifespanEvents.subscribe(
lambda _: self.schedule_update_ha_state()
@@ -137,21 +130,6 @@ class EcovacsLegacyVacuum(EcovacsLegacyEntity, StateVacuumEntity):
return None
@property
def battery_level(self) -> int | None:
"""Return the battery level of the vacuum cleaner."""
if self.device.battery_status is not None:
return self.device.battery_status * 100 # type: ignore[no-any-return]
return None
@property
def battery_icon(self) -> str:
"""Return the battery icon for the vacuum cleaner."""
return icon_for_battery_level(
battery_level=self.battery_level, charging=self.device.is_charging
)
@property
def fan_speed(self) -> str | None:
"""Return the fan speed of the vacuum cleaner."""
@@ -238,7 +216,6 @@ class EcovacsVacuum(
VacuumEntityFeature.PAUSE
| VacuumEntityFeature.STOP
| VacuumEntityFeature.RETURN_HOME
| VacuumEntityFeature.BATTERY
| VacuumEntityFeature.SEND_COMMAND
| VacuumEntityFeature.LOCATE
| VacuumEntityFeature.STATE
@@ -265,10 +242,6 @@ class EcovacsVacuum(
"""Set up the event listeners now that hass is ready."""
await super().async_added_to_hass()
async def on_battery(event: BatteryEvent) -> None:
self._attr_battery_level = event.value
self.async_write_ha_state()
async def on_rooms(event: RoomsEvent) -> None:
self._rooms = event.rooms
self.async_write_ha_state()
@@ -277,7 +250,6 @@ class EcovacsVacuum(
self._attr_activity = _STATE_TO_VACUUM_STATE[event.state]
self.async_write_ha_state()
self._subscribe(self._capability.battery.event, on_battery)
self._subscribe(self._capability.state.event, on_status)
if self._capability.fan_speed:

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/emoncms",
"iot_class": "local_polling",
"requirements": ["pyemoncms==0.1.1"]
"requirements": ["pyemoncms==0.1.2"]
}

View File

@@ -12,12 +12,26 @@
},
"data_description": {
"url": "Server URL starting with the protocol (http or https)",
"api_key": "Your 32 bits API key"
"api_key": "Your 32 bits API key",
"sync_mode": "Pick your feeds manually (default) or synchronize them at once"
}
},
"choose_feeds": {
"data": {
"include_only_feed_id": "Choose feeds to include"
},
"data_description": {
"include_only_feed_id": "Pick the feeds you want to synchronize"
}
},
"reconfigure": {
"data": {
"url": "[%key:common::config_flow::data::url%]",
"api_key": "[%key:common::config_flow::data::api_key%]"
},
"data_description": {
"url": "[%key:component::emoncms::config::step::user::data_description::url%]",
"api_key": "[%key:component::emoncms::config::step::user::data_description::api_key%]"
}
}
},
@@ -30,8 +44,8 @@
"selector": {
"sync_mode": {
"options": {
"auto": "Synchronize all available Feeds",
"manual": "Select which Feeds to synchronize"
"auto": "Synchronize all available feeds",
"manual": "Select which feeds to synchronize"
}
}
},
@@ -89,6 +103,9 @@
"init": {
"data": {
"include_only_feed_id": "[%key:component::emoncms::config::step::choose_feeds::data::include_only_feed_id%]"
},
"data_description": {
"include_only_feed_id": "[%key:component::emoncms::config::step::choose_feeds::data_description::include_only_feed_id%]"
}
}
}

View File

@@ -116,6 +116,9 @@ async def async_get_config_entry_diagnostics(
entities.append({"entity": entity_dict, "state": state_dict})
device_dict = asdict(device)
device_dict.pop("_cache", None)
# This can be removed when suggested_area is removed from DeviceEntry
device_dict.pop("_suggested_area")
device_dict.pop("is_new", None)
device_entities.append({"device": device_dict, "entities": entities})
# remove envoy serial

View File

@@ -7,7 +7,7 @@
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"quality_scale": "platinum",
"requirements": ["pyenphase==2.2.2"],
"requirements": ["pyenphase==2.2.3"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."

View File

@@ -51,6 +51,7 @@ from .const import (
DOMAIN,
)
from .dashboard import async_get_or_create_dashboard_manager, async_set_dashboard_info
from .encryption_key_storage import async_get_encryption_key_storage
from .entry_data import ESPHomeConfigEntry
from .manager import async_replace_device
@@ -159,7 +160,10 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle reauthorization flow."""
errors = {}
if await self._retrieve_encryption_key_from_dashboard():
if (
await self._retrieve_encryption_key_from_storage()
or await self._retrieve_encryption_key_from_dashboard()
):
error = await self.fetch_device_info()
if error is None:
return await self._async_authenticate_or_add()
@@ -226,9 +230,12 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
response = await self.fetch_device_info()
self._noise_psk = None
# Try to retrieve an existing key from dashboard or storage.
if (
self._device_name
and await self._retrieve_encryption_key_from_dashboard()
) or (
self._device_mac and await self._retrieve_encryption_key_from_storage()
):
response = await self.fetch_device_info()
@@ -284,6 +291,7 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
self._name = discovery_info.properties.get("friendly_name", device_name)
self._host = discovery_info.host
self._port = discovery_info.port
self._device_mac = mac_address
self._noise_required = bool(discovery_info.properties.get("api_encryption"))
# Check if already configured
@@ -308,10 +316,11 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
# Don't call _fetch_device_info() for ignored entries
raise AbortFlow("already_configured")
configured_host: str | None = entry.data.get(CONF_HOST)
configured_port: int | None = entry.data.get(CONF_PORT)
if configured_host == host and configured_port == port:
configured_port: int = entry.data.get(CONF_PORT, DEFAULT_PORT)
# When port is None (from DHCP discovery), only compare hosts
if configured_host == host and (port is None or configured_port == port):
# Don't probe to verify the mac is correct since
# the host and port matches.
# the host matches (and port matches if provided).
raise AbortFlow("already_configured")
configured_psk: str | None = entry.data.get(CONF_NOISE_PSK)
await self._fetch_device_info(host, port or configured_port, configured_psk)
@@ -772,6 +781,26 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
self._noise_psk = noise_psk
return True
async def _retrieve_encryption_key_from_storage(self) -> bool:
"""Try to retrieve the encryption key from storage.
Return boolean if a key was retrieved.
"""
# Try to get MAC address from current flow state or reauth entry
mac_address = self._device_mac
if mac_address is None and self._reauth_entry is not None:
# In reauth flow, get MAC from the existing entry's unique_id
mac_address = self._reauth_entry.unique_id
assert mac_address is not None
storage = await async_get_encryption_key_storage(self.hass)
if stored_key := await storage.async_get_key(mac_address):
self._noise_psk = stored_key
return True
return False
@staticmethod
@callback
def async_get_options_flow(

View File

@@ -0,0 +1,94 @@
"""Encryption key storage for ESPHome devices."""
from __future__ import annotations
import logging
from typing import TypedDict
from homeassistant.core import HomeAssistant
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.singleton import singleton
from homeassistant.helpers.storage import Store
from homeassistant.util.hass_dict import HassKey
_LOGGER = logging.getLogger(__name__)
ENCRYPTION_KEY_STORAGE_VERSION = 1
ENCRYPTION_KEY_STORAGE_KEY = "esphome.encryption_keys"
class EncryptionKeyData(TypedDict):
"""Encryption key storage data."""
keys: dict[str, str] # MAC address -> base64 encoded key
KEY_ENCRYPTION_STORAGE: HassKey[ESPHomeEncryptionKeyStorage] = HassKey(
"esphome_encryption_key_storage"
)
class ESPHomeEncryptionKeyStorage:
"""Storage for ESPHome encryption keys."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the encryption key storage."""
self.hass = hass
self._store = Store[EncryptionKeyData](
hass,
ENCRYPTION_KEY_STORAGE_VERSION,
ENCRYPTION_KEY_STORAGE_KEY,
encoder=JSONEncoder,
)
self._data: EncryptionKeyData | None = None
async def async_load(self) -> None:
"""Load encryption keys from storage."""
if self._data is None:
data = await self._store.async_load()
self._data = data or {"keys": {}}
async def async_save(self) -> None:
"""Save encryption keys to storage."""
if self._data is not None:
await self._store.async_save(self._data)
async def async_get_key(self, mac_address: str) -> str | None:
"""Get encryption key for a MAC address."""
await self.async_load()
assert self._data is not None
return self._data["keys"].get(mac_address.lower())
async def async_store_key(self, mac_address: str, key: str) -> None:
"""Store encryption key for a MAC address."""
await self.async_load()
assert self._data is not None
self._data["keys"][mac_address.lower()] = key
await self.async_save()
_LOGGER.debug(
"Stored encryption key for device with MAC %s",
mac_address,
)
async def async_remove_key(self, mac_address: str) -> None:
"""Remove encryption key for a MAC address."""
await self.async_load()
assert self._data is not None
lower_mac_address = mac_address.lower()
if lower_mac_address in self._data["keys"]:
del self._data["keys"][lower_mac_address]
await self.async_save()
_LOGGER.debug(
"Removed encryption key for device with MAC %s",
mac_address,
)
@singleton(KEY_ENCRYPTION_STORAGE, async_=True)
async def async_get_encryption_key_storage(
hass: HomeAssistant,
) -> ESPHomeEncryptionKeyStorage:
"""Get the encryption key storage instance."""
storage = ESPHomeEncryptionKeyStorage(hass)
await storage.async_load()
return storage

View File

@@ -3,8 +3,10 @@
from __future__ import annotations
import asyncio
import base64
from functools import partial
import logging
import secrets
from typing import TYPE_CHECKING, Any, NamedTuple
from aioesphomeapi import (
@@ -68,6 +70,7 @@ from .const import (
CONF_ALLOW_SERVICE_CALLS,
CONF_BLUETOOTH_MAC_ADDRESS,
CONF_DEVICE_NAME,
CONF_NOISE_PSK,
CONF_SUBSCRIBE_LOGS,
DEFAULT_ALLOW_SERVICE_CALLS,
DEFAULT_URL,
@@ -78,6 +81,7 @@ from .const import (
)
from .dashboard import async_get_dashboard
from .domain_data import DomainData
from .encryption_key_storage import async_get_encryption_key_storage
# Import config flow so that it's added to the registry
from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
@@ -85,9 +89,7 @@ from .entry_data import ESPHomeConfigEntry, RuntimeEntryData
DEVICE_CONFLICT_ISSUE_FORMAT = "device_conflict-{}"
if TYPE_CHECKING:
from aioesphomeapi.api_pb2 import ( # type: ignore[attr-defined]
SubscribeLogsResponse,
)
from aioesphomeapi.api_pb2 import SubscribeLogsResponse # type: ignore[attr-defined] # noqa: I001
_LOGGER = logging.getLogger(__name__)
@@ -515,6 +517,8 @@ class ESPHomeManager:
assert api_version is not None, "API version must be set"
entry_data.async_on_connect(device_info, api_version)
await self._handle_dynamic_encryption_key(device_info)
if device_info.name:
reconnect_logic.name = device_info.name
@@ -618,6 +622,7 @@ class ESPHomeManager:
),
):
return
if isinstance(err, InvalidEncryptionKeyAPIError):
if (
(received_name := err.received_name)
@@ -648,6 +653,93 @@ class ESPHomeManager:
return
self.entry.async_start_reauth(self.hass)
async def _handle_dynamic_encryption_key(
self, device_info: EsphomeDeviceInfo
) -> None:
"""Handle dynamic encryption keys.
If a device reports it supports encryption, but we connected without a key,
we need to generate and store one.
"""
noise_psk: str | None = self.entry.data.get(CONF_NOISE_PSK)
if noise_psk:
# we're already connected with a noise PSK - nothing to do
return
if not device_info.api_encryption_supported:
# device does not support encryption - nothing to do
return
# Connected to device without key and the device supports encryption
storage = await async_get_encryption_key_storage(self.hass)
# First check if we have a key in storage for this device
from_storage: bool = False
if self.entry.unique_id and (
stored_key := await storage.async_get_key(self.entry.unique_id)
):
_LOGGER.debug(
"Retrieved encryption key from storage for device %s",
self.entry.unique_id,
)
# Use the stored key
new_key = stored_key.encode()
new_key_str = stored_key
from_storage = True
else:
# No stored key found, generate a new one
_LOGGER.debug(
"Generating new encryption key for device %s", self.entry.unique_id
)
new_key = base64.b64encode(secrets.token_bytes(32))
new_key_str = new_key.decode()
try:
# Store the key on the device using the existing connection
result = await self.cli.noise_encryption_set_key(new_key)
except APIConnectionError as ex:
_LOGGER.error(
"Connection error while storing encryption key for device %s (%s): %s",
self.entry.data.get(CONF_DEVICE_NAME, self.host),
self.entry.unique_id,
ex,
)
return
else:
if not result:
_LOGGER.error(
"Failed to set dynamic encryption key on device %s (%s)",
self.entry.data.get(CONF_DEVICE_NAME, self.host),
self.entry.unique_id,
)
return
# Key stored successfully on device
assert self.entry.unique_id is not None
# Only store in storage if it was newly generated
if not from_storage:
await storage.async_store_key(self.entry.unique_id, new_key_str)
# Always update config entry
self.hass.config_entries.async_update_entry(
self.entry,
data={**self.entry.data, CONF_NOISE_PSK: new_key_str},
)
if from_storage:
_LOGGER.info(
"Set encryption key from storage on device %s (%s)",
self.entry.data.get(CONF_DEVICE_NAME, self.host),
self.entry.unique_id,
)
else:
_LOGGER.info(
"Generated and stored encryption key for device %s (%s)",
self.entry.data.get(CONF_DEVICE_NAME, self.host),
self.entry.unique_id,
)
@callback
def _async_handle_logging_changed(self, _event: Event) -> None:
"""Handle when the logging level changes."""

View File

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

View File

@@ -66,6 +66,26 @@ SENSOR_TYPES: dict[str, SensorEntityDescription] = {
key="last_alarm_type_name",
translation_key="last_alarm_type_name",
),
"Record_Mode": SensorEntityDescription(
key="Record_Mode",
translation_key="record_mode",
entity_registry_enabled_default=False,
),
"battery_camera_work_mode": SensorEntityDescription(
key="battery_camera_work_mode",
translation_key="battery_camera_work_mode",
entity_registry_enabled_default=False,
),
"powerStatus": SensorEntityDescription(
key="powerStatus",
translation_key="power_status",
entity_registry_enabled_default=False,
),
"OnlineStatus": SensorEntityDescription(
key="OnlineStatus",
translation_key="online_status",
entity_registry_enabled_default=False,
),
}
@@ -76,16 +96,26 @@ async def async_setup_entry(
) -> None:
"""Set up EZVIZ sensors based on a config entry."""
coordinator = entry.runtime_data
entities: list[EzvizSensor] = []
async_add_entities(
[
for camera, sensors in coordinator.data.items():
entities.extend(
EzvizSensor(coordinator, camera, sensor)
for camera in coordinator.data
for sensor, value in coordinator.data[camera].items()
if sensor in SENSOR_TYPES
if value is not None
]
)
for sensor, value in sensors.items()
if sensor in SENSOR_TYPES and value is not None
)
optionals = sensors.get("optionals", {})
entities.extend(
EzvizSensor(coordinator, camera, optional_key)
for optional_key in ("powerStatus", "OnlineStatus")
if optional_key in optionals
)
if "mode" in optionals.get("Record_Mode", {}):
entities.append(EzvizSensor(coordinator, camera, "mode"))
async_add_entities(entities)
class EzvizSensor(EzvizEntity, SensorEntity):

View File

@@ -147,6 +147,18 @@
},
"last_alarm_type_name": {
"name": "Last alarm type name"
},
"record_mode": {
"name": "Record mode"
},
"battery_camera_work_mode": {
"name": "Battery work mode"
},
"power_status": {
"name": "Power status"
},
"online_status": {
"name": "Online status"
}
},
"switch": {

View File

@@ -106,6 +106,7 @@ class FroniusSolarNet:
solar_net=self,
logger=_LOGGER,
name=f"{DOMAIN}_logger_{self.host}",
config_entry=self.config_entry,
)
await self.logger_coordinator.async_config_entry_first_refresh()
@@ -120,6 +121,7 @@ class FroniusSolarNet:
solar_net=self,
logger=_LOGGER,
name=f"{DOMAIN}_meters_{self.host}",
config_entry=self.config_entry,
)
)
@@ -129,6 +131,7 @@ class FroniusSolarNet:
solar_net=self,
logger=_LOGGER,
name=f"{DOMAIN}_ohmpilot_{self.host}",
config_entry=self.config_entry,
)
)
@@ -138,6 +141,7 @@ class FroniusSolarNet:
solar_net=self,
logger=_LOGGER,
name=f"{DOMAIN}_power_flow_{self.host}",
config_entry=self.config_entry,
)
)
@@ -147,6 +151,7 @@ class FroniusSolarNet:
solar_net=self,
logger=_LOGGER,
name=f"{DOMAIN}_storages_{self.host}",
config_entry=self.config_entry,
)
)
@@ -206,6 +211,7 @@ class FroniusSolarNet:
logger=_LOGGER,
name=_inverter_name,
inverter_info=_inverter_info,
config_entry=self.config_entry,
)
if self.config_entry.state == ConfigEntryState.LOADED:
await _coordinator.async_refresh()

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250702.3"]
"requirements": ["home-assistant-frontend==20250805.0"]
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/garages_amsterdam",
"iot_class": "cloud_polling",
"requirements": ["odp-amsterdam==6.1.1"]
"requirements": ["odp-amsterdam==6.1.2"]
}

View File

@@ -12,7 +12,7 @@
}
},
"confirm_discovery": {
"description": "DHCP reservation on your router is recommended. If not set up, the device may become unavailable until Home Assistant detects the new ip address. Refer to your router's user manual."
"description": "DHCP reservation on your router is recommended. If not set up, the device may become unavailable until Home Assistant detects the new IP address. Refer to your router's user manual."
}
},
"error": {

View File

@@ -230,7 +230,7 @@ async def async_setup_entry(
calendar_info = calendars[calendar_id]
else:
calendar_info = get_calendar_info(
hass, calendar_item.dict(exclude_unset=True)
hass, calendar_item.model_dump(exclude_unset=True)
)
new_calendars.append(calendar_info)
@@ -467,7 +467,7 @@ class GoogleCalendarEntity(
else:
start = DateOrDatetime(date=dtstart)
end = DateOrDatetime(date=dtend)
event = Event.parse_obj(
event = Event.model_validate(
{
EVENT_SUMMARY: kwargs[EVENT_SUMMARY],
"start": start,
@@ -538,7 +538,7 @@ async def async_create_event(entity: GoogleCalendarEntity, call: ServiceCall) ->
if EVENT_IN in call.data:
if EVENT_IN_DAYS in call.data[EVENT_IN]:
now = datetime.now()
now = datetime.now().date()
start_in = now + timedelta(days=call.data[EVENT_IN][EVENT_IN_DAYS])
end_in = start_in + timedelta(days=1)
@@ -547,7 +547,7 @@ async def async_create_event(entity: GoogleCalendarEntity, call: ServiceCall) ->
end = DateOrDatetime(date=end_in)
elif EVENT_IN_WEEKS in call.data[EVENT_IN]:
now = datetime.now()
now = datetime.now().date()
start_in = now + timedelta(weeks=call.data[EVENT_IN][EVENT_IN_WEEKS])
end_in = start_in + timedelta(days=1)

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==7.1.0", "oauth2client==4.1.3", "ical==10.0.4"]
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==11.0.0"]
}

View File

@@ -123,10 +123,10 @@
},
"ai_task_data": {
"initiate_flow": {
"user": "Add Generate data with AI service",
"reconfigure": "Reconfigure Generate data with AI service"
"user": "Add AI task",
"reconfigure": "Reconfigure AI task"
},
"entry_type": "Generate data with AI service",
"entry_type": "AI task",
"step": {
"set_options": {
"data": {

View File

@@ -56,12 +56,12 @@ async def basic_group_options_schema(
entity_selector: selector.Selector[Any] | vol.Schema
if handler is None:
entity_selector = selector.selector(
{"entity": {"domain": domain, "multiple": True}}
{"entity": {"domain": domain, "multiple": True, "reorder": True}}
)
else:
entity_selector = entity_selector_without_own_entities(
cast(SchemaOptionsFlowHandler, handler.parent_handler),
selector.EntitySelectorConfig(domain=domain, multiple=True),
selector.EntitySelectorConfig(domain=domain, multiple=True, reorder=True),
)
return vol.Schema(
@@ -78,7 +78,9 @@ def basic_group_config_schema(domain: str | list[str]) -> vol.Schema:
{
vol.Required("name"): selector.TextSelector(),
vol.Required(CONF_ENTITIES): selector.EntitySelector(
selector.EntitySelectorConfig(domain=domain, multiple=True),
selector.EntitySelectorConfig(
domain=domain, multiple=True, reorder=True
),
),
vol.Required(CONF_HIDE_MEMBERS, default=False): selector.BooleanSelector(),
}
@@ -139,9 +141,7 @@ async def light_switch_options_schema(
"""Generate options schema."""
return (await basic_group_options_schema(domain, handler)).extend(
{
vol.Required(
CONF_ALL, default=False, description={"advanced": True}
): selector.BooleanSelector(),
vol.Required(CONF_ALL, default=False): selector.BooleanSelector(),
}
)

View File

@@ -21,12 +21,14 @@
},
"binary_sensor": {
"title": "[%key:component::group::config::step::user::title%]",
"description": "If \"all entities\" is enabled, the group's state is on only if all members are on. If \"all entities\" is disabled, the group's state is on if any member is on.",
"data": {
"all": "All entities",
"entities": "Members",
"hide_members": "Hide members",
"name": "[%key:common::config_flow::data::name%]"
},
"data_description": {
"all": "If enabled, the group's state is on only if all members are on. If disabled, the group's state is on if any member is on."
}
},
"button": {
@@ -105,6 +107,9 @@
"device_class": "Device class",
"state_class": "State class",
"unit_of_measurement": "Unit of measurement"
},
"data_description": {
"ignore_non_numeric": "If enabled, the group's state is calculated if at least one member has a numerical value. If disabled, the group's state is calculated only if all group members have numerical values."
}
},
"switch": {
@@ -120,11 +125,13 @@
"options": {
"step": {
"binary_sensor": {
"description": "[%key:component::group::config::step::binary_sensor::description%]",
"data": {
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
"hide_members": "[%key:component::group::config::step::binary_sensor::data::hide_members%]"
},
"data_description": {
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
}
},
"button": {
@@ -146,11 +153,13 @@
}
},
"light": {
"description": "[%key:component::group::config::step::binary_sensor::description%]",
"data": {
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
"hide_members": "[%key:component::group::config::step::binary_sensor::data::hide_members%]"
},
"data_description": {
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
}
},
"lock": {
@@ -172,7 +181,6 @@
}
},
"sensor": {
"description": "If \"ignore non-numeric\" is enabled, the group's state is calculated if at least one member has a numerical value. If \"ignore non-numeric\" is disabled, the group's state is calculated only if all group members have numerical values.",
"data": {
"ignore_non_numeric": "[%key:component::group::config::step::sensor::data::ignore_non_numeric%]",
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
@@ -182,14 +190,19 @@
"device_class": "[%key:component::group::config::step::sensor::data::device_class%]",
"state_class": "[%key:component::group::config::step::sensor::data::state_class%]",
"unit_of_measurement": "[%key:component::group::config::step::sensor::data::unit_of_measurement%]"
},
"data_description": {
"ignore_non_numeric": "[%key:component::group::config::step::sensor::data_description::ignore_non_numeric%]"
}
},
"switch": {
"description": "[%key:component::group::config::step::binary_sensor::description%]",
"data": {
"all": "[%key:component::group::config::step::binary_sensor::data::all%]",
"entities": "[%key:component::group::config::step::binary_sensor::data::entities%]",
"hide_members": "[%key:component::group::config::step::binary_sensor::data::hide_members%]"
},
"data_description": {
"all": "[%key:component::group::config::step::binary_sensor::data_description::all%]"
}
}
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/growatt_server",
"iot_class": "cloud_polling",
"loggers": ["growattServer"],
"requirements": ["growattServer==1.6.0"]
"requirements": ["growattServer==1.7.1"]
}

View File

@@ -7,15 +7,7 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from aiohttp import ClientError
from habiticalib import (
HabiticaClass,
HabiticaException,
NotAuthorizedError,
Skill,
TaskType,
TooManyRequestsError,
)
from habiticalib import Habitica, HabiticaClass, Skill, TaskType
from homeassistant.components.button import (
DOMAIN as BUTTON_DOMAIN,
@@ -23,16 +15,11 @@ from homeassistant.components.button import (
ButtonEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import ASSETS_URL, DOMAIN
from .coordinator import (
HabiticaConfigEntry,
HabiticaData,
HabiticaDataUpdateCoordinator,
)
from .coordinator import HabiticaConfigEntry, HabiticaData
from .entity import HabiticaBase
PARALLEL_UPDATES = 1
@@ -42,7 +29,7 @@ PARALLEL_UPDATES = 1
class HabiticaButtonEntityDescription(ButtonEntityDescription):
"""Describes Habitica button entity."""
press_fn: Callable[[HabiticaDataUpdateCoordinator], Any]
press_fn: Callable[[Habitica], Any]
available_fn: Callable[[HabiticaData], bool]
class_needed: HabiticaClass | None = None
entity_picture: str | None = None
@@ -73,13 +60,13 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.RUN_CRON,
translation_key=HabiticaButtonEntity.RUN_CRON,
press_fn=lambda coordinator: coordinator.habitica.run_cron(),
press_fn=lambda habitica: habitica.run_cron(),
available_fn=lambda data: data.user.needsCron is True,
),
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.BUY_HEALTH_POTION,
translation_key=HabiticaButtonEntity.BUY_HEALTH_POTION,
press_fn=lambda coordinator: coordinator.habitica.buy_health_potion(),
press_fn=lambda habitica: habitica.buy_health_potion(),
available_fn=(
lambda data: (data.user.stats.gp or 0) >= 25
and (data.user.stats.hp or 0) < 50
@@ -89,7 +76,7 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
translation_key=HabiticaButtonEntity.ALLOCATE_ALL_STAT_POINTS,
press_fn=lambda coordinator: coordinator.habitica.allocate_stat_points(),
press_fn=lambda habitica: habitica.allocate_stat_points(),
available_fn=(
lambda data: data.user.preferences.automaticAllocation is True
and (data.user.stats.points or 0) > 0
@@ -98,7 +85,7 @@ BUTTON_DESCRIPTIONS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.REVIVE,
translation_key=HabiticaButtonEntity.REVIVE,
press_fn=lambda coordinator: coordinator.habitica.revive(),
press_fn=lambda habitica: habitica.revive(),
available_fn=lambda data: data.user.stats.hp == 0,
),
)
@@ -108,9 +95,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.MPHEAL,
translation_key=HabiticaButtonEntity.MPHEAL,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.ETHEREAL_SURGE)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.ETHEREAL_SURGE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 30
@@ -121,7 +106,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.EARTH,
translation_key=HabiticaButtonEntity.EARTH,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.EARTHQUAKE),
press_fn=lambda habitica: habitica.cast_skill(Skill.EARTHQUAKE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 35
@@ -132,9 +117,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.FROST,
translation_key=HabiticaButtonEntity.FROST,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.CHILLING_FROST)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.CHILLING_FROST),
# chilling frost can only be cast once per day (streaks buff is false)
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14
@@ -147,9 +130,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.DEFENSIVE_STANCE,
translation_key=HabiticaButtonEntity.DEFENSIVE_STANCE,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.DEFENSIVE_STANCE)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.DEFENSIVE_STANCE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 25
@@ -160,9 +141,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.VALOROUS_PRESENCE,
translation_key=HabiticaButtonEntity.VALOROUS_PRESENCE,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.VALOROUS_PRESENCE)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.VALOROUS_PRESENCE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 20
@@ -173,9 +152,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.INTIMIDATE,
translation_key=HabiticaButtonEntity.INTIMIDATE,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.INTIMIDATING_GAZE)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.INTIMIDATING_GAZE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14
and (data.user.stats.mp or 0) >= 15
@@ -186,11 +163,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.TOOLS_OF_TRADE,
translation_key=HabiticaButtonEntity.TOOLS_OF_TRADE,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(
Skill.TOOLS_OF_THE_TRADE
)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.TOOLS_OF_THE_TRADE),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 25
@@ -201,7 +174,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.STEALTH,
translation_key=HabiticaButtonEntity.STEALTH,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.STEALTH),
press_fn=lambda habitica: habitica.cast_skill(Skill.STEALTH),
# Stealth buffs stack and it can only be cast if the amount of
# buffs is smaller than the amount of unfinished dailies
available_fn=(
@@ -224,9 +197,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.HEAL,
translation_key=HabiticaButtonEntity.HEAL,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.HEALING_LIGHT)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.HEALING_LIGHT),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 11
and (data.user.stats.mp or 0) >= 15
@@ -238,11 +209,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.BRIGHTNESS,
translation_key=HabiticaButtonEntity.BRIGHTNESS,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(
Skill.SEARING_BRIGHTNESS
)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.SEARING_BRIGHTNESS),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 12
and (data.user.stats.mp or 0) >= 15
@@ -253,9 +220,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.PROTECT_AURA,
translation_key=HabiticaButtonEntity.PROTECT_AURA,
press_fn=(
lambda coordinator: coordinator.habitica.cast_skill(Skill.PROTECTIVE_AURA)
),
press_fn=lambda habitica: habitica.cast_skill(Skill.PROTECTIVE_AURA),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 13
and (data.user.stats.mp or 0) >= 30
@@ -266,7 +231,7 @@ CLASS_SKILLS: tuple[HabiticaButtonEntityDescription, ...] = (
HabiticaButtonEntityDescription(
key=HabiticaButtonEntity.HEAL_ALL,
translation_key=HabiticaButtonEntity.HEAL_ALL,
press_fn=lambda coordinator: coordinator.habitica.cast_skill(Skill.BLESSING),
press_fn=lambda habitica: habitica.cast_skill(Skill.BLESSING),
available_fn=(
lambda data: (data.user.stats.lvl or 0) >= 14
and (data.user.stats.mp or 0) >= 25
@@ -332,33 +297,9 @@ class HabiticaButton(HabiticaBase, ButtonEntity):
async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.entity_description.press_fn(self.coordinator)
except TooManyRequestsError as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="setup_rate_limit_exception",
translation_placeholders={"retry_after": str(e.retry_after)},
) from e
except NotAuthorizedError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_unallowed",
) from e
except HabiticaException as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
translation_placeholders={"reason": e.error.message},
) from e
except ClientError as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="service_call_exception",
translation_placeholders={"reason": str(e)},
) from e
else:
await self.coordinator.async_request_refresh()
await self.coordinator.execute(self.entity_description.press_fn)
await self.coordinator.async_request_refresh()
@property
def available(self) -> bool:

View File

@@ -164,7 +164,6 @@ class HabiticaConfigFlow(ConfigFlow, domain=DOMAIN):
data={
CONF_API_USER: str(login.id),
CONF_API_KEY: login.apiToken,
CONF_NAME: user.profile.name, # needed for api_call action
CONF_URL: DEFAULT_URL,
CONF_VERIFY_SSL: True,
},
@@ -200,7 +199,6 @@ class HabiticaConfigFlow(ConfigFlow, domain=DOMAIN):
data={
**user_input,
CONF_URL: user_input.get(CONF_URL, DEFAULT_URL),
CONF_NAME: user.profile.name, # needed for api_call action
},
)

View File

@@ -23,12 +23,12 @@ from habiticalib import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
ServiceValidationError,
)
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -106,12 +106,6 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
translation_placeholders={"reason": str(e)},
) from e
if not self.config_entry.data.get(CONF_NAME):
self.hass.config_entries.async_update_entry(
self.config_entry,
data={**self.config_entry.data, CONF_NAME: user.data.profile.name},
)
async def _async_update_data(self) -> HabiticaData:
try:
user = (await self.habitica.get_user()).data
@@ -137,19 +131,22 @@ class HabiticaDataUpdateCoordinator(DataUpdateCoordinator[HabiticaData]):
else:
return HabiticaData(user=user, tasks=tasks + completed_todos)
async def execute(
self, func: Callable[[HabiticaDataUpdateCoordinator], Any]
) -> None:
async def execute(self, func: Callable[[Habitica], Any]) -> None:
"""Execute an API call."""
try:
await func(self)
await func(self.habitica)
except TooManyRequestsError as e:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="setup_rate_limit_exception",
translation_placeholders={"retry_after": str(e.retry_after)},
) from e
except NotAuthorizedError as e:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="service_call_unallowed",
) from e
except HabiticaException as e:
raise HomeAssistantError(
translation_domain=DOMAIN,

View File

@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
from yarl import URL
from homeassistant.const import CONF_NAME, CONF_URL
from homeassistant.const import CONF_URL
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -37,7 +37,7 @@ class HabiticaBase(CoordinatorEntity[HabiticaDataUpdateCoordinator]):
entry_type=DeviceEntryType.SERVICE,
manufacturer=MANUFACTURER,
model=NAME,
name=coordinator.config_entry.data[CONF_NAME],
name=coordinator.data.user.profile.name,
configuration_url=(
URL(coordinator.config_entry.data[CONF_URL])
/ "profile"

View File

@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["habiticalib"],
"quality_scale": "platinum",
"requirements": ["habiticalib==0.4.0"]
"requirements": ["habiticalib==0.4.1"]
}

View File

@@ -7,6 +7,8 @@ from dataclasses import dataclass
from enum import StrEnum
from typing import Any
from habiticalib import Habitica
from homeassistant.components.switch import (
SwitchDeviceClass,
SwitchEntity,
@@ -15,11 +17,7 @@ from homeassistant.components.switch import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import (
HabiticaConfigEntry,
HabiticaData,
HabiticaDataUpdateCoordinator,
)
from .coordinator import HabiticaConfigEntry, HabiticaData
from .entity import HabiticaBase
PARALLEL_UPDATES = 1
@@ -29,8 +27,8 @@ PARALLEL_UPDATES = 1
class HabiticaSwitchEntityDescription(SwitchEntityDescription):
"""Describes Habitica switch entity."""
turn_on_fn: Callable[[HabiticaDataUpdateCoordinator], Any]
turn_off_fn: Callable[[HabiticaDataUpdateCoordinator], Any]
turn_on_fn: Callable[[Habitica], Any]
turn_off_fn: Callable[[Habitica], Any]
is_on_fn: Callable[[HabiticaData], bool | None]
@@ -45,8 +43,8 @@ SWTICH_DESCRIPTIONS: tuple[HabiticaSwitchEntityDescription, ...] = (
key=HabiticaSwitchEntity.SLEEP,
translation_key=HabiticaSwitchEntity.SLEEP,
device_class=SwitchDeviceClass.SWITCH,
turn_on_fn=lambda coordinator: coordinator.habitica.toggle_sleep(),
turn_off_fn=lambda coordinator: coordinator.habitica.toggle_sleep(),
turn_on_fn=lambda habitica: habitica.toggle_sleep(),
turn_off_fn=lambda habitica: habitica.toggle_sleep(),
is_on_fn=lambda data: data.user.preferences.sleep,
),
)

View File

@@ -9,6 +9,7 @@
"healthy": "Healthy",
"host_os": "Host operating system",
"installed_addons": "Installed add-ons",
"nameservers": "Nameservers",
"supervisor_api": "Supervisor API",
"supervisor_version": "Supervisor version",
"supported": "Supported",
@@ -225,6 +226,10 @@
"unsupported_virtualization_image": {
"title": "Unsupported system - Incorrect OS image for virtualization",
"description": "System is unsupported because the Home Assistant OS image in use is not intended for use in a virtualized environment. Use the link to learn more and how to fix this."
},
"unsupported_os_version": {
"title": "Unsupported system - Home Assistant OS version",
"description": "System is unsupported because the Home Assistant OS version in use is not supported. Use the link to learn more and how to fix this."
}
},
"entity": {

View File

@@ -54,6 +54,15 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"error": "Unsupported",
}
nameservers = set()
for interface in network_info.get("interfaces", []):
if not interface.get("primary"):
continue
if ipv4 := interface.get("ipv4"):
nameservers.update(ipv4.get("nameservers", []))
if ipv6 := interface.get("ipv6"):
nameservers.update(ipv6.get("nameservers", []))
information = {
"host_os": host_info.get("operating_system"),
"update_channel": info.get("channel"),
@@ -62,6 +71,7 @@ async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"docker_version": info.get("docker"),
"disk_total": f"{host_info.get('disk_total')} GB",
"disk_used": f"{host_info.get('disk_used')} GB",
"nameservers": ", ".join(nameservers),
"healthy": healthy,
"supported": supported,
"host_connectivity": network_info.get("host_internet"),

View File

@@ -2,11 +2,13 @@
from __future__ import annotations
import logging
from homeassistant.const import CONF_API_KEY, CONF_MODE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.start import async_at_started
from .const import TRAVEL_MODE_PUBLIC
from .const import CONF_TRAFFIC_MODE, TRAVEL_MODE_PUBLIC
from .coordinator import (
HereConfigEntry,
HERERoutingDataUpdateCoordinator,
@@ -15,6 +17,8 @@ from .coordinator import (
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, config_entry: HereConfigEntry) -> bool:
"""Set up HERE Travel Time from a config entry."""
@@ -43,3 +47,28 @@ async def async_unload_entry(
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
async def async_migrate_entry(
hass: HomeAssistant, config_entry: HereConfigEntry
) -> bool:
"""Migrate an old config entry."""
if config_entry.version == 1 and config_entry.minor_version == 1:
_LOGGER.debug(
"Migrating from version %s.%s",
config_entry.version,
config_entry.minor_version,
)
options = dict(config_entry.options)
options[CONF_TRAFFIC_MODE] = True
hass.config_entries.async_update_entry(
config_entry, options=options, version=1, minor_version=2
)
_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,
config_entry.minor_version,
)
return True

View File

@@ -33,6 +33,7 @@ from homeassistant.const import (
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.selector import (
BooleanSelector,
EntitySelector,
LocationSelector,
TimeSelector,
@@ -50,6 +51,7 @@ from .const import (
CONF_ORIGIN_LATITUDE,
CONF_ORIGIN_LONGITUDE,
CONF_ROUTE_MODE,
CONF_TRAFFIC_MODE,
DEFAULT_NAME,
DOMAIN,
ROUTE_MODE_FASTEST,
@@ -65,6 +67,7 @@ DEFAULT_OPTIONS = {
CONF_ROUTE_MODE: ROUTE_MODE_FASTEST,
CONF_ARRIVAL_TIME: None,
CONF_DEPARTURE_TIME: None,
CONF_TRAFFIC_MODE: True,
}
@@ -102,6 +105,7 @@ class HERETravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for HERE Travel Time."""
VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None:
"""Init Config Flow."""
@@ -307,7 +311,9 @@ class HERETravelTimeOptionsFlow(OptionsFlow):
"""Manage the HERE Travel Time options."""
if user_input is not None:
self._config = user_input
return await self.async_step_time_menu()
if self._config[CONF_TRAFFIC_MODE]:
return await self.async_step_time_menu()
return self.async_create_entry(title="", data=self._config)
schema = self.add_suggested_values_to_schema(
vol.Schema(
@@ -318,12 +324,21 @@ class HERETravelTimeOptionsFlow(OptionsFlow):
CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE]
),
): vol.In(ROUTE_MODES),
vol.Optional(
CONF_TRAFFIC_MODE,
default=self.config_entry.options.get(
CONF_TRAFFIC_MODE, DEFAULT_OPTIONS[CONF_TRAFFIC_MODE]
),
): BooleanSelector(),
}
),
{
CONF_ROUTE_MODE: self.config_entry.options.get(
CONF_ROUTE_MODE, DEFAULT_OPTIONS[CONF_ROUTE_MODE]
),
CONF_TRAFFIC_MODE: self.config_entry.options.get(
CONF_TRAFFIC_MODE, DEFAULT_OPTIONS[CONF_TRAFFIC_MODE]
),
},
)

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