Fixed dual controller support
This commit is contained in:
parent
21f7f42cc9
commit
a4bce53d1e
13 changed files with 469 additions and 238 deletions
48
demo.py
48
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"])
|
||||
|
|
|
|||
197
docs/Analog Stick Input.md
Normal file
197
docs/Analog Stick Input.md
Normal file
|
|
@ -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 Sticks<sup>1</sup>
|
||||
| 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 |
|
||||
|
||||
<sup>1</sup> 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']
|
||||
```
|
||||
|
|
@ -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 Sticks<sup>1</sup>
|
||||
| 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 |
|
||||
|
||||
<sup>1</sup> 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 =
|
||||
```
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
10
nxbt/nxbt.py
10
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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
81
scripts/sticks.py
Normal file
81
scripts/sticks.py
Normal file
|
|
@ -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)
|
||||
29
test.py
29
test.py
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue