Tutorial de programación. APIs de Microsoft Flight Simulator X. C++


Bien, este simulador nos proporciona unas APIs (Application Programming Interface) que nos permiten crear programas que se comuniquen con el simulador. Con esto podemos tanto como leer datos del simulador como modificarlos. Estas APIs vienen incluidas en la versión Deluxe del juego y recomiendo para usarlas el IDE de Microsoft, el Visual C++, por la simple razón de que configurar ahí las librerías para empezar a programar es muy simple, si puedes configurarlo todo en el IDE que más te guste, bien por ti.

Lo que se instala con las APIs es el SDK (Standard Development Kit), que incluye la documentación, ejemplos, y mucho más además de las librerías. Lo que haremos aquí será ir sobre un ejemplo.

Lo primero es crear un proyecto, del tipo Aplicación de Consola Win32. En la carpeta donde se instalan las APIs debes encontrar el archivo SimConnect.h y SimConnect.lib, y copiarlos en la carpeta del proyecto, en el mismo directorio en el que se creen los archivos de fuente principales. En mi caso llamé al proyecto VORdriver por ser el dispotivo VOR el primero que iba a simular, así que estos archivos de fuente son VORdriver.cpp y VORdriver.h.

Después debemos configurar el IDE para que el linker vincule las librerías de SimConnect a nuestro proyecto, para eso, en el IDE, vamos a: Proyecto/Propiedades de proyecto. Y en el menú de la izquierda: Propiedades de configuración/Vinculador/Línea de comandos. En opciones adicionales escribimos SimConnect.lib, y ya está listo.

Ya podemos empezar a programar.

El programa consiste básicamente en una serie de funciones y estructuras clave. La función clave es la función CALLBACK, que es llamada cada vez que el simulador o algún otro cliente informan de un evento, o de la llegada de algún tipo de datos.

Los eventos se encuentran en la documentación de la SDK, en la sección EVENT IDs. Uno que nos interesará mucho será “6 Hz”. Como su nombre indica, lo único que hace es saltar 6 veces por segundo. Esto nos es muy útil si por ejemplo necesitamos información del simulador continuamente, ya que podemos pedir información cada vez que salte este evento, y 6 herzios es una buena frecuencia (es aproximadamente la frecuencia de refresco del estado de los joysticks, según esa misma documentación).

Bien, lo que haremos será recorrer un ejemplo en el que mediante ese evento pediremos una serie de datos al simulador. Los datos serán, por tenerlos ya hecho, el nombre del avión y la desviación de las agujas de los indicadores VOR. Lo primero que haremos será crear una estructura con esos datos:

#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#include <Windows.h>
#include "SimConnect.h"
#include <string>
#include <sstream>
#include <iostream>
using namespace std;

int     quit = 0;
HANDLE  hSimConnect = NULL;

struct Struct1
{
    char    title[256];
    double  cdi1;
    double  cdi2;
	double  gsi1;
};

Las dos cdi son la “course deviation indicator”, la 1 del primer indicador y la 2 del segundo. gsi1 es el “glide slope indicator” del primer indicador (el segundo no tiene). Es importante definir como “double” todas las variables que devuelvan un resultado numérico, porque siempre se devuelven así, aunque los únicos valores posibles sean 0 ó 1, y si no puedes obtener resultados extraños.

Lo siguiente que haremos será crear varias enumeraciones:

static enum GROUP_ID{
	GROUP0,
};

static enum EVENT_ID{
	CONT,
};

static enum DATA_DEFINE_ID {
    DEFINITION_1,
};

static enum DATA_REQUEST_ID {
    REQUEST_1,
};

Las explico. La primera no es muy relevante en este caso. Sirve para poder crear un sistema de prioridades entre grupos de clientes a los que nos conectemos en el caso de hacerlo. En este caso sólo nos conectamos al simulador, así que no lo usaremos. Sin embargo en otros casos nos podría interesar crear más de un programa que se comunique con el simulador y que además esos se comuniquen entre ellos, por ejemplo lanzando eventos (también podemos definir eventos y lanzarlos, además de recibir los del simulador).

La segunda sí es importante, nos sirve para definir los eventos que usaremos, en este caso sólo 1, el CONT, que se corresponderá con el de 6 Hz.

La tercera la usaremos porque las definiciones posteriores se realizan agrupadas bajo un índice y este es el que usaremos, de nuevo, sólo tenemos una. La cuarta nos servirá para pedir datos al simulador, por la misma razón.

Ahora definimos la función CALLBACK:

void CALLBACK dmeDispatch(SIMCONNECT_RECV* pData, DWORD cbData, void *pContext) {
	HRESULT hr;

	switch (pData->dwID) {
		case SIMCONNECT_RECV_ID_EVENT:
		{
			SIMCONNECT_RECV_EVENT *evt = (SIMCONNECT_RECV_EVENT*)pData;
			switch(evt->uEventID) {
				case CONT:
					//Pedir datos
					hr = SimConnect_RequestDataOnSimObjectType(hSimConnect, REQUEST_1, DEFINITION_1, 0, SIMCONNECT_SIMOBJECT_TYPE_USER);
				break;
			}
			break;
		}

		case SIMCONNECT_RECV_ID_SIMOBJECT_DATA_BYTYPE:
		{
			SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE*)pData;
			switch(pObjData->dwRequestID)
            {
                case REQUEST_1:
                {
                    DWORD ObjectID = pObjData->dwObjectID;
                    Struct1 *pS = (Struct1*)&pObjData->dwData;
                    if (SUCCEEDED(StringCbLengthA(&pS->title[0], sizeof(pS->title), NULL))) // security check
                    {
                                                cout << "Title: " << pS->title << endl;
						cout << "CDI: " << pS->cdi1 << endl << "GSI: " << pS->gsi1 << endl;
						cout << "CDI2: " << pS->cdi2 << endl;
                    } 
					
                    break;
                }

                default:
                   break;
            }
            break;
			break;
		}
	}
}

Como ves a la función se le pasa como argumento un puntero a una estructura del tipo SIMCONNECT_RECV. Esta estructura tiene un miembro llamado dwId que nos permite decidir que tipo de datos nos está llegando. En este caso sólo separamos entre dos.

El primero es SIMCONNECT_RECV_ID_EVENT, que nos notifica que ha saltado algún tipo de evento. El segundo es SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE, que como su nombre indica supone la llegada de datos sobre algún objeto del simulador, en este caso el avión.

En el caso de que llegue un evento lo que hacemos es ver qué evento es el que ha saltado. Eso lo hacemos mediante otro switch. En este caso sólo pedimos un evento (luego veremos cómo se registran para que se nos notifique de ellos), así que sólo puede ser un evento, pero lo hacemos así porque en un futuro es probable que queramos usar más eventos.

Después de hacerle casting al puntero de la estructura, miramos el miembro uEventId para ver que evento es, y después de verificar que se trata de CONT, hacemos lo que queremos hacer, en este caso pedir al simulador que nos devuelva una estructura con los datos que queremos. La función asocia la orden al “número” REQUEST1, de manera que podamos filtrarlo luego, y pide los datos asociados a DEFINITION1.

En la segunda parte de la función verificamos que el objeto SIMCONNECT_RECV_SIMOBJECT_DATA_BYTYPE es el correspondiente a REQUEST1. Lo que contiene esta estructura es un puntero a otra estructura copia de la que hemos definido nosotros, con los datos del simulador en el momento de pedirlos. Podemos acudir a ellos (mediante el puntero pS), y hacer lo que queramos, en este caso los imprimimos en pantalla.

Eso es todo lo concerniente a la función CALLBACK. Ahora veamos la función main, donde inicializamos todo y hacemos las asociaciones.

int _tmain(int argc, _TCHAR* argv[])
{
	HRESULT hr;

    if (SUCCEEDED(SimConnect_Open(&hSimConnect, "VORdriver", NULL, 0, 0, 0)))
    {
        printf("\nConnected to Flight Simulator!");   
        
        // Set up the data definition, but do not yet do anything with it
        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Title", NULL, SIMCONNECT_DATATYPE_STRING256);
        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "NAV CDI:1", "number");
        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "NAV CDI:2", "number");
        hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "NAV GSI:1", "number");

        hr = SimConnect_SetNotificationGroupPriority(hSimConnect, GROUP0, SIMCONNECT_GROUP_PRIORITY_HIGHEST);
	hr = SimConnect_SubscribeToSystemEvent(hSimConnect, CONT, "6Hz");

        while( 1 )
        {
            SimConnect_CallDispatch(hSimConnect, dmeDispatch, NULL);
            Sleep(1);
        } 
        hr = SimConnect_Close(hSimConnect);
    }

	return 0;
}

Lo primero que hacemos es definir una variable del tipo HRESULT que nos permite leer lo que devuelven las funciones de las API. Después intentamos conectarnos al simulador dentro del if mediante la función SimConnect_Open(), la función nos pide una variable del tipo HANDLE y un nombre.

Si consigue conectarse, empezamos a hacer asociaciones. La función SimConnect_AddToDataDefinition nos permite asociar datos a DEFINITION1, el nombre de los datos está en la sección “Simulation Variables” de la documentación del SDK. En nuestro caso son TITLE, NAV CDI:X y NAV GSI:1.

Después la función SimConnect_SetNotificationGroupPriority() define las prioridades comentadas antes, aunque se puede quitar puesto que sólo nos conectamos al simulador.

Por último la función SimConnect_SubscribeToSystemEvent() nos permite decir que eventos queremos que nos notifiquen. Hay otra función SimConnect_MapClientEventToSimEvent(), que permite hacer que salte uno de nuestros eventos cuando salte el del simulador, pero me ha dado problemas.

Pues eso es todo, por último creamos un bucle infinito y usamos la función SimConnect_CallDispatch() continuamente para ver si hay algo que hacer.

Ahora ejecuta el simulador, abre un vuelo, y después compila y ejecuta el código. La consola debería mostrar el texto con el valor de las agujas de los indicador VOR, a una frecuencia de 6 veces por segundo.

Hasta aquí el artículo dedicado exclusivamente a las API, en la documentación hay un montón de ejemplos que me han sido muy útiles. Además en los siguientes artículos en los que hable de cacharros en concreto, también mostraré el código que uso para manejarlos, y lo explicaré.

Un saludo.

Anuncios
Esta entrada fue publicada en Manuales, Tutoriales y etiquetada , , , , , , , , , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s