-
Notifications
You must be signed in to change notification settings - Fork 1
/
joystick.py
253 lines (217 loc) · 7.67 KB
/
joystick.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# Source: https://github.com/marsauto/europilot/blob/master/europilot/joystick.py
# This code is NOT MINE.
import os
import sys
import time
import random
from binascii import hexlify
BUTTON_NAME = """
0200=wheel-axis
0201=clutch
0203=brake
0202=gas
0105=paddle-left
0104=paddle-right
0107=wheel-button-left-1
0114=wheel-button-left-2
0115=wheel-button-left-3
0106=wheel-button-right-1
0112=wheel-button-right-2
0113=wheel-button-right-3
0101=shifter-button-left
0102=shifter-button-right
0103=shifter-button-up
0100=shifter-button-down
0204=dpad-left/right
0205=dpad-up/down
010b=shifter-button-1
0108=shifter-button-2
0109=shifter-button-3
010a=shifter-button-4
010c=gear-1
010d=gear-2
010e=gear-3
010f=gear-4
0110=gear-5
0111=gear-6
0116=gear-R
"""
class Bytewurst(object):
def __init__(self, bs):
self.raw = bs
self.ints = map(ord, bs)
def __repr__(self):
return ' '.join(map(hexlify, self.raw))
@property
def int(self):
"""
For "01 0A" ints would be [1, 10], so::
>>> bs = '\x01\x0A'
>>> bw = Bytewurst(bs)
>>> bw.int == (1 * 1) + (10 * 256)
"""
def powergenerator(start=0):
"""Generate powers of 256"""
i = start
while True:
yield 256 ** i
i += 1
return sum(a * b for a, b in zip(self.ints, powergenerator()))
@property
def hexLE(self):
return hexlify(self.raw)
@property
def bits(self):
return ' '.join([format(x, '08b') for x in self.ints])
class Button(Bytewurst):
def __init__(self, bs):
super(Button, self).__init__(bs)
button_namedict = dict(line.split('=') for line in
BUTTON_NAME.strip().split('\n'))
self.name = button_namedict.get(self.hexLE, 'UNKNOWN:%s' % self.hexLE)
class Value(Bytewurst):
def __repr__(self):
if self.int == 0:
return ' off'
elif self.int == 1:
return ' on'
else:
return ' %d' % self.int
def int_normalized(self, name):
""" Normalizes value to an adequate range
For wheel values, the output range is normalized to [-32767, 32767].
For pedal values, the output range is normalized to [0, 65535].
For Arrow Pad values, the output range is normalized to [-1, 1].
"""
v = super(Value, self).int
if name == 'wheel-axis':
if v >= 32769:
v = v - 65536
elif name == 'clutch' or name == 'brake' or name == 'gas':
if v >= 32769:
v = -v + 98304
else:
v = -v + 32767
elif name == 'dpad-left/right' or name == 'dpad-up/down':
if v == 32769:
v = -1
elif v == 32767:
v = 1
return v
class Message(object):
def __init__(self, bs):
self.bs = bs
self.raw_seq = bs[0:4]
self.raw_value = bs[4:6]
self.raw_id = bs[6:8]
self.ints = map(ord, bs)
self.sequence = Bytewurst(bs[0:4])
self.value = Value(bs[4:6])
self.button = Button(bs[6:8])
def __repr__(self):
values = (self.button.name,
self.value.int_normalized(self.button.name))
return ' '.join(map(str, values))
class LinuxVirtualJoystick(object):
"""
A Virtual joystick driver is implemented by attaching userspace device
drivers in the kernel by outputting events into udev.
Relevant information about the virtual driver was derived with evtest.
A LinuxVirtualJoystick object must be initialized before ETS is started
for the game to recognize the virtual driver.
Note that you may have to adjust the control settings inside ETS to
map the events with the necessary values. Check the project page for more
details.
"""
def __init__(self, name='Virtual G27 Racing Wheel', bustype=0x0003,
vendor=0x046d, product=0xc29b, version=0x0111, events=None):
if events is None:
events = (
# EV_KEY
uinput.BTN_TRIGGER,
uinput.BTN_THUMB,
uinput.BTN_THUMB2,
uinput.BTN_TOP,
uinput.BTN_TOP2,
uinput.BTN_PINKIE,
uinput.BTN_BASE,
uinput.BTN_BASE2,
uinput.BTN_BASE3,
uinput.BTN_BASE4,
uinput.BTN_BASE5,
uinput.BTN_BASE6,
(0x01, 0x12c), # (event code 300)
(0x01, 0x12d), # (event code 301)
(0x01, 0x12e), # (event code 302)
uinput.BTN_DEAD,
uinput.BTN_TRIGGER_HAPPY1,
uinput.BTN_TRIGGER_HAPPY2,
uinput.BTN_TRIGGER_HAPPY3,
uinput.BTN_TRIGGER_HAPPY4,
uinput.BTN_TRIGGER_HAPPY5,
uinput.BTN_TRIGGER_HAPPY6,
uinput.BTN_TRIGGER_HAPPY7,
# EV_ABS
uinput.ABS_X + (-32767, 32767, 0, 0), # steering wheel
uinput.ABS_Z + (0, 65535, 0, 0), # acceleration
uinput.ABS_RZ + (0, 65535, 0, 0), # break
uinput.ABS_HAT0X + (-1, 1, 0, 0),
uinput.ABS_HAT0Y + (-1, 1, 0, 0),
)
self.device = uinput.Device(events,
name=name,
bustype=bustype,
vendor=vendor,
product=product,
version=version)
def emit(self, angle, accel=None, brk=None):
# emit as one atomic event, by using syn=True at the last emit call
# for more information check the source code at
# https://github.com/tuomasjjrasanen/python-uinput/blob/master/src/__init__.py#L191
if accel:
self.device.emit(uinput.ABS_Z, accel, syn=False)
if brk:
self.device.emit(uinput.ABS_RZ, brk, syn=False)
self.device.emit(uinput.ABS_X, angle, syn=True)
def __del__(self):
self.device.destroy()
# Handle platform dependent joystick impl
if sys.platform.startswith('linux'):
import uinput
VirtualJoystick = LinuxVirtualJoystick
elif sys.platform == 'darwin':
pass
if __name__ == '__main__':
def _dump_messages(input_):
with open(input_, 'rb') as device:
while True:
bs = device.read(8)
message = Message(bs)
print(message)
def _dump_dummy_messages():
# Need to simulate straight wheel axis to test realtime fps adjustment.
straight = False
straight_start_time = None
straight_duration = 5
while True:
if not straight and random.randint(0, 99) == 0:
# Straight wheel axis for 5 secs.
straight = True
straight_start_time = time.time()
if straight and \
time.time() - straight_start_time > straight_duration:
# End of straight wheel axis
straight = False
straight_start_time = None
# Axis value 0 will be regarded as straight wheel regardless of
# `train.FpsAdjuster._max_straight_wheel_axis`.
wheel_axis = random.randint(-32767, 32767) if not straight else 0
print('wheel-axis ' + str(wheel_axis))
# TODO: Get device path as argument
device = '/dev/input/js0'
if os.path.exists(device):
_dump_messages(device)
else:
# When joystick doesn't exist. Let's dump dummy messages.
# TODO: Warn this to stdout so that we can be aware of mock g27.
_dump_dummy_messages()