diff --git a/C/User Guide (C).txt b/C/User Guide (C).txt index 60e4bf0..da34ea9 100644 --- a/C/User Guide (C).txt +++ b/C/User Guide (C).txt @@ -155,6 +155,13 @@ BASIC FUNCTIONS char DREFArray[][100] = {"sim/cockpit/switches/gear_handle_status"}; requestDREF(theSocket, IP, 49009, DREFArray, strlen(DREFArray[0]),1); + 10. sendTEXT write some text to the screen. + INPUT: + outSocket (xpcSocket): Socket to use to send the command + message (char*): The string to be wrote to the screen + x (int): The x position where the text will be written. Set to -1 to use default position. + y (int): The y position where the text will be written. Set to -1 to use default position. + ----------------------------------- ADVANCED FUNCTIONS (These are mostly used by the xpcPlugin to read requests) 1. sendUDP @@ -179,9 +186,6 @@ PLANNED FUNCTIONS 5. parseWYPT 6. readWYPT 7. selectDATA - 8. sendTEXT - 9. readTEXT - 10. parseTEXT ----------------------------------- CONTACT diff --git a/C/src/xplaneConnect.c b/C/src/xplaneConnect.c index 9cc8575..313d9ef 100755 --- a/C/src/xplaneConnect.c +++ b/C/src/xplaneConnect.c @@ -419,6 +419,21 @@ short sendpCTRL(struct xpcSocket recfd, short numArgs, float valueArray[], char return 0; } +short sendTEXT(struct xpcSocket sendfd, char* msg, int x, int y) +{ + char buf[269] = { 0 }; + size_t msgLen = strnlen(msg, 255); + size_t len = 14 + msgLen; + strcpy(buf, "TEXT"); + memcpy(buf + 5, &x, sizeof(int)); + memcpy(buf + 9, &y, sizeof(int)); + buf[13] = msgLen; + strncpy(buf + 14, msg, msgLen); + + sendUDP(sendfd, buf, len); + return 0; +} + //READ //---------------------------------------- short readUDP(struct xpcSocket recfd, char *dataRef, struct sockaddr *recvaddr) diff --git a/C/src/xplaneConnect.h b/C/src/xplaneConnect.h index dff7b5d..38f18bf 100644 --- a/C/src/xplaneConnect.h +++ b/C/src/xplaneConnect.h @@ -81,7 +81,11 @@ short requestDREF(struct xpcSocket sendfd, struct xpcSocket recfd, char DREFArray[][100], short DREFSizes[], short listLength, float *resultArray[], short arraySizes[]); int parseGETD(const char my_message[], char *DREFArray[], int DREFSizes[]); short parseRequest(const char my_message[], float *resultArray[], short arraySizes[]); - short readRequest(struct xpcSocket recfd, float *dataRef[], short arraySizes[], struct sockaddr *recvaddr); + short readRequest(struct xpcSocket recfd, float *dataRef[], short arraySizes[], struct sockaddr *recvaddr); + + // Screen Text + short sendTEXT(struct xpcSocket sendfd, char* msg, int x, int y); + #endif //ifdef _h diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java index cdc4a37..4a34981 100644 --- a/Java/src/XPlaneConnect.java +++ b/Java/src/XPlaneConnect.java @@ -457,7 +457,7 @@ public class XPlaneConnect implements AutoCloseable cur += 4; } } - bb.put(cur, (byte)aircraft); + bb.put(cur, (byte) aircraft); //Build and send message ByteArrayOutputStream os = new ByteArrayOutputStream(); @@ -586,6 +586,55 @@ public class XPlaneConnect implements AutoCloseable 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 port on which the client will receive data from X-Plane. * diff --git a/MATLAB/+XPlaneConnect/sendTEXT.m b/MATLAB/+XPlaneConnect/sendTEXT.m new file mode 100644 index 0000000..499c12b --- /dev/null +++ b/MATLAB/+XPlaneConnect/sendTEXT.m @@ -0,0 +1,47 @@ +function [ status ] = sendTEXT( msg, varargin ) +% sendTEXT Sends a string to be displayed in X-Plane. +% +% Inputs +% msg: The string to be displayed +% x (optional): The distance from the left edge of the screen to display the message. +% y (optional): The distance from the bottom edge of the screen to display the message. +% IP Address (optional): IP Address of the machine that will receive the data as a character array. Default is '127.0.0.1' (local machine) +% port (optional): Port on the receiving machine where the data will be sent. Default is 49009 (XPlaneConnect). In general use 49009 to send to the plugin and 49005 to send to the x-plane udp +% +% Outputs +% status: 0 if successful, otherwise a negative value. +% +% Use +% 1. import XPlaneConnect.*; +% 2. #Set a message to be displayed near the top middle of the screen. +% 3. status = sendTEXT('Some text', 512, 600); +% +% Contributors +% Jason Watkins +% jason.w.watkins@nasa.gov +% +% To Do +% +% BEGIN CODE + +import XPlaneConnect.* +%% Handle Input +p = inputParser; +addRequired(p,'msg'); +addOptional(p,'x',-1,@isnumeric); +addOptional(p,'y',-1,@isnumeric); +addOptional(p,'IP','127.0.0.1',@ischar); +addOptional(p,'port',49009,@isnumeric); +parse(p,msg,varargin{:}); + +%% Body + header = ['TEXT'-0,0]; + dataStream = [header,... + typecast(uint32(p.Results.x), 'uint8'),... + typecast(uint32(p.Results.y), 'uint8'),... + uint8(length(msg)), msg-0]; + + % Send TEXT + status = sendUDP(dataStream, p.Results.IP, p.Results.port); +end + diff --git a/TestScripts/C Tests/main.c b/TestScripts/C Tests/main.c index 148637f..430db3d 100644 --- a/TestScripts/C Tests/main.c +++ b/TestScripts/C Tests/main.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include "xplaneconnect.h" @@ -84,6 +85,25 @@ short sendReadTest() // send/read Test return 0; } +short sendTEXTTest() +{ + printf("sendTEXT - "); + + // Setup + struct xpcSocket sendPort = openUDP(49064, "127.0.0.1", 49009); + int x = 100; + int y = 700; + char* msg = "XPlaneConnect test message. Now with 100% fewer new lines!"; + + // Test + sendTEXT(sendPort, msg, x, y); + // NOTE: Manually verify that msg appears on the screen in X-Plane! + + // Cleanup + closeUDP(sendPort); + return 0; +} + short requestDREFTest() // Request DREF Test (Required for next tests) { printf("requestDREF - "); @@ -542,15 +562,16 @@ int main(int argc, const char * argv[]) runTest(openTest); runTest(closeTest); - runTest(sendReadTest); + runTest(sendReadTest); + runTest(sendTEXTTest); runTest(requestDREFTest); runTest(sendDREFTest); runTest(sendDATATest); runTest(sendCTRLTest); runTest(sendpCTRLTest); runTest(sendPOSITest); - runTest(pauseTest); - runTest(connTest); + runTest(pauseTest); + runTest(connTest); printf( "----------------\nTest Summary\n\tFailed: %i\n\tPassed: %i\n", testFailed, testPassed ); diff --git a/TestScripts/Java Tests/XPlaneConnectTest.java b/TestScripts/Java Tests/XPlaneConnectTest.java index 3cfc43e..3667ebd 100644 --- a/TestScripts/Java Tests/XPlaneConnectTest.java +++ b/TestScripts/Java Tests/XPlaneConnectTest.java @@ -217,6 +217,36 @@ public class XPlaneConnectTest } } + @Test + public void testSendTEXT() throws IOException + { + String msg = "XPlaneConnect Java message test."; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + xpc.sendTEXT(msg, 200, 400); + } + } + + @Test + public void testSendTEXT_Multiline() throws IOException + { + String msg = "XPlaneConnect Java message test.\nNow with new lines!\rAnd another...\r\nAnd now a double."; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + xpc.sendTEXT(msg, 200, 400); + } + } + + @Test + public void testSendTEXT_Long() throws IOException + { + String msg = "XPlaneConnect Java message test.\nNow with new lines!\rAnd another...\r\nAnd now a double\nAnd finally a really long line because that seemed to break things."; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + xpc.sendTEXT(msg, 200, 400); + } + } + @Test public void testSendDREF() throws IOException { diff --git a/TestScripts/MATLAB Tests/sendTEXTTest.m b/TestScripts/MATLAB Tests/sendTEXTTest.m new file mode 100644 index 0000000..30f071b --- /dev/null +++ b/TestScripts/MATLAB Tests/sendTEXTTest.m @@ -0,0 +1,11 @@ +function sendTEXTTest() +%% Setup +addpath('../../MATLAB') +import XPlaneConnect.* + +%% Test +sendTEXT('sendTEXT test message M', 200, 400); + + +end + diff --git a/TestScripts/MATLAB Tests/tests.m b/TestScripts/MATLAB Tests/tests.m index 8492faa..8788712 100644 --- a/TestScripts/MATLAB Tests/tests.m +++ b/TestScripts/MATLAB Tests/tests.m @@ -12,6 +12,7 @@ disp(['XPC Tests-MATLAB (', os, ')']); theTests = {{@openCloseTest, 'Open/Close Test', 0},... {@sendReadTest,'Send/Read Test', 0},... + {@sendTEXTTest,'TEXT Test', 0},... {@requestDREFTest,'Request DREF Test', 0},... {@sendDREFTest,'Send DREF Test', 0},... {@DATATest,'DATA Test', 0},... diff --git a/xpcPlugin/XPCPlugin.cpp b/xpcPlugin/XPCPlugin.cpp index c6e1143..5ed485e 100755 --- a/xpcPlugin/XPCPlugin.cpp +++ b/xpcPlugin/XPCPlugin.cpp @@ -68,6 +68,7 @@ //#include "XPLMPlanes.h" #include "XPLMProcessing.h" #include "XPLMGraphics.h" +#include "xpcDrawing.h" #include "xpcPluginTools.h" #ifdef _WIN32 /* WIN32 SYSTEM */ @@ -124,6 +125,7 @@ int handleGETD(char *buf); int handleDREF(char *buf); int handleVIEW(); int handleDATA(char *buf, int buflen); +int handleTEXT(char *buf, int len); short handleInput(struct XPCMessage * theMessage); char setPOSI(short aircraft, float pos[3]); @@ -189,6 +191,8 @@ PLUGIN_API void XPluginDisable(void) closeUDP(recSocket); closeUDP(sendSocket); updateLog(logmsg,strlen(logmsg)); + + XPCClearMessage(); } PLUGIN_API int XPluginEnable(void) @@ -369,6 +373,10 @@ short handleInput(struct XPCMessage * theMessage) { handleDATA(theMessage->msg, theMessage->msglen); } + else if (strncmp(theMessage->head, "TEXT", 4) == 0) // Header = TEXT (Screen message) + { + handleTEXT(theMessage->msg, theMessage->msglen); + } else if ((strncmp(theMessage->head,"DSEL",4)==0) || (strncmp(theMessage->head,"USEL",4)==0)) // Header = DSEL/USEL (Select UDP Send) { sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP @@ -409,13 +417,13 @@ short handleInput(struct XPCMessage * theMessage) { sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP } - else if (strncmp(theMessage->head,"BOAT",4)==0) + else if (strncmp(theMessage->head, "BOAT", 4) == 0) { - sendBUF(theMessage->msg,theMessage->msglen); // Send to UDP + sendBUF(theMessage->msg, theMessage->msglen); // Send to UDP } else { //unrecognized header - sprintf(logmsg,"[EXEC] ERROR: Command %s not recognised",theMessage->head); + sprintf(logmsg,"[EXEC] ERROR: Command %s not recognized",theMessage->head); updateLog(logmsg, strlen(logmsg)); } current_connection = -1; @@ -491,6 +499,31 @@ int handleSIMU(char buf[]) return 0; } +int handleTEXT(char *buf, int len) +{ + char msg[256] = { 0 }; + if (len < 14) + { + updateLog("[TEXT] ERROR: Length less than 14 bytes", 39); + return -1; + } + size_t msgLen = (unsigned char)buf[13]; + if (msgLen == 0) + { + XPCClearMessage(); + updateLog("[TEXT] Text cleared", 19); + } + else + { + int x = *((int*)(buf + 5)); + int y = *((int*)(buf + 9)); + strncpy(msg, buf + 14, msgLen); + XPCSetMessage(x, y, msg); + updateLog("[TEXT] Text set", 15); + } + return 0; +} + char setDREF(XPLMDataRef theDREF, float floatarray[], short arrayStart, short arraySize) { XPLMDataTypeID dataType; diff --git a/xpcPlugin/XPlaneConnect/64/win.xpl b/xpcPlugin/XPlaneConnect/64/win.xpl index 4db8497..e287685 100644 Binary files a/xpcPlugin/XPlaneConnect/64/win.xpl and b/xpcPlugin/XPlaneConnect/64/win.xpl differ diff --git a/xpcPlugin/XPlaneConnect/mac.xpl b/xpcPlugin/XPlaneConnect/mac.xpl index 5422779..09174ef 100755 Binary files a/xpcPlugin/XPlaneConnect/mac.xpl and b/xpcPlugin/XPlaneConnect/mac.xpl differ diff --git a/xpcPlugin/XPlaneConnect/win.xpl b/xpcPlugin/XPlaneConnect/win.xpl index b0cd298..3d8f45a 100644 Binary files a/xpcPlugin/XPlaneConnect/win.xpl and b/xpcPlugin/XPlaneConnect/win.xpl differ diff --git a/xpcPlugin/xpcDrawing.cpp b/xpcPlugin/xpcDrawing.cpp new file mode 100644 index 0000000..16bb1b4 --- /dev/null +++ b/xpcPlugin/xpcDrawing.cpp @@ -0,0 +1,67 @@ +#include "xpcDrawing.h" +#include "XPLMDisplay.h" +#include "XPLMGraphics.h" +#include +#include + +//Internal Memory +static bool msgEnabled = false; +static int msgX = -1; +static int msgY = -1; +static char msgVal[256] = { 0 }; +static size_t newLineCount = 0; +static size_t newLines[64] = { 0 }; +static float rgb[3] = { 0.25F, 1.0F, 0.25F }; + +//Internal Functions +static int MessageDrawCallback(XPLMDrawingPhase inPhase, int inIsBefore, void * inRefcon) +{ + XPLMDrawString(rgb, msgX, msgY, msgVal, NULL, xplmFont_Basic); + int y = msgY - 16; + for (size_t i = 0; i < newLineCount; ++i) + { + XPLMDrawString(rgb, msgX, y, msgVal + newLines[i], NULL, xplmFont_Basic); + y -= 16; + } + return 1; +} + +//Public Functions +void XPCClearMessage() +{ + XPLMUnregisterDrawCallback(MessageDrawCallback, xplm_Phase_Window, 0, NULL); + msgEnabled = false; +} + +void XPCSetMessage(int x, int y, char* msg) +{ + //Determine size of message and clear instead if the message string + //is empty. + size_t len = strnlen(msg, 255); + if (len == 0) + { + XPCClearMessage(); + return; + } + + //Set the message, location, and mark new lines. + strncpy(msgVal, msg, len); + newLineCount = 0; + for (size_t i = 0; i < len && newLineCount < 64; ++i) + { + if (msgVal[i] == '\n' || msgVal[i] == '\r') + { + msgVal[i] = 0; + newLines[newLineCount++] = i + 1; + } + } + msgX = x < 0 ? 10 : x; + msgY = y < 0 ? 600 : y; + + //Enable drawing if necessary + if (!msgEnabled) + { + XPLMRegisterDrawCallback(MessageDrawCallback, xplm_Phase_LastCockpit, 0, NULL); + msgEnabled = true; + } +} \ No newline at end of file diff --git a/xpcPlugin/xpcDrawing.h b/xpcPlugin/xpcDrawing.h new file mode 100644 index 0000000..d821222 --- /dev/null +++ b/xpcPlugin/xpcDrawing.h @@ -0,0 +1,8 @@ +#ifndef xpcDrawing_h +#define xpcDrawing_h + +void XPCClearMessage(); + +void XPCSetMessage(int x, int y, char* msg); + +#endif diff --git a/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj b/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj index c4d69d2..81570d0 100755 --- a/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj +++ b/xpcPlugin/xpcPlugin.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ BE5F2FF118FCA1D500AFCD17 /* xpcPluginTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE5F2FF018FCA1D500AFCD17 /* xpcPluginTools.cpp */; }; BE8361EF18C5591C00E9C923 /* mac.xpl in CopyFiles */ = {isa = PBXBuildFile; fileRef = D607B19909A556E400699BC3 /* mac.xpl */; }; BEDC620418EDF1A7005DB364 /* xplaneConnect.c in Sources */ = {isa = PBXBuildFile; fileRef = BEDC620218EDF1A7005DB364 /* xplaneConnect.c */; }; + BEFBC0CB1AD4A0290025705B /* xpcDrawing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BEFBC0C91AD4A0290025705B /* xpcDrawing.cpp */; }; D6A7BDAA16A1DEA200D1426A /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6A7BDA916A1DEA200D1426A /* OpenGL.framework */; }; D6A7BDC116A1DEC000D1426A /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6A7BDC016A1DEC000D1426A /* CoreFoundation.framework */; }; D6A7BDF116A1DED200D1426A /* XPLM.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6A7BDF016A1DED200D1426A /* XPLM.framework */; }; @@ -37,6 +38,8 @@ BE5F2FF018FCA1D500AFCD17 /* xpcPluginTools.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xpcPluginTools.cpp; sourceTree = ""; }; BEDC620218EDF1A7005DB364 /* xplaneConnect.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = xplaneConnect.c; path = ../C/src/xplaneConnect.c; sourceTree = ""; }; BEDC620318EDF1A7005DB364 /* xplaneConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = xplaneConnect.h; path = ../C/src/xplaneConnect.h; sourceTree = ""; }; + BEFBC0C91AD4A0290025705B /* xpcDrawing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xpcDrawing.cpp; sourceTree = ""; }; + BEFBC0CA1AD4A0290025705B /* xpcDrawing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xpcDrawing.h; sourceTree = ""; }; D607B19909A556E400699BC3 /* mac.xpl */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = mac.xpl; sourceTree = BUILT_PRODUCTS_DIR; }; D6A7BDA916A1DEA200D1426A /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; D6A7BDC016A1DEC000D1426A /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; @@ -67,6 +70,8 @@ BEDC620318EDF1A7005DB364 /* xplaneConnect.h */, BE5F2FF018FCA1D500AFCD17 /* xpcPluginTools.cpp */, BE5F2FEF18FCA13700AFCD17 /* xpcPluginTools.h */, + BEFBC0C91AD4A0290025705B /* xpcDrawing.cpp */, + BEFBC0CA1AD4A0290025705B /* xpcDrawing.h */, ); name = "C Source"; sourceTree = ""; @@ -155,6 +160,7 @@ files = ( BEDC620418EDF1A7005DB364 /* xplaneConnect.c in Sources */, BE37D960187C8B0F0033B082 /* XPCPlugin.cpp in Sources */, + BEFBC0CB1AD4A0290025705B /* xpcDrawing.cpp in Sources */, BE5F2FF118FCA1D500AFCD17 /* xpcPluginTools.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj b/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj index e16b698..0cdd781 100644 --- a/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj +++ b/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj @@ -97,10 +97,12 @@ + + diff --git a/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj.filters b/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj.filters index a0809e4..0082526 100644 --- a/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj.filters +++ b/xpcPlugin/xpcPlugin/xpcPlugin.vcxproj.filters @@ -21,6 +21,9 @@ Header Files + + Header Files + @@ -32,6 +35,9 @@ Source Files + + Source Files + diff --git a/xpcPlugin/xpcPluginTools.cpp b/xpcPlugin/xpcPluginTools.cpp index 9b52d44..53228c7 100644 --- a/xpcPlugin/xpcPluginTools.cpp +++ b/xpcPlugin/xpcPluginTools.cpp @@ -380,7 +380,7 @@ int printBufferToLog(struct XPCMessage & msg) {// Header = CTRL (Control) xpcCtrl ctrl = parseCTRL(msg.msg); - sprintf(logmsg,"%s (%f %f %f) %f %hi %f",logmsg, ctrl.pitch, ctrl.roll, ctrl.yaw, ctrl.throttle, ctrl.gear, ctrl.flaps); + sprintf(logmsg,"%s (%f %f %f) %f %hhi %f",logmsg, ctrl.pitch, ctrl.roll, ctrl.yaw, ctrl.throttle, ctrl.gear, ctrl.flaps); updateLog(logmsg,strlen(logmsg));