Basic implementation of the Java client with integration tests.

This commit is contained in:
Jason Watkins
2015-03-31 12:05:04 -07:00
parent 0a09b11bae
commit b783b40073
8 changed files with 1101 additions and 4 deletions

2
Java/.idea/.name generated
View File

@@ -1 +1 @@
Java XPlaneConnect

View File

@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" build-on-make="true" name="XPlaneConnect:jar">
<output-path>$PROJECT_DIR$/out/artifacts/XPlaneConnect_jar</output-path>
<root id="archive" name="XPlaneConnect.jar">
<element id="module-output" name="XPlaneConnect" />
</root>
</artifact>
</component>

10
Java/.idea/libraries/junit_4_11.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="junit-4.11">
<CLASSES>
<root url="jar://$APPLICATION_HOME_DIR$/lib/junit-4.11.jar!/" />
<root url="jar://$APPLICATION_HOME_DIR$/lib/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

2
Java/.idea/misc.xml generated
View File

@@ -13,7 +13,7 @@
<ConfirmationsSetting value="0" id="Add" /> <ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" /> <ConfirmationsSetting value="0" id="Remove" />
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

2
Java/.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="" vcs="" /> <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component> </component>
</project> </project>

View File

@@ -7,5 +7,6 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="junit-4.11" level="project" />
</component> </component>
</module> </module>

View File

@@ -1,5 +1,588 @@
package gov.nasa.xpc; package gov.nasa.xpc;
public class XPlaneConnect 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 DatagramSocket socket;
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 socket.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.socket = new DatagramSocket(49008);
this.xplaneAddr = InetAddress.getLoopbackAddress();
this.xplanePort = 49009;
this.socket.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.socket = new DatagramSocket(port);
this.xplaneAddr = InetAddress.getByName(xplaneHost);
this.xplanePort = xplanePort;
this.socket.setSoTimeout(timeout);
}
/**
* Closes the underlying socket.
*/
public void close()
{
if(socket != null)
{
socket.close();
socket = 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
{
socket.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);
socket.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(bb.array());
sendUDP(os.toByteArray());
}
/**
* Sends command to X-Plane setting control surfaces on the player aircraft.
*
* @param ctrl <p>An array containing zero to six values representing control surface data as follows:</p>
* <ol>
* <li>Latitudinal Stick [-1,1]</li>
* <li>Longitudinal Stick [-1,1]</li>
* <li>Rudder Pedals [-1, 1]</li>
* <li>Throttle [-1, 1]</li>
* <li>Gear (0=up, 1=down)</li>
* <li>Flaps [0, 1]</li>
* </ol>
* <p>
* 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.
* </p>
* @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 <p>An array containing zero to six values representing control surface data as follows:</p>
* <ol>
* <li>Latitudinal Stick [-1,1]</li>
* <li>Longitudinal Stick [-1,1]</li>
* <li>Rudder Pedals [-1, 1]</li>
* <li>Throttle [-1, 1]</li>
* <li>Gear (0=up, 1=down)</li>
* <li>Flaps [0, 1]</li>
* </ol>
* <p>
* 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.
* </p>
* @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 <p>An array containing position elements as follows:</p>
* <ol>
* <li>Latitiude (deg)</li>
* <li>Longitude (deg)</li>
* <li>Altitude (m above MSL)</li>
* <li>Roll (deg)</li>
* <li>Pitch (deg)</li>
* <li>True Heading (deg)</li>
* <li>Gear (0=up, 1=down)</li>
* </ol>
* <p>
* 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.
* </p>
* @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 <p>An array containing position elements as follows:</p>
* <ol>
* <li>Latitiude (deg)</li>
* <li>Longitude (deg)</li>
* <li>Altitude (m above MSL)</li>
* <li>Roll (deg)</li>
* <li>Pitch (deg)</li>
* <li>True Heading (deg)</li>
* <li>Gear (0=up, 1=down)</li>
* </ol>
* <p>
* 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.
* </p>
* @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());
}
/**
*
*
* @param recvPort
* @throws IOException
*/
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());
socket.close();
socket = new DatagramSocket(recvPort);
}
} }

View File

@@ -0,0 +1,495 @@
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(49007, 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",
"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);
try
{
//TODO: Without this, The rudder dref seems to consistently report -2.00802304E8
Thread.sleep(200);
}
catch (InterruptedException ex){}
float[][] result = xpc.requestDREFs(drefs);
if(result.length < ctrl.length)
{
fail();
}
for(int i = 0; i < 6; ++i)
{
assertEquals(ctrl[i], Math.abs(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.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.
float[][] result = xpc.requestDREFs(drefs);
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], 1e-4);
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());
}
}
@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();
}
}
}