From a4bce53d1ebfa56fadbd549692520292e0e47eba Mon Sep 17 00:00:00 2001 From: Brikwerk Date: Tue, 2 Jun 2020 22:21:48 -0700 Subject: [PATCH] Fixed dual controller support --- demo.py | 48 +++--- docs/Analog Stick Input.md | 197 ++++++++++++++++++++++++ docs/Analog Stick and Button Input.md | 93 ------------ docs/Miscellaneous Notes.md | 19 +++ nxbt/bluez.py | 3 + nxbt/controller/controller.py | 2 - nxbt/controller/input.py | 1 - nxbt/controller/protocol.py | 10 +- nxbt/controller/server.py | 207 +++++++++++++++----------- nxbt/nxbt.py | 10 +- scripts/proxy.py | 7 +- scripts/sticks.py | 81 ++++++++++ test.py | 29 ---- 13 files changed, 469 insertions(+), 238 deletions(-) create mode 100644 docs/Analog Stick Input.md delete mode 100644 docs/Analog Stick and Button Input.md create mode 100644 scripts/sticks.py delete mode 100644 test.py diff --git a/demo.py b/demo.py index 06abe7c..429b28b 100644 --- a/demo.py +++ b/demo.py @@ -1,4 +1,5 @@ import time +from random import randint from nxbt import Nxbt from nxbt import ControllerTypes @@ -34,26 +35,39 @@ A 0.1s """ +def random_colour(): + + return [ + randint(0, 255), + randint(0, 255), + randint(0, 255), + ] + + if __name__ == "__main__": + # Loop over all Bluetooth adapters and create + # Switch Pro Controllers nxbt = Nxbt() adapters = nxbt.get_available_adapters() - index = nxbt.create_controller( - ControllerTypes.PRO_CONTROLLER, - adapters[0], - colour_body=[0xFF, 0x7B, 0x83], - colour_buttons=[0xFF, 0xF0, 0x78]) - index2 = nxbt.create_controller( - ControllerTypes.PRO_CONTROLLER, - adapters[1], - colour_body=[0xFF, 0xFF, 0xFF], - colour_buttons=[0xFF, 0xF0, 0x78]) - nxbt.macro(index2, MACRO, block=False) + # adapters = ["/org/bluez/hci0"] + controller_idxs = [] + for i in range(0, len(adapters)): + index = nxbt.create_controller( + ControllerTypes.PRO_CONTROLLER, + adapters[i], + colour_body=random_colour(), + colour_buttons=random_colour()) + controller_idxs.append(index) + # Run a macro on the last controller + nxbt.macro(controller_idxs[-1], MACRO, block=False) + + # Check the state while True: time.sleep(1) - state = nxbt.state[0] - if not state["errors"]: - print(state["finished_macros"]) - else: - print(state["errors"]) - break + for key in nxbt.state.keys(): + state = nxbt.state[key] + if not state["errors"]: + print(state) + else: + print(state["errors"]) diff --git a/docs/Analog Stick Input.md b/docs/Analog Stick Input.md new file mode 100644 index 0000000..89762ab --- /dev/null +++ b/docs/Analog Stick Input.md @@ -0,0 +1,197 @@ +# Analog Stick Input Information + +**Disclaimer:** A chunk info within this document is sourced from the Switch reverse engineering +effort at [DekuNukem's Repository](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering). + +The below sections contain info on the formulation and derivation of data +pertaining to the Nintendo Switch's controllers. The section on the analog +sticks contains info on encoding/decoding stick X/Y data, deadzones, +maximum range, etc. + +If you want to tweak or check out the full stick decode/encode script, +please visit the *scripts/sticks.py* script. + +## Analog Stick Information + +Information on a controller's analog sticks is stored in three primary +locations (user calibration excluded): + +| Obtained From | Byte # | Data Type | Info | +| --- | --- | --- | --- | --- | +| Standard Input Report | 6-11 | 2 uint16 | Contains X/Y Data of Analog Sticks1 +| SPI Flash Read (Offset 0x6080) | 13-30 | 12 uint16 LE | Dead Zone, Range ratio | +| SPI Flash Read (Offset 0x603D) | 7-24 | 12 uint16 LE | X/Y Min/Max and Centers | + +1 This data is relative, meaning that stick calibration data +*must* be used to encode/decode X and Y positions. + +## Decoding a Stick's Position + +**Note:** The following configuration values are used within Nxbt. + +First, we use the data obtained from the 0x603D SPI flash read to +derive the right/left stick calibration parameters. + +Sample data output by Nxbt: +``` +Payload: 0xA1 0x21 0x2B 0x90 0x00 0x00 0x00 0x74 0x58 0x75 0x4B 0x68 0x7C 0x90 + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 +Subcommand: 0x90 0x10 0x3D 0x60 0x00 0x00 0x19 0xBA 0xF5 0x62 0x6F 0xC8 0x77 0xED + 14 15 16 17 18 19 20 21 22 23 24 25 26 27 + 0x95 0x5B 0x16 0xD8 0x7D 0xF2 0xB5 0x5F 0x86 0x65 0x5E 0xFF 0x82 0x82 + 28 29 30 31 32 33 34 35 + 0x82 0x0F 0x0F 0x0F 0x00 0x00 0x00 0x00 +``` + +Which gives us: + +``` +Left Stick: 0xBA 0xF5 0x62 0x6F 0xC8 0x77 0xED 0x95 0x5B +Right Stick: 0x16 0xD8 0x7D 0xF2 0xB5 0x5F 0x86 0x65 0x5E +``` + +Using the following equations, we can decode these values into meaningful ones. +Each stick's data is treated as an array of byte values for the equations. + +```python +# The nine stick bytes are labelled stick_cal[0] - stick_cal[8] here +data = [0] * 6 +data[0] = (stick_cal[1] << 8) & 0xF00 | stick_cal[0]; +data[1] = (stick_cal[2] << 4) | (stick_cal[1] >> 4); +data[2] = (stick_cal[4] << 8) & 0xF00 | stick_cal[3]; +data[3] = (stick_cal[5] << 4) | (stick_cal[4] >> 4); +data[4] = (stick_cal[7] << 8) & 0xF00 | stick_cal[6]; +data[5] = (stick_cal[8] << 4) | (stick_cal[7] >> 4); + +# Using the above data to create right stick data +right_center_x = data[0]; +right_center_y = data[1]; +right_x_min = rstick_center_x - data[2]; +right_x_max = rstick_center_x + data[4]; +right_y_min = rstick_center_y - data[3]; +right_y_max = rstick_center_y + data[5]; + +# or left stick data +left_center_x = data[2] +left_center_y = data[3] +left_x_min = left_center_x - data[0] +left_x_max = left_center_x + data[4] +left_y_min = left_center_y - data[1] +left_y_max = left_center_y + data[5] +``` + +Resulting in the following values for the sticks: + +``` +Right Stick +~~~~~~~~~~~ +Center X = 2070 +Center Y = 2013 +X Min = 548 +X Max = 3484 +Y Min = 482 +Y Max = 3523 + +Left Stick +~~~~~~~~~~ +Center X = 2159 +Center Y = 1916 +X Min = 693 +X Max = 3676 +Y Min = 333 +Y Max = 3381 +``` + +Please note that the left stick calibration data is decoded slightly +different than the right stick calibration data. + +With the above calibration data, we can now decode a controller's +reported stick position: + +```python +# Sample Stick Data Conversion: +stick_data = [0xB3, 0x32, 0x6C] +stick_horizontal = stick_data[0] | ((stick_data[1] & 0xF) << 8) +stick_vertical = (stick_data[1] >> 4) | (stick_data[2] << 4) + +print("Example Left Stick Data to Ratio Conversion:") +print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") +print("Raw X/Y Uint16 Values:", stick_horizontal, stick_vertical) +ratio_x = abs((stick_horizontal - left_center_x)) / (left_x_min - left_center_x) +ratio_y = (stick_vertical - left_center_y) / (left_y_min - left_center_y) +print("Relative X/Y Values", ratio_x, ratio_y) +``` + +Which results in the ratios: + +``` +Example Left Stick Data to Ratio Conversion: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Raw X/Y Uint16 Values: 691 1731 +Relative X/Y Values -1.0013642564802183 0.11686670878079596 +``` + +We can see from the above data that the stick is being pushed left horizontally with +very little vertical component. + +## Converting Ratio-based Stick Position to a Calibrated Position + +Given the stick calibration settings from the previous section, +we can convert a given set of X/Y stick ratios to a calibrated set +of values. This worked example will use the ratios defined before +(-1.00136 X and 0.116866 Y). + +First, we need to convert our given ratios to the numeric range +defined by the calibration settings. Since we're using left stick ratios +for our example, our X values range from 693 - 3676 and our Y values range +from 333 - 3381. The following section of code demonstrates the math +behind this conversion. + +```python +print("Example Left Stick Ratio to Data Conversion:") +print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") +if ratio_x < 0: + data_x_converted = (abs(ratio_x) * (left_x_min - left_center_x) + left_center_x) +else: + data_x_converted = (abs(ratio_x) * (left_x_max - left_center_x) + left_center_x) +data_x_converted = int(round(data_x_converted)) + +if ratio_y < 0: + data_y_converted = (abs(ratio_y) * (left_y_min - left_center_y) + left_center_y) +else: + data_y_converted = (abs(ratio_y) * (left_y_max - left_center_y) + left_center_y) +data_y_converted = int(round(data_y_converted)) + +print("X/Y Converted Values:", data_x_converted, data_y_converted) +``` + +Which results in: + +``` +Example Left Stick Ratio to Data Conversion: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +X/Y Converted Values: 691 1731 +``` + +Since the stick's X/Y position is broken up into 3 bytes in the standard input +report, we need to split these uint16 values into 3 uint8 values. The following +code accomplishes this split: + +```python +# Converting the two X/Y uint16 values to 3 uint8 Little Endian values +converted_values = [ + # Get the last two hex digits + hex(data_x_converted & 0xFF), + # Combine the last digit of the Y uint16 and the first digit + # of the X uint16 + hex(((data_y_converted & 0xF) << 4) + (data_x_converted >> 8)), + # Get the first two digits of the Y uint16 + hex(data_y_converted >> 4)] +print("Uint8 Converted Values:", converted_values) +``` + +Which results bytes ready to be sent to the Switch: + +``` +Uint8 Converted Values: ['0xb3', '0x32', '0x6c'] +``` diff --git a/docs/Analog Stick and Button Input.md b/docs/Analog Stick and Button Input.md deleted file mode 100644 index c7e6d0b..0000000 --- a/docs/Analog Stick and Button Input.md +++ /dev/null @@ -1,93 +0,0 @@ -# Analog Stick and Button Input Information - -**Disclaimer:** The info within this document is sourced from the Switch reverse engineering -effort at [DekuNukem's Repository](https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering). - -The below sections contain info on the formulation and derivation of data -pertaining to the Nintendo Switch's controllers. The section on the analog -sticks contains info on encoding/decoding stick X/Y data, deadzones, -maximum range, etc. The button info section contains info on how each -button's state is communicated. - -## Analog Stick Information - -Information on a controller's analog sticks is stored in three primary -locations (user calibration excluded): - -| Obtained From | Byte # | Data Type | Info | -| --- | --- | --- | --- | --- | -| Standard Input Report | 6-11 | 2 uint16 | Contains X/Y Data of Analog Sticks1 -| SPI Flash Read (Offset 0x6080) | 13-30 | 12 uint16 LE | Dead Zone, Range ratio | -| SPI Flash Read (Offset 0x603D) | 7-24 | 12 uint16 LE | X/Y Min/Max and Centers | - -1 This data is relative, meaning that stick calibration data -*must* be used to encode/decode X and Y positions. - -## Decoding a Stick's Position - -**Note:** The following configuration values are used within Nxbt. - -First, we use the data obtained from the 0x603D SPI flash read to -derive the right/left stick calibration parameters. - -Sample data output by Nxbt: -``` -Payload: 0xA1 0x21 0x2B 0x90 0x00 0x00 0x00 0x74 0x58 0x75 0x4B 0x68 0x7C 0x90 - 0 1 2 3 4 5 6 7 8 9 10 11 12 13 -Subcommand: 0x90 0x10 0x3D 0x60 0x00 0x00 0x19 0xBA 0xF5 0x62 0x6F 0xC8 0x77 0xED - 14 15 16 17 18 19 20 21 22 23 24 25 26 27 - 0x95 0x5B 0x16 0xD8 0x7D 0xF2 0xB5 0x5F 0x86 0x65 0x5E 0xFF 0x82 0x82 - 28 29 30 31 32 33 34 35 - 0x82 0x0F 0x0F 0x0F 0x00 0x00 0x00 0x00 -``` - -Which gives us: - -``` -Left Stick: 0xBA 0xF5 0x62 0x6F 0xC8 0x77 0xED 0x95 0x5B -Right Stick: 0x16 0xD8 0x7D 0xF2 0xB5 0x5F 0x86 0x65 0x5E -``` - -Using the following equations, we can decode these values into meaningful ones. -Each stick's data is treated as an array of byte values for the equations. - -``` -# The nine stick bytes are labelled stick_cal[0] - stick_cal[8] here -uint16_t data[6] -data[0] = (stick_cal[1] << 8) & 0xF00 | stick_cal[0]; -data[1] = (stick_cal[2] << 4) | (stick_cal[1] >> 4); -data[2] = (stick_cal[4] << 8) & 0xF00 | stick_cal[3]; -data[3] = (stick_cal[5] << 4) | (stick_cal[4] >> 4); -data[4] = (stick_cal[7] << 8) & 0xF00 | stick_cal[6]; -data[5] = (stick_cal[8] << 4) | (stick_cal[7] >> 4); - -# These values used as such in, for example, a right stick -uint16_t rstick_center_x = data[0]; -uint16_t rstick_center_y = data[1]; -uint16_t rstick_x_min = rstick_center_x - data[2]; -uint16_t rstick_x_max = rstick_center_x + data[4]; -uint16_t rstick_y_min = rstick_center_y - data[3]; -uint16_t rstick_y_max = rstick_center_y + data[5]; -``` - -Resulting in the following values for the sticks: - -``` -Left Stick -~~~~~~~~~~ -Center X = -Center Y = -X Min = -X Max = -Y Min = -Y Max = - -Right Stick -~~~~~~~~~~~ -Center X = -Center Y = -X Min = -X Max = -Y Min = -Y Max = -``` diff --git a/docs/Miscellaneous Notes.md b/docs/Miscellaneous Notes.md index 2bd3bfe..2dd1351 100644 --- a/docs/Miscellaneous Notes.md +++ b/docs/Miscellaneous Notes.md @@ -21,3 +21,22 @@ inquiry input report packet. Eg: You could get away with emulating a Joy-Con (L) while having the Bluetooth alias set to "Pro Controller". + +## Pro Controller Grip Colours + +At the time of writing, grip colours are being read by the Switch, however, +they aren't being used to display the controller graphic. Eg: If the left +and right grip colours are set to white and the controller body is set to +black, the grip colours will be black. This is likely because Nintendo hasn't +produced any official Pro Controllers that feature a unique grip and body +colour. + +Currently, grip colours are hardcoded for the official, black Pro Controller. +The black pro controller reports all white (or blank) grip colours, however, +the Switch displays a slightly lighter grey when the icon is displayed. Any +emulated controller can produce this grip colour if the body colour is set +to #323232, the button colour set to #FFFFFF and the grip colours are set to +#FFFFFF. + +In the future, Nintendo may produce more Pro Controller colours, however, +at this point in time, setting the grip colour is not possible. diff --git a/nxbt/bluez.py b/nxbt/bluez.py index 6bb33e8..5d04cb6 100644 --- a/nxbt/bluez.py +++ b/nxbt/bluez.py @@ -150,6 +150,9 @@ def toggle_input_plugin(toggle): if cmd_err != "": raise Exception(cmd_err) + # Kill a bit of time here to ensure all services have restarted + time.sleep(0.5) + class BlueZ(): """Exposes the BlueZ D-Bus API as a Python object. diff --git a/nxbt/controller/controller.py b/nxbt/controller/controller.py index 7f8cd14..c895dea 100644 --- a/nxbt/controller/controller.py +++ b/nxbt/controller/controller.py @@ -65,5 +65,3 @@ class Controller(): self.bt.register_profile(self.SDP_RECORD_PATH, self.SDP_UUID, opts) except dbus.exceptions.DBusException: pass - - # self.bt.set_device_class(self.GAMEPAD_CLASS) diff --git a/nxbt/controller/input.py b/nxbt/controller/input.py index a7d2d89..37fe82a 100644 --- a/nxbt/controller/input.py +++ b/nxbt/controller/input.py @@ -82,7 +82,6 @@ class InputParser(): # Checking if this is a wait macro command if len(macro_input) < 2: - print("waiting") return # Arrays representing the 3 button bytes in the diff --git a/nxbt/controller/protocol.py b/nxbt/controller/protocol.py index 332a3f3..819be8b 100644 --- a/nxbt/controller/protocol.py +++ b/nxbt/controller/protocol.py @@ -99,13 +99,17 @@ class ControllerProtocol(): if self.controller_type == ControllerTypes.JOYCON_R: self.left_stick_status = [0x00] * 3 else: - self.left_stick_status = [0x74, 0x58, 0x75] + # Center values which are also reported under + # SPI Stick calibration reads + self.left_stick_status = [0x6F, 0xC8, 0x77] # Disable right stick if we have a left Joy-Con if self.controller_type == ControllerTypes.JOYCON_L: self.right_stick_status = [0x00] * 3 else: - self.right_stick_status = [0x4B, 0x68, 0x7C] + # Center values which are also reported under + # SPI Stick calibration reads + self.right_stick_status = [0x16, 0xD8, 0x7D] self.vibrator_report = random.choice(self.VIBRATOR_BYTES) @@ -278,8 +282,6 @@ class ControllerProtocol(): self.report[5] = shared self.report[6] = lower - print(self.report) - def set_device_info(self): # ACK Reply diff --git a/nxbt/controller/server.py b/nxbt/controller/server.py index cf213d3..9679efb 100644 --- a/nxbt/controller/server.py +++ b/nxbt/controller/server.py @@ -15,7 +15,19 @@ from .utils import format_msg_controller, format_msg_switch class ControllerServer(): def __init__(self, controller_type, adapter_path="/org/bluez/hci0", - lock=None, colour_body=None, colour_buttons=None): + state=None, task_queue=None, lock=None, colour_body=None, + colour_buttons=None): + + if state: + self.state = state + else: + self.state = { + "state": "", + "finished_macros": [], + "errors": None + } + + self.task_queue = task_queue self.controller_type = controller_type self.colour_body = colour_body @@ -38,7 +50,7 @@ class ControllerServer(): self.input = InputParser(self.protocol) - def run(self, reconnect_address=None, state=None, task_queue=None): + def run(self, reconnect_address=None): """Runs the mainloop of the controller server. :param reconnect_address: The Bluetooth MAC address of a @@ -46,115 +58,137 @@ class ControllerServer(): :type reconnect_address: string, optional """ - if state: - state["state"] = "initializing" + self.state["state"] = "initializing" try: # If we have a lock, prevent other controllers - # from initializing at the same time and saturating - # the DBus + # from initializing at the same time and saturating the DBus, + # potentially causing a kernel panic. if self.lock: self.lock.acquire() try: self.controller.setup() if reconnect_address: - itr, ctrl = self.reconnect(reconnect_address, state=state) + itr, ctrl = self.reconnect(reconnect_address) else: - itr, ctrl = self.connect(state=state) - except Exception: + itr, ctrl = self.connect() + finally: if self.lock: self.lock.release() self.switch_address = itr.getsockname()[0] - if state: - state["state"] = "connected" + self.state["state"] = "connected" - # Mainloop - while True: - # Attempt to get output from Switch + self.mainloop(itr, ctrl) + + except Exception: + self.state["state"] = "crashed" + self.state["errors"] = traceback.format_exc() + return self.state + + def mainloop(self, itr, ctrl): + + # Mainloop + while True: + # Attempt to get output from Switch + try: + reply = itr.recv(50) + if len(reply) > 40: + print(format_msg_switch(reply)) + except BlockingIOError: + reply = None + + # Getting any inputs from the task queue + if self.task_queue: try: - reply = itr.recv(50) - if len(reply) > 40: - print(format_msg_switch(reply)) - except BlockingIOError: - reply = None + msg = self.task_queue.get_nowait() + print(msg) + if msg: + self.input.buffer_macro( + msg["macro"], msg["macro_id"]) + except queue.Empty: + pass - # Getting any inputs from the task queue - if task_queue: - try: - msg = task_queue.get_nowait() - print(msg) - if msg: - self.input.buffer_macro( - msg["macro"], msg["macro_id"]) - except queue.Empty: - pass + self.protocol.process_commands(reply) + self.input.set_protocol_input(state=self.state) + msg = self.protocol.get_report() - self.protocol.process_commands(reply) - self.input.set_protocol_input(state=state) - msg = self.protocol.get_report() + if reply: + print(format_msg_controller(msg)) - if reply: - print(format_msg_controller(msg)) + try: + itr.sendall(msg) + except BlockingIOError: + continue + except OSError as e: + # Attempt to reconnect to the Switch + itr, ctrl = self.save_connection(e) - try: - itr.sendall(msg) - except BlockingIOError: - continue - except OSError as e: - # Attempt to reconnect to the Switch - if self.reconnect_counter < 2: - try: - print("Attempting to reconnect") - # Reinitialize the protocol - self.protocol = ControllerProtocol( - self.controller_type, - self.bt.address, - colour_body=self.colour_body, - colour_buttons=self.colour_buttons) - itr, ctrl = self.reconnect(self.switch_address, - state=state) - except OSError: - self.reconnect_counter += 1 - print(e) - time.sleep(0.5) - continue - # If we can't reconnect, transition to attempting - # to connect to any Switch. - else: - print("Connecting") - # Reinitialize the protocol - self.protocol = ControllerProtocol( - self.controller_type, - self.bt.address, - colour_body=self.colour_body, - colour_buttons=self.colour_buttons) - itr, ctrl = self.connect(state=state) - self.switch_address = itr.getsockname()[0] - - # Respond at 120Hz for Pro Controller - # or 60Hz for Joy-Cons - if self.controller_type == ControllerTypes.PRO_CONTROLLER: - time.sleep(1/120) - else: - time.sleep(1/60) - - except Exception as e: - if state: - state["state"] = "crashed" - state["errors"] = traceback.format_exc() + # Respond at 120Hz for Pro Controller + # or 60Hz for Joy-Cons + if self.controller_type == ControllerTypes.PRO_CONTROLLER: + time.sleep(1/120) else: - raise e + time.sleep(1/60) - def connect(self, state=None): + def save_connection(self, error, state=None): + + while self.reconnect_counter < 2: + try: + print("Attempting to reconnect") + # Reinitialize the protocol + self.protocol = ControllerProtocol( + self.controller_type, + self.bt.address, + colour_body=self.colour_body, + colour_buttons=self.colour_buttons) + if self.lock: + self.lock.acquire() + try: + itr, ctrl = self.reconnect(self.switch_address) + return itr, ctrl + finally: + if self.lock: + self.lock.release() + except OSError: + self.reconnect_counter += 1 + print(error) + time.sleep(0.5) + + # If we can't reconnect, transition to attempting + # to connect to any Switch. + print("Connecting") + self.reconnect_counter = 0 + + # Reinitialize the protocol + self.protocol = ControllerProtocol( + self.controller_type, + self.bt.address, + colour_body=self.colour_body, + colour_buttons=self.colour_buttons) + + if self.lock: + self.lock.acquire() + try: + itr, ctrl = self.connect() + finally: + if self.lock: + self.lock.release() + + self.state["state"] = "connected" + + self.switch_address = itr.getsockname()[0] + + return itr, ctrl + + def connect(self): """Configures as a specified controller, pairs with a Nintendo Switch, and creates/accepts sockets for communication with the Switch. """ - if state: - state["state"] = "connecting" + self.state["state"] = "connecting" # Creating control and interrupt sockets s_ctrl = socket.socket( @@ -223,15 +257,14 @@ class ControllerServer(): return itr, ctrl - def reconnect(self, reconnect_address, state=None): + def reconnect(self, reconnect_address): """Attempts to reconnect with a Switch at the given address. :param reconnect_address: The Bluetooth MAC address of the Switch :type reconnect_address: string """ - if state: - state["state"] = "reconnecting" + self.state["state"] = "reconnecting" # Creating control and interrupt sockets ctrl = socket.socket( diff --git a/nxbt/nxbt.py b/nxbt/nxbt.py index 4e9f4e2..9c61543 100644 --- a/nxbt/nxbt.py +++ b/nxbt/nxbt.py @@ -119,7 +119,7 @@ class Nxbt(): return macro_id - def create_controller(self, controller_type, adapter_path, block=True, + def create_controller(self, controller_type, adapter_path, colour_body=None, colour_buttons=None): if adapter_path not in self.get_available_adapters(): @@ -145,6 +145,9 @@ class Nxbt(): self.__controller_counter += 1 self.__adapters_in_use.append(adapter_path) + # Block until the controller is ready + # This needs to be done to prevent race conditions + # on DBus resources. if type(controller_index) == int: while True: if controller_index in self.manager_state.keys(): @@ -197,10 +200,11 @@ class ControllerManager(): server = ControllerServer(controller_type, adapter_path=adapter_path, lock=self.lock, + state=controller_state, + task_queue=controller_queue, colour_body=colour_body, colour_buttons=colour_buttons) - controller = Process(target=server.run, args=( - None, controller_state, controller_queue)) + controller = Process(target=server.run) controller.daemon = True controller.start() diff --git a/scripts/proxy.py b/scripts/proxy.py index e95b18c..0a3e8ee 100644 --- a/scripts/proxy.py +++ b/scripts/proxy.py @@ -107,8 +107,8 @@ if __name__ == "__main__": port_itr = 19 message_buffer = [] - bt = BlueZ() toggle_input_plugin(False) + bt = BlueZ(adapter_path="/org/bluez/hci0") controller = Controller(bt, controller_type) @@ -136,7 +136,7 @@ if __name__ == "__main__": # Ensure we are paired/connected to the JC print("Attempting to re-pair with device") - devices = bt.discover_devices(alias="Joy-Con (L)", timeout=8) + devices = bt.discover_devices(alias="Pro Controller", timeout=8) jc_device_path = None for key in devices.keys(): print(devices[key]["Address"]) @@ -291,3 +291,6 @@ if __name__ == "__main__": switch_ctrl.close() raise e + + finally: + toggle_input_plugin(True) diff --git a/scripts/sticks.py b/scripts/sticks.py new file mode 100644 index 0000000..bf717b6 --- /dev/null +++ b/scripts/sticks.py @@ -0,0 +1,81 @@ +# Left Stick Calibration +stick_cal_left = [0xBA, 0xF5, 0x62, 0x6F, 0xC8, 0x77, 0xED, 0x95, 0x5B] +# Right Stick Calibration +stick_cal_right = [0x16, 0xD8, 0x7D, 0xF2, 0xB5, 0x5F, 0x86, 0x65, 0x5E] +data_left = [0] * 6 +data_right = [0] * 6 + +# Left stick uint16 conversion +data_left[0] = (stick_cal_left[1] << 8) & 0xF00 | stick_cal_left[0] +data_left[1] = (stick_cal_left[2] << 4) | (stick_cal_left[1] >> 4) +data_left[2] = (stick_cal_left[4] << 8) & 0xF00 | stick_cal_left[3] +data_left[3] = (stick_cal_left[5] << 4) | (stick_cal_left[4] >> 4) +data_left[4] = (stick_cal_left[7] << 8) & 0xF00 | stick_cal_left[6] +data_left[5] = (stick_cal_left[8] << 4) | (stick_cal_left[7] >> 4) + +# Right stick uint16 conversion +data_right[0] = (stick_cal_right[1] << 8) & 0xF00 | stick_cal_right[0] +data_right[1] = (stick_cal_right[2] << 4) | (stick_cal_right[1] >> 4) +data_right[2] = (stick_cal_right[4] << 8) & 0xF00 | stick_cal_right[3] +data_right[3] = (stick_cal_right[5] << 4) | (stick_cal_right[4] >> 4) +data_right[4] = (stick_cal_right[7] << 8) & 0xF00 | stick_cal_right[6] +data_right[5] = (stick_cal_right[8] << 4) | (stick_cal_right[7] >> 4) + +# Left Stick Decode +left_center_x = data_left[2] +left_center_y = data_left[3] +left_x_min = left_center_x - data_left[0] +left_x_max = left_center_x + data_left[4] +left_y_min = left_center_y - data_left[1] +left_y_max = left_center_y + data_left[5] + +print("Left Stick Values:") +print("~~~~~~~~~~~~~~~~~~") +print("Left Center X and Y:", left_center_x, left_center_y) +print("Left X Min/Max: ", left_x_min, "", left_x_max) +print("Left Y Min/Max: ", left_y_min, "", left_y_max) + +# Right Stick Decode +right_center_x = data_right[0] +right_center_y = data_right[1] +right_x_min = right_center_x - data_right[2] +right_x_max = right_center_x + data_right[4] +right_y_min = right_center_y - data_right[3] +right_y_max = right_center_y + data_right[5] + +print("\nRight Stick Values:") +print("~~~~~~~~~~~~~~~~~~~") +print("Right Center X and Y:", right_center_x, right_center_y) +print("Right X Min/Max: ", right_x_min, "", right_x_max) +print("Right Y Min/Max: ", right_y_min, "", right_y_max) + +# Sample Stick Data Conversion: +stick_data = [0xB3, 0x32, 0x6C] +stick_horizontal = stick_data[0] | ((stick_data[1] & 0xF) << 8) +stick_vertical = (stick_data[1] >> 4) | (stick_data[2] << 4) + +print("\nExample Left Stick Data to Ratio Conversion:") +print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") +print("Raw X/Y Uint16 Values:", stick_horizontal, stick_vertical) +ratio_x = abs((stick_horizontal - left_center_x)) / (left_x_min - left_center_x) +ratio_y = (stick_vertical - left_center_y) / (left_y_min - left_center_y) +print("Relative X/Y Values", ratio_x, ratio_y) + +print("\nExample Left Stick Ratio to Data Conversion:") +print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~") +data_x_converted = (abs(ratio_x) * (left_x_min - left_center_x) + left_center_x) +data_x_converted = int(round(data_x_converted)) +data_y_converted = (abs(ratio_y) * (left_y_min - left_center_y) + left_center_y) +data_y_converted = int(round(data_y_converted)) +print("X/Y Converted Values:", data_x_converted, data_y_converted) + +# Converting the two X/Y uint16 values to 3 uint8 Little Endian values +converted_values = [ + # Get the last two hex digits + hex(data_x_converted & 0xFF), + # Combine the last digit of the Y uint16 and the first digit + # of the X uint16 + hex(((data_y_converted & 0xF) << 4) + (data_x_converted >> 8)), + # Get the first two digits of the Y uint16 + hex(data_y_converted >> 4)] +print("Uint8 Converted Values:", converted_values) diff --git a/test.py b/test.py deleted file mode 100644 index d320a88..0000000 --- a/test.py +++ /dev/null @@ -1,29 +0,0 @@ -from ctypes import c_uint16 - -# Left Stick Calibration -stick_cal = [0xBA, 0xF5, 0x62, 0x6F, 0xC8, 0x77, 0xED, 0x95, 0x5B] -# Right Stick Calibration -stick_cal = [0x16, 0xD8, 0x7D, 0xF2, 0xB5, 0x5F, 0x86, 0x65, 0x5E] -data = [0] * 6 - -# The nine stick bytes are labelled stick_cal[0] - stick_cal[8] here -data[0] = (stick_cal[1] << 8) & 0xF00 | stick_cal[0] -data[1] = (stick_cal[2] << 4) | (stick_cal[1] >> 4) -data[2] = (stick_cal[4] << 8) & 0xF00 | stick_cal[3] -data[3] = (stick_cal[5] << 4) | (stick_cal[4] >> 4) -data[4] = (stick_cal[7] << 8) & 0xF00 | stick_cal[6] -data[5] = (stick_cal[8] << 4) | (stick_cal[7] >> 4) - -# These values used as such in, for example, a right stick -center_x = data[0] -center_y = data[1] -x_min = c_uint16(center_x - data[2]) -x_max = c_uint16(center_x + data[4]) -y_min = c_uint16(center_y - data[3]) -y_max = c_uint16(center_y + data[5]) -center_x = c_uint16(data[0]) -center_y = c_uint16(data[1]) - -print("Center X and Y", center_x, center_y) -print("X Min/Max", x_min, x_max) -print("Y Min/Max", y_min, y_max)