diff --git a/frigate/events/maintainer.py b/frigate/events/maintainer.py
index f2989c065..40057958c 100644
--- a/frigate/events/maintainer.py
+++ b/frigate/events/maintainer.py
@@ -37,6 +37,14 @@ def should_update_db(prev_event: Event, current_event: Event) -> bool:
return False
+def should_update_state(prev_event: Event, current_event: Event) -> bool:
+ """If current event should update state, but not necessarily update the db."""
+ if prev_event["stationary"] != current_event["stationary"]:
+ return True
+
+ return False
+
+
class EventProcessor(threading.Thread):
def __init__(
self,
@@ -107,8 +115,11 @@ class EventProcessor(threading.Thread):
event_data: Event,
) -> None:
"""handle tracked object event updates."""
+ updated_db = False
+
# if this is the first message, just store it and continue, its not time to insert it in the db
if should_update_db(self.events_in_process[event_data["id"]], event_data):
+ updated_db = True
camera_config = self.config.cameras[camera]
event_config: EventsConfig = camera_config.record.events
width = camera_config.detect.width
@@ -210,6 +221,10 @@ class EventProcessor(threading.Thread):
.execute()
)
+ # check if the stored event_data should be updated
+ if updated_db or should_update_state(
+ self.events_in_process[event_data["id"]], event_data
+ ):
# update the stored copy for comparison on future update messages
self.events_in_process[event_data["id"]] = event_data
diff --git a/frigate/timeline.py b/frigate/timeline.py
index 861dbe411..73c0a61b4 100644
--- a/frigate/timeline.py
+++ b/frigate/timeline.py
@@ -79,14 +79,20 @@ class TimelineProcessor(threading.Thread):
if event_type == "start":
timeline_entry[Timeline.class_type] = "visible"
Timeline.insert(timeline_entry).execute()
- elif (
- event_type == "update"
- and prev_event_data["current_zones"] != event_data["current_zones"]
- and len(event_data["current_zones"]) > 0
- ):
- timeline_entry[Timeline.class_type] = "entered_zone"
- timeline_entry[Timeline.data]["zones"] = event_data["current_zones"]
- Timeline.insert(timeline_entry).execute()
+ elif event_type == "update":
+ # zones have been updated
+ if (
+ prev_event_data["current_zones"] != event_data["current_zones"]
+ and len(event_data["current_zones"]) > 0
+ ):
+ timeline_entry[Timeline.class_type] = "entered_zone"
+ timeline_entry[Timeline.data]["zones"] = event_data["current_zones"]
+ Timeline.insert(timeline_entry).execute()
+ elif prev_event_data["stationary"] != event_data["stationary"]:
+ timeline_entry[Timeline.class_type] = (
+ "stationary" if event_data["stationary"] else "active"
+ )
+ Timeline.insert(timeline_entry).execute()
elif event_type == "end":
timeline_entry[Timeline.class_type] = "gone"
Timeline.insert(timeline_entry).execute()
diff --git a/web/src/components/TimelineSummary.jsx b/web/src/components/TimelineSummary.jsx
index 8bf5bd1d7..92d79012e 100644
--- a/web/src/components/TimelineSummary.jsx
+++ b/web/src/components/TimelineSummary.jsx
@@ -3,8 +3,10 @@ import useSWR from 'swr';
import ActivityIndicator from './ActivityIndicator';
import { formatUnixTimestampToDateTime } from '../utils/dateUtil';
import About from '../icons/About';
+import ActiveObjectIcon from '../icons/ActiveObject';
import PlayIcon from '../icons/Play';
import ExitIcon from '../icons/Exit';
+import StationaryObjectIcon from '../icons/StationaryObject';
import { Zone } from '../icons/Zone';
import { useMemo, useState } from 'preact/hooks';
import Button from './Button';
@@ -77,31 +79,18 @@ export default function TimelineSummary({ event, onFrameSelected }) {
- {eventTimeline.map((item, index) =>
- item.class_type == 'visible' || item.class_type == 'gone' ? (
-
- ) : (
-
- )
- )}
+ {eventTimeline.map((item, index) => (
+
+ ))}
{timeIndex >= 0 ? (
@@ -124,26 +113,54 @@ export default function TimelineSummary({ event, onFrameSelected }) {
);
}
-function getTimelineItemDescription(config, timelineItem, event) {
- if (timelineItem.class_type == 'visible') {
- return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
- date_style: 'short',
- time_style: 'medium',
- time_format: config.ui.time_format,
- })}`;
- } else if (timelineItem.class_type == 'entered_zone') {
- return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones
- .join(' and ')
- .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
- date_style: 'short',
- time_style: 'medium',
- time_format: config.ui.time_format,
- })}`;
+function getTimelineIcon(classType) {
+ switch (classType) {
+ case 'visible':
+ return
;
+ case 'gone':
+ return
;
+ case 'active':
+ return
;
+ case 'stationary':
+ return
;
+ case 'entered_zone':
+ return
;
+ }
+}
+
+function getTimelineItemDescription(config, timelineItem, event) {
+ switch (timelineItem.class_type) {
+ case 'visible':
+ return `${event.label} detected at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
+ date_style: 'short',
+ time_style: 'medium',
+ time_format: config.ui.time_format,
+ })}`;
+ case 'entered_zone':
+ return `${event.label.replaceAll('_', ' ')} entered ${timelineItem.data.zones
+ .join(' and ')
+ .replaceAll('_', ' ')} at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
+ date_style: 'short',
+ time_style: 'medium',
+ time_format: config.ui.time_format,
+ })}`;
+ case 'active':
+ return `${event.label} became active at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
+ date_style: 'short',
+ time_style: 'medium',
+ time_format: config.ui.time_format,
+ })}`;
+ case 'stationary':
+ return `${event.label} became stationary at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
+ date_style: 'short',
+ time_style: 'medium',
+ time_format: config.ui.time_format,
+ })}`;
+ case 'gone':
+ return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
+ date_style: 'short',
+ time_style: 'medium',
+ time_format: config.ui.time_format,
+ })}`;
}
-
- return `${event.label} left at ${formatUnixTimestampToDateTime(timelineItem.timestamp, {
- date_style: 'short',
- time_style: 'medium',
- time_format: config.ui.time_format,
- })}`;
}
diff --git a/web/src/icons/ActiveObject.jsx b/web/src/icons/ActiveObject.jsx
new file mode 100644
index 000000000..75f8da5e6
--- /dev/null
+++ b/web/src/icons/ActiveObject.jsx
@@ -0,0 +1,15 @@
+import { h } from 'preact';
+import { memo } from 'preact/compat';
+
+export function ActiveObject({ className = '' }) {
+ return (
+
+ );
+}
+
+export default memo(ActiveObject);
diff --git a/web/src/icons/StationaryObject.jsx b/web/src/icons/StationaryObject.jsx
new file mode 100644
index 000000000..12ff746bc
--- /dev/null
+++ b/web/src/icons/StationaryObject.jsx
@@ -0,0 +1,15 @@
+import { h } from 'preact';
+import { memo } from 'preact/compat';
+
+export function StationaryObject({ className = '' }) {
+ return (
+
+ );
+}
+
+export default memo(StationaryObject);