Skip to content

Commit

Permalink
Simplify registering data definitions (#3)
Browse files Browse the repository at this point in the history
* Added enums for SimVars and units * Support for update data * Test Console controls airplane

* Cleanup * Updated readme

* Updated readme and package info

Co-authored-by: Timian Heber <[email protected]>
  • Loading branch information
TimianHeber and TimianHeberTPG committed Sep 13, 2020
1 parent a389719 commit da00377
Show file tree
Hide file tree
Showing 11 changed files with 400 additions and 46 deletions.
35 changes: 25 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,25 @@
A simple easy-to-use wrapper for the Flight Simulator 2020 SimConnect library. A simple solution to simple problems, if you already are fluent in SimConnect, this won't impress you much.
If, on the other hand, you just want to connect to Flight Simulator and read some information this may give you a quicker start.

FsConnect uses the _Microsoft.FlightSimulator.SimConnect_ .NET Framework library and the underlying native x64 _simconnect.dll_ library.
These files are distributed via the Flight Simulator 2020 SDK, currently version 0.5.1, but are included for easy use.

At the moment this project is intended as an easier to use wrapper than the current SimConnect for simple projects, creating a simpler C# programming model and reducing the need for repeated boiler plate code. Expect breaking changes.

## Current features
* Supports TCP connection, without use of SimConnect.cfg file
* Supports registering and requesting simple simulation variables.
* Supports updating simulation variables.
* Does not require a Windows message pump.
* NuGet package handles deployment of native binaries, just add reference to package.

# Getting started
* Download the Flight Simulator SDK and take a look at the Simvars sample project.
* Download the Flight Simulator SDK.
* See the list of available simulation variables in the SDK documentation: Documentation/04-Developer_Tools/SimConnect/SimConnect_Status_of_Simulation_Variables.html
* Determine unit to use when registering the data structure.
* Build a C# struct to hold data data definition. (See example below)
* Define a data definition and register it. (See example below)
* Take a look at the Simvars sample project and example below.

# Usage

Expand Down Expand Up @@ -38,7 +50,7 @@ namespace FsConnectTest
public String Title;
public double Latitude;
public double Longitude;
public double AltitudeAboveGround;
public double Altitude;
public double Heading;
}

Expand All @@ -47,16 +59,19 @@ namespace FsConnectTest
public static void Main()
{
FsConnect fsConnect = new FsConnect();
fsConnect.Connect("localhost", 500);
fsConnect.Connect("TestApp", "localhost", 500);
fsConnect.FsDataReceived += HandleReceivedFsData;

List<Tuple<string, string, SIMCONNECT_DATATYPE>> definition = new List<Tuple<string, string, SIMCONNECT_DATATYPE>>();
List<SimProperty> definition = new List<SimProperty>();

definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Title", null, SIMCONNECT_DATATYPE.STRING256));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Latitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Longitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Alt Above Ground", "feet", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Heading Degrees Gyro", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
// Consult the SDK for valid sim variable names, units and whether they can be written to.
definition.Add(new SimProperty("Title", null, SIMCONNECT_DATATYPE.STRING256));
definition.Add(new SimProperty("Plane Latitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty("Plane Longitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));

// Can also use predefined enums for sim variables and units (incomplete)
definition.Add(new SimProperty(FsSimVar.PlaneAltitude, FsUnit.Feet, SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.PlaneHeadingDegreesTrue, FsUnit.Degrees, SIMCONNECT_DATATYPE.FLOAT64));

fsConnect.RegisterDataDefinition<PlaneInfoResponse>(Requests.PlaneInfo, definition);

Expand All @@ -70,7 +85,7 @@ namespace FsConnectTest
if (e.RequestId == (uint)Requests.PlaneInfo)
{
PlaneInfoResponse r = (PlaneInfoResponse)e.Data;
Console.WriteLine($"{r.Latitude:F4} {r.Longitude:F4} {r.AltitudeAboveGround:F1} {r.Heading}");
Console.WriteLine($"{r.Latitude:F4} {r.Longitude:F4} {r.Altitude:F1} {r.Heading}");
}
}
}
Expand Down
132 changes: 111 additions & 21 deletions src/CTrue.FsConnect.TestConsole/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Net;
using CommandLine;
using CommandLine.Text;
using Microsoft.FlightSimulator.SimConnect;
Expand All @@ -11,6 +12,15 @@ namespace CTrue.FsConnect.TestConsole
/// </summary>
class Program
{
private static FsConnect _fsConnect;
private static Dictionary<ConsoleKey, Action> _keyHandlers = new Dictionary<ConsoleKey, Action>();
private static PlaneInfoResponse _planeInfoResponse;

private static double _deltaLatitude = 0.001;
private static double _deltaLongitude = 0.001;
private static double _deltaAltitude = 1000;
private static double _deltaHeading = 10;

static void Main(string[] args)
{
CommandLine.Parser parser = new CommandLine.Parser(with => with.HelpWriter = null);
Expand All @@ -25,37 +35,64 @@ private static void Run(Options commandLineOptions)
{
try
{
FsConnect fsConnect = new FsConnect();
_fsConnect = new FsConnect();

Console.WriteLine($"Connecting to Flight Simulator on {commandLineOptions.Hostname}:{commandLineOptions.Port}");
try
{
fsConnect.Connect(commandLineOptions.Hostname, commandLineOptions.Port);
_fsConnect.Connect("FsConnectTestConsole", commandLineOptions.Hostname, commandLineOptions.Port);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
return;
}

fsConnect.FsDataReceived += HandleReceivedFsData;

_fsConnect.FsDataReceived += HandleReceivedFsData;
Console.WriteLine("Initializing data definitions");
InitializeDataDefinitions(fsConnect);
InitializeDataDefinitions(_fsConnect);

_keyHandlers.Add(ConsoleKey.P, PollFlightSimulator);
_keyHandlers.Add(ConsoleKey.W, MoveForward);
_keyHandlers.Add(ConsoleKey.S, MoveBackward);
_keyHandlers.Add(ConsoleKey.A, MoveLeft);
_keyHandlers.Add(ConsoleKey.D, MoveRight);
_keyHandlers.Add(ConsoleKey.Q, RotateLeft);
_keyHandlers.Add(ConsoleKey.E, RotateRight);
_keyHandlers.Add(ConsoleKey.R, IncreaseAltitude);
_keyHandlers.Add(ConsoleKey.F, DecreaseAltitude);

Console.WriteLine("Press any key to request data from Flight Simulator or Q to quit.");
Console.WriteLine("Press any key to request data from Flight Simulator or ESC to quit.");
Console.WriteLine("Press WASD keys to move, Q and E to rotate, R and F to change altitude.");
ConsoleKeyInfo cki = Console.ReadKey(true);

while (cki.Key != ConsoleKey.Q)
_fsConnect.SetText("Test Console connected", 2);

_fsConnect.RequestData(Requests.PlaneInfo);

while (cki.Key != ConsoleKey.Escape)
{
fsConnect.RequestData(Requests.PlaneInfo);

if(_keyHandlers.ContainsKey(cki.Key))
{
_keyHandlers[cki.Key].Invoke();
}
else
{
PollFlightSimulator();
}

cki = Console.ReadKey(true);
}

Console.ReadKey(true);

Console.WriteLine("Disconnecting from Flight Simulator");
fsConnect.Disconnect();

_fsConnect.SetText("Test Console disconnecting", 1);
_fsConnect.Disconnect();
_fsConnect.Dispose();
_fsConnect = null;

Console.WriteLine("Done");
}
catch (Exception e)
Expand All @@ -64,18 +101,71 @@ private static void Run(Options commandLineOptions)
}
}

private static void IncreaseAltitude()
{
_planeInfoResponse.Altitude += _deltaAltitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void DecreaseAltitude()
{
_planeInfoResponse.Altitude -= _deltaAltitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void RotateRight()
{
_planeInfoResponse.Heading += FsUtils.Deg2Rad(_deltaHeading);
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void RotateLeft()
{
_planeInfoResponse.Heading -= FsUtils.Deg2Rad(_deltaHeading);
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void MoveRight()
{
_planeInfoResponse.Longitude += _deltaLongitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void MoveLeft()
{
_planeInfoResponse.Longitude -= _deltaLongitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void MoveBackward()
{
_planeInfoResponse.Latitude -= _deltaLatitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void MoveForward()
{
_planeInfoResponse.Latitude += _deltaLatitude;
_fsConnect.UpdateData(Definitions.PlaneInfo, _planeInfoResponse);
}

private static void PollFlightSimulator()
{
_fsConnect.RequestData(Requests.PlaneInfo);
}

private static void InitializeDataDefinitions(FsConnect fsConnect)
{
List<Tuple<string, string, SIMCONNECT_DATATYPE>> definition = new List<Tuple<string, string, SIMCONNECT_DATATYPE>>();
List<SimProperty> definition = new List<SimProperty>();

definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Title", null, SIMCONNECT_DATATYPE.STRING256));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Latitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Longitude", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Alt Above Ground", "feet", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("PLANE ALTITUDE", "feet", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new Tuple<string, string, SIMCONNECT_DATATYPE>("Plane Heading Degrees Gyro", "degrees", SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.Title, FsUnit.None, SIMCONNECT_DATATYPE.STRING256));
definition.Add(new SimProperty(FsSimVar.PlaneLatitude, FsUnit.Radians, SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.PlaneLongitude, FsUnit.Radians, SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.PlaneAltitudeAboveGround, FsUnit.Feet, SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.PlaneAltitude, FsUnit.Feet, SIMCONNECT_DATATYPE.FLOAT64));
definition.Add(new SimProperty(FsSimVar.PlaneHeadingDegreesTrue, FsUnit.Radians, SIMCONNECT_DATATYPE.FLOAT64));

fsConnect.RegisterDataDefinition<PlaneInfoResponse>(Requests.PlaneInfo, definition);
fsConnect.RegisterDataDefinition<PlaneInfoResponse>(Definitions.PlaneInfo, definition);
}

private static void HandleReceivedFsData(object sender, FsDataReceivedEventArgs e)
Expand All @@ -84,9 +174,9 @@ private static void HandleReceivedFsData(object sender, FsDataReceivedEventArgs
{
if(e.RequestId == (uint)Requests.PlaneInfo)
{
PlaneInfoResponse r = (PlaneInfoResponse)e.Data;
_planeInfoResponse = (PlaneInfoResponse)e.Data;

Console.WriteLine($"Pos: ({r.Latitude:F4}, {r.Longitude:F4}), Alt: {r.Altitude:F0} ft, Hdg: {r.Heading:F1} deg");
Console.WriteLine($"Pos: ({FsUtils.Rad2Deg(_planeInfoResponse.Latitude):F4}, {FsUtils.Rad2Deg(_planeInfoResponse.Longitude):F4}), Alt: {_planeInfoResponse.Altitude:F0} ft, Hdg: {FsUtils.Rad2Deg(_planeInfoResponse.Heading):F1} deg");
}
}
catch (Exception ex)
Expand Down
5 changes: 5 additions & 0 deletions src/CTrue.FsConnect.TestConsole/Requests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ public enum Requests
{
PlaneInfo=0
}

public enum Definitions
{
PlaneInfo=0
}
}
12 changes: 11 additions & 1 deletion src/CTrue.FsConnect/CTrue.FsConnect.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ Contains SimConnect binaries, as distributed by the Flight Simulator 20202 SDK 0
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<RepositoryUrl>https://github.com/c-true/FsConnect</RepositoryUrl>
<PackageTags>msfs flight-simulator simconnect</PackageTags>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReleaseNotes>Simplified registering data definitions.
Added support for updating data structures.</PackageReleaseNotes>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>

<ItemGroup>
Expand Down
63 changes: 53 additions & 10 deletions src/CTrue.FsConnect/FsConnect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,16 @@ private set
/// <inheritdoc />
public event EventHandler<FsErrorEventArgs> FsError;

public FsConnect()
{
}

/// <inheritdoc />
public void Connect(string hostName, uint port)
public void Connect(string applicationName, string hostName, uint port)
{
if (applicationName == null) throw new ArgumentNullException(nameof(applicationName));

CreateSimConnectConfigFile(hostName, port);

try
{
_simConnect = new SimConnect("FsConnect", IntPtr.Zero, 0, _simConnectEventHandle, 0);
_simConnect = new SimConnect(applicationName, IntPtr.Zero, 0, _simConnectEventHandle, 0);
}
catch (Exception e)
{
Expand Down Expand Up @@ -110,20 +108,36 @@ public void Disconnect()
}

/// <inheritdoc />
public void RegisterDataDefinition<T>(Enum id, List<Tuple<string, string, SIMCONNECT_DATATYPE>> definition) where T : struct
public void RegisterDataDefinition<T>(Enum id, List<SimProperty> definition) where T : struct
{
foreach (var item in definition)
{
_simConnect.AddToDataDefinition(id, item.Item1, item.Item2, item.Item3, 0.0f, SimConnect.SIMCONNECT_UNUSED);
_simConnect.AddToDataDefinition(id, item.Name, item.Unit, item.DataType, 0.0f, SimConnect.SIMCONNECT_UNUSED);
}

_simConnect.RegisterDataDefineStruct<T>(id);
}

/// <inheritdoc />
public void RequestData(Enum requestId)
{
if (_simConnect != null)
_simConnect.RequestDataOnSimObjectType( requestId, DEFINITIONS.Struct1, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
_simConnect?.RequestDataOnSimObjectType( requestId, DEFINITIONS.Struct1, 0, SIMCONNECT_SIMOBJECT_TYPE.USER);
}

/// <inheritdoc />
public void UpdateData<T>(Enum id, T data)
{
_simConnect?.SetDataOnSimObject(id, SimConnect.SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_DATA_SET_FLAG.DEFAULT, data);
}

/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="duration"></param>
public void SetText(string text, int duration)
{
_simConnect.Text(SIMCONNECT_TEXT_TYPE.PRINT_BLACK, duration, DEFINITIONS.Struct1, text);
}

private void SimConnect_OnRecvOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
Expand Down Expand Up @@ -193,5 +207,34 @@ private void CreateSimConnectConfigFile(string hostName, uint port)
throw new Exception("Could not create SimConnect.cfg file: " + e.Message, e);
}
}

// To detect redundant calls
private bool _disposed = false;

/// <summary>
/// Disconnects and disposes the client.
/// </summary>
public void Dispose() => Dispose(true);

/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}

if (disposing)
{
// Dispose managed state (managed objects).
_simConnect?.Dispose();
}

_disposed = true;
}

}
}
Loading

0 comments on commit da00377

Please sign in to comment.