mirror of
https://github.com/home-assistant/core.git
synced 2026-04-20 08:29:39 +02:00
Compare commits
186 Commits
2025.5.0b5
...
cloud-tts-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40c71fbaa9 | ||
|
|
a673bd7a91 | ||
|
|
121e9e4e7f | ||
|
|
452e946509 | ||
|
|
c3ce82d874 | ||
|
|
253217958b | ||
|
|
1447392847 | ||
|
|
32a6b8a0f8 | ||
|
|
0ec7dc5654 | ||
|
|
bdf6f7f590 | ||
|
|
fbae79fab2 | ||
|
|
2c34712069 | ||
|
|
40e3038775 | ||
|
|
e2c02706a0 | ||
|
|
313be7b30a | ||
|
|
d0ed8b67c4 | ||
|
|
deaaf2f082 | ||
|
|
ce95876d03 | ||
|
|
5475d7ef58 | ||
|
|
687c74ee4c | ||
|
|
c9a9488ff5 | ||
|
|
57217b46ed | ||
|
|
5a01521ff8 | ||
|
|
19a0a16915 | ||
|
|
62877c2c58 | ||
|
|
9479874bb4 | ||
|
|
241b6a0170 | ||
|
|
babc183834 | ||
|
|
f3371bcf39 | ||
|
|
33da5465bd | ||
|
|
5df3a9d76d | ||
|
|
ec4f4a4a1f | ||
|
|
46df29b390 | ||
|
|
60846434d3 | ||
|
|
66c86c0461 | ||
|
|
73996fb916 | ||
|
|
0edfbded23 | ||
|
|
212c3ddcca | ||
|
|
edcb090209 | ||
|
|
92010e1fca | ||
|
|
12f9a11716 | ||
|
|
0dd21f4c89 | ||
|
|
14f967cdd0 | ||
|
|
f3b23afc92 | ||
|
|
0bf807b96e | ||
|
|
1879b8c27f | ||
|
|
e3ed9fac78 | ||
|
|
b98a27d3d0 | ||
|
|
c73383ded3 | ||
|
|
36a08d04c5 | ||
|
|
8a95fffbab | ||
|
|
633c770a48 | ||
|
|
826d28974b | ||
|
|
135df5a24e | ||
|
|
2e8e13bffb | ||
|
|
5e8def837e | ||
|
|
14735cce26 | ||
|
|
d775e443f8 | ||
|
|
aa8dfa760d | ||
|
|
0043b18135 | ||
|
|
c14ddedfae | ||
|
|
a073a6b01e | ||
|
|
0713ac4977 | ||
|
|
3390dc0dbb | ||
|
|
445b38f25d | ||
|
|
9e4a20c267 | ||
|
|
d88cd72d13 | ||
|
|
66b2e06cd3 | ||
|
|
58906008b9 | ||
|
|
aa062515b8 | ||
|
|
65da1e79b9 | ||
|
|
41ecb24135 | ||
|
|
e3b3c32751 | ||
|
|
e2a8137140 | ||
|
|
fa6a2f08ab | ||
|
|
68d62ab58e | ||
|
|
c6b9a40234 | ||
|
|
e0916fdd26 | ||
|
|
cad2d72ed9 | ||
|
|
8eaddbf2b2 | ||
|
|
9b30f32cad | ||
|
|
c2a69bcb20 | ||
|
|
2e7b60c3ca | ||
|
|
eca811d0d4 | ||
|
|
8e202bc202 | ||
|
|
429682cecd | ||
|
|
9cd2080de2 | ||
|
|
2960271b81 | ||
|
|
8048d2bfb8 | ||
|
|
490bb46a82 | ||
|
|
1199353204 | ||
|
|
2c368c79d1 | ||
|
|
095318114b | ||
|
|
9e388f5b13 | ||
|
|
87fab1fa14 | ||
|
|
8046684179 | ||
|
|
5a475ec7ea | ||
|
|
8c6edd8b81 | ||
|
|
de496c693e | ||
|
|
cb37d4d36a | ||
|
|
2aa82da615 | ||
|
|
04982f5e12 | ||
|
|
b9e11b0f45 | ||
|
|
1e0d1c46ab | ||
|
|
b5d499dda8 | ||
|
|
d1615f9a6e | ||
|
|
516a3c0504 | ||
|
|
2a5c0d9b88 | ||
|
|
a15a3c12d5 | ||
|
|
a6131b3ebf | ||
|
|
b9aadb252f | ||
|
|
1264c2cbfa | ||
|
|
716b559e5d | ||
|
|
30e4264aa9 | ||
|
|
fb94f8ea18 | ||
|
|
aea5760424 | ||
|
|
debec3bfbc | ||
|
|
4122f94fb6 | ||
|
|
b48a2cf2b5 | ||
|
|
0ca9ad1cc0 | ||
|
|
ee555a3700 | ||
|
|
a2bc3e3908 | ||
|
|
64b7f2c285 | ||
|
|
db2435dc36 | ||
|
|
1d500fda67 | ||
|
|
558b0ec3b1 | ||
|
|
9780db1c22 | ||
|
|
5e39fb6da1 | ||
|
|
4450f919c3 | ||
|
|
3183bb78ff | ||
|
|
e74f918382 | ||
|
|
247d2e7efd | ||
|
|
32b7edb608 | ||
|
|
df4297be62 | ||
|
|
4c2e9fc759 | ||
|
|
2890fc7dd2 | ||
|
|
97be2c4ac9 | ||
|
|
762d284102 | ||
|
|
4967c287f8 | ||
|
|
5e463d6af4 | ||
|
|
cbf4676ae4 | ||
|
|
81444c8f4a | ||
|
|
9861bd88b9 | ||
|
|
b0f1c71129 | ||
|
|
86b845f04a | ||
|
|
3af0d6e484 | ||
|
|
fca62f1ae8 | ||
|
|
4e8d68a2ef | ||
|
|
883ab44437 | ||
|
|
abd17d9af9 | ||
|
|
a906a1754e | ||
|
|
255beafe08 | ||
|
|
e2679004a1 | ||
|
|
06bb692522 | ||
|
|
71599b8e75 | ||
|
|
79f8bea48d | ||
|
|
82b335a2c1 | ||
|
|
361d93eb96 | ||
|
|
bab699eb0c | ||
|
|
b8881ed85b | ||
|
|
4013b418dd | ||
|
|
80d714b865 | ||
|
|
7fcad580cb | ||
|
|
60b6ff4064 | ||
|
|
24252edf38 | ||
|
|
79aa7aacec | ||
|
|
92944fa509 | ||
|
|
c0f0a4a1ac | ||
|
|
a084b9fdde | ||
|
|
83b9b8b032 | ||
|
|
bc47049d42 | ||
|
|
17360ede28 | ||
|
|
f441f4d7c0 | ||
|
|
5ddc449247 | ||
|
|
dd8d714c94 | ||
|
|
c2079ddf6f | ||
|
|
5250590b17 | ||
|
|
93f4f14b2a | ||
|
|
ba712ed514 | ||
|
|
6e76ca0fb3 | ||
|
|
b0345cce68 | ||
|
|
c4eddc8d11 | ||
|
|
7d89804a87 | ||
|
|
b92f718e08 | ||
|
|
ad0209a4a0 | ||
|
|
d23d25c6b7 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -40,7 +40,7 @@ env:
|
||||
CACHE_VERSION: 12
|
||||
UV_CACHE_VERSION: 1
|
||||
MYPY_CACHE_VERSION: 9
|
||||
HA_SHORT_VERSION: "2025.5"
|
||||
HA_SHORT_VERSION: "2025.6"
|
||||
DEFAULT_PYTHON: "3.13"
|
||||
ALL_PYTHON_VERSIONS: "['3.13']"
|
||||
# 10.3 is the oldest supported version
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -24,11 +24,11 @@ jobs:
|
||||
uses: actions/checkout@v4.2.2
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3.28.16
|
||||
uses: github/codeql-action/init@v3.28.17
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3.28.16
|
||||
uses: github/codeql-action/analyze@v3.28.17
|
||||
with:
|
||||
category: "/language:python"
|
||||
|
||||
@@ -332,6 +332,7 @@ homeassistant.components.media_player.*
|
||||
homeassistant.components.media_source.*
|
||||
homeassistant.components.met_eireann.*
|
||||
homeassistant.components.metoffice.*
|
||||
homeassistant.components.miele.*
|
||||
homeassistant.components.mikrotik.*
|
||||
homeassistant.components.min_max.*
|
||||
homeassistant.components.minecraft_server.*
|
||||
|
||||
10
CODEOWNERS
generated
10
CODEOWNERS
generated
@@ -171,6 +171,8 @@ build.json @home-assistant/supervisor
|
||||
/homeassistant/components/avea/ @pattyland
|
||||
/homeassistant/components/awair/ @ahayworth @danielsjf
|
||||
/tests/components/awair/ @ahayworth @danielsjf
|
||||
/homeassistant/components/aws_s3/ @tomasbedrich
|
||||
/tests/components/aws_s3/ @tomasbedrich
|
||||
/homeassistant/components/axis/ @Kane610
|
||||
/tests/components/axis/ @Kane610
|
||||
/homeassistant/components/azure_data_explorer/ @kaareseras
|
||||
@@ -1318,8 +1320,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/ruuvitag_ble/ @akx
|
||||
/homeassistant/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/tests/components/rympro/ @OnFreund @elad-bar @maorcc
|
||||
/homeassistant/components/s3/ @tomasbedrich
|
||||
/tests/components/s3/ @tomasbedrich
|
||||
/homeassistant/components/sabnzbd/ @shaiu @jpbede
|
||||
/tests/components/sabnzbd/ @shaiu @jpbede
|
||||
/homeassistant/components/saj/ @fredericvl
|
||||
@@ -1678,8 +1678,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
|
||||
/homeassistant/components/vodafone_station/ @paoloantinori @chemelli74
|
||||
/tests/components/vodafone_station/ @paoloantinori @chemelli74
|
||||
/homeassistant/components/voip/ @balloob @synesthesiam
|
||||
/tests/components/voip/ @balloob @synesthesiam
|
||||
/homeassistant/components/voip/ @balloob @synesthesiam @jaminh
|
||||
/tests/components/voip/ @balloob @synesthesiam @jaminh
|
||||
/homeassistant/components/volumio/ @OnFreund
|
||||
/tests/components/volumio/ @OnFreund
|
||||
/homeassistant/components/volvooncall/ @molobrakos
|
||||
@@ -1796,6 +1796,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/zeversolar/ @kvanzuijlen
|
||||
/homeassistant/components/zha/ @dmulcahey @adminiuga @puddly @TheJulianJES
|
||||
/tests/components/zha/ @dmulcahey @adminiuga @puddly @TheJulianJES
|
||||
/homeassistant/components/zimi/ @markhannon
|
||||
/tests/components/zimi/ @markhannon
|
||||
/homeassistant/components/zodiac/ @JulienTant
|
||||
/tests/components/zodiac/ @JulienTant
|
||||
/homeassistant/components/zone/ @home-assistant/core
|
||||
|
||||
10
build.yaml
10
build.yaml
@@ -1,10 +1,10 @@
|
||||
image: ghcr.io/home-assistant/{arch}-homeassistant
|
||||
build_from:
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.02.1
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.02.1
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.02.1
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.02.1
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.02.1
|
||||
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.05.0
|
||||
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.05.0
|
||||
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.05.0
|
||||
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.05.0
|
||||
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.05.0
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
{
|
||||
"domain": "amazon",
|
||||
"name": "Amazon",
|
||||
"integrations": ["alexa", "amazon_polly", "aws", "fire_tv", "route53"]
|
||||
"integrations": [
|
||||
"alexa",
|
||||
"amazon_polly",
|
||||
"aws",
|
||||
"aws_s3",
|
||||
"fire_tv",
|
||||
"route53"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ POLLEN_CATEGORY_MAP = {
|
||||
2: "moderate",
|
||||
3: "high",
|
||||
4: "very_high",
|
||||
5: "extreme",
|
||||
}
|
||||
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
|
||||
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)
|
||||
|
||||
@@ -72,6 +72,7 @@
|
||||
"level": {
|
||||
"name": "Level",
|
||||
"state": {
|
||||
"extreme": "Extreme",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "Moderate",
|
||||
@@ -89,6 +90,7 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
@@ -123,6 +125,7 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
@@ -167,6 +170,7 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
@@ -181,6 +185,7 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
@@ -195,6 +200,7 @@
|
||||
"level": {
|
||||
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
|
||||
"state": {
|
||||
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
|
||||
"high": "[%key:common::state::high%]",
|
||||
"low": "[%key:common::state::low%]",
|
||||
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AdvantageAirDataConfigEntry
|
||||
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
from .const import ADVANTAGE_AIR_STATE_ON, DOMAIN
|
||||
from .entity import AdvantageAirEntity, AdvantageAirThingEntity
|
||||
from .models import AdvantageAirData
|
||||
|
||||
@@ -52,8 +52,8 @@ class AdvantageAirLight(AdvantageAirEntity, LightEntity):
|
||||
self._id: str = light["id"]
|
||||
self._attr_unique_id += f"-{self._id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(ADVANTAGE_AIR_DOMAIN, self._attr_unique_id)},
|
||||
via_device=(ADVANTAGE_AIR_DOMAIN, self.coordinator.data["system"]["rid"]),
|
||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||
via_device=(DOMAIN, self.coordinator.data["system"]["rid"]),
|
||||
manufacturer="Advantage Air",
|
||||
model=light.get("moduleType"),
|
||||
name=light["name"],
|
||||
|
||||
@@ -6,7 +6,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AdvantageAirDataConfigEntry
|
||||
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .entity import AdvantageAirEntity
|
||||
from .models import AdvantageAirData
|
||||
|
||||
@@ -32,9 +32,7 @@ class AdvantageAirApp(AdvantageAirEntity, UpdateEntity):
|
||||
"""Initialize the Advantage Air App."""
|
||||
super().__init__(instance)
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={
|
||||
(ADVANTAGE_AIR_DOMAIN, self.coordinator.data["system"]["rid"])
|
||||
},
|
||||
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
|
||||
manufacturer="Advantage Air",
|
||||
model=self.coordinator.data["system"]["sysType"],
|
||||
name=self.coordinator.data["system"]["name"],
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .const import DOMAIN as AGENT_DOMAIN, SERVER_URL
|
||||
from .const import DOMAIN, SERVER_URL
|
||||
|
||||
ATTRIBUTION = "ispyconnect.com"
|
||||
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
|
||||
@@ -46,7 +46,7 @@ async def async_setup_entry(
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={(AGENT_DOMAIN, agent_client.unique)},
|
||||
identifiers={(DOMAIN, agent_client.unique)},
|
||||
manufacturer="iSpyConnect",
|
||||
name=f"Agent {agent_client.name}",
|
||||
model="Agent DVR",
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AgentDVRConfigEntry
|
||||
from .const import DOMAIN as AGENT_DOMAIN
|
||||
from .const import DOMAIN
|
||||
|
||||
CONF_HOME_MODE_NAME = "home"
|
||||
CONF_AWAY_MODE_NAME = "away"
|
||||
@@ -47,7 +47,7 @@ class AgentBaseStation(AlarmControlPanelEntity):
|
||||
self._client = client
|
||||
self._attr_unique_id = f"{client.unique}_CP"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(AGENT_DOMAIN, client.unique)},
|
||||
identifiers={(DOMAIN, client.unique)},
|
||||
name=f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}",
|
||||
manufacturer="Agent",
|
||||
model=CONST_ALARM_CONTROL_PANEL_NAME,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The S3 integration."""
|
||||
"""The AWS S3 integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Backup platform for the S3 integration."""
|
||||
"""Backup platform for the AWS S3 integration."""
|
||||
|
||||
from collections.abc import AsyncIterator, Callable, Coroutine
|
||||
import functools
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Config flow for the S3 integration."""
|
||||
"""Config flow for the AWS S3 integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from aiobotocore.session import AioSession
|
||||
from botocore.exceptions import ClientError, ConnectionError, ParamValidationError
|
||||
@@ -17,6 +18,7 @@ from homeassistant.helpers.selector import (
|
||||
)
|
||||
|
||||
from .const import (
|
||||
AWS_DOMAIN,
|
||||
CONF_ACCESS_KEY_ID,
|
||||
CONF_BUCKET,
|
||||
CONF_ENDPOINT_URL,
|
||||
@@ -57,28 +59,34 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_ENDPOINT_URL: user_input[CONF_ENDPOINT_URL],
|
||||
}
|
||||
)
|
||||
try:
|
||||
session = AioSession()
|
||||
async with session.create_client(
|
||||
"s3",
|
||||
endpoint_url=user_input.get(CONF_ENDPOINT_URL),
|
||||
aws_secret_access_key=user_input[CONF_SECRET_ACCESS_KEY],
|
||||
aws_access_key_id=user_input[CONF_ACCESS_KEY_ID],
|
||||
) as client:
|
||||
await client.head_bucket(Bucket=user_input[CONF_BUCKET])
|
||||
except ClientError:
|
||||
errors["base"] = "invalid_credentials"
|
||||
except ParamValidationError as err:
|
||||
if "Invalid bucket name" in str(err):
|
||||
errors[CONF_BUCKET] = "invalid_bucket_name"
|
||||
except ValueError:
|
||||
|
||||
if not urlparse(user_input[CONF_ENDPOINT_URL]).hostname.endswith(
|
||||
AWS_DOMAIN
|
||||
):
|
||||
errors[CONF_ENDPOINT_URL] = "invalid_endpoint_url"
|
||||
except ConnectionError:
|
||||
errors[CONF_ENDPOINT_URL] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_BUCKET], data=user_input
|
||||
)
|
||||
try:
|
||||
session = AioSession()
|
||||
async with session.create_client(
|
||||
"s3",
|
||||
endpoint_url=user_input.get(CONF_ENDPOINT_URL),
|
||||
aws_secret_access_key=user_input[CONF_SECRET_ACCESS_KEY],
|
||||
aws_access_key_id=user_input[CONF_ACCESS_KEY_ID],
|
||||
) as client:
|
||||
await client.head_bucket(Bucket=user_input[CONF_BUCKET])
|
||||
except ClientError:
|
||||
errors["base"] = "invalid_credentials"
|
||||
except ParamValidationError as err:
|
||||
if "Invalid bucket name" in str(err):
|
||||
errors[CONF_BUCKET] = "invalid_bucket_name"
|
||||
except ValueError:
|
||||
errors[CONF_ENDPOINT_URL] = "invalid_endpoint_url"
|
||||
except ConnectionError:
|
||||
errors[CONF_ENDPOINT_URL] = "cannot_connect"
|
||||
else:
|
||||
return self.async_create_entry(
|
||||
title=user_input[CONF_BUCKET], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
@@ -1,18 +1,19 @@
|
||||
"""Constants for the S3 integration."""
|
||||
"""Constants for the AWS S3 integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
DOMAIN: Final = "s3"
|
||||
DOMAIN: Final = "aws_s3"
|
||||
|
||||
CONF_ACCESS_KEY_ID = "access_key_id"
|
||||
CONF_SECRET_ACCESS_KEY = "secret_access_key"
|
||||
CONF_ENDPOINT_URL = "endpoint_url"
|
||||
CONF_BUCKET = "bucket"
|
||||
|
||||
DEFAULT_ENDPOINT_URL = "https://s3.eu-central-1.amazonaws.com/"
|
||||
AWS_DOMAIN = "amazonaws.com"
|
||||
DEFAULT_ENDPOINT_URL = f"https://s3.eu-central-1.{AWS_DOMAIN}/"
|
||||
|
||||
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
|
||||
f"{DOMAIN}.backup_agent_listeners"
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"domain": "s3",
|
||||
"name": "S3",
|
||||
"domain": "aws_s3",
|
||||
"name": "AWS S3",
|
||||
"codeowners": ["@tomasbedrich"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/s3",
|
||||
"documentation": "https://www.home-assistant.io/integrations/aws_s3",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiobotocore"],
|
||||
@@ -9,19 +9,19 @@
|
||||
"endpoint_url": "Endpoint URL"
|
||||
},
|
||||
"data_description": {
|
||||
"access_key_id": "Access key ID to connect to S3 API",
|
||||
"secret_access_key": "Secret access key to connect to S3 API",
|
||||
"access_key_id": "Access key ID to connect to AWS S3 API",
|
||||
"secret_access_key": "Secret access key to connect to AWS S3 API",
|
||||
"bucket": "Bucket must already exist and be writable by the provided credentials.",
|
||||
"endpoint_url": "Endpoint URL provided to [Boto3 Session]({boto3_docs_url}). Region-specific [AWS S3 endpoints]({aws_s3_docs_url}) are available in their docs."
|
||||
},
|
||||
"title": "Add S3 bucket"
|
||||
"title": "Add AWS S3 bucket"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "[%key:component::s3::exceptions::cannot_connect::message%]",
|
||||
"invalid_bucket_name": "[%key:component::s3::exceptions::invalid_bucket_name::message%]",
|
||||
"invalid_credentials": "[%key:component::s3::exceptions::invalid_credentials::message%]",
|
||||
"invalid_endpoint_url": "Invalid endpoint URL"
|
||||
"cannot_connect": "[%key:component::aws_s3::exceptions::cannot_connect::message%]",
|
||||
"invalid_bucket_name": "[%key:component::aws_s3::exceptions::invalid_bucket_name::message%]",
|
||||
"invalid_credentials": "[%key:component::aws_s3::exceptions::invalid_credentials::message%]",
|
||||
"invalid_endpoint_url": "Invalid endpoint URL. Please make sure it's a valid AWS S3 endpoint URL."
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
@@ -14,7 +14,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
|
||||
from .const import DOMAIN as AXIS_DOMAIN
|
||||
from .const import DOMAIN
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .hub import AxisHub
|
||||
@@ -61,7 +61,7 @@ class AxisEntity(Entity):
|
||||
self.hub = hub
|
||||
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(AXIS_DOMAIN, hub.unique_id)},
|
||||
identifiers={(DOMAIN, hub.unique_id)},
|
||||
serial_number=hub.unique_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ class BackupConfig:
|
||||
if agent_id not in self.data.agents:
|
||||
old_agent_retention = None
|
||||
self.data.agents[agent_id] = AgentConfig(
|
||||
protected=agent_config.get("protected", False),
|
||||
protected=agent_config.get("protected", True),
|
||||
retention=new_agent_retention,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -30,6 +30,7 @@ class BackupCoordinatorData:
|
||||
"""Class to hold backup data."""
|
||||
|
||||
backup_manager_state: BackupManagerState
|
||||
last_attempted_automatic_backup: datetime | None
|
||||
last_successful_automatic_backup: datetime | None
|
||||
next_scheduled_automatic_backup: datetime | None
|
||||
|
||||
@@ -70,6 +71,7 @@ class BackupDataUpdateCoordinator(DataUpdateCoordinator[BackupCoordinatorData]):
|
||||
"""Update backup manager data."""
|
||||
return BackupCoordinatorData(
|
||||
self.backup_manager.state,
|
||||
self.backup_manager.config.data.last_attempted_automatic_backup,
|
||||
self.backup_manager.config.data.last_completed_automatic_backup,
|
||||
self.backup_manager.config.data.schedule.next_automatic_backup,
|
||||
)
|
||||
|
||||
@@ -46,6 +46,12 @@ BACKUP_MANAGER_DESCRIPTIONS = (
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: data.last_successful_automatic_backup,
|
||||
),
|
||||
BackupSensorEntityDescription(
|
||||
key="last_attempted_automatic_backup",
|
||||
translation_key="last_attempted_automatic_backup",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
value_fn=lambda data: data.last_attempted_automatic_backup,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
"next_scheduled_automatic_backup": {
|
||||
"name": "Next scheduled automatic backup"
|
||||
},
|
||||
"last_attempted_automatic_backup": {
|
||||
"name": "Last attempted automatic backup"
|
||||
},
|
||||
"last_successful_automatic_backup": {
|
||||
"name": "Last successful automatic backup"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Sign-in with Blink account",
|
||||
"title": "Sign in with Blink account",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
@@ -30,7 +30,7 @@
|
||||
"step": {
|
||||
"simple_options": {
|
||||
"data": {
|
||||
"scan_interval": "Scan Interval (seconds)"
|
||||
"scan_interval": "Scan interval (seconds)"
|
||||
},
|
||||
"title": "Blink options",
|
||||
"description": "Configure Blink integration"
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
"config_entry_id": {
|
||||
"name": "Integration ID",
|
||||
"description": "The Blink Integration ID."
|
||||
"description": "The Blink integration ID."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["bluemaestro-ble==0.4.0"]
|
||||
"requirements": ["bluemaestro-ble==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
"bleak==0.22.3",
|
||||
"bleak-retry-connector==3.9.0",
|
||||
"bluetooth-adapters==0.21.4",
|
||||
"bluetooth-auto-recovery==1.4.5",
|
||||
"bluetooth-auto-recovery==1.5.1",
|
||||
"bluetooth-data-tools==1.28.1",
|
||||
"dbus-fast==2.43.0",
|
||||
"habluetooth==3.45.0"
|
||||
"habluetooth==3.48.2"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -111,7 +111,7 @@ class BMWButton(BMWBaseEntity, ButtonEntity):
|
||||
await self.entity_description.remote_function(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
@@ -71,7 +71,7 @@ class BMWLock(BMWBaseEntity, LockEntity):
|
||||
self._attr_is_locked = None
|
||||
self.async_write_ha_state()
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
@@ -95,7 +95,7 @@ class BMWLock(BMWBaseEntity, LockEntity):
|
||||
self._attr_is_locked = None
|
||||
self.async_write_ha_state()
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
@@ -92,7 +92,7 @@ class BMWNotificationService(BaseNotificationService):
|
||||
|
||||
except (vol.Invalid, TypeError, ValueError) as ex:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_poi",
|
||||
translation_placeholders={
|
||||
"poi_exception": str(ex),
|
||||
@@ -107,7 +107,7 @@ class BMWNotificationService(BaseNotificationService):
|
||||
await vehicle.remote_services.trigger_send_poi(poi)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
@@ -110,7 +110,7 @@ class BMWNumber(BMWBaseEntity, NumberEntity):
|
||||
await self.entity_description.remote_service(self.vehicle, value)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
@@ -124,7 +124,7 @@ class BMWSelect(BMWBaseEntity, SelectEntity):
|
||||
await self.entity_description.remote_service(self.vehicle, option)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import DOMAIN as BMW_DOMAIN, BMWConfigEntry
|
||||
from . import DOMAIN, BMWConfigEntry
|
||||
from .coordinator import BMWDataUpdateCoordinator
|
||||
from .entity import BMWBaseEntity
|
||||
|
||||
@@ -112,7 +112,7 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
|
||||
await self.entity_description.remote_service_on(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
@@ -124,7 +124,7 @@ class BMWSwitch(BMWBaseEntity, SwitchEntity):
|
||||
await self.entity_description.remote_service_off(self.vehicle)
|
||||
except MyBMWAPIError as ex:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=BMW_DOMAIN,
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="remote_service_error",
|
||||
translation_placeholders={"exception": str(ex)},
|
||||
) from ex
|
||||
|
||||
@@ -5,7 +5,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientError, ClientResponseError, ClientTimeout
|
||||
from bond_async import Bond, BPUPSubscriptions, start_bpup
|
||||
from bond_async import Bond, BPUPSubscriptions, RequestorUUID, start_bpup
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
@@ -49,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool
|
||||
token=token,
|
||||
timeout=ClientTimeout(total=_API_TIMEOUT),
|
||||
session=async_get_clientsession(hass),
|
||||
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
|
||||
)
|
||||
hub = BondHub(bond, host)
|
||||
try:
|
||||
|
||||
@@ -8,7 +8,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientConnectionError, ClientResponseError
|
||||
from bond_async import Bond
|
||||
from bond_async import Bond, RequestorUUID
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState, ConfigFlow, ConfigFlowResult
|
||||
@@ -34,7 +34,12 @@ TOKEN_SCHEMA = vol.Schema({})
|
||||
|
||||
async def async_get_token(hass: HomeAssistant, host: str) -> str | None:
|
||||
"""Try to fetch the token from the bond device."""
|
||||
bond = Bond(host, "", session=async_get_clientsession(hass))
|
||||
bond = Bond(
|
||||
host,
|
||||
"",
|
||||
session=async_get_clientsession(hass),
|
||||
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
|
||||
)
|
||||
response: dict[str, str] = {}
|
||||
with contextlib.suppress(ClientConnectionError):
|
||||
response = await bond.token()
|
||||
@@ -45,7 +50,10 @@ async def _validate_input(hass: HomeAssistant, data: dict[str, Any]) -> tuple[st
|
||||
"""Validate the user input allows us to connect."""
|
||||
|
||||
bond = Bond(
|
||||
data[CONF_HOST], data[CONF_ACCESS_TOKEN], session=async_get_clientsession(hass)
|
||||
data[CONF_HOST],
|
||||
data[CONF_ACCESS_TOKEN],
|
||||
session=async_get_clientsession(hass),
|
||||
requestor_uuid=RequestorUUID.HOME_ASSISTANT,
|
||||
)
|
||||
try:
|
||||
hub = BondHub(bond, data[CONF_HOST])
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
"known_hosts": "Add known host"
|
||||
},
|
||||
"data_description": {
|
||||
"known_hosts": "Hostnames or IP-addresses of cast devices, use if mDNS discovery is not working"
|
||||
"known_hosts": "Hostnames or IP addresses of cast devices, use if mDNS discovery is not working"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"invalid_known_hosts": "Known hosts must be a comma separated list of hosts."
|
||||
"invalid_known_hosts": "Known hosts must be a comma-separated list of hosts."
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
||||
@@ -61,7 +61,6 @@ from .const import (
|
||||
CONF_RELAYER_SERVER,
|
||||
CONF_REMOTESTATE_SERVER,
|
||||
CONF_SERVICEHANDLERS_SERVER,
|
||||
CONF_THINGTALK_SERVER,
|
||||
CONF_USER_POOL_ID,
|
||||
DATA_CLOUD,
|
||||
DATA_CLOUD_LOG_HANDLER,
|
||||
@@ -134,7 +133,6 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
vol.Optional(CONF_CLOUDHOOK_SERVER): str,
|
||||
vol.Optional(CONF_RELAYER_SERVER): str,
|
||||
vol.Optional(CONF_REMOTESTATE_SERVER): str,
|
||||
vol.Optional(CONF_THINGTALK_SERVER): str,
|
||||
vol.Optional(CONF_SERVICEHANDLERS_SERVER): str,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -26,7 +26,11 @@ from homeassistant.core import Context, HassJob, HomeAssistant, callback
|
||||
from homeassistant.helpers.aiohttp_client import SERVER_SOFTWARE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
from homeassistant.util.aiohttp import MockRequest, serialize_response
|
||||
|
||||
from . import alexa_config, google_config
|
||||
@@ -409,3 +413,7 @@ class CloudClient(Interface):
|
||||
severity=IssueSeverity(severity),
|
||||
is_fixable=False,
|
||||
)
|
||||
|
||||
async def async_delete_repair_issue(self, identifier: str) -> None:
|
||||
"""Delete a repair issue."""
|
||||
async_delete_issue(hass=self._hass, domain=DOMAIN, issue_id=identifier)
|
||||
|
||||
@@ -81,7 +81,6 @@ CONF_ACME_SERVER = "acme_server"
|
||||
CONF_CLOUDHOOK_SERVER = "cloudhook_server"
|
||||
CONF_RELAYER_SERVER = "relayer_server"
|
||||
CONF_REMOTESTATE_SERVER = "remotestate_server"
|
||||
CONF_THINGTALK_SERVER = "thingtalk_server"
|
||||
CONF_SERVICEHANDLERS_SERVER = "servicehandlers_server"
|
||||
|
||||
MODE_DEV = "development"
|
||||
|
||||
@@ -16,7 +16,7 @@ from typing import Any, Concatenate, cast
|
||||
import aiohttp
|
||||
from aiohttp import web
|
||||
import attr
|
||||
from hass_nabucasa import AlreadyConnectedError, Cloud, auth, thingtalk
|
||||
from hass_nabucasa import AlreadyConnectedError, Cloud, auth
|
||||
from hass_nabucasa.const import STATE_DISCONNECTED
|
||||
from hass_nabucasa.voice_data import TTS_VOICES
|
||||
import voluptuous as vol
|
||||
@@ -104,7 +104,6 @@ def async_setup(hass: HomeAssistant) -> None:
|
||||
websocket_api.async_register_command(hass, alexa_list)
|
||||
websocket_api.async_register_command(hass, alexa_sync)
|
||||
|
||||
websocket_api.async_register_command(hass, thingtalk_convert)
|
||||
websocket_api.async_register_command(hass, tts_info)
|
||||
|
||||
hass.http.register_view(GoogleActionsSyncView)
|
||||
@@ -998,25 +997,6 @@ async def alexa_sync(
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command({"type": "cloud/thingtalk/convert", "query": str})
|
||||
@websocket_api.async_response
|
||||
async def thingtalk_convert(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Convert a query."""
|
||||
cloud = hass.data[DATA_CLOUD]
|
||||
|
||||
async with asyncio.timeout(10):
|
||||
try:
|
||||
connection.send_result(
|
||||
msg["id"], await thingtalk.async_convert(cloud, msg["query"])
|
||||
)
|
||||
except thingtalk.ThingTalkConversionError as err:
|
||||
connection.send_error(msg["id"], websocket_api.ERR_UNKNOWN_ERROR, str(err))
|
||||
|
||||
|
||||
@websocket_api.websocket_command({"type": "cloud/tts/info"})
|
||||
def tts_info(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -308,6 +308,51 @@ async def async_setup_entry(
|
||||
async_add_entities([CloudTTSEntity(cloud)])
|
||||
|
||||
|
||||
@callback
|
||||
def _async_get_supported_voices(language: str) -> list[Voice] | None:
|
||||
"""Return a list of supported voices for a language."""
|
||||
if not (voices := TTS_VOICES.get(language)):
|
||||
return None
|
||||
|
||||
result = []
|
||||
|
||||
for voice_id, voice_info in voices.items():
|
||||
if isinstance(voice_info, str):
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
voice_info,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
name = voice_info["name"]
|
||||
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
name,
|
||||
)
|
||||
)
|
||||
|
||||
if language == "pt-PT" and voice_id == "RaquelNeural":
|
||||
# RaquelNeural variants don't seem to be different
|
||||
# from the default voice
|
||||
continue
|
||||
|
||||
result.extend(
|
||||
[
|
||||
Voice(
|
||||
f"{voice_id}{VOICE_STYLE_SEPERATOR}{variant}",
|
||||
f"{name} ({variant})",
|
||||
)
|
||||
for variant in voice_info.get("variants", [])
|
||||
]
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class CloudTTSEntity(TextToSpeechEntity):
|
||||
"""Home Assistant Cloud text-to-speech entity."""
|
||||
|
||||
@@ -369,40 +414,7 @@ class CloudTTSEntity(TextToSpeechEntity):
|
||||
@callback
|
||||
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
|
||||
"""Return a list of supported voices for a language."""
|
||||
if not (voices := TTS_VOICES.get(language)):
|
||||
return None
|
||||
|
||||
result = []
|
||||
|
||||
for voice_id, voice_info in voices.items():
|
||||
if isinstance(voice_info, str):
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
voice_info,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
name = voice_info["name"]
|
||||
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
name,
|
||||
)
|
||||
)
|
||||
result.extend(
|
||||
[
|
||||
Voice(
|
||||
f"{voice_id}{VOICE_STYLE_SEPERATOR}{variant}",
|
||||
f"{name} ({variant})",
|
||||
)
|
||||
for variant in voice_info.get("variants", [])
|
||||
]
|
||||
)
|
||||
|
||||
return result
|
||||
return _async_get_supported_voices(language)
|
||||
|
||||
async def async_get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
@@ -418,9 +430,11 @@ class CloudTTSEntity(TextToSpeechEntity):
|
||||
language=language,
|
||||
voice=options.get(
|
||||
ATTR_VOICE,
|
||||
self._voice
|
||||
if language == self._language
|
||||
else DEFAULT_VOICES[language],
|
||||
(
|
||||
self._voice
|
||||
if language == self._language
|
||||
else DEFAULT_VOICES[language]
|
||||
),
|
||||
),
|
||||
gender=options.get(ATTR_GENDER),
|
||||
),
|
||||
@@ -435,6 +449,8 @@ class CloudTTSEntity(TextToSpeechEntity):
|
||||
class CloudProvider(Provider):
|
||||
"""Home Assistant Cloud speech API provider."""
|
||||
|
||||
has_entity = True
|
||||
|
||||
def __init__(self, cloud: Cloud[CloudClient]) -> None:
|
||||
"""Initialize cloud provider."""
|
||||
self.cloud = cloud
|
||||
@@ -465,40 +481,7 @@ class CloudProvider(Provider):
|
||||
@callback
|
||||
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
|
||||
"""Return a list of supported voices for a language."""
|
||||
if not (voices := TTS_VOICES.get(language)):
|
||||
return None
|
||||
|
||||
result = []
|
||||
|
||||
for voice_id, voice_info in voices.items():
|
||||
if isinstance(voice_info, str):
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
voice_info,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
name = voice_info["name"]
|
||||
|
||||
result.append(
|
||||
Voice(
|
||||
voice_id,
|
||||
name,
|
||||
)
|
||||
)
|
||||
result.extend(
|
||||
[
|
||||
Voice(
|
||||
f"{voice_id}{VOICE_STYLE_SEPERATOR}{variant}",
|
||||
f"{name} ({variant})",
|
||||
)
|
||||
for variant in voice_info.get("variants", [])
|
||||
]
|
||||
)
|
||||
|
||||
return result
|
||||
return _async_get_supported_voices(language)
|
||||
|
||||
@property
|
||||
def default_options(self) -> dict[str, str]:
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as DANFOSS_AIR_DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
def setup_platform(
|
||||
@@ -22,7 +22,7 @@ def setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the available Danfoss Air sensors etc."""
|
||||
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
sensors = [
|
||||
[
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as DANFOSS_AIR_DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,7 +28,7 @@ def setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the available Danfoss Air sensors etc."""
|
||||
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
sensors = [
|
||||
[
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as DANFOSS_AIR_DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,7 +24,7 @@ def setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Danfoss Air HRV switch platform."""
|
||||
data = hass.data[DANFOSS_AIR_DOMAIN]
|
||||
data = hass.data[DOMAIN]
|
||||
|
||||
switches = [
|
||||
[
|
||||
|
||||
@@ -13,7 +13,7 @@ from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN as DECONZ_DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .hub import DeconzHub
|
||||
from .util import serial_from_unique_id
|
||||
|
||||
@@ -59,12 +59,12 @@ class DeconzBase[_DeviceT: _DeviceType]:
|
||||
|
||||
return DeviceInfo(
|
||||
connections={(CONNECTION_ZIGBEE, self.serial)},
|
||||
identifiers={(DECONZ_DOMAIN, self.serial)},
|
||||
identifiers={(DOMAIN, self.serial)},
|
||||
manufacturer=self._device.manufacturer,
|
||||
model=self._device.model_id,
|
||||
name=self._device.name,
|
||||
sw_version=self._device.software_version,
|
||||
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
|
||||
via_device=(DOMAIN, self.hub.api.config.bridge_id),
|
||||
)
|
||||
|
||||
|
||||
@@ -176,9 +176,9 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return a device description for device registry."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DECONZ_DOMAIN, self._group_identifier)},
|
||||
identifiers={(DOMAIN, self._group_identifier)},
|
||||
manufacturer="Dresden Elektronik",
|
||||
model="deCONZ group",
|
||||
name=self.group.name,
|
||||
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
|
||||
via_device=(DOMAIN, self.hub.api.config.bridge_id),
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ from homeassistant.util.color import (
|
||||
)
|
||||
|
||||
from . import DeconzConfigEntry
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS
|
||||
from .const import DOMAIN, POWER_PLUGS
|
||||
from .entity import DeconzDevice
|
||||
from .hub import DeconzHub
|
||||
|
||||
@@ -395,11 +395,11 @@ class DeconzGroup(DeconzBaseLight[Group]):
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Return a device description for device registry."""
|
||||
return DeviceInfo(
|
||||
identifiers={(DECONZ_DOMAIN, self.unique_id)},
|
||||
identifiers={(DOMAIN, self.unique_id)},
|
||||
manufacturer="Dresden Elektronik",
|
||||
model="deCONZ group",
|
||||
name=self._device.name,
|
||||
via_device=(DECONZ_DOMAIN, self.hub.api.config.bridge_id),
|
||||
via_device=(DOMAIN, self.hub.api.config.bridge_id),
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/denonavr",
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["denonavr"],
|
||||
"requirements": ["denonavr==1.0.1"],
|
||||
"requirements": ["denonavr==1.1.0"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Denon",
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/dnsip",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["aiodns==3.2.0"]
|
||||
"requirements": ["aiodns==3.3.0"]
|
||||
}
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"events": "Comma separated list of events."
|
||||
"events": "Comma-separated list of events."
|
||||
},
|
||||
"data_description": {
|
||||
"events": "Add a comma separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
|
||||
"events": "Add a comma-separated event name for each event you wish to track. After entering them here, use the DoorBird app to assign them to a specific event.\n\nExample: somebody_pressed_the_button, motion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from homeassistant.components.notify import ATTR_TARGET, BaseNotificationService
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as DOVADO_DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -19,7 +19,7 @@ def get_service(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> DovadoSMSNotificationService:
|
||||
"""Get the Dovado Router SMS notification service."""
|
||||
return DovadoSMSNotificationService(hass.data[DOVADO_DOMAIN].client)
|
||||
return DovadoSMSNotificationService(hass.data[DOMAIN].client)
|
||||
|
||||
|
||||
class DovadoSMSNotificationService(BaseNotificationService):
|
||||
|
||||
@@ -20,7 +20,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import DOMAIN as DOVADO_DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30)
|
||||
|
||||
@@ -90,7 +90,7 @@ def setup_platform(
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Dovado sensor platform."""
|
||||
dovado = hass.data[DOVADO_DOMAIN]
|
||||
dovado = hass.data[DOMAIN]
|
||||
|
||||
sensors = config[CONF_SENSORS]
|
||||
entities = [
|
||||
|
||||
@@ -52,6 +52,7 @@ VALID_ENERGY_UNITS_GAS = {
|
||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
UnitOfVolume.LITERS,
|
||||
*VALID_ENERGY_UNITS,
|
||||
}
|
||||
VALID_VOLUME_UNITS_WATER: set[str] = {
|
||||
|
||||
@@ -50,6 +50,7 @@ GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = {
|
||||
UnitOfVolume.CENTUM_CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_FEET,
|
||||
UnitOfVolume.CUBIC_METERS,
|
||||
UnitOfVolume.LITERS,
|
||||
),
|
||||
}
|
||||
GAS_PRICE_UNITS = tuple(
|
||||
|
||||
@@ -22,5 +22,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["eq3btsmart"],
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.14.0"]
|
||||
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.15.1"]
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.config_entries import (
|
||||
SOURCE_IGNORE,
|
||||
SOURCE_REAUTH,
|
||||
SOURCE_RECONFIGURE,
|
||||
ConfigEntry,
|
||||
@@ -31,6 +32,7 @@ from homeassistant.config_entries import (
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
||||
@@ -302,7 +304,15 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
):
|
||||
return
|
||||
if entry.source == SOURCE_IGNORE:
|
||||
# Don't call _fetch_device_info() for ignored entries
|
||||
raise AbortFlow("already_configured")
|
||||
configured_host: str | None = entry.data.get(CONF_HOST)
|
||||
configured_port: int | None = entry.data.get(CONF_PORT)
|
||||
if configured_host == host and configured_port == port:
|
||||
# Don't probe to verify the mac is correct since
|
||||
# the host and port matches.
|
||||
raise AbortFlow("already_configured")
|
||||
configured_psk: str | None = entry.data.get(CONF_NOISE_PSK)
|
||||
await self._fetch_device_info(host, port or configured_port, configured_psk)
|
||||
updates: dict[str, Any] = {}
|
||||
|
||||
@@ -8,6 +8,7 @@ from collections.abc import Callable, Iterable
|
||||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
import logging
|
||||
from operator import delitem
|
||||
from typing import TYPE_CHECKING, Any, Final, TypedDict, cast
|
||||
|
||||
from aioesphomeapi import (
|
||||
@@ -183,18 +184,7 @@ class RuntimeEntryData:
|
||||
"""Register to receive callbacks when static info changes for an EntityInfo type."""
|
||||
callbacks = self.entity_info_callbacks.setdefault(entity_info_type, [])
|
||||
callbacks.append(callback_)
|
||||
return partial(
|
||||
self._async_unsubscribe_register_static_info, callbacks, callback_
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_register_static_info(
|
||||
self,
|
||||
callbacks: list[Callable[[list[EntityInfo]], None]],
|
||||
callback_: Callable[[list[EntityInfo]], None],
|
||||
) -> None:
|
||||
"""Unsubscribe to when static info is registered."""
|
||||
callbacks.remove(callback_)
|
||||
return partial(callbacks.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_register_key_static_info_updated_callback(
|
||||
@@ -206,18 +196,7 @@ class RuntimeEntryData:
|
||||
callback_key = (type(static_info), static_info.key)
|
||||
callbacks = self.entity_info_key_updated_callbacks.setdefault(callback_key, [])
|
||||
callbacks.append(callback_)
|
||||
return partial(
|
||||
self._async_unsubscribe_static_key_info_updated, callbacks, callback_
|
||||
)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_static_key_info_updated(
|
||||
self,
|
||||
callbacks: list[Callable[[EntityInfo], None]],
|
||||
callback_: Callable[[EntityInfo], None],
|
||||
) -> None:
|
||||
"""Unsubscribe to when static info is updated ."""
|
||||
callbacks.remove(callback_)
|
||||
return partial(callbacks.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_set_assist_pipeline_state(self, state: bool) -> None:
|
||||
@@ -232,14 +211,7 @@ class RuntimeEntryData:
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Subscribe to assist pipeline updates."""
|
||||
self.assist_pipeline_update_callbacks.append(update_callback)
|
||||
return partial(self._async_unsubscribe_assist_pipeline_update, update_callback)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_assist_pipeline_update(
|
||||
self, update_callback: CALLBACK_TYPE
|
||||
) -> None:
|
||||
"""Unsubscribe to assist pipeline updates."""
|
||||
self.assist_pipeline_update_callbacks.remove(update_callback)
|
||||
return partial(self.assist_pipeline_update_callbacks.remove, update_callback)
|
||||
|
||||
@callback
|
||||
def async_remove_entities(
|
||||
@@ -337,12 +309,7 @@ class RuntimeEntryData:
|
||||
def async_subscribe_device_updated(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
|
||||
"""Subscribe to state updates."""
|
||||
self.device_update_subscriptions.add(callback_)
|
||||
return partial(self._async_unsubscribe_device_update, callback_)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_device_update(self, callback_: CALLBACK_TYPE) -> None:
|
||||
"""Unsubscribe to device updates."""
|
||||
self.device_update_subscriptions.remove(callback_)
|
||||
return partial(self.device_update_subscriptions.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_subscribe_static_info_updated(
|
||||
@@ -350,14 +317,7 @@ class RuntimeEntryData:
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Subscribe to static info updates."""
|
||||
self.static_info_update_subscriptions.add(callback_)
|
||||
return partial(self._async_unsubscribe_static_info_updated, callback_)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_static_info_updated(
|
||||
self, callback_: Callable[[list[EntityInfo]], None]
|
||||
) -> None:
|
||||
"""Unsubscribe to static info updates."""
|
||||
self.static_info_update_subscriptions.remove(callback_)
|
||||
return partial(self.static_info_update_subscriptions.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_subscribe_state_update(
|
||||
@@ -369,14 +329,7 @@ class RuntimeEntryData:
|
||||
"""Subscribe to state updates."""
|
||||
subscription_key = (state_type, state_key)
|
||||
self.state_subscriptions[subscription_key] = entity_callback
|
||||
return partial(self._async_unsubscribe_state_update, subscription_key)
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_state_update(
|
||||
self, subscription_key: tuple[type[EntityState], int]
|
||||
) -> None:
|
||||
"""Unsubscribe to state updates."""
|
||||
self.state_subscriptions.pop(subscription_key)
|
||||
return partial(delitem, self.state_subscriptions, subscription_key)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, state: EntityState) -> None:
|
||||
@@ -523,7 +476,7 @@ class RuntimeEntryData:
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Register to receive callbacks when the Assist satellite's configuration is updated."""
|
||||
self.assist_satellite_config_update_callbacks.append(callback_)
|
||||
return lambda: self.assist_satellite_config_update_callbacks.remove(callback_)
|
||||
return partial(self.assist_satellite_config_update_callbacks.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_assist_satellite_config_updated(
|
||||
@@ -540,7 +493,7 @@ class RuntimeEntryData:
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Register to receive callbacks when the Assist satellite's wake word is set."""
|
||||
self.assist_satellite_set_wake_word_callbacks.append(callback_)
|
||||
return lambda: self.assist_satellite_set_wake_word_callbacks.remove(callback_)
|
||||
return partial(self.assist_satellite_set_wake_word_callbacks.remove, callback_)
|
||||
|
||||
@callback
|
||||
def async_assist_satellite_set_wake_word(self, wake_word_id: str) -> None:
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"mqtt": ["esphome/discover/#"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": [
|
||||
"aioesphomeapi==30.1.0",
|
||||
"aioesphomeapi==30.2.0",
|
||||
"esphome-dashboard-api==1.3.0",
|
||||
"bleak-esphome==2.14.0"
|
||||
"bleak-esphome==2.15.1"
|
||||
],
|
||||
"zeroconf": ["_esphomelib._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -195,7 +195,10 @@
|
||||
"message": "Error compiling {configuration}; Try again in ESPHome dashboard for more information."
|
||||
},
|
||||
"error_uploading": {
|
||||
"message": "Error during OTA of {configuration}; Try again in ESPHome dashboard for more information."
|
||||
"message": "Error during OTA (Over-The-Air) of {configuration}; Try again in ESPHome dashboard for more information."
|
||||
},
|
||||
"ota_in_progress": {
|
||||
"message": "An OTA (Over-The-Air) update is already in progress for {configuration}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ from homeassistant.util.enum import try_parse_enum
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ESPHomeDashboardCoordinator
|
||||
from .dashboard import async_get_dashboard
|
||||
from .domain_data import DomainData
|
||||
from .entity import (
|
||||
EsphomeEntity,
|
||||
convert_api_error_ha_error,
|
||||
@@ -62,7 +61,7 @@ async def async_setup_entry(
|
||||
|
||||
if (dashboard := async_get_dashboard(hass)) is None:
|
||||
return
|
||||
entry_data = DomainData.get(hass).get_entry_data(entry)
|
||||
entry_data = entry.runtime_data
|
||||
assert entry_data.device_info is not None
|
||||
device_name = entry_data.device_info.name
|
||||
unsubs: list[CALLBACK_TYPE] = []
|
||||
@@ -126,21 +125,17 @@ class ESPHomeDashboardUpdateEntity(
|
||||
(dr.CONNECTION_NETWORK_MAC, entry_data.device_info.mac_address)
|
||||
}
|
||||
)
|
||||
self._install_lock = asyncio.Lock()
|
||||
self._available_future: asyncio.Future[None] | None = None
|
||||
self._update_attrs()
|
||||
|
||||
@callback
|
||||
def _update_attrs(self) -> None:
|
||||
"""Update the supported features."""
|
||||
# If the device has deep sleep, we can't assume we can install updates
|
||||
# as the ESP will not be connectable (by design).
|
||||
coordinator = self.coordinator
|
||||
device_info = self._device_info
|
||||
# Install support can change at run time
|
||||
if (
|
||||
coordinator.last_update_success
|
||||
and coordinator.supports_update
|
||||
and not device_info.has_deep_sleep
|
||||
):
|
||||
if coordinator.last_update_success and coordinator.supports_update:
|
||||
self._attr_supported_features = UpdateEntityFeature.INSTALL
|
||||
else:
|
||||
self._attr_supported_features = NO_FEATURES
|
||||
@@ -179,6 +174,13 @@ class ESPHomeDashboardUpdateEntity(
|
||||
self, static_info: list[EntityInfo] | None = None
|
||||
) -> None:
|
||||
"""Handle updated data from the device."""
|
||||
if (
|
||||
self._entry_data.available
|
||||
and self._available_future
|
||||
and not self._available_future.done()
|
||||
):
|
||||
self._available_future.set_result(None)
|
||||
self._available_future = None
|
||||
self._update_attrs()
|
||||
self.async_write_ha_state()
|
||||
|
||||
@@ -193,17 +195,46 @@ class ESPHomeDashboardUpdateEntity(
|
||||
entry_data.async_subscribe_device_updated(self._handle_device_update)
|
||||
)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Handle entity about to be removed from Home Assistant."""
|
||||
if self._available_future and not self._available_future.done():
|
||||
self._available_future.cancel()
|
||||
self._available_future = None
|
||||
|
||||
async def _async_wait_available(self) -> None:
|
||||
"""Wait until the device is available."""
|
||||
# If the device has deep sleep, we need to wait for it to wake up
|
||||
# and connect to the network to be able to install the update.
|
||||
if self._entry_data.available:
|
||||
return
|
||||
self._available_future = self.hass.loop.create_future()
|
||||
try:
|
||||
await self._available_future
|
||||
finally:
|
||||
self._available_future = None
|
||||
|
||||
async def async_install(
|
||||
self, version: str | None, backup: bool, **kwargs: Any
|
||||
) -> None:
|
||||
"""Install an update."""
|
||||
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
|
||||
coordinator = self.coordinator
|
||||
api = coordinator.api
|
||||
device = coordinator.data.get(self._device_info.name)
|
||||
assert device is not None
|
||||
configuration = device["configuration"]
|
||||
try:
|
||||
if self._install_lock.locked():
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="ota_in_progress",
|
||||
translation_placeholders={
|
||||
"configuration": self._device_info.name,
|
||||
},
|
||||
)
|
||||
|
||||
# Ensure only one OTA per device at a time
|
||||
async with self._install_lock:
|
||||
# Ensure only one compile at a time for ALL devices
|
||||
async with self.hass.data.setdefault(KEY_UPDATE_LOCK, asyncio.Lock()):
|
||||
coordinator = self.coordinator
|
||||
api = coordinator.api
|
||||
device = coordinator.data.get(self._device_info.name)
|
||||
assert device is not None
|
||||
configuration = device["configuration"]
|
||||
if not await api.compile(configuration):
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
@@ -212,14 +243,25 @@ class ESPHomeDashboardUpdateEntity(
|
||||
"configuration": configuration,
|
||||
},
|
||||
)
|
||||
if not await api.upload(configuration, "OTA"):
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_uploading",
|
||||
translation_placeholders={
|
||||
"configuration": configuration,
|
||||
},
|
||||
)
|
||||
|
||||
# If the device uses deep sleep, there's a small chance it goes
|
||||
# to sleep right after the dashboard connects but before the OTA
|
||||
# starts. In that case, the update won't go through, so we try
|
||||
# again to catch it on its next wakeup.
|
||||
attempts = 2 if self._device_info.has_deep_sleep else 1
|
||||
try:
|
||||
for attempt in range(1, attempts + 1):
|
||||
await self._async_wait_available()
|
||||
if await api.upload(configuration, "OTA"):
|
||||
break
|
||||
if attempt == attempts:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_uploading",
|
||||
translation_placeholders={
|
||||
"configuration": configuration,
|
||||
},
|
||||
)
|
||||
finally:
|
||||
await self.coordinator.async_request_refresh()
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry)
|
||||
# if this is the last entry, remove the storage
|
||||
if len(entries) == 1:
|
||||
hass.data.pop(MY_KEY)
|
||||
return await hass.config_entries.async_unload_platforms(entry, Platform.EVENT)
|
||||
return await hass.config_entries.async_unload_platforms(entry, [Platform.EVENT])
|
||||
|
||||
|
||||
async def _async_update_listener(
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import DOMAIN as FIRESERVICEROTA_DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .coordinator import FireServiceConfigEntry, FireServiceRotaClient
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -106,7 +106,7 @@ class IncidentsSensor(RestoreEntity, SensorEntity):
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{FIRESERVICEROTA_DOMAIN}_{self._entry_id}_update",
|
||||
f"{DOMAIN}_{self._entry_id}_update",
|
||||
self.client_update,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN as FIRESERVICEROTA_DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .coordinator import (
|
||||
FireServiceConfigEntry,
|
||||
FireServiceRotaClient,
|
||||
@@ -122,7 +122,7 @@ class ResponseSwitch(SwitchEntity):
|
||||
self.async_on_remove(
|
||||
async_dispatcher_connect(
|
||||
self.hass,
|
||||
f"{FIRESERVICEROTA_DOMAIN}_{self._entry_id}_update",
|
||||
f"{DOMAIN}_{self._entry_id}_update",
|
||||
self.client_update,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN as FLO_DOMAIN, LOGGER
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
type FloConfigEntry = ConfigEntry[FloRuntimeData]
|
||||
|
||||
@@ -55,7 +55,7 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
hass,
|
||||
LOGGER,
|
||||
config_entry=config_entry,
|
||||
name=f"{FLO_DOMAIN}-{device_id}",
|
||||
name=f"{DOMAIN}-{device_id}",
|
||||
update_interval=timedelta(seconds=60),
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DOMAIN as FLO_DOMAIN
|
||||
from .const import DOMAIN
|
||||
from .coordinator import FloDeviceDataUpdateCoordinator
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class FloEntity(Entity):
|
||||
"""Return a device description for device registry."""
|
||||
return DeviceInfo(
|
||||
connections={(CONNECTION_NETWORK_MAC, self._device.mac_address)},
|
||||
identifiers={(FLO_DOMAIN, self._device.id)},
|
||||
identifiers={(DOMAIN, self._device.id)},
|
||||
serial_number=self._device.serial_number,
|
||||
manufacturer=self._device.manufacturer,
|
||||
model=self._device.model,
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
"""Support for Freebox devices (Freebox v6 and Freebox mini 4K)."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
from freebox_api.exceptions import HttpRequestError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import Event, HomeAssistant, ServiceCall
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
|
||||
from .const import DOMAIN, PLATFORMS, SERVICE_REBOOT
|
||||
from .router import FreeboxRouter, get_api
|
||||
from .const import PLATFORMS
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter, get_api
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FreeboxConfigEntry) -> bool:
|
||||
"""Set up Freebox entry."""
|
||||
api = await get_api(hass, entry.data[CONF_HOST])
|
||||
try:
|
||||
@@ -35,25 +31,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async_track_time_interval(hass, router.update_all, SCAN_INTERVAL)
|
||||
)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.unique_id] = router
|
||||
entry.runtime_data = router
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
# Services
|
||||
async def async_reboot(call: ServiceCall) -> None:
|
||||
"""Handle reboot service call."""
|
||||
# The Freebox reboot service has been replaced by a
|
||||
# dedicated button entity and marked as deprecated
|
||||
_LOGGER.warning(
|
||||
"The 'freebox.reboot' service is deprecated and "
|
||||
"replaced by a dedicated reboot button entity; please "
|
||||
"use that entity to reboot the freebox instead"
|
||||
)
|
||||
await router.reboot()
|
||||
|
||||
hass.services.async_register(DOMAIN, SERVICE_REBOOT, async_reboot)
|
||||
|
||||
async def async_close_connection(event: Event) -> None:
|
||||
"""Close Freebox connection on HA Stop."""
|
||||
await router.close()
|
||||
@@ -61,16 +42,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
entry.async_on_unload(
|
||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close_connection)
|
||||
)
|
||||
entry.async_on_unload(router.close)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: FreeboxConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
if unload_ok:
|
||||
router: FreeboxRouter = hass.data[DOMAIN].pop(entry.unique_id)
|
||||
await router.close()
|
||||
hass.services.async_remove(DOMAIN, SERVICE_REBOOT)
|
||||
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -7,13 +7,12 @@ from homeassistant.components.alarm_control_panel import (
|
||||
AlarmControlPanelEntityFeature,
|
||||
AlarmControlPanelState,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, FreeboxHomeCategory
|
||||
from .const import FreeboxHomeCategory
|
||||
from .entity import FreeboxHomeEntity
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
FREEBOX_TO_STATUS = {
|
||||
"alarm1_arming": AlarmControlPanelState.ARMING,
|
||||
@@ -29,11 +28,11 @@ FREEBOX_TO_STATUS = {
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up alarm panel."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
(
|
||||
|
||||
@@ -10,15 +10,14 @@ from homeassistant.components.binary_sensor import (
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN, FreeboxHomeCategory
|
||||
from .const import FreeboxHomeCategory
|
||||
from .entity import FreeboxHomeEntity
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -35,11 +34,11 @@ RAID_SENSORS: tuple[BinarySensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up binary sensors."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
|
||||
_LOGGER.debug("%s - %s - %s raid(s)", router.name, router.mac, len(router.raids))
|
||||
|
||||
|
||||
@@ -10,13 +10,11 @@ from homeassistant.components.button import (
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -45,11 +43,11 @@ BUTTON_DESCRIPTIONS: tuple[FreeboxButtonEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the buttons."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
entities = [
|
||||
FreeboxButton(router, description) for description in BUTTON_DESCRIPTIONS
|
||||
]
|
||||
|
||||
@@ -12,27 +12,26 @@ from homeassistant.components.ffmpeg.camera import (
|
||||
DEFAULT_ARGUMENTS,
|
||||
FFmpegCamera,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import ATTR_DETECTION, DOMAIN, FreeboxHomeCategory
|
||||
from .const import ATTR_DETECTION, FreeboxHomeCategory
|
||||
from .entity import FreeboxHomeEntity
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up cameras."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
tracked: set[str] = set()
|
||||
|
||||
@callback
|
||||
|
||||
@@ -8,7 +8,6 @@ import socket
|
||||
from homeassistant.const import Platform
|
||||
|
||||
DOMAIN = "freebox"
|
||||
SERVICE_REBOOT = "reboot"
|
||||
|
||||
APP_DESC = {
|
||||
"app_id": "hass",
|
||||
|
||||
@@ -6,22 +6,21 @@ from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.device_tracker import ScannerEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DEFAULT_DEVICE_NAME, DEVICE_ICONS, DOMAIN
|
||||
from .router import FreeboxRouter
|
||||
from .const import DEFAULT_DEVICE_NAME, DEVICE_ICONS
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up device tracker for Freebox component."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
tracked: set[str] = set()
|
||||
|
||||
@callback
|
||||
|
||||
@@ -38,6 +38,8 @@ from .const import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type FreeboxConfigEntry = ConfigEntry[FreeboxRouter]
|
||||
|
||||
|
||||
def is_json(json_str: str) -> bool:
|
||||
"""Validate if a String is a JSON value or not."""
|
||||
@@ -102,7 +104,7 @@ class FreeboxRouter:
|
||||
def __init__(
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
api: Freepybox,
|
||||
freebox_config: Mapping[str, Any],
|
||||
) -> None:
|
||||
|
||||
@@ -10,7 +10,6 @@ from homeassistant.components.sensor import (
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import PERCENTAGE, UnitOfDataRate, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
@@ -20,7 +19,7 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import DOMAIN
|
||||
from .entity import FreeboxHomeEntity
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,11 +60,11 @@ DISK_PARTITION_SENSORS: tuple[SensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the sensors."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
entities: list[SensorEntity] = []
|
||||
|
||||
_LOGGER.debug(
|
||||
|
||||
@@ -8,13 +8,11 @@ from typing import Any
|
||||
from freebox_api.exceptions import InsufficientPermissionsError
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .router import FreeboxRouter
|
||||
from .router import FreeboxConfigEntry, FreeboxRouter
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,11 +28,11 @@ SWITCH_DESCRIPTIONS = [
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: FreeboxConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the switch."""
|
||||
router: FreeboxRouter = hass.data[DOMAIN][entry.unique_id]
|
||||
router = entry.runtime_data
|
||||
entities = [
|
||||
FreeboxSwitch(router, entity_description)
|
||||
for entity_description in SWITCH_DESCRIPTIONS
|
||||
|
||||
@@ -15,6 +15,8 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_SSL,
|
||||
DOMAIN,
|
||||
FRITZ_AUTH_EXCEPTIONS,
|
||||
@@ -38,6 +40,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> bool:
|
||||
"""Set up fritzboxtools from config entry."""
|
||||
_LOGGER.debug("Setting up FRITZ!Box Tools component")
|
||||
|
||||
avm_wrapper = AvmWrapper(
|
||||
hass=hass,
|
||||
config_entry=entry,
|
||||
@@ -46,6 +49,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
username=entry.data[CONF_USERNAME],
|
||||
password=entry.data[CONF_PASSWORD],
|
||||
use_tls=entry.data.get(CONF_SSL, DEFAULT_SSL),
|
||||
device_discovery_enabled=entry.options.get(
|
||||
CONF_FEATURE_DEVICE_TRACKING, DEFAULT_CONF_FEATURE_DEVICE_TRACKING
|
||||
),
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -62,6 +68,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: FritzConfigEntry) -> boo
|
||||
raise ConfigEntryAuthFailed("Missing UPnP configuration")
|
||||
|
||||
await avm_wrapper.async_config_entry_first_refresh()
|
||||
await avm_wrapper.async_trigger_cleanup()
|
||||
|
||||
entry.runtime_data = avm_wrapper
|
||||
|
||||
|
||||
@@ -35,7 +35,9 @@ from homeassistant.helpers.service_info.ssdp import (
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
|
||||
from .const import (
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
CONF_OLD_DISCOVERY,
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_CONF_OLD_DISCOVERY,
|
||||
DEFAULT_HOST,
|
||||
DEFAULT_HTTP_PORT,
|
||||
@@ -72,7 +74,8 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
"""Initialize FRITZ!Box Tools flow."""
|
||||
self._name: str = ""
|
||||
self._password: str = ""
|
||||
self._use_tls: bool = False
|
||||
self._use_tls: bool = DEFAULT_SSL
|
||||
self._feature_device_discovery: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING
|
||||
self._port: int | None = None
|
||||
self._username: str = ""
|
||||
self._model: str = ""
|
||||
@@ -141,6 +144,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
options={
|
||||
CONF_CONSIDER_HOME: DEFAULT_CONSIDER_HOME.total_seconds(),
|
||||
CONF_OLD_DISCOVERY: DEFAULT_CONF_OLD_DISCOVERY,
|
||||
CONF_FEATURE_DEVICE_TRACKING: self._feature_device_discovery,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -204,6 +208,7 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
self._username = user_input[CONF_USERNAME]
|
||||
self._password = user_input[CONF_PASSWORD]
|
||||
self._use_tls = user_input[CONF_SSL]
|
||||
self._feature_device_discovery = user_input[CONF_FEATURE_DEVICE_TRACKING]
|
||||
self._port = self._determine_port(user_input)
|
||||
|
||||
error = await self.async_fritz_tools_init()
|
||||
@@ -234,6 +239,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
|
||||
vol.Required(
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
errors=errors or {},
|
||||
@@ -250,6 +259,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||
vol.Required(CONF_USERNAME): str,
|
||||
vol.Required(CONF_PASSWORD): str,
|
||||
vol.Optional(CONF_SSL, default=DEFAULT_SSL): bool,
|
||||
vol.Required(
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
default=DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
description_placeholders={"name": self._name},
|
||||
@@ -405,7 +418,7 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
|
||||
"""Handle options flow."""
|
||||
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
return self.async_create_entry(data=user_input)
|
||||
|
||||
options = self.config_entry.options
|
||||
data_schema = vol.Schema(
|
||||
@@ -420,6 +433,13 @@ class FritzBoxToolsOptionsFlowHandler(OptionsFlow):
|
||||
CONF_OLD_DISCOVERY,
|
||||
default=options.get(CONF_OLD_DISCOVERY, DEFAULT_CONF_OLD_DISCOVERY),
|
||||
): bool,
|
||||
vol.Optional(
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
default=options.get(
|
||||
CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
),
|
||||
): bool,
|
||||
}
|
||||
)
|
||||
return self.async_show_form(step_id="init", data_schema=data_schema)
|
||||
|
||||
@@ -40,6 +40,9 @@ PLATFORMS = [
|
||||
CONF_OLD_DISCOVERY = "old_discovery"
|
||||
DEFAULT_CONF_OLD_DISCOVERY = False
|
||||
|
||||
CONF_FEATURE_DEVICE_TRACKING = "feature_device_tracking"
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING = True
|
||||
|
||||
DSL_CONNECTION: Literal["dsl"] = "dsl"
|
||||
|
||||
DEFAULT_DEVICE_NAME = "Unknown device"
|
||||
|
||||
@@ -39,6 +39,7 @@ from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from .const import (
|
||||
CONF_OLD_DISCOVERY,
|
||||
DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
DEFAULT_CONF_OLD_DISCOVERY,
|
||||
DEFAULT_HOST,
|
||||
DEFAULT_SSL,
|
||||
@@ -175,6 +176,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
username: str = DEFAULT_USERNAME,
|
||||
host: str = DEFAULT_HOST,
|
||||
use_tls: bool = DEFAULT_SSL,
|
||||
device_discovery_enabled: bool = DEFAULT_CONF_FEATURE_DEVICE_TRACKING,
|
||||
) -> None:
|
||||
"""Initialize FritzboxTools class."""
|
||||
super().__init__(
|
||||
@@ -202,6 +204,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
self.port = port
|
||||
self.username = username
|
||||
self.use_tls = use_tls
|
||||
self.device_discovery_enabled = device_discovery_enabled
|
||||
self.has_call_deflections: bool = False
|
||||
self._model: str | None = None
|
||||
self._current_firmware: str | None = None
|
||||
@@ -332,10 +335,15 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
"entity_states": {},
|
||||
}
|
||||
try:
|
||||
await self.async_scan_devices()
|
||||
await self.async_update_device_info()
|
||||
|
||||
if self.device_discovery_enabled:
|
||||
await self.async_scan_devices()
|
||||
|
||||
entity_data["entity_states"] = await self.hass.async_add_executor_job(
|
||||
self._entity_states_update
|
||||
)
|
||||
|
||||
if self.has_call_deflections:
|
||||
entity_data[
|
||||
"call_deflections"
|
||||
@@ -521,7 +529,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
return {}
|
||||
|
||||
def manage_device_info(
|
||||
self, dev_info: Device, dev_mac: str, consider_home: bool
|
||||
self, dev_info: Device, dev_mac: str, consider_home: float
|
||||
) -> bool:
|
||||
"""Update device lists and return if device is new."""
|
||||
_LOGGER.debug("Client dev_info: %s", dev_info)
|
||||
@@ -551,12 +559,8 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
if new_device:
|
||||
async_dispatcher_send(self.hass, self.signal_device_new)
|
||||
|
||||
async def async_scan_devices(self, now: datetime | None = None) -> None:
|
||||
"""Scan for new devices and return a list of found device ids."""
|
||||
|
||||
if self.hass.is_stopping:
|
||||
_ha_is_stopping("scan devices")
|
||||
return
|
||||
async def async_update_device_info(self, now: datetime | None = None) -> None:
|
||||
"""Update own device information."""
|
||||
|
||||
_LOGGER.debug("Checking host info for FRITZ!Box device %s", self.host)
|
||||
(
|
||||
@@ -565,6 +569,13 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
self._release_url,
|
||||
) = await self._async_update_device_info()
|
||||
|
||||
async def async_scan_devices(self, now: datetime | None = None) -> None:
|
||||
"""Scan for new network devices."""
|
||||
|
||||
if self.hass.is_stopping:
|
||||
_ha_is_stopping("scan devices")
|
||||
return
|
||||
|
||||
_LOGGER.debug("Checking devices for FRITZ!Box device %s", self.host)
|
||||
_default_consider_home = DEFAULT_CONSIDER_HOME.total_seconds()
|
||||
if self._options:
|
||||
@@ -683,7 +694,10 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
|
||||
|
||||
async def async_trigger_cleanup(self) -> None:
|
||||
"""Trigger device trackers cleanup."""
|
||||
device_hosts = await self._async_update_hosts_info()
|
||||
_LOGGER.debug("Device tracker cleanup triggered")
|
||||
device_hosts = {self.mac: Device(True, "", "", "", "", None)}
|
||||
if self.device_discovery_enabled:
|
||||
device_hosts = await self._async_update_hosts_info()
|
||||
entity_reg: er.EntityRegistry = er.async_get(self.hass)
|
||||
config_entry = self.config_entry
|
||||
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"data_description_port": "Leave empty to use the default port.",
|
||||
"data_description_username": "Username for the FRITZ!Box.",
|
||||
"data_description_password": "Password for the FRITZ!Box.",
|
||||
"data_description_ssl": "Use SSL to connect to the FRITZ!Box."
|
||||
"data_description_ssl": "Use SSL to connect to the FRITZ!Box.",
|
||||
"data_description_feature_device_tracking": "Enable or disable the network device tracking feature.",
|
||||
"data_feature_device_tracking": "Enable network device tracking"
|
||||
},
|
||||
"config": {
|
||||
"flow_title": "{name}",
|
||||
@@ -15,12 +17,14 @@
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||
"ssl": "[%key:common::config_flow::data::ssl%]",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
|
||||
},
|
||||
"data_description": {
|
||||
"username": "[%key:component::fritz::common::data_description_username%]",
|
||||
"password": "[%key:component::fritz::common::data_description_password%]",
|
||||
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
|
||||
"ssl": "[%key:component::fritz::common::data_description_ssl%]",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
@@ -57,14 +61,16 @@
|
||||
"port": "[%key:common::config_flow::data::port%]",
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]",
|
||||
"ssl": "[%key:common::config_flow::data::ssl%]"
|
||||
"ssl": "[%key:common::config_flow::data::ssl%]",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
|
||||
},
|
||||
"data_description": {
|
||||
"host": "[%key:component::fritz::common::data_description_host%]",
|
||||
"port": "[%key:component::fritz::common::data_description_port%]",
|
||||
"username": "[%key:component::fritz::common::data_description_username%]",
|
||||
"password": "[%key:component::fritz::common::data_description_password%]",
|
||||
"ssl": "[%key:component::fritz::common::data_description_ssl%]"
|
||||
"ssl": "[%key:component::fritz::common::data_description_ssl%]",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -89,11 +95,13 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"consider_home": "Seconds to consider a device at 'home'",
|
||||
"old_discovery": "Enable old discovery method"
|
||||
"old_discovery": "Enable old discovery method",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_feature_device_tracking%]"
|
||||
},
|
||||
"data_description": {
|
||||
"consider_home": "Time in seconds to consider a device at home. Default is 180 seconds.",
|
||||
"old_discovery": "Enable old discovery method. This is needed for some scenarios."
|
||||
"old_discovery": "Enable old discovery method. This is needed for some scenarios.",
|
||||
"feature_device_tracking": "[%key:component::fritz::common::data_description_feature_device_tracking%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,6 +144,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||
|
||||
async def async_set_temperature(self, **kwargs: Any) -> None:
|
||||
"""Set new target temperature."""
|
||||
self.check_active_or_lock_mode()
|
||||
if kwargs.get(ATTR_HVAC_MODE) is HVACMode.OFF:
|
||||
await self.async_set_hkr_state("off")
|
||||
elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
|
||||
@@ -168,11 +169,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||
|
||||
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
|
||||
"""Set new operation mode."""
|
||||
if self.data.holiday_active or self.data.summer_active:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="change_hvac_while_active_mode",
|
||||
)
|
||||
self.check_active_or_lock_mode()
|
||||
if self.hvac_mode is hvac_mode:
|
||||
LOGGER.debug(
|
||||
"%s is already in requested hvac mode %s", self.name, hvac_mode
|
||||
@@ -204,11 +201,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||
|
||||
async def async_set_preset_mode(self, preset_mode: str) -> None:
|
||||
"""Set preset mode."""
|
||||
if self.data.holiday_active or self.data.summer_active:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="change_preset_while_active_mode",
|
||||
)
|
||||
self.check_active_or_lock_mode()
|
||||
await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode])
|
||||
|
||||
@property
|
||||
@@ -230,3 +223,17 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
|
||||
attrs[ATTR_STATE_WINDOW_OPEN] = self.data.window_open
|
||||
|
||||
return attrs
|
||||
|
||||
def check_active_or_lock_mode(self) -> None:
|
||||
"""Check if in summer/vacation mode or lock enabled."""
|
||||
if self.data.holiday_active or self.data.summer_active:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="change_settings_while_active_mode",
|
||||
)
|
||||
|
||||
if self.data.lock:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="change_settings_while_lock_enabled",
|
||||
)
|
||||
|
||||
@@ -88,11 +88,11 @@
|
||||
"manual_switching_disabled": {
|
||||
"message": "Can't toggle switch while manual switching is disabled for the device."
|
||||
},
|
||||
"change_preset_while_active_mode": {
|
||||
"message": "Can't change preset while holiday or summer mode is active on the device."
|
||||
"change_settings_while_lock_enabled": {
|
||||
"message": "Can't change settings while manual access for telephone, app, or user interface is disabled on the device"
|
||||
},
|
||||
"change_hvac_while_active_mode": {
|
||||
"message": "Can't change HVAC mode while holiday or summer mode is active on the device."
|
||||
"change_settings_while_active_mode": {
|
||||
"message": "Can't change settings while holiday or summer mode is active on the device."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Configure Prefixes",
|
||||
"title": "Configure prefixes",
|
||||
"data": {
|
||||
"prefixes": "Prefixes (comma separated list)"
|
||||
"prefixes": "Prefixes (comma-separated list)"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -140,16 +140,16 @@
|
||||
"ac_module_temperature_sensor_faulty_l3": "AC module temperature sensor faulty (L3)",
|
||||
"dc_module_temperature_sensor_faulty": "DC module temperature sensor faulty",
|
||||
"internal_processor_status": "Warning about the internal processor status. See status code for more information",
|
||||
"eeprom_reinitialised": "EEPROM has been re-initialised",
|
||||
"initialisation_error_usb_flash_drive_not_supported": "Initialisation error – USB flash drive is not supported",
|
||||
"initialisation_error_usb_stick_over_current": "Initialisation error – Overcurrent on USB stick",
|
||||
"eeprom_reinitialised": "EEPROM has been re-initialized",
|
||||
"initialisation_error_usb_flash_drive_not_supported": "Initialization error – USB flash drive is not supported",
|
||||
"initialisation_error_usb_stick_over_current": "Initialization error – Overcurrent on USB stick",
|
||||
"no_usb_flash_drive_connected": "No USB flash drive connected",
|
||||
"update_file_not_recognised_or_missing": "Update file not recognised or not present",
|
||||
"update_file_not_recognised_or_missing": "Update file not recognized or not present",
|
||||
"update_file_does_not_match_device": "Update file does not match the device, update file too old",
|
||||
"write_or_read_error_occurred": "Write or read error occurred",
|
||||
"file_could_not_be_opened": "File could not be opened",
|
||||
"log_file_cannot_be_saved": "Log file cannot be saved (e.g. USB flash drive is write protected or full)",
|
||||
"initialisation_error_file_system_error_on_usb": "Initialisation error in file system on USB flash drive",
|
||||
"initialisation_error_file_system_error_on_usb": "Initialization error in file system on USB flash drive",
|
||||
"error_during_logging_data_recording": "Error during recording of logging data",
|
||||
"error_during_update_process": "Error occurred during update process",
|
||||
"update_file_corrupt": "Update file corrupt",
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"integration_type": "system",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20250430.2"]
|
||||
"requirements": ["home-assistant-frontend==20250502.1"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Support for controlling Global Cache gc100."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import gc100
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -7,13 +9,14 @@ from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
CONF_PORTS = "ports"
|
||||
|
||||
DEFAULT_PORT = 4998
|
||||
DOMAIN = "gc100"
|
||||
|
||||
DATA_GC100 = "gc100"
|
||||
DATA_GC100: HassKey[GC100Device] = HassKey("gc100")
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import CONF_PORTS, DATA_GC100
|
||||
from . import CONF_PORTS, DATA_GC100, GC100Device
|
||||
|
||||
_SENSORS_SCHEMA = vol.Schema({cv.string: cv.string})
|
||||
|
||||
@@ -31,7 +31,7 @@ def setup_platform(
|
||||
) -> None:
|
||||
"""Set up the GC100 devices."""
|
||||
binary_sensors = []
|
||||
ports = config[CONF_PORTS]
|
||||
ports: list[dict[str, str]] = config[CONF_PORTS]
|
||||
for port in ports:
|
||||
for port_addr, port_name in port.items():
|
||||
binary_sensors.append(
|
||||
@@ -43,23 +43,23 @@ def setup_platform(
|
||||
class GC100BinarySensor(BinarySensorEntity):
|
||||
"""Representation of a binary sensor from GC100."""
|
||||
|
||||
def __init__(self, name, port_addr, gc100):
|
||||
def __init__(self, name: str, port_addr: str, gc100: GC100Device) -> None:
|
||||
"""Initialize the GC100 binary sensor."""
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._port_addr = port_addr
|
||||
self._gc100 = gc100
|
||||
self._state = None
|
||||
self._state: bool | None = None
|
||||
|
||||
# Subscribe to be notified about state changes (PUSH)
|
||||
self._gc100.subscribe(self._port_addr, self.set_state)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the entity."""
|
||||
return self._state
|
||||
|
||||
@@ -67,7 +67,7 @@ class GC100BinarySensor(BinarySensorEntity):
|
||||
"""Update the sensor state."""
|
||||
self._gc100.read_sensor(self._port_addr, self.set_state)
|
||||
|
||||
def set_state(self, state):
|
||||
def set_state(self, state: int) -> None:
|
||||
"""Set the current state."""
|
||||
self._state = state == 1
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@@ -16,7 +16,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
from . import CONF_PORTS, DATA_GC100
|
||||
from . import CONF_PORTS, DATA_GC100, GC100Device
|
||||
|
||||
_SWITCH_SCHEMA = vol.Schema({cv.string: cv.string})
|
||||
|
||||
@@ -33,7 +33,7 @@ def setup_platform(
|
||||
) -> None:
|
||||
"""Set up the GC100 devices."""
|
||||
switches = []
|
||||
ports = config[CONF_PORTS]
|
||||
ports: list[dict[str, str]] = config[CONF_PORTS]
|
||||
for port in ports:
|
||||
for port_addr, port_name in port.items():
|
||||
switches.append(GC100Switch(port_name, port_addr, hass.data[DATA_GC100]))
|
||||
@@ -43,20 +43,20 @@ def setup_platform(
|
||||
class GC100Switch(SwitchEntity):
|
||||
"""Represent a switch/relay from GC100."""
|
||||
|
||||
def __init__(self, name, port_addr, gc100):
|
||||
def __init__(self, name: str, port_addr: str, gc100: GC100Device) -> None:
|
||||
"""Initialize the GC100 switch."""
|
||||
self._name = name or DEVICE_DEFAULT_NAME
|
||||
self._port_addr = port_addr
|
||||
self._gc100 = gc100
|
||||
self._state = None
|
||||
self._state: bool | None = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the switch."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the entity."""
|
||||
return self._state
|
||||
|
||||
@@ -72,7 +72,7 @@ class GC100Switch(SwitchEntity):
|
||||
"""Update the sensor state."""
|
||||
self._gc100.read_sensor(self._port_addr, self.set_state)
|
||||
|
||||
def set_state(self, state):
|
||||
def set_state(self, state: int) -> None:
|
||||
"""Set the current state."""
|
||||
self._state = state == 1
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@@ -25,22 +25,17 @@ from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.util.unit_conversion import DistanceConverter
|
||||
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
||||
|
||||
from .const import ( # noqa: F401
|
||||
CONF_CATEGORIES,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
FEED,
|
||||
PLATFORMS,
|
||||
)
|
||||
from .const import CONF_CATEGORIES, DEFAULT_SCAN_INTERVAL, PLATFORMS # noqa: F401
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
type GdacsConfigEntry = ConfigEntry[GdacsFeedEntityManager]
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: GdacsConfigEntry
|
||||
) -> bool:
|
||||
"""Set up the GDACS component as config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
feeds = hass.data[DOMAIN].setdefault(FEED, {})
|
||||
|
||||
radius = config_entry.data[CONF_RADIUS]
|
||||
if hass.config.units is US_CUSTOMARY_SYSTEM:
|
||||
radius = DistanceConverter.convert(
|
||||
@@ -48,16 +43,15 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
)
|
||||
# Create feed entity manager for all platforms.
|
||||
manager = GdacsFeedEntityManager(hass, config_entry, radius)
|
||||
feeds[config_entry.entry_id] = manager
|
||||
config_entry.runtime_data = manager
|
||||
_LOGGER.debug("Feed entity manager added for %s", config_entry.entry_id)
|
||||
await manager.async_init()
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: GdacsConfigEntry) -> bool:
|
||||
"""Unload an GDACS component config entry."""
|
||||
manager: GdacsFeedEntityManager = hass.data[DOMAIN][FEED].pop(entry.entry_id)
|
||||
await manager.async_stop()
|
||||
await entry.runtime_data.async_stop()
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
@@ -65,7 +59,7 @@ class GdacsFeedEntityManager:
|
||||
"""Feed Entity Manager for GDACS feed."""
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, config_entry: ConfigEntry, radius_in_km: float
|
||||
self, hass: HomeAssistant, config_entry: GdacsConfigEntry, radius_in_km: float
|
||||
) -> None:
|
||||
"""Initialize the Feed Entity Manager."""
|
||||
self._hass = hass
|
||||
|
||||
@@ -10,8 +10,6 @@ DOMAIN = "gdacs"
|
||||
|
||||
PLATFORMS = [Platform.GEO_LOCATION, Platform.SENSOR]
|
||||
|
||||
FEED = "feed"
|
||||
|
||||
CONF_CATEGORIES = "categories"
|
||||
|
||||
DEFAULT_ICON = "mdi:alert"
|
||||
|
||||
@@ -7,26 +7,23 @@ from typing import Any
|
||||
from aio_georss_client.status_update import StatusUpdate
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import GdacsFeedEntityManager
|
||||
from .const import DOMAIN, FEED
|
||||
from . import GdacsConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_LATITUDE, CONF_LONGITUDE}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: GdacsConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data: dict[str, Any] = {
|
||||
"info": async_redact_data(config_entry.data, TO_REDACT),
|
||||
}
|
||||
|
||||
manager: GdacsFeedEntityManager = hass.data[DOMAIN][FEED][config_entry.entry_id]
|
||||
status_info: StatusUpdate = manager.status_info()
|
||||
status_info: StatusUpdate = config_entry.runtime_data.status_info()
|
||||
if status_info:
|
||||
data["service"] = {
|
||||
"status": status_info.status,
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import Any
|
||||
from aio_georss_gdacs.feed_entry import GdacsFeedEntry
|
||||
|
||||
from homeassistant.components.geo_location import GeolocationEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import UnitOfLength
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -19,8 +18,8 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.unit_conversion import DistanceConverter
|
||||
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
||||
|
||||
from . import GdacsFeedEntityManager
|
||||
from .const import DEFAULT_ICON, DOMAIN, FEED
|
||||
from . import GdacsConfigEntry, GdacsFeedEntityManager
|
||||
from .const import DEFAULT_ICON
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,11 +52,11 @@ SOURCE = "gdacs"
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: GdacsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the GDACS Feed platform."""
|
||||
manager: GdacsFeedEntityManager = hass.data[DOMAIN][FEED][entry.entry_id]
|
||||
manager = entry.runtime_data
|
||||
|
||||
@callback
|
||||
def async_add_geolocation(
|
||||
|
||||
@@ -10,15 +10,14 @@ from typing import Any
|
||||
from aio_georss_client.status_update import StatusUpdate
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import GdacsFeedEntityManager
|
||||
from .const import DOMAIN, FEED
|
||||
from . import GdacsConfigEntry, GdacsFeedEntityManager
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,12 +37,11 @@ PARALLEL_UPDATES = 0
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: GdacsConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the GDACS Feed platform."""
|
||||
manager: GdacsFeedEntityManager = hass.data[DOMAIN][FEED][entry.entry_id]
|
||||
sensor = GdacsSensor(entry, manager)
|
||||
sensor = GdacsSensor(entry, entry.runtime_data)
|
||||
async_add_entities([sensor])
|
||||
|
||||
|
||||
@@ -57,7 +55,7 @@ class GdacsSensor(SensorEntity):
|
||||
_attr_translation_key = "alerts"
|
||||
|
||||
def __init__(
|
||||
self, config_entry: ConfigEntry, manager: GdacsFeedEntityManager
|
||||
self, config_entry: GdacsConfigEntry, manager: GdacsFeedEntityManager
|
||||
) -> None:
|
||||
"""Initialize entity."""
|
||||
assert config_entry.unique_id
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""The Geocaching integration."""
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
@@ -8,13 +7,12 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import GeocachingDataUpdateCoordinator
|
||||
from .coordinator import GeocachingConfigEntry, GeocachingDataUpdateCoordinator
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: GeocachingConfigEntry) -> bool:
|
||||
"""Set up Geocaching from a config entry."""
|
||||
implementation = await async_get_config_entry_implementation(hass, entry)
|
||||
|
||||
@@ -25,15 +23,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
||||
entry.runtime_data = coordinator
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: GeocachingConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
|
||||
del hass.data[DOMAIN][entry.entry_id]
|
||||
return unload_ok
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
@@ -14,14 +14,20 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||
|
||||
from .const import DOMAIN, ENVIRONMENT, LOGGER, UPDATE_INTERVAL
|
||||
|
||||
type GeocachingConfigEntry = ConfigEntry[GeocachingDataUpdateCoordinator]
|
||||
|
||||
|
||||
class GeocachingDataUpdateCoordinator(DataUpdateCoordinator[GeocachingStatus]):
|
||||
"""Class to manage fetching Geocaching data from single endpoint."""
|
||||
|
||||
config_entry: ConfigEntry
|
||||
config_entry: GeocachingConfigEntry
|
||||
|
||||
def __init__(
|
||||
self, hass: HomeAssistant, *, entry: ConfigEntry, session: OAuth2Session
|
||||
self,
|
||||
hass: HomeAssistant,
|
||||
*,
|
||||
entry: GeocachingConfigEntry,
|
||||
session: OAuth2Session,
|
||||
) -> None:
|
||||
"""Initialize global Geocaching data updater."""
|
||||
self.session = session
|
||||
|
||||
@@ -9,14 +9,13 @@ from typing import cast
|
||||
from geocachingapi.models import GeocachingStatus
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import GeocachingDataUpdateCoordinator
|
||||
from .coordinator import GeocachingConfigEntry, GeocachingDataUpdateCoordinator
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -65,11 +64,11 @@ SENSORS: tuple[GeocachingSensorEntityDescription, ...] = (
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: GeocachingConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up a Geocaching sensor entry."""
|
||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities(
|
||||
GeocachingSensor(coordinator, description) for description in SENSORS
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from . import DOMAIN as GF_DOMAIN, TRACKER_UPDATE
|
||||
from . import DOMAIN, TRACKER_UPDATE
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -23,14 +23,14 @@ async def async_setup_entry(
|
||||
@callback
|
||||
def _receive_data(device, gps, location_name, attributes):
|
||||
"""Fire HA event to set location."""
|
||||
if device in hass.data[GF_DOMAIN]["devices"]:
|
||||
if device in hass.data[DOMAIN]["devices"]:
|
||||
return
|
||||
|
||||
hass.data[GF_DOMAIN]["devices"].add(device)
|
||||
hass.data[DOMAIN]["devices"].add(device)
|
||||
|
||||
async_add_entities([GeofencyEntity(device, gps, location_name, attributes)])
|
||||
|
||||
hass.data[GF_DOMAIN]["unsub_device_tracker"][config_entry.entry_id] = (
|
||||
hass.data[DOMAIN]["unsub_device_tracker"][config_entry.entry_id] = (
|
||||
async_dispatcher_connect(hass, TRACKER_UPDATE, _receive_data)
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ async def async_setup_entry(
|
||||
}
|
||||
|
||||
if dev_ids:
|
||||
hass.data[GF_DOMAIN]["devices"].update(dev_ids)
|
||||
hass.data[DOMAIN]["devices"].update(dev_ids)
|
||||
async_add_entities(GeofencyEntity(dev_id) for dev_id in dev_ids)
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
|
||||
self._unsub_dispatcher = None
|
||||
self._attr_unique_id = device
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(GF_DOMAIN, device)},
|
||||
identifiers={(DOMAIN, device)},
|
||||
name=device,
|
||||
)
|
||||
|
||||
@@ -93,7 +93,7 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
|
||||
"""Clean up after entity before removal."""
|
||||
await super().async_will_remove_from_hass()
|
||||
self._unsub_dispatcher()
|
||||
self.hass.data[GF_DOMAIN]["devices"].remove(self.unique_id)
|
||||
self.hass.data[DOMAIN]["devices"].remove(self.unique_id)
|
||||
|
||||
@callback
|
||||
def _async_receive_data(self, device, gps, location_name, attributes):
|
||||
|
||||
@@ -31,7 +31,6 @@ from .const import (
|
||||
DEFAULT_RADIUS,
|
||||
DEFAULT_SCAN_INTERVAL,
|
||||
DOMAIN,
|
||||
FEED,
|
||||
PLATFORMS,
|
||||
)
|
||||
|
||||
@@ -59,6 +58,8 @@ CONFIG_SCHEMA = vol.Schema(
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
type GeonetnzQuakesConfigEntry = ConfigEntry[GeonetnzQuakesFeedEntityManager]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the GeoNet NZ Quakes component."""
|
||||
@@ -89,11 +90,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: GeonetnzQuakesConfigEntry
|
||||
) -> bool:
|
||||
"""Set up the GeoNet NZ Quakes component as config entry."""
|
||||
hass.data.setdefault(DOMAIN, {})
|
||||
feeds = hass.data[DOMAIN].setdefault(FEED, {})
|
||||
|
||||
radius = config_entry.data[CONF_RADIUS]
|
||||
if hass.config.units is US_CUSTOMARY_SYSTEM:
|
||||
radius = DistanceConverter.convert(
|
||||
@@ -101,16 +101,17 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
||||
)
|
||||
# Create feed entity manager for all platforms.
|
||||
manager = GeonetnzQuakesFeedEntityManager(hass, config_entry, radius)
|
||||
feeds[config_entry.entry_id] = manager
|
||||
config_entry.runtime_data = manager
|
||||
_LOGGER.debug("Feed entity manager added for %s", config_entry.entry_id)
|
||||
await manager.async_init()
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: GeonetnzQuakesConfigEntry
|
||||
) -> bool:
|
||||
"""Unload an GeoNet NZ Quakes component config entry."""
|
||||
manager = hass.data[DOMAIN][FEED].pop(entry.entry_id)
|
||||
await manager.async_stop()
|
||||
await entry.runtime_data.async_stop()
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ PLATFORMS = [Platform.GEO_LOCATION, Platform.SENSOR]
|
||||
CONF_MINIMUM_MAGNITUDE = "minimum_magnitude"
|
||||
CONF_MMI = "mmi"
|
||||
|
||||
FEED = "feed"
|
||||
|
||||
DEFAULT_FILTER_TIME_INTERVAL = timedelta(days=7)
|
||||
DEFAULT_MINIMUM_MAGNITUDE = 0.0
|
||||
DEFAULT_MMI = 3
|
||||
|
||||
@@ -5,28 +5,23 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import GeonetnzQuakesFeedEntityManager
|
||||
from .const import DOMAIN, FEED
|
||||
from . import GeonetnzQuakesConfigEntry
|
||||
|
||||
TO_REDACT = {CONF_LATITUDE, CONF_LONGITUDE}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry
|
||||
hass: HomeAssistant, config_entry: GeonetnzQuakesConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
data: dict[str, Any] = {
|
||||
"info": async_redact_data(config_entry.data, TO_REDACT),
|
||||
}
|
||||
|
||||
manager: GeonetnzQuakesFeedEntityManager = hass.data[DOMAIN][FEED][
|
||||
config_entry.entry_id
|
||||
]
|
||||
status_info = manager.status_info()
|
||||
status_info = config_entry.runtime_data.status_info()
|
||||
if status_info:
|
||||
data["service"] = {
|
||||
"status": status_info.status,
|
||||
|
||||
@@ -9,7 +9,6 @@ from typing import Any
|
||||
from aio_geojson_geonetnz_quakes.feed_entry import GeonetnzQuakesFeedEntry
|
||||
|
||||
from homeassistant.components.geo_location import GeolocationEvent
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_TIME, UnitOfLength
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -18,8 +17,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.unit_conversion import DistanceConverter
|
||||
from homeassistant.util.unit_system import US_CUSTOMARY_SYSTEM
|
||||
|
||||
from . import GeonetnzQuakesFeedEntityManager
|
||||
from .const import DOMAIN, FEED
|
||||
from . import GeonetnzQuakesConfigEntry, GeonetnzQuakesFeedEntityManager
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -39,11 +37,11 @@ SOURCE = "geonetnz_quakes"
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
entry: GeonetnzQuakesConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the GeoNet NZ Quakes Feed platform."""
|
||||
manager: GeonetnzQuakesFeedEntityManager = hass.data[DOMAIN][FEED][entry.entry_id]
|
||||
manager = entry.runtime_data
|
||||
|
||||
@callback
|
||||
def async_add_geolocation(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user