460 lines
18 KiB
C#
460 lines
18 KiB
C#
using Microsoft.FlightSimulator.SimConnect;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
|
|
namespace DynamicLOD_ResetEdition
|
|
{
|
|
public class MobiSimConnect : IDisposable
|
|
{
|
|
public const string MOBIFLIGHT_CLIENT_DATA_NAME_COMMAND = "MobiFlight.Command";
|
|
public const string MOBIFLIGHT_CLIENT_DATA_NAME_RESPONSE = "MobiFlight.Response";
|
|
public const uint MOBIFLIGHT_MESSAGE_SIZE = 1024;
|
|
|
|
public const uint WM_PILOTSDECK_SIMCONNECT = 0x1989;
|
|
public const string CLIENT_NAME = "DynamicLOD_ResetEdition";
|
|
public const string PILOTSDECK_CLIENT_DATA_NAME_SIMVAR = $"{CLIENT_NAME}.LVars";
|
|
public const string PILOTSDECK_CLIENT_DATA_NAME_COMMAND = $"{CLIENT_NAME}.Command";
|
|
public const string PILOTSDECK_CLIENT_DATA_NAME_RESPONSE = $"{CLIENT_NAME}.Response";
|
|
|
|
protected SimConnect simConnect = null;
|
|
protected IntPtr simConnectHandle = IntPtr.Zero;
|
|
protected Thread simConnectThread = null;
|
|
private static bool cancelThread = false;
|
|
|
|
protected bool isSimConnected = false;
|
|
protected bool isMobiConnected = false;
|
|
protected bool isReceiveRunning = false;
|
|
public bool IsConnected { get { return isSimConnected && isMobiConnected; } }
|
|
public bool IsReady { get { return IsConnected && isReceiveRunning; } }
|
|
|
|
public bool SimIsPaused { get; private set; }
|
|
public bool SimIsRunning { get; private set; }
|
|
private const int fpsLen = 60;
|
|
private float[] fpsStatistic;
|
|
private int fpsIndex;
|
|
|
|
protected uint nextID = 1;
|
|
protected const int reorderTreshold = 150;
|
|
protected Dictionary<string, uint> addressToIndex = new();
|
|
protected Dictionary<uint, float> simVars = new();
|
|
|
|
public MobiSimConnect()
|
|
{
|
|
fpsIndex = -1;
|
|
fpsStatistic = new float[fpsLen];
|
|
}
|
|
|
|
public float GetAverageFPS()
|
|
{
|
|
if (fpsIndex == -1)
|
|
return 0;
|
|
else
|
|
{
|
|
return fpsStatistic.Sum() / fpsLen;
|
|
}
|
|
}
|
|
|
|
public bool Connect()
|
|
{
|
|
try
|
|
{
|
|
if (isSimConnected)
|
|
return true;
|
|
|
|
simConnect = new SimConnect(CLIENT_NAME, simConnectHandle, WM_PILOTSDECK_SIMCONNECT, null, 0);
|
|
simConnect.OnRecvOpen += new SimConnect.RecvOpenEventHandler(SimConnect_OnOpen);
|
|
simConnect.OnRecvQuit += new SimConnect.RecvQuitEventHandler(SimConnect_OnQuit);
|
|
simConnect.OnRecvException += new SimConnect.RecvExceptionEventHandler(SimConnect_OnException);
|
|
|
|
cancelThread = false;
|
|
simConnectThread = new(new ThreadStart(SimConnect_ReceiveThread))
|
|
{
|
|
IsBackground = true
|
|
};
|
|
simConnectHandle = new IntPtr(simConnectThread.ManagedThreadId);
|
|
simConnectThread.Start();
|
|
|
|
Logger.Log(LogLevel.Information, "MobiSimConnect:Connect", $"SimConnect Connection open");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
simConnectThread = null;
|
|
simConnectHandle = IntPtr.Zero;
|
|
cancelThread = true;
|
|
simConnect = null;
|
|
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:Connect", $"Exception while opening SimConnect! (Exception: {ex.GetType()} {ex.Message})");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected void SimConnect_OnOpen(SimConnect sender, SIMCONNECT_RECV_OPEN data)
|
|
{
|
|
try
|
|
{
|
|
isSimConnected = true;
|
|
simConnect.OnRecvClientData += new SimConnect.RecvClientDataEventHandler(SimConnect_OnClientData);
|
|
simConnect.OnRecvEvent += new SimConnect.RecvEventEventHandler(SimConnect_OnReceiveEvent);
|
|
simConnect.OnRecvEventFrame += new SimConnect.RecvEventFrameEventHandler(Simconnect_OnRecvEventFrame);
|
|
CreateDataAreaDefaultChannel();
|
|
CreateEventSubscription();
|
|
Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnOpen", $"SimConnect OnOpen received");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnOpen", $"Exception during SimConnect OnOpen! (Exception: {ex.GetType()} {ex.Message})");
|
|
}
|
|
}
|
|
|
|
protected void SimConnect_ReceiveThread()
|
|
{
|
|
ulong ticks = 0;
|
|
int delay = 100;
|
|
int repeat = 5000 / delay;
|
|
int errors = 0;
|
|
isReceiveRunning = true;
|
|
while (!cancelThread && simConnect != null && isReceiveRunning)
|
|
{
|
|
try
|
|
{
|
|
simConnect.ReceiveMessage();
|
|
|
|
if (isSimConnected && !isMobiConnected && ticks % (ulong)repeat == 0)
|
|
{
|
|
Logger.Log(LogLevel.Debug, "MobiSimConnect:SimConnect_ReceiveThread", $"Sending Ping to MobiFlight WASM Module");
|
|
SendMobiWasmCmd("MF.DummyCmd");
|
|
SendMobiWasmCmd("MF.Ping");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errors++;
|
|
if (errors > 6)
|
|
{
|
|
isReceiveRunning = false;
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_ReceiveThread", $"Maximum Errors reached, closing Receive Thread! (Exception: {ex.GetType()})");
|
|
return;
|
|
}
|
|
}
|
|
Thread.Sleep(delay);
|
|
ticks++;
|
|
}
|
|
isReceiveRunning = false;
|
|
return;
|
|
}
|
|
|
|
protected void CreateEventSubscription()
|
|
{
|
|
//simConnect.MapClientEventToSimEvent(SIM_EVENTS.SIM, "SIM");
|
|
//simConnect.AddClientEventToNotificationGroup(NOTFIY_GROUP.GROUP0, SIM_EVENTS.SIM, false);
|
|
//simConnect.MapClientEventToSimEvent(SIM_EVENTS.PAUSE, "PAUSE");
|
|
//simConnect.AddClientEventToNotificationGroup(NOTFIY_GROUP.GROUP0, SIM_EVENTS.PAUSE, false);
|
|
//simConnect.MapClientEventToSimEvent(SIM_EVENTS.FRAME, "FRAME");
|
|
//simConnect.AddClientEventToNotificationGroup(NOTFIY_GROUP.GROUP0, SIM_EVENTS.FRAME, false);
|
|
simConnect.SubscribeToSystemEvent(SIM_EVENTS.SIM, "sim");
|
|
simConnect.SubscribeToSystemEvent(SIM_EVENTS.PAUSE, "pause");
|
|
simConnect.SubscribeToSystemEvent(SIM_EVENTS.FRAME, "frame");
|
|
}
|
|
|
|
protected void CreateDataAreaDefaultChannel()
|
|
{
|
|
simConnect.MapClientDataNameToID(MOBIFLIGHT_CLIENT_DATA_NAME_COMMAND, MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_CMD);
|
|
|
|
simConnect.MapClientDataNameToID(MOBIFLIGHT_CLIENT_DATA_NAME_RESPONSE, MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE);
|
|
|
|
simConnect.AddToClientDataDefinition((SIMCONNECT_DEFINE_ID)0, 0, MOBIFLIGHT_MESSAGE_SIZE, 0, 0);
|
|
simConnect.RegisterStruct<SIMCONNECT_RECV_CLIENT_DATA, ResponseString>((SIMCONNECT_DEFINE_ID)0);
|
|
simConnect.RequestClientData(MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE,
|
|
(SIMCONNECT_REQUEST_ID)0,
|
|
(SIMCONNECT_DEFINE_ID)0,
|
|
SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET,
|
|
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED,
|
|
0,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
protected void CreateDataAreaClientChannel()
|
|
{
|
|
simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_COMMAND, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD);
|
|
|
|
simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_RESPONSE, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE);
|
|
|
|
simConnect.MapClientDataNameToID(PILOTSDECK_CLIENT_DATA_NAME_SIMVAR, PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_LVARS);
|
|
|
|
simConnect.AddToClientDataDefinition((SIMCONNECT_DEFINE_ID)0, 0, MOBIFLIGHT_MESSAGE_SIZE, 0, 0);
|
|
simConnect.RegisterStruct<SIMCONNECT_RECV_CLIENT_DATA, ResponseString>((SIMCONNECT_DEFINE_ID)0);
|
|
simConnect.RequestClientData(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_RESPONSE,
|
|
(SIMCONNECT_REQUEST_ID)0,
|
|
(SIMCONNECT_DEFINE_ID)0,
|
|
SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET,
|
|
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED,
|
|
0,
|
|
0,
|
|
0);
|
|
}
|
|
|
|
protected void SimConnect_OnClientData(SimConnect sender, SIMCONNECT_RECV_CLIENT_DATA data)
|
|
{
|
|
try
|
|
{
|
|
if (data.dwRequestID == 0)
|
|
{
|
|
var request = (ResponseString)data.dwData[0];
|
|
if (request.Data == "MF.Pong")
|
|
{
|
|
if (!isMobiConnected)
|
|
{
|
|
Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnClientData", $"MobiFlight WASM Ping acknowledged - opening Client Connection");
|
|
SendMobiWasmCmd($"MF.Clients.Add.{CLIENT_NAME}");
|
|
}
|
|
}
|
|
if (request.Data == $"MF.Clients.Add.{CLIENT_NAME}.Finished")
|
|
{
|
|
CreateDataAreaClientChannel();
|
|
isMobiConnected = true;
|
|
SendClientWasmCmd("MF.SimVars.Clear");
|
|
SendClientWasmCmd($"MF.Config.MAX_VARS_PER_FRAME.Set.{ServiceModel.MfLvarsPerFrame}");
|
|
Logger.Log(LogLevel.Information, "MobiSimConnect:SimConnect_OnClientData", $"MobiFlight WASM Client Connection opened");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var simData = (ClientDataValue)data.dwData[0];
|
|
if (simVars.ContainsKey(data.dwRequestID))
|
|
{
|
|
simVars[data.dwRequestID] = simData.data;
|
|
}
|
|
else
|
|
Logger.Log(LogLevel.Warning, "MobiSimConnect:SimConnect_OnClientData", $"The received ID '{data.dwRequestID}' is not subscribed! (Data: {data})");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnClientData", $"Exception during SimConnect OnClientData! (Exception: {ex.GetType()}) (Data: {data})");
|
|
}
|
|
}
|
|
|
|
protected void SimConnect_OnQuit(SimConnect sender, SIMCONNECT_RECV data)
|
|
{
|
|
Disconnect();
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
try
|
|
{
|
|
if (isMobiConnected)
|
|
SendClientWasmCmd("MF.SimVars.Clear");
|
|
|
|
cancelThread = true;
|
|
if (simConnectThread != null)
|
|
{
|
|
simConnectThread.Interrupt();
|
|
simConnectThread.Join(500);
|
|
simConnectThread = null;
|
|
}
|
|
|
|
if (simConnect != null)
|
|
{
|
|
simConnect.Dispose();
|
|
simConnect = null;
|
|
simConnectHandle = IntPtr.Zero;
|
|
}
|
|
|
|
isSimConnected = false;
|
|
isMobiConnected = false;
|
|
|
|
nextID = 1;
|
|
simVars.Clear();
|
|
addressToIndex.Clear();
|
|
Logger.Log(LogLevel.Information, "MobiSimConnect:Disconnect", $"SimConnect Connection closed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:Disconnect", $"Exception during disconnecting from SimConnect! (Exception: {ex.GetType()} {ex.Message})");
|
|
}
|
|
}
|
|
|
|
private void Simconnect_OnRecvEventFrame(SimConnect sender, SIMCONNECT_RECV_EVENT_FRAME recEvent)
|
|
{
|
|
if (SimIsRunning && !SimIsPaused && recEvent != null)
|
|
{
|
|
if (fpsIndex == -1)
|
|
{
|
|
fpsIndex = 1;
|
|
for (int i = 0; i < fpsStatistic.Length; i++)
|
|
{
|
|
fpsStatistic[i] = recEvent.fFrameRate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fpsStatistic[fpsIndex] = recEvent.fFrameRate;
|
|
fpsIndex++;
|
|
if (fpsIndex >= fpsStatistic.Length)
|
|
fpsIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SimConnect_OnReceiveEvent(SimConnect sender, SIMCONNECT_RECV_EVENT recEvent)
|
|
{
|
|
if (recEvent != null)
|
|
{
|
|
if (recEvent.uEventID == (uint)SIM_EVENTS.PAUSE)
|
|
{
|
|
if (recEvent.dwData == 1)
|
|
SimIsPaused = true;
|
|
else
|
|
SimIsPaused = false;
|
|
}
|
|
else if (recEvent.uEventID == (uint)SIM_EVENTS.SIM)
|
|
{
|
|
if (recEvent.dwData == 1)
|
|
SimIsRunning = true;
|
|
else
|
|
SimIsRunning = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Disconnect();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private void SendClientWasmCmd(string command)
|
|
{
|
|
SendWasmCmd(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, command);
|
|
}
|
|
|
|
private void SendClientWasmDummyCmd()
|
|
{
|
|
SendWasmCmd(PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, "MF.DummyCmd");
|
|
}
|
|
|
|
private void SendMobiWasmCmd(string command)
|
|
{
|
|
SendWasmCmd(MOBIFLIGHT_CLIENT_DATA_ID.MOBIFLIGHT_CMD, (MOBIFLIGHT_CLIENT_DATA_ID)0, command);
|
|
}
|
|
|
|
private void SendWasmCmd(Enum cmdChannelId, Enum cmdId, string command)
|
|
{
|
|
simConnect.SetClientData(cmdChannelId, cmdId, SIMCONNECT_CLIENT_DATA_SET_FLAG.DEFAULT, 0, new ClientDataString(command));
|
|
}
|
|
|
|
protected void SimConnect_OnException(SimConnect sender, SIMCONNECT_RECV_EXCEPTION data)
|
|
{
|
|
if (data.dwException != 3 && data.dwException != 29)
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:SimConnect_OnException", $"Exception received: (Exception: {data.dwException})");
|
|
}
|
|
|
|
public void SubscribeLvar(string address)
|
|
{
|
|
SubscribeVariable($"(L:{address})");
|
|
}
|
|
|
|
public void SubscribeSimVar(string name, string unit)
|
|
{
|
|
SubscribeVariable($"(A:{name}, {unit})");
|
|
}
|
|
|
|
protected void SubscribeVariable(string address)
|
|
{
|
|
try
|
|
{
|
|
if (!addressToIndex.ContainsKey(address))
|
|
{
|
|
RegisterVariable(nextID, address);
|
|
simVars.Add(nextID, 0.0f);
|
|
addressToIndex.Add(address, nextID);
|
|
|
|
nextID++;
|
|
}
|
|
else
|
|
Logger.Log(LogLevel.Warning, "MobiSimConnect:SubscribeAddress", $"The Address '{address}' is already subscribed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:SubscribeAddress", $"Exception while subscribing SimVar '{address}'! (Exception: {ex.GetType()}) (Message: {ex.Message})");
|
|
}
|
|
}
|
|
|
|
protected void RegisterVariable(uint ID, string address)
|
|
{
|
|
simConnect.AddToClientDataDefinition(
|
|
(SIMCONNECT_DEFINE_ID)ID,
|
|
(ID - 1) * sizeof(float),
|
|
sizeof(float),
|
|
0,
|
|
0);
|
|
|
|
simConnect?.RegisterStruct<SIMCONNECT_RECV_CLIENT_DATA, ClientDataValue>((SIMCONNECT_DEFINE_ID)ID);
|
|
|
|
simConnect?.RequestClientData(
|
|
PILOTSDECK_CLIENT_DATA_ID.MOBIFLIGHT_LVARS,
|
|
(SIMCONNECT_REQUEST_ID)ID,
|
|
(SIMCONNECT_DEFINE_ID)ID,
|
|
SIMCONNECT_CLIENT_DATA_PERIOD.ON_SET,
|
|
SIMCONNECT_CLIENT_DATA_REQUEST_FLAG.CHANGED,
|
|
0,
|
|
0,
|
|
0
|
|
);
|
|
|
|
SendClientWasmCmd($"MF.SimVars.Add.{address}");
|
|
}
|
|
|
|
public void UnsubscribeAll()
|
|
{
|
|
try
|
|
{
|
|
SendClientWasmCmd("MF.SimVars.Clear");
|
|
nextID = 1;
|
|
simVars.Clear();
|
|
addressToIndex.Clear();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.Log(LogLevel.Error, "MobiSimConnect:UnsubscribeAll", $"Exception while unsubscribing SimVars! (Exception: {ex.GetType()}) (Message: {ex.Message})");
|
|
}
|
|
}
|
|
|
|
public float ReadLvar(string address)
|
|
{
|
|
if (addressToIndex.TryGetValue($"(L:{address})", out uint index) && simVars.TryGetValue(index, out float value))
|
|
return value;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
public float ReadSimVar(string name, string unit)
|
|
{
|
|
string address = $"(A:{name}, {unit})";
|
|
if (addressToIndex.TryGetValue(address, out uint index) && simVars.TryGetValue(index, out float value))
|
|
return value;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
public void WriteLvar(string address, float value)
|
|
{
|
|
SendClientWasmCmd($"MF.SimVars.Set.{string.Format(new CultureInfo("en-US").NumberFormat, "{0:G}", value)} (>L:{address})");
|
|
SendClientWasmDummyCmd();
|
|
}
|
|
|
|
public void ExecuteCode(string code)
|
|
{
|
|
SendClientWasmCmd($"MF.SimVars.Set.{code}");
|
|
SendClientWasmDummyCmd();
|
|
}
|
|
}
|
|
}
|