Compare commits

..

125 Commits

Author SHA1 Message Date
Franck Nijhof 6952d2420f 2024.10.2 (#128176) 2024-10-11 18:25:02 +02:00
Franck Nijhof 9176994947 Bump version to 2024.10.2 2024-10-11 17:54:37 +02:00
Thomas55555 d389b55f40 Fix model in Husqvarna Automower (#128168) 2024-10-11 17:54:24 +02:00
Christopher Fenner 0ccff9fc54 Fix preset handling issue in ViCare (#128167)
* add test case

* fix test case

* fix issue

* change order
2024-10-11 17:54:21 +02:00
tronikos a8836ca7b6 Remove some redundant code in Opower's coordinator from the fix in #128141 (#128150) 2024-10-11 17:54:17 +02:00
tronikos f5d04a970f Bump opower to 0.8.3 (#128144) 2024-10-11 17:54:14 +02:00
tronikos 7aec98dafd Fix regression in Opower that was introduced in 2024.10.0 (#128141)
* Avoid KeyError when statistics have gaps

* fix break

* Remove unnecessary check
2024-10-11 17:54:10 +02:00
Marc Mueller 773564d4f5 Fix license script for ftfy (#128138) 2024-10-11 17:54:04 +02:00
Thomas55555 3e2edc1a2d Bump aioautomower to 2024.10.0 (#128137)
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2024-10-11 17:53:59 +02:00
Bram Kragten 9cfc9b9baf Update frontend to 20241002.3 (#128106) 2024-10-11 17:52:03 +02:00
David Knowles 92b67ead83 Increase Hydrawise polling interval to 60 seconds (#128090) 2024-10-11 17:50:35 +02:00
Steven B ee9525cc00 Fix ring realtime events (#128083) 2024-10-11 17:49:38 +02:00
kingy444 571bfaf5d7 Fix casing on Powerview Gen3 zeroconf discovery (#128076) 2024-10-11 17:37:31 +02:00
Matthias Alphart a3475607b2 Update xknxproject to 3.8.1 (#128057) 2024-10-11 17:37:28 +02:00
Michael f0a653d010 Add missing translation string for re-auth flows (#128055) 2024-10-11 17:37:23 +02:00
Steven B. eecdf66013 Fix missing reauth name translation placeholder in ring integration (#128048) 2024-10-11 17:37:19 +02:00
dontinelli f99db05a4a Add missing translation string in solarlog (#128015) 2024-10-11 17:37:16 +02:00
Teemu R. 635731421f Increase tplink climate precision (#127996) 2024-10-11 17:37:13 +02:00
Maikel Punie 44743df7d6 Bump pyduotecno to 2024.10.0 (#127979) 2024-10-11 17:37:09 +02:00
epenet 33617694cc Fix firmware version parsing in venstar (#127974) 2024-10-11 17:37:06 +02:00
Raman Gupta ed445d20b9 Fix zwave_js config validation for values (#127972) 2024-10-11 17:37:02 +02:00
Lenn 66c2fe091b Bump motionblindsble to 0.1.2 (#127954) 2024-10-11 17:36:59 +02:00
Antoine Reversat 8c80f47a35 Fix europe authentication in Fujitsu FGLair (#127947) 2024-10-11 17:36:53 +02:00
Marc Hörsken e37025c1c7 Update pywmspro to 0.2.1 to fix handling of unknown products (#127942) 2024-10-11 17:36:50 +02:00
Marc Hörsken 0aabde081b Fix discovery of WMS WebControl pro by using IP address (#127939) 2024-10-11 17:36:46 +02:00
Steven B. a1c9d53474 Bump python-kasa to 0.7.5 (#127934) 2024-10-11 17:36:43 +02:00
Maciej Bieniek 094996ad0c Bump imgw_pib library to version 1.0.6 (#127925)
Bump `imgw_pib`
2024-10-11 17:36:40 +02:00
Manu ce359a7689 Add support of due date calculation for grey dailies in Habitica integration (#127923)
Fix grey dailies due date calculation
2024-10-11 17:36:36 +02:00
epenet ee599160b3 Add missing translation string in yamaha_musiccast (#127912) 2024-10-11 17:36:32 +02:00
epenet dd076f7a13 Add missing translation string in otbr (#127909) 2024-10-11 17:36:29 +02:00
dcmeglio 3021d38b6f Bump pyeconet to 0.1.23 (#127896) 2024-10-11 17:36:25 +02:00
G Johansson bfcabeaf26 Bump holidays library to 0.58 (#127876) 2024-10-11 17:36:22 +02:00
G Johansson c31e0336dc Don't error with missing information in systemmonitor diagnostics (#127868) 2024-10-11 17:36:14 +02:00
G Johansson a1e42cac7a Fix merge_response template not mutate original object (#127865)
* Fix merge_response template not mutate original object

* Add comment
2024-10-11 17:36:11 +02:00
Michael 14a3e5b771 Add missing translation string in AVM Fritz!Smarthome (#127864) 2024-10-11 17:36:07 +02:00
azerty9971 5901c543da Fix wrong DPTypes returned by Tuya's cloud (#127860)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-10-11 17:36:04 +02:00
Jon Seager 456b80e6ae Bump pytouchlinesl to 0.1.8 (#127859) 2024-10-11 17:36:00 +02:00
dontinelli e5644ae011 Reverse unintended change of unique_id for solarlog (#127845) 2024-10-11 17:35:57 +02:00
epenet 41c794c733 Add missing and fix incorrect translation string in duotecno (#127834) 2024-10-11 17:35:53 +02:00
epenet a481448d46 Fix incorrect translation string in bryant_evolution (#127830) 2024-10-11 17:35:49 +02:00
epenet 2bd7ce618a Add missing translation string in blebox (#127827) 2024-10-11 17:35:45 +02:00
Erik Montnemery da1ac4f1e9 Correct cleanup of sensor statistics repairs (#127826) 2024-10-11 17:35:42 +02:00
epenet f0cb638106 Fix incorrect translation string in azure event hub (#127820) 2024-10-11 17:35:38 +02:00
epenet dad2396d01 Add missing and fix incorrect translation string in aurora (#127818) 2024-10-11 17:35:34 +02:00
epenet 91e4d8b663 Fix incorrect translation string in analytics_insights (#127815) 2024-10-11 17:35:31 +02:00
epenet e35496133e Add missing and fix incorrect translation string in alarmdecoder (#127814) 2024-10-11 17:35:28 +02:00
epenet 3be808ae1e Fix incorrect string in amberlectric (#127807) 2024-10-11 17:35:23 +02:00
TimL c5772916a1 Bump pysmlight to v0.1.3 (#127804)
Bump pysmlight v0.1.3

Co-authored-by: Tim Lunn <tim@feathertop.org>
2024-10-11 17:35:18 +02:00
Álvaro Fernández Rojas 8cd63b80b1 Update aioairzone-cloud to v0.6.6 (#127789) 2024-10-11 17:35:14 +02:00
Johan Gustafsson c087654386 Fix aurora alert sensor always Off (#127780) 2024-10-11 17:35:11 +02:00
Erik Montnemery 60b9e65c78 Bump pychromecast to 14.0.3 (#127778) 2024-10-11 17:35:08 +02:00
J. Nick Koston 79b304a5d2 Bump DoorBirdPy to 3.0.4 (#127760)
changelog: https://gitlab.com/klikini/doorbirdpy/-/compare/3.0.3...eea287316c6fd84b63cc67fd743cc1128ea14568?from_project_id=7409088&straight=false

fixes #126598
2024-10-11 17:35:00 +02:00
Franck Nijhof bb9fd126e5 Update DoorBirdPy to 3.0.3 (#126949) 2024-10-11 17:34:40 +02:00
dontinelli bff2d5c26c Bump solarlog_cli to 0.3.1 (#127753) 2024-10-11 17:31:18 +02:00
Ricardo Marques 46d9ac8380 Fix custom account config flow setup (#127750) 2024-10-11 17:31:15 +02:00
Simon Lamon 5da3ca4bb1 Bump python-linkplay to 0.0.15 (#127748) 2024-10-11 17:31:12 +02:00
Johan Gustafsson 2c99fdc092 Fix Aurora integration casts longitude and latitude to integer (#127740)
Fix Aurora integration casts longitude and latitude to integer (#100817)
2024-10-11 17:31:07 +02:00
Raj Laud 31a075fb13 Remove stale references in squeezebox services.yaml (#127739) 2024-10-11 17:31:04 +02:00
René Klomp 1d132d7a1e Migrate SMA unique id to str (#127732) 2024-10-11 17:31:01 +02:00
Michael 3b6f88cfa7 Increase connection timeout in CalDAV (#127727) 2024-10-11 17:30:57 +02:00
Joost Lekkerkerker b927763d8d Add translation string for Withings wrong account (#127719) 2024-10-11 17:30:54 +02:00
Joost Lekkerkerker d00e1cb6a5 Bump airgradient to 0.9.1 (#127718) 2024-10-11 17:30:50 +02:00
Joost Lekkerkerker adf7474edb Bump NYT Games to 0.4.3 (#127717) 2024-10-11 17:30:46 +02:00
Joost Lekkerkerker 041d663cb8 Fix Withings log message (#127716) 2024-10-11 17:30:42 +02:00
Joost Lekkerkerker 37f611a8d3 Fix typo in HDMI CEC (#127714) 2024-10-11 17:30:39 +02:00
David Knowles be99329efa Fix problems with automatic management of Schlage locks (#127689)
Use the correct identifiers for existing lock devices
2024-10-11 17:30:35 +02:00
Marc Mueller 327cb70bb8 Revert "Fix enum lookup (#125220)" (#127680)
This reverts commit 1bc63a61be.
2024-10-11 17:30:32 +02:00
dontinelli be2b5a4c3a Bump fyta_cli to 0.6.7 (#127650) 2024-10-11 17:30:29 +02:00
Richard Cox d1eda9dd73 Update Radarr config flow to standardize ports (#127620) 2024-10-11 17:30:25 +02:00
Brett Adams b902cb5a13 Fix wake up in Tesla Fleet (#127615) 2024-10-11 17:30:21 +02:00
tronikos 1184ee4a59 Bump opower to 0.8.2 (#127598)
* Bump opower to 0.8.1 to fix enmax

* Update manifest.json

* Update requirements_all.txt

* Update requirements_test_all.txt
2024-10-11 17:30:18 +02:00
Noah Husby 2cf898afcc Bump aiostreammagic to 2.5.0 (#127595) 2024-10-11 17:30:13 +02:00
Louis Christ df53e19eda Bump pyblu to 1.0.3 (#127571) 2024-10-11 17:28:18 +02:00
Brett Adams 7f79b26341 Fix Island status in Teslemetry (#127504) 2024-10-11 17:28:13 +02:00
Franck Nijhof 2182bc3af2 2024.10.1 (#127566) 2024-10-04 19:33:37 +02:00
Franck Nijhof 2cbf53ad7b Bump version to 2024.10.1 2024-10-04 14:57:14 +02:00
Joost Lekkerkerker c52607b465 Revert "Bump pychromecast to 14.0.2 (#127333)" (#127555)
This reverts commit 2ab66f62fa.
2024-10-04 14:57:01 +02:00
Joost Lekkerkerker 087566072d Strip the NYT Games token (#127548) 2024-10-04 14:56:58 +02:00
Joost Lekkerkerker 6b814afd39 Create new clientsession for NYT Games (#127547) 2024-10-04 14:56:54 +02:00
Franck Nijhof ea8aa6b07d Adjust polling rate of Rituals Perfume Genie (#127544) 2024-10-04 14:56:51 +02:00
Paarth Shah 1b0f731e30 Bump matrix-nio to 0.25.2 (#127535) 2024-10-04 14:56:48 +02:00
robinostlund 1ebde4a880 Fix int value in unique_id for Tellduslive (#127526)
Fix int in unique_id
2024-10-04 14:56:45 +02:00
Andrew Jackson e53bd477b4 Bump aiomealie to 0.9.3 (#127454) 2024-10-04 14:56:41 +02:00
Brett Adams 3f9287c36b Add missing number platform to init of Tesla Fleet (#127406)
Add number platform to init
2024-10-04 14:56:38 +02:00
Erik Montnemery b2b940fc32 Remove assumption in ConfigEntryItems about unique unique_id (#127399) 2024-10-04 14:56:35 +02:00
TimL 7d9e170512 Bump pysmlight 0.1.2 (#127376)
Co-authored-by: Tim Lunn <tim@feathertop.org>
2024-10-04 14:56:29 +02:00
Paul Bottein 6ab92abe80 Fix device id support for alarm control panel template (#127340) 2024-10-04 14:47:25 +02:00
Franck Nijhof 5db4a73d8e 2024.10.0 (#126782) 2024-10-02 19:41:46 +02:00
Franck Nijhof acb0aeaa9a Bump version to 2024.10.0 2024-10-02 19:17:08 +02:00
Michael Hansen dc7c909316 Bump intents to 2024.10.2 (#127338) 2024-10-02 19:14:50 +02:00
Erik Montnemery a052e15319 Bump pychromecast to 14.0.2 (#127333) 2024-10-02 19:12:56 +02:00
Franck Nijhof a50b299a82 Bump version to 2024.10.0b12 2024-10-02 17:18:01 +02:00
Bram Kragten a6808a8fda Update frontend to 20241002.2 (#127331) 2024-10-02 17:16:58 +02:00
Franck Nijhof 7ac944c537 Bump version to 2024.10.0b11 2024-10-02 16:01:13 +02:00
Bram Kragten 7d3dd2dd6b Update frontend to 20241002.1 (#127292) 2024-10-02 16:00:43 +02:00
Christopher Fenner 48538ef5d5 Fix climate entity in ViCare integration (#127128)
do not reset _attributes
2024-10-02 16:00:38 +02:00
Franck Nijhof 5365439fd4 Bump version to 2024.10.0b10 2024-10-02 10:52:33 +02:00
Erik Montnemery 9c28a4e8a0 Make recorder WS command recorder/clear_statistics wait (#127120) 2024-10-02 10:51:30 +02:00
Bram Kragten 565203047c Update frontend to 20241002.0 (#127264) 2024-10-02 10:50:39 +02:00
Erik Montnemery b9795a2ae7 Make recorder WS command recorder/update_statistics_metadata wait (#127179) 2024-10-02 10:50:35 +02:00
Franck Nijhof 4e4f8ee3a4 Bump version to 2024.10.0b9 2024-10-02 09:26:37 +02:00
Martin Hjelmare b8fd921c81 Revert "Support Z-Wave JS dimming lights using color intensity (#122639)" (#127256)
This reverts commit c7cfd56b72.
2024-10-02 09:26:28 +02:00
Teemu R. fcf91954ff Remove codefences from issue titles (#127254) 2024-10-02 09:26:25 +02:00
Michael Hansen 49708196ac Run unsubscribe callbacks when Assist satellite entity is removed from HA (#127234)
* Unsubscribe when removed from HA

* Use builtin async_on_remove
2024-10-02 09:26:22 +02:00
functionpointer 8c8a2eef21 Fix Tibber get_prices when called with aware datetime (#123289)
* Tibber: Add extra test to expose aware/naive datetime issue

* Tibber: Fix get_prices action not working with aware datetimes

* Tibber: Simplify comparison

* Tibber: Combine timezone tests into single parametrized one

* Tibber: Split test again to prevent if statement
2024-10-02 09:26:15 +02:00
Franck Nijhof 749a5b37c9 Bump version to 2024.10.0b8 2024-10-01 22:14:57 +02:00
epenet 60079a14e7 Update log error message for Samsung TV (#127231) 2024-10-01 22:14:50 +02:00
Erik Montnemery df6edd09c0 Don't create statistics issues when sensor is unavailable or unknown (#127226) 2024-10-01 22:14:47 +02:00
epenet 88ff94dd69 Use reconfigure_confirm in bryant_evolution config flow (#127222) 2024-10-01 22:14:44 +02:00
epenet 067b81a60b Use reconfigure_confirm in enphase_envoy config flow (#127221) 2024-10-01 22:14:40 +02:00
epenet 03553b8bb9 Use reconfigure_confirm in homeworks config flow (#127218)
* Use reconfigure_confirm in homeworks config flow

* Fix tests
2024-10-01 22:14:37 +02:00
Franck Nijhof bce7552d4d Update gotailwind to 0.2.4 (#127129) 2024-10-01 22:14:34 +02:00
Russell Cloran 53a2777831 Update prometheus-client to 0.21.0 (#126965) 2024-10-01 22:14:31 +02:00
Bill Flood 507492947a Fix Tailwind cover exception when door is already in the requested state (#124543) 2024-10-01 22:14:27 +02:00
Franck Nijhof 41b3eb9f79 Bump version to 2024.10.0b7 2024-10-01 14:54:05 +02:00
epenet f2c746122e Use reconfigure_confirm in google_travel_time config flow (#127220) 2024-10-01 14:53:39 +02:00
epenet e25a54aef4 Use reconfigure_confirm in lcn config flow (#127217) 2024-10-01 14:53:36 +02:00
epenet 5c42e45048 Fix reconfigure_confirm logic in madvr config flow (#127216) 2024-10-01 14:53:32 +02:00
epenet c8b92bc858 Use reconfigure_confirm in solarlog config flow (#127215)
* Use reconfigure_confirm in solarlog config flow

* Fix test
2024-10-01 14:53:29 +02:00
epenet 9d059fcfaa Use reconfigure_confirm in vallox config flow (#127214) 2024-10-01 14:53:26 +02:00
Martin Hjelmare ce5f193219 Fix Z-Wave rediscovery (#127213) 2024-10-01 14:53:22 +02:00
Erik Montnemery 92023ecbe6 Update assist_satellite connection test sound (#127183) 2024-10-01 14:53:19 +02:00
cdnninja 1e0164a96a Allows unload when unsupported devices vesync (#127153)
Allows unload when unsupported devices
2024-10-01 14:53:15 +02:00
G Johansson 6f5eac3143 Add config flow validation that calibration factor is not zero (#127136)
* Add config flow validation that calibration factor is not zero

* Add test
2024-10-01 14:50:10 +02:00
Nerdix 60dfccb747 Roborock fix "selected map" when first map in list is selected (#127126)
* avoid None when current_map = 0

* combine statements
2024-10-01 14:46:41 +02:00
198 changed files with 2833 additions and 1387 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ on:
type: boolean
env:
CACHE_VERSION: 10
CACHE_VERSION: 11
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.10"
@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.9.0"],
"requirements": ["airgradient==0.9.1"],
"zeroconf": ["_airgradient._tcp.local."]
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airzone_cloud",
"iot_class": "cloud_push",
"loggers": ["aioairzone_cloud"],
"requirements": ["aioairzone-cloud==0.6.5"]
"requirements": ["aioairzone-cloud==0.6.6"]
}
@@ -22,7 +22,8 @@
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"create_entry": {
"default": "Successfully connected to AlarmDecoder."
@@ -37,7 +38,7 @@
"title": "Configure AlarmDecoder",
"description": "What would you like to edit?",
"data": {
"edit_select": "Edit"
"edit_selection": "Edit"
}
},
"arm_settings": {
@@ -10,7 +10,7 @@
},
"site": {
"data": {
"site_nmi": "Site NMI",
"site_id": "Site NMI",
"site_name": "Site Name"
},
"description": "Select the NMI of the site you would like to add"
@@ -17,7 +17,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
"no_integration_selected": "You must select at least one integration to track"
"no_integrations_selected": "You must select at least one integration to track"
}
},
"options": {
@@ -37,7 +37,7 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"error": {
"no_integration_selected": "[%key:component::analytics_insights::config::error::no_integration_selected%]"
"no_integrations_selected": "[%key:component::analytics_insights::config::error::no_integrations_selected%]"
}
},
"entity": {
Binary file not shown.
@@ -4,6 +4,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import CONF_THRESHOLD, DEFAULT_THRESHOLD
from .coordinator import AuroraDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
@@ -21,9 +22,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bo
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(update_listener))
return True
async def update_listener(hass: HomeAssistant, entry: AuroraConfigEntry) -> None:
"""Handle options update."""
entry.runtime_data.threshold = int(
entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
)
# refresh the state of the visibility alert binary sensor
await entry.runtime_data.async_request_refresh()
async def async_unload_entry(hass: HomeAssistant, entry: AuroraConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
@@ -38,8 +38,8 @@ class AuroraDataUpdateCoordinator(DataUpdateCoordinator[int]):
)
self.api = AuroraForecast(async_get_clientsession(hass))
self.latitude = int(self.config_entry.data[CONF_LATITUDE])
self.longitude = int(self.config_entry.data[CONF_LONGITUDE])
self.latitude = round(self.config_entry.data[CONF_LATITUDE])
self.longitude = round(self.config_entry.data[CONF_LONGITUDE])
self.threshold = int(
self.config_entry.options.get(CONF_THRESHOLD, DEFAULT_THRESHOLD)
)
+3 -2
View File
@@ -14,14 +14,15 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"options": {
"step": {
"init": {
"data": {
"threshold": "Threshold (%)"
"forecast_threshold": "Threshold (%)"
}
}
}
@@ -38,7 +38,7 @@
},
"options": {
"step": {
"options": {
"init": {
"title": "Options for the Azure Event Hub.",
"data": {
"send_interval": "Interval between sending batches to the hub."
+3 -1
View File
@@ -15,7 +15,9 @@
"description": "Set up your BleBox to integrate with Home Assistant.",
"data": {
"host": "[%key:common::config_flow::data::ip%]",
"port": "[%key:common::config_flow::data::port%]"
"password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]",
"username": "[%key:common::config_flow::data::username%]"
},
"title": "Set up your BleBox device"
}
@@ -6,7 +6,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling",
"requirements": ["pyblu==1.0.2"],
"requirements": ["pyblu==1.0.3"],
"zeroconf": [
{
"type": "_musc._tcp.local."
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
@@ -61,6 +62,12 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle integration reconfiguration."""
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle integration reconfiguration."""
@@ -83,5 +90,7 @@ class BryantConfigFlow(ConfigFlow, domain=DOMAIN):
)
errors["base"] = "cannot_connect"
return self.async_show_form(
step_id="reconfigure", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
step_id="reconfigure_confirm",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
)
@@ -1,6 +1,11 @@
{
"config": {
"step": {
"reconfigure_confirm": {
"data": {
"filename": "[%key:component::bryant_evolution::config::step::user::data::filename%]"
}
},
"user": {
"data": {
"filename": "Serial port filename"
+1 -1
View File
@@ -34,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
ssl_verify_cert=entry.data[CONF_VERIFY_SSL],
timeout=10,
timeout=30,
)
try:
await hass.async_add_executor_job(client.principal)
@@ -111,7 +111,7 @@
},
"issues": {
"deprecated_service_calendar_list_events": {
"title": "Detected use of deprecated action `calendar.list_events`",
"title": "Detected use of deprecated action calendar.list_events",
"fix_flow": {
"step": {
"confirm": {
@@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["aiostreammagic"],
"requirements": ["aiostreammagic==2.3.1"],
"requirements": ["aiostreammagic==2.5.0"],
"zeroconf": ["_stream-magic._tcp.local.", "_smoip._tcp.local."]
}
+1 -1
View File
@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/cast",
"iot_class": "local_polling",
"loggers": ["casttube", "pychromecast"],
"requirements": ["PyChromecast==14.0.1"],
"requirements": ["PyChromecast==14.0.3"],
"zeroconf": ["_googlecast._tcp.local."]
}
+1 -1
View File
@@ -25,7 +25,7 @@
},
"issues": {
"deprecated_gender": {
"title": "The `{deprecated_option}` text-to-speech option is deprecated",
"title": "The {deprecated_option} text-to-speech option is deprecated",
"fix_flow": {
"step": {
"confirm": {
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==1.7.4", "home-assistant-intents==2024.9.23"]
"requirements": ["hassil==1.7.4", "home-assistant-intents==2024.10.2"]
}
@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/doorbird",
"iot_class": "local_push",
"loggers": ["doorbirdpy"],
"requirements": ["DoorBirdPy==3.0.2"],
"requirements": ["DoorBirdPy==3.0.4"],
"zeroconf": [
{
"type": "_axis-video._tcp.local.",
@@ -7,5 +7,5 @@
"iot_class": "local_push",
"loggers": ["pyduotecno", "pyduotecno-node", "pyduotecno-unit"],
"quality_scale": "silver",
"requirements": ["pyDuotecno==2024.9.0"]
"requirements": ["pyDuotecno==2024.10.0"]
}
@@ -5,18 +5,21 @@
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
"password": "[%key:common::config_flow::data::password%]",
"port": "[%key:common::config_flow::data::port%]"
},
"data_description": {
"host": "The hostname or IP address of your Duotecno device."
}
}
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"entity": {
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/econet",
"iot_class": "cloud_push",
"loggers": ["paho_mqtt", "pyeconet"],
"requirements": ["pyeconet==0.1.22"]
"requirements": ["pyeconet==0.1.23"]
}
@@ -54,6 +54,8 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_reconnect_entry: ConfigEntry
def __init__(self) -> None:
"""Initialize an envoy flow."""
self.ip_address: str | None = None
@@ -233,17 +235,22 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Add reconfigure step to allow to manually reconfigure a config entry."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._reconnect_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Add reconfigure step to allow to manually reconfigure a config entry."""
errors: dict[str, str] = {}
description_placeholders: dict[str, str] = {}
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
suggested_values: dict[str, Any] | MappingProxyType[str, Any] = (
user_input or entry.data
user_input or self._reconnect_entry.data
)
host: Any = suggested_values.get(CONF_HOST)
@@ -284,7 +291,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
error="reconfigure_successful",
)
if not self.unique_id:
await self.async_set_unique_id(entry.unique_id)
await self.async_set_unique_id(self._reconnect_entry.unique_id)
self.context["title_placeholders"] = {
CONF_SERIAL: self.unique_id,
@@ -292,7 +299,7 @@ class EnphaseConfigFlow(ConfigFlow, domain=DOMAIN):
}
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
self._async_generate_schema(), suggested_values
),
@@ -13,7 +13,7 @@
"host": "The hostname or IP address of your Enphase Envoy gateway."
}
},
"reconfigure": {
"reconfigure_confirm": {
"description": "[%key:component::enphase_envoy::config::step::user::description%]",
"data": {
"host": "[%key:common::config_flow::data::host%]",
@@ -212,7 +212,7 @@ class EsphomeAssistSatellite(
)
if feature_flags & VoiceAssistantFeature.API_AUDIO:
# TCP audio
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
self.cli.subscribe_voice_assistant(
handle_start=self.handle_pipeline_start,
handle_stop=self.handle_pipeline_stop,
@@ -222,7 +222,7 @@ class EsphomeAssistSatellite(
)
else:
# UDP audio
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
self.cli.subscribe_voice_assistant(
handle_start=self.handle_pipeline_start,
handle_stop=self.handle_pipeline_stop,
@@ -235,7 +235,7 @@ class EsphomeAssistSatellite(
assert (self.registry_entry is not None) and (
self.registry_entry.device_id is not None
)
self.entry_data.disconnect_callbacks.add(
self.async_on_remove(
async_register_timer_handler(
self.hass, self.registry_entry.device_id, self.handle_timer_event
)
@@ -47,6 +47,7 @@
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
}
},
@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20240930.0"]
"requirements": ["home-assistant-frontend==20241002.3"]
}
@@ -9,5 +9,5 @@ DOMAIN = "fujitsu_fglair"
CONF_REGION = "region"
CONF_EUROPE = "is_europe"
REGION_EU = "EU"
REGION_EU = "eu"
REGION_DEFAULT = "default"
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
"iot_class": "cloud_polling",
"requirements": ["ayla-iot-unofficial==1.4.1"]
"requirements": ["ayla-iot-unofficial==1.4.2"]
}
+1 -1
View File
@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["fyta_cli==0.6.6"]
"requirements": ["fyta_cli==0.6.7"]
}
+5 -3
View File
@@ -172,10 +172,12 @@ class BaseGoogleCloudProvider:
_LOGGER.error("Error: %s when validating options: %s", err, options)
return None, None
encoding = texttospeech.AudioEncoding(options[CONF_ENCODING])
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender(
encoding: texttospeech.AudioEncoding = texttospeech.AudioEncoding[
options[CONF_ENCODING]
] # type: ignore[misc]
gender: texttospeech.SsmlVoiceGender | None = texttospeech.SsmlVoiceGender[
options[CONF_GENDER]
)
] # type: ignore[misc]
voice = options[CONF_VOICE]
if voice:
gender = None
@@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
@@ -21,7 +21,8 @@
"wrong_account": "Wrong account: Please authenticate with the right account.",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]"
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any
import voluptuous as vol
@@ -207,6 +208,8 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
_context_entry: ConfigEntry
@staticmethod
@callback
def async_get_options_flow(
@@ -235,28 +238,33 @@ class GoogleTravelTimeConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
if TYPE_CHECKING:
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration."""
errors: dict[str, str] | None = None
user_input = user_input or {}
if user_input:
if user_input is not None:
errors = await validate_input(self.hass, user_input)
if not errors:
return self.async_update_reload_and_abort(
entry,
self._context_entry,
data=user_input,
reason="reconfigure_successful",
)
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
RECONFIGURE_SCHEMA, entry.data.copy()
RECONFIGURE_SCHEMA, self._context_entry.data.copy()
),
errors=errors,
)
@@ -11,7 +11,7 @@
"destination": "Destination"
}
},
"reconfigure": {
"reconfigure_confirm": {
"description": "[%key:component::google_travel_time::config::step::user::description%]",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
@@ -152,7 +152,7 @@
},
"issues": {
"deprecated_task_entity": {
"title": "The Habitica `{task_name}` sensor is deprecated",
"title": "The Habitica {task_name} sensor is deprecated",
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts to replace the sensor entity with the newly added todo entity.\nWhen you are done migrating you can disable `{entity}`."
}
},
@@ -14,6 +14,9 @@ from homeassistant.util import dt as dt_util
def next_due_date(task: dict[str, Any], last_cron: str) -> datetime.date | None:
"""Calculate due date for dailies and yesterdailies."""
if task["everyX"] == 0 or not task.get("nextDue"): # grey dailies never become due
return None
today = to_date(last_cron)
startdate = to_date(task["startDate"])
if TYPE_CHECKING:
@@ -24,11 +24,11 @@
},
"cmd": {
"name": "Command",
"description": "Command itself. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Command itself. Could be decimal number or string with hexadecimal notation: \"0x10\"."
},
"dst": {
"name": "Destination",
"description": "Destination for command. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Destination for command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
},
"raw": {
"name": "Raw",
@@ -36,7 +36,7 @@
},
"src": {
"name": "Source",
"description": "Source of command. Could be decimal number or string with hexadeximal notation: \"0x10\"."
"description": "Source of command. Could be decimal number or string with hexadecimal notation: \"0x10\"."
}
}
},
@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.57", "babel==2.15.0"]
"requirements": ["holidays==0.58", "babel==2.15.0"]
}
@@ -19,7 +19,7 @@
"description": "The currency {currency} is no longer in use, please reconfigure the currency configuration."
},
"legacy_templates_false": {
"title": "`legacy_templates` config key is being removed",
"title": "legacy_templates config key is being removed",
"description": "Nothing will change with your templates.\n\nRemove the `legacy_templates` key from the `homeassistant` configuration in your configuration.yaml file and restart Home Assistant to fix this issue."
},
"legacy_templates_true": {
@@ -43,7 +43,7 @@
"description": "It's not possible to configure {platform} {domain} by adding `{platform_key}` to the {domain} configuration. Please check the documentation for more information on how to set up this integration.\n\nTo resolve this:\n1. Remove `{platform_key}` occurences from the `{domain}:` configuration in your YAML configuration file.\n2. Restart Home Assistant.\n\nExample that should be removed:\n{yaml_example}"
},
"storage_corruption": {
"title": "Storage corruption detected for `{storage_key}`",
"title": "Storage corruption detected for {storage_key}",
"fix_flow": {
"step": {
"confirm": {
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
from functools import partial
import logging
from typing import Any
@@ -557,6 +558,8 @@ OPTIONS_FLOW = {
class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Lutron Homeworks."""
_context_entry: ConfigEntry
async def _validate_edit_controller(
self, user_input: dict[str, Any]
) -> dict[str, Any]:
@@ -580,18 +583,24 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
return user_input
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle a reconfigure flow."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfigure flow."""
errors = {}
suggested_values = {
CONF_HOST: entry.options[CONF_HOST],
CONF_PORT: entry.options[CONF_PORT],
CONF_USERNAME: entry.data.get(CONF_USERNAME),
CONF_PASSWORD: entry.data.get(CONF_PASSWORD),
CONF_HOST: self._context_entry.options[CONF_HOST],
CONF_PORT: self._context_entry.options[CONF_PORT],
CONF_USERNAME: self._context_entry.data.get(CONF_USERNAME),
CONF_PASSWORD: self._context_entry.data.get(CONF_PASSWORD),
}
if user_input:
@@ -608,16 +617,16 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
else:
password = user_input.pop(CONF_PASSWORD, None)
username = user_input.pop(CONF_USERNAME, None)
new_data = entry.data | {
new_data = self._context_entry.data | {
CONF_PASSWORD: password,
CONF_USERNAME: username,
}
new_options = entry.options | {
new_options = self._context_entry.options | {
CONF_HOST: user_input[CONF_HOST],
CONF_PORT: user_input[CONF_PORT],
}
return self.async_update_reload_and_abort(
entry,
self._context_entry,
data=new_data,
options=new_options,
reason="reconfigure_successful",
@@ -625,7 +634,7 @@ class HomeworksConfigFlowHandler(ConfigFlow, domain=DOMAIN):
)
return self.async_show_form(
step_id="reconfigure",
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
DATA_SCHEMA_EDIT_CONTROLLER, suggested_values
),
@@ -22,7 +22,7 @@
"name": "[%key:component::homeworks::config::step::user::data_description::name%]"
}
},
"reconfigure": {
"reconfigure_confirm": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"port": "[%key:common::config_flow::data::port%]",
@@ -45,8 +45,8 @@
},
"data_description": {
"name": "A unique name identifying the Lutron Homeworks controller",
"password": "[%key:component::homeworks::config::step::reconfigure::data_description::password%]",
"username": "[%key:component::homeworks::config::step::reconfigure::data_description::username%]"
"password": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::password%]",
"username": "[%key:component::homeworks::config::step::reconfigure_confirm::data_description::username%]"
},
"description": "Add a Lutron Homeworks controller"
}
@@ -23,7 +23,7 @@ _LOGGER = logging.getLogger(__name__)
HAP_SUFFIX = "._hap._tcp.local."
POWERVIEW_G2_SUFFIX = "._powerview._tcp.local."
POWERVIEW_G3_SUFFIX = "._powerview-g3._tcp.local."
POWERVIEW_G3_SUFFIX = "._PowerView-G3._tcp.local."
async def validate_input(hass: HomeAssistant, hub_address: str) -> dict[str, str]:
@@ -19,5 +19,5 @@
"iot_class": "local_polling",
"loggers": ["aiopvapi"],
"requirements": ["aiopvapi==3.1.1"],
"zeroconf": ["_powerview._tcp.local.", "_powerview-g3._tcp.local."]
"zeroconf": ["_powerview._tcp.local.", "_PowerView-G3._tcp.local."]
}
@@ -125,7 +125,9 @@ class AutomowerBaseEntity(CoordinatorEntity[AutomowerDataUpdateCoordinator]):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mower_id)},
manufacturer="Husqvarna",
model=self.mower_attributes.system.model,
model=self.mower_attributes.system.model.removeprefix(
"HUSQVARNA "
).removeprefix("Husqvarna "),
name=self.mower_attributes.system.name,
serial_number=self.mower_attributes.system.serial_number,
suggested_area="Garden",
@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower",
"iot_class": "cloud_push",
"loggers": ["aioautomower"],
"requirements": ["aioautomower==2024.9.3"]
"requirements": ["aioautomower==2024.10.0"]
}
+1 -1
View File
@@ -10,7 +10,7 @@ DEFAULT_WATERING_TIME = timedelta(minutes=15)
MANUFACTURER = "Hydrawise"
SCAN_INTERVAL = timedelta(seconds=30)
SCAN_INTERVAL = timedelta(seconds=60)
SIGNAL_UPDATE_HYDRAWISE = "hydrawise_update"
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["imgw_pib==1.0.5"]
"requirements": ["imgw_pib==1.0.6"]
}
+2 -1
View File
@@ -29,7 +29,8 @@
"invalid_host": "The host entry was not in full URL format, e.g., http://192.168.10.100:80"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"options": {
@@ -24,6 +24,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
+1 -1
View File
@@ -12,7 +12,7 @@
"quality_scale": "platinum",
"requirements": [
"xknx==3.2.0",
"xknxproject==3.8.0",
"xknxproject==3.8.1",
"knx-frontend==2024.9.10.221729"
],
"single_config_entry": true
+25 -12
View File
@@ -2,6 +2,7 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
@@ -9,7 +10,7 @@ import pypck
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
from homeassistant.const import (
CONF_BASE,
CONF_DEVICES,
@@ -113,6 +114,8 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
MINOR_VERSION = 2
_context_entry: ConfigEntry
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Import existing configuration from LCN."""
# validate the imported connection parameters
@@ -193,31 +196,41 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_create_entry(title=data[CONF_HOST], data=data)
async def async_step_reconfigure(
self, entry_data: Mapping[str, Any]
) -> config_entries.ConfigFlowResult:
"""Reconfigure LCN configuration."""
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
self._context_entry = entry
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> config_entries.ConfigFlowResult:
"""Reconfigure LCN configuration."""
errors = None
entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
assert entry
if user_input is not None:
user_input[CONF_HOST] = entry.data[CONF_HOST]
user_input[CONF_HOST] = self._context_entry.data[CONF_HOST]
await self.hass.config_entries.async_unload(entry.entry_id)
await self.hass.config_entries.async_unload(self._context_entry.entry_id)
if (error := await validate_connection(user_input)) is not None:
errors = {CONF_BASE: error}
if errors is None:
data = entry.data.copy()
data = self._context_entry.data.copy()
data.update(user_input)
self.hass.config_entries.async_update_entry(entry, data=data)
await self.hass.config_entries.async_setup(entry.entry_id)
self.hass.config_entries.async_update_entry(
self._context_entry, data=data
)
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
return self.async_abort(reason="reconfigure_successful")
await self.hass.config_entries.async_setup(entry.entry_id)
await self.hass.config_entries.async_setup(self._context_entry.entry_id)
return self.async_show_form(
step_id="reconfigure",
data_schema=self.add_suggested_values_to_schema(CONFIG_SCHEMA, entry.data),
step_id="reconfigure_confirm",
data_schema=self.add_suggested_values_to_schema(
CONFIG_SCHEMA, self._context_entry.data
),
errors=errors or {},
)
+1 -1
View File
@@ -34,7 +34,7 @@
"acknowledge": "Retry sendig commands if no response is received (increases bus traffic)."
}
},
"reconfigure": {
"reconfigure_confirm": {
"title": "Reconfigure LCN host",
"description": "Reconfigure connection to LCN host.",
"data": {
@@ -6,6 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/linkplay",
"integration_type": "hub",
"iot_class": "local_polling",
"requirements": ["python-linkplay==0.0.12"],
"loggers": ["linkplay"],
"requirements": ["python-linkplay==0.0.15"],
"zeroconf": ["_linkplay._tcp.local."]
}
@@ -1,6 +1,7 @@
"""Config flow for the integration."""
import asyncio
from collections.abc import Mapping
import logging
from typing import Any
@@ -41,17 +42,17 @@ class MadVRConfigFlow(ConfigFlow, domain=DOMAIN):
return await self._handle_config_step(user_input)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reconfiguration of the device."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reconfigure_confirm(user_input)
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
return await self._handle_config_step(user_input, step_id="reconfigure")
return await self._handle_config_step(user_input, step_id="reconfigure_confirm")
async def _handle_config_step(
self, user_input: dict[str, Any] | None = None, step_id: str = "user"
+1 -1
View File
@@ -13,7 +13,7 @@
"port": "The port your madVR Envy is listening on. In 99% of cases, leave this as the default."
}
},
"reconfigure": {
"reconfigure_confirm": {
"title": "Reconfigure madVR Envy",
"description": "Your device needs to be on in order to reconfigure the integation.",
"data": {
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/matrix",
"iot_class": "cloud_push",
"loggers": ["matrix_client"],
"requirements": ["matrix-nio==0.25.1", "Pillow==10.4.0"]
"requirements": ["matrix-nio==0.25.2", "Pillow==10.4.0"]
}
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/mealie",
"integration_type": "service",
"iot_class": "local_polling",
"requirements": ["aiomealie==0.9.2"]
"requirements": ["aiomealie==0.9.3"]
}
+2 -1
View File
@@ -19,7 +19,8 @@
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
@@ -21,6 +21,7 @@
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]",
"wrong_account": "You can only reauthenticate this entry with the same microBees account."
},
+4 -4
View File
@@ -71,15 +71,15 @@
},
"issues": {
"removed_lazy_error_count": {
"title": "`{config_key}` configuration key is being removed",
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue. All errors will be reported, as lazy_error_count is accepted but ignored"
},
"deprecated_retries": {
"title": "`{config_key}` configuration key is being removed",
"title": "{config_key} configuration key is being removed",
"description": "Please remove the `{config_key}` key from the {integration} entry in your configuration.yaml file and restart Home Assistant to fix this issue.\n\nThe maximum number of retries is now fixed to 3."
},
"missing_modbus_name": {
"title": "Modbus entry with host `{sub_2}` missing name",
"title": "Modbus entry with host {sub_2} missing name",
"description": "Please add `{sub_1}` key to the {integration} entry with host `{sub_2}` in your configuration.yaml file and restart Home Assistant to fix this issue\n\n. `{sub_1}: {sub_3}` have been added."
},
"duplicate_modbus_entry": {
@@ -99,7 +99,7 @@
"description": "Please add at least one entity to Modbus {sub_1} in your configuration.yaml file and restart Home Assistant to fix this issue."
},
"deprecated_restart": {
"title": "`modbus.restart` is being removed",
"title": "modbus.restart is being removed",
"description": "Please use reload yaml via the developer tools in the UI instead of via the `modbus.restart` action."
}
}
@@ -12,6 +12,7 @@ from homeassistant.const import CONF_NAME, Platform
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowError,
SchemaFlowFormStep,
)
from homeassistant.helpers.selector import (
@@ -33,11 +34,13 @@ from .const import (
)
async def validate_duplicate(
async def validate_input(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate already existing entry."""
handler.parent_handler._async_abort_entries_match({**handler.options, **user_input}) # noqa: SLF001
if user_input[CONF_CALIBRATION_FACTOR] == 0.0:
raise SchemaFlowError("calibration_is_zero")
return user_input
@@ -74,13 +77,13 @@ DATA_SCHEMA_CONFIG = vol.Schema(
CONFIG_FLOW = {
"user": SchemaFlowFormStep(
schema=DATA_SCHEMA_CONFIG,
validate_user_input=validate_duplicate,
validate_user_input=validate_input,
),
}
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(
DATA_SCHEMA_OPTIONS,
validate_user_input=validate_duplicate,
validate_user_input=validate_input,
)
}
@@ -3,6 +3,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"calibration_is_zero": "Calibration factor can't be zero."
},
"step": {
"user": {
"description": "Add Mold indicator helper",
@@ -27,6 +30,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"calibration_is_zero": "Calibration factor can't be zero."
},
"step": {
"init": {
"description": "Adjust the calibration factor as required",
@@ -14,5 +14,5 @@
"integration_type": "device",
"iot_class": "assumed_state",
"loggers": ["motionblindsble"],
"requirements": ["motionblindsble==0.1.1"]
"requirements": ["motionblindsble==0.1.2"]
}
+1 -1
View File
@@ -74,7 +74,7 @@
}
},
"migrate_notify_service": {
"title": "Legacy action `notify.{service_name}` stll being used",
"title": "Legacy action notify.{service_name} stll being used",
"fix_flow": {
"step": {
"confirm": {
@@ -7,7 +7,7 @@ from nyt_games import NYTGamesClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TOKEN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .coordinator import NYTGamesCoordinator
@@ -23,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: NYTGamesConfigEntry) ->
"""Set up NYTGames from a config entry."""
client = NYTGamesClient(
entry.data[CONF_TOKEN], session=async_get_clientsession(hass)
entry.data[CONF_TOKEN], session=async_create_clientsession(hass)
)
coordinator = NYTGamesCoordinator(hass, client)
@@ -7,7 +7,7 @@ import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_TOKEN
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from .const import DOMAIN, LOGGER
@@ -21,8 +21,9 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
session = async_get_clientsession(self.hass)
client = NYTGamesClient(user_input[CONF_TOKEN], session=session)
session = async_create_clientsession(self.hass)
token = user_input[CONF_TOKEN].strip()
client = NYTGamesClient(token, session=session)
try:
user_id = await client.get_user_id()
except NYTGamesAuthenticationError:
@@ -35,7 +36,9 @@ class NYTGamesConfigFlow(ConfigFlow, domain=DOMAIN):
else:
await self.async_set_unique_id(str(user_id))
self._abort_if_unique_id_configured()
return self.async_create_entry(title="NYT Games", data=user_input)
return self.async_create_entry(
title="NYT Games", data={CONF_TOKEN: token}
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_TOKEN): str}),
@@ -23,7 +23,7 @@ class NYTGamesData:
wordle: Wordle
spelling_bee: SpellingBee | None
connections: Connections
connections: Connections | None
class NYTGamesCoordinator(DataUpdateCoordinator[NYTGamesData]):
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nyt_games",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["nyt_games==0.4.2"]
"requirements": ["nyt_games==0.4.3"]
}
+6 -4
View File
@@ -161,10 +161,11 @@ async def async_setup_entry(
NYTGamesSpellingBeeSensor(coordinator, description)
for description in SPELLING_BEE_SENSORS
)
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
if coordinator.data.connections is not None:
entities.extend(
NYTGamesConnectionsSensor(coordinator, description)
for description in CONNECTIONS_SENSORS
)
async_add_entities(entities)
@@ -236,4 +237,5 @@ class NYTGamesConnectionsSensor(ConnectionsEntity, SensorEntity):
@property
def native_value(self) -> StateType | date:
"""Return the state of the sensor."""
assert self.coordinator.data.connections is not None
return self.entity_description.value_fn(self.coordinator.data.connections)
+23 -11
View File
@@ -130,20 +130,32 @@ class OpowerCoordinator(DataUpdateCoordinator[dict[str, Forecast]]):
continue
start = cost_reads[0].start_time
_LOGGER.debug("Getting statistics at: %s", start)
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
start + timedelta(seconds=1),
{cost_statistic_id, consumption_statistic_id},
"hour",
None,
{"sum"},
)
# In the common case there should be a previous statistic at start time
# so we only need to fetch one statistic. If there isn't any, fetch all.
for end in (start + timedelta(seconds=1), None):
stats = await get_instance(self.hass).async_add_executor_job(
statistics_during_period,
self.hass,
start,
end,
{cost_statistic_id, consumption_statistic_id},
"hour",
None,
{"sum"},
)
if stats:
break
if end:
_LOGGER.debug(
"Not found. Trying to find the oldest statistic after %s",
start,
)
# We are in this code path only if get_last_statistics found a stat
# so statistics_during_period should also have found at least one.
assert stats
cost_sum = cast(float, stats[cost_statistic_id][0]["sum"])
consumption_sum = cast(float, stats[consumption_statistic_id][0]["sum"])
last_stats_time = stats[consumption_statistic_id][0]["start"]
assert last_stats_time == start.timestamp()
cost_statistics = []
consumption_statistics = []
@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/opower",
"iot_class": "cloud_polling",
"loggers": ["opower"],
"requirements": ["opower==0.8.0"]
"requirements": ["opower==0.8.3"]
}
+3 -1
View File
@@ -13,7 +13,9 @@
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"abort": {
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
"already_configured": "The Thread border router is already configured",
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
"issues": {
@@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
client_session=async_get_clientsession(hass),
)
if custom_account := entry.data.get(CONF_ACCOUNT) is not None:
if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
try:
@@ -49,7 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_update_data() -> OVODailyUsage:
"""Fetch data from OVO Energy."""
if custom_account := entry.data.get(CONF_ACCOUNT) is not None:
if (custom_account := entry.data.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
async with asyncio.timeout(10):
@@ -46,7 +46,7 @@ class OVOEnergyFlowHandler(ConfigFlow, domain=DOMAIN):
client_session=async_get_clientsession(self.hass),
)
if custom_account := user_input.get(CONF_ACCOUNT) is not None:
if (custom_account := user_input.get(CONF_ACCOUNT)) is not None:
client.custom_account_id = custom_account
try:
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/prometheus",
"iot_class": "assumed_state",
"loggers": ["prometheus_client"],
"requirements": ["prometheus-client==0.17.1"]
"requirements": ["prometheus-client==0.21.0"]
}
@@ -10,6 +10,7 @@ from aiopyarr import exceptions
from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient
import voluptuous as vol
from yarl import URL
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY, CONF_URL, CONF_VERIFY_SSL
@@ -54,6 +55,12 @@ class RadarrConfigFlow(ConfigFlow, domain=DOMAIN):
user_input = dict(self.entry.data) if self.entry else None
else:
# aiopyarr defaults to the service port if one isn't given
# this is counter to standard practice where http = 80
# and https = 443.
url = URL(user_input[CONF_URL])
user_input[CONF_URL] = f"{url.scheme}://{url.host}:{url.port}{url.path}"
try:
if result := await validate_input(self.hass, user_input):
user_input[CONF_API_KEY] = result[1]
+6 -3
View File
@@ -570,9 +570,11 @@ class Recorder(threading.Thread):
)
@callback
def async_clear_statistics(self, statistic_ids: list[str]) -> None:
def async_clear_statistics(
self, statistic_ids: list[str], *, on_done: Callable[[], None] | None = None
) -> None:
"""Clear statistics for a list of statistic_ids."""
self.queue_task(ClearStatisticsTask(statistic_ids))
self.queue_task(ClearStatisticsTask(on_done, statistic_ids))
@callback
def async_update_statistics_metadata(
@@ -581,11 +583,12 @@ class Recorder(threading.Thread):
*,
new_statistic_id: str | UndefinedType = UNDEFINED,
new_unit_of_measurement: str | None | UndefinedType = UNDEFINED,
on_done: Callable[[], None] | None = None,
) -> None:
"""Update statistics metadata for a statistic_id."""
self.queue_task(
UpdateStatisticsMetadataTask(
statistic_id, new_statistic_id, new_unit_of_measurement
on_done, statistic_id, new_statistic_id, new_unit_of_measurement
)
)
@@ -60,17 +60,21 @@ class ChangeStatisticsUnitTask(RecorderTask):
class ClearStatisticsTask(RecorderTask):
"""Object to store statistics_ids which for which to remove statistics."""
on_done: Callable[[], None] | None
statistic_ids: list[str]
def run(self, instance: Recorder) -> None:
"""Handle the task."""
statistics.clear_statistics(instance, self.statistic_ids)
if self.on_done:
self.on_done()
@dataclass(slots=True)
class UpdateStatisticsMetadataTask(RecorderTask):
"""Object to store statistics_id and unit for update of statistics metadata."""
on_done: Callable[[], None] | None
statistic_id: str
new_statistic_id: str | None | UndefinedType
new_unit_of_measurement: str | None | UndefinedType
@@ -83,6 +87,8 @@ class UpdateStatisticsMetadataTask(RecorderTask):
self.new_statistic_id,
self.new_unit_of_measurement,
)
if self.on_done:
self.on_done()
@dataclass(slots=True)
@@ -2,6 +2,7 @@
from __future__ import annotations
import asyncio
from datetime import datetime as dt
from typing import Any, Literal, cast
@@ -48,6 +49,9 @@ from .statistics import (
)
from .util import PERIOD_SCHEMA, get_instance, resolve_period
CLEAR_STATISTICS_TIME_OUT = 10
UPDATE_STATISTICS_METADATA_TIME_OUT = 10
UNIT_SCHEMA = vol.Schema(
{
vol.Optional("conductivity"): vol.In(ConductivityConverter.VALID_UNITS),
@@ -319,8 +323,8 @@ async def ws_update_statistics_issues(
vol.Required("statistic_ids"): [str],
}
)
@callback
def ws_clear_statistics(
@websocket_api.async_response
async def ws_clear_statistics(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Clear statistics for a list of statistic_ids.
@@ -328,7 +332,23 @@ def ws_clear_statistics(
Note: The WS call posts a job to the recorder's queue and then returns, it doesn't
wait until the job is completed.
"""
get_instance(hass).async_clear_statistics(msg["statistic_ids"])
done_event = asyncio.Event()
def clear_statistics_done() -> None:
hass.loop.call_soon_threadsafe(done_event.set)
get_instance(hass).async_clear_statistics(
msg["statistic_ids"], on_done=clear_statistics_done
)
try:
async with asyncio.timeout(CLEAR_STATISTICS_TIME_OUT):
await done_event.wait()
except TimeoutError:
connection.send_error(
msg["id"], websocket_api.ERR_TIMEOUT, "clear_statistics timed out"
)
return
connection.send_result(msg["id"])
@@ -357,17 +377,33 @@ async def ws_get_statistics_metadata(
vol.Required("unit_of_measurement"): vol.Any(str, None),
}
)
@callback
def ws_update_statistics_metadata(
@websocket_api.async_response
async def ws_update_statistics_metadata(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Update statistics metadata for a statistic_id.
Only the normalized unit of measurement can be updated.
"""
done_event = asyncio.Event()
def update_statistics_metadata_done() -> None:
hass.loop.call_soon_threadsafe(done_event.set)
get_instance(hass).async_update_statistics_metadata(
msg["statistic_id"], new_unit_of_measurement=msg["unit_of_measurement"]
msg["statistic_id"],
new_unit_of_measurement=msg["unit_of_measurement"],
on_done=update_statistics_metadata_done,
)
try:
async with asyncio.timeout(UPDATE_STATISTICS_METADATA_TIME_OUT):
await done_event.wait()
except TimeoutError:
connection.send_error(
msg["id"], websocket_api.ERR_TIMEOUT, "update_statistics_metadata timed out"
)
return
connection.send_result(msg["id"])
+29 -17
View File
@@ -10,13 +10,9 @@ import uuid
from ring_doorbell import Auth, Ring, RingDevices
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN
from homeassistant.const import APPLICATION_NAME, CONF_DEVICE_ID, CONF_TOKEN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
device_registry as dr,
entity_registry as er,
instance_id,
)
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_LISTEN_CREDENTIALS, DOMAIN, PLATFORMS
@@ -38,18 +34,12 @@ class RingData:
type RingConfigEntry = ConfigEntry[RingData]
async def get_auth_agent_id(hass: HomeAssistant) -> tuple[str, str]:
"""Return user-agent and hardware id for Auth instantiation.
def get_auth_user_agent() -> str:
"""Return user-agent for Auth instantiation.
user_agent will be the display name in the ring.com authorised devices.
hardware_id will uniquely describe the authorised HA device.
"""
user_agent = f"{APPLICATION_NAME}/{DOMAIN}-integration"
# Generate a new uuid from the instance_uuid to keep the HA one private
instance_uuid = uuid.UUID(hex=await instance_id.async_get(hass))
hardware_id = str(uuid.uuid5(instance_uuid, user_agent))
return user_agent, hardware_id
return f"{APPLICATION_NAME}/{DOMAIN}-integration"
async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool:
@@ -69,13 +59,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: RingConfigEntry) -> bool
data={**entry.data, CONF_LISTEN_CREDENTIALS: token},
)
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
client_session = async_get_clientsession(hass)
auth = Auth(
user_agent,
entry.data[CONF_TOKEN],
token_updater,
hardware_id=hardware_id,
hardware_id=entry.data[CONF_DEVICE_ID],
http_client_session=client_session,
)
ring = Ring(auth)
@@ -138,3 +128,25 @@ async def _migrate_old_unique_ids(hass: HomeAssistant, entry_id: str) -> None:
return None
await er.async_migrate_entries(hass, entry_id, _async_migrator)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entry."""
entry_version = entry.version
entry_minor_version = entry.minor_version
new_minor_version = 2
if entry_version == 1 and entry_minor_version == 1:
_LOGGER.debug(
"Migrating from version %s.%s", entry_version, entry_minor_version
)
hardware_id = str(uuid.uuid4())
hass.config_entries.async_update_entry(
entry,
data={**entry.data, CONF_DEVICE_ID: hardware_id},
minor_version=new_minor_version,
)
_LOGGER.debug(
"Migration to version %s.%s complete", entry_version, new_minor_version
)
return True
+35 -9
View File
@@ -3,18 +3,25 @@
from collections.abc import Mapping
import logging
from typing import Any
import uuid
from ring_doorbell import Auth, AuthenticationError, Requires2FAError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
from homeassistant.const import (
CONF_DEVICE_ID,
CONF_NAME,
CONF_PASSWORD,
CONF_TOKEN,
CONF_USERNAME,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from . import get_auth_agent_id
from .const import CONF_2FA, DOMAIN
from . import get_auth_user_agent
from .const import CONF_2FA, CONF_CONFIG_ENTRY_MINOR_VERSION, DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -23,11 +30,15 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
)
STEP_REAUTH_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str, Any]:
async def validate_input(
hass: HomeAssistant, hardware_id: str, data: dict[str, str]
) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
user_agent, hardware_id = await get_auth_agent_id(hass)
user_agent = get_auth_user_agent()
auth = Auth(
user_agent,
http_client_session=async_get_clientsession(hass),
@@ -52,8 +63,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Ring."""
VERSION = 1
MINOR_VERSION = CONF_CONFIG_ENTRY_MINOR_VERSION
user_pass: dict[str, Any] = {}
hardware_id: str | None = None
reauth_entry: ConfigEntry | None = None
async def async_step_user(
@@ -64,8 +77,10 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
await self.async_set_unique_id(user_input[CONF_USERNAME])
self._abort_if_unique_id_configured()
if not self.hardware_id:
self.hardware_id = str(uuid.uuid4())
try:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
@@ -78,7 +93,11 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
else:
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data={CONF_USERNAME: user_input[CONF_USERNAME], CONF_TOKEN: token},
data={
CONF_DEVICE_ID: self.hardware_id,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
},
)
return self.async_show_form(
@@ -120,8 +139,13 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input:
user_input[CONF_USERNAME] = self.reauth_entry.data[CONF_USERNAME]
# Reauth will use the same hardware id and re-authorise an existing
# authorised device.
if not self.hardware_id:
self.hardware_id = self.reauth_entry.data[CONF_DEVICE_ID]
assert self.hardware_id
try:
token = await validate_input(self.hass, user_input)
token = await validate_input(self.hass, self.hardware_id, user_input)
except Require2FA:
self.user_pass = user_input
return await self.async_step_2fa()
@@ -134,6 +158,7 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data = {
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_TOKEN: token,
CONF_DEVICE_ID: self.hardware_id,
}
self.hass.config_entries.async_update_entry(
self.reauth_entry, data=data
@@ -146,7 +171,8 @@ class RingConfigFlow(ConfigFlow, domain=DOMAIN):
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
description_placeholders={
CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME]
CONF_USERNAME: self.reauth_entry.data[CONF_USERNAME],
CONF_NAME: self.reauth_entry.data[CONF_USERNAME],
},
)
+3
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
from datetime import timedelta
from typing import Final
from homeassistant.const import Platform
@@ -31,3 +32,5 @@ SCAN_INTERVAL = timedelta(minutes=1)
CONF_2FA = "2fa"
CONF_LISTEN_CREDENTIALS = "listen_token"
CONF_CONFIG_ENTRY_MINOR_VERSION: Final = 2
+1 -1
View File
@@ -120,7 +120,7 @@
},
"issues": {
"deprecated_entity": {
"title": "Detected deprecated `{platform}` entity usage",
"title": "Detected deprecated {platform} entity usage",
"description": "We detected that entity `{entity}` is being used in `{info}`\n\nWe have created a new `{new_platform}` entity and you should migrate `{info}` to use this new entity.\n\nWhen you are done migrating `{info}` and are ready to have the deprecated `{entity}` entity removed, disable the entity and restart Home Assistant."
}
}
+2 -1
View File
@@ -28,7 +28,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"options": {
@@ -12,7 +12,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ACCOUNT_HASH, DOMAIN
from .const import ACCOUNT_HASH, DOMAIN, UPDATE_INTERVAL
from .coordinator import RitualsDataUpdateCoordinator
PLATFORMS = [
@@ -37,9 +37,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# Migrate old unique_ids to the new format
async_migrate_entities_unique_ids(hass, entry, account_devices)
# The API provided by Rituals is currently rate limited to 30 requests
# per hour per IP address. To avoid hitting this limit, we will adjust
# the polling interval based on the number of diffusers one has.
update_interval = UPDATE_INTERVAL * len(account_devices)
# Create a coordinator for each diffuser
coordinators = {
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser)
diffuser.hublot: RitualsDataUpdateCoordinator(hass, diffuser, update_interval)
for diffuser in account_devices
}
@@ -45,6 +45,7 @@ class RitualsPerfumeGenieConfigFlow(ConfigFlow, domain=DOMAIN):
try:
await account.authenticate()
except ClientResponseError:
_LOGGER.exception("Unexpected response")
errors["base"] = "cannot_connect"
except AuthenticationException:
errors["base"] = "invalid_auth"
@@ -6,4 +6,8 @@ DOMAIN = "rituals_perfume_genie"
ACCOUNT_HASH = "account_hash"
UPDATE_INTERVAL = timedelta(minutes=2)
# The API provided by Rituals is currently rate limited to 30 requests
# per hour per IP address. To avoid hitting this limit, the polling
# interval is set to 3 minutes. This also gives a little room for
# Home Assistant restarts.
UPDATE_INTERVAL = timedelta(minutes=3)
@@ -1,5 +1,6 @@
"""The Rituals Perfume Genie data update coordinator."""
from datetime import timedelta
import logging
from pyrituals import Diffuser
@@ -7,7 +8,7 @@ from pyrituals import Diffuser
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN, UPDATE_INTERVAL
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -15,14 +16,19 @@ _LOGGER = logging.getLogger(__name__)
class RitualsDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Class to manage fetching Rituals Perfume Genie device data from single endpoint."""
def __init__(self, hass: HomeAssistant, diffuser: Diffuser) -> None:
def __init__(
self,
hass: HomeAssistant,
diffuser: Diffuser,
update_interval: timedelta,
) -> None:
"""Initialize global Rituals Perfume Genie data updater."""
self.diffuser = diffuser
super().__init__(
hass,
_LOGGER,
name=f"{DOMAIN}-{diffuser.hublot}",
update_interval=UPDATE_INTERVAL,
update_interval=update_interval,
)
async def _async_update_data(self) -> None:
+1 -1
View File
@@ -148,6 +148,6 @@ class RoborockCurrentMapSelectEntity(RoborockCoordinatedEntityV1, SelectEntity):
@property
def current_option(self) -> str | None:
"""Get the current status of the select entity from device_status."""
if current_map := self.coordinator.current_map:
if (current_map := self.coordinator.current_map) is not None:
return self.coordinator.maps[current_map].name
return None
+2 -1
View File
@@ -14,7 +14,8 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"entity": {
@@ -249,7 +249,7 @@ async def _async_create_bridge_with_updated_data(
updated_data[CONF_MODEL] = model
if model_requires_encryption(model) and method != METHOD_ENCRYPTED_WEBSOCKET:
LOGGER.warning(
LOGGER.debug(
(
"Detected model %s for %s. Some televisions from H and J series use "
"an encrypted protocol but you are using %s which may not be supported"
@@ -90,13 +90,21 @@ class SchlageDataUpdateCoordinator(DataUpdateCoordinator[SchlageData]):
devices = dr.async_entries_for_config_entry(
device_registry, self.config_entry.entry_id
)
previous_locks = {device.id for device in devices}
previous_locks = set()
previous_locks_by_lock_id = {}
for device in devices:
for domain, identifier in device.identifiers:
if domain == DOMAIN:
previous_locks.add(identifier)
previous_locks_by_lock_id[identifier] = device
continue
current_locks = set(self.data.locks.keys())
if removed_locks := previous_locks - current_locks:
LOGGER.debug("Removed locks: %s", ", ".join(removed_locks))
for device_id in removed_locks:
for lock_id in removed_locks:
device_registry.async_update_device(
device_id=device_id,
device_id=previous_locks_by_lock_id[lock_id].id,
remove_config_entry_id=self.config_entry.entry_id,
)
+47 -31
View File
@@ -4,8 +4,8 @@ from __future__ import annotations
from collections import defaultdict
from collections.abc import Callable, Iterable
from contextlib import suppress
import datetime
from functools import partial
import itertools
import logging
import math
@@ -38,6 +38,7 @@ from homeassistant.helpers.entity import entity_sources
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from homeassistant.loader import async_suggest_report_issue
from homeassistant.util import dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe
from homeassistant.util.enum import try_parse_enum
from homeassistant.util.hass_dict import HassKey
@@ -179,6 +180,14 @@ def _entity_history_to_float_and_state(
return float_states
def _is_numeric(state: State) -> bool:
"""Return if the state is numeric."""
with suppress(ValueError, TypeError):
if (num_state := float(state.state)) is not None and math.isfinite(num_state):
return True
return False
def _normalize_states(
hass: HomeAssistant,
old_metadatas: dict[str, tuple[int, StatisticMetaData]],
@@ -677,33 +686,31 @@ def list_statistic_ids(
@callback
def _update_issues(
report_issue: Callable[[str, str, dict[str, Any]], None],
clear_issue: Callable[[str, str], None],
sensor_states: list[State],
metadatas: dict[str, tuple[int, StatisticMetaData]],
) -> None:
"""Update repair issues."""
for state in sensor_states:
entity_id = state.entity_id
numeric = _is_numeric(state)
state_class = try_parse_enum(
SensorStateClass, state.attributes.get(ATTR_STATE_CLASS)
)
state_unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
if metadata := metadatas.get(entity_id):
if state_class is None:
if numeric and state_class is None:
# Sensor no longer has a valid state class
report_issue(
"state_class_removed",
entity_id,
{"statistic_id": entity_id},
)
else:
clear_issue("state_class_removed", entity_id)
metadata_unit = metadata[1]["unit_of_measurement"]
converter = statistics.STATISTIC_UNIT_TO_UNIT_CONVERTER.get(metadata_unit)
if not converter:
if not _equivalent_units({state_unit, metadata_unit}):
if numeric and not _equivalent_units({state_unit, metadata_unit}):
# The unit has changed, and it's not possible to convert
report_issue(
"units_changed",
@@ -715,9 +722,7 @@ def _update_issues(
"supported_unit": metadata_unit,
},
)
else:
clear_issue("units_changed", entity_id)
elif state_unit not in converter.VALID_UNITS:
elif numeric and state_unit not in converter.VALID_UNITS:
# The state unit can't be converted to the unit in metadata
valid_units = (unit or "<None>" for unit in converter.VALID_UNITS)
valid_units_str = ", ".join(sorted(valid_units))
@@ -731,8 +736,6 @@ def _update_issues(
"supported_unit": valid_units_str,
},
)
else:
clear_issue("units_changed", entity_id)
def update_statistics_issues(
@@ -746,36 +749,50 @@ def update_statistics_issues(
instance, session, statistic_source=RECORDER_DOMAIN
)
@callback
def get_sensor_statistics_issues(hass: HomeAssistant) -> set[str]:
"""Return a list of statistics issues."""
issues = set()
issue_registry = ir.async_get(hass)
for issue in issue_registry.issues.values():
if (
issue.domain != DOMAIN
or not (issue_data := issue.data)
or issue_data.get("issue_type")
not in ("state_class_removed", "units_changed")
):
continue
issues.add(issue.issue_id)
return issues
issues = run_callback_threadsafe(
hass.loop, get_sensor_statistics_issues, hass
).result()
def create_issue_registry_issue(
issue_type: str, statistic_id: str, data: dict[str, Any]
) -> None:
"""Create an issue registry issue."""
hass.loop.call_soon_threadsafe(
partial(
ir.async_create_issue,
hass,
DOMAIN,
f"{issue_type}_{statistic_id}",
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
translation_placeholders=data,
)
)
def delete_issue_registry_issue(issue_type: str, statistic_id: str) -> None:
"""Delete an issue registry issue."""
hass.loop.call_soon_threadsafe(
ir.async_delete_issue, hass, DOMAIN, f"{issue_type}_{statistic_id}"
issue_id = f"{issue_type}_{statistic_id}"
issues.discard(issue_id)
ir.create_issue(
hass,
DOMAIN,
issue_id,
data=data | {"issue_type": issue_type},
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key=issue_type,
translation_placeholders=data,
)
_update_issues(
create_issue_registry_issue,
delete_issue_registry_issue,
sensor_states,
metadatas,
)
for issue_id in issues:
hass.loop.call_soon_threadsafe(ir.async_delete_issue, hass, DOMAIN, issue_id)
def validate_statistics(
@@ -801,7 +818,6 @@ def validate_statistics(
_update_issues(
create_statistic_validation_issue,
lambda issue_type, statistic_id: None,
sensor_states,
metadatas,
)
+18
View File
@@ -135,3 +135,21 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
data[PYSMA_REMOVE_LISTENER]()
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate entry."""
_LOGGER.debug("Migrating from version %s", entry.version)
if entry.version == 1:
# 1 -> 2: Unique ID from integer to string
if entry.minor_version == 1:
minor_version = 2
hass.config_entries.async_update_entry(
entry, unique_id=str(entry.unique_id), minor_version=minor_version
)
_LOGGER.debug("Migration successful")
return True
+2 -1
View File
@@ -40,6 +40,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SMA."""
VERSION = 1
MINOR_VERSION = 2
def __init__(self) -> None:
"""Initialize."""
@@ -76,7 +77,7 @@ class SmaConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "unknown"
if not errors:
await self.async_set_unique_id(device_info["serial"])
await self.async_set_unique_id(str(device_info["serial"]))
self._abort_if_unique_id_configured(updates=self._data)
return self.async_create_entry(
title=self._data[CONF_HOST], data=self._data
@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/smlight",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["pysmlight==0.1.1"],
"requirements": ["pysmlight==0.1.3"],
"zeroconf": [
{
"type": "_slzb-06._tcp.local."

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