2022-07-23 19:23:32 +00:00
using Microsoft.FlightSimulator.SimConnect ;
using MSFSPopoutPanelManager.Shared ;
using System ;
using System.Collections.Generic ;
using System.Diagnostics ;
2023-07-12 22:41:31 +00:00
using System.Reflection ;
2022-08-13 06:14:49 +00:00
using System.Threading.Tasks ;
2022-07-23 19:23:32 +00:00
using System.Timers ;
2023-07-30 21:30:47 +00:00
using static MSFSPopoutPanelManager . SimConnectAgent . SimDataDefinitions ;
2022-07-23 19:23:32 +00:00
namespace MSFSPopoutPanelManager.SimConnectAgent
{
public class SimConnector
{
private const int MSFS_CONNECTION_RETRY_TIMEOUT = 2000 ;
2023-07-12 22:41:31 +00:00
private const uint WM_USER_SIMCONNECT = 0x0402 ;
2022-07-23 19:23:32 +00:00
private SimConnect _simConnect ;
private Timer _connectionTimer ;
2022-08-13 06:14:49 +00:00
private bool _isDisabledReconnect ;
2022-07-23 19:23:32 +00:00
2023-07-12 22:41:31 +00:00
private List < SimConnectDataDefinition > _simConnectRequiredDataDefinitions ;
private List < SimConnectDataDefinition > _simConnectHudBarDataDefinitions ;
private FieldInfo [ ] _simConnectStructFields ;
2022-07-23 19:23:32 +00:00
public event EventHandler < string > OnException ;
2023-07-12 22:41:31 +00:00
public event EventHandler < List < SimDataItem > > OnReceivedRequiredData ;
public event EventHandler < List < SimDataItem > > OnReceivedHudBarData ;
2022-07-23 19:23:32 +00:00
public event EventHandler OnConnected ;
public event EventHandler OnDisconnected ;
2023-07-12 22:41:31 +00:00
public event EventHandler < SimConnectEvent > OnReceiveSystemEvent ;
public event EventHandler < string > OnActiveAircraftChanged ;
2022-07-23 19:23:32 +00:00
public bool Connected { get ; set ; }
2023-07-12 22:41:31 +00:00
public SimConnector ( )
{
_simConnectStructFields = typeof ( SimConnectStruct ) . GetFields ( BindingFlags . Public | BindingFlags . Instance ) ;
_simConnectRequiredDataDefinitions = SimDataDefinitions . GetRequiredDefinitions ( ) ;
}
2022-07-23 19:23:32 +00:00
public void Start ( )
{
_connectionTimer = new Timer ( ) ;
_connectionTimer . Interval = MSFS_CONNECTION_RETRY_TIMEOUT ;
_connectionTimer . Enabled = true ;
_connectionTimer . Elapsed + = ( source , e ) = >
{
try
{
2023-07-12 22:41:31 +00:00
Debug . WriteLine ( "Connecting to MSFS..." ) ;
2022-07-23 19:23:32 +00:00
InitializeSimConnect ( ) ;
}
catch
{
// handle SimConnect instantiation error when MSFS is not connected
}
} ;
}
public void Restart ( )
{
_connectionTimer . Enabled = true ;
}
2023-07-12 22:41:31 +00:00
public void SetSimConnectHudBarDataDefinition ( SimDataDefinitionType definitionType )
{
_simConnectHudBarDataDefinitions = SimDataDefinitions . GetHudBarDefinitions ( definitionType ) ;
AddHudBarDataDefinitions ( ) ;
}
2022-07-23 19:23:32 +00:00
private void HandleOnRecvOpen ( SimConnect sender , SIMCONNECT_RECV_OPEN data )
{
ReceiveMessage ( ) ;
}
public void StopAndReconnect ( )
{
2023-07-12 22:41:31 +00:00
if ( ! _isDisabledReconnect )
{
Stop ( ) ;
InitializeSimConnect ( ) ;
}
2022-07-23 19:23:32 +00:00
}
public void Stop ( )
{
if ( _simConnect ! = null )
{
// Dispose serves the same purpose as SimConnect_Close()
2023-07-12 22:41:31 +00:00
//try { _simConnect.Dispose(); } catch { }
2022-07-23 19:23:32 +00:00
_simConnect = null ;
}
Connected = false ;
}
2023-07-12 22:41:31 +00:00
public void RequestRequiredData ( )
2022-07-23 19:23:32 +00:00
{
if ( _simConnect = = null | | ! Connected )
return ;
2023-07-12 22:41:31 +00:00
try
{
_simConnect . RequestDataOnSimObjectType ( DATA_REQUEST . REQUIRED_REQUEST , DATA_DEFINITION . REQUIRED_DEFINITION , 0 , SIMCONNECT_SIMOBJECT_TYPE . USER ) ;
}
catch
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
if ( ! _isDisabledReconnect )
_isDisabledReconnect = true ;
OnException ? . Invoke ( this , null ) ;
}
}
public void RequestHudBarData ( )
{
if ( _simConnect = = null | | ! Connected )
return ;
try
{
if ( _simConnectHudBarDataDefinitions ! = null )
_simConnect . RequestDataOnSimObjectType ( DATA_REQUEST . HUDBAR_REQUEST , DATA_DEFINITION . HUDBAR_DEFINITION , 0 , SIMCONNECT_SIMOBJECT_TYPE . USER ) ;
}
catch
{
if ( ! _isDisabledReconnect )
_isDisabledReconnect = true ;
OnException ? . Invoke ( this , null ) ;
2022-07-23 19:23:32 +00:00
}
}
public void ReceiveMessage ( )
{
if ( _simConnect = = null )
return ;
try
{
2022-08-13 06:14:49 +00:00
if ( ! _isDisabledReconnect )
_simConnect . ReceiveMessage ( ) ;
2022-07-23 19:23:32 +00:00
}
catch ( Exception ex )
{
if ( ex . Message ! = "0xC00000B0" )
{
FileLogger . WriteLog ( $"SimConnector: SimConnect receive message exception - {ex.Message}" , StatusMessageType . Error ) ;
2022-08-13 06:14:49 +00:00
}
2022-07-23 19:23:32 +00:00
2022-08-13 06:14:49 +00:00
if ( ! _isDisabledReconnect )
{
// Prevent multiple reconnects from running
_isDisabledReconnect = true ;
2023-07-12 22:41:31 +00:00
OnException ? . Invoke ( this , null ) ;
2022-07-23 19:23:32 +00:00
}
}
}
public void TransmitActionEvent ( ActionEvent eventID , uint data )
{
if ( _simConnect ! = null )
{
try
{
_simConnect . TransmitClientEvent ( 0 U , eventID , data , NotificationGroup . GROUP0 , SIMCONNECT_EVENT_FLAG . GROUPID_IS_PRIORITY ) ;
2023-07-12 22:41:31 +00:00
System . Threading . Thread . Sleep ( 200 ) ;
2022-07-23 19:23:32 +00:00
}
catch ( Exception ex )
{
var eventName = eventID . ToString ( ) [ 4. . ] ; // trim out KEY_ prefix
FileLogger . WriteLog ( $"SimConnector: SimConnect transmit event exception - EventName: {eventName} - {ex.Message}" , StatusMessageType . Error ) ;
}
}
}
2023-07-30 21:30:47 +00:00
public void SetDataObject ( WriteableVariableName propName , object dValue )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
try
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
var dataStruct = new WriteableDataStruct ( ) ;
dataStruct . Prop0 = ( double ) dValue ;
2023-07-30 21:30:47 +00:00
switch ( propName )
{
case WriteableVariableName . TrackIREnable :
_simConnect . SetDataOnSimObject ( DATA_DEFINITION . WRITEABLE_TRACKIR_DEFINITION , SimConnect . SIMCONNECT_OBJECT_ID_USER , SIMCONNECT_DATA_SET_FLAG . DEFAULT , dataStruct ) ;
break ;
case WriteableVariableName . CockpitCameraZoom :
_simConnect . SetDataOnSimObject ( DATA_DEFINITION . WRITEABLE_COCKPITCAMERAZOOM_DEFINITION , SimConnect . SIMCONNECT_OBJECT_ID_USER , SIMCONNECT_DATA_SET_FLAG . DEFAULT , dataStruct ) ;
break ;
2023-08-16 03:41:14 +00:00
case WriteableVariableName . CameraRequestAction :
_simConnect . SetDataOnSimObject ( DATA_DEFINITION . WRITEABLE_CAMERAREQUESTACTION_DEFINITION , SimConnect . SIMCONNECT_OBJECT_ID_USER , SIMCONNECT_DATA_SET_FLAG . DEFAULT , dataStruct ) ;
break ;
2023-07-30 21:30:47 +00:00
}
2023-07-12 22:41:31 +00:00
}
catch ( Exception ex )
{
FileLogger . WriteLog ( $"SimConnector: Unable to set SimConnect variable - {ex.Message}" , StatusMessageType . Error ) ;
2022-07-23 19:23:32 +00:00
}
}
private void InitializeSimConnect ( )
{
2023-07-12 22:41:31 +00:00
Debug . WriteLine ( "Trying to start simConnect" ) ;
2022-07-23 19:23:32 +00:00
_simConnect = new SimConnect ( "MSFS Pop Out Panel Manager" , Process . GetCurrentProcess ( ) . MainWindowHandle , WM_USER_SIMCONNECT , null , 0 ) ;
2023-07-12 22:41:31 +00:00
Debug . WriteLine ( "SimConnect started" ) ;
2022-07-23 19:23:32 +00:00
_connectionTimer . Enabled = false ;
// Listen to connect and quit msgs
_simConnect . OnRecvOpen + = HandleOnRecvOpen ;
_simConnect . OnRecvQuit + = HandleOnRecvQuit ;
_simConnect . OnRecvException + = HandleOnRecvException ;
_simConnect . OnRecvEvent + = HandleOnReceiveSystemEvent ;
_simConnect . OnRecvSimobjectDataBytype + = HandleOnRecvSimobjectDataBytype ;
2022-08-01 23:21:42 +00:00
_simConnect . OnRecvEventFilename + = HandleOnRecvEventFilename ;
2022-08-02 15:51:28 +00:00
_simConnect . OnRecvSystemState + = HandleOnRecvSystemState ;
2022-07-23 19:23:32 +00:00
// Register simConnect system events
2023-07-12 22:41:31 +00:00
_simConnect . UnsubscribeFromSystemEvent ( SimConnectEvent . SIMSTART ) ;
_simConnect . SubscribeToSystemEvent ( SimConnectEvent . SIMSTART , "SimStart" ) ;
_simConnect . UnsubscribeFromSystemEvent ( SimConnectEvent . SIMSTOP ) ;
_simConnect . SubscribeToSystemEvent ( SimConnectEvent . SIMSTOP , "SimStop" ) ;
_simConnect . UnsubscribeFromSystemEvent ( SimConnectEvent . VIEW ) ;
_simConnect . SubscribeToSystemEvent ( SimConnectEvent . VIEW , "View" ) ;
_simConnect . UnsubscribeFromSystemEvent ( SimConnectEvent . AIRCRAFTLOADED ) ;
_simConnect . SubscribeToSystemEvent ( SimConnectEvent . AIRCRAFTLOADED , "AircraftLoaded" ) ;
2022-07-23 19:23:32 +00:00
2023-07-12 22:41:31 +00:00
AddRequiredDataDefinitions ( ) ;
SetupActionEvents ( ) ;
2022-07-23 19:23:32 +00:00
2022-08-02 15:51:28 +00:00
_simConnect . RequestSystemState ( SystemStateRequestId . AIRCRAFTPATH , "AircraftLoaded" ) ;
2022-08-13 06:14:49 +00:00
_isDisabledReconnect = false ;
Task . Run ( ( ) = >
{
for ( var i = 0 ; i < 5 ; i + + )
{
System . Threading . Thread . Sleep ( 1000 ) ;
ReceiveMessage ( ) ;
}
} ) ;
2022-07-23 19:23:32 +00:00
OnConnected ? . Invoke ( this , null ) ;
2022-08-13 06:14:49 +00:00
Connected = true ;
2022-07-23 19:23:32 +00:00
}
2022-08-02 15:51:28 +00:00
private void HandleOnRecvSystemState ( SimConnect sender , SIMCONNECT_RECV_SYSTEM_STATE data )
{
switch ( ( SystemStateRequestId ) Enum . Parse ( typeof ( SystemStateRequestId ) , data . dwRequestID . ToString ( ) ) )
{
case SystemStateRequestId . AIRCRAFTPATH :
SetActiveAircraftTitle ( data . szString ) ;
break ;
}
}
2022-08-01 23:21:42 +00:00
private void HandleOnRecvEventFilename ( SimConnect sender , SIMCONNECT_RECV_EVENT_FILENAME data )
{
switch ( data . uEventID )
{
2023-07-12 22:41:31 +00:00
case ( uint ) SimConnectEvent . AIRCRAFTLOADED :
2022-08-02 15:51:28 +00:00
SetActiveAircraftTitle ( data . szFileName ) ;
2022-08-01 23:21:42 +00:00
break ;
}
}
2023-07-12 22:41:31 +00:00
private void SetupActionEvents ( )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
// Setup SimEvent mapping
//_simConnect.SetInputGroupPriority(NotificationGroup.GROUP0, (uint)SIMCONNECT_EVENT_FLAG.GROUPID_IS_PRIORITY);
foreach ( ActionEvent item in Enum . GetValues ( typeof ( ActionEvent ) ) )
{
_simConnect . MapClientEventToSimEvent ( item , item . ToString ( ) ) ;
//_simConnect.AddClientEventToNotificationGroup(NotificationGroup.GROUP0, item, true);
}
}
2022-07-23 19:23:32 +00:00
2023-07-12 22:41:31 +00:00
private void AddRequiredDataDefinitions ( )
{
if ( _simConnect = = null | | _simConnectRequiredDataDefinitions = = null )
return ;
2022-07-23 19:23:32 +00:00
2023-07-12 22:41:31 +00:00
foreach ( var definition in _simConnectRequiredDataDefinitions )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
if ( definition . DefinitionId = = DATA_DEFINITION . REQUIRED_DEFINITION & & definition . DataDefinitionType = = DataDefinitionType . SimConnect )
2022-07-23 19:23:32 +00:00
{
2022-08-01 23:21:42 +00:00
SIMCONNECT_DATATYPE simmConnectDataType ;
switch ( definition . DataType )
{
case DataType . String :
simmConnectDataType = SIMCONNECT_DATATYPE . STRING256 ;
break ;
case DataType . Float64 :
simmConnectDataType = SIMCONNECT_DATATYPE . FLOAT64 ;
break ;
default :
simmConnectDataType = SIMCONNECT_DATATYPE . FLOAT64 ;
break ;
}
2023-07-12 22:41:31 +00:00
_simConnect . AddToDataDefinition ( definition . DefinitionId , definition . VariableName , definition . SimConnectUnit , simmConnectDataType , 0.0f , SimConnect . SIMCONNECT_UNUSED ) ;
}
}
2022-08-01 23:21:42 +00:00
2023-07-30 21:30:47 +00:00
_simConnect . AddToDataDefinition ( DATA_DEFINITION . WRITEABLE_TRACKIR_DEFINITION , "TRACK IR ENABLE" , "bool" , SIMCONNECT_DATATYPE . FLOAT64 , 0.0f , SimConnect . SIMCONNECT_UNUSED ) ;
_simConnect . AddToDataDefinition ( DATA_DEFINITION . WRITEABLE_COCKPITCAMERAZOOM_DEFINITION , "COCKPIT CAMERA ZOOM" , "percentage" , SIMCONNECT_DATATYPE . FLOAT64 , 0.0f , SimConnect . SIMCONNECT_UNUSED ) ;
2023-08-16 03:41:14 +00:00
_simConnect . AddToDataDefinition ( DATA_DEFINITION . WRITEABLE_CAMERAREQUESTACTION_DEFINITION , "CAMERA REQUEST ACTION" , "enum" , SIMCONNECT_DATATYPE . FLOAT64 , 0.0f , SimConnect . SIMCONNECT_UNUSED ) ;
2022-08-01 23:21:42 +00:00
2023-07-12 22:41:31 +00:00
_simConnect . RegisterDataDefineStruct < SimConnectStruct > ( DATA_DEFINITION . REQUIRED_DEFINITION ) ;
2023-07-30 21:30:47 +00:00
_simConnect . RegisterDataDefineStruct < SimConnectStruct > ( DATA_DEFINITION . WRITEABLE_TRACKIR_DEFINITION ) ;
_simConnect . RegisterDataDefineStruct < SimConnectStruct > ( DATA_DEFINITION . WRITEABLE_COCKPITCAMERAZOOM_DEFINITION ) ;
2023-07-12 22:41:31 +00:00
}
2022-08-01 23:21:42 +00:00
2023-07-12 22:41:31 +00:00
private void AddHudBarDataDefinitions ( )
{
if ( _simConnect = = null )
return ;
2022-07-23 19:23:32 +00:00
2023-07-12 22:41:31 +00:00
_simConnect . ClearDataDefinition ( DATA_DEFINITION . HUDBAR_DEFINITION ) ;
if ( _simConnectHudBarDataDefinitions = = null )
return ;
foreach ( var definition in _simConnectHudBarDataDefinitions )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
if ( definition . DefinitionId = = DATA_DEFINITION . HUDBAR_DEFINITION & & definition . DataDefinitionType = = DataDefinitionType . SimConnect )
{
SIMCONNECT_DATATYPE simmConnectDataType ;
switch ( definition . DataType )
{
case DataType . String :
simmConnectDataType = SIMCONNECT_DATATYPE . STRING256 ;
break ;
case DataType . Float64 :
simmConnectDataType = SIMCONNECT_DATATYPE . FLOAT64 ;
break ;
default :
simmConnectDataType = SIMCONNECT_DATATYPE . FLOAT64 ;
break ;
}
_simConnect . AddToDataDefinition ( definition . DefinitionId , definition . VariableName , definition . SimConnectUnit , simmConnectDataType , 0.0f , SimConnect . SIMCONNECT_UNUSED ) ;
}
2022-07-23 19:23:32 +00:00
}
2023-07-12 22:41:31 +00:00
_simConnect . RegisterDataDefineStruct < SimConnectStruct > ( DATA_DEFINITION . HUDBAR_DEFINITION ) ;
2022-07-23 19:23:32 +00:00
}
private void HandleOnRecvQuit ( SimConnect sender , SIMCONNECT_RECV data )
{
Stop ( ) ;
OnDisconnected ? . Invoke ( this , null ) ;
}
private void HandleOnRecvException ( SimConnect sender , SIMCONNECT_RECV_EXCEPTION data )
{
SIMCONNECT_EXCEPTION e = ( SIMCONNECT_EXCEPTION ) data . dwException ;
switch ( e )
{
case SIMCONNECT_EXCEPTION . DATA_ERROR :
case SIMCONNECT_EXCEPTION . NAME_UNRECOGNIZED :
case SIMCONNECT_EXCEPTION . ALREADY_CREATED :
case SIMCONNECT_EXCEPTION . UNRECOGNIZED_ID :
case SIMCONNECT_EXCEPTION . EVENT_ID_DUPLICATE :
break ;
default :
OnException ? . Invoke ( this , $"SimConnector: SimConnect on recieve exception - {e}" ) ;
break ;
}
}
2023-07-12 22:41:31 +00:00
private void HandleOnReceiveSystemEvent ( SimConnect sender , SIMCONNECT_RECV_EVENT data )
{
var systemEvent = ( ( SimConnectEvent ) data . uEventID ) ;
OnReceiveSystemEvent ? . Invoke ( this , systemEvent ) ;
}
2022-07-23 19:23:32 +00:00
private void HandleOnRecvSimobjectDataBytype ( SimConnect sender , SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data )
{
2023-07-12 22:41:31 +00:00
if ( _simConnect = = null | | ! Connected )
return ;
if ( data . dwRequestID = = ( int ) DATA_REQUEST . REQUIRED_REQUEST )
ParseRequiredReceivedSimData ( data ) ;
else if ( data . dwRequestID = = ( int ) DATA_REQUEST . HUDBAR_REQUEST )
ParseHudBarReceivedSimData ( data ) ;
}
private void ParseRequiredReceivedSimData ( SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data )
{
if ( _simConnectRequiredDataDefinitions = = null )
2022-07-23 19:23:32 +00:00
return ;
try
{
2023-07-12 22:41:31 +00:00
var simData = new List < SimDataItem > ( ) ;
var simDataStruct = ( SimConnectStruct ) data . dwData [ 0 ] ;
int i = 0 ;
foreach ( var definition in _simConnectRequiredDataDefinitions )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
if ( definition . DataDefinitionType = = DataDefinitionType . SimConnect )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
simData . Add ( new SimDataItem { PropertyName = definition . PropName , Value = ( double ) _simConnectStructFields [ i ] . GetValue ( simDataStruct ) } ) ;
i + + ;
2022-07-23 19:23:32 +00:00
}
}
2023-07-12 22:41:31 +00:00
OnReceivedRequiredData ? . Invoke ( this , simData ) ;
2022-07-23 19:23:32 +00:00
}
catch ( Exception ex )
{
2023-07-12 22:41:31 +00:00
FileLogger . WriteException ( $"SimConnector: SimConnect received required data exception - {ex.Message}" , ex ) ;
2022-07-23 19:23:32 +00:00
}
}
2023-07-12 22:41:31 +00:00
private void ParseHudBarReceivedSimData ( SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE data )
2022-07-23 19:23:32 +00:00
{
2023-07-12 22:41:31 +00:00
try
{
if ( _simConnectHudBarDataDefinitions = = null )
return ;
var simData = new List < SimDataItem > ( ) ;
var simDataStruct = ( SimConnectStruct ) data . dwData [ 0 ] ;
int i = 0 ;
lock ( _simConnectHudBarDataDefinitions )
{
foreach ( var definition in _simConnectHudBarDataDefinitions )
{
if ( definition . DataDefinitionType = = DataDefinitionType . SimConnect )
{
simData . Add ( new SimDataItem { PropertyName = definition . PropName , Value = ( double ) _simConnectStructFields [ i ] . GetValue ( simDataStruct ) } ) ;
i + + ;
}
}
}
OnReceivedHudBarData ? . Invoke ( this , simData ) ;
}
catch ( Exception ex )
{
FileLogger . WriteException ( $"SimConnector: SimConnect received hud bar data exception - {ex.Message}" , ex ) ;
StopAndReconnect ( ) ;
}
2022-07-23 19:23:32 +00:00
}
2022-08-02 15:51:28 +00:00
private void SetActiveAircraftTitle ( string aircraftFilePath )
{
var filePathToken = aircraftFilePath . Split ( @"\" ) ;
2022-08-13 06:14:49 +00:00
if ( filePathToken . Length > 1 )
{
var aircraftName = filePathToken [ filePathToken . Length - 2 ] ;
aircraftName = aircraftName . Replace ( "_" , " " ) . ToUpper ( ) ;
2023-07-12 22:41:31 +00:00
OnActiveAircraftChanged ? . Invoke ( this , aircraftName ) ;
2022-08-13 06:14:49 +00:00
}
2022-08-02 15:51:28 +00:00
}
2022-07-23 19:23:32 +00:00
}
}