This commit is contained in:
Franck Nijhof 2024-07-30 11:12:45 +02:00 committed by GitHub
commit 17930a6d66
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 1313 additions and 47 deletions

View File

@ -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,

View File

@ -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"]
} }

View File

@ -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

View File

@ -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."]
} }

View File

@ -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,

View File

@ -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"
] ]

View File

@ -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)

View File

@ -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 (

View File

@ -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)
) )

View File

@ -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:

View File

@ -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)

View File

@ -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"]
} }

View File

@ -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:

View File

@ -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"]
} }

View File

@ -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,

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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"
} }
} }
} }

View File

@ -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,
} }
) )

View File

@ -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"
}, },
{ {

View File

@ -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"]

View File

@ -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

View File

@ -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."""

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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: