From 292f347982d954094a7949a81d80c246d9d8ff1a Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Fri, 15 May 2015 16:40:10 -0700 Subject: [PATCH 1/7] Added GCTRL and GPOS commands to the plugin. --- xpcPlugin/DataManager.cpp | 17 +++++----- xpcPlugin/MessageHandlers.cpp | 64 +++++++++++++++++++++++++++++++++++ xpcPlugin/MessageHandlers.h | 2 ++ 3 files changed, 75 insertions(+), 8 deletions(-) diff --git a/xpcPlugin/DataManager.cpp b/xpcPlugin/DataManager.cpp index 42048e5..eccb8c2 100644 --- a/xpcPlugin/DataManager.cpp +++ b/xpcPlugin/DataManager.cpp @@ -169,8 +169,8 @@ namespace XPC sprintf(multi, "sim/multiplayer/position/plane%i_gear_deploy", i); mdrefs[i][DREF_GearDeploy] = XPLMFindDataRef(multi); sprintf(multi, "sim/multiplayer/position/plane%i_flap_ratio", i); - mdrefs[i][DREF_FlapSetting] = XPLMFindDataRef(multi); // Can't set the actual flap setting on npc aircraft mdrefs[i][DREF_FlapActual] = XPLMFindDataRef(multi); + mdrefs[i][DREF_FlapSetting] = mdrefs[i][DREF_FlapActual]; // Can't set the actual flap setting on npc aircraft sprintf(multi, "sim/multiplayer/position/plane%i_flap_ratio2", i); mdrefs[i][DREF_FlapActual2] = XPLMFindDataRef(multi); sprintf(multi, "sim/multiplayer/position/plane%i_spoiler_ratio", i); @@ -183,6 +183,7 @@ namespace XPC mdrefs[i][DREF_Sweep] = XPLMFindDataRef(multi); sprintf(multi, "sim/multiplayer/position/plane%i_throttle", i); mdrefs[i][DREF_ThrottleActual] = XPLMFindDataRef(multi); + mdrefs[i][DREF_ThrottleSet] = mdrefs[i][DREF_ThrottleActual]; // No throttle set for multiplayer planes. sprintf(multi, "sim/multiplayer/position/plane%i_yolk_pitch", i); mdrefs[i][DREF_YokePitch] = XPLMFindDataRef(multi); sprintf(multi, "sim/multiplayer/position/plane%i_yolk_roll", i); @@ -729,13 +730,13 @@ namespace XPC // http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf float q[4]; float halfRad = 0.00872664625997F; - orient[2] = halfRad * orient[2]; - orient[0] = halfRad * orient[0]; - orient[1] = halfRad * orient[1]; - q[0] = cos(orient[2]) * cos(orient[0]) * cos(orient[1]) + sin(orient[2]) * sin(orient[0]) * sin(orient[1]); - q[1] = cos(orient[2]) * cos(orient[0]) * sin(orient[1]) - sin(orient[2]) * sin(orient[0]) * cos(orient[1]); - q[2] = cos(orient[2]) * sin(orient[0]) * cos(orient[1]) + sin(orient[2]) * cos(orient[0]) * sin(orient[1]); - q[3] = sin(orient[2]) * cos(orient[0]) * cos(orient[1]) - cos(orient[2]) * sin(orient[0]) * sin(orient[1]); + float theta = halfRad * orient[0]; + float phi = halfRad * orient[1]; + float psi = halfRad * orient[2]; + q[0] = cos(phi) * cos(theta) * cos(psi) + sin(phi) * sin(theta) * sin(psi); + q[1] = sin(phi) * cos(theta) * cos(psi) - cos(phi) * sin(theta) * sin(psi); + q[2] = cos(phi) * sin(theta) * cos(psi) + sin(phi) * cos(theta) * sin(psi); + q[3] = cos(phi) * cos(theta) * sin(psi) - sin(phi) * sin(theta) * cos(psi); // If the sim is un-paused, this will overwrite the pitch/roll/yaw // values set above. diff --git a/xpcPlugin/MessageHandlers.cpp b/xpcPlugin/MessageHandlers.cpp index d72b8a8..4709165 100644 --- a/xpcPlugin/MessageHandlers.cpp +++ b/xpcPlugin/MessageHandlers.cpp @@ -55,6 +55,8 @@ namespace XPC handlers.insert(std::make_pair("TEXT", MessageHandlers::HandleText)); handlers.insert(std::make_pair("WYPT", MessageHandlers::HandleWypt)); handlers.insert(std::make_pair("VIEW", MessageHandlers::HandleView)); + handlers.insert(std::make_pair("GETC", MessageHandlers::HandleGetC)); + handlers.insert(std::make_pair("GETP", MessageHandlers::HandleGetP)); // X-Plane data messages handlers.insert(std::make_pair("DSEL", MessageHandlers::HandleXPlaneData)); handlers.insert(std::make_pair("USEL", MessageHandlers::HandleXPlaneData)); @@ -425,6 +427,43 @@ namespace XPC } } + void MessageHandlers::HandleGetC(const Message& msg) + { + const unsigned char* buffer = msg.GetBuffer(); + std::size_t size = msg.GetSize(); + if (size != 6) + { + Log::FormatLine(LOG_ERROR, "GCTL", "Unexpected message length: %u", size); + return; + } + unsigned char aircraft = buffer[5]; + // TODO(jason-watkins): Get proper printf specifier for unsigned char + Log::FormatLine(LOG_TRACE, "GCTL", "Getting control information for aircraft %u", aircraft); + + float throttle[8]; + unsigned char response[31] = "CTRL"; + *((float*)(response + 5)) = DataManager::GetFloat(DREF_Elevator, aircraft); + *((float*)(response + 9)) = DataManager::GetFloat(DREF_Aileron, aircraft); + *((float*)(response + 13)) = DataManager::GetFloat(DREF_Rudder, aircraft); + DataManager::GetFloatArray(DREF_ThrottleSet, throttle, 8, aircraft); + *((float*)(response + 17)) = throttle[0]; + if (aircraft == 0) + { + response[21] = (char)DataManager::GetInt(DREF_GearHandle, aircraft); + } + else + { + float mpGear[10]; + DataManager::GetFloatArray(DREF_GearDeploy, mpGear, 10, aircraft); + response[21] = mpGear[0] > 0.5 ? 1 : 0; + } + *((float*)(response + 22)) = DataManager::GetFloat(DREF_FlapSetting, aircraft); + response[26] = aircraft; + *((float*)(response + 27)) = DataManager::GetFloat(DREF_SpeedBrakeSet, aircraft); + + sock->SendTo(response, 31, &connection.addr); + } + void MessageHandlers::HandleGetD(const Message& msg) { const unsigned char* buffer = msg.GetBuffer(); @@ -471,6 +510,31 @@ namespace XPC sock->SendTo(response, cur, &connection.addr); } + void MessageHandlers::HandleGetP(const Message& msg) + { + const unsigned char* buffer = msg.GetBuffer(); + std::size_t size = msg.GetSize(); + if (size != 6) + { + Log::FormatLine(LOG_ERROR, "GPOS", "Unexpected message length: %u", size); + return; + } + unsigned char aircraft = buffer[5]; + Log::FormatLine(LOG_TRACE, "GPOS", "Getting position information for aircraft %u", aircraft); + + unsigned char response[34] = "POSI"; + response[5] = (char)DataManager::GetInt(DREF_GearHandle, aircraft); + *((float*)(response + 6)) = (float)DataManager::GetDouble(DREF_Latitude, aircraft); + *((float*)(response + 10)) = (float)DataManager::GetDouble(DREF_Longitude, aircraft); + *((float*)(response + 14)) = (float)DataManager::GetDouble(DREF_Elevation, aircraft); + *((float*)(response + 18)) = DataManager::GetFloat(DREF_Pitch, aircraft); + *((float*)(response + 22)) = DataManager::GetFloat(DREF_Roll, aircraft); + *((float*)(response + 26)) = DataManager::GetFloat(DREF_HeadingTrue, aircraft); + *((float*)(response + 30)) = (float)DataManager::GetInt(DREF_GearHandle, aircraft); + + sock->SendTo(response, 34, &connection.addr); + } + void MessageHandlers::HandlePosi(const Message& msg) { // Update log diff --git a/xpcPlugin/MessageHandlers.h b/xpcPlugin/MessageHandlers.h index 13742ee..d7f317d 100644 --- a/xpcPlugin/MessageHandlers.h +++ b/xpcPlugin/MessageHandlers.h @@ -43,7 +43,9 @@ namespace XPC static void HandleCtrl(const Message& msg); static void HandleData(const Message& msg); static void HandleDref(const Message& msg); + static void HandleGetC(const Message& msg); static void HandleGetD(const Message& msg); + static void HandleGetP(const Message& msg); static void HandlePosi(const Message& msg); static void HandleSimu(const Message& msg); static void HandleText(const Message& msg); From 0b562d1fce24a12e6d60b762688fd560a897d71c Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Sat, 16 May 2015 09:26:27 -0700 Subject: [PATCH 2/7] Added getPOSI and getCTRL support to the C client. --- C/src/xplaneConnect.c | 67 +++++++++++++++++++++++++++++++++ C/src/xplaneConnect.h | 18 +++++++++ TestScripts/C Tests/CtrlTests.h | 39 +++++++++++++++++++ TestScripts/C Tests/PosiTests.h | 37 ++++++++++++++++++ TestScripts/C Tests/main.c | 4 ++ xpcPlugin/MessageHandlers.cpp | 5 ++- 6 files changed, 169 insertions(+), 1 deletion(-) diff --git a/C/src/xplaneConnect.c b/C/src/xplaneConnect.c index e4e633e..9969a57 100755 --- a/C/src/xplaneConnect.c +++ b/C/src/xplaneConnect.c @@ -521,6 +521,38 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char /*****************************************************************************/ /**** POSI functions ****/ /*****************************************************************************/ +int getPOSI(XPCSocket sock, float values[7], char ac) +{ + // Setup send command + unsigned char buffer[6] = "GETP"; + buffer[5] = ac; + + // Send command + if (sendUDP(sock, buffer, 6) < 0) + { + printError("getPOSI", "Failed to send command."); + return -1; + } + + // Get response + unsigned char readBuffer[34]; + int readResult = readUDP(sock, readBuffer, 34); + if (readResult < 0) + { + printError("getPOSI", "Failed to read response."); + return -2; + } + if (readResult != 34) + { + printError("getPOSI", "Unexpected response length."); + return -3; + } + + // Copy response into values + memcpy(values, readBuffer + 6, 7 * sizeof(float)); + return 0; +} + int sendPOSI(XPCSocket sock, float values[], int size, char ac) { // Validate input @@ -565,6 +597,41 @@ int sendPOSI(XPCSocket sock, float values[], int size, char ac) /*****************************************************************************/ /**** CTRL functions ****/ /*****************************************************************************/ +int getCTRL(XPCSocket sock, float values[7], char ac) +{ + // Setup send command + unsigned char buffer[6] = "GETC"; + buffer[5] = ac; + + // Send command + if (sendUDP(sock, buffer, 6) < 0) + { + printError("getCTRL", "Failed to send command."); + return -1; + } + + // Get response + unsigned char readBuffer[31]; + int readResult = readUDP(sock, readBuffer, 31); + if (readResult < 0) + { + printError("getCTRL", "Failed to read response."); + return -2; + } + if (readResult != 31) + { + printError("getCTRL", "Unexpected response length."); + return -3; + } + + // Copy response into values + memcpy(values, readBuffer + 5, 4 * sizeof(float)); + values[4] = readBuffer[21]; + values[5] = *((float*)(readBuffer + 22)); + values[6] = *((float*)(readBuffer + 27)); + return 0; +} + int sendCTRL(XPCSocket sock, float values[], int size, char ac) { // Validate input diff --git a/C/src/xplaneConnect.h b/C/src/xplaneConnect.h index 3827a68..161178a 100644 --- a/C/src/xplaneConnect.h +++ b/C/src/xplaneConnect.h @@ -196,6 +196,14 @@ int getDREFs(XPCSocket sock, const char* drefs[], float* values[], unsigned char // Position +/// Gets the position and orientation of the specified aircraft. +/// +/// \param sock The socket used to send the command and receive the response. +/// \param values An array to store the position information returned by the +/// plugin. The format of values is [Lat, Lon, Alt, Pitch, Roll, Yaw, Gear] +/// \returns 0 if successful, otherwise a negative value. +int getPOSI(XPCSocket sock, float values[7], char ac); + /// Sets the position and orientation of the specified aircraft. /// /// \param sock The socket to use to send the command. @@ -209,6 +217,16 @@ int sendPOSI(XPCSocket sock, float values[], int size, char ac); // Controls +/// Gets the control surface information for the specified aircraft. +/// +/// \param sock The socket used to send the command and receive the response. +/// \param values An array to store the position information returned by the +/// plugin. The format of values is [Elevator, Aileron, Rudder, +/// Throttle, Gear, Flaps, Speed Brakes] +/// \param ac The aircraft to set the control surfaces of. 0 is the main/player aircraft. +/// \returns 0 if successful, otherwise a negative value. +int getCTRL(XPCSocket sock, float values[7], char ac); + /// Sets the control surfaces of the specified aircraft. /// /// \param sock The socket to use to send the command. diff --git a/TestScripts/C Tests/CtrlTests.h b/TestScripts/C Tests/CtrlTests.h index ccf0569..7b9e445 100644 --- a/TestScripts/C Tests/CtrlTests.h +++ b/TestScripts/C Tests/CtrlTests.h @@ -38,6 +38,33 @@ int doCTRLTest(char* drefs[7], float values[], int size, int ac, float expected[ return compareArray(expected, actual, 7); } +int doGETCTest(float values[7], int ac, float expected[7]) +{ + // Execute Test + float actual[7]; + XPCSocket sock = openUDP(IP); + int result = sendCTRL(sock, values, 7, ac); + if (result >= 0) + { + result = getCTRL(sock, actual, ac); + } + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test values + for (int i = 0; i < 7; ++i) + { + if (fabs(expected[i] - actual[i]) > 1e-4) + { + return -10 - i; + } + } + return 0; +} + int basicCTRLTest(char** drefs, int ac) { // Set control surfaces to known state. @@ -146,4 +173,16 @@ int testCTRL_Speedbrakes() } return 0; } + +int testGETC() +{ + float CTRL[7] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F, -1.5F }; + return doGETCTest(CTRL, 0, CTRL); +} + +int testGETC_NonPlayer() +{ + float CTRL[7] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F, -1.5F }; + return doGETCTest(CTRL, 2, CTRL); +} #endif \ No newline at end of file diff --git a/TestScripts/C Tests/PosiTests.h b/TestScripts/C Tests/PosiTests.h index d8b5673..a46ca61 100644 --- a/TestScripts/C Tests/PosiTests.h +++ b/TestScripts/C Tests/PosiTests.h @@ -40,6 +40,33 @@ int doPOSITest(char* drefs[7], float values[], int size, int ac, float expected[ return compareArray(expected, actual, 7); } +int doGETPTest(float values[7], int ac, float expected[7]) +{ + // Execute Test + float actual[7]; + XPCSocket sock = openUDP(IP); + int result = sendPOSI(sock, values, 7, ac); + if (result >= 0) + { + result = getPOSI(sock, actual, ac); + } + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test values + for (int i = 0; i < 7; ++i) + { + if (fabs(expected[i] - actual[i]) > 1e-4) + { + return -10 - i; + } + } + return 0; +} + int basicPOSITest(char** drefs, int ac) { // Set psoition and initial orientation @@ -116,6 +143,16 @@ int testPOSI_NonPlayer() return basicPOSITest(drefs, 1); } +int testGetPOSI_Player() +{ + float POSI[7] = { 37.524F, -122.06899F, 2500, 0, 0, 0, 1 }; + doGETPTest(POSI, 0, POSI); +} +int testGetPOSI_NonPlayer() +{ + float POSI[7] = { 37.624F, -122.06899F, 1500, 0, 0, 0, 1 }; + doGETPTest(POSI, 3, POSI); +} #endif \ No newline at end of file diff --git a/TestScripts/C Tests/main.c b/TestScripts/C Tests/main.c index 0f5cdd9..26428eb 100644 --- a/TestScripts/C Tests/main.c +++ b/TestScripts/C Tests/main.c @@ -40,9 +40,13 @@ int main(int argc, const char * argv[]) runTest(testCTRL_Player, "CTRL (player)"); runTest(testCTRL_NonPlayer, "CTRL (non-player)"); runTest(testCTRL_Speedbrakes, "CTRL (speedbrakes)"); + runTest(testGETC, "GETC (player)"); + runTest(testGETC_NonPlayer, "GETC (Non-player)"); // POSI runTest(testPOSI_Player, "POSI (player)"); runTest(testPOSI_NonPlayer, "POSI (non-player)"); + runTest(testGetPOSI_Player, "GETP (player)"); + runTest(testGetPOSI_NonPlayer, "GETP (non-player)"); // Data runTest(testDATA, "DATA"); // Text diff --git a/xpcPlugin/MessageHandlers.cpp b/xpcPlugin/MessageHandlers.cpp index 4709165..d94be09 100644 --- a/xpcPlugin/MessageHandlers.cpp +++ b/xpcPlugin/MessageHandlers.cpp @@ -530,7 +530,10 @@ namespace XPC *((float*)(response + 18)) = DataManager::GetFloat(DREF_Pitch, aircraft); *((float*)(response + 22)) = DataManager::GetFloat(DREF_Roll, aircraft); *((float*)(response + 26)) = DataManager::GetFloat(DREF_HeadingTrue, aircraft); - *((float*)(response + 30)) = (float)DataManager::GetInt(DREF_GearHandle, aircraft); + + float gear[10]; + DataManager::GetFloatArray(DREF_GearDeploy, gear, 10, aircraft); + *((float*)(response + 30)) = gear[0]; sock->SendTo(response, 34, &connection.addr); } From 4b6b1d57cef74ac20ff5ed28b3aff8da95042d7a Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Tue, 19 May 2015 08:17:53 -0700 Subject: [PATCH 3/7] Fixed logging typos --- xpcPlugin/UDPSocket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xpcPlugin/UDPSocket.cpp b/xpcPlugin/UDPSocket.cpp index 349f127..210cc56 100644 --- a/xpcPlugin/UDPSocket.cpp +++ b/xpcPlugin/UDPSocket.cpp @@ -135,7 +135,7 @@ namespace XPC } else { - Log::FormatLine(LOG_INFO, tag, "Send failed. (remote: %s)", GetHost(remote).c_str()); + Log::FormatLine(LOG_INFO, tag, "Send succeeded. (remote: %s)", GetHost(remote).c_str()); } } From b903bdd90d0ddd6ede4a0e3deebdcd31b5171dde Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Tue, 19 May 2015 15:18:24 -0700 Subject: [PATCH 4/7] Added getCTRL and getPOSI support to python client. --- Python/src/xpc.py | 49 +++++++++++++++++++++++++++++-- TestScripts/Python Tests/Tests.py | 48 ++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/Python/src/xpc.py b/Python/src/xpc.py index c19bbdb..e2b5c5e 100644 --- a/Python/src/xpc.py +++ b/Python/src/xpc.py @@ -143,7 +143,29 @@ class XPlaneConnect(object): buffer += struct.pack(" Date: Wed, 20 May 2015 09:32:09 -0700 Subject: [PATCH 5/7] Fix typo in sendCTRL doc comments. --- Java/src/XPlaneConnect.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java index ba545d5..3c22601 100644 --- a/Java/src/XPlaneConnect.java +++ b/Java/src/XPlaneConnect.java @@ -409,6 +409,7 @@ public class XPlaneConnect implements AutoCloseable *
  • Throttle [-1, 1]
  • *
  • Gear (0=up, 1=down)
  • *
  • Flaps [0, 1]
  • + *
  • Speedbrakes [-0.5, 1.5]
  • * *

    * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To @@ -433,6 +434,7 @@ public class XPlaneConnect implements AutoCloseable *

  • Throttle [-1, 1]
  • *
  • Gear (0=up, 1=down)
  • *
  • Flaps [0, 1]
  • + *
  • Speedbrakes [-0.5, 1.5]
  • * *

    * If @{code ctrl} is less than 6 elements long, The missing elements will not be changed. To From a738414ac1b4fd61c27471d4ec70a3a0889309f5 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Wed, 20 May 2015 10:31:27 -0700 Subject: [PATCH 6/7] Added getCTRL and getPOSI support to Java client. --- Java/src/XPlaneConnect.java | 79 +++++++++++++++++++ TestScripts/Java Tests/XPlaneConnectTest.java | 27 +++++++ 2 files changed, 106 insertions(+) diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java index 3c22601..ad3307e 100644 --- a/Java/src/XPlaneConnect.java +++ b/Java/src/XPlaneConnect.java @@ -398,6 +398,47 @@ public class XPlaneConnect implements AutoCloseable 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. * @@ -494,6 +535,44 @@ public class XPlaneConnect implements AutoCloseable 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 float[] 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 + float[] result = new float[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. * diff --git a/TestScripts/Java Tests/XPlaneConnectTest.java b/TestScripts/Java Tests/XPlaneConnectTest.java index fc7c76e..2e94bb5 100644 --- a/TestScripts/Java Tests/XPlaneConnectTest.java +++ b/TestScripts/Java Tests/XPlaneConnectTest.java @@ -698,4 +698,31 @@ public class XPlaneConnectTest } } + + @Test + public void testGetPOSI() throws IOException + { + float[] values = { 37.524F, -122.06899F, 2500.0F, 45.0F, -45.0F, 15.0F, 1.0F }; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + xpc.pauseSim(true); + xpc.sendPOSI(values); + float[] actual = xpc.getPOSI(0); + + assertArrayEquals(values, actual, 1e-4F); + } + } + + @Test + public void testGetCTRL() throws IOException + { + float[] values = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F, -1.5F }; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + xpc.sendCTRL(values); + float[] actual = xpc.getCTRL(0); + + assertArrayEquals(values, actual, 1e-4F); + } + } } \ No newline at end of file From 00b61f6051ad3b75c8384f5f4ca3ff9904190a8a Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Tue, 2 Jun 2015 14:13:28 -0700 Subject: [PATCH 7/7] Added support for GETP and GETC to MATLAB client. --- MATLAB/+XPlaneConnect/XPlaneConnect.jar | Bin 8294 -> 8657 bytes MATLAB/+XPlaneConnect/getCTRL.m | 29 ++++++++++++++++++ MATLAB/+XPlaneConnect/getPOSI.m | 29 ++++++++++++++++++ TestScripts/MATLAB Tests/getCTRLTest.m | 15 +++++++++ TestScripts/MATLAB Tests/getPOSITest.m | 17 ++++++++++ .../{CTRLTest.m => sendCTRLTest.m} | 2 +- .../{POSITest.m => sendPOSITest.m} | 2 +- TestScripts/MATLAB Tests/tests.m | 6 ++-- 8 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 MATLAB/+XPlaneConnect/getCTRL.m create mode 100644 MATLAB/+XPlaneConnect/getPOSI.m create mode 100644 TestScripts/MATLAB Tests/getCTRLTest.m create mode 100644 TestScripts/MATLAB Tests/getPOSITest.m rename TestScripts/MATLAB Tests/{CTRLTest.m => sendCTRLTest.m} (98%) rename TestScripts/MATLAB Tests/{POSITest.m => sendPOSITest.m} (96%) diff --git a/MATLAB/+XPlaneConnect/XPlaneConnect.jar b/MATLAB/+XPlaneConnect/XPlaneConnect.jar index c7797111c75228c2b0d16de3685d4e7e92acf838..eba5283b15fa5c42fbefe5574182eeb93a54c183 100644 GIT binary patch delta 6489 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