From 388329b21804fabe12ea577ff1642583776eca59 Mon Sep 17 00:00:00 2001 From: Sander Datema Date: Sat, 27 Jun 2020 21:14:42 +0200 Subject: [PATCH] Add support for sendCOMM method Credits go to user @angelsware, I just did a copy/paste from #120 --- C/src/xplaneConnect.c | 37 +++++ C/src/xplaneConnect.h | 7 + Java/src/XPlaneConnect.java | 36 +++++ Python/src/xpc.py | 129 +++++++++++------- xpcPlugin/DataManager.cpp | 16 +++ xpcPlugin/DataManager.h | 5 + xpcPlugin/Message.cpp | 5 + xpcPlugin/MessageHandlers.cpp | 26 ++++ xpcPlugin/MessageHandlers.h | 1 + xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj | 2 + 10 files changed, 211 insertions(+), 53 deletions(-) diff --git a/C/src/xplaneConnect.c b/C/src/xplaneConnect.c index 208ed7f..f1da967 100755 --- a/C/src/xplaneConnect.c +++ b/C/src/xplaneConnect.c @@ -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 ****/ +/*****************************************************************************/ \ No newline at end of file diff --git a/C/src/xplaneConnect.h b/C/src/xplaneConnect.h index 842a215..3dcf236 100644 --- a/C/src/xplaneConnect.h +++ b/C/src/xplaneConnect.h @@ -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 diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java index 675da9b..df32cff 100644 --- a/Java/src/XPlaneConnect.java +++ b/Java/src/XPlaneConnect.java @@ -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. * diff --git a/Python/src/xpc.py b/Python/src/xpc.py index 71b7eb2..7a6f926 100644 --- a/Python/src/xpc.py +++ b/Python/src/xpc.py @@ -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 = " #include @@ -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; diff --git a/xpcPlugin/DataManager.h b/xpcPlugin/DataManager.h index 15f1218..27e3a40 100644 --- a/xpcPlugin/DataManager.h +++ b/xpcPlugin/DataManager.h @@ -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(); diff --git a/xpcPlugin/Message.cpp b/xpcPlugin/Message.cpp index 3dcaf3b..318cf5e 100644 --- a/xpcPlugin/Message.cpp +++ b/xpcPlugin/Message.cpp @@ -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 "; diff --git a/xpcPlugin/MessageHandlers.cpp b/xpcPlugin/MessageHandlers.cpp index 9e5c799..5b1d79d 100644 --- a/xpcPlugin/MessageHandlers.cpp +++ b/xpcPlugin/MessageHandlers.cpp @@ -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 diff --git a/xpcPlugin/MessageHandlers.h b/xpcPlugin/MessageHandlers.h index ee39492..15379c4 100644 --- a/xpcPlugin/MessageHandlers.h +++ b/xpcPlugin/MessageHandlers.h @@ -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); diff --git a/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj b/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj index 9112e86..6af7a97 100755 --- a/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj +++ b/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj @@ -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 = "";