hal_input.py
1 #!/usr/bin/env python 2 # Copyright 2007 Jeff Epler <jepler@unpythonic.net> 3 # 4 # This program is free software; you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation; either version 2 of the License, or 7 # (at your option) any later version. 8 # 9 # This program is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with this program; if not, write to the Free Software 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 17 18 import linux_event, sys, os, fcntl, hal, select, time, glob, fnmatch, select 19 from hal import * 20 21 def tohalname(s): return str(s).lower().replace("_", "-") 22 23 class HalWrapper: 24 def __init__(self, comp): 25 self._comp = comp 26 self._drive = {} 27 self._pins = set() 28 self._params = set() 29 30 def drive(self): 31 for k, v in self._drive.items(): setattr(self._comp, k, v) 32 33 def newpin(self, *args): 34 self._pins.add(args[0]) 35 return self._comp.newpin(*args) 36 def newparam(self, *args): 37 self._params.add(args[0]) 38 return self._comp.newparam(*args) 39 40 def __getitem__(self, k): 41 if k in self._drive: return self._drive[k] 42 return self._comp[k] 43 44 def __setitem__(self, k, v): 45 if k in self._params: 46 self._comp[k] = v; return 47 if k in self._pins: 48 self._comp[k] = v; return 49 raise KeyError, k 50 self._drive[k] = v 51 52 class HalInputDevice: 53 def __init__(self, comp, idx, name, parts='KRAL'): 54 self.device = linux_event.InputDevice(name) 55 56 self.idx = idx 57 self.codes = set() 58 self.last = {} 59 self.rel_items = [] 60 self.abs_items = [] 61 self.comp = comp 62 self.parts = parts 63 64 if 'K' in parts: 65 for key in self.device.get_bits('EV_KEY'): 66 key = tohalname(key) 67 self.codes.add(key) 68 comp.newpin("%s.%s" % (idx, key), HAL_BIT, HAL_OUT) 69 comp.newpin("%s.%s-not" % (idx, key), HAL_BIT, HAL_OUT) 70 self.set(key + "-not", 1) 71 72 if 'R' in parts: 73 for axis in self.device.get_bits('EV_REL'): 74 name = tohalname(axis) 75 self.codes.add(name) 76 comp.newpin("%s.%s-position" % (idx, name), HAL_FLOAT, HAL_OUT) 77 comp.newpin("%s.%s-counts" % (idx, name), HAL_S32, HAL_OUT) 78 comp.newpin("%s.%s-reset" % (idx, name), HAL_BIT, HAL_IN) 79 comp.newpin("%s.%s-scale" % (idx, name), HAL_FLOAT, HAL_IN) 80 self.set(name + '-scale', 1.) 81 self.rel_items.append(name) 82 83 if 'A' in parts: 84 for axis in self.device.get_bits('EV_ABS'): 85 name = tohalname(axis) 86 self.codes.add(name) 87 absinfo = self.device.get_absinfo(axis) 88 comp.newpin("%s.%s-position" % (idx, name), HAL_FLOAT, HAL_OUT) 89 comp.newpin("%s.%s-counts" % (idx, name), HAL_S32, HAL_OUT) 90 comp.newpin("%s.%s-is-pos" % (idx, name), HAL_BIT, HAL_OUT) 91 comp.newpin("%s.%s-is-neg" % (idx, name), HAL_BIT, HAL_OUT) 92 comp.newpin("%s.%s-scale" % (idx, name), HAL_FLOAT, HAL_IN) 93 comp.newpin("%s.%s-offset" % (idx, name), HAL_FLOAT, HAL_IN) 94 comp.newpin("%s.%s-fuzz" % (idx, name), HAL_S32, HAL_IN) 95 comp.newpin("%s.%s-flat" % (idx, name), HAL_S32, HAL_IN) 96 comp.newparam("%s.%s-min" % (idx, name), HAL_S32, HAL_RO) 97 comp.newparam("%s.%s-max" % (idx, name), HAL_S32, HAL_RO) 98 center = (absinfo.minimum + absinfo.maximum)/2. 99 halfrange = (absinfo.maximum - absinfo.minimum)/2. or 1 100 self.set(name + "-counts", absinfo.value) 101 self.set(name + "-position", 102 (absinfo.value - center) / halfrange) 103 self.set(name + "-scale", halfrange) 104 self.set(name + "-offset", center) 105 self.set(name + "-fuzz", absinfo.fuzz) 106 self.set(name + "-flat", absinfo.flat) 107 self.set(name + "-min", absinfo.minimum) 108 self.set(name + "-max", absinfo.maximum) 109 self.abs_items.append(name) 110 111 self.ledmap = {} 112 if 'L' in parts: 113 for led in self.device.get_bits('EV_LED'): 114 name = tohalname(led) 115 self.ledmap[name] = led 116 comp.newpin("%s.%s" % (idx, name), HAL_BIT, HAL_IN) 117 comp.newpin("%s.%s-invert" % (idx, name), HAL_BIT, HAL_IN) 118 self.last[name] = 0 119 self.device.write_event('EV_LED', led, 0) 120 121 def get(self, name): 122 name = "%s.%s" % (self.idx, name) 123 return self.comp[name] 124 125 def set(self, name, value): 126 name = "%s.%s" % (self.idx, name) 127 self.comp[name] = value 128 129 def update(self): 130 while self.device.readable(): 131 ev = self.device.read_event() 132 if ev.type == 'EV_SYN': continue 133 elif ev.type == 'EV_SND': continue 134 elif ev.type == 'EV_MSC': continue 135 elif ev.type == 'EV_LED': continue 136 elif ev.type == 'EV_KEY' and 'K' not in self.parts: continue 137 elif ev.type == 'EV_REL' and 'R' not in self.parts: continue 138 elif ev.type == 'EV_ABS' and 'A' not in self.parts: continue 139 code = tohalname(ev.code) 140 if code not in self.codes: 141 print >>sys.stderr, "Unexpected event", ev.type, ev.code 142 continue 143 if ev.type == 'EV_KEY': 144 if ev.value: 145 self.set(code, 1) 146 self.set(code + "-not", 0) 147 else: 148 self.set(code, 0) 149 self.set(code + "-not", 1) 150 elif ev.type == 'EV_REL': 151 self.set(code + "-counts", self.get(code + "-counts") + ev.value) 152 elif ev.type == 'EV_ABS': 153 flat = self.get(code + "-flat") 154 fuzz = self.get(code + "-fuzz") 155 center = int(self.get(code + "-offset")) 156 if ev.value < center-flat or ev.value > center+flat: 157 value = ev.value 158 else: value = center 159 if abs(value - self.get(code + "-counts")) > fuzz: 160 self.set(code + "-counts", value) 161 162 163 for a in self.abs_items: 164 value = self.get(a + "-counts") 165 scale = self.get(a + "-scale") or 1 166 offset = self.get(a + "-offset") 167 position = (value - offset) / scale 168 self.set(a + "-position", position) 169 # Use .01 because my Joystick isn't exactly zero at rest. maybe should be a parameter? 170 self.set(a + "-is-neg", (position < -.01) ) 171 self.set(a + "-is-pos", (position > .01) ) 172 173 for r in self.rel_items: 174 reset = self.get(r + "-reset") 175 scale = self.get(r + "-scale") or 1 176 if reset: self.set(r + "-counts", 0) 177 self.set(r + "-position", self.get(r + "-counts") / scale) 178 179 for k, v in self.last.items(): 180 # Note: this is OK because the hal module always returns True or False for HAL_BIT values 181 u = self.comp["%s.%s" % (self.idx, k)] != self.comp["%s.%s-invert" % (self.idx, k)] 182 if u != self.last[k]: 183 led = self.ledmap[k] 184 self.device.write_event('EV_LED', led, u) 185 self.last[k] = u 186 187 h = component("hal_input") 188 w = HalWrapper(h) 189 h.setprefix("input") 190 d = [] 191 i = 0 192 parts = 'KRAL' 193 for f in sys.argv[1:]: 194 if f.startswith("-"): 195 parts = f[1:] 196 else: 197 try: 198 d.append(HalInputDevice(w, i, f, parts)) 199 except LookupError, detail: 200 raise SystemExit, detail 201 parts = 'KRAL' 202 i += 1 203 w.drive() 204 h.ready() 205 206 fds = [dev.device.fileno() for dev in d] 207 try: 208 while 1: 209 select.select(fds, [], [], .01) 210 for i in d: i.update() 211 w.drive() 212 except KeyboardInterrupt: 213 pass