mirror of
https://github.com/home-assistant/core.git
synced 2026-03-22 02:35:12 +01:00
Compare commits
3 Commits
habluetoot
...
matter_syn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0063dc81d3 | ||
|
|
7463bb79dd | ||
|
|
d17b681477 |
@@ -4,9 +4,11 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Types import NullValue
|
||||
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
@@ -17,6 +19,7 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .entity import MatterEntity, MatterEntityDescription
|
||||
from .helpers import get_matter
|
||||
@@ -52,6 +55,67 @@ class MatterCommandButton(MatterEntity, ButtonEntity):
|
||||
await self.send_device_command(self.entity_description.command())
|
||||
|
||||
|
||||
# CHIP epoch: 2000-01-01 00:00:00 UTC
|
||||
CHIP_EPOCH = datetime(2000, 1, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
class MatterTimeSyncButton(MatterEntity, ButtonEntity):
|
||||
"""Button to synchronize time to a Matter device."""
|
||||
|
||||
entity_description: MatterButtonEntityDescription
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Sync Home Assistant time to the Matter device."""
|
||||
now = dt_util.utcnow()
|
||||
tz = dt_util.get_default_time_zone()
|
||||
delta = now - CHIP_EPOCH
|
||||
utc_us = (
|
||||
(delta.days * 86400 * 1_000_000)
|
||||
+ (delta.seconds * 1_000_000)
|
||||
+ delta.microseconds
|
||||
)
|
||||
|
||||
# Compute timezone and DST offsets
|
||||
local_now = now.astimezone(tz)
|
||||
utc_offset_delta = local_now.utcoffset()
|
||||
utc_offset = int(utc_offset_delta.total_seconds()) if utc_offset_delta else 0
|
||||
dst_offset_delta = local_now.dst()
|
||||
dst_offset = int(dst_offset_delta.total_seconds()) if dst_offset_delta else 0
|
||||
standard_offset = utc_offset - dst_offset
|
||||
|
||||
# 1. Set timezone
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetTimeZone(
|
||||
timeZone=[
|
||||
clusters.TimeSynchronization.Structs.TimeZoneStruct(
|
||||
offset=standard_offset, validAt=0, name=str(tz)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# 2. Set DST offset
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetDSTOffset(
|
||||
DSTOffset=[
|
||||
clusters.TimeSynchronization.Structs.DSTOffsetStruct(
|
||||
offset=dst_offset,
|
||||
validStarting=0,
|
||||
validUntil=NullValue,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Set UTC time
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetUTCTime(
|
||||
UTCTime=utc_us,
|
||||
granularity=clusters.TimeSynchronization.Enums.GranularityEnum.kMicrosecondsGranularity,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Discovery schema(s) to map Matter Attributes to HA entities
|
||||
DISCOVERY_SCHEMAS = [
|
||||
MatterDiscoverySchema(
|
||||
@@ -169,4 +233,16 @@ DISCOVERY_SCHEMAS = [
|
||||
value_contains=clusters.WaterHeaterManagement.Commands.CancelBoost.command_id,
|
||||
allow_multi=True, # Also used in water_heater
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BUTTON,
|
||||
entity_description=MatterButtonEntityDescription(
|
||||
key="TimeSynchronizationSyncTimeButton",
|
||||
translation_key="sync_time",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
entity_class=MatterTimeSyncButton,
|
||||
required_attributes=(clusters.TimeSynchronization.Attributes.UTCTime,),
|
||||
allow_multi=True,
|
||||
allow_none_value=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
},
|
||||
"stop": {
|
||||
"default": "mdi:stop"
|
||||
},
|
||||
"sync_time": {
|
||||
"default": "mdi:clock-check-outline"
|
||||
}
|
||||
},
|
||||
"fan": {
|
||||
|
||||
@@ -141,6 +141,9 @@
|
||||
},
|
||||
"stop": {
|
||||
"name": "[%key:common::action::stop%]"
|
||||
},
|
||||
"sync_time": {
|
||||
"name": "Sync time"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
|
||||
@@ -1325,6 +1325,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_shutter][button.eve_shutter_switch_20eci1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_shutter_switch_20eci1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000094-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_shutter][button.eve_shutter_switch_20eci1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Shutter Switch 20ECI1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_shutter_switch_20eci1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1376,6 +1426,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_thermo_20ebp1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Thermo 20EBP1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_thermo_20ebp1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1427,6 +1527,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_thermo_20ecd1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Thermo 20ECD1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_thermo_20ecd1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_weather_sensor][button.eve_weather_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1935,6 +2085,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[ikea_air_quality_monitor][button.alpstuga_air_quality_monitor_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.alpstuga_air_quality_monitor_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[ikea_air_quality_monitor][button.alpstuga_air_quality_monitor_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'ALPSTUGA air quality monitor Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.alpstuga_air_quality_monitor_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm30][button.white_series_onoff_switch_identify_load_control-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -2845,6 +3045,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_leak_sensor][button.water_leak_detector_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.water_leak_detector_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000020-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_leak_sensor][button.water_leak_detector_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Water Leak Detector Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.water_leak_detector_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_lock][button.mock_lock_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -4211,6 +4461,56 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_light_switch][button.light_switch_example_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.light_switch_example_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-000000000000008E-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_light_switch][button.light_switch_example_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Light switch example Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.light_switch_example_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_refrigerator][button.refrigerator_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""Test Matter switches."""
|
||||
"""Test Matter buttons."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Types import NullValue
|
||||
from matter_server.client.models.node import MatterNode
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
@@ -10,6 +12,7 @@ from syrupy.assertion import SnapshotAssertion
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .common import snapshot_matter_entities
|
||||
|
||||
@@ -107,3 +110,82 @@ async def test_smoke_detector_self_test(
|
||||
endpoint_id=1,
|
||||
command=clusters.SmokeCoAlarm.Commands.SelfTestRequest(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-06-15T12:00:00+00:00")
|
||||
@pytest.mark.parametrize("node_fixture", ["ikea_air_quality_monitor"])
|
||||
async def test_time_sync_button(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test button entity is created for a Matter TimeSynchronization Cluster."""
|
||||
entity_id = "button.alpstuga_air_quality_monitor_sync_time"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.attributes["friendly_name"] == "ALPSTUGA air quality monitor Sync time"
|
||||
# test press action
|
||||
await hass.services.async_call(
|
||||
"button",
|
||||
"press",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert matter_client.send_device_command.call_count == 3
|
||||
|
||||
# Compute expected values based on HA's configured timezone
|
||||
chip_epoch = datetime(2000, 1, 1, tzinfo=UTC)
|
||||
frozen_now = datetime(2025, 6, 15, 12, 0, 0, tzinfo=UTC)
|
||||
delta = frozen_now - chip_epoch
|
||||
expected_utc_us = (
|
||||
(delta.days * 86400 * 1_000_000)
|
||||
+ (delta.seconds * 1_000_000)
|
||||
+ delta.microseconds
|
||||
)
|
||||
ha_tz = dt_util.get_default_time_zone()
|
||||
local_now = frozen_now.astimezone(ha_tz)
|
||||
utc_offset_delta = local_now.utcoffset()
|
||||
utc_offset = int(utc_offset_delta.total_seconds()) if utc_offset_delta else 0
|
||||
dst_offset_delta = local_now.dst()
|
||||
dst_offset = int(dst_offset_delta.total_seconds()) if dst_offset_delta else 0
|
||||
standard_offset = utc_offset - dst_offset
|
||||
|
||||
# Verify SetTimeZone command
|
||||
assert matter_client.send_device_command.call_args_list[0] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetTimeZone(
|
||||
timeZone=[
|
||||
clusters.TimeSynchronization.Structs.TimeZoneStruct(
|
||||
offset=standard_offset,
|
||||
validAt=0,
|
||||
name=str(ha_tz),
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
# Verify SetDSTOffset command
|
||||
assert matter_client.send_device_command.call_args_list[1] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetDSTOffset(
|
||||
DSTOffset=[
|
||||
clusters.TimeSynchronization.Structs.DSTOffsetStruct(
|
||||
offset=dst_offset,
|
||||
validStarting=0,
|
||||
validUntil=NullValue,
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
# Verify SetUTCTime command
|
||||
assert matter_client.send_device_command.call_args_list[2] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetUTCTime(
|
||||
UTCTime=expected_utc_us,
|
||||
granularity=clusters.TimeSynchronization.Enums.GranularityEnum.kMicrosecondsGranularity,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user