forked from home-assistant/core
Compare commits
130 Commits
2025.5.0b9
...
whirlpool_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32308649a0 | ||
|
|
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 | ||
|
|
c469720166 | ||
|
|
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,8 +171,6 @@ 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
|
||||
@@ -1320,6 +1318,8 @@ 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 @jaminh
|
||||
/tests/components/voip/ @balloob @synesthesiam @jaminh
|
||||
/homeassistant/components/voip/ @balloob @synesthesiam
|
||||
/tests/components/voip/ @balloob @synesthesiam
|
||||
/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.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
|
||||
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
|
||||
codenotary:
|
||||
signer: notary@home-assistant.io
|
||||
base_image: notary@home-assistant.io
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
{
|
||||
"domain": "amazon",
|
||||
"name": "Amazon",
|
||||
"integrations": [
|
||||
"alexa",
|
||||
"amazon_polly",
|
||||
"aws",
|
||||
"aws_s3",
|
||||
"fire_tv",
|
||||
"route53"
|
||||
]
|
||||
"integrations": ["alexa", "amazon_polly", "aws", "fire_tv", "route53"]
|
||||
}
|
||||
|
||||
@@ -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.1"]
|
||||
"requirements": ["bluemaestro-ble==0.4.0"]
|
||||
}
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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:
|
||||
|
||||
@@ -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}."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,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
|
||||
@@ -178,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()
|
||||
|
||||
@@ -192,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,
|
||||
@@ -211,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(
|
||||
|
||||
@@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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==20250506.0"]
|
||||
"requirements": ["home-assistant-frontend==20250502.0"]
|
||||
}
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
|
||||
from google.genai import Client
|
||||
from google.genai.errors import APIError, ClientError
|
||||
from google.genai.types import File, FileState
|
||||
from requests.exceptions import Timeout
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -34,8 +32,6 @@ from .const import (
|
||||
CONF_CHAT_MODEL,
|
||||
CONF_PROMPT,
|
||||
DOMAIN,
|
||||
FILE_POLLING_INTERVAL_SECONDS,
|
||||
LOGGER,
|
||||
RECOMMENDED_CHAT_MODEL,
|
||||
TIMEOUT_MILLIS,
|
||||
)
|
||||
@@ -95,40 +91,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
)
|
||||
prompt_parts.append(uploaded_file)
|
||||
|
||||
async def wait_for_file_processing(uploaded_file: File) -> None:
|
||||
"""Wait for file processing to complete."""
|
||||
while True:
|
||||
uploaded_file = await client.aio.files.get(
|
||||
name=uploaded_file.name,
|
||||
config={"http_options": {"timeout": TIMEOUT_MILLIS}},
|
||||
)
|
||||
if uploaded_file.state not in (
|
||||
FileState.STATE_UNSPECIFIED,
|
||||
FileState.PROCESSING,
|
||||
):
|
||||
break
|
||||
LOGGER.debug(
|
||||
"Waiting for file `%s` to be processed, current state: %s",
|
||||
uploaded_file.name,
|
||||
uploaded_file.state,
|
||||
)
|
||||
await asyncio.sleep(FILE_POLLING_INTERVAL_SECONDS)
|
||||
|
||||
if uploaded_file.state == FileState.FAILED:
|
||||
raise HomeAssistantError(
|
||||
f"File `{uploaded_file.name}` processing failed, reason: {uploaded_file.error.message}"
|
||||
)
|
||||
|
||||
await hass.async_add_executor_job(append_files_to_prompt)
|
||||
|
||||
tasks = [
|
||||
asyncio.create_task(wait_for_file_processing(part))
|
||||
for part in prompt_parts
|
||||
if isinstance(part, File) and part.state != FileState.ACTIVE
|
||||
]
|
||||
async with asyncio.timeout(TIMEOUT_MILLIS / 1000):
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
try:
|
||||
response = await client.aio.models.generate_content(
|
||||
model=RECOMMENDED_CHAT_MODEL, contents=prompt_parts
|
||||
|
||||
@@ -26,4 +26,3 @@ CONF_USE_GOOGLE_SEARCH_TOOL = "enable_google_search_tool"
|
||||
RECOMMENDED_USE_GOOGLE_SEARCH_TOOL = False
|
||||
|
||||
TIMEOUT_MILLIS = 10000
|
||||
FILE_POLLING_INTERVAL_SECONDS = 0.05
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants for the habitica integration."""
|
||||
|
||||
from homeassistant.const import APPLICATION_NAME, CONF_PATH, __version__
|
||||
from homeassistant.const import APPLICATION_NAME, __version__
|
||||
|
||||
CONF_API_USER = "api_user"
|
||||
|
||||
@@ -13,15 +13,6 @@ HABITICANS_URL = "https://habitica.com/static/img/home-main@3x.ffc32b12.png"
|
||||
|
||||
DOMAIN = "habitica"
|
||||
|
||||
# service constants
|
||||
SERVICE_API_CALL = "api_call"
|
||||
ATTR_PATH = CONF_PATH
|
||||
ATTR_ARGS = "args"
|
||||
|
||||
# event constants
|
||||
EVENT_API_CALL_SUCCESS = f"{DOMAIN}_{SERVICE_API_CALL}_success"
|
||||
ATTR_DATA = "data"
|
||||
|
||||
MANUFACTURER = "HabitRPG, Inc."
|
||||
NAME = "Habitica"
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.components.todo import ATTR_RENAME
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_DATE, ATTR_NAME, CONF_NAME
|
||||
from homeassistant.const import ATTR_DATE, ATTR_NAME
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
@@ -38,28 +38,24 @@ from homeassistant.core import (
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.selector import ConfigEntrySelector
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
ATTR_ADD_CHECKLIST_ITEM,
|
||||
ATTR_ALIAS,
|
||||
ATTR_ARGS,
|
||||
ATTR_CLEAR_DATE,
|
||||
ATTR_CLEAR_REMINDER,
|
||||
ATTR_CONFIG_ENTRY,
|
||||
ATTR_COST,
|
||||
ATTR_COUNTER_DOWN,
|
||||
ATTR_COUNTER_UP,
|
||||
ATTR_DATA,
|
||||
ATTR_DIRECTION,
|
||||
ATTR_FREQUENCY,
|
||||
ATTR_INTERVAL,
|
||||
ATTR_ITEM,
|
||||
ATTR_KEYWORD,
|
||||
ATTR_NOTES,
|
||||
ATTR_PATH,
|
||||
ATTR_PRIORITY,
|
||||
ATTR_REMINDER,
|
||||
ATTR_REMOVE_CHECKLIST_ITEM,
|
||||
@@ -78,10 +74,8 @@ from .const import (
|
||||
ATTR_UNSCORE_CHECKLIST_ITEM,
|
||||
ATTR_UP_DOWN,
|
||||
DOMAIN,
|
||||
EVENT_API_CALL_SUCCESS,
|
||||
SERVICE_ABORT_QUEST,
|
||||
SERVICE_ACCEPT_QUEST,
|
||||
SERVICE_API_CALL,
|
||||
SERVICE_CANCEL_QUEST,
|
||||
SERVICE_CAST_SKILL,
|
||||
SERVICE_CREATE_DAILY,
|
||||
@@ -106,14 +100,6 @@ from .coordinator import HabiticaConfigEntry
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
SERVICE_API_CALL_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_NAME): str,
|
||||
vol.Required(ATTR_PATH): vol.All(cv.ensure_list, [str]),
|
||||
vol.Optional(ATTR_ARGS): dict,
|
||||
}
|
||||
)
|
||||
|
||||
SERVICE_CAST_SKILL_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY): ConfigEntrySelector({"integration": DOMAIN}),
|
||||
@@ -266,46 +252,6 @@ def get_config_entry(hass: HomeAssistant, entry_id: str) -> HabiticaConfigEntry:
|
||||
def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
"""Set up services for Habitica integration."""
|
||||
|
||||
async def handle_api_call(call: ServiceCall) -> None:
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"deprecated_api_call",
|
||||
breaks_in_ha_version="2025.6.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_api_call",
|
||||
)
|
||||
_LOGGER.warning(
|
||||
"Deprecated action called: 'habitica.api_call' is deprecated and will be removed in Home Assistant version 2025.6.0"
|
||||
)
|
||||
|
||||
name = call.data[ATTR_NAME]
|
||||
path = call.data[ATTR_PATH]
|
||||
entries: list[HabiticaConfigEntry] = hass.config_entries.async_entries(DOMAIN)
|
||||
|
||||
api = None
|
||||
for entry in entries:
|
||||
if entry.data[CONF_NAME] == name:
|
||||
api = await entry.runtime_data.habitica.habitipy()
|
||||
break
|
||||
if api is None:
|
||||
_LOGGER.error("API_CALL: User '%s' not configured", name)
|
||||
return
|
||||
try:
|
||||
for element in path:
|
||||
api = api[element]
|
||||
except KeyError:
|
||||
_LOGGER.error(
|
||||
"API_CALL: Path %s is invalid for API on '{%s}' element", path, element
|
||||
)
|
||||
return
|
||||
kwargs = call.data.get(ATTR_ARGS, {})
|
||||
data = await api(**kwargs)
|
||||
hass.bus.async_fire(
|
||||
EVENT_API_CALL_SUCCESS, {ATTR_NAME: name, ATTR_PATH: path, ATTR_DATA: data}
|
||||
)
|
||||
|
||||
async def cast_skill(call: ServiceCall) -> ServiceResponse:
|
||||
"""Skill action."""
|
||||
entry = get_config_entry(hass, call.data[ATTR_CONFIG_ENTRY])
|
||||
@@ -928,12 +874,6 @@ def async_setup_services(hass: HomeAssistant) -> None: # noqa: C901
|
||||
schema=SERVICE_CREATE_TASK_SCHEMA,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
SERVICE_API_CALL,
|
||||
handle_api_call,
|
||||
schema=SERVICE_API_CALL_SCHEMA,
|
||||
)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN,
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
# Describes the format for Habitica service
|
||||
api_call:
|
||||
fields:
|
||||
name:
|
||||
required: true
|
||||
example: "xxxNotAValidNickxxx"
|
||||
selector:
|
||||
text:
|
||||
path:
|
||||
required: true
|
||||
example: '["tasks", "user", "post"]'
|
||||
selector:
|
||||
object:
|
||||
args:
|
||||
example: '{"text": "Use API from Home Assistant", "type": "todo"}'
|
||||
selector:
|
||||
object:
|
||||
cast_skill:
|
||||
fields:
|
||||
config_entry: &config_entry
|
||||
|
||||
@@ -526,31 +526,9 @@
|
||||
"deprecated_entity": {
|
||||
"title": "The Habitica {name} entity is deprecated",
|
||||
"description": "The Habitica entity `{entity}` is deprecated and will be removed in a future release.\nPlease update your automations and scripts, disable `{entity}` and reload the integration/restart Home Assistant to fix this issue."
|
||||
},
|
||||
"deprecated_api_call": {
|
||||
"title": "The Habitica action habitica.api_call is deprecated",
|
||||
"description": "The Habitica action `habitica.api_call` is deprecated and will be removed in Home Assistant 2025.5.0.\n\nPlease update your automations and scripts to use other Habitica actions and entities."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"api_call": {
|
||||
"name": "API name",
|
||||
"description": "Calls Habitica API.",
|
||||
"fields": {
|
||||
"name": {
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"description": "Habitica's username to call for."
|
||||
},
|
||||
"path": {
|
||||
"name": "[%key:common::config_flow::data::path%]",
|
||||
"description": "Items from API URL in form of an array with method attached at the end. Consult https://habitica.com/apidoc/. Example uses https://habitica.com/apidoc/#api-Task-CreateUserTasks."
|
||||
},
|
||||
"args": {
|
||||
"name": "Args",
|
||||
"description": "Any additional JSON or URL parameter arguments. See apidoc mentioned for path. Example uses same API endpoint."
|
||||
}
|
||||
}
|
||||
},
|
||||
"cast_skill": {
|
||||
"name": "Cast a skill",
|
||||
"description": "Uses a skill or spell from your Habitica character on a specific task to affect its progress or status.",
|
||||
|
||||
@@ -109,7 +109,6 @@ class HassIOIngress(HomeAssistantView):
|
||||
delete = _handle
|
||||
patch = _handle
|
||||
options = _handle
|
||||
head = _handle
|
||||
|
||||
async def _handle_websocket(
|
||||
self, request: web.Request, token: str, path: str
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "HEOS Options",
|
||||
"description": "You can sign-in to your HEOS Account to access favorites, streaming services, and other features. Clearing the credentials will sign-out of your account.",
|
||||
"title": "HEOS options",
|
||||
"description": "You can sign in to your HEOS Account to access favorites, streaming services, and other features. Clearing the credentials will sign out of your account.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
@@ -102,7 +102,7 @@
|
||||
},
|
||||
"move_queue_item": {
|
||||
"name": "Move queue item",
|
||||
"description": "Move one or more items within the play queue.",
|
||||
"description": "Moves one or more items within the play queue.",
|
||||
"fields": {
|
||||
"queue_ids": {
|
||||
"name": "Queue IDs",
|
||||
|
||||
@@ -4,8 +4,6 @@ from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiohomeconnect.client import Client as HomeConnectClient
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
|
||||
@@ -14,7 +12,7 @@ from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
|
||||
|
||||
|
||||
async def _generate_appliance_diagnostics(
|
||||
client: HomeConnectClient, appliance: HomeConnectApplianceData
|
||||
appliance: HomeConnectApplianceData,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
**appliance.info.to_dict(),
|
||||
@@ -31,9 +29,7 @@ async def async_get_config_entry_diagnostics(
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
return {
|
||||
appliance.info.ha_id: await _generate_appliance_diagnostics(
|
||||
entry.runtime_data.client, appliance
|
||||
)
|
||||
appliance.info.ha_id: await _generate_appliance_diagnostics(appliance)
|
||||
for appliance in entry.runtime_data.data.values()
|
||||
}
|
||||
|
||||
@@ -45,6 +41,4 @@ async def async_get_device_diagnostics(
|
||||
ha_id = next(
|
||||
(identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN),
|
||||
)
|
||||
return await _generate_appliance_diagnostics(
|
||||
entry.runtime_data.client, entry.runtime_data.data[ha_id]
|
||||
)
|
||||
return await _generate_appliance_diagnostics(entry.runtime_data.data[ha_id])
|
||||
|
||||
@@ -32,7 +32,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
|
||||
"""Generic Home Connect entity (base class)."""
|
||||
|
||||
_attr_should_poll = False
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -39,11 +39,11 @@ PARALLEL_UPDATES = 1
|
||||
class HomeConnectLightEntityDescription(LightEntityDescription):
|
||||
"""Light entity description."""
|
||||
|
||||
brightness_key: SettingKey | None = None
|
||||
brightness_key: SettingKey
|
||||
brightness_scale: tuple[float, float]
|
||||
color_key: SettingKey | None = None
|
||||
enable_custom_color_value_key: str | None = None
|
||||
custom_color_key: SettingKey | None = None
|
||||
brightness_scale: tuple[float, float] = (0.0, 100.0)
|
||||
|
||||
|
||||
LIGHTS: tuple[HomeConnectLightEntityDescription, ...] = (
|
||||
|
||||
@@ -4,6 +4,20 @@
|
||||
"codeowners": ["@DavidMStraub", "@Diegorro98", "@MartinHjelmare"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials", "repairs"],
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "balay-*",
|
||||
"macaddress": "C8D778*"
|
||||
},
|
||||
{
|
||||
"hostname": "(bosch|siemens)-*",
|
||||
"macaddress": "68A40E*"
|
||||
},
|
||||
{
|
||||
"hostname": "siemens-*",
|
||||
"macaddress": "38B4D3*"
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/home_connect",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["aiohomeconnect"],
|
||||
|
||||
@@ -11,6 +11,7 @@ from homeassistant.components.number import (
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
@@ -79,7 +80,7 @@ NUMBERS = (
|
||||
NumberEntityDescription(
|
||||
key=SettingKey.COOKING_HOOD_COLOR_TEMPERATURE_PERCENT,
|
||||
translation_key="color_temperature_percent",
|
||||
native_unit_of_measurement="%",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
NumberEntityDescription(
|
||||
key=SettingKey.LAUNDRY_CARE_WASHER_I_DOS_1_BASE_LEVEL,
|
||||
|
||||
@@ -159,7 +159,6 @@ SENSORS = (
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=StatusKey.BSH_COMMON_BATTERY_LEVEL,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
translation_key="battery_level",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
key=StatusKey.BSH_COMMON_VIDEO_CAMERA_STATE,
|
||||
|
||||
@@ -1585,9 +1585,6 @@
|
||||
"name": "Ristretto espresso cups",
|
||||
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
|
||||
},
|
||||
"battery_level": {
|
||||
"name": "Battery level"
|
||||
},
|
||||
"camera_state": {
|
||||
"name": "Camera state",
|
||||
"state": {
|
||||
|
||||
@@ -79,7 +79,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Call when entity is added to hass."""
|
||||
await super().async_added_to_hass()
|
||||
if self.bsh_key == SettingKey.BSH_COMMON_ALARM_CLOCK:
|
||||
if self.bsh_key is SettingKey.BSH_COMMON_ALARM_CLOCK:
|
||||
automations = automations_with_entity(self.hass, self.entity_id)
|
||||
scripts = scripts_with_entity(self.hass, self.entity_id)
|
||||
items = automations + scripts
|
||||
@@ -123,7 +123,7 @@ class HomeConnectTimeEntity(HomeConnectEntity, TimeEntity):
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Call when entity will be removed from hass."""
|
||||
if self.bsh_key == SettingKey.BSH_COMMON_ALARM_CLOCK:
|
||||
if self.bsh_key is SettingKey.BSH_COMMON_ALARM_CLOCK:
|
||||
async_delete_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
|
||||
@@ -23,7 +23,6 @@ from huawei_lte_api.exceptions import (
|
||||
from requests.exceptions import Timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.notify import DOMAIN as NOTIFY_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import (
|
||||
ATTR_HW_VERSION,
|
||||
@@ -90,36 +89,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
NOTIFY_SCHEMA = vol.Any(
|
||||
None,
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_RECIPIENT): vol.Any(
|
||||
None, vol.All(cv.ensure_list, [cv.string])
|
||||
),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.All(
|
||||
cv.ensure_list,
|
||||
[
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_URL): cv.url,
|
||||
vol.Optional(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(NOTIFY_DOMAIN): NOTIFY_SCHEMA,
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
SERVICE_SCHEMA = vol.Schema({vol.Optional(CONF_URL): cv.url})
|
||||
|
||||
|
||||
@@ -37,6 +37,137 @@
|
||||
"default": "mdi:antenna"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"uptime": {
|
||||
"default": "mdi:timer-outline"
|
||||
},
|
||||
"wan_ip_address": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"wan_ipv6_address": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"cell_id": {
|
||||
"default": "mdi:antenna"
|
||||
},
|
||||
"cqi0": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"cqi1": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"enodeb_id": {
|
||||
"default": "mdi:antenna"
|
||||
},
|
||||
"lac": {
|
||||
"default": "mdi:map-marker"
|
||||
},
|
||||
"nei_cellid": {
|
||||
"default": "mdi:antenna"
|
||||
},
|
||||
"nrcqi0": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"nrcqi1": {
|
||||
"default": "mdi:speedometer"
|
||||
},
|
||||
"pci": {
|
||||
"default": "mdi:antenna"
|
||||
},
|
||||
"rac": {
|
||||
"default": "mdi:map-marker"
|
||||
},
|
||||
"tac": {
|
||||
"default": "mdi:map-marker"
|
||||
},
|
||||
"sms_unread": {
|
||||
"default": "mdi:email-arrow-left"
|
||||
},
|
||||
"current_day_transfer": {
|
||||
"default": "mdi:arrow-up-down-bold"
|
||||
},
|
||||
"current_month_download": {
|
||||
"default": "mdi:download"
|
||||
},
|
||||
"current_month_upload": {
|
||||
"default": "mdi:upload"
|
||||
},
|
||||
"wifi_clients_connected": {
|
||||
"default": "mdi:wifi"
|
||||
},
|
||||
"primary_dns_server": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"primary_ipv6_dns_server": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"secondary_dns_server": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"secondary_ipv6_dns_server": {
|
||||
"default": "mdi:ip"
|
||||
},
|
||||
"current_connection_duration": {
|
||||
"default": "mdi:timer-outline"
|
||||
},
|
||||
"current_connection_download": {
|
||||
"default": "mdi:download"
|
||||
},
|
||||
"current_download_rate": {
|
||||
"default": "mdi:download"
|
||||
},
|
||||
"current_connection_upload": {
|
||||
"default": "mdi:upload"
|
||||
},
|
||||
"current_upload_rate": {
|
||||
"default": "mdi:upload"
|
||||
},
|
||||
"total_connected_duration": {
|
||||
"default": "mdi:timer-outline"
|
||||
},
|
||||
"total_download": {
|
||||
"default": "mdi:download"
|
||||
},
|
||||
"total_upload": {
|
||||
"default": "mdi:upload"
|
||||
},
|
||||
"sms_deleted_device": {
|
||||
"default": "mdi:email-minus"
|
||||
},
|
||||
"sms_drafts_device": {
|
||||
"default": "mdi:email-arrow-right-outline"
|
||||
},
|
||||
"sms_inbox_device": {
|
||||
"default": "mdi:email"
|
||||
},
|
||||
"sms_capacity_device": {
|
||||
"default": "mdi:email"
|
||||
},
|
||||
"sms_outbox_device": {
|
||||
"default": "mdi:email-arrow-right"
|
||||
},
|
||||
"sms_unread_device": {
|
||||
"default": "mdi:email-arrow-left"
|
||||
},
|
||||
"sms_drafts_sim": {
|
||||
"default": "mdi:email-arrow-right-outline"
|
||||
},
|
||||
"sms_inbox_sim": {
|
||||
"default": "mdi:email"
|
||||
},
|
||||
"sms_capacity_sim": {
|
||||
"default": "mdi:email"
|
||||
},
|
||||
"sms_outbox_sim": {
|
||||
"default": "mdi:email-arrow-right"
|
||||
},
|
||||
"sms_unread_sim": {
|
||||
"default": "mdi:email-arrow-left"
|
||||
},
|
||||
"sms_messages_sim": {
|
||||
"default": "mdi:email-arrow-left"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"mobile_data": {
|
||||
"default": "mdi:signal-off",
|
||||
|
||||
@@ -138,7 +138,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"uptime": HuaweiSensorEntityDescription(
|
||||
key="uptime",
|
||||
translation_key="uptime",
|
||||
icon="mdi:timer-outline",
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
@@ -146,14 +145,12 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"WanIPAddress": HuaweiSensorEntityDescription(
|
||||
key="WanIPAddress",
|
||||
translation_key="wan_ip_address",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=True,
|
||||
),
|
||||
"WanIPv6Address": HuaweiSensorEntityDescription(
|
||||
key="WanIPv6Address",
|
||||
translation_key="wan_ipv6_address",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
},
|
||||
@@ -181,19 +178,16 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"cell_id": HuaweiSensorEntityDescription(
|
||||
key="cell_id",
|
||||
translation_key="cell_id",
|
||||
icon="mdi:antenna",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"cqi0": HuaweiSensorEntityDescription(
|
||||
key="cqi0",
|
||||
translation_key="cqi0",
|
||||
icon="mdi:speedometer",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"cqi1": HuaweiSensorEntityDescription(
|
||||
key="cqi1",
|
||||
translation_key="cqi1",
|
||||
icon="mdi:speedometer",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"dl_mcs": HuaweiSensorEntityDescription(
|
||||
@@ -230,7 +224,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"enodeb_id": HuaweiSensorEntityDescription(
|
||||
key="enodeb_id",
|
||||
translation_key="enodeb_id",
|
||||
icon="mdi:antenna",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"ims": HuaweiSensorEntityDescription(
|
||||
@@ -241,7 +234,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"lac": HuaweiSensorEntityDescription(
|
||||
key="lac",
|
||||
translation_key="lac",
|
||||
icon="mdi:map-marker",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"ltedlfreq": HuaweiSensorEntityDescription(
|
||||
@@ -279,7 +271,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"nei_cellid": HuaweiSensorEntityDescription(
|
||||
key="nei_cellid",
|
||||
translation_key="nei_cellid",
|
||||
icon="mdi:antenna",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"nrbler": HuaweiSensorEntityDescription(
|
||||
@@ -290,13 +281,11 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"nrcqi0": HuaweiSensorEntityDescription(
|
||||
key="nrcqi0",
|
||||
translation_key="nrcqi0",
|
||||
icon="mdi:speedometer",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"nrcqi1": HuaweiSensorEntityDescription(
|
||||
key="nrcqi1",
|
||||
translation_key="nrcqi1",
|
||||
icon="mdi:speedometer",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"nrdlbandwidth": HuaweiSensorEntityDescription(
|
||||
@@ -376,7 +365,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"pci": HuaweiSensorEntityDescription(
|
||||
key="pci",
|
||||
translation_key="pci",
|
||||
icon="mdi:antenna",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"plmn": HuaweiSensorEntityDescription(
|
||||
@@ -387,7 +375,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"rac": HuaweiSensorEntityDescription(
|
||||
key="rac",
|
||||
translation_key="rac",
|
||||
icon="mdi:map-marker",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"rrc_status": HuaweiSensorEntityDescription(
|
||||
@@ -458,7 +445,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"tac": HuaweiSensorEntityDescription(
|
||||
key="tac",
|
||||
translation_key="tac",
|
||||
icon="mdi:map-marker",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"tdd": HuaweiSensorEntityDescription(
|
||||
@@ -522,7 +508,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"UnreadMessage": HuaweiSensorEntityDescription(
|
||||
key="UnreadMessage",
|
||||
translation_key="sms_unread",
|
||||
icon="mdi:email-arrow-left",
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -536,7 +521,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_day_transfer",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:arrow-up-down-bold",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
last_reset_item="CurrentDayDuration",
|
||||
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
||||
@@ -546,7 +530,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_month_download",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:download",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
last_reset_item="MonthDuration",
|
||||
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
||||
@@ -556,7 +539,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_month_upload",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:upload",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
last_reset_item="MonthDuration",
|
||||
last_reset_format_fn=format_last_reset_elapsed_seconds,
|
||||
@@ -580,32 +562,27 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"CurrentWifiUser": HuaweiSensorEntityDescription(
|
||||
key="CurrentWifiUser",
|
||||
translation_key="wifi_clients_connected",
|
||||
icon="mdi:wifi",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"PrimaryDns": HuaweiSensorEntityDescription(
|
||||
key="PrimaryDns",
|
||||
translation_key="primary_dns_server",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"PrimaryIPv6Dns": HuaweiSensorEntityDescription(
|
||||
key="PrimaryIPv6Dns",
|
||||
translation_key="primary_ipv6_dns_server",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"SecondaryDns": HuaweiSensorEntityDescription(
|
||||
key="SecondaryDns",
|
||||
translation_key="secondary_dns_server",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
"SecondaryIPv6Dns": HuaweiSensorEntityDescription(
|
||||
key="SecondaryIPv6Dns",
|
||||
translation_key="secondary_ipv6_dns_server",
|
||||
icon="mdi:ip",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
},
|
||||
@@ -618,14 +595,12 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_connection_duration",
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
icon="mdi:timer-outline",
|
||||
),
|
||||
"CurrentDownload": HuaweiSensorEntityDescription(
|
||||
key="CurrentDownload",
|
||||
translation_key="current_connection_download",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:download",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
"CurrentDownloadRate": HuaweiSensorEntityDescription(
|
||||
@@ -633,7 +608,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_download_rate",
|
||||
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
icon="mdi:download",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"CurrentUpload": HuaweiSensorEntityDescription(
|
||||
@@ -641,7 +615,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_connection_upload",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:upload",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
"CurrentUploadRate": HuaweiSensorEntityDescription(
|
||||
@@ -649,7 +622,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="current_upload_rate",
|
||||
native_unit_of_measurement=UnitOfDataRate.BYTES_PER_SECOND,
|
||||
device_class=SensorDeviceClass.DATA_RATE,
|
||||
icon="mdi:upload",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
"TotalConnectTime": HuaweiSensorEntityDescription(
|
||||
@@ -657,7 +629,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="total_connected_duration",
|
||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
icon="mdi:timer-outline",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
"TotalDownload": HuaweiSensorEntityDescription(
|
||||
@@ -665,7 +636,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="total_download",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:download",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
"TotalUpload": HuaweiSensorEntityDescription(
|
||||
@@ -673,7 +643,6 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
translation_key="total_upload",
|
||||
native_unit_of_measurement=UnitOfInformation.BYTES,
|
||||
device_class=SensorDeviceClass.DATA_SIZE,
|
||||
icon="mdi:upload",
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
},
|
||||
@@ -719,62 +688,50 @@ SENSOR_META: dict[str, HuaweiSensorGroup] = {
|
||||
"LocalDeleted": HuaweiSensorEntityDescription(
|
||||
key="LocalDeleted",
|
||||
translation_key="sms_deleted_device",
|
||||
icon="mdi:email-minus",
|
||||
),
|
||||
"LocalDraft": HuaweiSensorEntityDescription(
|
||||
key="LocalDraft",
|
||||
translation_key="sms_drafts_device",
|
||||
icon="mdi:email-arrow-right-outline",
|
||||
),
|
||||
"LocalInbox": HuaweiSensorEntityDescription(
|
||||
key="LocalInbox",
|
||||
translation_key="sms_inbox_device",
|
||||
icon="mdi:email",
|
||||
),
|
||||
"LocalMax": HuaweiSensorEntityDescription(
|
||||
key="LocalMax",
|
||||
translation_key="sms_capacity_device",
|
||||
icon="mdi:email",
|
||||
),
|
||||
"LocalOutbox": HuaweiSensorEntityDescription(
|
||||
key="LocalOutbox",
|
||||
translation_key="sms_outbox_device",
|
||||
icon="mdi:email-arrow-right",
|
||||
),
|
||||
"LocalUnread": HuaweiSensorEntityDescription(
|
||||
key="LocalUnread",
|
||||
translation_key="sms_unread_device",
|
||||
icon="mdi:email-arrow-left",
|
||||
),
|
||||
"SimDraft": HuaweiSensorEntityDescription(
|
||||
key="SimDraft",
|
||||
translation_key="sms_drafts_sim",
|
||||
icon="mdi:email-arrow-right-outline",
|
||||
),
|
||||
"SimInbox": HuaweiSensorEntityDescription(
|
||||
key="SimInbox",
|
||||
translation_key="sms_inbox_sim",
|
||||
icon="mdi:email",
|
||||
),
|
||||
"SimMax": HuaweiSensorEntityDescription(
|
||||
key="SimMax",
|
||||
translation_key="sms_capacity_sim",
|
||||
icon="mdi:email",
|
||||
),
|
||||
"SimOutbox": HuaweiSensorEntityDescription(
|
||||
key="SimOutbox",
|
||||
translation_key="sms_outbox_sim",
|
||||
icon="mdi:email-arrow-right",
|
||||
),
|
||||
"SimUnread": HuaweiSensorEntityDescription(
|
||||
key="SimUnread",
|
||||
translation_key="sms_unread_sim",
|
||||
icon="mdi:email-arrow-left",
|
||||
),
|
||||
"SimUsed": HuaweiSensorEntityDescription(
|
||||
key="SimUsed",
|
||||
translation_key="sms_messages_sim",
|
||||
icon="mdi:email-arrow-left",
|
||||
),
|
||||
},
|
||||
),
|
||||
@@ -870,7 +827,7 @@ class HuaweiLteSensor(HuaweiLteBaseEntityWithDevice, SensorEntity):
|
||||
"""Return icon for sensor."""
|
||||
if self.entity_description.icon_fn:
|
||||
return self.entity_description.icon_fn(self.state)
|
||||
return self.entity_description.icon
|
||||
return super().icon
|
||||
|
||||
@property
|
||||
def device_class(self) -> SensorDeviceClass | None:
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
},
|
||||
"data_description": {
|
||||
"name": "Used to distinguish between notification services in case there are multiple Huawei LTE devices configured. Changes to this option value take effect after Home Assistant restart.",
|
||||
"recipient": "Comma separated list of default recipient SMS phone numbers for the notification service, used in case the notification sender does not specify any.",
|
||||
"recipient": "Comma-separated list of default recipient SMS phone numbers for the notification service, used in case the notification sender does not specify any.",
|
||||
"track_wired_clients": "Whether the device tracker entities track also clients attached to the router's wired Ethernet network, in addition to wireless clients.",
|
||||
"unauthenticated_mode": "Whether to run in unauthenticated mode. Unauthenticated mode provides a limited set of features, but may help in case there are problems accessing the router's web interface from a browser while the integration is active. Changes to this option value take effect after integration reload."
|
||||
}
|
||||
|
||||
@@ -3,29 +3,18 @@
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aioautomower.model import MowerActivities, MowerAttributes
|
||||
|
||||
from homeassistant.components.automation import automations_with_entity
|
||||
from homeassistant.components.binary_sensor import (
|
||||
DOMAIN as BINARY_SENSOR_DOMAIN,
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
from homeassistant.components.script import scripts_with_entity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
|
||||
from . import AutomowerConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AutomowerDataUpdateCoordinator
|
||||
from .entity import AutomowerBaseEntity
|
||||
|
||||
@@ -34,13 +23,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
def entity_used_in(hass: HomeAssistant, entity_id: str) -> list[str]:
|
||||
"""Get list of related automations and scripts."""
|
||||
used_in = automations_with_entity(hass, entity_id)
|
||||
used_in += scripts_with_entity(hass, entity_id)
|
||||
return used_in
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AutomowerBinarySensorEntityDescription(BinarySensorEntityDescription):
|
||||
"""Describes Automower binary sensor entity."""
|
||||
@@ -59,12 +41,6 @@ MOWER_BINARY_SENSOR_TYPES: tuple[AutomowerBinarySensorEntityDescription, ...] =
|
||||
translation_key="leaving_dock",
|
||||
value_fn=lambda data: data.mower.activity == MowerActivities.LEAVING,
|
||||
),
|
||||
AutomowerBinarySensorEntityDescription(
|
||||
key="returning_to_dock",
|
||||
translation_key="returning_to_dock",
|
||||
value_fn=lambda data: data.mower.activity == MowerActivities.GOING_HOME,
|
||||
entity_registry_enabled_default=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -107,39 +83,3 @@ class AutomowerBinarySensorEntity(AutomowerBaseEntity, BinarySensorEntity):
|
||||
def is_on(self) -> bool:
|
||||
"""Return the state of the binary sensor."""
|
||||
return self.entity_description.value_fn(self.mower_attributes)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Raise issue when entity is registered and was not disabled."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.unique_id
|
||||
if not (
|
||||
entity_id := er.async_get(self.hass).async_get_entity_id(
|
||||
BINARY_SENSOR_DOMAIN, DOMAIN, self.unique_id
|
||||
)
|
||||
):
|
||||
return
|
||||
if (
|
||||
self.enabled
|
||||
and self.entity_description.key == "returning_to_dock"
|
||||
and entity_used_in(self.hass, entity_id)
|
||||
):
|
||||
async_create_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"deprecated_entity_{self.entity_description.key}",
|
||||
breaks_in_ha_version="2025.6.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="deprecated_entity",
|
||||
translation_placeholders={
|
||||
"entity_name": str(self.name),
|
||||
"entity": entity_id,
|
||||
},
|
||||
)
|
||||
else:
|
||||
async_delete_issue(
|
||||
self.hass,
|
||||
DOMAIN,
|
||||
f"deprecated_task_entity_{self.entity_description.key}",
|
||||
)
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@@ -67,7 +67,9 @@ rules:
|
||||
reconfiguration-flow:
|
||||
status: exempt
|
||||
comment: no configuration possible
|
||||
repair-issues: done
|
||||
repair-issues:
|
||||
status: exempt
|
||||
comment: no issues available
|
||||
stale-devices: done
|
||||
|
||||
# Platinum
|
||||
|
||||
@@ -19,10 +19,10 @@ _LOGGER = logging.getLogger(__name__)
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
HEADLIGHT_MODES: list = [
|
||||
HeadlightModes.ALWAYS_OFF.lower(),
|
||||
HeadlightModes.ALWAYS_ON.lower(),
|
||||
HeadlightModes.EVENING_AND_NIGHT.lower(),
|
||||
HeadlightModes.EVENING_ONLY.lower(),
|
||||
HeadlightModes.ALWAYS_OFF,
|
||||
HeadlightModes.ALWAYS_ON,
|
||||
HeadlightModes.EVENING_AND_NIGHT,
|
||||
HeadlightModes.EVENING_ONLY,
|
||||
]
|
||||
|
||||
|
||||
@@ -65,13 +65,11 @@ class AutomowerSelectEntity(AutomowerControlEntity, SelectEntity):
|
||||
@property
|
||||
def current_option(self) -> str:
|
||||
"""Return the current option for the entity."""
|
||||
return cast(
|
||||
HeadlightModes, self.mower_attributes.settings.headlight.mode
|
||||
).lower()
|
||||
return cast(HeadlightModes, self.mower_attributes.settings.headlight.mode)
|
||||
|
||||
@handle_sending_exception()
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
await self.coordinator.api.commands.set_headlight_mode(
|
||||
self.mower_id, cast(HeadlightModes, option.upper())
|
||||
self.mower_id, HeadlightModes(option)
|
||||
)
|
||||
|
||||
@@ -39,9 +39,6 @@
|
||||
"binary_sensor": {
|
||||
"leaving_dock": {
|
||||
"name": "Leaving dock"
|
||||
},
|
||||
"returning_to_dock": {
|
||||
"name": "Returning to dock"
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
@@ -323,12 +320,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"deprecated_entity": {
|
||||
"title": "The Husqvarna Automower {entity_name} sensor is deprecated",
|
||||
"description": "The Husqvarna Automower entity `{entity}` is deprecated and will be removed in a future release.\nYou can use the new returning state of the lawn mower entity instead.\nPlease update your automations and scripts to replace the sensor entity with the newly added lawn mower entity.\nWhen you are done migrating you can disable `{entity}`."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"override_schedule": {
|
||||
"name": "Override schedule",
|
||||
|
||||
@@ -10,7 +10,9 @@ import voluptuous as vol
|
||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
||||
from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
@@ -62,3 +64,19 @@ class KnockiConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
errors=errors,
|
||||
data_schema=DATA_SCHEMA,
|
||||
)
|
||||
|
||||
async def async_step_dhcp(
|
||||
self, discovery_info: DhcpServiceInfo
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a DHCP discovery."""
|
||||
device_registry = dr.async_get(self.hass)
|
||||
if device_entry := device_registry.async_get_device(
|
||||
identifiers={(DOMAIN, discovery_info.hostname)}
|
||||
):
|
||||
device_registry.async_update_device(
|
||||
device_entry.id,
|
||||
new_connections={
|
||||
(dr.CONNECTION_NETWORK_MAC, discovery_info.macaddress)
|
||||
},
|
||||
)
|
||||
return await super().async_step_dhcp(discovery_info)
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
"name": "Knocki",
|
||||
"codeowners": ["@joostlek", "@jgatto1", "@JakeBosh"],
|
||||
"config_flow": true,
|
||||
"dhcp": [
|
||||
{
|
||||
"hostname": "knc*"
|
||||
}
|
||||
],
|
||||
"documentation": "https://www.home-assistant.io/integrations/knocki",
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
|
||||
@@ -50,10 +50,8 @@ rules:
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: This is a cloud service and does not benefit from device updates.
|
||||
discovery: todo
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: todo
|
||||
docs-examples: todo
|
||||
docs-known-limitations: todo
|
||||
|
||||
@@ -37,5 +37,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pylamarzocco"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pylamarzocco==2.0.0"]
|
||||
"requirements": ["pylamarzocco==2.0.0b7"]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ from .coordinator import LitterRobotDataUpdateCoordinator
|
||||
_WhiskerEntityT = TypeVar("_WhiskerEntityT", bound=Robot | Pet)
|
||||
|
||||
|
||||
def get_device_info(whisker_entity: _WhiskerEntityT) -> DeviceInfo:
|
||||
def get_device_info(whisker_entity: Robot | Pet) -> DeviceInfo:
|
||||
"""Get device info for a robot or pet."""
|
||||
if isinstance(whisker_entity, Robot):
|
||||
return DeviceInfo(
|
||||
|
||||
@@ -36,6 +36,11 @@ _LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PRODID = "-//homeassistant.io//local_calendar 1.0//EN"
|
||||
|
||||
# The calendar on disk is only changed when this entity is updated, so there
|
||||
# is no need to poll for changes. The calendar enttiy base class will handle
|
||||
# refreshing the entity state based on the start or end time of the event.
|
||||
SCAN_INTERVAL = timedelta(days=1)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -93,6 +93,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
|
||||
DOMAIN,
|
||||
SERVICE_VOLUME_SET,
|
||||
required_domains={DOMAIN},
|
||||
required_states={MediaPlayerState.PLAYING},
|
||||
required_features=MediaPlayerEntityFeature.VOLUME_SET,
|
||||
required_slots={
|
||||
ATTR_MEDIA_VOLUME_LEVEL: intent.IntentSlotInfo(
|
||||
@@ -158,6 +159,7 @@ class MediaUnpauseHandler(intent.ServiceIntentHandler):
|
||||
DOMAIN,
|
||||
SERVICE_MEDIA_PLAY,
|
||||
required_domains={DOMAIN},
|
||||
required_states={MediaPlayerState.PAUSED},
|
||||
description="Resumes a media player",
|
||||
platforms={DOMAIN},
|
||||
device_classes={MediaPlayerDeviceClass},
|
||||
|
||||
@@ -57,8 +57,8 @@ ATA_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATA_HVAC_MODE_LOOKUP.items()}
|
||||
|
||||
|
||||
ATW_ZONE_HVAC_MODE_LOOKUP = {
|
||||
atw.ZONE_STATUS_HEAT: HVACMode.HEAT,
|
||||
atw.ZONE_STATUS_COOL: HVACMode.COOL,
|
||||
atw.ZONE_OPERATION_MODE_HEAT: HVACMode.HEAT,
|
||||
atw.ZONE_OPERATION_MODE_COOL: HVACMode.COOL,
|
||||
}
|
||||
ATW_ZONE_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATW_ZONE_HVAC_MODE_LOOKUP.items()}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from pymiele import MieleEnum
|
||||
|
||||
DOMAIN = "miele"
|
||||
MANUFACTURER = "Miele"
|
||||
|
||||
@@ -246,6 +248,7 @@ STATE_PROGRAM_PHASE_OVEN = {
|
||||
}
|
||||
STATE_PROGRAM_PHASE_WARMING_DRAWER = {
|
||||
0: "not_running",
|
||||
3073: "heating_up",
|
||||
3075: "door_open",
|
||||
3094: "keeping_warm",
|
||||
3088: "cooling_down",
|
||||
@@ -324,13 +327,17 @@ STATE_PROGRAM_PHASE: dict[int, dict[int, str]] = {
|
||||
MieleAppliance.ROBOT_VACUUM_CLEANER: STATE_PROGRAM_PHASE_ROBOT_VACUUM_CLEANER,
|
||||
}
|
||||
|
||||
STATE_PROGRAM_TYPE = {
|
||||
0: "normal_operation_mode",
|
||||
1: "own_program",
|
||||
2: "automatic_program",
|
||||
3: "cleaning_care_program",
|
||||
4: "maintenance_program",
|
||||
}
|
||||
|
||||
class StateProgramType(MieleEnum):
|
||||
"""Defines program types."""
|
||||
|
||||
normal_operation_mode = 0
|
||||
own_program = 1
|
||||
automatic_program = 2
|
||||
cleaning_care_program = 3
|
||||
maintenance_program = 4
|
||||
unknown = -9999
|
||||
|
||||
|
||||
WASHING_MACHINE_PROGRAM_ID: dict[int, str] = {
|
||||
-1: "no_program", # Extrapolated from other device types.
|
||||
@@ -404,14 +411,21 @@ DISHWASHER_PROGRAM_ID: dict[int, str] = {
|
||||
TUMBLE_DRYER_PROGRAM_ID: dict[int, str] = {
|
||||
-1: "no_program", # Extrapolated from other device types.
|
||||
0: "no_program", # Extrapolated from other device types
|
||||
2: "cottons",
|
||||
3: "minimum_iron",
|
||||
4: "woollens_handcare",
|
||||
5: "delicates",
|
||||
6: "warm_air",
|
||||
8: "express",
|
||||
10: "automatic_plus",
|
||||
20: "cottons",
|
||||
23: "cottons_hygiene",
|
||||
30: "minimum_iron",
|
||||
31: "gentle_minimum_iron",
|
||||
31: "bed_linen",
|
||||
40: "woollens_handcare",
|
||||
50: "delicates",
|
||||
60: "warm_air",
|
||||
66: "eco",
|
||||
70: "cool_air",
|
||||
80: "express",
|
||||
90: "cottons",
|
||||
@@ -449,17 +463,29 @@ OVEN_PROGRAM_ID: dict[int, str] = {
|
||||
31: "bottom_heat",
|
||||
35: "moisture_plus_auto_roast",
|
||||
40: "moisture_plus_fan_plus",
|
||||
48: "moisture_plus_auto_roast",
|
||||
49: "moisture_plus_fan_plus",
|
||||
50: "moisture_plus_intensive_bake",
|
||||
51: "moisture_plus_conventional_heat",
|
||||
74: "moisture_plus_intensive_bake",
|
||||
76: "moisture_plus_conventional_heat",
|
||||
49: "moisture_plus_fan_plus",
|
||||
323: "pyrolytic",
|
||||
326: "descale",
|
||||
335: "shabbat_program",
|
||||
336: "yom_tov",
|
||||
356: "defrost",
|
||||
357: "drying",
|
||||
358: "heat_crockery",
|
||||
360: "low_temperature_cooking",
|
||||
361: "steam_cooking",
|
||||
362: "keeping_warm",
|
||||
512: "1_tray",
|
||||
513: "2_trays",
|
||||
529: "baking_tray",
|
||||
554: "baiser_one_large",
|
||||
555: "baiser_several_small",
|
||||
556: "lemon_meringue_pie",
|
||||
557: "viennese_apple_strudel",
|
||||
621: "prove_15_min",
|
||||
622: "prove_30_min",
|
||||
623: "prove_45_min",
|
||||
@@ -673,7 +699,7 @@ STEAM_OVEN_MICRO_PROGRAM_ID: dict[int, str] = {
|
||||
2019: "defrosting_with_steam",
|
||||
2020: "blanching",
|
||||
2021: "bottling",
|
||||
2022: "heat_crockery",
|
||||
2022: "sterilize_crockery",
|
||||
2023: "prove_dough",
|
||||
2027: "soak",
|
||||
2029: "reheating_with_microwave",
|
||||
@@ -745,7 +771,7 @@ STEAM_OVEN_MICRO_PROGRAM_ID: dict[int, str] = {
|
||||
2129: "potatoes_floury_diced",
|
||||
2130: "german_turnip_sliced",
|
||||
2131: "german_turnip_cut_into_batons",
|
||||
2132: "german_turnip_sliced",
|
||||
2132: "german_turnip_diced",
|
||||
2133: "pumpkin_diced",
|
||||
2134: "corn_on_the_cob",
|
||||
2135: "mangel_cut",
|
||||
|
||||
@@ -30,9 +30,9 @@ from homeassistant.helpers.typing import StateType
|
||||
from .const import (
|
||||
STATE_PROGRAM_ID,
|
||||
STATE_PROGRAM_PHASE,
|
||||
STATE_PROGRAM_TYPE,
|
||||
STATE_STATUS_TAGS,
|
||||
MieleAppliance,
|
||||
StateProgramType,
|
||||
StateStatus,
|
||||
)
|
||||
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
|
||||
@@ -144,6 +144,7 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
||||
MieleAppliance.STEAM_OVEN,
|
||||
MieleAppliance.MICROWAVE,
|
||||
MieleAppliance.COFFEE_SYSTEM,
|
||||
MieleAppliance.ROBOT_VACUUM_CLEANER,
|
||||
MieleAppliance.WASHER_DRYER,
|
||||
MieleAppliance.STEAM_OVEN_COMBI,
|
||||
MieleAppliance.STEAM_OVEN_MICRO,
|
||||
@@ -180,10 +181,10 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
||||
description=MieleSensorDescription(
|
||||
key="state_program_type",
|
||||
translation_key="program_type",
|
||||
value_fn=lambda value: value.state_program_type,
|
||||
value_fn=lambda value: StateProgramType(value.state_program_type).name,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=sorted(set(STATE_PROGRAM_TYPE.values())),
|
||||
options=sorted(set(StateProgramType.keys())),
|
||||
),
|
||||
),
|
||||
MieleSensorDefinition(
|
||||
@@ -439,8 +440,6 @@ async def async_setup_entry(
|
||||
entity_class = MieleProgramIdSensor
|
||||
case "state_program_phase":
|
||||
entity_class = MielePhaseSensor
|
||||
case "state_program_type":
|
||||
entity_class = MieleTypeSensor
|
||||
case _:
|
||||
entity_class = MieleSensor
|
||||
if (
|
||||
@@ -552,22 +551,6 @@ class MielePhaseSensor(MieleSensor):
|
||||
)
|
||||
|
||||
|
||||
class MieleTypeSensor(MieleSensor):
|
||||
"""Representation of the program type sensor."""
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the state of the sensor."""
|
||||
ret_val = STATE_PROGRAM_TYPE.get(int(self.device.state_program_type))
|
||||
if ret_val is None:
|
||||
_LOGGER.debug(
|
||||
"Unknown program type: %s on device type: %s",
|
||||
self.device.state_program_type,
|
||||
self.device.device_type,
|
||||
)
|
||||
return ret_val
|
||||
|
||||
|
||||
class MieleProgramIdSensor(MieleSensor):
|
||||
"""Representation of the program id sensor."""
|
||||
|
||||
|
||||
@@ -313,6 +313,8 @@
|
||||
"automatic_plus": "Automatic plus",
|
||||
"baking_tray": "Baking tray",
|
||||
"barista_assistant": "BaristaAssistant",
|
||||
"baser_one_large": "Baiser one large",
|
||||
"baser_severall_small": "Baiser several small",
|
||||
"basket_program": "Basket program",
|
||||
"basmati_rice_rapid_steam_cooking": "Basmati rice (rapid steam cooking)",
|
||||
"basmati_rice_steam_cooking": "Basmati rice (steam cooking)",
|
||||
@@ -468,7 +470,7 @@
|
||||
"gentle_minimum_iron": "Gentle minimum iron",
|
||||
"gentle_smoothing": "Gentle smoothing",
|
||||
"german_turnip_cut_into_batons": "German turnip (cut into batons)",
|
||||
"german_turnip_sliced": "German turnip (sliced)",
|
||||
"german_turnip_diced": "German turnip (diced)",
|
||||
"gilt_head_bream_fillet": "Gilt-head bream (fillet)",
|
||||
"gilt_head_bream_whole": "Gilt-head bream (whole)",
|
||||
"glasses_warm": "Glasses warm",
|
||||
@@ -489,7 +491,6 @@
|
||||
"greenage_plums": "Greenage plums",
|
||||
"halibut_fillet_2_cm": "Halibut (fillet, 2 cm)",
|
||||
"halibut_fillet_3_cm": "Halibut (fillet, 3 cm)",
|
||||
"heat_crockery": "Heat crockery",
|
||||
"heating_damp_flannels": "Heating damp flannels",
|
||||
"hens_eggs_size_l_hard": "Hen’s eggs (size „L“, hard)",
|
||||
"hens_eggs_size_l_medium": "Hen’s eggs (size „L“, medium)",
|
||||
@@ -529,9 +530,11 @@
|
||||
"latte_macchiato": "Latte macchiato",
|
||||
"leek_pieces": "Leek (pieces)",
|
||||
"leek_rings": "Leek (rings)",
|
||||
"lemon_meringue_pie": "Lemon meringue pie",
|
||||
"long_coffee": "Long coffee",
|
||||
"long_grain_rice_general_rapid_steam_cooking": "Long grain rice (general, rapid steam cooking)",
|
||||
"long_grain_rice_general_steam_cooking": "Long grain rice (general, steam cooking)",
|
||||
"low_temperature_cooking": "Low temperature cooking",
|
||||
"maintenance": "Maintenance program",
|
||||
"make_yoghurt": "Make yoghurt",
|
||||
"mangel_cut": "Mangel (cut)",
|
||||
@@ -670,6 +673,7 @@
|
||||
"prove_dough": "Prove dough",
|
||||
"pumpkin_diced": "Pumpkin (diced)",
|
||||
"pumpkin_soup": "Pumpkin soup",
|
||||
"pyrolytic": "Pyrolytic",
|
||||
"quick_mw": "Quick MW",
|
||||
"quick_power_wash": "QuickPowerWash",
|
||||
"quinces_diced": "Quinces (diced)",
|
||||
@@ -722,6 +726,7 @@
|
||||
"sea_devil_fillet_3_cm": "Sea devil (fillet, 3 cm)",
|
||||
"sea_devil_fillet_4_cm": "Sea devil (fillet, 4 cm)",
|
||||
"separate_rinse_starch": "Separate rinse/starch",
|
||||
"shabbat_program": "Shabbat program",
|
||||
"sheyang_rapid_steam_cooking": "Sheyang (rapid steam cooking)",
|
||||
"sheyang_steam_cooking": "Sheyang (steam cooking)",
|
||||
"shirts": "Shirts",
|
||||
@@ -752,6 +757,7 @@
|
||||
"steam_care": "Steam care",
|
||||
"steam_cooking": "Steam cooking",
|
||||
"steam_smoothing": "Steam smoothing",
|
||||
"sterilize_crockery": "Sterilize crockery",
|
||||
"stuffed_cabbage": "Stuffed cabbage",
|
||||
"sweat_onions": "Sweat onions",
|
||||
"swede_cut_into_batons": "Swede (cut into batons)",
|
||||
@@ -790,6 +796,7 @@
|
||||
"veal_sausages": "Veal sausages",
|
||||
"venus_clams": "Venus clams",
|
||||
"very_hot_water": "Very hot water",
|
||||
"viennese_apple_strudel": "Viennese apple strudel",
|
||||
"viennese_silverside": "Viennese silverside",
|
||||
"warm_air": "Warm air",
|
||||
"wheat_cracked": "Wheat (cracked)",
|
||||
@@ -814,6 +821,7 @@
|
||||
"yellow_beans_cut": "Yellow beans (cut)",
|
||||
"yellow_beans_whole": "Yellow beans (whole)",
|
||||
"yellow_split_peas": "Yellow split peas",
|
||||
"yom_tov": "Yom tov",
|
||||
"zander_fillet": "Zander (fillet)"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -465,7 +465,7 @@ class PlatformField:
|
||||
required: bool
|
||||
validator: Callable[..., Any]
|
||||
error: str | None = None
|
||||
default: str | int | bool | None | vol.Undefined = vol.UNDEFINED
|
||||
default: str | int | bool | vol.Undefined = vol.UNDEFINED
|
||||
is_schema_default: bool = False
|
||||
exclude_from_reconfig: bool = False
|
||||
conditions: tuple[dict[str, Any], ...] | None = None
|
||||
@@ -498,7 +498,8 @@ def validate_light_platform_config(user_data: dict[str, Any]) -> dict[str, str]:
|
||||
if user_data.get(CONF_MIN_KELVIN, DEFAULT_MIN_KELVIN) >= user_data.get(
|
||||
CONF_MAX_KELVIN, DEFAULT_MAX_KELVIN
|
||||
):
|
||||
errors["advanced_settings"] = "max_below_min_kelvin"
|
||||
errors[CONF_MAX_KELVIN] = "max_below_min_kelvin"
|
||||
errors[CONF_MIN_KELVIN] = "max_below_min_kelvin"
|
||||
return errors
|
||||
|
||||
|
||||
@@ -514,7 +515,6 @@ COMMON_ENTITY_FIELDS = {
|
||||
required=False,
|
||||
validator=str,
|
||||
exclude_from_reconfig=True,
|
||||
default=None,
|
||||
),
|
||||
CONF_ENTITY_PICTURE: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, validator=cv.url, error="invalid_url"
|
||||
@@ -1150,7 +1150,7 @@ ENTITY_CONFIG_VALIDATOR: dict[
|
||||
}
|
||||
|
||||
MQTT_DEVICE_PLATFORM_FIELDS = {
|
||||
ATTR_NAME: PlatformField(selector=TEXT_SELECTOR, required=True, validator=str),
|
||||
ATTR_NAME: PlatformField(selector=TEXT_SELECTOR, required=False, validator=str),
|
||||
ATTR_SW_VERSION: PlatformField(
|
||||
selector=TEXT_SELECTOR, required=False, validator=str
|
||||
),
|
||||
@@ -1275,10 +1275,7 @@ def validate_user_input(
|
||||
try:
|
||||
validator(value)
|
||||
except (ValueError, vol.Error, vol.Invalid):
|
||||
data_schema_field = data_schema_fields[field]
|
||||
errors[data_schema_field.section or field] = (
|
||||
data_schema_field.error or "invalid_input"
|
||||
)
|
||||
errors[field] = data_schema_fields[field].error or "invalid_input"
|
||||
|
||||
if config_validator is not None:
|
||||
if TYPE_CHECKING:
|
||||
@@ -1327,10 +1324,7 @@ def data_schema_from_fields(
|
||||
vol.Required(field_name, default=field_details.default)
|
||||
if field_details.required
|
||||
else vol.Optional(
|
||||
field_name,
|
||||
default=field_details.default
|
||||
if field_details.default is not None
|
||||
else vol.UNDEFINED,
|
||||
field_name, default=field_details.default
|
||||
): field_details.selector(component_data_with_user_input) # type: ignore[operator]
|
||||
if field_details.custom_filtering
|
||||
else field_details.selector
|
||||
@@ -1381,17 +1375,12 @@ def data_schema_from_fields(
|
||||
@callback
|
||||
def subentry_schema_default_data_from_fields(
|
||||
data_schema_fields: dict[str, PlatformField],
|
||||
component_data: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Generate custom data schema from platform fields or device data."""
|
||||
return {
|
||||
key: field.default
|
||||
for key, field in data_schema_fields.items()
|
||||
if _check_conditions(field, component_data)
|
||||
and (
|
||||
field.is_schema_default
|
||||
or (field.default is not vol.UNDEFINED and key not in component_data)
|
||||
)
|
||||
if field.is_schema_default
|
||||
}
|
||||
|
||||
|
||||
@@ -2217,10 +2206,7 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
|
||||
for component_data in self._subentry_data["components"].values():
|
||||
platform = component_data[CONF_PLATFORM]
|
||||
subentry_default_data = subentry_schema_default_data_from_fields(
|
||||
COMMON_ENTITY_FIELDS
|
||||
| PLATFORM_ENTITY_FIELDS[platform]
|
||||
| PLATFORM_MQTT_FIELDS[platform],
|
||||
component_data,
|
||||
PLATFORM_ENTITY_FIELDS[platform]
|
||||
)
|
||||
component_data.update(subentry_default_data)
|
||||
|
||||
|
||||
@@ -244,7 +244,6 @@
|
||||
"title": "Configure MQTT device \"{mqtt_device}\"",
|
||||
"description": "Please configure MQTT specific details for {platform} entity \"{entity}\":",
|
||||
"data": {
|
||||
"on_command_type": "ON command type",
|
||||
"blue_template": "Blue template",
|
||||
"brightness_template": "Brightness template",
|
||||
"command_template": "Command template",
|
||||
@@ -255,6 +254,7 @@
|
||||
"force_update": "Force update",
|
||||
"green_template": "Green template",
|
||||
"last_reset_value_template": "Last reset value template",
|
||||
"on_command_type": "ON command type",
|
||||
"optimistic": "Optimistic",
|
||||
"payload_off": "Payload \"off\"",
|
||||
"payload_on": "Payload \"on\"",
|
||||
@@ -275,19 +275,19 @@
|
||||
"command_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-command-templates-with-mqtt) to render the payload to be published at the command topic.",
|
||||
"command_topic": "The publishing topic that will be used to control the {platform} entity. [Learn more.]({url}#command_topic)",
|
||||
"color_temp_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract color temperature in Kelvin from the state payload value. Expected result of the template is an integer.",
|
||||
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
|
||||
"green_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract green color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"last_reset_value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the last reset. When Last reset template is set, the State class option must be Total. [Learn more.]({url}#last_reset_value_template)",
|
||||
"force_update": "Sends update events even if the value hasn’t changed. Useful if you want to have meaningful value graphs in history. [Learn more.]({url}#force_update)",
|
||||
"on_command_type": "Defines when the payload \"on\" is sent. Using \"Last\" (the default) will send any style (brightness, color, etc) topics first and then a payload \"on\" to the command topic. Using \"First\" will send the payload \"on\" and then any style topics. Using \"Brightness\" will only send brightness commands instead of the payload \"on\" to turn the light on.",
|
||||
"optimistic": "Flag that defines if the {platform} entity works in optimistic mode. [Learn more.]({url}#optimistic)",
|
||||
"payload_off": "The payload that represents the off state.",
|
||||
"payload_on": "The payload that represents the on state.",
|
||||
"payload_off": "The payload that represents the \"off\" state.",
|
||||
"payload_on": "The payload that represents the \"on\" state.",
|
||||
"qos": "The QoS value a {platform} entity should use.",
|
||||
"red_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract red color from the state payload value. Expected result of the template is an integer from 0-255 range.",
|
||||
"retain": "Select if values published by the {platform} entity should be retained at the MQTT broker.",
|
||||
"state_template": "[Template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract state from the state payload value.",
|
||||
"state_topic": "The MQTT topic subscribed to receive {platform} state values. [Learn more.]({url}#state_topic)",
|
||||
"supported_color_modes": "A list of color modes supported by the list. Possible color modes are On/Off, Brightness, Color temperature, HS, XY, RGB, RGBW, RGBWW, White. Note that if On/Off or Brightness are used, that must be the only value in the list. [Learn more.]({url}#supported_color_modes)",
|
||||
"supported_color_modes": "A list of color modes supported by the light. Possible color modes are On/Off, Brightness, Color temperature, HS, XY, RGB, RGBW, RGBWW, White. Note that if On/Off or Brightness are used, that must be the only value in the list. [Learn more.]({url}#supported_color_modes)",
|
||||
"value_template": "Defines a [template](https://www.home-assistant.io/docs/configuration/templating/#using-value-templates-with-mqtt) to extract the {platform} entity value. [Learn more.]({url}#value_template)"
|
||||
},
|
||||
"sections": {
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/nexia",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["nexia"],
|
||||
"requirements": ["nexia==2.7.0"]
|
||||
"requirements": ["nexia==2.9.0"]
|
||||
}
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
"user": {
|
||||
"description": "Configure hosts to be scanned by Nmap. Network address and excludes can be IP addresses (192.168.1.1), IP networks (192.168.0.0/24) or IP ranges (192.168.1.0-32).",
|
||||
"data": {
|
||||
"hosts": "Network addresses (comma separated) to scan",
|
||||
"hosts": "Network addresses (comma-separated) to scan",
|
||||
"home_interval": "Minimum number of minutes between scans of active devices (preserve battery)",
|
||||
"exclude": "Network addresses (comma separated) to exclude from scanning",
|
||||
"exclude": "Network addresses (comma-separated) to exclude from scanning",
|
||||
"scan_options": "Raw configurable scan options for Nmap"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"user": {
|
||||
"data": {
|
||||
"county": "County",
|
||||
"phone_number": "Phone Number"
|
||||
"phone_number": "Phone number"
|
||||
},
|
||||
"data_description": {
|
||||
"county": "County used for outage number retrieval",
|
||||
|
||||
@@ -29,8 +29,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: RehlkoConfigEntry) -> bo
|
||||
"""Set up Rehlko from a config entry."""
|
||||
websession = async_get_clientsession(hass)
|
||||
rehlko = AioKem(session=websession)
|
||||
# If requests take more than 20 seconds; timeout and let the setup retry.
|
||||
rehlko.set_timeout(20)
|
||||
|
||||
async def async_refresh_token_update(refresh_token: str) -> None:
|
||||
"""Handle refresh token update."""
|
||||
@@ -89,8 +87,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: RehlkoConfigEntry) -> bo
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
# Retrys enabled after successful connection to prevent blocking startup
|
||||
rehlko.set_retry_policy(retry_count=3, retry_delays=[5, 10, 20])
|
||||
# Rehlko service can be slow to respond, increase timeout for polls.
|
||||
rehlko.set_timeout(100)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["renault_api"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["renault-api==0.3.1"]
|
||||
"requirements": ["renault-api==0.3.0"]
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Sign-in with Ring account",
|
||||
"title": "Sign in with Ring account",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
|
||||
@@ -53,7 +53,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) ->
|
||||
)
|
||||
_LOGGER.debug("Getting home data")
|
||||
try:
|
||||
home_data = await api_client.get_home_data_v2(user_data)
|
||||
home_data = await api_client.get_home_data_v3(user_data)
|
||||
except RoborockInvalidCredentials as err:
|
||||
raise ConfigEntryAuthFailed(
|
||||
"Invalid credentials",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""The AWS S3 integration."""
|
||||
"""The S3 integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import cast
|
||||
|
||||
from aiobotocore.client import AioBaseClient as S3Client
|
||||
from aiobotocore.session import AioSession
|
||||
from botocore.config import Config
|
||||
from botocore.exceptions import ClientError, ConnectionError, ParamValidationError
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
@@ -32,6 +33,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: S3ConfigEntry) -> bool:
|
||||
"""Set up S3 from a config entry."""
|
||||
|
||||
data = cast(dict, entry.data)
|
||||
# due to https://github.com/home-assistant/core/issues/143995
|
||||
config = Config(
|
||||
request_checksum_calculation="when_required",
|
||||
response_checksum_validation="when_required",
|
||||
)
|
||||
try:
|
||||
session = AioSession()
|
||||
# pylint: disable-next=unnecessary-dunder-call
|
||||
@@ -40,6 +46,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: S3ConfigEntry) -> bool:
|
||||
endpoint_url=data.get(CONF_ENDPOINT_URL),
|
||||
aws_secret_access_key=data[CONF_SECRET_ACCESS_KEY],
|
||||
aws_access_key_id=data[CONF_ACCESS_KEY_ID],
|
||||
config=config,
|
||||
).__aenter__()
|
||||
await client.head_bucket(Bucket=data[CONF_BUCKET])
|
||||
except ClientError as err:
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Backup platform for the AWS S3 integration."""
|
||||
"""Backup platform for the S3 integration."""
|
||||
|
||||
from collections.abc import AsyncIterator, Callable, Coroutine
|
||||
import functools
|
||||
@@ -1,9 +1,8 @@
|
||||
"""Config flow for the AWS S3 integration."""
|
||||
"""Config flow for the 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
|
||||
@@ -18,7 +17,6 @@ from homeassistant.helpers.selector import (
|
||||
)
|
||||
|
||||
from .const import (
|
||||
AWS_DOMAIN,
|
||||
CONF_ACCESS_KEY_ID,
|
||||
CONF_BUCKET,
|
||||
CONF_ENDPOINT_URL,
|
||||
@@ -59,34 +57,28 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
CONF_ENDPOINT_URL: user_input[CONF_ENDPOINT_URL],
|
||||
}
|
||||
)
|
||||
|
||||
if not urlparse(user_input[CONF_ENDPOINT_URL]).hostname.endswith(
|
||||
AWS_DOMAIN
|
||||
):
|
||||
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:
|
||||
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_create_entry(
|
||||
title=user_input[CONF_BUCKET], data=user_input
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
@@ -1,19 +1,18 @@
|
||||
"""Constants for the AWS S3 integration."""
|
||||
"""Constants for the S3 integration."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Final
|
||||
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
DOMAIN: Final = "aws_s3"
|
||||
DOMAIN: Final = "s3"
|
||||
|
||||
CONF_ACCESS_KEY_ID = "access_key_id"
|
||||
CONF_SECRET_ACCESS_KEY = "secret_access_key"
|
||||
CONF_ENDPOINT_URL = "endpoint_url"
|
||||
CONF_BUCKET = "bucket"
|
||||
|
||||
AWS_DOMAIN = "amazonaws.com"
|
||||
DEFAULT_ENDPOINT_URL = f"https://s3.eu-central-1.{AWS_DOMAIN}/"
|
||||
DEFAULT_ENDPOINT_URL = "https://s3.eu-central-1.amazonaws.com/"
|
||||
|
||||
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
|
||||
f"{DOMAIN}.backup_agent_listeners"
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"domain": "aws_s3",
|
||||
"name": "AWS S3",
|
||||
"domain": "s3",
|
||||
"name": "S3",
|
||||
"codeowners": ["@tomasbedrich"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/aws_s3",
|
||||
"documentation": "https://www.home-assistant.io/integrations/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 AWS S3 API",
|
||||
"secret_access_key": "Secret access key to connect to AWS S3 API",
|
||||
"access_key_id": "Access key ID to connect to S3 API",
|
||||
"secret_access_key": "Secret access key to connect to 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 AWS S3 bucket"
|
||||
"title": "Add S3 bucket"
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"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."
|
||||
"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"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
||||
@@ -46,6 +46,7 @@ from homeassistant.const import (
|
||||
CONF_TOKEN,
|
||||
)
|
||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_component
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
@@ -53,6 +54,7 @@ from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
CONF_SESSION_ID,
|
||||
DOMAIN,
|
||||
ENCRYPTED_WEBSOCKET_PORT,
|
||||
LEGACY_PORT,
|
||||
LOGGER,
|
||||
@@ -371,9 +373,13 @@ class SamsungTVLegacyBridge(SamsungTVBridge):
|
||||
except (ConnectionClosed, BrokenPipeError):
|
||||
# BrokenPipe can occur when the commands is sent to fast
|
||||
self._remote = None
|
||||
except (UnhandledResponse, AccessDenied):
|
||||
except (UnhandledResponse, AccessDenied) as err:
|
||||
# We got a response so it's on.
|
||||
LOGGER.debug("Failed sending command %s", key, exc_info=True)
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_sending_command",
|
||||
translation_placeholders={"error": repr(err), "host": self.host},
|
||||
) from err
|
||||
except OSError:
|
||||
# Different reasons, e.g. hostname not resolveable
|
||||
pass
|
||||
|
||||
@@ -29,13 +29,14 @@ from homeassistant.components.media_player import (
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.async_ import create_eager_task
|
||||
|
||||
from .bridge import SamsungTVWSBridge
|
||||
from .const import CONF_SSDP_RENDERING_CONTROL_LOCATION, LOGGER
|
||||
from .const import CONF_SSDP_RENDERING_CONTROL_LOCATION, DOMAIN, LOGGER
|
||||
from .coordinator import SamsungTVConfigEntry, SamsungTVDataUpdateCoordinator
|
||||
from .entity import SamsungTVEntity
|
||||
|
||||
@@ -308,7 +309,12 @@ class SamsungTVDevice(SamsungTVEntity, MediaPlayerEntity):
|
||||
try:
|
||||
await dmr_device.async_set_volume_level(volume)
|
||||
except UpnpActionResponseError as err:
|
||||
LOGGER.warning("Unable to set volume level on %s: %r", self._host, err)
|
||||
assert self._host
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="error_set_volume",
|
||||
translation_placeholders={"error": repr(err), "host": self._host},
|
||||
) from err
|
||||
|
||||
async def async_volume_up(self) -> None:
|
||||
"""Volume up the media player."""
|
||||
|
||||
@@ -68,6 +68,12 @@
|
||||
"service_unsupported": {
|
||||
"message": "Entity {entity} does not support this action."
|
||||
},
|
||||
"error_set_volume": {
|
||||
"message": "Unable to set volume level on {host}: {error}"
|
||||
},
|
||||
"error_sending_command": {
|
||||
"message": "Unable to send command to {host}: {error}"
|
||||
},
|
||||
"encrypted_mode_auth_failed": {
|
||||
"message": "Token and session ID are required in encrypted mode."
|
||||
},
|
||||
|
||||
@@ -42,6 +42,8 @@ from .utils import (
|
||||
is_rpc_momentary_input,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class BlockBinarySensorDescription(
|
||||
|
||||
@@ -32,6 +32,8 @@ from .const import DOMAIN, LOGGER, SHELLY_GAS_MODELS
|
||||
from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoordinator
|
||||
from .utils import get_device_entry_gen, get_rpc_key_ids
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ShellyButtonDescription[
|
||||
|
||||
@@ -51,6 +51,8 @@ from .utils import (
|
||||
is_rpc_thermostat_internal_actuator,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -21,6 +21,8 @@ from .coordinator import ShellyBlockCoordinator, ShellyConfigEntry, ShellyRpcCoo
|
||||
from .entity import ShellyBlockEntity, ShellyRpcEntity
|
||||
from .utils import get_device_entry_gen, get_rpc_key_ids
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -38,6 +38,8 @@ from .utils import (
|
||||
is_rpc_momentary_input,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class ShellyBlockEventDescription(EventEntityDescription):
|
||||
|
||||
@@ -49,6 +49,8 @@ from .utils import (
|
||||
percentage_to_brightness,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -42,6 +42,8 @@ from .utils import (
|
||||
get_virtual_component_ids,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class BlockNumberDescription(BlockEntityDescription, NumberEntityDescription):
|
||||
|
||||
@@ -33,7 +33,7 @@ rules:
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
parallel-updates: todo
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: done
|
||||
|
||||
@@ -42,7 +42,7 @@ rules:
|
||||
diagnostics: done
|
||||
discovery-update-info: done
|
||||
discovery: done
|
||||
docs-data-update: todo
|
||||
docs-data-update: done
|
||||
docs-examples: done
|
||||
docs-known-limitations: done
|
||||
docs-supported-devices: done
|
||||
@@ -56,8 +56,8 @@ rules:
|
||||
entity-device-class: done
|
||||
entity-disabled-by-default: done
|
||||
entity-translations: todo
|
||||
exception-translations: todo
|
||||
icon-translations: todo
|
||||
exception-translations: done
|
||||
icon-translations: done
|
||||
reconfiguration-flow: done
|
||||
repair-issues: done
|
||||
stale-devices:
|
||||
|
||||
@@ -28,6 +28,8 @@ from .utils import (
|
||||
get_virtual_component_ids,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RpcSelectDescription(RpcEntityDescription, SelectEntityDescription):
|
||||
|
||||
@@ -63,6 +63,8 @@ from .utils import (
|
||||
is_rpc_wifi_stations_disabled,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class BlockSensorDescription(BlockEntityDescription, SensorEntityDescription):
|
||||
|
||||
@@ -39,6 +39,8 @@ from .utils import (
|
||||
is_rpc_exclude_from_relay,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class BlockSwitchDescription(BlockEntityDescription, SwitchEntityDescription):
|
||||
|
||||
@@ -28,6 +28,8 @@ from .utils import (
|
||||
get_virtual_component_ids,
|
||||
)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RpcTextDescription(RpcEntityDescription, TextEntityDescription):
|
||||
|
||||
@@ -47,6 +47,8 @@ from .utils import get_device_entry_gen, get_release_url
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class RpcUpdateDescription(RpcEntityDescription, UpdateEntityDescription):
|
||||
|
||||
@@ -25,6 +25,8 @@ from .entity import (
|
||||
)
|
||||
from .utils import async_remove_shelly_entity, get_device_entry_gen
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BlockValveDescription(BlockEntityDescription, ValveEntityDescription):
|
||||
|
||||
@@ -30,5 +30,5 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pysmartthings"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pysmartthings==3.2.1"]
|
||||
"requirements": ["pysmartthings==3.2.0"]
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ class SmartThingsSelectDescription(SelectEntityDescription):
|
||||
options_attribute: Attribute
|
||||
status_attribute: Attribute
|
||||
command: Command
|
||||
default_options: list[str] | None = None
|
||||
|
||||
|
||||
CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
@@ -47,7 +46,6 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
|
||||
status_attribute=Attribute.MACHINE_STATE,
|
||||
command=Command.SET_MACHINE_STATE,
|
||||
default_options=["run", "pause", "stop"],
|
||||
),
|
||||
Capability.WASHER_OPERATING_STATE: SmartThingsSelectDescription(
|
||||
key=Capability.WASHER_OPERATING_STATE,
|
||||
@@ -57,7 +55,6 @@ CAPABILITIES_TO_SELECT: dict[Capability | str, SmartThingsSelectDescription] = {
|
||||
options_attribute=Attribute.SUPPORTED_MACHINE_STATES,
|
||||
status_attribute=Attribute.MACHINE_STATE,
|
||||
command=Command.SET_MACHINE_STATE,
|
||||
default_options=["run", "pause", "stop"],
|
||||
),
|
||||
Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT: SmartThingsSelectDescription(
|
||||
key=Capability.SAMSUNG_CE_AUTO_DISPENSE_DETERGENT,
|
||||
@@ -117,12 +114,8 @@ class SmartThingsSelectEntity(SmartThingsEntity, SelectEntity):
|
||||
@property
|
||||
def options(self) -> list[str]:
|
||||
"""Return the list of options."""
|
||||
return (
|
||||
self.get_attribute_value(
|
||||
self.entity_description.key, self.entity_description.options_attribute
|
||||
)
|
||||
or self.entity_description.default_options
|
||||
or []
|
||||
return self.get_attribute_value(
|
||||
self.entity_description.key, self.entity_description.options_attribute
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
@@ -631,7 +631,7 @@ CAPABILITY_TO_SENSORS: dict[
|
||||
SmartThingsSensorEntityDescription(
|
||||
key="powerEnergy_meter",
|
||||
translation_key="power_energy",
|
||||
state_class=SensorStateClass.TOTAL,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
device_class=SensorDeviceClass.ENERGY,
|
||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||
value_fn=lambda value: value["powerEnergy"] / 1000,
|
||||
|
||||
@@ -29,17 +29,17 @@
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "SMS Verification",
|
||||
"title": "SMS verification",
|
||||
"description": "Enter your phone number (same as what you used to register to the tami4 app)",
|
||||
"data": {
|
||||
"phone": "Phone Number"
|
||||
"phone": "Phone number"
|
||||
}
|
||||
},
|
||||
"otp": {
|
||||
"title": "[%key:component::tami4::config::step::user::title%]",
|
||||
"description": "Enter the code you received via SMS",
|
||||
"data": {
|
||||
"otp": "SMS Code"
|
||||
"otp": "SMS code"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user