diff --git a/.gitignore b/.gitignore
index 247528f..1f8fc83 100644
--- a/.gitignore
+++ b/.gitignore
@@ -69,6 +69,13 @@ xcuserdata
*.moved-aside
*.xccheckout
+# IntelliJ Files #
+##################
+*/.idea/workspace.xml
+*/.idea/tasks.xml
+*/.idea/dictionaries/
+out/
+
# MATLAB backup files #
#######################
*.m~
diff --git a/Java/.idea/.name b/Java/.idea/.name
new file mode 100644
index 0000000..885cc23
--- /dev/null
+++ b/Java/.idea/.name
@@ -0,0 +1 @@
+XPlaneConnect
\ No newline at end of file
diff --git a/Java/.idea/artifacts/XPlaneConnect_jar.xml b/Java/.idea/artifacts/XPlaneConnect_jar.xml
new file mode 100644
index 0000000..89ac298
--- /dev/null
+++ b/Java/.idea/artifacts/XPlaneConnect_jar.xml
@@ -0,0 +1,8 @@
+
+
+ $PROJECT_DIR$/out/artifacts/XPlaneConnect_jar
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/compiler.xml b/Java/.idea/compiler.xml
new file mode 100644
index 0000000..96cc43e
--- /dev/null
+++ b/Java/.idea/compiler.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/copyright/profiles_settings.xml b/Java/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..e7bedf3
--- /dev/null
+++ b/Java/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/libraries/junit_4_11.xml b/Java/.idea/libraries/junit_4_11.xml
new file mode 100644
index 0000000..0a24ba1
--- /dev/null
+++ b/Java/.idea/libraries/junit_4_11.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/misc.xml b/Java/.idea/misc.xml
new file mode 100644
index 0000000..a61f7bc
--- /dev/null
+++ b/Java/.idea/misc.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/modules.xml b/Java/.idea/modules.xml
new file mode 100644
index 0000000..3132258
--- /dev/null
+++ b/Java/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/.idea/vcs.xml b/Java/.idea/vcs.xml
new file mode 100644
index 0000000..6c0b863
--- /dev/null
+++ b/Java/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/XPlaneConnect.iml b/Java/XPlaneConnect.iml
new file mode 100644
index 0000000..6315e70
--- /dev/null
+++ b/Java/XPlaneConnect.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java
new file mode 100644
index 0000000..25dc70c
--- /dev/null
+++ b/Java/src/XPlaneConnect.java
@@ -0,0 +1,600 @@
+package gov.nasa.xpc;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.AutoCloseable;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Represents a client that can connect to and interact with the X-Plane Connect plugin.
+ *
+ * @author Jason Watkins
+ * @version 0.1
+ * @since 2015-03-31
+ */
+public class XPlaneConnect implements AutoCloseable
+{
+ private static int clientNum;
+ private DatagramSocket outSocket;
+ private DatagramSocket inSocket;
+ private InetAddress xplaneAddr;
+ private int xplanePort;
+
+ /**
+ * Gets the port on which the client receives data from the plugin.
+ *
+ * @return The incoming port number.
+ */
+ public int getRecvPort() { return inSocket.getLocalPort(); }
+
+ /**
+ * Gets the port on which the client sends data to X-Plane.
+ *
+ * @return The outgoing port number.
+ */
+ public int getXPlanePort() { return xplanePort; }
+
+ /**
+ * Sets the port on which the client sends data to X-Plane
+ *
+ * @param port The new outgoing port number.
+ * @throws IllegalArgumentException If {@code port} is not a valid port number.
+ */
+ public void setXPlanePort(int port)
+ {
+ if(port < 0 || port >= 0xFFFF)
+ {
+ throw new IllegalArgumentException("Invalid port (must be non-negative and less than 65536).");
+ }
+ xplanePort = port;
+ }
+
+ /**
+ * Gets the hostname of the X-Plane host.
+ *
+ * @return The hostname of the X-Plane host.
+ */
+ public String getXPlaneAddr() { return xplaneAddr.getHostAddress(); }
+
+ /**
+ * Sets the hostname of the X-Plane host.
+ *
+ * @param host The new hostname of the X-Plane host machine.
+ * @throws UnknownHostException {@code host} is not valid.
+ */
+ public void setXplaneAddr(String host) throws UnknownHostException
+ {
+ xplaneAddr = InetAddress.getByName(host);
+ }
+
+ /**
+ * Initializes a new instance of the {@code XPlaneConnect} class using default ports and assuming X-Plane is running on the
+ * local machine.
+ *
+ * @throws SocketException If this instance is unable to bind to the default receive port.
+ */
+ public XPlaneConnect() throws SocketException
+ {
+ this(100);
+ }
+
+ /**
+ * Initializes a new instance of the {@code XPlaneConnect} class with the specified timeout using default ports and
+ * assuming X-Plane is running on the local machine.
+ *
+ * @param timeout The time, in milliseconds, after which read attempts will timeout.
+ * @throws SocketException If this instance is unable to bind to the default receive port.
+ */
+ public XPlaneConnect(int timeout) throws SocketException
+ {
+ this.inSocket = new DatagramSocket(49008);
+ this.outSocket = new DatagramSocket(50000 + ++clientNum);
+ this.xplaneAddr = InetAddress.getLoopbackAddress();
+ this.xplanePort = 49009;
+ this.inSocket.setSoTimeout(timeout);
+ }
+
+ /**
+ * Initializes a new instance of the {@code XPlaneConnect} class using the specified ports and X-Plane host.
+ *
+ * @param port The port on which the X-Plane Connect plugin is sending data.
+ * @param xplaneHost The network host on which X-Plane is running.
+ * @param xplanePort The port on which the X-Plane Connect plugin is listening.
+ * @throws java.net.SocketException If this instance is unable to bind to the specified port.
+ * @throws java.net.UnknownHostException If the specified hostname can not be resolved.
+ */
+ public XPlaneConnect(int port, String xplaneHost, int xplanePort)
+ throws java.net.SocketException, java.net.UnknownHostException
+ {
+ this(port, xplaneHost, xplanePort, 100);
+ }
+
+ /**
+ * Initializes a new instance of the {@code XPlaneConnect} class using the specified ports, hostname, and timeout.
+ *
+ * @param port The port on which the X-Plane Connect plugin is sending data.
+ * @param xplaneHost The network host on which X-Plane is running.
+ * @param xplanePort The port on which the X-Plane Connect plugin is listening.
+ * @param timeout The time, in milliseconds, after which read attempts will timeout.
+ * @throws java.net.SocketException If this instance is unable to bind to the specified port.
+ * @throws java.net.UnknownHostException If the specified hostname can not be resolved.
+ */
+ public XPlaneConnect(int port, String xplaneHost, int xplanePort, int timeout)
+ throws java.net.SocketException, java.net.UnknownHostException
+ {
+ this.inSocket = new DatagramSocket(port);
+ this.outSocket = new DatagramSocket(50000 + ++clientNum);
+ this.xplaneAddr = InetAddress.getByName(xplaneHost);
+ this.xplanePort = xplanePort;
+ this.inSocket.setSoTimeout(timeout);
+ }
+
+ /**
+ * Closes the underlying inSocket.
+ */
+ public void close()
+ {
+ if(inSocket != null)
+ {
+ inSocket.close();
+ inSocket = null;
+ }
+ if(outSocket != null)
+ {
+ outSocket.close();
+ outSocket = null;
+ }
+ }
+
+ /**
+ * Read data from the X-Plane plugin. This method will read whatever data is available and return it.
+ *
+ * @return The data send from X-Plane.
+ * @throws IOException If the read operation fails
+ */
+ private byte[] readUDP() throws IOException //TODO: Store data in a class level buffer to account for partial messages
+ {
+ byte[] buffer = new byte[2048];
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
+ try
+ {
+ inSocket.receive(packet);
+ return Arrays.copyOf(buffer, buffer[4]);
+ }
+ catch (SocketTimeoutException ex)
+ {
+ return new byte[0];
+ }
+ }
+
+ /**
+ * Send the given data to the X-Plane plugin. This method automatically sets the length byte before sending,
+ * overwriting any value previously stored in @{code buffer[4]}.
+ *
+ * @param buffer The data to send.
+ * @throws IOException If the send operation fails.
+ */
+ private void sendUDP(byte[] buffer) throws IOException
+ {
+ if(buffer.length < 5 || buffer.length > 255)
+ {
+ throw new IllegalArgumentException("buffer must be between 5 and 255 bytes long.");
+ }
+ buffer[4] = (byte)buffer.length;
+
+ DatagramPacket packet = new DatagramPacket(buffer, buffer.length, xplaneAddr, xplanePort);
+ outSocket.send(packet);
+ }
+
+ /**
+ * Pauses or unpauses X-Plane.
+ *
+ * @param pause {@code true} to pause the simulator; {@code false} to un-pause.
+ * @throws IOException If the command cannot be sent.
+ */
+ public void pauseSim(boolean pause) throws IOException
+ {
+ // S I M U LEN VAL
+ byte[] msg = {0x53, 0x49, 0x4D, 0x55, 0x00, 0x00};
+ msg[5] = (byte)(pause ? 0x01 : 0x00);
+ sendUDP(msg);
+ }
+
+ /**
+ * Requests a single dref value from X-Plane.
+ *
+ * @param dref The name of the dref requested.
+ * @return A byte array representing data dependent on the dref requested.
+ * @throws IOException If either the request or the response fails.
+ */
+ public float[] requestDREF(String dref) throws IOException
+ {
+ return requestDREFs(new String[]{dref})[0];
+ }
+
+ /**
+ * Requests several dref values from X-Plane.
+ *
+ * @param drefs An array of dref names to request.
+ * @return A multidimensional array representing the data for each requested dref.
+ * @throws IOException If either the request or the response fails.
+ */
+ public float[][] requestDREFs(String[] drefs) throws IOException
+ {
+ //Preconditions
+ if(drefs == null || drefs.length == 0)
+ {
+ throw new IllegalArgumentException("drefs must be a valid array with at least one dref.");
+ }
+ if(drefs.length > 255)
+ {
+ throw new IllegalArgumentException("Can not request more than 255 DREFs at once.");
+ }
+
+ //Convert drefs to bytes.
+ byte[][] drefBytes = new byte[drefs.length][];
+ for(int i = 0; i < drefs.length; ++i)
+ {
+ drefBytes[i] = drefs[i].getBytes(StandardCharsets.UTF_8);
+ if(drefBytes[i].length == 0)
+ {
+ throw new IllegalArgumentException("DREF " + i + " is an empty string!");
+ }
+ if(drefBytes[i].length > 255)
+ {
+ throw new IllegalArgumentException("DREF " + i + " is too long (must be less than 255 bytes in UTF-8). Are you sure this is a valid DREF?");
+ }
+ }
+
+ //Build and send message
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("GETD".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write(drefs.length);
+ for(byte[] dref : drefBytes)
+ {
+ os.write(dref.length);
+ os.write(dref, 0, dref.length);
+ }
+ sendUDP(os.toByteArray());
+
+ //Read response
+ for(int i = 0; i < 40; ++i)
+ {
+ byte[] data = readUDP();
+ if(data.length == 0)
+ {
+ continue;
+ }
+ if(data.length < 6)
+ {
+ throw new Error("Response too short"); //TODO: Make custom error type
+ }
+ if(data[5] != drefs.length)
+ {
+ throw new Error("Unexpected response length"); //TODO: Make custom error type
+ }
+ float[][] result = new float[drefs.length][];
+ ByteBuffer bb = ByteBuffer.wrap(data);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ int cur = 6;
+ for(int j = 0; j < result.length; ++j)
+ {
+ result[j] = new float[data[cur++]];
+ for(int k = 0; k < result[j].length; ++k) //TODO: There must be a better way to do this
+ {
+ result[j][k] = bb.getFloat(cur);
+ cur += 4;
+ }
+ }
+ return result;
+ }
+ throw new IOException("No response received.");
+ }
+
+ public void sendDREF(String dref, float value) throws IOException
+ {
+ sendDREF(dref, new float[] {value});
+ }
+
+ /**
+ * Sends a command to X-Plane that sets the given DREF.
+ *
+ * @param dref The name of the DREF to set.
+ * @param value An array of floating point values whose structure depends on the dref specified.
+ * @throws IOException If the command cannot be sent.
+ */
+ public void sendDREF(String dref, float[] value) throws IOException
+ {
+ //Preconditions
+ if(dref == null)
+ {
+ throw new IllegalArgumentException("dref must be a valid string.");
+ }
+ if(value == null || value.length == 0)
+ {
+ throw new IllegalArgumentException("value must be non-null and should contain at least one value.");
+ }
+
+ //Convert drefs to bytes.
+ byte[] drefBytes = dref.getBytes(StandardCharsets.UTF_8);
+ if(drefBytes.length == 0)
+ {
+ throw new IllegalArgumentException("DREF is an empty string!");
+ }
+ if(drefBytes.length > 255)
+ {
+ throw new IllegalArgumentException("dref must be less than 255 bytes in UTF-8. Are you sure this is a valid dref?");
+ }
+
+ ByteBuffer bb = ByteBuffer.allocate(4 * value.length);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ for(int i = 0; i < value.length; ++i)
+ {
+ bb.putFloat(i * 4, value[i]);
+ }
+
+ //Build and send message
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("DREF".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write(drefBytes.length);
+ os.write(drefBytes, 0, drefBytes.length);
+ os.write(value.length);
+ os.write(bb.array());
+ sendUDP(os.toByteArray());
+ }
+
+ /**
+ * Sends command to X-Plane setting control surfaces on the player aircraft.
+ *
+ * @param ctrl
An array containing zero to six values representing control surface data as follows:
+ *
+ * - Latitudinal Stick [-1,1]
+ * - Longitudinal Stick [-1,1]
+ * - Rudder Pedals [-1, 1]
+ * - Throttle [-1, 1]
+ * - Gear (0=up, 1=down)
+ * - Flaps [0, 1]
+ *
+ *
+ * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To
+ * change values in the middle of the array without affecting the preceding values, set the
+ * preceding values to -998.
+ *
+ * @throws IOException If the command cannot be sent.
+ */
+ public void sendCTRL(float[] ctrl) throws IOException
+ {
+ sendCTRL(ctrl, 0);
+ }
+
+ /**
+ * Sends command to X-Plane setting control surfaces on the specified aircraft.
+ *
+ * @param ctrl An array containing zero to six values representing control surface data as follows:
+ *
+ * - Latitudinal Stick [-1,1]
+ * - Longitudinal Stick [-1,1]
+ * - Rudder Pedals [-1, 1]
+ * - Throttle [-1, 1]
+ * - Gear (0=up, 1=down)
+ * - Flaps [0, 1]
+ *
+ *
+ * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To
+ * change values in the middle of the array without affecting the preceding values, set the
+ * preceding values to -998.
+ *
+ * @param aircraft The aircraft to set. 0 for the player's aircraft.
+ * @throws IOException If the command cannot be sent.
+ */
+ private void sendCTRL(float[] ctrl, int aircraft) throws IOException
+ {
+ //Preconditions
+ if(ctrl == null)
+ {
+ throw new IllegalArgumentException("ctrl must no be null.");
+ }
+ if(ctrl.length > 6)
+ {
+ throw new IllegalArgumentException("ctrl must have 6 or fewer elements.");
+ }
+ if(aircraft < 0)
+ {
+ throw new IllegalArgumentException("aircraft must be non-negative.");
+ }
+ if(aircraft != 0) //TODO: Implement support for non-player aircraft on plugin side.
+ {
+ throw new Error("Non-player aircraft not supported yet.");
+ }
+
+ //Pad command values and convert to bytes
+ int i;
+ int cur = 0;
+ ByteBuffer bb = ByteBuffer.allocate(22);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ for(i = 0; i < ctrl.length; ++i)
+ {
+ if(i == 4)
+ {
+ bb.put(cur, (byte) ctrl[i]);
+ cur += 1;
+ }
+ else
+ {
+ bb.putFloat(cur, ctrl[i]);
+ cur += 4;
+ }
+ }
+ for(; i < 6; ++i)
+ {
+ if(i == 4)
+ {
+ bb.put(cur, (byte) 0);
+ cur += 1;
+ }
+ else
+ {
+ bb.putFloat(cur, -998);
+ cur += 4;
+ }
+ }
+
+ //Build and send message
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("CTRL".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write(bb.array());
+ sendUDP(os.toByteArray());
+ }
+
+ /**
+ * Sets the position of the player aircraft.
+ *
+ * @param posi An array containing position elements as follows:
+ *
+ * - Latitiude (deg)
+ * - Longitude (deg)
+ * - Altitude (m above MSL)
+ * - Roll (deg)
+ * - Pitch (deg)
+ * - True Heading (deg)
+ * - Gear (0=up, 1=down)
+ *
+ *
+ * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To
+ * change values in the middle of the array without affecting the preceding values, set the
+ * preceding values to -998.
+ *
+ * @throws IOException If the command can not be sent.
+ */
+ public void sendPOSI(float[] posi) throws IOException
+ {
+ sendPOSI(posi, 0);
+ }
+
+ /**
+ * Sets the position of the specified aircraft.
+ *
+ * @param posi An array containing position elements as follows:
+ *
+ * - Latitiude (deg)
+ * - Longitude (deg)
+ * - Altitude (m above MSL)
+ * - Roll (deg)
+ * - Pitch (deg)
+ * - True Heading (deg)
+ * - Gear (0=up, 1=down)
+ *
+ *
+ * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To
+ * change values in the middle of the array without affecting the preceding values, set the
+ * preceding values to -998.
+ *
+ * @param aircraft The aircraft to set. 0 for the player aircraft.
+ * @throws IOException If the command can not be sent.
+ */
+ public void sendPOSI(float[] posi, int aircraft) throws IOException
+ {
+ //Preconditions
+ if(posi == null)
+ {
+ throw new IllegalArgumentException("posi must no be null.");
+ }
+ if(posi.length > 7)
+ {
+ throw new IllegalArgumentException("posi must have 7 or fewer elements.");
+ }
+ if(aircraft < 0 || aircraft > 255)
+ {
+ throw new IllegalArgumentException("aircraft must be between 0 and 255.");
+ }
+
+ //Pad command values and convert to bytes
+ int i;
+ ByteBuffer bb = ByteBuffer.allocate(28);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ for(i = 0; i < posi.length; ++i)
+ {
+ bb.putFloat(i * 4, posi[i]);
+ }
+ for(; i < 7; ++i)
+ {
+ bb.putFloat(i * 4, -998);
+ }
+
+ //Build and send message
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("POSI".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write(aircraft);
+ os.write(bb.array());
+ sendUDP(os.toByteArray());
+ }
+
+ public void sendDATA(float[][] data) throws IOException
+ {
+ //Preconditions
+ if(data == null || data.length == 0)
+ {
+ throw new IllegalArgumentException("data must be a non-null, non-empty array.");
+ }
+
+ //Convert data to bytes
+ ByteBuffer bb = ByteBuffer.allocate(4 * 9 * data.length);
+ bb.order(ByteOrder.LITTLE_ENDIAN);
+ for(int i = 0; i < data.length; ++i)
+ {
+ int rowStart = 9 * 4 * i;
+ float[] row = data[i];
+ if(row.length != 9)
+ {
+ throw new IllegalArgumentException("Rows must contain exactly 9 items. (Row " + i + ")");
+ }
+
+ bb.putInt(rowStart, (int) row[0]);
+ for(int j = 1; j < row.length; ++j)
+ {
+ bb.putFloat(rowStart + 4 * j, row[j]);
+ }
+ }
+
+ //Build and send message
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("DATA".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write(bb.array());
+ sendUDP(os.toByteArray());
+ }
+
+ /**
+ * Sets the port on which the client will receive data from X-Plane.
+ *
+ * @param recvPort The new incoming port number.
+ * @throws IOException If the command cannnot be sent.
+ */
+ public void setCONN(int recvPort) throws IOException
+ {
+ if(recvPort < 0 || recvPort >= 0xFFFF)
+ {
+ throw new IllegalArgumentException("Invalid port (must be non-negative and less than 65536).");
+ }
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ os.write("CONN".getBytes(StandardCharsets.UTF_8));
+ os.write(0xFF); //Placeholder for message length
+ os.write((byte) recvPort);
+ os.write((byte) (recvPort >> 8));
+ sendUDP(os.toByteArray());
+
+ int soTimeout = inSocket.getSoTimeout();
+ inSocket.close();
+ inSocket = new DatagramSocket(recvPort);
+ inSocket.setSoTimeout(soTimeout);
+ }
+}
diff --git a/Java/src/java Tests/java Tests.xcodeproj/project.pbxproj b/Java/src/java Tests/java Tests.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..aa3288a
--- /dev/null
+++ b/Java/src/java Tests/java Tests.xcodeproj/project.pbxproj
@@ -0,0 +1,170 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 46;
+ objects = {
+
+/* Begin PBXGroup section */
+ BE9440A11ACF1B7A009068A6 = {
+ isa = PBXGroup;
+ children = (
+ );
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXLegacyTarget section */
+ BE9440A61ACF1B7A009068A6 /* java Tests */ = {
+ isa = PBXLegacyTarget;
+ buildArgumentsString = "$(ACTION)";
+ buildConfigurationList = BE9440A91ACF1B7A009068A6 /* Build configuration list for PBXLegacyTarget "java Tests" */;
+ buildPhases = (
+ );
+ buildToolPath = /usr/bin/javac;
+ dependencies = (
+ );
+ name = "java Tests";
+ passBuildSettingsInEnvironment = 1;
+ productName = "java Tests";
+ };
+/* End PBXLegacyTarget section */
+
+/* Begin PBXProject section */
+ BE9440A21ACF1B7A009068A6 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 0500;
+ ORGANIZATIONNAME = "NASA Diagnostics and Prognostics Group";
+ };
+ buildConfigurationList = BE9440A51ACF1B7A009068A6 /* Build configuration list for PBXProject "java Tests" */;
+ compatibilityVersion = "Xcode 3.2";
+ developmentRegion = English;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ );
+ mainGroup = BE9440A11ACF1B7A009068A6;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ BE9440A61ACF1B7A009068A6 /* java Tests */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin XCBuildConfiguration section */
+ BE9440A71ACF1B7A009068A6 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.9;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ };
+ name = Debug;
+ };
+ BE9440A81ACF1B7A009068A6 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = YES;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_ENABLE_OBJC_EXCEPTIONS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.9;
+ SDKROOT = macosx;
+ };
+ name = Release;
+ };
+ BE9440AA1ACF1B7A009068A6 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ DEBUGGING_SYMBOLS = YES;
+ GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ BE9440AB1ACF1B7A009068A6 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ OTHER_CFLAGS = "";
+ OTHER_LDFLAGS = "";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ BE9440A51ACF1B7A009068A6 /* Build configuration list for PBXProject "java Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BE9440A71ACF1B7A009068A6 /* Debug */,
+ BE9440A81ACF1B7A009068A6 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ BE9440A91ACF1B7A009068A6 /* Build configuration list for PBXLegacyTarget "java Tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ BE9440AA1ACF1B7A009068A6 /* Debug */,
+ BE9440AB1ACF1B7A009068A6 /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = BE9440A21ACF1B7A009068A6 /* Project object */;
+}
diff --git a/Java/src/java Tests/java Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Java/src/java Tests/java Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..c5f49e9
--- /dev/null
+++ b/Java/src/java Tests/java Tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/Java/src/test/XPlaneConnectTest.java b/Java/src/test/XPlaneConnectTest.java
new file mode 100644
index 0000000..defc80c
--- /dev/null
+++ b/Java/src/test/XPlaneConnectTest.java
@@ -0,0 +1,494 @@
+package gov.nasa.xpc.test;
+
+import gov.nasa.xpc.XPlaneConnect;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+
+public class XPlaneConnectTest
+{
+ @Test
+ public void constructorTest()
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ assertNotNull(xpc);
+ }
+ catch (SocketException ex)
+ {
+ fail();
+ }
+ try(XPlaneConnect xpc = new XPlaneConnect(200))
+ {
+ assertNotNull(xpc);
+ }
+ catch (SocketException ex)
+ {
+ fail();
+ }
+ try(XPlaneConnect xpc = new XPlaneConnect(49007, "127.0.0.1", 49009))
+ {
+ assertNotNull(xpc);
+ }
+ catch (SocketException | UnknownHostException ex)
+ {
+ fail();
+ }
+ try(XPlaneConnect xpc = new XPlaneConnect(49007, "127.0.0.1", 49009, 200))
+ {
+ assertNotNull(xpc);
+ }
+ catch (SocketException | UnknownHostException ex)
+ {
+ fail();
+ }
+ }
+
+ @Test
+ public void testGetRecvPort() throws SocketException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ assertEquals(49008, xpc.getRecvPort());
+ }
+ }
+
+ @Test
+ public void testGetXPlanePort() throws SocketException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ assertEquals(49009, xpc.getXPlanePort());
+ }
+ }
+
+ @Test
+ public void testGetXPlaneAddr() throws SocketException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ assertEquals("127.0.0.1", xpc.getXPlaneAddr());
+ }
+ }
+
+ @Test
+ public void testSetXPlaneAddr() throws SocketException, UnknownHostException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.setXplaneAddr("10.10.0.100");
+ assertEquals("10.10.0.100", xpc.getXPlaneAddr());
+ }
+ }
+
+ @Test
+ public void constructorTest_SocketAlreadyBound() throws SocketException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ try(XPlaneConnect xpc2 = new XPlaneConnect())
+ {
+ fail();
+ }
+ catch (SocketException ex) {}
+ }
+ catch (SocketException ex)
+ {
+ fail();
+ }
+ }
+
+ @Test(expected = UnknownHostException.class)
+ public void constructorTest_InvalidHost() throws UnknownHostException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect(49007, "notarealhost", 49009))
+ {
+
+ }
+ catch (SocketException ex)
+ {
+ fail();
+ }
+ fail();
+ }
+
+ @Test
+ public void testRequestDREF() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ float[] result = xpc.requestDREF(dref);
+ assertEquals(1, result.length);
+ }
+ }
+
+ @Test
+ public void testRequestDREFs() throws IOException
+ {
+ String[] drefs =
+ {
+ "sim/cockpit/switches/gear_handle_status",
+ "sim/cockpit2/switches/panel_brightness_ratio"
+ };
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ float[][] result = xpc.requestDREFs(drefs);
+ assertEquals(2, result.length);
+ assertEquals(1, result[0].length);
+ assertEquals(4, result[1].length);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRequestDREFs_NullArgument() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.requestDREFs(null);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRequestDREFs_EmptyArgument() throws IOException
+ {
+ String[] drefs = new String[0];
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.requestDREFs(drefs);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRequestDREFs_TooManyDREFs() throws IOException
+ {
+ String[] drefs = new String[300];
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.requestDREFs(drefs);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRequestDREFs_DREFTooLong() throws IOException
+ {
+ String longDREF = "sim/cockpit/switches/i/am/a/very/long/fake/dref/that/is/over/255/characters/./which/means/that/my/length/cant/be/encoded/in/the/single/byte/allocated/by/the/message/format/,/so/i/should/cause/an/exception/instead/./i/am/still/not/long/enough/./almost/there";
+ String[] drefs = new String[]{longDREF};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.requestDREFs(drefs);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testRequestDREF_DREFEmpty() throws IOException
+ {
+ String dref = "";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.requestDREF(dref);
+ fail();
+ }
+ }
+
+ @Test
+ public void testPauseSim() throws IOException
+ {
+ String dref = "sim/operation/override/override_planepath";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.pauseSim(true);
+ float[] result = xpc.requestDREF(dref);
+ //assertEquals(1, result.length); //TODO: Why is this result 20 elements long in Java? (It's only one in MATLAB)
+ assertEquals(1, result[0], 1e-4);
+
+ xpc.pauseSim(false);
+ result = xpc.requestDREF(dref);
+ //assertEquals(1, result.length);
+ assertEquals(0, result[0], 1e-4);
+ }
+ }
+
+ @Test
+ public void testSendDREF() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ float gearHandle = xpc.requestDREF(dref)[0];
+
+ float value = gearHandle > 0.5 ? 0 : 1;
+ xpc.sendDREF(dref, value);
+
+ float result = xpc.requestDREF(dref)[0];
+ assertEquals(value, result, 1e-4);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_NullDREF() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(null, 0);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_NullValue() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(dref, null);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_EmptyValue() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(dref, new float[0]);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_MessageTooLong() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(dref, new float[200]);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_DREFTooLong() throws IOException
+ {
+ String dref = "sim/cockpit/switches/i/am/a/very/long/fake/dref/that/is/over/255/characters/./which/means/that/my/length/cant/be/encoded/in/the/single/byte/allocated/by/the/message/format/,/so/i/should/cause/an/exception/instead/./i/am/still/not/long/enough/./almost/there";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(dref, 0);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendDREF_DREFEmpty() throws IOException
+ {
+ String dref = "";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDREF(dref, 0);
+ fail();
+ }
+ }
+
+ @Test
+ public void testSendCTRL() throws IOException
+ {
+ String[] 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"
+ };
+ float[] ctrl = new float[] {0, 0, 1, 0.8F, 0, 1};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendCTRL(ctrl);
+ float[][] result = xpc.requestDREFs(drefs);
+ if(result.length < ctrl.length)
+ {
+ fail();
+ }
+ for(int i = 0; i < 6; ++i)
+ {
+ assertEquals(ctrl[i], result[i][0], 1e-2);
+ }
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendCTRL_NullCtrl() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendCTRL(null);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendCTRL_LongCtrl() throws IOException
+ {
+ float[] ctrl = new float[] {0, 0, 1, 0.8F, 0, 1, -998};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendCTRL(ctrl);
+ }
+ }
+
+ @Test
+ public void testSendPOSI() throws IOException
+ {
+ String[] drefs = {
+ "sim/flightmodel/position/latitude",
+ "sim/flightmodel/position/longitude",
+ "sim/flightmodel/position/y_agl",
+ "sim/flightmodel/position/phi",
+ "sim/flightmodel/position/theta",
+ "sim/flightmodel/position/psi",
+ "sim/cockpit/switches/gear_handle_status"
+ };
+ float[] posi = new float[] {37.524F, -122.06899F, 2500, 0, 0, 0, 1};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.pauseSim(true);
+ xpc.sendPOSI(posi);
+ //TODO: It seems that these calls are a bit too fast. The dref request often gets stale data, causing the test to fail incorrectly.
+ try {Thread.sleep(100);}catch(InterruptedException ex){}
+ float[][] result = xpc.requestDREFs(drefs);
+ xpc.pauseSim(false);
+ if(result.length < posi.length)
+ {
+ fail();
+ }
+ assertEquals(posi[0], result[0][0], 1e-4);
+ assertEquals(posi[1], result[1][0], 1e-4);
+ assertEquals(posi[2], result[2][0], 10);
+ assertEquals(posi[3], result[3][0], 1e-4);
+ assertEquals(posi[4], result[4][0], 1e-4);
+ assertEquals(posi[5], result[5][0], 1e-4);
+ assertEquals(posi[6], result[6][0], 1e-4);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendPOSI_NullCtrl() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendPOSI(null);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendPOSI_LongCtrl() throws IOException
+ {
+ float[] posi = new float[] {37.524F, -122.06899F, 2500, 0, 0, 0, 1, -998};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendPOSI(posi);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendPOSI_NegativeAircraftNum() throws IOException
+ {
+ float[] posi = new float[] {37.524F, -122.06899F, 2500, 0, 0, 0, 1, -998};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendPOSI(posi, -1);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendPOSI_LargeAircraftNum() throws IOException
+ {
+ float[] posi = new float[] {37.524F, -122.06899F, 2500, 0, 0, 0, 1, -998};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendPOSI(posi, 300);
+ }
+ }
+
+ @Test
+ public void testSendDATA() throws IOException
+ {
+ float[][] data = {{25,0.8F,-988,-988,-988,-988,-988,-988,-988}};
+ String dref = "sim/flightmodel/engine/ENGN_thro";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDATA(data);
+ float[] result = xpc.requestDREF(dref);
+ assertEquals(data[0][1], result[0], 1e-4);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendData_NullData() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDATA(null);
+ }
+ fail();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSendData_InvalidData() throws IOException
+ {
+ float[][] data = {{25,0.8F,-988,-988,-988,-988,-988,-988,-988,-988}};
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.sendDATA(data);
+ }
+ fail();
+ }
+
+ @Test
+ public void testSetCONN() throws IOException
+ {
+ String dref = "sim/cockpit/switches/gear_handle_status";
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ int p = xpc.getRecvPort();
+ xpc.setCONN(49055);
+ assertEquals(49055, xpc.getRecvPort());
+ float[] result = xpc.requestDREF(dref);
+ assertEquals(1, result.length);
+
+ xpc.setCONN(p);
+ assertEquals(p, xpc.getRecvPort());
+ result = xpc.requestDREF(dref);
+ assertEquals(1, result.length);
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetCONN_NegativePort() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.setCONN(-1);
+ fail();
+ }
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testSetCONN_LargePort() throws IOException
+ {
+ try(XPlaneConnect xpc = new XPlaneConnect())
+ {
+ xpc.setCONN(65536);
+ fail();
+ }
+ }
+}
\ No newline at end of file