mirror of
https://github.com/home-assistant/core.git
synced 2025-12-20 02:48:57 +00:00
Fix Matter epoch timestamp sensors (#157600)
This commit is contained in:
@@ -183,10 +183,35 @@ PUMP_CONTROL_MODE_MAP = {
|
||||
clusters.PumpConfigurationAndControl.Enums.ControlModeEnum.kUnknownEnumValue: None,
|
||||
}
|
||||
|
||||
MATTER_2000_TO_UNIX_EPOCH_OFFSET = (
|
||||
946684800 # Seconds from Matter 2000 epoch to Unix epoch
|
||||
)
|
||||
HUMIDITY_SCALING_FACTOR = 100
|
||||
TEMPERATURE_SCALING_FACTOR = 100
|
||||
|
||||
|
||||
def matter_epoch_seconds_to_utc(x: int | None) -> datetime | None:
|
||||
"""Convert Matter epoch seconds (since 2000-01-01) to UTC datetime.
|
||||
|
||||
Returns None for non-positive or None values (represents unknown/absent).
|
||||
"""
|
||||
if x is None or x <= 0:
|
||||
return None
|
||||
return dt_util.utc_from_timestamp(x + MATTER_2000_TO_UNIX_EPOCH_OFFSET)
|
||||
|
||||
|
||||
def matter_epoch_microseconds_to_utc(x: int | None) -> datetime | None:
|
||||
"""Convert Matter epoch microseconds (since 2000-01-01) to UTC datetime.
|
||||
|
||||
The value is in microseconds; convert to seconds before applying offset.
|
||||
Returns None for non-positive or None values.
|
||||
"""
|
||||
if x is None or x <= 0:
|
||||
return None
|
||||
seconds = x // 1_000_000
|
||||
return dt_util.utc_from_timestamp(seconds + MATTER_2000_TO_UNIX_EPOCH_OFFSET)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
@@ -1468,7 +1493,8 @@ DISCOVERY_SCHEMAS = [
|
||||
translation_key="auto_close_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
state_class=None,
|
||||
device_to_ha=(lambda x: dt_util.utc_from_timestamp(x) if x > 0 else None),
|
||||
# AutoCloseTime is defined as epoch-us in the spec
|
||||
device_to_ha=matter_epoch_microseconds_to_utc,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
featuremap_contains=clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync,
|
||||
@@ -1483,7 +1509,8 @@ DISCOVERY_SCHEMAS = [
|
||||
translation_key="estimated_end_time",
|
||||
device_class=SensorDeviceClass.TIMESTAMP,
|
||||
state_class=None,
|
||||
device_to_ha=(lambda x: dt_util.utc_from_timestamp(x) if x > 0 else None),
|
||||
# EstimatedEndTime is defined as epoch-s (Matter 2000 epoch) in the spec
|
||||
device_to_ha=matter_epoch_seconds_to_utc,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.ServiceArea.Attributes.EstimatedEndTime,),
|
||||
|
||||
@@ -357,7 +357,7 @@
|
||||
],
|
||||
"1/336/2": [],
|
||||
"1/336/3": 7,
|
||||
"1/336/4": 1756501200,
|
||||
"1/336/4": 809816400,
|
||||
"1/336/5": [],
|
||||
"1/336/65532": 6,
|
||||
"1/336/65533": 1,
|
||||
|
||||
@@ -239,7 +239,7 @@
|
||||
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
|
||||
"1/129/0": 0,
|
||||
"1/129/1": 0,
|
||||
"1/129/2": 0,
|
||||
"1/129/2": 789004800000000,
|
||||
"1/129/3": null,
|
||||
"1/129/4": 0,
|
||||
"1/129/5": 0,
|
||||
@@ -248,7 +248,7 @@
|
||||
"1/129/8": 100,
|
||||
"1/129/9": 0,
|
||||
"1/129/10": 0,
|
||||
"1/129/65532": 0,
|
||||
"1/129/65532": 1,
|
||||
"1/129/65533": 1,
|
||||
"1/129/65528": [],
|
||||
"1/129/65529": [0, 1],
|
||||
|
||||
@@ -11147,6 +11147,55 @@
|
||||
'state': 'stopped',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[valve][sensor.valve_auto_close_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.valve_auto_close_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Auto-close time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'auto_close_time',
|
||||
'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-ValveConfigurationAndControlAutoCloseTime-129-2',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[valve][sensor.valve_auto_close_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Valve Auto-close time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.valve_auto_close_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '2025-01-01T00:00:00+00:00',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[window_covering_full][sensor.mock_full_window_covering_target_opening_position-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -642,7 +642,7 @@ async def test_vacuum_actions(
|
||||
assert state
|
||||
assert state.state == "2025-08-29T21:00:00+00:00"
|
||||
|
||||
set_node_attribute(matter_node, 1, 336, 4, 1756502000)
|
||||
set_node_attribute(matter_node, 1, 336, 4, 809817200)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.mock_vacuum_estimated_end_time")
|
||||
@@ -732,3 +732,35 @@ async def test_optional_door_event_sensors_from_featuremap(
|
||||
state = hass.states.get(entity_id_closed)
|
||||
assert state
|
||||
assert state.state == "8"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["valve"])
|
||||
async def test_valve(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test valve AutoCloseTime sensor with Matter epoch microseconds conversion."""
|
||||
# ValveConfigurationAndControl Cluster / AutoCloseTime attribute (1/129/2)
|
||||
# Initial value is 789004800000000 microseconds = 2025-01-01 00:00:00 UTC
|
||||
state = hass.states.get("sensor.valve_auto_close_time")
|
||||
assert state
|
||||
assert state.state == "2025-01-01T00:00:00+00:00"
|
||||
|
||||
# Set to another timestamp: 820540800000000 microseconds
|
||||
# = 820540800 seconds since 2000-01-01 = 1767225600 Unix epoch
|
||||
# = 2026-01-01 00:00:00 UTC
|
||||
set_node_attribute(matter_node, 1, 129, 2, 820540800000000)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.valve_auto_close_time")
|
||||
assert state
|
||||
assert state.state == "2026-01-01T00:00:00+00:00"
|
||||
|
||||
# Test setting to 0 (invalid/null) - should result in unknown state
|
||||
set_node_attribute(matter_node, 1, 129, 2, 0)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.valve_auto_close_time")
|
||||
assert state
|
||||
assert state.state == "unknown"
|
||||
|
||||
Reference in New Issue
Block a user