/
tryusb.py
259 lines (226 loc) · 9.62 KB
/
tryusb.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
254
255
256
257
258
259
#
# Copyright (C) 2016 Dave Hocker (email: AtHomeX10@gmail.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the LICENSE file for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (the LICENSE file). If not, see <http://www.gnu.org/licenses/>.
#
"""
This code was written expressly to learn about the Anyma uDMX interface and
how to work with it using Python and pyusb on a raspberry pi (RPi).
Much of the learning started with the C++ based uDMX utility written
by Markus Baertschi (https://github.com/markusb/uDMX-linux). Many thanks
to Markus for this work.
After gaining enough knowledge on how to use pyusb, my next step will be
to write a Python version of the uDMX utility (which I will push to my github
accuont). Hopefully, the combination of tryusb.py and uDMX.py will provide
a good base for others wanting to work with an inexpensive DMX interface on an RPi.
Side Note: You can find a number of clone uDMX interfaces on eBay. The one
used for this exercise was purchased from tomtop_auction
http://www.ebay.com/usr/tomtop_auction?_trksid=p2047675.l2559
This is not an endoresement or recommendation. It is just information in
case you are looking for a uDMX type of interface.
"""
# Requires a venv with pyusb installed. See requirements.txt.
# If you apply the udev rule, the uDMX will be accessible as the pi user.
# If youdo not apply the udev rule, you must be running sudo.
# This appears to be the recommended way to activate the venv
# when running under sudo. However, it hard wires the venv to this script.
# At a minimum we need a runtime test so the work around is only executed
# when running on an RPi.
# the rest of this research code assumes the udev rule has been applied.
# import platform
# Here we make a gross assumption that an arm cpu is an RPi.
# if platform.machine().startswith("arm"):
# activate_this = "/home/pi/Virtualenvs/pyusb/bin/activate_this.py"
# execfile(activate_this, dict(__file__=activate_this))
import usb # This is pyusb
import time
def ctrl_transfer(dev, bmRequestType, channelMode, wValue=0, wIndex=0, data_or_wLength=1):
"""
Send control transfer with retry
Retrying would probably not be necessary if we were sending whole 512 byte blocks.
But, overflow errors seem to occur randomly when sending partial blocks.
:param dev:
:param bmRequestType:
:param channelMode:
:param wValue:
:param wIndex:
:param data_or_wLength:
:return:
"""
retry = 5
n = 0
while retry:
try:
n = dev.ctrl_transfer(bmRequestType, channelMode, wValue=wValue,
wIndex=wIndex, data_or_wLength=data_or_wLength)
return n
except Exception as ex:
print(ex)
if retry <= 0:
return 0
retry -= 1
# DEVICE ID 16c0:05dc
# USB id's for uDMX (aka Anyma uDMX)
# The attached DMX device was a Venue ThinPar64.
# This is the vendor ID and product ID for the Anyma uDMX clone interface.
vid = 0x16c0
pid = 0x05dc
# Find the uDMX interface by vendor and product ID
dev = usb.core.find(idVendor=vid, idProduct=pid)
# This would find the uDMX by bus number and address (aka port)
# dev = usb.core.find(bus=1, address=4)
#
# You could find the uDMX by using the SYMLINK that the udev rule sets up.
# links = glob.glob("/dev/uDMXusb*")
# os.path.realpath(links[0]) # returns something like /dev/bus/usb/001/004
# You can use a regex to pull out the bus (001) and address (004).
if dev is None:
print("uDMX device was not found")
exit(0)
#
# It looks like the only way to determine what methods and
# properties are available on a given pyusb object requires looking
# at the source code on github: https://github.com/walac/pyusb/tree/master/usb
#
print("**********uDMX Device")
print("type:", type(dev))
print(dev)
print("**********MANUFACTURER")
print(dev.manufacturer)
if dev.manufacturer != "www.anyma.ch":
print("Error - expected www.anyma.ch, actual " + dev.manufacturer)
print("**********PRODUCT")
print(dev.product)
dev.set_configuration()
cfg = dev.get_active_configuration()
print("**********CONFIGURATION")
print("type:", type(cfg))
print(cfg)
intf = cfg[0, 0]
print("**********INTERFACE")
print("type:", type(intf))
print(intf)
ep = usb.util.find_descriptor(intf,
# match the first OUT endpoint
custom_match= \
lambda e: \
usb.util.endpoint_direction(e.bEndpointAddress) == \
usb.util.ENDPOINT_OUT)
print("**********ENDPOINT")
if ep:
print(ep)
else:
print("This device.configuration.interface does not have an OUT endpoint")
status = usb.control.get_status(dev)
print("**********STATUS")
print("Status:", status)
cmd_SetSingleChannel = 1
"""
usb request for cmd_SetSingleChannel:
bmRequestType: ignored by device, should be USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT
bRequest: cmd_SetSingleChannel
wValue: value of channel to set [0 .. 255]
wIndex: channel index to set [0 .. 511]
wLength: ignored (pass 1)
"""
cmd_SetChannelRange = 2
"""
usb request for cmd_SetChannelRange:
bmRequestType: ignored by device, should be USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT
bRequest: cmd_SetChannelRange
wValue: number of channels to set [1 .. 512-wIndex]
wIndex: index of first channel to set [0 .. 511]
wLength: length of data, must be >= wValue
"""
# From pyusb (usb.core)
# class Device
# def ctrl_transfer(self, bmRequestType, bRequest, wValue=0, wIndex=0,
# data_or_wLength = None, timeout = None):
"""
Do a control transfer on the endpoint 0.
This method is used to issue a control transfer over the endpoint 0
(endpoint 0 is required to always be a control endpoint).
The parameters bmRequestType, bRequest, wValue and wIndex are the same
of the USB Standard Control Request format.
Control requests may or may not have a data payload to write/read.
In cases which it has, the direction bit of the bmRequestType
field is used to infer the desired request direction. For
host to device requests (OUT), data_or_wLength parameter is
the data payload to send, and it must be a sequence type convertible
to an array object. In this case, the return value is the number
of bytes written in the data payload. For device to host requests
(IN), data_or_wLength is either the wLength parameter of the control
request specifying the number of bytes to read in data payload, and
the return value is an array object with data read, or an array
object which the data will be read to, and the return value is the
number of bytes read.
"""
# All data tranfers use this request type. This is more for
# the PyUSB package than for the uDMX.
bmRequestType = usb.util.CTRL_TYPE_VENDOR | usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_OUT
# bmRequestType = usb.util.CTRL_RECIPIENT_DEVICE | usb.util.CTRL_OUT
# Single channel value transfer
# Interface for setting a single channel data value
# wValue - the value, 0-255, to be set.
# wIndex - channel number, 0-511, being set. In DMX terms this corresponds to channel 1-512.
# data_or_wLength - not used for this case. However, on success this will be the return value.
# Set RGB mode (Venue ThinPar64 is in 7-channel mode)
channel = 5 # mode channel 6
channel_value = 0 # RGB mode
n = ctrl_transfer(dev, bmRequestType, cmd_SetSingleChannel, wValue=channel_value, wIndex=channel, data_or_wLength=1)
print("Sent:", n)
# Bring dimmer to 100%
channel = 6 # dimmer channel 7
channel_value = 255 # on all the way
n = ctrl_transfer(dev, bmRequestType, cmd_SetSingleChannel, wValue=channel_value, wIndex=channel, data_or_wLength=1)
print("Sent:", n)
# Turn light on
channel = 2 # blue channel
channel_value = 255 # on all the way
n = ctrl_transfer(dev, bmRequestType, cmd_SetSingleChannel, wValue=channel_value, wIndex=channel, data_or_wLength=1)
print("Sent:", n)
channel = 0 # red channel
channel_value = 255 # on all the way
n = ctrl_transfer(dev, bmRequestType, cmd_SetSingleChannel, wValue=channel_value, wIndex=channel, data_or_wLength=1)
print("Sent:", n)
print("Red/Blue")
print("Sleeping...")
time.sleep(3.000)
# Turn red off
channel_value = 0
n = ctrl_transfer(dev, bmRequestType, cmd_SetSingleChannel, wValue=channel_value, wIndex=channel, data_or_wLength=1)
print("Sent:", n)
print("Blue")
print("Sleeping...")
time.sleep(2.000)
# Interface for setting multiple channels at one time
# wValue - number of channels/bytes to be set. e.g. len(bytearray)
# wIndex - starting channel number, 0-511.
# data_or_wLength - sequence type of data, e.g. a bytearray
channel_values = [255, 255, 255] # RGB all on
channel = 0 # Red channel 1
# Turn on red, green and blue lights
n = ctrl_transfer(dev, bmRequestType, cmd_SetChannelRange, wValue=len(channel_values), \
wIndex=channel, data_or_wLength=channel_values)
print("Sent:", n)
print("White")
print("Sleeping...")
time.sleep(3.000)
channel_values = [0, 0, 0] # all off
# Turn off red, green and blue
n = ctrl_transfer(dev, bmRequestType, cmd_SetChannelRange, wValue=len(channel_values), \
wIndex=channel, data_or_wLength=channel_values)
print("Sent:", n)
print("All off")
del dev
dev = None