Welcome, guest | Sign In | My Account | Store | Cart

What this recipe does:

Maps linux usb hid ioctls and related C structs to python; Call ioctls, make some sense of output. Prints all reports for the device with some info.

Works with python 2.4 (tested python 2.4.6 on linux amd64). Would need changes (e.g. print) for python 3.0. Might need changes (ioctl signed/unsigned "FIX") for newer python than tested.

Python, 213 lines
  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
#!/usr/bin/python
import struct, array, fcntl

class struxx:
  _fields = None
  _format = None
  _buffer = None
  def __init__(self):
    self.reset()

  def __len__(self):
    """binary represntation length, for fields, use __dict__ or something"""
    return struct.calcsize(self._format)

  def __iter__(self):
    return [getattr(self, field) for field in self._fields.split(";")].__iter__()

  def reset(self):
    for field in self._fields.split(";"):
      setattr(self, field, 0)
    self._buffer = array.array('B', [0]*len(self))

  def pack(self):
    self._buffer = array.array('B', struct.pack(self._format, *self))

  def unpack(self):
    rv = struct.unpack(self._format, self._buffer)
    for i in range(len(rv)):
      setattr(self, self._fields.split(";")[i], rv[i])

  def ioctl(self, fd, ioctlno):
    self.pack()
    rv = fcntl.ioctl(fd, ioctlno, self._buffer, True)
    self.unpack()
    return rv

class uint(struxx):
  _fields = "uint"
  _format = "I"
  def get_version(self, fd): return self.ioctl(fd, HIDIOCGVERSION)
  def get_flags(self, fd): return self.ioctl(fd, HIDIOCGFLAG)
  def set_flags(self, fd): return self.ioctl(fd, HIDIOCSFLAG)

class hiddev_devinfo(struxx):
  _fields = "bustype;busnum;devnum;ifnum;vendor;product;version;num_applications"
  _format = "IIIIhhhI"
  def get(self, fd): return self.ioctl(fd, HIDIOCGDEVINFO)

class hiddev_string_descriptor(struxx):
  _fields = "index;value"
  _format = "i256c"

  def reset(self):
    self.index = 0
    self.value = '\0'*256

  def pack(self):
    tmp = struct.pack("i", self.index) + self.value[:256].ljust(256, '\0')
    self._buffer = array.array('B', tmp)

  def unpack(self):
    self.index = struct.unpack("i", self._buffer[:4])
    self.value = self._buffer[4:].tostring()

  def get_string(self, fd, idx):
    self.index = idx
    return self.ioctl(fd, HIDIOCGSTRING)

class hiddev_report_info(struxx):
  _fields = "report_type;report_id;num_fields"
  _format = "III"
  def get_info(self, fd): return self.ioctl(fd, HIDIOCGREPORTINFO)

class hiddev_field_info(struxx):
  _fields = "report_type;report_id;field_index;maxusage;flags;physical;logical;application;logical_minimum;logical_maximum;physical_minimum;physical_maximum;unit_exponent;unit"
  _format = "I"*8+"i"*4+"II"
  def get_info(self, fd): return self.ioctl(fd, HIDIOCGFIELDINFO)

class hiddev_usage_ref(struxx):
  _fields = "report_type;report_id;field_index;usage_index;usage_code;value"
  _format = "I"*5+"i"

class hiddev_collection_info(struxx):
  _fields = "index;type;usage;level"
  _format = "I"*4
  def get_info(self, fd, index):
    self.index = index
    return self.ioctl(fd, HIDIOCGCOLLECTIONINFO)

class hiddev_event(struxx):
  _fields = "hid;value"
  _format = "Hi"

IOCPARM_MASK = 0x7f
IOC_NONE = 0x20000000
IOC_WRITE = 0x40000000
IOC_READ = 0x80000000

def FIX(x): return struct.unpack("i", struct.pack("I", x))[0]

def _IO(x,y): return FIX(IOC_NONE|(ord(x)<<8)|y)
def _IOR(x,y,t): return FIX(IOC_READ|((t&IOCPARM_MASK)<<16)|(ord(x)<<8)|y)
def _IOW(x,y,t): return FIX(IOC_WRITE|((t&IOCPARM_MASK)<<16)|(ord(x)<<8)|y)
def _IOWR(x,y,t): return FIX(IOC_READ|IOC_WRITE|((t&IOCPARM_MASK)<<16)|(ord(x)<<8)|y)

HIDIOCGVERSION         =_IOR('H', 0x01, struct.calcsize("I"))
HIDIOCAPPLICATION      =_IO('H', 0x02)
HIDIOCGDEVINFO         =_IOR('H', 0x03, len(hiddev_devinfo()))
HIDIOCGSTRING          =_IOR('H', 0x04, len(hiddev_string_descriptor()))
HIDIOCINITREPORT       =_IO('H', 0x05)
def HIDIOCGNAME(buflen): return _IOR('H', 0x06, buflen)
HIDIOCGREPORT          =_IOW('H', 0x07, len(hiddev_report_info()))
HIDIOCSREPORT          =_IOW('H', 0x08, len(hiddev_report_info()))
HIDIOCGREPORTINFO      =_IOWR('H', 0x09, len(hiddev_report_info()))
HIDIOCGFIELDINFO       =_IOWR('H', 0x0A, len(hiddev_field_info()))
HIDIOCGUSAGE           =_IOWR('H', 0x0B, len(hiddev_usage_ref()))
HIDIOCSUSAGE           =_IOW('H', 0x0C, len(hiddev_usage_ref()))
HIDIOCGUCODE           =_IOWR('H', 0x0D, len(hiddev_usage_ref()))
HIDIOCGFLAG            =_IOR('H', 0x0E, struct.calcsize("I"))
HIDIOCSFLAG            =_IOW('H', 0x0F, struct.calcsize("I"))
HIDIOCGCOLLECTIONINDEX =_IOW('H', 0x10, len(hiddev_usage_ref()))
HIDIOCGCOLLECTIONINFO  =_IOWR('H', 0x11, len(hiddev_collection_info()))
def HIDIOCGPHYS(buflen): return _IOR('H', 0x12, buflen)

HID_REPORT_TYPE_INPUT   =1
HID_REPORT_TYPE_OUTPUT  =2
HID_REPORT_TYPE_FEATURE =3
HID_REPORT_TYPE_MIN     =1
HID_REPORT_TYPE_MAX     =3
HID_REPORT_ID_UNKNOWN =0xffffffff
HID_REPORT_ID_FIRST   =0x00000100
HID_REPORT_ID_NEXT    =0x00000200
HID_REPORT_ID_MASK    =0x000000ff
HID_REPORT_ID_MAX     =0x000000ff

def enum_reports(fd):
  for report_type in (HID_REPORT_TYPE_INPUT,
                      HID_REPORT_TYPE_OUTPUT,
                      HID_REPORT_TYPE_FEATURE):
    for i in range(HID_REPORT_ID_MAX+1):
      try:
        ri = hiddev_report_info()
        ri.report_type = report_type
        ri.report_id = i
        #print "trying", ri.__dict__
        ri.get_info(fd)
        print "%s(%s): %s fields" % ({1: 'input', 2:'output', 3:'feature'}.get(ri.report_type), ri.report_id, ri.num_fields)
        for field in range(ri.num_fields):
          fi = hiddev_field_info()
          fi.report_type = ri.report_type
          fi.report_id = ri.report_id
          fi.field_index = field
          fi.get_info(fd)
          print ", ".join(["%s:%s" % (key, fi.__dict__[key]) for key in fi.__dict__ if key not in ("report_type", "report_id", "_buffer") and fi.__dict__[key] ])
        #print report_info.__dict__
        print
      except IOError:
        pass


if __name__=="__main__":
#  name = ""
#  for name in globals():
#    if name.startswith("HID"):
#      if type(globals()[name]) == int:
#        print name, "\t%x" % globals()[name]

  f = open("/dev/usb/hiddev0", "r")
  tmp = uint()
  tmp.get_version(f)
  print "version 0x%x" % tmp.uint
  tmp.get_flags(f)
  print "flags 0x%x" % tmp.uint
  tmp.uint = 3
  tmp.set_flags(f)
  tmp.get_flags(f)
  print "flags 0x%x" % tmp.uint


  devinfo = hiddev_devinfo()
  devinfo.get(f)
  print "devinfo", devinfo.__dict__

  enum_reports(f)

def get_device_name(f):
  a = array.array('B', [0]*1024)
  fcntl.ioctl(f, HIDIOCGNAME(1024), a, True)
  print a

def get_some_strings(f):
  for i in range(-10000, 10000):
    try:
      string = hiddev_string_descriptor()
      string.get_string(f, i)
      print "string %s: %s", string.index, repr(string.value)
    except IOError:
      pass


def show_all_collections(f):
  for i in range(256):
    try:
      collection_info = hiddev_collection_info()
      collection_info.get_info(f, i)
      print "coll %s" % i, collection_info.__dict__
      print """
idnex: %(index)s
type:  %(type)s
level: %(level)s
usage: 0x%(usage)x""" % collection_info.__dict__
    except IOError:
      pass

1 comment

Dima Tisnek (author) 12 years, 5 months ago  # | flag

Further updates here in my blog, Pythonic Wisdom.