848
|
1 #!/usr/bin/env python
|
|
2 #
|
|
3 # Copyright 2005 Free Software Foundation, Inc.
|
|
4 #
|
|
5 # This file is part of GNU Radio
|
|
6 #
|
|
7 # GNU Radio is free software; you can redistribute it and/or modify
|
|
8 # it under the terms of the GNU General Public License as published by
|
|
9 # the Free Software Foundation; either version 3, or (at your option)
|
|
10 # any later version.
|
|
11 #
|
|
12 # GNU Radio is distributed in the hope that it will be useful,
|
|
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15 # GNU General Public License for more details.
|
|
16 #
|
|
17 # You should have received a copy of the GNU General Public License
|
|
18 # along with GNU Radio; see the file COPYING. If not, write to
|
|
19 # the Free Software Foundation, Inc., 51 Franklin Street,
|
|
20 # Boston, MA 02110-1301, USA.
|
|
21 #
|
|
22
|
|
23 """
|
|
24 Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs
|
|
25
|
|
26 modified by drewp@bigasterisk.com
|
|
27 """
|
|
28
|
|
29 import os, time
|
|
30 import sys
|
|
31 import struct
|
|
32 import exceptions
|
|
33 import threading
|
|
34
|
|
35
|
|
36 def hexint(mask):
|
|
37 """
|
|
38 Convert unsigned masks into signed ints.
|
|
39
|
|
40 This allows us to use hex constants like 0xf0f0f0f2 when talking to
|
|
41 our hardware and not get screwed by them getting treated as python
|
|
42 longs.
|
|
43 """
|
|
44 if mask >= 2**31:
|
|
45 return int(mask-2**32)
|
|
46 return mask
|
|
47 imported_ok = True
|
|
48
|
|
49 try:
|
|
50 import select
|
|
51 import fcntl
|
|
52 except ImportError:
|
|
53 imported_ok = False
|
|
54
|
|
55
|
|
56 # First a little bit of background:
|
|
57 #
|
|
58 # The Griffin PowerMate has
|
|
59 # * a single knob which rotates
|
|
60 # * a single button (pressing the knob)
|
|
61 #
|
|
62 # The Contour ShuttleXpress (aka SpaceShuttle) has
|
|
63 # * "Jog Wheel" -- the knob (rotary encoder) on the inside
|
|
64 # * "Shuttle Ring" -- the spring loaded rubber covered ring
|
|
65 # * 5 buttons
|
|
66 #
|
|
67 # The Contour ShuttlePro has
|
|
68 # * "Jog Wheel" -- the knob (rotary encoder) on the inside
|
|
69 # * "Shuttle Ring" -- the spring loaded rubber covered ring
|
|
70 # * 13 buttons
|
|
71 #
|
|
72 # The Contour ShuttlePro V2 has
|
|
73 # *"Jog Wheel" -- the knob (rotary encoder) on the inside
|
|
74 # * "Shuttle Ring" -- the spring loaded rubber covered ring
|
|
75 # * 15 buttons
|
|
76
|
|
77 # We remap all the buttons on the devices so that they start at zero.
|
|
78
|
|
79 # For the ShuttleXpress the buttons are 0 to 4 (left to right)
|
|
80
|
|
81 # For the ShuttlePro, we number the buttons immediately above
|
|
82 # the ring 0 to 4 (left to right) so that they match our numbering
|
|
83 # on the ShuttleXpress. The top row is 5, 6, 7, 8. The first row below
|
|
84 # the ring is 9, 10, and the bottom row is 11, 12.
|
|
85
|
|
86 # For the ShuttlePro V2, buttons 13 & 14 are to the
|
|
87 # left and right of the wheel respectively.
|
|
88
|
|
89 # We generate 3 kinds of events:
|
|
90 #
|
|
91 # button press/release (button_number, press/release)
|
|
92 # knob rotation (relative_clicks) # typically -1, +1
|
|
93 # shuttle position (absolute_position) # -7,-6,...,0,...,6,7
|
|
94
|
|
95 # ----------------------------------------------------------------
|
|
96 # Our ID's for the devices:
|
|
97 # Not to be confused with anything related to magic hardware numbers.
|
|
98
|
|
99 ID_POWERMATE = 'powermate'
|
|
100 ID_SHUTTLE_XPRESS = 'shuttle xpress'
|
|
101 ID_SHUTTLE_PRO = 'shuttle pro'
|
|
102 ID_SHUTTLE_PRO_V2 = 'shuttle pro v2'
|
|
103
|
|
104 # ------------------------------------------------------------------------
|
|
105 # format of messages that we read from /dev/input/event*
|
|
106 # See /usr/include/linux/input.h for more info
|
|
107 #
|
|
108 #struct input_event {
|
|
109 # struct timeval time; = {long seconds, long microseconds}
|
|
110 # unsigned short type;
|
|
111 # unsigned short code;
|
|
112 # unsigned int value;
|
|
113 #};
|
|
114
|
|
115 input_event_struct = "@llHHi"
|
|
116 input_event_size = struct.calcsize(input_event_struct)
|
|
117
|
|
118 # ------------------------------------------------------------------------
|
|
119 # input_event types
|
|
120 # ------------------------------------------------------------------------
|
|
121
|
|
122 IET_SYN = 0x00 # aka RESET
|
|
123 IET_KEY = 0x01 # key or button press/release
|
|
124 IET_REL = 0x02 # relative movement (knob rotation)
|
|
125 IET_ABS = 0x03 # absolute position (graphics pad, etc)
|
|
126 IET_MSC = 0x04
|
|
127 IET_LED = 0x11
|
|
128 IET_SND = 0x12
|
|
129 IET_REP = 0x14
|
|
130 IET_FF = 0x15
|
|
131 IET_PWR = 0x16
|
|
132 IET_FF_STATUS = 0x17
|
|
133 IET_MAX = 0x1f
|
|
134
|
|
135 # ------------------------------------------------------------------------
|
|
136 # input_event codes (there are a zillion of them, we only define a few)
|
|
137 # ------------------------------------------------------------------------
|
|
138
|
|
139 # these are valid for IET_KEY
|
|
140
|
|
141 IEC_BTN_0 = 0x100
|
|
142 IEC_BTN_1 = 0x101
|
|
143 IEC_BTN_2 = 0x102
|
|
144 IEC_BTN_3 = 0x103
|
|
145 IEC_BTN_4 = 0x104
|
|
146 IEC_BTN_5 = 0x105
|
|
147 IEC_BTN_6 = 0x106
|
|
148 IEC_BTN_7 = 0x107
|
|
149 IEC_BTN_8 = 0x108
|
|
150 IEC_BTN_9 = 0x109
|
|
151 IEC_BTN_10 = 0x10a
|
|
152 IEC_BTN_11 = 0x10b
|
|
153 IEC_BTN_12 = 0x10c
|
|
154 IEC_BTN_13 = 0x10d
|
|
155 IEC_BTN_14 = 0x10e
|
|
156 IEC_BTN_15 = 0x10f
|
|
157
|
|
158 # these are valid for IET_REL (Relative axes)
|
|
159
|
|
160 IEC_REL_X = 0x00
|
|
161 IEC_REL_Y = 0x01
|
|
162 IEC_REL_Z = 0x02
|
|
163 IEC_REL_HWHEEL = 0x06
|
|
164 IEC_REL_DIAL = 0x07 # rotating the knob
|
|
165 IEC_REL_WHEEL = 0x08 # moving the shuttle ring
|
|
166 IEC_REL_MISC = 0x09
|
|
167 IEC_REL_MAX = 0x0f
|
|
168
|
|
169 # ------------------------------------------------------------------------
|
|
170
|
|
171 class powermate(object):
|
|
172 """
|
|
173 Interface to Griffin PowerMate and Contour Shuttles
|
|
174 """
|
|
175 def __init__(self, filename=None, on_event=lambda ev: None):
|
|
176 self.on_event = on_event
|
|
177 self.handle = -1
|
|
178 if not imported_ok:
|
|
179 raise exceptions.RuntimeError, 'powermate not supported on this platform'
|
|
180
|
|
181 if filename:
|
|
182 if not self._open_device(filename):
|
|
183 raise exceptions.RuntimeError, 'Unable to find powermate'
|
|
184 else:
|
|
185 ok = False
|
|
186 for d in range(0, 16):
|
|
187 if self._open_device("/dev/input/event%d" % d):
|
|
188 ok = True
|
|
189 break
|
|
190 if not ok:
|
|
191 raise exceptions.RuntimeError, 'Unable to find powermate'
|
|
192
|
|
193 def __del__(self):
|
|
194 self.keep_running = False
|
|
195 if self.handle >= 0:
|
|
196 os.close(self.handle)
|
|
197 self.handle = -1
|
|
198
|
|
199 def _open_device(self, filename):
|
|
200 try:
|
|
201 self.handle = os.open(filename, os.O_RDWR)
|
|
202 if self.handle < 0:
|
|
203 print "can't open file"
|
|
204 return False
|
|
205
|
|
206 # read event device name
|
|
207 name = fcntl.ioctl(self.handle, hexint(0x80ff4506), chr(0) * 256)
|
|
208 name = name.replace(chr(0), '')
|
|
209 print "%s name is %s" % (filename, name)
|
|
210 # do we see anything we recognize?
|
|
211 if name == 'Griffin PowerMate' or name == 'Griffin SoundKnob':
|
|
212 self.id = ID_POWERMATE
|
|
213 self.mapper = _powermate_remapper()
|
|
214 elif name == 'CAVS SpaceShuttle A/V' or name == 'Contour Design ShuttleXpress':
|
|
215 self.id = ID_SHUTTLE_XPRESS
|
|
216 self.mapper = _contour_remapper()
|
|
217 elif name == 'Contour Design ShuttlePRO':
|
|
218 self.id = ID_SHUTTLE_PRO
|
|
219 self.mapper = _contour_remapper()
|
|
220 elif name == 'Contour Design ShuttlePRO v2':
|
|
221 self.id = ID_SHUTTLE_PRO_V2
|
|
222 self.mapper = _contour_remapper()
|
|
223 else:
|
|
224 os.close(self.handle)
|
|
225 self.handle = -1
|
|
226 return False
|
|
227
|
|
228 # get exclusive control of the device, using ioctl EVIOCGRAB
|
|
229 # there may be an issue with this on non x86 platforms and if
|
|
230 # the _IOW,_IOC,... macros in <asm/ioctl.h> are changed
|
|
231 fcntl.ioctl(self.handle,hexint(0x40044590), 1)
|
|
232 return True
|
|
233 except exceptions.OSError:
|
|
234 return False
|
|
235
|
|
236
|
|
237 def set_event_receiver(self, obj):
|
|
238 self.event_receiver = obj
|
|
239
|
|
240
|
|
241 def set_led_state(self, static_brightness, pulse_speed=0,
|
|
242 pulse_table=0, pulse_on_sleep=0, pulse_on_wake=0):
|
|
243 """
|
|
244 What do these magic values mean...
|
|
245 """
|
|
246 if self.id != ID_POWERMATE:
|
|
247 return False
|
|
248
|
|
249 static_brightness &= 0xff;
|
|
250 if pulse_speed < 0:
|
|
251 pulse_speed = 0
|
|
252 if pulse_speed > 510:
|
|
253 pulse_speed = 510
|
|
254 if pulse_table < 0:
|
|
255 pulse_table = 0
|
|
256 if pulse_table > 2:
|
|
257 pulse_table = 2
|
|
258 pulse_on_sleep = not not pulse_on_sleep # not not = convert to 0/1
|
|
259 pulse_on_wake = not not pulse_on_wake
|
|
260 magic = (static_brightness
|
|
261 | (pulse_speed << 8)
|
|
262 | (pulse_table << 17)
|
|
263 | (pulse_on_sleep << 19)
|
|
264 | (pulse_on_wake << 20))
|
|
265 data = struct.pack(input_event_struct, 0, 0, 0x04, 0x01, magic)
|
|
266 os.write(self.handle, data)
|
|
267 return True
|
|
268
|
|
269 def read_next(self):
|
|
270 s = os.read (self.handle, input_event_size)
|
|
271 if not s:
|
|
272 return
|
|
273
|
|
274 raw_input_event = struct.unpack(input_event_struct,s)
|
|
275 sec, usec, type, code, val = self.mapper(raw_input_event)
|
|
276
|
|
277 if type == IET_SYN: # ignore
|
|
278 pass
|
|
279 elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness)
|
|
280 pass
|
|
281 elif type == IET_REL and code == IEC_REL_DIAL:
|
|
282 self.on_event({"dial":val})
|
|
283 elif type == IET_REL and code == IEC_REL_WHEEL:
|
|
284 self.on_event({"shuttle":val})
|
|
285 elif type == IET_KEY:
|
|
286 self.on_event({"key":{"button":code - IEC_BTN_0, "press":val}})
|
|
287 else:
|
|
288 print "powermate: unrecognized event: type = 0x%x code = 0x%x val = %d" % (type, code, val)
|
|
289
|
|
290
|
|
291 class _powermate_remapper(object):
|
|
292 def __init__(self):
|
|
293 pass
|
|
294 def __call__(self, event):
|
|
295 """
|
|
296 Notice how nice and simple this is...
|
|
297 """
|
|
298 return event
|
|
299
|
|
300 class _contour_remapper(object):
|
|
301 def __init__(self):
|
|
302 self.prev = None
|
|
303 def __call__(self, event):
|
|
304 """
|
|
305 ...and how screwed up this is
|
|
306 """
|
|
307 sec, usec, type, code, val = event
|
|
308 if type == IET_REL and code == IEC_REL_WHEEL:
|
|
309 # === Shuttle ring ===
|
|
310 # First off, this really ought to be IET_ABS, not IET_REL!
|
|
311 # They never generate a zero value so you can't
|
|
312 # tell when the shuttle ring is back in the center.
|
|
313 # We kludge around this by calling both -1 and 1 zero.
|
|
314 if val == -1 or val == 1:
|
|
315 return (sec, usec, type, code, 0)
|
|
316 return event
|
|
317
|
|
318 if type == IET_REL and code == IEC_REL_DIAL:
|
|
319 # === Jog knob (rotary encoder) ===
|
|
320 # Dim wits got it wrong again! This one should return a
|
|
321 # a relative value, e.g., -1, +1. Instead they return
|
|
322 # a total that runs modulo 256 (almost!). For some
|
|
323 # reason they count like this 253, 254, 255, 1, 2, 3
|
|
324
|
|
325 if self.prev is None: # first time call
|
|
326 self.prev = val
|
|
327 return (sec, usec, IET_SYN, 0, 0) # will be ignored above
|
|
328
|
|
329 diff = val - self.prev
|
|
330 if diff == 0: # sometimes it just sends stuff...
|
|
331 return (sec, usec, IET_SYN, 0, 0) # will be ignored above
|
|
332
|
|
333 if abs(diff) > 100: # crossed into the twilight zone
|
|
334 if self.prev > val: # we've wrapped going forward
|
|
335 self.prev = val
|
|
336 return (sec, usec, type, code, +1)
|
|
337 else: # we've wrapped going backward
|
|
338 self.prev = val
|
|
339 return (sec, usec, type, code, -1)
|
|
340
|
|
341 self.prev = val
|
|
342 return (sec, usec, type, code, diff)
|
|
343
|
|
344 if type == IET_KEY:
|
|
345 # remap keys so that all 3 gadgets have buttons 0 to 4 in common
|
|
346 return (sec, usec, type,
|
|
347 (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8,
|
|
348 IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4,
|
|
349 IEC_BTN_9, IEC_BTN_10,
|
|
350 IEC_BTN_11, IEC_BTN_12,
|
|
351 IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val)
|
|
352
|
|
353 return event
|
|
354
|
894
|
355 import restkit
|
|
356 reasoning = restkit.Resource("http://bang:9071/", timeout=1)
|
|
357
|
|
358 from rdflib import Namespace
|
|
359 SHUTTLEPRO = Namespace("http://projects.bigasterisk.com/room/livingRoom/shuttlepro/")
|
|
360 ROOM = Namespace("http://projects.bigasterisk.com/room/")
|
|
361
|
848
|
362 if __name__ == '__main__':
|
894
|
363 import restkit, urllib
|
|
364 mpd = restkit.Resource("http://slash:9009/")
|
848
|
365 def ev(what):
|
|
366 print 'ev', what
|
|
367 if 'key' in what and what['press']:
|
|
368 print mpd.post("addAndPlay/%s" % what['key']['button']).body_string()
|
|
369 p = powermate("/dev/input/by-id/usb-Contour_Design_ShuttlePRO-event-if00", ev)
|
|
370 while True:
|
|
371 p.read_next()
|