Merge pull request #218 from nasa/develop

Version 1.3 RC6
This commit is contained in:
Jason Watkins
2020-06-27 12:54:00 -07:00
committed by GitHub
26 changed files with 1846 additions and 666 deletions

1
.gitignore vendored
View File

@@ -93,3 +93,4 @@ out/
# Internal Docs #
#################
Docs/~$Capability Matrix.xlsx
*.xpl

View File

@@ -26,6 +26,8 @@
#include <sys/time.h>
#include "stdio.h"
struct timeval tv;
#ifdef WIN32
HANDLE hStdIn = NULL;
INPUT_RECORD buffer;
@@ -51,7 +53,6 @@ int waitForInput()
#else
int fdstdin = 0;
fd_set fds;
struct timeval tv;
int waitForInput()
{
@@ -70,7 +71,7 @@ int main(void)
tv.tv_usec = 100 * 1000;
while (1)
{
float posi[7]; // FIXME: change this to the 64-bit lat/lon/h
double posi[7];
int result = getPOSI(client, posi, aircraftNum);
if (result < 0) // Error in getPOSI
{

View File

@@ -65,7 +65,7 @@ void record(char* path, int interval, int duration)
XPCSocket sock = openUDP("127.0.0.1");
for (int i = 0; i < count; ++i)
{
float posi[7];
double posi[7];
int result = getPOSI(sock, posi, 0);
playbackSleep(interval);
if (result < 0)

View File

@@ -117,13 +117,16 @@ XPCSocket aopenUDP(const char *xpIP, unsigned short xpPort, unsigned short port)
exit(EXIT_FAILURE);
}
// Set timeout to 100ms
// Set socket timeout period for sendUDP to 1 millisecond
// Without this, playback may become choppy due to process blocking
#ifdef _WIN32
DWORD timeout = 100;
// Minimum socket timeout in Windows is 1 millisecond (0 makes it blocking)
DWORD timeout = 1;
#else
// Set socket timeout to 1 millisecond = 1,000 microseconds to make it the same as Windows (0 makes it blocking)
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 250000;
timeout.tv_usec = 1000;
#endif
if (setsockopt(sock.sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0)
{
@@ -185,16 +188,16 @@ int sendUDP(XPCSocket sock, char buffer[], int len)
/// \param sock The socket to read from.
/// \param buffer A pointer to the location to store the data.
/// \param len The number of bytes to read.
/// \returns If an error occurs, a negative number. Otehrwise, the number of bytes read.
/// \returns If an error occurs, a negative number. Otherwise, the number of bytes read.
int readUDP(XPCSocket sock, char buffer[], int len)
{
#ifdef _WIN32
// Windows readUDP needs the select command- minimum timeout is 1ms.
// Without this playback becomes choppy
// For readUDP, use the select command - minimum timeout of 0 makes it polling.
// Without this, playback may become choppy due to process blocking
// Definitions
FD_SET stReadFDS;
FD_SET stExceptFDS;
fd_set stReadFDS;
fd_set stExceptFDS;
struct timeval timeout;
// Setup for Select
FD_ZERO(&stReadFDS);
@@ -202,12 +205,13 @@ int readUDP(XPCSocket sock, char buffer[], int len)
FD_ZERO(&stExceptFDS);
FD_SET(sock.sock, &stExceptFDS);
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100000;
// Set timeout period for select to 0.05 sec = 50 milliseconds = 50,000 microseconds (0 makes it polling)
// TO DO - This could be set to 0 if a message handling system were implemented, like in the plugin.
timeout.tv_sec = 0;
timeout.tv_usec = 50000;
// Select Command
int status = select(-1, &stReadFDS, (FD_SET*)0, &stExceptFDS, &tv);
int status = select(sock.sock+1, &stReadFDS, NULL, &stExceptFDS, &timeout);
if (status < 0)
{
printError("readUDP", "Select command error");
@@ -218,11 +222,9 @@ int readUDP(XPCSocket sock, char buffer[], int len)
// No data
return 0;
}
// If no error: Read Data
status = recv(sock.sock, buffer, len, 0);
#else
// For apple or linux-just read - will timeout in 0.5 ms
int status = (int)recv(sock.sock, buffer, len, 0);
#endif
if (status < 0)
{
printError("readUDP", "Error reading socket");
@@ -378,16 +380,16 @@ int readDATA(XPCSocket sock, float data[][9], int rows)
/*****************************************************************************/
/**** DREF functions ****/
/*****************************************************************************/
int sendDREF(XPCSocket sock, const char* dref, float value[], int size)
int sendDREF(XPCSocket sock, const char* dref, float values[], int size)
{
return sendDREFs(sock, &dref, &value, &size, 1);
return sendDREFs(sock, &dref, &values, &size, 1);
}
int sendDREFs(XPCSocket sock, const char* drefs[], float* values[], int sizes[], int count)
{
// Setup command
// Max size is technically unlimited.
unsigned char buffer[65536] = "DREF";
char buffer[65536] = "DREF";
int pos = 5;
int i; // Iterator
for (i = 0; i < count; ++i)
@@ -433,7 +435,7 @@ int sendDREFRequest(XPCSocket sock, const char* drefs[], unsigned char count)
// Setup command
// 6 byte header + potentially 255 drefs, each 256 chars long.
// Easiest to just round to an even 2^16.
unsigned char buffer[65536] = "GETD";
char buffer[65536] = "GETD";
buffer[5] = count;
int len = 6;
int i; // iterator
@@ -460,7 +462,7 @@ int sendDREFRequest(XPCSocket sock, const char* drefs[], unsigned char count)
int getDREFResponse(XPCSocket sock, float* values[], unsigned char count, int sizes[])
{
unsigned char buffer[65536];
char buffer[65536];
int result = readUDP(sock, buffer, 65536);
if (result < 0)
@@ -516,7 +518,7 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char
int result = sendDREFRequest(sock, drefs, count);
if (result < 0)
{
// A error ocurred while sending.
// An error ocurred while sending.
// sendDREFRequest will print an error message, so just return.
return -1;
}
@@ -524,7 +526,7 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char
// Read Response
if (getDREFResponse(sock, values, count, sizes) < 0)
{
// A error ocurred while reading the response.
// An error ocurred while reading the response.
// getDREFResponse will print an error message, so just return.
return -2;
}
@@ -537,10 +539,10 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char
/*****************************************************************************/
/**** POSI functions ****/
/*****************************************************************************/
int getPOSI(XPCSocket sock, float values[7], char ac)
int getPOSI(XPCSocket sock, double values[7], char ac)
{
// Setup send command
unsigned char buffer[6] = "GETP";
char buffer[6] = "GETP";
buffer[5] = ac;
// Send command
@@ -551,22 +553,41 @@ int getPOSI(XPCSocket sock, float values[7], char ac)
}
// Get response
unsigned char readBuffer[34];
int readResult = readUDP(sock, readBuffer, 34);
char readBuffer[46];
float f[7];
int readResult = readUDP(sock, readBuffer, 46);
// Copy response into values
if (readResult < 0)
{
printError("getPOSI", "Failed to read response.");
return -2;
}
if (readResult != 34)
else if (readResult == 34) /* lat/lon/h as 32-bit float */
{
memcpy(f, readBuffer + 6, 7 * sizeof(float));
values[0] = (double)f[0];
values[1] = (double)f[1];
values[2] = (double)f[2];
values[3] = (double)f[3];
values[4] = (double)f[4];
values[5] = (double)f[5];
values[6] = (double)f[6];
}
else if (readResult == 46) /* lat/lon/h as 64-bit double */
{
memcpy(values, readBuffer + 6, 3 * sizeof(double));
memcpy(f, readBuffer + 30, 4 * sizeof(float));
values[3] = (double)f[0];
values[4] = (double)f[1];
values[5] = (double)f[2];
values[6] = (double)f[3];
}
else
{
printError("getPOSI", "Unexpected response length.");
return -3;
}
// TODO: change this to the 64-bit lat/lon/h
// Copy response into values
memcpy(values, readBuffer + 6, 7 * sizeof(float));
return 0;
}
@@ -585,7 +606,7 @@ int sendPOSI(XPCSocket sock, double values[], int size, char ac)
}
// Setup command
unsigned char buffer[46] = "POSI";
char buffer[46] = "POSI";
buffer[4] = 0xff; //Placeholder for message length
buffer[5] = ac;
int i; // iterator
@@ -621,13 +642,147 @@ int sendPOSI(XPCSocket sock, double values[], int size, char ac)
/**** End POSI functions ****/
/*****************************************************************************/
/*****************************************************************************/
/**** TERR functions ****/
/*****************************************************************************/
int sendTERRRequest(XPCSocket sock, double posi[3], char ac)
{
// Setup send command
char buffer[30] = "GETT";
buffer[5] = ac;
memcpy(&buffer[6], posi, 3 * sizeof(double));
// Send command
if (sendUDP(sock, buffer, 30) < 0)
{
printError("getTERR", "Failed to send command.");
return -1;
}
return 0;
}
int getTERRResponse(XPCSocket sock, double values[11], char ac)
{
// Get response
char readBuffer[62];
int readResult = readUDP(sock, readBuffer, 62);
if (readResult < 0)
{
printError("getTERR", "Failed to read response.");
return -2;
}
if (readResult != 62)
{
printError("getTERR", "Unexpected response length.");
return -3;
}
// Copy response into outputs
float f[8];
ac = readBuffer[5];
memcpy(values, readBuffer + 6, 3 * sizeof(double));
memcpy(f, readBuffer + 30, 8 * sizeof(float));
values[ 3] = (double)f[0];
values[ 4] = (double)f[1];
values[ 5] = (double)f[2];
values[ 6] = (double)f[3];
values[ 7] = (double)f[4];
values[ 8] = (double)f[5];
values[ 9] = (double)f[6];
values[10] = (double)f[7];
return 0;
}
int sendPOST(XPCSocket sock, double posi[], int size, double values[11], char ac)
{
// Validate input
if (ac < 0 || ac > 20)
{
printError("sendPOST", "aircraft should be a value between 0 and 20.");
return -1;
}
if (size < 1 || size > 7)
{
printError("sendPOST", "size should be a value between 1 and 7.");
return -2;
}
// Setup command
char buffer[46] = "POST";
buffer[4] = 0xff; //Placeholder for message length
buffer[5] = ac;
int i; // iterator
for (i = 0; i < 7; i++) // double for lat/lon/h
{
double val = -998;
if (i < size)
{
val = posi[i];
}
if (i < 3) /* lat/lon/h */
{
memcpy(&buffer[6 + i*8], &val, sizeof(double));
}
else /* attitude and gear */
{
float f = (float)val;
memcpy(&buffer[18 + i*4], &f, sizeof(float));
}
}
// Send Command
if (sendUDP(sock, buffer, 46) < 0)
{
printError("sendPOST", "Failed to send command");
return -3;
}
// Read Response
int result = getTERRResponse(sock, values, ac);
if (result < 0)
{
// A error ocurred while reading the response.
// getTERRResponse will print an error message, so just return.
return result;
}
return 0;
}
int getTERR(XPCSocket sock, double posi[3], double values[11], char ac)
{
// Send Command
int result = sendTERRRequest(sock, posi, ac);
if (result < 0)
{
// An error ocurred while sending.
// sendTERRRequest will print an error message, so just return.
return result;
}
// Read Response
result = getTERRResponse(sock, values, ac);
if (result < 0)
{
// An error ocurred while reading the response.
// getTERRResponse will print an error message, so just return.
return result;
}
return 0;
}
/*****************************************************************************/
/**** End TERR functions ****/
/*****************************************************************************/
/*****************************************************************************/
/**** CTRL functions ****/
/*****************************************************************************/
int getCTRL(XPCSocket sock, float values[7], char ac)
{
// Setup send command
unsigned char buffer[6] = "GETC";
char buffer[6] = "GETC";
buffer[5] = ac;
// Send command
@@ -638,7 +793,7 @@ int getCTRL(XPCSocket sock, float values[7], char ac)
}
// Get response
unsigned char readBuffer[31];
char readBuffer[31];
int readResult = readUDP(sock, readBuffer, 31);
if (readResult < 0)
{
@@ -675,7 +830,7 @@ int sendCTRL(XPCSocket sock, float values[], int size, char ac)
// Setup Command
// 5 byte header + 5 float values * 4 + 2 byte values
unsigned char buffer[31] = "CTRL";
char buffer[31] = "CTRL";
int cur = 5;
int i; // iterator
for (i = 0; i < 6; i++)
@@ -818,3 +973,40 @@ int sendVIEW(XPCSocket sock, VIEW_TYPE view)
/*****************************************************************************/
/**** End View functions ****/
/*****************************************************************************/
/*****************************************************************************/
/**** Comm functions ****/
/*****************************************************************************/
int sendCOMM(XPCSocket sock, const char* comm) {
// Setup command
// Max size is technically unlimited.
unsigned char buffer[65536] = "COMM";
int pos = 5;
int commLen = strnlen(comm, 256);
if (pos + commLen + 2 > 65536)
{
printError("sendCOMM", "About to overrun the send buffer!");
return -4;
}
if (commLen > 255)
{
printError("sendCOMM", "comm is too long. Must be less than 256 characters.");
return -1;
}
// Copy comm to buffer
buffer[pos++] = (unsigned char)commLen;
memcpy(buffer + pos, comm, commLen);
pos += commLen;
// Send command
if (sendUDP(sock, buffer, pos) < 0)
{
printError("setDREF", "Failed to send command");
return -3;
}
return 0;
}
/*****************************************************************************/
/**** End Comm functions ****/
/*****************************************************************************/

View File

@@ -146,10 +146,10 @@ int sendDATA(XPCSocket sock, float data[][9], int rows);
/// the type described on the wiki. This doesn't cause any data loss for most datarefs.
/// \param sock The socket to use to send the command.
/// \param dref The name of the dataref to set.
/// \param value An array of values representing the data to set.
/// \param values An array of values representing the data to set.
/// \param size The number of elements in values.
/// \returns 0 if successful, otherwise a negative value.
int sendDREF(XPCSocket sock, const char* dref, float value[], int size);
int sendDREF(XPCSocket sock, const char* dref, float values[], int size);
/// Sets the specified datarefs to the specified values.
///
@@ -159,7 +159,7 @@ int sendDREF(XPCSocket sock, const char* dref, float value[], int size);
/// the type described on the wiki. This doesn't cause any data loss for most datarefs.
/// \param sock The socket to use to send the command.
/// \param drefs The names of the datarefs to set.
/// \param values A multidimensional array containing the values for each dataref to set.
/// \param values A 2D array containing the values for each dataref to set.
/// \param sizes The number of elements in each array in values
/// \param count The number of datarefs being set.
/// \returns 0 if successful, otherwise a negative value.
@@ -173,13 +173,13 @@ int sendDREFs(XPCSocket sock, const char* drefs[], float* values[], int sizes[],
/// the type described on the wiki. This doesn't cause any data loss for most datarefs.
/// \param sock The socket to use to send the command.
/// \param dref The name of the dataref to get.
/// \param values The array in which the value of the dataref will be stored.
/// \param values The array in which the values of the dataref will be stored.
/// \param size The number of elements in values. The actual number of elements copied in will
/// be set when the function returns.
/// \returns 0 if successful, otherwise a negative value.
int getDREF(XPCSocket sock, const char* dref, float values[], int* size);
/// Gets the value of the specified dataref.
/// Gets the values of the specified datarefs.
///
/// \details dref names and their associated data types can be found on the XPSDK wiki at
/// http://www.xsquawkbox.net/xpsdk/docs/DataRefs.html. The size of values should match
@@ -189,7 +189,7 @@ int getDREF(XPCSocket sock, const char* dref, float values[], int* size);
/// \param drefs The names of the datarefs to get.
/// \param values A 2D array in which the values of the datarefs will be stored.
/// \param count The number of datarefs being requested.
/// \param size The number of elements in each row of values. The size of each row will be set
/// \param sizes The number of elements in each row of values. The size of each row will be set
/// to the actual number of elements copied in for that row.
/// \returns 0 if successful, otherwise a negative value.
int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char count, int sizes[]);
@@ -201,20 +201,56 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char
/// \param sock The socket used to send the command and receive the response.
/// \param values An array to store the position information returned by the
/// plugin. The format of values is [Lat, Lon, Alt, Pitch, Roll, Yaw, Gear]
/// \param ac The aircraft number to get the position of. 0 for the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int getPOSI(XPCSocket sock, float values[7], char ac);
int getPOSI(XPCSocket sock, double values[7], char ac);
/// Sets the position and orientation of the specified aircraft.
///
/// \param sock The socket to use to send the command.
/// \param values An array representing position data about the aircraft. The format of values is
/// \param values A double array representing position data about the aircraft. The format of values is
/// [Lat, Lon, Alt, Pitch, Roll, Yaw, Gear]. If less than 7 values are specified,
/// the unspecified values will be left unchanged.
/// \param size The number of elements in values.
/// \param ac The aircraft number to set the position of. 0 for the player aircraft.
/// \param ac The aircraft number to set the position of. 0 for the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int sendPOSI(XPCSocket sock, double values[], int size, char ac);
// Terrain
/// Sets the position and orientation and gets the terrain information of the specified aircraft.
///
/// \param sock The socket to use to send the command.
/// \param posi A double array representing position data about the aircraft. The format of values is
/// [Lat, Lon, Alt, Pitch, Roll, Yaw, Gear]. If less than 7 values are specified,
/// the unspecified values will be left unchanged.
/// \param size The number of elements in posi.
/// \param values A double array with the information for the terrain output. The format of values is
/// [Lat, Lon, Alt, Nx, Ny, Nz, Vx, Vy, Vz, wet, result]. The first three are for output of
/// the Lat and Lon of the aircraft with the terrain height directly below. The next three
/// represent the terrain normal. The next three represent the velocity of the terrain.
/// The wet variable is 0.0 if the terrain is dry and 1.0 if wet.
/// The last output is the terrain probe result parameter.
/// \param ac The aircraft number to set the position of. 0 for the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int sendPOST(XPCSocket sock, double posi[], int size, double values[11], char ac);
/// Gets the terrain information of the specified aircraft.
///
/// \param sock The socket to use to send the command.
/// \param posi A double array representing position data about the aircraft. The format of values is
/// [Lat, Lon, Alt].
/// -998 used for [Lat, Lon, Alt] to request terrain info at the current aircraft position.
/// \param values A double array with the information for the terrain output. The format of values is
/// [Lat, Lon, Alt, Nx, Ny, Nz, Vx, Vy, Vz, wet, result]. The first three are for output of
/// the Lat and Lon of the aircraft with the terrain height directly below. The next three
/// represent the terrain normal. The next three represent the velocity of the terrain.
/// The wet variable is 0.0 if the terrain is dry and 1.0 if wet.
/// The last output is the terrain probe result parameter.
/// \param ac The aircraft number to get the terrain data of. 0 for the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int getTERR(XPCSocket sock, double posi[3], double values[11], char ac);
// Controls
/// Gets the control surface information for the specified aircraft.
@@ -223,7 +259,7 @@ int sendPOSI(XPCSocket sock, double values[], int size, char ac);
/// \param values An array to store the position information returned by the
/// plugin. The format of values is [Elevator, Aileron, Rudder,
/// Throttle, Gear, Flaps, Speed Brakes]
/// \param ac The aircraft to set the control surfaces of. 0 is the main/player aircraft.
/// \param ac The aircraft to get the control surfaces of. 0 is the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int getCTRL(XPCSocket sock, float values[7], char ac);
@@ -234,7 +270,7 @@ int getCTRL(XPCSocket sock, float values[7], char ac);
/// [Elevator, Aileron, Rudder, Throttle, Gear, Flaps, Speed Brakes]. If less than
/// 6 values are specified, the unspecified values will be left unchanged.
/// \param size The number of elements in values.
/// \param ac The aircraft number to set the control surfaces of. 0 for the player aircraft.
/// \param ac The aircraft to set the control surfaces of. 0 for the main/user's aircraft.
/// \returns 0 if successful, otherwise a negative value.
int sendCTRL(XPCSocket sock, float values[], int size, char ac);
@@ -267,6 +303,13 @@ int sendVIEW(XPCSocket sock, VIEW_TYPE view);
/// \returns 0 if successful, otherwise a negative value.
int sendWYPT(XPCSocket sock, WYPT_OP op, float points[], int count);
/// Sends commands.
///
/// \param sock The socket to use to send the command.
/// \param comm The command string.
/// \returns 0 if successful, otherwise a negative value.
int sendCOMM(XPCSocket sock, const char* comm);
#ifdef __cplusplus
}
#endif

View File

@@ -889,6 +889,42 @@ public class XPlaneConnect implements AutoCloseable
sendUDP(os.toByteArray());
}
/**
* Send a command to X-Plane.
*
* @param comm The name of the X-Plane command to send.
* @throws IOException If the command cannot be sent.
*/
public void sendCOMM(String comm) throws IOException
{
//Preconditions
if(comm == null || comm.length() == 0)
{
throw new IllegalArgumentException(("comm must be non-empty."));
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("COMM".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
//Convert comm to bytes.
byte[] commBytes = comm.getBytes(StandardCharsets.UTF_8);
if (commBytes.length == 0)
{
throw new IllegalArgumentException("COMM is an empty string!");
}
if (commBytes.length > 255)
{
throw new IllegalArgumentException("comm must be less than 255 bytes in UTF-8. Are you sure this is a valid comm?");
}
//Build and send message
os.write(commBytes.length);
os.write(commBytes);
sendUDP(os.toByteArray());
}
/**
* Sets the port on which the client will receive data from X-Plane.
*

View File

@@ -416,6 +416,27 @@ class XPlaneConnect(object):
buffer = struct.pack("<4sxBB" + str(len(points)) + "f", "WYPT", op, len(points), *points)
self.sendUDP(buffer)
def sendCOMM(self, comm):
'''Sets the specified datarefs to the specified values.
Args:
drefs: A list of names of the datarefs to set.
values: A list of scalar or vector values to set.
'''
if comm == None:
raise ValueError("comm must be non-empty.")
buffer = struct.pack("<4sx", "COMM")
if len(comm) == 0 or len(comm) > 255:
raise ValueError("comm must be a non-empty string less than 256 characters.")
# Pack message
fmt = "<B{0:d}s".format(len(comm))
buffer += struct.pack(fmt, len(comm), comm)
# Send
self.sendUDP(buffer)
class ViewType(object):
Forwards = 73
Down = 74

View File

@@ -1,7 +1,6 @@
import socket
import struct
class XPlaneConnect(object):
"""XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin."""
socket = None
@@ -121,7 +120,7 @@ class XPlaneConnect(object):
buffer = self.readUDP()
if len(buffer) < 6:
return None
rows = (len(buffer) - 5) / 36
rows = (len(buffer) - 5) // 36
data = []
for i in range(rows):
data.append(struct.unpack_from(b"9f", buffer, 5 + 36*i))
@@ -158,10 +157,13 @@ class XPlaneConnect(object):
# Read response
resultBuf = self.readUDP()
if len(resultBuf) != 34:
if len(resultBuf) == 34:
result = struct.unpack(b"<4sxBfffffff", resultBuf)
elif len(resultBuf) == 46:
result = struct.unpack(b"<4sxBdddffff", resultBuf)
else:
raise ValueError("Unexpected response length.")
result = struct.unpack(b"<4sxBfffffff", resultBuf)
if result[0] != b"POSI":
raise ValueError("Unexpected header: " + result[0])
@@ -197,6 +199,9 @@ class XPlaneConnect(object):
val = -998
if i < len(values):
val = values[i]
if i < 3:
buffer += struct.pack(b"<d", val)
else:
buffer += struct.pack(b"<f", val)
# Send
@@ -257,7 +262,7 @@ class XPlaneConnect(object):
val = values[i]
if i == 4:
val = -1 if (abs(val + 998) < 1e-4) else val
buffer += struct.pack(b"b", val)
buffer += struct.pack(b"b", int(val))
else:
buffer += struct.pack(b"<f", val)

View File

@@ -6,7 +6,7 @@
#include "Test.h"
#include "xplaneConnect.h"
int doCTRLTest(XPCSocket *sock, char* drefs[7], float values[], int size, int ac, float expected[7])
int doCTRLTest(XPCSocket *sock, const char* drefs[7], float values[], int size, int ac, float expected[7])
{
float* data[7];
int sizes[7];
@@ -66,7 +66,7 @@ int doGETCTest(float values[7], int ac, float expected[7])
return 0;
}
int basicCTRLTest(char** drefs, int ac)
int basicCTRLTest(const char** drefs, int ac)
{
// Set control surfaces to known state.
float CTRL[6] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F };
@@ -117,7 +117,7 @@ int basicCTRLTest(char** drefs, int ac)
int testCTRL_Player()
{
char* drefs[] =
const char* drefs[] =
{
"sim/cockpit2/controls/yoke_pitch_ratio",
"sim/cockpit2/controls/yoke_roll_ratio",
@@ -132,7 +132,7 @@ int testCTRL_Player()
int testCTRL_NonPlayer()
{
char* drefs[] =
const char* drefs[] =
{
"sim/multiplayer/position/plane1_yolk_pitch",
"sim/multiplayer/position/plane1_yolk_roll",
@@ -147,7 +147,7 @@ int testCTRL_NonPlayer()
int testCTRL_Speedbrakes()
{
char* drefs[] =
const char* drefs[] =
{
"sim/cockpit2/controls/yoke_pitch_ratio",
"sim/cockpit2/controls/yoke_roll_ratio",

View File

@@ -10,7 +10,7 @@ int testDATA()
{
// Initialize
int i, j; // Iterator
char* drefs[100] =
const char* drefs[100] =
{
"sim/aircraft/parts/acf_gear_deploy"
};

View File

@@ -6,7 +6,7 @@
#include "Test.h"
#include "xplaneConnect.h"
int doGETDTest(char* drefs[], float* expected[], int count, int sizes[])
int doGETDTest(const char* drefs[], float* expected[], int count, int sizes[])
{
// Setup memory
int* asizes = (int*)malloc(sizeof(int) * count);
@@ -37,7 +37,7 @@ int doGETDTest(char* drefs[], float* expected[], int count, int sizes[])
return result;
}
int doDREFTest(char* drefs[], float* values[], float* expected[], int count, int sizes[])
int doDREFTest(const char* drefs[], float* values[], float* expected[], int count, int sizes[])
{
// Setup memory
int* asizes = (int*)malloc(sizeof(int) * count);
@@ -74,7 +74,7 @@ int doDREFTest(char* drefs[], float* values[], float* expected[], int count, int
int testGETD_Basic()
{
char* drefs[] =
const char* drefs[] =
{
"sim/cockpit/switches/gear_handle_status", //int
"sim/cockpit/autopilot/altitude", //float
@@ -99,20 +99,20 @@ int testGETD_Basic()
int testGETD_TestFloat()
{
char* dref = "sim/test/test_float";
const char* dref = "sim/test/test_float";
int size = 1;
float* expected[1];
expected[0] = (float*)malloc(sizeof(float));
expected[0][0] = 0.0F;
int result = doGETDTest(&dref, &expected, 1, &size);
int result = doGETDTest(&dref, expected, 1, &size);
free(expected[0]);
return result;
}
int testGETD_Types()
{
char* drefs[] =
const char* drefs[] =
{
"sim/cockpit/switches/gear_handle_status", //int
"sim/cockpit/autopilot/altitude", //float
@@ -173,7 +173,7 @@ int testGETD_Types()
int testDREF()
{
char* drefs[] =
const char* drefs[] =
{
"sim/cockpit/switches/gear_handle_status", //int
"sim/cockpit/autopilot/altitude", //float

View File

@@ -6,7 +6,7 @@
#include "Test.h"
#include "xplaneConnect.h"
int doPOSITest(char* drefs[7], double values[], int size, int ac, double expected[7])
int doPOSITest(const char* drefs[7], double values[], int size, int ac, double expected[7])
{
float* data[7];
int sizes[7];
@@ -43,7 +43,7 @@ int doPOSITest(char* drefs[7], double values[], int size, int ac, double expecte
int doGETPTest(double values[7], int ac, double expected[7])
{
// Execute Test
float actual[7];
double actual[7];
XPCSocket sock = openUDP(IP);
int result = sendPOSI(sock, values, 7, ac);
if (result >= 0)
@@ -67,7 +67,7 @@ int doGETPTest(double values[7], int ac, double expected[7])
return 0;
}
int basicPOSITest(char** drefs, int ac)
int basicPOSITest(const char** drefs, int ac)
{
// Set psoition and initial orientation
double POSI[7] = { 37.524, -122.06899, 2500, 0, 0, 0, 1 };
@@ -115,7 +115,7 @@ int basicPOSITest(char** drefs, int ac)
int testPOSI_Player()
{
char* drefs[] =
const char* drefs[] =
{
"sim/flightmodel/position/latitude",
"sim/flightmodel/position/longitude",
@@ -130,7 +130,7 @@ int testPOSI_Player()
int testPOSI_NonPlayer()
{
char* drefs[] =
const char* drefs[] =
{
"sim/multiplayer/position/plane1_lat",
"sim/multiplayer/position/plane1_lon",

View File

@@ -0,0 +1,434 @@
import random
import unittest
import importlib
import time
from importlib.machinery import SourceFileLoader
xpc = SourceFileLoader('xpc', '../../Python3/src/xpc.py').load_module()
class XPCTests(unittest.TestCase):
"""Tests the functionality of the XPlaneConnect class."""
def test_init(self):
try:
client = xpc.XPlaneConnect()
except:
self.fail("Default constructor failed.")
try:
client = xpc.XPlaneConnect("I'm not a real host")
self.fail("Failed to catch invalid XP host.")
except ValueError:
pass
try:
client = xpc.XPlaneConnect("127.0.0.1", 90001)
self.fail("Failed to catch invalid XP port.")
except ValueError:
pass
try:
client = xpc.XPlaneConnect("127.0.0.1", -1)
self.fail("Failed to catch invalid XP port.")
except ValueError:
pass
try:
client = xpc.XPlaneConnect("127.0.0.1", 49009, 90001)
self.fail("Failed to catch invalid local port.")
except ValueError:
pass
try:
client = xpc.XPlaneConnect("127.0.0.1", 49009, -1)
self.fail("Failed to catch invalid XP port.")
except ValueError:
pass
try:
client = xpc.XPlaneConnect("127.0.0.1", 49009, 0, -1)
self.fail("Failed to catch invalid timeout.")
except ValueError:
pass
def test_close(self):
client = xpc.XPlaneConnect("127.0.0.1", 49009, 49063)
client.close()
self.assertIsNone(client.socket)
client = xpc.XPlaneConnect("127.0.0.1", 49009, 49063)
def test_send_read(self):
# Init
test = "\x00\x01\x02\x03\x05"
# Setup
sender = xpc.XPlaneConnect("127.0.0.1", 49063, 49064)
receiver = xpc.XPlaneConnect("127.0.0.1", 49009, 49063)
# Execution
sender.sendUDP(test.encode())
buf = receiver.readUDP()
# Cleanup
sender.close()
receiver.close()
# Tests
for a, b in zip(test, buf.decode()):
self.assertEqual(a, b)
def test_getDREFs(self):
# Setup
client = xpc.XPlaneConnect()
drefs = ["sim/cockpit/switches/gear_handle_status",\
"sim/cockpit2/switches/panel_brightness_ratio"]
# Execution
result = client.getDREFs(drefs)
# Cleanup
client.close()
# Tests
self.assertEqual(2, len(result))
self.assertEqual(1, len(result[0]))
self.assertEqual(4, len(result[1]))
def test_sendDREF(self):
dref = "sim/cockpit/switches/gear_handle_status"
value = None
def do_test():
# Setup
client = xpc.XPlaneConnect()
# Execute
client.sendDREF(dref, value)
result = client.getDREF(dref)
# Cleanup
client.close()
# Tests
self.assertEqual(1, len(result))
self.assertEqual(value, result[0])
# Test 1
value = 1
do_test()
# Test 2
value = 0
do_test()
def test_sendDREFs(self):
drefs = [\
"sim/cockpit/switches/gear_handle_status",\
"sim/cockpit/autopilot/altitude"]
values = None
def do_test():
# Setup
client = xpc.XPlaneConnect()
# Execute
client.sendDREFs(drefs, values)
result = client.getDREFs(drefs)
# Cleanup
client.close()
# Tests
self.assertEqual(2, len(result))
self.assertEqual(1, len(result[0]))
self.assertEqual(values[0], result[0][0])
self.assertEqual(1, len(result[1]))
self.assertEqual(values[1], result[1][0])
# Test 1
values = [1, 2000]
do_test()
# Test 2
values = [0, 4000]
do_test()
def test_sendDATA(self):
# Setup
dref = "sim/aircraft/parts/acf_gear_deploy"
data = [[ 14, 1, 0, -998, -998, -998, -998, -998, -998 ]]
client = xpc.XPlaneConnect()
# Execute
client.sendDATA(data)
result = client.getDREF(dref)
# Cleanup
client.close()
#Tests
self.assertEqual(result[0], data[0][1])
def test_pauseSim(self):
dref = "sim/operation/override/override_planepath"
value = None
expected = None
def do_test():
# Setup
client = xpc.XPlaneConnect()
# Execute
client.pauseSim(value)
result = client.getDREF(dref)
# Cleanup
client.close()
# Test
self.assertAlmostEqual(expected, result[0])
# Test 1
value = True
expected = 1.0
do_test()
# Test 2
value = False
expected = 0.0
do_test()
# Test 3
value = 1
expected = 1.0
do_test()
# Test 4
value = 2
expected = 0.0
do_test()
def test_getCTRL(self):
values = None
ac = 0
expected = None
def do_test():
with xpc.XPlaneConnect() as client:
# Execute
client.sendCTRL(values, ac)
result = client.getCTRL(ac)
# Test
self.assertEqual(len(result), len(expected))
for a, e in zip(result, expected):
self.assertAlmostEqual(a, e, 4)
values = [0.0, 0.0, 0.0, 0.8, 1.0, 0.5, -1.5]
expected = values
ac = 0
do_test()
ac = 3
do_test()
def test_sendCTRL(self):
# Setup
drefs = ["sim/cockpit2/controls/yoke_pitch_ratio",\
"sim/cockpit2/controls/yoke_roll_ratio",\
"sim/cockpit2/controls/yoke_heading_ratio",\
"sim/flightmodel/engine/ENGN_thro",\
"sim/cockpit/switches/gear_handle_status",\
"sim/flightmodel/controls/flaprqst"]
ctrl = []
def do_test():
client = xpc.XPlaneConnect()
# Execute
client.sendCTRL(ctrl)
result = client.getDREFs(drefs)
# Cleanup
client.close()
# Tests
self.assertEqual(6, len(result))
for i in range(6):
self.assertAlmostEqual(ctrl[i], result[i][0], 4)
# Test 1
ctrl = [ -1.0, -1.0, -1.0, 0.0, 1.0, 1.0 ]
do_test()
# Test 2
ctrl = [ 1.0, 1.0, 1.0, 0.0, 1.0, 0.5 ]
do_test()
# Test 2
ctrl = [ 0.0, 0.0, 0.0, 0.8, 1.0, 0.0 ]
do_test()
def test_sendCTRL_speedbrake(self):
# Setup
dref = "sim/flightmodel/controls/sbrkrqst"
ctrl = []
def do_test():
client = xpc.XPlaneConnect()
# Execute
client.sendCTRL(ctrl)
result = client.getDREF(dref)
# Cleanup
client.close()
# Tests
self.assertAlmostEqual(result[0], ctrl[6])
# Test 1
ctrl = [-998, -998, -998, -998, -998, -998, -0.5]
do_test()
# Test 2
ctrl[6] = 1.0
do_test()
# Test 2
ctrl[6] = 0.0
do_test()
def test_getPOSI(self):
values = None
ac = 0
expected = None
def do_test():
with xpc.XPlaneConnect() as client:
# Execute
client.pauseSim(True)
client.sendPOSI(values, ac)
result = client.getPOSI(ac)
client.pauseSim(False)
# Test
self.assertEqual(len(result), len(expected))
for a, e in zip(result, expected):
self.assertAlmostEqual(a, e, 4)
values = [ 37.524, -122.06899, 2500, 45, -45, 15, 1 ]
expected = values
ac = 0
do_test()
ac = 3
do_test()
def test_sendPOSI(self):
# Setup
drefs = ["sim/flightmodel/position/latitude",\
"sim/flightmodel/position/longitude",\
"sim/flightmodel/position/elevation",\
"sim/flightmodel/position/theta",\
"sim/flightmodel/position/phi",\
"sim/flightmodel/position/psi",\
"sim/cockpit/switches/gear_handle_status"]
posi = None
def do_test():
client = xpc.XPlaneConnect()
# Execute
client.pauseSim(True)
client.sendPOSI(posi)
result = client.getDREFs(drefs)
client.pauseSim(False)
# Cleanup
client.close()
# Tests
self.assertEqual(7, len(result))
for i in range(7):
self.assertAlmostEqual(posi[i], result[i][0], 4)
# Test 1
posi = [ 37.524, -122.06899, 2500, 5, 7, 11, 1 ]
do_test()
# Test 2
posi = [ 38, -121.0, 2000, -10, 0, 0, 0 ]
do_test()
def test_sendTEXT(self):
# Setup
client = xpc.XPlaneConnect()
x = 200
y = 700
msg = "Python sendTEXT test message."
# Execution
client.sendTEXT(msg, x, y)
# NOTE: Manually verify that msg appears on the screen in X-Plane
# Cleanup
client.close()
def test_sendView(self):
# Setup
dref = "sim/graphics/view/view_type"
fwd = 1000
chase = 1017
#Execution
with xpc.XPlaneConnect() as client:
client.sendVIEW(xpc.ViewType.Forwards)
result = client.getDREF(dref)
self.assertAlmostEqual(fwd, result[0], 1e-4)
client.sendVIEW(xpc.ViewType.Chase)
result = client.getDREF(dref)
self.assertAlmostEqual(chase, result[0], 1e-4)
def test_sendWYPT(self):
# Setup
client = xpc.XPlaneConnect()
points = [\
37.5245, -122.06899, 2500,\
37.455397, -122.050037, 2500,\
37.469567, -122.051411, 2500,\
37.479376, -122.060509, 2300,\
37.482237, -122.076130, 2100,\
37.474881, -122.087288, 1900,\
37.467660, -122.079391, 1700,\
37.466298, -122.090549, 1500,\
37.362562, -122.039223, 1000,\
37.361448, -122.034416, 1000,\
37.361994, -122.026348, 1000,\
37.365541, -122.022572, 1000,\
37.373727, -122.024803, 1000,\
37.403869, -122.041283, 50,\
37.418544, -122.049222, 6]
# Execution
client.sendPOSI([37.5245, -122.06899, 2500])
client.sendWYPT(3, [])
client.sendWYPT(1, points)
# NOTE: Manually verify that points appear on the screen in X-Plane
# Cleanup
client.close()
def test_setCONN(self):
# Setup
dref = "sim/cockpit/switches/gear_handle_status";
client = xpc.XPlaneConnect()
# Execute
client.setCONN(49055)
result = client.getDREF(dref)
# Cleanup
client.close()
# Test
self.assertEqual(1, len(result))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>6931ebb2-4e01-4c5a-86b6-668c0e75051b</ProjectGuid>
<ProjectHome>.</ProjectHome>
<StartupFile>Tests.py</StartupFile>
<SearchPath>..\..\Python\src\</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<Name>Tests</Name>
<RootNamespace>Tests</RootNamespace>
<InterpreterId>{9a7a9026-48c1-4688-9d5d-e5699d47d074}</InterpreterId>
<InterpreterVersion>2.7</InterpreterVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
</PropertyGroup>
<ItemGroup>
<Compile Include="Tests.py" />
</ItemGroup>
<ItemGroup>
<InterpreterReference Include="{9a7a9026-48c1-4688-9d5d-e5699d47d074}\2.7" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<PtvsTargetsFile>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets</PtvsTargetsFile>
</PropertyGroup>
<Import Condition="Exists($(PtvsTargetsFile))" Project="$(PtvsTargetsFile)" />
<Import Condition="!Exists($(PtvsTargetsFile))" Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<!-- Uncomment the CoreCompile target to enable the Build command in
Visual Studio and specify your pre- and post-build commands in
the BeforeBuild and AfterBuild targets below. -->
<!--<Target Name="CoreCompile" />-->
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
</Project>

View File

@@ -10,7 +10,7 @@ trigger:
- ms-*
tags:
include:
- '*'
- "*"
# pr:
# - '*'
variables:
@@ -27,58 +27,57 @@ variables:
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#sep-github
xpcGitHubConnection: XPC-Azure
stages:
- stage: Build
jobs:
- job: macOS
pool:
vmImage: 'macos-latest'
vmImage: "macos-latest"
steps:
- task: Xcode@5
inputs:
actions: 'build'
scheme: ''
sdk: 'macosx10.14'
configuration: 'Release'
xcWorkspacePath: 'xpcPlugin/xpcPlugin.xcodeproj'
xcodeVersion: 'default' # Options: 8, 9, 10, default, specifyPath
actions: "build"
scheme: ""
sdk: "macosx10.15"
configuration: "Release"
xcWorkspacePath: "xpcPlugin/xpcPlugin.xcodeproj"
xcodeVersion: "default" # Options: 8, 9, 10, default, specifyPath
- task: PublishPipelineArtifact@0
inputs:
artifactName: '$(xpcArtifactNameMac)'
targetPath: '$(xpcBuildOutputPath)'
artifactName: "$(xpcArtifactNameMac)"
targetPath: "$(xpcBuildOutputPath)"
- job: Linux
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- script: |
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
sudo apt-get update
sudo apt-get install -y g++-5 mesa-common-dev g++-multilib g++-5-multilib
displayName: 'apt install'
displayName: "apt install"
- script: |
cd xpcPlugin
cmake .
make
displayName: 'make'
displayName: "make"
- task: PublishPipelineArtifact@0
inputs:
artifactName: 'xpc-linux'
targetPath: '$(xpcBuildOutputPath)'
artifactName: "xpc-linux"
targetPath: "$(xpcBuildOutputPath)"
- job: Windows
pool:
vmImage: 'vs2017-win2016'
vmImage: "vs2017-win2016"
variables:
solution: 'xpcPlugin/xpcPlugin/xpcPlugin.sln'
buildPlatform: 'Win32|x64'
buildConfiguration: 'Release'
solution: "xpcPlugin/xpcPlugin/xpcPlugin.sln"
buildPlatform: "Win32|x64"
buildConfiguration: "Release"
appxPackageDir: '$(build.artifactStagingDirectory)\AppxPackages\\'
steps:
@@ -86,25 +85,25 @@ stages:
- task: NuGetCommand@2
inputs:
restoreSolution: '$(solution)'
restoreSolution: "$(solution)"
- task: VSBuild@1
inputs:
platform: 'Win32'
solution: '$(solution)'
configuration: '$(buildConfiguration)'
platform: "Win32"
solution: "$(solution)"
configuration: "$(buildConfiguration)"
msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload'
- task: VSBuild@1
inputs:
platform: 'x64'
solution: '$(solution)'
configuration: '$(buildConfiguration)'
platform: "x64"
solution: "$(solution)"
configuration: "$(buildConfiguration)"
msbuildArgs: '/p:AppxBundlePlatforms="$(buildPlatform)" /p:AppxPackageDir="$(appxPackageDir)" /p:AppxBundle=Always /p:UapAppxPackageBuildMode=StoreUpload'
- task: Bash@3
inputs:
targetType: 'inline'
targetType: "inline"
script: |
rm $(xpcBuildOutputPath)/win.exp
rm $(xpcBuildOutputPath)/win.lib
@@ -113,54 +112,54 @@ stages:
- task: PublishPipelineArtifact@0
inputs:
artifactName: '$(xpcArtifactNameWindows)'
targetPath: '$(xpcBuildOutputPath)'
artifactName: "$(xpcArtifactNameWindows)"
targetPath: "$(xpcBuildOutputPath)"
- stage: Deploy
jobs:
- job: Package_Binaries
displayName: 'Package Binaries'
displayName: "Package Binaries"
pool:
vmImage: 'ubuntu-latest'
vmImage: "ubuntu-latest"
steps:
- task: DownloadPipelineArtifact@0
inputs:
artifactName: '$(xpcArtifactNameMac)'
artifactName: "$(xpcArtifactNameMac)"
targetPath: $(System.DefaultWorkingDirectory)/$(xpcPackageName)
- task: DownloadPipelineArtifact@0
inputs:
artifactName: '$(xpcArtifactNameLinux)'
artifactName: "$(xpcArtifactNameLinux)"
targetPath: $(System.DefaultWorkingDirectory)/$(xpcPackageName)
- task: DownloadPipelineArtifact@0
inputs:
artifactName: '$(xpcArtifactNameWindows)'
artifactName: "$(xpcArtifactNameWindows)"
targetPath: $(System.DefaultWorkingDirectory)/$(xpcPackageName)
- task: PublishPipelineArtifact@0
inputs:
artifactName: '$(xpcPackageName)'
artifactName: "$(xpcPackageName)"
targetPath: $(System.DefaultWorkingDirectory)/$(xpcPackageName)
# Archive Files
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: '$(System.DefaultWorkingDirectory)/$(xpcPackageName)'
rootFolderOrFile: "$(System.DefaultWorkingDirectory)/$(xpcPackageName)"
includeRootFolder: true
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(xpcPackageName).zip'
archiveType: "zip"
archiveFile: "$(Build.ArtifactStagingDirectory)/$(xpcPackageName).zip"
# Uploads the .zip to GitHub and creates a new "Release" with the commit tag
# This tasks runs only on pushed tags
- task: GitHubRelease@0
inputs:
gitHubConnection: '$(xpcGitHubConnection)'
repositoryName: '$(Build.Repository.Name)'
action: 'create'
target: '$(Build.SourceVersion)'
tagSource: 'auto'
gitHubConnection: "$(xpcGitHubConnection)"
repositoryName: "$(Build.Repository.Name)"
action: "create"
target: "$(Build.SourceVersion)"
tagSource: "auto"
tag: $(tagName)
assets: '$(Build.ArtifactStagingDirectory)/$(xpcPackageName).zip'
assets: "$(Build.ArtifactStagingDirectory)/$(xpcPackageName).zip"
isPreRelease: true
isDraft: true

View File

@@ -0,0 +1,173 @@
//
// support.cpp
// xpcPlugin
//
// Created by Kai Lehmkuehler on 14/01/2019.
//
#include <stdio.h>
#include "MessageHandlers.h"
#include "DataManager.h"
#include "Log.h"
#include "XPLMUtilities.h"
#include "XPLMGraphics.h"
#include <cmath>
#include <cstring>
namespace XPC
{
// Runway Camera Callback: Places the camera at campos.loc and points it at the aircraft, similar to a remote piloting experience.
// Optionally, the direction of the camera can be specified by the user.
int MessageHandlers::CamCallback_RunwayCam( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon)
{
if (outCameraPosition && !inIsLosingControl)
{
struct CameraProperties* campos = (struct CameraProperties*)inRefcon;
// camera position
double clat = campos->loc[0];
double clon = campos->loc[1];
double calt = campos->loc[2];
double cX;
double cY;
double cZ;
XPLMWorldToLocal(clat, clon, calt, &cX, &cY, &cZ);
outCameraPosition->x = cX;
outCameraPosition->y = cY;
outCameraPosition->z = cZ;
// Log::FormatLine(LOG_TRACE, "CAM", "Cam pos %f %f %f", clat, clon, calt);
// calculate camera direction to point at the aircraft
if(campos->direction[0] < -180)
{
// local aircraft position
double x = XPC::DataManager::GetDouble(XPC::DREF_LocalX, 0);
double y = XPC::DataManager::GetDouble(XPC::DREF_LocalY, 0);
double z = XPC::DataManager::GetDouble(XPC::DREF_LocalZ, 0);
// relative position vector camera to aircraft
double dx = x - cX;
double dy = y - cY;
double dz = z - cZ;
// Log::FormatLine(LOG_TRACE, "CAM", "Cam vect %f %f %f", dx, dy, dz);
double pi = 3.141592653589793;
// horizontal distance
double dist = sqrt(dx*dx + dz*dz);
outCameraPosition->pitch = atan2(dy, dist) * 180.0/pi;
double angle = atan2(dz, dx) * 180.0/pi; // rel to pos right (pos X)
outCameraPosition->heading = 90 + angle; // rel to north
// Log::FormatLine(LOG_TRACE, "CAM", "Cam p %f hdg %f ", outCameraPosition->pitch, outCameraPosition->heading);
outCameraPosition->roll = 0;
}
// point camera at specified direction
else
{
outCameraPosition->roll = campos->direction[0];
outCameraPosition->pitch = campos->direction[1];
outCameraPosition->heading = campos->direction[2];
}
outCameraPosition->zoom = campos->zoom;
}
return 1;
}
// Chase Camera Callback: Places the camera at campos.loc RELATIVE to and MOVING with the aircraft, pointing at the specified
// direction.
int MessageHandlers::CamCallback_ChaseCam( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon)
{
if (outCameraPosition && !inIsLosingControl)
{
double DTR = 3.141592653589793 / 180.0;
struct CameraProperties* campos = (struct CameraProperties*)inRefcon;
// Camera location relative to aircraft (local axes)
// local aircraft position
double x = XPC::DataManager::GetDouble(XPC::DREF_LocalX, 0); // +east
double y = XPC::DataManager::GetDouble(XPC::DREF_LocalY, 0); // +up
double z = XPC::DataManager::GetDouble(XPC::DREF_LocalZ, 0); // +south
// aircraft attitude - degrees
double phi = XPC::DataManager::GetFloat(XPC::DREF_Roll, 0);
double the = XPC::DataManager::GetFloat(XPC::DREF_Pitch, 0);
double psi = XPC::DataManager::GetFloat(XPC::DREF_HeadingTrue, 0);
// camera position vector with respect to aircraft CG [m] (body axes)
double c_x = campos->loc[0]; // back
double c_y = campos->loc[1]; // right
double c_z = campos->loc[2]; // up
// camera position vector in local axes - will move with aircraft if not along principal axes
// cLocal = Leb * cBody
// http://www.xsquawkbox.net/xpsdk/mediawiki/ScreenCoordinates
double x_phi=c_y*cos(phi*DTR) + c_z*sin(phi*DTR);
double y_phi=c_z*cos(phi*DTR) - c_y*sin(phi*DTR);
double z_phi=c_x;
double x_the=x_phi;
double y_the=y_phi*cos(the*DTR) - z_phi*sin(the*DTR);
double z_the=z_phi*cos(the*DTR) + y_phi*sin(the*DTR);
double x_wrl=x_the*cos(psi*DTR) - z_the*sin(psi*DTR);
double y_wrl=y_the ;
double z_wrl=z_the*cos(psi*DTR) + x_the*sin(psi*DTR);
outCameraPosition->x = x + x_wrl;
outCameraPosition->y = y + y_wrl;
outCameraPosition->z = z + z_wrl;
// set direction value to -998 to keep camera pointing straight along that axis
if(campos->direction[0] < -180)
{
outCameraPosition->roll = 0;
}
else
{
outCameraPosition->roll = phi + campos->direction[0];
}
if(campos->direction[1] < -180)
{
outCameraPosition->pitch = 0;
}
else
{
outCameraPosition->pitch = the + campos->direction[1];
}
if(campos->direction[2] < -180)
{
outCameraPosition->heading = 0;
}
else
{
outCameraPosition->heading = psi + campos->direction[2];
}
outCameraPosition->zoom = campos->zoom;
}
return 1;
}
}

View File

@@ -19,6 +19,7 @@
#include "XPLMDataAccess.h"
#include "XPLMGraphics.h"
#include "XPLMUtilities.h"
#include <algorithm>
#include <cmath>
@@ -668,6 +669,11 @@ namespace XPC
return;
}
if (IsDefault(pos[0]) && IsDefault(pos[1]) && IsDefault(pos[2]))
{
Log::WriteLine(LOG_INFO, "DMAN", "Skipped SetPosition. All values were default");
return;
}
if (IsDefault(pos[0]))
{
pos[0] = GetDouble(DREF_Latitude, aircraft);
@@ -718,6 +724,12 @@ namespace XPC
return;
}
if (IsDefault(orient[0]) && IsDefault(orient[1]) && IsDefault(orient[2]))
{
Log::WriteLine(LOG_INFO, "DMAN", "Skipped SetPosition. All values were default");
return;
}
if (IsDefault(orient[0]))
{
orient[0] = GetFloat(DREF_Pitch, aircraft);
@@ -780,6 +792,21 @@ namespace XPC
Set(DREF_FlapActual, value);
}
void DataManager::Execute(const std::string& comm)
{
Log::FormatLine(LOG_INFO, "DMAN", "Executing command (value:%s)", comm.c_str());
XPLMCommandRef xcref = XPLMFindCommand(comm.c_str());
if (!xcref)
{
// COMM does not exist
Log::FormatLine(LOG_ERROR, "DMAN", "ERROR: invalid COMM %s", comm.c_str());
return;
}
XPLMCommandOnce(xcref);
}
float DataManager::GetDefaultValue()
{
return -998.0F;

View File

@@ -410,6 +410,11 @@ namespace XPC
/// \param value The flaps settings. Should be between 0.0 (no flaps) and 1.0 (full flaps).
static void SetFlaps(float value);
/// Executes a command
///
/// \param comm The name of the command to execute.
static void Execute(const std::string& comm);
/// Gets a default value that indicates that a dataref should not be changed.
static float GetDefaultValue();

View File

@@ -121,7 +121,7 @@ namespace XPC
}
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
}
else if (head == "GETC" || head == "GETP")
else if (head == "GETC" || head == "GETP" || head == "GETT")
{
ss << " Aircraft:" << (int)buffer[5];
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
@@ -138,14 +138,28 @@ namespace XPC
cur += 1 + buffer[cur];
}
}
else if (head == "POSI")
else if (head == "POSI" || head == "POST")
{
char aircraft = buffer[5];
float gear = *((float*)(buffer + 30));
float pos[3];
float gear;
double pos[3];
float orient[3];
memcpy(pos, buffer + 6, 12);
if (size == 34) /* lat/lon/h as 32-bit float */
{
float posd_32[3];
memcpy(posd_32, buffer + 6, 12);
pos[0] = posd_32[0];
pos[1] = posd_32[1];
pos[2] = posd_32[2];
memcpy(orient, buffer + 18, 12);
memcpy(&gear, buffer + 30, 4);
}
else if (size == 46) /* lat/lon/h as 64-bit double */
{
memcpy(pos, buffer + 6, 24);
memcpy(orient, buffer + 30, 12);
memcpy(&gear, buffer + 42, 4);
}
ss << " AC:" << (int)aircraft;
ss << " Pos:(" << pos[0] << ' ' << pos[1] << ' ' << pos[2] << ") Orient:(";
ss << orient[0] << ' ' << orient[1] << ' ' << orient[2] << ") Gear:";
@@ -162,6 +176,11 @@ namespace XPC
ss << "Type:" << *((unsigned long*)(buffer + 5));
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
}
else if (head == "COMM")
{
ss << "Type:" << *((unsigned long*)(buffer + 5));
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
}
else
{
ss << " UNKNOWN HEADER ";

View File

@@ -14,15 +14,16 @@
// may be used to endorse or promote products derived from this software
// without specific prior written permission from the authors or
// Laminar Research, respectively.
#include "MessageHandlers.h"
#include "DataManager.h"
#include "Drawing.h"
#include "Log.h"
#include "XPLMUtilities.h"
#include "XPLMScenery.h"
#include "XPLMGraphics.h"
#include <cmath>
#include <cstring>
#include <cstdint>
@@ -30,7 +31,6 @@
#define MULTICAST_GROUP "239.255.1.1"
#define MULITCAST_PORT 49710
namespace XPC
{
std::map<std::string, MessageHandlers::ConnectionInfo> MessageHandlers::connections;
@@ -42,6 +42,9 @@ namespace XPC
static sockaddr multicast_address = UDPSocket::GetAddr(MULTICAST_GROUP, MULITCAST_PORT);
// define a static terrain probe handler (do not re-create probe for each query)
XPLMProbeRef Terrain_probe = nullptr;
void MessageHandlers::SetSocket(UDPSocket* socket)
{
Log::WriteLine(LOG_TRACE, "MSGH", "Setting socket");
@@ -60,12 +63,15 @@ namespace XPC
handlers.insert(std::make_pair("DREF", MessageHandlers::HandleDref));
handlers.insert(std::make_pair("GETD", MessageHandlers::HandleGetD));
handlers.insert(std::make_pair("POSI", MessageHandlers::HandlePosi));
handlers.insert(std::make_pair("POST", MessageHandlers::HandlePosT));
handlers.insert(std::make_pair("SIMU", MessageHandlers::HandleSimu));
handlers.insert(std::make_pair("TEXT", MessageHandlers::HandleText));
handlers.insert(std::make_pair("WYPT", MessageHandlers::HandleWypt));
handlers.insert(std::make_pair("VIEW", MessageHandlers::HandleView));
handlers.insert(std::make_pair("GETC", MessageHandlers::HandleGetC));
handlers.insert(std::make_pair("GETP", MessageHandlers::HandleGetP));
handlers.insert(std::make_pair("COMM", MessageHandlers::HandleComm));
handlers.insert(std::make_pair("GETT", MessageHandlers::HandleGetT));
// X-Plane data messages
handlers.insert(std::make_pair("DSEL", MessageHandlers::HandleXPlaneData));
handlers.insert(std::make_pair("USEL", MessageHandlers::HandleXPlaneData));
@@ -554,21 +560,20 @@ namespace XPC
unsigned char aircraft = buffer[5];
Log::FormatLine(LOG_TRACE, "GPOS", "Getting position information for aircraft %u", aircraft);
unsigned char response[34] = "POSI";
unsigned char response[46] = "POSI";
response[5] = (char)DataManager::GetInt(DREF_GearHandle, aircraft);
// TODO change lat/lon/h to double?
*((float*)(response + 6)) = (float)DataManager::GetDouble(DREF_Latitude, aircraft);
*((float*)(response + 10)) = (float)DataManager::GetDouble(DREF_Longitude, aircraft);
*((float*)(response + 14)) = (float)DataManager::GetDouble(DREF_Elevation, aircraft);
*((float*)(response + 18)) = DataManager::GetFloat(DREF_Pitch, aircraft);
*((float*)(response + 22)) = DataManager::GetFloat(DREF_Roll, aircraft);
*((float*)(response + 26)) = DataManager::GetFloat(DREF_HeadingTrue, aircraft);
*((double*)(response + 6)) = DataManager::GetDouble(DREF_Latitude, aircraft);
*((double*)(response + 14)) = DataManager::GetDouble(DREF_Longitude, aircraft);
*((double*)(response + 22)) = DataManager::GetDouble(DREF_Elevation, aircraft);
*((float*)(response + 30)) = DataManager::GetFloat(DREF_Pitch, aircraft);
*((float*)(response + 34)) = DataManager::GetFloat(DREF_Roll, aircraft);
*((float*)(response + 38)) = DataManager::GetFloat(DREF_HeadingTrue, aircraft);
float gear[10];
DataManager::GetFloatArray(DREF_GearDeploy, gear, 10, aircraft);
*((float*)(response + 30)) = gear[0];
*((float*)(response + 42)) = gear[0];
sock->SendTo(response, 34, &connection.addr);
sock->SendTo(response, 46, &connection.addr);
}
void MessageHandlers::HandlePosi(const Message& msg)
@@ -580,21 +585,26 @@ namespace XPC
const std::size_t size = msg.GetSize();
char aircraftNumber = buffer[5];
float gear = *((float*)(buffer + 42));
float gear;
double posd[3];
float orient[3];
if (size == 34) /* lat/lon/h as 32-bit float */
{
posd[0] = *((float*)&buffer[6]);
posd[1] = *((float*)&buffer[10]);
posd[2] = *((float*)&buffer[14]);
float posd_32[3];
memcpy(posd_32, buffer + 6, 12);
/* convert float to double */
posd[0] = posd_32[0];
posd[1] = posd_32[1];
posd[2] = posd_32[2];
memcpy(orient, buffer + 18, 12);
memcpy(&gear, buffer + 30, 4);
}
else if (size == 46) /* lat/lon/h as 64-bit double */
{
memcpy(posd, buffer + 6, 3*8);
memcpy(posd, buffer + 6, 24);
memcpy(orient, buffer + 30, 12);
memcpy(&gear, buffer + 42, 4);
}
else
{
@@ -602,7 +612,6 @@ namespace XPC
return;
}
/* convert float to double */
DataManager::SetPosition(posd, aircraftNumber);
DataManager::SetOrientation(orient, aircraftNumber);
if (gear >= 0)
@@ -623,6 +632,135 @@ namespace XPC
}
}
void MessageHandlers::HandlePosT(const Message& msg)
{
MessageHandlers::HandlePosi(msg);
const unsigned char* buffer = msg.GetBuffer();
char aircraftNumber = buffer[5];
Log::FormatLine(LOG_TRACE, "POST", "Getting terrain information for aircraft %u", aircraftNumber);
double pos[3];
pos[0] = DataManager::GetDouble(DREF_Latitude, aircraftNumber);
pos[1] = DataManager::GetDouble(DREF_Longitude, aircraftNumber);
pos[2] = 0.0;
MessageHandlers::SendTerr(pos, aircraftNumber);
}
void MessageHandlers::HandleGetT(const Message& msg)
{
const unsigned char* buffer = msg.GetBuffer();
std::size_t size = msg.GetSize();
if (size != 30)
{
Log::FormatLine(LOG_ERROR, "GETT", "Unexpected message length: %u", size);
return;
}
unsigned char aircraft = buffer[5];
Log::FormatLine(LOG_TRACE, "GETT", "Getting terrain information for aircraft %u", aircraft);
double pos[3];
memcpy(pos, buffer + 6, 24);
if(pos[0] == -998 || pos[1] == -998 || pos[2] == -998)
{
// get terrain properties at aircraft location
pos[0] = DataManager::GetDouble(DREF_Latitude, aircraft);
pos[1] = DataManager::GetDouble(DREF_Longitude, aircraft);
pos[2] = 0.0;
}
MessageHandlers::SendTerr(pos, aircraft);
}
void MessageHandlers::SendTerr(double pos[3], char aircraft)
{
double lat, lon, alt, X, Y, Z;
// Init terrain probe (if required) and probe data struct
static XPLMProbeInfo_t probe_data;
probe_data.structSize = sizeof(XPLMProbeInfo_t);
if(Terrain_probe == nullptr)
{
Log::FormatLine(LOG_TRACE, "TERR", "Create terrain probe for aircraft %u", aircraft);
Terrain_probe = XPLMCreateProbe(0);
}
// terrain probe at specified location
// Follow the process in the following post to get accurate results
// https://forums.x-plane.org/index.php?/forums/topic/38688-how-do-i-use-xplmprobeterrainxyz/&page=2
// transform probe location to local coordinates
// Step 1. Convert lat/lon/0 to XYZ
XPLMWorldToLocal(pos[0], pos[1], pos[2], &X, &Y, &Z);
// query probe
// Step 2. Probe XYZ to get a new Y
int rc = XPLMProbeTerrainXYZ(Terrain_probe, X, Y, Z, &probe_data);
if(rc > 0)
{
Log::FormatLine(LOG_ERROR, "TERR", "Probe failed. Return Value %u", rc);
XPLMDestroyProbe(Terrain_probe);
Terrain_probe = nullptr;
return;
}
// transform probe location to world coordinates
// Step 3. Convert that new XYZ back to LLE
XPLMLocalToWorld(probe_data.locationX, probe_data.locationY, probe_data.locationZ, &lat, &lon, &alt);
Log::FormatLine(LOG_TRACE, "TERR", "Conv LLA=%f, %f, %f", lat, lon, alt);
// transform probe location to local coordinates
// Step 4. NOW convert your original lat/lon with the elevation from step 3 to XYZ
XPLMWorldToLocal(pos[0], pos[1], alt, &X, &Y, &Z);
// query probe
// Step 5. Re-probe with the NEW XYZ
rc = XPLMProbeTerrainXYZ(Terrain_probe, X, Y, Z, &probe_data);
if(rc == 0)
{
// transform probe location to world coordinates
// Step 6. You now have a new Y, and your XYZ will be closer to correct for high elevations far from the origin.
XPLMLocalToWorld(probe_data.locationX, probe_data.locationY, probe_data.locationZ, &lat, &lon, &alt);
Log::FormatLine(LOG_TRACE, "TERR", "Probe LLA %lf %lf %lf", lat, lon, alt);
}
else
{
lat = -998;
lon = -998;
alt = -998;
Log::FormatLine(LOG_TRACE, "TERR", "Probe failed. Return Value %u", rc);
}
// keep probe for next query
// XPLMDestroyProbe(Terrain_probe);
// Assemble response message
unsigned char response[62] = "TERR";
response[5] = aircraft;
// terrain height over msl at lat/lon point
memcpy(response + 6, &lat, 8);
memcpy(response + 14, &lon, 8);
memcpy(response + 22, &alt, 8);
// terrain normal vector
memcpy(response + 30, &probe_data.normalX, 4);
memcpy(response + 34, &probe_data.normalY, 4);
memcpy(response + 38, &probe_data.normalZ, 4);
// terrain velocity
memcpy(response + 42, &probe_data.velocityX, 4);
memcpy(response + 46, &probe_data.velocityY, 4);
memcpy(response + 50, &probe_data.velocityZ, 4);
// terrain type
memcpy(response + 54, &probe_data.is_wet, 4);
// probe status
memcpy(response + 58, &rc, 4);
sock->SendTo(response, 62, &connection.addr);
}
void MessageHandlers::HandleSimu(const Message& msg)
{
// Update log
@@ -723,105 +861,85 @@ namespace XPC
// Update Log
Log::FormatLine(LOG_TRACE, "VIEW", "Message Received(Conn %i)", connection.id);
int enable_camera_location = 0;
bool enable_advanced_camera = false;
const std::size_t size = msg.GetSize();
if (size == 9)
{
// default view switcher as before
}
else if (size == 37)
else if (size == 49)
{
// Allow camera location control
enable_camera_location = 1;
enable_advanced_camera = true;
}
else
{
Log::FormatLine(LOG_ERROR, "VIEW", "Error: Unexpected length. Message was %d bytes, expected 9 or 37.", size);
Log::FormatLine(LOG_ERROR, "VIEW", "Error: Unexpected length. Message was %d bytes, expected 9 or 49.", size);
return;
}
// get msg data
const unsigned char* buffer = msg.GetBuffer();
int type = *((int*)(buffer + 5));
XPLMCommandKeyStroke(type);
if(type == 79 && enable_camera_location == 1) // runway camera view
// get view type
int view_type;
memcpy(&view_type, buffer + 5, 4);
// set view by calling the corresponding key stroke
XPLMCommandKeyStroke(view_type);
VIEW_TYPE viewRunway = VIEW_TYPE::XPC_VIEW_RUNWAY;
VIEW_TYPE viewChase = VIEW_TYPE::XPC_VIEW_CHASE;
// advanced runway camera view
if(view_type == static_cast<int>(viewRunway) && enable_advanced_camera == true)
{
static struct CameraProperties campos;
static struct CameraProperties campos; // static variable for continuous callback access
campos.loc[0] = *(double*)(buffer+9);
campos.loc[1] = *(double*)(buffer+17);
campos.loc[2] = *(double*)(buffer+25);
campos.direction[0] = -998;
campos.direction[1] = -998;
campos.direction[2] = -998;
campos.zoom = *(float*)(buffer+33);
memcpy(&campos, buffer+9 , sizeof(struct CameraProperties));
Log::FormatLine(LOG_TRACE, "VIEW", "Cam pos %f %f %f zoom %f", campos.loc[0], campos.loc[1], campos.loc[2],campos.zoom);
Log::FormatLine(LOG_TRACE, "VIEW", "Cam pos %f %f %f zoom %f", campos.loc[0], campos.loc[1], campos.loc[2], campos.zoom);
XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CamFunc, &campos);
XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CamCallback_RunwayCam, &campos);
}
// advanced chase camera view
else if(view_type == static_cast<int>(viewChase) && enable_advanced_camera == true)
{
static struct CameraProperties campos; // static variable for continuous callback access
memcpy(&campos, buffer+9 , sizeof(struct CameraProperties));
Log::FormatLine(LOG_TRACE, "VIEW", "Cam pos %f %f %f zoom %f", campos.loc[0], campos.loc[1], campos.loc[2], campos.zoom);
XPLMControlCamera(xplm_ControlCameraUntilViewChanges, CamCallback_ChaseCam, &campos);
}
}
int MessageHandlers::CamFunc( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon)
void MessageHandlers::HandleComm(const Message& msg)
{
if (outCameraPosition && !inIsLosingControl)
Log::FormatLine(LOG_TRACE, "COMM", "Request to execute COMM command received (Conn %i)", connection.id);
const unsigned char* buffer = msg.GetBuffer();
std::size_t size = msg.GetSize();
std::size_t pos = 5;
while (pos < size)
{
struct CameraProperties* campos = (struct CameraProperties*)inRefcon;
// camera position
double clat = campos->loc[0];
double clon = campos->loc[1];
double calt = campos->loc[2];
double cX, cY, cZ;
XPLMWorldToLocal(clat, clon, calt, &cX, &cY, &cZ);
outCameraPosition->x = cX;
outCameraPosition->y = cY;
outCameraPosition->z = cZ;
// Log::FormatLine(LOG_TRACE, "CAM", "Cam pos %f %f %f", clat, clon, calt);
if(campos->direction[0] == -998) // calculate camera direction
unsigned char len = buffer[pos++];
if (pos + len > size)
{
// aircraft position
double x = XPC::DataManager::GetDouble(XPC::DREF_LocalX, 0);
double y = XPC::DataManager::GetDouble(XPC::DREF_LocalY, 0);
double z = XPC::DataManager::GetDouble(XPC::DREF_LocalZ, 0);
// relative position vector cam to plane
double dx = x - cX;
double dy = y - cY;
double dz = z - cZ;
// Log::FormatLine(LOG_TRACE, "CAM", "Cam vect %f %f %f", dx, dy, dz);
double pi = 3.141592653589793;
// horizontal distance
double dist = sqrt(dx*dx + dz*dz);
outCameraPosition->pitch = atan2(dy, dist) * 180.0/pi;
double angle = atan2(dz, dx) * 180.0/pi; // rel to pos right (pos X)
outCameraPosition->heading = 90 + angle; // rel to north
// Log::FormatLine(LOG_TRACE, "CAM", "Cam p %f hdg %f ", outCameraPosition->pitch, outCameraPosition->heading);
outCameraPosition->roll = 0;
break;
}
else
std::string comm = std::string((char*)buffer + pos, len);
pos += len;
DataManager::Execute(comm);
Log::FormatLine(LOG_DEBUG, "COMM", "Execute command %s", comm.c_str());
}
if (pos != size)
{
outCameraPosition->roll = campos->direction[0];
outCameraPosition->pitch = campos->direction[1];
outCameraPosition->heading = campos->direction[2];
Log::WriteLine(LOG_ERROR, "COMM", "ERROR: Command did not terminate at the expected position.");
}
outCameraPosition->zoom = campos->zoom;
}
return 1;
}
void MessageHandlers::HandleWypt(const Message& msg)

View File

@@ -15,6 +15,23 @@ namespace XPC
/// A function that handles a message.
typedef void(*MessageHandler)(const Message&);
enum class VIEW_TYPE
{
XPC_VIEW_FORWARDS = 73,
XPC_VIEW_DOWN,
XPC_VIEW_LEFT,
XPC_VIEW_RIGHT,
XPC_VIEW_BACK,
XPC_VIEW_TOWER,
XPC_VIEW_RUNWAY,
XPC_VIEW_CHASE,
XPC_VIEW_FOLLOW,
XPC_VIEW_FOLLOWWITHPANEL,
XPC_VIEW_SPOT,
XPC_VIEW_FULLSCREENWITHHUD,
XPC_VIEW_FULLSCREENNOHUD,
};
/// Handles incoming messages and manages connections.
///
/// \author Jason Watkins
@@ -41,6 +58,8 @@ namespace XPC
static void SendBeacon(const std::string& pluginVersion, unsigned short pluginReceivePort, int xplaneVersion);
static void SendTerr(double pos[3], char aircraft);
private:
// One handler per message type. Message types are descripbed on the
// wiki at https://github.com/nasa/XPlaneConnect/wiki/Network-Information
@@ -51,16 +70,20 @@ namespace XPC
static void HandleGetC(const Message& msg);
static void HandleGetD(const Message& msg);
static void HandleGetP(const Message& msg);
static void HandleGetT(const Message& msg);
static void HandlePosi(const Message& msg);
static void HandlePosT(const Message& msg);
static void HandleSimu(const Message& msg);
static void HandleText(const Message& msg);
static void HandleWypt(const Message& msg);
static void HandleView(const Message& msg);
static void HandleComm(const Message& msg);
static void HandleXPlaneData(const Message& msg);
static void HandleUnknown(const Message& msg);
static int CamFunc( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon);
static int CamCallback_RunwayCam( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon);
static int CamCallback_ChaseCam( XPLMCameraPosition_t * outCameraPosition, int inIsLosingControl, void *inRefcon);
struct CameraProperties{
double loc[3];

View File

@@ -56,23 +56,26 @@ namespace XPC
return;
}
// Set Timout
int usTimeOut = 500;
// Set timeout period for SendTo to 1 millisecond
// Without this, playback may become choppy due to process blocking
#ifdef _WIN32
DWORD msTimeOutWin = 1; // Minimum socket timeout in Windows is 1ms
if(setsockopt(this->sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&msTimeOutWin, sizeof(msTimeOutWin)) != 0)
// Minimum socket timeout in Windows is 1 millisecond (0 makes it blocking)
DWORD timeout = 1;
#else
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 1000;
#endif
if (setsockopt(this->sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0)
{
#ifdef _WIN32
int err = WSAGetLastError();
Log::FormatLine(LOG_ERROR, tag, "ERROR: Failed to set timeout. (Error code %i)", err);
}
#else
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = usTimeOut;
setsockopt(this->sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(struct timeval));
Log::WriteLine(LOG_ERROR, tag, "ERROR: Failed to set timeout.");
#endif
}
}
UDPSocket::~UDPSocket()
{
@@ -87,43 +90,54 @@ namespace XPC
int UDPSocket::Read(unsigned char* dst, int maxLen, sockaddr* recvAddr) const
{
socklen_t recvaddrlen = sizeof(*recvAddr);
int status = 0;
#ifdef _WIN32
// Windows readUDP needs the select command- minimum timeout is 1ms.
// Without this playback becomes choppy
// For Read, use the select command - minimum timeout of 0 makes it polling.
// Without this, playback may become choppy due to process blocking
// Definitions
FD_SET stReadFDS;
FD_SET stExceptFDS;
timeval tv;
fd_set stReadFDS;
fd_set stExceptFDS;
struct timeval timeout;
// Setup for Select
FD_ZERO(&stReadFDS);
FD_SET(sock, &stReadFDS);
FD_ZERO(&stExceptFDS);
FD_SET(sock, &stExceptFDS);
tv.tv_sec = 0;
tv.tv_usec = 250;
// Set timeout period for select to 0 to poll the socket
timeout.tv_sec = 0;
timeout.tv_usec = 0;
// Select Command
int result = select(-1, &stReadFDS, (FD_SET *)0, &stExceptFDS, &tv);
if (result == SOCKET_ERROR)
int status = select(sock+1, &stReadFDS, NULL, &stExceptFDS, &timeout);
if (status < 0)
{
#ifdef _WIN32
int err = WSAGetLastError();
Log::FormatLine(LOG_ERROR, tag, "ERROR: Select failed. (Error code %i)", err);
#else
Log::WriteLine(LOG_ERROR, tag, "ERROR: Select failed.");
#endif
return -1;
}
if (result <= 0) // No Data or error
if (status == 0)
{
// No data
return -1;
}
// If no error: Read Data
status = recvfrom(sock, (char*)dst, maxLen, 0, recvAddr, &recvaddrlen);
if (status < 0)
{
#ifdef _WIN32
int err = WSAGetLastError();
Log::FormatLine(LOG_ERROR, tag, "ERROR: Receive failed. (Error code %i)", err);
#else
// For apple or linux-just read - will timeout in 0.5 ms
status = (int)recvfrom(sock, dst, 5000, 0, recvAddr, &recvaddrlen);
Log::WriteLine(LOG_ERROR, tag, "ERROR: Receive failed.");
#endif
}
return status;
}

View File

@@ -13,6 +13,8 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif

View File

@@ -8,6 +8,7 @@
/* Begin PBXBuildFile section */
3D0F44CE21C6D3E7008A0655 /* Timer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3D0F44CD21C6D3E7008A0655 /* Timer.cpp */; };
5B36040D23731E4A003ACE12 /* CameraCallbacks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B36040C23731E4A003ACE12 /* CameraCallbacks.cpp */; };
BE37D960187C8B0F0033B082 /* XPCPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE37D95E187C8B0F0033B082 /* XPCPlugin.cpp */; };
BE8361EF18C5591C00E9C923 /* mac.xpl in CopyFiles */ = {isa = PBXBuildFile; fileRef = D607B19909A556E400699BC3 /* mac.xpl */; };
BEABAD371AE041A3007BA7DA /* DataManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEABAD2B1AE041A3007BA7DA /* DataManager.cpp */; };
@@ -39,6 +40,7 @@
/* Begin PBXFileReference section */
3D0F44CC21C6D3E7008A0655 /* Timer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Timer.h; sourceTree = "<group>"; };
3D0F44CD21C6D3E7008A0655 /* Timer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Timer.cpp; sourceTree = "<group>"; };
5B36040C23731E4A003ACE12 /* CameraCallbacks.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CameraCallbacks.cpp; sourceTree = "<group>"; };
BE37D95E187C8B0F0033B082 /* XPCPlugin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XPCPlugin.cpp; sourceTree = SOURCE_ROOT; usesTabs = 1; };
BEABAD2B1AE041A3007BA7DA /* DataManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DataManager.cpp; sourceTree = "<group>"; };
BEABAD2C1AE041A3007BA7DA /* DataManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataManager.h; sourceTree = "<group>"; };
@@ -53,7 +55,6 @@
BEABAD3D1AE0498D007BA7DA /* UDPSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UDPSocket.cpp; sourceTree = "<group>"; };
BEABAD3E1AE0498D007BA7DA /* UDPSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UDPSocket.h; sourceTree = "<group>"; };
BEDC620218EDF1A7005DB364 /* xplaneConnect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = xplaneConnect.c; path = ../C/src/xplaneConnect.c; sourceTree = "<group>"; };
BEDC620318EDF1A7005DB364 /* xplaneConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xplaneConnect.h; path = ../C/src/xplaneConnect.h; sourceTree = "<group>"; };
D607B19909A556E400699BC3 /* mac.xpl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = mac.xpl; sourceTree = BUILT_PRODUCTS_DIR; };
D6A7BDA916A1DEA200D1426A /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
D6A7BDC016A1DEC000D1426A /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; };
@@ -79,6 +80,7 @@
AC4E46B809C2E0B3006B7E1B /* src */ = {
isa = PBXGroup;
children = (
5B36040C23731E4A003ACE12 /* CameraCallbacks.cpp */,
3D0F44CD21C6D3E7008A0655 /* Timer.cpp */,
BE37D95E187C8B0F0033B082 /* XPCPlugin.cpp */,
BEDC620218EDF1A7005DB364 /* xplaneConnect.c */,
@@ -96,7 +98,6 @@
isa = PBXGroup;
children = (
3D0F44CC21C6D3E7008A0655 /* Timer.h */,
BEDC620318EDF1A7005DB364 /* xplaneConnect.h */,
BEABAD2C1AE041A3007BA7DA /* DataManager.h */,
BEABAD301AE041A3007BA7DA /* Drawing.h */,
BEABAD321AE041A3007BA7DA /* Log.h */,
@@ -191,6 +192,7 @@
files = (
BEABAD3A1AE041A3007BA7DA /* Log.cpp in Sources */,
BEABAD3B1AE041A3007BA7DA /* Message.cpp in Sources */,
5B36040D23731E4A003ACE12 /* CameraCallbacks.cpp in Sources */,
BEABAD3C1AE041A3007BA7DA /* MessageHandlers.cpp in Sources */,
BEDC620418EDF1A7005DB364 /* xplaneConnect.c in Sources */,
BEABAD371AE041A3007BA7DA /* DataManager.cpp in Sources */,
@@ -219,6 +221,7 @@
"APL=1",
"IBM=0",
"LIN=0",
XPLM200,
);
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_VERSION = "";
@@ -230,6 +233,10 @@
MACH_O_TYPE = mh_bundle;
MACOSX_DEPLOYMENT_TARGET = 10.7;
ONLY_ACTIVE_ARCH = YES;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DXPLM200",
);
OTHER_LDFLAGS = (
"$(OTHER_LDFLAGS)",
"-Wl,-exported_symbol",
@@ -268,6 +275,7 @@
"APL=1",
"IBM=0",
"LIN=0",
XPLM200,
);
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_VERSION = "";
@@ -278,6 +286,10 @@
);
MACH_O_TYPE = mh_bundle;
MACOSX_DEPLOYMENT_TARGET = 10.7;
OTHER_CPLUSPLUSFLAGS = (
"$(OTHER_CFLAGS)",
"-DXPLM200",
);
OTHER_LDFLAGS = (
"$(OTHER_LDFLAGS)",
"-Wl,-exported_symbol",

View File

@@ -22,7 +22,7 @@
<ProjectGuid>{6AEF5D28-2701-44FF-AE10-1DEF07C5CAEA}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>xpcPlugin</RootNamespace>
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
<WindowsTargetPlatformVersion>10.0.17134.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@@ -210,6 +210,7 @@
<ClInclude Include="..\UDPSocket.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\CameraCallbacks.cpp" />
<ClCompile Include="..\DataManager.cpp" />
<ClCompile Include="..\Drawing.cpp" />
<ClCompile Include="..\Log.cpp" />

View File

@@ -33,6 +33,9 @@
<ClInclude Include="..\MessageHandlers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\Timer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\XPCPlugin.cpp">
@@ -56,6 +59,12 @@
<ClCompile Include="..\MessageHandlers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\Timer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\CameraCallbacks.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Library Include="..\SDK\Libraries\Win\XPLM.lib">