Anda di halaman 1dari 19

N0800

--------------Follow
How to fly
This Plane
AI Aircrafts
SimConnect
Using
Tutorial
Waypoints
For Flight
in FSX SDK
Simulator X ---------------

N0800 Tutorial

program. The console output of the program shows what we are doing.

What is this tutorial for? The tutorial is to understand how we send nearby waypoints What do you need to complete this tutorial? MS FSX SDK, which is on the CD
to a second aircraft, to fly it smoothly towards the waypoints. Well also drop a parachute of MS FSX Professional. A compiler, which can be MS Visual Studio C++, C# or VB,
at the location of the waypoints, to visualize where is the second aircraft flying to. In the
which is available for free at Microsoft.
picture below, our aircraft is on top and the second aircraft is controlled by our SimConnect

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
hasnt 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 specific way.
The callback procedure is the core of the client design. It identifies 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 notification. 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

void C A LL B A C K

P ro g ra m S ta rt

M yD isp atch P ro c
(S IM C O N N E C T_ R E C V * pD ata)

C ontinue = true
s w itc h(pD ata->dw ID )

S im C onnect_C a llD isp atc h


(M yD is patc hP ro c)

S leep()

C ontinue = = true

c a se S IM C O N N EC T_R E C V_ ID _ E XC E P T IO N :

bre a k;
c a se S IM C O N N EC T_R E C V_ ID _ E V EN T :

bre a k;
c a se S IM C O N N EC T_R E C V_ ID _ E V EN T _O BJ E C T _ AD D R EM O V E :

bre a k;
c a se S IM C O N N EC T_R E C V_ ID _ S IM O BJ EC T_ D AT A :

bre a k;
c a se

P ro g ra m E n d

R o u tin e E n d

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 notified 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 final order for processing
messages of identical priority is managed by SimConnect.

SimConnect Documentation
The main documentation for SimConnect use is the Help file 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 definetely C++
oriented, and leaves the C# or VB programmer with additional difficulties related to calling

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).
Lets see with this C portion of code:
struct Struct1
{
char* title;
double latitude;
double longitude;
double altitude;
};

In C# youll 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 youll find 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,
youll 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. Thats 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 youll find 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, youll probably end up with Visual Studio, which comes for free in
its Express edition (well... youre pestered to register after a month, so youll certainly pay
some price in the end). I dont know about using Eclipse, which I like definitely more than
VSE. I started with VSE to avoid additional difficulties 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 efficient with C/C++, youll 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 Ill 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 file) explains how to to set up your IDE correctly.
For this tutorial, all the code is in the same source file. 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 didnt install it with FSX initially.
By creating a SimConnect.ini file in your My Documents\Flight Simulator X
Files folder you can also enable a debug window in FSX. This is briefly explained in the
help file. You can copy here the default ini file found in the SDK folder. No need to create
one from scratch.

simulation. Such events may need to be reported by the server to all or some of its clients
(5).

At this time, you now have the blue part of this diagram (1, 2 and 3):

If FSX is terminated by the user (or for some other reason), a message will be sent by the
server to the clients.

2
C++
S ource
3

In the end the client has to close the connection with the server to free any allocated
resources.

1
S im C o nnect
A P I H eader

On his side, the user (8) is controlling FSX. If a client wants to be informed when some
users 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.

V isual
C++

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
clients callback procedure, with a parameter being a pointer to a message structure. All
messages are declared this way: SIMCONNECT_RECV* pData.

C om pile T im e
R un T im e
F S X U ser

4
8
5

7
FSX
S im ulation
E ngine

S im C onnect
S erv er

S im C onnect
A pplication (C lient)
10

6
S im C o nnect
A P I Library

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

SIMCONNECT_RECV definition:
struct SIMCONNECT_RECV
{
DWORD
dwSize;
DWORD
dwVersion;
DWORD
dwID;
};

// record size
// interface version
// 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 wont manipulate these
values directly. Instead well 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, well need to cast the intial


SIMCONNECT_RECV* pData to the actual structure it points. For example, if this is an
OPEN message identified 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, its exactly the same than the basic one:
struct SIMCONNECT_RECV_QUIT : public SIMCONNECT_RECV
{
};

No field is added.
An answer to a request for the altitude of our aircraft will be more interesting.
So well move on and discuss our real example. Since every client must open a connection
to SimConnect, lets see how we do that and how we handle the reply from the server.

Message loop
Actually lets construct the first 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. Well 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. Well 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

printf(\nError %d, hr);

and the programm will quit. It can happen if FSX is not active when we run our little
SimConnect client.

}
else
{

In case we were able to post our request to open a connection, well set keep_going to
true and enter an infinite loop. In fact well need to have another part of the program
resetting this flag to false at some point, so that we can exit the loop and terminate the
program.
Until then, in this loop well 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 constructors 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

// 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 finalized. 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. Lets 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. Well add
more case as soon as well need to process other types of messages. All OPEN messages

will be processed the same way: well 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 first
thing is to create this aircraft, but when creating an object, we need to provide a position
(latitude, longitude and altitude). In our case well 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 dont want to add the code required to takeoff the AI aircraft).
Well request our position. Our means users. All objects in the simulation are identified
by a unique number. The users aircraft has a constant ID defined 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-definition 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_define. This definition 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 users aircraft
Request_User_Position(SIMCONNECT_OBJECT_ID_USER);
// (the position will be received later)
}

A call to define our set of data to be returned, another call to send the data request.
Lets go to the details of the data definition. What we see is that we need to maintain a
list of data-define identifiers well 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 definition somewhere in its tables
associated with our client.
// Unique IDs of data definitions hosted by SimConnect
static enum DATA {
DATA_ACFT_POSITION,
....
};

Well add IDs to this enumeration when well need more. Note that we could just use
plain numbers instead of enum, but this would be a little bit difficult to remember them.
Now lets 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 first parameters: hSimConnect and DATA_ACFT_POSITION. The first one
is to identify our connection instance to SimConnect. Data definition are of course kept
isolated from definitions of other connections. The second parameter tells SimConnect
which definition 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, well 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. Well also provide an attitude, but it will be straight and level
whatever our attitude.

Now lets see how to request these data to SimConnect.


// Request the position of the users 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. Lets 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
users 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 defined in the header file:
// 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 well have so many requests that we
cant remember for sure... so well provide a number now, and SimConnect will include

in its replies. This mechanism will be needed for every request. So well 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 users aircraft position has been sent, what do we do? Well,
we cant 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 lets 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


Lets 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 users aircraft
{
double AIdistance = .2; // NM from users position
double AIRelBearing = 270; // degrees clockwise
double AIHeight = -50; // meters above

double AIRelSpeed = 0;// kts above users 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 thats 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 definition
(DATA_ACFT_POSITION). We need to define 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 users aircraft, but what we want is
to create another aircraft near the users aircraft. Near will be defined 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 defined by its lat
and lon, well need to perform some spherical calculation. Well leave this duty to a short
routine not described here. To avoid collisions, well also allow to define the second aircraft
altitude relative to our. And to allow some tuning, well also allow to add some kts to its
speed. This give us these variables that can be adjusted to have some fun.

double
double
double
double

AIdistance
AIRelBearing
AIHeight
AIRelSpeed

=
=
=
=

.2;
270;
-50;
0;

//
//
//
//

NM from users position


degrees clockwise
meters above
kts above users 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 defined 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 defined 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 identifier to SimConnect. Our enumeration for request looks now like
this:
static enum REQUESTS {
REQUEST_INIT_POSITION,
REQUEST_CREATE_AI,
}

How to you find 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 (defined in the API header file) or
something else. We just check S_OK to confirm the request was accepted.
We have submitted our creation request, the next step is to receive the notification 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 notification 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 specifically
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 notifications when these objects are
destructed. This ID assignment notification will help us. However well 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? Its
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 well have a
handler for additions.

repeat the string in the message, only EventID. That means EventID is another unique
number that will be defined in an enumeration:
// Unique IDs for notified events
static enum EVENTS{
EVENT_ADDED,
EVENT_REMOVED,
};

We need to handle both notifications 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;

Somewhere in our code, we need to subscribe to object addition and object removal
events. This has to be before the first creation request is submitted. A valid time is when
we send our data definition (function Prepare_Data). We can add the code to subscribe
here and rename the function:

To know if the notification 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. Lets put that
toghether in the function that well call when notified:

void Prepare_Data_And_Events()
{
// set up the data definitions
...

// 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);
// lets 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!);

// 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 notified for. However when SimConnect sends the notification, it wont

printf(\nRestart me.);

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;

}
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 isnt 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
confirms the removal, then we reset the flag keep_going that was set when the aircraft
was confirmed as created. We also reset the flag which indicates the AI_Acft_ID
variable contains a valid ID.
You may wonder why we dont use the EVENT_ADDED case to confirm our AI aircraft
was created. The reason is that we cant, because the SIMCONNECT_RECV_EVENT_
OBJECT_ADDREMOVE notification doesnt 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 notification of the ID assignment.

Detecting when a new object is assigned its ID by FSX.


In our tutorial, a new category of notifications 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)
{

}
}

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 flag 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. Well use it to send a new
waypoint to the AI aircraft. This way well feed the aircraft with waypoints that keep it in
the vicinity.
SimConnect_SubscribeToSystemEvent(hSimConnect,
EVENT_TIMER, 4sec);

Requesting periodically a new users position


So lets 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 flying 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 (well 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 users
aircraft. This is what well 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 notifications, while toogling
Paused needs to be linked to a new event from SimConnect. Well 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 notified of the users 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 flag, set only for the initial request. This is our handler,
modified to suit both types of requests:
// Request the position of the users 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 dont send useless waypoints to
the AI aircraft when the simulation is paused. The waypoints would be all identical, since
the users aircraft is not moving, and in addition the AI aircraft would receive them all at
the same time, when the simulation is restarted. Being notified of Pause is a matter of
subscribing to the appropriate event and again to handle the notifications. Lets 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 well be notified 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 definition 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 flag to the appropriate value immediately.
The notifications 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 flag
accordingly.

Computing the position of the next waypoint

case REQUEST_NEXT_POSITION:
// Periodic update of the users 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 users aircraft position from SimConnect, we check we really need to
create a waypoint:
if (AI_Acft_Valid && keep_going)

We need to create a waypoint when we are notified of the current users position. The
corresponding data request is submitted every 4 sec by the EVENT_TIMER handler. We
already have a handler that processes notifications 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;

i.e. the AI aircraft creation has taken place (confirmed by AI_Acft_Valid) and the AI
aircraft hasnt been destructed (confirmed by keep_going). The position of the waypoint
will be relative to the position of the users aircraft: distance in NM, relative angle, altitude
difference, speed difference.
// WP position and desired
double WPdistance
= .5;
double WPRelBearing = 0;
double WPHeight
= -50;
double WPRelSpeed
= 5;

speed at the WP
// NM
// degrees clockwise
// meters above
// 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, well 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, well need to provide flags 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 dont use it here (we should force its use by adding a flag to our
flags value).

// Unique IDs of data definitions hosted by SimConnect


static enum DATA {
DATA_ACFT_POSITION,
DATA_NEXT_POSITION,
};

Lets now define 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 define 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.
Lets 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 definition (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);

To send the WP to the aircraft, well just use the function that sends data to an object.
Sending data to an object requires a data definition to tell SimConnect how to understand
what we are sending. We already defined data, well just add the new definition to the
existing ones.

Thats it. The AI aircraft is now flying towards this WP. It would be nice to visualize it. This
will be the icing on the cake.

As the data definition needs to be assigned a unique ID, well also update our data
definition enumeration to add the symbol DATA_NEXT_POSITION:

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 dont 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. Well use the SimConnect function
SimConnect_AICreateSimulatedObject which allow us to create objects that
dont fly.
// 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 configuration file of the object in FSX
SimObjects folder. There is no list of objects in the SDK help file, only some examples.
If you run this program now, itll work. Ensure you launch it while youre already flying
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....
Youll 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 flight 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 notified 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 significant 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 its sufficient. Youll 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.
Ive no clue about whats wrong, if you see where is the problem... send me your tested
code. Thanks.

( c ) 2009, RWY Ahead


You can do whatever you want with this tutorial and its code, its public domain.
This code is for simulation only, do not use it in nuclear plants without permission.

Anda mungkin juga menyukai