mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Merge branch 'dev' into reorder-group-member
This commit is contained in:
commit
643c2cb7b7
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["aioamazondevices"],
|
"loggers": ["aioamazondevices"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["aioamazondevices==3.2.10"]
|
"requirements": ["aioamazondevices==3.5.0"]
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["bluecurrent_api"],
|
"loggers": ["bluecurrent_api"],
|
||||||
"requirements": ["bluecurrent-api==1.2.3"]
|
"requirements": ["bluecurrent-api==1.2.4"]
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ _CLOUD_ERRORS: dict[
|
|||||||
] = {
|
] = {
|
||||||
TimeoutError: (
|
TimeoutError: (
|
||||||
HTTPStatus.BAD_GATEWAY,
|
HTTPStatus.BAD_GATEWAY,
|
||||||
"Unable to reach the Home Assistant cloud.",
|
"Unable to reach the Home Assistant Cloud.",
|
||||||
),
|
),
|
||||||
aiohttp.ClientError: (
|
aiohttp.ClientError: (
|
||||||
HTTPStatus.INTERNAL_SERVER_ERROR,
|
HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
@ -10,8 +10,6 @@ from typing import Any
|
|||||||
|
|
||||||
from jsonpath import jsonpath
|
from jsonpath import jsonpath
|
||||||
|
|
||||||
from homeassistant.components.sensor import SensorDeviceClass
|
|
||||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_COMMAND,
|
CONF_COMMAND,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
@ -188,16 +186,7 @@ class CommandSensor(ManualTriggerSensorEntity):
|
|||||||
self.entity_id, variables, None
|
self.entity_id, variables, None
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.device_class not in {
|
self._set_native_value_with_possible_timestamp(value)
|
||||||
SensorDeviceClass.DATE,
|
|
||||||
SensorDeviceClass.TIMESTAMP,
|
|
||||||
}:
|
|
||||||
self._attr_native_value = value
|
|
||||||
elif value is not None:
|
|
||||||
self._attr_native_value = async_parse_date_datetime(
|
|
||||||
value, self.entity_id, self.device_class
|
|
||||||
)
|
|
||||||
|
|
||||||
self._process_manual_data(variables)
|
self._process_manual_data(variables)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@ -320,7 +320,12 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
|||||||
# changed state, then we know it will still be zero.
|
# changed state, then we know it will still be zero.
|
||||||
return
|
return
|
||||||
schedule_max_sub_interval_exceeded(new_state)
|
schedule_max_sub_interval_exceeded(new_state)
|
||||||
calc_derivative(new_state, new_state.state, event.data["old_last_reported"])
|
calc_derivative(
|
||||||
|
new_state,
|
||||||
|
new_state.state,
|
||||||
|
event.data["last_reported"],
|
||||||
|
event.data["old_last_reported"],
|
||||||
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def on_state_changed(event: Event[EventStateChangedData]) -> None:
|
def on_state_changed(event: Event[EventStateChangedData]) -> None:
|
||||||
@ -334,19 +339,27 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
|||||||
schedule_max_sub_interval_exceeded(new_state)
|
schedule_max_sub_interval_exceeded(new_state)
|
||||||
old_state = event.data["old_state"]
|
old_state = event.data["old_state"]
|
||||||
if old_state is not None:
|
if old_state is not None:
|
||||||
calc_derivative(new_state, old_state.state, old_state.last_reported)
|
calc_derivative(
|
||||||
|
new_state,
|
||||||
|
old_state.state,
|
||||||
|
new_state.last_updated,
|
||||||
|
old_state.last_reported,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
# On first state change from none, update availability
|
# On first state change from none, update availability
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
def calc_derivative(
|
def calc_derivative(
|
||||||
new_state: State, old_value: str, old_last_reported: datetime
|
new_state: State,
|
||||||
|
old_value: str,
|
||||||
|
new_timestamp: datetime,
|
||||||
|
old_timestamp: datetime,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle the sensor state changes."""
|
"""Handle the sensor state changes."""
|
||||||
if not _is_decimal_state(old_value):
|
if not _is_decimal_state(old_value):
|
||||||
if self._last_valid_state_time:
|
if self._last_valid_state_time:
|
||||||
old_value = self._last_valid_state_time[0]
|
old_value = self._last_valid_state_time[0]
|
||||||
old_last_reported = self._last_valid_state_time[1]
|
old_timestamp = self._last_valid_state_time[1]
|
||||||
else:
|
else:
|
||||||
# Sensor becomes valid for the first time, just keep the restored value
|
# Sensor becomes valid for the first time, just keep the restored value
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
@ -358,12 +371,10 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
|||||||
"" if unit is None else unit
|
"" if unit is None else unit
|
||||||
)
|
)
|
||||||
|
|
||||||
self._prune_state_list(new_state.last_reported)
|
self._prune_state_list(new_timestamp)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
elapsed_time = (
|
elapsed_time = (new_timestamp - old_timestamp).total_seconds()
|
||||||
new_state.last_reported - old_last_reported
|
|
||||||
).total_seconds()
|
|
||||||
delta_value = Decimal(new_state.state) - Decimal(old_value)
|
delta_value = Decimal(new_state.state) - Decimal(old_value)
|
||||||
new_derivative = (
|
new_derivative = (
|
||||||
delta_value
|
delta_value
|
||||||
@ -392,12 +403,10 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# add latest derivative to the window list
|
# add latest derivative to the window list
|
||||||
self._state_list.append(
|
self._state_list.append((old_timestamp, new_timestamp, new_derivative))
|
||||||
(old_last_reported, new_state.last_reported, new_derivative)
|
|
||||||
)
|
|
||||||
self._last_valid_state_time = (
|
self._last_valid_state_time = (
|
||||||
new_state.state,
|
new_state.state,
|
||||||
new_state.last_reported,
|
new_timestamp,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If outside of time window just report derivative (is the same as modeling it in the window),
|
# If outside of time window just report derivative (is the same as modeling it in the window),
|
||||||
@ -405,9 +414,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
|
|||||||
if elapsed_time > self._time_window:
|
if elapsed_time > self._time_window:
|
||||||
derivative = new_derivative
|
derivative = new_derivative
|
||||||
else:
|
else:
|
||||||
derivative = self._calc_derivative_from_state_list(
|
derivative = self._calc_derivative_from_state_list(new_timestamp)
|
||||||
new_state.last_reported
|
|
||||||
)
|
|
||||||
self._write_native_value(derivative)
|
self._write_native_value(derivative)
|
||||||
|
|
||||||
source_state = self.hass.states.get(self._sensor_source_id)
|
source_state = self.hass.states.get(self._sensor_source_id)
|
||||||
|
@ -295,23 +295,7 @@ class RuntimeEntryData:
|
|||||||
needed_platforms.add(Platform.BINARY_SENSOR)
|
needed_platforms.add(Platform.BINARY_SENSOR)
|
||||||
needed_platforms.add(Platform.SELECT)
|
needed_platforms.add(Platform.SELECT)
|
||||||
|
|
||||||
ent_reg = er.async_get(hass)
|
needed_platforms.update(INFO_TYPE_TO_PLATFORM[type(info)] for info in infos)
|
||||||
registry_get_entity = ent_reg.async_get_entity_id
|
|
||||||
for info in infos:
|
|
||||||
platform = INFO_TYPE_TO_PLATFORM[type(info)]
|
|
||||||
needed_platforms.add(platform)
|
|
||||||
# If the unique id is in the old format, migrate it
|
|
||||||
# except if they downgraded and upgraded, there might be a duplicate
|
|
||||||
# so we want to keep the one that was already there.
|
|
||||||
if (
|
|
||||||
(old_unique_id := info.unique_id)
|
|
||||||
and (old_entry := registry_get_entity(platform, DOMAIN, old_unique_id))
|
|
||||||
and (new_unique_id := build_device_unique_id(mac, info))
|
|
||||||
!= old_unique_id
|
|
||||||
and not registry_get_entity(platform, DOMAIN, new_unique_id)
|
|
||||||
):
|
|
||||||
ent_reg.async_update_entity(old_entry, new_unique_id=new_unique_id)
|
|
||||||
|
|
||||||
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
await self._ensure_platforms_loaded(hass, entry, needed_platforms)
|
||||||
|
|
||||||
# Make a dict of the EntityInfo by type and send
|
# Make a dict of the EntityInfo by type and send
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
"mqtt": ["esphome/discover/#"],
|
"mqtt": ["esphome/discover/#"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aioesphomeapi==35.0.0",
|
"aioesphomeapi==36.0.1",
|
||||||
"esphome-dashboard-api==1.3.0",
|
"esphome-dashboard-api==1.3.0",
|
||||||
"bleak-esphome==3.1.0"
|
"bleak-esphome==3.1.0"
|
||||||
],
|
],
|
||||||
|
@ -20,5 +20,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||||
"integration_type": "system",
|
"integration_type": "system",
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"requirements": ["home-assistant-frontend==20250702.2"]
|
"requirements": ["home-assistant-frontend==20250702.3"]
|
||||||
}
|
}
|
||||||
|
@ -541,6 +541,11 @@ class AutomowerSensorEntity(AutomowerBaseEntity, SensorEntity):
|
|||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return self.entity_description.extra_state_attributes_fn(self.mower_attributes)
|
return self.entity_description.extra_state_attributes_fn(self.mower_attributes)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return the available attribute of the entity."""
|
||||||
|
return super().available and self.native_value is not None
|
||||||
|
|
||||||
|
|
||||||
class WorkAreaSensorEntity(WorkAreaAvailableEntity, SensorEntity):
|
class WorkAreaSensorEntity(WorkAreaAvailableEntity, SensorEntity):
|
||||||
"""Defining the Work area sensors with WorkAreaSensorEntityDescription."""
|
"""Defining the Work area sensors with WorkAreaSensorEntityDescription."""
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["imgw_pib==1.4.1"]
|
"requirements": ["imgw_pib==1.4.2"]
|
||||||
}
|
}
|
||||||
|
@ -463,7 +463,7 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Handle sensor state update when sub interval is configured."""
|
"""Handle sensor state update when sub interval is configured."""
|
||||||
self._integrate_on_state_update_with_max_sub_interval(
|
self._integrate_on_state_update_with_max_sub_interval(
|
||||||
None, event.data["old_state"], event.data["new_state"]
|
None, None, event.data["old_state"], event.data["new_state"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -472,13 +472,17 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Handle sensor state report when sub interval is configured."""
|
"""Handle sensor state report when sub interval is configured."""
|
||||||
self._integrate_on_state_update_with_max_sub_interval(
|
self._integrate_on_state_update_with_max_sub_interval(
|
||||||
event.data["old_last_reported"], None, event.data["new_state"]
|
event.data["old_last_reported"],
|
||||||
|
event.data["last_reported"],
|
||||||
|
None,
|
||||||
|
event.data["new_state"],
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _integrate_on_state_update_with_max_sub_interval(
|
def _integrate_on_state_update_with_max_sub_interval(
|
||||||
self,
|
self,
|
||||||
old_last_reported: datetime | None,
|
old_timestamp: datetime | None,
|
||||||
|
new_timestamp: datetime | None,
|
||||||
old_state: State | None,
|
old_state: State | None,
|
||||||
new_state: State | None,
|
new_state: State | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -489,7 +493,9 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
"""
|
"""
|
||||||
self._cancel_max_sub_interval_exceeded_callback()
|
self._cancel_max_sub_interval_exceeded_callback()
|
||||||
try:
|
try:
|
||||||
self._integrate_on_state_change(old_last_reported, old_state, new_state)
|
self._integrate_on_state_change(
|
||||||
|
old_timestamp, new_timestamp, old_state, new_state
|
||||||
|
)
|
||||||
self._last_integration_trigger = _IntegrationTrigger.StateEvent
|
self._last_integration_trigger = _IntegrationTrigger.StateEvent
|
||||||
self._last_integration_time = datetime.now(tz=UTC)
|
self._last_integration_time = datetime.now(tz=UTC)
|
||||||
finally:
|
finally:
|
||||||
@ -503,7 +509,7 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Handle sensor state change."""
|
"""Handle sensor state change."""
|
||||||
return self._integrate_on_state_change(
|
return self._integrate_on_state_change(
|
||||||
None, event.data["old_state"], event.data["new_state"]
|
None, None, event.data["old_state"], event.data["new_state"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -512,12 +518,16 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Handle sensor state report."""
|
"""Handle sensor state report."""
|
||||||
return self._integrate_on_state_change(
|
return self._integrate_on_state_change(
|
||||||
event.data["old_last_reported"], None, event.data["new_state"]
|
event.data["old_last_reported"],
|
||||||
|
event.data["last_reported"],
|
||||||
|
None,
|
||||||
|
event.data["new_state"],
|
||||||
)
|
)
|
||||||
|
|
||||||
def _integrate_on_state_change(
|
def _integrate_on_state_change(
|
||||||
self,
|
self,
|
||||||
old_last_reported: datetime | None,
|
old_timestamp: datetime | None,
|
||||||
|
new_timestamp: datetime | None,
|
||||||
old_state: State | None,
|
old_state: State | None,
|
||||||
new_state: State | None,
|
new_state: State | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -531,16 +541,17 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
|
|
||||||
if old_state:
|
if old_state:
|
||||||
# state has changed, we recover old_state from the event
|
# state has changed, we recover old_state from the event
|
||||||
|
new_timestamp = new_state.last_updated
|
||||||
old_state_state = old_state.state
|
old_state_state = old_state.state
|
||||||
old_last_reported = old_state.last_reported
|
old_timestamp = old_state.last_reported
|
||||||
else:
|
else:
|
||||||
# event state reported without any state change
|
# first state or event state reported without any state change
|
||||||
old_state_state = new_state.state
|
old_state_state = new_state.state
|
||||||
|
|
||||||
self._attr_available = True
|
self._attr_available = True
|
||||||
self._derive_and_set_attributes_from_state(new_state)
|
self._derive_and_set_attributes_from_state(new_state)
|
||||||
|
|
||||||
if old_last_reported is None and old_state is None:
|
if old_timestamp is None and old_state is None:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -551,11 +562,12 @@ class IntegrationSensor(RestoreSensor):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
assert old_last_reported is not None
|
assert new_timestamp is not None
|
||||||
|
assert old_timestamp is not None
|
||||||
elapsed_seconds = Decimal(
|
elapsed_seconds = Decimal(
|
||||||
(new_state.last_reported - old_last_reported).total_seconds()
|
(new_timestamp - old_timestamp).total_seconds()
|
||||||
if self._last_integration_trigger == _IntegrationTrigger.StateEvent
|
if self._last_integration_trigger == _IntegrationTrigger.StateEvent
|
||||||
else (new_state.last_reported - self._last_integration_time).total_seconds()
|
else (new_timestamp - self._last_integration_time).total_seconds()
|
||||||
)
|
)
|
||||||
|
|
||||||
area = self._method.calculate_area_with_two_states(elapsed_seconds, *states)
|
area = self._method.calculate_area_with_two_states(elapsed_seconds, *states)
|
||||||
|
@ -98,6 +98,12 @@ def validate_sensor_state_and_device_class_config(config: ConfigType) -> ConfigT
|
|||||||
f"together with state class `{state_class}`"
|
f"together with state class `{state_class}`"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
unit_of_measurement: str | None
|
||||||
|
if (
|
||||||
|
unit_of_measurement := config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
|
) is not None and not unit_of_measurement.strip():
|
||||||
|
config.pop(CONF_UNIT_OF_MEASUREMENT)
|
||||||
|
|
||||||
# Only allow `options` to be set for `enum` sensors
|
# Only allow `options` to be set for `enum` sensors
|
||||||
# to limit the possible sensor values
|
# to limit the possible sensor values
|
||||||
if (options := config.get(CONF_OPTIONS)) is not None:
|
if (options := config.get(CONF_OPTIONS)) is not None:
|
||||||
|
@ -39,7 +39,10 @@ class OllamaTaskEntity(
|
|||||||
):
|
):
|
||||||
"""Ollama AI Task entity."""
|
"""Ollama AI Task entity."""
|
||||||
|
|
||||||
_attr_supported_features = ai_task.AITaskEntityFeature.GENERATE_DATA
|
_attr_supported_features = (
|
||||||
|
ai_task.AITaskEntityFeature.GENERATE_DATA
|
||||||
|
| ai_task.AITaskEntityFeature.SUPPORT_ATTACHMENTS
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_generate_data(
|
async def _async_generate_data(
|
||||||
self,
|
self,
|
||||||
|
@ -106,9 +106,18 @@ def _convert_content(
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
if isinstance(chat_content, conversation.UserContent):
|
if isinstance(chat_content, conversation.UserContent):
|
||||||
|
images: list[ollama.Image] = []
|
||||||
|
for attachment in chat_content.attachments or ():
|
||||||
|
if not attachment.mime_type.startswith("image/"):
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="unsupported_attachment_type",
|
||||||
|
)
|
||||||
|
images.append(ollama.Image(value=attachment.path))
|
||||||
return ollama.Message(
|
return ollama.Message(
|
||||||
role=MessageRole.USER.value,
|
role=MessageRole.USER.value,
|
||||||
content=chat_content.content,
|
content=chat_content.content,
|
||||||
|
images=images or None,
|
||||||
)
|
)
|
||||||
if isinstance(chat_content, conversation.SystemContent):
|
if isinstance(chat_content, conversation.SystemContent):
|
||||||
return ollama.Message(
|
return ollama.Message(
|
||||||
|
@ -94,5 +94,10 @@
|
|||||||
"download": "[%key:component::ollama::config_subentries::conversation::progress::download%]"
|
"download": "[%key:component::ollama::config_subentries::conversation::progress::download%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"exceptions": {
|
||||||
|
"unsupported_attachment_type": {
|
||||||
|
"message": "Ollama only supports image attachments in user content, but received non-image attachment."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,9 +13,7 @@ from homeassistant.components.sensor import (
|
|||||||
CONF_STATE_CLASS,
|
CONF_STATE_CLASS,
|
||||||
DOMAIN as SENSOR_DOMAIN,
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||||
SensorDeviceClass,
|
|
||||||
)
|
)
|
||||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
CONF_FORCE_UPDATE,
|
CONF_FORCE_UPDATE,
|
||||||
@ -181,18 +179,6 @@ class RestSensor(ManualTriggerSensorEntity, RestEntity):
|
|||||||
self.entity_id, variables, None
|
self.entity_id, variables, None
|
||||||
)
|
)
|
||||||
|
|
||||||
if value is None or self.device_class not in (
|
self._set_native_value_with_possible_timestamp(value)
|
||||||
SensorDeviceClass.DATE,
|
|
||||||
SensorDeviceClass.TIMESTAMP,
|
|
||||||
):
|
|
||||||
self._attr_native_value = value
|
|
||||||
self._process_manual_data(variables)
|
|
||||||
self.async_write_ha_state()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_native_value = async_parse_date_datetime(
|
|
||||||
value, self.entity_id, self.device_class
|
|
||||||
)
|
|
||||||
|
|
||||||
self._process_manual_data(variables)
|
self._process_manual_data(variables)
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
@ -7,8 +7,7 @@ from typing import Any, cast
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.sensor import CONF_STATE_CLASS, SensorDeviceClass
|
from homeassistant.components.sensor import CONF_STATE_CLASS
|
||||||
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ATTRIBUTE,
|
CONF_ATTRIBUTE,
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
@ -218,17 +217,7 @@ class ScrapeSensor(CoordinatorEntity[ScrapeCoordinator], ManualTriggerSensorEnti
|
|||||||
self.entity_id, variables, None
|
self.entity_id, variables, None
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.device_class not in {
|
self._set_native_value_with_possible_timestamp(value)
|
||||||
SensorDeviceClass.DATE,
|
|
||||||
SensorDeviceClass.TIMESTAMP,
|
|
||||||
}:
|
|
||||||
self._attr_native_value = value
|
|
||||||
self._process_manual_data(variables)
|
|
||||||
return
|
|
||||||
|
|
||||||
self._attr_native_value = async_parse_date_datetime(
|
|
||||||
value, self.entity_id, self.device_class
|
|
||||||
)
|
|
||||||
self._process_manual_data(variables)
|
self._process_manual_data(variables)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pysmarlaapi", "pysignalr"],
|
"loggers": ["pysmarlaapi", "pysignalr"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pysmarlaapi==0.9.0"]
|
"requirements": ["pysmarlaapi==0.9.1"]
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ class SnmpSensor(ManualTriggerSensorEntity):
|
|||||||
self.entity_id, variables, STATE_UNKNOWN
|
self.entity_id, variables, STATE_UNKNOWN
|
||||||
)
|
)
|
||||||
|
|
||||||
self._attr_native_value = value
|
self._set_native_value_with_possible_timestamp(value)
|
||||||
self._process_manual_data(variables)
|
self._process_manual_data(variables)
|
||||||
|
|
||||||
|
|
||||||
|
@ -401,9 +401,10 @@ class SQLSensor(ManualTriggerSensorEntity):
|
|||||||
if data is not None and self._template is not None:
|
if data is not None and self._template is not None:
|
||||||
variables = self._template_variables_with_value(data)
|
variables = self._template_variables_with_value(data)
|
||||||
if self._render_availability_template(variables):
|
if self._render_availability_template(variables):
|
||||||
self._attr_native_value = self._template.async_render_as_value_template(
|
_value = self._template.async_render_as_value_template(
|
||||||
self.entity_id, variables, None
|
self.entity_id, variables, None
|
||||||
)
|
)
|
||||||
|
self._set_native_value_with_possible_timestamp(_value)
|
||||||
self._process_manual_data(variables)
|
self._process_manual_data(variables)
|
||||||
else:
|
else:
|
||||||
self._attr_native_value = data
|
self._attr_native_value = data
|
||||||
|
@ -727,12 +727,11 @@ class StatisticsSensor(SensorEntity):
|
|||||||
|
|
||||||
def _async_handle_new_state(
|
def _async_handle_new_state(
|
||||||
self,
|
self,
|
||||||
reported_state: State | None,
|
reported_state: State,
|
||||||
|
timestamp: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle the sensor state changes."""
|
"""Handle the sensor state changes."""
|
||||||
if (new_state := reported_state) is None:
|
self._add_state_to_queue(reported_state, timestamp)
|
||||||
return
|
|
||||||
self._add_state_to_queue(new_state)
|
|
||||||
self._async_purge_update_and_schedule()
|
self._async_purge_update_and_schedule()
|
||||||
|
|
||||||
if self._preview_callback:
|
if self._preview_callback:
|
||||||
@ -747,14 +746,18 @@ class StatisticsSensor(SensorEntity):
|
|||||||
self,
|
self,
|
||||||
event: Event[EventStateChangedData],
|
event: Event[EventStateChangedData],
|
||||||
) -> None:
|
) -> None:
|
||||||
self._async_handle_new_state(event.data["new_state"])
|
if (new_state := event.data["new_state"]) is None:
|
||||||
|
return
|
||||||
|
self._async_handle_new_state(new_state, new_state.last_updated_timestamp)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _async_stats_sensor_state_report_listener(
|
def _async_stats_sensor_state_report_listener(
|
||||||
self,
|
self,
|
||||||
event: Event[EventStateReportedData],
|
event: Event[EventStateReportedData],
|
||||||
) -> None:
|
) -> None:
|
||||||
self._async_handle_new_state(event.data["new_state"])
|
self._async_handle_new_state(
|
||||||
|
event.data["new_state"], event.data["last_reported"].timestamp()
|
||||||
|
)
|
||||||
|
|
||||||
async def _async_stats_sensor_startup(self) -> None:
|
async def _async_stats_sensor_startup(self) -> None:
|
||||||
"""Add listener and get recorded state.
|
"""Add listener and get recorded state.
|
||||||
@ -785,7 +788,9 @@ class StatisticsSensor(SensorEntity):
|
|||||||
"""Register callbacks."""
|
"""Register callbacks."""
|
||||||
await self._async_stats_sensor_startup()
|
await self._async_stats_sensor_startup()
|
||||||
|
|
||||||
def _add_state_to_queue(self, new_state: State) -> None:
|
def _add_state_to_queue(
|
||||||
|
self, new_state: State, last_reported_timestamp: float
|
||||||
|
) -> None:
|
||||||
"""Add the state to the queue."""
|
"""Add the state to the queue."""
|
||||||
|
|
||||||
# Attention: it is not safe to store the new_state object,
|
# Attention: it is not safe to store the new_state object,
|
||||||
@ -805,7 +810,7 @@ class StatisticsSensor(SensorEntity):
|
|||||||
self.states.append(new_state.state == "on")
|
self.states.append(new_state.state == "on")
|
||||||
else:
|
else:
|
||||||
self.states.append(float(new_state.state))
|
self.states.append(float(new_state.state))
|
||||||
self.ages.append(new_state.last_reported_timestamp)
|
self.ages.append(last_reported_timestamp)
|
||||||
self._attr_extra_state_attributes[STAT_SOURCE_VALUE_VALID] = True
|
self._attr_extra_state_attributes[STAT_SOURCE_VALUE_VALID] = True
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self._attr_extra_state_attributes[STAT_SOURCE_VALUE_VALID] = False
|
self._attr_extra_state_attributes[STAT_SOURCE_VALUE_VALID] = False
|
||||||
@ -1062,7 +1067,7 @@ class StatisticsSensor(SensorEntity):
|
|||||||
self._fetch_states_from_database
|
self._fetch_states_from_database
|
||||||
):
|
):
|
||||||
for state in reversed(states):
|
for state in reversed(states):
|
||||||
self._add_state_to_queue(state)
|
self._add_state_to_queue(state, state.last_reported_timestamp)
|
||||||
self._calculate_state_attributes(state)
|
self._calculate_state_attributes(state)
|
||||||
self._async_purge_update_and_schedule()
|
self._async_purge_update_and_schedule()
|
||||||
|
|
||||||
|
@ -41,5 +41,5 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["switchbot"],
|
"loggers": ["switchbot"],
|
||||||
"quality_scale": "gold",
|
"quality_scale": "gold",
|
||||||
"requirements": ["PySwitchbot==0.68.1"]
|
"requirements": ["PySwitchbot==0.68.2"]
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ from homeassistant.components.alarm_control_panel import (
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE,
|
ATTR_CODE,
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
@ -31,7 +30,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, selector, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -43,8 +42,16 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import TriggerUpdateCoordinator
|
from .coordinator import TriggerUpdateCoordinator
|
||||||
from .entity import AbstractTemplateEntity
|
from .entity import AbstractTemplateEntity
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
make_template_entity_common_modern_schema,
|
||||||
|
)
|
||||||
from .trigger_entity import TriggerEntity
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -88,27 +95,28 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Alarm Control Panel"
|
DEFAULT_NAME = "Template Alarm Control Panel"
|
||||||
|
|
||||||
ALARM_CONTROL_PANEL_SCHEMA = vol.All(
|
ALARM_CONTROL_PANEL_COMMON_SCHEMA = vol.Schema(
|
||||||
vol.Schema(
|
{
|
||||||
{
|
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_HOME_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_HOME_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_VACATION_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_VACATION_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
|
||||||
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
|
vol.Optional(CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name): cv.enum(
|
||||||
vol.Optional(
|
TemplateCodeFormat
|
||||||
CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name
|
),
|
||||||
): cv.enum(TemplateCodeFormat),
|
vol.Optional(CONF_DISARM_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_DISARM_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_STATE): cv.template,
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
vol.Optional(CONF_TRIGGER_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_TRIGGER_ACTION): cv.SCRIPT_SCHEMA,
|
}
|
||||||
}
|
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ALARM_CONTROL_PANEL_YAML_SCHEMA = ALARM_CONTROL_PANEL_COMMON_SCHEMA.extend(
|
||||||
|
make_template_entity_common_modern_schema(DEFAULT_NAME).schema
|
||||||
|
)
|
||||||
|
|
||||||
LEGACY_ALARM_CONTROL_PANEL_SCHEMA = vol.Schema(
|
ALARM_CONTROL_PANEL_LEGACY_YAML_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
@ -130,59 +138,29 @@ LEGACY_ALARM_CONTROL_PANEL_SCHEMA = vol.Schema(
|
|||||||
PLATFORM_SCHEMA = ALARM_CONTROL_PANEL_PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = ALARM_CONTROL_PANEL_PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ALARM_CONTROL_PANELS): cv.schema_with_slug_keys(
|
vol.Required(CONF_ALARM_CONTROL_PANELS): cv.schema_with_slug_keys(
|
||||||
LEGACY_ALARM_CONTROL_PANEL_SCHEMA
|
ALARM_CONTROL_PANEL_LEGACY_YAML_SCHEMA
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
ALARM_CONTROL_PANEL_CONFIG_SCHEMA = vol.Schema(
|
ALARM_CONTROL_PANEL_CONFIG_ENTRY_SCHEMA = ALARM_CONTROL_PANEL_COMMON_SCHEMA.extend(
|
||||||
{
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
vol.Optional(CONF_ARM_AWAY_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_ARM_CUSTOM_BYPASS_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_ARM_HOME_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_ARM_NIGHT_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_ARM_VACATION_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_CODE_ARM_REQUIRED, default=True): cv.boolean,
|
|
||||||
vol.Optional(CONF_CODE_FORMAT, default=TemplateCodeFormat.number.name): cv.enum(
|
|
||||||
TemplateCodeFormat
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
vol.Optional(CONF_DISARM_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Required(CONF_NAME): cv.template,
|
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
|
||||||
vol.Optional(CONF_TRIGGER_ACTION): cv.SCRIPT_SCHEMA,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def rewrite_options_to_modern_conf(option_config: dict[str, dict]) -> dict[str, dict]:
|
|
||||||
"""Rewrite option configuration to modern configuration."""
|
|
||||||
option_config = {**option_config}
|
|
||||||
|
|
||||||
if CONF_VALUE_TEMPLATE in option_config:
|
|
||||||
option_config[CONF_STATE] = option_config.pop(CONF_VALUE_TEMPLATE)
|
|
||||||
|
|
||||||
return option_config
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
_options = rewrite_options_to_modern_conf(_options)
|
config_entry,
|
||||||
validated_config = ALARM_CONTROL_PANEL_CONFIG_SCHEMA(_options)
|
async_add_entities,
|
||||||
async_add_entities(
|
StateAlarmControlPanelEntity,
|
||||||
[
|
ALARM_CONTROL_PANEL_CONFIG_ENTRY_SCHEMA,
|
||||||
StateAlarmControlPanelEntity(
|
True,
|
||||||
hass,
|
|
||||||
validated_config,
|
|
||||||
config_entry.entry_id,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -211,11 +189,14 @@ def async_create_preview_alarm_control_panel(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> StateAlarmControlPanelEntity:
|
) -> StateAlarmControlPanelEntity:
|
||||||
"""Create a preview alarm control panel."""
|
"""Create a preview alarm control panel."""
|
||||||
updated_config = rewrite_options_to_modern_conf(config)
|
return async_setup_template_preview(
|
||||||
validated_config = ALARM_CONTROL_PANEL_CONFIG_SCHEMA(
|
hass,
|
||||||
updated_config | {CONF_NAME: name}
|
name,
|
||||||
|
config,
|
||||||
|
StateAlarmControlPanelEntity,
|
||||||
|
ALARM_CONTROL_PANEL_CONFIG_ENTRY_SCHEMA,
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
return StateAlarmControlPanelEntity(hass, validated_config, None)
|
|
||||||
|
|
||||||
|
|
||||||
class AbstractTemplateAlarmControlPanel(
|
class AbstractTemplateAlarmControlPanel(
|
||||||
|
@ -22,7 +22,6 @@ from homeassistant.const import (
|
|||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_ENTITY_PICTURE_TEMPLATE,
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
CONF_FRIENDLY_NAME_TEMPLATE,
|
CONF_FRIENDLY_NAME_TEMPLATE,
|
||||||
CONF_ICON_TEMPLATE,
|
CONF_ICON_TEMPLATE,
|
||||||
@ -38,7 +37,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, selector, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -50,8 +49,16 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import CONF_AVAILABILITY_TEMPLATE
|
from .const import CONF_AVAILABILITY_TEMPLATE
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
from .template_entity import TEMPLATE_ENTITY_COMMON_SCHEMA, TemplateEntity
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TEMPLATE_ENTITY_COMMON_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
)
|
||||||
from .trigger_entity import TriggerEntity
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
CONF_DELAY_ON = "delay_on"
|
CONF_DELAY_ON = "delay_on"
|
||||||
@ -64,7 +71,7 @@ LEGACY_FIELDS = {
|
|||||||
CONF_VALUE_TEMPLATE: CONF_STATE,
|
CONF_VALUE_TEMPLATE: CONF_STATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
BINARY_SENSOR_SCHEMA = vol.Schema(
|
BINARY_SENSOR_COMMON_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_AUTO_OFF): vol.Any(cv.positive_time_period, cv.template),
|
vol.Optional(CONF_AUTO_OFF): vol.Any(cv.positive_time_period, cv.template),
|
||||||
vol.Optional(CONF_DELAY_OFF): vol.Any(cv.positive_time_period, cv.template),
|
vol.Optional(CONF_DELAY_OFF): vol.Any(cv.positive_time_period, cv.template),
|
||||||
@ -73,15 +80,17 @@ BINARY_SENSOR_SCHEMA = vol.Schema(
|
|||||||
vol.Required(CONF_STATE): cv.template,
|
vol.Required(CONF_STATE): cv.template,
|
||||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
}
|
}
|
||||||
).extend(TEMPLATE_ENTITY_COMMON_SCHEMA.schema)
|
|
||||||
|
|
||||||
BINARY_SENSOR_CONFIG_SCHEMA = BINARY_SENSOR_SCHEMA.extend(
|
|
||||||
{
|
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
LEGACY_BINARY_SENSOR_SCHEMA = vol.All(
|
BINARY_SENSOR_YAML_SCHEMA = BINARY_SENSOR_COMMON_SCHEMA.extend(
|
||||||
|
TEMPLATE_ENTITY_COMMON_SCHEMA.schema
|
||||||
|
)
|
||||||
|
|
||||||
|
BINARY_SENSOR_CONFIG_ENTRY_SCHEMA = BINARY_SENSOR_COMMON_SCHEMA.extend(
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
|
)
|
||||||
|
|
||||||
|
BINARY_SENSOR_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(ATTR_ENTITY_ID),
|
cv.deprecated(ATTR_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -106,7 +115,7 @@ LEGACY_BINARY_SENSOR_SCHEMA = vol.All(
|
|||||||
PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(
|
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(
|
||||||
LEGACY_BINARY_SENSOR_SCHEMA
|
BINARY_SENSOR_LEGACY_YAML_SCHEMA
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -138,11 +147,12 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = BINARY_SENSOR_CONFIG_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities(
|
async_add_entities,
|
||||||
[StateBinarySensorEntity(hass, validated_config, config_entry.entry_id)]
|
StateBinarySensorEntity,
|
||||||
|
BINARY_SENSOR_CONFIG_ENTRY_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -151,8 +161,9 @@ def async_create_preview_binary_sensor(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> StateBinarySensorEntity:
|
) -> StateBinarySensorEntity:
|
||||||
"""Create a preview sensor."""
|
"""Create a preview sensor."""
|
||||||
validated_config = BINARY_SENSOR_CONFIG_SCHEMA(config | {CONF_NAME: name})
|
return async_setup_template_preview(
|
||||||
return StateBinarySensorEntity(hass, validated_config, None)
|
hass, name, config, StateBinarySensorEntity, BINARY_SENSOR_CONFIG_ENTRY_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateBinarySensorEntity(TemplateEntity, BinarySensorEntity, RestoreEntity):
|
class StateBinarySensorEntity(TemplateEntity, BinarySensorEntity, RestoreEntity):
|
||||||
|
@ -14,9 +14,9 @@ from homeassistant.components.button import (
|
|||||||
ButtonEntity,
|
ButtonEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_DEVICE_ID, CONF_NAME
|
from homeassistant.const import CONF_DEVICE_CLASS
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import config_validation as cv, selector
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -24,29 +24,31 @@ from homeassistant.helpers.entity_platform import (
|
|||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
|
|
||||||
from .const import CONF_PRESS, DOMAIN
|
from .const import CONF_PRESS, DOMAIN
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import async_setup_template_entry, async_setup_template_platform
|
||||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
make_template_entity_common_modern_schema,
|
||||||
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DEFAULT_NAME = "Template Button"
|
DEFAULT_NAME = "Template Button"
|
||||||
DEFAULT_OPTIMISTIC = False
|
DEFAULT_OPTIMISTIC = False
|
||||||
|
|
||||||
BUTTON_SCHEMA = vol.Schema(
|
BUTTON_YAML_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
vol.Required(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||||
}
|
}
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
|
|
||||||
CONFIG_BUTTON_SCHEMA = vol.Schema(
|
BUTTON_CONFIG_ENTRY_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME): cv.template,
|
|
||||||
vol.Optional(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_PRESS): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
}
|
||||||
)
|
).extend(TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
@ -73,11 +75,12 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = CONFIG_BUTTON_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities(
|
async_add_entities,
|
||||||
[StateButtonEntity(hass, validated_config, config_entry.entry_id)]
|
StateButtonEntity,
|
||||||
|
BUTTON_CONFIG_ENTRY_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,57 +102,57 @@ CONFIG_SECTION_SCHEMA = vol.All(
|
|||||||
{
|
{
|
||||||
vol.Optional(CONF_ACTIONS): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_ACTIONS): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_BINARY_SENSORS): cv.schema_with_slug_keys(
|
vol.Optional(CONF_BINARY_SENSORS): cv.schema_with_slug_keys(
|
||||||
binary_sensor_platform.LEGACY_BINARY_SENSOR_SCHEMA
|
binary_sensor_platform.BINARY_SENSOR_LEGACY_YAML_SCHEMA
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
|
vol.Optional(CONF_CONDITIONS): cv.CONDITIONS_SCHEMA,
|
||||||
vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys(
|
vol.Optional(CONF_SENSORS): cv.schema_with_slug_keys(
|
||||||
sensor_platform.LEGACY_SENSOR_SCHEMA
|
sensor_platform.SENSOR_LEGACY_YAML_SCHEMA
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_TRIGGERS): cv.TRIGGER_SCHEMA,
|
vol.Optional(CONF_TRIGGERS): cv.TRIGGER_SCHEMA,
|
||||||
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
vol.Optional(CONF_VARIABLES): cv.SCRIPT_VARIABLES_SCHEMA,
|
||||||
vol.Optional(DOMAIN_ALARM_CONTROL_PANEL): vol.All(
|
vol.Optional(DOMAIN_ALARM_CONTROL_PANEL): vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
[alarm_control_panel_platform.ALARM_CONTROL_PANEL_SCHEMA],
|
[alarm_control_panel_platform.ALARM_CONTROL_PANEL_YAML_SCHEMA],
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_BINARY_SENSOR): vol.All(
|
vol.Optional(DOMAIN_BINARY_SENSOR): vol.All(
|
||||||
cv.ensure_list, [binary_sensor_platform.BINARY_SENSOR_SCHEMA]
|
cv.ensure_list, [binary_sensor_platform.BINARY_SENSOR_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_BUTTON): vol.All(
|
vol.Optional(DOMAIN_BUTTON): vol.All(
|
||||||
cv.ensure_list, [button_platform.BUTTON_SCHEMA]
|
cv.ensure_list, [button_platform.BUTTON_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_COVER): vol.All(
|
vol.Optional(DOMAIN_COVER): vol.All(
|
||||||
cv.ensure_list, [cover_platform.COVER_SCHEMA]
|
cv.ensure_list, [cover_platform.COVER_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_FAN): vol.All(
|
vol.Optional(DOMAIN_FAN): vol.All(
|
||||||
cv.ensure_list, [fan_platform.FAN_SCHEMA]
|
cv.ensure_list, [fan_platform.FAN_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_IMAGE): vol.All(
|
vol.Optional(DOMAIN_IMAGE): vol.All(
|
||||||
cv.ensure_list, [image_platform.IMAGE_SCHEMA]
|
cv.ensure_list, [image_platform.IMAGE_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_LIGHT): vol.All(
|
vol.Optional(DOMAIN_LIGHT): vol.All(
|
||||||
cv.ensure_list, [light_platform.LIGHT_SCHEMA]
|
cv.ensure_list, [light_platform.LIGHT_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_LOCK): vol.All(
|
vol.Optional(DOMAIN_LOCK): vol.All(
|
||||||
cv.ensure_list, [lock_platform.LOCK_SCHEMA]
|
cv.ensure_list, [lock_platform.LOCK_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_NUMBER): vol.All(
|
vol.Optional(DOMAIN_NUMBER): vol.All(
|
||||||
cv.ensure_list, [number_platform.NUMBER_SCHEMA]
|
cv.ensure_list, [number_platform.NUMBER_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_SELECT): vol.All(
|
vol.Optional(DOMAIN_SELECT): vol.All(
|
||||||
cv.ensure_list, [select_platform.SELECT_SCHEMA]
|
cv.ensure_list, [select_platform.SELECT_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_SENSOR): vol.All(
|
vol.Optional(DOMAIN_SENSOR): vol.All(
|
||||||
cv.ensure_list, [sensor_platform.SENSOR_SCHEMA]
|
cv.ensure_list, [sensor_platform.SENSOR_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_SWITCH): vol.All(
|
vol.Optional(DOMAIN_SWITCH): vol.All(
|
||||||
cv.ensure_list, [switch_platform.SWITCH_SCHEMA]
|
cv.ensure_list, [switch_platform.SWITCH_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_VACUUM): vol.All(
|
vol.Optional(DOMAIN_VACUUM): vol.All(
|
||||||
cv.ensure_list, [vacuum_platform.VACUUM_SCHEMA]
|
cv.ensure_list, [vacuum_platform.VACUUM_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
vol.Optional(DOMAIN_WEATHER): vol.All(
|
vol.Optional(DOMAIN_WEATHER): vol.All(
|
||||||
cv.ensure_list, [weather_platform.WEATHER_SCHEMA]
|
cv.ensure_list, [weather_platform.WEATHER_YAML_SCHEMA]
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
"""Constants for the Template Platform Components."""
|
"""Constants for the Template Platform Components."""
|
||||||
|
|
||||||
from homeassistant.const import Platform
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.const import CONF_ICON, CONF_NAME, CONF_UNIQUE_ID, Platform
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
CONF_ATTRIBUTE_TEMPLATES = "attribute_templates"
|
||||||
@ -16,6 +19,15 @@ CONF_STEP = "step"
|
|||||||
CONF_TURN_OFF = "turn_off"
|
CONF_TURN_OFF = "turn_off"
|
||||||
CONF_TURN_ON = "turn_on"
|
CONF_TURN_ON = "turn_on"
|
||||||
|
|
||||||
|
TEMPLATE_ENTITY_BASE_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_ICON): cv.template,
|
||||||
|
vol.Optional(CONF_NAME): cv.template,
|
||||||
|
vol.Optional(CONF_PICTURE): cv.template,
|
||||||
|
vol.Optional(CONF_UNIQUE_ID): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
DOMAIN = "template"
|
DOMAIN = "template"
|
||||||
|
|
||||||
PLATFORM_STORAGE_KEY = "template_platforms"
|
PLATFORM_STORAGE_KEY = "template_platforms"
|
||||||
|
@ -91,7 +91,7 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Cover"
|
DEFAULT_NAME = "Template Cover"
|
||||||
|
|
||||||
COVER_SCHEMA = vol.All(
|
COVER_YAML_SCHEMA = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Inclusive(CLOSE_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA,
|
vol.Inclusive(CLOSE_ACTION, CONF_OPEN_AND_CLOSE): cv.SCRIPT_SCHEMA,
|
||||||
@ -110,7 +110,7 @@ COVER_SCHEMA = vol.All(
|
|||||||
cv.has_at_least_one_key(OPEN_ACTION, POSITION_ACTION),
|
cv.has_at_least_one_key(OPEN_ACTION, POSITION_ACTION),
|
||||||
)
|
)
|
||||||
|
|
||||||
LEGACY_COVER_SCHEMA = vol.All(
|
COVER_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_ENTITY_ID),
|
cv.deprecated(CONF_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -134,7 +134,7 @@ LEGACY_COVER_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = COVER_PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = COVER_PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_COVERS): cv.schema_with_slug_keys(LEGACY_COVER_SCHEMA)}
|
{vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_LEGACY_YAML_SCHEMA)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Fan"
|
DEFAULT_NAME = "Template Fan"
|
||||||
|
|
||||||
FAN_SCHEMA = vol.All(
|
FAN_YAML_SCHEMA = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DIRECTION): cv.template,
|
vol.Optional(CONF_DIRECTION): cv.template,
|
||||||
@ -101,7 +101,7 @@ FAN_SCHEMA = vol.All(
|
|||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
)
|
)
|
||||||
|
|
||||||
LEGACY_FAN_SCHEMA = vol.All(
|
FAN_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_ENTITY_ID),
|
cv.deprecated(CONF_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -126,7 +126,7 @@ LEGACY_FAN_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_FANS): cv.schema_with_slug_keys(LEGACY_FAN_SCHEMA)}
|
{vol.Required(CONF_FANS): cv.schema_with_slug_keys(FAN_LEGACY_YAML_SCHEMA)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,14 +5,19 @@ import itertools
|
|||||||
import logging
|
import logging
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import blueprint
|
from homeassistant.components import blueprint
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ENTITY_PICTURE_TEMPLATE,
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
CONF_FRIENDLY_NAME,
|
CONF_FRIENDLY_NAME,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ICON_TEMPLATE,
|
CONF_ICON_TEMPLATE,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
|
CONF_STATE,
|
||||||
CONF_UNIQUE_ID,
|
CONF_UNIQUE_ID,
|
||||||
|
CONF_VALUE_TEMPLATE,
|
||||||
SERVICE_RELOAD,
|
SERVICE_RELOAD,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
@ -20,6 +25,7 @@ from homeassistant.exceptions import PlatformNotReady
|
|||||||
from homeassistant.helpers import template
|
from homeassistant.helpers import template
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
async_get_platforms,
|
async_get_platforms,
|
||||||
)
|
)
|
||||||
@ -228,3 +234,41 @@ async def async_setup_template_platform(
|
|||||||
discovery_info["entities"],
|
discovery_info["entities"],
|
||||||
discovery_info["unique_id"],
|
discovery_info["unique_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_template_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
config_entry: ConfigEntry,
|
||||||
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
|
state_entity_cls: type[TemplateEntity],
|
||||||
|
config_schema: vol.Schema,
|
||||||
|
replace_value_template: bool = False,
|
||||||
|
) -> None:
|
||||||
|
"""Setup the Template from a config entry."""
|
||||||
|
options = dict(config_entry.options)
|
||||||
|
options.pop("template_type")
|
||||||
|
|
||||||
|
if replace_value_template and CONF_VALUE_TEMPLATE in options:
|
||||||
|
options[CONF_STATE] = options.pop(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
validated_config = config_schema(options)
|
||||||
|
|
||||||
|
async_add_entities(
|
||||||
|
[state_entity_cls(hass, validated_config, config_entry.entry_id)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def async_setup_template_preview[T: TemplateEntity](
|
||||||
|
hass: HomeAssistant,
|
||||||
|
name: str,
|
||||||
|
config: ConfigType,
|
||||||
|
state_entity_cls: type[T],
|
||||||
|
schema: vol.Schema,
|
||||||
|
replace_value_template: bool = False,
|
||||||
|
) -> T:
|
||||||
|
"""Setup the Template preview."""
|
||||||
|
if replace_value_template and CONF_VALUE_TEMPLATE in config:
|
||||||
|
config[CONF_STATE] = config.pop(CONF_VALUE_TEMPLATE)
|
||||||
|
|
||||||
|
validated_config = schema(config | {CONF_NAME: name})
|
||||||
|
return state_entity_cls(hass, validated_config, None)
|
||||||
|
@ -13,10 +13,10 @@ from homeassistant.components.image import (
|
|||||||
ImageEntity,
|
ImageEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, CONF_URL, CONF_VERIFY_SSL
|
from homeassistant.const import CONF_URL, CONF_VERIFY_SSL
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, selector
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -26,8 +26,9 @@ from homeassistant.util import dt as dt_util
|
|||||||
|
|
||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import CONF_PICTURE
|
from .const import CONF_PICTURE
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import async_setup_template_entry, async_setup_template_platform
|
||||||
from .template_entity import (
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
TemplateEntity,
|
TemplateEntity,
|
||||||
make_template_entity_common_modern_attributes_schema,
|
make_template_entity_common_modern_attributes_schema,
|
||||||
)
|
)
|
||||||
@ -39,7 +40,7 @@ DEFAULT_NAME = "Template Image"
|
|||||||
|
|
||||||
GET_IMAGE_TIMEOUT = 10
|
GET_IMAGE_TIMEOUT = 10
|
||||||
|
|
||||||
IMAGE_SCHEMA = vol.Schema(
|
IMAGE_YAML_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_URL): cv.template,
|
vol.Required(CONF_URL): cv.template,
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
|
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
|
||||||
@ -47,14 +48,12 @@ IMAGE_SCHEMA = vol.Schema(
|
|||||||
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
||||||
|
|
||||||
|
|
||||||
IMAGE_CONFIG_SCHEMA = vol.Schema(
|
IMAGE_CONFIG_ENTRY_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME): cv.template,
|
|
||||||
vol.Required(CONF_URL): cv.template,
|
vol.Required(CONF_URL): cv.template,
|
||||||
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
|
vol.Optional(CONF_VERIFY_SSL, default=True): bool,
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
}
|
||||||
)
|
).extend(TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
@ -81,11 +80,12 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = IMAGE_CONFIG_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities(
|
async_add_entities,
|
||||||
[StateImageEntity(hass, validated_config, config_entry.entry_id)]
|
StateImageEntity,
|
||||||
|
IMAGE_CONFIG_ENTRY_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Light"
|
DEFAULT_NAME = "Template Light"
|
||||||
|
|
||||||
LIGHT_SCHEMA = vol.Schema(
|
LIGHT_YAML_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
|
vol.Inclusive(CONF_EFFECT_ACTION, "effect"): cv.SCRIPT_SCHEMA,
|
||||||
vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template,
|
vol.Inclusive(CONF_EFFECT_LIST, "effect"): cv.template,
|
||||||
@ -147,7 +147,7 @@ LIGHT_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
|
|
||||||
LEGACY_LIGHT_SCHEMA = vol.All(
|
LIGHT_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_ENTITY_ID),
|
cv.deprecated(CONF_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -186,7 +186,7 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
cv.removed(CONF_WHITE_VALUE_ACTION),
|
cv.removed(CONF_WHITE_VALUE_ACTION),
|
||||||
cv.removed(CONF_WHITE_VALUE_TEMPLATE),
|
cv.removed(CONF_WHITE_VALUE_TEMPLATE),
|
||||||
LIGHT_PLATFORM_SCHEMA.extend(
|
LIGHT_PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LEGACY_LIGHT_SCHEMA)}
|
{vol.Required(CONF_LIGHTS): cv.schema_with_slug_keys(LIGHT_LEGACY_YAML_SCHEMA)}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ LEGACY_FIELDS = {
|
|||||||
CONF_VALUE_TEMPLATE: CONF_STATE,
|
CONF_VALUE_TEMPLATE: CONF_STATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
LOCK_SCHEMA = vol.All(
|
LOCK_YAML_SCHEMA = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_CODE_FORMAT): cv.template,
|
vol.Optional(CONF_CODE_FORMAT): cv.template,
|
||||||
@ -68,7 +68,6 @@ LOCK_SCHEMA = vol.All(
|
|||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = LOCK_PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = LOCK_PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_CODE_FORMAT_TEMPLATE): cv.template,
|
vol.Optional(CONF_CODE_FORMAT_TEMPLATE): cv.template,
|
||||||
|
@ -18,14 +18,13 @@ from homeassistant.components.number import (
|
|||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_OPTIMISTIC,
|
CONF_OPTIMISTIC,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, selector
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -34,8 +33,16 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|||||||
|
|
||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN
|
from .const import CONF_MAX, CONF_MIN, CONF_STEP, DOMAIN
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
make_template_entity_common_modern_schema,
|
||||||
|
)
|
||||||
from .trigger_entity import TriggerEntity
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -45,30 +52,31 @@ CONF_SET_VALUE = "set_value"
|
|||||||
DEFAULT_NAME = "Template Number"
|
DEFAULT_NAME = "Template Number"
|
||||||
DEFAULT_OPTIMISTIC = False
|
DEFAULT_OPTIMISTIC = False
|
||||||
|
|
||||||
NUMBER_SCHEMA = vol.Schema(
|
NUMBER_COMMON_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_STATE): cv.template,
|
|
||||||
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Required(CONF_STEP): cv.template,
|
|
||||||
vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): cv.template,
|
|
||||||
vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): cv.template,
|
vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): cv.template,
|
||||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): cv.template,
|
||||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
||||||
}
|
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
|
||||||
NUMBER_CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_NAME): cv.template,
|
|
||||||
vol.Required(CONF_STATE): cv.template,
|
vol.Required(CONF_STATE): cv.template,
|
||||||
vol.Required(CONF_STEP): cv.template,
|
vol.Required(CONF_STEP): cv.template,
|
||||||
vol.Required(CONF_SET_VALUE): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_MIN): cv.template,
|
|
||||||
vol.Optional(CONF_MAX): cv.template,
|
|
||||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
NUMBER_YAML_SCHEMA = (
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
|
.extend(NUMBER_COMMON_SCHEMA.schema)
|
||||||
|
)
|
||||||
|
|
||||||
|
NUMBER_CONFIG_ENTRY_SCHEMA = NUMBER_COMMON_SCHEMA.extend(
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -94,11 +102,12 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = NUMBER_CONFIG_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities(
|
async_add_entities,
|
||||||
[StateNumberEntity(hass, validated_config, config_entry.entry_id)]
|
StateNumberEntity,
|
||||||
|
NUMBER_CONFIG_ENTRY_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -107,8 +116,9 @@ def async_create_preview_number(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> StateNumberEntity:
|
) -> StateNumberEntity:
|
||||||
"""Create a preview number."""
|
"""Create a preview number."""
|
||||||
validated_config = NUMBER_CONFIG_SCHEMA(config | {CONF_NAME: name})
|
return async_setup_template_preview(
|
||||||
return StateNumberEntity(hass, validated_config, None)
|
hass, name, config, StateNumberEntity, NUMBER_CONFIG_ENTRY_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateNumberEntity(TemplateEntity, NumberEntity):
|
class StateNumberEntity(TemplateEntity, NumberEntity):
|
||||||
|
@ -15,9 +15,9 @@ from homeassistant.components.select import (
|
|||||||
SelectEntity,
|
SelectEntity,
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_DEVICE_ID, CONF_NAME, CONF_OPTIMISTIC, CONF_STATE
|
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, CONF_STATE
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.helpers import config_validation as cv, selector
|
from homeassistant.helpers import config_validation as cv
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -27,8 +27,16 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .entity import AbstractTemplateEntity
|
from .entity import AbstractTemplateEntity
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
from .template_entity import TemplateEntity, make_template_entity_common_modern_schema
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
make_template_entity_common_modern_schema,
|
||||||
|
)
|
||||||
from .trigger_entity import TriggerEntity
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -39,26 +47,28 @@ CONF_SELECT_OPTION = "select_option"
|
|||||||
DEFAULT_NAME = "Template Select"
|
DEFAULT_NAME = "Template Select"
|
||||||
DEFAULT_OPTIMISTIC = False
|
DEFAULT_OPTIMISTIC = False
|
||||||
|
|
||||||
SELECT_SCHEMA = vol.Schema(
|
SELECT_COMMON_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
vol.Optional(ATTR_OPTIONS): cv.template,
|
||||||
vol.Required(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Required(ATTR_OPTIONS): cv.template,
|
|
||||||
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
|
||||||
}
|
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
|
||||||
|
|
||||||
|
|
||||||
SELECT_CONFIG_SCHEMA = vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_NAME): cv.template,
|
|
||||||
vol.Required(CONF_STATE): cv.template,
|
|
||||||
vol.Required(CONF_OPTIONS): cv.template,
|
|
||||||
vol.Optional(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
vol.Optional(CONF_STATE): cv.template,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SELECT_YAML_SCHEMA = (
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
|
.extend(SELECT_COMMON_SCHEMA.schema)
|
||||||
|
)
|
||||||
|
|
||||||
|
SELECT_CONFIG_ENTRY_SCHEMA = SELECT_COMMON_SCHEMA.extend(
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@ -84,10 +94,13 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = SELECT_CONFIG_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities([TemplateSelect(hass, validated_config, config_entry.entry_id)])
|
async_add_entities,
|
||||||
|
TemplateSelect,
|
||||||
|
SELECT_CONFIG_ENTRY_SCHEMA,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
@ -95,8 +108,9 @@ def async_create_preview_select(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> TemplateSelect:
|
) -> TemplateSelect:
|
||||||
"""Create a preview select."""
|
"""Create a preview select."""
|
||||||
validated_config = SELECT_CONFIG_SCHEMA(config | {CONF_NAME: name})
|
return async_setup_template_preview(
|
||||||
return TemplateSelect(hass, validated_config, None)
|
hass, name, config, TemplateSelect, SELECT_CONFIG_ENTRY_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AbstractTemplateSelect(AbstractTemplateEntity, SelectEntity):
|
class AbstractTemplateSelect(AbstractTemplateEntity, SelectEntity):
|
||||||
|
@ -15,6 +15,7 @@ from homeassistant.components.sensor import (
|
|||||||
DOMAIN as SENSOR_DOMAIN,
|
DOMAIN as SENSOR_DOMAIN,
|
||||||
ENTITY_ID_FORMAT,
|
ENTITY_ID_FORMAT,
|
||||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||||
|
STATE_CLASSES_SCHEMA,
|
||||||
RestoreSensor,
|
RestoreSensor,
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
@ -25,7 +26,6 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_ENTITY_PICTURE_TEMPLATE,
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
CONF_FRIENDLY_NAME,
|
CONF_FRIENDLY_NAME,
|
||||||
CONF_FRIENDLY_NAME_TEMPLATE,
|
CONF_FRIENDLY_NAME_TEMPLATE,
|
||||||
@ -43,19 +43,26 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, selector, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.trigger_template_entity import TEMPLATE_SENSOR_BASE_SCHEMA
|
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE
|
from .const import CONF_ATTRIBUTE_TEMPLATES, CONF_AVAILABILITY_TEMPLATE
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
from .template_entity import TEMPLATE_ENTITY_COMMON_SCHEMA, TemplateEntity
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
|
TEMPLATE_ENTITY_COMMON_SCHEMA,
|
||||||
|
TemplateEntity,
|
||||||
|
)
|
||||||
from .trigger_entity import TriggerEntity
|
from .trigger_entity import TriggerEntity
|
||||||
|
|
||||||
LEGACY_FIELDS = {
|
LEGACY_FIELDS = {
|
||||||
@ -77,29 +84,31 @@ def validate_last_reset(val):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
SENSOR_SCHEMA = vol.All(
|
SENSOR_COMMON_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_STATE): cv.template,
|
||||||
|
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
|
||||||
|
vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA,
|
||||||
|
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
SENSOR_YAML_SCHEMA = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_STATE): cv.template,
|
|
||||||
vol.Optional(ATTR_LAST_RESET): cv.template,
|
vol.Optional(ATTR_LAST_RESET): cv.template,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(TEMPLATE_SENSOR_BASE_SCHEMA.schema)
|
.extend(SENSOR_COMMON_SCHEMA.schema)
|
||||||
.extend(TEMPLATE_ENTITY_COMMON_SCHEMA.schema),
|
.extend(TEMPLATE_ENTITY_COMMON_SCHEMA.schema),
|
||||||
validate_last_reset,
|
validate_last_reset,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SENSOR_CONFIG_ENTRY_SCHEMA = SENSOR_COMMON_SCHEMA.extend(
|
||||||
SENSOR_CONFIG_SCHEMA = vol.All(
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
vol.Schema(
|
|
||||||
{
|
|
||||||
vol.Required(CONF_STATE): cv.template,
|
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
|
||||||
).extend(TEMPLATE_SENSOR_BASE_SCHEMA.schema),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
LEGACY_SENSOR_SCHEMA = vol.All(
|
SENSOR_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(ATTR_ENTITY_ID),
|
cv.deprecated(ATTR_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -141,7 +150,9 @@ PLATFORM_SCHEMA = vol.All(
|
|||||||
{
|
{
|
||||||
vol.Optional(CONF_TRIGGER): cv.match_all, # to raise custom warning
|
vol.Optional(CONF_TRIGGER): cv.match_all, # to raise custom warning
|
||||||
vol.Optional(CONF_TRIGGERS): cv.match_all, # to raise custom warning
|
vol.Optional(CONF_TRIGGERS): cv.match_all, # to raise custom warning
|
||||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(LEGACY_SENSOR_SCHEMA),
|
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(
|
||||||
|
SENSOR_LEGACY_YAML_SCHEMA
|
||||||
|
),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
extra_validation_checks,
|
extra_validation_checks,
|
||||||
@ -176,11 +187,12 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
validated_config = SENSOR_CONFIG_SCHEMA(_options)
|
config_entry,
|
||||||
async_add_entities(
|
async_add_entities,
|
||||||
[StateSensorEntity(hass, validated_config, config_entry.entry_id)]
|
StateSensorEntity,
|
||||||
|
SENSOR_CONFIG_ENTRY_SCHEMA,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -189,8 +201,9 @@ def async_create_preview_sensor(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> StateSensorEntity:
|
) -> StateSensorEntity:
|
||||||
"""Create a preview sensor."""
|
"""Create a preview sensor."""
|
||||||
validated_config = SENSOR_CONFIG_SCHEMA(config | {CONF_NAME: name})
|
return async_setup_template_preview(
|
||||||
return StateSensorEntity(hass, validated_config, None)
|
hass, name, config, StateSensorEntity, SENSOR_CONFIG_ENTRY_SCHEMA
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateSensorEntity(TemplateEntity, SensorEntity):
|
class StateSensorEntity(TemplateEntity, SensorEntity):
|
||||||
|
@ -16,7 +16,6 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID,
|
ATTR_ENTITY_ID,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
CONF_DEVICE_ID,
|
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
CONF_SWITCHES,
|
CONF_SWITCHES,
|
||||||
@ -29,7 +28,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, selector, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
from homeassistant.helpers.entity_platform import (
|
from homeassistant.helpers.entity_platform import (
|
||||||
AddConfigEntryEntitiesCallback,
|
AddConfigEntryEntitiesCallback,
|
||||||
AddEntitiesCallback,
|
AddEntitiesCallback,
|
||||||
@ -39,8 +38,13 @@ from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
|||||||
|
|
||||||
from . import TriggerUpdateCoordinator
|
from . import TriggerUpdateCoordinator
|
||||||
from .const import CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
from .const import CONF_TURN_OFF, CONF_TURN_ON, DOMAIN
|
||||||
from .helpers import async_setup_template_platform
|
from .helpers import (
|
||||||
|
async_setup_template_entry,
|
||||||
|
async_setup_template_platform,
|
||||||
|
async_setup_template_preview,
|
||||||
|
)
|
||||||
from .template_entity import (
|
from .template_entity import (
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA,
|
||||||
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
|
TEMPLATE_ENTITY_COMMON_SCHEMA_LEGACY,
|
||||||
TemplateEntity,
|
TemplateEntity,
|
||||||
make_template_entity_common_modern_schema,
|
make_template_entity_common_modern_schema,
|
||||||
@ -55,16 +59,19 @@ LEGACY_FIELDS = {
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Switch"
|
DEFAULT_NAME = "Template Switch"
|
||||||
|
|
||||||
|
SWITCH_COMMON_SCHEMA = vol.Schema(
|
||||||
SWITCH_SCHEMA = vol.Schema(
|
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
vol.Optional(CONF_STATE): cv.template,
|
||||||
vol.Required(CONF_TURN_ON): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_TURN_ON): cv.SCRIPT_SCHEMA,
|
||||||
vol.Required(CONF_TURN_OFF): cv.SCRIPT_SCHEMA,
|
vol.Optional(CONF_TURN_OFF): cv.SCRIPT_SCHEMA,
|
||||||
}
|
}
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
)
|
||||||
|
|
||||||
LEGACY_SWITCH_SCHEMA = vol.All(
|
SWITCH_YAML_SCHEMA = SWITCH_COMMON_SCHEMA.extend(
|
||||||
|
make_template_entity_common_modern_schema(DEFAULT_NAME).schema
|
||||||
|
)
|
||||||
|
|
||||||
|
SWITCH_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(ATTR_ENTITY_ID),
|
cv.deprecated(ATTR_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -79,17 +86,11 @@ LEGACY_SWITCH_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = SWITCH_PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(LEGACY_SWITCH_SCHEMA)}
|
{vol.Required(CONF_SWITCHES): cv.schema_with_slug_keys(SWITCH_LEGACY_YAML_SCHEMA)}
|
||||||
)
|
)
|
||||||
|
|
||||||
SWITCH_CONFIG_SCHEMA = vol.Schema(
|
SWITCH_CONFIG_ENTRY_SCHEMA = SWITCH_COMMON_SCHEMA.extend(
|
||||||
{
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA.schema
|
||||||
vol.Required(CONF_NAME): cv.template,
|
|
||||||
vol.Optional(CONF_STATE): cv.template,
|
|
||||||
vol.Optional(CONF_TURN_ON): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_TURN_OFF): cv.SCRIPT_SCHEMA,
|
|
||||||
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -129,12 +130,13 @@ async def async_setup_entry(
|
|||||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize config entry."""
|
"""Initialize config entry."""
|
||||||
_options = dict(config_entry.options)
|
await async_setup_template_entry(
|
||||||
_options.pop("template_type")
|
hass,
|
||||||
_options = rewrite_options_to_modern_conf(_options)
|
config_entry,
|
||||||
validated_config = SWITCH_CONFIG_SCHEMA(_options)
|
async_add_entities,
|
||||||
async_add_entities(
|
StateSwitchEntity,
|
||||||
[StateSwitchEntity(hass, validated_config, config_entry.entry_id)]
|
SWITCH_CONFIG_ENTRY_SCHEMA,
|
||||||
|
True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -143,9 +145,14 @@ def async_create_preview_switch(
|
|||||||
hass: HomeAssistant, name: str, config: dict[str, Any]
|
hass: HomeAssistant, name: str, config: dict[str, Any]
|
||||||
) -> StateSwitchEntity:
|
) -> StateSwitchEntity:
|
||||||
"""Create a preview switch."""
|
"""Create a preview switch."""
|
||||||
updated_config = rewrite_options_to_modern_conf(config)
|
return async_setup_template_preview(
|
||||||
validated_config = SWITCH_CONFIG_SCHEMA(updated_config | {CONF_NAME: name})
|
hass,
|
||||||
return StateSwitchEntity(hass, validated_config, None)
|
name,
|
||||||
|
config,
|
||||||
|
StateSwitchEntity,
|
||||||
|
SWITCH_CONFIG_ENTRY_SCHEMA,
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class StateSwitchEntity(TemplateEntity, SwitchEntity, RestoreEntity):
|
class StateSwitchEntity(TemplateEntity, SwitchEntity, RestoreEntity):
|
||||||
|
@ -12,6 +12,7 @@ import voluptuous as vol
|
|||||||
|
|
||||||
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
|
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE_ID,
|
||||||
CONF_ENTITY_PICTURE_TEMPLATE,
|
CONF_ENTITY_PICTURE_TEMPLATE,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ICON_TEMPLATE,
|
CONF_ICON_TEMPLATE,
|
||||||
@ -30,7 +31,7 @@ from homeassistant.core import (
|
|||||||
validate_state,
|
validate_state,
|
||||||
)
|
)
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv, selector
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.event import (
|
from homeassistant.helpers.event import (
|
||||||
TrackTemplate,
|
TrackTemplate,
|
||||||
@ -46,7 +47,6 @@ from homeassistant.helpers.template import (
|
|||||||
result_as_boolean,
|
result_as_boolean,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.trigger_template_entity import (
|
from homeassistant.helpers.trigger_template_entity import (
|
||||||
TEMPLATE_ENTITY_BASE_SCHEMA,
|
|
||||||
make_template_entity_base_schema,
|
make_template_entity_base_schema,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
@ -57,6 +57,7 @@ from .const import (
|
|||||||
CONF_AVAILABILITY,
|
CONF_AVAILABILITY,
|
||||||
CONF_AVAILABILITY_TEMPLATE,
|
CONF_AVAILABILITY_TEMPLATE,
|
||||||
CONF_PICTURE,
|
CONF_PICTURE,
|
||||||
|
TEMPLATE_ENTITY_BASE_SCHEMA,
|
||||||
)
|
)
|
||||||
from .entity import AbstractTemplateEntity
|
from .entity import AbstractTemplateEntity
|
||||||
|
|
||||||
@ -91,6 +92,13 @@ TEMPLATE_ENTITY_COMMON_SCHEMA = (
|
|||||||
.extend(TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema)
|
.extend(TEMPLATE_ENTITY_ATTRIBUTES_SCHEMA.schema)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TEMPLATE_ENTITY_COMMON_CONFIG_ENTRY_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_NAME): cv.template,
|
||||||
|
vol.Optional(CONF_DEVICE_ID): selector.DeviceSelector(),
|
||||||
|
}
|
||||||
|
).extend(TEMPLATE_ENTITY_AVAILABILITY_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
def make_template_entity_common_modern_schema(
|
def make_template_entity_common_modern_schema(
|
||||||
default_name: str,
|
default_name: str,
|
||||||
|
@ -76,7 +76,7 @@ LEGACY_FIELDS = {
|
|||||||
CONF_VALUE_TEMPLATE: CONF_STATE,
|
CONF_VALUE_TEMPLATE: CONF_STATE,
|
||||||
}
|
}
|
||||||
|
|
||||||
VACUUM_SCHEMA = vol.All(
|
VACUUM_YAML_SCHEMA = vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_BATTERY_LEVEL): cv.template,
|
vol.Optional(CONF_BATTERY_LEVEL): cv.template,
|
||||||
@ -94,7 +94,7 @@ VACUUM_SCHEMA = vol.All(
|
|||||||
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_attributes_schema(DEFAULT_NAME).schema)
|
||||||
)
|
)
|
||||||
|
|
||||||
LEGACY_VACUUM_SCHEMA = vol.All(
|
VACUUM_LEGACY_YAML_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_ENTITY_ID),
|
cv.deprecated(CONF_ENTITY_ID),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
@ -119,7 +119,7 @@ LEGACY_VACUUM_SCHEMA = vol.All(
|
|||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_VACUUMS): cv.schema_with_slug_keys(LEGACY_VACUUM_SCHEMA)}
|
{vol.Required(CONF_VACUUMS): cv.schema_with_slug_keys(VACUUM_LEGACY_YAML_SCHEMA)}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +31,12 @@ from homeassistant.components.weather import (
|
|||||||
WeatherEntity,
|
WeatherEntity,
|
||||||
WeatherEntityFeature,
|
WeatherEntityFeature,
|
||||||
)
|
)
|
||||||
from homeassistant.const import CONF_TEMPERATURE_UNIT, STATE_UNAVAILABLE, STATE_UNKNOWN
|
from homeassistant.const import (
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_TEMPERATURE_UNIT,
|
||||||
|
STATE_UNAVAILABLE,
|
||||||
|
STATE_UNKNOWN,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
from homeassistant.exceptions import TemplateError
|
from homeassistant.exceptions import TemplateError
|
||||||
from homeassistant.helpers import config_validation as cv, template
|
from homeassistant.helpers import config_validation as cv, template
|
||||||
@ -100,7 +105,7 @@ CONF_APPARENT_TEMPERATURE_TEMPLATE = "apparent_temperature_template"
|
|||||||
|
|
||||||
DEFAULT_NAME = "Template Weather"
|
DEFAULT_NAME = "Template Weather"
|
||||||
|
|
||||||
WEATHER_SCHEMA = vol.Schema(
|
WEATHER_YAML_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
||||||
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
||||||
@ -126,7 +131,32 @@ WEATHER_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
).extend(make_template_entity_common_modern_schema(DEFAULT_NAME).schema)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = WEATHER_PLATFORM_SCHEMA.extend(WEATHER_SCHEMA.schema)
|
PLATFORM_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_APPARENT_TEMPERATURE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_ATTRIBUTION_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_CLOUD_COVERAGE_TEMPLATE): cv.template,
|
||||||
|
vol.Required(CONF_CONDITION_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_DEW_POINT_TEMPLATE): cv.template,
|
||||||
|
vol.Required(CONF_HUMIDITY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_DAILY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_HOURLY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_FORECAST_TWICE_DAILY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_OZONE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.template,
|
||||||
|
vol.Optional(CONF_PRECIPITATION_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_PRESSURE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_PRESSURE_UNIT): vol.In(PressureConverter.VALID_UNITS),
|
||||||
|
vol.Required(CONF_TEMPERATURE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_TEMPERATURE_UNIT): vol.In(TemperatureConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_VISIBILITY_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_VISIBILITY_UNIT): vol.In(DistanceConverter.VALID_UNITS),
|
||||||
|
vol.Optional(CONF_WIND_BEARING_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_WIND_GUST_SPEED_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_WIND_SPEED_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_WIND_SPEED_UNIT): vol.In(SpeedConverter.VALID_UNITS),
|
||||||
|
}
|
||||||
|
).extend(WEATHER_PLATFORM_SCHEMA.schema)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_platform(
|
async def async_setup_platform(
|
||||||
|
@ -157,7 +157,6 @@ class EventStateEventData(TypedDict):
|
|||||||
"""Base class for EVENT_STATE_CHANGED and EVENT_STATE_REPORTED data."""
|
"""Base class for EVENT_STATE_CHANGED and EVENT_STATE_REPORTED data."""
|
||||||
|
|
||||||
entity_id: str
|
entity_id: str
|
||||||
new_state: State | None
|
|
||||||
|
|
||||||
|
|
||||||
class EventStateChangedData(EventStateEventData):
|
class EventStateChangedData(EventStateEventData):
|
||||||
@ -166,6 +165,7 @@ class EventStateChangedData(EventStateEventData):
|
|||||||
A state changed event is fired when on state write the state is changed.
|
A state changed event is fired when on state write the state is changed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
new_state: State | None
|
||||||
old_state: State | None
|
old_state: State | None
|
||||||
|
|
||||||
|
|
||||||
@ -175,6 +175,8 @@ class EventStateReportedData(EventStateEventData):
|
|||||||
A state reported event is fired when on state write the state is unchanged.
|
A state reported event is fired when on state write the state is unchanged.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
last_reported: datetime.datetime
|
||||||
|
new_state: State
|
||||||
old_last_reported: datetime.datetime
|
old_last_reported: datetime.datetime
|
||||||
|
|
||||||
|
|
||||||
@ -1749,18 +1751,38 @@ class CompressedState(TypedDict):
|
|||||||
|
|
||||||
|
|
||||||
class State:
|
class State:
|
||||||
"""Object to represent a state within the state machine.
|
"""Object to represent a state within the state machine."""
|
||||||
|
|
||||||
entity_id: the entity that is represented.
|
entity_id: str
|
||||||
state: the state of the entity
|
"""The entity that is represented by the state."""
|
||||||
attributes: extra information on entity and state
|
domain: str
|
||||||
last_changed: last time the state was changed.
|
"""Domain of the entity that is represented by the state."""
|
||||||
last_reported: last time the state was reported.
|
object_id: str
|
||||||
last_updated: last time the state or attributes were changed.
|
"""object_id: Object id of this state."""
|
||||||
context: Context in which it was created
|
state: str
|
||||||
domain: Domain of this state.
|
"""The state of the entity."""
|
||||||
object_id: Object id of this state.
|
attributes: ReadOnlyDict[str, Any]
|
||||||
|
"""Extra information on entity and state"""
|
||||||
|
last_changed: datetime.datetime
|
||||||
|
"""Last time the state was changed."""
|
||||||
|
last_reported: datetime.datetime
|
||||||
|
"""Last time the state was reported.
|
||||||
|
|
||||||
|
Note: When the state is set and neither the state nor attributes are
|
||||||
|
changed, the existing state will be mutated with an updated last_reported.
|
||||||
|
|
||||||
|
When handling a state change event, the last_reported attribute of the old
|
||||||
|
state will not be modified and can safely be used. The last_reported attribute
|
||||||
|
of the new state may be modified and the last_updated attribute should be used
|
||||||
|
instead.
|
||||||
|
|
||||||
|
When handling a state report event, the last_reported attribute may be
|
||||||
|
modified and last_reported from the event data should be used instead.
|
||||||
"""
|
"""
|
||||||
|
last_updated: datetime.datetime
|
||||||
|
"""Last time the state or attributes were changed."""
|
||||||
|
context: Context
|
||||||
|
"""Context in which the state was created."""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
"_cache",
|
"_cache",
|
||||||
@ -1841,7 +1863,20 @@ class State:
|
|||||||
|
|
||||||
@under_cached_property
|
@under_cached_property
|
||||||
def last_reported_timestamp(self) -> float:
|
def last_reported_timestamp(self) -> float:
|
||||||
"""Timestamp of last report."""
|
"""Timestamp of last report.
|
||||||
|
|
||||||
|
Note: When the state is set and neither the state nor attributes are
|
||||||
|
changed, the existing state will be mutated with an updated last_reported.
|
||||||
|
|
||||||
|
When handling a state change event, the last_reported_timestamp attribute
|
||||||
|
of the old state will not be modified and can safely be used. The
|
||||||
|
last_reported_timestamp attribute of the new state may be modified and the
|
||||||
|
last_updated_timestamp attribute should be used instead.
|
||||||
|
|
||||||
|
When handling a state report event, the last_reported_timestamp attribute may
|
||||||
|
be modified and last_reported from the event data should be used instead.
|
||||||
|
"""
|
||||||
|
|
||||||
return self.last_reported.timestamp()
|
return self.last_reported.timestamp()
|
||||||
|
|
||||||
@under_cached_property
|
@under_cached_property
|
||||||
@ -2340,6 +2375,7 @@ class StateMachine:
|
|||||||
EVENT_STATE_REPORTED,
|
EVENT_STATE_REPORTED,
|
||||||
{
|
{
|
||||||
"entity_id": entity_id,
|
"entity_id": entity_id,
|
||||||
|
"last_reported": now,
|
||||||
"old_last_reported": old_last_reported,
|
"old_last_reported": old_last_reported,
|
||||||
"new_state": old_state,
|
"new_state": old_state,
|
||||||
},
|
},
|
||||||
|
@ -813,6 +813,7 @@ class EntitySelectorConfig(BaseSelectorConfig, EntityFilterSelectorConfig, total
|
|||||||
exclude_entities: list[str]
|
exclude_entities: list[str]
|
||||||
include_entities: list[str]
|
include_entities: list[str]
|
||||||
multiple: bool
|
multiple: bool
|
||||||
|
reorder: bool
|
||||||
filter: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig]
|
filter: EntityFilterSelectorConfig | list[EntityFilterSelectorConfig]
|
||||||
|
|
||||||
|
|
||||||
@ -829,6 +830,7 @@ class EntitySelector(Selector[EntitySelectorConfig]):
|
|||||||
vol.Optional("exclude_entities"): [str],
|
vol.Optional("exclude_entities"): [str],
|
||||||
vol.Optional("include_entities"): [str],
|
vol.Optional("include_entities"): [str],
|
||||||
vol.Optional("multiple", default=False): cv.boolean,
|
vol.Optional("multiple", default=False): cv.boolean,
|
||||||
|
vol.Optional("reorder", default=False): cv.boolean,
|
||||||
vol.Optional("filter"): vol.All(
|
vol.Optional("filter"): vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
[ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA],
|
[ENTITY_FILTER_SELECTOR_CONFIG_SCHEMA],
|
||||||
|
@ -13,8 +13,10 @@ from homeassistant.components.sensor import (
|
|||||||
CONF_STATE_CLASS,
|
CONF_STATE_CLASS,
|
||||||
DEVICE_CLASSES_SCHEMA,
|
DEVICE_CLASSES_SCHEMA,
|
||||||
STATE_CLASSES_SCHEMA,
|
STATE_CLASSES_SCHEMA,
|
||||||
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_PICTURE,
|
ATTR_ENTITY_PICTURE,
|
||||||
ATTR_FRIENDLY_NAME,
|
ATTR_FRIENDLY_NAME,
|
||||||
@ -389,3 +391,20 @@ class ManualTriggerSensorEntity(ManualTriggerEntity, SensorEntity):
|
|||||||
ManualTriggerEntity.__init__(self, hass, config)
|
ManualTriggerEntity.__init__(self, hass, config)
|
||||||
self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
|
self._attr_native_unit_of_measurement = config.get(CONF_UNIT_OF_MEASUREMENT)
|
||||||
self._attr_state_class = config.get(CONF_STATE_CLASS)
|
self._attr_state_class = config.get(CONF_STATE_CLASS)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _set_native_value_with_possible_timestamp(self, value: Any) -> None:
|
||||||
|
"""Set native value with possible timestamp.
|
||||||
|
|
||||||
|
If self.device_class is `date` or `timestamp`,
|
||||||
|
it will try to parse the value to a date/datetime object.
|
||||||
|
"""
|
||||||
|
if self.device_class not in (
|
||||||
|
SensorDeviceClass.DATE,
|
||||||
|
SensorDeviceClass.TIMESTAMP,
|
||||||
|
):
|
||||||
|
self._attr_native_value = value
|
||||||
|
elif value is not None:
|
||||||
|
self._attr_native_value = async_parse_date_datetime(
|
||||||
|
value, self.entity_id, self.device_class
|
||||||
|
)
|
||||||
|
@ -38,7 +38,7 @@ habluetooth==4.0.1
|
|||||||
hass-nabucasa==0.107.1
|
hass-nabucasa==0.107.1
|
||||||
hassil==2.2.3
|
hassil==2.2.3
|
||||||
home-assistant-bluetooth==1.13.1
|
home-assistant-bluetooth==1.13.1
|
||||||
home-assistant-frontend==20250702.2
|
home-assistant-frontend==20250702.3
|
||||||
home-assistant-intents==2025.6.23
|
home-assistant-intents==2025.6.23
|
||||||
httpx==0.28.1
|
httpx==0.28.1
|
||||||
ifaddr==0.2.0
|
ifaddr==0.2.0
|
||||||
|
@ -589,10 +589,6 @@ filterwarnings = [
|
|||||||
# -- Websockets 14.1
|
# -- Websockets 14.1
|
||||||
# https://websockets.readthedocs.io/en/stable/howto/upgrade.html
|
# https://websockets.readthedocs.io/en/stable/howto/upgrade.html
|
||||||
"ignore:websockets.legacy is deprecated:DeprecationWarning:websockets.legacy",
|
"ignore:websockets.legacy is deprecated:DeprecationWarning:websockets.legacy",
|
||||||
# https://github.com/bluecurrent/HomeAssistantAPI/pull/19 - >=1.2.4
|
|
||||||
"ignore:websockets.client.connect is deprecated:DeprecationWarning:bluecurrent_api.websocket",
|
|
||||||
"ignore:websockets.client.WebSocketClientProtocol is deprecated:DeprecationWarning:bluecurrent_api.websocket",
|
|
||||||
"ignore:websockets.exceptions.InvalidStatusCode is deprecated:DeprecationWarning:bluecurrent_api.websocket",
|
|
||||||
# https://github.com/graphql-python/gql/pull/543 - >=4.0.0a0
|
# https://github.com/graphql-python/gql/pull/543 - >=4.0.0a0
|
||||||
"ignore:websockets.client.WebSocketClientProtocol is deprecated:DeprecationWarning:gql.transport.websockets_base",
|
"ignore:websockets.client.WebSocketClientProtocol is deprecated:DeprecationWarning:gql.transport.websockets_base",
|
||||||
|
|
||||||
|
14
requirements_all.txt
generated
14
requirements_all.txt
generated
@ -84,7 +84,7 @@ PyQRCode==1.2.1
|
|||||||
PyRMVtransport==0.3.3
|
PyRMVtransport==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.68.1
|
PySwitchbot==0.68.2
|
||||||
|
|
||||||
# homeassistant.components.switchmate
|
# homeassistant.components.switchmate
|
||||||
PySwitchmate==0.5.1
|
PySwitchmate==0.5.1
|
||||||
@ -185,7 +185,7 @@ aioairzone-cloud==0.6.15
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==3.2.10
|
aioamazondevices==3.5.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -247,7 +247,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==35.0.0
|
aioesphomeapi==36.0.1
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -634,7 +634,7 @@ blinkpy==0.23.0
|
|||||||
blockchain==1.4.4
|
blockchain==1.4.4
|
||||||
|
|
||||||
# homeassistant.components.blue_current
|
# homeassistant.components.blue_current
|
||||||
bluecurrent-api==1.2.3
|
bluecurrent-api==1.2.4
|
||||||
|
|
||||||
# homeassistant.components.bluemaestro
|
# homeassistant.components.bluemaestro
|
||||||
bluemaestro-ble==0.4.1
|
bluemaestro-ble==0.4.1
|
||||||
@ -1168,7 +1168,7 @@ hole==0.9.0
|
|||||||
holidays==0.76
|
holidays==0.76
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250702.2
|
home-assistant-frontend==20250702.3
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.6.23
|
home-assistant-intents==2025.6.23
|
||||||
@ -1234,7 +1234,7 @@ ihcsdk==2.8.5
|
|||||||
imeon_inverter_api==0.3.12
|
imeon_inverter_api==0.3.12
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.4.1
|
imgw_pib==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@ -2346,7 +2346,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smarla
|
# homeassistant.components.smarla
|
||||||
pysmarlaapi==0.9.0
|
pysmarlaapi==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.2.8
|
pysmartthings==3.2.8
|
||||||
|
14
requirements_test_all.txt
generated
14
requirements_test_all.txt
generated
@ -81,7 +81,7 @@ PyQRCode==1.2.1
|
|||||||
PyRMVtransport==0.3.3
|
PyRMVtransport==0.3.3
|
||||||
|
|
||||||
# homeassistant.components.switchbot
|
# homeassistant.components.switchbot
|
||||||
PySwitchbot==0.68.1
|
PySwitchbot==0.68.2
|
||||||
|
|
||||||
# homeassistant.components.syncthru
|
# homeassistant.components.syncthru
|
||||||
PySyncThru==0.8.0
|
PySyncThru==0.8.0
|
||||||
@ -173,7 +173,7 @@ aioairzone-cloud==0.6.15
|
|||||||
aioairzone==1.0.0
|
aioairzone==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.alexa_devices
|
# homeassistant.components.alexa_devices
|
||||||
aioamazondevices==3.2.10
|
aioamazondevices==3.5.0
|
||||||
|
|
||||||
# homeassistant.components.ambient_network
|
# homeassistant.components.ambient_network
|
||||||
# homeassistant.components.ambient_station
|
# homeassistant.components.ambient_station
|
||||||
@ -235,7 +235,7 @@ aioelectricitymaps==0.4.0
|
|||||||
aioemonitor==1.0.5
|
aioemonitor==1.0.5
|
||||||
|
|
||||||
# homeassistant.components.esphome
|
# homeassistant.components.esphome
|
||||||
aioesphomeapi==35.0.0
|
aioesphomeapi==36.0.1
|
||||||
|
|
||||||
# homeassistant.components.flo
|
# homeassistant.components.flo
|
||||||
aioflo==2021.11.0
|
aioflo==2021.11.0
|
||||||
@ -565,7 +565,7 @@ blebox-uniapi==2.5.0
|
|||||||
blinkpy==0.23.0
|
blinkpy==0.23.0
|
||||||
|
|
||||||
# homeassistant.components.blue_current
|
# homeassistant.components.blue_current
|
||||||
bluecurrent-api==1.2.3
|
bluecurrent-api==1.2.4
|
||||||
|
|
||||||
# homeassistant.components.bluemaestro
|
# homeassistant.components.bluemaestro
|
||||||
bluemaestro-ble==0.4.1
|
bluemaestro-ble==0.4.1
|
||||||
@ -1017,7 +1017,7 @@ hole==0.9.0
|
|||||||
holidays==0.76
|
holidays==0.76
|
||||||
|
|
||||||
# homeassistant.components.frontend
|
# homeassistant.components.frontend
|
||||||
home-assistant-frontend==20250702.2
|
home-assistant-frontend==20250702.3
|
||||||
|
|
||||||
# homeassistant.components.conversation
|
# homeassistant.components.conversation
|
||||||
home-assistant-intents==2025.6.23
|
home-assistant-intents==2025.6.23
|
||||||
@ -1068,7 +1068,7 @@ igloohome-api==0.1.1
|
|||||||
imeon_inverter_api==0.3.12
|
imeon_inverter_api==0.3.12
|
||||||
|
|
||||||
# homeassistant.components.imgw_pib
|
# homeassistant.components.imgw_pib
|
||||||
imgw_pib==1.4.1
|
imgw_pib==1.4.2
|
||||||
|
|
||||||
# homeassistant.components.incomfort
|
# homeassistant.components.incomfort
|
||||||
incomfort-client==0.6.9
|
incomfort-client==0.6.9
|
||||||
@ -1949,7 +1949,7 @@ pysma==0.7.5
|
|||||||
pysmappee==0.2.29
|
pysmappee==0.2.29
|
||||||
|
|
||||||
# homeassistant.components.smarla
|
# homeassistant.components.smarla
|
||||||
pysmarlaapi==0.9.0
|
pysmarlaapi==0.9.1
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.2.8
|
pysmartthings==3.2.8
|
||||||
|
@ -38,6 +38,9 @@ FIELD_SCHEMA = vol.Schema(
|
|||||||
TRIGGER_SCHEMA = vol.Any(
|
TRIGGER_SCHEMA = vol.Any(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
|
vol.Optional("target"): vol.Any(
|
||||||
|
selector.TargetSelector.CONFIG_SCHEMA, None
|
||||||
|
),
|
||||||
vol.Optional("fields"): vol.Schema({str: FIELD_SCHEMA}),
|
vol.Optional("fields"): vol.Schema({str: FIELD_SCHEMA}),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -20,13 +21,15 @@ DOWNLOAD_DIR = Path("build/translations-download").absolute()
|
|||||||
def run_download_docker():
|
def run_download_docker():
|
||||||
"""Run the Docker image to download the translations."""
|
"""Run the Docker image to download the translations."""
|
||||||
print("Running Docker to download latest translations.")
|
print("Running Docker to download latest translations.")
|
||||||
run = subprocess.run(
|
result = subprocess.run(
|
||||||
[
|
[
|
||||||
"docker",
|
"docker",
|
||||||
"run",
|
"run",
|
||||||
"-v",
|
"-v",
|
||||||
f"{DOWNLOAD_DIR}:/opt/dest/locale",
|
f"{DOWNLOAD_DIR}:/opt/dest/locale",
|
||||||
"--rm",
|
"--rm",
|
||||||
|
"--user",
|
||||||
|
f"{os.getuid()}:{os.getgid()}",
|
||||||
f"lokalise/lokalise-cli-2:{CLI_2_DOCKER_IMAGE}",
|
f"lokalise/lokalise-cli-2:{CLI_2_DOCKER_IMAGE}",
|
||||||
# Lokalise command
|
# Lokalise command
|
||||||
"lokalise2",
|
"lokalise2",
|
||||||
@ -52,7 +55,7 @@ def run_download_docker():
|
|||||||
)
|
)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
if run.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise ExitApp("Failed to download translations")
|
raise ExitApp("Failed to download translations")
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,6 +203,7 @@
|
|||||||
'light',
|
'light',
|
||||||
]),
|
]),
|
||||||
'multiple': False,
|
'multiple': False,
|
||||||
|
'reorder': False,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
@ -217,6 +218,7 @@
|
|||||||
'binary_sensor',
|
'binary_sensor',
|
||||||
]),
|
]),
|
||||||
'multiple': False,
|
'multiple': False,
|
||||||
|
'reorder': False,
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
@ -40,7 +40,6 @@ async def test_generic_alarm_control_panel_requires_code(
|
|||||||
object_id="myalarm_control_panel",
|
object_id="myalarm_control_panel",
|
||||||
key=1,
|
key=1,
|
||||||
name="my alarm_control_panel",
|
name="my alarm_control_panel",
|
||||||
unique_id="my_alarm_control_panel",
|
|
||||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||||
| EspHomeACPFeatures.ARM_HOME
|
| EspHomeACPFeatures.ARM_HOME
|
||||||
@ -173,7 +172,6 @@ async def test_generic_alarm_control_panel_no_code(
|
|||||||
object_id="myalarm_control_panel",
|
object_id="myalarm_control_panel",
|
||||||
key=1,
|
key=1,
|
||||||
name="my alarm_control_panel",
|
name="my alarm_control_panel",
|
||||||
unique_id="my_alarm_control_panel",
|
|
||||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||||
| EspHomeACPFeatures.ARM_HOME
|
| EspHomeACPFeatures.ARM_HOME
|
||||||
@ -219,7 +217,6 @@ async def test_generic_alarm_control_panel_missing_state(
|
|||||||
object_id="myalarm_control_panel",
|
object_id="myalarm_control_panel",
|
||||||
key=1,
|
key=1,
|
||||||
name="my alarm_control_panel",
|
name="my alarm_control_panel",
|
||||||
unique_id="my_alarm_control_panel",
|
|
||||||
supported_features=EspHomeACPFeatures.ARM_AWAY
|
supported_features=EspHomeACPFeatures.ARM_AWAY
|
||||||
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
| EspHomeACPFeatures.ARM_CUSTOM_BYPASS
|
||||||
| EspHomeACPFeatures.ARM_HOME
|
| EspHomeACPFeatures.ARM_HOME
|
||||||
|
@ -953,7 +953,6 @@ async def test_tts_format_from_media_player(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=[
|
supported_formats=[
|
||||||
MediaPlayerSupportedFormat(
|
MediaPlayerSupportedFormat(
|
||||||
@ -1020,7 +1019,6 @@ async def test_tts_minimal_format_from_media_player(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=[
|
supported_formats=[
|
||||||
MediaPlayerSupportedFormat(
|
MediaPlayerSupportedFormat(
|
||||||
@ -1156,7 +1154,6 @@ async def test_announce_media_id(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=[
|
supported_formats=[
|
||||||
MediaPlayerSupportedFormat(
|
MediaPlayerSupportedFormat(
|
||||||
@ -1437,7 +1434,6 @@ async def test_start_conversation_media_id(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=[
|
supported_formats=[
|
||||||
MediaPlayerSupportedFormat(
|
MediaPlayerSupportedFormat(
|
||||||
|
@ -24,7 +24,6 @@ async def test_binary_sensor_generic_entity(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
esphome_state, hass_state = binary_state
|
esphome_state, hass_state = binary_state
|
||||||
@ -52,7 +51,6 @@ async def test_status_binary_sensor(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
is_status_binary_sensor=True,
|
is_status_binary_sensor=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -80,7 +78,6 @@ async def test_binary_sensor_missing_state(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [BinarySensorState(key=1, state=True, missing_state=True)]
|
states = [BinarySensorState(key=1, state=True, missing_state=True)]
|
||||||
@ -107,7 +104,6 @@ async def test_binary_sensor_has_state_false(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -152,14 +148,12 @@ async def test_binary_sensors_same_key_different_device_id(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Motion",
|
name="Motion",
|
||||||
unique_id="motion_1",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Motion",
|
name="Motion",
|
||||||
unique_id="motion_2",
|
|
||||||
device_id=22222222,
|
device_id=22222222,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -235,14 +229,12 @@ async def test_binary_sensor_main_and_sub_device_same_key(
|
|||||||
object_id="main_sensor",
|
object_id="main_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Main Sensor",
|
name="Main Sensor",
|
||||||
unique_id="main_1",
|
|
||||||
device_id=0, # Main device
|
device_id=0, # Main device
|
||||||
),
|
),
|
||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="sub_sensor",
|
object_id="sub_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Sub Sensor",
|
name="Sub Sensor",
|
||||||
unique_id="sub_1",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -18,7 +18,6 @@ async def test_button_generic_entity(
|
|||||||
object_id="mybutton",
|
object_id="mybutton",
|
||||||
key=1,
|
key=1,
|
||||||
name="my button",
|
name="my button",
|
||||||
unique_id="my_button",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
|
@ -30,7 +30,6 @@ async def test_camera_single_image(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -75,7 +74,6 @@ async def test_camera_single_image_unavailable_before_requested(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -113,7 +111,6 @@ async def test_camera_single_image_unavailable_during_request(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -155,7 +152,6 @@ async def test_camera_stream(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -212,7 +208,6 @@ async def test_camera_stream_unavailable(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -249,7 +244,6 @@ async def test_camera_stream_with_disconnection(
|
|||||||
object_id="mycamera",
|
object_id="mycamera",
|
||||||
key=1,
|
key=1,
|
||||||
name="my camera",
|
name="my camera",
|
||||||
unique_id="my_camera",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
|
@ -58,7 +58,6 @@ async def test_climate_entity(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
supports_action=True,
|
supports_action=True,
|
||||||
visual_min_temperature=10.0,
|
visual_min_temperature=10.0,
|
||||||
@ -110,7 +109,6 @@ async def test_climate_entity_with_step_and_two_point(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
supports_two_point_target_temperature=True,
|
supports_two_point_target_temperature=True,
|
||||||
visual_target_temperature_step=2,
|
visual_target_temperature_step=2,
|
||||||
@ -187,7 +185,6 @@ async def test_climate_entity_with_step_and_target_temp(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
visual_target_temperature_step=2,
|
visual_target_temperature_step=2,
|
||||||
visual_current_temperature_step=2,
|
visual_current_temperature_step=2,
|
||||||
@ -345,7 +342,6 @@ async def test_climate_entity_with_humidity(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
supports_two_point_target_temperature=True,
|
supports_two_point_target_temperature=True,
|
||||||
supports_action=True,
|
supports_action=True,
|
||||||
@ -409,7 +405,6 @@ async def test_climate_entity_with_inf_value(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
supports_two_point_target_temperature=True,
|
supports_two_point_target_temperature=True,
|
||||||
supports_action=True,
|
supports_action=True,
|
||||||
@ -465,7 +460,6 @@ async def test_climate_entity_attributes(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=True,
|
supports_current_temperature=True,
|
||||||
visual_target_temperature_step=2,
|
visual_target_temperature_step=2,
|
||||||
visual_current_temperature_step=2,
|
visual_current_temperature_step=2,
|
||||||
@ -520,7 +514,6 @@ async def test_climate_entity_attribute_current_temperature_unsupported(
|
|||||||
object_id="myclimate",
|
object_id="myclimate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my climate",
|
name="my climate",
|
||||||
unique_id="my_climate",
|
|
||||||
supports_current_temperature=False,
|
supports_current_temperature=False,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -41,7 +41,6 @@ async def test_cover_entity(
|
|||||||
object_id="mycover",
|
object_id="mycover",
|
||||||
key=1,
|
key=1,
|
||||||
name="my cover",
|
name="my cover",
|
||||||
unique_id="my_cover",
|
|
||||||
supports_position=True,
|
supports_position=True,
|
||||||
supports_tilt=True,
|
supports_tilt=True,
|
||||||
supports_stop=True,
|
supports_stop=True,
|
||||||
@ -169,7 +168,6 @@ async def test_cover_entity_without_position(
|
|||||||
object_id="mycover",
|
object_id="mycover",
|
||||||
key=1,
|
key=1,
|
||||||
name="my cover",
|
name="my cover",
|
||||||
unique_id="my_cover",
|
|
||||||
supports_position=False,
|
supports_position=False,
|
||||||
supports_tilt=False,
|
supports_tilt=False,
|
||||||
supports_stop=False,
|
supports_stop=False,
|
||||||
|
@ -26,7 +26,6 @@ async def test_generic_date_entity(
|
|||||||
object_id="mydate",
|
object_id="mydate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my date",
|
name="my date",
|
||||||
unique_id="my_date",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [DateState(key=1, year=2024, month=12, day=31)]
|
states = [DateState(key=1, year=2024, month=12, day=31)]
|
||||||
@ -62,7 +61,6 @@ async def test_generic_date_missing_state(
|
|||||||
object_id="mydate",
|
object_id="mydate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my date",
|
name="my date",
|
||||||
unique_id="my_date",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [DateState(key=1, missing_state=True)]
|
states = [DateState(key=1, missing_state=True)]
|
||||||
|
@ -26,7 +26,6 @@ async def test_generic_datetime_entity(
|
|||||||
object_id="mydatetime",
|
object_id="mydatetime",
|
||||||
key=1,
|
key=1,
|
||||||
name="my datetime",
|
name="my datetime",
|
||||||
unique_id="my_datetime",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [DateTimeState(key=1, epoch_seconds=1713270896)]
|
states = [DateTimeState(key=1, epoch_seconds=1713270896)]
|
||||||
@ -65,7 +64,6 @@ async def test_generic_datetime_missing_state(
|
|||||||
object_id="mydatetime",
|
object_id="mydatetime",
|
||||||
key=1,
|
key=1,
|
||||||
name="my datetime",
|
name="my datetime",
|
||||||
unique_id="my_datetime",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [DateTimeState(key=1, missing_state=True)]
|
states = [DateTimeState(key=1, missing_state=True)]
|
||||||
|
@ -51,13 +51,11 @@ async def test_entities_removed(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="mybinary_sensor_to_be_removed",
|
object_id="mybinary_sensor_to_be_removed",
|
||||||
key=2,
|
key=2,
|
||||||
name="my binary_sensor to be removed",
|
name="my binary_sensor to be removed",
|
||||||
unique_id="mybinary_sensor_to_be_removed",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -100,7 +98,6 @@ async def test_entities_removed(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -140,13 +137,11 @@ async def test_entities_removed_after_reload(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="mybinary_sensor_to_be_removed",
|
object_id="mybinary_sensor_to_be_removed",
|
||||||
key=2,
|
key=2,
|
||||||
name="my binary_sensor to be removed",
|
name="my binary_sensor to be removed",
|
||||||
unique_id="mybinary_sensor_to_be_removed",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -214,7 +209,6 @@ async def test_entities_removed_after_reload(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
mock_device.client.list_entities_services = AsyncMock(
|
mock_device.client.list_entities_services = AsyncMock(
|
||||||
@ -267,7 +261,6 @@ async def test_entities_for_entire_platform_removed(
|
|||||||
object_id="mybinary_sensor_to_be_removed",
|
object_id="mybinary_sensor_to_be_removed",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor to be removed",
|
name="my binary_sensor to be removed",
|
||||||
unique_id="mybinary_sensor_to_be_removed",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -325,7 +318,6 @@ async def test_entity_info_object_ids(
|
|||||||
object_id="object_id_is_used",
|
object_id="object_id_is_used",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -350,13 +342,11 @@ async def test_deep_sleep_device(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
SensorInfo(
|
SensorInfo(
|
||||||
object_id="my_sensor",
|
object_id="my_sensor",
|
||||||
key=3,
|
key=3,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -456,7 +446,6 @@ async def test_esphome_device_without_friendly_name(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -486,7 +475,6 @@ async def test_entity_without_name_device_with_friendly_name(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="",
|
name="",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -519,7 +507,6 @@ async def test_entity_id_preserved_on_upgrade(
|
|||||||
object_id="my",
|
object_id="my",
|
||||||
key=1,
|
key=1,
|
||||||
name="my",
|
name="my",
|
||||||
unique_id="binary_sensor_my",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -560,7 +547,6 @@ async def test_entity_id_preserved_on_upgrade_old_format_entity_id(
|
|||||||
object_id="my",
|
object_id="my",
|
||||||
key=1,
|
key=1,
|
||||||
name="my",
|
name="my",
|
||||||
unique_id="binary_sensor_my",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -601,7 +587,6 @@ async def test_entity_id_preserved_on_upgrade_when_in_storage(
|
|||||||
object_id="my",
|
object_id="my",
|
||||||
key=1,
|
key=1,
|
||||||
name="my",
|
name="my",
|
||||||
unique_id="binary_sensor_my",
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -660,7 +645,6 @@ async def test_deep_sleep_added_after_setup(
|
|||||||
object_id="test",
|
object_id="test",
|
||||||
key=1,
|
key=1,
|
||||||
name="test",
|
name="test",
|
||||||
unique_id="test",
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
states=[
|
states=[
|
||||||
@ -732,7 +716,6 @@ async def test_entity_assignment_to_sub_device(
|
|||||||
object_id="main_sensor",
|
object_id="main_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Main Sensor",
|
name="Main Sensor",
|
||||||
unique_id="main_sensor",
|
|
||||||
device_id=0,
|
device_id=0,
|
||||||
),
|
),
|
||||||
# Entity for sub device 1
|
# Entity for sub device 1
|
||||||
@ -740,7 +723,6 @@ async def test_entity_assignment_to_sub_device(
|
|||||||
object_id="motion",
|
object_id="motion",
|
||||||
key=2,
|
key=2,
|
||||||
name="Motion",
|
name="Motion",
|
||||||
unique_id="motion",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
# Entity for sub device 2
|
# Entity for sub device 2
|
||||||
@ -748,7 +730,6 @@ async def test_entity_assignment_to_sub_device(
|
|||||||
object_id="door",
|
object_id="door",
|
||||||
key=3,
|
key=3,
|
||||||
name="Door",
|
name="Door",
|
||||||
unique_id="door",
|
|
||||||
device_id=22222222,
|
device_id=22222222,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -932,7 +913,6 @@ async def test_entity_switches_between_devices(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Test Sensor",
|
name="Test Sensor",
|
||||||
unique_id="sensor",
|
|
||||||
# device_id omitted - entity belongs to main device
|
# device_id omitted - entity belongs to main device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -964,7 +944,6 @@ async def test_entity_switches_between_devices(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Test Sensor",
|
name="Test Sensor",
|
||||||
unique_id="sensor",
|
|
||||||
device_id=11111111, # Now on sub device 1
|
device_id=11111111, # Now on sub device 1
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -993,7 +972,6 @@ async def test_entity_switches_between_devices(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Test Sensor",
|
name="Test Sensor",
|
||||||
unique_id="sensor",
|
|
||||||
device_id=22222222, # Now on sub device 2
|
device_id=22222222, # Now on sub device 2
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1020,7 +998,6 @@ async def test_entity_switches_between_devices(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Test Sensor",
|
name="Test Sensor",
|
||||||
unique_id="sensor",
|
|
||||||
# device_id omitted - back to main device
|
# device_id omitted - back to main device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1063,7 +1040,6 @@ async def test_entity_id_uses_sub_device_name(
|
|||||||
object_id="main_sensor",
|
object_id="main_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Main Sensor",
|
name="Main Sensor",
|
||||||
unique_id="main_sensor",
|
|
||||||
device_id=0,
|
device_id=0,
|
||||||
),
|
),
|
||||||
# Entity for sub device 1
|
# Entity for sub device 1
|
||||||
@ -1071,7 +1047,6 @@ async def test_entity_id_uses_sub_device_name(
|
|||||||
object_id="motion",
|
object_id="motion",
|
||||||
key=2,
|
key=2,
|
||||||
name="Motion",
|
name="Motion",
|
||||||
unique_id="motion",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
# Entity for sub device 2
|
# Entity for sub device 2
|
||||||
@ -1079,7 +1054,6 @@ async def test_entity_id_uses_sub_device_name(
|
|||||||
object_id="door",
|
object_id="door",
|
||||||
key=3,
|
key=3,
|
||||||
name="Door",
|
name="Door",
|
||||||
unique_id="door",
|
|
||||||
device_id=22222222,
|
device_id=22222222,
|
||||||
),
|
),
|
||||||
# Entity without name on sub device
|
# Entity without name on sub device
|
||||||
@ -1087,7 +1061,6 @@ async def test_entity_id_uses_sub_device_name(
|
|||||||
object_id="sensor_no_name",
|
object_id="sensor_no_name",
|
||||||
key=4,
|
key=4,
|
||||||
name="",
|
name="",
|
||||||
unique_id="sensor_no_name",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1147,7 +1120,6 @@ async def test_entity_id_with_empty_sub_device_name(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Sensor",
|
name="Sensor",
|
||||||
unique_id="sensor",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1187,8 +1159,7 @@ async def test_unique_id_migration_when_entity_moves_between_devices(
|
|||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="temperature",
|
object_id="temperature",
|
||||||
key=1,
|
key=1,
|
||||||
name="Temperature",
|
name="Temperature", # This field is not used by the integration
|
||||||
unique_id="unused", # This field is not used by the integration
|
|
||||||
device_id=0, # Main device
|
device_id=0, # Main device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1250,8 +1221,7 @@ async def test_unique_id_migration_when_entity_moves_between_devices(
|
|||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="temperature", # Same object_id
|
object_id="temperature", # Same object_id
|
||||||
key=1, # Same key - this is what identifies the entity
|
key=1, # Same key - this is what identifies the entity
|
||||||
name="Temperature",
|
name="Temperature", # This field is not used
|
||||||
unique_id="unused", # This field is not used
|
|
||||||
device_id=22222222, # Now on sub-device
|
device_id=22222222, # Now on sub-device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1312,7 +1282,6 @@ async def test_unique_id_migration_sub_device_to_main_device(
|
|||||||
object_id="temperature",
|
object_id="temperature",
|
||||||
key=1,
|
key=1,
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
unique_id="unused",
|
|
||||||
device_id=22222222, # On sub-device
|
device_id=22222222, # On sub-device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1347,7 +1316,6 @@ async def test_unique_id_migration_sub_device_to_main_device(
|
|||||||
object_id="temperature",
|
object_id="temperature",
|
||||||
key=1,
|
key=1,
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
unique_id="unused",
|
|
||||||
device_id=0, # Now on main device
|
device_id=0, # Now on main device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1407,7 +1375,6 @@ async def test_unique_id_migration_between_sub_devices(
|
|||||||
object_id="temperature",
|
object_id="temperature",
|
||||||
key=1,
|
key=1,
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
unique_id="unused",
|
|
||||||
device_id=22222222, # On kitchen_controller
|
device_id=22222222, # On kitchen_controller
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1442,7 +1409,6 @@ async def test_unique_id_migration_between_sub_devices(
|
|||||||
object_id="temperature",
|
object_id="temperature",
|
||||||
key=1,
|
key=1,
|
||||||
name="Temperature",
|
name="Temperature",
|
||||||
unique_id="unused",
|
|
||||||
device_id=33333333, # Now on bedroom_controller
|
device_id=33333333, # Now on bedroom_controller
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1501,7 +1467,6 @@ async def test_entity_device_id_rename_in_yaml(
|
|||||||
object_id="sensor",
|
object_id="sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="Sensor",
|
name="Sensor",
|
||||||
unique_id="unused",
|
|
||||||
device_id=11111111,
|
device_id=11111111,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1563,7 +1528,6 @@ async def test_entity_device_id_rename_in_yaml(
|
|||||||
object_id="sensor", # Same object_id
|
object_id="sensor", # Same object_id
|
||||||
key=1, # Same key
|
key=1, # Same key
|
||||||
name="Sensor",
|
name="Sensor",
|
||||||
unique_id="unused",
|
|
||||||
device_id=99999999, # New device_id after rename
|
device_id=99999999, # New device_id after rename
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1636,8 +1600,7 @@ async def test_entity_with_unicode_name(
|
|||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id=sanitized_object_id, # ESPHome sends the sanitized version
|
object_id=sanitized_object_id, # ESPHome sends the sanitized version
|
||||||
key=1,
|
key=1,
|
||||||
name=unicode_name, # But also sends the original Unicode name
|
name=unicode_name, # But also sends the original Unicode name,
|
||||||
unique_id="unicode_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [BinarySensorState(key=1, state=True)]
|
states = [BinarySensorState(key=1, state=True)]
|
||||||
@ -1677,8 +1640,7 @@ async def test_entity_without_name_uses_device_name_only(
|
|||||||
BinarySensorInfo(
|
BinarySensorInfo(
|
||||||
object_id="some_sanitized_id",
|
object_id="some_sanitized_id",
|
||||||
key=1,
|
key=1,
|
||||||
name="", # Empty name
|
name="", # Empty name,
|
||||||
unique_id="no_name_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [BinarySensorState(key=1, state=True)]
|
states = [BinarySensorState(key=1, state=True)]
|
||||||
|
@ -15,49 +15,6 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
from .conftest import MockGenericDeviceEntryType
|
from .conftest import MockGenericDeviceEntryType
|
||||||
|
|
||||||
|
|
||||||
async def test_migrate_entity_unique_id(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
entity_registry: er.EntityRegistry,
|
|
||||||
mock_client: APIClient,
|
|
||||||
mock_generic_device_entry: MockGenericDeviceEntryType,
|
|
||||||
) -> None:
|
|
||||||
"""Test a generic sensor entity unique id migration."""
|
|
||||||
entity_registry.async_get_or_create(
|
|
||||||
"sensor",
|
|
||||||
"esphome",
|
|
||||||
"my_sensor",
|
|
||||||
suggested_object_id="old_sensor",
|
|
||||||
disabled_by=None,
|
|
||||||
)
|
|
||||||
entity_info = [
|
|
||||||
SensorInfo(
|
|
||||||
object_id="mysensor",
|
|
||||||
key=1,
|
|
||||||
name="my sensor",
|
|
||||||
unique_id="my_sensor",
|
|
||||||
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
|
||||||
icon="mdi:leaf",
|
|
||||||
)
|
|
||||||
]
|
|
||||||
states = [SensorState(key=1, state=50)]
|
|
||||||
user_service = []
|
|
||||||
await mock_generic_device_entry(
|
|
||||||
mock_client=mock_client,
|
|
||||||
entity_info=entity_info,
|
|
||||||
user_service=user_service,
|
|
||||||
states=states,
|
|
||||||
)
|
|
||||||
state = hass.states.get("sensor.old_sensor")
|
|
||||||
assert state is not None
|
|
||||||
assert state.state == "50"
|
|
||||||
entry = entity_registry.async_get("sensor.old_sensor")
|
|
||||||
assert entry is not None
|
|
||||||
assert entity_registry.async_get_entity_id("sensor", "esphome", "my_sensor") is None
|
|
||||||
# Note that ESPHome includes the EntityInfo type in the unique id
|
|
||||||
# as this is not a 1:1 mapping to the entity platform (ie. text_sensor)
|
|
||||||
assert entry.unique_id == "11:22:33:44:55:AA-sensor-mysensor"
|
|
||||||
|
|
||||||
|
|
||||||
async def test_migrate_entity_unique_id_downgrade_upgrade(
|
async def test_migrate_entity_unique_id_downgrade_upgrade(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entity_registry: er.EntityRegistry,
|
entity_registry: er.EntityRegistry,
|
||||||
@ -84,7 +41,6 @@ async def test_migrate_entity_unique_id_downgrade_upgrade(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
||||||
icon="mdi:leaf",
|
icon="mdi:leaf",
|
||||||
)
|
)
|
||||||
|
@ -20,7 +20,6 @@ async def test_generic_event_entity(
|
|||||||
object_id="myevent",
|
object_id="myevent",
|
||||||
key=1,
|
key=1,
|
||||||
name="my event",
|
name="my event",
|
||||||
unique_id="my_event",
|
|
||||||
event_types=["type1", "type2"],
|
event_types=["type1", "type2"],
|
||||||
device_class=EventDeviceClass.BUTTON,
|
device_class=EventDeviceClass.BUTTON,
|
||||||
)
|
)
|
||||||
|
@ -44,7 +44,6 @@ async def test_fan_entity_with_all_features_old_api(
|
|||||||
object_id="myfan",
|
object_id="myfan",
|
||||||
key=1,
|
key=1,
|
||||||
name="my fan",
|
name="my fan",
|
||||||
unique_id="my_fan",
|
|
||||||
supports_direction=True,
|
supports_direction=True,
|
||||||
supports_speed=True,
|
supports_speed=True,
|
||||||
supports_oscillation=True,
|
supports_oscillation=True,
|
||||||
@ -147,7 +146,6 @@ async def test_fan_entity_with_all_features_new_api(
|
|||||||
object_id="myfan",
|
object_id="myfan",
|
||||||
key=1,
|
key=1,
|
||||||
name="my fan",
|
name="my fan",
|
||||||
unique_id="my_fan",
|
|
||||||
supported_speed_count=4,
|
supported_speed_count=4,
|
||||||
supports_direction=True,
|
supports_direction=True,
|
||||||
supports_speed=True,
|
supports_speed=True,
|
||||||
@ -317,7 +315,6 @@ async def test_fan_entity_with_no_features_new_api(
|
|||||||
object_id="myfan",
|
object_id="myfan",
|
||||||
key=1,
|
key=1,
|
||||||
name="my fan",
|
name="my fan",
|
||||||
unique_id="my_fan",
|
|
||||||
supports_direction=False,
|
supports_direction=False,
|
||||||
supports_speed=False,
|
supports_speed=False,
|
||||||
supports_oscillation=False,
|
supports_oscillation=False,
|
||||||
|
@ -56,7 +56,6 @@ async def test_light_on_off(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[ESPColorMode.ON_OFF],
|
supported_color_modes=[ESPColorMode.ON_OFF],
|
||||||
@ -98,7 +97,6 @@ async def test_light_brightness(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[LightColorCapability.BRIGHTNESS],
|
supported_color_modes=[LightColorCapability.BRIGHTNESS],
|
||||||
@ -226,7 +224,6 @@ async def test_light_legacy_brightness(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[LightColorCapability.BRIGHTNESS, 2],
|
supported_color_modes=[LightColorCapability.BRIGHTNESS, 2],
|
||||||
@ -282,7 +279,6 @@ async def test_light_brightness_on_off(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[ESPColorMode.ON_OFF, ESPColorMode.BRIGHTNESS],
|
supported_color_modes=[ESPColorMode.ON_OFF, ESPColorMode.BRIGHTNESS],
|
||||||
@ -358,7 +354,6 @@ async def test_light_legacy_white_converted_to_brightness(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -423,7 +418,6 @@ async def test_light_legacy_white_with_rgb(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[color_mode, color_mode_2],
|
supported_color_modes=[color_mode, color_mode_2],
|
||||||
@ -478,7 +472,6 @@ async def test_light_brightness_on_off_with_unknown_color_mode(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -555,7 +548,6 @@ async def test_light_on_and_brightness(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -607,7 +599,6 @@ async def test_rgb_color_temp_light(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=color_modes,
|
supported_color_modes=color_modes,
|
||||||
@ -698,7 +689,6 @@ async def test_light_rgb(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
LightColorCapability.RGB
|
LightColorCapability.RGB
|
||||||
| LightColorCapability.ON_OFF
|
| LightColorCapability.ON_OFF
|
||||||
@ -821,7 +811,6 @@ async def test_light_rgbw(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
LightColorCapability.RGB
|
LightColorCapability.RGB
|
||||||
| LightColorCapability.WHITE
|
| LightColorCapability.WHITE
|
||||||
@ -991,7 +980,6 @@ async def test_light_rgbww_with_cold_warm_white_support(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1200,7 +1188,6 @@ async def test_light_rgbww_without_cold_warm_white_support(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1439,7 +1426,6 @@ async def test_light_color_temp(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153.846161,
|
min_mireds=153.846161,
|
||||||
max_mireds=370.370361,
|
max_mireds=370.370361,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1514,7 +1500,6 @@ async def test_light_color_temp_no_mireds_set(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=0,
|
min_mireds=0,
|
||||||
max_mireds=0,
|
max_mireds=0,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1610,7 +1595,6 @@ async def test_light_color_temp_legacy(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153.846161,
|
min_mireds=153.846161,
|
||||||
max_mireds=370.370361,
|
max_mireds=370.370361,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1695,7 +1679,6 @@ async def test_light_rgb_legacy(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153.846161,
|
min_mireds=153.846161,
|
||||||
max_mireds=370.370361,
|
max_mireds=370.370361,
|
||||||
supported_color_modes=[
|
supported_color_modes=[
|
||||||
@ -1795,7 +1778,6 @@ async def test_light_effects(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
effects=["effect1", "effect2"],
|
effects=["effect1", "effect2"],
|
||||||
@ -1859,7 +1841,6 @@ async def test_only_cold_warm_white_support(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[color_modes],
|
supported_color_modes=[color_modes],
|
||||||
@ -1955,7 +1936,6 @@ async def test_light_no_color_modes(
|
|||||||
object_id="mylight",
|
object_id="mylight",
|
||||||
key=1,
|
key=1,
|
||||||
name="my light",
|
name="my light",
|
||||||
unique_id="my_light",
|
|
||||||
min_mireds=153,
|
min_mireds=153,
|
||||||
max_mireds=400,
|
max_mireds=400,
|
||||||
supported_color_modes=[color_mode],
|
supported_color_modes=[color_mode],
|
||||||
|
@ -34,7 +34,6 @@ async def test_lock_entity_no_open(
|
|||||||
object_id="mylock",
|
object_id="mylock",
|
||||||
key=1,
|
key=1,
|
||||||
name="my lock",
|
name="my lock",
|
||||||
unique_id="my_lock",
|
|
||||||
supports_open=False,
|
supports_open=False,
|
||||||
requires_code=False,
|
requires_code=False,
|
||||||
)
|
)
|
||||||
@ -72,7 +71,6 @@ async def test_lock_entity_start_locked(
|
|||||||
object_id="mylock",
|
object_id="mylock",
|
||||||
key=1,
|
key=1,
|
||||||
name="my lock",
|
name="my lock",
|
||||||
unique_id="my_lock",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [LockEntityState(key=1, state=ESPHomeLockState.LOCKED)]
|
states = [LockEntityState(key=1, state=ESPHomeLockState.LOCKED)]
|
||||||
@ -99,7 +97,6 @@ async def test_lock_entity_supports_open(
|
|||||||
object_id="mylock",
|
object_id="mylock",
|
||||||
key=1,
|
key=1,
|
||||||
name="my lock",
|
name="my lock",
|
||||||
unique_id="my_lock",
|
|
||||||
supports_open=True,
|
supports_open=True,
|
||||||
requires_code=True,
|
requires_code=True,
|
||||||
)
|
)
|
||||||
|
@ -55,7 +55,6 @@ async def test_media_player_entity(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -202,7 +201,6 @@ async def test_media_player_entity_with_source(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -318,7 +316,6 @@ async def test_media_player_proxy(
|
|||||||
object_id="mymedia_player",
|
object_id="mymedia_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="my media_player",
|
name="my media_player",
|
||||||
unique_id="my_media_player",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=[
|
supported_formats=[
|
||||||
MediaPlayerSupportedFormat(
|
MediaPlayerSupportedFormat(
|
||||||
@ -477,7 +474,6 @@ async def test_media_player_formats_reload_preserves_data(
|
|||||||
object_id="test_media_player",
|
object_id="test_media_player",
|
||||||
key=1,
|
key=1,
|
||||||
name="Test Media Player",
|
name="Test Media Player",
|
||||||
unique_id="test_unique_id",
|
|
||||||
supports_pause=True,
|
supports_pause=True,
|
||||||
supported_formats=supported_formats,
|
supported_formats=supported_formats,
|
||||||
)
|
)
|
||||||
|
@ -35,7 +35,6 @@ async def test_generic_number_entity(
|
|||||||
object_id="mynumber",
|
object_id="mynumber",
|
||||||
key=1,
|
key=1,
|
||||||
name="my number",
|
name="my number",
|
||||||
unique_id="my_number",
|
|
||||||
max_value=100,
|
max_value=100,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
step=1,
|
step=1,
|
||||||
@ -75,7 +74,6 @@ async def test_generic_number_nan(
|
|||||||
object_id="mynumber",
|
object_id="mynumber",
|
||||||
key=1,
|
key=1,
|
||||||
name="my number",
|
name="my number",
|
||||||
unique_id="my_number",
|
|
||||||
max_value=100,
|
max_value=100,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
step=1,
|
step=1,
|
||||||
@ -107,7 +105,6 @@ async def test_generic_number_with_unit_of_measurement_as_empty_string(
|
|||||||
object_id="mynumber",
|
object_id="mynumber",
|
||||||
key=1,
|
key=1,
|
||||||
name="my number",
|
name="my number",
|
||||||
unique_id="my_number",
|
|
||||||
max_value=100,
|
max_value=100,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
step=1,
|
step=1,
|
||||||
@ -140,7 +137,6 @@ async def test_generic_number_entity_set_when_disconnected(
|
|||||||
object_id="mynumber",
|
object_id="mynumber",
|
||||||
key=1,
|
key=1,
|
||||||
name="my number",
|
name="my number",
|
||||||
unique_id="my_number",
|
|
||||||
max_value=100,
|
max_value=100,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
step=1,
|
step=1,
|
||||||
|
@ -133,7 +133,6 @@ async def test_device_conflict_migration(
|
|||||||
object_id="mybinary_sensor",
|
object_id="mybinary_sensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my binary_sensor",
|
name="my binary_sensor",
|
||||||
unique_id="my_binary_sensor",
|
|
||||||
is_status_binary_sensor=True,
|
is_status_binary_sensor=True,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -67,7 +67,6 @@ async def test_select_generic_entity(
|
|||||||
object_id="myselect",
|
object_id="myselect",
|
||||||
key=1,
|
key=1,
|
||||||
name="my select",
|
name="my select",
|
||||||
unique_id="my_select",
|
|
||||||
options=["a", "b"],
|
options=["a", "b"],
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
@ -54,7 +54,6 @@ async def test_generic_numeric_sensor(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [SensorState(key=1, state=50)]
|
states = [SensorState(key=1, state=50)]
|
||||||
@ -110,7 +109,6 @@ async def test_generic_numeric_sensor_with_entity_category_and_icon(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
entity_category=ESPHomeEntityCategory.DIAGNOSTIC,
|
||||||
icon="mdi:leaf",
|
icon="mdi:leaf",
|
||||||
)
|
)
|
||||||
@ -147,7 +145,6 @@ async def test_generic_numeric_sensor_state_class_measurement(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
state_class=ESPHomeSensorStateClass.MEASUREMENT,
|
state_class=ESPHomeSensorStateClass.MEASUREMENT,
|
||||||
device_class="power",
|
device_class="power",
|
||||||
unit_of_measurement="W",
|
unit_of_measurement="W",
|
||||||
@ -184,7 +181,6 @@ async def test_generic_numeric_sensor_device_class_timestamp(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
device_class="timestamp",
|
device_class="timestamp",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -212,7 +208,6 @@ async def test_generic_numeric_sensor_legacy_last_reset_convert(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
legacy_last_reset_type=LastResetType.AUTO,
|
legacy_last_reset_type=LastResetType.AUTO,
|
||||||
state_class=ESPHomeSensorStateClass.MEASUREMENT,
|
state_class=ESPHomeSensorStateClass.MEASUREMENT,
|
||||||
)
|
)
|
||||||
@ -242,7 +237,6 @@ async def test_generic_numeric_sensor_no_state(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = []
|
states = []
|
||||||
@ -269,7 +263,6 @@ async def test_generic_numeric_sensor_nan_state(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [SensorState(key=1, state=math.nan, missing_state=False)]
|
states = [SensorState(key=1, state=math.nan, missing_state=False)]
|
||||||
@ -296,7 +289,6 @@ async def test_generic_numeric_sensor_missing_state(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [SensorState(key=1, state=True, missing_state=True)]
|
states = [SensorState(key=1, state=True, missing_state=True)]
|
||||||
@ -323,7 +315,6 @@ async def test_generic_text_sensor(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [TextSensorState(key=1, state="i am a teapot")]
|
states = [TextSensorState(key=1, state="i am a teapot")]
|
||||||
@ -350,7 +341,6 @@ async def test_generic_text_sensor_missing_state(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [TextSensorState(key=1, state=True, missing_state=True)]
|
states = [TextSensorState(key=1, state=True, missing_state=True)]
|
||||||
@ -377,7 +367,6 @@ async def test_generic_text_sensor_device_class_timestamp(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -406,7 +395,6 @@ async def test_generic_text_sensor_device_class_date(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
device_class=SensorDeviceClass.DATE,
|
device_class=SensorDeviceClass.DATE,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -435,7 +423,6 @@ async def test_generic_numeric_sensor_empty_string_uom(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
unit_of_measurement="",
|
unit_of_measurement="",
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
@ -493,7 +480,6 @@ async def test_suggested_display_precision_by_device_class(
|
|||||||
object_id="mysensor",
|
object_id="mysensor",
|
||||||
key=1,
|
key=1,
|
||||||
name="my sensor",
|
name="my sensor",
|
||||||
unique_id="my_sensor",
|
|
||||||
accuracy_decimals=expected_precision,
|
accuracy_decimals=expected_precision,
|
||||||
device_class=device_class.value,
|
device_class=device_class.value,
|
||||||
unit_of_measurement=unit_of_measurement,
|
unit_of_measurement=unit_of_measurement,
|
||||||
|
@ -26,7 +26,6 @@ async def test_switch_generic_entity(
|
|||||||
object_id="myswitch",
|
object_id="myswitch",
|
||||||
key=1,
|
key=1,
|
||||||
name="my switch",
|
name="my switch",
|
||||||
unique_id="my_switch",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [SwitchState(key=1, state=True)]
|
states = [SwitchState(key=1, state=True)]
|
||||||
@ -78,14 +77,12 @@ async def test_switch_sub_device_non_zero_device_id(
|
|||||||
object_id="main_switch",
|
object_id="main_switch",
|
||||||
key=1,
|
key=1,
|
||||||
name="Main Switch",
|
name="Main Switch",
|
||||||
unique_id="main_switch_1",
|
|
||||||
device_id=0, # Main device
|
device_id=0, # Main device
|
||||||
),
|
),
|
||||||
SwitchInfo(
|
SwitchInfo(
|
||||||
object_id="sub_switch",
|
object_id="sub_switch",
|
||||||
key=2,
|
key=2,
|
||||||
name="Sub Switch",
|
name="Sub Switch",
|
||||||
unique_id="sub_switch_1",
|
|
||||||
device_id=11111111, # Sub-device
|
device_id=11111111, # Sub-device
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -26,7 +26,6 @@ async def test_generic_text_entity(
|
|||||||
object_id="mytext",
|
object_id="mytext",
|
||||||
key=1,
|
key=1,
|
||||||
name="my text",
|
name="my text",
|
||||||
unique_id="my_text",
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
min_length=0,
|
min_length=0,
|
||||||
pattern=None,
|
pattern=None,
|
||||||
@ -66,7 +65,6 @@ async def test_generic_text_entity_no_state(
|
|||||||
object_id="mytext",
|
object_id="mytext",
|
||||||
key=1,
|
key=1,
|
||||||
name="my text",
|
name="my text",
|
||||||
unique_id="my_text",
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
min_length=0,
|
min_length=0,
|
||||||
pattern=None,
|
pattern=None,
|
||||||
@ -97,7 +95,6 @@ async def test_generic_text_entity_missing_state(
|
|||||||
object_id="mytext",
|
object_id="mytext",
|
||||||
key=1,
|
key=1,
|
||||||
name="my text",
|
name="my text",
|
||||||
unique_id="my_text",
|
|
||||||
max_length=100,
|
max_length=100,
|
||||||
min_length=0,
|
min_length=0,
|
||||||
pattern=None,
|
pattern=None,
|
||||||
|
@ -26,7 +26,6 @@ async def test_generic_time_entity(
|
|||||||
object_id="mytime",
|
object_id="mytime",
|
||||||
key=1,
|
key=1,
|
||||||
name="my time",
|
name="my time",
|
||||||
unique_id="my_time",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [TimeState(key=1, hour=12, minute=34, second=56)]
|
states = [TimeState(key=1, hour=12, minute=34, second=56)]
|
||||||
@ -62,7 +61,6 @@ async def test_generic_time_missing_state(
|
|||||||
object_id="mytime",
|
object_id="mytime",
|
||||||
key=1,
|
key=1,
|
||||||
name="my time",
|
name="my time",
|
||||||
unique_id="my_time",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [TimeState(key=1, missing_state=True)]
|
states = [TimeState(key=1, missing_state=True)]
|
||||||
|
@ -436,7 +436,6 @@ async def test_generic_device_update_entity(
|
|||||||
object_id="myupdate",
|
object_id="myupdate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my update",
|
name="my update",
|
||||||
unique_id="my_update",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -470,7 +469,6 @@ async def test_generic_device_update_entity_has_update(
|
|||||||
object_id="myupdate",
|
object_id="myupdate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my update",
|
name="my update",
|
||||||
unique_id="my_update",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
states = [
|
states = [
|
||||||
@ -561,7 +559,6 @@ async def test_update_entity_release_notes(
|
|||||||
object_id="myupdate",
|
object_id="myupdate",
|
||||||
key=1,
|
key=1,
|
||||||
name="my update",
|
name="my update",
|
||||||
unique_id="my_update",
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ async def test_valve_entity(
|
|||||||
object_id="myvalve",
|
object_id="myvalve",
|
||||||
key=1,
|
key=1,
|
||||||
name="my valve",
|
name="my valve",
|
||||||
unique_id="my_valve",
|
|
||||||
supports_position=True,
|
supports_position=True,
|
||||||
supports_stop=True,
|
supports_stop=True,
|
||||||
)
|
)
|
||||||
@ -134,7 +133,6 @@ async def test_valve_entity_without_position(
|
|||||||
object_id="myvalve",
|
object_id="myvalve",
|
||||||
key=1,
|
key=1,
|
||||||
name="my valve",
|
name="my valve",
|
||||||
unique_id="my_valve",
|
|
||||||
supports_position=False,
|
supports_position=False,
|
||||||
supports_stop=False,
|
supports_stop=False,
|
||||||
)
|
)
|
||||||
|
@ -10,7 +10,7 @@ import pytest
|
|||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
|
from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL
|
||||||
from homeassistant.const import STATE_UNKNOWN, Platform
|
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ async def test_sensor_unknown_states(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("sensor.test_mower_1_mode")
|
state = hass.states.get("sensor.test_mower_1_mode")
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_cutting_blade_usage_time_sensor(
|
async def test_cutting_blade_usage_time_sensor(
|
||||||
@ -78,7 +78,7 @@ async def test_next_start_sensor(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
state = hass.states.get("sensor.test_mower_1_next_start")
|
state = hass.states.get("sensor.test_mower_1_next_start")
|
||||||
assert state.state == STATE_UNKNOWN
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def test_work_area_sensor(
|
async def test_work_area_sensor(
|
||||||
|
@ -924,6 +924,30 @@ async def test_invalid_unit_of_measurement(
|
|||||||
"device_class": None,
|
"device_class": None,
|
||||||
"unit_of_measurement": None,
|
"unit_of_measurement": None,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Test 4",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"device_class": "ph",
|
||||||
|
"unit_of_measurement": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test 5",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"device_class": "ph",
|
||||||
|
"unit_of_measurement": " ",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test 6",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"device_class": None,
|
||||||
|
"unit_of_measurement": "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test 7",
|
||||||
|
"state_topic": "test-topic",
|
||||||
|
"device_class": None,
|
||||||
|
"unit_of_measurement": " ",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -936,10 +960,25 @@ async def test_valid_device_class_and_uom(
|
|||||||
await mqtt_mock_entry()
|
await mqtt_mock_entry()
|
||||||
|
|
||||||
state = hass.states.get("sensor.test_1")
|
state = hass.states.get("sensor.test_1")
|
||||||
|
assert state is not None
|
||||||
assert state.attributes["device_class"] == "temperature"
|
assert state.attributes["device_class"] == "temperature"
|
||||||
state = hass.states.get("sensor.test_2")
|
state = hass.states.get("sensor.test_2")
|
||||||
|
assert state is not None
|
||||||
assert "device_class" not in state.attributes
|
assert "device_class" not in state.attributes
|
||||||
state = hass.states.get("sensor.test_3")
|
state = hass.states.get("sensor.test_3")
|
||||||
|
assert state is not None
|
||||||
|
assert "device_class" not in state.attributes
|
||||||
|
state = hass.states.get("sensor.test_4")
|
||||||
|
assert state is not None
|
||||||
|
assert state.attributes["device_class"] == "ph"
|
||||||
|
state = hass.states.get("sensor.test_5")
|
||||||
|
assert state is not None
|
||||||
|
assert state.attributes["device_class"] == "ph"
|
||||||
|
state = hass.states.get("sensor.test_6")
|
||||||
|
assert state is not None
|
||||||
|
assert "device_class" not in state.attributes
|
||||||
|
state = hass.states.get("sensor.test_7")
|
||||||
|
assert state is not None
|
||||||
assert "device_class" not in state.attributes
|
assert "device_class" not in state.attributes
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
"""Test AI Task platform of Ollama integration."""
|
"""Test AI Task platform of Ollama integration."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import ollama
|
||||||
import pytest
|
import pytest
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import ai_task
|
from homeassistant.components import ai_task, media_source
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import entity_registry as er, selector
|
from homeassistant.helpers import entity_registry as er, selector
|
||||||
@ -243,3 +245,115 @@ async def test_generate_invalid_structured_data(
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("mock_init_component")
|
||||||
|
async def test_generate_data_with_attachment(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test AI Task data generation with image attachments."""
|
||||||
|
entity_id = "ai_task.ollama_ai_task"
|
||||||
|
|
||||||
|
# Mock the Ollama chat response as an async iterator
|
||||||
|
async def mock_chat_response():
|
||||||
|
"""Mock streaming response."""
|
||||||
|
yield {
|
||||||
|
"message": {"role": "assistant", "content": "Generated test data"},
|
||||||
|
"done": True,
|
||||||
|
"done_reason": "stop",
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.media_source.async_resolve_media",
|
||||||
|
side_effect=[
|
||||||
|
media_source.PlayMedia(
|
||||||
|
url="http://example.com/doorbell_snapshot.jpg",
|
||||||
|
mime_type="image/jpeg",
|
||||||
|
path=Path("doorbell_snapshot.jpg"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"ollama.AsyncClient.chat",
|
||||||
|
return_value=mock_chat_response(),
|
||||||
|
) as mock_chat,
|
||||||
|
):
|
||||||
|
result = await ai_task.async_generate_data(
|
||||||
|
hass,
|
||||||
|
task_name="Test Task",
|
||||||
|
entity_id=entity_id,
|
||||||
|
instructions="Generate test data",
|
||||||
|
attachments=[
|
||||||
|
{"media_content_id": "media-source://media/doorbell_snapshot.jpg"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.data == "Generated test data"
|
||||||
|
|
||||||
|
assert mock_chat.call_count == 1
|
||||||
|
messages = mock_chat.call_args[1]["messages"]
|
||||||
|
assert len(messages) == 2
|
||||||
|
chat_message = messages[1]
|
||||||
|
assert chat_message.role == "user"
|
||||||
|
assert chat_message.content == "Generate test data"
|
||||||
|
assert chat_message.images == [
|
||||||
|
ollama.Image(value=Path("doorbell_snapshot.jpg")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("mock_init_component")
|
||||||
|
async def test_generate_data_with_unsupported_file_format(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
entity_registry: er.EntityRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test AI Task data generation with image attachments."""
|
||||||
|
entity_id = "ai_task.ollama_ai_task"
|
||||||
|
|
||||||
|
# Mock the Ollama chat response as an async iterator
|
||||||
|
async def mock_chat_response():
|
||||||
|
"""Mock streaming response."""
|
||||||
|
yield {
|
||||||
|
"message": {"role": "assistant", "content": "Generated test data"},
|
||||||
|
"done": True,
|
||||||
|
"done_reason": "stop",
|
||||||
|
}
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.media_source.async_resolve_media",
|
||||||
|
side_effect=[
|
||||||
|
media_source.PlayMedia(
|
||||||
|
url="http://example.com/doorbell_snapshot.jpg",
|
||||||
|
mime_type="image/jpeg",
|
||||||
|
path=Path("doorbell_snapshot.jpg"),
|
||||||
|
),
|
||||||
|
media_source.PlayMedia(
|
||||||
|
url="http://example.com/context.txt",
|
||||||
|
mime_type="text/plain",
|
||||||
|
path=Path("context.txt"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"ollama.AsyncClient.chat",
|
||||||
|
return_value=mock_chat_response(),
|
||||||
|
),
|
||||||
|
pytest.raises(
|
||||||
|
HomeAssistantError,
|
||||||
|
match="Ollama only supports image attachments in user content",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await ai_task.async_generate_data(
|
||||||
|
hass,
|
||||||
|
task_name="Test Task",
|
||||||
|
entity_id=entity_id,
|
||||||
|
instructions="Generate test data",
|
||||||
|
attachments=[
|
||||||
|
{"media_content_id": "media-source://media/doorbell_snapshot.jpg"},
|
||||||
|
{"media_content_id": "media-source://media/context.txt"},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@ -231,6 +231,11 @@ def test_device_selector_schema_error(schema) -> None:
|
|||||||
["sensor.abc123", "sensor.ghi789"],
|
["sensor.abc123", "sensor.ghi789"],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
{"multiple": True, "reorder": True},
|
||||||
|
((["sensor.abc123", "sensor.def456"],)),
|
||||||
|
(None, "abc123", ["sensor.abc123", None]),
|
||||||
|
),
|
||||||
(
|
(
|
||||||
{"filter": {"domain": "light"}},
|
{"filter": {"domain": "light"}},
|
||||||
("light.abc123", FAKE_UUID),
|
("light.abc123", FAKE_UUID),
|
||||||
|
@ -1091,6 +1091,7 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"multiple": False,
|
"multiple": False,
|
||||||
|
"reorder": False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1113,6 +1114,7 @@ async def test_async_get_all_descriptions_filter(hass: HomeAssistant) -> None:
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"multiple": False,
|
"multiple": False,
|
||||||
|
"reorder": False,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,10 @@ from typing import Any
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
|
from homeassistant.components.sensor.helpers import async_parse_date_datetime
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICE_CLASS,
|
||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
CONF_STATE,
|
CONF_STATE,
|
||||||
@ -20,6 +23,7 @@ from homeassistant.helpers.trigger_template_entity import (
|
|||||||
CONF_AVAILABILITY,
|
CONF_AVAILABILITY,
|
||||||
CONF_PICTURE,
|
CONF_PICTURE,
|
||||||
ManualTriggerEntity,
|
ManualTriggerEntity,
|
||||||
|
ManualTriggerSensorEntity,
|
||||||
ValueTemplate,
|
ValueTemplate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -288,3 +292,38 @@ async def test_trigger_template_complex(hass: HomeAssistant) -> None:
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert entity.some_other_key == {"test_key": "test_data"}
|
assert entity.some_other_key == {"test_key": "test_data"}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_manual_trigger_sensor_entity_with_date(
|
||||||
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||||
|
) -> None:
|
||||||
|
"""Test manual trigger template entity when availability template isn't used."""
|
||||||
|
config = {
|
||||||
|
CONF_NAME: template.Template("test_entity", hass),
|
||||||
|
CONF_STATE: template.Template("{{ as_datetime(value) }}", hass),
|
||||||
|
CONF_DEVICE_CLASS: SensorDeviceClass.TIMESTAMP,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestEntity(ManualTriggerSensorEntity):
|
||||||
|
"""Test entity class."""
|
||||||
|
|
||||||
|
extra_template_keys = (CONF_STATE,)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self) -> bool | None:
|
||||||
|
"""Return extra attributes."""
|
||||||
|
return "2025-01-01T00:00:00+00:00"
|
||||||
|
|
||||||
|
entity = TestEntity(hass, config)
|
||||||
|
entity.entity_id = "test.entity"
|
||||||
|
variables = entity._template_variables_with_value("2025-01-01T00:00:00+00:00")
|
||||||
|
assert entity._render_availability_template(variables) is True
|
||||||
|
assert entity.available is True
|
||||||
|
entity._set_native_value_with_possible_timestamp(entity.state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entity.native_value == async_parse_date_datetime(
|
||||||
|
"2025-01-01T00:00:00+00:00", entity.entity_id, entity.device_class
|
||||||
|
)
|
||||||
|
assert entity.state == "2025-01-01T00:00:00+00:00"
|
||||||
|
assert entity.device_class == SensorDeviceClass.TIMESTAMP
|
||||||
|
Loading…
x
Reference in New Issue
Block a user