Post on 11-Feb-2016
description
N0800
Follow
This Plane
SimConnect
Tutorial
For Flight
Simulator X
---------------
How to fl y
AI Aircrafts
Using
Waypoints
in FSX SDK
---------------
N0800 Tutorial
What is this tutorial for? The tutorial is to understand how we send nearby waypoints
to a second aircraft, to fl y it smoothly towards the waypoints. We’ll also drop a parachute
at the location of the waypoints, to visualize where is the second aircraft fl ying to. In the
picture below, our aircraft is on top and the second aircraft is controlled by our SimConnect
program. The console output of the program shows what we are doing.
What do you need to complete this tutorial? MS FSX SDK, which is on the CD
of “MS FSX Professional”. A compiler, which can be MS Visual Studio C++, C# or VB,
which is available for free at Microsoft.
SimConnect Overview
SimConnect is a gateway that allows external programs to interact with FSX.
SimConnect is part of FSX SDK. So you need to install the SDK if you want to use
SimConnect. It displays itself as a server and the external programs are clients.
SimConnect allows the client to receive information about FSX simulation. The
client may ask details of the current state of the simulation, for instance which objects,
like aircrafts, are currently simulated; or what are the parameters, like the altitude, of
these objects.
An asynchronous request / reply mechanism is used to obtain data. The client
usually sends a request to SimConnect, SimConnect replies to this request later. The client
hasn’t to wait for the reply, it can perform other tasks in the meantime. When SimConnect
is ready to reply, it sends an event message to the client, using a messaging system.
The client needs to receive all event messages, and dispatch them according
to their nature. To do that it monitors continuously for new SimConnect messages thru
an endless loop. When a message is available, the loop transfers the control to a callback
routine designed to handle these messages in a specifi c way.
The callback procedure is the core of the client design. It identifi es the type of
message received, and execute appropriate calls so that the application is aware of replies
to previous requests. Basically a SimConnect client application is a program that sends
requests to SimConnect and manage replies in an asynchronous way in the callback
procedure.
The client may also “subscribe for events notifi cation”. It informs SimConnect
of its interest in being informed about changes, e.g. the simulation being paused, or
an object being removed from the simulation. When such event occurs, SimConnect
just sends an event message to be received and processed by the client in the callback
procedure.
SimConnect allows the client to change the status of the simulation. The client
can move objects, create new ones, control cameras, modify FSX menus, display dialog
boxes, etc. To do that, the client sends data, aimed to some object in the simulation.
SimConnect receives these data asynchronously too. It processes them when possible, and
according to some priority set by the client.
SimConnect manages multiple clients. Clients may reside on the same computer or
not. The connection between the server and the clients relies on IP streams and pipes,
but these details are mostly transparent to the clients programmers. The SimConnect
API manages the communication when data transfers are needed. Clients may ask other
clients to be notifi ed of what they send to SimConnect. The FSX simulation engine by itself
is also a SimConnect client. All messages inbound and outbound are processed by the
server based on priorities that may be assigned by clients. The fi nal order for processing
messages of identical priority is managed by SimConnect.
SimConnect Documentation
The main documentation for SimConnect use is the Help fi le included in FSX SDK.
Unfortunately this documentation is disappointing, it is not at all aimed to start
programming SimConnect applications, it is just the raw documentation of the
SimConnect API, in alphabetical order of the functions. In addition, it is defi netely C++
oriented, and leaves the C# or VB programmer with additional diffi culties related to calling
Program S ta rt
C ontinue = true
S im C onnect_C a llD isp atc h
(M yD is patc hP ro c )
S leep()
C ontinue = = true
void C A LL B A C K
M yD isp atch P ro c(S IM C O N N E C T_ R EC V* pD ata)
sw itch(pD ata->dw ID )
{
case S IM CON N EC T_R EC V_ID_EXC EPTION :
…
break;
case S IM CON N EC T_R EC V_ID_EVEN T:
…
break;
case S IM CON N EC T_R EC V_ID_EVEN T_O BJECT_ADD R EM O VE:
…
break;
case S IM CON N EC T_R EC V_ID_SIM O BJEC T_D ATA:
…
break;
case …
Program End R outine End
the C API from a non C application. Additional mechanisms are needed to check / convert
/ validate data types being sent to SimConnect by the client (MS calls that “marshalling”).
Let’s see with this C portion of code:
struct Struct1
{
char* title;
double latitude;
double longitude;
double altitude;
};
In C# you’ll have to write your code this way:
[StructLayout(LayoutKind.Sequential,
CharSet = CharSet.Ansi, Pack = 1)]
struct Struct1
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public String title;
public double latitude;
public double longitude;
public double altitude;
};
In the SDK documentation you’ll fi nd a small set of examples in C# and VB. However all
the API is described using C approach. So be prepared to face additional challenges if you
have or want to use compilers producing “managed code” for the .NET framework.
When it comes to searching the internet for answers not found in the SDK documentation,
you’ll also feel that there is a very limited number of urls reported by your prefered search
engine. Most of them point to MSDN. Many of those pages will discuss something not
working as it should, or will ask for explanations about how things should be done. You
may end thinking this is just emphasizing the fact that there are so many functions in
the API, and so little overview on how to use them. And no SimConnect Primer available
so far. All things considered, you got FSX SDK in FSX professional for a mere 10 bucks
compared to the standard version. That’s OK!
By the way, after MS realized Flight Simulator was a valuable platform for simulation
of any kind, and could be a gold mine in the serious companies sectors, they started
working on the generic simulation engine “Enterprise Simulation Platform”. ESP has now
several versions, but for the time being the documentation related to ESP can be used for
SimConnect in FSX. This is what you’ll fi nd on MSDN.
If you start programming for SimConnect, bear in mind you are tackling new frontiers on
your own. No “beam me up, Scotty!”. So... how do you start programming SimConnect
clients?
IDE and programming language
For the IDE, you’ll probably end up with Visual Studio, which comes for free in
its Express edition (well... you’re pestered to register after a month, so you’ll certainly pay
some price in the end). I don’t know about using Eclipse, which I like defi nitely more than
VSE. I started with VSE to avoid additional diffi culties to code the examples found in the
documentation.
Which language to use for coding SimConnect clients? You may use C/C++ or
MS CLI managed languages (C# , VB, VJ). If you are effi cient with C/C++, you’ll be safer
than with the others. There is also an independent implementation of the SimConnect API
in Java (jSimConnect) with maybe the same drawbacks than MS managed code, but it
could be worth a try.
For Kaspersky customers who want to install Visual Studio. MS has an article
about incompatibility between KAV and VS and suggests installing with KAV inactive. I
had not this problem, however, after I installed VSE, KAV started blocking web pages with
embedded scripts. KAV is now complaining to be unable to load VBScript.dll. Kaspersky
support is leading nowhere. Once this problem existed with leftovers after NAV uninstall,
so the only suggestion they provide is to uninstall NAV properly whatever your problem
is. My KAV licence is expiring in a month, so I’ll live with that until then and uninstall KAV
(properly).
What you need to code a SimConnect Client (with C++)
You need SimConnect.h and SimConnect.lib. The overview of the “SimConnect
SDK Reference” (the SDK help fi le) explains how to to set up your IDE correctly.
For this tutorial, all the code is in the same source fi le. The project is created from the
Win32 Console Application template that comes with VS. The output of the printf calls will
be displayed in the DOS-like window. This is a nice way to debug asynchronous calls.
Of course, you need to install the FSX SDK if you didn’t install it with FSX initially.
By creating a SimConnect.ini fi le in your My Documents\Flight Simulator X
Files folder you can also enable a debug window in FSX. This is briefl y explained in the
help fi le. You can copy here the default ini fi le found in the SDK folder. No need to create
one from scratch.
At this time, you now have the blue part of this diagram (1, 2 and 3):
S im C onnect
A P I H eader
V isua l
C ++
C ++
S ource
FS X
S im ula tion
E ngine
S im C onnect
S erver
S im C onnect
A pplica tion (C lient)
S im C onnect
A P I L ibrary
FS X U ser
FS X
S im ula tion
E ngine
S im C onnect
S erver
S im C onnect
A pplica tion (C lient)
S im C onnect
A P I L ibrary
FS X U ser
C om pile T im e
R un T im e
2
3 1
4
5 7
8
9106
The SimConnect API header (3) contains all the declarations needed to work with the
API (6). After you compile your code and link it (4) with the API library, you can run the
executable client (5). This is how it will work:
(5) the client opens a connection with the server (7). When the connection request has
been processed successfully, the server post a message (10) to the client to inform it. The
client can now send requests and data to the server.
To answer such request the server (7) may need to talk with FSX (9).
Conversely FSX (9) will have to inform the server (7) about events occuring in the
simulation. Such events may need to be reported by the server to all or some of its clients
(5).
On his side, the user (8) is controlling FSX. If a client wants to be informed when some
user’s event occurs, it may “subscribe” to it. Such events detected by the simulation engine
(9) will be transferred to the server and then to the client.
If FSX is terminated by the user (or for some other reason), a message will be sent by the
server to the clients.
In the end the client has to close the connection with the server to free any allocated
resources.
We mentionned the server informing (calling back) the client of two events: the
connection process success and FSX being terminated. Technically this is a call of the
client’s callback procedure, with a parameter being a pointer to a message structure. All
messages are declared this way: SIMCONNECT_RECV* pData.
SIMCONNECT_RECV defi nition:
struct SIMCONNECT_RECV
{
DWORD dwSize; // record size
DWORD dwVersion; // interface version
DWORD dwID; // see SIMCONNECT_RECV_ID
};
dwID is an integer identifying the kind of message being received. For a feedback after
a connection request it will be 2, for FSX quitting it will be 3. We won’t manipulate these
values directly. Instead we’ll use the enumerated values provided in SimConnect.h:
// Receive data types
enum SIMCONNECT_RECV_ID {
SIMCONNECT_RECV_ID_NULL,
SIMCONNECT_RECV_ID_EXCEPTION,
SIMCONNECT_RECV_ID_OPEN,
SIMCONNECT_RECV_ID_QUIT,
SIMCONNECT_RECV_ID_EVENT,
SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE,
SIMCONNECT_RECV_ID_EVENT_FILENAME,
SIMCONNECT_RECV_ID_EVENT_FRAME,
SIMCONNECT_RECV_ID_SIMOBJECT_DATA,
...
SIMCONNECT_RECV_ID_EVENT_RACE_LAP,
};
Depending on the SIMCONNECT_RECV_ID value, we’ll need to cast the intial
SIMCONNECT_RECV* pData to the actual structure it points. For example, if this is an
“OPEN” message identifi ed by pData->dwID == SIMCONNECT_RECV_ID_OPEN, then
pData actually points to a structure declared this way:
struct SIMCONNECT_RECV_OPEN : public SIMCONNECT_RECV
{
char szApplicationName[256];
DWORD dwApplicationVersionMajor;
DWORD dwApplicationVersionMinor;
DWORD dwApplicationBuildMajor;
DWORD dwApplicationBuildMinor;
DWORD dwSimConnectVersionMajor;
DWORD dwSimConnectVersionMinor;
DWORD dwSimConnectBuildMajor;
DWORD dwSimConnectBuildMinor;
DWORD dwReserved1;
DWORD dwReserved2;
};
This structure inherits from SIMCONNECT_RECV.
When we receive this message, we have plenty of data that can be accessed with the
pointer after a cast:
SIMCONNECT_RECV_OPEN* pOpen = (SIMCONNECT_RECV_OPEN*)pData;
pOpen->szApplicationName will be the name of the application. Sure this is not of
a critical interest in this tutorial, but you see the principle.
Regarding the structure returned after we receive a SIMCONNECT_RECV_ID_QUIT
message, it’s exactly the same than the basic one:
struct SIMCONNECT_RECV_QUIT : public SIMCONNECT_RECV
{
};
No fi eld is added.
An answer to a request for the altitude of our aircraft will be more interesting.
So we’ll move on and discuss our real example. Since every client must open a connection
to SimConnect, let’s see how we do that and how we handle the reply from the server.
Message loop
Actually let’s construct the fi rst bricks of our masterpiece ;-) starting with the constructor
of our main class (FollowThisPlane). As you recall, we need to have an endless loop
that wait for incoming message posted by the server, and transfer them to a callback
procedure. We’ll execute this loop just after we have requested to open a connection with
the server:
HANDLE hSimConnect = NULL;
bool keep_going = false;
// Constructor. Establish a connection with SimConnect
void FollowThisPlane()
{
// Connect to FSX
Connect();
if (hSimConnect != NULL)
{
// Connected. Loop until FSX exits
keep_going = true;
while(keep_going)
{
SimConnect_CallDispatch(hSimConnect,
MyDispatchProc, NULL);
Sleep(1);
}
// FSX exited. Close the connection with SimConnect
Disconnect();
}
else
{
// Not able to connect. Not retrying in this version
}
// (the program now quits)
}
The call Connect(); will initiate a connection, but the outcome of this request will
arrive asynchronously later. We’ll have a look at what we do within this call. When
Connect() returns, the variable hSimConnect will have been positionned by
SimConnect when requesting to open a connection. In case we were not able to talk
to SimConnect, this variable will remain NULL. In this case, our constructor will return
and the programm will quit. It can happen if FSX is not active when we run our little
SimConnect client.
In case we were able to post our request to open a connection, we’ll set keep_going to
true and enter an infi nite loop. In fact we’ll need to have another part of the program
resetting this fl ag to false at some point, so that we can exit the loop and terminate the
program.
Until then, in this loop we’ll call SimConnect_CallDispatch(hSimConnect,
MyDispatchProc, NULL) and then Sleep(1). SimConnect_CallDispatch
just looks for the next SimConnect message waiting in the connection queue associated
with hSimConnect and instructs SimConnect to call another portion of our code
(MyDispatchProc) to handle this message.
Bear in mind that this function excepted, a SimConnect client is completely asynchronous.
The client waits for a message to arrive, and at the same time performs other tasks, likely
in relation with the messages already received. This means the client must have different
threads working in parallel.The constructor’s thread is likely to be waiting most of the
time. SimConnect will call MyDispatchProc() in a new thread.
When SimConnect_CallDispatch() returns, we just Sleep() for 1 ms to ensure
other processes can run.
Opening a connection with SimConnect server
We mentionned the call to Connect() included in our messages loop. Here is the code
we write in this function:
// Try to connect to FSX
void Connect()
{
HRESULT hr;
if (hSimConnect == NULL)
{
printf(“\nRequesting a SimConnect connection”);
hr = SimConnect_Open(&hSimConnect,
“Follow This Plane”, NULL, 0, 0, 0);
if (hr != S_OK)
{
// FSX may be inactive
printf(“\nError %d”, hr);
}
else
{
// Connection to FSX initialized
printf(
“\nConnection request being processed (%d)”,
hSimConnect);
// (When connection process completes, an
// OPEN message will be received)
}
}
}
Receiving the Open event from SimConnect
Using SimConnect_Open() we requested SimConnect to open a connection with our
client. The server already returned the handle for this connection (in hSimConnect),
however the connection is not yet fi nalized. SimConnect has to complete the request and
then to send an event of type SIMCONNECT_RECV_ID_OPEN. This event will ultimately
be transferred to our callback procedure which needs to take care of it. Let’s see what we
do in this procedure:
// SimConnect sends a message to this client, dispatch it.
void CALLBACK MyDispatchProc(
SIMCONNECT_RECV* pData, DWORD cbData, void *pContext)
{
switch(pData->dwID)
case SIMCONNECT_RECV_ID_OPEN:
// the connection process is complete,
Process_Connected();
break;
}
When the callback procedure is called by SimConnect, pData points to the message.
However, as we have already seen, this message contains only information about the
application and server, useless for this tutorial. Thus we ignore the message data, and
call another function Process_Connected() that must take care of the connection
completion. All messages will be processed using the callback procedure above. We’ll add
more case as soon as we’ll need to process other types of messages. All OPEN messages
will be processed the same way: we’ll call Process_Connected().
Processing the connection completion
When entering Process_Connected() called by the callback procedure, we now know
we are connected to SimConnected, so to FSX for all practical purposes. Our scenario for
this client is to have another aircraft controlled by FSX AI following our aircraft. The fi rst
thing is to create this aircraft, but when creating an object, we need to provide a position
(latitude, longitude and altitude). In our case we’ll create it near our own aircraft. So we
need to know were we are. By the way, this program assumes we have already taken off
(because we don’t want to add the code required to takeoff the AI aircraft).
We’ll request our position. “Our” means “user’s”. All objects in the simulation are identifi ed
by a unique number. The user’s aircraft has a constant ID defi ned in SimConnect.h:
SIMCONNECT_OBJECT_ID_USER. There is no particular API function to request a
position, this falls into requesting a data about an object, and happens to be performed
by calling SimConnect_RequestDataOnSimObject(). This function is used with a
pre-defi nition of the data we want. In our case we want 3 data: latitude, longitude and
altitude. What we need to do is to register our set of data into the server. MS calls that a
“data_defi ne”. This defi nition will also tell the server which units we want to use for our
data (an altitude may be returned in feet or in meters for instance)
Our routine to process the connection completion looks like this:
// The connection process is completed
void Process_Connected()
{
printf(“\nConnected.”);
// prepare data definitions
Prepare_Data();
// request the initial position of the user’s aircraft
Request_User_Position(SIMCONNECT_OBJECT_ID_USER);
// (the position will be received later)
}
A call to defi ne our set of data to be returned, another call to send the data request.
Let’s go to the details of the data defi nition. What we see is that we need to maintain a
list of “data-defi ne” identifi ers we’ll use when talking with the server.
We bind the ID with the list of data and units. Then when we want the server to return
the data, we only provide the ID. The server has the defi nition somewhere in its tables
associated with our client.
// Unique IDs of data definitions hosted by SimConnect
static enum DATA {
DATA_ACFT_POSITION,
....
};
We’ll add IDs to this enumeration when we’ll need more. Note that we could just use
plain numbers instead of enum, but this would be a little bit diffi cult to remember them.
Now let’s explain to SimConnect which data we are looking for:
// prepare data definitions
void Prepare_Data()
{
// set up the data definitions
printf(“\nSending Data definitions.”);
SimConnect_AddToDataDefinition(hSimConnect,
DATA_ACFT_POSITION, “Plane Altitude”, “feet”);
SimConnect_AddToDataDefinition(hSimConnect,
DATA_ACFT_POSITION, “Plane Latitude”, “degrees”);
SimConnect_AddToDataDefinition(hSimConnect,
DATA_ACFT_POSITION, “Plane Longitude”, “degrees”);
SimConnect_AddToDataDefinition(hSimConnect,
DATA_ACFT_POSITION, “Heading Indicator”, “degrees”);
SimConnect_AddToDataDefinition(hSimConnect,
DATA_ACFT_POSITION, “Airspeed True”, “knots”);
}
We call 5 times SimConnect_AddToDataDefinition(). Each time we provide
these two fi rst parameters: hSimConnect and DATA_ACFT_POSITION. The fi rst one
is to identify our connection instance to SimConnect. Data defi nition are of course kept
isolated from defi nitions of other connections. The second parameter tells SimConnect
which defi nition we are taking about.
The two last parameters are to be read as a pair “data name”-”unit to use”. You can see
we are also requesting our heading and our speed, we’ll need them too when creating
the AI aircraft, because we will want it to move at some speed (not free falling) and in the
same direction than us. We’ll also provide an attitude, but it will be “straight and level”
whatever our attitude.
Now let’s see how to request these data to SimConnect.
// Request the position of the user’s aircraft
void Request_User_Position(SIMCONNECT_OBJECT_ID SimObject)
{
SimConnect_RequestDataOnSimObject(
hSimConnect,
REQUEST_INIT_POSITION,
DATA_ACFT_POSITION,
SimObject,
SIMCONNECT_PERIOD_ONCE);
printf(“\nInitial position requested for %d.”, SimObject);
// (the position will be received later)
}
We provide the handle to our connection hSimConnect, this will be the case for all
functions. SimConnect needs to know who is talking and check we are not unknown
strangers. Let’s forget the second parameter for a couple of minutes. We also provide the
ID of the data we want. With that, SimConnect knows we want latitude, longitude, etc
as well as which units to use for the answer. We then provide the ID of an object in the
simulation. When we called Request_User_Position(), we provided the ID of the
user’s aircraft as a paramater, we are using it here. The last parameter says how many
times we want the data to be returned by SimConnect. As you remember SimConnect is
asynchronous and will not provide the data we want during the call to SimConnect_
RequestDataOnSimObject. Instead it will post a message when ready. In addition
it can post this message at interval. We just need to tell what we want. The different
possibilities are defi ned in the header fi le:
// Object Data Request Period values
enum SIMCONNECT_PERIOD {
SIMCONNECT_PERIOD_NEVER,
SIMCONNECT_PERIOD_ONCE,
SIMCONNECT_PERIOD_VISUAL_FRAME,
SIMCONNECT_PERIOD_SIM_FRAME,
SIMCONNECT_PERIOD_SECOND,
};
The difference between “visual frame” and “sim frame” is about including or not the
frames that are not rendered. However the documentation is not so clear about that.
Anyway SimConnect will send us a nice message with the info we want. We need to
be reminded of our request at that time, because we’ll have so many requests that we
can’t remember for sure... so we’ll provide a number now, and SimConnect will include
in its replies. This mechanism will be needed for every request. So we’ll maintain an
enumeration which is assured to grow up shortly:
// Unique IDs of requests to SimConnect
static enum REQUESTS {
REQUEST_INIT_POSITION,
};
As you can see, this is the second parameter we used in our request.
Now that our request for the user’s aircraft position has been sent, what do we do? Well,
we can’t do anything before receiving the reply. It will come thru a call to our callback
procedure. But we need to handle it. It will have the type SIMCONNECT_RECV_ID_
SIMOBJECT_DATA. So let’s just add another case in our MyDispatchProc() routine:
case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:
// An answer to a data request on an object
Process_Data(pData);
break;
Receiving the aircraft position
Let’s also add this new function:
// A reply to a data request
void Process_Data(SIMCONNECT_RECV *pData) {
SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData =
SIMCONNECT_RECV_SIMOBJECT_DATA *)pData;
DWORD ObjectID = pObjData->dwObjectID;
switch(pObjData->dwRequestID)
{
case REQUEST_INIT_POSITION:
// answer to the initial request for user position
HRESULT hr;
ACFT_PARAM *pS = (ACFT_PARAM*)&pObjData->dwData;
printf(“\nUser %.4f %.4f %.f Hdg=%.f Knts=%.f”,
ObjectID, pS->latitude, pS->longitude,
pS->altitude, pS->heading, pS->airspeed);
// AI position related to the user’s aircraft
{
double AIdistance = .2; // NM from user’s position
double AIRelBearing = 270; // degrees clockwise
double AIHeight = -50; // meters above
double AIRelSpeed = 0;// kts above user’s speed
// compute position as Lat / Lon
ACFT_POSITION result = Compute_Location(
pS->latitude, pS->longitude, pS->altitude,
AIdistance,
fmod(pS->heading + AIRelBearing, 360),
AIHeight);
// Prepare data for the creation
SIMCONNECT_DATA_INITPOSITION Init;
Init.Altitude = result.alt;
Init.Latitude = result.lat;
Init.Longitude = result.lon;
Init.Pitch = 0.0;
Init.Bank = 0.0;
Init.Heading = pS->heading;
Init.OnGround = 0;
Init.Airspeed =
(DWORD)(pS->airspeed + AIRelSpeed);
hr = SimConnect_AICreateNonATCAircraft(
hSimConnect,
Container_Title,
Tail_Number,
Init,
REQUEST_CREATE_AI);
if (hr == S_OK)
{
printf(“\nAI aircraft requested”);
AI_Acft_Requested = true;
}
else
{
// problem while creating the AI aircraft
printf(“\AI creation request. Error %d”,
hr);
}
}
break;
}
}
So what do we do here? First we cast our message pointer to the type used with a
SIMCONNECT_RECV_ID_SIMOBJECT_DATA message:
SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData =
SIMCONNECT_RECV_SIMOBJECT_DATA *)pData;
These data are related to an object which ID is in dwObjectID: DWORD ObjectID = pObjData->dwObjectID;
We could check it is actually the same object than the one for which we requested data,
but that’s assumed here. We may limit our check to the request ID:
switch(pObjData->dwRequestID)
{
case REQUEST_INIT_POSITION:
Should we receive data not related to our own request, it would be ignored. What is of
interest here is the values of the requested aircraft parameters. There is a pointer to them
in dwData. This is a pointer to a structure crafted by SimConnect from our data defi nition
(DATA_ACFT_POSITION). We need to defi ne somewhere in our program the same data
structure:
// returned after a data request based on DATA_ACFT_POSITION
struct ACFT_PARAM
{
double altitude;
double latitude;
double longitude;
double heading;
double airspeed;
};
and now we can cast the pointer returned to a pointer to this type:
ACFT_PARAM *pS = (ACFT_PARAM*)&pObjData->dwData;
Creating a new aircraft
At this point in time we have the parameters of the user’s aircraft, but what we want is
to create another aircraft near the user’s aircraft. “Near” will be defi ned by a distance and
a bearing relative to the user (so that we can place the second aircraft “at 1 NM, and
30°” for instance). Since SimConnect will insist on having this point defi ned by its lat
and lon, we’ll need to perform some spherical calculation. We’ll leave this duty to a short
routine not described here. To avoid collisions, we’ll also allow to defi ne the second aircraft
altitude relative to our. And to allow some tuning, we’ll also allow to add some kts to its
speed. This give us these variables that can be adjusted to have some fun.
double AIdistance = .2; // NM from user’s position
double AIRelBearing = 270; // degrees clockwise
double AIHeight = -50; // meters above
double AIRelSpeed = 0; // kts above user’s speed
Then, as mentionned we call Compute_Location(), our spherical routine:
// compute position as Lat / Lon
ACFT_POSITION result = Compute_Location(
pS->latitude, pS->longitude, pS->altitude,
AIdistance,
fmod(pS->heading + AIRelBearing, 360),
AIHeight);
ACFT_POSITION is a structure defi ned elsewhere in the program:
struct ACFT_POSITION
{
double lat;
double lon;
double alt;
};
We are now nearly ready to create this awaited new aircraft. We just need to put the data
needed for the creation into the structure requested by the SimConnect API. (this structure
SIMCONNECT_DATA_INITPOSITION is defi ned in the API header).
// Prepare data for the creation of a SimConnect AI object
SIMCONNECT_DATA_INITPOSITION Init;
Init.Altitude = result.alt;
Init.Latitude = result.lat;
Init.Longitude = result.lon;
Init.Pitch = 0.0;
Init.Bank = 0.0;
Init.Heading = pS->heading;
Init.OnGround = 0;
Init.Airspeed = (DWORD)(pS->airspeed + AIRelSpeed);
We ask SimConnect to create the aircraft:
char Container_Title[] = “Mooney Bravo”;
char Tail_Number[] = “N0800”;
HRESULT hr = SimConnect_AICreateNonATCAircraft(
hSimConnect,
Container_Title,
Tail_Number,
Init,
REQUEST_CREATE_AI);
Container_Title is the name of the AI object to create (i.e. the type of object to
create), while Tail_Number is obviousy the call sign. As usual for us now, we need to
provide a request identifi er to SimConnect. Our enumeration for request looks now like
this:
static enum REQUESTS {
REQUEST_INIT_POSITION,
REQUEST_CREATE_AI,
}
How to you fi nd the container title? Another gap in FSX SDK documentation. The list is not
provided, instead you have to look, as explained, into FSX description of objects in your
FSX folders.
If you started coding in C# or any MS managed code, bear in mind that you need to
“marshal” your data structures, with an additional effort for the strings.
The creation function returns a result that is S_OK (defi ned in the API header fi le) or
something else. We just check S_OK to confi rm the request was accepted.
We have submitted our creation request, the next step is to receive the notifi cation of its
completion. It will arrive thru the callback procedure and will be tagged with message
type SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE. We need to add some
code in our callback procedure.
Receiving a notifi cation for the creation of an object
Each time an AI object is created into or deleted from the simulation, the clients are
informed by calling their callback procedure with a SIMCONNECT_RECV_ID_EVENT_
OBJECT_ADDREMOVE message (this is the same message both the creation and the
removal). However they need to subscribe to this information. So basically, if you create
an AI object, you are not informed of its creation or removal unless you have specifi cally
asked for.
That may seem strange, but you need to consider the number of objects created by FSX
itself, and by other clients as well. What we are really interested in is a way to be informed
only about the creation of objects we requested ourselves. SimConnect will notify a client,
without a need for prior subscription, each time an object created by the client is assigned
an object ID. Note that there is no corresponding notifi cations when these objects are
destructed. This ID assignment notifi cation will help us. However we’ll need also to be
informed of our aircraft removal, because we need to stop sending request to FSX as soon
as our little client has no purpose anymore (in this case maybe we should re-create it? It’s
left up to you...).
The AI aircraft should be removed after a collision (actually not) or some other event FSX
would jugde critical (we lack information on that). It will also be removed if it leaves the
“reality bubble” of the user, that if it is further than something around 100 km. The SDK
documentation talks about that.
It is clear we have to implement the handler for removals. For the same price we’ll have a
handler for additions.
Somewhere in our code, we need to subscribe to object addition and object removal
events. This has to be before the fi rst creation request is submitted. A valid time is when
we send our data defi nition (function Prepare_Data). We can add the code to subscribe
here and rename the function:
void Prepare_Data_And_Events()
{
// set up the data definitions
...
// subscribe to events
printf(“\nSubscribing to events.”);
SimConnect_SubscribeToSystemEvent(hSimConnect,
EVENT_ADDED, “ObjectAdded”);
SimConnect_SubscribeToSystemEvent(hSimConnect,
EVENT_REMOVED, “ObjectRemoved”);
}
SimConnect_SubscribeToSystemEvent is declared this way:
HRESULT SimConnect_SubscribeToSystemEvent(
HANDLE hSimConnect,
SIMCONNECT_CLIENT_EVENT_ID EventID,
const char* SystemEventName
);
It needs two parameters in addition of the connection handle: the SystemEventName
which is a string, and the EventID which is a number. The string tells SimConnect what
we want to be notifi ed for. However when SimConnect sends the notifi cation, it won’t
repeat the string in the message, only EventID. That means EventID is another unique
number that will be defi ned in an enumeration:
// Unique IDs for notified events
static enum EVENTS{
EVENT_ADDED,
EVENT_REMOVED,
};
We need to handle both notifi cations by adding this code in our callback procedure:
case SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE:
// AI object added or removed
Process_Object_Added_Removed(pData);
break;
To know if the notifi cation is for an addition or for a removal, we need to check the
uEventID reported in the message and compare it with the event ID provided for the
subscription to this class of events. We know what was the request for. Let’s put that
toghether in the function that we’ll call when notifi ed:
// An object has been added or removed
void Process_Object_Added_Removed(SIMCONNECT_RECV *pData)
{
SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *pAddRem =
(SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *) pData;
switch (pAddRem->uEventID)
{
case EVENT_ADDED:
// AI addition
printf(
“\nNew AI object added to FSX (%d, type %d)”,
pAddRem->dwData, pAddRem->eObjType);
break;
case EVENT_REMOVED:
// AI object removed
printf(
“\nAI object removed (%d)”, pAddRem->dwData);
// let’s check if it is ours
if (pAddRem->dwData == AI_Acft_ID)
{
// Our AI acft has died
keep_going = false;
AI_Acft_Valid = false;
printf(“, our AI aircraft!”);
printf(“\nRestart me.”);
}
break;
}
}
In the code above, we cast the message pointer to the actual structure for this event. Then
we look at uEventID to sort additions and removals based on the event ID provided at
the subscription. For the additions we do nothing except leaving a debug trace. For the
removal, we check if the object removed isn’t our aircraft. As we will see in a couple of
minutes, this object ID has been stored in the variable AI_Acft_ID. If the comparaison
confi rms the removal, then we reset the fl ag keep_going that was set when the aircraft
was confi rmed as created. We also reset the fl ag which indicates the AI_Acft_ID
variable contains a valid ID.
You may wonder why we don’t use the EVENT_ADDED case to confi rm our AI aircraft
was created. The reason is that we can’t, because the SIMCONNECT_RECV_EVENT_
OBJECT_ADDREMOVE notifi cation doesn’t carry the Request ID that led to the creation.
Without this information, we cannot know if the new object is the one we are expecting...
So we are bound to handle also the notifi cation of the ID assignment.
Detecting when a new object is assigned its ID by FSX.
In our tutorial, a new category of notifi cations to be handled means a new case in our
callback procedure, and a new function to do the actual work.
case SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:
// an answer to a AI creation request
Process_Object_ID_Assignment(pData);
break;
The new function:
// An object has been created and assigned an ID
void Process_Object_ID_Assignment(SIMCONNECT_RECV *pData)
{
SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *pAssObjData = (SIMCONNECT_
RECV_ASSIGNED_OBJECT_ID *)pData;
// We need to confirm this is one of our objects
switch (pAssObjData->dwRequestID)
{
case REQUEST_CREATE_AI:
// Our AI aircraft
printf(“\nAI Aircraft created (%d)”
pAssObjData->dwObjectID);
AI_Acft_ID = pAssObjData->dwObjectID;
AI_Acft_Valid = true;
// Start a timer
SimConnect_SubscribeToSystemEvent(hSimConnect,
EVENT_TIMER, “4sec”);
printf(“\nSubscribing to Timer ticks.”);
break;
}
}
Again what we do is pretty straightforward. We compare the message Request ID
(dwRequestID) with our request ID used to create the AI aircraft (REQUEST_CREATE_
AI). If they match, we have now a second aircraft up and running. We store its object
ID for later use (removal detection mentioned previously). We also set a fl ag to indicate
AI_Acft_ID is now valid.
AI_Acft_ID = pAssObjData->dwObjectID;
AI_Acft_Valid = true;
Then we subscribe to a timer event sent every 4 seconds. We’ll use it to send a new
waypoint to the AI aircraft. This way we’ll feed the aircraft with waypoints that keep it in
the vicinity.
SimConnect_SubscribeToSystemEvent(hSimConnect,
EVENT_TIMER, “4sec”);
Requesting periodically a new user’s position
So let’s see the second part of this tutorial, where we have to receive ticks, create a
waypoint, and send it to the AI aircraft. Fortunately enough, a non ATC aircraft still knows
how to keep fl ying and how to move towards a waypoint. And the simulation takes care of
this job very smoothly.
The last touch of fun will be to add markers at the location of the waypoints, to have a
visual clue of where the AI aircraft is expected to go.
To receive the timer events, we need to upgrade our callback procedure, and to create
another handler it can call. Those simple events like timer (we’ll also use a pause
detection later) are all received under the same message type, SIMCONNECT_RECV_ID_
EVENT.
case SIMCONNECT_RECV_ID_EVENT:
// One of the FSX events this client has subscribed to
Process_Event(pData);
break;
The handler we add now contains this code:
// Message received is a RECV_EVENT
void Process_Event(SIMCONNECT_RECV *pData) {
SIMCONNECT_RECV_EVENT *evt= (SIMCONNECT_RECV_EVENT *)pData;
switch(evt->uEventID)
{
case EVENT_TIMER:
// time to update things...
if (keep_going && !Paused)
{
// request the new position of the user
Request_User_Position(
SIMCONNECT_OBJECT_ID_USER, false);
}
break;
}
}
}
We check the message is for EVENT_TIMER. At this time we should request the current
position of the user aircraft. We would then wait for the server reply, and in the handler
of the reply we would create a new waypoint based on the current position of the user’s
aircraft. This is what we’ll do except that we need to stop creating waypoints when the
simulation is paused or when the AI aircraft has been removed:
if (keep_going && !Paused)
{
// request the new position of the user
Request_User_Position(
SIMCONNECT_OBJECT_ID_USER, false);
}
keep_going will be reset in the handler for object removal notifi cations, while toogling
Paused needs to be linked to a new event from SimConnect. We’ll take care of that soon.
Regarding requesting the current position, it would make sense to use the code we wrote
for asking the initial position. However when we are notifi ed of the user’s position, we
need to know which request is answered to execute the related actions set which are
different. It means we need to have different request IDs, we cannot reuse REQUEST_
INIT_POSITION. We will insert another ID, REQUEST_NEXT_POSITION, in our
enumeration of request IDs. To tell Request_User_Position() which request ID
needs to be used, we use a boolean fl ag, set only for the initial request. This is our handler,
modifi ed to suit both types of requests:
// Request the position of the user’s aircraft
void Request_User_Position(
SIMCONNECT_OBJECT_ID SimObject, bool Initial)
{
SimConnect_RequestDataOnSimObject(
hSimConnect,
Initial ?
REQUEST_INIT_POSITION : REQUEST_NEXT_POSITION,
DATA_ACFT_POSITION,
SimObject,
SIMCONNECT_PERIOD_ONCE);
printf(“\n%s position requested (%d).”,
Initial ? “Initial” : “Current”, SimObject);
// (the position will be received later)
}
We mentionned our need to capture pauses so that we don’t send useless waypoints to
the AI aircraft when the simulation is paused. The waypoints would be all identical, since
the user’s aircraft is not moving, and in addition the AI aircraft would receive them all at
the same time, when the simulation is restarted. Being notifi ed of Pause is a matter of
subscribing to the appropriate event and again to handle the notifi cations. Let’s add a
subscription at the same location than the previous ones:
void Prepare_Data_And_Events()
{
// set up the data definitions
...
// subscribe to events
...
HRESULT hr = SimConnect_SubscribeToSystemEvent(
hSimConnect, EVENT_PAUSE_TOGGLE, “Pause”);
Paused = (hr == 0) ? false : true;
}
We are subscribing to SimConnect event “Pause”, and we use the event ID EVENT_
PAUSE_TOGGLE to remember what is this event. (for later, when we’ll be notifi ed and
provided this event ID) This new ID has been added to our EVENTS enumeration (this
is straightforward, not need to show you the updated code). Note that the order of the
symbols in the enum defi nition has no importance, only their uniqueness is meaningful.
When we subscribe to events like Pause, the call to SimConnect returns the current status
of the parameter. Here it returns 0 if the simulation is currently unpaused. this allow us to
set our Paused fl ag to the appropriate value immediately.
The notifi cations are handled this way, reusing the existing event handler already used to
handle EVENT_TIMER:
switch(evt->uEventID)
{
case EVENT_TIMER:
...
case EVENT_PAUSE_TOGGLE:
// the simulation is paused or unpaused
Paused = evt->dwData == 0 ? false : true;
break;
}
We get the new status of the simulation in dwData. We update our Paused fl ag
accordingly.
Computing the position of the next waypoint
We need to create a waypoint when we are notifi ed of the current user’s position. The
corresponding data request is submitted every 4 sec by the EVENT_TIMER handler. We
already have a handler that processes notifi cations of data delivery (Process_Data()),
we just need to adapt it so that it can handle replies to REQUEST_NEXT_POSITION
requests in addition to replies to REQUEST_INIT_POSITION requests.
case REQUEST_INIT_POSITION:
...
...
break;
case REQUEST_NEXT_POSITION:
// Periodic update of the user’s position
// We send the coordinates of a waypoint nearby
// But only if the AI aircraft is flying...
if (AI_Acft_Valid && keep_going)
{
printf(“\nNew position received.”);
{
// WP position and desired speed at the WP
double WPdistance = .5; // NM
double WPRelBearing = 0; // degrees clockwise
double WPHeight = -50; // meters above
double WPRelSpeed = 5; // kts above
// Compute Lat / Lon of the WP
ACFT_POSITION result = Compute_Location(
pS->latitude, pS->longitude,
pS->altitude, WPdistance,
fmod(pS->heading + WPRelBearing, 360),
WPHeight);
// force VS calculation
unsigned long flags =
SIMCONNECT_WAYPOINT_SPEED_REQUESTED |
SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED;
// (code to create the WP goes here)
// (see next section)
When we receive the user’s aircraft position from SimConnect, we check we really need to
create a waypoint:
if (AI_Acft_Valid && keep_going)
i.e. the AI aircraft creation has taken place (confi rmed by AI_Acft_Valid) and the AI
aircraft hasn’t been destructed (confi rmed by keep_going). The position of the waypoint
will be relative to the position of the user’s aircraft: distance in NM, relative angle, altitude
difference, speed difference.
// WP position and desired speed at the WP
double WPdistance = .5; // NM
double WPRelBearing = 0; // degrees clockwise
double WPHeight = -50; // meters above
double WPRelSpeed = 5; // kts above
Since we need to provide a latitude and longitude rather than a distance and a bearing
for the creation of the waypoint, we call again our spherical routine. We ensure the
absoulte bearing is in the range 0-360° by calling a fmod.
When creating the waypoint, we’ll specify a speed. This is the desired speed for the aircraft
when it reaches the waypoint. Similarly we want FSX simulation to compute whatever
vertical speed is required to reach the altitude at the waypoint. When calling the function
that creates the WP, we’ll need to provide fl ags that ask for the speed and the vertical
speed to be used. They are ORed:
flags =
SIMCONNECT_WAYPOINT_SPEED_REQUESTED |
SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED;
We have collected all the info we need to create the WP.
Creating a waypoint
First, we goup the WP data in an array of SIMCONNECT_DATA_WAYPOINT elements. If
we want to send multiple waypoints, then we describe one WP per array element.
// Prepare data for the creation of a WP
SIMCONNECT_DATA_WAYPOINT waypoint[1];
waypoint[0].Latitude = result.lat;
waypoint[0].Longitude = result.lon;
waypoint[0].Altitude = result.alt;
waypoint[0].Flags = flags;
waypoint[0].ktsSpeed = pS->airspeed + WPRelSpeed;
waypoint[0].percentThrottle = 0;
The percentThrottle value is the quantity of power we want for the aircraft when
it reaches the WP. We don’t use it here (we should force its use by adding a fl ag to our
flags value).
To send the WP to the aircraft, we’ll just use the function that “sends data” to an object.
Sending data to an object requires a data defi nition to tell SimConnect how to understand
what we are sending. We already defi ned data, we’ll just add the new defi nition to the
existing ones.
As the data defi nition needs to be assigned a unique ID, we’ll also update our data
defi nition enumeration to add the symbol DATA_NEXT_POSITION:
// Unique IDs of data definitions hosted by SimConnect
static enum DATA {
DATA_ACFT_POSITION,
DATA_NEXT_POSITION,
};
Let’s now defi ne these WP data:
void Prepare_Data_And_Events()
{
// set up the data definitions
...
...
SimConnect_AddToDataDefinition(
hSimConnect, DATA_NEXT_POSITION,
“AI Waypoint List”, “number”,
SIMCONNECT_DATATYPE_WAYPOINT);
...
...
}
To defi ne DATA_NEXT_POSITION, we send only one piece of data “AI Waypoint
List”, the unit has to be tagged “number” and the structure of the data is
SIMCONNECT_DATATYPE_WAYPOINT.
Let’s send our WP as a “data” using SimConnect_SetDataOnSimObject(). The
object we target is our AI aircraft, which Id is AI_Acft_ID. We need to tell SimConnect
how many elements there is in the array (ARRAYSIZE(waypoint)), and what is the
total size of the data. We also need to provide a pointer to the data (waypoint) as well
as the the data defi nition (DATA_NEXT_POSITION). // Send the WP to the AI aircraft
hr = SimConnect_SetDataOnSimObject(
hSimConnect, DATA_NEXT_POSITION,
AI_Acft_ID, 0, ARRAYSIZE(waypoint),
sizeof(waypoint[0]), waypoint);
printf(“\nNew waypoint sent to AI aircraft);
That’s it. The AI aircraft is now fl ying towards this WP. It would be nice to visualize it. This
will be the icing on the cake.
Creating a marker
The green vertical arrow or the point of interest objects used in missions would be
perfect markers to visualize the location of a waypoint. However, I don’t know how to
use them in a SimConnect application. It seems the only objects we can create are those
found in the SimObjects folder of FSX. So the best I can think of is a parachute.
Like we did for the AI aircraft, we need to tell SimConnect where to create the object. This
will be described in a structure SIMCONNECT_DATA_INITPOSITION.
// Prepare data for a SimConnect AI object
SIMCONNECT_DATA_INITPOSITION Init;
Init.Altitude = result.alt;
Init.Latitude = result.lat;
Init.Longitude = result.lon;
Init.Pitch = 0.0;
Init.Bank = 0.0;
Init.Heading = pS->heading;
Init.OnGround = 0;
Init.Airspeed = 0;
With this set of data, we can create the little parachute. We’ll use the SimConnect function
SimConnect_AICreateSimulatedObject which allow us to create objects that
don’t fl y. // Create an AI object to mark the position
SimConnect_AICreateSimulatedObject(hSimConnect,
“Food_pallet”,
Init,
REQUEST_ADD_MARKER);
Note that we used the new request ID REQUEST_ADD_MARKER which will allow us
to detect the creation completion. As usual, the enumeration of request IDs has been
updated.
The name of the container to create is found in the confi guration fi le of the object in FSX
SimObjects folder. There is no list of objects in the SDK help fi le, only some examples.
If you run this program now, it’ll work. Ensure you launch it while you’re already fl ying
your aircraft in FSX. If this is not the case, the AI aircraft will be created with a speed
of 0 kt. And maybe below the ground level, depending on the settings you chose for the
position where to create the AI....
You’ll see the AI aircraft poping up nearby out of nowhere. And also those magenta
parachutes dropped every four seconds.They will materialize your route on the ground
(actually a route parallel to your route). When we send a single WP to the aircraft, the
previous WPs are cancelled from its fl ight plan. Only the most recent parachute is actually
materializing an active WP. If you want, you can add some code to your program, to
remove the previous parachute when a new one is created. It should be in the section
of code that is called when we are notifi ed that an ID has been assigned to our last
parachute (Process_Object_ID_Assignment()):
case REQUEST_ADD_MARKER:
// no more than one marker in the simulation
Previous_Marker = Last_Marker;
Last_Marker = pAssObjData->dwObjectID;
if (Previous_Marker != NULL)
{
// remove the previous marker
SimConnect_AIRemoveObject(hSimConnect,
Previous_Marker, REQUEST_REMOVE_MARKER);
}
Also add these two declarations somewhere:
DWORD Previous_Marker;
DWORD Last_Marker;
With that last set of instructions, we have completed the tutorial. The full code
contains non signifi cant additions that you will be able to discover by reading thru
FollowThisPlane.cpp source.
PS: the code used to compute the coordinates of a point based on its distance and
bearing relative to a point with known coordinates is:
const double PI = 3.14159265358979323846;
const double DEG_TO_RAD = 3.14159265358979323846 / 180.0;
const double EARTH_RADIUS_NM = 6371.008 / 1.852;
double lat1 = refLat * DEG_TO_RAD;
double lon1 = refLon * DEG_TO_RAD;
double d_R = dist / EARTH_RADIUS_NM;
double brng = bearing * DEG_TO_RAD;
result.lat = (
asin(sin(lat1)*cos(d_R) +
cos(lat1)*sin(d_R)*cos(brng)))
/ DEG_TO_RAD;
result.lon = (
lon1 +
atan2(
sin(brng)*sin(d_R)*cos(lat1),
cos(d_R)-sin(lat1)*sin(result.lat)))
/ DEG_TO_RAD;
The result is not good, though for the purpose it’s suffi cient. You’ll see the effect when
setting the waypoint position at some distance (1/2 NM) and the bearing at 360°. The WP
is not created directly ahead, but on a side. Regardless of the bearing set, when your turn
your aircraft, the position of the parachute creation will move related to the heading, it
will change from left to right or right to left.
I’ve no clue about what’s wrong, if you see where is the problem... send me your tested
code. Thanks.
( c ) 2009, RWY AheadYou can do whatever you want with this tutorial and its code, it’s public domain.
This code is for simulation only, do not use it in nuclear plants without permission.