Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 554c6e0cca | |||
| 7bac640267 | |||
| 26796f87cd | |||
| e2dd897ac7 | |||
| 3bbe4baaf7 | |||
| d409b86217 | |||
| 7928c15849 | |||
| d197debbc0 | |||
| 55b9dee448 | |||
| 5c6984d326 | |||
| a7787d6080 | |||
| 2db60340c2 | |||
| c121631fef | |||
| b0fb16d48d | |||
| 3e07f6543e | |||
| d4c2356c70 | |||
| eec617b391 | |||
| b15c9ad130 | |||
| 0128d85999 | |||
| e69ca0cf80 | |||
| 0719753be3 | |||
| ba3181d4e7 | |||
| e58750555e | |||
| 026687299d | |||
| 3eed552c56 | |||
| 15a4514c7d | |||
| b5445c0061 | |||
| 1d0584a90d | |||
| 158b795c70 | |||
| 4994229215 | |||
| c022c32d2f | |||
| d2ef3ca100 | |||
| 00faadcfea | |||
| a6ff52b300 | |||
| da0d65ca5b | |||
| 2266e97417 | |||
| d471de5645 | |||
| 38674f0dc2 | |||
| b192ca4bad | |||
| 73a59523f5 | |||
| 05324dedd0 | |||
| f1e5f73d7e | |||
| 7b23f21712 | |||
| 4dde314338 | |||
| cba12fb598 | |||
| 63e38b4d8d | |||
| 7eded95315 | |||
| e493fe1105 | |||
| 646c230940 | |||
| 5276a3688e | |||
| 0616bf16f4 | |||
| fbe1811e2b | |||
| 2333c10915 |
Generated
+2
-2
@@ -1498,8 +1498,8 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/switch_as_x/ @home-assistant/core
|
||||
/homeassistant/components/switchbee/ @jafar-atili
|
||||
/tests/components/switchbee/ @jafar-atili
|
||||
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski
|
||||
/homeassistant/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski @zerzhang
|
||||
/tests/components/switchbot/ @danielhiversen @RenierM26 @murtas @Eloston @dsypniewski @zerzhang
|
||||
/homeassistant/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur
|
||||
/tests/components/switchbot_cloud/ @SeraphicRav @laurence-presland @Gigatrappeur
|
||||
/homeassistant/components/switcher_kis/ @thecode @YogevBokobza
|
||||
|
||||
@@ -39,11 +39,20 @@ async def async_setup_entry(
|
||||
session = async_create_clientsession(
|
||||
hass, timeout=ClientTimeout(connect=10, total=12 * 60 * 60)
|
||||
)
|
||||
container_client = ContainerClient(
|
||||
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
|
||||
container_name=entry.data[CONF_CONTAINER_NAME],
|
||||
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
|
||||
transport=AioHttpTransport(session=session),
|
||||
|
||||
def create_container_client() -> ContainerClient:
|
||||
"""Create a ContainerClient."""
|
||||
|
||||
return ContainerClient(
|
||||
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
|
||||
container_name=entry.data[CONF_CONTAINER_NAME],
|
||||
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
|
||||
transport=AioHttpTransport(session=session),
|
||||
)
|
||||
|
||||
# has a blocking call to open in cpython
|
||||
container_client: ContainerClient = await hass.async_add_executor_job(
|
||||
create_container_client
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -27,9 +27,25 @@ _LOGGER = logging.getLogger(__name__)
|
||||
class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for azure storage."""
|
||||
|
||||
def get_account_url(self, account_name: str) -> str:
|
||||
"""Get the account URL."""
|
||||
return f"https://{account_name}.blob.core.windows.net/"
|
||||
async def get_container_client(
|
||||
self, account_name: str, container_name: str, storage_account_key: str
|
||||
) -> ContainerClient:
|
||||
"""Get the container client.
|
||||
|
||||
ContainerClient has a blocking call to open in cpython
|
||||
"""
|
||||
|
||||
session = async_get_clientsession(self.hass)
|
||||
|
||||
def create_container_client() -> ContainerClient:
|
||||
return ContainerClient(
|
||||
account_url=f"https://{account_name}.blob.core.windows.net/",
|
||||
container_name=container_name,
|
||||
credential=storage_account_key,
|
||||
transport=AioHttpTransport(session=session),
|
||||
)
|
||||
|
||||
return await self.hass.async_add_executor_job(create_container_client)
|
||||
|
||||
async def validate_config(
|
||||
self, container_client: ContainerClient
|
||||
@@ -58,11 +74,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._async_abort_entries_match(
|
||||
{CONF_ACCOUNT_NAME: user_input[CONF_ACCOUNT_NAME]}
|
||||
)
|
||||
container_client = ContainerClient(
|
||||
account_url=self.get_account_url(user_input[CONF_ACCOUNT_NAME]),
|
||||
container_client = await self.get_container_client(
|
||||
account_name=user_input[CONF_ACCOUNT_NAME],
|
||||
container_name=user_input[CONF_CONTAINER_NAME],
|
||||
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
|
||||
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
)
|
||||
errors = await self.validate_config(container_client)
|
||||
|
||||
@@ -99,12 +114,12 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
reauth_entry = self._get_reauth_entry()
|
||||
|
||||
if user_input is not None:
|
||||
container_client = ContainerClient(
|
||||
account_url=self.get_account_url(reauth_entry.data[CONF_ACCOUNT_NAME]),
|
||||
container_client = await self.get_container_client(
|
||||
account_name=reauth_entry.data[CONF_ACCOUNT_NAME],
|
||||
container_name=reauth_entry.data[CONF_CONTAINER_NAME],
|
||||
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
|
||||
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
)
|
||||
|
||||
errors = await self.validate_config(container_client)
|
||||
if not errors:
|
||||
return self.async_update_reload_and_abort(
|
||||
@@ -129,13 +144,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
reconfigure_entry = self._get_reconfigure_entry()
|
||||
|
||||
if user_input is not None:
|
||||
container_client = ContainerClient(
|
||||
account_url=self.get_account_url(
|
||||
reconfigure_entry.data[CONF_ACCOUNT_NAME]
|
||||
),
|
||||
container_client = await self.get_container_client(
|
||||
account_name=reconfigure_entry.data[CONF_ACCOUNT_NAME],
|
||||
container_name=user_input[CONF_CONTAINER_NAME],
|
||||
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
|
||||
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
|
||||
)
|
||||
errors = await self.validate_config(container_client)
|
||||
if not errors:
|
||||
|
||||
@@ -55,7 +55,6 @@ from homeassistant.helpers.deprecation import (
|
||||
DeprecatedConstantEnum,
|
||||
all_with_deprecated_constants,
|
||||
check_if_deprecated_constant,
|
||||
deprecated_function,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
from homeassistant.helpers.entity import Entity, EntityDescription
|
||||
@@ -86,10 +85,10 @@ from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
|
||||
from .webrtc import (
|
||||
DATA_ICE_SERVERS,
|
||||
CameraWebRTCProvider,
|
||||
WebRTCAnswer,
|
||||
WebRTCAnswer, # noqa: F401
|
||||
WebRTCCandidate, # noqa: F401
|
||||
WebRTCClientConfiguration,
|
||||
WebRTCError,
|
||||
WebRTCError, # noqa: F401
|
||||
WebRTCMessage, # noqa: F401
|
||||
WebRTCSendMessage,
|
||||
async_get_supported_provider,
|
||||
@@ -473,9 +472,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
self.async_update_token()
|
||||
self._create_stream_lock: asyncio.Lock | None = None
|
||||
self._webrtc_provider: CameraWebRTCProvider | None = None
|
||||
self._supports_native_sync_webrtc = (
|
||||
type(self).async_handle_web_rtc_offer != Camera.async_handle_web_rtc_offer
|
||||
)
|
||||
self._supports_native_async_webrtc = (
|
||||
type(self).async_handle_async_webrtc_offer
|
||||
!= Camera.async_handle_async_webrtc_offer
|
||||
@@ -579,15 +575,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""
|
||||
return None
|
||||
|
||||
async def async_handle_web_rtc_offer(self, offer_sdp: str) -> str | None:
|
||||
"""Handle the WebRTC offer and return an answer.
|
||||
|
||||
This is used by cameras with CameraEntityFeature.STREAM
|
||||
and StreamType.WEB_RTC.
|
||||
|
||||
Integrations can override with a native WebRTC implementation.
|
||||
"""
|
||||
|
||||
async def async_handle_async_webrtc_offer(
|
||||
self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
|
||||
) -> None:
|
||||
@@ -600,42 +587,6 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
Integrations can override with a native WebRTC implementation.
|
||||
"""
|
||||
if self._supports_native_sync_webrtc:
|
||||
try:
|
||||
answer = await deprecated_function(
|
||||
"async_handle_async_webrtc_offer",
|
||||
breaks_in_ha_version="2025.6",
|
||||
)(self.async_handle_web_rtc_offer)(offer_sdp)
|
||||
except ValueError as ex:
|
||||
_LOGGER.error("Error handling WebRTC offer: %s", ex)
|
||||
send_message(
|
||||
WebRTCError(
|
||||
"webrtc_offer_failed",
|
||||
str(ex),
|
||||
)
|
||||
)
|
||||
except TimeoutError:
|
||||
# This catch was already here and should stay through the deprecation
|
||||
_LOGGER.error("Timeout handling WebRTC offer")
|
||||
send_message(
|
||||
WebRTCError(
|
||||
"webrtc_offer_failed",
|
||||
"Timeout handling WebRTC offer",
|
||||
)
|
||||
)
|
||||
else:
|
||||
if answer:
|
||||
send_message(WebRTCAnswer(answer))
|
||||
else:
|
||||
_LOGGER.error("Error handling WebRTC offer: No answer")
|
||||
send_message(
|
||||
WebRTCError(
|
||||
"webrtc_offer_failed",
|
||||
"No answer on WebRTC offer",
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if self._webrtc_provider:
|
||||
await self._webrtc_provider.async_handle_async_webrtc_offer(
|
||||
self, offer_sdp, session_id, send_message
|
||||
@@ -764,9 +715,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
new_provider = None
|
||||
|
||||
# Skip all providers if the camera has a native WebRTC implementation
|
||||
if not (
|
||||
self._supports_native_sync_webrtc or self._supports_native_async_webrtc
|
||||
):
|
||||
if not self._supports_native_async_webrtc:
|
||||
# Camera doesn't have a native WebRTC implementation
|
||||
new_provider = await self._async_get_supported_webrtc_provider(
|
||||
async_get_supported_provider
|
||||
@@ -798,17 +747,12 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the WebRTC client configuration and extend it with the registered ice servers."""
|
||||
config = self._async_get_webrtc_client_configuration()
|
||||
|
||||
if not self._supports_native_sync_webrtc:
|
||||
# Until 2024.11, the frontend was not resolving any ice servers
|
||||
# The async approach was added 2024.11 and new integrations need to use it
|
||||
ice_servers = [
|
||||
server
|
||||
for servers in self.hass.data.get(DATA_ICE_SERVERS, [])
|
||||
for server in servers()
|
||||
]
|
||||
config.configuration.ice_servers.extend(ice_servers)
|
||||
|
||||
config.get_candidates_upfront = self._supports_native_sync_webrtc
|
||||
ice_servers = [
|
||||
server
|
||||
for servers in self.hass.data.get(DATA_ICE_SERVERS, [])
|
||||
for server in servers()
|
||||
]
|
||||
config.configuration.ice_servers.extend(ice_servers)
|
||||
|
||||
return config
|
||||
|
||||
@@ -838,7 +782,7 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Return the camera capabilities."""
|
||||
frontend_stream_types = set()
|
||||
if CameraEntityFeature.STREAM in self.supported_features_compat:
|
||||
if self._supports_native_sync_webrtc or self._supports_native_async_webrtc:
|
||||
if self._supports_native_async_webrtc:
|
||||
# The camera has a native WebRTC implementation
|
||||
frontend_stream_types.add(StreamType.WEB_RTC)
|
||||
else:
|
||||
|
||||
@@ -111,13 +111,11 @@ class WebRTCClientConfiguration:
|
||||
|
||||
configuration: RTCConfiguration = field(default_factory=RTCConfiguration)
|
||||
data_channel: str | None = None
|
||||
get_candidates_upfront: bool = False
|
||||
|
||||
def to_frontend_dict(self) -> dict[str, Any]:
|
||||
"""Return a dict that can be used by the frontend."""
|
||||
data: dict[str, Any] = {
|
||||
"configuration": self.configuration.to_dict(),
|
||||
"getCandidatesUpfront": self.get_candidates_upfront,
|
||||
}
|
||||
if self.data_channel is not None:
|
||||
data["dataChannel"] = self.data_channel
|
||||
|
||||
@@ -43,6 +43,7 @@ VALID_REPAIR_TRANSLATION_KEYS = {
|
||||
"no_subscription",
|
||||
"warn_bad_custom_domain_configuration",
|
||||
"reset_bad_custom_domain_configuration",
|
||||
"subscription_expired",
|
||||
}
|
||||
|
||||
|
||||
@@ -404,7 +405,12 @@ class CloudClient(Interface):
|
||||
) -> None:
|
||||
"""Create a repair issue."""
|
||||
if translation_key not in VALID_REPAIR_TRANSLATION_KEYS:
|
||||
raise ValueError(f"Invalid translation key {translation_key}")
|
||||
_LOGGER.error(
|
||||
"Invalid translation key %s for repair issue %s",
|
||||
translation_key,
|
||||
identifier,
|
||||
)
|
||||
return
|
||||
async_create_issue(
|
||||
hass=self._hass,
|
||||
domain=DOMAIN,
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
"reset_bad_custom_domain_configuration": {
|
||||
"title": "Custom domain ignored",
|
||||
"description": "The DNS configuration for your custom domain ({custom_domains}) is not correct. This domain has now been ignored and will not be used for Home Assistant Cloud. If you want to use this domain, please fix the DNS configuration and restart Home Assistant. If you do not need this anymore, you can remove it from the account page."
|
||||
},
|
||||
"subscription_expired": {
|
||||
"title": "Subscription has expired",
|
||||
"description": "Your Home Assistant Cloud subscription has expired. Resubscribe at {account_url}."
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["aiocomelit"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["aiocomelit==0.12.0"]
|
||||
"requirements": ["aiocomelit==0.12.1"]
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ def websocket_list_areas(
|
||||
vol.Optional("humidity_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("icon"): str,
|
||||
vol.Optional("labels"): [str],
|
||||
vol.Optional("motion_entity_id"): vol.Any(str, None),
|
||||
vol.Required("name"): str,
|
||||
vol.Optional("picture"): vol.Any(str, None),
|
||||
vol.Optional("temperature_entity_id"): vol.Any(str, None),
|
||||
@@ -112,6 +113,7 @@ def websocket_delete_area(
|
||||
vol.Optional("humidity_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("icon"): vol.Any(str, None),
|
||||
vol.Optional("labels"): [str],
|
||||
vol.Optional("motion_entity_id"): vol.Any(str, None),
|
||||
vol.Optional("name"): str,
|
||||
vol.Optional("picture"): vol.Any(str, None),
|
||||
vol.Optional("temperature_entity_id"): vol.Any(str, None),
|
||||
|
||||
@@ -9,13 +9,12 @@ import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.websocket_api import ERR_NOT_FOUND, require_admin
|
||||
from homeassistant.core import HomeAssistant, callback, split_entity_id
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers.entity_component import async_get_entity_suggested_object_id
|
||||
from homeassistant.helpers.json import json_dumps
|
||||
|
||||
|
||||
@@ -23,7 +22,6 @@ from homeassistant.helpers.json import json_dumps
|
||||
def async_setup(hass: HomeAssistant) -> bool:
|
||||
"""Enable the Entity Registry views."""
|
||||
|
||||
websocket_api.async_register_command(hass, websocket_get_automatic_entity_ids)
|
||||
websocket_api.async_register_command(hass, websocket_get_entities)
|
||||
websocket_api.async_register_command(hass, websocket_get_entity)
|
||||
websocket_api.async_register_command(hass, websocket_list_entities_for_display)
|
||||
@@ -318,43 +316,3 @@ def websocket_remove_entity(
|
||||
|
||||
registry.async_remove(msg["entity_id"])
|
||||
connection.send_message(websocket_api.result_message(msg["id"]))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "config/entity_registry/get_automatic_entity_ids",
|
||||
vol.Required("entity_ids"): cv.entity_ids,
|
||||
}
|
||||
)
|
||||
@callback
|
||||
def websocket_get_automatic_entity_ids(
|
||||
hass: HomeAssistant,
|
||||
connection: websocket_api.ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
) -> None:
|
||||
"""Return the automatic entity IDs for the given entity IDs.
|
||||
|
||||
This is used to help user reset entity IDs which have been customized by the user.
|
||||
"""
|
||||
registry = er.async_get(hass)
|
||||
|
||||
entity_ids = msg["entity_ids"]
|
||||
automatic_entity_ids: dict[str, str | None] = {}
|
||||
for entity_id in entity_ids:
|
||||
if not (entry := registry.entities.get(entity_id)):
|
||||
automatic_entity_ids[entity_id] = None
|
||||
continue
|
||||
if (
|
||||
suggested := async_get_entity_suggested_object_id(hass, entity_id)
|
||||
) == split_entity_id(entry.entity_id)[1]:
|
||||
# No need to generate a new entity ID
|
||||
automatic_entity_ids[entity_id] = None
|
||||
continue
|
||||
automatic_entity_ids[entity_id] = registry.async_generate_entity_id(
|
||||
entry.domain,
|
||||
suggested or f"{entry.platform}_{entry.unique_id}",
|
||||
)
|
||||
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg["id"], automatic_entity_ids)
|
||||
)
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "local_push",
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["debugpy==1.8.13"]
|
||||
"requirements": ["debugpy==1.8.14"]
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
],
|
||||
"quality_scale": "internal",
|
||||
"requirements": [
|
||||
"aiodhcpwatcher==1.1.1",
|
||||
"aiodhcpwatcher==1.2.0",
|
||||
"aiodiscover==2.7.0",
|
||||
"cached-ipaddress==0.10.0"
|
||||
]
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
"ac_module_temperature_sensor_faulty_l2": "AC module temperature sensor faulty (L2)",
|
||||
"dc_component_measured_in_grid_too_high": "DC component measured in the grid too high",
|
||||
"fixed_voltage_mode_out_of_range": "Fixed voltage mode has been selected instead of MPP voltage mode and the fixed voltage has been set to too low or too high a value",
|
||||
"safety_cut_out_triggered": "Safety cut out via option card or RECERBO has triggered",
|
||||
"safety_cut_out_triggered": "Safety cut-out via option card or RECERBO has triggered",
|
||||
"no_communication_between_power_stage_and_control_system": "No communication possible between power stage set and control system",
|
||||
"hardware_id_problem": "Hardware ID problem",
|
||||
"unique_id_conflict": "Unique ID conflict",
|
||||
@@ -148,7 +148,7 @@
|
||||
"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)",
|
||||
"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": "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",
|
||||
@@ -166,7 +166,7 @@
|
||||
"invalid_device_type": "Invalid device type",
|
||||
"insulation_measurement_triggered": "Insulation measurement triggered",
|
||||
"inverter_settings_changed_restart_required": "Inverter settings have been changed, inverter restart required",
|
||||
"wired_shut_down_triggered": "Wired shut down triggered",
|
||||
"wired_shut_down_triggered": "Wired shutdown triggered",
|
||||
"grid_frequency_exceeded_limit_reconnecting": "The grid frequency has exceeded a limit value when reconnecting",
|
||||
"mains_voltage_dependent_power_reduction": "Mains voltage-dependent power reduction",
|
||||
"too_little_dc_power_for_feed_in_operation": "Too little DC power for feed-in operation",
|
||||
|
||||
@@ -14,49 +14,78 @@ from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.storage import Store
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
DATA_STORAGE: HassKey[tuple[dict[str, Store], dict[str, dict]]] = HassKey(
|
||||
"frontend_storage"
|
||||
)
|
||||
DATA_STORAGE: HassKey[dict[str, UserStore]] = HassKey("frontend_storage")
|
||||
STORAGE_VERSION_USER_DATA = 1
|
||||
|
||||
|
||||
@callback
|
||||
def _initialize_frontend_storage(hass: HomeAssistant) -> None:
|
||||
"""Set up frontend storage."""
|
||||
if DATA_STORAGE in hass.data:
|
||||
return
|
||||
hass.data[DATA_STORAGE] = ({}, {})
|
||||
|
||||
|
||||
async def async_setup_frontend_storage(hass: HomeAssistant) -> None:
|
||||
"""Set up frontend storage."""
|
||||
_initialize_frontend_storage(hass)
|
||||
websocket_api.async_register_command(hass, websocket_set_user_data)
|
||||
websocket_api.async_register_command(hass, websocket_get_user_data)
|
||||
websocket_api.async_register_command(hass, websocket_subscribe_user_data)
|
||||
|
||||
|
||||
async def async_user_store(
|
||||
hass: HomeAssistant, user_id: str
|
||||
) -> tuple[Store, dict[str, Any]]:
|
||||
async def async_user_store(hass: HomeAssistant, user_id: str) -> UserStore:
|
||||
"""Access a user store."""
|
||||
_initialize_frontend_storage(hass)
|
||||
stores, data = hass.data[DATA_STORAGE]
|
||||
stores = hass.data.setdefault(DATA_STORAGE, {})
|
||||
if (store := stores.get(user_id)) is None:
|
||||
store = stores[user_id] = Store(
|
||||
store = stores[user_id] = UserStore(hass, user_id)
|
||||
await store.async_load()
|
||||
|
||||
return store
|
||||
|
||||
|
||||
class UserStore:
|
||||
"""User store for frontend data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, user_id: str) -> None:
|
||||
"""Initialize the user store."""
|
||||
self._store = _UserStore(hass, user_id)
|
||||
self.data: dict[str, Any] = {}
|
||||
self.subscriptions: dict[str | None, list[Callable[[], None]]] = {}
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load the data from the store."""
|
||||
self.data = await self._store.async_load() or {}
|
||||
|
||||
async def async_set_item(self, key: str, value: Any) -> None:
|
||||
"""Set an item item and save the store."""
|
||||
self.data[key] = value
|
||||
await self._store.async_save(self.data)
|
||||
for cb in self.subscriptions.get(None, []):
|
||||
cb()
|
||||
for cb in self.subscriptions.get(key, []):
|
||||
cb()
|
||||
|
||||
@callback
|
||||
def async_subscribe(
|
||||
self, key: str | None, on_update_callback: Callable[[], None]
|
||||
) -> Callable[[], None]:
|
||||
"""Save the data to the store."""
|
||||
self.subscriptions.setdefault(key, []).append(on_update_callback)
|
||||
|
||||
def unsubscribe() -> None:
|
||||
"""Unsubscribe from the store."""
|
||||
self.subscriptions[key].remove(on_update_callback)
|
||||
|
||||
return unsubscribe
|
||||
|
||||
|
||||
class _UserStore(Store[dict[str, Any]]):
|
||||
"""User store for frontend data."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, user_id: str) -> None:
|
||||
"""Initialize the user store."""
|
||||
super().__init__(
|
||||
hass,
|
||||
STORAGE_VERSION_USER_DATA,
|
||||
f"frontend.user_data_{user_id}",
|
||||
)
|
||||
|
||||
if user_id not in data:
|
||||
data[user_id] = await store.async_load() or {}
|
||||
|
||||
return store, data[user_id]
|
||||
|
||||
|
||||
def with_store(
|
||||
def with_user_store(
|
||||
orig_func: Callable[
|
||||
[HomeAssistant, ActiveConnection, dict[str, Any], Store, dict[str, Any]],
|
||||
[HomeAssistant, ActiveConnection, dict[str, Any], UserStore],
|
||||
Coroutine[Any, Any, None],
|
||||
],
|
||||
) -> Callable[
|
||||
@@ -65,17 +94,17 @@ def with_store(
|
||||
"""Decorate function to provide data."""
|
||||
|
||||
@wraps(orig_func)
|
||||
async def with_store_func(
|
||||
async def with_user_store_func(
|
||||
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
|
||||
) -> None:
|
||||
"""Provide user specific data and store to function."""
|
||||
user_id = connection.user.id
|
||||
|
||||
store, user_data = await async_user_store(hass, user_id)
|
||||
store = await async_user_store(hass, user_id)
|
||||
|
||||
await orig_func(hass, connection, msg, store, user_data)
|
||||
await orig_func(hass, connection, msg, store)
|
||||
|
||||
return with_store_func
|
||||
return with_user_store_func
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
@@ -86,41 +115,57 @@ def with_store(
|
||||
}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@with_store
|
||||
@with_user_store
|
||||
async def websocket_set_user_data(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
store: Store,
|
||||
data: dict[str, Any],
|
||||
store: UserStore,
|
||||
) -> None:
|
||||
"""Handle set global data command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
data[msg["key"]] = msg["value"]
|
||||
await store.async_save(data)
|
||||
connection.send_message(websocket_api.result_message(msg["id"]))
|
||||
"""Handle set user data command."""
|
||||
await store.async_set_item(msg["key"], msg["value"])
|
||||
connection.send_result(msg["id"])
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{vol.Required("type"): "frontend/get_user_data", vol.Optional("key"): str}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@with_store
|
||||
@with_user_store
|
||||
async def websocket_get_user_data(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
store: Store,
|
||||
data: dict[str, Any],
|
||||
store: UserStore,
|
||||
) -> None:
|
||||
"""Handle get global data command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg["id"], {"value": data.get(msg["key"]) if "key" in msg else data}
|
||||
)
|
||||
"""Handle get user data command."""
|
||||
data = store.data
|
||||
connection.send_result(
|
||||
msg["id"], {"value": data.get(msg["key"]) if "key" in msg else data}
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{vol.Required("type"): "frontend/subscribe_user_data", vol.Optional("key"): str}
|
||||
)
|
||||
@websocket_api.async_response
|
||||
@with_user_store
|
||||
async def websocket_subscribe_user_data(
|
||||
hass: HomeAssistant,
|
||||
connection: ActiveConnection,
|
||||
msg: dict[str, Any],
|
||||
store: UserStore,
|
||||
) -> None:
|
||||
"""Handle subscribe to user data command."""
|
||||
key: str | None = msg.get("key")
|
||||
|
||||
def on_data_update() -> None:
|
||||
"""Handle user data update."""
|
||||
data = store.data
|
||||
connection.send_event(
|
||||
msg["id"], {"value": data.get(key) if key is not None else data}
|
||||
)
|
||||
|
||||
connection.subscriptions[msg["id"]] = store.async_subscribe(key, on_data_update)
|
||||
on_data_update()
|
||||
connection.send_result(msg["id"])
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/google",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["googleapiclient"],
|
||||
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.2.1"]
|
||||
"requirements": ["gcal-sync==7.0.1", "oauth2client==4.1.3", "ical==9.2.2"]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import jwt
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult
|
||||
from homeassistant.helpers import config_entry_oauth2_flow
|
||||
from homeassistant.helpers import config_entry_oauth2_flow, device_registry as dr
|
||||
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
@@ -58,3 +59,22 @@ class OAuth2FlowHandler(
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
return await super().async_oauth_create_entry(data)
|
||||
|
||||
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),
|
||||
(DOMAIN, discovery_info.hostname.split("-")[-1]),
|
||||
}
|
||||
):
|
||||
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)
|
||||
|
||||
@@ -110,14 +110,14 @@ class AutomowerLawnMowerEntity(AutomowerAvailableEntity, LawnMowerEntity):
|
||||
mower_attributes = self.mower_attributes
|
||||
if mower_attributes.mower.state in PAUSED_STATES:
|
||||
return LawnMowerActivity.PAUSED
|
||||
if mower_attributes.mower.state in MowerStates.IN_OPERATION:
|
||||
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
|
||||
return LawnMowerActivity.RETURNING
|
||||
return LawnMowerActivity.MOWING
|
||||
if (mower_attributes.mower.state == "RESTRICTED") or (
|
||||
mower_attributes.mower.activity in DOCKED_ACTIVITIES
|
||||
):
|
||||
return LawnMowerActivity.DOCKED
|
||||
if mower_attributes.mower.state in MowerStates.IN_OPERATION:
|
||||
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
|
||||
return LawnMowerActivity.RETURNING
|
||||
return LawnMowerActivity.MOWING
|
||||
return LawnMowerActivity.ERROR
|
||||
|
||||
@property
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/husqvarna_automower_ble",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["automower-ble==0.2.0"]
|
||||
"requirements": ["automower-ble==0.2.1"]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["xknx", "xknxproject"],
|
||||
"requirements": [
|
||||
"xknx==3.6.0",
|
||||
"xknx==3.8.0",
|
||||
"xknxproject==3.8.2",
|
||||
"knx-frontend==2025.4.1.91934"
|
||||
],
|
||||
|
||||
@@ -19,7 +19,6 @@ from homeassistant.const import (
|
||||
CONF_PORT,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
@@ -44,21 +43,6 @@ CONFIG_SCHEMA = vol.Schema(CONFIG_DATA)
|
||||
USER_SCHEMA = vol.Schema(USER_DATA)
|
||||
|
||||
|
||||
def get_config_entry(
|
||||
hass: HomeAssistant, data: ConfigType
|
||||
) -> config_entries.ConfigEntry | None:
|
||||
"""Check config entries for already configured entries based on the ip address/port."""
|
||||
return next(
|
||||
(
|
||||
entry
|
||||
for entry in hass.config_entries.async_entries(DOMAIN)
|
||||
if entry.data[CONF_IP_ADDRESS] == data[CONF_IP_ADDRESS]
|
||||
and entry.data[CONF_PORT] == data[CONF_PORT]
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
|
||||
async def validate_connection(data: ConfigType) -> str | None:
|
||||
"""Validate if a connection to LCN can be established."""
|
||||
error = None
|
||||
@@ -120,19 +104,20 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="user", data_schema=USER_SCHEMA)
|
||||
|
||||
errors = None
|
||||
if get_config_entry(self.hass, user_input):
|
||||
errors = {CONF_BASE: "already_configured"}
|
||||
elif (error := await validate_connection(user_input)) is not None:
|
||||
errors = {CONF_BASE: error}
|
||||
self._async_abort_entries_match(
|
||||
{
|
||||
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
}
|
||||
)
|
||||
|
||||
if errors is not None:
|
||||
if (error := await validate_connection(user_input)) is not None:
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.add_suggested_values_to_schema(
|
||||
USER_SCHEMA, user_input
|
||||
),
|
||||
errors=errors,
|
||||
errors={CONF_BASE: error},
|
||||
)
|
||||
|
||||
data: dict = {
|
||||
@@ -152,15 +137,21 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
user_input[CONF_HOST] = reconfigure_entry.data[CONF_HOST]
|
||||
|
||||
await self.hass.config_entries.async_unload(reconfigure_entry.entry_id)
|
||||
if (error := await validate_connection(user_input)) is not None:
|
||||
errors = {CONF_BASE: error}
|
||||
self._async_abort_entries_match(
|
||||
{
|
||||
CONF_IP_ADDRESS: user_input[CONF_IP_ADDRESS],
|
||||
CONF_PORT: user_input[CONF_PORT],
|
||||
}
|
||||
)
|
||||
|
||||
if errors is None:
|
||||
await self.hass.config_entries.async_unload(reconfigure_entry.entry_id)
|
||||
|
||||
if (error := await validate_connection(user_input)) is None:
|
||||
return self.async_update_reload_and_abort(
|
||||
reconfigure_entry, data_updates=user_input
|
||||
)
|
||||
|
||||
errors = {CONF_BASE: error}
|
||||
await self.hass.config_entries.async_setup(reconfigure_entry.entry_id)
|
||||
|
||||
return self.async_show_form(
|
||||
|
||||
@@ -66,11 +66,11 @@
|
||||
"error": {
|
||||
"authentication_error": "Authentication failed. Wrong username or password.",
|
||||
"license_error": "Maximum number of connections was reached. An additional licence key is required.",
|
||||
"connection_refused": "Unable to connect to PCHK. Check IP and port.",
|
||||
"already_configured": "PCHK connection using the same ip address/port is already configured."
|
||||
"connection_refused": "Unable to connect to PCHK. Check IP and port."
|
||||
},
|
||||
"abort": {
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
|
||||
"already_configured": "PCHK connection using the same ip address/port is already configured."
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["ical"],
|
||||
"requirements": ["ical==9.2.1"]
|
||||
"requirements": ["ical==9.2.2"]
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["ical==9.2.1"]
|
||||
"requirements": ["ical==9.2.2"]
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ from .const import MieleAppliance
|
||||
from .coordinator import MieleConfigEntry
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ from .const import DOMAIN, PROCESS_ACTION, MieleActions, MieleAppliance
|
||||
from .coordinator import MieleConfigEntry
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ from .const import DEVICE_TYPE_TAGS, DISABLED_TEMP_ENTITIES, DOMAIN, MieleApplia
|
||||
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ from .const import DOMAIN, POWER_OFF, POWER_ON, VENTILATION_STEP, MieleAppliance
|
||||
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SPEED_RANGE = (1, 4)
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
"core_target_temperature": {
|
||||
"default": "mdi:thermometer-probe"
|
||||
},
|
||||
"target_temperature": {
|
||||
"default": "mdi:thermometer-check"
|
||||
},
|
||||
"drying_step": {
|
||||
"default": "mdi:water-outline"
|
||||
},
|
||||
|
||||
@@ -23,6 +23,8 @@ from .const import AMBIENT_LIGHT, DOMAIN, LIGHT, LIGHT_OFF, LIGHT_ON, MieleAppli
|
||||
from .coordinator import MieleConfigEntry
|
||||
from .entity import MieleDevice, MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["pymiele"],
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["pymiele==0.5.1"],
|
||||
"requirements": ["pymiele==0.5.2"],
|
||||
"single_config_entry": true,
|
||||
"zeroconf": ["_mieleathome._tcp.local."]
|
||||
}
|
||||
|
||||
@@ -32,18 +32,23 @@ rules:
|
||||
Handled by a setting in manifest.json as there is no account information in API
|
||||
|
||||
# Silver
|
||||
action-exceptions: todo
|
||||
action-exceptions:
|
||||
status: done
|
||||
comment: No custom actions are defined
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: No configuration parameters
|
||||
docs-installation-parameters: todo
|
||||
docs-installation-parameters:
|
||||
status: exempt
|
||||
comment: |
|
||||
Integration uses account linking via Nabu casa so no installation parameters are needed.
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
parallel-updates:
|
||||
status: exempt
|
||||
comment: Handled by coordinator
|
||||
log-when-unavailable:
|
||||
status: done
|
||||
comment: Handled by DataUpdateCoordinator
|
||||
parallel-updates: done
|
||||
reauthentication-flow: done
|
||||
test-coverage: todo
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ from .const import (
|
||||
from .coordinator import MieleConfigEntry, MieleDataUpdateCoordinator
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DISABLED_TEMPERATURE = -32768
|
||||
@@ -382,6 +384,7 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
||||
MieleAppliance.OVEN,
|
||||
MieleAppliance.OVEN_MICROWAVE,
|
||||
MieleAppliance.STEAM_OVEN_COMBI,
|
||||
MieleAppliance.STEAM_OVEN_MK2,
|
||||
),
|
||||
description=MieleSensorDescription(
|
||||
key="state_core_target_temperature",
|
||||
@@ -398,6 +401,29 @@ SENSOR_TYPES: Final[tuple[MieleSensorDefinition, ...]] = (
|
||||
),
|
||||
),
|
||||
),
|
||||
MieleSensorDefinition(
|
||||
types=(
|
||||
MieleAppliance.WASHING_MACHINE,
|
||||
MieleAppliance.WASHER_DRYER,
|
||||
MieleAppliance.OVEN,
|
||||
MieleAppliance.OVEN_MICROWAVE,
|
||||
MieleAppliance.STEAM_OVEN_MICRO,
|
||||
MieleAppliance.STEAM_OVEN_COMBI,
|
||||
MieleAppliance.STEAM_OVEN_MK2,
|
||||
),
|
||||
description=MieleSensorDescription(
|
||||
key="state_target_temperature",
|
||||
translation_key="target_temperature",
|
||||
zone=1,
|
||||
device_class=SensorDeviceClass.TEMPERATURE,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=(
|
||||
lambda value: cast(int, value.state_target_temperature[0].temperature)
|
||||
/ 100.0
|
||||
),
|
||||
),
|
||||
),
|
||||
MieleSensorDefinition(
|
||||
types=(
|
||||
MieleAppliance.OVEN,
|
||||
|
||||
@@ -876,6 +876,9 @@
|
||||
"core_temperature": {
|
||||
"name": "Core temperature"
|
||||
},
|
||||
"target_temperature": {
|
||||
"name": "Target temperature"
|
||||
},
|
||||
"core_target_temperature": {
|
||||
"name": "Core target temperature"
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ from .const import (
|
||||
from .coordinator import MieleConfigEntry
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ from .const import DOMAIN, PROCESS_ACTION, PROGRAM_ID, MieleActions, MieleApplia
|
||||
from .coordinator import MieleConfigEntry
|
||||
from .entity import MieleEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# The following const classes define program speeds and programs for the vacuum cleaner.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"sections": {
|
||||
"auth": {
|
||||
"name": "Authentication",
|
||||
"description": "Depending on whether the server is configured to support access control, some topics may be read/write protected so that only users with the correct credentials can subscribe or publish to them. To publish/subscribe to protected topics, you can provide a username and password.",
|
||||
"description": "Depending on whether the server is configured to support access control, some topics may be read/write protected so that only users with the correct credentials can subscribe or publish to them. To publish/subscribe to protected topics, you can provide a username and password. Home Assistant will automatically generate an access token to authenticate with ntfy.",
|
||||
"data": {
|
||||
"username": "[%key:common::config_flow::data::username%]",
|
||||
"password": "[%key:common::config_flow::data::password%]"
|
||||
|
||||
@@ -32,6 +32,8 @@ from .const import (
|
||||
PLACEHOLDER_WEBHOOK_URL,
|
||||
)
|
||||
|
||||
AUTH_TOKEN_URL = "https://intercom.help/plaato/en/articles/5004720-auth_token"
|
||||
|
||||
|
||||
class PlaatoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handles a Plaato config flow."""
|
||||
@@ -153,7 +155,10 @@ class PlaatoConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
step_id="api_method",
|
||||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
description_placeholders={PLACEHOLDER_DEVICE_TYPE: device_type.name},
|
||||
description_placeholders={
|
||||
PLACEHOLDER_DEVICE_TYPE: device_type.name,
|
||||
"auth_token_url": AUTH_TOKEN_URL,
|
||||
},
|
||||
)
|
||||
|
||||
async def _get_webhook_id(self):
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"api_method": {
|
||||
"title": "Select API method",
|
||||
"description": "To be able to query the API an 'auth token' is required which can be obtained by following [these instructions](https://plaato.zendesk.com/hc/en-us/articles/360003234717-Auth-token)\n\n Selected device: **{device_type}** \n\nIf you prefer to use the built-in webhook method (Airlock only) please check the box below and leave 'Auth token' blank",
|
||||
"description": "To be able to query the API an 'auth token' is required which can be obtained by following [these instructions]({auth_token_url})\n\nSelected device: **{device_type}** \n\nIf you prefer to use the built-in webhook method (Airlock only) please check the box below and leave 'Auth token' blank",
|
||||
"data": {
|
||||
"use_webhook": "Use webhook",
|
||||
"token": "Auth token"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["ical"],
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["ical==9.2.1"]
|
||||
"requirements": ["ical==9.2.2"]
|
||||
}
|
||||
|
||||
@@ -21,26 +21,19 @@ from homeassistant.const import (
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
|
||||
from .bridge import (
|
||||
SamsungTVBridge,
|
||||
async_get_device_info,
|
||||
mac_from_device_info,
|
||||
model_requires_encryption,
|
||||
)
|
||||
from .bridge import SamsungTVBridge, mac_from_device_info, model_requires_encryption
|
||||
from .const import (
|
||||
CONF_SESSION_ID,
|
||||
CONF_SSDP_MAIN_TV_AGENT_LOCATION,
|
||||
CONF_SSDP_RENDERING_CONTROL_LOCATION,
|
||||
DOMAIN,
|
||||
ENTRY_RELOAD_COOLDOWN,
|
||||
LEGACY_PORT,
|
||||
LOGGER,
|
||||
METHOD_ENCRYPTED_WEBSOCKET,
|
||||
METHOD_LEGACY,
|
||||
UPNP_SVC_MAIN_TV_AGENT,
|
||||
UPNP_SVC_RENDERING_CONTROL,
|
||||
)
|
||||
@@ -180,30 +173,10 @@ async def _async_create_bridge_with_updated_data(
|
||||
"""Create a bridge object and update any missing data in the config entry."""
|
||||
updated_data: dict[str, str | int] = {}
|
||||
host: str = entry.data[CONF_HOST]
|
||||
port: int | None = entry.data.get(CONF_PORT)
|
||||
method: str | None = entry.data.get(CONF_METHOD)
|
||||
method: str = entry.data[CONF_METHOD]
|
||||
load_info_attempted = False
|
||||
info: dict[str, Any] | None = None
|
||||
|
||||
if not port or not method:
|
||||
LOGGER.debug("Attempting to get port or method for %s", host)
|
||||
if method == METHOD_LEGACY:
|
||||
port = LEGACY_PORT
|
||||
else:
|
||||
# When we imported from yaml we didn't setup the method
|
||||
# because we didn't know it
|
||||
_result, port, method, info = await async_get_device_info(hass, host)
|
||||
load_info_attempted = True
|
||||
if not port or not method:
|
||||
raise ConfigEntryNotReady(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="failed_to_determine_connection_method",
|
||||
)
|
||||
|
||||
LOGGER.debug("Updated port to %s and method to %s for %s", port, method, host)
|
||||
updated_data[CONF_PORT] = port
|
||||
updated_data[CONF_METHOD] = method
|
||||
|
||||
bridge = _async_get_device_bridge(hass, {**entry.data, **updated_data})
|
||||
|
||||
mac: str | None = entry.data.get(CONF_MAC)
|
||||
|
||||
@@ -56,7 +56,6 @@ from .const import (
|
||||
RESULT_INVALID_PIN,
|
||||
RESULT_NOT_SUPPORTED,
|
||||
RESULT_SUCCESS,
|
||||
RESULT_UNKNOWN_HOST,
|
||||
SUCCESSFUL_RESULTS,
|
||||
UPNP_SVC_MAIN_TV_AGENT,
|
||||
UPNP_SVC_RENDERING_CONTROL,
|
||||
@@ -110,9 +109,11 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 2
|
||||
|
||||
_host: str
|
||||
_bridge: SamsungTVBridge
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize flow."""
|
||||
self._host: str = ""
|
||||
self._mac: str | None = None
|
||||
self._udn: str | None = None
|
||||
self._upnp_udn: str | None = None
|
||||
@@ -125,13 +126,11 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._name: str | None = None
|
||||
self._title: str = ""
|
||||
self._id: int | None = None
|
||||
self._bridge: SamsungTVBridge | None = None
|
||||
self._device_info: dict[str, Any] | None = None
|
||||
self._authenticator: SamsungTVEncryptedWSAsyncAuthenticator | None = None
|
||||
|
||||
def _base_config_entry(self) -> dict[str, Any]:
|
||||
"""Generate the base config entry without the method."""
|
||||
assert self._bridge is not None
|
||||
return {
|
||||
CONF_HOST: self._host,
|
||||
CONF_MAC: self._mac,
|
||||
@@ -145,7 +144,6 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
|
||||
def _get_entry_from_bridge(self) -> ConfigFlowResult:
|
||||
"""Get device entry."""
|
||||
assert self._bridge
|
||||
data = self._base_config_entry()
|
||||
if self._bridge.token:
|
||||
data[CONF_TOKEN] = self._bridge.token
|
||||
@@ -165,7 +163,6 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self, raise_on_progress: bool = True
|
||||
) -> None:
|
||||
"""Set the unique id from the udn."""
|
||||
assert self._host is not None
|
||||
# Set the unique id without raising on progress in case
|
||||
# there are two SSDP flows with for each ST
|
||||
await self.async_set_unique_id(self._udn, raise_on_progress=False)
|
||||
@@ -252,38 +249,44 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self._mac = mac
|
||||
return True
|
||||
|
||||
async def _async_set_name_host_from_input(self, user_input: dict[str, Any]) -> None:
|
||||
async def _async_set_name_host_from_input(self, user_input: dict[str, Any]) -> bool:
|
||||
try:
|
||||
self._host = await self.hass.async_add_executor_job(
|
||||
socket.gethostbyname, user_input[CONF_HOST]
|
||||
)
|
||||
except socket.gaierror as err:
|
||||
raise AbortFlow(RESULT_UNKNOWN_HOST) from err
|
||||
LOGGER.debug("Failed to get IP for %s: %s", user_input[CONF_HOST], err)
|
||||
return False
|
||||
self._title = self._host
|
||||
return True
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a flow initialized by the user."""
|
||||
errors: dict[str, str] | None = None
|
||||
if user_input is not None:
|
||||
await self._async_set_name_host_from_input(user_input)
|
||||
await self._async_create_bridge()
|
||||
assert self._bridge
|
||||
self._async_abort_entries_match({CONF_HOST: self._host})
|
||||
if self._bridge.method != METHOD_LEGACY:
|
||||
# Legacy bridge does not provide device info
|
||||
await self._async_set_device_unique_id(raise_on_progress=False)
|
||||
if self._bridge.method == METHOD_ENCRYPTED_WEBSOCKET:
|
||||
return await self.async_step_encrypted_pairing()
|
||||
return await self.async_step_pairing({})
|
||||
if await self._async_set_name_host_from_input(user_input):
|
||||
await self._async_create_bridge()
|
||||
self._async_abort_entries_match({CONF_HOST: self._host})
|
||||
if self._bridge.method != METHOD_LEGACY:
|
||||
# Legacy bridge does not provide device info
|
||||
await self._async_set_device_unique_id(raise_on_progress=False)
|
||||
if self._bridge.method == METHOD_ENCRYPTED_WEBSOCKET:
|
||||
return await self.async_step_encrypted_pairing()
|
||||
return await self.async_step_pairing({})
|
||||
errors = {"base": "invalid_host"}
|
||||
|
||||
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
|
||||
return self.async_show_form(
|
||||
step_id="user",
|
||||
data_schema=self.add_suggested_values_to_schema(DATA_SCHEMA, user_input),
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
async def async_step_pairing(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a pairing by accepting the message on the TV."""
|
||||
assert self._bridge is not None
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
result = await self._bridge.async_try_connect()
|
||||
@@ -305,7 +308,6 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Handle a encrypted pairing."""
|
||||
assert self._host is not None
|
||||
await self._async_start_encrypted_pairing(self._host)
|
||||
assert self._authenticator is not None
|
||||
errors: dict[str, str] = {}
|
||||
@@ -420,7 +422,6 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
@callback
|
||||
def _async_start_discovery_with_mac_address(self) -> None:
|
||||
"""Start discovery."""
|
||||
assert self._host is not None
|
||||
if (entry := self._async_update_existing_matching_entry()) and entry.unique_id:
|
||||
# If we have the unique id and the mac we abort
|
||||
# as we do not need anything else
|
||||
@@ -518,7 +519,6 @@ class SamsungTVConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle user-confirmation of discovered node."""
|
||||
if user_input is not None:
|
||||
await self._async_create_bridge()
|
||||
assert self._bridge
|
||||
if self._bridge.method == METHOD_ENCRYPTED_WEBSOCKET:
|
||||
return await self.async_step_encrypted_pairing()
|
||||
return await self.async_step_pairing({})
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
},
|
||||
"error": {
|
||||
"auth_missing": "[%key:component::samsungtv::config::abort::auth_missing%]",
|
||||
"invalid_host": "Host is invalid, please try again.",
|
||||
"invalid_pin": "PIN is invalid, please try again."
|
||||
},
|
||||
"abort": {
|
||||
@@ -52,7 +53,6 @@
|
||||
"id_missing": "This Samsung device doesn't have a SerialNumber.",
|
||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||
"not_supported": "This Samsung device is currently not supported.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33,7 +33,11 @@ from homeassistant.const import (
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr, issue_registry as ir
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, format_mac
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_BLUETOOTH,
|
||||
CONNECTION_NETWORK_MAC,
|
||||
format_mac,
|
||||
)
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||
|
||||
from .bluetooth import async_connect_scanner
|
||||
@@ -160,6 +164,11 @@ class ShellyCoordinatorBase[_DeviceT: BlockDevice | RpcDevice](
|
||||
"""Sleep period of the device."""
|
||||
return self.config_entry.data.get(CONF_SLEEP_PERIOD, 0)
|
||||
|
||||
@property
|
||||
def connections(self) -> set[tuple[str, str]]:
|
||||
"""Connections of the device."""
|
||||
return {(CONNECTION_NETWORK_MAC, self.mac)}
|
||||
|
||||
def async_setup(self, pending_platforms: list[Platform] | None = None) -> None:
|
||||
"""Set up the coordinator."""
|
||||
self._pending_platforms = pending_platforms
|
||||
@@ -167,7 +176,7 @@ class ShellyCoordinatorBase[_DeviceT: BlockDevice | RpcDevice](
|
||||
device_entry = dev_reg.async_get_or_create(
|
||||
config_entry_id=self.config_entry.entry_id,
|
||||
name=self.name,
|
||||
connections={(CONNECTION_NETWORK_MAC, self.mac)},
|
||||
connections=self.connections,
|
||||
identifiers={(DOMAIN, self.mac)},
|
||||
manufacturer="Shelly",
|
||||
model=get_shelly_model_name(self.model, self.sleep_period, self.device),
|
||||
@@ -523,6 +532,14 @@ class ShellyRpcCoordinator(ShellyCoordinatorBase[RpcDevice]):
|
||||
"""
|
||||
return format_mac(bluetooth_mac_from_primary_mac(self.mac)).upper()
|
||||
|
||||
@property
|
||||
def connections(self) -> set[tuple[str, str]]:
|
||||
"""Connections of the device."""
|
||||
connections = super().connections
|
||||
if not self.sleep_period:
|
||||
connections.add((CONNECTION_BLUETOOTH, self.bluetooth_source))
|
||||
return connections
|
||||
|
||||
async def async_device_online(self, source: str) -> None:
|
||||
"""Handle device going online."""
|
||||
if not self.sleep_period:
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
}
|
||||
},
|
||||
"confirm_discovery": {
|
||||
"description": "Do you want to set up the {model} at {host}?\n\nBattery-powered devices that are password protected must be woken up before continuing with setting up.\nBattery-powered devices that are not password protected will be added when the device wakes up, you can now manually wake the device up using a button on it or wait for the next data update from the device."
|
||||
"description": "Do you want to set up the {model} at {host}?\n\nBattery-powered devices that are password-protected must be woken up before continuing with setting up.\nBattery-powered devices that are not password-protected will be added when the device wakes up, you can now manually wake the device up using a button on it or wait for the next data update from the device."
|
||||
},
|
||||
"reconfigure": {
|
||||
"description": "Update configuration for {device_name}.\n\nBefore setup, battery-powered devices must be woken up, you can now wake the device up using a button on it.",
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
"""Offer sun based automation rules."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from typing import cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_CONDITION, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.condition import (
|
||||
ConditionCheckerType,
|
||||
condition_trace_set_result,
|
||||
condition_trace_update_result,
|
||||
trace_condition_function,
|
||||
)
|
||||
from homeassistant.helpers.sun import get_astral_event_date
|
||||
from homeassistant.helpers.typing import ConfigType, TemplateVarsType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
CONDITION_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
**cv.CONDITION_BASE_SCHEMA,
|
||||
vol.Required(CONF_CONDITION): "sun",
|
||||
vol.Optional("before"): cv.sun_event,
|
||||
vol.Optional("before_offset"): cv.time_period,
|
||||
vol.Optional("after"): vol.All(
|
||||
vol.Lower, vol.Any(SUN_EVENT_SUNSET, SUN_EVENT_SUNRISE)
|
||||
),
|
||||
vol.Optional("after_offset"): cv.time_period,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key("before", "after"),
|
||||
)
|
||||
|
||||
|
||||
def sun(
|
||||
hass: HomeAssistant,
|
||||
before: str | None = None,
|
||||
after: str | None = None,
|
||||
before_offset: timedelta | None = None,
|
||||
after_offset: timedelta | None = None,
|
||||
) -> bool:
|
||||
"""Test if current time matches sun requirements."""
|
||||
utcnow = dt_util.utcnow()
|
||||
today = dt_util.as_local(utcnow).date()
|
||||
before_offset = before_offset or timedelta(0)
|
||||
after_offset = after_offset or timedelta(0)
|
||||
|
||||
sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today)
|
||||
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, today)
|
||||
|
||||
has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after)
|
||||
has_sunset_condition = SUN_EVENT_SUNSET in (before, after)
|
||||
|
||||
after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date()
|
||||
if after_sunrise and has_sunrise_condition:
|
||||
tomorrow = today + timedelta(days=1)
|
||||
sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow)
|
||||
|
||||
after_sunset = today > dt_util.as_local(cast(datetime, sunset)).date()
|
||||
if after_sunset and has_sunset_condition:
|
||||
tomorrow = today + timedelta(days=1)
|
||||
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow)
|
||||
|
||||
# Special case: before sunrise OR after sunset
|
||||
# This will handle the very rare case in the polar region when the sun rises/sets
|
||||
# but does not set/rise.
|
||||
# However this entire condition does not handle those full days of darkness
|
||||
# or light, the following should be used instead:
|
||||
#
|
||||
# condition:
|
||||
# condition: state
|
||||
# entity_id: sun.sun
|
||||
# state: 'above_horizon' (or 'below_horizon')
|
||||
#
|
||||
if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET:
|
||||
wanted_time_before = cast(datetime, sunrise) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
wanted_time_after = cast(datetime, sunset) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
return utcnow < wanted_time_before or utcnow > wanted_time_after
|
||||
|
||||
if sunrise is None and has_sunrise_condition:
|
||||
# There is no sunrise today
|
||||
condition_trace_set_result(False, message="no sunrise today")
|
||||
return False
|
||||
|
||||
if sunset is None and has_sunset_condition:
|
||||
# There is no sunset today
|
||||
condition_trace_set_result(False, message="no sunset today")
|
||||
return False
|
||||
|
||||
if before == SUN_EVENT_SUNRISE:
|
||||
wanted_time_before = cast(datetime, sunrise) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
if utcnow > wanted_time_before:
|
||||
return False
|
||||
|
||||
if before == SUN_EVENT_SUNSET:
|
||||
wanted_time_before = cast(datetime, sunset) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
if utcnow > wanted_time_before:
|
||||
return False
|
||||
|
||||
if after == SUN_EVENT_SUNRISE:
|
||||
wanted_time_after = cast(datetime, sunrise) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
if utcnow < wanted_time_after:
|
||||
return False
|
||||
|
||||
if after == SUN_EVENT_SUNSET:
|
||||
wanted_time_after = cast(datetime, sunset) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
if utcnow < wanted_time_after:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def async_condition_from_config(config: ConfigType) -> ConditionCheckerType:
|
||||
"""Wrap action method with sun based condition."""
|
||||
before = config.get("before")
|
||||
after = config.get("after")
|
||||
before_offset = config.get("before_offset")
|
||||
after_offset = config.get("after_offset")
|
||||
|
||||
@trace_condition_function
|
||||
def sun_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
||||
"""Validate time based if-condition."""
|
||||
return sun(hass, before, after, before_offset, after_offset)
|
||||
|
||||
return sun_if
|
||||
@@ -32,7 +32,8 @@
|
||||
"@RenierM26",
|
||||
"@murtas",
|
||||
"@Eloston",
|
||||
"@dsypniewski"
|
||||
"@dsypniewski",
|
||||
"@zerzhang"
|
||||
],
|
||||
"config_flow": true,
|
||||
"dependencies": ["bluetooth_adapters"],
|
||||
|
||||
@@ -95,13 +95,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
energysites: list[TeslemetryEnergyData] = []
|
||||
|
||||
# Create the stream
|
||||
stream = TeslemetryStream(
|
||||
session,
|
||||
access_token,
|
||||
server=f"{region.lower()}.teslemetry.com",
|
||||
parse_timestamp=True,
|
||||
manual=True,
|
||||
)
|
||||
stream: TeslemetryStream | None = None
|
||||
|
||||
for product in products:
|
||||
if (
|
||||
@@ -123,6 +117,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
serial_number=vin,
|
||||
)
|
||||
|
||||
# Create stream if required
|
||||
if not stream:
|
||||
stream = TeslemetryStream(
|
||||
session,
|
||||
access_token,
|
||||
server=f"{region.lower()}.teslemetry.com",
|
||||
parse_timestamp=True,
|
||||
manual=True,
|
||||
)
|
||||
|
||||
remove_listener = stream.async_add_listener(
|
||||
create_handle_vehicle_stream(vin, coordinator),
|
||||
{"vin": vin},
|
||||
@@ -240,7 +244,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
entry.runtime_data = TeslemetryData(vehicles, energysites, scopes)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
entry.async_create_background_task(hass, stream.listen(), "Teslemetry Stream")
|
||||
if stream:
|
||||
entry.async_create_background_task(hass, stream.listen(), "Teslemetry Stream")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -469,6 +469,7 @@ class ResultStream:
|
||||
use_file_cache: bool
|
||||
language: str
|
||||
options: dict
|
||||
supports_streaming_input: bool
|
||||
|
||||
_manager: SpeechManager
|
||||
|
||||
@@ -484,7 +485,10 @@ class ResultStream:
|
||||
|
||||
@callback
|
||||
def async_set_message(self, message: str) -> None:
|
||||
"""Set message to be generated."""
|
||||
"""Set message to be generated.
|
||||
|
||||
This method will leverage a disk cache to speed up generation.
|
||||
"""
|
||||
self._result_cache.set_result(
|
||||
self._manager.async_cache_message_in_memory(
|
||||
engine=self.engine,
|
||||
@@ -497,7 +501,10 @@ class ResultStream:
|
||||
|
||||
@callback
|
||||
def async_set_message_stream(self, message_stream: AsyncGenerator[str]) -> None:
|
||||
"""Set a stream that will generate the message."""
|
||||
"""Set a stream that will generate the message.
|
||||
|
||||
This method can result in faster first byte when generating long responses.
|
||||
"""
|
||||
self._result_cache.set_result(
|
||||
self._manager.async_cache_message_stream_in_memory(
|
||||
engine=self.engine,
|
||||
@@ -726,6 +733,10 @@ class SpeechManager:
|
||||
if (engine_instance := get_engine_instance(self.hass, engine)) is None:
|
||||
raise HomeAssistantError(f"Provider {engine} not found")
|
||||
|
||||
supports_streaming_input = (
|
||||
isinstance(engine_instance, TextToSpeechEntity)
|
||||
and engine_instance.async_supports_streaming_input()
|
||||
)
|
||||
language, options = self.process_options(engine_instance, language, options)
|
||||
if use_file_cache is None:
|
||||
use_file_cache = self.use_file_cache
|
||||
@@ -741,6 +752,7 @@ class SpeechManager:
|
||||
engine=engine,
|
||||
language=language,
|
||||
options=options,
|
||||
supports_streaming_input=supports_streaming_input,
|
||||
_manager=self,
|
||||
)
|
||||
self.token_to_stream[token] = result_stream
|
||||
|
||||
@@ -89,6 +89,13 @@ class TextToSpeechEntity(RestoreEntity, cached_properties=CACHED_PROPERTIES_WITH
|
||||
"""Return a mapping with the default options."""
|
||||
return self._attr_default_options
|
||||
|
||||
@classmethod
|
||||
def async_supports_streaming_input(cls) -> bool:
|
||||
"""Return if the TTS engine supports streaming input."""
|
||||
return (
|
||||
cls.async_stream_tts_audio is not TextToSpeechEntity.async_stream_tts_audio
|
||||
)
|
||||
|
||||
@callback
|
||||
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
|
||||
"""Return a list of supported voices for a language."""
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"tls": "Enable this if you use a secure connection to your Velbus interface, like a Signum.",
|
||||
"host": "The IP address or hostname of the Velbus interface.",
|
||||
"port": "The port number of the Velbus interface.",
|
||||
"password": "The password of the Velbus interface, this is only needed if the interface is password protected."
|
||||
"password": "The password of the Velbus interface, this is only needed if the interface is password-protected."
|
||||
},
|
||||
"description": "TCP/IP configuration, in case you use a Signum, VelServ, velbus-tcp or any other Velbus to TCP/IP interface."
|
||||
},
|
||||
@@ -58,7 +58,7 @@
|
||||
"services": {
|
||||
"sync_clock": {
|
||||
"name": "Sync clock",
|
||||
"description": "Syncs the Velbus modules clock to the Home Assistant clock, this is the same as the 'sync clock' from VelbusLink.",
|
||||
"description": "Syncs the clock of the Velbus modules to the Home Assistant clock, this is the same as the 'sync clock' from VelbusLink.",
|
||||
"fields": {
|
||||
"interface": {
|
||||
"name": "Interface",
|
||||
@@ -104,7 +104,7 @@
|
||||
},
|
||||
"set_memo_text": {
|
||||
"name": "Set memo text",
|
||||
"description": "Sets the memo text to the display of modules like VMBGPO, VMBGPOD Be sure the page(s) of the module is configured to display the memo text.",
|
||||
"description": "Sets the memo text to the display of modules like VMBGPO, VMBGPOD. Be sure the pages of the modules are configured to display the memo text.",
|
||||
"fields": {
|
||||
"interface": {
|
||||
"name": "[%key:component::velbus::services::sync_clock::fields::interface::name%]",
|
||||
|
||||
@@ -65,7 +65,7 @@ def setup_platform(
|
||||
name = travel_time.get(CONF_NAME) or travel_time.get(CONF_ID)
|
||||
sensors.append(
|
||||
WashingtonStateTravelTimeSensor(
|
||||
name, config.get(CONF_API_KEY), travel_time.get(CONF_ID)
|
||||
name, config[CONF_API_KEY], travel_time.get(CONF_ID)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -82,20 +82,20 @@ class WashingtonStateTransportSensor(SensorEntity):
|
||||
|
||||
_attr_icon = ICON
|
||||
|
||||
def __init__(self, name, access_code):
|
||||
def __init__(self, name: str, access_code: str) -> None:
|
||||
"""Initialize the sensor."""
|
||||
self._data = {}
|
||||
self._data: dict[str, str | int | None] = {}
|
||||
self._access_code = access_code
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._state: int | None = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
"""Return the name of the sensor."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def native_value(self):
|
||||
def native_value(self) -> int | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self._state
|
||||
|
||||
@@ -106,7 +106,7 @@ class WashingtonStateTravelTimeSensor(WashingtonStateTransportSensor):
|
||||
_attr_attribution = ATTRIBUTION
|
||||
_attr_native_unit_of_measurement = UnitOfTime.MINUTES
|
||||
|
||||
def __init__(self, name, access_code, travel_time_id):
|
||||
def __init__(self, name: str, access_code: str, travel_time_id: str) -> None:
|
||||
"""Construct a travel time sensor."""
|
||||
self._travel_time_id = travel_time_id
|
||||
WashingtonStateTransportSensor.__init__(self, name, access_code)
|
||||
@@ -123,13 +123,17 @@ class WashingtonStateTravelTimeSensor(WashingtonStateTransportSensor):
|
||||
_LOGGER.warning("Invalid response from WSDOT API")
|
||||
else:
|
||||
self._data = response.json()
|
||||
self._state = self._data.get(ATTR_CURRENT_TIME)
|
||||
_state = self._data.get(ATTR_CURRENT_TIME)
|
||||
if not isinstance(_state, int):
|
||||
self._state = None
|
||||
else:
|
||||
self._state = _state
|
||||
|
||||
@property
|
||||
def extra_state_attributes(self) -> dict[str, Any] | None:
|
||||
"""Return other details about the sensor state."""
|
||||
if self._data is not None:
|
||||
attrs = {}
|
||||
attrs: dict[str, str | int | None | datetime] = {}
|
||||
for key in (
|
||||
ATTR_AVG_TIME,
|
||||
ATTR_NAME,
|
||||
@@ -144,12 +148,15 @@ class WashingtonStateTravelTimeSensor(WashingtonStateTransportSensor):
|
||||
return None
|
||||
|
||||
|
||||
def _parse_wsdot_timestamp(timestamp):
|
||||
def _parse_wsdot_timestamp(timestamp: Any) -> datetime | None:
|
||||
"""Convert WSDOT timestamp to datetime."""
|
||||
if not timestamp:
|
||||
if not isinstance(timestamp, str):
|
||||
return None
|
||||
# ex: Date(1485040200000-0800)
|
||||
milliseconds, tzone = re.search(r"Date\((\d+)([+-]\d\d)\d\d\)", timestamp).groups()
|
||||
timestamp_parts = re.search(r"Date\((\d+)([+-]\d\d)\d\d\)", timestamp)
|
||||
if timestamp_parts is None:
|
||||
return None
|
||||
milliseconds, tzone = timestamp_parts.groups()
|
||||
return datetime.fromtimestamp(
|
||||
int(milliseconds) / 1000, tz=timezone(timedelta(hours=int(tzone)))
|
||||
)
|
||||
|
||||
@@ -278,6 +278,39 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
# and we'll handle the clean up below.
|
||||
await driver_events.setup(driver)
|
||||
|
||||
if (old_unique_id := entry.unique_id) is not None and old_unique_id != (
|
||||
new_unique_id := str(driver.controller.home_id)
|
||||
):
|
||||
device_registry = dr.async_get(hass)
|
||||
controller_model = "Unknown model"
|
||||
if (
|
||||
(own_node := driver.controller.own_node)
|
||||
and (
|
||||
controller_device_entry := device_registry.async_get_device(
|
||||
identifiers={get_device_id(driver, own_node)}
|
||||
)
|
||||
)
|
||||
and (model := controller_device_entry.model)
|
||||
):
|
||||
controller_model = model
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"migrate_unique_id.{entry.entry_id}",
|
||||
data={
|
||||
"config_entry_id": entry.entry_id,
|
||||
"config_entry_title": entry.title,
|
||||
"controller_model": controller_model,
|
||||
"new_unique_id": new_unique_id,
|
||||
"old_unique_id": old_unique_id,
|
||||
},
|
||||
is_fixable=True,
|
||||
severity=IssueSeverity.ERROR,
|
||||
translation_key="migrate_unique_id",
|
||||
)
|
||||
else:
|
||||
async_delete_issue(hass, DOMAIN, f"migrate_unique_id.{entry.entry_id}")
|
||||
|
||||
# If the listen task is already failed, we need to raise ConfigEntryNotReady
|
||||
if listen_task.done():
|
||||
listen_error, error_message = _get_listen_task_error(listen_task)
|
||||
|
||||
@@ -71,6 +71,7 @@ from homeassistant.components.websocket_api import (
|
||||
ActiveConnection,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.const import CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -88,13 +89,16 @@ from .const import (
|
||||
DATA_CLIENT,
|
||||
DOMAIN,
|
||||
EVENT_DEVICE_ADDED_TO_REGISTRY,
|
||||
LOGGER,
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT,
|
||||
USER_AGENT,
|
||||
)
|
||||
from .helpers import (
|
||||
CannotConnect,
|
||||
async_enable_statistics,
|
||||
async_get_node_from_device_id,
|
||||
async_get_provisioning_entry_from_device_id,
|
||||
async_get_version_info,
|
||||
get_device_id,
|
||||
)
|
||||
|
||||
@@ -2865,6 +2869,25 @@ async def websocket_hard_reset_controller(
|
||||
async with asyncio.timeout(HARD_RESET_CONTROLLER_DRIVER_READY_TIMEOUT):
|
||||
await wait_driver_ready.wait()
|
||||
|
||||
# When resetting the controller, the controller home id is also changed.
|
||||
# The controller state in the client is stale after resetting the controller,
|
||||
# so get the new home id with a new client using the helper function.
|
||||
# The client state will be refreshed by reloading the config entry,
|
||||
# after the unique id of the config entry has been updated.
|
||||
try:
|
||||
version_info = await async_get_version_info(hass, entry.data[CONF_URL])
|
||||
except CannotConnect:
|
||||
# Just log this error, as there's nothing to do about it here.
|
||||
# The stale unique id needs to be handled by a repair flow,
|
||||
# after the config entry has been reloaded.
|
||||
LOGGER.error(
|
||||
"Failed to get server version, cannot update config entry"
|
||||
"unique id with new home id, after controller reset"
|
||||
)
|
||||
else:
|
||||
hass.config_entries.async_update_entry(
|
||||
entry, unique_id=str(version_info.home_id)
|
||||
)
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
|
||||
@@ -9,14 +9,13 @@ import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
from awesomeversion import AwesomeVersion
|
||||
from serial.tools import list_ports
|
||||
import voluptuous as vol
|
||||
from zwave_js_server.client import Client
|
||||
from zwave_js_server.exceptions import FailedCommand
|
||||
from zwave_js_server.model.driver import Driver
|
||||
from zwave_js_server.version import VersionInfo, get_server_version
|
||||
from zwave_js_server.version import VersionInfo
|
||||
|
||||
from homeassistant.components import usb
|
||||
from homeassistant.components.hassio import (
|
||||
@@ -36,7 +35,6 @@ from homeassistant.const import CONF_NAME, CONF_URL
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.hassio import is_hassio
|
||||
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
|
||||
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
||||
@@ -69,6 +67,7 @@ from .const import (
|
||||
DOMAIN,
|
||||
RESTORE_NVM_DRIVER_READY_TIMEOUT,
|
||||
)
|
||||
from .helpers import CannotConnect, async_get_version_info
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -79,7 +78,6 @@ ADDON_SETUP_TIMEOUT = 5
|
||||
ADDON_SETUP_TIMEOUT_ROUNDS = 40
|
||||
CONF_EMULATE_HARDWARE = "emulate_hardware"
|
||||
CONF_LOG_LEVEL = "log_level"
|
||||
SERVER_VERSION_TIMEOUT = 10
|
||||
|
||||
ADDON_LOG_LEVELS = {
|
||||
"error": "Error",
|
||||
@@ -130,22 +128,6 @@ async def validate_input(hass: HomeAssistant, user_input: dict) -> VersionInfo:
|
||||
raise InvalidInput("cannot_connect") from err
|
||||
|
||||
|
||||
async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo:
|
||||
"""Return Z-Wave JS version info."""
|
||||
try:
|
||||
async with asyncio.timeout(SERVER_VERSION_TIMEOUT):
|
||||
version_info: VersionInfo = await get_server_version(
|
||||
ws_address, async_get_clientsession(hass)
|
||||
)
|
||||
except (TimeoutError, aiohttp.ClientError) as err:
|
||||
# We don't want to spam the log if the add-on isn't started
|
||||
# or takes a long time to start.
|
||||
_LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err)
|
||||
raise CannotConnect from err
|
||||
|
||||
return version_info
|
||||
|
||||
|
||||
def get_usb_ports() -> dict[str, str]:
|
||||
"""Return a dict of USB ports and their friendly names."""
|
||||
ports = list_ports.comports()
|
||||
@@ -1357,10 +1339,6 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
return client.driver
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Indicate connection error."""
|
||||
|
||||
|
||||
class InvalidInput(HomeAssistantError):
|
||||
"""Error to indicate input data is invalid."""
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Callable
|
||||
from dataclasses import astuple, dataclass
|
||||
import logging
|
||||
from typing import Any, cast
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
from zwave_js_server.client import Client as ZwaveClient
|
||||
from zwave_js_server.const import (
|
||||
@@ -25,6 +27,7 @@ from zwave_js_server.model.value import (
|
||||
ValueDataType,
|
||||
get_value_id_str,
|
||||
)
|
||||
from zwave_js_server.version import VersionInfo, get_server_version
|
||||
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
@@ -38,6 +41,7 @@ from homeassistant.const import (
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.group import expand_entity_ids
|
||||
from homeassistant.helpers.typing import ConfigType, VolSchemaType
|
||||
@@ -54,6 +58,8 @@ from .const import (
|
||||
LOGGER,
|
||||
)
|
||||
|
||||
SERVER_VERSION_TIMEOUT = 10
|
||||
|
||||
|
||||
@dataclass
|
||||
class ZwaveValueID:
|
||||
@@ -568,3 +574,23 @@ def get_network_identifier_for_notification(
|
||||
return f"`{config_entry.title}`, with the home ID `{home_id}`,"
|
||||
return f"with the home ID `{home_id}`"
|
||||
return ""
|
||||
|
||||
|
||||
async def async_get_version_info(hass: HomeAssistant, ws_address: str) -> VersionInfo:
|
||||
"""Return Z-Wave JS version info."""
|
||||
try:
|
||||
async with asyncio.timeout(SERVER_VERSION_TIMEOUT):
|
||||
version_info: VersionInfo = await get_server_version(
|
||||
ws_address, async_get_clientsession(hass)
|
||||
)
|
||||
except (TimeoutError, aiohttp.ClientError) as err:
|
||||
# We don't want to spam the log if the add-on isn't started
|
||||
# or takes a long time to start.
|
||||
LOGGER.debug("Failed to connect to Z-Wave JS server: %s", err)
|
||||
raise CannotConnect from err
|
||||
|
||||
return version_info
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Indicate connection error."""
|
||||
|
||||
@@ -57,6 +57,47 @@ class DeviceConfigFileChangedFlow(RepairsFlow):
|
||||
)
|
||||
|
||||
|
||||
class MigrateUniqueIDFlow(RepairsFlow):
|
||||
"""Handler for an issue fixing flow."""
|
||||
|
||||
def __init__(self, data: dict[str, str]) -> None:
|
||||
"""Initialize."""
|
||||
self.description_placeholders: dict[str, str] = {
|
||||
"config_entry_title": data["config_entry_title"],
|
||||
"controller_model": data["controller_model"],
|
||||
"new_unique_id": data["new_unique_id"],
|
||||
"old_unique_id": data["old_unique_id"],
|
||||
}
|
||||
self._config_entry_id: str = data["config_entry_id"]
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
return await self.async_step_confirm()
|
||||
|
||||
async def async_step_confirm(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the confirm step of a fix flow."""
|
||||
if user_input is not None:
|
||||
config_entry = self.hass.config_entries.async_get_entry(
|
||||
self._config_entry_id
|
||||
)
|
||||
# If config entry was removed, we can ignore the issue.
|
||||
if config_entry is not None:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
config_entry,
|
||||
unique_id=self.description_placeholders["new_unique_id"],
|
||||
)
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="confirm",
|
||||
description_placeholders=self.description_placeholders,
|
||||
)
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant, issue_id: str, data: dict[str, str] | None
|
||||
) -> RepairsFlow:
|
||||
@@ -65,4 +106,7 @@ async def async_create_fix_flow(
|
||||
if issue_id.split(".")[0] == "device_config_file_changed":
|
||||
assert data
|
||||
return DeviceConfigFileChangedFlow(data)
|
||||
if issue_id.split(".")[0] == "migrate_unique_id":
|
||||
assert data
|
||||
return MigrateUniqueIDFlow(data)
|
||||
return ConfirmRepairFlow()
|
||||
|
||||
@@ -273,6 +273,17 @@
|
||||
"invalid_server_version": {
|
||||
"description": "The version of Z-Wave Server you are currently running is too old for this version of Home Assistant. Please update the Z-Wave Server to the latest version to fix this issue.",
|
||||
"title": "Newer version of Z-Wave Server needed"
|
||||
},
|
||||
"migrate_unique_id": {
|
||||
"fix_flow": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "A Z-Wave controller of model {controller_model} with a different ID ({new_unique_id}) than the previously connected controller ({old_unique_id}) was connected to the {config_entry_title} configuration entry.\n\nReasons for a different controller ID could be:\n\n1. The controller was factory reset, with a 3rd party application.\n2. A controller Non Volatile Memory (NVM) backup was restored to the controller, with a 3rd party application.\n3. A different controller was connected to this configuration entry.\n\nIf a different controller was connected, you should instead set up a new configuration entry for the new controller.\n\nIf you are sure that the current controller is the correct controller you can confirm this by pressing Submit, and the configuration entry will remember the new controller ID.",
|
||||
"title": "An unknown controller was detected"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "An unknown controller was detected"
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
|
||||
@@ -866,17 +866,17 @@ class Config:
|
||||
# pylint: disable-next=import-outside-toplevel
|
||||
from .components.frontend import storage as frontend_store
|
||||
|
||||
_, owner_data = await frontend_store.async_user_store(
|
||||
owner_store = await frontend_store.async_user_store(
|
||||
self.hass, owner.id
|
||||
)
|
||||
|
||||
if (
|
||||
"language" in owner_data
|
||||
and "language" in owner_data["language"]
|
||||
"language" in owner_store.data
|
||||
and "language" in owner_store.data["language"]
|
||||
):
|
||||
with suppress(vol.InInvalid):
|
||||
data["language"] = cv.language(
|
||||
owner_data["language"]["language"]
|
||||
owner_store.data["language"]["language"]
|
||||
)
|
||||
# pylint: disable-next=broad-except
|
||||
except Exception:
|
||||
|
||||
@@ -40,7 +40,7 @@ EVENT_AREA_REGISTRY_UPDATED: EventType[EventAreaRegistryUpdatedData] = EventType
|
||||
)
|
||||
STORAGE_KEY = "core.area_registry"
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 8
|
||||
STORAGE_VERSION_MINOR = 9
|
||||
|
||||
|
||||
class _AreaStoreData(TypedDict):
|
||||
@@ -52,6 +52,7 @@ class _AreaStoreData(TypedDict):
|
||||
icon: str | None
|
||||
id: str
|
||||
labels: list[str]
|
||||
motion_entity_id: str | None
|
||||
name: str
|
||||
picture: str | None
|
||||
temperature_entity_id: str | None
|
||||
@@ -82,6 +83,7 @@ class AreaEntry(NormalizedNameBaseRegistryEntry):
|
||||
icon: str | None
|
||||
id: str
|
||||
labels: set[str] = field(default_factory=set)
|
||||
motion_entity_id: str | None
|
||||
picture: str | None
|
||||
temperature_entity_id: str | None
|
||||
_cache: dict[str, Any] = field(default_factory=dict, compare=False, init=False)
|
||||
@@ -98,6 +100,7 @@ class AreaEntry(NormalizedNameBaseRegistryEntry):
|
||||
"humidity_entity_id": self.humidity_entity_id,
|
||||
"icon": self.icon,
|
||||
"labels": list(self.labels),
|
||||
"motion_entity_id": self.motion_entity_id,
|
||||
"name": self.name,
|
||||
"picture": self.picture,
|
||||
"temperature_entity_id": self.temperature_entity_id,
|
||||
@@ -157,6 +160,11 @@ class AreaRegistryStore(Store[AreasRegistryStoreData]):
|
||||
area["humidity_entity_id"] = None
|
||||
area["temperature_entity_id"] = None
|
||||
|
||||
if old_minor_version < 9:
|
||||
# Version 1.9 adds motion_entity_id
|
||||
for area_data in old_data["areas"]:
|
||||
area_data["motion_entity_id"] = None
|
||||
|
||||
if old_major_version > 1:
|
||||
raise NotImplementedError
|
||||
return old_data # type: ignore[return-value]
|
||||
@@ -278,6 +286,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None = None,
|
||||
icon: str | None = None,
|
||||
labels: set[str] | None = None,
|
||||
motion_entity_id: str | None = None,
|
||||
picture: str | None = None,
|
||||
temperature_entity_id: str | None = None,
|
||||
) -> AreaEntry:
|
||||
@@ -293,6 +302,9 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
if humidity_entity_id is not None:
|
||||
_validate_humidity_entity(self.hass, humidity_entity_id)
|
||||
|
||||
if motion_entity_id is not None:
|
||||
_validate_motion_entity(self.hass, motion_entity_id)
|
||||
|
||||
if temperature_entity_id is not None:
|
||||
_validate_temperature_entity(self.hass, temperature_entity_id)
|
||||
|
||||
@@ -303,6 +315,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
icon=icon,
|
||||
id=self._generate_id(name),
|
||||
labels=labels or set(),
|
||||
motion_entity_id=motion_entity_id,
|
||||
name=name,
|
||||
picture=picture,
|
||||
temperature_entity_id=temperature_entity_id,
|
||||
@@ -345,6 +358,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
icon: str | None | UndefinedType = UNDEFINED,
|
||||
labels: set[str] | UndefinedType = UNDEFINED,
|
||||
motion_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
name: str | UndefinedType = UNDEFINED,
|
||||
picture: str | None | UndefinedType = UNDEFINED,
|
||||
temperature_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
@@ -357,6 +371,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id=humidity_entity_id,
|
||||
icon=icon,
|
||||
labels=labels,
|
||||
motion_entity_id=motion_entity_id,
|
||||
name=name,
|
||||
picture=picture,
|
||||
temperature_entity_id=temperature_entity_id,
|
||||
@@ -381,6 +396,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
humidity_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
icon: str | None | UndefinedType = UNDEFINED,
|
||||
labels: set[str] | UndefinedType = UNDEFINED,
|
||||
motion_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
name: str | UndefinedType = UNDEFINED,
|
||||
picture: str | None | UndefinedType = UNDEFINED,
|
||||
temperature_entity_id: str | None | UndefinedType = UNDEFINED,
|
||||
@@ -396,6 +412,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
("humidity_entity_id", humidity_entity_id),
|
||||
("icon", icon),
|
||||
("labels", labels),
|
||||
("motion_entity_id", motion_entity_id),
|
||||
("picture", picture),
|
||||
("temperature_entity_id", temperature_entity_id),
|
||||
)
|
||||
@@ -405,6 +422,9 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
if "humidity_entity_id" in new_values and humidity_entity_id is not None:
|
||||
_validate_humidity_entity(self.hass, new_values["humidity_entity_id"])
|
||||
|
||||
if "motion_entity_id" in new_values and motion_entity_id is not None:
|
||||
_validate_motion_entity(self.hass, new_values["motion_entity_id"])
|
||||
|
||||
if "temperature_entity_id" in new_values and temperature_entity_id is not None:
|
||||
_validate_temperature_entity(self.hass, new_values["temperature_entity_id"])
|
||||
|
||||
@@ -440,6 +460,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
icon=area["icon"],
|
||||
id=area["id"],
|
||||
labels=set(area["labels"]),
|
||||
motion_entity_id=area["motion_entity_id"],
|
||||
name=area["name"],
|
||||
picture=area["picture"],
|
||||
temperature_entity_id=area["temperature_entity_id"],
|
||||
@@ -462,6 +483,7 @@ class AreaRegistry(BaseRegistry[AreasRegistryStoreData]):
|
||||
"icon": entry.icon,
|
||||
"id": entry.id,
|
||||
"labels": list(entry.labels),
|
||||
"motion_entity_id": entry.motion_entity_id,
|
||||
"name": entry.name,
|
||||
"picture": entry.picture,
|
||||
"temperature_entity_id": entry.temperature_entity_id,
|
||||
@@ -569,3 +591,18 @@ def _validate_humidity_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
or state.attributes.get(ATTR_DEVICE_CLASS) != SensorDeviceClass.HUMIDITY
|
||||
):
|
||||
raise ValueError(f"Entity {entity_id} is not a humidity sensor")
|
||||
|
||||
|
||||
def _validate_motion_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
"""Validate motion entity."""
|
||||
# pylint: disable=import-outside-toplevel
|
||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||
|
||||
if not (state := hass.states.get(entity_id)):
|
||||
raise ValueError(f"Entity {entity_id} does not exist")
|
||||
|
||||
if (
|
||||
state.domain != "binary_sensor"
|
||||
or state.attributes.get(ATTR_DEVICE_CLASS) != BinarySensorDeviceClass.MOTION
|
||||
):
|
||||
raise ValueError(f"Entity {entity_id} is not a motion binary_sensor")
|
||||
|
||||
@@ -42,8 +42,6 @@ from homeassistant.const import (
|
||||
ENTITY_MATCH_ANY,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
SUN_EVENT_SUNRISE,
|
||||
SUN_EVENT_SUNSET,
|
||||
WEEKDAYS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State, callback
|
||||
@@ -60,7 +58,6 @@ from homeassistant.util import dt as dt_util
|
||||
from homeassistant.util.async_ import run_callback_threadsafe
|
||||
|
||||
from . import config_validation as cv, entity_registry as er
|
||||
from .sun import get_astral_event_date
|
||||
from .template import Template, render_complex
|
||||
from .trace import (
|
||||
TraceElement,
|
||||
@@ -85,7 +82,6 @@ _PLATFORM_ALIASES = {
|
||||
"numeric_state": None,
|
||||
"or": None,
|
||||
"state": None,
|
||||
"sun": None,
|
||||
"template": None,
|
||||
"time": None,
|
||||
"trigger": None,
|
||||
@@ -655,105 +651,6 @@ def state_from_config(config: ConfigType) -> ConditionCheckerType:
|
||||
return if_state
|
||||
|
||||
|
||||
def sun(
|
||||
hass: HomeAssistant,
|
||||
before: str | None = None,
|
||||
after: str | None = None,
|
||||
before_offset: timedelta | None = None,
|
||||
after_offset: timedelta | None = None,
|
||||
) -> bool:
|
||||
"""Test if current time matches sun requirements."""
|
||||
utcnow = dt_util.utcnow()
|
||||
today = dt_util.as_local(utcnow).date()
|
||||
before_offset = before_offset or timedelta(0)
|
||||
after_offset = after_offset or timedelta(0)
|
||||
|
||||
sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, today)
|
||||
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, today)
|
||||
|
||||
has_sunrise_condition = SUN_EVENT_SUNRISE in (before, after)
|
||||
has_sunset_condition = SUN_EVENT_SUNSET in (before, after)
|
||||
|
||||
after_sunrise = today > dt_util.as_local(cast(datetime, sunrise)).date()
|
||||
if after_sunrise and has_sunrise_condition:
|
||||
tomorrow = today + timedelta(days=1)
|
||||
sunrise = get_astral_event_date(hass, SUN_EVENT_SUNRISE, tomorrow)
|
||||
|
||||
after_sunset = today > dt_util.as_local(cast(datetime, sunset)).date()
|
||||
if after_sunset and has_sunset_condition:
|
||||
tomorrow = today + timedelta(days=1)
|
||||
sunset = get_astral_event_date(hass, SUN_EVENT_SUNSET, tomorrow)
|
||||
|
||||
# Special case: before sunrise OR after sunset
|
||||
# This will handle the very rare case in the polar region when the sun rises/sets
|
||||
# but does not set/rise.
|
||||
# However this entire condition does not handle those full days of darkness
|
||||
# or light, the following should be used instead:
|
||||
#
|
||||
# condition:
|
||||
# condition: state
|
||||
# entity_id: sun.sun
|
||||
# state: 'above_horizon' (or 'below_horizon')
|
||||
#
|
||||
if before == SUN_EVENT_SUNRISE and after == SUN_EVENT_SUNSET:
|
||||
wanted_time_before = cast(datetime, sunrise) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
wanted_time_after = cast(datetime, sunset) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
return utcnow < wanted_time_before or utcnow > wanted_time_after
|
||||
|
||||
if sunrise is None and has_sunrise_condition:
|
||||
# There is no sunrise today
|
||||
condition_trace_set_result(False, message="no sunrise today")
|
||||
return False
|
||||
|
||||
if sunset is None and has_sunset_condition:
|
||||
# There is no sunset today
|
||||
condition_trace_set_result(False, message="no sunset today")
|
||||
return False
|
||||
|
||||
if before == SUN_EVENT_SUNRISE:
|
||||
wanted_time_before = cast(datetime, sunrise) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
if utcnow > wanted_time_before:
|
||||
return False
|
||||
|
||||
if before == SUN_EVENT_SUNSET:
|
||||
wanted_time_before = cast(datetime, sunset) + before_offset
|
||||
condition_trace_update_result(wanted_time_before=wanted_time_before)
|
||||
if utcnow > wanted_time_before:
|
||||
return False
|
||||
|
||||
if after == SUN_EVENT_SUNRISE:
|
||||
wanted_time_after = cast(datetime, sunrise) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
if utcnow < wanted_time_after:
|
||||
return False
|
||||
|
||||
if after == SUN_EVENT_SUNSET:
|
||||
wanted_time_after = cast(datetime, sunset) + after_offset
|
||||
condition_trace_update_result(wanted_time_after=wanted_time_after)
|
||||
if utcnow < wanted_time_after:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sun_from_config(config: ConfigType) -> ConditionCheckerType:
|
||||
"""Wrap action method with sun based condition."""
|
||||
before = config.get("before")
|
||||
after = config.get("after")
|
||||
before_offset = config.get("before_offset")
|
||||
after_offset = config.get("after_offset")
|
||||
|
||||
@trace_condition_function
|
||||
def sun_if(hass: HomeAssistant, variables: TemplateVarsType = None) -> bool:
|
||||
"""Validate time based if-condition."""
|
||||
return sun(hass, before, after, before_offset, after_offset)
|
||||
|
||||
return sun_if
|
||||
|
||||
|
||||
def template(
|
||||
hass: HomeAssistant, value_template: Template, variables: TemplateVarsType = None
|
||||
) -> bool:
|
||||
@@ -1054,8 +951,10 @@ async def async_validate_condition_config(
|
||||
return config
|
||||
|
||||
platform = await _async_get_condition_platform(hass, config)
|
||||
if platform is not None and hasattr(platform, "async_validate_condition_config"):
|
||||
return await platform.async_validate_condition_config(hass, config)
|
||||
if platform is not None:
|
||||
if hasattr(platform, "async_validate_condition_config"):
|
||||
return await platform.async_validate_condition_config(hass, config)
|
||||
return cast(ConfigType, platform.CONDITION_SCHEMA(config))
|
||||
if platform is None and condition in ("numeric_state", "state"):
|
||||
validator = cast(
|
||||
Callable[[HomeAssistant, ConfigType], ConfigType],
|
||||
|
||||
@@ -1084,10 +1084,13 @@ def renamed(
|
||||
return validator
|
||||
|
||||
|
||||
type ValueSchemas = dict[Hashable, VolSchemaType | Callable[[Any], dict[str, Any]]]
|
||||
|
||||
|
||||
def key_value_schemas(
|
||||
key: str,
|
||||
value_schemas: dict[Hashable, VolSchemaType | Callable[[Any], dict[str, Any]]],
|
||||
default_schema: VolSchemaType | None = None,
|
||||
value_schemas: ValueSchemas,
|
||||
default_schema: VolSchemaType | Callable[[Any], dict[str, Any]] | None = None,
|
||||
default_description: str | None = None,
|
||||
) -> Callable[[Any], dict[Hashable, Any]]:
|
||||
"""Create a validator that validates based on a value for specific key.
|
||||
@@ -1735,25 +1738,41 @@ CONDITION_SHORTHAND_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
BUILT_IN_CONDITIONS: ValueSchemas = {
|
||||
"and": AND_CONDITION_SCHEMA,
|
||||
"device": DEVICE_CONDITION_SCHEMA,
|
||||
"not": NOT_CONDITION_SCHEMA,
|
||||
"numeric_state": NUMERIC_STATE_CONDITION_SCHEMA,
|
||||
"or": OR_CONDITION_SCHEMA,
|
||||
"state": STATE_CONDITION_SCHEMA,
|
||||
"template": TEMPLATE_CONDITION_SCHEMA,
|
||||
"time": TIME_CONDITION_SCHEMA,
|
||||
"trigger": TRIGGER_CONDITION_SCHEMA,
|
||||
"zone": ZONE_CONDITION_SCHEMA,
|
||||
}
|
||||
|
||||
|
||||
# This is first round of validation, we don't want to mutate the config here already,
|
||||
# just ensure basics as condition type and alias are there.
|
||||
def _base_condition_validator(value: Any) -> Any:
|
||||
vol.Schema(
|
||||
{
|
||||
**CONDITION_BASE_SCHEMA,
|
||||
CONF_CONDITION: vol.NotIn(BUILT_IN_CONDITIONS),
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)(value)
|
||||
return value
|
||||
|
||||
|
||||
CONDITION_SCHEMA: vol.Schema = vol.Schema(
|
||||
vol.Any(
|
||||
vol.All(
|
||||
expand_condition_shorthand,
|
||||
key_value_schemas(
|
||||
CONF_CONDITION,
|
||||
{
|
||||
"and": AND_CONDITION_SCHEMA,
|
||||
"device": DEVICE_CONDITION_SCHEMA,
|
||||
"not": NOT_CONDITION_SCHEMA,
|
||||
"numeric_state": NUMERIC_STATE_CONDITION_SCHEMA,
|
||||
"or": OR_CONDITION_SCHEMA,
|
||||
"state": STATE_CONDITION_SCHEMA,
|
||||
"sun": SUN_CONDITION_SCHEMA,
|
||||
"template": TEMPLATE_CONDITION_SCHEMA,
|
||||
"time": TIME_CONDITION_SCHEMA,
|
||||
"trigger": TRIGGER_CONDITION_SCHEMA,
|
||||
"zone": ZONE_CONDITION_SCHEMA,
|
||||
},
|
||||
BUILT_IN_CONDITIONS,
|
||||
_base_condition_validator,
|
||||
),
|
||||
),
|
||||
dynamic_template_condition,
|
||||
@@ -1780,20 +1799,11 @@ CONDITION_ACTION_SCHEMA: vol.Schema = vol.Schema(
|
||||
expand_condition_shorthand,
|
||||
key_value_schemas(
|
||||
CONF_CONDITION,
|
||||
{
|
||||
"and": AND_CONDITION_SCHEMA,
|
||||
"device": DEVICE_CONDITION_SCHEMA,
|
||||
"not": NOT_CONDITION_SCHEMA,
|
||||
"numeric_state": NUMERIC_STATE_CONDITION_SCHEMA,
|
||||
"or": OR_CONDITION_SCHEMA,
|
||||
"state": STATE_CONDITION_SCHEMA,
|
||||
"sun": SUN_CONDITION_SCHEMA,
|
||||
"template": TEMPLATE_CONDITION_SCHEMA,
|
||||
"time": TIME_CONDITION_SCHEMA,
|
||||
"trigger": TRIGGER_CONDITION_SCHEMA,
|
||||
"zone": ZONE_CONDITION_SCHEMA,
|
||||
},
|
||||
dynamic_template_condition_action,
|
||||
BUILT_IN_CONDITIONS,
|
||||
vol.Any(
|
||||
dynamic_template_condition_action,
|
||||
_base_condition_validator,
|
||||
),
|
||||
"a list of conditions or a valid template",
|
||||
),
|
||||
)
|
||||
@@ -1852,7 +1862,7 @@ def _base_trigger_list_flatten(triggers: list[Any]) -> list[Any]:
|
||||
return flatlist
|
||||
|
||||
|
||||
# This is first round of validation, we don't want to process the config here already,
|
||||
# This is first round of validation, we don't want to mutate the config here already,
|
||||
# just ensure basics as platform and ID are there.
|
||||
def _base_trigger_validator(value: Any) -> Any:
|
||||
_base_trigger_validator_schema(value)
|
||||
|
||||
@@ -29,27 +29,20 @@ from homeassistant.core import (
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.loader import async_get_integration, bind_hass
|
||||
from homeassistant.setup import async_prepare_setup_platform
|
||||
from homeassistant.util.hass_dict import HassKey
|
||||
|
||||
from . import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
discovery,
|
||||
entity,
|
||||
entity_registry as er,
|
||||
service,
|
||||
)
|
||||
from .entity_platform import EntityPlatform, async_calculate_suggested_object_id
|
||||
from . import config_validation as cv, discovery, entity, service
|
||||
from .entity_platform import EntityPlatform
|
||||
from .typing import ConfigType, DiscoveryInfoType, VolDictType, VolSchemaType
|
||||
|
||||
DEFAULT_SCAN_INTERVAL = timedelta(seconds=15)
|
||||
DATA_INSTANCES: HassKey[dict[str, EntityComponent]] = HassKey("entity_components")
|
||||
DATA_INSTANCES = "entity_components"
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
"""Trigger an update for an entity."""
|
||||
domain = entity_id.partition(".")[0]
|
||||
entity_comp: EntityComponent[entity.Entity] | None
|
||||
entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain)
|
||||
|
||||
if entity_comp is None:
|
||||
@@ -67,37 +60,6 @@ async def async_update_entity(hass: HomeAssistant, entity_id: str) -> None:
|
||||
await entity_obj.async_update_ha_state(True)
|
||||
|
||||
|
||||
@callback
|
||||
def async_get_entity_suggested_object_id(
|
||||
hass: HomeAssistant, entity_id: str
|
||||
) -> str | None:
|
||||
"""Get the suggested object id for an entity.
|
||||
|
||||
Raises HomeAssistantError if the entity is not in the registry.
|
||||
"""
|
||||
entity_registry = er.async_get(hass)
|
||||
if not (entity_entry := entity_registry.async_get(entity_id)):
|
||||
raise HomeAssistantError(f"Entity {entity_id} is not in the registry.")
|
||||
|
||||
domain = entity_id.partition(".")[0]
|
||||
|
||||
if entity_entry.suggested_object_id:
|
||||
return entity_entry.suggested_object_id
|
||||
|
||||
if entity_entry.name:
|
||||
return entity_entry.name
|
||||
|
||||
entity_comp = hass.data.get(DATA_INSTANCES, {}).get(domain)
|
||||
entity_obj = entity_comp.get_entity(entity_id) if entity_comp else None
|
||||
if entity_obj:
|
||||
device: dr.DeviceEntry | None = None
|
||||
if device_id := entity_entry.device_id:
|
||||
device = dr.async_get(hass).async_get(device_id)
|
||||
return async_calculate_suggested_object_id(entity_obj, device)
|
||||
|
||||
return entity_entry.calculated_object_id
|
||||
|
||||
|
||||
class EntityComponent[_EntityT: entity.Entity = entity.Entity]:
|
||||
"""The EntityComponent manages platforms that manage entities.
|
||||
|
||||
@@ -133,7 +95,7 @@ class EntityComponent[_EntityT: entity.Entity = entity.Entity]:
|
||||
self.async_add_entities = domain_platform.async_add_entities
|
||||
self.add_entities = domain_platform.add_entities
|
||||
self._entities: dict[str, entity.Entity] = domain_platform.domain_entities
|
||||
hass.data.setdefault(DATA_INSTANCES, {})[domain] = self # type: ignore[assignment]
|
||||
hass.data.setdefault(DATA_INSTANCES, {})[domain] = self
|
||||
|
||||
@property
|
||||
def entities(self) -> Iterable[_EntityT]:
|
||||
|
||||
@@ -764,7 +764,7 @@ class EntityPlatform:
|
||||
already_exists = True
|
||||
return (already_exists, restored)
|
||||
|
||||
async def _async_add_entity(
|
||||
async def _async_add_entity( # noqa: C901
|
||||
self,
|
||||
entity: Entity,
|
||||
update_before_add: bool,
|
||||
@@ -843,18 +843,31 @@ class EntityPlatform:
|
||||
else:
|
||||
device = None
|
||||
|
||||
# An entity may suggest the entity_id by setting entity_id itself
|
||||
calculated_object_id: str | None = None
|
||||
suggested_entity_id: str | None = entity.entity_id
|
||||
if suggested_entity_id is not None:
|
||||
suggested_object_id = split_entity_id(entity.entity_id)[1]
|
||||
else:
|
||||
calculated_object_id = async_calculate_suggested_object_id(
|
||||
entity, device
|
||||
)
|
||||
if not registered_entity_id:
|
||||
# Do not bother working out a suggested_object_id
|
||||
# if the entity is already registered as it will
|
||||
# be ignored.
|
||||
#
|
||||
# An entity may suggest the entity_id by setting entity_id itself
|
||||
suggested_entity_id: str | None = entity.entity_id
|
||||
if suggested_entity_id is not None:
|
||||
suggested_object_id = split_entity_id(entity.entity_id)[1]
|
||||
else:
|
||||
if device and entity.has_entity_name:
|
||||
device_name = device.name_by_user or device.name
|
||||
if entity.use_device_name:
|
||||
suggested_object_id = device_name
|
||||
else:
|
||||
suggested_object_id = (
|
||||
f"{device_name} {entity.suggested_object_id}"
|
||||
)
|
||||
if not suggested_object_id:
|
||||
suggested_object_id = entity.suggested_object_id
|
||||
|
||||
if self.entity_namespace is not None and suggested_object_id is not None:
|
||||
suggested_object_id = f"{self.entity_namespace} {suggested_object_id}"
|
||||
if self.entity_namespace is not None:
|
||||
suggested_object_id = (
|
||||
f"{self.entity_namespace} {suggested_object_id}"
|
||||
)
|
||||
|
||||
disabled_by: RegistryEntryDisabler | None = None
|
||||
if not entity.entity_registry_enabled_default:
|
||||
@@ -868,7 +881,6 @@ class EntityPlatform:
|
||||
self.domain,
|
||||
self.platform_name,
|
||||
entity.unique_id,
|
||||
calculated_object_id=calculated_object_id,
|
||||
capabilities=entity.capability_attributes,
|
||||
config_entry=self.config_entry,
|
||||
config_subentry_id=config_subentry_id,
|
||||
@@ -1112,27 +1124,6 @@ class EntityPlatform:
|
||||
await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
@callback
|
||||
def async_calculate_suggested_object_id(
|
||||
entity: Entity, device: dev_reg.DeviceEntry | None
|
||||
) -> str | None:
|
||||
"""Calculate the suggested object ID for an entity."""
|
||||
calculated_object_id: str | None = None
|
||||
if device and entity.has_entity_name:
|
||||
device_name = device.name_by_user or device.name
|
||||
if entity.use_device_name:
|
||||
calculated_object_id = device_name
|
||||
else:
|
||||
calculated_object_id = f"{device_name} {entity.suggested_object_id}"
|
||||
if not calculated_object_id:
|
||||
calculated_object_id = entity.suggested_object_id
|
||||
|
||||
if (platform := entity.platform) and platform.entity_namespace is not None:
|
||||
calculated_object_id = f"{platform.entity_namespace} {calculated_object_id}"
|
||||
|
||||
return calculated_object_id
|
||||
|
||||
|
||||
current_platform: ContextVar[EntityPlatform | None] = ContextVar(
|
||||
"current_platform", default=None
|
||||
)
|
||||
|
||||
@@ -79,7 +79,7 @@ EVENT_ENTITY_REGISTRY_UPDATED: EventType[EventEntityRegistryUpdatedData] = Event
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 17
|
||||
STORAGE_VERSION_MINOR = 16
|
||||
STORAGE_KEY = "core.entity_registry"
|
||||
|
||||
CLEANUP_INTERVAL = 3600 * 24
|
||||
@@ -195,11 +195,9 @@ class RegistryEntry:
|
||||
name: str | None = attr.ib(default=None)
|
||||
options: ReadOnlyEntityOptionsType = attr.ib(converter=_protect_entity_options)
|
||||
# As set by integration
|
||||
calculated_object_id: str | None = attr.ib()
|
||||
original_device_class: str | None = attr.ib()
|
||||
original_icon: str | None = attr.ib()
|
||||
original_name: str | None = attr.ib()
|
||||
suggested_object_id: str | None = attr.ib()
|
||||
supported_features: int = attr.ib()
|
||||
translation_key: str | None = attr.ib()
|
||||
unit_of_measurement: str | None = attr.ib()
|
||||
@@ -339,7 +337,6 @@ class RegistryEntry:
|
||||
{
|
||||
"aliases": list(self.aliases),
|
||||
"area_id": self.area_id,
|
||||
"calculated_object_id": self.calculated_object_id,
|
||||
"categories": self.categories,
|
||||
"capabilities": self.capabilities,
|
||||
"config_entry_id": self.config_entry_id,
|
||||
@@ -362,7 +359,6 @@ class RegistryEntry:
|
||||
"original_icon": self.original_icon,
|
||||
"original_name": self.original_name,
|
||||
"platform": self.platform,
|
||||
"suggested_object_id": self.suggested_object_id,
|
||||
"supported_features": self.supported_features,
|
||||
"translation_key": self.translation_key,
|
||||
"unique_id": self.unique_id,
|
||||
@@ -552,12 +548,6 @@ class EntityRegistryStore(storage.Store[dict[str, list[dict[str, Any]]]]):
|
||||
for entity in data["deleted_entities"]:
|
||||
entity["config_subentry_id"] = None
|
||||
|
||||
if old_minor_version < 17:
|
||||
# Version 1.17 adds calculated_object_id and suggested_object_id
|
||||
for entity in data["entities"]:
|
||||
entity["calculated_object_id"] = None
|
||||
entity["suggested_object_id"] = None
|
||||
|
||||
if old_major_version > 1:
|
||||
raise NotImplementedError
|
||||
return data
|
||||
@@ -846,7 +836,6 @@ class EntityRegistry(BaseRegistry):
|
||||
unique_id: str,
|
||||
*,
|
||||
# To influence entity ID generation
|
||||
calculated_object_id: str | None = None,
|
||||
suggested_object_id: str | None = None,
|
||||
# To disable or hide an entity if it gets created
|
||||
disabled_by: RegistryEntryDisabler | None = None,
|
||||
@@ -919,7 +908,7 @@ class EntityRegistry(BaseRegistry):
|
||||
|
||||
entity_id = self.async_generate_entity_id(
|
||||
domain,
|
||||
suggested_object_id or calculated_object_id or f"{platform}_{unique_id}",
|
||||
suggested_object_id or f"{platform}_{unique_id}",
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -953,8 +942,6 @@ class EntityRegistry(BaseRegistry):
|
||||
original_icon=none_if_undefined(original_icon),
|
||||
original_name=none_if_undefined(original_name),
|
||||
platform=platform,
|
||||
calculated_object_id=calculated_object_id,
|
||||
suggested_object_id=suggested_object_id,
|
||||
supported_features=none_if_undefined(supported_features) or 0,
|
||||
translation_key=none_if_undefined(translation_key),
|
||||
unique_id=unique_id,
|
||||
@@ -1363,7 +1350,6 @@ class EntityRegistry(BaseRegistry):
|
||||
entities[entity["entity_id"]] = RegistryEntry(
|
||||
aliases=set(entity["aliases"]),
|
||||
area_id=entity["area_id"],
|
||||
calculated_object_id=entity["calculated_object_id"],
|
||||
categories=entity["categories"],
|
||||
capabilities=entity["capabilities"],
|
||||
config_entry_id=entity["config_entry_id"],
|
||||
@@ -1392,7 +1378,6 @@ class EntityRegistry(BaseRegistry):
|
||||
original_icon=entity["original_icon"],
|
||||
original_name=entity["original_name"],
|
||||
platform=entity["platform"],
|
||||
suggested_object_id=entity["suggested_object_id"],
|
||||
supported_features=entity["supported_features"],
|
||||
translation_key=entity["translation_key"],
|
||||
unique_id=entity["unique_id"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Automatically generated by gen_requirements_all.py, do not edit
|
||||
|
||||
aiodhcpwatcher==1.1.1
|
||||
aiodhcpwatcher==1.2.0
|
||||
aiodiscover==2.7.0
|
||||
aiodns==3.4.0
|
||||
aiohasupervisor==0.3.1
|
||||
|
||||
Generated
+8
-8
@@ -211,10 +211,10 @@ aiobafi6==0.9.0
|
||||
aiobotocore==2.21.1
|
||||
|
||||
# homeassistant.components.comelit
|
||||
aiocomelit==0.12.0
|
||||
aiocomelit==0.12.1
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodhcpwatcher==1.1.1
|
||||
aiodhcpwatcher==1.2.0
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodiscover==2.7.0
|
||||
@@ -542,7 +542,7 @@ aurorapy==0.2.7
|
||||
autarco==3.1.0
|
||||
|
||||
# homeassistant.components.husqvarna_automower_ble
|
||||
automower-ble==0.2.0
|
||||
automower-ble==0.2.1
|
||||
|
||||
# homeassistant.components.generic
|
||||
# homeassistant.components.stream
|
||||
@@ -750,7 +750,7 @@ datapoint==0.9.9
|
||||
dbus-fast==2.43.0
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.8.13
|
||||
debugpy==1.8.14
|
||||
|
||||
# homeassistant.components.decora_wifi
|
||||
# decora-wifi==1.4
|
||||
@@ -983,7 +983,7 @@ gardena-bluetooth==1.6.0
|
||||
gassist-text==0.0.12
|
||||
|
||||
# homeassistant.components.google
|
||||
gcal-sync==7.0.0
|
||||
gcal-sync==7.0.1
|
||||
|
||||
# homeassistant.components.geniushub
|
||||
geniushub-client==0.7.1
|
||||
@@ -1197,7 +1197,7 @@ ibmiotf==0.3.4
|
||||
# homeassistant.components.local_calendar
|
||||
# homeassistant.components.local_todo
|
||||
# homeassistant.components.remote_calendar
|
||||
ical==9.2.1
|
||||
ical==9.2.2
|
||||
|
||||
# homeassistant.components.caldav
|
||||
icalendar==6.1.0
|
||||
@@ -2132,7 +2132,7 @@ pymeteoclimatic==0.1.0
|
||||
pymicro-vad==1.0.1
|
||||
|
||||
# homeassistant.components.miele
|
||||
pymiele==0.5.1
|
||||
pymiele==0.5.2
|
||||
|
||||
# homeassistant.components.xiaomi_tv
|
||||
pymitv==1.4.3
|
||||
@@ -3101,7 +3101,7 @@ xbox-webapi==2.1.0
|
||||
xiaomi-ble==0.38.0
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==3.6.0
|
||||
xknx==3.8.0
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.8.2
|
||||
|
||||
@@ -18,7 +18,7 @@ pre-commit==4.0.0
|
||||
pydantic==2.11.3
|
||||
pylint==3.3.7
|
||||
pylint-per-file-ignores==1.4.0
|
||||
pipdeptree==2.25.1
|
||||
pipdeptree==2.26.1
|
||||
pytest-asyncio==0.26.0
|
||||
pytest-aiohttp==1.1.0
|
||||
pytest-cov==6.0.0
|
||||
|
||||
Generated
+8
-8
@@ -199,10 +199,10 @@ aiobafi6==0.9.0
|
||||
aiobotocore==2.21.1
|
||||
|
||||
# homeassistant.components.comelit
|
||||
aiocomelit==0.12.0
|
||||
aiocomelit==0.12.1
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodhcpwatcher==1.1.1
|
||||
aiodhcpwatcher==1.2.0
|
||||
|
||||
# homeassistant.components.dhcp
|
||||
aiodiscover==2.7.0
|
||||
@@ -497,7 +497,7 @@ aurorapy==0.2.7
|
||||
autarco==3.1.0
|
||||
|
||||
# homeassistant.components.husqvarna_automower_ble
|
||||
automower-ble==0.2.0
|
||||
automower-ble==0.2.1
|
||||
|
||||
# homeassistant.components.generic
|
||||
# homeassistant.components.stream
|
||||
@@ -647,7 +647,7 @@ datapoint==0.9.9
|
||||
dbus-fast==2.43.0
|
||||
|
||||
# homeassistant.components.debugpy
|
||||
debugpy==1.8.13
|
||||
debugpy==1.8.14
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
deebot-client==13.1.0
|
||||
@@ -837,7 +837,7 @@ gardena-bluetooth==1.6.0
|
||||
gassist-text==0.0.12
|
||||
|
||||
# homeassistant.components.google
|
||||
gcal-sync==7.0.0
|
||||
gcal-sync==7.0.1
|
||||
|
||||
# homeassistant.components.geniushub
|
||||
geniushub-client==0.7.1
|
||||
@@ -1018,7 +1018,7 @@ ibeacon-ble==1.2.0
|
||||
# homeassistant.components.local_calendar
|
||||
# homeassistant.components.local_todo
|
||||
# homeassistant.components.remote_calendar
|
||||
ical==9.2.1
|
||||
ical==9.2.2
|
||||
|
||||
# homeassistant.components.caldav
|
||||
icalendar==6.1.0
|
||||
@@ -1744,7 +1744,7 @@ pymeteoclimatic==0.1.0
|
||||
pymicro-vad==1.0.1
|
||||
|
||||
# homeassistant.components.miele
|
||||
pymiele==0.5.1
|
||||
pymiele==0.5.2
|
||||
|
||||
# homeassistant.components.mochad
|
||||
pymochad==0.2.0
|
||||
@@ -2509,7 +2509,7 @@ xbox-webapi==2.1.0
|
||||
xiaomi-ble==0.38.0
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknx==3.6.0
|
||||
xknx==3.8.0
|
||||
|
||||
# homeassistant.components.knx
|
||||
xknxproject==3.8.2
|
||||
|
||||
Generated
+1
-1
@@ -24,7 +24,7 @@ RUN --mount=from=ghcr.io/astral-sh/uv:0.7.1,source=/uv,target=/bin/uv \
|
||||
--no-cache \
|
||||
-c /usr/src/homeassistant/homeassistant/package_constraints.txt \
|
||||
-r /usr/src/homeassistant/requirements.txt \
|
||||
stdlib-list==0.10.0 pipdeptree==2.25.1 tqdm==4.67.1 ruff==0.11.0 \
|
||||
stdlib-list==0.10.0 pipdeptree==2.26.1 tqdm==4.67.1 ruff==0.11.0 \
|
||||
PyTurboJPEG==1.7.5 go2rtc-client==0.1.2 ha-ffmpeg==3.2.2 hassil==2.2.3 home-assistant-intents==2025.5.7 mutagen==1.47.0 pymicro-vad==1.0.1 pyspeex-noise==1.0.2
|
||||
|
||||
LABEL "name"="hassfest"
|
||||
|
||||
@@ -651,7 +651,6 @@ class RegistryEntryWithDefaults(er.RegistryEntry):
|
||||
"""Helper to create a registry entry with defaults."""
|
||||
|
||||
capabilities: Mapping[str, Any] | None = attr.ib(default=None)
|
||||
calculated_object_id: str | None = attr.ib(default=None)
|
||||
config_entry_id: str | None = attr.ib(default=None)
|
||||
config_subentry_id: str | None = attr.ib(default=None)
|
||||
created_at: datetime = attr.ib(factory=dt_util.utcnow)
|
||||
@@ -670,7 +669,6 @@ class RegistryEntryWithDefaults(er.RegistryEntry):
|
||||
original_device_class: str | None = attr.ib(default=None)
|
||||
original_icon: str | None = attr.ib(default=None)
|
||||
original_name: str | None = attr.ib(default=None)
|
||||
suggested_object_id: str | None = attr.ib(default=None)
|
||||
supported_features: int = attr.ib(default=0)
|
||||
translation_key: str | None = attr.ib(default=None)
|
||||
unit_of_measurement: str | None = attr.ib(default=None)
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Timer running',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Timer running',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'timer_running',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_timer_running',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Reset timer',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Reset timer',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_timer',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_reset_timer',
|
||||
@@ -53,7 +51,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Start/stop timer',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -77,7 +74,6 @@
|
||||
'original_name': 'Start/stop timer',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'start_stop',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_start_stop',
|
||||
@@ -102,7 +98,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Tare',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -126,7 +121,6 @@
|
||||
'original_name': 'Tare',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tare',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_tare',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Battery',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_battery',
|
||||
@@ -58,7 +56,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Volume flow rate',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -87,7 +84,6 @@
|
||||
'original_name': 'Volume flow rate',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_flow_rate',
|
||||
@@ -115,7 +111,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'LUNAR-DDEEFF Weight',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -141,7 +136,6 @@
|
||||
'original_name': 'Weight',
|
||||
'platform': 'acaia',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_weight',
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -245,7 +245,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -269,7 +268,6 @@
|
||||
'original_name': None,
|
||||
'platform': 'accuweather',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <WeatherEntityFeature: 1>,
|
||||
'translation_key': None,
|
||||
'unique_id': '0123456',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Calibrate CO2 sensor',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Calibrate CO2 sensor',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_calibration',
|
||||
'unique_id': '84fce612f5b8-co2_calibration',
|
||||
@@ -53,7 +51,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Test LED bar',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -77,7 +74,6 @@
|
||||
'original_name': 'Test LED bar',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_bar_test',
|
||||
'unique_id': '84fce612f5b8-led_bar_test',
|
||||
@@ -102,7 +98,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Calibrate CO2 sensor',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -126,7 +121,6 @@
|
||||
'original_name': 'Calibrate CO2 sensor',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_calibration',
|
||||
'unique_id': '84fce612f5b8-co2_calibration',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display brightness',
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
@@ -33,7 +32,6 @@
|
||||
'original_name': 'Display brightness',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_brightness',
|
||||
'unique_id': '84fce612f5b8-display_brightness',
|
||||
@@ -63,7 +61,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient LED bar brightness',
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
@@ -92,7 +89,6 @@
|
||||
'original_name': 'LED bar brightness',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_bar_brightness',
|
||||
'unique_id': '84fce612f5b8-led_bar_brightness',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient CO2 automatic baseline duration',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'1',
|
||||
@@ -37,7 +36,6 @@
|
||||
'original_name': 'CO2 automatic baseline duration',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_automatic_baseline_calibration',
|
||||
'unique_id': '84fce612f5b8-co2_automatic_baseline_calibration',
|
||||
@@ -70,7 +68,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Configuration source',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'cloud',
|
||||
@@ -99,7 +96,6 @@
|
||||
'original_name': 'Configuration source',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'configuration_control',
|
||||
'unique_id': '84fce612f5b8-configuration_control',
|
||||
@@ -128,7 +124,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display PM standard',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'ugm3',
|
||||
@@ -157,7 +152,6 @@
|
||||
'original_name': 'Display PM standard',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_pm_standard',
|
||||
'unique_id': '84fce612f5b8-display_pm_standard',
|
||||
@@ -186,7 +180,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display temperature unit',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'c',
|
||||
@@ -215,7 +208,6 @@
|
||||
'original_name': 'Display temperature unit',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_temperature_unit',
|
||||
'unique_id': '84fce612f5b8-display_temperature_unit',
|
||||
@@ -244,7 +236,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient LED bar mode',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'off',
|
||||
@@ -274,7 +265,6 @@
|
||||
'original_name': 'LED bar mode',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_bar_mode',
|
||||
'unique_id': '84fce612f5b8-led_bar_mode',
|
||||
@@ -304,7 +294,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index learning offset',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'12',
|
||||
@@ -336,7 +325,6 @@
|
||||
'original_name': 'NOx index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nox_index_learning_time_offset',
|
||||
'unique_id': '84fce612f5b8-nox_index_learning_time_offset',
|
||||
@@ -368,7 +356,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index learning offset',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'12',
|
||||
@@ -400,7 +387,6 @@
|
||||
'original_name': 'VOC index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'voc_index_learning_time_offset',
|
||||
'unique_id': '84fce612f5b8-voc_index_learning_time_offset',
|
||||
@@ -432,7 +418,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient CO2 automatic baseline duration',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'1',
|
||||
@@ -465,7 +450,6 @@
|
||||
'original_name': 'CO2 automatic baseline duration',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_automatic_baseline_calibration',
|
||||
'unique_id': '84fce612f5b8-co2_automatic_baseline_calibration',
|
||||
@@ -498,7 +482,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Configuration source',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'cloud',
|
||||
@@ -527,7 +510,6 @@
|
||||
'original_name': 'Configuration source',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'configuration_control',
|
||||
'unique_id': '84fce612f5b8-configuration_control',
|
||||
@@ -556,7 +538,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index learning offset',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'12',
|
||||
@@ -588,7 +569,6 @@
|
||||
'original_name': 'NOx index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nox_index_learning_time_offset',
|
||||
'unique_id': '84fce612f5b8-nox_index_learning_time_offset',
|
||||
@@ -620,7 +600,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index learning offset',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'12',
|
||||
@@ -652,7 +631,6 @@
|
||||
'original_name': 'VOC index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'voc_index_learning_time_offset',
|
||||
'unique_id': '84fce612f5b8-voc_index_learning_time_offset',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Carbon dioxide',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'Carbon dioxide',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-co2',
|
||||
@@ -58,7 +56,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Carbon dioxide automatic baseline calibration',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -82,7 +79,6 @@
|
||||
'original_name': 'Carbon dioxide automatic baseline calibration',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_automatic_baseline_calibration_days',
|
||||
'unique_id': '84fce612f5b8-co2_automatic_baseline_calibration_days',
|
||||
@@ -109,7 +105,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display brightness',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -133,7 +128,6 @@
|
||||
'original_name': 'Display brightness',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_brightness',
|
||||
'unique_id': '84fce612f5b8-display_brightness',
|
||||
@@ -159,7 +153,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display PM standard',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'ugm3',
|
||||
@@ -188,7 +181,6 @@
|
||||
'original_name': 'Display PM standard',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_pm_standard',
|
||||
'unique_id': '84fce612f5b8-display_pm_standard',
|
||||
@@ -218,7 +210,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Display temperature unit',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'c',
|
||||
@@ -247,7 +238,6 @@
|
||||
'original_name': 'Display temperature unit',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'display_temperature_unit',
|
||||
'unique_id': '84fce612f5b8-display_temperature_unit',
|
||||
@@ -277,7 +267,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -303,7 +292,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-humidity',
|
||||
@@ -331,7 +319,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient LED bar brightness',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -355,7 +342,6 @@
|
||||
'original_name': 'LED bar brightness',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_bar_brightness',
|
||||
'unique_id': '84fce612f5b8-led_bar_brightness',
|
||||
@@ -381,7 +367,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient LED bar mode',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'off',
|
||||
@@ -411,7 +396,6 @@
|
||||
'original_name': 'LED bar mode',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_bar_mode',
|
||||
'unique_id': '84fce612f5b8-led_bar_mode',
|
||||
@@ -442,7 +426,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -468,7 +451,6 @@
|
||||
'original_name': 'NOx index',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nitrogen_index',
|
||||
'unique_id': '84fce612f5b8-nitrogen_index',
|
||||
@@ -494,7 +476,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index learning offset',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -518,7 +499,6 @@
|
||||
'original_name': 'NOx index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nox_learning_offset',
|
||||
'unique_id': '84fce612f5b8-nox_learning_offset',
|
||||
@@ -545,7 +525,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient PM0.3',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -571,7 +550,6 @@
|
||||
'original_name': 'PM0.3',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'pm003_count',
|
||||
'unique_id': '84fce612f5b8-pm003',
|
||||
@@ -598,7 +576,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient PM1',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -624,7 +601,6 @@
|
||||
'original_name': 'PM1',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-pm01',
|
||||
@@ -652,7 +628,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient PM10',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -678,7 +653,6 @@
|
||||
'original_name': 'PM10',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-pm10',
|
||||
@@ -706,7 +680,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient PM2.5',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -732,7 +705,6 @@
|
||||
'original_name': 'PM2.5',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-pm02',
|
||||
@@ -760,7 +732,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Raw NOx',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -786,7 +757,6 @@
|
||||
'original_name': 'Raw NOx',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'raw_nitrogen',
|
||||
'unique_id': '84fce612f5b8-nox_raw',
|
||||
@@ -813,7 +783,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Raw PM2.5',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -839,7 +808,6 @@
|
||||
'original_name': 'Raw PM2.5',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'raw_pm02',
|
||||
'unique_id': '84fce612f5b8-pm02_raw',
|
||||
@@ -867,7 +835,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Raw VOC',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -893,7 +860,6 @@
|
||||
'original_name': 'Raw VOC',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'raw_total_volatile_organic_component',
|
||||
'unique_id': '84fce612f5b8-tvoc_raw',
|
||||
@@ -920,7 +886,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -946,7 +911,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-signal_strength',
|
||||
@@ -974,7 +938,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1000,7 +963,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-temperature',
|
||||
@@ -1028,7 +990,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1054,7 +1015,6 @@
|
||||
'original_name': 'VOC index',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_volatile_organic_component_index',
|
||||
'unique_id': '84fce612f5b8-tvoc',
|
||||
@@ -1080,7 +1040,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index learning offset',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1104,7 +1063,6 @@
|
||||
'original_name': 'VOC index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tvoc_learning_offset',
|
||||
'unique_id': '84fce612f5b8-tvoc_learning_offset',
|
||||
@@ -1131,7 +1089,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Carbon dioxide automatic baseline calibration',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1155,7 +1112,6 @@
|
||||
'original_name': 'Carbon dioxide automatic baseline calibration',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co2_automatic_baseline_calibration_days',
|
||||
'unique_id': '84fce612f5b8-co2_automatic_baseline_calibration_days',
|
||||
@@ -1182,7 +1138,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1208,7 +1163,6 @@
|
||||
'original_name': 'NOx index',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nitrogen_index',
|
||||
'unique_id': '84fce612f5b8-nitrogen_index',
|
||||
@@ -1234,7 +1188,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient NOx index learning offset',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1258,7 +1211,6 @@
|
||||
'original_name': 'NOx index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'nox_learning_offset',
|
||||
'unique_id': '84fce612f5b8-nox_learning_offset',
|
||||
@@ -1285,7 +1237,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Raw NOx',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1311,7 +1262,6 @@
|
||||
'original_name': 'Raw NOx',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'raw_nitrogen',
|
||||
'unique_id': '84fce612f5b8-nox_raw',
|
||||
@@ -1338,7 +1288,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Raw VOC',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1364,7 +1313,6 @@
|
||||
'original_name': 'Raw VOC',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'raw_total_volatile_organic_component',
|
||||
'unique_id': '84fce612f5b8-tvoc_raw',
|
||||
@@ -1391,7 +1339,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1417,7 +1364,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-signal_strength',
|
||||
@@ -1445,7 +1391,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1471,7 +1416,6 @@
|
||||
'original_name': 'VOC index',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_volatile_organic_component_index',
|
||||
'unique_id': '84fce612f5b8-tvoc',
|
||||
@@ -1497,7 +1441,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient VOC index learning offset',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1521,7 +1464,6 @@
|
||||
'original_name': 'VOC index learning offset',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tvoc_learning_offset',
|
||||
'unique_id': '84fce612f5b8-tvoc_learning_offset',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Post data to Airgradient',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Post data to Airgradient',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'post_data_to_airgradient',
|
||||
'unique_id': '84fce612f5b8-post_data_to_airgradient',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airgradient Firmware',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Firmware',
|
||||
'platform': 'airgradient',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '84fce612f5b8-update',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Carbon monoxide',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -33,7 +32,6 @@
|
||||
'original_name': 'Carbon monoxide',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'co',
|
||||
'unique_id': '123-456-co',
|
||||
@@ -63,7 +61,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Common air quality index',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -90,7 +87,6 @@
|
||||
'original_name': 'Common air quality index',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'caqi',
|
||||
'unique_id': '123-456-caqi',
|
||||
@@ -120,7 +116,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -149,7 +144,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-humidity',
|
||||
@@ -178,7 +172,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Nitrogen dioxide',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -207,7 +200,6 @@
|
||||
'original_name': 'Nitrogen dioxide',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-no2',
|
||||
@@ -238,7 +230,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Ozone',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -267,7 +258,6 @@
|
||||
'original_name': 'Ozone',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-o3',
|
||||
@@ -298,7 +288,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home PM1',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -327,7 +316,6 @@
|
||||
'original_name': 'PM1',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-pm1',
|
||||
@@ -356,7 +344,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home PM10',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -385,7 +372,6 @@
|
||||
'original_name': 'PM10',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-pm10',
|
||||
@@ -416,7 +402,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home PM2.5',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -445,7 +430,6 @@
|
||||
'original_name': 'PM2.5',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-pm25',
|
||||
@@ -476,7 +460,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -505,7 +488,6 @@
|
||||
'original_name': 'Pressure',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-pressure',
|
||||
@@ -534,7 +516,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Sulphur dioxide',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -563,7 +544,6 @@
|
||||
'original_name': 'Sulphur dioxide',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-so2',
|
||||
@@ -594,7 +574,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Home Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -623,7 +602,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airly',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123-456-temperature',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Zone 1 Damper',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Damper',
|
||||
'platform': 'airtouch5',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 7>,
|
||||
'translation_key': 'damper',
|
||||
'unique_id': 'zone_1_open_percentage',
|
||||
@@ -56,7 +54,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Zone 2 Damper',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -80,7 +77,6 @@
|
||||
'original_name': 'Damper',
|
||||
'platform': 'airtouch5',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <CoverEntityFeature: 7>,
|
||||
'translation_key': 'damper',
|
||||
'unique_id': 'zone_2_open_percentage',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airzone 2:1 Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_2:1_humidity',
|
||||
@@ -58,7 +56,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airzone 2:1 Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -84,7 +81,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_2:1_temp',
|
||||
@@ -112,7 +108,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airzone DHW Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -138,7 +133,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_dhw_temp',
|
||||
@@ -166,7 +160,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Airzone WebServer RSSI',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -192,7 +185,6 @@
|
||||
'original_name': 'RSSI',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'rssi',
|
||||
'unique_id': 'airzone_unique_id_ws_wifi-rssi',
|
||||
@@ -220,7 +212,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Aux Heat Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -246,7 +237,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_4:1_temp',
|
||||
@@ -274,7 +264,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Despacho Battery',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -300,7 +289,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:4_thermostat-battery',
|
||||
@@ -328,7 +316,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Despacho Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -354,7 +341,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:4_humidity',
|
||||
@@ -382,7 +368,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Despacho Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -408,7 +393,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'thermostat_signal',
|
||||
'unique_id': 'airzone_unique_id_1:4_thermostat-signal',
|
||||
@@ -435,7 +419,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Despacho Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -461,7 +444,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:4_temp',
|
||||
@@ -489,7 +471,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'DKN Plus Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -515,7 +496,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_3:1_temp',
|
||||
@@ -543,7 +523,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #1 Battery',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -569,7 +548,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:3_thermostat-battery',
|
||||
@@ -597,7 +575,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #1 Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -623,7 +600,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:3_humidity',
|
||||
@@ -651,7 +627,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #1 Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -677,7 +652,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'thermostat_signal',
|
||||
'unique_id': 'airzone_unique_id_1:3_thermostat-signal',
|
||||
@@ -704,7 +678,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #1 Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -730,7 +703,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:3_temp',
|
||||
@@ -758,7 +730,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #2 Battery',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -784,7 +755,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:5_thermostat-battery',
|
||||
@@ -812,7 +782,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #2 Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -838,7 +807,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:5_humidity',
|
||||
@@ -866,7 +834,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #2 Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -892,7 +859,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'thermostat_signal',
|
||||
'unique_id': 'airzone_unique_id_1:5_thermostat-signal',
|
||||
@@ -919,7 +885,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm #2 Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -945,7 +910,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:5_temp',
|
||||
@@ -973,7 +937,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm Ppal Battery',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -999,7 +962,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:2_thermostat-battery',
|
||||
@@ -1027,7 +989,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm Ppal Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1053,7 +1014,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:2_humidity',
|
||||
@@ -1081,7 +1041,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm Ppal Signal strength',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1107,7 +1066,6 @@
|
||||
'original_name': 'Signal strength',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'thermostat_signal',
|
||||
'unique_id': 'airzone_unique_id_1:2_thermostat-signal',
|
||||
@@ -1134,7 +1092,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Dorm Ppal Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1160,7 +1117,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:2_temp',
|
||||
@@ -1188,7 +1144,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Salon Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1214,7 +1169,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:1_humidity',
|
||||
@@ -1242,7 +1196,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Salon Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1268,7 +1221,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'airzone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'airzone_unique_id_1:1_temp',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Absolute pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -36,7 +35,6 @@
|
||||
'original_name': 'Absolute pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'absolute_pressure',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_baromabsin',
|
||||
@@ -66,7 +64,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Daily rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -98,7 +95,6 @@
|
||||
'original_name': 'Daily rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'daily_rain',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_dailyrainin',
|
||||
@@ -128,7 +124,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Dew point',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -157,7 +152,6 @@
|
||||
'original_name': 'Dew point',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dew_point',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_dewPoint',
|
||||
@@ -187,7 +181,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Feels like',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -216,7 +209,6 @@
|
||||
'original_name': 'Feels like',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'feels_like',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_feelsLike',
|
||||
@@ -246,7 +238,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Hourly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -278,7 +269,6 @@
|
||||
'original_name': 'Hourly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hourly_rain',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_hourlyrainin',
|
||||
@@ -308,7 +298,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -337,7 +326,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_humidity',
|
||||
@@ -367,7 +355,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Irradiance',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -396,7 +383,6 @@
|
||||
'original_name': 'Irradiance',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_solarradiation',
|
||||
@@ -426,7 +412,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Last rain',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -450,7 +435,6 @@
|
||||
'original_name': 'Last rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'last_rain',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_lastRain',
|
||||
@@ -478,7 +462,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Max daily gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -510,7 +493,6 @@
|
||||
'original_name': 'Max daily gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_daily_gust',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_maxdailygust',
|
||||
@@ -540,7 +522,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Monthly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -572,7 +553,6 @@
|
||||
'original_name': 'Monthly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'monthly_rain',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_monthlyrainin',
|
||||
@@ -602,7 +582,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Relative pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -634,7 +613,6 @@
|
||||
'original_name': 'Relative pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'relative_pressure',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_baromrelin',
|
||||
@@ -664,7 +642,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -693,7 +670,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_tempf',
|
||||
@@ -723,7 +699,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A UV index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -752,7 +727,6 @@
|
||||
'original_name': 'UV index',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uv_index',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_uv',
|
||||
@@ -781,7 +755,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Weekly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -813,7 +786,6 @@
|
||||
'original_name': 'Weekly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'weekly_rain',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_weeklyrainin',
|
||||
@@ -843,7 +815,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Wind direction',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT_ANGLE: 'measurement_angle'>,
|
||||
}),
|
||||
@@ -872,7 +843,6 @@
|
||||
'original_name': 'Wind direction',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_direction',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_winddir',
|
||||
@@ -902,7 +872,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Wind gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -934,7 +903,6 @@
|
||||
'original_name': 'Wind gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_gust',
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_windgustmph',
|
||||
@@ -964,7 +932,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station A Wind speed',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -996,7 +963,6 @@
|
||||
'original_name': 'Wind speed',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'AA:AA:AA:AA:AA:AA_windspeedmph',
|
||||
@@ -1026,7 +992,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Absolute pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1058,7 +1023,6 @@
|
||||
'original_name': 'Absolute pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'absolute_pressure',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_baromabsin',
|
||||
@@ -1088,7 +1052,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Daily rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -1120,7 +1083,6 @@
|
||||
'original_name': 'Daily rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'daily_rain',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_dailyrainin',
|
||||
@@ -1150,7 +1112,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Dew point',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1179,7 +1140,6 @@
|
||||
'original_name': 'Dew point',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dew_point',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_dewPoint',
|
||||
@@ -1209,7 +1169,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Feels like',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1238,7 +1197,6 @@
|
||||
'original_name': 'Feels like',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'feels_like',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_feelsLike',
|
||||
@@ -1268,7 +1226,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Hourly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1300,7 +1257,6 @@
|
||||
'original_name': 'Hourly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hourly_rain',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_hourlyrainin',
|
||||
@@ -1330,7 +1286,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1359,7 +1314,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_humidity',
|
||||
@@ -1389,7 +1343,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Irradiance',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1418,7 +1371,6 @@
|
||||
'original_name': 'Irradiance',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_solarradiation',
|
||||
@@ -1448,7 +1400,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Last rain',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -1472,7 +1423,6 @@
|
||||
'original_name': 'Last rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'last_rain',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_lastRain',
|
||||
@@ -1500,7 +1450,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Max daily gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1532,7 +1481,6 @@
|
||||
'original_name': 'Max daily gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_daily_gust',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_maxdailygust',
|
||||
@@ -1562,7 +1510,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Monthly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -1594,7 +1541,6 @@
|
||||
'original_name': 'Monthly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'monthly_rain',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_monthlyrainin',
|
||||
@@ -1624,7 +1570,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Relative pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1656,7 +1601,6 @@
|
||||
'original_name': 'Relative pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'relative_pressure',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_baromrelin',
|
||||
@@ -1686,7 +1630,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1715,7 +1658,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_tempf',
|
||||
@@ -1745,7 +1687,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C UV index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1774,7 +1715,6 @@
|
||||
'original_name': 'UV index',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uv_index',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_uv',
|
||||
@@ -1803,7 +1743,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Weekly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -1835,7 +1774,6 @@
|
||||
'original_name': 'Weekly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'weekly_rain',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_weeklyrainin',
|
||||
@@ -1865,7 +1803,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Wind direction',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT_ANGLE: 'measurement_angle'>,
|
||||
}),
|
||||
@@ -1894,7 +1831,6 @@
|
||||
'original_name': 'Wind direction',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_direction',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_winddir',
|
||||
@@ -1924,7 +1860,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Wind gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -1956,7 +1891,6 @@
|
||||
'original_name': 'Wind gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_gust',
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_windgustmph',
|
||||
@@ -1986,7 +1920,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station C Wind speed',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2018,7 +1951,6 @@
|
||||
'original_name': 'Wind speed',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'CC:CC:CC:CC:CC:CC_windspeedmph',
|
||||
@@ -2048,7 +1980,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Absolute pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2080,7 +2011,6 @@
|
||||
'original_name': 'Absolute pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'absolute_pressure',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_baromabsin',
|
||||
@@ -2109,7 +2039,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Daily rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -2141,7 +2070,6 @@
|
||||
'original_name': 'Daily rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'daily_rain',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_dailyrainin',
|
||||
@@ -2170,7 +2098,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Dew point',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2199,7 +2126,6 @@
|
||||
'original_name': 'Dew point',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dew_point',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_dewPoint',
|
||||
@@ -2228,7 +2154,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Feels like',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2257,7 +2182,6 @@
|
||||
'original_name': 'Feels like',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'feels_like',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_feelsLike',
|
||||
@@ -2286,7 +2210,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Hourly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2318,7 +2241,6 @@
|
||||
'original_name': 'Hourly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hourly_rain',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_hourlyrainin',
|
||||
@@ -2347,7 +2269,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2376,7 +2297,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_humidity',
|
||||
@@ -2405,7 +2325,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Irradiance',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2434,7 +2353,6 @@
|
||||
'original_name': 'Irradiance',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_solarradiation',
|
||||
@@ -2463,7 +2381,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Max daily gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2495,7 +2412,6 @@
|
||||
'original_name': 'Max daily gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_daily_gust',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_maxdailygust',
|
||||
@@ -2524,7 +2440,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Monthly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -2556,7 +2471,6 @@
|
||||
'original_name': 'Monthly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'monthly_rain',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_monthlyrainin',
|
||||
@@ -2585,7 +2499,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Relative pressure',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2617,7 +2530,6 @@
|
||||
'original_name': 'Relative pressure',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'relative_pressure',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_baromrelin',
|
||||
@@ -2646,7 +2558,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2675,7 +2586,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_tempf',
|
||||
@@ -2704,7 +2614,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D UV index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2733,7 +2642,6 @@
|
||||
'original_name': 'UV index',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'uv_index',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_uv',
|
||||
@@ -2761,7 +2669,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Weekly rain',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -2793,7 +2700,6 @@
|
||||
'original_name': 'Weekly rain',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'weekly_rain',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_weeklyrainin',
|
||||
@@ -2822,7 +2728,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Wind direction',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT_ANGLE: 'measurement_angle'>,
|
||||
}),
|
||||
@@ -2851,7 +2756,6 @@
|
||||
'original_name': 'Wind direction',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_direction',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_winddir',
|
||||
@@ -2880,7 +2784,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Wind gust',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2912,7 +2815,6 @@
|
||||
'original_name': 'Wind gust',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wind_gust',
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_windgustmph',
|
||||
@@ -2941,7 +2843,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Station D Wind speed',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -2973,7 +2874,6 @@
|
||||
'original_name': 'Wind speed',
|
||||
'platform': 'ambient_network',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'DD:DD:DD:DD:DD:DD_windspeedmph',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics core_samba',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'core_samba',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'addons',
|
||||
'unique_id': 'addon_core_samba_active_installations',
|
||||
@@ -57,7 +55,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics hacs (custom)',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -83,7 +80,6 @@
|
||||
'original_name': 'hacs (custom)',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'custom_integrations',
|
||||
'unique_id': 'custom_hacs_active_installations',
|
||||
@@ -110,7 +106,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics myq',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -136,7 +131,6 @@
|
||||
'original_name': 'myq',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'core_integrations',
|
||||
'unique_id': 'core_myq_active_installations',
|
||||
@@ -163,7 +157,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics spotify',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -189,7 +182,6 @@
|
||||
'original_name': 'spotify',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'core_integrations',
|
||||
'unique_id': 'core_spotify_active_installations',
|
||||
@@ -216,7 +208,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics Total active installations',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -242,7 +233,6 @@
|
||||
'original_name': 'Total active installations',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_active_installations',
|
||||
'unique_id': 'total_active_installations',
|
||||
@@ -269,7 +259,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics Total reported integrations',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -295,7 +284,6 @@
|
||||
'original_name': 'Total reported integrations',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_reports_integrations',
|
||||
'unique_id': 'total_reports_integrations',
|
||||
@@ -322,7 +310,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Homeassistant Analytics YouTube',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL: 'total'>,
|
||||
}),
|
||||
@@ -348,7 +335,6 @@
|
||||
'original_name': 'YouTube',
|
||||
'platform': 'analytics_insights',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'core_integrations',
|
||||
'unique_id': 'core_youtube_active_installations',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'My water heater Energy usage',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -33,7 +32,6 @@
|
||||
'original_name': 'Energy usage',
|
||||
'platform': 'aosmith',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'energy_usage',
|
||||
'unique_id': 'energy_usage_junctionId',
|
||||
@@ -61,7 +59,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'My water heater Hot water availability',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -85,7 +82,6 @@
|
||||
'original_name': 'Hot water availability',
|
||||
'platform': 'aosmith',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'hot_water_availability',
|
||||
'unique_id': 'hot_water_availability_junctionId',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'My water heater',
|
||||
'capabilities': dict({
|
||||
'max_temp': 130,
|
||||
'min_temp': 95,
|
||||
@@ -31,7 +30,6 @@
|
||||
'original_name': None,
|
||||
'platform': 'aosmith',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <WaterHeaterEntityFeature: 5>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'junctionId',
|
||||
@@ -64,7 +62,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'My water heater',
|
||||
'capabilities': dict({
|
||||
'max_temp': 130,
|
||||
'min_temp': 95,
|
||||
@@ -96,7 +93,6 @@
|
||||
'original_name': None,
|
||||
'platform': 'aosmith',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <WaterHeaterEntityFeature: 7>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'junctionId',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title DC 1 short circuit error status',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'DC 1 short circuit error status',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dc_1_short_circuit_error_status',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_dc_1_short_circuit_error_status',
|
||||
@@ -54,7 +52,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title DC 2 short circuit error status',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -78,7 +75,6 @@
|
||||
'original_name': 'DC 2 short circuit error status',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dc_2_short_circuit_error_status',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_dc_2_short_circuit_error_status',
|
||||
@@ -104,7 +100,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Off-grid status',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -128,7 +123,6 @@
|
||||
'original_name': 'Off-grid status',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'off_grid_status',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_off_grid_status',
|
||||
@@ -154,7 +148,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Output fault status',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -178,7 +171,6 @@
|
||||
'original_name': 'Output fault status',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'output_fault_status',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_output_fault_status',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Max output',
|
||||
'capabilities': dict({
|
||||
'max': 1000,
|
||||
'min': 0,
|
||||
@@ -33,7 +32,6 @@
|
||||
'original_name': 'Max output',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'max_output',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_output_limit',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Lifetime production of P1',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'Lifetime production of P1',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'lifetime_production_p1',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_lifetime_production_p1',
|
||||
@@ -58,7 +56,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Lifetime production of P2',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -84,7 +81,6 @@
|
||||
'original_name': 'Lifetime production of P2',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'lifetime_production_p2',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_lifetime_production_p2',
|
||||
@@ -112,7 +108,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Power of P1',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -138,7 +133,6 @@
|
||||
'original_name': 'Power of P1',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_power_p1',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_total_power_p1',
|
||||
@@ -166,7 +160,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Power of P2',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -192,7 +185,6 @@
|
||||
'original_name': 'Power of P2',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_power_p2',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_total_power_p2',
|
||||
@@ -220,7 +212,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Production of today',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -246,7 +237,6 @@
|
||||
'original_name': 'Production of today',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'today_production',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_today_production',
|
||||
@@ -274,7 +264,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Production of today from P1',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -300,7 +289,6 @@
|
||||
'original_name': 'Production of today from P1',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'today_production_p1',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_today_production_p1',
|
||||
@@ -328,7 +316,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Production of today from P2',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -354,7 +341,6 @@
|
||||
'original_name': 'Production of today from P2',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'today_production_p2',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_today_production_p2',
|
||||
@@ -382,7 +368,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Total lifetime production',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.TOTAL_INCREASING: 'total_increasing'>,
|
||||
}),
|
||||
@@ -408,7 +393,6 @@
|
||||
'original_name': 'Total lifetime production',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'lifetime_production',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_lifetime_production',
|
||||
@@ -436,7 +420,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Total power',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -462,7 +445,6 @@
|
||||
'original_name': 'Total power',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'total_power',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_total_power',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Mock Title Inverter status',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Inverter status',
|
||||
'platform': 'apsystems',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'inverter_status',
|
||||
'unique_id': 'MY_SERIAL_NUMBER_inverter_status',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Battery',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -28,7 +27,6 @@
|
||||
'original_name': 'Battery',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'DSN-battery',
|
||||
@@ -55,7 +53,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Salt left side percentage',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -81,7 +78,6 @@
|
||||
'original_name': 'Salt left side percentage',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'salt_left_side_percentage',
|
||||
'unique_id': 'DSN-salt_left_side_percentage',
|
||||
@@ -108,7 +104,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Salt left side time remaining',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -132,7 +127,6 @@
|
||||
'original_name': 'Salt left side time remaining',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'salt_left_side_time_remaining',
|
||||
'unique_id': 'DSN-salt_left_side_time_remaining',
|
||||
@@ -159,7 +153,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Salt right side percentage',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -185,7 +178,6 @@
|
||||
'original_name': 'Salt right side percentage',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'salt_right_side_percentage',
|
||||
'unique_id': 'DSN-salt_right_side_percentage',
|
||||
@@ -212,7 +204,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Salt right side time remaining',
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -236,7 +227,6 @@
|
||||
'original_name': 'Salt right side time remaining',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'salt_right_side_time_remaining',
|
||||
'unique_id': 'DSN-salt_right_side_time_remaining',
|
||||
@@ -263,7 +253,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'AquaCell name Wi-Fi strength',
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'high',
|
||||
@@ -293,7 +282,6 @@
|
||||
'original_name': 'Wi-Fi strength',
|
||||
'platform': 'aquacell',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'wi_fi_strength',
|
||||
'unique_id': 'DSN-wi_fi_strength',
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor Air quality index',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -30,7 +29,6 @@
|
||||
'original_name': 'Air quality index',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_AQI',
|
||||
@@ -42,7 +40,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor Carbon dioxide',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -68,7 +65,6 @@
|
||||
'original_name': 'Carbon dioxide',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_CO2',
|
||||
@@ -80,7 +76,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor Humidity',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -106,7 +101,6 @@
|
||||
'original_name': 'Humidity',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_Humidity',
|
||||
@@ -118,7 +112,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor PM10',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -144,7 +137,6 @@
|
||||
'original_name': 'PM10',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_PM10',
|
||||
@@ -156,7 +148,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor PM2.5',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -182,7 +173,6 @@
|
||||
'original_name': 'PM2.5',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_PM25',
|
||||
@@ -194,7 +184,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor Temperature',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -220,7 +209,6 @@
|
||||
'original_name': 'Temperature',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'test-serial-number_Temperature',
|
||||
@@ -232,7 +220,6 @@
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'calculated_object_id': 'Test Sensor Total volatile organic compounds',
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
@@ -258,7 +245,6 @@
|
||||
'original_name': 'Total volatile organic compounds',
|
||||
'platform': 'arve',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'tvoc',
|
||||
'unique_id': 'test-serial-number_TVOC',
|
||||
|
||||
@@ -37,7 +37,7 @@ from tests.common import (
|
||||
mock_platform,
|
||||
)
|
||||
from tests.components.stt.common import MockSTTProvider, MockSTTProviderEntity
|
||||
from tests.components.tts.common import MockTTSProvider
|
||||
from tests.components.tts.common import MockTTSEntity, MockTTSProvider
|
||||
|
||||
_TRANSCRIPT = "test transcript"
|
||||
|
||||
@@ -68,6 +68,15 @@ async def mock_tts_provider() -> MockTTSProvider:
|
||||
return provider
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_tts_entity() -> MockTTSEntity:
|
||||
"""Test TTS entity."""
|
||||
entity = MockTTSEntity("en")
|
||||
entity._attr_unique_id = "test_tts"
|
||||
entity._attr_supported_languages = ["en-US"]
|
||||
return entity
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def mock_stt_provider() -> MockSTTProvider:
|
||||
"""Mock STT provider."""
|
||||
@@ -198,6 +207,7 @@ async def init_supporting_components(
|
||||
mock_stt_provider: MockSTTProvider,
|
||||
mock_stt_provider_entity: MockSTTProviderEntity,
|
||||
mock_tts_provider: MockTTSProvider,
|
||||
mock_tts_entity: MockTTSEntity,
|
||||
mock_wake_word_provider_entity: MockWakeWordEntity,
|
||||
mock_wake_word_provider_entity2: MockWakeWordEntity2,
|
||||
config_flow_fixture,
|
||||
@@ -209,7 +219,7 @@ async def init_supporting_components(
|
||||
) -> bool:
|
||||
"""Set up test config entry."""
|
||||
await hass.config_entries.async_forward_entry_setups(
|
||||
config_entry, [Platform.STT, Platform.WAKE_WORD]
|
||||
config_entry, [Platform.STT, Platform.TTS, Platform.WAKE_WORD]
|
||||
)
|
||||
return True
|
||||
|
||||
@@ -230,6 +240,14 @@ async def init_supporting_components(
|
||||
"""Set up test stt platform via config entry."""
|
||||
async_add_entities([mock_stt_provider_entity])
|
||||
|
||||
async def async_setup_entry_tts_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up test tts platform via config entry."""
|
||||
async_add_entities([mock_tts_entity])
|
||||
|
||||
async def async_setup_entry_wake_word_platform(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
@@ -253,6 +271,7 @@ async def init_supporting_components(
|
||||
"test.tts",
|
||||
MockTTSPlatform(
|
||||
async_get_engine=AsyncMock(return_value=mock_tts_provider),
|
||||
async_setup_entry=async_setup_entry_tts_platform,
|
||||
),
|
||||
)
|
||||
mock_platform(
|
||||
|
||||
@@ -74,17 +74,17 @@
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
}),
|
||||
'type': <PipelineEventType.TTS_START: 'tts-start'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
@@ -395,17 +395,17 @@
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
}),
|
||||
'type': <PipelineEventType.TTS_START: 'tts-start'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
|
||||
@@ -1,4 +1,158 @@
|
||||
# serializer version: 1
|
||||
# name: test_chat_log_tts_streaming[to_stream_tts0]
|
||||
list([
|
||||
dict({
|
||||
'data': dict({
|
||||
'conversation_id': 'mock-ulid',
|
||||
'language': 'en',
|
||||
'pipeline': <ANY>,
|
||||
'tts_output': dict({
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'mocked-token.mp3',
|
||||
'url': '/api/tts_proxy/mocked-token.mp3',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.RUN_START: 'run-start'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'conversation_id': 'mock-ulid',
|
||||
'device_id': None,
|
||||
'engine': 'test-agent',
|
||||
'intent_input': 'Set a timer',
|
||||
'language': 'en',
|
||||
'prefer_local_intents': False,
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_START: 'intent-start'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'role': 'assistant',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': 'hello,',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': ' ',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': 'how',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': ' ',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': 'are',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': ' ',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': 'you',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'chat_log_delta': dict({
|
||||
'content': '?',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_PROGRESS: 'intent-progress'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'intent_output': dict({
|
||||
'continue_conversation': True,
|
||||
'conversation_id': <ANY>,
|
||||
'response': dict({
|
||||
'card': dict({
|
||||
}),
|
||||
'data': dict({
|
||||
'failed': list([
|
||||
]),
|
||||
'success': list([
|
||||
]),
|
||||
'targets': list([
|
||||
]),
|
||||
}),
|
||||
'language': 'en',
|
||||
'response_type': 'action_done',
|
||||
'speech': dict({
|
||||
'plain': dict({
|
||||
'extra_data': None,
|
||||
'speech': 'hello, how are you?',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'processed_locally': False,
|
||||
}),
|
||||
'type': <PipelineEventType.INTENT_END: 'intent-end'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': 'hello, how are you?',
|
||||
'voice': None,
|
||||
}),
|
||||
'type': <PipelineEventType.TTS_START: 'tts-start'>,
|
||||
}),
|
||||
dict({
|
||||
'data': dict({
|
||||
'tts_output': dict({
|
||||
'media_id': 'media-source://tts/tts.test?message=hello,+how+are+you?&language=en_US&tts_options=%7B%7D',
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'mocked-token.mp3',
|
||||
'url': '/api/tts_proxy/mocked-token.mp3',
|
||||
}),
|
||||
}),
|
||||
'type': <PipelineEventType.TTS_END: 'tts-end'>,
|
||||
}),
|
||||
dict({
|
||||
'data': None,
|
||||
'type': <PipelineEventType.RUN_END: 'run-end'>,
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_pipeline_language_used_instead_of_conversation_language
|
||||
list([
|
||||
dict({
|
||||
|
||||
@@ -71,16 +71,16 @@
|
||||
# ---
|
||||
# name: test_audio_pipeline.5
|
||||
dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_audio_pipeline.6
|
||||
dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
@@ -162,16 +162,16 @@
|
||||
# ---
|
||||
# name: test_audio_pipeline_debug.5
|
||||
dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_audio_pipeline_debug.6
|
||||
dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
@@ -265,16 +265,16 @@
|
||||
# ---
|
||||
# name: test_audio_pipeline_with_enhancements.5
|
||||
dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_audio_pipeline_with_enhancements.6
|
||||
dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
@@ -378,16 +378,16 @@
|
||||
# ---
|
||||
# name: test_audio_pipeline_with_wake_word_no_timeout.7
|
||||
dict({
|
||||
'engine': 'test',
|
||||
'language': 'en-US',
|
||||
'engine': 'tts.test',
|
||||
'language': 'en_US',
|
||||
'tts_input': "Sorry, I couldn't understand that",
|
||||
'voice': 'james_earl_jones',
|
||||
'voice': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_audio_pipeline_with_wake_word_no_timeout.8
|
||||
dict({
|
||||
'tts_output': dict({
|
||||
'media_id': "media-source://tts/test?message=Sorry,+I+couldn't+understand+that&language=en-US&tts_options=%7B%22voice%22:%22james_earl_jones%22%7D",
|
||||
'media_id': "media-source://tts/tts.test?message=Sorry,+I+couldn't+understand+that&language=en_US&tts_options=%7B%7D",
|
||||
'mime_type': 'audio/mpeg',
|
||||
'token': 'test_token.mp3',
|
||||
'url': '/api/tts_proxy/test_token.mp3',
|
||||
|
||||
@@ -40,6 +40,7 @@ from . import MANY_LANGUAGES, process_events
|
||||
from .conftest import (
|
||||
MockSTTProvider,
|
||||
MockSTTProviderEntity,
|
||||
MockTTSEntity,
|
||||
MockTTSProvider,
|
||||
MockWakeWordEntity,
|
||||
make_10ms_chunk,
|
||||
@@ -62,6 +63,12 @@ async def load_homeassistant(hass: HomeAssistant) -> None:
|
||||
assert await async_setup_component(hass, "homeassistant", {})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def disable_tts_entity(mock_tts_entity: tts.TextToSpeechEntity) -> None:
|
||||
"""Disable the TTS entity."""
|
||||
mock_tts_entity._attr_entity_registry_enabled_default = False
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_components")
|
||||
async def test_load_pipelines(hass: HomeAssistant) -> None:
|
||||
"""Make sure that we can load/save data correctly."""
|
||||
@@ -283,6 +290,7 @@ async def test_migrate_pipeline_store(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_supporting_components")
|
||||
@pytest.mark.usefixtures("disable_tts_entity")
|
||||
async def test_create_default_pipeline(hass: HomeAssistant) -> None:
|
||||
"""Test async_create_default_pipeline."""
|
||||
assert await async_setup_component(hass, "assist_pipeline", {})
|
||||
@@ -430,6 +438,7 @@ async def test_default_pipeline_no_stt_tts(
|
||||
],
|
||||
)
|
||||
@pytest.mark.usefixtures("init_supporting_components")
|
||||
@pytest.mark.usefixtures("disable_tts_entity")
|
||||
async def test_default_pipeline(
|
||||
hass: HomeAssistant,
|
||||
mock_stt_provider_entity: MockSTTProviderEntity,
|
||||
@@ -474,6 +483,7 @@ async def test_default_pipeline(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_supporting_components")
|
||||
@pytest.mark.usefixtures("disable_tts_entity")
|
||||
async def test_default_pipeline_unsupported_stt_language(
|
||||
hass: HomeAssistant, mock_stt_provider_entity: MockSTTProviderEntity
|
||||
) -> None:
|
||||
@@ -504,6 +514,7 @@ async def test_default_pipeline_unsupported_stt_language(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_supporting_components")
|
||||
@pytest.mark.usefixtures("disable_tts_entity")
|
||||
async def test_default_pipeline_unsupported_tts_language(
|
||||
hass: HomeAssistant, mock_tts_provider: MockTTSProvider
|
||||
) -> None:
|
||||
@@ -825,7 +836,7 @@ def test_pipeline_run_equality(hass: HomeAssistant, init_components) -> None:
|
||||
async def test_tts_audio_output(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_tts_provider: MockTTSProvider,
|
||||
mock_tts_entity: MockTTSProvider,
|
||||
init_components,
|
||||
pipeline_data: assist_pipeline.pipeline.PipelineData,
|
||||
mock_chat_session: chat_session.ChatSession,
|
||||
@@ -869,7 +880,7 @@ async def test_tts_audio_output(
|
||||
== 1
|
||||
)
|
||||
|
||||
with patch.object(mock_tts_provider, "get_tts_audio") as mock_get_tts_audio:
|
||||
with patch.object(mock_tts_entity, "get_tts_audio") as mock_get_tts_audio:
|
||||
await pipeline_input.execute()
|
||||
|
||||
for event in events:
|
||||
@@ -881,14 +892,14 @@ async def test_tts_audio_output(
|
||||
# Ensure that no unsupported options were passed in
|
||||
assert mock_get_tts_audio.called
|
||||
options = mock_get_tts_audio.call_args_list[0].kwargs["options"]
|
||||
extra_options = set(options).difference(mock_tts_provider.supported_options)
|
||||
extra_options = set(options).difference(mock_tts_entity.supported_options)
|
||||
assert len(extra_options) == 0, extra_options
|
||||
|
||||
|
||||
async def test_tts_wav_preferred_format(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_tts_provider: MockTTSProvider,
|
||||
mock_tts_entity: MockTTSEntity,
|
||||
init_components,
|
||||
mock_chat_session: chat_session.ChatSession,
|
||||
pipeline_data: assist_pipeline.pipeline.PipelineData,
|
||||
@@ -920,7 +931,7 @@ async def test_tts_wav_preferred_format(
|
||||
await pipeline_input.validate()
|
||||
|
||||
# Make the TTS provider support preferred format options
|
||||
supported_options = list(mock_tts_provider.supported_options or [])
|
||||
supported_options = list(mock_tts_entity.supported_options or [])
|
||||
supported_options.extend(
|
||||
[
|
||||
tts.ATTR_PREFERRED_FORMAT,
|
||||
@@ -931,8 +942,8 @@ async def test_tts_wav_preferred_format(
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(mock_tts_provider, "_supported_options", supported_options),
|
||||
patch.object(mock_tts_provider, "get_tts_audio") as mock_get_tts_audio,
|
||||
patch.object(mock_tts_entity, "_supported_options", supported_options),
|
||||
patch.object(mock_tts_entity, "get_tts_audio") as mock_get_tts_audio,
|
||||
):
|
||||
await pipeline_input.execute()
|
||||
|
||||
@@ -955,7 +966,7 @@ async def test_tts_wav_preferred_format(
|
||||
async def test_tts_dict_preferred_format(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_tts_provider: MockTTSProvider,
|
||||
mock_tts_entity: MockTTSEntity,
|
||||
init_components,
|
||||
mock_chat_session: chat_session.ChatSession,
|
||||
pipeline_data: assist_pipeline.pipeline.PipelineData,
|
||||
@@ -992,7 +1003,7 @@ async def test_tts_dict_preferred_format(
|
||||
await pipeline_input.validate()
|
||||
|
||||
# Make the TTS provider support preferred format options
|
||||
supported_options = list(mock_tts_provider.supported_options or [])
|
||||
supported_options = list(mock_tts_entity.supported_options or [])
|
||||
supported_options.extend(
|
||||
[
|
||||
tts.ATTR_PREFERRED_FORMAT,
|
||||
@@ -1003,8 +1014,8 @@ async def test_tts_dict_preferred_format(
|
||||
)
|
||||
|
||||
with (
|
||||
patch.object(mock_tts_provider, "_supported_options", supported_options),
|
||||
patch.object(mock_tts_provider, "get_tts_audio") as mock_get_tts_audio,
|
||||
patch.object(mock_tts_entity, "_supported_options", supported_options),
|
||||
patch.object(mock_tts_entity, "get_tts_audio") as mock_get_tts_audio,
|
||||
):
|
||||
await pipeline_input.execute()
|
||||
|
||||
@@ -1545,3 +1556,143 @@ async def test_pipeline_language_used_instead_of_conversation_language(
|
||||
mock_async_converse.call_args_list[0].kwargs.get("language")
|
||||
== pipeline.language
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"to_stream_tts",
|
||||
[
|
||||
[
|
||||
"hello,",
|
||||
" ",
|
||||
"how",
|
||||
" ",
|
||||
"are",
|
||||
" ",
|
||||
"you",
|
||||
"?",
|
||||
]
|
||||
],
|
||||
)
|
||||
async def test_chat_log_tts_streaming(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
init_components,
|
||||
mock_chat_session: chat_session.ChatSession,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_tts_entity: MockTTSEntity,
|
||||
pipeline_data: assist_pipeline.pipeline.PipelineData,
|
||||
to_stream_tts: list[str],
|
||||
) -> None:
|
||||
"""Test that chat log events are streamed to the TTS entity."""
|
||||
events: list[assist_pipeline.PipelineEvent] = []
|
||||
|
||||
pipeline_store = pipeline_data.pipeline_store
|
||||
pipeline_id = pipeline_store.async_get_preferred_item()
|
||||
pipeline = assist_pipeline.pipeline.async_get_pipeline(hass, pipeline_id)
|
||||
await assist_pipeline.pipeline.async_update_pipeline(
|
||||
hass, pipeline, conversation_engine="test-agent"
|
||||
)
|
||||
pipeline = assist_pipeline.pipeline.async_get_pipeline(hass, pipeline_id)
|
||||
|
||||
pipeline_input = assist_pipeline.pipeline.PipelineInput(
|
||||
intent_input="Set a timer",
|
||||
session=mock_chat_session,
|
||||
run=assist_pipeline.pipeline.PipelineRun(
|
||||
hass,
|
||||
context=Context(),
|
||||
pipeline=pipeline,
|
||||
start_stage=assist_pipeline.PipelineStage.INTENT,
|
||||
end_stage=assist_pipeline.PipelineStage.TTS,
|
||||
event_callback=events.append,
|
||||
),
|
||||
)
|
||||
|
||||
received_tts = []
|
||||
|
||||
async def async_stream_tts_audio(
|
||||
request: tts.TTSAudioRequest,
|
||||
) -> tts.TTSAudioResponse:
|
||||
"""Mock stream TTS audio."""
|
||||
|
||||
async def gen_data():
|
||||
async for msg in request.message_gen:
|
||||
received_tts.append(msg)
|
||||
yield msg.encode()
|
||||
|
||||
return tts.TTSAudioResponse(
|
||||
extension="mp3",
|
||||
data_gen=gen_data(),
|
||||
)
|
||||
|
||||
mock_tts_entity.async_stream_tts_audio = async_stream_tts_audio
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.assist_pipeline.pipeline.conversation.async_get_agent_info",
|
||||
return_value=conversation.AgentInfo(id="test-agent", name="Test Agent"),
|
||||
):
|
||||
await pipeline_input.validate()
|
||||
|
||||
async def mock_converse(
|
||||
hass: HomeAssistant,
|
||||
text: str,
|
||||
conversation_id: str | None,
|
||||
context: Context,
|
||||
language: str | None = None,
|
||||
agent_id: str | None = None,
|
||||
device_id: str | None = None,
|
||||
extra_system_prompt: str | None = None,
|
||||
):
|
||||
"""Mock converse."""
|
||||
conversation_input = conversation.ConversationInput(
|
||||
text=text,
|
||||
context=context,
|
||||
conversation_id=conversation_id,
|
||||
device_id=device_id,
|
||||
language=language,
|
||||
agent_id=agent_id,
|
||||
extra_system_prompt=extra_system_prompt,
|
||||
)
|
||||
|
||||
async def stream_llm_response():
|
||||
yield {"role": "assistant"}
|
||||
for chunk in to_stream_tts:
|
||||
yield {"content": chunk}
|
||||
|
||||
with (
|
||||
chat_session.async_get_chat_session(hass, conversation_id) as session,
|
||||
conversation.async_get_chat_log(
|
||||
hass,
|
||||
session,
|
||||
conversation_input,
|
||||
) as chat_log,
|
||||
):
|
||||
async for _content in chat_log.async_add_delta_content_stream(
|
||||
agent_id, stream_llm_response()
|
||||
):
|
||||
pass
|
||||
intent_response = intent.IntentResponse(language)
|
||||
intent_response.async_set_speech("".join(to_stream_tts))
|
||||
return conversation.ConversationResult(
|
||||
response=intent_response,
|
||||
conversation_id=chat_log.conversation_id,
|
||||
continue_conversation=chat_log.continue_conversation,
|
||||
)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.assist_pipeline.pipeline.conversation.async_converse",
|
||||
mock_converse,
|
||||
):
|
||||
await pipeline_input.execute()
|
||||
|
||||
stream = tts.async_get_stream(hass, events[0].data["tts_output"]["token"])
|
||||
assert stream is not None
|
||||
tts_result = "".join(
|
||||
[chunk.decode() async for chunk in stream.async_stream_result()]
|
||||
)
|
||||
|
||||
streamed_text = "".join(to_stream_tts)
|
||||
assert tts_result == streamed_text
|
||||
assert len(received_tts) == 1
|
||||
assert "".join(received_tts) == streamed_text
|
||||
|
||||
assert process_events(events) == snapshot
|
||||
|
||||
@@ -1153,9 +1153,9 @@ async def test_get_pipeline(
|
||||
"name": "Home Assistant",
|
||||
"stt_engine": "stt.mock_stt",
|
||||
"stt_language": "en-US",
|
||||
"tts_engine": "test",
|
||||
"tts_language": "en-US",
|
||||
"tts_voice": "james_earl_jones",
|
||||
"tts_engine": "tts.test",
|
||||
"tts_language": "en_US",
|
||||
"tts_voice": None,
|
||||
"wake_word_entity": None,
|
||||
"wake_word_id": None,
|
||||
"prefer_local_intents": False,
|
||||
@@ -1179,9 +1179,9 @@ async def test_get_pipeline(
|
||||
# It found these defaults
|
||||
"stt_engine": "stt.mock_stt",
|
||||
"stt_language": "en-US",
|
||||
"tts_engine": "test",
|
||||
"tts_language": "en-US",
|
||||
"tts_voice": "james_earl_jones",
|
||||
"tts_engine": "tts.test",
|
||||
"tts_language": "en_US",
|
||||
"tts_voice": None,
|
||||
"wake_word_entity": None,
|
||||
"wake_word_id": None,
|
||||
"prefer_local_intents": False,
|
||||
@@ -1266,9 +1266,9 @@ async def test_list_pipelines(
|
||||
"name": "Home Assistant",
|
||||
"stt_engine": "stt.mock_stt",
|
||||
"stt_language": "en-US",
|
||||
"tts_engine": "test",
|
||||
"tts_language": "en-US",
|
||||
"tts_voice": "james_earl_jones",
|
||||
"tts_engine": "tts.test",
|
||||
"tts_language": "en_US",
|
||||
"tts_voice": None,
|
||||
"wake_word_entity": None,
|
||||
"wake_word_id": None,
|
||||
"prefer_local_intents": False,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user