Source code for psychopy.hardware

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import absolute_import, print_function

from builtins import map
from builtins import range
from past.builtins import basestring
import sys
import glob
import collections
from itertools import chain
from psychopy import logging
__all__ = ['forp', 'cedrus', 'minolta', 'pr', 'crs', 'iolab']


def getSerialPorts():
    """Finds the names of all (virtual) serial ports present on the system

    :returns:

    Returns an iterable with all the serial ports.
    """
    if sys.platform == "darwin":
        ports = [
            '/dev/tty.USA*',  # keyspan twin adapter is usually USA28X13P1.1
            '/dev/tty.Key*',  # some are Keyspan.1 or Keyserial.1
            '/dev/tty.modem*',
            '/dev/cu.usbmodem*',  # for PR650
            '/dev/tty.usbserial*',  # for the 'Plugable' converter,
                                    # according to Tushar Chauhan
        ]
    elif sys.platform.startswith("linux"):
        ports = [
            "/dev/ttyACM?",  # USB CDC devices (virtual serial ports)
            "/dev/ttyUSB?",  # USB to serial adapters using the
                             # usb-serial kernel module
            "/dev/ttyS?",   # genuine serial ports usually
                            # /dev/ttyS0 or /dev/ttyS1
        ]
    elif sys.platform == "cygwin":
        # I don't think anyone has actually tried this
        # Cygwin maps the windows serial ports like this
        ports = ["/dev/ttyS?", ]
    elif sys.platform == "win32":
        # While PsychoPy does support using numeric values to specify
        # which serial port to use, it is better in this case to
        # provide a cannoncial name.
        return map("COM{0}".format, range(11))  # COM0-10
    else:
        logging.error("We don't support serial ports on {0} yet!"
                      .format(sys.platform))
        return []

    # This creates an iterator for each glob expression. The glob
    # expressions are then chained together. This is more efficient
    # because it means we don't perform the lookups before we actually
    # need to.
    return chain.from_iterable(map(glob.iglob, ports))


def getAllPhotometers():
    """Gets all available photometers.

    The returned photometers may vary depending on which drivers are
    installed. Standalone PsychoPy ships with libraries for all supported
    photometers.

    :returns:
    A list of all photometer classes
    """
    from . import minolta, pr
    from . import crs

    photometers = [pr.PR650, pr.PR655, minolta.LS100]
    if hasattr(crs, "ColorCAL"):
        photometers.append(crs.ColorCAL)

    return photometers


def getPhotometerByName(name):
    """Gets a Photometer class by name.
    You can use either short names like pr650 or a long name like
    CRS ColorCAL.

    :parameters:
        name : The name of the device

    :returns:
    Returns the photometer matching the passed in device
    name or none if we were unable to find it.

    """
    for photom in getAllPhotometers():
        # longName is used from the GUI and driverFor is for coders
        if name.lower() in photom.driverFor or name == photom.longName:
            return photom


[docs]def findPhotometer(ports=None, device=None): """Try to find a connected photometer/photospectrometer! PsychoPy will sweep a series of serial ports trying to open them. If a port successfully opens then it will try to issue a command to the device. If it responds with one of the expected values then it is assumed to be the appropriate device. :parameters: ports : a list of ports to search Each port can be a string (e.g. 'COM1', ''/dev/tty.Keyspan1.1') or a number (for win32 comports only). If none are provided then PsychoPy will sweep COM0-10 on win32 and search known likely port names on macOS and Linux. device : string giving expected device (e.g. 'PR650', 'PR655', 'LS100', 'LS110'). If this is not given then an attempt will be made to find a device of any type, but this often fails :returns: * An object representing the first photometer found * None if the ports didn't yield a valid response * None if there were not even any valid ports (suggesting a driver not being installed) e.g.:: # sweeps ports 0 to 10 searching for a PR655 photom = findPhotometer(device='PR655') print(photom.getLum()) if hasattr(photom, 'getSpectrum'): # can retrieve spectrum (e.g. a PR650) print(photom.getSpectrum()) """ if isinstance(device, basestring): photometers = [getPhotometerByName(device)] elif isinstance(device, collections.Iterable): # if we find a string assume it is a name, otherwise treat it like a # photometer photometers = [getPhotometerByName(d) if isinstance(d, basestring) else d for d in device] else: photometers = getAllPhotometers() # determine candidate ports if ports is None: ports = getSerialPorts() elif type(ports) in (int, float) or isinstance(ports, basestring): ports = [ports] # so that we can iterate # go through each port in turn photom = None logging.info('scanning serial ports...') logging.flush() for thisPort in ports: logging.info('...{}'.format(thisPort)) logging.flush() for Photometer in photometers: # Looks like we got an invalid photometer, carry on if Photometer is None: continue try: photom = Photometer(port=thisPort) except Exception as ex: msg = "Couldn't initialize photometer {0}: {1}" logging.error(msg.format(Photometer.__name__, ex)) # We threw an exception so we should just skip ahead continue if photom.OK: logging.info(' ...found a %s\n' % (photom.type)) logging.flush() # we're now sure that this is the correct device and that # it's configured now increase the number of attempts made # to communicate for temperamental devices! if hasattr(photom, 'setMaxAttempts'): photom.setMaxAttempts(10) # we found one so stop looking return photom else: if photom.com and photom.com.isOpen: logging.info('closing port') photom.com.close() # If we got here we didn't find one logging.info('...nope!\n\t') logging.flush() return None

Back to top