pythonnnnnnnnnnnnnn

This commit is contained in:
cs-powell
2025-02-10 16:48:34 -05:00
parent e15c534c07
commit a1aaf7704d
5 changed files with 1418 additions and 2 deletions

View File

@@ -1,6 +1,5 @@
package gov.nasa.xpc.ex;
import gov.nasa.xpc.XPlaneConnect;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.Arrays; import java.util.Arrays;

View File

@@ -0,0 +1,956 @@
//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 src.discovery.ViewType;
import src.discovery.WaypointOp;
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
}
}

View File

@@ -0,0 +1,145 @@
from time import sleep
import xpc
from model import AircraftLandingModel
def ex():
print("X-Plane Connect example script")
print("Setting up simulation")
with xpc.XPlaneConnect() as client:
# Verify connection
try:
# If X-Plane does not respond to the request, a timeout error
# will be raised.
client.getDREF("sim/test/test_float")
except:
print("Error establishing connection to X-Plane.")
print("Exiting...")
return
test = AircraftLandingModel(client)
# # Set position of the player aircraft
# print("Setting position")
# # Lat Lon Alt Pitch Roll Yaw Gear
# posi = [37.524, -122.06899, 2500, 0, 0, 0, 1]
# client.sendPOSI(posi)
# # Set position of a non-player aircraft
# print("Setting NPC position")
# # Lat Lon Alt Pitch Roll Yaw Gear
# posi = [37.52465, -122.06899, 2500, 0, 20, 0, 1]
# client.sendPOSI(posi, 1)
# # Set angle of attack, velocity, and orientation using the DATA command
# print("Setting orientation")
# data = [\
# [18, 0, -998, 0, -998, -998, -998, -998, -998],\
# [ 3, 130, 130, 130, 130, -998, -998, -998, -998],\
# [16, 0, 0, 0, -998, -998, -998, -998, -998]\
# ]
# client.sendDATA(data)
# Set control surfaces and throttle of the player aircraft using sendCTRL
print("Setting controls")
ctrl = [0.0, 0.0, 0.0, 0.0]
client.sendCTRL(ctrl)
# Pitch, Roll, Rudder, Throttle
# Pause the sim
client.pauseSim(False)
count = 0
innercount = 0
#ONE SECOND INCREMENTS
# while(count < 1000000 ):
# client.pauseSim(False)
# sleep(1.0)
# print("Pausing" + str(count))
# client.pauseSim(True)
# sleep(1.0)
# count+=1
#0.1 SECOND INCREMENTS
# while(count < 1000000 ):
# client.pauseSim(False)
# sleep(0.1)
# print("Pausing" + str(count))
# client.pauseSim(True)
# sleep(0.1)
# count+=1
#0.001 SECOND INCREMENTS
# while(count < 1000000 ):
# client.pauseSim(False)
# sleep(0.001)
# print("Pausing" + str(count))
# client.pauseSim(True)
# sleep(0.001)
# count+=1
#0.00001 SECOND INCREMENTS
# while(count < 1000000 ):
# client.pauseSim(False)
# sleep(0.00001)
# print("Pausing" + str(count))
# client.pauseSim(True)
# sleep(0.00001)
# count+=1
# sim/operation/override/override_timestep
#Doing stuff In between Test SECOND INCREMENTS
while(count < 1000000 ):
#50 Millisecond Timesteps
sleep(0.05)
client.pauseSim(False) #Unpause
sleep(0.05) # Run 50 Milliseconds
client.pauseSim(True) # Pause Simulator
#Run Model (Send commands to simulator within this process)
####Insert Model Here, some assembly required#######
test.update_aircraft_state()
test.update_controls_simultaneously()
#Please work
#Repeat
print("Advanced 50 Milliseconds: Step #" + str(count))
count+=1
# print("Pausing" + str(count))
# innercount = 0
# client.pauseSim(True)
# sleep(0.05) #Simulate 50 milliseconds
# sleep(0.01) #Simulate 50 milliseconds
# # while(innercount < 100000):
# # # print("Doing Stuff" + str(innercount)) # Simulates the model running and computing for 50 Milliseconds maybe?
# # innercount+=1
# # else:
# client.pauseSim(False)
# print("Exit Pause" + str(count))
# sleep(0.5)
# count+=1
# Toggle pause state to resume
print("Resuming")
client.pauseSim(False)
# Let the sim run for a bit.
sleep(4)
print("End of Python client example")
input("Press any key to exit...")
if __name__ == "__main__":
ex()

142
Python3/src/model.py Normal file
View File

@@ -0,0 +1,142 @@
import pyactr
# from XPlaneConnect import *
import xpc
# Initialize XPlaneConnect client
class AircraftLandingModel(pyactr.ACTRModel):
def __init__(self,client):
super().__init__()
# Initialize the declarative memory (DM)
# self.decmem.add(
# [
# ("airspeed", 100), # Current airspeed (e.g., 100 knots)
# ("roll", 0), # Current roll (0 for wings level)
# ("heading", 0), # Current heading
# ("descent_rate", 500), # Current descent rate in fpm
# ("target_airspeed", 80), # Target airspeed during descent
# ("target_roll", 0), # Target roll (wings level)
# ("target_heading", 90), # Target heading (runway heading)
# ("target_descent_rate", 500) # Target descent rate (fpm)
# ]
# )
self.client = client
self.airspeed = 100
self.roll = 0
self.heading = 0
self.descent_rate = 500
self.target_airspeed = 80
self.target_roll = 0
self.target_heading = 0
self.target_descent_rate = 500
# Declare the state for previous values
self.previous_airspeed = None
self.previous_roll = None
self.previous_heading = None
self.previous_descent_rate = None
# Initialize the integral errors for each parameter
self.integral_airspeed = 0
self.integral_roll = 0
self.integral_heading = 0
self.integral_descent_rate = 0
# Integral gains (tune these values for performance)
self.Kp = 0.1 # Proportional gain
self.Ki = 0.01 # Integral gain
def proportionalIntegralControl(self, current, target, integral_error):
"""
Proportional-Integral control rule implementation for multiple parameters.
"""
# Calculate the error (current - target)
error = target - current
# Update the integral of the error
integral_error += error
# Calculate the control value using the PI formula
control_value = (self.Kp * error) + (self.Ki * integral_error)
return control_value, integral_error # Return control value and updated integral error
def update_controls_simultaneously(self):
"""
Update all controls at the same time by calculating control values for each parameter.
"""
# Compute control values for all parameters (yoke pull, yoke steer, rudder, throttle)
yoke_pull, self.integral_airspeed = self.proportionalIntegralControl(self.airspeed, self.target_airspeed, self.integral_airspeed)
yoke_steer, self.integral_roll = self.proportionalIntegralControl(self.roll, self.target_roll, self.integral_roll)
rudder, self.integral_heading = self.proportionalIntegralControl(self.heading, self.target_heading, self.integral_heading)
throttle, self.integral_descent_rate = self.proportionalIntegralControl(self.descent_rate, self.target_descent_rate, self.integral_descent_rate)
# Send all controls simultaneously to X-Plane
self.send_controls_to_xplane(yoke_pull, yoke_steer, rudder, throttle)
def send_controls_to_xplane(self, yoke_pull, yoke_steer, rudder, throttle):
"""
Sends all control inputs to X-Plane using XPlaneConnect
"""
# Send yoke pull, yoke steer, rudder, and throttle simultaneously
self.client.sendControls([yoke_pull, yoke_steer, 0, rudder, throttle]) # Control inputs: [yoke_pull, yoke_steer, roll/pitch, rudder, throttle]
# Update the model's DM based on X-Plane data
def update_aircraft_state(self):
# Retrieve current data from X-Plane
airspeed = self.client.getDREF("sim/cockpit2/gauges/indicators/airspeed_kts_pilot")
roll = self.client.getDREF("sim/cockpit2/gauges/indicators/roll_AHARS_deg_pilot")
heading = self.client.getDREF("sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot")
descent_rate = self.client.getDREF("sim/flightmodel/position/vh_ind_fpm")
# Update the model's declarative memory
# model.declarative_memory["airspeed"] = airspeed
# model.declarative_memory["roll"] = roll
# model.declarative_memory["heading"] = heading
# model.declarative_memory["descent_rate"] = descent_rate
self.airspeed = airspeed
self.roll = roll
self.heading = heading
self.descent_rate = descent_rate
# def rules(self):
# """
# Define the rules for descent control using proportional-integral control for all controls at once.
# """
# return [
# # Rule to adjust all controls simultaneously based on PI control for each parameter
# pyactr.Production(
# condition=pyactr.Condition("airspeed", "airspeed") &
# pyactr.Condition("roll", "roll") &
# pyactr.Condition("heading", "heading") &
# pyactr.Condition("descent_rate", "descent_rate") &
# pyactr.Condition("target_airspeed", "target_airspeed") &
# pyactr.Condition("target_roll", "target_roll") &
# pyactr.Condition("target_heading", "target_heading") &
# pyactr.Condition("target_descent_rate", "target_descent_rate"),
# action=self.update_controls_simultaneously(),
# ),
# ]
# # Function to get the current dataref value for a given parameter
# def getDref(parameter_name):
# # Depending on the parameter name, you would query X-Plane datarefs
# if parameter_name == "Airspeed":
# # Get airspeed dataref
# return client.getData([DATAREF_AIRSPEED])
# elif parameter_name == "Roll":
# # Get roll angle dataref
# return client.getData([DATAREF_ROLL])
# elif parameter_name == "Hdg":
# # Get heading dataref
# return client.getData([DATAREF_HEADING])
# elif parameter_name == "DescentRate":
# # Get descent rate dataref
# return client.getData([DATAREF_DESCENT_RATE])

174
Python3/src/pyactrDemo.py Normal file
View File

@@ -0,0 +1,174 @@
from time import sleep
import xpc
import pyactr as actr #Importing ACT-R python edition
def ex():
## ACT-R Setup
playing_memory = actr.ACTRModel()
actr.chunktype("pitch", "up, throttle")
initial_chunk = actr.makechunk(typename="pitch", up="20")
## Define new chunk type(s)
#
# Control Updates
# Rudder = #### (0.5)
# Elevator = #### (0)
# Aileron = ####
# Throttle = #####
# Rules to define How much to change based on current parameters
# Within a given rule, multiple values can be updated (which values depend on
# acceptable ranges/current envrionment state/current goal state(pre-touchdown (cares about altitude) vs.
# after touchdown braking (doesn't care about altitude)))
actr.chunktype("Control Update", "rudder, elevator, aileron, throttle")
##Use goal states to frame acceptable ranges for parameters (descent vs touchdown vs level flight etc etc.)
## Divide landing task into subproblems
## Level Flight
# Initial Descent
# .......
# and/or tracking landing signal (ILS, RNAV, etc.) [Quantitative measure]
## Environment Conditions: Calm...very calm
##First Focus: Begin Simulation with the tedious button pressing all complete -- [go the route of Dead Reckoning]
# (i.e. focus on command and control not beuracracy)
# Define acceptable range of parameters for each stage
# Stabilized descent (Maintaining a given descent rate in FPM -- Maintaining Speed, loosing altitude)
# --> Rudder
# --> Aileron
# --> Elevator
# --> Throttle
# -->> Altitude (Throtte)
# -->> Airspeed (Pitch = Elevator)
# -->> Pitch (Elevator)
# -->>>>>>>>>> Transition Point
# Initial Parameter Above a certain altitude (50ish feet)
# Search for established equations & control schema/relationships for the controls and/or environmental stimuli
# Round out/ Flare & Crosswind Corections(i.e. don't land sideways) [Arresting the descent, maintaing wings level,
# Letting the speed bleed off while maintaing altitude]
# -->
# Touchdown
# -->
# Rollout & Braking & Centerline tracking
# -->
goal = playing_memory.set_goal("goal")
goal.add(initial_chunk)
print(goal)
playing_memory.productionstring(name="startpitch", string="""
=goal>
isa pitch
up 20
throttle None
?manual>
state free
==>
=goal>
isa pitch
throttle 100""") #this rule will be modified later
playing_memory.productionstring(name="modifyPitch", string="""
=goal>
isa pitch
up 20
throttle 100
==>
+manual>
isa _manual
#
cmd press_key
key 1
#
=goal>
isa pitch
throttle None""") #this rule works correctly
simulation_game = playing_memory.simulation()
simulation_game.run()
# print(goal)
# final_chunk = goal.pop()
# print(final_chunk)
# print("X-Plane Connect example script")
# print("Setting up simulation")
# with xpc.XPlaneConnect() as client:
# # Verify connection
# try:
# # If X-Plane does not respond to the request, a timeout error
# # will be raised.
# client.getDREF("sim/test/test_float")
# except:
# print("Error establishing connection to X-Plane.")
# print("Exiting...")
# return
# # Set position of the player aircraft
# print("Setting position")
# # Lat Lon Alt Pitch Roll Yaw Gear
# posi = [37.524, -122.06899, 2500, 0, 0, 0, 1]
# client.sendPOSI(posi)
# # Set position of a non-player aircraft
# print("Setting NPC position")
# # Lat Lon Alt Pitch Roll Yaw Gear
# posi = [37.52465, -122.06899, 2500, 0, 20, 0, 1]
# client.sendPOSI(posi, 1)
# # Set angle of attack, velocity, and orientation using the DATA command
# print("Setting orientation")
# data = [\
# [18, 0, -998, 0, -998, -998, -998, -998, -998],\
# [ 3, 130, 130, 130, 130, -998, -998, -998, -998],\
# [16, 0, 0, 0, -998, -998, -998, -998, -998]\
# ]
# client.sendDATA(data)
# # Set control surfaces and throttle of the player aircraft using sendCTRL
# print("Setting controls")
# ctrl = [0.0, 0.0, 0.0, 0.8]
# client.sendCTRL(ctrl)
# # Pause the sim
# print("Pausing")
# client.pauseSim(True)
# sleep(2)
# # Toggle pause state to resume
# print("Resuming")
# client.pauseSim(False)
# # Stow landing gear using a dataref
# print("Stowing gear")
# gear_dref = "sim/cockpit/switches/gear_handle_status"
# client.sendDREF(gear_dref, 0)
# # Let the sim run for a bit.
# sleep(4)
# # Make sure gear was stowed successfully
# gear_status = client.getDREF(gear_dref)
# if gear_status[0] == 0:
# print("Gear stowed")
# else:
# print("Error stowing gear")
print("End of Python client example")
input("Press any key to exit...")
if __name__ == "__main__":
ex()