Avoid additional timestamp conversion to set state (#118885)

Avoid addtional timestamp conversion to set state

Since we already have the timestamp, we can pass it on to the State
object and avoid the additional timestamp conversion which can be as
much as 30% of the state write runtime.

Since datetime objects are limited to microsecond precision, we need
to adjust some tests to account for the additional precision that we
will now be able to get in the database
This commit is contained in:
J. Nick Koston 2024-06-05 22:43:34 -05:00 committed by GitHub
parent 475c20d529
commit f9205cd88d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 332 additions and 143 deletions

View file

@ -1762,6 +1762,7 @@ class State:
context: Context | None = None,
validate_entity_id: bool | None = True,
state_info: StateInfo | None = None,
last_updated_timestamp: float | None = None,
) -> None:
"""Initialize a new state."""
state = str(state)
@ -1793,6 +1794,7 @@ class State:
# so we will set the timestamp values here to avoid the overhead of
# the function call in the property we know will always be called.
last_updated = self.last_updated
if not last_updated_timestamp:
last_updated_timestamp = last_updated.timestamp()
self.last_updated_timestamp = last_updated_timestamp
if self.last_changed == last_updated:
@ -2309,6 +2311,7 @@ class StateMachine:
context,
old_state is None,
state_info,
timestamp,
)
if old_state is not None:
old_state.expire()

View file

@ -466,16 +466,24 @@ async def test_history_stream_historical_only(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "changed"})
sensor_three_last_updated = hass.states.get("sensor.three").last_updated
sensor_three_last_updated_timestamp = hass.states.get(
"sensor.three"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.four", "off", attributes={"any": "again"})
sensor_four_last_updated = hass.states.get("sensor.four").last_updated
sensor_four_last_updated_timestamp = hass.states.get(
"sensor.four"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -506,17 +514,27 @@ async def test_history_stream_historical_only(
assert response == {
"event": {
"end_time": sensor_four_last_updated.timestamp(),
"start_time": now.timestamp(),
"end_time": pytest.approx(sensor_four_last_updated_timestamp),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.four": [
{"lu": sensor_four_last_updated.timestamp(), "s": "off"}
{
"lu": pytest.approx(sensor_four_last_updated_timestamp),
"s": "off",
}
],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.three": [
{"lu": sensor_three_last_updated.timestamp(), "s": "off"}
{
"lu": pytest.approx(sensor_three_last_updated_timestamp),
"s": "off",
}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
},
},
"id": 1,
@ -817,10 +835,14 @@ async def test_history_stream_live_no_attributes_minimal_response(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -846,15 +868,19 @@ async def test_history_stream_live_no_attributes_minimal_response(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
},
},
"id": 1,
@ -866,14 +892,22 @@ async def test_history_stream_live_no_attributes_minimal_response(
hass.states.async_set("sensor.two", "two", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
response = await client.receive_json()
assert response == {
"event": {
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"}
],
},
},
"id": 1,
@ -894,10 +928,14 @@ async def test_history_stream_live(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -923,24 +961,24 @@ async def test_history_stream_live(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [
{
"a": {"any": "attr"},
"lu": sensor_one_last_updated.timestamp(),
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
"sensor.two": [
{
"a": {"any": "attr"},
"lu": sensor_two_last_updated.timestamp(),
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "off",
}
],
@ -955,24 +993,30 @@ async def test_history_stream_live(
hass.states.async_set("sensor.two", "two", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_changed = hass.states.get("sensor.one").last_changed
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_one_last_changed_timestamp = hass.states.get(
"sensor.one"
).last_changed_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
response = await client.receive_json()
assert response == {
"event": {
"states": {
"sensor.one": [
{
"lc": sensor_one_last_changed.timestamp(),
"lu": sensor_one_last_updated.timestamp(),
"lc": pytest.approx(sensor_one_last_changed_timestamp),
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
"a": {"diff": "attr"},
}
],
"sensor.two": [
{
"lu": sensor_two_last_updated.timestamp(),
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "two",
"a": {"any": "attr"},
}
@ -997,10 +1041,14 @@ async def test_history_stream_live_minimal_response(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1026,24 +1074,24 @@ async def test_history_stream_live_minimal_response(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"end_time": pytest.approx(first_end_time),
"start_time": now.timestamp(),
"states": {
"sensor.one": [
{
"a": {"any": "attr"},
"lu": sensor_one_last_updated.timestamp(),
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
"sensor.two": [
{
"a": {"any": "attr"},
"lu": sensor_two_last_updated.timestamp(),
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "off",
}
],
@ -1057,8 +1105,12 @@ async def test_history_stream_live_minimal_response(
hass.states.async_set("sensor.one", "on", attributes={"diff": "attr"})
hass.states.async_set("sensor.two", "two", attributes={"any": "attr"})
# Only sensor.two has changed
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
hass.states.async_remove("sensor.one")
hass.states.async_remove("sensor.two")
await async_recorder_block_till_done(hass)
@ -1069,7 +1121,7 @@ async def test_history_stream_live_minimal_response(
"states": {
"sensor.two": [
{
"lu": sensor_two_last_updated.timestamp(),
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "two",
"a": {"any": "attr"},
}
@ -1094,10 +1146,14 @@ async def test_history_stream_live_no_attributes(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1123,18 +1179,26 @@ async def test_history_stream_live_no_attributes(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [
{"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"}
{
"a": {},
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
"sensor.two": [
{"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"}
{
"a": {},
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "off",
}
],
},
},
@ -1147,14 +1211,22 @@ async def test_history_stream_live_no_attributes(
hass.states.async_set("sensor.two", "two", attributes={"diff": "attr"})
await async_recorder_block_till_done(hass)
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
response = await client.receive_json()
assert response == {
"event": {
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"}
],
},
},
"id": 1,
@ -1176,10 +1248,14 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1205,15 +1281,19 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
},
},
"id": 1,
@ -1225,14 +1305,22 @@ async def test_history_stream_live_no_attributes_minimal_response_specific_entit
hass.states.async_set("sensor.two", "two", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
response = await client.receive_json()
assert response == {
"event": {
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"}
],
},
},
"id": 1,
@ -1254,10 +1342,14 @@ async def test_history_stream_live_with_future_end_time(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1287,15 +1379,19 @@ async def test_history_stream_live_with_future_end_time(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
},
},
"id": 1,
@ -1307,14 +1403,22 @@ async def test_history_stream_live_with_future_end_time(
hass.states.async_set("sensor.two", "two", attributes={"any": "attr"})
await async_recorder_block_till_done(hass)
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
response = await client.receive_json()
assert response == {
"event": {
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "one"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "two"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "one"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "two"}
],
},
},
"id": 1,
@ -1450,10 +1554,14 @@ async def test_overflow_queue(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1481,18 +1589,24 @@ async def test_overflow_queue(
assert response["type"] == "result"
response = await client.receive_json()
first_end_time = sensor_two_last_updated.timestamp()
first_end_time = sensor_two_last_updated_timestamp
assert response == {
"event": {
"end_time": first_end_time,
"start_time": now.timestamp(),
"end_time": pytest.approx(first_end_time),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [
{"lu": sensor_one_last_updated.timestamp(), "s": "on"}
{
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
"sensor.two": [
{"lu": sensor_two_last_updated.timestamp(), "s": "off"}
{
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "off",
}
],
},
},
@ -1522,10 +1636,14 @@ async def test_history_during_period_for_invalid_entity_ids(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "again"})
await async_recorder_block_till_done(hass)
@ -1550,7 +1668,11 @@ async def test_history_during_period_for_invalid_entity_ids(
assert response == {
"result": {
"sensor.one": [
{"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"}
{
"a": {},
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
},
"id": 1,
@ -1574,10 +1696,18 @@ async def test_history_during_period_for_invalid_entity_ids(
assert response == {
"result": {
"sensor.one": [
{"a": {}, "lu": sensor_one_last_updated.timestamp(), "s": "on"}
{
"a": {},
"lu": pytest.approx(sensor_one_last_updated_timestamp),
"s": "on",
}
],
"sensor.two": [
{"a": {}, "lu": sensor_two_last_updated.timestamp(), "s": "off"}
{
"a": {},
"lu": pytest.approx(sensor_two_last_updated_timestamp),
"s": "off",
}
],
},
"id": 2,
@ -1670,10 +1800,14 @@ async def test_history_stream_for_invalid_entity_ids(
await async_setup_component(hass, "sensor", {})
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "on", attributes={"any": "attr"})
sensor_one_last_updated = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "again"})
await async_recorder_block_till_done(hass)
@ -1703,10 +1837,12 @@ async def test_history_stream_for_invalid_entity_ids(
response = await client.receive_json()
assert response == {
"event": {
"end_time": sensor_one_last_updated.timestamp(),
"start_time": now.timestamp(),
"end_time": pytest.approx(sensor_one_last_updated_timestamp),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
},
},
"id": 1,
@ -1733,11 +1869,15 @@ async def test_history_stream_for_invalid_entity_ids(
response = await client.receive_json()
assert response == {
"event": {
"end_time": sensor_two_last_updated.timestamp(),
"start_time": now.timestamp(),
"end_time": pytest.approx(sensor_two_last_updated_timestamp),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.one": [{"lu": sensor_one_last_updated.timestamp(), "s": "on"}],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
"sensor.one": [
{"lu": pytest.approx(sensor_one_last_updated_timestamp), "s": "on"}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
},
},
"id": 2,
@ -1841,21 +1981,31 @@ async def test_history_stream_historical_only_with_start_time_state_past(
now = dt_util.utcnow()
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.one", "second", attributes={"any": "attr"})
sensor_one_last_updated_second = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_second_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await asyncio.sleep(0.00001)
hass.states.async_set("sensor.one", "third", attributes={"any": "attr"})
sensor_one_last_updated_third = hass.states.get("sensor.one").last_updated
sensor_one_last_updated_third_timestamp = hass.states.get(
"sensor.one"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.two", "off", attributes={"any": "attr"})
sensor_two_last_updated = hass.states.get("sensor.two").last_updated
sensor_two_last_updated_timestamp = hass.states.get(
"sensor.two"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.three", "off", attributes={"any": "changed"})
sensor_three_last_updated = hass.states.get("sensor.three").last_updated
sensor_three_last_updated_timestamp = hass.states.get(
"sensor.three"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("sensor.four", "off", attributes={"any": "again"})
sensor_four_last_updated = hass.states.get("sensor.four").last_updated
sensor_four_last_updated_timestamp = hass.states.get(
"sensor.four"
).last_updated_timestamp
await async_recorder_block_till_done(hass)
hass.states.async_set("switch.excluded", "off", attributes={"any": "again"})
await async_wait_recording_done(hass)
@ -1885,24 +2035,38 @@ async def test_history_stream_historical_only_with_start_time_state_past(
assert response == {
"event": {
"end_time": sensor_four_last_updated.timestamp(),
"start_time": now.timestamp(),
"end_time": pytest.approx(sensor_four_last_updated_timestamp),
"start_time": pytest.approx(now.timestamp()),
"states": {
"sensor.four": [
{"lu": sensor_four_last_updated.timestamp(), "s": "off"}
{
"lu": pytest.approx(sensor_four_last_updated_timestamp),
"s": "off",
}
],
"sensor.one": [
{
"lu": now.timestamp(),
"lu": pytest.approx(now.timestamp()),
"s": "first",
}, # should use start time state
{"lu": sensor_one_last_updated_second.timestamp(), "s": "second"},
{"lu": sensor_one_last_updated_third.timestamp(), "s": "third"},
{
"lu": pytest.approx(sensor_one_last_updated_second_timestamp),
"s": "second",
},
{
"lu": pytest.approx(sensor_one_last_updated_third_timestamp),
"s": "third",
},
],
"sensor.three": [
{"lu": sensor_three_last_updated.timestamp(), "s": "off"}
{
"lu": pytest.approx(sensor_three_last_updated_timestamp),
"s": "off",
}
],
"sensor.two": [
{"lu": pytest.approx(sensor_two_last_updated_timestamp), "s": "off"}
],
"sensor.two": [{"lu": sensor_two_last_updated.timestamp(), "s": "off"}],
},
},
"id": 1,

View file

@ -630,7 +630,7 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
assert msg["event"]["start_time"] == now.timestamp()
@ -679,17 +679,17 @@ async def test_subscribe_unsubscribe_logbook_stream_excluded_entities(
{
"entity_id": "light.alpha",
"state": "off",
"when": alpha_off_state.last_updated.timestamp(),
"when": alpha_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "off",
"when": zulu_off_state.last_updated.timestamp(),
"when": zulu_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "on",
"when": zulu_on_state.last_updated.timestamp(),
"when": zulu_on_state.last_updated_timestamp,
},
]
@ -1033,7 +1033,7 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
assert msg["event"]["start_time"] == now.timestamp()
@ -1082,17 +1082,17 @@ async def test_logbook_stream_excluded_entities_inherits_filters_from_recorder(
{
"entity_id": "light.alpha",
"state": "off",
"when": alpha_off_state.last_updated.timestamp(),
"when": alpha_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "off",
"when": zulu_off_state.last_updated.timestamp(),
"when": zulu_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "on",
"when": zulu_on_state.last_updated.timestamp(),
"when": zulu_on_state.last_updated_timestamp,
},
]
@ -1201,7 +1201,7 @@ async def test_subscribe_unsubscribe_logbook_stream(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
assert msg["event"]["start_time"] == now.timestamp()
@ -1241,17 +1241,17 @@ async def test_subscribe_unsubscribe_logbook_stream(
{
"entity_id": "light.alpha",
"state": "off",
"when": alpha_off_state.last_updated.timestamp(),
"when": alpha_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "off",
"when": zulu_off_state.last_updated.timestamp(),
"when": zulu_off_state.last_updated_timestamp,
},
{
"entity_id": "light.zulu",
"state": "on",
"when": zulu_on_state.last_updated.timestamp(),
"when": zulu_on_state.last_updated_timestamp,
},
]
@ -1514,7 +1514,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
@ -1613,7 +1613,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_with_end_time(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
@ -1716,7 +1716,7 @@ async def test_subscribe_unsubscribe_logbook_stream_entities_past_only(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
@ -1804,7 +1804,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query(
{
"entity_id": "binary_sensor.is_light",
"state": "on",
"when": current_state.last_updated.timestamp(),
"when": current_state.last_updated_timestamp,
}
]
@ -1817,7 +1817,7 @@ async def test_subscribe_unsubscribe_logbook_stream_big_query(
{
"entity_id": "binary_sensor.four_days_ago",
"state": "off",
"when": four_day_old_state.last_updated.timestamp(),
"when": four_day_old_state.last_updated_timestamp,
}
]
@ -2363,7 +2363,7 @@ async def test_subscribe_disconnected(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
@ -2790,7 +2790,7 @@ async def test_logbook_stream_ignores_forced_updates(
{
"entity_id": "binary_sensor.is_light",
"state": "off",
"when": state.last_updated.timestamp(),
"when": state.last_updated_timestamp,
}
]
assert msg["event"]["start_time"] == now.timestamp()

View file

@ -96,9 +96,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
message = _state_diff_event(last_state_event)
assert message == {
"c": {
"light.window": {
"+": {"lc": new_state.last_changed.timestamp(), "s": "off"}
}
"light.window": {"+": {"lc": new_state.last_changed_timestamp, "s": "off"}}
}
}
@ -117,7 +115,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"light.window": {
"+": {
"c": {"parent_id": "new-parent-id"},
"lc": new_state.last_changed.timestamp(),
"lc": new_state.last_changed_timestamp,
"s": "red",
}
}
@ -144,7 +142,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"parent_id": "another-new-parent-id",
"user_id": "new-user-id",
},
"lc": new_state.last_changed.timestamp(),
"lc": new_state.last_changed_timestamp,
"s": "green",
}
}
@ -168,7 +166,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"light.window": {
"+": {
"c": {"user_id": "another-new-user-id"},
"lc": new_state.last_changed.timestamp(),
"lc": new_state.last_changed_timestamp,
"s": "blue",
}
}
@ -194,7 +192,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"light.window": {
"+": {
"c": "id-new",
"lc": new_state.last_changed.timestamp(),
"lc": new_state.last_changed_timestamp,
"s": "yellow",
}
}
@ -216,7 +214,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"+": {
"a": {"new": "attr"},
"c": {"id": new_context.id, "parent_id": None, "user_id": None},
"lc": new_state.last_changed.timestamp(),
"lc": new_state.last_changed_timestamp,
"s": "purple",
}
}
@ -232,7 +230,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
assert message == {
"c": {
"light.window": {
"+": {"lc": new_state.last_changed.timestamp(), "s": "green"},
"+": {"lc": new_state.last_changed_timestamp, "s": "green"},
"-": {"a": ["new"]},
}
}
@ -254,7 +252,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"light.window": {
"+": {
"a": {"list_attr": ["a", "b", "c", "d"], "list_attr_2": ["a", "b"]},
"lu": new_state.last_updated.timestamp(),
"lu": new_state.last_updated_timestamp,
}
}
}
@ -275,7 +273,7 @@ async def test_state_diff_event(hass: HomeAssistant) -> None:
"light.window": {
"+": {
"a": {"list_attr": ["a", "b", "c", "e"]},
"lu": new_state.last_updated.timestamp(),
"lu": new_state.last_updated_timestamp,
},
"-": {"a": ["list_attr_2"]},
}

View file

@ -2834,8 +2834,32 @@ async def test_state_change_events_context_id_match_state_time(
assert state.last_updated == events[0].time_fired
assert len(state.context.id) == 26
# ULIDs store time to 3 decimal places compared to python timestamps
assert _ulid_timestamp(state.context.id) == int(
state.last_updated.timestamp() * 1000
assert _ulid_timestamp(state.context.id) == int(state.last_updated_timestamp * 1000)
async def test_state_change_events_match_time_with_limits_of_precision(
hass: HomeAssistant,
) -> None:
"""Ensure last_updated matches last_updated_timestamp within limits of precision.
The last_updated_timestamp uses the same precision as time.time() which is
a bit better than the precision of datetime.now() which is used for last_updated
on some platforms.
"""
events = async_capture_events(hass, ha.EVENT_STATE_CHANGED)
hass.states.async_set("light.bedroom", "on")
await hass.async_block_till_done()
state: State = hass.states.get("light.bedroom")
assert state.last_updated == events[0].time_fired
assert state.last_updated_timestamp == pytest.approx(
events[0].time_fired.timestamp()
)
assert state.last_updated_timestamp == pytest.approx(state.last_updated.timestamp())
assert state.last_updated_timestamp == state.last_changed_timestamp
assert state.last_updated_timestamp == pytest.approx(state.last_changed.timestamp())
assert state.last_updated_timestamp == state.last_reported_timestamp
assert state.last_updated_timestamp == pytest.approx(
state.last_reported.timestamp()
)