plugin sdk events - Genetec/DAP GitHub Wiki
Plugins can subscribe to and receive events from Security Center entities. This guide covers plugin-specific event handling patterns.
For general event and action concepts, see Platform SDK Events and Platform SDK Actions.
Plugins have two ways to declare which events they want to receive:
Override SupportedEventSubscription to declaratively specify event types at the class level:
public class MyPlugin : Plugin
{
public override List<EventType> SupportedEventSubscription => new List<EventType>
{
EventType.AccessGranted,
EventType.AccessRefused,
EventType.DoorOpenedForTooLong,
EventType.DoorOpenWhileLockSecure
};
protected override void OnPluginLoaded()
{
Engine.EventReceived += OnEventReceived;
}
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
// Handle events
Logger.TraceDebug($"Event received: {e.EventType} from {e.SourceGuid}");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Engine.EventReceived -= OnEventReceived;
}
}
}When to use:
- Event types are known at compile time
- Event subscription does not change during plugin lifetime
- Cleaner separation between declaration and handling
Use Engine.SetEventFilter() for dynamic event subscription in OnPluginLoaded():
protected override void OnPluginLoaded()
{
// Set event filter dynamically
Engine.SetEventFilter(new List<EventType>
{
EventType.AccessGranted,
EventType.AccessRefused,
EventType.DoorOpenedForTooLong
});
Engine.EventReceived += OnEventReceived;
}When to use:
- Event types depend on configuration
- Event subscription changes at runtime
- Need to add/remove event types dynamically
The Engine provides several methods for managing event filters:
| Method | Description |
|---|---|
SetEventFilter(IEnumerable<EventType>) |
Sets the event filter, replacing previous filters |
AddToEventFilter(EventType) |
Adds a single event type to the current filter |
RemoveFromEventFilter(EventType) |
Removes a single event type from the current filter |
GetEventFilter() |
Returns the current list of filtered event types |
ClearEventFilter() |
Clears the filter to receive all events |
// Add event type based on configuration
if (config.MonitorDoorEvents)
{
Engine.AddToEventFilter(EventType.DoorOpenedForTooLong);
Engine.AddToEventFilter(EventType.DoorOpenWhileLockSecure);
}
// Remove event type when no longer needed
Engine.RemoveFromEventFilter(EventType.DoorOpenedForTooLong);
// Get current filter
List<EventType> currentFilter = Engine.GetEventFilter();Understanding when to use each approach:
SupportedEventSubscription (property override):
- Read by the plugin host at initialization time
- Sets the initial event filter before
OnPluginLoaded()runs - Cannot be changed at runtime
- Returns an empty list by default (no events)
- Best for static, known event types
SetEventFilter (Engine method):
- Called at runtime, typically in
OnPluginLoaded() - Replaces any events set by
SupportedEventSubscription - Can be modified throughout plugin lifetime
- Best for dynamic, configuration-driven subscriptions
How they interact:
public class MyPlugin : Plugin
{
// 1. Plugin host reads this at initialization
// Sets initial filter to AccessGranted, AccessRefused
public override List<EventType> SupportedEventSubscription => new List<EventType>
{
EventType.AccessGranted,
EventType.AccessRefused
};
protected override void OnPluginLoaded()
{
// 2. This REPLACES the initial filter (not adds to it)
// Now only receiving DoorOpenedForTooLong events
Engine.SetEventFilter(new List<EventType>
{
EventType.DoorOpenedForTooLong
});
// 3. Use AddToEventFilter to ADD to current filter
Engine.AddToEventFilter(EventType.DoorOpenWhileLockSecure);
// Now receiving: DoorOpenedForTooLong, DoorOpenWhileLockSecure
Engine.EventReceived += OnEventReceived;
}
}Recommendations:
- Use
SupportedEventSubscriptionwhen event types are fixed and known at compile time - Use
SetEventFilter()when event types depend on configuration or change at runtime - Do not use both unless you intend for
SetEventFilter()to overrideSupportedEventSubscription - Use
AddToEventFilter()to extend the current filter without replacing it
Subscribe to Engine.EventReceived to receive events:
protected override void OnPluginLoaded()
{
Engine.SetEventFilter(new List<EventType>
{
EventType.AccessGranted,
EventType.AccessRefused
});
Engine.EventReceived += OnEventReceived;
}
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
switch (e.EventType)
{
case EventType.AccessGranted:
HandleAccessGranted(e);
break;
case EventType.AccessRefused:
HandleAccessRefused(e);
break;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Engine.EventReceived -= OnEventReceived;
}
}| Property | Type | Description |
|---|---|---|
EventType |
EventType |
The type of event received |
SourceGuid |
Guid |
The entity GUID that generated the event |
Timestamp |
DateTime |
When the event occurred (UTC) |
GroupId |
Guid |
Context identifier for grouping related events |
Event |
Event |
The underlying Event object with additional data |
Cast EventReceivedEventArgs to specialized types for specific event data:
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
if (e is LicensePlateReadEventArgs lprEvent)
{
Logger.TraceDebug($"License plate read: {lprEvent.Context} at {lprEvent.SourceGuid}");
}
else if (e is CustomEventReceivedEventArgs customEvent)
{
Logger.TraceDebug($"Custom event: {customEvent.CustomEventId}, Message: {customEvent.Message}");
}
else if (e is AlarmTriggeredRoutableEventArgs alarmEvent)
{
Logger.TraceDebug($"Alarm triggered: {alarmEvent.AlarmEvent.AlarmGuid}");
}
}Plugins can also receive actions sent to them or their owned entities.
protected override void OnPluginLoaded()
{
Engine.ActionReceived += OnActionReceived;
}
private void OnActionReceived(object sender, ActionReceivedEventArgs e)
{
Logger.TraceDebug($"Action received: {e.ActionType}");
switch (e.ActionType)
{
case ActionType.TriggerAlarm:
HandleTriggerAlarm(e);
break;
case ActionType.Custom:
if (e.Action is CustomAction customAction)
{
HandleCustomAction(customAction);
}
break;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Engine.ActionReceived -= OnActionReceived;
}
}Event handlers are called on the engine thread, making it safe to perform Engine operations directly:
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
// Safe to access Engine directly - we're on the engine thread
var entity = Engine.GetEntity(e.SourceGuid);
Logger.TraceDebug($"Event {e.EventType} from {entity.Name}");
}For operations that take time (network calls, file I/O), offload to a background thread:
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
// Capture event data
var eventData = new EventData
{
EventType = e.EventType,
SourceGuid = e.SourceGuid,
Timestamp = e.Timestamp
};
// Process on background thread
Task.Run(async () =>
{
// Long-running operation (network, database, etc.)
await SendToExternalSystemAsync(eventData);
// Queue entity updates back to engine thread
Engine.QueueUpdate(() =>
{
ModifyPluginState(new PluginStateEntry("LastEvent",
$"Processed {eventData.EventType} at {DateTime.UtcNow}"));
});
});
}public class AccessMonitorPlugin : Plugin
{
private readonly ConcurrentQueue<AccessEvent> m_eventQueue = new();
private CancellationTokenSource m_cancellation;
private Task m_processingTask;
// Declaratively specify supported events
public override List<EventType> SupportedEventSubscription => new List<EventType>
{
EventType.AccessGranted,
EventType.AccessRefused,
EventType.DoorOpenedForTooLong,
EventType.DoorOpenWhileLockSecure
};
protected override void OnPluginLoaded()
{
Engine.EventReceived += OnEventReceived;
Engine.ActionReceived += OnActionReceived;
ModifyPluginState(new PluginStateEntry("Status", "Monitoring access events"));
}
protected override void OnPluginStart()
{
// Start background processor
m_cancellation = new CancellationTokenSource();
m_processingTask = Task.Run(() => ProcessEventsAsync(m_cancellation.Token));
}
private void OnEventReceived(object sender, EventReceivedEventArgs e)
{
// Quick processing on engine thread
var accessEvent = new AccessEvent
{
EventType = e.EventType,
SourceGuid = e.SourceGuid,
Timestamp = e.Timestamp
};
// Queue for background processing
m_eventQueue.Enqueue(accessEvent);
}
private void OnActionReceived(object sender, ActionReceivedEventArgs e)
{
Logger.TraceDebug($"Action received: {e.ActionType}");
}
private async Task ProcessEventsAsync(CancellationToken cancel)
{
while (!cancel.IsCancellationRequested)
{
if (m_eventQueue.TryDequeue(out var accessEvent))
{
try
{
await SendToExternalSystemAsync(accessEvent);
}
catch (Exception ex)
{
Logger.TraceError(ex, "Failed to process event");
}
}
else
{
await Task.Delay(100, cancel);
}
}
}
private async Task SendToExternalSystemAsync(AccessEvent accessEvent)
{
// Send to external system
await Task.Delay(10); // Simulated network call
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
Engine.EventReceived -= OnEventReceived;
Engine.ActionReceived -= OnActionReceived;
m_cancellation?.Cancel();
}
}
}
public class AccessEvent
{
public EventType EventType { get; set; }
public Guid SourceGuid { get; set; }
public DateTime Timestamp { get; set; }
}- Platform SDK Events - Subscribing to system events
- Platform SDK Actions - Handling actions
- Plugin SDK Threading - Threading patterns for event handlers
- Plugin SDK Lifecycle - Event handler registration timing
- Plugin SDK Overview - Plugin architecture