Source code for rusocsci.buttonbox

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
RuSocSci module for the BITSI buttonbox

Copyright (C) 2013-2014 Wilbert van Ham, Radboud University Nijmegen
Distributed under the terms of the GNU General Public License (GPL) version 3 or newer.
"""
import sys, serial, time, os, re, logging, glob
from . import utils

# our buttonbox has id 0403:6001 fro  its UART IC
# todo: add support for 2341:0001 (Arduino Uno)
# make sure you have the pyusb module installed
# for window you may need this: http://sourceforge.net/apps/trac/libusb-win32/wiki

[docs]class Buttonbox(object): """ Object that connects tot RuSocSci BITSI buttonbox. Typical usage:: from rusocsci import buttonbox bb = buttonbox.Buttonbox() # connect to last inserted buttonbox print("Press any key") l = bb.waitKeys(10) if l: print("key pressed: {}".format(l[0])) else: print("no key pressed.") or for non-blocking input (using the Python 3 print function):: from __future__ import print_function from rusocsci import buttonbox import time, sys bb = buttonbox.Buttonbox() # connect to last inserted buttonbox print("Press any key") while not len(bb.getKeys()): # while there is no input print(".", end='') sys.stdout.flush() time.sleep(1) """ def __init__(self, id=0, port=None): self._port = utils.getPort(id, port) self._device = None if not self._port: raise Exception("No USB serial device connected, could not get port name.") [self._device, idString] = utils.open(self._port) if not self._device: logging.critical("No BITSI buttonbox connected, could not connect to port {}".format(self._port)) elif idString == "BITSI mode, Ready!" or idString == "BITSI event mode, Ready!": logging.debug("Device is a BITSI buttonbox ({}): {}".format(len(idString), idString)) else: logging.error("Device is NOT a BITSI buttonbox ({}): {}".format(len(idString), idString))
[docs] def close(self): if self._device: self._device.close()
[docs] def clearEvents(self): """ Clear previous events that are still in the input buffer. """ if self._device == None: raise Exception("No buttonbox connected") try: self._device.flushInput() except Exception as e: raise Exception("Could not clear buttonbox buffer:\n{}".format(e))
[docs] def getButtons(self, buttonList=None): """ Returns a list of buttons that were pressed on the buttonbox. :Parameters: buttonList : **None** or [] Allows the user to specify a set of keys to check for. Only keypresses from this set of keys will be removed from the keyboard buffer. If the keyList is None all keys will be checked and the key buffer will be cleared completely. NB, pygame doesn't return timestamps (they are always 0) timeStamped : **False** or True or `Clock` If True will return a list of tuples instead of a list of keynames. Each tuple has (keyname, time). If a `core.Clock` is given then the time will be relative to the `Clock`'s last reset there is no timestamp in our buttonbox. Use buttonbox.waitkeys if you want timestamps. """ if self._device == None: raise Exception("No buttonbox connected") self._device.timeout = 0 cList = self._device.read(1024) #if len(cList)>0: #logging.debug("read {} bytes: {}".format(len(cList), cList)) cListSelected = [] for c in cList: if buttonList==None or c in buttonList: cListSelected.append(c) return cListSelected
[docs] def getKeys(self, keyList=None): """ Mutatis Mutandis identical to getButtons """ return self.getButtons(buttonList=keyList)
[docs] def waitButtons(self, maxWait=float("inf"), buttonList=None, timeStamped=False, flush=True): """ Same as getButtons(), but halts everything (including drawing) while awaiting input from buttonbox. Implicitly clears buttonbox, so any preceding buttonpresses will be lost. :Parameters: maxWait : any numeric value. Maximum number of seconds period and which buttons to wait for. Default is float('inf') which simply waits forever. buttonList: List of one character strings containing the buttons to react to. All other button presses will be ignored. Note that for BITSI buttonboxes the buttons are identified by capital letters upon press and by lower case letters upon release. Returns None if times out. Returns a list of one character upon succes, like the PsychoPy event module. """ if self._device == None: raise Exception("No buttonbox connected") if flush: self._device.flushInput() t = time.time() while maxWait > time.time() - t: if maxWait == float("inf"): self._device.timeout = None else: self._device.timeout = maxWait - (time.time() - t) c = self._device.read(1) if buttonList==None or c in buttonList: # return break else: # as if nothing pressed c = '' if c=='': # version 0.7 addition to comply with PsychoPy return None if hasattr(timeStamped, 'timeAtLastReset'): return [(c, time.time() - timeStamped.timeAtLastReset)] elif timeStamped: # return as a one item list to mimic getButtons behaviour return [(c, time.time() - t)] else: return [c] # version 0.7 change to comply with PsychoPy
[docs] def waitKeys(self, maxWait=float("inf"), keyList=None, timeStamped=False): """ Mutatis Mutandis identical tot waitButtons """ return self.waitButtons(maxWait=maxWait, buttonList=keyList, timeStamped=False, flush=True)
[docs] def setLeds(self, leds=[False,False,False,False,False,False,False,False], val=None): """ Set buttonbox LEDs to a certain pattern """ if self._device == None: raise Exception("No buttonbox connected") if val == None: val = 0 for i in range(8): if len(leds)>i: if leds[i]: val += 1<<i else: break self._device.write(chr(val))
[docs] def sendMarker(self, markers=[False,False,False,False,False,False,False,False], val=None): """ Mutatis Mutandis identical to setLeds() """ return self.setLeds(leds=markers, val=val)
[docs] def waitLeds(self, leds=[False,False,False,False,False,False,False,False], wait=1.0, val=None): """ Set buttonbox LEDs to a certain pattern and wait a while. Reset afterwards. """ if self._device == None: raise Exception("No buttonbox connected") self.setLeds(leds, val) time.sleep(wait) self.setLeds()