forked from john-holly/pewreview-driver
/
launcher.py
159 lines (127 loc) · 3.73 KB
/
launcher.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
#!/usr/bin/env python3
#
# Author: John Holly
#
# Driver for Dream Cheeky USB Missile Launcher (The green/black model with 3 darts).
# Don't buy it from here, this is just the same model..
#
# https://www.amazon.com/Unknown-782-USB-Missile-Launcher/dp/B000XYR2CM/ref=sr_1_4?keywords=dream+cheeky&qid=1571775979&sr=8-4
#
# Please verify your USB missile launcher's vendor ID and product ID match what is
# listed below. This will not work for the Thunder or Storm launchers.
#
# $ lsusb
# ...
# Bus 003 Device 018: ID 0a81:0701 Chesen Electronics Corp. USB Missile Launcher
#
from enum import Enum
import log
import os
import sys
import time
import hid
"""
RQ_TYPE_TO_CLASS_INTERFACE (0x21 or 100001):
This is a bitfield for specifying the recipient and type of the request.
This specifies the missile launcher as a human interface device in the
setup packet.
Recipient: Interface
Type: Class
(In the following table D<num> is the bit position)
D7 Data Phase Transfer Direction
0 = Host to Device
1 = Device to Host
D6..5 Type
0 = Standard
1 = Class
2 = Vendor
3 = Reserved
D4..0 Recipient
0 = Device
1 = Interface
2 = Endpoint
3 = Other
"""
TO_CLASS_INTERFACE = 0x21
# Request type used - we are setting the configuration of hardware state
SET_CONFIGURATION = 0x09
# Vendor/Product ID
CHESEN_ELECTRONICS_CORP = 0x0a81
USB_MISSILE_LAUNCHER = 0x0701
FIRE_TIME = 6250
def usleep(millis):
time.sleep(millis / 1000.0)
class Command(Enum):
UP = [0x02]
DOWN = [0x01]
LEFT = [0x04]
RIGHT = [0x08]
FIRE = [0x10]
RESET = [0x20]
STATUS = [0x40]
class TimedCommand:
def __init__(self, cmd, nanos):
self.cmd = cmd
self.nanos = nanos
class Launcher:
logger = log.get(__name__)
def __init__(self):
self.dev = hid.device(CHESEN_ELECTRONICS_CORP, USB_MISSILE_LAUNCHER)
if self.dev is None:
raise ValueError('Launcher not found.')
else:
self.dev.open(CHESEN_ELECTRONICS_CORP, USB_MISSILE_LAUNCHER)
def send(self, cmd):
self.dev.send_feature_report(cmd)
def up(self):
self.logger.info(Command.UP.name)
self.send(Command.UP)
def down(self):
self.logger.info(Command.DOWN.name)
self.send(Command.DOWN)
def left(self):
self.logger.info(Command.LEFT.name)
self.send(Command.LEFT)
def right(self):
self.logger.info(Command.RIGHT.name)
self.send(Command.RIGHT)
def fire(self, nanos=FIRE_TIME):
self.logger.info(Command.FIRE.name)
self.send(Command.FIRE)
usleep(nanos)
def stop(self):
"""
Stops an ongoing command
:return:
"""
self.logger.info(Command.RESET.name)
self.send(Command.RESET.value)
def stream(self, stream):
"""
Execute a chain of commands
:param stream: A list of TimedCommand
:return:
"""
self.logger.debug("Received stream: {}".format([c.cmd for c in stream]))
for command in stream:
self.logger.info(command.cmd.name)
self.send(command.cmd.value)
usleep(command.nanos)
self.stop()
def demo():
launcher = Launcher()
launcher.stream([
TimedCommand(Command.UP, 1000),
TimedCommand(Command.DOWN, 1000),
TimedCommand(Command.LEFT, 1000),
TimedCommand(Command.RIGHT, 1000),
TimedCommand(Command.FIRE, FIRE_TIME),
TimedCommand(Command.FIRE, FIRE_TIME),
TimedCommand(Command.FIRE, FIRE_TIME),
TimedCommand(Command.RESET, 0),
])
if __name__ == '__main__':
# TODO: Create udev rules so I can run unprivileged
if not os.geteuid() == 0:
sys.exit("Script must be run as root.")
demo()