class ParallelTrigger(object): """Parallel port and dummy triggering support .. warning:: When using the parallel port, calling :meth:`expyfun.ExperimentController.start_stimulus` will automatically invoke a stamping of the 1 trigger, which will in turn cause a delay equal to that of ``high_duration``. This can effect e.g. :class:`EyelinkController` timing. Parameters ---------- mode : str 'parallel' for real use. 'dummy', passes all calls. address : str | int | None The address to use. On Linux this should be a string path like ``'/dev/parport0'`` (equivalent to None), on Windows it should be an integer address like ``888`` or ``0x378`` (equivalent to None). high_duration : float Amount of time (seconds) to leave the trigger high whenever sending a trigger. verbose : bool, str, int, or None If not None, override default verbose level (see expyfun.verbose). Notes ----- Parallel port activation is enabled by using the ``trigger_controller`` argument of :class:`expyfun.ExperimentController`. On Linux, parallel port may require some combination of the following: 1. ``sudo modprobe ppdev`` 2. Add user to ``lp`` group (``/etc/group``) 3. Run ``sudo rmmod lp`` (otherwise ``lp`` takes exclusive control) 4. Edit ``/etc/modprobe.d/blacklist.conf`` to add ``blacklist lp`` The ``parallel`` module must also be installed. On Windows, you may need to download ``inpout32.dll`` from someplace like http://www.highrez.co.uk/downloads/inpout32/. """ @verbose_dec def __init__(self, mode='dummy', address=None, high_duration=0.005, verbose=None): if mode == 'parallel': if sys.platform.startswith('linux'): address = '/dev/parport0' if address is None else address if not isinstance(address, string_types): raise ValueError('addrss must be a string or None, got %s ' 'of type %s' % (address, type(address))) from parallel import Parallel self._port = Parallel(address) self._portname = address self._set_data = self._port.setData elif sys.platform.startswith('win'): from ctypes import windll if not hasattr(windll, 'inpout32'): raise SystemError( 'Must have inpout32 installed, see:\n\n' 'http://www.highrez.co.uk/downloads/inpout32/') base = 0x378 if address is None else address if isinstance(base, string_types): base = int(base, 16) if not isinstance(base, int): raise ValueError('address must be int or None, got %s of ' 'type %s' % (base, type(base))) self._port = windll.inpout32 mask = np.uint8(1 << 5 | 1 << 6 | 1 << 7) # Use ECP to put the port into byte mode val = int((self._port.Inp32(base + 0x402) & ~mask) | (1 << 5)) self._port.Out32(base + 0x402, val) # Now to make sure the port is in output mode we need to make # sure that bit 5 of the control register is not set val = int(self._port.Inp32(base + 2) & ~np.uint8(1 << 5)) self._port.Out32(base + 2, val) self._set_data = lambda data: self._port.Out32(base, data) self._portname = str(base) else: raise NotImplementedError('Parallel port triggering only ' 'supported on Linux and Windows') else: # mode == 'dummy': self._port = self._portname = None self._trigger_list = list() self._set_data = lambda x: (self._trigger_list.append(x) if x != 0 else None) self.high_duration = high_duration self.mode = mode def __repr__(self): return '<ParallelTrigger : %s (%s)>' % (self.mode, self._portname) def _stamp_trigger(self, trig): """Fake stamping""" self._set_data(int(trig)) wait_secs(self.high_duration) self._set_data(0) def stamp_triggers(self, triggers, delay=0.03, wait_for_last=True): """Stamp a list of triggers with a given inter-trigger delay Parameters ---------- triggers : list No input checking is done, so ensure triggers is a list, with each entry an integer with fewer than 8 bits (max 255). delay : float The inter-trigger delay. wait_for_last : bool If True, wait for last trigger to be stamped before returning. """ for ti, trig in enumerate(triggers): self._stamp_trigger(trig) if ti < len(triggers) - 1 or wait_for_last: wait_secs(delay - self.high_duration) def close(self): """Release hardware interfaces """ if hasattr(self, '_port'): del self._port def __del__(self): return self.close()
class ParallelTrigger(object): """Parallel port and dummy triggering support. .. warning:: When using the parallel port, calling :meth:`expyfun.ExperimentController.start_stimulus` will automatically invoke a stamping of the 1 trigger, which will in turn cause a delay equal to that of ``trigger_duration``. This can effect e.g. :class:`EyelinkController` timing. Parameters ---------- mode : str 'parallel' for real use. 'dummy', passes all calls. address : str | int | None The address to use. On Linux this should be a string path like ``'/dev/parport0'`` (equivalent to None), on Windows it should be an integer address like ``888`` or ``0x378`` (equivalent to None). The config variable ``TRIGGER_ADDRESS`` can be used to set this permanently. trigger_duration : float Amount of time (seconds) to leave the trigger high whenever sending a trigger. ec : instance of ExperimentController The ExperimentController. verbose : bool, str, int, or None If not None, override default verbose level. Notes ----- Parallel port activation is enabled by using the ``trigger_controller`` argument of :class:`expyfun.ExperimentController`. """ @verbose_dec def __init__(self, mode='dummy', address=None, trigger_duration=0.01, ec=None, verbose=None): self.ec = ec if mode == 'parallel': if sys.platform.startswith('linux'): address = '/dev/parport0' if address is None else address if not isinstance(address, string_types): raise ValueError('addrss must be a string or None, got %s ' 'of type %s' % (address, type(address))) from parallel import Parallel logger.info('Expyfun: Using address %s' % (address,)) self._port = Parallel(address) self._portname = address self._set_data = self._port.setData elif sys.platform.startswith('win'): from ctypes import windll if not hasattr(windll, 'inpout32'): raise SystemError( 'Must have inpout32 installed, see:\n\n' 'http://www.highrez.co.uk/downloads/inpout32/') base = '0x378' if address is None else address logger.info('Expyfun: Using base address %s' % (base,)) if isinstance(base, string_types): base = int(base, 16) if not isinstance(base, int): raise ValueError('address must be int or None, got %s of ' 'type %s' % (base, type(base))) self._port = windll.inpout32 mask = np.uint8(1 << 5 | 1 << 6 | 1 << 7) # Use ECP to put the port into byte mode val = int((self._port.Inp32(base + 0x402) & ~mask) | (1 << 5)) self._port.Out32(base + 0x402, val) # Now to make sure the port is in output mode we need to make # sure that bit 5 of the control register is not set val = int(self._port.Inp32(base + 2) & ~np.uint8(1 << 5)) self._port.Out32(base + 2, val) self._set_data = lambda data: self._port.Out32(base, data) self._portname = str(base) else: raise NotImplementedError('Parallel port triggering only ' 'supported on Linux and Windows') else: # mode == 'dummy': self._port = self._portname = None self._trigger_list = list() self._set_data = lambda x: (self._trigger_list.append(x) if x != 0 else None) self.trigger_duration = trigger_duration self.mode = mode def __repr__(self): return '<ParallelTrigger : %s (%s)>' % (self.mode, self._portname) def _stamp_trigger(self, trig): """Fake stamping.""" self._set_data(int(trig)) self.ec.wait_secs(self.trigger_duration) self._set_data(0) def stamp_triggers(self, triggers, delay=None, wait_for_last=True, is_trial_id=False): """Stamp a list of triggers with a given inter-trigger delay. Parameters ---------- triggers : list No input checking is done, so ensure triggers is a list, with each entry an integer with fewer than 8 bits (max 255). delay : float | None The inter-trigger-onset delay (includes "on" time). If None, will use twice the trigger duration (50% duty cycle). wait_for_last : bool If True, wait for last trigger to be stamped before returning. is_trial_id : bool No effect for this trigger controller. """ if delay is None: delay = 2 * self.trigger_duration for ti, trig in enumerate(triggers): self._stamp_trigger(trig) if ti < len(triggers) - 1 or wait_for_last: self.ec.wait_secs(delay - self.trigger_duration) def close(self): """Release hardware interfaces.""" if hasattr(self, '_port'): del self._port def __del__(self): return self.close()