forked from home-assistant/core
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6952d2420f | |||
| 9176994947 | |||
| d389b55f40 | |||
| 0ccff9fc54 | |||
| a8836ca7b6 | |||
| f5d04a970f | |||
| 7aec98dafd | |||
| 773564d4f5 | |||
| 3e2edc1a2d | |||
| 9cfc9b9baf | |||
| 92b67ead83 | |||
| ee9525cc00 | |||
| 571bfaf5d7 | |||
| a3475607b2 | |||
| f0a653d010 | |||
| eecdf66013 | |||
| f99db05a4a | |||
| 635731421f | |||
| 44743df7d6 | |||
| 33617694cc | |||
| ed445d20b9 | |||
| 66c2fe091b | |||
| 8c80f47a35 | |||
| e37025c1c7 | |||
| 0aabde081b | |||
| a1c9d53474 | |||
| 094996ad0c | |||
| ce359a7689 | |||
| ee599160b3 | |||
| dd076f7a13 | |||
| 3021d38b6f | |||
| bfcabeaf26 | |||
| c31e0336dc | |||
| a1e42cac7a | |||
| 14a3e5b771 | |||
| 5901c543da | |||
| 456b80e6ae | |||
| e5644ae011 | |||
| 41c794c733 | |||
| a481448d46 | |||
| 2bd7ce618a | |||
| da1ac4f1e9 | |||
| f0cb638106 | |||
| dad2396d01 | |||
| 91e4d8b663 | |||
| e35496133e | |||
| 3be808ae1e | |||
| c5772916a1 | |||
| 8cd63b80b1 | |||
| c087654386 | |||
| 60b9e65c78 | |||
| 79b304a5d2 | |||
| bb9fd126e5 | |||
| bff2d5c26c | |||
| 46d9ac8380 | |||
| 5da3ca4bb1 | |||
| 2c99fdc092 | |||
| 31a075fb13 | |||
| 1d132d7a1e | |||
| 3b6f88cfa7 | |||
| b927763d8d | |||
| d00e1cb6a5 | |||
| adf7474edb | |||
| 041d663cb8 | |||
| 37f611a8d3 | |||
| be99329efa | |||
| 327cb70bb8 | |||
| be2b5a4c3a | |||
| d1eda9dd73 | |||
| b902cb5a13 | |||
| 1184ee4a59 | |||
| 2cf898afcc | |||
| df53e19eda | |||
| 7f79b26341 | |||
| 2182bc3af2 | |||
| 2cbf53ad7b | |||
| c52607b465 | |||
| 087566072d | |||
| 6b814afd39 | |||
| ea8aa6b07d | |||
| 1b0f731e30 | |||
| 1ebde4a880 | |||
| e53bd477b4 | |||
| 3f9287c36b | |||
| b2b940fc32 | |||
| 7d9e170512 | |||
| 6ab92abe80 | |||
| 5db4a73d8e | |||
| acb0aeaa9a | |||
| dc7c909316 | |||
| a052e15319 | |||
| a50b299a82 | |||
| a6808a8fda | |||
| 7ac944c537 | |||
| 7d3dd2dd6b | |||
| 48538ef5d5 | |||
| 5365439fd4 | |||
| 9c28a4e8a0 | |||
| 565203047c | |||
| b9795a2ae7 | |||
| 4e4f8ee3a4 | |||
| b8fd921c81 | |||
| fcf91954ff | |||
| 49708196ac | |||
| 8c8a2eef21 | |||
| 749a5b37c9 | |||
| 60079a14e7 | |||
| df6edd09c0 | |||
| 88ff94dd69 | |||
| 067b81a60b | |||
| 03553b8bb9 | |||
| bce7552d4d | |||
| 53a2777831 | |||
| 507492947a | |||
| 41b3eb9f79 | |||
| f2c746122e | |||
| e25a54aef4 | |||
| 5c42e45048 | |||
| c8b92bc858 | |||
| 9d059fcfaa | |||
| ce5f193219 | |||
| 92023ecbe6 | |||
| 1e0164a96a | |||
| 6f5eac3143 | |||
| 60dfccb747 |
@@ -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": {
|
||||
|
||||
Executable → Regular
BIN
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)
|
||||
)
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."]
|
||||
}
|
||||
|
||||
@@ -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."]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {},
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
},
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"])
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user