-
Notifications
You must be signed in to change notification settings - Fork 6
plugin sdk events
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;
}
}
}- 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;
}- 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:
- 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
- Called at runtime, typically in
OnPluginLoaded() - Replaces any events set by
SupportedEventSubscription - Can be modified throughout plugin lifetime
- Best for dynamic, configuration-driven subscriptions
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;
}
}- 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
After the plugin registers its subscriptions, handle incoming events through the engine callback and dispatch them based on the event type.
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; }
}- Working with events: Subscribing to system events from the Platform SDK.
- Receiving actions: Receiving actions directed at entities.
- Plugin SDK threading: Engine thread, QueueUpdate, and async patterns for plugins.
- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
- Plugin SDK overview: Plugin architecture, lifecycle, and components.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- Mobile credentials
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- .NET 8 support
- Overview
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Under the hood
- Troubleshooting