Add support for sendCOMM method

Credits go to user @angelsware, I just did a copy/paste from #120
This commit is contained in:
Sander Datema
2020-06-27 21:14:42 +02:00
parent 76f7d28ac8
commit 388329b218
10 changed files with 211 additions and 53 deletions

View File

@@ -973,3 +973,40 @@ int sendVIEW(XPCSocket sock, VIEW_TYPE view)
/*****************************************************************************/
/**** End View functions ****/
/*****************************************************************************/
/*****************************************************************************/
/**** Comm functions ****/
/*****************************************************************************/
int sendCOMM(XPCSocket sock, const char* comm) {
// Setup command
// Max size is technically unlimited.
unsigned char buffer[65536] = "COMM";
int pos = 5;
int commLen = strnlen(comm, 256);
if (pos + commLen + 2 > 65536)
{
printError("sendCOMM", "About to overrun the send buffer!");
return -4;
}
if (commLen > 255)
{
printError("sendCOMM", "comm is too long. Must be less than 256 characters.");
return -1;
}
// Copy comm to buffer
buffer[pos++] = (unsigned char)commLen;
memcpy(buffer + pos, comm, commLen);
pos += commLen;
// Send command
if (sendUDP(sock, buffer, pos) < 0)
{
printError("setDREF", "Failed to send command");
return -3;
}
return 0;
}
/*****************************************************************************/
/**** End Comm functions ****/
/*****************************************************************************/

View File

@@ -303,6 +303,13 @@ int sendVIEW(XPCSocket sock, VIEW_TYPE view);
/// \returns 0 if successful, otherwise a negative value.
int sendWYPT(XPCSocket sock, WYPT_OP op, float points[], int count);
/// Sends commands.
///
/// \param sock The socket to use to send the command.
/// \param comm The command string.
/// \returns 0 if successful, otherwise a negative value.
int sendCOMM(XPCSocket sock, const char* comm);
#ifdef __cplusplus
}
#endif

View File

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

View File

@@ -1,20 +1,22 @@
import socket
import struct
class XPlaneConnect(object):
'''XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin.'''
"""XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin."""
socket = None
# Basic Functions
def __init__(self, xpHost = 'localhost', xpPort = 49009, port = 0, timeout = 100):
'''Sets up a new connection to an X-Plane Connect plugin running in X-Plane.
def __init__(self, xpHost="localhost", xpPort=49009, port=0, timeout=100):
"""Sets up a new connection to an X-Plane Connect plugin running in X-Plane.
Args:
xpHost: The hostname of the machine running X-Plane.
xpPort: The port on which the XPC plugin is listening. Usually 49007.
port: The port which will be used to send and receive data.
timeout: The period (in milliseconds) after which read attempts will fail.
'''
"""
# Validate parameters
xpIP = None
@@ -51,59 +53,59 @@ class XPlaneConnect(object):
self.close()
def close(self):
'''Closes the specified connection and releases resources associated with it.'''
"""Closes the specified connection and releases resources associated with it."""
if self.socket is not None:
self.socket.close()
self.socket = None
def sendUDP(self, buffer):
'''Sends a message over the underlying UDP socket.'''
"""Sends a message over the underlying UDP socket."""
# Preconditions
if(len(buffer) == 0):
if len(buffer) == 0:
raise ValueError("sendUDP: buffer is empty.")
self.socket.sendto(buffer, 0, self.xpDst)
def readUDP(self):
'''Reads a message from the underlying UDP socket.'''
"""Reads a message from the underlying UDP socket."""
return self.socket.recv(16384)
# Configuration
def setCONN(self, port):
'''Sets the port on which the client sends and receives data.
"""Sets the port on which the client sends and receives data.
Args:
port: The new port to use.
'''
#Validate parameters
"""
# Validate parameters
if port < 0 or port > 65535:
raise ValueError("The specified port is not a valid port number.")
#Send command
# Send command
buffer = struct.pack("<4sxH", "CONN", port)
self.sendUDP(buffer)
#Rebind socket
# Rebind socket
clientAddr = ("0.0.0.0", port)
timeout = self.socket.gettimeout();
self.socket.close();
timeout = self.socket.gettimeout()
self.socket.close()
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
self.socket.bind(clientAddr)
self.socket.settimeout(timeout)
#Read response
# Read response
buffer = self.socket.recv(1024)
def pauseSim(self, pause):
'''Pauses or un-pauses the physics simulation engine in X-Plane.
"""Pauses or un-pauses the physics simulation engine in X-Plane.
Args:
pause: True to pause the simulation for all a/c; False to resume for all a/c.
2 to switch the status for all a/c. 100:119 to pause a/c 0:19. 200:219 to
resume a/c 0:19
'''
"""
pause = int(pause)
if pause < 0 or (pause > 2 and pause < 100) or (pause > 119 and pause < 200) or pause > 219 :
if pause < 0 or (pause > 2 and pause < 100) or (pause > 119 and pause < 200) or pause > 219:
raise ValueError("Invalid argument for pause command.")
buffer = struct.pack("<4sxB", "SIMU", pause)
@@ -111,30 +113,30 @@ class XPlaneConnect(object):
# X-Plane UDP Data
def readDATA(self):
'''Reads X-Plane data.
"""Reads X-Plane data.
Returns: A 2 dimensional array containing 0 or more rows of data. Each array
in the result will have 9 elements, the first of which is the row number which
that array represents data for, and the rest of which are the data elements in
that row.
'''
buffer = self.readUDP();
"""
buffer = self.readUDP()
if len(buffer) < 6:
return None
rows = (len(buffer) - 5) / 36
data = []
for i in range(rows):
data.append(struct.unpack_from("9f", buffer, 5 + 36*i))
data.append(struct.unpack_from("9f", buffer, 5 + 36 * i))
return data
def sendDATA(self, data):
'''Sends X-Plane data over the underlying UDP socket.
"""Sends X-Plane data over the underlying UDP socket.
Args:
data: An array of values representing data rows to be set. Each array in `data`
should have 9 elements, the first of which is a row number in the range (0-134),
and the rest of which are the values to set for that data row.
'''
"""
if len(data) > 134:
raise ValueError("Too many rows in data.")
@@ -146,12 +148,12 @@ class XPlaneConnect(object):
self.sendUDP(buffer)
# Position
def getPOSI(self, ac = 0):
'''Gets position information for the specified aircraft.
def getPOSI(self, ac=0):
"""Gets position information for the specified aircraft.
Args:
ac: The aircraft to set the control surfaces of. 0 is the main/player aircraft.
'''
"""
# Send request
buffer = struct.pack("<4sxB", "GETP", ac)
self.sendUDP(buffer)
@@ -168,8 +170,8 @@ class XPlaneConnect(object):
# Drop the header & ac from the return value
return result[2:]
def sendPOSI(self, values, ac = 0):
'''Sets position information on the specified aircraft.
def sendPOSI(self, values, ac=0):
"""Sets position information on the specified aircraft.
Args:
values: The position values to set. `values` is a array containing up to
@@ -184,7 +186,7 @@ class XPlaneConnect(object):
* True Heading (deg)
* Gear (0=up, 1=down)
ac: The aircraft to set the control surfaces of. 0 is the main/player aircraft.
'''
"""
# Preconditions
if len(values) < 1 or len(values) > 7:
raise ValueError("Must have between 0 and 7 items in values.")
@@ -204,12 +206,12 @@ class XPlaneConnect(object):
self.sendUDP(buffer)
# Controls
def getCTRL(self, ac = 0):
'''Gets the control surface information for the specified aircraft.
def getCTRL(self, ac=0):
"""Gets the control surface information for the specified aircraft.
Args:
ac: The aircraft to set the control surfaces of. 0 is the main/player aircraft.
'''
"""
# Send request
buffer = struct.pack("<4sxB", "GETC", ac)
self.sendUDP(buffer)
@@ -224,11 +226,11 @@ class XPlaneConnect(object):
raise ValueError("Unexpected header: " + result[0])
# Drop the header from the return value
result =result[1:7] + result[8:]
result = result[1:7] + result[8:]
return result
def sendCTRL(self, values, ac = 0):
'''Sets control surface information on the specified aircraft.
def sendCTRL(self, values, ac=0):
"""Sets control surface information on the specified aircraft.
Args:
values: The control surface values to set. `values` is a array containing up to
@@ -243,7 +245,7 @@ class XPlaneConnect(object):
* Flaps [0, 1]
* Speedbrakes [-0.5, 1.5]
ac: The aircraft to set the control surfaces of. 0 is the main/player aircraft.
'''
"""
# Preconditions
if len(values) < 1 or len(values) > 7:
raise ValueError("Must have between 0 and 6 items in values.")
@@ -270,21 +272,21 @@ class XPlaneConnect(object):
# DREF Manipulation
def sendDREF(self, dref, values):
'''Sets the specified dataref to the specified value.
"""Sets the specified dataref to the specified value.
Args:
dref: The name of the datarefs to set.
values: Either a scalar value or a sequence of values.
'''
"""
self.sendDREFs([dref], [values])
def sendDREFs(self, drefs, values):
'''Sets the specified datarefs to the specified values.
"""Sets the specified datarefs to the specified values.
Args:
drefs: A list of names of the datarefs to set.
values: A list of scalar or vector values to set.
'''
"""
if len(drefs) != len(values):
raise ValueError("drefs and values must have the same number of elements.")
@@ -312,24 +314,24 @@ class XPlaneConnect(object):
self.sendUDP(buffer)
def getDREF(self, dref):
'''Gets the value of an X-Plane dataref.
"""Gets the value of an X-Plane dataref.
Args:
dref: The name of the dataref to get.
Returns: A sequence of data representing the values of the requested dataref.
'''
"""
return self.getDREFs([dref])[0]
def getDREFs(self, drefs):
'''Gets the value of one or more X-Plane datarefs.
"""Gets the value of one or more X-Plane datarefs.
Args:
drefs: The names of the datarefs to get.
Returns: A multidimensional sequence of data representing the values of the requested
datarefs.
'''
"""
# Send request
buffer = struct.pack("<4sxB", "GETD", len(drefs))
for dref in drefs:
@@ -352,8 +354,8 @@ class XPlaneConnect(object):
return result
# Drawing
def sendTEXT(self, msg, x = -1, y = -1):
'''Sets a message that X-Plane will display on the screen.
def sendTEXT(self, msg, x=-1, y=-1):
"""Sets a message that X-Plane will display on the screen.
Args:
msg: The string to display on the screen
@@ -363,7 +365,7 @@ class XPlaneConnect(object):
y: The distance in pixels from the bottom edge of the screen to display the
message. A value of -1 indicates that the default vertical position should be
used.
'''
"""
if y < -1:
raise ValueError("y must be greater than or equal to -1.")
@@ -375,12 +377,12 @@ class XPlaneConnect(object):
self.sendUDP(buffer)
def sendVIEW(self, view):
'''Sets the camera view in X-Plane
"""Sets the camera view in X-Plane
Args:
view: The view to use. The ViewType class provides named constants
for known views.
'''
"""
# Preconditions
if view < ViewType.Forwards or view > ViewType.FullscreenNoHud:
raise ValueError("Unknown view command.")
@@ -392,7 +394,7 @@ class XPlaneConnect(object):
self.sendUDP(buffer)
def sendWYPT(self, op, points):
'''Adds, removes, or clears waypoints. Waypoints are three dimensional points on or
"""Adds, removes, or clears waypoints. Waypoints are three dimensional points on or
above the Earth's surface that are represented visually in the simulator. Each
point consists of a latitude and longitude expressed in fractional degrees and
an altitude expressed as meters above sea level.
@@ -402,7 +404,7 @@ class XPlaneConnect(object):
`2` to remove waypoints, and `3` to clear all waypoints.
points: A sequence of floating point values representing latitude, longitude, and
altitude triples. The length of this array should always be divisible by 3.
'''
"""
if op < 1 or op > 3:
raise ValueError("Invalid operation specified.")
if len(points) % 3 != 0:
@@ -416,6 +418,27 @@ class XPlaneConnect(object):
buffer = struct.pack("<4sxBB" + str(len(points)) + "f", "WYPT", op, len(points), *points)
self.sendUDP(buffer)
def sendCOMM(self, comm):
'''Sets the specified datarefs to the specified values.
Args:
drefs: A list of names of the datarefs to set.
values: A list of scalar or vector values to set.
'''
if comm == None:
raise ValueError("comm must be non-empty.")
buffer = struct.pack("<4sx", "COMM")
if len(comm) == 0 or len(comm) > 255:
raise ValueError("comm must be a non-empty string less than 256 characters.")
# Pack message
fmt = "<B{0:d}s".format(len(comm))
buffer += struct.pack(fmt, len(comm), comm)
# Send
self.sendUDP(buffer)
class ViewType(object):
Forwards = 73
Down = 74

View File

@@ -19,6 +19,7 @@
#include "XPLMDataAccess.h"
#include "XPLMGraphics.h"
#include "XPLMUtilities.h"
#include <algorithm>
#include <cmath>
@@ -791,6 +792,21 @@ namespace XPC
Set(DREF_FlapActual, value);
}
void DataManager::Execute(const std::string& comm)
{
Log::FormatLine(LOG_INFO, "DMAN", "Executing command (value:%s)", comm.c_str());
XPLMCommandRef xcref = XPLMFindCommand(comm.c_str());
if (!xcref)
{
// COMM does not exist
Log::FormatLine(LOG_ERROR, "DMAN", "ERROR: invalid COMM %s", comm.c_str());
return;
}
XPLMCommandOnce(xcref);
}
float DataManager::GetDefaultValue()
{
return -998.0F;

View File

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

View File

@@ -176,6 +176,11 @@ namespace XPC
ss << "Type:" << *((unsigned long*)(buffer + 5));
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
}
else if (head == "COMM")
{
ss << "Type:" << *((unsigned long*)(buffer + 5));
Log::WriteLine(LOG_DEBUG, "DBUG", ss.str());
}
else
{
ss << " UNKNOWN HEADER ";

View File

@@ -70,6 +70,7 @@ namespace XPC
handlers.insert(std::make_pair("VIEW", MessageHandlers::HandleView));
handlers.insert(std::make_pair("GETC", MessageHandlers::HandleGetC));
handlers.insert(std::make_pair("GETP", MessageHandlers::HandleGetP));
handlers.insert(std::make_pair("COMM", MessageHandlers::HandleComm));
handlers.insert(std::make_pair("GETT", MessageHandlers::HandleGetT));
// X-Plane data messages
handlers.insert(std::make_pair("DSEL", MessageHandlers::HandleXPlaneData));
@@ -916,6 +917,31 @@ namespace XPC
}
}
void MessageHandlers::HandleComm(const Message& msg)
{
Log::FormatLine(LOG_TRACE, "COMM", "Request to execute COMM command received (Conn %i)", connection.id);
const unsigned char* buffer = msg.GetBuffer();
std::size_t size = msg.GetSize();
std::size_t pos = 5;
while (pos < size)
{
unsigned char len = buffer[pos++];
if (pos + len > size)
{
break;
}
std::string comm = std::string((char*)buffer + pos, len);
pos += len;
DataManager::Execute(comm);
Log::FormatLine(LOG_DEBUG, "COMM", "Execute command %s", comm.c_str());
}
if (pos != size)
{
Log::WriteLine(LOG_ERROR, "COMM", "ERROR: Command did not terminate at the expected position.");
}
}
void MessageHandlers::HandleWypt(const Message& msg)
{
// Update Log

View File

@@ -77,6 +77,7 @@ namespace XPC
static void HandleText(const Message& msg);
static void HandleWypt(const Message& msg);
static void HandleView(const Message& msg);
static void HandleComm(const Message& msg);
static void HandleXPlaneData(const Message& msg);
static void HandleUnknown(const Message& msg);

View File

@@ -221,6 +221,7 @@
"APL=1",
"IBM=0",
"LIN=0",
XPLM200,
);
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_VERSION = "";
@@ -274,6 +275,7 @@
"APL=1",
"IBM=0",
"LIN=0",
XPLM200,
);
GCC_SYMBOLS_PRIVATE_EXTERN = YES;
GCC_VERSION = "";