legacy commit 4 (backlog)

This commit is contained in:
cs-powell
2024-11-23 23:43:56 -05:00
parent 7678b4e1f9
commit d2156ae9bc
21 changed files with 7277 additions and 7 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="Java">
<CLASSES>
<root url="jar://$PROJECT_DIR$/../../out/artifacts/XPlaneConnect_jar/XPlaneConnect.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="ProjectKey">
<option name="state" value="project://e2804f05-5315-4fc6-a121-c522a6c26470" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/BasicOperation.iml" filepath="$PROJECT_DIR$/BasicOperation.iml" />
</modules>
</component>
</project>

View File

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

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="gov.nasa.xpc.ex" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="Java" level="project" />
</component>
</module>

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,634 @@
package gov.nasa.xpc.ex;
import gov.nasa.xpc.XPlaneConnect;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.SocketException;
import java.util.Arrays;
import javax.swing.*;
import javax.swing.border.Border;
import javax.imageio.ImageIO;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.*;
/**
* An example program demonstrating the basic features of the X-Plane Connect toolbox.
*
* @author Jason Watkins
* @version 1.0
* @since 2015-04-06
*/
public class Main
{
public static void main(String[] args) {
System.out.println("X-Plane Connect example program");
System.out.println("Setting up simulation...");
JFrame frame = new JFrame();
try(XPlaneConnect xpc = new XPlaneConnect())
{
// Ensure connection established.
xpc.getDREF("sim/test/test_float");
// System.out.println("Setting player aircraft position");
// double[] posi = new double[] {37.524, -122.06899, 2500, 0, 0, 0, 1};
// xpc.sendPOSI(posi);
// System.out.println("Setting another aircraft position");
// posi[0] = 37.52465;
// posi[4] = 20;
// xpc.sendPOSI(posi, 1);
System.out.println("Setting rates");
float[][] data = new float[3][9];
for(float[] row : data)
{
Arrays.fill(row, -998);
}
data[0][0] = 0; //Alpha
data[0][1] = 0;
data[0][3] = 0;
data[1][0] = 0; //Velocity
data[1][1] = 0;
data[1][2] = 0;
data[1][3] = 0;
data[1][4] = 0;
data[2][0] = 0; //PQR
data[2][1] = 0;
data[2][2] = 0;
data[2][3] = 0;
xpc.sendDATA(data);
System.out.println("Trying something new!!");
frame.setPreferredSize(new Dimension(1700,270));
frame.setLayout(new GridLayout(1,2));
frame.setVisible(true);
JPanel display = new JPanel();
display.setPreferredSize(new Dimension(100,100));
display.setLayout(new GridLayout(2,4));
Font fontTitles = new Font("Impact", 1,40);
Font fontData = new Font("Monospaced", 1,30);
JLabel titleOne = new JLabel("Elevator");
titleOne.setFont(fontTitles);
titleOne.setHorizontalAlignment(SwingConstants.CENTER);
titleOne.setVerticalAlignment(SwingConstants.BOTTOM);
JLabel titleTwo = new JLabel("Roll");
titleTwo.setFont(fontTitles);
titleTwo.setHorizontalAlignment(SwingConstants.CENTER);
titleTwo.setVerticalAlignment(SwingConstants.BOTTOM);
JLabel titleThree = new JLabel("Yaw");
titleThree.setFont(fontTitles);
titleThree.setHorizontalAlignment(SwingConstants.CENTER);
titleThree.setVerticalAlignment(SwingConstants.BOTTOM);
JLabel titleFour = new JLabel("Throttle");
titleFour.setFont(fontTitles);
titleFour.setHorizontalAlignment(SwingConstants.CENTER);
titleFour.setVerticalAlignment(SwingConstants.BOTTOM);
JLabel one = new JLabel();
one.setFont(fontData);
one.setHorizontalAlignment(SwingConstants.CENTER);
one.setVerticalAlignment(SwingConstants.TOP);
JLabel two = new JLabel();
two.setFont(fontData);
two.setHorizontalAlignment(SwingConstants.CENTER);
two.setVerticalAlignment(SwingConstants.TOP);
JLabel three = new JLabel();
three.setFont(fontData);
three.setHorizontalAlignment(SwingConstants.CENTER);
three.setVerticalAlignment(SwingConstants.TOP);
JLabel four = new JLabel();
four.setFont(fontData);
four.setHorizontalAlignment(SwingConstants.CENTER);
four.setVerticalAlignment(SwingConstants.TOP);
one.setText("Test");
two.setText("Test");
three.setText("Test");
four.setText("Test");
display.add(titleOne);
display.add(titleTwo);
display.add(titleThree);
display.add(titleFour);
display.add(one);
display.add(two);
display.add(three);
display.add(four);
JPanel visualizer = new JPanel();
visualizer.setLayout(new GridLayout(2,1));
//Col 1 of Visualizer
JPanel yokeGrid = new JPanel();
yokeGrid.setMinimumSize(new Dimension(100,100));
//grid.setOpaque(true);
// grid.setLayout(null);
yokeGrid.setBackground(Color.black);
yokeGrid.setVisible(true);
Integer layer1 = 0;
Integer layer2 = 1;
// grid.setLayout(new GridLayout(1,1));
yokeGrid.setLayout(new OverlayLayout(yokeGrid));
yokePosition yoke = new yokePosition(yokeGrid.getWidth(), yokeGrid.getHeight());
// yaw yaw = new yaw(grid.getWidth(), grid.getHeight());
axis axis = new axis(yokeGrid.getWidth(), yokeGrid.getHeight());
// grid.setLayer(axis, layer1);
yokeGrid.add(axis);
yokeGrid.add(yoke);
// grid.add(yaw);
// grid.setLayer(yoke, layer2);
Border greenLine = BorderFactory.createLineBorder(Color.green);
yokeGrid.setBorder(greenLine);
visualizer.add(yokeGrid);
//Row 2 of Visualizer
//Rudder Vizualizer
rudderPosition rudderGrid = new rudderPosition(yokeGrid.getWidth(), 0);
visualizer.add(rudderGrid);
frame.add(display); // Left Side
frame.add(visualizer); // Right Side
frame.pack();
// JLabel text = new JLabel("Hello");
// JLabel text2 = new JLabel("Hello");
// JPanel display = new JPanel();
// display.setLayout(new GridLayout());
// display.setPreferredSize(size);
// display.add(text);
// display.add(text2);
// container.add(display);
// container.pack();
int aircraft = 0;
boolean takeoff = true;
boolean climb = false;
boolean cruise = false;
boolean throttleFull = false;
boolean brakeOff = false;
boolean switchTrack = false;
String dref = "sim/cockpit2/gauges/indicators/airspeed_kts_pilot";
String dref2 = "sim/flightmodel/position/true_phi";
String drefHDG = "sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot";
String drefHDGBug = "sim/cockpit/autopilot/heading_mag";
String drefAltitude = "sim/cockpit2/gauges/indicators/altitude_ft_pilot";
File file = new File("/Users/flyingtopher/Desktop/Code Citadel/School/2. Research/Fork Clone/XPlaneConnectCSP/Java/Project_Models/Model-1/src/output");
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file, true);
BufferedWriter bw = new BufferedWriter(fw);
while(true) {
//THE GETTERS
double[] posi1 = xpc.getPOSI(aircraft); // FIXME: change this to 64-bit double
float[] ctrl1 = xpc.getCTRL(aircraft);
float[] value = xpc.getDREF(dref);
float[] value2 = xpc.getDREF(dref2);
float[] valueHDG = xpc.getDREF(drefHDG);
float[] valueHDGBug = xpc.getDREF(drefHDGBug);
float[] valueAltitude = xpc.getDREF(drefAltitude);
float bugged = 50;
float rwyHDG = valueHDGBug[0];
/*
* Outputs
*/
// System.out.format("\r[Elevator: %2f] [Roll: %2f] [Yaw: %2f] ---- [Throttle:%2f] ---- [Flaps: %2f] -- [Data Ref: %2f] -- [T/O: %b ][Cruise: %b ]",
// ctrl1[0], ctrl1[1], ctrl1[2], ctrl1[3], ctrl1[5], valueAltitude[0], takeoff, cruise);
String log = String.format("[Elevator: %2f] [Roll: %2f] [Yaw: %2f] [Throttle:%2f] [Flaps: %2f] [Data Ref: %2f] [T/O: %b ][Cruise: %b ]",
ctrl1[0], ctrl1[1], ctrl1[2], ctrl1[3], ctrl1[5], valueAltitude[0], takeoff, cruise);
one.setText(String.valueOf(ctrl1[0]));
if(ctrl1[0] >= 0 ) {
one.setForeground(Color.green);
} else {
one.setForeground(Color.red);
}
two.setText(String.valueOf(ctrl1[1]));
if(ctrl1[1] >= 0 ) {
two.setForeground(Color.green);
} else {
two.setForeground(Color.red);
}
three.setText(String.valueOf(ctrl1[2]));
if(ctrl1[2] >= 0 ) {
three.setForeground(Color.green);
} else {
three.setForeground(Color.red);
}
four.setText(String.valueOf(ctrl1[3]));
if(ctrl1[3] >= 0 ) {
four.setForeground(Color.green);
} else {
four.setForeground(Color.red);
}
yoke.setXBound(yokeGrid.getWidth());
yoke.setYBound(yokeGrid.getHeight());
yoke.setX(ctrl1[1]);
yoke.setY(ctrl1[0]);
yoke.repaint();
rudderGrid.setXBound(yokeGrid.getWidth());
rudderGrid.setYBound(yokeGrid.getHeight());
rudderGrid.setX(ctrl1[2]);
rudderGrid.repaint();
// yaw.setYBound(grid.getHeight());
// yaw.setYBound(grid.getHeight());
// yaw.setX(ctrl1[2]);
// yaw.repaint();
axis.setXBound(yokeGrid.getWidth());
axis.setYBound(yokeGrid.getHeight());
axis.repaint();
//Writing Data to a File
try {
bw.write(log + "\n");
bw.flush();
} catch (IOException e) {
System.out.println("Log Data Failed");
}
/*
* Flight Controls
*/
// //Basic Autopilot For Roll (based on yoke position)
// float[] rollRight = {-998.0f, ctrl1[1]-(ctrl1[1]/2)};
// float[] rollLeft = {-998.0f, ctrl1[1]-(ctrl1[1]/2)};
// if(ctrl1[1] < 0) {
// xpc.sendCTRL(rollRight);
// } else if(ctrl1[1] > 0) {
// xpc.sendCTRL(rollLeft);
// }
//Basic Autopilot For Pitch (based on VSI)
// float[] pitchUp = {ctrl1[0] + 0.01f};
// float[] pitchDown = {ctrl1[0]- 0.01f};
// if(value[0] < 0) {
// xpc.sendCTRL(pitchUp);
// } else if(value[0] > 0) {
// xpc.sendCTRL(pitchDown);
// }
if(valueAltitude[0] > 1000f && !switchTrack && takeoff == true) {
System.out.println("In switch");
takeoff = false;
cruise = true;
switchTrack = true;
} else if (valueAltitude[0] < 1000f && switchTrack && takeoff == true) {
takeoff = true;
cruise = false;
switchTrack = false;
}
//Control Profiles
if(takeoff) {
float[] fullThrottle = {-998.0f, -998.0f, -998.0f, 1f};
if(!throttleFull) {
xpc.sendCTRL(fullThrottle);
throttleFull = true;
}
if(throttleFull && !brakeOff) {
String parkingBrake = "sim/cockpit2/controls/parking_brake_ratio";
xpc.sendDREF(parkingBrake, 0f);
brakeOff = true;
}
//Takeoff Pitch Control
float[] pitchUp = {ctrl1[0] + 0.01f};
if(value[0] > bugged) {
if(ctrl1[0] < 0.1f){
xpc.sendCTRL(pitchUp);
}
}
//Takeoff Roll Control
float[] rollRight = {-998.0f, ctrl1[1] + 0.01f};
float[] rollLeft = {-998.0f, ctrl1[1] - 0.01f};
if(value2[0] < 0 && value[0] > bugged) {
if(ctrl1[1] < 0.15f) {
xpc.sendCTRL(rollRight);
}
} else if(value2[0] > 0 && value[0] > bugged) {
if(ctrl1[1] > -0.15f) {
xpc.sendCTRL(rollLeft);
}
}
//Takeoff Rudder Control 
float[] yawRight = {-998.0f, -998.0f, ctrl1[2] + 0.03f};
float[] yawLeft = {-998.0f, -998.0f, ctrl1[2] - 0.03f};
if(valueHDG[0] < rwyHDG && value[0] > 0) {
if(ctrl1[2] < 0.5f) {
xpc.sendCTRL(yawRight); // YAW RIGHT
}
} else if(valueHDG[0] > rwyHDG && value[0] > 0) {
if(ctrl1[2] > -0.5f){
xpc.sendCTRL(yawLeft); // YAW LEFT
}
}
}
if(cruise) {
//Basic Autopilot For Roll (based on bank angle)
float[] rollRight = {-998.0f, ctrl1[1] + 0.01f};
float[] rollLeft = {-998.0f, ctrl1[1] - 0.01f};
if(value2[0] < 0) {
if(ctrl1[1] < 0.15f) {
xpc.sendCTRL(rollRight);
}
} else if(value2[0] > 0) {
if(ctrl1[1] > -0.15f) {
xpc.sendCTRL(rollLeft);
}
}
//Basic Autopilot For Pitch (based on speed)
float[] pitchUp = {ctrl1[0] + 0.01f};
float[] pitchDown = {ctrl1[0]- 0.01f};
if(value[0] > bugged+25) {
if(ctrl1[0] < 0.2f) {
xpc.sendCTRL(pitchUp);
}
} else if(value[0] < bugged+25) {
if(ctrl1[0] > -0.2f) {
xpc.sendCTRL(pitchDown);
}
}
}
try {
Thread.sleep(0);
}
catch (InterruptedException ex) {}
if (System.in.available() > 0) {
break;
}
}
// System.out.println("Setting controls");
// float[] ctrl = new float[4];
// ctrl[3] = 0.8F;
// xpc.sendCTRL(ctrl);
// System.out.println("Pausing sim");
// xpc.pauseSim(true);
// try { Thread.sleep(5000); } catch (InterruptedException ex) {}
// System.out.println("Un-pausing");
// xpc.pauseSim(false);
System.out.println("Example complete");
}
catch (SocketException ex)
{
System.out.println("Unable to set up the connection. (Error message was '" + ex.getMessage() + "'.)");
}
catch (IOException ex)
{
System.out.println("Something went wrong with one of the commands. (Error message was '" + ex.getMessage() + "'.)");
frame.dispose();
}
System.out.println("Exiting");
}
//Helper Methods
// public static void logData (BufferedWriter bw, String log) {
// bw.write(log);
// }
}
class axis extends JComponent {
int xBound = 0;
int yBound = 0;
axis(int currentX, int currentY) {
xBound = currentX;
yBound = currentY;
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.green);
g2.drawLine(xBound/2, 0, xBound/2, yBound);
g2.drawLine(0, yBound/2, xBound, yBound/2);
}
public void setXBound(int newX){
this.xBound = newX;
}
public void setYBound(int newY){
this.yBound = newY;
}
}
// class yaw extends JComponent {
// int xBound;
// int yBound;
// float x = 0;
// int currX = (int)(x*xBound) + xBound/2;
// yaw(int currentX, int currentY) {
// xBound = currentX;
// yBound = currentY;
// }
// public void paint(Graphics g)
// {
// Graphics2D g2 = (Graphics2D) g;
// g2.setColor(Color.blue);
// g2.drawLine(currX, 0, currX, yBound);
// g2.fillOval(currX, yBound/2, 20, 20);
// }
// public void setXBound(int newX){
// this.xBound = newX;
// }
// public void setYBound(int newY){
// this.yBound = newY;
// }
// public void setX(float newX){
// this.x = newX;
// }
// }
class yokePosition extends JComponent {
float x = 0;
int xBound = 0;
float y = 0;
int yBound = 0;
int width = 50;
int height = 50;
yokePosition(int currentX, int currentY) {
xBound = currentX;
yBound = currentY;
}
yokePosition() {
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
try {
final BufferedImage image = ImageIO.read(new File("/Users/flyingtopher/Desktop/Code Citadel/School/2. Research/Fork Clone/XPlaneConnectCSP/Java/Project_Models/Model-1/content/Yoke Symbol.png"));
Image scaledImage = image.getScaledInstance(xBound, yBound, Image.SCALE_DEFAULT);
g.drawImage(scaledImage, 0, 0, getFocusCycleRootAncestor());
} catch (IOException e){
System.out.print("Rudder Image Failed");
}
g2.setColor(Color.red);
int currX = (int)(x*xBound) + xBound/2 - width/2;
int currY = (int)(y*yBound) + yBound/2 - height/2;
System.out.println("CurrX, CurrY: " + currX +", " + currY);
// g2.drawOval(currX, currY, width, height);
g2.fillOval(currX, currY, width, height);
g.setColor(Color.green);
int height = g.getFontMetrics().getHeight();
g.drawString("Nose Down", (xBound/2) + 5, height);
g.drawString("Nose Up", (xBound/2) + 5, yBound - height/2);
g.drawString("Roll Left",0, yBound/2);
int width = g.getFontMetrics().stringWidth("Roll Right");
g.drawString("Roll Right", xBound - width, yBound/2);
}
public void setX(float newX){
this.x = newX;
}
public void setXBound(int newX){
this.xBound = newX;
}
public void setY(float newY){
this.y = newY;
}
public void setYBound(int newY){
this.yBound = newY;
}
}
class rudderPosition extends JComponent {
int xBound = 0;
int yBound = 0;
float rudderTravel = 0;
rudderPosition(int currentXBound, int currentYBound) {
xBound = currentXBound;
yBound = currentYBound;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
int spacer = 10;
g.setColor(Color.gray);
g2.fillRect(0, spacer, xBound, yBound - spacer);
g2.setColor(Color.red);
int width = (int) (xBound/2 * rudderTravel);
//g2.drawRect(xBound/2, 0 +spacer, width, yBound);
g2.fillRect(xBound/2, 0 + spacer, width, yBound);
g.drawString("Yaw Left",0, yBound/2);
int textWidth = g.getFontMetrics().stringWidth("Roll Right");
g.drawString("Yaw Right", xBound - textWidth, yBound/2);
try {
final BufferedImage image = ImageIO.read(new File("/Users/flyingtopher/Desktop/Code Citadel/School/2. Research/Fork Clone/XPlaneConnectCSP/Java/Project_Models/Model-1/content/Rudder Pedals.png"));
Image scaledImage = image.getScaledInstance(xBound, yBound, Image.SCALE_DEFAULT);
g.drawImage(scaledImage, 0, 0, getFocusCycleRootAncestor());
} catch (IOException e) {
System.out.print("Rudder Image Failed");
}
}
public void setX(float newX) {
this.rudderTravel = newX;
}
public void setXBound(int newX) {
this.xBound = newX;
}
public void setYBound(int newY) {
this.yBound = newY;
}
}

View File

@@ -0,0 +1,954 @@
//NOTICES:
// Copyright (c) 2013-2018 United States Government as represented by the Administrator of the
// National Aeronautics and Space Administration. All Rights Reserved.
//
// DISCLAIMERS
// No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
// EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE
// SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT
// SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
// THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY
// GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE,
// SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE.
// FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY
// SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
//
// Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES
// GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF
// RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES,
// EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR
// RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
// HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY
// PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER
// SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
package gov.nasa.xpc;
import gov.nasa.xpc.discovery.Beacon;
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 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(0);
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 xpHost The network host on which X-Plane is running.
* @param xpPort The port on which the X-Plane Connect plugin is listening.
* @param port The local port to use when sending and receiving data from XPC.
* @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(String xpHost, int xpPort, int port)
throws java.net.SocketException, java.net.UnknownHostException
{
this(xpHost, xpPort, port, 100);
}
/**
* Initializes a new instance of the {@code XPlaneConnect} class from a received discovery Beacon
* @param beacon The beacon received from {@code XPlaneConnectDiscovery}
* @throws SocketException If this instance is unable to bind to the specified port.
*/
public XPlaneConnect(Beacon beacon) throws SocketException {
this.socket = new DatagramSocket(0);
this.xplaneAddr = beacon.getXplaneAddress();
this.xplanePort = beacon.getPluginPort();
this.socket.setSoTimeout(100);
}
/**
* Initializes a new instance of the {@code XPlaneConnect} class using the specified ports, hostname, and timeout.
*
* @param xpHost The network host on which X-Plane is running.
* @param xpPort The port on which the X-Plane Connect plugin is listening.
* @param port The port on which the X-Plane Connect plugin is sending data.
* @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(String xpHost, int xpPort, int port, int timeout)
throws java.net.SocketException, java.net.UnknownHostException
{
this.socket = new DatagramSocket(port);
this.xplaneAddr = InetAddress.getByName(xpHost);
this.xplanePort = xpPort;
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
{
byte[] buffer = new byte[65536];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
try
{
socket.receive(packet);
return Arrays.copyOf(buffer, packet.getLength());
}
catch (SocketTimeoutException ex)
{
return new byte[0];
}
}
/**
* Send the given data to the X-Plane plugin.
*
* @param buffer The data to send.
* @throws IOException If the send operation fails.
*/
private void sendUDP(byte[] buffer) throws IOException
{
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);
}
/**
* Pauses, unpauses, or switches the pause state of X-Plane.
*
* @param pause {@code 1} to pause the simulator, {@code 0} to unpause, or {@code 2} to switch.
* @throws IllegalArgumentException If the values of {@code pause} is not a valid command.
* @throws IOException If the command cannot be sent.
*/
public void pauseSim(int pause) throws IOException
{
if(pause < 0 || (pause > 2 && pause < 100) || (pause > 119 && pause < 200) || pause > 219)
{
throw new IllegalArgumentException("pause must be a value in the range [0, 2], [100, 119], or [200, 219].");
}
// S I M U LEN VAL
byte[] msg = {0x53, 0x49, 0x4D, 0x55, 0x00, 0x00};
msg[5] = (byte)pause;
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[] getDREF(String dref) throws IOException
{
return getDREFs(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[][] getDREFs(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
byte[] data = readUDP();
if(data.length == 0)
{
throw new IOException("No response received.");
}
if(data.length < 6)
{
throw new IOException("Response too short");
}
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;
}
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 X-Plane dataref 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
{
sendDREFs(new String[] {dref}, new float[][] {value});
}
/**
* Sends a command to X-Plane that sets the given DREF.
*
* @param drefs The names of the X-Plane datarefs to set.
* @param values A sequence of arrays of floating point values whose structure depends on the drefs specified.
* @throws IOException If the command cannot be sent.
*/
public void sendDREFs(String[] drefs, float[][] values) throws IOException
{
//Preconditions
if(drefs == null || drefs.length == 0)
{
throw new IllegalArgumentException(("drefs must be non-empty."));
}
if(values == null || values.length != drefs.length)
{
throw new IllegalArgumentException("values must be of the same size as drefs.");
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("DREF".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
for(int i = 0; i < drefs.length; ++i)
{
String dref = drefs[i];
float[] value = values[i];
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 j = 0; j < value.length; ++j)
{
bb.putFloat(j * 4, value[j]);
}
//Build and send message
os.write(drefBytes.length);
os.write(drefBytes, 0, drefBytes.length);
os.write(value.length);
os.write(bb.array());
}
sendUDP(os.toByteArray());
}
/**
* Gets the control surface information for the specified airplane.
*
* @param ac The aircraft to get control surface information for.
* @return An array containing control surface data in the same format as {@code sendCTRL}.
* @throws IOException If the command cannot be sent or a response cannot be read.
*/
public float[] getCTRL(int ac) throws IOException
{
// Send request
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("GETC".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(ac);
sendUDP(os.toByteArray());
// Read response
byte[] data = readUDP();
if(data.length == 0)
{
throw new IOException("No response received.");
}
if(data.length < 31)
{
throw new IOException("Response too short");
}
// Parse response
float[] result = new float[7];
ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.LITTLE_ENDIAN);
result[0] = bb.getFloat(5);
result[1] = bb.getFloat(9);
result[2] = bb.getFloat(13);
result[3] = bb.getFloat(17);
result[4] = bb.get(21);
result[5] = bb.getFloat(22);
result[6] = bb.getFloat(27);
return result;
}
/**
* Sends command to X-Plane setting control surfaces on the player ac.
*
* @param values <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>
* <li>Speedbrakes [-0.5, 1.5]</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[] values) throws IOException
{
sendCTRL(values, 0);
}
/**
* Sends command to X-Plane setting control surfaces on the specified ac.
*
* @param values <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>
* <li>Speedbrakes [-0.5, 1.5]</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 ac The ac to set. 0 for the player's ac.
* @throws IOException If the command cannot be sent.
*/
public void sendCTRL(float[] values, int ac) throws IOException
{
//Preconditions
if(values == null)
{
throw new IllegalArgumentException("ctrl must no be null.");
}
if(values.length > 7)
{
throw new IllegalArgumentException("ctrl must have 7 or fewer elements.");
}
if(ac < 0 || ac > 9)
{
throw new IllegalArgumentException("ac must be non-negative and less than 9.");
}
//Pad command values and convert to bytes
int i;
int cur = 0;
ByteBuffer bb = ByteBuffer.allocate(26);
bb.order(ByteOrder.LITTLE_ENDIAN);
for(i = 0; i < 6; ++i)
{
if(i == 4)
{
if(i >= values.length)
{
bb.put(cur, (byte)-1);
}
else
{
bb.put(cur, (byte)values[i]);
}
cur += 1;
}
else if (i >= values.length)
{
bb.putFloat(cur, -998);
cur+= 4;
}
else
{
bb.putFloat(cur, values[i]);
cur += 4;
}
}
bb.put(cur++, (byte) ac);
bb.putFloat(cur, values.length == 7 ? values[6] : -998);
//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());
}
/**
* Gets position information for the specified airplane.
*
* @param ac The aircraft to get position information for.
* @return An array containing control surface data in the same format as {@code sendPOSI}.
* @throws IOException If the command cannot be sent or a response cannot be read.
*/
public double[] getPOSI(int ac) throws IOException
{
// Send request
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("GETP".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(ac);
sendUDP(os.toByteArray());
// Read response
byte[] data = readUDP();
if(data.length == 0)
{
throw new IOException("No response received.");
}
if(data.length < 34)
{
throw new IOException("Response too short");
}
// Parse response
double[] result = new double[7];
ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.LITTLE_ENDIAN);
for(int i = 0; i < 7; ++i)
{
result[i] = bb.getFloat(6 + 4 * i);
}
return result;
}
/**
* Sets the position of the player ac.
*
* @param values <p>An array containing position elements as follows:</p>
* <ol>
* <li>Latitude (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(double[] values) throws IOException
{
sendPOSI(values, 0);
}
/**
* Sets the position of the specified ac with double precision coordinates.
*
* @param values <p>An array containing position elements as follows:</p>
* <ol>
* <li>Latitude (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 ac The ac to set. 0 for the player ac.
* @throws IOException If the command can not be sent.
*/
public void sendPOSI(double[] values, int ac) throws IOException
{
//Preconditions
if(values == null)
{
throw new IllegalArgumentException("posi must no be null.");
}
if(values.length > 7)
{
throw new IllegalArgumentException("posi must have 7 or fewer elements.");
}
if(ac < 0 || ac > 255)
{
throw new IllegalArgumentException("ac must be between 0 and 255.");
}
//Pad command values and convert to bytes
int i;
ByteBuffer bb = ByteBuffer.allocate(40);
bb.order(ByteOrder.LITTLE_ENDIAN);
for(i = 0; i < values.length; ++i)
{
if(i<3) /* lat/lon/height as double */
{
bb.putDouble(values[i]);
}
else
{
bb.putFloat((float)values[i]);
}
}
for(; i < 7; ++i)
{
bb.putFloat(-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(ac);
os.write(bb.array());
sendUDP(os.toByteArray());
}
/**
* Reads X-Plane data
*
* @return The data read.
* @throws IOException If the read operation fails.
*/
public float[][] readData() throws IOException
{
byte[] buffer = readUDP();
ByteBuffer bb = ByteBuffer.wrap(buffer);
int cur = 5;
int len = bb.get(cur++);
float[][] result = new float[bb.get(len)][9];
for(int i = 0; i < len; ++i)
{
for(int j = 0; j < 9; ++j)
{
result[i][j] = bb.getFloat(cur);
cur += 4;
}
}
return result;
}
/**
* Sends data to X-Plane
*
* @param data The data to send.
* @throws IOException If the command cannot be sent.
*/
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());
}
/**
* Selects what data X-Plane will export over UDP.
*
* @param rows The row numbers to select.
* @throws IOException If the command cannot be sent.
*/
public void selectDATA(int[] rows) throws IOException
{
//Preconditions
if(rows == null || rows.length == 0)
{
throw new IllegalArgumentException("rows must be a non-null, non-empty array.");
}
//Convert data to bytes
ByteBuffer bb = ByteBuffer.allocate(4 * rows.length);
bb.order(ByteOrder.LITTLE_ENDIAN);
for(int i = 0; i < rows.length; ++i)
{
bb.putInt(i * 4, rows[i]);
}
//Build and send message
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("DSEL".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(bb.array());
sendUDP(os.toByteArray());
}
/**
* Sets a message to be displayed on the screen in X-Plane at the default screen location.
*
* @param msg The message to display. Should not contain any newline characters.
* @throws IOException If the command cannot be sent.
*/
public void sendTEXT(String msg) throws IOException
{
sendTEXT(msg, -1, -1);
}
/**
* Sets a message to be displayed on the screen in X-Plane at the specified coordinates.
*
* @param msg The message to display. Should not contain any newline characters.
* @param x The number of pixels from the right edge of the screen to display the text.
* @param y The number of pixels from the bottom edge of the screen to display the text.
* @throws IOException If the command cannot be sent.
*/
public void sendTEXT(String msg, int x, int y) throws IOException
{
//Preconditions
if(msg == null)
{
msg = "";
}
//Convert drefs to bytes.
byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);
if(msgBytes.length > 255)
{
throw new IllegalArgumentException("msg must be less than 255 bytes in UTF-8.");
}
ByteBuffer bb = ByteBuffer.allocate(8);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(0, x);
bb.putInt(4, y);
//Build and send message
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("TEXT".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(bb.array());
os.write(msgBytes.length);
os.write(msgBytes);
sendUDP(os.toByteArray());
}
/**
* Sets the camera view in X-Plane.
*
* @param view The view to use.
* @throws IOException If the command cannot be sent.
*/
public void sendVIEW(ViewType view) throws IOException
{
ByteBuffer bb = ByteBuffer.allocate(4);
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(view.getValue());
//Build and send message
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("VIEW".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(bb.array());
sendUDP(os.toByteArray());
}
/**
* Adds, removes, or clears a set of waypoints. If the command is clear, the points are ignored
* and all points are removed.
*
* @param op The operation to perform.
* @param points An array of values representing points. Each triplet in the array will be
* interpreted as a (Lat, Lon, Alt) point.
* @throws IOException If the command cannot be sent.
*/
public void sendWYPT(WaypointOp op, float[] points) throws IOException
{
//Preconditions
if(points.length % 3 != 0)
{
throw new IllegalArgumentException("points.length should be divisible by 3.");
}
if(points.length / 3 > 255)
{
throw new IllegalArgumentException("Too many points. Must be less than 256.");
}
//Convert points to bytes
ByteBuffer bb = ByteBuffer.allocate(4 * points.length);
bb.order(ByteOrder.LITTLE_ENDIAN);
for(float f : points)
{
bb.putFloat(f);
}
//Build and send message
ByteArrayOutputStream os = new ByteArrayOutputStream();
os.write("WYPT".getBytes(StandardCharsets.UTF_8));
os.write(0xFF); //Placeholder for message length
os.write(op.getValue());
os.write(points.length / 3);
os.write(bb.array());
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.
*
* @param port The new incoming port number.
* @throws IOException If the command cannot be sent.
*/
public void setCONN(int port) throws IOException
{
if(port < 0 || port >= 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) port);
os.write((byte) (port >> 8));
sendUDP(os.toByteArray());
int soTimeout = socket.getSoTimeout();
socket.close();
socket = new DatagramSocket(port);
socket.setSoTimeout(soTimeout);
readUDP(); // Try to read response
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff