forked from fossephate/switch-controller
-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbridge.py
executable file
·218 lines (166 loc) · 7.61 KB
/
bridge.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python3
import argparse
from contextlib import contextmanager
import sdl2
import sdl2.ext
import struct
import binascii
import serial
import math
import time
from tqdm import tqdm
def enumerate_controllers():
print('Controllers connected to this system:')
for n in range(sdl2.SDL_NumJoysticks()):
name = sdl2.SDL_JoystickNameForIndex(n)
if name is not None:
name = name.decode('utf8')
print(n, ':', name)
print('Note: These are numbered by connection order. Numbers will change if you unplug a controller.')
def get_controller(c):
try:
n = int(c, 10)
return sdl2.SDL_GameControllerOpen(n)
except ValueError:
for n in range(sdl2.SDL_NumJoysticks()):
name = sdl2.SDL_JoystickNameForIndex(n)
if name is not None:
name = name.decode('utf8')
if name == c:
return sdl2.SDL_GameControllerOpen(n)
raise Exception('Controller not found: %s'.format(c))
buttonmapping = [
sdl2.SDL_CONTROLLER_BUTTON_X, # Y
sdl2.SDL_CONTROLLER_BUTTON_A, # B
sdl2.SDL_CONTROLLER_BUTTON_B, # A
sdl2.SDL_CONTROLLER_BUTTON_Y, # X
sdl2.SDL_CONTROLLER_BUTTON_LEFTSHOULDER, # L
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, # R
sdl2.SDL_CONTROLLER_BUTTON_INVALID, # ZL
sdl2.SDL_CONTROLLER_BUTTON_INVALID, # ZR
sdl2.SDL_CONTROLLER_BUTTON_BACK, # SELECT
sdl2.SDL_CONTROLLER_BUTTON_START, # START
sdl2.SDL_CONTROLLER_BUTTON_LEFTSTICK, # LCLICK
sdl2.SDL_CONTROLLER_BUTTON_RIGHTSTICK, # RCLICK
sdl2.SDL_CONTROLLER_BUTTON_GUIDE, # HOME
sdl2.SDL_CONTROLLER_BUTTON_INVALID, # CAPTURE
]
axismapping = [
sdl2.SDL_CONTROLLER_AXIS_LEFTX, # LX
sdl2.SDL_CONTROLLER_AXIS_LEFTY, # LY
sdl2.SDL_CONTROLLER_AXIS_RIGHTX, # RX
sdl2.SDL_CONTROLLER_AXIS_RIGHTY, # RY
]
hatmapping = [
sdl2.SDL_CONTROLLER_BUTTON_DPAD_UP, # UP
sdl2.SDL_CONTROLLER_BUTTON_DPAD_RIGHT, # RIGHT
sdl2.SDL_CONTROLLER_BUTTON_DPAD_DOWN, # DOWN
sdl2.SDL_CONTROLLER_BUTTON_DPAD_LEFT, # LEFT
]
hatcodes = [8, 0, 2, 1, 4, 8, 3, 8, 6, 7, 8, 8, 5, 8, 8]
axis_deadzone = 1000
trigger_deadzone = 0
def controller_states(controller_id):
sdl2.SDL_Init(sdl2.SDL_INIT_GAMECONTROLLER)
controller = get_controller(controller_id)
try:
print('Using "{:s}" for input.'.format(
sdl2.SDL_JoystickName(sdl2.SDL_GameControllerGetJoystick(controller)).decode('utf8')))
except AttributeError:
print('Using controller {:s} for input.'.format(controller_id))
while True:
buttons = sum([sdl2.SDL_GameControllerGetButton(controller, b)<<n for n,b in enumerate(buttonmapping)])
buttons |= (abs(sdl2.SDL_GameControllerGetAxis(controller, sdl2.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > trigger_deadzone) << 6
buttons |= (abs(sdl2.SDL_GameControllerGetAxis(controller, sdl2.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > trigger_deadzone) << 7
hat = hatcodes[sum([sdl2.SDL_GameControllerGetButton(controller, b)<<n for n,b in enumerate(hatmapping)])]
rawaxis = [sdl2.SDL_GameControllerGetAxis(controller, n) for n in axismapping]
axis = [((0 if abs(x) < axis_deadzone else x) >> 8) + 128 for x in rawaxis]
rawbytes = struct.pack('>BHBBBB', hat, buttons, *axis)
yield binascii.hexlify(rawbytes) + b'\n'
def replay_states(filename):
with open(filename, 'rb') as replay:
yield from replay.readlines()
def example_macro():
buttons = 0
hat = 8
rx = 128
ry = 128
for i in range(240):
lx = int((1.0 + math.sin(2 * math.pi * i / 240)) * 127)
ly = int((1.0 + math.cos(2 * math.pi * i / 240)) * 127)
rawbytes = struct.pack('>BHBBBB', hat, buttons, lx, ly, rx, ry)
yield binascii.hexlify(rawbytes) + b'\n'
class InputStack(object):
def __init__(self):
self.l = []
def push(self, it):
self.l.append(it)
def pop(self):
self.l.pop()
def __iter__(self):
return self
def __next__(self):
while True:
try:
return next(self.l[-1])
except StopIteration:
self.l.pop()
except IndexError:
raise StopIteration
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--list-controllers', action='store_true', help='Display a list of controllers attached to the system.')
parser.add_argument('-c', '--controller', type=str, default='0', help='Controller to use. Default: 0.')
parser.add_argument('-b', '--baud-rate', type=int, default=115200, help='Baud rate. Default: 115200.')
parser.add_argument('-p', '--port', type=str, default='/dev/ttyUSB0', help='Serial port. Default: /dev/ttyUSB0.')
parser.add_argument('-R', '--record', type=str, default=None, help='Record events to file.')
parser.add_argument('-P', '--playback', type=str, default=None, help='Play back events from file.')
parser.add_argument('-d', '--dontexit', action='store_true', help='Switch to live input when playback finishes, instead of exiting. Default: False.')
parser.add_argument('-q', '--quiet', action='store_true', help='Disable speed meter. Default: False.')
args = parser.parse_args()
if args.list_controllers:
sdl2.SDL_Init(sdl2.SDL_INIT_GAMECONTROLLER)
enumerate_controllers()
exit(0)
ser = serial.Serial(args.port, args.baud_rate, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=None)
print('Using {:s} at {:d} baud for comms.'.format(args.port, args.baud_rate))
input_stack = InputStack()
if args.playback is None or args.dontexit:
live = controller_states(args.controller)
next(live) # pull a controller update to make it print the name before starting speed meter
input_stack.push(live)
if args.playback is not None:
input_stack.push(replay_states(args.playback))
with (open(args.record, 'wb') if args.record is not None else contextmanager(lambda: iter([None]))()) as record:
with tqdm(unit=' updates', disable=args.quiet) as pbar:
try:
while True:
for event in sdl2.ext.get_events():
# we have to fetch the events from SDL in order for the controller
# state to be updated.
# example of running a macro when a joystick button is pressed:
#if event.type == sdl2.SDL_JOYBUTTONDOWN:
# if event.jbutton.button == 1:
# input_stack.push(example_macro())
# or play from file:
# input_stack.push(replay_states(filename))
pass
try:
message = next(input_stack)
ser.write(message)
if record is not None:
record.write(message)
except StopIteration:
break
# update speed meter on console.
pbar.set_description('Sent {:s}'.format(message[:-1].decode('utf8')))
pbar.update()
while True:
# wait for the arduino to request another state.
response = ser.read(1)
if response == b'U':
break
elif response == b'X':
print('Arduino reported buffer overrun.')
except KeyboardInterrupt:
print('\nExiting due to keyboard interrupt.')