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)