From e3bc25d5103cba857a487ab804c273fb737100ff Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Wed, 6 May 2015 15:13:58 -0700 Subject: [PATCH 1/7] Updated description of logging levels and changed default logging verbosity. --- xpcPlugin/Log.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xpcPlugin/Log.h b/xpcPlugin/Log.h index 84d75c0..b7413c1 100644 --- a/xpcPlugin/Log.h +++ b/xpcPlugin/Log.h @@ -12,10 +12,11 @@ // As a result, these log messages may be the only indication of failure. // 2: All errors. Any time something unexpected happens, log it. // 3: Significant actions. Any time something happens outside of normal -// command processing, log it. +// command processing, log it. Will also log when commands are received. +// 4: Detailed actions. Log aditional information as commands are processed. // 5: Everything. Log nearly every single action the plugin takes. This may // have a detrimental impact on X-Plane performance. -#define LOG_VERBOSITY 2 +#define LOG_VERBOSITY 3 namespace XPC { From 0884cfd3955054fc8cb8c94913c122e7a69deeed Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Wed, 6 May 2015 15:13:21 -0700 Subject: [PATCH 2/7] Added support for setting multiple datarefs to the plugin. - The DREF command now uses the packet length to detect and set multiple datarefs in a single packet. --- xpcPlugin/MessageHandlers.cpp | 43 +++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/xpcPlugin/MessageHandlers.cpp b/xpcPlugin/MessageHandlers.cpp index 17e5cb0..1a94bf3 100644 --- a/xpcPlugin/MessageHandlers.cpp +++ b/xpcPlugin/MessageHandlers.cpp @@ -402,18 +402,41 @@ namespace XPC void MessageHandlers::HandleDref(Message& msg) { - const unsigned char* buffer = msg.GetBuffer(); - unsigned char len = buffer[5]; - std::string dref = std::string((char*)buffer + 6, len); - - unsigned char valueCount = buffer[6 + len]; - float* values = (float*)(buffer + 7 + len); - -#if LOG_VERBOSITY > 1 - Log::FormatLine("[DREF] Request to set DREF value received (Conn %i): %s", connection.id, dref.c_str()); +#if LOG_VERBOSITY >= 3 + Log::FormatLine("[DREF] Request to set DREF value received (Conn %i)", connection.id); #endif + 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 dref = std::string((char*)buffer + pos, len); + pos += len; - DataManager::Set(dref, values, valueCount); + unsigned char valueCount = buffer[pos++]; + if (pos + 4 * valueCount > size) + { + break; + } + float* values = (float*)(buffer + pos); + pos += 4 * valueCount; + + DataManager::Set(dref, values, valueCount); +#if LOG_VERBOSITY >= 4 + Log::FormatLine("[DREF] Set %d values for %s", valueCount, dref.c_str()); +#endif + } +#if LOG_VERBOSITY >= 2 + if (pos != size) + { + Log::WriteLine("[DREF] Error: Command did not terminate at the expected position."); + } +#endif } void MessageHandlers::HandleGetD(Message& msg) From ec759cbbff1ce48edcb92c2e4060e161173215d4 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Thu, 7 May 2015 10:04:29 -0700 Subject: [PATCH 3/7] Refactored C Tests The C client tests were getting a bit unwieldy. Moved each block of tests to a separate file based on the functionality being tested. I've just put all of the test code directly in the headers because there doesn't seem to be any reason for the test headers to be included anywhere except in main.c. --- TestScripts/C Tests.win/CTests.vcxproj | 10 + .../C Tests.win/CTests.vcxproj.filters | 30 + TestScripts/C Tests/CtrlTests.h | 147 +++ TestScripts/C Tests/DataTests.h | 62 + TestScripts/C Tests/DrefTests.h | 247 ++++ TestScripts/C Tests/PosiTests.h | 120 ++ TestScripts/C Tests/SimuTests.h | 80 ++ TestScripts/C Tests/Test.c | 51 + TestScripts/C Tests/Test.h | 25 + TestScripts/C Tests/TextTests.h | 32 + TestScripts/C Tests/UDPTests.h | 55 + TestScripts/C Tests/WyptTests.h | 42 + TestScripts/C Tests/main.c | 1079 +---------------- 13 files changed, 940 insertions(+), 1040 deletions(-) create mode 100644 TestScripts/C Tests/CtrlTests.h create mode 100644 TestScripts/C Tests/DataTests.h create mode 100644 TestScripts/C Tests/DrefTests.h create mode 100644 TestScripts/C Tests/PosiTests.h create mode 100644 TestScripts/C Tests/SimuTests.h create mode 100644 TestScripts/C Tests/Test.c create mode 100644 TestScripts/C Tests/Test.h create mode 100644 TestScripts/C Tests/TextTests.h create mode 100644 TestScripts/C Tests/UDPTests.h create mode 100644 TestScripts/C Tests/WyptTests.h diff --git a/TestScripts/C Tests.win/CTests.vcxproj b/TestScripts/C Tests.win/CTests.vcxproj index c057e7d..b546a64 100644 --- a/TestScripts/C Tests.win/CTests.vcxproj +++ b/TestScripts/C Tests.win/CTests.vcxproj @@ -21,9 +21,19 @@ + + + + + + + + + + {BC701AF4-552C-4C9D-82A1-B352542783A4} diff --git a/TestScripts/C Tests.win/CTests.vcxproj.filters b/TestScripts/C Tests.win/CTests.vcxproj.filters index cd3ca2c..6e294b0 100644 --- a/TestScripts/C Tests.win/CTests.vcxproj.filters +++ b/TestScripts/C Tests.win/CTests.vcxproj.filters @@ -21,10 +21,40 @@ Source Files + + Source Files + Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + \ No newline at end of file diff --git a/TestScripts/C Tests/CtrlTests.h b/TestScripts/C Tests/CtrlTests.h new file mode 100644 index 0000000..3465f70 --- /dev/null +++ b/TestScripts/C Tests/CtrlTests.h @@ -0,0 +1,147 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef CTRLTESTS_H +#define CTRLTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int doCTRLTest(char* drefs[7], float values[], int size, int ac, float expected[7]) +{ + float* data[7]; + int sizes[7]; + for (int i = 0; i < 7; ++i) + { + data[i] = (float*)malloc(sizeof(float) * 16); + sizes[i] = 16; + } + + // Execute command + XPCSocket sock = openUDP(IP); + int result = sendCTRL(sock, values, size, ac); + if (result >= 0) + { + result = getDREFs(sock, drefs, data, 7, sizes); + } + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test values + float actual[7]; + for (int i = 0; i < 7; ++i) + { + actual[i] = data[i][0]; + } + return compareArray(expected, actual, 7); +} + +int basicCTRLTest(char** drefs, int ac) +{ + // Set control surfaces to known state. + float CTRL[6] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F }; + float expected[7] = { 0.0F, 0.0F, 0.0F, NAN, NAN, NAN, NAN }; + int result = doCTRLTest(drefs, CTRL, 3, ac, expected); + if (result < 0) + { + return -10000 + result; + } + + // Test control surfaces and set other values to known state. + expected[0] = CTRL[0] = 0.2F; + expected[1] = CTRL[1] = 0.1F; + expected[2] = CTRL[2] = 0.1F; + expected[3] = 0.8F; + expected[4] = 1.0F; + expected[5] = 0.5F; + result = doCTRLTest(drefs, CTRL, 6, ac, expected); + if (result < 0) + { + return -20000 + result; + } + + // Test other values and verify control surfaces unchanged. + CTRL[0] = -998; + CTRL[1] = -998; + CTRL[2] = -998; + expected[3] = CTRL[3] = 0.9F; + expected[4] = CTRL[4] = 0.0F; + expected[5] = CTRL[5] = 0.75F; + result = doCTRLTest(drefs, CTRL, 6, ac, expected); + if (result < 0) + { + return -30000 + result; + } +} + +int testCTRL_Player() +{ + char* drefs[] = + { + "sim/cockpit2/controls/yoke_pitch_ratio", + "sim/cockpit2/controls/yoke_roll_ratio", + "sim/cockpit2/controls/yoke_heading_ratio", + "sim/flightmodel/engine/ENGN_thro", + "sim/cockpit/switches/gear_handle_status", + "sim/flightmodel/controls/flaprqst", + "sim/flightmodel/controls/sbrkrqst" + }; + return basicCTRLTest(drefs, 0); +} + +int testCTRL_NonPlayer() +{ + char* drefs[] = + { + "sim/multiplayer/position/plane1_yolk_pitch", + "sim/multiplayer/position/plane1_yolk_roll", + "sim/multiplayer/position/plane1_yolk_yaw", + "sim/multiplayer/position/plane1_throttle", + "sim/multiplayer/position/plane1_gear_deploy", + "sim/multiplayer/position/plane1_flap_ratio", + "sim/multiplayer/position/plane1_sbrkrqst" + }; + return basicCTRLTest(drefs, 1); +} + +int testCTRL_Speedbrakes() +{ + char* drefs[] = + { + "sim/cockpit2/controls/yoke_pitch_ratio", + "sim/cockpit2/controls/yoke_roll_ratio", + "sim/cockpit2/controls/yoke_heading_ratio", + "sim/flightmodel/engine/ENGN_thro", + "sim/cockpit/switches/gear_handle_status", + "sim/flightmodel/controls/flaprqst", + "sim/flightmodel/controls/sbrkrqst" + }; + + // Arm + float CTRL[7] = { -998, -998, -998, -998, -998, -998, -0.5F }; + float expected[7] = { NAN, NAN, NAN, NAN, NAN, NAN, -0.5F }; + int result = doCTRLTest(drefs, CTRL, 7, 0, expected); + if (result < 0) + { + return -10000 + result; + } + + // Set to full + expected[6] = CTRL[6] = 1.5F; + result = doCTRLTest(drefs, CTRL, 7, 0, expected); + if (result < 0) + { + return -20000 + result; + } + + // Retract + expected[6] = CTRL[6] = 0.0F; + result = doCTRLTest(drefs, CTRL, 7, 0, expected); + if (result < 0) + { + return -30000 + result; + } +} +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/DataTests.h b/TestScripts/C Tests/DataTests.h new file mode 100644 index 0000000..2bf1564 --- /dev/null +++ b/TestScripts/C Tests/DataTests.h @@ -0,0 +1,62 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef DATATESTS_H +#define DATATESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int testDATA() +{ + // Initialize + int i, j; // Iterator + char* drefs[100] = + { + "sim/aircraft/parts/acf_gear_deploy" + }; + float* data[100]; // array for result of getDREFs + int sizes[100]; + float DATA[4][9]; // Array for sendDATA + XPCSocket sock = openUDP(IP); + + // Setup + for (int i = 0; i < 100; ++i) + { + data[i] = (float*)malloc(40 * sizeof(float)); + sizes[i] = 40; + } + for (i = 0; i < 4; i++) + { + for (j = 0; j < 9; j++) + { + data[i][j] = -998; + } + } + DATA[0][0] = 14; // Gear + DATA[0][1] = 1; + DATA[0][2] = 0; + + // Execution + sendDATA(sock, DATA, 1); + int result = getDREFs(sock, drefs, data, 1, sizes); + + // Close + closeUDP(sock); + + // Tests + if (result < 0)// Request 1 value + { + return -1; + } + if (sizes[0] != 10) + { + return -2; + } + if (*data[0] != data[0][1]) + { + return -3; + } + return 0; +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/DrefTests.h b/TestScripts/C Tests/DrefTests.h new file mode 100644 index 0000000..50338a7 --- /dev/null +++ b/TestScripts/C Tests/DrefTests.h @@ -0,0 +1,247 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef DREFTESTS_H +#define DREFTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int doGETDTest(char* drefs[], float* expected[], int count, int sizes[]) +{ + // Setup memory + int* asizes = (int*)malloc(sizeof(int) * count); + float** actual = (float**)malloc(sizeof(float*) * count); + for (int i = 0; i < count; ++i) + { + asizes[i] = sizes[i]; + actual[i] = (float*)malloc(sizeof(float) * sizes[i]); + } + + // Execute command + XPCSocket sock = openUDP(IP); + int result = getDREFs(sock, drefs, actual, count, asizes); + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test sizes and values + result = compareArrays(expected, sizes, actual, asizes, count); + for (int i = 0; i < count; ++i) + { + free(actual[i]); + } + free(actual); + free(asizes); + return result; +} + +int doDREFTest(char* drefs[], float* values[], float* expected[], int count, int sizes[]) +{ + // Setup memory + int* asizes = (int*)malloc(sizeof(int) * count); + float** actual = (float**)malloc(sizeof(float*) * count); + for (int i = 0; i < count; ++i) + { + asizes[i] = sizes[i]; + actual[i] = (float*)malloc(sizeof(float) * sizes[i]); + } + + // Execute command + XPCSocket sock = openUDP(IP); + int result = sendDREFs(sock, drefs, values, sizes, count); + if (result >= 0) + { + result = getDREFs(sock, drefs, actual, count, asizes); + } + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test sizes and values + result = compareArrays(expected, sizes, actual, asizes, count); + for (int i = 0; i < count; ++i) + { + free(actual[i]); + } + free(actual); + free(asizes); + return result; +} + +int testGETD_Basic() +{ + char* drefs[] = + { + "sim/cockpit/switches/gear_handle_status", //int + "sim/cockpit/autopilot/altitude", //float + "sim/aircraft/prop/acf_prop_type", //int[8] + "sim/cockpit2/switches/panel_brightness_ratio", //float[4] + "sim/aircraft/view/acf_tailnum", //byte[40] + "sim/flightmodel/position/elevation" //double + }; + int sizes[] = { 1, 1, 8, 4, 40, 1 }; + float* expected[6]; + for (int i = 0; i < 6; ++i) + { + expected[i] = (float*)malloc(sizeof(float) * sizes[i]); + for (int j = 0; j < sizes[i]; ++j) + { + expected[i][j] = NAN; + } + } + + return doGETDTest(drefs, expected, 6, sizes); +} + +int testGETD_TestFloat() +{ + char* dref = "sim/test/test_float"; + int size = 1; + float* expected[1]; + expected[0] = (float*)malloc(sizeof(float)); + expected[0][0] = 0.0F; + + int result = doGETDTest(&dref, &expected, 1, &size); + free(expected[0]); + return result; +} + +int testGETD_Types() +{ + char* drefs[] = + { + "sim/cockpit/switches/gear_handle_status", //int + "sim/cockpit/autopilot/altitude", //float + "sim/aircraft/prop/acf_prop_type", //int[8] + "sim/cockpit2/switches/panel_brightness_ratio", //float[4] + "sim/aircraft/view/acf_tailnum", //byte[40] + "sim/flightmodel/position/elevation" //double + }; + int sizes[] = { 1, 1, 8, 4, 40, 1 }; + float* data[6]; + for (int i = 0; i < 6; ++i) + { + data[i] = (float*)malloc(sizeof(float) * sizes[i]); + } + + // Execute command + XPCSocket sock = openUDP(IP); + int result = getDREFs(sock, drefs, data, 6, sizes); + closeUDP(sock); + + // Tests + if (result < 0) + { + return -1; + } + // Verify sizes + if (sizes[0] != 1 || sizes[1] != 1 || sizes[2] != 8 + || sizes[3] != 4 || sizes[4] != 40 || sizes[5] != 1) + { + return -2; + } + // Verify integer drefs are integers + if ((float)((int)data[0][0]) != data[0][0]) + { + return -3; + } + for (int i = 0; i < 8; ++i) + { + if ((float)((int)data[2][i]) != data[2][i]) + { + return -3; + } + } + for (int i = 0; i < 40; ++i) + { + if ((float)((char)data[4][i]) != data[4][i]) + { + return -3; + } + } + // Verify tail number has at least one valid character + if (data[4][0] <= 0 || data[4][0] > 127) + { + return -4; + } + return 0; +} + +int testDREF() +{ + char* drefs[] = + { + "sim/cockpit/switches/gear_handle_status", //int + "sim/cockpit/autopilot/altitude", //float + "sim/aircraft/prop/acf_prop_type", //int[8] + "sim/cockpit2/switches/panel_brightness_ratio", //float[4] + "sim/aircraft/view/acf_tailnum", //byte[40] + "sim/flightmodel/position/elevation" //double - Read only + }; + float* values[6]; + float* expected[6]; + int sizes[6]; + + // Setup + sizes[0] = 1; + values[0] = (float*)malloc(sizes[0] * sizeof(float)); + expected[0] = values[0]; + values[0][0] = 1; + + sizes[1] = 1; + values[1] = (float*)malloc(sizes[1] * sizeof(float)); + expected[1] = values[1]; + values[1][0] = 4000.0F; + + sizes[2] = 8; + values[2] = (float*)malloc(sizes[2] * sizeof(float)); + expected[2] = values[2]; + for (int i = 0; i < 8; ++i) + { + values[2][i] = 0; + } + + sizes[3] = 4; + values[3] = (float*)malloc(sizes[3] * sizeof(float)); + expected[3] = values[3]; + for (int i = 0; i < 4; ++i) + { + values[3][i] = 0.25F; + } + + sizes[4] = 40; + values[4] = (float*)malloc(sizes[4] * sizeof(float)); + expected[4] = (float*)malloc(sizes[4] * sizeof(float)); + memset(values[4], 0, sizes[4] * sizeof(float)); + values[4][0] = 78.0F; //N + values[4][1] = 55.0F; //7 + values[4][2] = 52.0F; //4 + values[4][3] = 56.0F; //8 + values[4][4] = 53.0F; //5 + values[4][5] = 89.0F; //Y + + expected[4][0] = 78.0F; //N + expected[4][1] = 55.0F; //7 + expected[4][2] = 52.0F; //4 + expected[4][3] = 56.0F; //8 + expected[4][4] = 53.0F; //5 + expected[4][5] = 89.0F; //Y + for (int i = 6; i < sizes[4]; ++i) + { + expected[4][i] = NAN; + } + + sizes[5] = 1; + values[5] = (float*)malloc(sizes[5] * sizeof(float)); + expected[5] = (float*)malloc(sizes[5] * sizeof(float)); + values[5][0] = 5000.0F; + expected[5][0] = NAN; + + return doDREFTest(drefs, values, expected, 6, sizes); +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/PosiTests.h b/TestScripts/C Tests/PosiTests.h new file mode 100644 index 0000000..dfc7e07 --- /dev/null +++ b/TestScripts/C Tests/PosiTests.h @@ -0,0 +1,120 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef POSITESTS_H +#define POSITESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int doPOSITest(char* drefs[7], float values[], int size, int ac, float expected[7]) +{ + float* data[7]; + int sizes[7]; + for (int i = 0; i < 7; ++i) + { + data[i] = (float*)malloc(sizeof(float) * 10); + sizes[i] = 10; + } + + // Execute command + XPCSocket sock = openUDP(IP); + pauseSim(sock, 1); + int result = sendPOSI(sock, values, size, ac); + if (result >= 0) + { + result = getDREFs(sock, drefs, data, 7, sizes); + } + pauseSim(sock, 0); + closeUDP(sock); + if (result < 0) + { + return -1; + } + + // Test values + float actual[7]; + for (int i = 0; i < 7; ++i) + { + actual[i] = data[i][0]; + } + return compareArray(expected, actual, 7); +} + +int basicPOSITest(char** drefs, int ac) +{ + // Set psoition and initial orientation + float POSI[7] = { 37.524F, -122.06899F, 2500, 0, 0, 0, 1 }; + float expected[7] = { 37.524F, -122.06899F, 2500, 0, 0, 0, 1 }; + int result = doPOSITest(drefs, POSI, 7, ac, expected); + if (result < 0) + { + return -10000 + result; + } + + // Set orientation + POSI[0] = -998.0F; + POSI[1] = -998.0F; + POSI[2] = -998.0F; + POSI[3] = 5.0F; + POSI[4] = -5.0F; + POSI[5] = 10.0F; + POSI[6] = 0; + + float *loc[3]; + for (int i = 0; i < 3; ++i) + { + loc[i] = (float*)malloc(sizeof(float)); + } + int sizes[3] = { 1, 1, 1 }; + XPCSocket sock = openUDP(IP); + pauseSim(sock, 1); + getDREFs(sock, drefs, loc, 3, sizes); + closeUDP(sock); + + expected[0] = loc[0][0]; + expected[1] = loc[1][0]; + expected[2] = loc[2][0]; + expected[3] = 5.0F; + expected[4] = -5.0F; + expected[5] = 10.0F; + expected[6] = 0.0F; + result = doPOSITest(drefs, POSI, 7, ac, expected); + if (result < 0) + { + return -20000 + result; + } +} + +int testPOSI_Player() +{ + char* drefs[] = + { + "sim/flightmodel/position/latitude", + "sim/flightmodel/position/longitude", + "sim/flightmodel/position/elevation", + "sim/flightmodel/position/theta", + "sim/flightmodel/position/phi", + "sim/flightmodel/position/psi", + "sim/cockpit/switches/gear_handle_status" + }; + return basicPOSITest(drefs, 0); +} + +int testPOSI_NonPlayer() +{ + char* drefs[] = + { + "sim/multiplayer/position/plane1_lat", + "sim/multiplayer/position/plane1_lon", + "sim/multiplayer/position/plane1_el", + "sim/multiplayer/position/plane1_the", + "sim/multiplayer/position/plane1_phi", + "sim/multiplayer/position/plane1_psi", + "sim/multiplayer/position/plane1_gear_deploy" + }; + return basicPOSITest(drefs, 1); +} + + + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/SimuTests.h b/TestScripts/C Tests/SimuTests.h new file mode 100644 index 0000000..1c807b7 --- /dev/null +++ b/TestScripts/C Tests/SimuTests.h @@ -0,0 +1,80 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef SIMUTESTS_H +#define SIMIUTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int doSIMUTest(int value, float expected) +{ + int size = 20; + float actual[20]; + char* dref = "sim/operation/override/override_planepath"; + + XPCSocket sock = openUDP(IP); + int result = pauseSim(sock, value); + if (result >= 0) + { + result = getDREF(sock, dref, &actual, &size); + } + closeUDP(sock); + if (result < 0) + { + return -1; + } + + for (int i = 0; i < 20; ++i) + { + if (!feq(actual[i], expected) && !isnan(expected)) + { + return -100 - i; + } + } +} + +int testSIMU_Basic() +{ + int result = doSIMUTest(0, 0); + if (result < 0) + { + return -1; + } + + result = doSIMUTest(1, 1); + if (result < 0) + { + return -2; + } + + result = doSIMUTest(0, 0); + if (result < 0) + { + return -3; + } + return 0; +} + +int testSIMU_Toggle() +{ + int result = doSIMUTest(0, 0); + if (result < 0) + { + return -1; + } + + result = doSIMUTest(2, 1); + if (result < 0) + { + return -1; + } + + result = doSIMUTest(2, 0); + if (result < 0) + { + return -3; + } + return 0; +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/Test.c b/TestScripts/C Tests/Test.c new file mode 100644 index 0000000..cce713c --- /dev/null +++ b/TestScripts/C Tests/Test.c @@ -0,0 +1,51 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#include "Test.h" + +int testFailed = 0; +int testPassed = 0; + +void runTest(int(*test)(), char* name) +{ + printf("Running test %s... ", name); + int result = test(); // Run Test + if (result == 0) + { + printf("PASSED\n"); + testPassed++; + } + else + { + printf("Test %s - FAILED\n\tError: %i\n", name, result); + testFailed++; + } +} + +int compareFloat(float expected, float actual) +{ + return feq(expected, actual) || isnan(expected) ? 0 : -1; +} + +int compareArray(float expected[], float actual[], int size) +{ + return compareArrays(&expected, &size, &actual, &size, 1); +} + +int compareArrays(float* expected[], int esizes[], float* actual[], int asizes[], int count) +{ + for (int i = 0; i < count; ++i) + { + if (esizes[i] != asizes[i]) + { + return -100 - i; + } + for (int j = 0; j < esizes[i]; ++j) + { + if (!feq(actual[i][j], expected[i][j]) && !isnan(expected[i][j])) + { + return -1000 - i * 100 - j; + } + } + } + return 0; +} \ No newline at end of file diff --git a/TestScripts/C Tests/Test.h b/TestScripts/C Tests/Test.h new file mode 100644 index 0000000..c05a5eb --- /dev/null +++ b/TestScripts/C Tests/Test.h @@ -0,0 +1,25 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef TESTRUNNER_H +#define TESTRUNNER_H + +#include +#include +#include +#include +#include + +#define feq(x, y) (fabs(x - y) < 1e-4) + +#define IP "127.0.0.1" + +extern int testFailed; +extern int testPassed; + +void runTest(int(*test)(), char* name); + +int compareFloat(float expected, float actual); +int compareArray(float expected[], float actual[], int size); +int compareArrays(float* expected[], int esizes[], float* actual[], int asizes[], int count); + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/TextTests.h b/TestScripts/C Tests/TextTests.h new file mode 100644 index 0000000..badf16e --- /dev/null +++ b/TestScripts/C Tests/TextTests.h @@ -0,0 +1,32 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef TEXTTESTS_H +#define TEXTTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int testTEXT() +{ + // Setup + XPCSocket sendPort = openUDP(IP); + int x = 100; + int y = 700; + char* msg = "This is an X-Plane Connect test message.\nThis should be a new line.\r\nThat will be parsed as two line breaks."; + + // Test + sendTEXT(sendPort, msg, x, y); + // NOTE: Manually verify that msg appears on the screen in X-Plane! + + sendTEXT(sendPort, "Another test message", x, y); + // NOTE: Manually verify that msg appears on the screen and that no part of the previous + // message is visible. + + sendTEXT(sendPort, NULL, -1, -1); + + // Cleanup + closeUDP(sendPort); + return 0; +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/UDPTests.h b/TestScripts/C Tests/UDPTests.h new file mode 100644 index 0000000..3c3d3fa --- /dev/null +++ b/TestScripts/C Tests/UDPTests.h @@ -0,0 +1,55 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef UDPTESTS_H +#define UDPTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int testOpen() +{ + XPCSocket sock = openUDP("localhost"); + int result = strncmp(sock.xpIP, "127.0.0.1", 16); + closeUDP(sock); + return result; +} + +int testClose() +{ + XPCSocket sendPort = aopenUDP(IP, 49009, 49063); + closeUDP(sendPort); + sendPort = aopenUDP(IP, 49009, 49063); + closeUDP(sendPort); + return 0; +} + +int testCONN() +{ + // Initialize + char* drefs[] = + { + "sim/cockpit/switches/gear_handle_status" + }; + float data[1]; + int size = 1; + XPCSocket sock = openUDP(IP); +#if (__APPLE__ || __linux) + usleep(0); +#endif + + // Execution + setCONN(&sock, 49055); + int result = getDREF(sock, drefs[0], data, &size); + + // Close + closeUDP(sock); + + // Test + if (result < 0)// No data received + { + return -1; + } + return 0; +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/WyptTests.h b/TestScripts/C Tests/WyptTests.h new file mode 100644 index 0000000..fce3c2c --- /dev/null +++ b/TestScripts/C Tests/WyptTests.h @@ -0,0 +1,42 @@ +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#ifndef WYPTTESTS_H +#define WYPTTESTS_H + +#include "Test.h" +#include "xplaneConnect.h" + +int testWYPT() +{ + // Setup + XPCSocket sock = openUDP(IP); + float points[] = + { + 37.5245F, -122.06899F, 2500, + 37.455397F, -122.050037F, 2500, + 37.469567F, -122.051411F, 2500, + 37.479376F, -122.060509F, 2300, + 37.482237F, -122.076130F, 2100, + 37.474881F, -122.087288F, 1900, + 37.467660F, -122.079391F, 1700, + 37.466298F, -122.090549F, 1500, + 37.362562F, -122.039223F, 1000, + 37.361448F, -122.034416F, 1000, + 37.361994F, -122.026348F, 1000, + 37.365541F, -122.022572F, 1000, + 37.373727F, -122.024803F, 1000, + 37.403869F, -122.041283F, 50, + 37.418544F, -122.049222F, 6 + }; + + // Test + sendWYPT(sock, XPC_WYPT_CLR, NULL, 0); + sendWYPT(sock, XPC_WYPT_ADD, points, 15); + // NOTE: Visually ensure waypoints are added in the sim + + // Cleanup + closeUDP(sock); + return 0; +} + +#endif \ No newline at end of file diff --git a/TestScripts/C Tests/main.c b/TestScripts/C Tests/main.c index 5f84eb8..ae01451 100644 --- a/TestScripts/C Tests/main.c +++ b/TestScripts/C Tests/main.c @@ -1,1029 +1,14 @@ -// -// main.cpp -// XPC Tests -// -// Created by Chris Teubert on 11/25/14. -// Copyright (c) 2014 Chris Teubert. All rights reserved. -// - -#include -#include -#include -#include -#include -#include "xplaneConnect.h" - -#define IP "127.0.0.1" - -int testFailed = 0; -int testPassed = 0; - -void runTest(int (*test)(), char* name) -{ - int result = test(); // Run Test - printf("Test %i: %s - ", testPassed + testFailed + 1, name); - if (result == 0) - { - printf("PASSED\n"); - testPassed++; - } - else - { - printf("FAILED\n\tError: %i\n", result); - testFailed++; - } -} - -int openTest() // openUDP Test -{ - XPCSocket sock = openUDP("localhost"); - int result = strncmp(sock.xpIP, "127.0.0.1", 16); - closeUDP(sock); - return result; -} - -int closeTest() // closeUDP test -{ - XPCSocket sendPort = aopenUDP(IP, 49009, 49063); - closeUDP(sendPort); - sendPort = aopenUDP(IP, 49009, 49063); - closeUDP(sendPort); - return 0; -} - -int sendTEXTTest() -{ - // Setup - XPCSocket sendPort = openUDP(IP); - int x = 100; - int y = 700; - char* msg = "This is an X-Plane Connect test message.\nThis should be a new line.\r\nThat will be parsed as two line breaks."; - - // Test - sendTEXT(sendPort, msg, x, y); - // NOTE: Manually verify that msg appears on the screen in X-Plane! - - sendTEXT(sendPort, "Another test message", x, y); - // NOTE: Manually verify that msg appears on the screen and that no part of the previous - // message is visible. - - sendTEXT(sendPort, NULL, -1, -1); - - // Cleanup - closeUDP(sendPort); - return 0; -} - -int getDREFTest() // Request DREF Test (Required for next tests) -{ - // Initialization - // Get one DREF of each type (int, float, int[], float[], double, byte[]) - #define GETD_COUNT 6 - char* drefs[GETD_COUNT] = - { - "sim/cockpit/switches/gear_handle_status", //int - "sim/cockpit/autopilot/altitude", //float - "sim/aircraft/prop/acf_prop_type", //int[8] - "sim/cockpit2/switches/panel_brightness_ratio", //float[4] - "sim/aircraft/view/acf_tailnum", //byte[40] - "sim/flightmodel/position/elevation" //double - }; - float* data[GETD_COUNT]; - int sizes[GETD_COUNT]; - XPCSocket sock = openUDP(IP); - - // Setup - for (int i = 0; i < GETD_COUNT; ++i) - { - data[i] = (float*)malloc(256 * sizeof(float)); - sizes[i] = 256; - } - - // Execution - int result = getDREFs(sock, drefs, data, GETD_COUNT, sizes); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - // Verify sizes - if (sizes[0] != 1 || sizes[1] != 1 || sizes[2] != 8 - || sizes[3] != 4 || sizes[4] != 40 || sizes[5] != 1) - { - return -2; - } - // Verify integer drefs are integers - if ((float)((int)data[0][0]) != data[0][0]) - { - return -3; - } - for (int i = 0; i < 8; ++i) - { - if ((float)((int)data[2][i]) != data[2][i]) - { - return -3; - } - } - for (int i = 0; i < 40; ++i) - { - if ((float)((char)data[4][i]) != data[4][i]) - { - return -3; - } - } - // Verify tail number has at least one valid character - if (data[4][0] <= 0 || data[4][0] > 127) - { - return -4; - } - return 0; -} - -int sendDREFTest() // sendDREF test -{ - // Initialization - // Set one DREF of each type (int, float, int[], float[], double, byte[]) - // Also set one read-only to make sure it fails - #define DREF_COUNT 6 - char* drefs[DREF_COUNT] = - { - "sim/cockpit/switches/gear_handle_status", //int - "sim/cockpit/autopilot/altitude", //float - "sim/aircraft/prop/acf_prop_type", //int[8] - "sim/cockpit2/switches/panel_brightness_ratio", //float[4] - "sim/aircraft/view/acf_tailnum", //byte[40] - "sim/flightmodel/position/elevation" //double - Read only - }; - float* data[DREF_COUNT]; - int sizes[DREF_COUNT]; - float* values[DREF_COUNT]; - XPCSocket sock = openUDP(IP); - - // Setup - sizes[0] = 1; - values[0] = (float*)malloc(sizes[0] * sizeof(float)); - values[0][0] = 1; - - sizes[1] = 1; - values[1] = (float*)malloc(sizes[1] * sizeof(float)); - values[1][0] = 4000.0F; - - sizes[2] = 8; - values[2] = (float*)malloc(sizes[2] * sizeof(float)); - for (int i = 0; i < 8; ++i) - { - values[2][i] = 0; - } - - sizes[3] = 4; - values[3] = (float*)malloc(sizes[3] * sizeof(float)); - for (int i = 0; i < 4; ++i) - { - values[3][i] = 0.25F; - } - - sizes[4] = 40; - values[4] = (float*)malloc(sizes[4] * sizeof(float)); - memset(values[4], 0, sizes[4] * sizeof(float)); - values[4][0] = 78.0F; //N - values[4][1] = 55.0F; //7 - values[4][2] = 52.0F; //4 - values[4][3] = 56.0F; //8 - values[4][4] = 53.0F; //5 - values[4][5] = 89.0F; //Y - - sizes[5] = 1; - values[5] = (float*)malloc(sizes[5] * sizeof(float)); - values[5][0] = 5000.0F; - - // Execution - for (int i = 0; i < DREF_COUNT; ++i) - { - sendDREF(sock, drefs[i], values[i], sizes[i]); - data[i] = (float*)malloc(256 * sizeof(float)); - sizes[i] = 256; - } - int result = getDREFs(sock, drefs, data, DREF_COUNT, sizes); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - // Verify gear handle was set - if (sizes[0] != 1 || data[0][0] != 1) - { - return -2; - } - // Verify autopilot altitude was set - if (sizes[1] != 1 || data[1][0] != 4000.0F) - { - return -3; - } - // Verify prop type was set - if (sizes[2] != 8) - { - return -4; - } - for (int i = 0; i < 8; ++i) - { - if (data[2][i] != values[2][i]) - { - return -4; - } - } - // Verify panel brightness was set - if (sizes[3] != 4) - { - return -5; - } - for (int i = 0; i < 4; ++i) - { - if (data[3][i] != values[3][i]) - { - return -5; - } - } - // Verify tail number was set - for (int i = 0; i < 6; ++i) - { - if (data[4][i] != values[4][i]) - { - return -6; - } - } - // Verify aircraft elevation was NOT set - if (sizes[5] != 1 || data[5][0] == 5000.0F) - { - return -7; - } - return 0; -} - -int sendDATATest() // sendDATA test -{ - // Initialize - int i,j; // Iterator - char* drefs[100] = - { - "sim/aircraft/parts/acf_gear_deploy" - }; - float* data[100]; // array for result of getDREFs - int sizes[100]; - float DATA[4][9]; // Array for sendDATA - XPCSocket sock = openUDP(IP); - - // Setup - for (int i = 0; i < 100; ++i) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - for (i = 0; i < 4; i++) - { - for (j = 0; j < 9; j++) - { - data[i][j] = -998; - } - } - DATA[0][0] = 14; // Gear - DATA[0][1] = 1; - DATA[0][2] = 0; - - // Execution - sendDATA(sock, DATA, 1); - int result = getDREFs(sock, drefs, data, 1, sizes); - - // Close - closeUDP(sock); - - // Tests - if ( result < 0 )// Request 1 value - { - return -1; - } - if (sizes[0] != 10) - { - return -2; - } - if (*data[0] != data[0][1]) - { - return -3; - } - return 0; -} - -int psendCTRLTest() // sendCTRL test -{ - // Initialize - char* drefs[100] = - { - "sim/cockpit2/controls/yoke_pitch_ratio", - "sim/cockpit2/controls/yoke_roll_ratio", - "sim/cockpit2/controls/yoke_heading_ratio", - "sim/flightmodel/engine/ENGN_thro", - "sim/cockpit/switches/gear_handle_status", - "sim/flightmodel/controls/flaprqst" - }; - float* data[100]; - int sizes[100]; - float CTRL[6] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F }; - XPCSocket sock = openUDP(IP); - - // Setup - for (int i = 0; i < 100; i++) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - - // Execute 1 - // 0 pitch, roll, yaw - sendCTRL(sock, CTRL, 3, 0); - int result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - for (int i = 0; i < 3; i++) - { - if (fabs(data[i][0] - CTRL[i]) > 1e-4) - { - return -i - 11; - } - } - - sock = openUDP(IP); - // Execute 2 - // Set non-zero pitch, roll, & yaw. Also set throttle, gear, and flaps - CTRL[0] = 0.2F; - CTRL[1] = 0.1F; - CTRL[2] = 0.1F; - sendCTRL(sock, CTRL, 6, 0); - result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -2; - } - for (int i = 0; i < 6; i++) - { - if (fabs(data[i][0] - CTRL[i]) > 1e-4) - { - return -i - 21; - } - } - - sock = openUDP(IP); - // Execute 3 - // Set non-zero pitch, roll, & yaw. Also set throttle, gear, and flaps - float expected[] = { data[0][0], data[1][0], data[2][0], CTRL[3], CTRL[4], CTRL[5] }; - CTRL[0] = -998.0F; - CTRL[1] = -998.0F; - CTRL[2] = -998.0F; - sendCTRL(sock, CTRL, 6, 0); - result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -3; - } - for (int i = 0; i < 6; i++) - { - if (fabs(data[i][0] - expected[i]) > 1e-2) - { - return -i - 31; - } - } - - return 0; -} - -int sendCTRLTest() -{ - // Initialize - char* drefs[100] = - { - "sim/multiplayer/position/plane1_yolk_pitch", - "sim/multiplayer/position/plane1_yolk_roll", - "sim/multiplayer/position/plane1_yolk_yaw", - "sim/multiplayer/position/plane1_throttle", - "sim/multiplayer/position/plane1_gear_deploy", - "sim/multiplayer/position/plane1_flap_ratio", - }; - float* data[100]; - int sizes[100]; - float CTRL[6] = { 0.0F, 0.0F, 0.0F, 0.8F, 1.0F, 0.5F }; - XPCSocket sock = openUDP(IP); - - // Setup - for (int i = 0; i < 100; i++) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - - // Execute 1 - // 0 pitch, roll, yaw - sendCTRL(sock, CTRL, 3, 1); - int result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - for (int i = 0; i < 3; i++) - { - if (fabs(data[i][0] - CTRL[i]) > 1e-4) - { - return -i - 11; - } - } - - sock = openUDP(IP); - // Execute 2 - // Set non-zero pitch, roll, & yaw. Also set throttle, gear, and flaps - CTRL[0] = 0.2F; - CTRL[1] = 0.1F; - CTRL[2] = 0.1F; - sendCTRL(sock, CTRL, 6, 1); - result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -2; - } - for (int i = 0; i < 6; i++) - { - if (fabs(data[i][0] - CTRL[i]) > 1e-4) - { - return -i - 21; - } - } - - sock = openUDP(IP); - // Execute 3 - // Set non-zero pitch, roll, & yaw. Also set throttle, gear, and flaps - float expected[] = { data[0][0], data[1][0], data[2][0], CTRL[3], CTRL[4], CTRL[5] }; - CTRL[0] = -998.0F; - CTRL[1] = -998.0F; - CTRL[2] = -998.0F; - sendCTRL(sock, CTRL, 6, 1); - result = getDREFs(sock, drefs, data, 6, sizes); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -3; - } - for (int i = 0; i < 6; i++) - { - if (fabs(data[i][0] - expected[i]) > 1e-2) - { - return -i - 31; - } - } - - return 0; -} - - -int sendCTRLspeedbrakeTest() // sendCTRL test -{ - // Initialize - char* dref = "sim/flightmodel/controls/sbrkrqst"; - float data; - int size = 1; - float CTRL[7] = { -998.0F, -998.0F, -998.0F, -998.0F, -998.0F, -998.0F, -0.5F }; - XPCSocket sock = openUDP(IP); - - // Execute 1 - // Arm speedbrakes - sendCTRL(sock, CTRL, 7, 0); - int result = getDREF(sock, dref, &data, &size); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - if (fabs(data - CTRL[6]) > 1e-4) - { - return -11; - } - - sock = openUDP(IP); - // Execute 2 - // Set full speedbrakes - CTRL[6] = 1.0F; - sendCTRL(sock, CTRL, 7, 0); - result = getDREF(sock, dref, &data, &size); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -2; - } - if (fabs(data - CTRL[6]) > 1e-4) - { - return -21; - } - - sock = openUDP(IP); - // Execute 3 - // Retract speedbrakes - CTRL[6] = 0.0F; - sendCTRL(sock, CTRL, 7, 0); - result = getDREF(sock, dref, &data, &size); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -3; - } - if (fabs(data - CTRL[6]) > 1e-4) - { - return -31; - } - - sock = openUDP(IP); - // Execute 4 - // Verify -998 does not change value. - CTRL[6] = -998.0F; - sendCTRL(sock, CTRL, 7, 0); - result = getDREF(sock, dref, &data, &size); - - // Close socket - closeUDP(sock); - - // Tests - if (result < 0) - { - return -4; - } - if (data> 1e-4) - { - return -41; - } - - return 0; -} - -int psendPOSITest() // sendPOSI test -{ - // Initialization - int i; // Iterator - char* drefs[100] = - { - "sim/flightmodel/position/latitude", - "sim/flightmodel/position/longitude", - "sim/flightmodel/position/elevation", - "sim/flightmodel/position/theta", - "sim/flightmodel/position/phi", - "sim/flightmodel/position/psi", - "sim/cockpit/switches/gear_handle_status" - }; - float* data[100]; - int sizes[100]; - float POSI[7] = { 37.524F, -122.06899F, 2500, 0, 0, 0, 1 }; - XPCSocket sock = openUDP(IP); - - // Setup - for (i = 0; i < 100; i++) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - - // Execution 1 - pauseSim(sock, 1); - sendPOSI(sock, POSI, 7, 0); - int result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - for (i = 0; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 11; - } - } - - // Setup 2 - sock = openUDP(IP); - POSI[0] = -998.0F; - POSI[1] = -998.0F; - POSI[2] = -998.0F; - POSI[3] = 5.0F; - POSI[4] = -5.0F; - POSI[5] = 10.0F; - POSI[6] = 0; - - // Execution 2 - pauseSim(sock, 1); - float *loc[3]; - for(int i = 0; i < 3; ++i) - { - loc[i] = (float*)malloc(sizeof(float)); - } - getDREFs(sock, drefs, &loc, 3, sizes); - sendPOSI(sock, POSI, 7, 0); - result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -2; - } - // Compare position to make sure they weren't set - for (int i = 0; i < 3; ++i) - { - // Note: Because the sim was paused when both of these were read, we really do expect *exactly* - // the same value even though we are comparing floats. - if (data[i][0] != loc[i][0]) - { - return -i - 21; - } - } - // Compare everything else. - for (i = 3; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 21; - } - } - - // Setup 3 - sock = openUDP(IP); - POSI[0] = 37.524F; - POSI[1] = -122.06899F; - POSI[2] = 20000; - POSI[3] = 15.0F; - POSI[4] = -25.0F; - POSI[5] = -10.0F; - POSI[6] = 1; - - // Execution 3 - pauseSim(sock, 1); - sendPOSI(sock, POSI, 3, 0); - result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -3; - } - // Compare position to make sure it was set. - for (int i = 0; i < 3; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 31; - } - } - // Compare everything else to make sure it *wasn't*. - for (i = 3; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) < 1) - { - return -i - 31; - } - } - - return 0; -} - -int sendPOSITest() // sendPOSI test -{ - // Initialization - int i; // Iterator - char* drefs[100] = - { - "sim/multiplayer/position/plane1_lat", - "sim/multiplayer/position/plane1_lon", - "sim/multiplayer/position/plane1_el", - "sim/multiplayer/position/plane1_the", - "sim/multiplayer/position/plane1_phi", - "sim/multiplayer/position/plane1_psi", - "sim/multiplayer/position/plane1_gear_deploy" - }; - float* data[100]; - int sizes[100]; - float POSI[7] = { 37.524F, -122.06899F, 2500, 0, 0, 0, 1 }; - XPCSocket sock = openUDP(IP); - - // Setup - for (i = 0; i < 100; i++) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - - // Execution 1 - pauseSim(sock, 1); - sendPOSI(sock, POSI, 7, 1); - int result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -1; - } - for (i = 0; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 11; - } - } - - // Setup 2 - sock = openUDP(IP); - POSI[0] = -998.0F; - POSI[1] = -998.0F; - POSI[2] = -998.0F; - POSI[3] = 5.0F; - POSI[4] = -5.0F; - POSI[5] = 10.0F; - POSI[6] = 0; - - // Execution 2 - pauseSim(sock, 1); - float* loc[3]; - for(int i = 0; i < 3; ++i) - { - loc[i] = (float*)malloc(sizeof(float)); - } - getDREFs(sock, drefs, loc, 3, sizes); - sendPOSI(sock, POSI, 7, 1); - result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -2; - } - // Compare position to make sure they weren't set - for (int i = 0; i < 3; ++i) - { - // Note: Because the sim was paused when both of these were read, we really do expect *exactly* - // the same value even though we are comparing floats. - if (data[i][0] != loc[i][0]) - { - return -i - 21; - } - } - // Compare everything else. - for (i = 3; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 21; - } - } - - // Setup 3 - sock = openUDP(IP); - POSI[0] = 37.524F; - POSI[1] = -122.06899; - POSI[2] = 20000; - POSI[3] = 15.0F; - POSI[4] = -25.0F; - POSI[5] = -10.0F; - POSI[6] = 1; - - // Execution 2 - pauseSim(sock, 1); - sendPOSI(sock, POSI, 3, 1); - result = getDREFs(sock, drefs, data, 7, sizes); - pauseSim(sock, 0); - - // Close - closeUDP(sock); - - // Tests - if (result < 0) - { - return -3; - } - // Compare position to make sure it was set. - for (int i = 0; i < 3; ++i) - { - if (fabs(data[i][0] - POSI[i]) > 1e-4) - { - return -i - 31; - } - } - // Compare everything else to make sure it *wasn't*. - for (i = 3; i < 7; ++i) - { - if (fabs(data[i][0] - POSI[i]) < 1) - { - return -i - 31; - } - } - - return 0; -} - -int sendWYPTTest() -{ - // Setup - XPCSocket sock = openUDP(IP); - float points[] = - { - 37.5245F, -122.06899F, 2500, - 37.455397F, -122.050037F, 2500, - 37.469567F, -122.051411F, 2500, - 37.479376F, -122.060509F, 2300, - 37.482237F, -122.076130F, 2100, - 37.474881F, -122.087288F, 1900, - 37.467660F, -122.079391F, 1700, - 37.466298F, -122.090549F, 1500, - 37.362562F, -122.039223F, 1000, - 37.361448F, -122.034416F, 1000, - 37.361994F, -122.026348F, 1000, - 37.365541F, -122.022572F, 1000, - 37.373727F, -122.024803F, 1000, - 37.403869F, -122.041283F, 50, - 37.418544F, -122.049222F, 6 - }; - - // Test - sendWYPT(sock, XPC_WYPT_CLR, NULL, 0); - sendWYPT(sock, XPC_WYPT_ADD, points, 15); - // NOTE: Visually ensure waypoints are added in the sim - - // Cleanup - closeUDP(sock); - return 0; -} - -int pauseTest() // pauseSim test -{ - // Setup - // Note: Always run this test to the end so that the sim ends up unpaused in the - // case where commands are working but reading results isn't. - int result = 0; - char* dref = "sim/operation/override/override_planepath"; - int size = 20; - float data[20]; - XPCSocket sock = openUDP(IP); - - // Execute - pauseSim(sock, 0); - result = getDREF(sock, dref, data, &size); - - // Test - if (result < 0) - { - result = -1; - } - else if (data[0] != 0) - { - result = -2; - } - - if (result == 0) - { - // Execute 2 - pauseSim(sock, 2); - result = getDREF(sock, dref, data, &size); - - // Test 2 - if (result < 0) - { - result = -3; - } - else if (data[0] != 1) - { - result = -4; - } - } - - if (result == 0) - { - // Execute 3 - pauseSim(sock, 0); - result = getDREF(sock, dref, data, &size); - - // Test 2 - if (result < 0) - { - result = -5; - } - else if (data[0] != 0) - { - result = -6; - } - } - - // Close - closeUDP(sock); - - return result; -} - -int connTest() // setConn test -{ - // Initialize - char* drefs[100] = - { - "sim/cockpit/switches/gear_handle_status" - }; - float* data[100]; - int sizes[100]; - XPCSocket sock = openUDP(IP); -#if (__APPLE__ || __linux) - usleep(0); -#endif - - // Setup - for (int i = 0; i < 100; ++i) - { - data[i] = (float*)malloc(40 * sizeof(float)); - sizes[i] = 40; - } - - // Execution - setCONN(&sock, 49055); - int result = getDREF(sock, drefs[0], data[0], sizes); - - // Close - closeUDP(sock); - - // Test - if ( result < 0 )// No data received - { - return -1; - } - return 0; -} +//Copyright (c) 2013-2015 United States Government as represented by the Administrator of the +//National Aeronautics and Space Administration. All Rights Reserved. +#include "Test.h" +#include "UDPTests.h" +#include "DrefTests.h" +#include "SimuTests.h" +#include "CtrlTests.h" +#include "PosiTests.h" +#include "DataTests.h" +#include "TextTests.h" +#include "WyptTests.h" int main(int argc, const char * argv[]) { @@ -1035,22 +20,36 @@ int main(int argc, const char * argv[]) printf("(Mac) \n"); #elif (__linux) printf("(Linux) \n"); +#else + printf("(Unable to determine operating system) \n") #endif - runTest(openTest, "open"); - runTest(closeTest, "close"); - runTest(pauseTest, "SIMU"); - runTest(getDREFTest, "GETD"); - runTest(sendDREFTest, "DREF"); - runTest(sendDATATest, "DATA"); - runTest(sendCTRLTest, "CTRL"); - runTest(psendCTRLTest, "CTRL (player)"); - runTest(sendCTRLspeedbrakeTest, "CTRL (speedbrake)"); - runTest(sendPOSITest, "POSI"); - runTest(psendPOSITest, "POSI (player)"); - runTest(sendWYPTTest, "WYPT"); - runTest(sendTEXTTest, "TEXT"); - runTest(connTest, "CONN"); + // Basic Networking + runTest(testOpen, "open"); + runTest(testClose, "close"); + // Datarefs + runTest(testGETD_Basic, "GETD"); + runTest(testGETD_Types, "GETD (types)"); + runTest(testGETD_TestFloat, "GETD (test float)"); + runTest(testDREF, "DREF"); + // Pause + runTest(testSIMU_Basic, "SIMU"); + runTest(testSIMU_Toggle, "SIMU (toggle)"); + // CTRL + runTest(testCTRL_Player, "CTRL (player)"); + runTest(testCTRL_NonPlayer, "CTRL (non-player)"); + runTest(testCTRL_Speedbrakes, "CTRL (speedbrakes)"); + // POSI + runTest(testPOSI_Player, "POSI (player)"); + runTest(testPOSI_NonPlayer, "POSI (non-player)"); + // Data + runTest(testDATA, "DATA"); + // Text + runTest(testTEXT, "TEXT"); + // Waypoints + runTest(testWYPT, "WYPT"); + // setConn + runTest(testCONN, "CONN"); printf( "----------------\nTest Summary\n\tFailed: %i\n\tPassed: %i\n", testFailed, testPassed ); From f2e64a30f2fe5f639ed620a8493b532456a3d4eb Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Wed, 6 May 2015 15:30:49 -0700 Subject: [PATCH 4/7] Added support for sending multiple datarefs to the C client. --- C/src/xplaneConnect.c | 60 ++++++++++++++++++++++++++----------------- C/src/xplaneConnect.h | 24 +++++++++++++---- 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/C/src/xplaneConnect.c b/C/src/xplaneConnect.c index 98be7f5..74b098d 100755 --- a/C/src/xplaneConnect.c +++ b/C/src/xplaneConnect.c @@ -366,32 +366,46 @@ int readDATA(XPCSocket sock, float data[][9], int rows) /*****************************************************************************/ int sendDREF(XPCSocket sock, const char* dref, float value[], int size) { - // Setup command - // 5 byte header + max 255 char dref name + max 255 values * 4 bytes per value = 1279 - unsigned char buffer[1279] = "DREF"; - int drefLen = strnlen(dref, 256); - if (drefLen > 255) - { - printError("setDREF", "dref length is too long. Must be less than 256 characters."); - return -1; - } - if (size > 255) - { - printError("setDREF", "size is too big. Must be less than 256."); - return -2; - } - int len = 7 + drefLen + size * sizeof(float); - - // Copy dref to buffer - buffer[5] = (unsigned char)drefLen; - memcpy(buffer + 6, dref, drefLen); + return sendDREFs(sock, &dref, &value, &size, 1); +} - // Copy values to buffer - buffer[6 + drefLen] = (unsigned char)size; - memcpy(buffer + 7 + drefLen, value, size * sizeof(float)); +int sendDREFs(XPCSocket sock, const char* drefs[], float* values[], int sizes[], int count) +{ + // Setup command + // Max size is technically unlimited. + unsigned char buffer[65536] = "DREF"; + int pos = 5; + for (int i = 0; i < count; ++i) + { + int drefLen = strnlen(drefs[i], 256); + if (pos + drefLen + sizes[i] * 4 + 2 > 65536) + { + printError("sendDREF", "About to overrun the send buffer!"); + return -4; + } + if (drefLen > 255) + { + printError("sendDREF", "dref %d is too long. Must be less than 256 characters.", i); + return -1; + } + if (sizes[i] > 255) + { + printError("sendDREF", "size %d is too big. Must be less than 256.", i); + return -2; + } + // Copy dref to buffer + buffer[pos++] = (unsigned char)drefLen; + memcpy(buffer + pos, drefs[i], drefLen); + pos += drefLen; + + // Copy values to buffer + buffer[pos++] = (unsigned char)sizes[i]; + memcpy(buffer + pos, values[i], sizes[i] * sizeof(float)); + pos += sizes[i] * sizeof(float); + } // Send command - if (sendUDP(sock, buffer, len) < 0) + if (sendUDP(sock, buffer, pos) < 0) { printError("setDREF", "Failed to send command"); return -3; diff --git a/C/src/xplaneConnect.h b/C/src/xplaneConnect.h index abcfd1f..391ec54 100644 --- a/C/src/xplaneConnect.h +++ b/C/src/xplaneConnect.h @@ -127,13 +127,27 @@ int sendDATA(XPCSocket sock, float data[][9], int rows); /// http://www.xsquawkbox.net/xpsdk/docs/DataRefs.html. The size of values should match /// the size given on that page. XPC currently sends all values as floats regardless of /// the type described on the wiki. This doesn't cause any data loss for most datarefs. -/// \param sock The socket to use to send the command. -/// \param dref The name of the dataref to set. -/// \param values An array of values representing the data to set. -/// \param size The number of elements in values. -/// \returns 0 if successful, otherwise a negative value. +/// \param sock The socket to use to send the command. +/// \param dref The name of the dataref to set. +/// \param value An array of values representing the data to set. +/// \param size The number of elements in values. +/// \returns 0 if successful, otherwise a negative value. int sendDREF(XPCSocket sock, const char* dref, float value[], int size); +/// Sets the specified datarefs to the specified values. +/// +/// \details dref names and their associated data types can be found on the XPSDK wiki at +/// http://www.xsquawkbox.net/xpsdk/docs/DataRefs.html. The size of values should match +/// the size given on that page. XPC currently sends all values as floats regardless of +/// the type described on the wiki. This doesn't cause any data loss for most datarefs. +/// \param sock The socket to use to send the command. +/// \param drefs The names of the datarefs to set. +/// \param values A multidimensional array containing the values for each dataref to set. +/// \param sizes The number of elements in each array in values +/// \param count The number of datarefs being set. +/// \returns 0 if successful, otherwise a negative value. +int sendDREFs(XPCSocket sock, const char* drefs[], float* values[], int sizes[], int count); + /// Gets the value of the specified dataref. /// /// \details dref names and their associated data types can be found on the XPSDK wiki at From 5717b0f67a2f4e89cd2d2bab81aeb1a7484415c5 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Thu, 7 May 2015 14:11:56 -0700 Subject: [PATCH 5/7] Added support for sending multiple datarefs to the Python client and fixed setCONN bug. --- Python/src/xpc.py | 62 ++++++++++++++++++++++--------- TestScripts/Python Tests/Tests.py | 31 ++++++++++++++++ 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/Python/src/xpc.py b/Python/src/xpc.py index 13d3bbd..32b4d3c 100644 --- a/Python/src/xpc.py +++ b/Python/src/xpc.py @@ -79,8 +79,20 @@ class XPlaneConnect(object): if port < 0 or port > 65535: raise ValueError("The specified port is not a valid port number.") + #Send command buffer = struct.pack("<4sxH", "CONN", port) self.sendUDP(buffer) + + #Rebind socket + clientAddr = ("0.0.0.0", port) + 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 + buffer = self.socket.recv(1024) def pauseSim(self, pause): '''Pauses or un-pauses the physics simulation engine in X-Plane. @@ -208,31 +220,45 @@ class XPlaneConnect(object): # Send self.sendUDP(buffer) - # DREF Manipulation + # DREF Manipulation def sendDREF(self, dref, values): '''Sets the specified dataref to the specified value. Args: - dref: The name of the dataref to set. + dref: The name of the datarefs to set. values: Either a scalar value or a sequence of values. ''' - # Preconditions - if len(dref) == 0 or len(dref) > 255: - raise ValueError("dref must be a non-empty string less than 256 characters.") - if values == None: - raise ValueError("values must be a scalar or sequence of floats.") + self.sendDREFs([dref], [values]) + + def sendDREFs(self, drefs, 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.") + + buffer = struct.pack("<4sx", "DREF") + for i in range(len(drefs)): + dref = drefs[i] + value = values[i] + # Preconditions + if len(dref) == 0 or len(dref) > 255: + raise ValueError("dref must be a non-empty string less than 256 characters.") + if value == None: + raise ValueError("value must be a scalar or sequence of floats.") - # Pack message - fmt = "" - buffer = None - if hasattr(values, "__len__"): - if len(values) > 255: - raise ValueError("values must have less than 256 items.") - fmt = "<4sxB{0:d}sB{1:d}f".format(len(dref), len(values)) - buffer = struct.pack(fmt, "DREF", len(dref), dref, len(values), values) - else: - fmt = "<4sxB{0:d}sBf".format(len(dref)) - buffer = struct.pack(fmt, "DREF", len(dref), dref, 1, values) + # Pack message + if hasattr(value, "__len__"): + if len(value) > 255: + raise ValueError("value must have less than 256 items.") + fmt = " Date: Thu, 7 May 2015 15:05:29 -0700 Subject: [PATCH 6/7] Added support for sending multiple datarefs to the Java client. --- Java/src/XPlaneConnect.java | 81 ++++++++++++------- TestScripts/Java Tests/XPlaneConnectTest.java | 27 +++++++ 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/Java/src/XPlaneConnect.java b/Java/src/XPlaneConnect.java index 511613b..2e1f336 100644 --- a/Java/src/XPlaneConnect.java +++ b/Java/src/XPlaneConnect.java @@ -331,43 +331,70 @@ public class XPlaneConnect implements AutoCloseable * @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(dref == null) + if(drefs == null || drefs.length == 0) { - throw new IllegalArgumentException("dref must be a valid string."); + throw new IllegalArgumentException(("drefs must be non-empty.")); } - if(value == null || value.length == 0) + if(values == null || values.length != drefs.length) { - throw new IllegalArgumentException("value must be non-null and should contain at least one value."); + throw new IllegalArgumentException("values must be of the same size as drefs."); } - //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 i = 0; i < value.length; ++i) - { - bb.putFloat(i * 4, value[i]); - } - - //Build and send message ByteArrayOutputStream os = new ByteArrayOutputStream(); os.write("DREF".getBytes(StandardCharsets.UTF_8)); os.write(0xFF); //Placeholder for message length - os.write(drefBytes.length); - os.write(drefBytes, 0, drefBytes.length); - os.write(value.length); - os.write(bb.array()); + 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()); } diff --git a/TestScripts/Java Tests/XPlaneConnectTest.java b/TestScripts/Java Tests/XPlaneConnectTest.java index 216a3d7..e3caba5 100644 --- a/TestScripts/Java Tests/XPlaneConnectTest.java +++ b/TestScripts/Java Tests/XPlaneConnectTest.java @@ -333,6 +333,33 @@ public class XPlaneConnectTest } } + @Test + public void testSendDREFs() throws IOException + { + String[] drefs = + { + "sim/cockpit/switches/gear_handle_status", + "sim/cockpit/autopilot/altitude" + }; + try(XPlaneConnect xpc = new XPlaneConnect()) + { + float[][] values = {{1}, {2000}}; + xpc.sendDREFs(drefs, values); + + float[][] result = xpc.getDREFs(drefs); + assertEquals(values[0][0], result[0][0], 1e-4); + assertEquals(values[1][0], result[1][0], 1e-4); + + values[0][0] = 0; + values[1][0] = 4000; + xpc.sendDREFs(drefs, values); + + result = xpc.getDREFs(drefs); + assertEquals(values[0][0], result[0][0], 1e-4); + assertEquals(values[1][0], result[1][0], 1e-4); + } + } + @Test(expected = IllegalArgumentException.class) public void testSendDREF_NullDREF() throws IOException { From 22a864a8dc93779e66477cc0f3567983c92894b9 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Thu, 7 May 2015 15:19:59 -0700 Subject: [PATCH 7/7] Added support for sending multiple datarefs to the MATLAB client. --- MATLAB/+XPlaneConnect/XPlaneConnect.jar | Bin 6821 -> 7035 bytes MATLAB/+XPlaneConnect/sendDREF.m | 34 +++++----------------- MATLAB/+XPlaneConnect/sendDREFs.m | 37 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 27 deletions(-) create mode 100644 MATLAB/+XPlaneConnect/sendDREFs.m diff --git a/MATLAB/+XPlaneConnect/XPlaneConnect.jar b/MATLAB/+XPlaneConnect/XPlaneConnect.jar index 755bb2935c7c642d34542cded39df7ee6558f8a8..e4c0af51c2433fa6c376180c7268507b1f57c6bf 100644 GIT binary patch delta 5945 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 bc&A3!q`sV%761S*ER$OrL delta 5730 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 Uz!d-hkSdcg8$