Skip to content

Commit

Permalink
Adding C# client
Browse files Browse the repository at this point in the history
  • Loading branch information
frankracis committed Apr 11, 2021
1 parent 1d4337e commit be7813c
Show file tree
Hide file tree
Showing 9 changed files with 697 additions and 0 deletions.
31 changes: 31 additions & 0 deletions CSharp/JetBridge.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31129.286
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheoMessin.JetBridge.Client", "TheoMessin.JetBridge.Client\TheoMessin.JetBridge.Client.csproj", "{966BB3B6-3B9A-4FDF-A794-AEE6F63FE7AF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TheoMessin.JetBridge.Example", "TheoMessin.JetBridge.Example\TheoMessin.JetBridge.Example.csproj", "{C006ED21-7035-4092-9A95-1B1777BE48B8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{966BB3B6-3B9A-4FDF-A794-AEE6F63FE7AF}.Debug|x64.ActiveCfg = Debug|x64
{966BB3B6-3B9A-4FDF-A794-AEE6F63FE7AF}.Debug|x64.Build.0 = Debug|x64
{966BB3B6-3B9A-4FDF-A794-AEE6F63FE7AF}.Release|x64.ActiveCfg = Release|x64
{966BB3B6-3B9A-4FDF-A794-AEE6F63FE7AF}.Release|x64.Build.0 = Release|x64
{C006ED21-7035-4092-9A95-1B1777BE48B8}.Debug|x64.ActiveCfg = Debug|x64
{C006ED21-7035-4092-9A95-1B1777BE48B8}.Debug|x64.Build.0 = Debug|x64
{C006ED21-7035-4092-9A95-1B1777BE48B8}.Release|x64.ActiveCfg = Release|Any CPU
{C006ED21-7035-4092-9A95-1B1777BE48B8}.Release|x64.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {EBDF73D0-D7A6-4049-9DAB-B71F544C6D80}
EndGlobalSection
EndGlobal
5 changes: 5 additions & 0 deletions CSharp/JetBridge.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Downlink/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Messin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Theo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Uplink/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
268 changes: 268 additions & 0 deletions CSharp/TheoMessin.JetBridge.Client/JetBridgeClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.FlightSimulator.SimConnect;

namespace TheoMessin.JetBridge.Client
{
/// <summary>
/// Client for JetBridge
/// </summary>
public class JetBridgeClient
{
#region Fields

/// <summary>
/// SimConnect instance
/// </summary>
readonly SimConnect _simConnect;

/// <summary>
/// Generate random IDs
/// </summary>
readonly Random _idGenerator = new Random();

/// <summary>
/// Map of request id to response
/// </summary>
readonly Dictionary<int, PacketResponse> _responses = new Dictionary<int, PacketResponse>();

#endregion

#region Constructors / Finalizers

/// <summary>
/// Constructor
/// </summary>
/// <param name="simConnect">SimConnect instance to communicate with</param>
public JetBridgeClient(SimConnect simConnect)
{
_simConnect = simConnect;
}

#endregion

#region Enums

/// <summary>
/// Client data areas
/// </summary>
enum ClientDataAreas
{
/// <summary>
/// Data returned from JetBridge
/// </summary>
PublicDownlinkArea = 5321,

/// <summary>
/// Data sent to JetBridge
/// </summary>
PublicUplinkArea = 5322,
};

/// <summary>
/// Data definitions
/// </summary>
enum ClientDataDefinitions
{
/// <summary>
/// <see cref="Packet"/> data type
/// </summary>
PacketDefinition = 5321,
};

/// <summary>
/// Request identifiers
/// </summary>
enum DataRequest
{
/// <summary>
/// Not currently used
/// </summary>
// ReSharper disable once UnusedMember.Local
UplinkRequest = 5321,

/// <summary>
/// Return data from JetBridge
/// </summary>
DownlinkRequest = 5322,
};

#endregion

#region Methods

/// <summary>
/// Connect to SimConnect
/// </summary>
/// <remarks>
/// Call this after SimConnect has connected to the simulator
/// </remarks>
public void Connect()
{
_simConnect.OnRecvClientData += OnRecvClientData;
_simConnect.AddToClientDataDefinition(ClientDataDefinitions.PacketDefinition, 0, (uint)Marshal.SizeOf<Packet>(), 0, 0);
_simConnect.RegisterStruct<SIMCONNECT_RECV_CLIENT_DATA, Packet>(ClientDataDefinitions.PacketDefinition);
_simConnect.MapClientDataNameToID(ChannelNames.PublicDownlinkChannel, ClientDataAreas.PublicDownlinkArea);
_simConnect.MapClientDataNameToID(ChannelNames.PublicUplinkChannel, ClientDataAreas.PublicUplinkArea);
_simConnect.RequestClientData(ClientDataAreas.PublicDownlinkArea, DataRequest.DownlinkRequest, ClientDataDefinitions.PacketDefinition, SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET, SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED, 0, 0, 0);
}


/// <summary>
/// Send a command to JetBridge
/// </summary>
/// <param name="command">Command to send</param>
/// <returns>Response to the command, or <c>null</c> if no response was received</returns>
public string SendCommand(string command)
{
return SendCommand(command, TimeSpan.FromSeconds(1));
}

/// <summary>
/// Send a command to JetBridge
/// </summary>
/// <param name="command">Command to send</param>
/// <param name="timeout">Time to wait for a response</param>
/// <returns>Response to the command, or <c>null</c> if no response was received</returns>
public string SendCommand(string command, TimeSpan timeout)
{
var p = new Packet
{
id = _idGenerator.Next(),
data = command
};

using (var response = new PacketResponse())
{
_responses.Add(p.id, response);
_simConnect.SetClientData(ClientDataAreas.PublicUplinkArea, ClientDataDefinitions.PacketDefinition, 0, 0, p);
var received = response.Received.WaitOne(timeout);
_responses.Remove(p.id);
return received ? response.Response : null;
}
}

/// <summary>
/// Handle received data
/// </summary>
/// <param name="sender">SimConnect client</param>
/// <param name="data">Received data</param>
void OnRecvClientData(SimConnect sender, SIMCONNECT_RECV_CLIENT_DATA data)
{
if (data.dwDefineID == (uint)ClientDataDefinitions.PacketDefinition)
{
if (data.dwData[0] is Packet p)
{
if (_responses.TryGetValue(p.id, out var response))
{
response.Response = p.data;
}
}
}
}

#endregion

#region Structs

/// <summary>
/// Wire format of packet data
/// </summary>
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Packet
{
/// <summary>
/// Packet Id
/// </summary>
public int id;

/// <summary>
/// Packet data
/// </summary>
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string data;
}

#endregion

#region Nested Types

/// <summary>
/// Registered channel names
/// </summary>
static class ChannelNames
{
#region Fields

/// <summary>
/// Receive data from JetBridge
/// </summary>
public const string PublicDownlinkChannel = "theomessin.jetbridge.downlink";

/// <summary>
/// Send data to JetBridge
/// </summary>
public const string PublicUplinkChannel = "theomessin.jetbridge.uplink";

#endregion
}

/// <summary>
/// Hold a response from JetBridge
/// </summary>
class PacketResponse : IDisposable
{
#region Fields

/// <summary>
/// Set when <see cref="Response"/> is set
/// </summary>
readonly ManualResetEvent _done = new ManualResetEvent(false);

/// <summary>
/// Backing field for <see cref="Response"/>
/// </summary>
string _response;

#endregion

#region Properties

/// <summary>
/// Response from JetBridge
/// </summary>
public string Response
{
get => _response;
set
{
_response = value;
_done.Set();
}
}

/// <summary>
/// Handle that is set when <see cref="Response"/> is set
/// </summary>
public WaitHandle Received => _done;

#endregion

#region Methods

/// <summary>
/// Free up resources
/// </summary>
public void Dispose()
{
_done?.Dispose();
}

#endregion
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Platforms>x64</Platforms>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Description>
<![CDATA[ JetBridge Client
.NET Client to use SimConnect with the JetBridge module for Microsoft Flight Simulator
]]>
</Description>
<Version>0.1.0</Version>
</PropertyGroup>

<PropertyGroup>
<MsfsSdkLocation Condition="'$(MsfsSdkLocation)'==''">C:\MSFS SDK</MsfsSdkLocation>
</PropertyGroup>

<ItemGroup>
<Reference Include="Microsoft.FlightSimulator.SimConnect">
<HintPath>$(MsfsSdkLocation)\SimConnect SDK\lib\managed\Microsoft.FlightSimulator.SimConnect.dll</HintPath>
</Reference>
</ItemGroup>

</Project>
Loading

0 comments on commit be7813c

Please sign in to comment.