mirror of
https://github.com/home-assistant/core.git
synced 2026-06-26 08:35:38 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6860e0f3b9 | |||
| d3be0cc852 | |||
| c830c05cbd | |||
| 2be55a06cc | |||
| d786fb16a0 | |||
| f78dd797b1 | |||
| 0d957a971d | |||
| cff3a711f3 | |||
| 177c4a4fb5 | |||
| 7d8204f5e7 | |||
| 9aed167f71 | |||
| a8630f5570 | |||
| 2a75b0e2fb | |||
| 9c4ad761c4 | |||
| 8e3e1044a1 | |||
| bec6c94e32 | |||
| c9729df69a | |||
| 70ff0fd682 | |||
| 258ae6d506 | |||
| 4f93afd6ae | |||
| 7968fc4809 | |||
| 975f2a831e |
@@ -39,7 +39,7 @@ on:
|
||||
env:
|
||||
CACHE_VERSION: 3
|
||||
MYPY_CACHE_VERSION: 1
|
||||
HA_SHORT_VERSION: "2026.7"
|
||||
HA_SHORT_VERSION: "2026.8"
|
||||
ADDITIONAL_PYTHON_VERSIONS: "[]"
|
||||
# 10.3 is the oldest supported version
|
||||
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
|
||||
|
||||
Generated
+2
-2
@@ -790,8 +790,8 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/html5/ @alexyao2015 @tr4nt0r
|
||||
/homeassistant/components/http/ @home-assistant/core
|
||||
/tests/components/http/ @home-assistant/core
|
||||
/homeassistant/components/huawei_lte/ @scop @fphammerle
|
||||
/tests/components/huawei_lte/ @scop @fphammerle
|
||||
/homeassistant/components/huawei_lte/ @fphammerle
|
||||
/tests/components/huawei_lte/ @fphammerle
|
||||
/homeassistant/components/hue/ @marcelveldt
|
||||
/tests/components/hue/ @marcelveldt
|
||||
/homeassistant/components/hue_ble/ @flip-dots
|
||||
|
||||
@@ -15,16 +15,15 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
LIGHT_LUX,
|
||||
PERCENTAGE,
|
||||
UnitOfApparentPower,
|
||||
UnitOfDensity,
|
||||
UnitOfElectricCurrent,
|
||||
UnitOfElectricPotential,
|
||||
UnitOfEnergy,
|
||||
UnitOfFrequency,
|
||||
UnitOfPower,
|
||||
UnitOfRatio,
|
||||
UnitOfReactiveEnergy,
|
||||
UnitOfReactivePower,
|
||||
UnitOfSpeed,
|
||||
@@ -53,19 +52,19 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm1",
|
||||
device_class=SensorDeviceClass.PM1,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm2_5",
|
||||
device_class=SensorDeviceClass.PM25,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
key="pm10",
|
||||
device_class=SensorDeviceClass.PM10,
|
||||
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
|
||||
native_unit_of_measurement=UnitOfDensity.MICROGRAMS_PER_CUBIC_METER,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -84,7 +83,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
@@ -179,7 +178,7 @@ SENSOR_TYPES: tuple[BleBoxSensorEntityDescription, ...] = (
|
||||
BleBoxSensorEntityDescription(
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
),
|
||||
BleBoxSensorEntityDescription(
|
||||
|
||||
@@ -21,6 +21,6 @@
|
||||
"bluetooth-auto-recovery==1.6.4",
|
||||
"bluetooth-data-tools==1.29.18",
|
||||
"dbus-fast==5.0.22",
|
||||
"habluetooth==6.23.1"
|
||||
"habluetooth==6.25.1"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
)
|
||||
|
||||
from .auth import DropboxConfigEntryAuth
|
||||
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN
|
||||
from .const import DATA_BACKUP_AGENT_LISTENERS, DOMAIN, OAUTH2_SCOPES
|
||||
|
||||
type DropboxConfigEntry = ConfigEntry[DropboxAPIClient]
|
||||
|
||||
@@ -31,6 +31,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: DropboxConfigEntry) -> b
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="oauth2_implementation_unavailable",
|
||||
) from err
|
||||
|
||||
token = entry.data["token"]
|
||||
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_scopes",
|
||||
)
|
||||
if "refresh_token" not in token:
|
||||
raise ConfigEntryAuthFailed(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="missing_refresh_token",
|
||||
)
|
||||
|
||||
oauth2_session = OAuth2Session(hass, entry, oauth2_implementation)
|
||||
|
||||
auth = DropboxConfigEntryAuth(
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Application credentials platform for the Dropbox integration."""
|
||||
|
||||
from typing import override
|
||||
|
||||
from homeassistant.components.application_credentials import ClientCredential
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
@@ -9,14 +7,14 @@ from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
LocalOAuth2ImplementationWithPkce,
|
||||
)
|
||||
|
||||
from .const import OAUTH2_AUTHORIZE, OAUTH2_SCOPES, OAUTH2_TOKEN
|
||||
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
|
||||
|
||||
|
||||
async def async_get_auth_implementation(
|
||||
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
|
||||
) -> AbstractOAuth2Implementation:
|
||||
"""Return custom auth implementation."""
|
||||
return DropboxOAuth2Implementation(
|
||||
"""Return auth implementation."""
|
||||
return LocalOAuth2ImplementationWithPkce(
|
||||
hass,
|
||||
auth_domain,
|
||||
credential.client_id,
|
||||
@@ -24,21 +22,3 @@ async def async_get_auth_implementation(
|
||||
OAUTH2_TOKEN,
|
||||
credential.client_secret,
|
||||
)
|
||||
|
||||
|
||||
class DropboxOAuth2Implementation(LocalOAuth2ImplementationWithPkce):
|
||||
"""Custom Dropbox OAuth2 implementation.
|
||||
|
||||
Adds the necessary authorize url parameters.
|
||||
"""
|
||||
|
||||
@property
|
||||
@override
|
||||
def extra_authorize_data(self) -> dict:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
data: dict = {
|
||||
"token_access_type": "offline",
|
||||
"scope": " ".join(OAUTH2_SCOPES),
|
||||
}
|
||||
data.update(super().extra_authorize_data)
|
||||
return data
|
||||
|
||||
@@ -12,7 +12,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
||||
|
||||
from .auth import DropboxConfigFlowAuth
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, OAUTH2_SCOPES
|
||||
|
||||
|
||||
class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
@@ -26,6 +26,15 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
|
||||
@property
|
||||
@override
|
||||
def extra_authorize_data(self) -> dict:
|
||||
"""Extra data that needs to be appended to the authorize url."""
|
||||
return {
|
||||
"token_access_type": "offline",
|
||||
"scope": " ".join(OAUTH2_SCOPES),
|
||||
}
|
||||
|
||||
@override
|
||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Create an entry for the flow, or update existing entry."""
|
||||
@@ -51,6 +60,9 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
self, entry_data: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""Perform reauth upon an API authentication error."""
|
||||
token = entry_data[CONF_TOKEN]
|
||||
if not set(token.get("scope", "").split()).issuperset(OAUTH2_SCOPES):
|
||||
return await self.async_step_reauth_permissions()
|
||||
return await self.async_step_reauth_confirm()
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
@@ -60,3 +72,11 @@ class DropboxConfigFlow(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_step_reauth_permissions(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog that informs the user that additional permissions are required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_permissions")
|
||||
return await self.async_step_user()
|
||||
|
||||
@@ -12,6 +12,7 @@ OAUTH2_SCOPES = [
|
||||
"account_info.read",
|
||||
"files.content.read",
|
||||
"files.content.write",
|
||||
"files.metadata.read",
|
||||
]
|
||||
|
||||
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/dropbox",
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["python-dropbox-api==0.1.4"]
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ rules:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
integration-owner: done
|
||||
log-when-unavailable: todo
|
||||
log-when-unavailable:
|
||||
status: exempt
|
||||
comment: Integration does not have any entities.
|
||||
parallel-updates:
|
||||
status: exempt
|
||||
comment: Integration does not make any entity updates.
|
||||
|
||||
@@ -24,10 +24,20 @@
|
||||
"reauth_confirm": {
|
||||
"description": "The Dropbox integration needs to re-authenticate your account.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
},
|
||||
"reauth_permissions": {
|
||||
"description": "The Dropbox integration requires additional permissions to function correctly.",
|
||||
"title": "[%key:common::config_flow::title::reauth%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"missing_refresh_token": {
|
||||
"message": "[%key:component::dropbox::config::step::reauth_confirm::description%]"
|
||||
},
|
||||
"missing_scopes": {
|
||||
"message": "[%key:component::dropbox::config::step::reauth_permissions::description%]"
|
||||
},
|
||||
"oauth2_implementation_unavailable": {
|
||||
"message": "[%key:common::exceptions::oauth2_implementation_unavailable::message%]"
|
||||
}
|
||||
|
||||
@@ -15,10 +15,9 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONCENTRATION_PARTS_PER_MILLION,
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfRatio,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@@ -72,7 +71,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
key="target_flow_level",
|
||||
translation_key="target_flow_level",
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
suggested_display_precision=0,
|
||||
value_fn=lambda node: (
|
||||
node.ventilation.flow_lvl_tgt if node.ventilation else None
|
||||
@@ -96,7 +95,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
key="co2",
|
||||
device_class=SensorDeviceClass.CO2,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
|
||||
native_unit_of_measurement=UnitOfRatio.PARTS_PER_MILLION,
|
||||
value_fn=lambda node: node.sensor.co2 if node.sensor else None,
|
||||
node_types=(
|
||||
NodeType.BSCO2,
|
||||
@@ -108,7 +107,7 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
DucoSensorEntityDescription(
|
||||
key="iaq_co2",
|
||||
translation_key="iaq_co2",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda node: node.sensor.iaq_co2 if node.sensor else None,
|
||||
@@ -123,14 +122,14 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
key="humidity",
|
||||
device_class=SensorDeviceClass.HUMIDITY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
value_fn=lambda node: node.sensor.rh if node.sensor else None,
|
||||
node_types=(NodeType.BSRH, NodeType.UCRH, NodeType.VLVRH, NodeType.VLVCO2RH),
|
||||
),
|
||||
DucoSensorEntityDescription(
|
||||
key="iaq_rh",
|
||||
translation_key="iaq_rh",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
native_unit_of_measurement=UnitOfRatio.PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_registry_enabled_default=False,
|
||||
value_fn=lambda node: node.sensor.iaq_rh if node.sensor else None,
|
||||
|
||||
@@ -52,7 +52,9 @@ __all__ = [
|
||||
"DoorbellEventType",
|
||||
"EventDeviceClass",
|
||||
"EventEntity",
|
||||
"EventEntityCapabilityAttribute",
|
||||
"EventEntityDescription",
|
||||
"EventEntityStateAttribute",
|
||||
]
|
||||
|
||||
# mypy: disallow-any-generics
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["homematicip"],
|
||||
"requirements": ["homematicip==2.13.1"]
|
||||
"requirements": ["homematicip==2.13.2"]
|
||||
}
|
||||
|
||||
@@ -245,11 +245,14 @@ class HuaweiLteConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
)
|
||||
assert conn
|
||||
|
||||
def _get_info_and_disconnect() -> tuple[dict, dict]:
|
||||
result = get_device_info(conn)
|
||||
self._disconnect(conn)
|
||||
return result
|
||||
|
||||
info, wlan_settings = await self.hass.async_add_executor_job(
|
||||
get_device_info, conn
|
||||
_get_info_and_disconnect
|
||||
)
|
||||
# pylint: disable-next=home-assistant-sequential-executor-jobs
|
||||
await self.hass.async_add_executor_job(self._disconnect, conn)
|
||||
|
||||
user_input.update(
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "huawei_lte",
|
||||
"name": "Huawei LTE",
|
||||
"codeowners": ["@scop", "@fphammerle"],
|
||||
"codeowners": ["@fphammerle"],
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/huawei_lte",
|
||||
"integration_type": "device",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Event parser and human readable log generator."""
|
||||
|
||||
from homeassistant.components.automation import EVENT_AUTOMATION_TRIGGERED
|
||||
from homeassistant.components.event import EventEntityStateAttribute
|
||||
from homeassistant.components.script import EVENT_SCRIPT_STARTED
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import EVENT_CALL_SERVICE, EVENT_LOGBOOK_ENTRY
|
||||
@@ -39,8 +40,12 @@ LOGBOOK_ENTRY_SOURCE = "source"
|
||||
LOGBOOK_ENTRY_MESSAGE = "message"
|
||||
LOGBOOK_ENTRY_NAME = "name"
|
||||
LOGBOOK_ENTRY_STATE = "state"
|
||||
LOGBOOK_ENTRY_ATTRIBUTES = "attributes"
|
||||
LOGBOOK_ENTRY_WHEN = "when"
|
||||
|
||||
# State attributes surfaced in logbook entries; extend as needed.
|
||||
EXPOSED_STATE_ATTRIBUTES = {EventEntityStateAttribute.EVENT_TYPE}
|
||||
|
||||
# Automation events that can affect an entity_id or device_id
|
||||
AUTOMATION_EVENTS = {EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED}
|
||||
|
||||
|
||||
@@ -106,10 +106,11 @@ CONTEXT_PARENT_ID_BIN_POS: Final = 6
|
||||
STATE_POS: Final = 7
|
||||
ENTITY_ID_POS: Final = 8
|
||||
ICON_POS: Final = 9
|
||||
CONTEXT_ONLY_POS: Final = 10
|
||||
ATTRIBUTES_POS: Final = 10
|
||||
CONTEXT_ONLY_POS: Final = 11
|
||||
# - For EventAsRow, additional fields are:
|
||||
DATA_POS: Final = 11
|
||||
CONTEXT_POS: Final = 12
|
||||
DATA_POS: Final = 12
|
||||
CONTEXT_POS: Final = 13
|
||||
|
||||
|
||||
@final # Final to allow direct checking of the type instead of using isinstance
|
||||
@@ -129,6 +130,7 @@ class EventAsRow(NamedTuple):
|
||||
state: str | None
|
||||
entity_id: str | None
|
||||
icon: str | None
|
||||
attributes: Mapping[str, Any] | None
|
||||
context_only: bool | None
|
||||
|
||||
# Additional fields for EventAsRow
|
||||
@@ -152,6 +154,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
|
||||
state=None,
|
||||
entity_id=None,
|
||||
icon=None,
|
||||
attributes=None,
|
||||
context_only=None,
|
||||
data=event.data,
|
||||
context=context,
|
||||
@@ -175,6 +178,7 @@ def async_event_to_row(event: Event) -> EventAsRow:
|
||||
state=new_state.state,
|
||||
entity_id=new_state.entity_id,
|
||||
icon=new_state.attributes.get(ATTR_ICON),
|
||||
attributes=new_state.attributes,
|
||||
context_only=None,
|
||||
data=event.data,
|
||||
context=context,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Event parser and human readable log generator."""
|
||||
|
||||
from collections.abc import Callable, Collection, Generator, Sequence
|
||||
from collections.abc import Callable, Collection, Generator, Mapping, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime as dt
|
||||
import logging
|
||||
@@ -16,6 +16,7 @@ from homeassistant.components.recorder import get_instance
|
||||
from homeassistant.components.recorder.filters import Filters
|
||||
from homeassistant.components.recorder.models import (
|
||||
bytes_to_uuid_hex_or_none,
|
||||
decode_attributes_from_source,
|
||||
extract_event_type_ids,
|
||||
extract_metadata_ids,
|
||||
process_timestamp_to_utc_isoformat,
|
||||
@@ -53,6 +54,8 @@ from .const import (
|
||||
CONTEXT_STATE,
|
||||
CONTEXT_USER_ID,
|
||||
DOMAIN,
|
||||
EXPOSED_STATE_ATTRIBUTES,
|
||||
LOGBOOK_ENTRY_ATTRIBUTES,
|
||||
LOGBOOK_ENTRY_DOMAIN,
|
||||
LOGBOOK_ENTRY_ENTITY_ID,
|
||||
LOGBOOK_ENTRY_ICON,
|
||||
@@ -64,6 +67,7 @@ from .const import (
|
||||
)
|
||||
from .helpers import is_sensor_continuous
|
||||
from .models import (
|
||||
ATTRIBUTES_POS,
|
||||
CONTEXT_ID_BIN_POS,
|
||||
CONTEXT_ONLY_POS,
|
||||
CONTEXT_PARENT_ID_BIN_POS,
|
||||
@@ -273,6 +277,24 @@ class EventProcessor:
|
||||
)
|
||||
|
||||
|
||||
def _exposed_state_attributes(
|
||||
row: Row | EventAsRow, attr_cache: dict[str, dict[str, Any]]
|
||||
) -> dict[str, Any]:
|
||||
"""Return the allowlisted state attributes for a state change row."""
|
||||
attributes: Mapping[str, Any] | None
|
||||
if type(row) is EventAsRow:
|
||||
attributes = row[ATTRIBUTES_POS]
|
||||
else:
|
||||
attributes = decode_attributes_from_source(row[ATTRIBUTES_POS], attr_cache)
|
||||
if not attributes:
|
||||
return {}
|
||||
return {
|
||||
name: attributes[name]
|
||||
for name in EXPOSED_STATE_ATTRIBUTES
|
||||
if name in attributes
|
||||
}
|
||||
|
||||
|
||||
def _humanify(
|
||||
hass: HomeAssistant,
|
||||
rows: Generator[EventAsRow] | Sequence[Row] | Result,
|
||||
@@ -294,6 +316,8 @@ def _humanify(
|
||||
get_context = context_augmenter.get_context
|
||||
context_id_bin: bytes
|
||||
data: dict[str, Any]
|
||||
# Decode each shared attribute set only once per run.
|
||||
attr_cache: dict[str, dict[str, Any]] = {}
|
||||
|
||||
context_user_ids = logbook_run.context_user_ids
|
||||
# Skip the LRU write on one-shot runs — the LogbookRun is discarded.
|
||||
@@ -337,6 +361,8 @@ def _humanify(
|
||||
data[LOGBOOK_ENTRY_NAME] = entity_name_cache_get(entity_id)
|
||||
if icon := row[ICON_POS]:
|
||||
data[LOGBOOK_ENTRY_ICON] = icon
|
||||
if exposed := _exposed_state_attributes(row, attr_cache):
|
||||
data[LOGBOOK_ENTRY_ATTRIBUTES] = exposed
|
||||
|
||||
elif event_type in external_events:
|
||||
domain, describe_event = external_events[event_type]
|
||||
|
||||
@@ -14,6 +14,7 @@ from homeassistant.components.recorder.db_schema import (
|
||||
EVENTS_CONTEXT_ID_BIN_INDEX,
|
||||
OLD_FORMAT_ATTRS_JSON,
|
||||
OLD_STATE,
|
||||
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
|
||||
SHARED_ATTRS_JSON,
|
||||
SHARED_DATA_OR_LEGACY_EVENT_DATA,
|
||||
STATES_CONTEXT_ID_BIN_INDEX,
|
||||
@@ -65,12 +66,14 @@ STATE_COLUMNS = (
|
||||
States.state.label("state"),
|
||||
StatesMeta.entity_id.label("entity_id"),
|
||||
ICON_OR_OLD_FORMAT_ICON_JSON,
|
||||
SHARED_ATTR_OR_LEGACY_ATTRIBUTES,
|
||||
)
|
||||
|
||||
STATE_CONTEXT_ONLY_COLUMNS = (
|
||||
States.state.label("state"),
|
||||
StatesMeta.entity_id.label("entity_id"),
|
||||
literal(value=None, type_=sqlalchemy.String).label("icon"),
|
||||
literal(value=None, type_=sqlalchemy.String).label("attributes"),
|
||||
)
|
||||
|
||||
EVENT_COLUMNS_FOR_STATE_SELECT = (
|
||||
@@ -93,6 +96,7 @@ EMPTY_STATE_COLUMNS = (
|
||||
literal(value=None, type_=sqlalchemy.String).label("state"),
|
||||
literal(value=None, type_=sqlalchemy.String).label("entity_id"),
|
||||
literal(value=None, type_=sqlalchemy.String).label("icon"),
|
||||
literal(value=None, type_=sqlalchemy.String).label("attributes"),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -15,16 +15,29 @@ from homeassistant.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_USERNAME,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.selector import (
|
||||
TextSelector,
|
||||
TextSelectorConfig,
|
||||
TextSelectorType,
|
||||
)
|
||||
from homeassistant.helpers.service_info import zeroconf
|
||||
|
||||
from .const import CONF_SERIAL, DOMAIN
|
||||
|
||||
USER_SCHEMA = vol.Schema({vol.Required(CONF_HOST): cv.string})
|
||||
USER_SCHEMA = vol.Schema({vol.Required(CONF_HOST): TextSelector()})
|
||||
|
||||
AUTH_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string}
|
||||
{
|
||||
vol.Required(CONF_USERNAME): TextSelector(
|
||||
TextSelectorConfig(autocomplete="username")
|
||||
),
|
||||
vol.Required(CONF_PASSWORD): TextSelector(
|
||||
TextSelectorConfig(
|
||||
type=TextSelectorType.PASSWORD, autocomplete="current-password"
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from .context import (
|
||||
from .database import DatabaseEngine, DatabaseOptimizer, UnsupportedDialect
|
||||
from .event import extract_event_type_ids
|
||||
from .state import LazyState, extract_metadata_ids, row_to_compressed_state
|
||||
from .state_attributes import decode_attributes_from_source
|
||||
from .statistics import (
|
||||
CalendarStatisticPeriod,
|
||||
FixedStatisticPeriod,
|
||||
@@ -44,6 +45,7 @@ __all__ = [
|
||||
"bytes_to_ulid_or_none",
|
||||
"bytes_to_uuid_hex_or_none",
|
||||
"datetime_to_timestamp_or_none",
|
||||
"decode_attributes_from_source",
|
||||
"extract_event_type_ids",
|
||||
"extract_metadata_ids",
|
||||
"process_timestamp",
|
||||
|
||||
@@ -321,6 +321,7 @@ SENSOR_TYPES: tuple[RenaultSensorEntityDescription[Any], ...] = (
|
||||
options=[
|
||||
"always",
|
||||
"delayed",
|
||||
"delegated",
|
||||
"scheduled",
|
||||
],
|
||||
value_lambda=_get_charging_settings_mode_formatted,
|
||||
|
||||
@@ -157,6 +157,7 @@
|
||||
"state": {
|
||||
"always": "Always",
|
||||
"delayed": "Delayed",
|
||||
"delegated": "Delegated",
|
||||
"scheduled": "Scheduled"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -42,5 +42,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["switchbot"],
|
||||
"quality_scale": "gold",
|
||||
"requirements": ["PySwitchbot==2.2.0"]
|
||||
"requirements": ["PySwitchbot==2.3.0"]
|
||||
}
|
||||
|
||||
@@ -14,5 +14,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["switchbot_api"],
|
||||
"requirements": ["switchbot-api==2.11.1"]
|
||||
"requirements": ["switchbot-api==2.12.0"]
|
||||
}
|
||||
|
||||
@@ -67,12 +67,13 @@ class Tami4ConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
if user_input is not None:
|
||||
otp = user_input["otp"]
|
||||
try:
|
||||
refresh_token = await self.hass.async_add_executor_job(
|
||||
Tami4EdgeAPI.submit_otp, self.phone, otp
|
||||
)
|
||||
# pylint: disable-next=home-assistant-sequential-executor-jobs
|
||||
api = await self.hass.async_add_executor_job(
|
||||
Tami4EdgeAPI, refresh_token
|
||||
|
||||
def _submit_otp_and_create_api() -> tuple[str, Tami4EdgeAPI]:
|
||||
refresh_token = Tami4EdgeAPI.submit_otp(self.phone, otp)
|
||||
return refresh_token, Tami4EdgeAPI(refresh_token)
|
||||
|
||||
refresh_token, api = await self.hass.async_add_executor_job(
|
||||
_submit_otp_and_create_api
|
||||
)
|
||||
except exceptions.OTPFailedException:
|
||||
errors["base"] = "invalid_auth"
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["uiprotect"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["uiprotect==14.0.0"]
|
||||
"requirements": ["uiprotect==15.0.0"]
|
||||
}
|
||||
|
||||
@@ -62,10 +62,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: VeraConfigEntry) -> bool
|
||||
controller = veraApi.VeraController(base_url, subscription_registry)
|
||||
|
||||
try:
|
||||
all_devices = await hass.async_add_executor_job(controller.get_devices)
|
||||
|
||||
# pylint: disable-next=home-assistant-sequential-executor-jobs
|
||||
all_scenes = await hass.async_add_executor_job(controller.get_scenes)
|
||||
def _get_devices_and_scenes():
|
||||
"""Get devices and scenes from the Vera controller."""
|
||||
return controller.get_devices(), controller.get_scenes()
|
||||
|
||||
all_devices, all_scenes = await hass.async_add_executor_job(
|
||||
_get_devices_and_scenes
|
||||
)
|
||||
except RequestException as exception:
|
||||
# There was a network related error connecting to the Vera controller.
|
||||
_LOGGER.exception("Error communicating with Vera API")
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"universal_silabs_flasher",
|
||||
"serialx"
|
||||
],
|
||||
"requirements": ["zha==2.0.0", "zha-quirks==2.0.0"],
|
||||
"requirements": ["zha==2.0.0", "zha-quirks==2.1.0"],
|
||||
"usb": [
|
||||
{
|
||||
"description": "*2652*",
|
||||
|
||||
@@ -407,6 +407,9 @@
|
||||
"reset_alarm": {
|
||||
"name": "Reset alarm"
|
||||
},
|
||||
"reset_energy": {
|
||||
"name": "Reset energy"
|
||||
},
|
||||
"reset_frost_lock": {
|
||||
"name": "Frost lock reset"
|
||||
},
|
||||
@@ -1387,6 +1390,12 @@
|
||||
"status_indication": {
|
||||
"name": "Status indication"
|
||||
},
|
||||
"switch_action_l1": {
|
||||
"name": "Switch action L1"
|
||||
},
|
||||
"switch_action_l2": {
|
||||
"name": "Switch action L2"
|
||||
},
|
||||
"switch_actions": {
|
||||
"name": "Switch actions"
|
||||
},
|
||||
@@ -1399,6 +1408,12 @@
|
||||
"switch_type": {
|
||||
"name": "Switch type"
|
||||
},
|
||||
"switch_type_l1": {
|
||||
"name": "Switch type L1"
|
||||
},
|
||||
"switch_type_l2": {
|
||||
"name": "Switch type L2"
|
||||
},
|
||||
"temperature_display_mode": {
|
||||
"name": "Temperature display mode"
|
||||
},
|
||||
|
||||
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
||||
|
||||
APPLICATION_NAME: Final = "HomeAssistant"
|
||||
MAJOR_VERSION: Final = 2026
|
||||
MINOR_VERSION: Final = 7
|
||||
MINOR_VERSION: Final = 8
|
||||
PATCH_VERSION: Final = "0.dev0"
|
||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||
|
||||
@@ -35,7 +35,7 @@ file-read-backwards==2.0.0
|
||||
fnv-hash-fast==2.0.3
|
||||
go2rtc-client==0.4.0
|
||||
ha-ffmpeg==3.2.2
|
||||
habluetooth==6.23.1
|
||||
habluetooth==6.25.1
|
||||
hass-nabucasa==2.2.0
|
||||
hassil==3.8.0
|
||||
home-assistant-bluetooth==2.0.0
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "homeassistant"
|
||||
version = "2026.7.0.dev0"
|
||||
version = "2026.8.0.dev0"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*", "homeassistant/backports/LICENSE*"]
|
||||
description = "Open-source home automation platform running on Python 3."
|
||||
|
||||
Generated
+6
-6
@@ -83,7 +83,7 @@ PyRMVtransport==0.3.3
|
||||
PySrDaliGateway==0.21.0
|
||||
|
||||
# homeassistant.components.switchbot
|
||||
PySwitchbot==2.2.0
|
||||
PySwitchbot==2.3.0
|
||||
|
||||
# homeassistant.components.switchmate
|
||||
PySwitchmate==0.5.1
|
||||
@@ -1219,7 +1219,7 @@ ha-xthings-cloud==1.0.5
|
||||
habiticalib==0.4.7
|
||||
|
||||
# homeassistant.components.bluetooth
|
||||
habluetooth==6.23.1
|
||||
habluetooth==6.25.1
|
||||
|
||||
# homeassistant.components.hanna
|
||||
hanna-cloud==0.0.7
|
||||
@@ -1284,7 +1284,7 @@ homekit-audio-proxy==1.2.1
|
||||
homelink-integration-api==0.0.5
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==2.13.1
|
||||
homematicip==2.13.2
|
||||
|
||||
# homeassistant.components.homevolt
|
||||
homevolt==0.5.0
|
||||
@@ -3114,7 +3114,7 @@ surepy==0.9.0
|
||||
swisshydrodata==0.1.0
|
||||
|
||||
# homeassistant.components.switchbot_cloud
|
||||
switchbot-api==2.11.1
|
||||
switchbot-api==2.12.0
|
||||
|
||||
# homeassistant.components.synology_srm
|
||||
synology-srm==0.2.0
|
||||
@@ -3245,7 +3245,7 @@ uasiren==0.0.1
|
||||
uhooapi==1.2.8
|
||||
|
||||
# homeassistant.components.unifiprotect
|
||||
uiprotect==14.0.0
|
||||
uiprotect==15.0.0
|
||||
|
||||
# homeassistant.components.landisgyr_heat_meter
|
||||
ultraheat-api==0.6.1
|
||||
@@ -3453,7 +3453,7 @@ zeroconf==0.150.0
|
||||
zeversolar==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha-quirks==2.0.0
|
||||
zha-quirks==2.1.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zha==2.0.0
|
||||
|
||||
@@ -128,6 +128,54 @@ async def test_already_configured(
|
||||
assert result["reason"] == "already_configured"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
@pytest.mark.parametrize(
|
||||
("token", "expected_step"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"access_token": "mock-access-token",
|
||||
"expires_at": 9_999_999_999,
|
||||
"scope": " ".join(OAUTH2_SCOPES),
|
||||
},
|
||||
"reauth_confirm",
|
||||
),
|
||||
(
|
||||
{
|
||||
"access_token": "mock-access-token",
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"expires_at": 9_999_999_999,
|
||||
"scope": "account_info.read files.content.read files.content.write",
|
||||
},
|
||||
"reauth_permissions",
|
||||
),
|
||||
],
|
||||
ids=["missing_refresh_token", "missing_scope"],
|
||||
)
|
||||
async def test_reauth_confirm_step(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry,
|
||||
token: dict[str, object],
|
||||
expected_step: str,
|
||||
) -> None:
|
||||
"""Test reauth shows the correct confirmation step for the broken token."""
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry, data={**mock_config_entry.data, "token": token}
|
||||
)
|
||||
|
||||
result = await mock_config_entry.start_reauth_flow(hass)
|
||||
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["step_id"] == expected_step
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
|
||||
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
||||
assert result["step_id"] == "auth"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("current_request_with_host")
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
"""Test the Dropbox integration setup."""
|
||||
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from python_dropbox_api import DropboxAuthException, DropboxUnknownException
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.components.dropbox.const import DOMAIN, OAUTH2_SCOPES
|
||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
ImplementationUnavailableError,
|
||||
@@ -80,6 +82,46 @@ async def test_setup_entry_implementation_unavailable(
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_dropbox_client")
|
||||
@pytest.mark.parametrize(
|
||||
"token",
|
||||
[
|
||||
{
|
||||
"access_token": "mock-access-token",
|
||||
"expires_at": 9_999_999_999,
|
||||
"scope": " ".join(OAUTH2_SCOPES),
|
||||
},
|
||||
{
|
||||
"access_token": "mock-access-token",
|
||||
"refresh_token": "mock-refresh-token",
|
||||
"expires_at": 9_999_999_999,
|
||||
"scope": "account_info.read files.content.read files.content.write",
|
||||
},
|
||||
],
|
||||
ids=["missing_refresh_token", "missing_scope"],
|
||||
)
|
||||
async def test_setup_entry_triggers_reauth(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
token: dict[str, Any],
|
||||
) -> None:
|
||||
"""Test that a broken token triggers a reauth flow during setup."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
mock_config_entry, data={**mock_config_entry.data, "token": token}
|
||||
)
|
||||
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
|
||||
|
||||
flows = hass.config_entries.flow.async_progress_by_handler(DOMAIN)
|
||||
assert len(flows) == 1
|
||||
assert flows[0]["context"]["source"] == SOURCE_REAUTH
|
||||
assert flows[0]["context"]["entry_id"] == mock_config_entry.entry_id
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("mock_dropbox_client")
|
||||
async def test_unload_entry(
|
||||
hass: HomeAssistant,
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_113_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.bathroom_rh_humidity-state]
|
||||
@@ -44,7 +44,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bathroom RH Humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.bathroom_rh_humidity',
|
||||
@@ -90,7 +90,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_rh',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_113_iaq_rh',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.bathroom_rh_humidity_air_quality_index-state]
|
||||
@@ -98,7 +98,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bathroom RH Humidity air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.bathroom_rh_humidity_air_quality_index',
|
||||
@@ -144,7 +144,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_60_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.bedroom_valve_humidity-state]
|
||||
@@ -153,7 +153,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom valve Humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.bedroom_valve_humidity',
|
||||
@@ -199,7 +199,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_rh',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_60_iaq_rh',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.bedroom_valve_humidity_air_quality_index-state]
|
||||
@@ -207,7 +207,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Bedroom valve Humidity air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.bedroom_valve_humidity_air_quality_index',
|
||||
@@ -253,7 +253,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_61_co2',
|
||||
'unit_of_measurement': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.hall_valve_carbon_dioxide-state]
|
||||
@@ -262,7 +262,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hall valve Carbon dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.hall_valve_carbon_dioxide',
|
||||
@@ -308,7 +308,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_co2',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_61_iaq_co2',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.hall_valve_co2_air_quality_index-state]
|
||||
@@ -316,7 +316,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Hall valve CO2 air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.hall_valve_co2_air_quality_index',
|
||||
@@ -362,7 +362,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_50_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.kitchen_rh_humidity-state]
|
||||
@@ -371,7 +371,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen RH Humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.kitchen_rh_humidity',
|
||||
@@ -417,7 +417,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_rh',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_50_iaq_rh',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.kitchen_rh_humidity_air_quality_index-state]
|
||||
@@ -425,7 +425,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Kitchen RH Humidity air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.kitchen_rh_humidity_air_quality_index',
|
||||
@@ -635,7 +635,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'target_flow_level',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_1_target_flow_level',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_target_flow_level-state]
|
||||
@@ -643,7 +643,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Living Target flow level',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.living_target_flow_level',
|
||||
@@ -781,7 +781,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_2_co2',
|
||||
'unit_of_measurement': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.office_co2_carbon_dioxide-state]
|
||||
@@ -790,7 +790,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Office CO2 Carbon dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.office_co2_carbon_dioxide',
|
||||
@@ -836,7 +836,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_co2',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_2_iaq_co2',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.office_co2_co2_air_quality_index-state]
|
||||
@@ -844,7 +844,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Office CO2 CO2 air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.office_co2_co2_air_quality_index',
|
||||
@@ -890,7 +890,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_62_co2',
|
||||
'unit_of_measurement': 'ppm',
|
||||
'unit_of_measurement': <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.study_valve_carbon_dioxide-state]
|
||||
@@ -899,7 +899,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'carbon_dioxide',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Carbon dioxide',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: 'ppm',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PARTS_PER_MILLION: 'ppm'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.study_valve_carbon_dioxide',
|
||||
@@ -945,7 +945,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_co2',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_62_iaq_co2',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.study_valve_co2_air_quality_index-state]
|
||||
@@ -953,7 +953,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve CO2 air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.study_valve_co2_air_quality_index',
|
||||
@@ -999,7 +999,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_62_humidity',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.study_valve_humidity-state]
|
||||
@@ -1008,7 +1008,7 @@
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'humidity',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Humidity',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.study_valve_humidity',
|
||||
@@ -1054,7 +1054,7 @@
|
||||
'supported_features': 0,
|
||||
'translation_key': 'iaq_rh',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_62_iaq_rh',
|
||||
'unit_of_measurement': '%',
|
||||
'unit_of_measurement': <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.study_valve_humidity_air_quality_index-state]
|
||||
@@ -1062,7 +1062,7 @@
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'Study valve Humidity air quality index',
|
||||
<SensorEntityCapabilityAttribute.STATE_CLASS: 'state_class'>: <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: '%',
|
||||
<EntityStateAttribute.UNIT_OF_MEASUREMENT: 'unit_of_measurement'>: <UnitOfRatio.PERCENTAGE: '%'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.study_valve_humidity_air_quality_index',
|
||||
|
||||
@@ -42,6 +42,7 @@ from homeassistant.const import (
|
||||
EVENT_LOGBOOK_ENTRY,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Context, Event, HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
@@ -345,6 +346,7 @@ def create_state_changed_event_from_old_new(
|
||||
state=new_state and new_state.get("state"),
|
||||
entity_id=entity_id,
|
||||
icon=None,
|
||||
attributes=None,
|
||||
context_only=False,
|
||||
data=None,
|
||||
context=None,
|
||||
@@ -1857,6 +1859,46 @@ async def test_icon_and_state(
|
||||
assert response_json[2]["state"] == STATE_OFF
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("recorder_mock")
|
||||
async def test_state_attributes_in_logbook(
|
||||
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
) -> None:
|
||||
"""Test state attributes are exposed in the logbook like the history.
|
||||
|
||||
The recorded subset is surfaced, so attributes the recorder excludes
|
||||
(such as supported_features) must not appear.
|
||||
"""
|
||||
await asyncio.gather(
|
||||
*[
|
||||
async_setup_component(hass, domain, {})
|
||||
for domain in ("homeassistant", "logbook")
|
||||
]
|
||||
)
|
||||
await async_recorder_block_till_done(hass)
|
||||
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||
|
||||
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
|
||||
hass.states.async_set(
|
||||
"event.doorbell",
|
||||
"2024-01-01T00:00:00.000+00:00",
|
||||
{"event_type": "ring", "supported_features": 1},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"event.doorbell", "2024-01-01T00:01:00.000+00:00", {"event_type": "motion"}
|
||||
)
|
||||
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
client = await hass_client()
|
||||
response_json = await _async_fetch_logbook(client)
|
||||
|
||||
entries = [e for e in response_json if e.get("entity_id") == "event.doorbell"]
|
||||
assert len(entries) == 2
|
||||
assert entries[0]["attributes"] == {"event_type": "ring"}
|
||||
assert entries[1]["attributes"] == {"event_type": "motion"}
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("recorder_mock")
|
||||
async def test_fire_logbook_entries(
|
||||
hass: HomeAssistant, hass_client: ClientSessionGenerator
|
||||
@@ -3335,6 +3377,7 @@ async def test_parent_user_attribution_does_not_use_origin_event_fallback(
|
||||
state=STATE_ON,
|
||||
entity_id="switch.heater",
|
||||
icon=None,
|
||||
attributes=None,
|
||||
context_only=False,
|
||||
data={},
|
||||
context=child_context,
|
||||
|
||||
@@ -19,6 +19,7 @@ def test_lazy_event_partial_state_context() -> None:
|
||||
state="state",
|
||||
entity_id="entity_id",
|
||||
icon="icon",
|
||||
attributes=None,
|
||||
context_only=False,
|
||||
data={},
|
||||
context=Mock(),
|
||||
|
||||
@@ -35,6 +35,7 @@ from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import Event, HomeAssistant, State, callback
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
@@ -1479,6 +1480,64 @@ async def test_subscribe_unsubscribe_logbook_stream(
|
||||
) == listeners_without_writes(init_listeners)
|
||||
|
||||
|
||||
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
|
||||
async def test_subscribe_logbook_stream_state_attributes(
|
||||
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
"""Test the live logbook stream exposes allowlisted state attributes."""
|
||||
now = dt_util.utcnow()
|
||||
await asyncio.gather(
|
||||
*[
|
||||
async_setup_component(hass, domain, {})
|
||||
for domain in ("homeassistant", "logbook")
|
||||
]
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
hass.states.async_set("binary_sensor.is_light", STATE_ON)
|
||||
hass.states.async_set("binary_sensor.is_light", STATE_OFF)
|
||||
await hass.async_block_till_done()
|
||||
await async_wait_recording_done(hass)
|
||||
|
||||
websocket_client = await hass_ws_client()
|
||||
await websocket_client.send_json(
|
||||
{"id": 7, "type": "logbook/event_stream", "start_time": now.isoformat()}
|
||||
)
|
||||
|
||||
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||
assert msg["id"] == 7
|
||||
assert msg["type"] == TYPE_RESULT
|
||||
assert msg["success"]
|
||||
|
||||
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||
assert msg["event"]["partial"] is True
|
||||
|
||||
await hass.async_block_till_done()
|
||||
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||
assert "partial" not in msg["event"]
|
||||
assert msg["event"]["events"] == []
|
||||
|
||||
hass.states.async_set("event.doorbell", STATE_UNKNOWN, {"event_type": None})
|
||||
hass.states.async_set(
|
||||
"event.doorbell",
|
||||
"2024-01-01T00:00:00.000+00:00",
|
||||
{"event_type": "ring", "supported_features": 1},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||
assert msg["id"] == 7
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["events"] == [
|
||||
{
|
||||
"entity_id": "event.doorbell",
|
||||
"state": "2024-01-01T00:00:00.000+00:00",
|
||||
"attributes": {"event_type": "ring"},
|
||||
"when": ANY,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@patch("homeassistant.components.logbook.websocket_api.EVENT_COALESCE_TIME", 0)
|
||||
async def test_subscribe_unsubscribe_logbook_stream_entities(
|
||||
recorder_mock: Recorder, hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
|
||||
@@ -80,6 +80,29 @@ async def test_missing_sensor_graceful_handling(
|
||||
assert state.state == "Charging"
|
||||
|
||||
|
||||
async def test_websocket_callback_updates_entities(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_charger: MagicMock,
|
||||
) -> None:
|
||||
"""Test the websocket callback pushes updates to entity state."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state
|
||||
assert state.state == "Charging"
|
||||
|
||||
mock_charger.status = "Sleeping"
|
||||
await mock_charger.callback()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("sensor.openevse_mock_config_charging_status")
|
||||
assert state
|
||||
assert state.state == "Sleeping"
|
||||
|
||||
|
||||
async def test_sensor_unavailable_on_coordinator_timeout(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
|
||||
@@ -318,6 +318,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -359,6 +360,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -1143,6 +1145,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -1184,6 +1187,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -2411,6 +2415,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -2452,6 +2457,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -3400,6 +3406,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -3441,6 +3448,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -4273,6 +4281,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -4314,6 +4323,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -5088,6 +5098,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -5129,6 +5140,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -5971,6 +5983,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
@@ -6012,6 +6025,7 @@
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'always',
|
||||
'delayed',
|
||||
'delegated',
|
||||
'scheduled',
|
||||
]),
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user