Skip to content

Commit a83f8eb

Browse files
authored
Add SimConnect_SubscribeToSystemEvent handling (#15)
1 parent f606b9c commit a83f8eb

4 files changed

Lines changed: 167 additions & 1 deletion

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// <copyright file="SimSystemEventReceivedEventArgs.cs" company="BARS">
2+
// Copyright (c) BARS. All rights reserved.
3+
// </copyright>
4+
5+
namespace SimConnect.NET.Events
6+
{
7+
/// <summary>
8+
/// Provides data for an event that is raised when a Simconnect system event is raised.
9+
/// </summary>
10+
/// <remarks>
11+
/// Initializes a new instance of the <see cref="SimSystemEventReceivedEventArgs"/> class with the specified event identifier and.
12+
/// associated data.
13+
/// </remarks>
14+
/// <param name="eventId">The unique identifier for the system event.</param>
15+
/// <param name="data">The data associated with the system event.</param>
16+
public class SimSystemEventReceivedEventArgs(uint eventId, uint data) : EventArgs
17+
{
18+
/// <summary>
19+
/// Gets the unique identifier for the event.
20+
/// </summary>
21+
public uint EventId { get; } = eventId;
22+
23+
/// <summary>
24+
/// Gets the data associated with the event.
25+
/// </summary>
26+
public uint Data { get; } = data;
27+
}
28+
}

src/SimConnect.NET/SimConnectClient.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// Copyright (c) BARS. All rights reserved.
33
// </copyright>
44

5-
using System;
65
using System.Runtime.InteropServices;
76
using System.Threading;
87
using System.Threading.Tasks;
@@ -70,6 +69,11 @@ public SimConnectClient(string applicationName = "SimConnect.NET Client")
7069
/// </summary>
7170
public event EventHandler<RawSimConnectMessageEventArgs>? RawMessageReceived;
7271

72+
/// <summary>
73+
/// Occurs when a subscribed event is fired.
74+
/// </summary>
75+
public event EventHandler<SimSystemEventReceivedEventArgs>? SystemEventReceived;
76+
7377
/// <summary>
7478
/// Gets a value indicating whether the client is connected to SimConnect.
7579
/// </summary>
@@ -345,6 +349,40 @@ public async Task DisconnectAsync()
345349
}
346350
}
347351

352+
/// <summary>
353+
/// Subscribes to a specific simulator system event.
354+
/// </summary>
355+
/// <param name="systemEventName">The name of the system event (e.g., "SimStart", "4Sec", "Crashed").</param>
356+
/// <param name="systemEventId">A user-defined ID to identify this subscription.</param>
357+
/// <param name="cancellationToken">Cancellation token for the operation.</param>
358+
/// <returns>A task representing the event.</returns>
359+
/// <exception cref="InvalidOperationException">Thrown when a sim connection wasn't found.</exception>
360+
/// <exception cref="SimConnectException">Thrown when the event wasn't subscribed.</exception>
361+
public async Task SubscribeToEventAsync(string systemEventName, uint systemEventId, CancellationToken cancellationToken = default)
362+
{
363+
ObjectDisposedException.ThrowIf(this.disposed, nameof(SimConnectClient));
364+
365+
if (!this.isConnected)
366+
{
367+
throw new InvalidOperationException("Not connected to SimConnect.");
368+
}
369+
370+
await Task.Run(
371+
() =>
372+
{
373+
var result = SimConnectNative.SimConnect_SubscribeToSystemEvent(
374+
this.simConnectHandle,
375+
systemEventId,
376+
systemEventName);
377+
378+
if (result != (int)SimConnectError.None)
379+
{
380+
throw new SimConnectException($"Failed to subscribe to event {systemEventName}: {(SimConnectError)result}", (SimConnectError)result);
381+
}
382+
},
383+
cancellationToken).ConfigureAwait(false);
384+
}
385+
348386
/// <summary>
349387
/// Processes the next SimConnect message.
350388
/// </summary>
@@ -419,6 +457,9 @@ public async Task<bool> ProcessNextMessageAsync(CancellationToken cancellationTo
419457
case SimConnectRecvId.VorList:
420458
case SimConnectRecvId.NdbList:
421459
break;
460+
case SimConnectRecvId.Event:
461+
this.ProcessSystemEvent(ppData);
462+
break;
422463
default:
423464
this.simVarManager?.ProcessReceivedData(ppData, pcbData);
424465
break;
@@ -543,6 +584,29 @@ private void ProcessOpen(IntPtr ppData)
543584
}
544585
}
545586

587+
/// <summary>
588+
/// Processes a system event message from SimConnect.
589+
/// </summary>
590+
/// <param name="ppData">Pointer to the received Event data.</param>
591+
private void ProcessSystemEvent(IntPtr ppData)
592+
{
593+
try
594+
{
595+
var recvEvent = Marshal.PtrToStructure<SimConnectRecvEvent>(ppData);
596+
597+
this.SystemEventReceived?.Invoke(this, new SimSystemEventReceivedEventArgs(recvEvent.EventId, recvEvent.Data));
598+
599+
if (SimConnectLogger.IsLevelEnabled(SimConnectLogger.LogLevel.Debug))
600+
{
601+
SimConnectLogger.Debug($"System Event Received: ID={recvEvent.EventId} Data={recvEvent.Data}");
602+
}
603+
}
604+
catch (Exception ex) when (!ExceptionHelper.IsCritical(ex))
605+
{
606+
SimConnectLogger.Error("Error processing system event", ex);
607+
}
608+
}
609+
546610
/// <summary>
547611
/// Starts the background message processing loop.
548612
/// </summary>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// <copyright file="SystemEventSubscriptionTests.cs" company="BARS">
2+
// Copyright (c) BARS. All rights reserved.
3+
// </copyright>
4+
5+
namespace SimConnect.NET.Tests.Net8.Tests
6+
{
7+
internal class SystemEventSubscriptionTests : ISimConnectTest
8+
{
9+
public string Name => "SystemEventSubscription";
10+
11+
public string Description => "Tests system event subscription";
12+
13+
public string Category => "System Event";
14+
15+
public async Task<bool> RunAsync(SimConnectClient client, CancellationToken cancellationToken = default)
16+
{
17+
try
18+
{
19+
if (!client.IsConnected)
20+
{
21+
Console.WriteLine(" ❌ Client should already be connected");
22+
return false;
23+
}
24+
25+
Console.WriteLine(" ✅ Connection status verified");
26+
27+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
28+
cts.CancelAfter(TimeSpan.FromSeconds(15));
29+
30+
bool testEventReceived = false;
31+
client.SystemEventReceived += (sender, e) =>
32+
{
33+
switch (e.EventId)
34+
{
35+
case 100:
36+
Console.WriteLine("4 seconds has passed!");
37+
testEventReceived = true;
38+
break;
39+
}
40+
};
41+
42+
await client.SubscribeToEventAsync("4sec", 100, cts.Token);
43+
44+
Console.WriteLine("Listening for events...");
45+
46+
while (!testEventReceived && !cts.Token.IsCancellationRequested)
47+
{
48+
await Task.Delay(500, cts.Token);
49+
}
50+
if (!testEventReceived)
51+
{
52+
Console.WriteLine(" ❌ Did not receive expected system event");
53+
return false;
54+
}
55+
Console.WriteLine(" ✅ Received expected system event");
56+
return true;
57+
}
58+
catch (OperationCanceledException)
59+
{
60+
Console.WriteLine(" ❌ Connection test timed out");
61+
return false;
62+
}
63+
catch (Exception ex)
64+
{
65+
Console.WriteLine($" ❌ Connection test failed: {ex.Message}");
66+
return false;
67+
}
68+
}
69+
}
70+
}

tests/SimConnect.NET.Tests.Net8/Tests/TestRunner.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ public TestRunner()
3131
new InputEventTests(),
3232
new InputEventValueTests(),
3333
new PerformanceTests(),
34+
new SystemEventSubscriptionTests(),
3435
};
3536
}
3637

@@ -143,6 +144,9 @@ private static TestOptions ParseArguments(string[] args)
143144
case "--verbose":
144145
options.Verbose = true;
145146
break;
147+
case "--test-events":
148+
options.Categories.Add("System Event");
149+
break;
146150
}
147151
}
148152

0 commit comments

Comments
 (0)