From b6233ab5d7c01e26b8925b2ca2584792e0844f86 Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Tue, 2 Dec 2014 22:50:47 -0800 Subject: [PATCH 01/10] Started implementation of Python version of XPC client. --- Python/src/xplaneConnect.py | 102 ++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 Python/src/xplaneConnect.py diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py new file mode 100644 index 0000000..7278f2e --- /dev/null +++ b/Python/src/xplaneConnect.py @@ -0,0 +1,102 @@ +import socket + +class XPlaneConnect: + '''XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin.''' + + # Basic Functions + def __init__(self, port, xp_ip = 'localhost', xp_port = 49009): + '''Creates a new XPlaneConnect interface, and binds a UDP socket based on the specified parameters.''' + + # Setup server port + server = (socket.INADDR_ANY, socket.htons(port)) + + # Setup XPlane IP and port + if xp_ip == 'localhost' or xp_ip is None: + self.xp_ip = '127.0.0.1' + else: + self.xp_ip = xp_ip + + self.xp_port = xp_port + + # Create and bind socket + # TODO: Raise a friendly error if socket creation/binding fails + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + self.socket.bind(server) + + # Set timeout + timeout_ms = 500 + self.socket.settimeout(timeout_ms / 1000) + + def __del__(self): + self.close() + + def close(): + '''Closes the underlying UDP socket''' + if self.socket is not None: + self.socket.close() + self.socket = None + + def send_udp(msg): + pass + + def read_udp(): + pass + + # Configuration + def set_conn(rec_port): + pass + + def pause_sim(state): + pass + + # UDP Data + def parse_data(msg, dataRef): + pass + + def read_data(msg, dataRef): + pass + + def send_data(msg, dataRef, rows): + pass + + # Position + def parse_pos(msg, resultArray, gear): + pass + + def read_pos(resultArray, gear): + pass + + def send_pos(ACNum, numArgs, valueArray): + pass + + # Controls + def parse_ctrl(msg, resultArray, gear): + pass + + def read_ctrol(resultArray, gear): + pass + + def send_pos(numArgs, valueArray): + pass + + # DREF Manipulation + def read_dref(resultArray): + pass + + def parse_dref(msg, dataRef, length_of_dref, values): + pass + + def send_dref(dataRef, length_of_dref, values): + pass + + def request_dref(dref_array, dref_sizes, list_len, resultArray): + pass + + def parse_getd(msg, dref_array, dref_sizes): + pass + + def parse_request(msg, result_array, array_sizes): + pass + + def read_request(data_ref, array_sizes, recv_addr): + pass \ No newline at end of file From 04897b878e7b63733b08bcfb0c77e64949c9de98 Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Sat, 6 Dec 2014 14:29:11 -0800 Subject: [PATCH 02/10] More python! --- Python/src/xplaneConnect.py | 194 ++++++++++++++++++++++++++++-------- 1 file changed, 154 insertions(+), 40 deletions(-) diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index 7278f2e..34fa61d 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -1,4 +1,5 @@ import socket +import struct class XPlaneConnect: '''XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin.''' @@ -24,79 +25,192 @@ class XPlaneConnect: self.socket.bind(server) # Set timeout - timeout_ms = 500 - self.socket.settimeout(timeout_ms / 1000) + timeout_us = 500 + self.socket.settimeout(timeout_ms / 1000000) def __del__(self): self.close() - def close(): + def close(self): '''Closes the underlying UDP socket''' if self.socket is not None: self.socket.close() self.socket = None - def send_udp(msg): - pass + def send_udp(self, msg): + '''Sends a message over the underlying UDP socket.''' + msg_len = len(msg) + msg[4] = msg_len - def read_udp(): - pass + # Preconditions + if(msg_len <= 0): # Require message length greater than 0. + raise RuntimeError("send_udp: message length must be psoitive >0") + + # Code + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # TODO: Why? + + self.socket.send(msg) + + def read_udp(self, recv_addr = None): + if recv_addr is None: + recv_addr = (self.xp_ip, self.xp_port) + + return self.socket.recv(5000) # TODO: Validate that this matches the behavior of the C version # Configuration - def set_conn(rec_port): - pass + def set_conn(self, rec_port): + msg = struct.pack("!4sH", ("CONN", rec_port)) + self.send_udp(msg) - def pause_sim(state): - pass + def pause_sim(self, pause): + '''Pauses or unpauses the X-Plane simulation''' + pause_val = 0 + if pause: + pause_val = 1 + + msg = struct.pack("4sBB", ("SIMU", pause_val, 0)) + self.send_udp(msg) # UDP Data - def parse_data(msg, dataRef): + def parse_data(self, data): pass - def read_data(msg, dataRef): - pass + def read_data(self): + buf = self.read_udp() + if buf[0] != 0: + return self.parse_data(buf) + else: + return None - def send_data(msg, dataRef, rows): - pass + def send_data(self, dataRef): + '''Sends X-Plane data over the underlying UDP socket.''' + msg = struct.pack("4s", "DATA") + for row in dataRef: + struct.pack_into("!I", msg, len(msg), row[0]) + for i in range(8): + struct.pack_into("!f", msg, len(msg), row[1][i]) + self.send_udp(msg) # Position - def parse_pos(msg, resultArray, gear): + def parse_pos(self, data): pass - def read_pos(resultArray, gear): - pass + def read_pos(self, resultArray, gear): + buf = self.read_udp() + if buf[0] != 0: + return self.parse_pos(buf) + else: + return None - def send_pos(ACNum, numArgs, valueArray): - pass + def send_pos(self, ac_num, values): + # Preconditions + if len(values) < 1: + raise RuntimeError("send_pos: Must have at least one argument") + # Header and Aircraft num + msg = struct.pack("!4sB", ("POSI", ac_num)) + + # States + for i in range(7): + val = -998.5 # TODO: Why? + if i < len(values): + val = values[i] + struct.pack_into("!f", msg, len(msg), val) + + # Send + self.send_udp(msg) +#} # Controls - def parse_ctrl(msg, resultArray, gear): + def parse_ctrl(self, data): pass - def read_ctrol(resultArray, gear): - pass + def read_ctrol(self, resultArray, gear): + buf = self.read_udp() + if buf[0] != 0: + return self.parse_ctrl(buf) + else: + return float("NaN") - def send_pos(numArgs, valueArray): - pass + def send_ctrol(self, values): + # Preconditions + if len(values) < 1: + raise RuntimeError("send_ctrl: Must have at least one argument") + + # Header + msg = struct.pack("!4s", "CTRL") + + # States + for i in range(6): + val = -998.5 # TODO: Why? + if i < len(values): + val = values[i] + struct.pack_into("!f", msg, len(msg), val) + + # Send + self.send_udp(msg) # DREF Manipulation - def read_dref(resultArray): + def read_dref(self, resultArray): + buf = self.read_udp() + if buf[0] != 0: + return self.parse_dref(buf) + else: + return None + + def parse_dref(self, data): pass - def parse_dref(msg, dataRef, length_of_dref, values): + def send_dref(self, data_ref, values): + '''Sends X-Plane dref over the underlying UDP socket.''' + msg_len = 7 + len(data_ref) + len(values) * 4 + + # Header + msg = struct.pack("4s", "DREF") + + # DRef + struct.pack_into("B", msg, len(msg), len(data_ref)) + struct.pack_into(str(len(data_ref)) + "B", msg, len(msg), data_ref) # TODO: Verify byte order (should be network order?) + + # Values + struct.pack_into("B", msg, len(msg), len(values)) + struct.pack_into(str(len(values)) + "f", msg, len(msg), values) # TODO: Verify byte order (should be network order?) + + self.send_udp(msg) + + def request_dref(self, dref_array, resultArray): + '''Requests drefs and reads the response.''' + self.send_request(dref_array) + for i in range(80): + size = self.read_dref(resultArray) + if size != -1: + return size + + return -1 + + def parse_getd(self, msg, dref_array, dref_sizes): pass - def send_dref(dataRef, length_of_dref, values): + def send_request(self, dref_array): + '''Sends a request over the underlying UDP socket.''' + # Header + msg = struct.pack("4s", "GETD"); + + # Number of values; + struct.pack_into("B", msg, len(msg), len(dref_array)) + + # The Rest + for dref in dref_array: + struct.pack_into("B", msg, len(msg), len(dref_array)) + struct.pack_into("100B", msg, len(msg), dref_array) + + self.send_udp(msg) + + def parse_request(self, data): pass - def request_dref(dref_array, dref_sizes, list_len, resultArray): - pass - - def parse_getd(msg, dref_array, dref_sizes): - pass - - def parse_request(msg, result_array, array_sizes): - pass - - def read_request(data_ref, array_sizes, recv_addr): - pass \ No newline at end of file + def read_request(self, data_ref, array_sizes, recv_addr): + buf = self.read_udp() + if buf[0] != 0: + return self.parse_request(buf) + else: + return None \ No newline at end of file From fd63976e4decfb87498b72b3561147cb04b97797 Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Mon, 8 Dec 2014 20:08:04 -0800 Subject: [PATCH 03/10] Finished basic Python impl. --- Python/src/xplaneConnect.py | 72 +++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index 34fa61d..6f10e65 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -72,8 +72,18 @@ class XPlaneConnect: self.send_udp(msg) # UDP Data - def parse_data(self, data): - pass + def parse_data(self, msg): + total_cols = ((len(data) - 5) / 36) + data = [] + + # Input Validation + for i in range(total_cols): + data.append([]) + data[i].append(struct.unpack_from("f", msg, 5 + 36*i)) + for j in range(1, 9): + data[i].append(struct.unpack_from("f", msg, 5 + 4*j + 36*i)) + + return data def read_data(self): buf = self.read_udp() @@ -92,8 +102,16 @@ class XPlaneConnect: self.send_udp(msg) # Position - def parse_pos(self, data): - pass + def parse_pos(self, msg, array_size): + if array_size < 1: + return None + + gear = struct.unpack_from("f", msg, 30) + result = [] + for i in range(min(array_size, 6)): + result.append(struct.unpack_from("f", msg, i*4 + 6)) + + return result, gear def read_pos(self, resultArray, gear): buf = self.read_udp() @@ -109,7 +127,6 @@ class XPlaneConnect: # Header and Aircraft num msg = struct.pack("!4sB", ("POSI", ac_num)) - # States for i in range(7): val = -998.5 # TODO: Why? @@ -119,10 +136,15 @@ class XPlaneConnect: # Send self.send_udp(msg) -#} + # Controls - def parse_ctrl(self, data): - pass + def parse_ctrl(self, msg): + result = [] + for i in range(4): + result.append(struct.unpack_from("f", msg, i*4 + i)) + gear = struct.unpack_from("B", msg, 21) + flaps = struct.unpack_from("f", msg, 22) + return flaps, gear, result def read_ctrol(self, resultArray, gear): buf = self.read_udp() @@ -157,8 +179,12 @@ class XPlaneConnect: else: return None - def parse_dref(self, data): - pass + def parse_dref(self, msg): + len_dref = struct.unpack_from("B", msg, 5) + dref = struct.unpack_from(str(len_dref) + "B", msg, 6) + len_val = struct.unpack_from("B", msg, 6 + len_dref) + values = struct.unpack_from(str(len_val) + "f", msg, 7 + len_dref) + return dref, values def send_dref(self, data_ref, values): '''Sends X-Plane dref over the underlying UDP socket.''' @@ -188,7 +214,16 @@ class XPlaneConnect: return -1 def parse_getd(self, msg, dref_array, dref_sizes): - pass + len_list = struct.unpack_from("B", msg, 5) + counter = 6 + drefs = [] + + for i in range(len_list): + dref_size = struct.unpack_from("B", msg, counter) + drefs.append(struct.unpack_from(str(dref_size) + "B", msg, counter + 1)) + counter += dref_size + 1 + + return drefs def send_request(self, dref_array): '''Sends a request over the underlying UDP socket.''' @@ -205,10 +240,19 @@ class XPlaneConnect: self.send_udp(msg) - def parse_request(self, data): - pass + def parse_request(self, msg): + count = msg[5] + cursor = 6 + result = [] + for i in range(count): + arr_size = msg[cursor] + data = struct.unpack_from(str(arr_size) + "f", msg, cursor) + result.append(data) + cursor += arr_size - def read_request(self, data_ref, array_sizes, recv_addr): + return result + + def read_request(self, recv_addr): buf = self.read_udp() if buf[0] != 0: return self.parse_request(buf) From 4aaca80d3e66dbaa67aa12a4d6c23b0ccb5a5c3c Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Mon, 8 Dec 2014 20:09:44 -0800 Subject: [PATCH 04/10] Added PyTools project files for Python API --- Python/xplaneConnect.pyproj | 49 +++++++++++++++++++++++++++++++++++++ Python/xplaneConnect.sln | 20 +++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 Python/xplaneConnect.pyproj create mode 100644 Python/xplaneConnect.sln diff --git a/Python/xplaneConnect.pyproj b/Python/xplaneConnect.pyproj new file mode 100644 index 0000000..3e9e65f --- /dev/null +++ b/Python/xplaneConnect.pyproj @@ -0,0 +1,49 @@ + + + + Debug + 2.0 + 3c7a940d-17c8-4e91-882f-9bc8b1d2f54b + . + src/xplaneConnect.py + + + . + . + xplaneConnect + xplaneConnect + {2af0f10d-7135-4994-9156-5d01c9c11b7e} + 2.7 + + + true + false + + + true + false + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets + + + + + + + + + + \ No newline at end of file diff --git a/Python/xplaneConnect.sln b/Python/xplaneConnect.sln new file mode 100644 index 0000000..415b6fa --- /dev/null +++ b/Python/xplaneConnect.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "xplaneConnect", "xplaneConnect.pyproj", "{3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal From 9fb2274ae8a0fb664558994c5e17451a12b8140b Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Tue, 9 Dec 2014 19:09:29 -0800 Subject: [PATCH 05/10] Started implementing tests --- Python/xplaneConnect.sln | 4 ++ TestScripts/Python Tests/Tests.py | 85 +++++++++++++++++++++++++++ TestScripts/Python Tests/Tests.pyproj | 46 +++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 TestScripts/Python Tests/Tests.py create mode 100644 TestScripts/Python Tests/Tests.pyproj diff --git a/Python/xplaneConnect.sln b/Python/xplaneConnect.sln index 415b6fa..cc45d45 100644 --- a/Python/xplaneConnect.sln +++ b/Python/xplaneConnect.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "xplaneConnect", "xplaneConnect.pyproj", "{3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}" EndProject +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Tests", "..\TestScripts\Python Tests\Tests.pyproj", "{6931EBB2-4E01-4C5A-86B6-668C0E75051B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -13,6 +15,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6931EBB2-4E01-4C5A-86B6-668C0E75051B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6931EBB2-4E01-4C5A-86B6-668C0E75051B}.Release|Any CPU.ActiveCfg = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TestScripts/Python Tests/Tests.py b/TestScripts/Python Tests/Tests.py new file mode 100644 index 0000000..412c890 --- /dev/null +++ b/TestScripts/Python Tests/Tests.py @@ -0,0 +1,85 @@ +import random +import unittest +import imp + +xplaneConnect = imp.load_source('xplaneConnect', '../../Python/src/xplaneConnect.py') + +class XPCTests(unittest.TestCase): + """Tests the functionality of the XPlaneConnect class.""" + + def test_init(self): + xpc = xplaneConnect.XPlaneConnect(49062, "127.0.0.1", 49009) + self.assertTrue(True) + + def test_close(self): + xpc = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + xpc.close() + self.assertIsNone(xpc.socket) + xpc = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + self.assertTrue(True) + + def test_send_read(self): + # Init + test = "\x00\x01\x02\x03\x05" + + # Setup + sender = xplaneConnect.XPlaneConnect(49064, "127.0.0.1", 49063) + receiver = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + + # Execution + sender.send_udp(test) + buf = receiver.read_udp() + + # Cleanup + sender.close() + receiver.close() + + # Tests + for a, b in zip(test, buf): + self.assertEqual(a, b) + + def test_request_dref(self): + # Init + dref_array = [] + + # Setup + sender = xplaneConnect.XPlaneConnect(49064, "127.0.0.1", 49009) + dref_array.append("sim/cockpit/switches/gear_handle_status") + dref_array.append("cockpit2/switches/panel_brightness_ratio") + + # Execution + result = sender.request_dref(dref_array) + + # Cleanup + sender.close() + receiver.close() + + # Tests + self.assertEqual(2, len(result)) + self.assertEqual(1, len(result[0])) + self.assertEqual(4, len(result[1])) + + def test_send_dref(self): + # Init + dref_array = [] + + # Setup + sender = xplaneConnect.XPlaneConnect(49066, "127.0.0.1", 49009) + receiver = xplaneConnect.XPlaneConnect(49008, "127.0.0.1", 49009) + dref_array.append("sim/cockpit/switches/gear_handle_status") + + # Execution + sender.send_dref(dref_array[0]) + result = receiver.request_dref(dref_array) + + # Cleanup + sender.close() + receiver.close() + + # Tests + self.assertEqual(1, len(result)) + self.assertEqual(1, len(result[0])) + self.assertEqual(0.0, result[0][0]) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/TestScripts/Python Tests/Tests.pyproj b/TestScripts/Python Tests/Tests.pyproj new file mode 100644 index 0000000..d084785 --- /dev/null +++ b/TestScripts/Python Tests/Tests.pyproj @@ -0,0 +1,46 @@ + + + + Debug + 2.0 + 6931ebb2-4e01-4c5a-86b6-668c0e75051b + . + Tests.py + + + . + . + Tests + Tests + {2af0f10d-7135-4994-9156-5d01c9c11b7e} + 2.7 + + + true + false + + + true + false + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets + + + + + + + + + + \ No newline at end of file From 18aa9fa251403afcdf895246e10963b4fb1e9589 Mon Sep 17 00:00:00 2001 From: jwatkins42 Date: Tue, 9 Dec 2014 19:10:09 -0800 Subject: [PATCH 06/10] Fixed several bugs based on test results --- Python/src/xplaneConnect.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index 6f10e65..e6d0532 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -9,7 +9,7 @@ class XPlaneConnect: '''Creates a new XPlaneConnect interface, and binds a UDP socket based on the specified parameters.''' # Setup server port - server = (socket.INADDR_ANY, socket.htons(port)) + self.server = ("0.0.0.0", port) # Setup XPlane IP and port if xp_ip == 'localhost' or xp_ip is None: @@ -22,11 +22,11 @@ class XPlaneConnect: # Create and bind socket # TODO: Raise a friendly error if socket creation/binding fails self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - self.socket.bind(server) + self.socket.bind(self.server) # Set timeout timeout_us = 500 - self.socket.settimeout(timeout_ms / 1000000) + self.socket.settimeout(timeout_us / 1000000) def __del__(self): self.close() @@ -40,16 +40,19 @@ class XPlaneConnect: def send_udp(self, msg): '''Sends a message over the underlying UDP socket.''' msg_len = len(msg) - msg[4] = msg_len + msg = list(msg) + msg[4] = chr(msg_len) + msg = "".join(msg) # Preconditions if(msg_len <= 0): # Require message length greater than 0. raise RuntimeError("send_udp: message length must be psoitive >0") + on = 1 # Code - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # TODO: Why? + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, on) - self.socket.send(msg) + self.socket.sendto(msg, 0, (self.xp_ip, self.xp_port)) def read_udp(self, recv_addr = None): if recv_addr is None: @@ -172,7 +175,7 @@ class XPlaneConnect: self.send_udp(msg) # DREF Manipulation - def read_dref(self, resultArray): + def read_dref(self): buf = self.read_udp() if buf[0] != 0: return self.parse_dref(buf) @@ -203,15 +206,15 @@ class XPlaneConnect: self.send_udp(msg) - def request_dref(self, dref_array, resultArray): + def request_dref(self, dref_array): '''Requests drefs and reads the response.''' self.send_request(dref_array) for i in range(80): - size = self.read_dref(resultArray) - if size != -1: - return size + result = self.read_dref() + if siresultze is not None: + return result - return -1 + return None def parse_getd(self, msg, dref_array, dref_sizes): len_list = struct.unpack_from("B", msg, 5) From 2a874eb514df800fe6d10556dbe1ab1277ce3370 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Sun, 26 Apr 2015 09:31:44 -0700 Subject: [PATCH 07/10] Refactored Python client to match other clients. --- Python/src/xplaneConnect.py | 430 ++++++++++++++++++++---------------- Python/xplaneConnect.pyproj | 8 +- Python/xplaneConnect.sln | 2 +- 3 files changed, 241 insertions(+), 199 deletions(-) diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index e6d0532..6ecfacc 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -5,259 +5,301 @@ class XPlaneConnect: '''XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin.''' # Basic Functions - def __init__(self, port, xp_ip = 'localhost', xp_port = 49009): - '''Creates a new XPlaneConnect interface, and binds a UDP socket based on the specified parameters.''' + def __init__(self, xpHost = 'localhost', xpPort = 49009, port = 0, timeout = 100): + '''Sets up a new connection to an X-Plane Connect plugin running in X-Plane. + + Args: + xpHost: The hostname of the machine running X-Plane. + xpPort: The port on which the XPC plugin is listening. Usually 49007. + port: The port which will be used to send and receive data. + timeout: The period (in milliseconds) after which read attempts will fail. + ''' - # Setup server port - self.server = ("0.0.0.0", port) + # Validate parameters + xpIP = None + try: + xpIP = socket.gethostbyname(xpHost) + except: + raise ValueError("Unable to resolve xpHost.") + + if xpPort < 0 or xpPort > 65535: + raise ValueError("The specified X-Plane port is not a valid port number.") + if port < 0 or port > 65535: + raise ValueError("The specified port is not a valid port number.") + if timeout < 0: + raise ValueError("timeout must be non-negative.") # Setup XPlane IP and port - if xp_ip == 'localhost' or xp_ip is None: - self.xp_ip = '127.0.0.1' - else: - self.xp_ip = xp_ip + self.xpDst = (socket.inet_pton(socket.AF_INET, xpIP), xpPort) - self.xp_port = xp_port - - # Create and bind socket - # TODO: Raise a friendly error if socket creation/binding fails + # Create and bind socket + clientAddr = (socket.INADDR_ANY, port) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) - self.socket.bind(self.server) - - # Set timeout - timeout_us = 500 - self.socket.settimeout(timeout_us / 1000000) + self.socket.bind(clientAddr) + self.socket.settimeout(timeout / 1000) def __del__(self): self.close() def close(self): - '''Closes the underlying UDP socket''' + '''Closes the specified connection and releases resources associated with it.''' if self.socket is not None: self.socket.close() self.socket = None - def send_udp(self, msg): + def sendUDP(self, buffer): '''Sends a message over the underlying UDP socket.''' - msg_len = len(msg) - msg = list(msg) - msg[4] = chr(msg_len) - msg = "".join(msg) - # Preconditions - if(msg_len <= 0): # Require message length greater than 0. - raise RuntimeError("send_udp: message length must be psoitive >0") + if(len(buffer) == 0): + raise ValueError("sendUDP: buffer is empty.") - on = 1 - # Code - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, on) + self.socket.sendto(buffer, 0, self.xpDst) - self.socket.sendto(msg, 0, (self.xp_ip, self.xp_port)) - - def read_udp(self, recv_addr = None): - if recv_addr is None: - recv_addr = (self.xp_ip, self.xp_port) - - return self.socket.recv(5000) # TODO: Validate that this matches the behavior of the C version + def readUDP(self): + '''Reads a message from the underlying UDP socket.''' + return self.socket.recv(16384) # Configuration - def set_conn(self, rec_port): - msg = struct.pack("!4sH", ("CONN", rec_port)) - self.send_udp(msg) + def setCONN(self, port): + '''Sets the port on which the client sends and receives data. + + Args: + port: The new port to use. + ''' + #Validate parameters + if port < 0 or port > 65535: + raise ValueError("The specified port is not a valid port number.") - def pause_sim(self, pause): - '''Pauses or unpauses the X-Plane simulation''' + buffer = struct.pack("<4sxH", ("CONN", port)) + self.sendUDP(buffer) + + def pauseSim(self, pause): + '''Pauses or un-pauses the physics simulation engine in X-Plane. + + Args: + pause: True to pause the simulation; False to resume. + ''' pause_val = 0 if pause: pause_val = 1 - msg = struct.pack("4sBB", ("SIMU", pause_val, 0)) - self.send_udp(msg) + buffer = struct.pack("<4sxB", ("SIMU", pause_val)) + self.sendUDP(buffer) - # UDP Data - def parse_data(self, msg): - total_cols = ((len(data) - 5) / 36) - data = [] - - # Input Validation - for i in range(total_cols): - data.append([]) - data[i].append(struct.unpack_from("f", msg, 5 + 36*i)) - for j in range(1, 9): - data[i].append(struct.unpack_from("f", msg, 5 + 4*j + 36*i)) + # X-Plane UDP Data + def readDATA(self): + '''Reads X-Plane data. - return data - - def read_data(self): - buf = self.read_udp() - if buf[0] != 0: - return self.parse_data(buf) - else: + Returns: A 2 dimensional array containing 0 or more rows of data. Each array + in the result will have 9 elements, the first of which is the row number which + that array represents data for, and the rest of which are the data elements in + that row. + ''' + buffer = self.readUDP(); + if len(buffer) < 6: return None + rows = (len(buffer) - 5) / 36 + data = [] + for i in range(rows): + data.append(struct.unpack_from("9f", buffer, 5 + 36*i)) + return data - def send_data(self, dataRef): - '''Sends X-Plane data over the underlying UDP socket.''' - msg = struct.pack("4s", "DATA") - for row in dataRef: - struct.pack_into("!I", msg, len(msg), row[0]) - for i in range(8): - struct.pack_into("!f", msg, len(msg), row[1][i]) - self.send_udp(msg) + def sendDATA(self, data): + '''Sends X-Plane data over the underlying UDP socket. + + Args: + data: An array of values representing data rows to be set. Each array in `data` + should have 9 elements, the first of which is a row number in the range (0-134), + and the rest of which are the values to set for that data row. + ''' + if len(data) > 134: + raise ValueError("Too many rows in data.") + + buffer = struct.pack("4sx", "DATA") + for row in data: + if len(row) != 9: + raise ValueError("Row does not contain exactly 9 values. <" + str(row) + ">") + struct.pack_into(" 7: + raise ValueError("Must have between 0 and 7 items in values.") + if ac < 0 or ac > 20: + raise ValueError("Aircraft number must be between 0 and 20.") - # Header and Aircraft num - msg = struct.pack("!4sB", ("POSI", ac_num)) - # States + # Pack message + buffer = struct.pack("<4sxB", ("POSI", ac)) for i in range(7): - val = -998.5 # TODO: Why? + val = -998 if i < len(values): val = values[i] - struct.pack_into("!f", msg, len(msg), val) + struct.pack_into(" 6: + raise ValueError("Must have between 0 and 6 items in values.") + if ac < 0 or ac > 20: + raise ValueError("Aircraft number must be between 0 and 20.") - # Header - msg = struct.pack("!4s", "CTRL") - - # States + # Pack message + buffer = struct.pack("<4sx", "CTRL") for i in range(6): - val = -998.5 # TODO: Why? + val = -998 if i < len(values): val = values[i] - struct.pack_into("!f", msg, len(msg), val) + struct.pack_into(" 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.") + + # Pack message + fmt = "" + buffer = None + if hasattr(values, "__len__"): + if len(values) > 255: + raise ValueError("values must have less than 256 items.") + fmt = "<4sxB" + len(dref) + "sB" + len(values) + "f" + buffer = struct.pack(fmt, ("DREF", len(dref), dref, len(values), values)) else: - return None + fmt = "<4sxB" + len(dref) + "sBf" + buffer = struct.pack(fmt, ("DREF", len(dref), dref, 1, values)) - def parse_dref(self, msg): - len_dref = struct.unpack_from("B", msg, 5) - dref = struct.unpack_from(str(len_dref) + "B", msg, 6) - len_val = struct.unpack_from("B", msg, 6 + len_dref) - values = struct.unpack_from(str(len_val) + "f", msg, 7 + len_dref) - return dref, values + # Send + self.sendUDP(buffer) - def send_dref(self, data_ref, values): - '''Sends X-Plane dref over the underlying UDP socket.''' - msg_len = 7 + len(data_ref) + len(values) * 4 - - # Header - msg = struct.pack("4s", "DREF") + def getDREF(self, dref): + '''Gets the value of an X-Plane dataref. + + Args: + dref: The name of the dataref to get. - # DRef - struct.pack_into("B", msg, len(msg), len(data_ref)) - struct.pack_into(str(len(data_ref)) + "B", msg, len(msg), data_ref) # TODO: Verify byte order (should be network order?) + Returns: A sequence of data representing the values of the requested dataref. + ''' + return getDREFS([dref])[0] - # Values - struct.pack_into("B", msg, len(msg), len(values)) - struct.pack_into(str(len(values)) + "f", msg, len(msg), values) # TODO: Verify byte order (should be network order?) + def getDREFs(self, drefs): + '''Gets the value of one or more X-Plane datarefs. - self.send_udp(msg) + Args: + drefs: The names of the datarefs to get. - def request_dref(self, dref_array): - '''Requests drefs and reads the response.''' - self.send_request(dref_array) - for i in range(80): - result = self.read_dref() - if siresultze is not None: - return result + Returns: A multidimensional sequence of data representing the values of the requested + datarefs. + ''' + # Send request + buffer = struct.pack("<4sxB" ("GETD", len(drefs))) + for dref in drefs: + struct.pack_into(" 3: + raise ValueError("Invalid operation specified.") + if len(points) % 3 != 0: + raise ValueError("Invalid points. Points should be divisible by 3.") + if len(points) / 3 > 255: + raise ValueError("Too many points. You can only send 255 points at a time.") + + buffer = struct.pack("<4sxBB" + len(points) + "f", ("WYPT", op, len(points), points)) + self.sendUDP(buffer) diff --git a/Python/xplaneConnect.pyproj b/Python/xplaneConnect.pyproj index 3e9e65f..83e08b4 100644 --- a/Python/xplaneConnect.pyproj +++ b/Python/xplaneConnect.pyproj @@ -5,13 +5,13 @@ 2.0 3c7a940d-17c8-4e91-882f-9bc8b1d2f54b . - src/xplaneConnect.py + src/XPlaneConnect.py . . - xplaneConnect - xplaneConnect + XPlaneConnect + XPlaneConnect {2af0f10d-7135-4994-9156-5d01c9c11b7e} 2.7 @@ -24,7 +24,7 @@ false - + diff --git a/Python/xplaneConnect.sln b/Python/xplaneConnect.sln index cc45d45..862da8d 100644 --- a/Python/xplaneConnect.sln +++ b/Python/xplaneConnect.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 VisualStudioVersion = 12.0.31101.0 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "xplaneConnect", "xplaneConnect.pyproj", "{3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}" +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "XPlaneConnect", "XPlaneConnect.pyproj", "{3C7A940D-17C8-4E91-882F-9BC8B1D2F54B}" EndProject Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "Tests", "..\TestScripts\Python Tests\Tests.pyproj", "{6931EBB2-4E01-4C5A-86B6-668C0E75051B}" EndProject From 31095b716b6780b1264594f144bcc2563bd5cd51 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Mon, 27 Apr 2015 15:34:33 -0700 Subject: [PATCH 08/10] Updated Python client unit tests. --- Python/xplaneConnect.pyproj | 4 +-- TestScripts/Python Tests/Tests.py | 42 ++++++++++----------------- TestScripts/Python Tests/Tests.pyproj | 4 +-- 3 files changed, 20 insertions(+), 30 deletions(-) diff --git a/Python/xplaneConnect.pyproj b/Python/xplaneConnect.pyproj index 83e08b4..bc9ff8a 100644 --- a/Python/xplaneConnect.pyproj +++ b/Python/xplaneConnect.pyproj @@ -12,7 +12,7 @@ . XPlaneConnect XPlaneConnect - {2af0f10d-7135-4994-9156-5d01c9c11b7e} + {9a7a9026-48c1-4688-9d5d-e5699d47d074} 2.7 @@ -30,7 +30,7 @@ - + 10.0 diff --git a/TestScripts/Python Tests/Tests.py b/TestScripts/Python Tests/Tests.py index 412c890..46ac11d 100644 --- a/TestScripts/Python Tests/Tests.py +++ b/TestScripts/Python Tests/Tests.py @@ -8,14 +8,14 @@ class XPCTests(unittest.TestCase): """Tests the functionality of the XPlaneConnect class.""" def test_init(self): - xpc = xplaneConnect.XPlaneConnect(49062, "127.0.0.1", 49009) + xpc = xplaneConnect.XPlaneConnect() self.assertTrue(True) def test_close(self): - xpc = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) xpc.close() self.assertIsNone(xpc.socket) - xpc = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) self.assertTrue(True) def test_send_read(self): @@ -23,12 +23,12 @@ class XPCTests(unittest.TestCase): test = "\x00\x01\x02\x03\x05" # Setup - sender = xplaneConnect.XPlaneConnect(49064, "127.0.0.1", 49063) - receiver = xplaneConnect.XPlaneConnect(49063, "127.0.0.1", 49009) + sender = xplaneConnect.XPlaneConnect("127.0.0.1", 49063, 49064) + receiver = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) # Execution - sender.send_udp(test) - buf = receiver.read_udp() + sender.sendUDP(test) + buf = receiver.readUDP() # Cleanup sender.close() @@ -39,20 +39,15 @@ class XPCTests(unittest.TestCase): self.assertEqual(a, b) def test_request_dref(self): - # Init - dref_array = [] - # Setup - sender = xplaneConnect.XPlaneConnect(49064, "127.0.0.1", 49009) - dref_array.append("sim/cockpit/switches/gear_handle_status") - dref_array.append("cockpit2/switches/panel_brightness_ratio") + xpc = xplaneConnect.XPlaneConnect() + drefs = ["sim/cockpit/switches/gear_handle_status", "cockpit2/switches/panel_brightness_ratio"] # Execution - result = sender.request_dref(dref_array) + result = xpc.getDREFs(drefs) # Cleanup - sender.close() - receiver.close() + xpc.close() # Tests self.assertEqual(2, len(result)) @@ -60,21 +55,16 @@ class XPCTests(unittest.TestCase): self.assertEqual(4, len(result[1])) def test_send_dref(self): - # Init - dref_array = [] - # Setup - sender = xplaneConnect.XPlaneConnect(49066, "127.0.0.1", 49009) - receiver = xplaneConnect.XPlaneConnect(49008, "127.0.0.1", 49009) - dref_array.append("sim/cockpit/switches/gear_handle_status") + xpc = xplaneConnect.XPlaneConnect() + dref = "sim/cockpit/switches/gear_handle_status" # Execution - sender.send_dref(dref_array[0]) - result = receiver.request_dref(dref_array) + sender.sendDREF(dref_array) + result = receiver.getDREF(dref_array) # Cleanup - sender.close() - receiver.close() + xpc.close(); # Tests self.assertEqual(1, len(result)) diff --git a/TestScripts/Python Tests/Tests.pyproj b/TestScripts/Python Tests/Tests.pyproj index d084785..d39f8ab 100644 --- a/TestScripts/Python Tests/Tests.pyproj +++ b/TestScripts/Python Tests/Tests.pyproj @@ -12,7 +12,7 @@ . Tests Tests - {2af0f10d-7135-4994-9156-5d01c9c11b7e} + {9a7a9026-48c1-4688-9d5d-e5699d47d074} 2.7 @@ -27,7 +27,7 @@ - + 10.0 From 685d121edde05fdbdc78e41564f440e6ac748876 Mon Sep 17 00:00:00 2001 From: Jason Watkins Date: Tue, 28 Apr 2015 11:54:48 -0700 Subject: [PATCH 09/10] Fixed several bugs found by unit tests. --- .gitignore | 1 + Python/src/xplaneConnect.py | 29 ++++++----- TestScripts/Python Tests/Tests.py | 83 +++++++++++++++++++++++++++---- 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index e1c7a44..2575f4c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ *.exe *.o *.so +*.pyc # Logs and databases # ###################### diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index 6ecfacc..696f063 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -30,13 +30,14 @@ class XPlaneConnect: raise ValueError("timeout must be non-negative.") # Setup XPlane IP and port - self.xpDst = (socket.inet_pton(socket.AF_INET, xpIP), xpPort) + self.xpDst = (xpIP, xpPort) # Create and bind socket - clientAddr = (socket.INADDR_ANY, port) + clientAddr = ("0.0.0.0", port) self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) self.socket.bind(clientAddr) - self.socket.settimeout(timeout / 1000) + timeout /= 1000.0 + self.socket.settimeout(timeout) def __del__(self): self.close() @@ -212,11 +213,11 @@ class XPlaneConnect: if hasattr(values, "__len__"): if len(values) > 255: raise ValueError("values must have less than 256 items.") - fmt = "<4sxB" + len(dref) + "sB" + len(values) + "f" - buffer = struct.pack(fmt, ("DREF", len(dref), dref, len(values), values)) + 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" + len(dref) + "sBf" - buffer = struct.pack(fmt, ("DREF", len(dref), dref, 1, values)) + fmt = "<4sxB{0:d}sBf".format(len(dref)) + buffer = struct.pack(fmt, "DREF", len(dref), dref, 1, values) # Send self.sendUDP(buffer) @@ -229,7 +230,7 @@ class XPlaneConnect: Returns: A sequence of data representing the values of the requested dataref. ''' - return getDREFS([dref])[0] + return self.getDREFs([dref])[0] def getDREFs(self, drefs): '''Gets the value of one or more X-Plane datarefs. @@ -241,20 +242,22 @@ class XPlaneConnect: datarefs. ''' # Send request - buffer = struct.pack("<4sxB" ("GETD", len(drefs))) + buffer = struct.pack("<4sxB", "GETD", len(drefs)) for dref in drefs: - struct.pack_into(" Date: Fri, 1 May 2015 13:50:48 -0700 Subject: [PATCH 10/10] Finished implementing unit tests and fixed bugs found. --- Python/src/xplaneConnect.py | 30 ++-- TestScripts/Python Tests/Tests.py | 258 ++++++++++++++++++++++++------ 2 files changed, 229 insertions(+), 59 deletions(-) diff --git a/Python/src/xplaneConnect.py b/Python/src/xplaneConnect.py index 696f063..69ab40b 100644 --- a/Python/src/xplaneConnect.py +++ b/Python/src/xplaneConnect.py @@ -1,8 +1,9 @@ import socket import struct -class XPlaneConnect: +class XPlaneConnect(object): '''XPlaneConnect (XPC) facilitates communication to and from the XPCPlugin.''' + socket = None # Basic Functions def __init__(self, xpHost = 'localhost', xpPort = 49009, port = 0, timeout = 100): @@ -71,7 +72,7 @@ class XPlaneConnect: if port < 0 or port > 65535: raise ValueError("The specified port is not a valid port number.") - buffer = struct.pack("<4sxH", ("CONN", port)) + buffer = struct.pack("<4sxH", "CONN", port) self.sendUDP(buffer) def pauseSim(self, pause): @@ -84,7 +85,7 @@ class XPlaneConnect: if pause: pause_val = 1 - buffer = struct.pack("<4sxB", ("SIMU", pause_val)) + buffer = struct.pack("<4sxB", "SIMU", pause_val) self.sendUDP(buffer) # X-Plane UDP Data @@ -116,11 +117,11 @@ class XPlaneConnect: if len(data) > 134: raise ValueError("Too many rows in data.") - buffer = struct.pack("4sx", "DATA") + buffer = struct.pack("<4sxB", "DATA", len(data)) for row in data: if len(row) != 9: raise ValueError("Row does not contain exactly 9 values. <" + str(row) + ">") - struct.pack_into(" 255: raise ValueError("Too many points. You can only send 255 points at a time.") - buffer = struct.pack("<4sxBB" + len(points) + "f", ("WYPT", op, len(points), points)) + if op == 3: + buffer = struct.pack("<4sxBB", "WYPT", 3, 0) + else: + buffer = struct.pack("<4sxBB" + str(len(points)) + "f", "WYPT", op, len(points), *points) self.sendUDP(buffer) diff --git a/TestScripts/Python Tests/Tests.py b/TestScripts/Python Tests/Tests.py index ba36066..a3ea597 100644 --- a/TestScripts/Python Tests/Tests.py +++ b/TestScripts/Python Tests/Tests.py @@ -1,65 +1,66 @@ import random import unittest import imp +import time -xplaneConnect = imp.load_source('xplaneConnect', '../../Python/src/xplaneConnect.py') +xpc = imp.load_source('xplaneConnect', '../../Python/src/xplaneConnect.py') class XPCTests(unittest.TestCase): """Tests the functionality of the XPlaneConnect class.""" def test_init(self): try: - xpc = xplaneConnect.XPlaneConnect() + client = xpc.XPlaneConnect() except: self.fail("Default constructor failed.") try: - xpc = xplaneConnect.XPlaneConnect("I'm not a real host") + client = xpc.XPlaneConnect("I'm not a real host") self.fail("Failed to catch invalid XP host.") except ValueError: pass try: - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 90001) + client = xpc.XPlaneConnect("127.0.0.1", 90001) self.fail("Failed to catch invalid XP port.") except ValueError: pass try: - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", -1) + client = xpc.XPlaneConnect("127.0.0.1", -1) self.fail("Failed to catch invalid XP port.") except ValueError: pass try: - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 90001) + client = xpc.XPlaneConnect("127.0.0.1", 49009, 90001) self.fail("Failed to catch invalid local port.") except ValueError: pass try: - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, -1) + client = xpc.XPlaneConnect("127.0.0.1", 49009, -1) self.fail("Failed to catch invalid XP port.") except ValueError: pass try: - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 0, -1) + client = xpc.XPlaneConnect("127.0.0.1", 49009, 0, -1) self.fail("Failed to catch invalid timeout.") except ValueError: pass def test_close(self): - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) - xpc.close() - self.assertIsNone(xpc.socket) - xpc = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) + client = xpc.XPlaneConnect("127.0.0.1", 49009, 49063) + client.close() + self.assertIsNone(client.socket) + client = xpc.XPlaneConnect("127.0.0.1", 49009, 49063) def test_send_read(self): # Init test = "\x00\x01\x02\x03\x05" # Setup - sender = xplaneConnect.XPlaneConnect("127.0.0.1", 49063, 49064) - receiver = xplaneConnect.XPlaneConnect("127.0.0.1", 49009, 49063) + sender = xpc.XPlaneConnect("127.0.0.1", 49063, 49064) + receiver = xpc.XPlaneConnect("127.0.0.1", 49009, 49063) # Execution sender.sendUDP(test) @@ -75,15 +76,15 @@ class XPCTests(unittest.TestCase): def test_getDREFs(self): # Setup - xpc = xplaneConnect.XPlaneConnect() + client = xpc.XPlaneConnect() drefs = ["sim/cockpit/switches/gear_handle_status",\ "sim/cockpit2/switches/panel_brightness_ratio"] # Execution - result = xpc.getDREFs(drefs) + result = client.getDREFs(drefs) # Cleanup - xpc.close() + client.close() # Tests self.assertEqual(2, len(result)) @@ -91,46 +92,207 @@ class XPCTests(unittest.TestCase): self.assertEqual(4, len(result[1])) def test_sendDREF(self): - # Setup - xpc = xplaneConnect.XPlaneConnect() dref = "sim/cockpit/switches/gear_handle_status" + value = None + def do_test(): + # Setup + client = xpc.XPlaneConnect() - # Execution - xpc.sendDREF(dref, 0.0) - result = xpc.getDREF(dref) + # Execute + client.sendDREF(dref, value) + result = client.getDREF(dref) - # Cleanup - xpc.close() + # Cleanup + client.close() - # Tests - self.assertEqual(1, len(result)) - self.assertEqual(0.0, result[0]) - - def test_pauseSim(self): - # Setup - xpc = xplaneConnect.XPlaneConnect() - dref = "sim/operation/override/override_planepath" - - # Execution 1 - xpc.pauseSim(True) - result = xpc.getDREF(dref) - - # Cleanup 1 - xpc.close() + # Tests + self.assertEqual(1, len(result)) + self.assertEqual(value, result[0]) # Test 1 - self.assertAlmostEqual(1.0, result[0]) + value = 1 + do_test() - # Execution 2 - xpc = xplaneConnect.XPlaneConnect() - xpc.pauseSim(False) - result = xpc.getDREF(dref) + # Test 2 + value = 0 + do_test() + + def test_sendDATA(self): + # Setup + dref = "sim/aircraft/parts/acf_gear_deploy" + data = [[ 14, 1, 0, -998, -998, -998, -998, -998, -998 ]] + client = xpc.XPlaneConnect() + + # Execute + client.sendDATA(data) + result = client.getDREF(dref) + + # Cleanup + client.close() + + #Tests + self.assertEqual(result[0], data[0][1]) + + def test_pauseSim(self): + dref = "sim/operation/override/override_planepath" + value = None + expected = None + def do_test(): + # Setup + client = xpc.XPlaneConnect() + + # Execute + client.pauseSim(value) + result = client.getDREF(dref) + + # Cleanup + client.close() + + # Test + self.assertAlmostEqual(expected, result[0]) + + # Test 1 + value = True + expected = 1.0 + do_test() + + # Test 2 + value = False + expected = 0.0 + do_test() + + + def test_sendCTRL(self): + # Setup + 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"] + ctrl = [] + + def do_test(): + client = xpc.XPlaneConnect() + + # Execute + client.sendCTRL(ctrl) + result = client.getDREFs(drefs) + + # Cleanup + client.close() + + # Tests + self.assertEqual(6, len(result)) + for i in range(6): + self.assertAlmostEqual(ctrl[i], result[i][0], 4) + + # Test 1 + ctrl = [ -1.0, -1.0, -1.0, 0.0, 1.0, 1.0 ] + do_test() + + # Test 2 + ctrl = [ 1.0, 1.0, 1.0, 0.0, 1.0, 0.5 ] + do_test() + + # Test 2 + ctrl = [ 0.0, 0.0, 0.0, 0.8, 1.0, 0.0 ] + do_test() + + def test_sendPOSI(self): + # Setup + 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"] + posi = None + def do_test(): + client = xpc.XPlaneConnect() + + # Execute + client.pauseSim(True) + client.sendPOSI(posi) + result = client.getDREFs(drefs) + client.pauseSim(False) + + # Cleanup + client.close() + + # Tests + self.assertEqual(7, len(result)) + for i in range(7): + self.assertAlmostEqual(posi[i], result[i][0], 4) + + # Test 1 + posi = [ 37.524, -122.06899, 2500, 5, 7, 11, 1 ] + do_test() + + # Test 2 + posi = [ 38, -121.0, 2000, -10, 0, 0, 0 ] + do_test() + + def test_sendTEXT(self): + # Setup + client = xpc.XPlaneConnect() + x = 200 + y = 700 + msg = "Python sendTEXT test message." + + # Execution + client.sendTEXT(msg, x, y) + # NOTE: Manually verify that msg appears on the screen in X-Plane + + # Cleanup + client.close() + + def test_sendWYPT(self): + # Setup + client = xpc.XPlaneConnect() + points = [\ + 37.5245, -122.06899, 2500,\ + 37.455397, -122.050037, 2500,\ + 37.469567, -122.051411, 2500,\ + 37.479376, -122.060509, 2300,\ + 37.482237, -122.076130, 2100,\ + 37.474881, -122.087288, 1900,\ + 37.467660, -122.079391, 1700,\ + 37.466298, -122.090549, 1500,\ + 37.362562, -122.039223, 1000,\ + 37.361448, -122.034416, 1000,\ + 37.361994, -122.026348, 1000,\ + 37.365541, -122.022572, 1000,\ + 37.373727, -122.024803, 1000,\ + 37.403869, -122.041283, 50,\ + 37.418544, -122.049222, 6] + + # Execution + client.sendPOSI([37.5245, -122.06899, 2500]) + client.sendWYPT(3, []) + client.sendWYPT(1, points) + # NOTE: Manually verify that points appear on the screen in X-Plane + + # Cleanup + client.close() + + def test_setCONN(self): + # Setup + dref = "sim/cockpit/switches/gear_handle_status"; + client = xpc.XPlaneConnect() + + # Execute + client.setCONN(49055) + result = client.getDREF(dref) + + # Cleanup + client.close() + + # Test + self.assertEqual(1, len(result)) - # Cleanup 2 - xpc.close() - # Tests - self.assertEqual(0.0, result[0]) if __name__ == '__main__': unittest.main() \ No newline at end of file