Simplify logbook duplicate handling (#32572)
This commit is contained in:
parent
a0787cd9be
commit
17215709e1
2 changed files with 39 additions and 64 deletions
|
@ -203,9 +203,6 @@ def humanify(hass, events):
|
||||||
"""
|
"""
|
||||||
domain_prefixes = tuple(f"{dom}." for dom in CONTINUOUS_DOMAINS)
|
domain_prefixes = tuple(f"{dom}." for dom in CONTINUOUS_DOMAINS)
|
||||||
|
|
||||||
# Track last states to filter out duplicates
|
|
||||||
last_state = {}
|
|
||||||
|
|
||||||
# Group events in batches of GROUP_BY_MINUTES
|
# Group events in batches of GROUP_BY_MINUTES
|
||||||
for _, g_events in groupby(
|
for _, g_events in groupby(
|
||||||
events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES
|
events, lambda event: event.time_fired.minute // GROUP_BY_MINUTES
|
||||||
|
@ -255,13 +252,6 @@ def humanify(hass, events):
|
||||||
if event.event_type == EVENT_STATE_CHANGED:
|
if event.event_type == EVENT_STATE_CHANGED:
|
||||||
to_state = State.from_dict(event.data.get("new_state"))
|
to_state = State.from_dict(event.data.get("new_state"))
|
||||||
|
|
||||||
# Filter out states that become same state again (force_update=True)
|
|
||||||
# or light becoming different color
|
|
||||||
if last_state.get(to_state.entity_id) == to_state.state:
|
|
||||||
continue
|
|
||||||
|
|
||||||
last_state[to_state.entity_id] = to_state.state
|
|
||||||
|
|
||||||
domain = to_state.domain
|
domain = to_state.domain
|
||||||
|
|
||||||
# Skip all but the last sensor state
|
# Skip all but the last sensor state
|
||||||
|
@ -468,25 +458,21 @@ def _keep_event(hass, event, entities_filter):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Do not report on new entities
|
# Do not report on new entities
|
||||||
if event.data.get("old_state") is None:
|
old_state = event.data.get("old_state")
|
||||||
|
if old_state is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
new_state = event.data.get("new_state")
|
|
||||||
|
|
||||||
# Do not report on entity removal
|
# Do not report on entity removal
|
||||||
if not new_state:
|
new_state = event.data.get("new_state")
|
||||||
|
if new_state is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
attributes = new_state.get("attributes", {})
|
# Do not report on only attribute changes
|
||||||
|
if new_state.get("state") == old_state.get("state"):
|
||||||
# If last_changed != last_updated only attributes have changed
|
|
||||||
# we do not report on that yet.
|
|
||||||
last_changed = new_state.get("last_changed")
|
|
||||||
last_updated = new_state.get("last_updated")
|
|
||||||
if last_changed != last_updated:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
domain = split_entity_id(entity_id)[0]
|
domain = split_entity_id(entity_id)[0]
|
||||||
|
attributes = new_state.get("attributes", {})
|
||||||
|
|
||||||
# Also filter auto groups.
|
# Also filter auto groups.
|
||||||
if domain == "group" and attributes.get("auto", False):
|
if domain == "group" and attributes.get("auto", False):
|
||||||
|
|
|
@ -568,14 +568,35 @@ class TestComponentLogbook(unittest.TestCase):
|
||||||
|
|
||||||
def test_exclude_attribute_changes(self):
|
def test_exclude_attribute_changes(self):
|
||||||
"""Test if events of attribute changes are filtered."""
|
"""Test if events of attribute changes are filtered."""
|
||||||
entity_id = "switch.bla"
|
|
||||||
entity_id2 = "switch.blu"
|
|
||||||
pointA = dt_util.utcnow()
|
pointA = dt_util.utcnow()
|
||||||
pointB = pointA + timedelta(minutes=1)
|
pointB = pointA + timedelta(minutes=1)
|
||||||
|
pointC = pointB + timedelta(minutes=1)
|
||||||
|
|
||||||
eventA = self.create_state_changed_event(pointA, entity_id, 10)
|
state_off = ha.State("light.kitchen", "off", {}, pointA, pointA).as_dict()
|
||||||
eventB = self.create_state_changed_event(
|
state_100 = ha.State(
|
||||||
pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB
|
"light.kitchen", "on", {"brightness": 100}, pointB, pointB
|
||||||
|
).as_dict()
|
||||||
|
state_200 = ha.State(
|
||||||
|
"light.kitchen", "on", {"brightness": 200}, pointB, pointC
|
||||||
|
).as_dict()
|
||||||
|
|
||||||
|
eventA = ha.Event(
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
{
|
||||||
|
"entity_id": "light.kitchen",
|
||||||
|
"old_state": state_off,
|
||||||
|
"new_state": state_100,
|
||||||
|
},
|
||||||
|
time_fired=pointB,
|
||||||
|
)
|
||||||
|
eventB = ha.Event(
|
||||||
|
EVENT_STATE_CHANGED,
|
||||||
|
{
|
||||||
|
"entity_id": "light.kitchen",
|
||||||
|
"old_state": state_100,
|
||||||
|
"new_state": state_200,
|
||||||
|
},
|
||||||
|
time_fired=pointC,
|
||||||
)
|
)
|
||||||
|
|
||||||
entities_filter = logbook._generate_filter_from_config({})
|
entities_filter = logbook._generate_filter_from_config({})
|
||||||
|
@ -588,7 +609,7 @@ class TestComponentLogbook(unittest.TestCase):
|
||||||
|
|
||||||
assert 1 == len(entries)
|
assert 1 == len(entries)
|
||||||
self.assert_entry(
|
self.assert_entry(
|
||||||
entries[0], pointA, "bla", domain="switch", entity_id=entity_id
|
entries[0], pointB, "kitchen", domain="light", entity_id="light.kitchen"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_home_assistant_start_stop_grouped(self):
|
def test_home_assistant_start_stop_grouped(self):
|
||||||
|
@ -1231,15 +1252,16 @@ class TestComponentLogbook(unittest.TestCase):
|
||||||
last_updated=None,
|
last_updated=None,
|
||||||
):
|
):
|
||||||
"""Create state changed event."""
|
"""Create state changed event."""
|
||||||
# Logbook only cares about state change events that
|
old_state = ha.State(
|
||||||
# contain an old state but will not actually act on it.
|
entity_id, "old", attributes, last_changed, last_updated
|
||||||
state = ha.State(
|
).as_dict()
|
||||||
|
new_state = ha.State(
|
||||||
entity_id, state, attributes, last_changed, last_updated
|
entity_id, state, attributes, last_changed, last_updated
|
||||||
).as_dict()
|
).as_dict()
|
||||||
|
|
||||||
return ha.Event(
|
return ha.Event(
|
||||||
EVENT_STATE_CHANGED,
|
EVENT_STATE_CHANGED,
|
||||||
{"entity_id": entity_id, "old_state": state, "new_state": state},
|
{"entity_id": entity_id, "old_state": old_state, "new_state": new_state},
|
||||||
time_fired=event_time_fired,
|
time_fired=event_time_fired,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1435,39 +1457,6 @@ async def test_humanify_script_started_event(hass):
|
||||||
assert event2["entity_id"] == "script.bye"
|
assert event2["entity_id"] == "script.bye"
|
||||||
|
|
||||||
|
|
||||||
async def test_humanify_same_state(hass):
|
|
||||||
"""Test humanifying Script Run event."""
|
|
||||||
state_50 = ha.State("light.kitchen", "on", {"brightness": 50}).as_dict()
|
|
||||||
state_100 = ha.State("light.kitchen", "on", {"brightness": 100}).as_dict()
|
|
||||||
state_200 = ha.State("light.kitchen", "on", {"brightness": 200}).as_dict()
|
|
||||||
|
|
||||||
events = list(
|
|
||||||
logbook.humanify(
|
|
||||||
hass,
|
|
||||||
[
|
|
||||||
ha.Event(
|
|
||||||
EVENT_STATE_CHANGED,
|
|
||||||
{
|
|
||||||
"entity_id": "light.kitchen",
|
|
||||||
"old_state": state_50,
|
|
||||||
"new_state": state_100,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ha.Event(
|
|
||||||
EVENT_STATE_CHANGED,
|
|
||||||
{
|
|
||||||
"entity_id": "light.kitchen",
|
|
||||||
"old_state": state_100,
|
|
||||||
"new_state": state_200,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(events) == 1
|
|
||||||
|
|
||||||
|
|
||||||
async def test_logbook_describe_event(hass, hass_client):
|
async def test_logbook_describe_event(hass, hass_client):
|
||||||
"""Test teaching logbook about a new event."""
|
"""Test teaching logbook about a new event."""
|
||||||
await hass.async_add_executor_job(init_recorder_component, hass)
|
await hass.async_add_executor_job(init_recorder_component, hass)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue