3 # Copyright 2005 Free Software Foundation, Inc.
5 # This file is part of GNU Radio
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)
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.
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.
24 Handler for Griffin PowerMate, Contour ShuttlePro & ShuttleXpress USB knobs
26 This is Linux and wxPython specific.
35 from gnuradio import gru
46 # First a little bit of background:
48 # The Griffin PowerMate has
49 # * a single knob which rotates
50 # * a single button (pressing the knob)
52 # The Contour ShuttleXpress (aka SpaceShuttle) has
53 # * "Jog Wheel" -- the knob (rotary encoder) on the inside
54 # * "Shuttle Ring" -- the spring loaded rubber covered ring
57 # The Contour ShuttlePro has
58 # * "Jog Wheel" -- the knob (rotary encoder) on the inside
59 # * "Shuttle Ring" -- the spring loaded rubber covered ring
62 # The Contour ShuttlePro V2 has
63 # *"Jog Wheel" -- the knob (rotary encoder) on the inside
64 # * "Shuttle Ring" -- the spring loaded rubber covered ring
67 # We remap all the buttons on the devices so that they start at zero.
69 # For the ShuttleXpress the buttons are 0 to 4 (left to right)
71 # For the ShuttlePro, we number the buttons immediately above
72 # the ring 0 to 4 (left to right) so that they match our numbering
73 # on the ShuttleXpress. The top row is 5, 6, 7, 8. The first row below
74 # the ring is 9, 10, and the bottom row is 11, 12.
76 # For the ShuttlePro V2, buttons 13 & 14 are to the
77 # left and right of the wheel respectively.
79 # We generate 3 kinds of events:
81 # button press/release (button_number, press/release)
82 # knob rotation (relative_clicks) # typically -1, +1
83 # shuttle position (absolute_position) # -7,-6,...,0,...,6,7
85 # ----------------------------------------------------------------
86 # Our ID's for the devices:
87 # Not to be confused with anything related to magic hardware numbers.
89 ID_POWERMATE = 'powermate'
90 ID_SHUTTLE_XPRESS = 'shuttle xpress'
91 ID_SHUTTLE_PRO = 'shuttle pro'
92 ID_SHUTTLE_PRO_V2 = 'shuttle pro v2'
94 # ------------------------------------------------------------------------
95 # format of messages that we read from /dev/input/event*
96 # See /usr/include/linux/input.h for more info
99 # struct timeval time; = {long seconds, long microseconds}
100 # unsigned short type;
101 # unsigned short code;
102 # unsigned int value;
105 input_event_struct = "@llHHi"
106 input_event_size = struct.calcsize(input_event_struct)
108 # ------------------------------------------------------------------------
110 # ------------------------------------------------------------------------
112 IET_SYN = 0x00 # aka RESET
113 IET_KEY = 0x01 # key or button press/release
114 IET_REL = 0x02 # relative movement (knob rotation)
115 IET_ABS = 0x03 # absolute position (graphics pad, etc)
125 # ------------------------------------------------------------------------
126 # input_event codes (there are a zillion of them, we only define a few)
127 # ------------------------------------------------------------------------
129 # these are valid for IET_KEY
148 # these are valid for IET_REL (Relative axes)
153 IEC_REL_HWHEEL = 0x06
154 IEC_REL_DIAL = 0x07 # rotating the knob
155 IEC_REL_WHEEL = 0x08 # moving the shuttle ring
159 # ------------------------------------------------------------------------
161 class powermate(threading.Thread):
163 Interface to Griffin PowerMate and Contour Shuttles
165 def __init__(self, event_receiver=None, filename=None, **kwargs):
166 self.event_receiver = event_receiver
169 raise exceptions.RuntimeError, 'powermate not supported on this platform'
172 if not self._open_device(filename):
173 raise exceptions.RuntimeError, 'Unable to find powermate'
176 for d in range(0, 16):
177 if self._open_device("/dev/input/event%d" % d):
181 raise exceptions.RuntimeError, 'Unable to find powermate'
183 threading.Thread.__init__(self, **kwargs)
185 self.keep_running = True
189 self.keep_running = False
191 os.close(self.handle)
194 def _open_device(self, filename):
196 self.handle = os.open(filename, os.O_RDWR)
200 # read event device name
201 name = fcntl.ioctl(self.handle, gru.hexint(0x80ff4506), chr(0) * 256)
202 name = name.replace(chr(0), '')
204 # do we see anything we recognize?
205 if name == 'Griffin PowerMate' or name == 'Griffin SoundKnob':
206 self.id = ID_POWERMATE
207 self.mapper = _powermate_remapper()
208 elif name == 'CAVS SpaceShuttle A/V' or name == 'Contour Design ShuttleXpress':
209 self.id = ID_SHUTTLE_XPRESS
210 self.mapper = _contour_remapper()
211 elif name == 'Contour Design ShuttlePRO':
212 self.id = ID_SHUTTLE_PRO
213 self.mapper = _contour_remapper()
214 elif name == 'Contour Design ShuttlePRO v2':
215 self.id = ID_SHUTTLE_PRO_V2
216 self.mapper = _contour_remapper()
218 os.close(self.handle)
222 # get exclusive control of the device, using ioctl EVIOCGRAB
223 # there may be an issue with this on non x86 platforms and if
224 # the _IOW,_IOC,... macros in <asm/ioctl.h> are changed
225 fcntl.ioctl(self.handle,gru.hexint(0x40044590), 1)
227 except exceptions.OSError:
231 def set_event_receiver(self, obj):
232 self.event_receiver = obj
235 def set_led_state(self, static_brightness, pulse_speed=0,
236 pulse_table=0, pulse_on_sleep=0, pulse_on_wake=0):
238 What do these magic values mean...
240 if self.id != ID_POWERMATE:
243 static_brightness &= 0xff;
246 if pulse_speed > 510:
252 pulse_on_sleep = not not pulse_on_sleep # not not = convert to 0/1
253 pulse_on_wake = not not pulse_on_wake
254 magic = (static_brightness
256 | (pulse_table << 17)
257 | (pulse_on_sleep << 19)
258 | (pulse_on_wake << 20))
259 data = struct.pack(input_event_struct, 0, 0, 0x04, 0x01, magic)
260 os.write(self.handle, data)
264 while (self.keep_running):
265 s = os.read (self.handle, input_event_size)
267 self.keep_running = False
270 raw_input_event = struct.unpack(input_event_struct,s)
271 sec, usec, type, code, val = self.mapper(raw_input_event)
273 if self.event_receiver is None:
276 if type == IET_SYN: # ignore
278 elif type == IET_MSC: # ignore (seems to be PowerMate reporting led brightness)
280 elif type == IET_REL and code == IEC_REL_DIAL:
281 #print "Dial: %d" % (val,)
282 wx.PostEvent(self.event_receiver, PMRotateEvent(val))
283 elif type == IET_REL and code == IEC_REL_WHEEL:
284 #print "Shuttle: %d" % (val,)
285 wx.PostEvent(self.event_receiver, PMShuttleEvent(val))
286 elif type == IET_KEY:
287 #print "Key: Btn%d %d" % (code - IEC_BTN_0, val)
288 wx.PostEvent(self.event_receiver,
289 PMButtonEvent(code - IEC_BTN_0, val))
291 print "powermate: unrecognized event: type = 0x%x code = 0x%x val = %d" % (type, code, val)
294 class _powermate_remapper(object):
297 def __call__(self, event):
299 Notice how nice and simple this is...
303 class _contour_remapper(object):
306 def __call__(self, event):
308 ...and how screwed up this is
310 sec, usec, type, code, val = event
311 if type == IET_REL and code == IEC_REL_WHEEL:
312 # === Shuttle ring ===
313 # First off, this really ought to be IET_ABS, not IET_REL!
314 # They never generate a zero value so you can't
315 # tell when the shuttle ring is back in the center.
316 # We kludge around this by calling both -1 and 1 zero.
317 if val == -1 or val == 1:
318 return (sec, usec, type, code, 0)
321 if type == IET_REL and code == IEC_REL_DIAL:
322 # === Jog knob (rotary encoder) ===
323 # Dim wits got it wrong again! This one should return a
324 # a relative value, e.g., -1, +1. Instead they return
325 # a total that runs modulo 256 (almost!). For some
326 # reason they count like this 253, 254, 255, 1, 2, 3
328 if self.prev is None: # first time call
330 return (sec, usec, IET_SYN, 0, 0) # will be ignored above
332 diff = val - self.prev
333 if diff == 0: # sometimes it just sends stuff...
334 return (sec, usec, IET_SYN, 0, 0) # will be ignored above
336 if abs(diff) > 100: # crossed into the twilight zone
337 if self.prev > val: # we've wrapped going forward
339 return (sec, usec, type, code, +1)
340 else: # we've wrapped going backward
342 return (sec, usec, type, code, -1)
345 return (sec, usec, type, code, diff)
348 # remap keys so that all 3 gadgets have buttons 0 to 4 in common
349 return (sec, usec, type,
350 (IEC_BTN_5, IEC_BTN_6, IEC_BTN_7, IEC_BTN_8,
351 IEC_BTN_0, IEC_BTN_1, IEC_BTN_2, IEC_BTN_3, IEC_BTN_4,
352 IEC_BTN_9, IEC_BTN_10,
353 IEC_BTN_11, IEC_BTN_12,
354 IEC_BTN_13, IEC_BTN_14)[code - IEC_BTN_0], val)
358 # ------------------------------------------------------------------------
359 # new wxPython event classes
360 # ------------------------------------------------------------------------
362 grEVT_POWERMATE_BUTTON = wx.NewEventType()
363 grEVT_POWERMATE_ROTATE = wx.NewEventType()
364 grEVT_POWERMATE_SHUTTLE = wx.NewEventType()
366 EVT_POWERMATE_BUTTON = wx.PyEventBinder(grEVT_POWERMATE_BUTTON, 0)
367 EVT_POWERMATE_ROTATE = wx.PyEventBinder(grEVT_POWERMATE_ROTATE, 0)
368 EVT_POWERMATE_SHUTTLE = wx.PyEventBinder(grEVT_POWERMATE_SHUTTLE, 0)
370 class PMButtonEvent(wx.PyEvent):
371 def __init__(self, button, value):
372 wx.PyEvent.__init__(self)
373 self.SetEventType(grEVT_POWERMATE_BUTTON)
378 self.__class__(self.GetId())
381 class PMRotateEvent(wx.PyEvent):
382 def __init__(self, delta):
383 wx.PyEvent.__init__(self)
384 self.SetEventType (grEVT_POWERMATE_ROTATE)
388 self.__class__(self.GetId())
391 class PMShuttleEvent(wx.PyEvent):
392 def __init__(self, position):
393 wx.PyEvent.__init__(self)
394 self.SetEventType (grEVT_POWERMATE_SHUTTLE)
395 self.position = position
398 self.__class__(self.GetId())
400 # ------------------------------------------------------------------------
402 # ------------------------------------------------------------------------
404 if __name__ == '__main__':
405 class Frame(wx.Frame):
406 def __init__(self,parent=None,id=-1,title='Title',
407 pos=wx.DefaultPosition, size=(400,200)):
408 wx.Frame.__init__(self,parent,id,title,pos,size)
409 EVT_POWERMATE_BUTTON(self, self.on_button)
410 EVT_POWERMATE_ROTATE(self, self.on_rotate)
411 EVT_POWERMATE_SHUTTLE(self, self.on_shuttle)
412 self.brightness = 128
416 self.pm = powermate(self)
418 sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n")
421 self.pm.set_led_state(self.brightness, self.pulse_speed)
424 def on_button(self, evt):
425 print "Button %d %s" % (evt.button,
426 ("Released", "Pressed")[evt.value])
428 def on_rotate(self, evt):
429 print "Rotated %d" % (evt.delta,)
431 new = max(0, min(255, self.brightness + evt.delta))
432 if new != self.brightness:
433 self.brightness = new
434 self.pm.set_led_state(self.brightness, self.pulse_speed)
436 def on_shuttle(self, evt):
437 print "Shuttle %d" % (evt.position,)
441 title='PowerMate Demo'
442 self.frame = Frame(parent=None,id=-1,title=title)
444 self.SetTopWindow(self.frame)