Module ioLabs
[frames] | no frames]

Source Code for Module ioLabs

   1  ''' 
   2  the module for interfacing with an ioLan button box. 
   3  USBBox is the main class that should be used from this module 
   4  ''' 
   5   
   6  # turn on logging so we can see what's going on 
   7  import logging 
   8  #format='%(asctime)s %(levelname)s %(message)s' 
   9  #logging.basicConfig(level=logging.INFO,format=format) 
  10   
  11  import time 
  12  import struct 
  13  from Queue import Queue, Empty 
  14  import hid 
  15   
  16  IO_LABS_VENDOR_ID=0x19BC 
  17  BUTTON_BOX_PRODUCT_ID=0x0001 
  18   
19 -def is_usb_bbox(device):
20 return device.vendor == IO_LABS_VENDOR_ID and device.product == BUTTON_BOX_PRODUCT_ID
21
22 -class dict_struct:
23 '''simple class that takes keyword arguments and uses them to create fields on itself'''
24 - def __init__(self,**kw):
25 self.__dict__.update(kw)
26
27 - def __str__(self):
28 attribs=[] 29 for key,value in self.__dict__.items(): 30 attribs.append('%s=%s'%(key,value)) 31 return ','.join(attribs)
32
33 - def __repr__(self):
34 attribs=[] 35 for key,value in self.__dict__.items(): 36 attribs.append('%s=%r'%(key,value)) 37 return "dict_struct(%s)" % ','.join(attribs)
38 39 # commands in form id : ('name','pack format','field names') 40 # formats ignore identity byte (it's assumed to always be there) 41 # see "struct" module documentation for details of pack format strings 42 COMMAND_SUMMARY={ 43 # SYSTEM commands 44 0x21 : ('PACSET', 'BBBBBBB', ('data1','data2','data3','data4','data5','data6','data7') ), 45 0x59 : ('VERGET', 'xxxxxxx', () ), 46 0x5A : ('NUMGET', 'xxxxxxx', () ), 47 0x52 : ('RESRTC', 'xxxxxxx', () ), 48 0x54 : ('RTCGET', 'xxxxxxx', () ), 49 0x48 : ('HBSET', 'Hxxxxx', ('rate',) ), 50 0x51 : ('QPURGE', 'xxxxxxx', () ), 51 0x53 : ('SEROUT', 'BBBBBBB', ('nb_data','data1','data2','data3','data4','data5','data6') ), 52 0x43 : ('VCKSET', 'BBBBBxx', ('action','data1','data2','data3','data4') ), 53 0x56 : ('VCKGET', 'xxxxxxx', () ), 54 # PRIMARY input commands 55 0x4D : ('MSKSET', 'xBBxxxx', ('port3_bits','port1_bits') ), 56 0x3F : ('MSKGET', 'xxxxxxx', () ), 57 0x47 : ('KEYGET', 'xxxxxxx', () ), 58 0x4A : ('DEBSET', 'xBBBBBB', ('port1_down','port1_up','int0_down','int0_up','int1_down','int1_up') ), 59 0x46 : ('DEBGET', 'xxxxxxx', () ), 60 # GENERAL purpose I/O commands 61 0x57 : ('DIRSET', 'BBBxxxx', ('port2_mode','port2_bits','port0_bits') ), 62 0x44 : ('DIRGET', 'xxxxxxx', () ), 63 0x4C : ('LOGSET', 'xBBxxxx', ('port2_bits','port0_bits') ), 64 0x42 : ('LOGGET', 'xxxxxxx', () ), 65 0x30 : ('P0SET', 'Bxxxxxx', ('bits',) ), 66 0x41 : ('P0AND', 'Bxxxxxx', ('bits',) ), 67 0x4F : ('P0_OR', 'Bxxxxxx', ('bits',) ), 68 0x58 : ('P0XOR', 'Bxxxxxx', ('bits',) ), 69 0x32 : ('P2SET', 'Bxxxxxx', ('bits',) ), 70 0x61 : ('P2AND', 'Bxxxxxx', ('bits',) ), 71 0x6F : ('P2_OR', 'Bxxxxxx', ('bits',) ), 72 0x78 : ('P2XOR', 'Bxxxxxx', ('bits',) ), 73 0x3D : ('PXSET', 'xBBxxxx', ('port2_bits', 'port0_bits' ) ), 74 0x26 : ('PXAND', 'xBBxxxx', ('port2_bits', 'port0_bits' ) ), 75 0x2B : ('PX_OR', 'xBBxxxx', ('port2_bits', 'port0_bits' ) ), 76 0x5E : ('PXXOR', 'xBBxxxx', ('port2_bits', 'port0_bits' ) ), 77 0x50 : ('PXGET', 'xxxxxxx', () ), 78 0x4E : ('PXPGET', 'xxxxxxx', () ), 79 } 80 81 REPORT_SUMMARY = { 82 0x59 : ('VERREP', 'BBBBBxx', ('error_code','rel_main','rev_main','rel_dtvk','rev_dtvk') ), 83 0x5A : ('NUMREP', 'Bx5s', ('error_code','serial_num') ), 84 0x54 : ('RTCREP', 'BxxI', ('queue_len', 'rtc') ), 85 0x48 : ('HBREP', 'BHI', ('queue_len', 'rate', 'rtc' ) ), 86 0x53 : ('SERIN', 'BBBBBBB', ('status_code','data1','data2','data3','data4','data5','data6') ), 87 0x56 : ('VCKREP', 'BBBBBBB', ('status_code','min_duration','min_silence','trigger_level','peak_level','primary_gain','secondary_gain') ), 88 0x4A : ('DEBREP', 'xBBBBBB', ('port1_down','port1_up','int0_down','int0_up','int1_down','int1_up') ), 89 0x44 : ('KEYDN', 'xxBI', ('key_code','rtc') ), 90 0x55 : ('KEYUP', 'xxBI', ('key_code','rtc') ), 91 0x4B : ('KEYREP', 'xBBI', ('port3_bits','port1_bits','rtc') ), 92 0x50 : ('PXREP', 'xBBI', ('port2_bits','port0_bits','rtc') ), 93 0x4E : ('PXPREP', 'xBBI', ('port2_bits','port0_bits','rtc') ), 94 0x4D : ('MSKREP', 'xBBI', ('port3_bits','port1_bits','rtc') ), 95 0x57 : ('DIRREP', 'BBBI', ('port2_mode','port2_bits','port0_bits','rtc') ), 96 0x4C : ('LOGREP', 'xBBI', ('port2_bits','port0_bits','rtc') ), 97 0x45 : ('ERROR', 'BBBBBBB', ('data1','data2','data3','data4','data5','data6','data7') ) 98 } 99
100 -class messages:
101 '''class to handle message id lookup, and packing message objects into binary'''
102 - def __init__(self,message_summaries):
103 self.message_summaries=message_summaries 104 for message_id,message_summary in message_summaries.items(): 105 # add field for the ID 106 message_name=message_summary[0] 107 self.__dict__[message_name]=message_id 108 # add function to pack args 109 self.__dict__[message_name.lower()]=self._create_packing_function(message_id,message_summary)
110
111 - def ALL_IDS(self):
112 return self.message_summaries.keys()
113
114 - def _create_packing_function(self,message_id,message_summary):
115 # always big endian format 116 format='>B'+message_summary[1] 117 expected_args=message_summary[2] 118 def packing_function(*args): 119 if len(args) != len(expected_args): 120 raise RuntimeError("wrong number of args for: %s(), expected: %s" % (message_summary[0].lower(),expected_args)) 121 return struct.pack(format,message_id,*args)
122 return packing_function
123
124 - def name_from_id(self,message_id):
125 ''' 126 get the 'name' from the id of the message 127 ''' 128 return self.message_summaries[message_id][0]
129
130 - def parse(self,message_data):
131 ''' 132 convert raw binary data into a structure 133 ''' 134 id_byte=struct.unpack('B',message_data[0])[0] 135 # see if we know how to parse this message 136 if self.message_summaries.has_key(id_byte): 137 summary=self.message_summaries[id_byte] 138 139 format='>B'+summary[1] 140 unpacked=struct.unpack(format,message_data) 141 msg_fields={'name':summary[0]} 142 field_names=('id',)+summary[2] 143 if len(field_names) != len(unpacked): 144 raise RuntimeError("message did not unpack correctly: %r"%message_data) 145 for name,value in zip(field_names,unpacked): 146 msg_fields[name]=value 147 return dict_struct(**msg_fields) 148 else: 149 # otherwise just return it in a raw format 150 logging.info("unknown message id: %d",id_byte) 151 return dict_struct(id=id_byte,message_data=message_data)
152 153 ################################# 154 # objects for accessing commands and reports 155 # provides access to IDs and function for parsing and 156 # packing message 157 COMMAND=messages(COMMAND_SUMMARY) 158 REPORT=messages(REPORT_SUMMARY) 159
160 -class Commands:
161 ''' 162 class to handle sending reports to device and parsing incoming reports. 163 dynamically looks up/creates method for sending reports when none is 164 found on the class. this will let us override the default behavior 165 to make things friendlier when appropriate. 166 all received messages are queued up and require a call to 'process_received_reports' 167 to trigger the user's callbacks, so as to avoid thread issues. 168 '''
169 - def __init__(self,device):
170 self.device=device 171 self.callbacks={} 172 self.default_callbacks=set() 173 self.queue=Queue() 174 self.device.set_interrupt_report_callback(self._report_received)
175
176 - def _report_received(self,device,report_data):
177 logging.info('%r',report_data) 178 msg=REPORT.parse(report_data) 179 logging.info('received msg: %r',msg) 180 self.queue.put(msg)
181
182 - def process_received_reports(self,block=False,timeout=10):
183 ''' 184 process all reports that have been received and call the 185 relevant callbacks. 186 by default this method returns immediately if no reports 187 are on the queue, but can be made to block and wait for 188 a report if needeed 189 ''' 190 while block or not self.queue.empty(): 191 report=self.queue.get(block,timeout) 192 self._process_report(report) 193 # we always stop blocking after we've received 194 # at least one report 195 block=False
196
197 - def _process_report(self,report):
198 callbacks=self.callbacks.get(report.id,self.default_callbacks) 199 for callback in callbacks: 200 callback(report)
201
202 - def get_received_reports(self):
203 ''' 204 return a list of received reports (removes them from the queue) 205 ''' 206 reports=[] 207 while not self.queue.empty(): 208 msg=self.queue.get() 209 reports.append(msg) 210 return reports
211
212 - def clear_received_reports(self):
213 '''remove all received reports from the queue''' 214 while not self.queue.empty(): 215 self.queue.get()
216
217 - def add_callback(self,report_id,report_callback):
218 ''' 219 add a callback function that will be called when 220 a report with the given id arrives. callback 221 should take a single value that is the report 222 that was received 223 ''' 224 callbacks=self.callbacks.get(report_id,set()) 225 callbacks.add(report_callback) 226 self.callbacks[report_id]=callbacks
227
228 - def remove_callback(self,report_id,report_callback):
229 callbacks=self.callbacks.get(report_id,set()) 230 callbacks.discard(report_callback) # remove if present
231
232 - def add_default_callback(self,report_callback):
233 self.default_callbacks.add(report_callback)
234
235 - def remove_default_callback(self,report_callback):
236 self.default_callbacks.discard(report_callback)
237
238 - def wait_for_report(self,report_id):
239 ''' 240 blocks until we receive a report with the given id 241 from the box and then returns it (may trigger other callbacks) 242 ''' 243 # register a callback for the report we want 244 reports=[] 245 def callback(report): 246 reports.append(report)
247 self.add_callback(report_id,callback) 248 249 # process reports, until we get the one we want 250 try: 251 try: 252 while len(reports) == 0: 253 self.process_received_reports(block=True,timeout=2) 254 except Empty: 255 return None 256 finally: 257 # then remove the callback, as we don't need it anymore 258 self.remove_callback(report_id,callback) 259 260 # return the report we received 261 return reports[0]
262
263 - def send_wait_reply(self,command_id,report_id,*args):
264 ''' 265 send a command with the given arguments and wait for the reply 266 ''' 267 command_name=COMMAND.name_from_id(command_id).lower() 268 getattr(self,command_name)(*args) # send the command 269 return self.wait_for_report(report_id)
270
271 - def send_wait_field(self,command_id,report_id,field_name,*args):
272 ''' 273 send a command (with the args), wait for the report and return the field on the 274 report 275 ''' 276 return getattr(self.send_wait_reply(command_id,report_id,*args),field_name)
277
278 - def __getattr__(self,name):
279 '''return a function to send the named command to the device''' 280 packing_function=getattr(COMMAND,name,None) 281 if packing_function: 282 return lambda *arg: self.device.set_report(packing_function(*arg)) 283 raise AttributeError("couldn't find: %s" % name)
284 285
286 -class Line(object):
287 '''base class for Lines (individual parts of ports)'''
288 - def __init__(self,port,mask):
289 self._port=port 290 self._mask=mask
291
292 - def _bit_state(self,bits):
293 '''return 1/0 depending on whether relevent bit set''' 294 if (bits & self._mask) != 0: 295 return 1 296 else: 297 return 0;
298
299 - def _set_bit_state(self,bits,high):
300 if high: 301 return bits | self._mask 302 else: 303 return bits & (self._mask ^ 0xFF)
304 305
306 -class Port0_2(object):
307 ''' 308 class representing ports 0 and 2 (leds) 309 '''
310 - def __init__(self,commands,port_num):
311 self._commands=commands 312 self._port_num=port_num 313 self._port_bits='port%d_bits'%port_num
314 315 316 # direction property
317 - def _get_direction(self):
318 return self._commands.send_wait_field(COMMAND.DIRGET,REPORT.DIRREP,self._port_bits)
319
320 - def _set_direction(self,bits):
321 # get original state 322 rep=self._commands.send_wait_reply(COMMAND.DIRGET,REPORT.DIRREP) 323 setattr(rep,self._port_bits,bits) # update bits for this port 324 # send set command and wait for reply 325 self._commands.send_wait_reply(COMMAND.DIRSET,REPORT.DIRREP,0,rep.port2_bits,rep.port0_bits)
326 327 direction=property(_get_direction,_set_direction) 328 '''get/set the direction of the port''' 329 330 # logic property
331 - def _get_logic(self):
332 return self._commands.send_wait_field(COMMAND.LOGGET,REPORT.LOGREP,self._port_bits)
333
334 - def _set_logic(self,bits):
335 rep=self._commands.send_wait_reply(COMMAND.LOGGET,REPORT.LOGREP) 336 setattr(rep,self._port_bits,bits) 337 self._commands.send_wait_reply(COMMAND.LOGSET,REPORT.LOGREP,rep.port2_bits,rep.port0_bits)
338 339 logic=property(_get_logic,_set_logic) 340 '''get/set the logic on the port''' 341 342 # state property
343 - def _get_state(self):
344 return self._commands.send_wait_field(COMMAND.PXGET,REPORT.PXREP,self._port_bits)
345
346 - def _set_state(self,bits):
347 port_set=getattr(self._commands,'p%dset'%self._port_num) 348 port_set(bits) # either p0set or p2set 349 # then wait for reply (to avoid messing things up) 350 self._commands.wait_for_report(REPORT.PXREP)
351 352 state=property(_get_state,_set_state) 353 '''get/set the port state''' 354 355 356 # logic methods (and/or/xor)
357 - def _logic_state(self,logic_bits,command_format):
358 port_logic=getattr(self._commands,command_format%self._port_num) 359 port_logic(logic_bits) 360 return getattr(self._commands.wait_for_report(REPORT.PXREP),self._port_bits)
361
362 - def and_state(self,and_bits):
363 '''logically 'and' the value on the port, returns the port state''' 364 return self._logic_state(and_bits,'p%dand')
365
366 - def or_state(self,or_bits):
367 '''logically 'or' the value on the port, returns the port state''' 368 return self._logic_state(or_bits,'p%d_or')
369
370 - def xor_state(self,xor_bits):
371 '''logically 'xor' the value on the port, returns the port state''' 372 return self._logic_state(xor_bits,'p%dxor')
373
374 - def _get_line(self,line_no):
375 ''' 376 return an object that let's the user modify/query values 377 on a single line of the port (1-bit) 378 ''' 379 #inner class for individual lines 380 class PortLine(Line): 381 # state property 382 def _get_state(self): 383 return self._bit_state(self._port.state)
384 385 def _set_state(self,high): 386 if high: 387 self._port.or_state(self._mask) 388 else: 389 self._port.and_state(self._mask ^ 0xFF) # invert mask
390 391 state=property(_get_state,_set_state) 392 393 # direction property 394 def _get_direction(self): 395 return self._bit_state(self._port.direction) 396 397 def _set_direction(self,high): 398 self._port.direction=self._set_bit_state(self._port.direction,high) 399 400 direction=property(_get_direction,_set_direction) 401 402 # logic property 403 def _get_logic(self): 404 return self._bit_state(self._port.logic) 405 406 def _set_logic(self,high): 407 self._port.logic=self._set_bit_state(self._port.logic,high) 408 409 logic=property(_get_logic,_set_logic) 410 411 mask=1<<line_no 412 # return new PortLine object 413 return PortLine(self,mask) 414 415 # properties for each individual line 416 line0=property(lambda self: self._get_line(0)) 417 line1=property(lambda self: self._get_line(1)) 418 line2=property(lambda self: self._get_line(2)) 419 line3=property(lambda self: self._get_line(3)) 420 line4=property(lambda self: self._get_line(4)) 421 line5=property(lambda self: self._get_line(5)) 422 line6=property(lambda self: self._get_line(6)) 423 line7=property(lambda self: self._get_line(7)) 424 425 # property for all 8 lines 426 lines=property(lambda self: [self._get_line(i) for i in range(8)]) 427 ''' 428 list of individual lines. 429 each line has properties for state, direction and logic 430 that can be used to alter the individual bits/lines on the whole 431 port 432 ''' 433 434 435
436 -def _set_debounce(commands,port1_down=None,port1_up=None,int0_down=None,int0_up=None,int1_down=None,int1_up=None):
437 '''helper for setting debounce of a single field''' 438 rep=commands.send_wait_reply(COMMAND.DEBGET,REPORT.DEBREP) 439 440 if port1_down is not None: 441 rep.port1_down=port1_down 442 if port1_up is not None: 443 rep.port1_up=port1_up 444 if int0_down is not None: 445 rep.int0_down=int0_down 446 if int0_up is not None: 447 rep.int0_up=int0_up 448 if int1_down is not None: 449 rep.int1_down=int1_down 450 if int1_up is not None: 451 rep.int1_up=int1_up 452 453 commands.send_wait_reply( 454 COMMAND.DEBSET, 455 REPORT.DEBREP, 456 rep.port1_down,rep.port1_up, 457 rep.int0_down,rep.int0_up, 458 rep.int1_down,rep.int1_up)
459 460
461 -class Buttons(object):
462 '''class that represents the buttons on the USBBox'''
463 - def __init__(self,commands):
464 self._commands=commands
465
466 - def _get_enabled(self):
467 return self._commands.send_wait_field(COMMAND.MSKGET,REPORT.MSKREP,'port1_bits')
468
469 - def _set_enabled(self,enabled):
470 # get previous value 471 rep=self._commands.send_wait_reply(COMMAND.MSKGET,REPORT.MSKREP) 472 # update port 1 473 rep.port1_bits=enabled 474 # set mask - keeping old port3 value 475 self._commands.send_wait_reply(COMMAND.MSKSET,REPORT.MSKREP,rep.port3_bits,rep.port1_bits)
476 477 enabled=property(_get_enabled,_set_enabled) 478 '''get/set enable/disabled status of all buttons''' 479
480 - def _get_debounce_down(self):
481 return self._commands.send_wait_field(COMMAND.DEBGET,REPORT.DEBREP,'port1_down')
482
483 - def _set_debounce_down(self,debounce):
484 _set_debounce(self._commands,port1_down=debounce)
485 486 debounce_down=property(_get_debounce_down,_set_debounce_down) 487
488 - def _get_debounce_up(self):
489 return self._commands.send_wait_field(COMMAND.DEBGET,REPORT.DEBREP,'port1_up')
490
491 - def _set_debounce_up(self,debounce):
492 _set_debounce(self._commands,port1_up=debounce)
493 494 debounce_up=property(_get_debounce_up,_set_debounce_up) 495
496 - def _get_state(self):
497 return self._commands.send_wait_field(COMMAND.KEYGET,REPORT.KEYREP,'port1_bits')
498 499 state=property(_get_state) 500 '''get the state of all buttons (key report port1_bits value)''' 501
502 - def _get_line(self,line_no):
503 ''' 504 return an object that let's the user modify/query values 505 on a single line of the port (1-bit) 506 ''' 507 #inner class for individual lines 508 class ButtonLine(Line): 509 # state property 510 def _get_state(self): 511 return self._bit_state(self._port.state)
512 513 state=property(_get_state) 514 '''state of the line''' 515 516 # enabled property 517 def _get_enabled(self): 518 return self._bit_state(self._port.enabled)
519 520 def _set_enabled(self,high): 521 self._port.enabled=self._set_bit_state(self._port.enabled,high) 522 523 enabled=property(_get_enabled,_set_enabled) 524 '''get/set whether the line/button is enable or not''' 525 526 mask=1<<line_no 527 # return new ButtonLine object 528 return ButtonLine(self,mask) 529 530 # properties for each individual line 531 line0=property(lambda self: self._get_line(0)) 532 '''individual line (one button)''' 533 line1=property(lambda self: self._get_line(1)) 534 line2=property(lambda self: self._get_line(2)) 535 line3=property(lambda self: self._get_line(3)) 536 line4=property(lambda self: self._get_line(4)) 537 line5=property(lambda self: self._get_line(5)) 538 line6=property(lambda self: self._get_line(6)) 539 line7=property(lambda self: self._get_line(7)) 540 541 lines=property(lambda self: [self._get_line(i) for i in range(8)]) 542 '''property for all 8 lines. 543 each line (button) has a state and enabled property 544 so each button can be queried/modified separately 545 ''' 546 547
548 -class Interrupt(object):
549 '''either int0 or int1 on the USBBox'''
550 - def __init__(self,commands,mask,int_num):
551 self._commands=commands 552 self._mask=mask 553 self._int_num=int_num # interrupt number
554
555 - def _get_enabled(self):
556 bits=self._commands.send_wait_field(COMMAND.MSKGET,REPORT.MSKREP,'port3_bits') 557 if bits & self._mask != 0: # if bit set 558 return 1 559 else: 560 return 0
561
562 - def _set_enabled(self,enabled):
563 # get previous value 564 rep=self._commands.send_wait_reply(COMMAND.MSKGET,REPORT.MSKREP) 565 # update port 3 value 566 if enabled: 567 rep.port3_bits=rep.port3_bits | self._mask 568 else: 569 rep.port3_bits=rep.port3_bits & (self._mask ^ 0xFF) 570 self._commands.send_wait_reply(COMMAND.MSKSET,REPORT.MSKREP,rep.port3_bits,rep.port1_bits)
571 572 enabled=property(_get_enabled,_set_enabled) 573 '''enable/disable the interrupt''' 574
575 - def _get_debounce_down(self):
576 return self._commands.send_wait_field(COMMAND.DEBGET,REPORT.DEBREP,'int%d_down'%self._int_num)
577
578 - def _set_debounce_down(self,debounce):
579 # use keyword expansion to expand this 580 args={} 581 args['int%d_down'%self._int_num]=debounce 582 _set_debounce(self._commands,**args)
583 584 debounce_down=property(_get_debounce_down,_set_debounce_down) 585 586
587 - def _get_debounce_up(self):
588 return self._commands.send_wait_field(COMMAND.DEBGET,REPORT.DEBREP,'int%d_up'%self._int_num)
589
590 - def _set_debounce_up(self,debounce):
591 # use keyword expansion to expand this 592 args={} 593 args['int%d_up'%self._int_num]=debounce 594 _set_debounce(self._commands,**args)
595 596 debounce_up=property(_get_debounce_up,_set_debounce_up)
597 598
599 -def _get_voice_key(commands,field):
600 while True: 601 rep=commands.send_wait_reply(COMMAND.VCKGET,REPORT.VCKREP) 602 if rep.status_code in [0x58,0x28]: 603 break # got reply back ok 604 if rep.status_code == 0x48: # basic NAK error is ok 605 # sleep a little bit to let the value finish getting written 606 # then we'll try again 607 time.sleep(0.005) 608 else: 609 raise RuntimeError("error getting voice key code: 0x%x"%rep.status_code) 610 611 return getattr(rep,field)
612
613 -def _set_voice_key(commands,min_duration=None,min_silence=None,trigger_level=None,mic_pass_thru=0):
614 '''helper for setting voice_key setting of a single field''' 615 rep=commands.send_wait_reply(COMMAND.VCKGET,REPORT.VCKREP) 616 617 if min_duration is not None: 618 rep.min_duration=min_duration 619 if min_silence is not None: 620 rep.min_silence=min_silence 621 if trigger_level is not None: 622 rep.trigger_level=trigger_level 623 624 rep=commands.send_wait_reply( 625 COMMAND.VCKSET, 626 REPORT.VCKREP, 627 0xB8, 628 rep.min_duration, 629 rep.min_silence, 630 rep.trigger_level, 631 mic_pass_thru) 632 633 if not rep.status_code in [0x58,0x28]: 634 raise RuntimeError("error setting voice key code: 0x%x"%rep.status_code) 635 636 # artificial delay to let values be written 637 time.sleep(0.005)
638
639 -class VoiceKey(Interrupt):
640 '''object representing the voice input on the USBBox'''
641 - def __init__(self,commands):
642 Interrupt.__init__(self,commands,mask=(1<<2),int_num=0) 643 self._mic_pass_thru=0 # can't get pass thru state from box, so have to remember it
644 645 # primary gain property
646 - def _get_primary_gain(self):
647 return self._commands.send_wait_field(COMMAND.VCKGET,REPORT.VCKREP,'primary_gain')
648
649 - def _set_primary_gain(self,gain):
650 self._commands.send_wait_reply(COMMAND.VCKSET,REPORT.VCKREP,0xA9,gain,0,0,0)
651 652 primary_gain=property(_get_primary_gain,_set_primary_gain) 653 654 # secondary_gain property
655 - def _get_secondary_gain(self):
656 return self._commands.send_wait_field(COMMAND.VCKGET,REPORT.VCKREP,'secondary_gain')
657
658 - def _set_secondary_gain(self,gain):
659 self._commands.send_wait_reply(COMMAND.VCKSET,REPORT.VCKREP,0xAA,gain,0,0,0)
660 661 secondary_gain=property(_get_secondary_gain,_set_secondary_gain) 662 663 # min_duration property
664 - def _get_min_duration(self):
665 return _get_voice_key(self._commands,'min_duration')
666
667 - def _set_min_duration(self,duration):
668 _set_voice_key(self._commands,min_duration=duration,mic_pass_thru=self._mic_pass_thru)
669 670 min_duration=property(_get_min_duration,_set_min_duration) 671 672 # min_silence property
673 - def _get_min_silence(self):
674 return _get_voice_key(self._commands,'min_silence')
675
676 - def _set_min_silence(self,duration):
677 _set_voice_key(self._commands,min_silence=duration,mic_pass_thru=self._mic_pass_thru)
678 679 min_silence=property(_get_min_silence,_set_min_silence) 680 681 # trigger_level property
682 - def _get_trigger_level(self):
683 return _get_voice_key(self._commands,'trigger_level')
684
685 - def _set_trigger_level(self,level):
686 _set_voice_key(self._commands,trigger_level=level,mic_pass_thru=self._mic_pass_thru)
687 688 trigger_level=property(_get_trigger_level,_set_trigger_level) 689 '''get/set trigger level''' 690 691 692 # mic_pass_thru property
693 - def _get_mic_pass_thru(self):
694 return self._mic_pass_thru
695
696 - def _set_mic_pass_thru(self,mic_pass_thru):
697 if mic_pass_thru: 698 self._mic_pass_thru=1 699 else: 700 self._mic_pass_thru=0 701 _set_voice_key(self._commands,mic_pass_thru=self._mic_pass_thru)
702 703 mic_pass_thru=property(_get_mic_pass_thru,_set_mic_pass_thru) 704 '''get/set mic pass through state'''
705 706
707 -class Serial(object):
708 '''serial port on the USBBox'''
709 - def __init__(self,commands):
710 self._commands=commands 711 # add object as callback to 712 # receive incoming packets 713 self._commands.add_callback( 714 REPORT.SERIN, 715 self._serial_in 716 ) 717 self._bytes_received=[]
718
719 - def _serial_in(self,report):
720 num_bytes=report.status_code 721 if num_bytes <= 6: 722 # we've received bytes written to the serial port 723 for i in range(0,num_bytes): 724 # save data1-data6 as appropriate 725 self._bytes_received.append(getattr(report,'data%d'%(i+1))) 726 elif report.status_code in ['0xFF','0xFE','0xF7']: 727 raise RuntimeError("error reading from serial port: 0x%x"%report.status_code)
728
729 - def write(self,bytes):
730 '''write bytes to the serial port''' 731 for i in range(0,len(bytes),6): 732 b=list(bytes[i:i+6]) # no more than six bytes at a time 733 # convert into ints 734 b=[struct.unpack('B',byte)[0] for byte in b] 735 num_b=len(b) 736 while len(b) < 6: 737 b.append(0) # add padding bytes 738 739 # might be receiving bytes at same time, so should loop until we get something 740 # that isn't receiving bytes (those will be dealt with in callback on _serial_in) 741 while True: 742 rep=self._commands.send_wait_reply(COMMAND.SEROUT,REPORT.SERIN,num_b,*b) 743 if rep.status_code == 0xF0: 744 break # got confirmation we've transmitted ok
745
746 - def read(self):
747 ''' 748 wait for input on the serial port. blocks for a while, but times 749 out if nothing received and returns an empty string 750 ''' 751 self._commands.wait_for_report(REPORT.SERIN) 752 # return what we've received so far (which may be nothing) 753 bytes = ''.join([struct.pack('B',byte) for byte in self._bytes_received]) 754 self._bytes_received[:]=[] # clear received bytes 755 return bytes
756 757 758
759 -class USBBox(object):
760 '''the USBBox itself''' 761
762 - def __init__(self,do_reset=True):
763 self._device=None 764 for dev in hid.find_hid_devices(): 765 if is_usb_bbox(dev): 766 logging.info("found USB button box") 767 self._device=dev 768 break 769 770 if self._device is None: 771 raise RuntimeError("could not find button box - check it's plugged in") 772 773 self._device.open() 774 775 self._commands=Commands(self._device) 776 777 self.recording=False 778 self.recording_callback=None 779 self.report_ids=None 780 781 self._port0=Port0_2(self.commands,0) 782 self._port1=Buttons(self.commands) 783 self._port2=Port0_2(self.commands,2) 784 785 self._int0=VoiceKey(self.commands) 786 self._int1=Interrupt(self.commands,(1<<3),int_num=1) 787 788 self._serial=Serial(self.commands) 789 790 if do_reset: 791 self.reset_box()
792
793 - def __del__(self):
794 if self.device is not None: 795 self.device.close()
796 797 device = property(lambda self: self._device) 798 '''the HIDDevice (the physical box itself)''' 799 800 commands = property(lambda self: self._commands) 801 '''Commands object for low-level API''' 802 803 port0 = property(lambda self: self._port0) 804 '''Port0_2 object for "port 0" on the box''' 805 806 port1 = property(lambda self: self._port1) 807 '''Buttons object for "port 1" on the box''' 808 809 port2 = property(lambda self: self._port2) 810 '''Port0_2 object for "port 2" on the box''' 811 812 int0 = property(lambda self: self._int0) 813 '''VoiceKey object for "interrupt 0" on the box''' 814 815 int1 = property(lambda self: self._int1) 816 '''Interrupt object for "interrupt 1" on the box''' 817 818 serial = property(lambda self: self._serial) 819 '''Serial object for the serial port on the box''' 820 821 # add synonyms 822 leds=port2 823 '''synonym for port2''' 824 buttons=port1 825 '''synonym for port1''' 826 voice_key=int0 827 '''synonym for int0''' 828 optic_key=int1 829 '''synonym for int1''' 830 831
832 - def send_command(self,command_id,bytes=''):
833 '''send a command to the box (one command_id byte and 7 data bytes)''' 834 self.device.set_report(struct.pack("B7s",command_id,bytes))
835
836 - def process_received_reports(self):
837 '''process any received reports and call registered callbacks''' 838 self.commands.process_received_reports()
839
840 - def clear_received_reports(self):
842
843 - def start_recording(self,report_ids,out_file):
844 ''' 845 whenever we read a report write it to the given 846 file (if the id is in report_ids) 847 ''' 848 if self._recording: 849 raise RuntimeError("sorry already recording, please stop_recording() first") 850 self._recording=True 851 # save report to file 852 self._recording_callback=lambda report: out_file.write("%s\n"%report) 853 self._report_ids=set(report_ids) 854 for report_id in self._report_ids: 855 self.commands.add_callback(report_id,self._recording_callback)
856
857 - def stop_recording(self):
858 '''removes the callbacks we had in place for recording''' 859 if self._recording: 860 # make sure we process any remaing reports 861 self.process_received_reports() 862 self._recording=False 863 self._out_file=None 864 for report_id in self._report_ids: 865 self.commands.remove_callback(report_id,self._recording_callback) 866 self._report_ids=None
867 868 # serial_num property
869 - def _get_serial_num(self):
870 return self.commands.send_wait_field(COMMAND.NUMGET,REPORT.NUMREP,'serial_num')
871 serial_num=property(_get_serial_num) 872 '''read the serial number of the box''' 873 874 # PAC property
875 - def _set_PAC(self,value):
876 # this will expand value into separate arguments 877 # so we can pass a string for the PAC code, rather 878 # than individual bytes 879 self.commands.pacset(*value)
880 PAC=property(fset=_set_PAC) 881 ''' set the PAC code (write only) ''' 882 883 # version property (main board)
884 - def _get_version(self):
885 rep=self.commands.send_wait_reply(COMMAND.VERGET,REPORT.VERREP) 886 return (rep.rel_main,rep.rev_main)
887 version=property(_get_version) 888 '''get the main board version number''' 889 890 # voice_version property
891 - def _get_voice_version(self):
892 rep=self.commands.send_wait_reply(COMMAND.VERGET,REPORT.VERREP) 893 return (rep.rel_dtvk,rep.rev_dtvk)
894 voice_version=property(_get_voice_version) 895 '''get the version number of the voice board''' 896 897 # clock property
898 - def _get_clock(self):
899 return self.commands.send_wait_field(COMMAND.RTCGET,REPORT.RTCREP,'rtc')
900 clock=property(_get_clock) 901 '''get the current clock value''' 902 903 # heartbeat property (write only)
904 - def _set_heartbeat(self,value):
905 self.commands.hbset(value)
906 heartbeat=property(fset=_set_heartbeat) 907 '''set the heartbeat rate (write only)''' 908
909 - def purge_queue(self):
910 '''purge the event queue on the box''' 911 self.commands.qpurge()
912
913 - def reset_clock(self):
914 '''reset the clock on the box (returns a key report)''' 915 return self.commands.send_wait_reply(COMMAND.RESRTC,REPORT.KEYREP)
916
917 - def enable_loopback(self):
918 '''enable loopback (LEDs on/off with button presses)''' 919 rep=self.commands.send_wait_reply(COMMAND.DIRGET,REPORT.DIRREP) 920 self.commands.send_wait_reply(COMMAND.DIRSET,REPORT.DIRREP,1,rep.port2_bits,rep.port0_bits)
921
922 - def disable_loopback(self):
923 rep=self.commands.send_wait_reply(COMMAND.DIRGET,REPORT.DIRREP) 924 self.commands.send_wait_reply(COMMAND.DIRSET,REPORT.DIRREP,0,rep.port2_bits,rep.port0_bits)
925
926 - def wait_for_keydown(self):
927 '''wait for a key to be pressed and returns the report''' 928 return self.commands.wait_for_report(REPORT.KEYDN)
929
930 - def wait_for_keyup(self):
931 '''wait for a key to be released and returns the report''' 932 return self.commands.wait_for_report(REPORT.KEYUP)
933
934 - def reset_box(self):
935 '''set box to some known values''' 936 self.disable_loopback() 937 938 self.heartbeat=30000 # 30 seconds 939 940 self.buttons.debounce_up=5 # ms 941 self.buttons.debounce_down=20 # ms 942 self.int0.debounce_up=5 # ms 943 self.int0.debounce_down=20 # ms 944 self.int1.debounce_up=5 # ms 945 self.int1.debounce_down=20 # ms 946 947 self.port0.logic=0 948 self.port0.state=0xff 949 950 self.port2.logic=0 951 self.port2.state=0xff 952 953 self.buttons.enabled=0xff 954 self.int0.enabled=1 955 self.int1.enabled=1 956 957 self.reset_clock() 958 959 self.purge_queue()
960 961 if __name__ == '__main__': 962 import sys 963 964 usbbox=USBBox() 965 966 print "USBBox connected" 967 print "serial #:",usbbox.serial_num 968 print "version:",usbbox.version 969 print "voice version:", usbbox.voice_version 970 971 # attached a callback for every report type
972 - def report_callback(msg):
973 print "received:",msg
974 for command_id in REPORT.ALL_IDS(): 975 usbbox.commands.add_callback(command_id,report_callback) 976 977 from StringIO import StringIO 978 outfile=StringIO() 979 # record all incoming reports 980 usbbox.start_recording(REPORT_SUMMARY.keys(),outfile) 981 982 import re 983 984 while True: 985 time.sleep(0.5) # sleep a little to let any reports get received 986 usbbox.process_received_reports() 987 command=raw_input("command: ").strip() 988 if command == 'exit': 989 break 990 elif command == '': 991 continue 992 elif command == 'help': 993 # print list of available commands 994 print "commands:" 995 print " exit" 996 print " help" 997 for command_id in COMMAND_SUMMARY.keys(): 998 command_name=COMMAND_SUMMARY[command_id][0].lower() 999 command_args=COMMAND_SUMMARY[command_id][2] 1000 command_args=['<%s>' % arg for arg in command_args] 1001 print " %s %s" % (command_name,' '.join(command_args)) 1002 else: 1003 try: 1004 command_parts=command.split() 1005 command_name,command_args=command_parts[0],command_parts[1:] 1006 # check command is known 1007 known=False 1008 for command_id in COMMAND_SUMMARY.keys(): 1009 if COMMAND_SUMMARY[command_id][0].lower() == command_name: 1010 known=True 1011 break 1012 if not known: 1013 print "error, unknown command: " + command_name 1014 else: 1015 command_fn=getattr(usbbox.commands,command_name) 1016 # turn all arguments into int's 1017 command_args=[int(arg) for arg in command_args] 1018 command_fn(*command_args) 1019 except: 1020 print "error running: " + command 1021 1022 # make sure we process any remaining reports 1023 usbbox.process_received_reports() 1024 usbbox.stop_recording() 1025 1026 print "recorded reports:" 1027 print outfile.getvalue() 1028