mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
2024.7.4 (#122770)
This commit is contained in:
commit
17930a6d66
@ -60,6 +60,7 @@ AUTH_EXCEPTIONS = (
|
|||||||
exceptions.NoCredentialsError,
|
exceptions.NoCredentialsError,
|
||||||
)
|
)
|
||||||
CONNECTION_TIMEOUT_EXCEPTIONS = (
|
CONNECTION_TIMEOUT_EXCEPTIONS = (
|
||||||
|
OSError,
|
||||||
asyncio.CancelledError,
|
asyncio.CancelledError,
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
exceptions.ConnectionLostError,
|
exceptions.ConnectionLostError,
|
||||||
|
@ -6,5 +6,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
|
||||||
"requirements": ["py-sucks==0.9.10", "deebot-client==8.1.1"]
|
"requirements": ["py-sucks==0.9.10", "deebot-client==8.2.0"]
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,14 @@ SUPPORTED_SCHEMA_KEYS = {
|
|||||||
|
|
||||||
def _format_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
def _format_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""Format the schema to protobuf."""
|
"""Format the schema to protobuf."""
|
||||||
|
if (subschemas := schema.get("anyOf")) or (subschemas := schema.get("allOf")):
|
||||||
|
for subschema in subschemas: # Gemini API does not support anyOf and allOf keys
|
||||||
|
if "type" in subschema: # Fallback to first subschema with 'type' field
|
||||||
|
return _format_schema(subschema)
|
||||||
|
return _format_schema(
|
||||||
|
subschemas[0]
|
||||||
|
) # Or, if not found, to any of the subschemas
|
||||||
|
|
||||||
result = {}
|
result = {}
|
||||||
for key, val in schema.items():
|
for key, val in schema.items():
|
||||||
if key not in SUPPORTED_SCHEMA_KEYS:
|
if key not in SUPPORTED_SCHEMA_KEYS:
|
||||||
@ -81,12 +89,22 @@ def _format_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
|||||||
key = "type_"
|
key = "type_"
|
||||||
val = val.upper()
|
val = val.upper()
|
||||||
elif key == "format":
|
elif key == "format":
|
||||||
|
if (schema.get("type") == "string" and val != "enum") or (
|
||||||
|
schema.get("type") not in ("number", "integer", "string")
|
||||||
|
):
|
||||||
|
continue
|
||||||
key = "format_"
|
key = "format_"
|
||||||
elif key == "items":
|
elif key == "items":
|
||||||
val = _format_schema(val)
|
val = _format_schema(val)
|
||||||
elif key == "properties":
|
elif key == "properties":
|
||||||
val = {k: _format_schema(v) for k, v in val.items()}
|
val = {k: _format_schema(v) for k, v in val.items()}
|
||||||
result[key] = val
|
result[key] = val
|
||||||
|
|
||||||
|
if result.get("type_") == "OBJECT" and not result.get("properties"):
|
||||||
|
# An object with undefined properties is not supported by Gemini API.
|
||||||
|
# Fallback to JSON string. This will probably fail for most tools that want it,
|
||||||
|
# but we don't have a better fallback strategy so far.
|
||||||
|
result["properties"] = {"json": {"type_": "STRING"}}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@
|
|||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["aiohue"],
|
"loggers": ["aiohue"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["aiohue==4.7.1"],
|
"requirements": ["aiohue==4.7.2"],
|
||||||
"zeroconf": ["_hue._tcp.local."]
|
"zeroconf": ["_hue._tcp.local."]
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,7 @@ async def async_setup_hue_events(bridge: HueBridge):
|
|||||||
CONF_ID: slugify(f"{hue_device.metadata.name} Button"),
|
CONF_ID: slugify(f"{hue_device.metadata.name} Button"),
|
||||||
CONF_DEVICE_ID: device.id, # type: ignore[union-attr]
|
CONF_DEVICE_ID: device.id, # type: ignore[union-attr]
|
||||||
CONF_UNIQUE_ID: hue_resource.id,
|
CONF_UNIQUE_ID: hue_resource.id,
|
||||||
CONF_TYPE: hue_resource.button.last_event.value,
|
CONF_TYPE: hue_resource.button.button_report.event.value,
|
||||||
CONF_SUBTYPE: hue_resource.metadata.control_id,
|
CONF_SUBTYPE: hue_resource.metadata.control_id,
|
||||||
}
|
}
|
||||||
hass.bus.async_fire(ATTR_HUE_EVENT, data)
|
hass.bus.async_fire(ATTR_HUE_EVENT, data)
|
||||||
@ -79,7 +79,7 @@ async def async_setup_hue_events(bridge: HueBridge):
|
|||||||
data = {
|
data = {
|
||||||
CONF_DEVICE_ID: device.id, # type: ignore[union-attr]
|
CONF_DEVICE_ID: device.id, # type: ignore[union-attr]
|
||||||
CONF_UNIQUE_ID: hue_resource.id,
|
CONF_UNIQUE_ID: hue_resource.id,
|
||||||
CONF_TYPE: hue_resource.relative_rotary.last_event.action.value,
|
CONF_TYPE: hue_resource.relative_rotary.rotary_report.action.value,
|
||||||
CONF_SUBTYPE: hue_resource.relative_rotary.last_event.rotation.direction.value,
|
CONF_SUBTYPE: hue_resource.relative_rotary.last_event.rotation.direction.value,
|
||||||
CONF_DURATION: hue_resource.relative_rotary.last_event.rotation.duration,
|
CONF_DURATION: hue_resource.relative_rotary.last_event.rotation.duration,
|
||||||
CONF_STEPS: hue_resource.relative_rotary.last_event.rotation.steps,
|
CONF_STEPS: hue_resource.relative_rotary.last_event.rotation.steps,
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
|
"loggers": ["aiolifx", "aiolifx_effects", "bitstring"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"aiolifx==1.0.5",
|
"aiolifx==1.0.6",
|
||||||
"aiolifx-effects==0.3.2",
|
"aiolifx-effects==0.3.2",
|
||||||
"aiolifx-themes==0.4.15"
|
"aiolifx-themes==0.4.15"
|
||||||
]
|
]
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from typing import Any
|
from typing import Any, cast
|
||||||
|
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
from mastodon.Mastodon import MastodonAPIError, MastodonUnauthorizedError
|
from mastodon.Mastodon import MastodonAPIError, MastodonUnauthorizedError
|
||||||
@ -71,11 +71,15 @@ class MastodonNotificationService(BaseNotificationService):
|
|||||||
|
|
||||||
def send_message(self, message: str = "", **kwargs: Any) -> None:
|
def send_message(self, message: str = "", **kwargs: Any) -> None:
|
||||||
"""Toot a message, with media perhaps."""
|
"""Toot a message, with media perhaps."""
|
||||||
|
|
||||||
|
target = None
|
||||||
|
if (target_list := kwargs.get(ATTR_TARGET)) is not None:
|
||||||
|
target = cast(list[str], target_list)[0]
|
||||||
|
|
||||||
data = kwargs.get(ATTR_DATA)
|
data = kwargs.get(ATTR_DATA)
|
||||||
|
|
||||||
media = None
|
media = None
|
||||||
mediadata = None
|
mediadata = None
|
||||||
target = None
|
|
||||||
sensitive = False
|
sensitive = False
|
||||||
content_warning = None
|
content_warning = None
|
||||||
|
|
||||||
@ -87,7 +91,6 @@ class MastodonNotificationService(BaseNotificationService):
|
|||||||
return
|
return
|
||||||
mediadata = self._upload_media(media)
|
mediadata = self._upload_media(media)
|
||||||
|
|
||||||
target = data.get(ATTR_TARGET)
|
|
||||||
sensitive = data.get(ATTR_MEDIA_WARNING)
|
sensitive = data.get(ATTR_MEDIA_WARNING)
|
||||||
content_warning = data.get(ATTR_CONTENT_WARNING)
|
content_warning = data.get(ATTR_CONTENT_WARNING)
|
||||||
|
|
||||||
|
@ -168,10 +168,10 @@ class MatterLock(MatterEntity, LockEntity):
|
|||||||
|
|
||||||
LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id)
|
LOGGER.debug("Lock state: %s for %s", lock_state, self.entity_id)
|
||||||
|
|
||||||
if lock_state is clusters.DoorLock.Enums.DlLockState.kUnlatched:
|
if lock_state == clusters.DoorLock.Enums.DlLockState.kUnlatched:
|
||||||
self._attr_is_locked = False
|
self._attr_is_locked = False
|
||||||
self._attr_is_open = True
|
self._attr_is_open = True
|
||||||
if lock_state is clusters.DoorLock.Enums.DlLockState.kLocked:
|
elif lock_state == clusters.DoorLock.Enums.DlLockState.kLocked:
|
||||||
self._attr_is_locked = True
|
self._attr_is_locked = True
|
||||||
self._attr_is_open = False
|
self._attr_is_open = False
|
||||||
elif lock_state in (
|
elif lock_state in (
|
||||||
|
@ -251,7 +251,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
mqtt_data.client.async_restore_tracked_subscriptions(
|
mqtt_data.client.async_restore_tracked_subscriptions(
|
||||||
mqtt_data.subscriptions_to_restore
|
mqtt_data.subscriptions_to_restore
|
||||||
)
|
)
|
||||||
mqtt_data.subscriptions_to_restore = []
|
mqtt_data.subscriptions_to_restore = set()
|
||||||
mqtt_data.reload_dispatchers.append(
|
mqtt_data.reload_dispatchers.append(
|
||||||
entry.add_update_listener(_async_config_entry_updated)
|
entry.add_update_listener(_async_config_entry_updated)
|
||||||
)
|
)
|
||||||
|
@ -428,12 +428,12 @@ class MQTT:
|
|||||||
await self.async_init_client()
|
await self.async_init_client()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def subscriptions(self) -> list[Subscription]:
|
def subscriptions(self) -> set[Subscription]:
|
||||||
"""Return the tracked subscriptions."""
|
"""Return the tracked subscriptions."""
|
||||||
return [
|
return {
|
||||||
*chain.from_iterable(self._simple_subscriptions.values()),
|
*chain.from_iterable(self._simple_subscriptions.values()),
|
||||||
*self._wildcard_subscriptions,
|
*self._wildcard_subscriptions,
|
||||||
]
|
}
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
"""Clean up listeners."""
|
"""Clean up listeners."""
|
||||||
@ -736,7 +736,7 @@ class MQTT:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_restore_tracked_subscriptions(
|
def async_restore_tracked_subscriptions(
|
||||||
self, subscriptions: list[Subscription]
|
self, subscriptions: set[Subscription]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Restore tracked subscriptions after reload."""
|
"""Restore tracked subscriptions after reload."""
|
||||||
for subscription in subscriptions:
|
for subscription in subscriptions:
|
||||||
|
@ -423,7 +423,7 @@ class MqttData:
|
|||||||
reload_handlers: dict[str, CALLBACK_TYPE] = field(default_factory=dict)
|
reload_handlers: dict[str, CALLBACK_TYPE] = field(default_factory=dict)
|
||||||
reload_schema: dict[str, VolSchemaType] = field(default_factory=dict)
|
reload_schema: dict[str, VolSchemaType] = field(default_factory=dict)
|
||||||
state_write_requests: EntityTopicState = field(default_factory=EntityTopicState)
|
state_write_requests: EntityTopicState = field(default_factory=EntityTopicState)
|
||||||
subscriptions_to_restore: list[Subscription] = field(default_factory=list)
|
subscriptions_to_restore: set[Subscription] = field(default_factory=set)
|
||||||
tags: dict[str, dict[str, MQTTTagScanner]] = field(default_factory=dict)
|
tags: dict[str, dict[str, MQTTTagScanner]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
"documentation": "https://www.home-assistant.io/integrations/reolink",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
"loggers": ["reolink_aio"],
|
"loggers": ["reolink_aio"],
|
||||||
"requirements": ["reolink-aio==0.9.4"]
|
"requirements": ["reolink-aio==0.9.5"]
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ async def _generate_trackables(
|
|||||||
trackable = await trackable.details()
|
trackable = await trackable.details()
|
||||||
|
|
||||||
# Check that the pet has tracker linked.
|
# Check that the pet has tracker linked.
|
||||||
if not trackable["device_id"]:
|
if not trackable.get("device_id"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if "details" not in trackable:
|
if "details" not in trackable:
|
||||||
|
@ -7,5 +7,5 @@
|
|||||||
"integration_type": "device",
|
"integration_type": "device",
|
||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["aiotractive"],
|
"loggers": ["aiotractive"],
|
||||||
"requirements": ["aiotractive==0.5.6"]
|
"requirements": ["aiotractive==0.6.0"]
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="air_temp",
|
key="air_temp",
|
||||||
translation_key="air_temperature",
|
translation_key="air_temperature",
|
||||||
value_fn=lambda data: data.air_temp or 0,
|
value_fn=lambda data: data.air_temp,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@ -69,7 +69,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="road_temp",
|
key="road_temp",
|
||||||
translation_key="road_temperature",
|
translation_key="road_temperature",
|
||||||
value_fn=lambda data: data.road_temp or 0,
|
value_fn=lambda data: data.road_temp,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@ -91,7 +91,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="wind_speed",
|
key="wind_speed",
|
||||||
value_fn=lambda data: data.windforce or 0,
|
value_fn=lambda data: data.windforce,
|
||||||
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@ -99,7 +99,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="wind_speed_max",
|
key="wind_speed_max",
|
||||||
translation_key="wind_speed_max",
|
translation_key="wind_speed_max",
|
||||||
value_fn=lambda data: data.windforcemax or 0,
|
value_fn=lambda data: data.windforcemax,
|
||||||
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
native_unit_of_measurement=UnitOfSpeed.METERS_PER_SECOND,
|
||||||
device_class=SensorDeviceClass.WIND_SPEED,
|
device_class=SensorDeviceClass.WIND_SPEED,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@ -107,7 +107,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
value_fn=lambda data: data.humidity or 0,
|
value_fn=lambda data: data.humidity,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
device_class=SensorDeviceClass.HUMIDITY,
|
device_class=SensorDeviceClass.HUMIDITY,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@ -115,7 +115,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
),
|
),
|
||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="precipitation_amount",
|
key="precipitation_amount",
|
||||||
value_fn=lambda data: data.precipitation_amount or 0,
|
value_fn=lambda data: data.precipitation_amount,
|
||||||
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
native_unit_of_measurement=UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR,
|
||||||
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
device_class=SensorDeviceClass.PRECIPITATION_INTENSITY,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@ -130,7 +130,7 @@ SENSOR_TYPES: tuple[TrafikverketSensorEntityDescription, ...] = (
|
|||||||
TrafikverketSensorEntityDescription(
|
TrafikverketSensorEntityDescription(
|
||||||
key="dew_point",
|
key="dew_point",
|
||||||
translation_key="dew_point",
|
translation_key="dew_point",
|
||||||
value_fn=lambda data: data.dew_point or 0,
|
value_fn=lambda data: data.dew_point,
|
||||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||||
device_class=SensorDeviceClass.TEMPERATURE,
|
device_class=SensorDeviceClass.TEMPERATURE,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
|
@ -950,6 +950,8 @@ class ViCareSensor(ViCareEntity, SensorEntity):
|
|||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
super().__init__(device_config, api, description.key)
|
super().__init__(device_config, api, description.key)
|
||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
|
# run update to have device_class set depending on unit_of_measurement
|
||||||
|
self.update()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
|
@ -579,6 +579,15 @@ DISCOVERY_SCHEMAS = [
|
|||||||
),
|
),
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
),
|
),
|
||||||
|
# ZVIDAR Z-CM-V01 (SmartWings/Deyi WM25L/V Z-Wave Motor for Roller Shade)
|
||||||
|
ZWaveDiscoverySchema(
|
||||||
|
platform=Platform.COVER,
|
||||||
|
hint="shade",
|
||||||
|
manufacturer_id={0x045A},
|
||||||
|
product_id={0x0507},
|
||||||
|
product_type={0x0904},
|
||||||
|
primary_value=SWITCH_MULTILEVEL_CURRENT_VALUE_SCHEMA,
|
||||||
|
),
|
||||||
# Vision Security ZL7432 In Wall Dual Relay Switch
|
# Vision Security ZL7432 In Wall Dual Relay Switch
|
||||||
ZWaveDiscoverySchema(
|
ZWaveDiscoverySchema(
|
||||||
platform=Platform.SWITCH,
|
platform=Platform.SWITCH,
|
||||||
|
@ -24,7 +24,7 @@ if TYPE_CHECKING:
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2024
|
MAJOR_VERSION: Final = 2024
|
||||||
MINOR_VERSION: Final = 7
|
MINOR_VERSION: Final = 7
|
||||||
PATCH_VERSION: Final = "3"
|
PATCH_VERSION: Final = "4"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 12, 0)
|
||||||
|
@ -615,6 +615,9 @@ class ScriptTool(Tool):
|
|||||||
entity_registry = er.async_get(hass)
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
self.name = split_entity_id(script_entity_id)[1]
|
self.name = split_entity_id(script_entity_id)[1]
|
||||||
|
if self.name[0].isdigit():
|
||||||
|
self.name = "_" + self.name
|
||||||
|
self._entity_id = script_entity_id
|
||||||
self.parameters = vol.Schema({})
|
self.parameters = vol.Schema({})
|
||||||
entity_entry = entity_registry.async_get(script_entity_id)
|
entity_entry = entity_registry.async_get(script_entity_id)
|
||||||
if entity_entry and entity_entry.unique_id:
|
if entity_entry and entity_entry.unique_id:
|
||||||
@ -715,7 +718,7 @@ class ScriptTool(Tool):
|
|||||||
SCRIPT_DOMAIN,
|
SCRIPT_DOMAIN,
|
||||||
SERVICE_TURN_ON,
|
SERVICE_TURN_ON,
|
||||||
{
|
{
|
||||||
ATTR_ENTITY_ID: SCRIPT_DOMAIN + "." + self.name,
|
ATTR_ENTITY_ID: self._entity_id,
|
||||||
ATTR_VARIABLES: tool_input.tool_args,
|
ATTR_VARIABLES: tool_input.tool_args,
|
||||||
},
|
},
|
||||||
context=llm_context.context,
|
context=llm_context.context,
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2024.7.3"
|
version = "2024.7.4"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -264,7 +264,7 @@ aioharmony==0.2.10
|
|||||||
aiohomekit==3.1.5
|
aiohomekit==3.1.5
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==4.7.1
|
aiohue==4.7.2
|
||||||
|
|
||||||
# homeassistant.components.imap
|
# homeassistant.components.imap
|
||||||
aioimaplib==1.1.0
|
aioimaplib==1.1.0
|
||||||
@ -282,7 +282,7 @@ aiolifx-effects==0.3.2
|
|||||||
aiolifx-themes==0.4.15
|
aiolifx-themes==0.4.15
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.0.5
|
aiolifx==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
aiolivisi==0.0.19
|
aiolivisi==0.0.19
|
||||||
@ -386,7 +386,7 @@ aiosyncthing==0.5.1
|
|||||||
aiotankerkoenig==0.4.1
|
aiotankerkoenig==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.tractive
|
# homeassistant.components.tractive
|
||||||
aiotractive==0.5.6
|
aiotractive==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==79
|
aiounifi==79
|
||||||
@ -709,7 +709,7 @@ debugpy==1.8.1
|
|||||||
# decora==0.6
|
# decora==0.6
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==8.1.1
|
deebot-client==8.2.0
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -2460,7 +2460,7 @@ renault-api==0.2.4
|
|||||||
renson-endura-delta==1.7.1
|
renson-endura-delta==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.9.4
|
reolink-aio==0.9.5
|
||||||
|
|
||||||
# homeassistant.components.idteck_prox
|
# homeassistant.components.idteck_prox
|
||||||
rfk101py==0.0.1
|
rfk101py==0.0.1
|
||||||
|
@ -240,7 +240,7 @@ aioharmony==0.2.10
|
|||||||
aiohomekit==3.1.5
|
aiohomekit==3.1.5
|
||||||
|
|
||||||
# homeassistant.components.hue
|
# homeassistant.components.hue
|
||||||
aiohue==4.7.1
|
aiohue==4.7.2
|
||||||
|
|
||||||
# homeassistant.components.imap
|
# homeassistant.components.imap
|
||||||
aioimaplib==1.1.0
|
aioimaplib==1.1.0
|
||||||
@ -255,7 +255,7 @@ aiolifx-effects==0.3.2
|
|||||||
aiolifx-themes==0.4.15
|
aiolifx-themes==0.4.15
|
||||||
|
|
||||||
# homeassistant.components.lifx
|
# homeassistant.components.lifx
|
||||||
aiolifx==1.0.5
|
aiolifx==1.0.6
|
||||||
|
|
||||||
# homeassistant.components.livisi
|
# homeassistant.components.livisi
|
||||||
aiolivisi==0.0.19
|
aiolivisi==0.0.19
|
||||||
@ -359,7 +359,7 @@ aiosyncthing==0.5.1
|
|||||||
aiotankerkoenig==0.4.1
|
aiotankerkoenig==0.4.1
|
||||||
|
|
||||||
# homeassistant.components.tractive
|
# homeassistant.components.tractive
|
||||||
aiotractive==0.5.6
|
aiotractive==0.6.0
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==79
|
aiounifi==79
|
||||||
@ -590,7 +590,7 @@ dbus-fast==2.22.1
|
|||||||
debugpy==1.8.1
|
debugpy==1.8.1
|
||||||
|
|
||||||
# homeassistant.components.ecovacs
|
# homeassistant.components.ecovacs
|
||||||
deebot-client==8.1.1
|
deebot-client==8.2.0
|
||||||
|
|
||||||
# homeassistant.components.ihc
|
# homeassistant.components.ihc
|
||||||
# homeassistant.components.namecheapdns
|
# homeassistant.components.namecheapdns
|
||||||
@ -1924,7 +1924,7 @@ renault-api==0.2.4
|
|||||||
renson-endura-delta==1.7.1
|
renson-endura-delta==1.7.1
|
||||||
|
|
||||||
# homeassistant.components.reolink
|
# homeassistant.components.reolink
|
||||||
reolink-aio==0.9.4
|
reolink-aio==0.9.5
|
||||||
|
|
||||||
# homeassistant.components.rflink
|
# homeassistant.components.rflink
|
||||||
rflink==0.0.66
|
rflink==0.0.66
|
||||||
|
@ -442,6 +442,24 @@
|
|||||||
description: "Test function"
|
description: "Test function"
|
||||||
parameters {
|
parameters {
|
||||||
type_: OBJECT
|
type_: OBJECT
|
||||||
|
properties {
|
||||||
|
key: "param3"
|
||||||
|
value {
|
||||||
|
type_: OBJECT
|
||||||
|
properties {
|
||||||
|
key: "json"
|
||||||
|
value {
|
||||||
|
type_: STRING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
properties {
|
||||||
|
key: "param2"
|
||||||
|
value {
|
||||||
|
type_: NUMBER
|
||||||
|
}
|
||||||
|
}
|
||||||
properties {
|
properties {
|
||||||
key: "param1"
|
key: "param1"
|
||||||
value {
|
value {
|
||||||
@ -449,7 +467,6 @@
|
|||||||
description: "Test parameters"
|
description: "Test parameters"
|
||||||
items {
|
items {
|
||||||
type_: STRING
|
type_: STRING
|
||||||
format_: "lower"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,9 @@ async def test_function_call(
|
|||||||
{
|
{
|
||||||
vol.Optional("param1", description="Test parameters"): [
|
vol.Optional("param1", description="Test parameters"): [
|
||||||
vol.All(str, vol.Lower)
|
vol.All(str, vol.Lower)
|
||||||
]
|
],
|
||||||
|
vol.Optional("param2"): vol.Any(float, int),
|
||||||
|
vol.Optional("param3"): dict,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1487,6 +1487,10 @@
|
|||||||
"on": {
|
"on": {
|
||||||
"on": true
|
"on": true
|
||||||
},
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "7cee478d-6455-483a-9e32-9f9fdcbcc4f6",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
"type": "grouped_light"
|
"type": "grouped_light"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1498,6 +1502,10 @@
|
|||||||
"on": {
|
"on": {
|
||||||
"on": true
|
"on": true
|
||||||
},
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "7cee478d-6455-483a-9e32-9f9fdcbcc4f6",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
"type": "grouped_light"
|
"type": "grouped_light"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1509,6 +1517,10 @@
|
|||||||
"on": {
|
"on": {
|
||||||
"on": false
|
"on": false
|
||||||
},
|
},
|
||||||
|
"owner": {
|
||||||
|
"rid": "7cee478d-6455-483a-9e32-9f9fdcbcc4f6",
|
||||||
|
"rtype": "zone"
|
||||||
|
},
|
||||||
"type": "grouped_light"
|
"type": "grouped_light"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,12 @@ async def test_hue_event(
|
|||||||
|
|
||||||
# Emit button update event
|
# Emit button update event
|
||||||
btn_event = {
|
btn_event = {
|
||||||
"button": {"last_event": "initial_press"},
|
"button": {
|
||||||
|
"button_report": {
|
||||||
|
"event": "initial_press",
|
||||||
|
"updated": "2021-10-01T12:00:00Z",
|
||||||
|
}
|
||||||
|
},
|
||||||
"id": "c658d3d8-a013-4b81-8ac6-78b248537e70",
|
"id": "c658d3d8-a013-4b81-8ac6-78b248537e70",
|
||||||
"metadata": {"control_id": 1},
|
"metadata": {"control_id": 1},
|
||||||
"type": "button",
|
"type": "button",
|
||||||
@ -41,7 +46,7 @@ async def test_hue_event(
|
|||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
assert events[0].data["id"] == "wall_switch_with_2_controls_button"
|
assert events[0].data["id"] == "wall_switch_with_2_controls_button"
|
||||||
assert events[0].data["unique_id"] == btn_event["id"]
|
assert events[0].data["unique_id"] == btn_event["id"]
|
||||||
assert events[0].data["type"] == btn_event["button"]["last_event"]
|
assert events[0].data["type"] == btn_event["button"]["button_report"]["event"]
|
||||||
assert events[0].data["subtype"] == btn_event["metadata"]["control_id"]
|
assert events[0].data["subtype"] == btn_event["metadata"]["control_id"]
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import pytest
|
|||||||
|
|
||||||
from homeassistant.components.lock import (
|
from homeassistant.components.lock import (
|
||||||
STATE_LOCKED,
|
STATE_LOCKED,
|
||||||
|
STATE_OPEN,
|
||||||
STATE_UNLOCKED,
|
STATE_UNLOCKED,
|
||||||
LockEntityFeature,
|
LockEntityFeature,
|
||||||
)
|
)
|
||||||
@ -82,12 +83,12 @@ async def test_lock(
|
|||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNLOCKED
|
assert state.state == STATE_UNLOCKED
|
||||||
|
|
||||||
set_node_attribute(door_lock, 1, 257, 0, 0)
|
set_node_attribute(door_lock, 1, 257, 0, 1)
|
||||||
await trigger_subscription_callback(hass, matter_client)
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
state = hass.states.get("lock.mock_door_lock_lock")
|
state = hass.states.get("lock.mock_door_lock_lock")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_UNLOCKED
|
assert state.state == STATE_LOCKED
|
||||||
|
|
||||||
set_node_attribute(door_lock, 1, 257, 0, None)
|
set_node_attribute(door_lock, 1, 257, 0, None)
|
||||||
await trigger_subscription_callback(hass, matter_client)
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
@ -213,9 +214,16 @@ async def test_lock_with_unbolt(
|
|||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_OPENING
|
assert state.state == STATE_OPENING
|
||||||
|
|
||||||
set_node_attribute(door_lock_with_unbolt, 1, 257, 3, 0)
|
set_node_attribute(door_lock_with_unbolt, 1, 257, 0, 0)
|
||||||
await trigger_subscription_callback(hass, matter_client)
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
state = hass.states.get("lock.mock_door_lock_lock")
|
state = hass.states.get("lock.mock_door_lock_lock")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == STATE_LOCKED
|
assert state.state == STATE_UNLOCKED
|
||||||
|
|
||||||
|
set_node_attribute(door_lock_with_unbolt, 1, 257, 0, 3)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get("lock.mock_door_lock_lock")
|
||||||
|
assert state
|
||||||
|
assert state.state == STATE_OPEN
|
@ -472,6 +472,12 @@ def iblinds_v3_state_fixture():
|
|||||||
return json.loads(load_fixture("zwave_js/cover_iblinds_v3_state.json"))
|
return json.loads(load_fixture("zwave_js/cover_iblinds_v3_state.json"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="zvidar_state", scope="package")
|
||||||
|
def zvidar_state_fixture():
|
||||||
|
"""Load the ZVIDAR node state fixture data."""
|
||||||
|
return json.loads(load_fixture("zwave_js/cover_zvidar_state.json"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="qubino_shutter_state", scope="package")
|
@pytest.fixture(name="qubino_shutter_state", scope="package")
|
||||||
def qubino_shutter_state_fixture():
|
def qubino_shutter_state_fixture():
|
||||||
"""Load the Qubino Shutter node state fixture data."""
|
"""Load the Qubino Shutter node state fixture data."""
|
||||||
@ -1081,6 +1087,14 @@ def iblinds_v3_cover_fixture(client, iblinds_v3_state):
|
|||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="zvidar")
|
||||||
|
def zvidar_cover_fixture(client, zvidar_state):
|
||||||
|
"""Mock a ZVIDAR window cover node."""
|
||||||
|
node = Node(client, copy.deepcopy(zvidar_state))
|
||||||
|
client.driver.controller.nodes[node.node_id] = node
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="qubino_shutter")
|
@pytest.fixture(name="qubino_shutter")
|
||||||
def qubino_shutter_cover_fixture(client, qubino_shutter_state):
|
def qubino_shutter_cover_fixture(client, qubino_shutter_state):
|
||||||
"""Mock a Qubino flush shutter node."""
|
"""Mock a Qubino flush shutter node."""
|
||||||
|
1120
tests/components/zwave_js/fixtures/cover_zvidar_state.json
Normal file
1120
tests/components/zwave_js/fixtures/cover_zvidar_state.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -49,6 +49,18 @@ async def test_iblinds_v2(hass: HomeAssistant, client, iblinds_v2, integration)
|
|||||||
assert state
|
assert state
|
||||||
|
|
||||||
|
|
||||||
|
async def test_zvidar_state(hass: HomeAssistant, client, zvidar, integration) -> None:
|
||||||
|
"""Test that an ZVIDAR Z-CM-V01 multilevel switch value is discovered as a cover."""
|
||||||
|
node = zvidar
|
||||||
|
assert node.device_class.specific.label == "Unused"
|
||||||
|
|
||||||
|
state = hass.states.get("light.window_blind_controller")
|
||||||
|
assert not state
|
||||||
|
|
||||||
|
state = hass.states.get("cover.window_blind_controller")
|
||||||
|
assert state
|
||||||
|
|
||||||
|
|
||||||
async def test_ge_12730(hass: HomeAssistant, client, ge_12730, integration) -> None:
|
async def test_ge_12730(hass: HomeAssistant, client, ge_12730, integration) -> None:
|
||||||
"""Test GE 12730 Fan Controller v2.0 multilevel switch is discovered as a fan."""
|
"""Test GE 12730 Fan Controller v2.0 multilevel switch is discovered as a fan."""
|
||||||
node = ge_12730
|
node = ge_12730
|
||||||
|
@ -780,6 +780,46 @@ async def test_script_tool(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_script_tool_name(hass: HomeAssistant) -> None:
|
||||||
|
"""Test that script tool name is not started with a digit."""
|
||||||
|
assert await async_setup_component(hass, "homeassistant", {})
|
||||||
|
context = Context()
|
||||||
|
llm_context = llm.LLMContext(
|
||||||
|
platform="test_platform",
|
||||||
|
context=context,
|
||||||
|
user_prompt="test_text",
|
||||||
|
language="*",
|
||||||
|
assistant="conversation",
|
||||||
|
device_id=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a script with a unique ID
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"script",
|
||||||
|
{
|
||||||
|
"script": {
|
||||||
|
"123456": {
|
||||||
|
"description": "This is a test script",
|
||||||
|
"sequence": [],
|
||||||
|
"fields": {
|
||||||
|
"beer": {"description": "Number of beers", "required": True},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async_expose_entity(hass, "conversation", "script.123456", True)
|
||||||
|
|
||||||
|
api = await llm.async_get_api(hass, "assist", llm_context)
|
||||||
|
|
||||||
|
tools = [tool for tool in api.tools if isinstance(tool, llm.ScriptTool)]
|
||||||
|
assert len(tools) == 1
|
||||||
|
|
||||||
|
tool = tools[0]
|
||||||
|
assert tool.name == "_123456"
|
||||||
|
|
||||||
|
|
||||||
async def test_selector_serializer(
|
async def test_selector_serializer(
|
||||||
hass: HomeAssistant, llm_context: llm.LLMContext
|
hass: HomeAssistant, llm_context: llm.LLMContext
|
||||||
) -> None:
|
) -> None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user