Beispiel #1
0
    def __init__(self, name, role, ports, **kwargs):
        """
        ports (string): pattern of the name of the serial ports to try to connect to
          find the devices. It can have a "glob", for example: "/dev/ttyUSB*"
        """
        model.Emitter.__init__(self, name, role, **kwargs)
        self._ports = ports
        self._master, self._devices = self._getAvailableDevices(ports)
        if not self._devices:
            raise HwError(
                "No Omicron xX device found for ports '%s', check "
                "that '%s' is turned on and connected to the computer." %
                (ports, name))

        spectra = [
        ]  # list of tuples: 99% low, 25% low, centre, 25% high, 99% high in m
        max_power = []  # list of float (W)
        for d in self._devices:
            spectra.append(d.wavelength)
            max_power.append(d.max_power)

        self._shape = ()

        # power of the whole device (=> max power of the device with max power)
        self.power = model.FloatContinuous(0., (0., max(max_power)), unit="W")
        self.power.subscribe(self._updatePower)

        # ratio of power per device
        # => if some device don't support max power, clamped before 1
        self.emissions = model.ListVA([0.] * len(self._devices),
                                      unit="",
                                      setter=self._setEmissions)
        # info on what device is which wavelength
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)

        # Ensure the whole Hub is turned on
        if self._master:
            try:
                self._master.PowerOn()
            except OXXError:
                raise HwError(
                    "Failed to power on the master device, check the interlock."
                )

        # make sure everything is off (turning on the HUB will turn on the lights)
        self._updateIntensities(self.power.value, self.emissions.value)

        # set SW version
        driver_name = self._devices[0].acc.driver
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__,
                                                      driver_name)
Beispiel #2
0
    def _openDevice(self, sn=None):
        """
        sn (None or str): serial number
        return (0 <= int < 8): device ID
        raises: HwError if the device doesn't exist or cannot be opened
        """
        sn_str = create_string_buffer(8)
        for i in range(MAXDEVNUM):
            try:
                self._dll.PH_OpenDevice(i, sn_str)
            except PHError as ex:
                if ex.errno == -1:  # ERROR_DEVICE_OPEN_FAIL == no device with this idx
                    pass
                else:
                    logging.warning("Failure to open device %d: %s", i, ex)
                continue

            if sn is None or sn_str.value == sn:
                return i
            else:
                logging.info("Skipping device %d, with S/N %s", i,
                             sn_str.value)
        else:
            # TODO: if a PHError happened indicate the error in the message
            raise HwError(
                "No PicoHarp300 found, check the device is turned on and connected to the computer"
            )
Beispiel #3
0
    def __init__(self, name, role, axes, ranges=None, choices=None, **kwargs):
        """
        axes (set of string): names of the axes
        ranges (dict string -> float,float): min/max of the axis
        choices (dict string -> set): alternative to ranges, these are the choices of the axis
        """
        assert len(axes) > 0

        # Special file "stage.fail" => will cause simulation of hardware error
        if os.path.exists("stage.fail"):
            raise HwError("stage.fail file present, simulating error")

        # Transform the input style of the Stage to the style of the GenericComponent
        if ranges is None:
            ranges = {}
        if choices is None:
            choices = {}

        axes_dict = {}
        for a in axes:
            d = {}
            if a in ranges:
                d["range"] = ranges[a]
            elif a in choices:
                d["choices"] = choices[a]
            axes_dict[a] = d

        GenericComponent.__init__(self,
                                  name,
                                  role,
                                  vas=None,
                                  axes=axes_dict,
                                  **kwargs)
Beispiel #4
0
 def _tryRecover(self):
     self._recovering = True
     self.state._set_value(HwError("Connection lost, reconnecting..."),
                           force_write=True)
     # Retry to open the serial port (in case it was unplugged)
     # _ser_access should already be acquired, but since it's an RLock it can be acquired
     # again in the same thread
     try:
         with self._ser_access:
             while True:
                 if self._serial:
                     self._serial.close()
                 self._serial = None
                 try:
                     logging.debug("Searching for the device on port %s",
                                   self._portpattern)
                     min_axis = min(self._axis_map.values())
                     self._port = self._findDevice(self._portpattern,
                                                   min_axis)
                 except IOError:
                     time.sleep(2)
                 except Exception:
                     logging.exception(
                         "Unexpected error while trying to recover device")
                     raise
                 else:
                     # We found it back!
                     break
         # it now should be accessible again
         self.state._set_value(model.ST_RUNNING, force_write=True)
         logging.info("Recovered device on port %s", self._port)
     finally:
         self._recovering = False
Beispiel #5
0
    def _openSerialPort(port):
        """
        Opens the given serial port the right way for a PiezoMotor PMD device.
        port (string): the name of the serial port (e.g., /dev/ttyUSB0)
        return (serial): the opened serial port
        raise HwError: if the serial port cannot be opened (doesn't exist, or
          already opened)
        """
        # For debugging purpose
        if port == "/dev/fake":
            return PMDSimulator(timeout=0.1)

        try:
            ser = serial.Serial(
                port=port,
                baudrate=BAUDRATE,
                bytesize=serial.EIGHTBITS,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                timeout=0.3,  # s
            )
        except IOError:
            raise HwError("Failed to find a PMD controller on port '%s'. "
                          "Check that the device is turned on and "
                          "connected to the computer." % (port, ))

        return ser
Beispiel #6
0
    def __init__(self, name, role, axes, ranges=None, **kwargs):
        """
        axes (set of string): names of the axes
        ranges (dict string -> float,float): min/max of the axis
        """
        assert len(axes) > 0
        if ranges is None:
            ranges = {}

        axes_def = {}
        self._position = {}
        init_speed = {}
        for a in axes:
            rng = ranges.get(a, (-0.1, 0.1))
            axes_def[a] = model.Axis(unit="m", range=rng, speed=(0., 10.))
            # start at the centre
            self._position[a] = (rng[0] + rng[1]) / 2
            init_speed[a] = 1.0  # we are fast!

        model.Actuator.__init__(self, name, role, axes=axes_def, **kwargs)

        # Special file "stage.fail" => will cause simulation of hardware error
        if os.path.exists("stage.fail"):
            raise HwError("stage.fail file present, simulating error")

        self._executor = model.CancellableThreadPoolExecutor(max_workers=1)

        # RO, as to modify it the client must use .moveRel() or .moveAbs()
        self.position = model.VigilantAttribute({}, unit="m", readonly=True)
        self._updatePosition()

        self.speed = model.MultiSpeedVA(init_speed, (0., 10.), "m/s")
Beispiel #7
0
    def _sendCommand(self, cmd):
        """
        cmd (str): command to be sent to Power Control unit.
        returns (str): answer received from the Power Control unit
        raises:
            IOError: if an ERROR is returned by the Power Control firmware.
        """
        cmd = (cmd + "\n").encode('latin1')
        with self._ser_access:
            logging.debug("Sending command %s" % to_str_escape(cmd))
            self._serial.write(cmd)

            ans = b''
            char = None
            while char != b'\n':
                char = self._serial.read()
                if not char:
                    logging.error("Timeout after receiving %s",
                                  to_str_escape(ans))
                    # TODO: See how you should handle a timeout before you raise
                    # an HWError
                    raise HwError(
                        "Power Control Unit connection timeout. "
                        "Please turn off and on the power to the box.")
                # Handle ERROR coming from Power control unit firmware
                ans += char

            logging.debug("Received answer %s", to_str_escape(ans))
            ans = ans.decode('latin1')
            if ans.startswith("ERROR"):
                raise PowerControlError(ans.split(' ', 1)[1])

            return ans.rstrip()
Beispiel #8
0
    def __init__(self, name, role, axes, ranges=None, **kwargs):
        """
        axes (set of string): names of the axes
        """
        assert len(axes) > 0
        if ranges is None:
            ranges = {}

        axes_def = {}
        self._position = {}
        init_speed = {}
        for a in axes:
            rng = ranges.get(a, [-0.1, 0.1])
            axes_def[a] = model.Axis(unit="m", range=rng, speed=[0., 10.])
            # start at the centre
            self._position[a] = (rng[0] + rng[1]) / 2
            init_speed[a] = 10.0  # we are super fast!

        model.Actuator.__init__(self, name, role, axes=axes_def, **kwargs)

        # Special file "stage.fail" => will cause simulation of hardware error
        if os.path.exists("stage.fail"):
            raise HwError("stage.fail file present, simulating error")

        # RO, as to modify it the client must use .moveRel() or .moveAbs()
        self.position = model.VigilantAttribute(self._applyInversionAbs(
            self._position),
                                                unit="m",
                                                readonly=True)

        self.speed = model.MultiSpeedVA(init_speed, [0., 10.], "m/s")
Beispiel #9
0
    def _findDevice(self, port=None, serialnum=None):
        """
        Look for a compatible device. Requires at least one of the arguments port and serialnum.
        port (str): port (e.g. "/dev/ttyUSB0") or pattern for port ("/dev/ttyUSB*"), "/dev/fake" will start the simulator
        serialnum (str): serial number
        return (str): the name of the port used
        raises:
            HwError: if no device on the ports with the given serial number is found
        """
        # At least one of the arguments port and serialnum must be specified
        if not port and not serialnum:
            raise ValueError(
                "At least one of the arguments 'port' and 'serialnum' must be specified."
            )

        # For debugging purpose
        if port == "/dev/fake":
            return port

        # If no ports specified, check all available ports
        if port:
            names = list(serial.tools.list_ports.grep(port))
        else:
            names = serial.tools.list_ports.comports(
            )  # search all serial ports

        # Look for serial number if available, otherwise make sure only one port matches the port pattern.
        if serialnum:
            for port in names:
                if serialnum in port.serial_number:
                    return port.device  # Found it!
            else:
                raise HwError(
                    "Beam controller device with serial number %s not found for port %s. "
                    % (serialnum, names) + "Check the connection.")
        else:
            if len(names) == 1:
                port = names[0]
                return port.device
            elif len(names) > 1:
                raise HwError(
                    "Multiple ports detected for beam controller. Please specify a serial number."
                )
            else:
                raise HwError(
                    "Beam controller device not found for port %s. Check the connection."
                    % port)
Beispiel #10
0
    def _findDevice(self, ports, baudrate=19200):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        baudrate (0<int)
        return:
           (str): the name of the port used
           (str): the hardware version string
           Note: will also update ._file and ._serial
        raises:
            IOError: if no devices are found
        """
        # For debugging purpose
        if ports == "/dev/fake":
            self._serial = ESPSimulator(timeout=1)
            self._file = None
            ve = self.GetVersion()
            return ports, ve

        if os.name == "nt":
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                # Ensure no one will talk to it simultaneously, and we don't talk to devices already in use
                self._file = open(n)  # Open in RO, just to check for lock
                try:
                    fcntl.flock(
                        self._file.fileno(), fcntl.LOCK_EX
                        | fcntl.LOCK_NB)  # Raises IOError if cannot lock
                except IOError:
                    logging.info("Port %s is busy, will not use", n)
                    continue

                self._serial = self._openSerialPort(n, baudrate)

                try:
                    ve = self.GetVersion()
                except ESPError as e:
                    # Can happen if the device has received some weird characters
                    # => try again (now that it's flushed)
                    logging.info(
                        "Device answered by an error %s, will try again", e)
                    ve = self.GetVersion()
                return n, ve
            except (IOError, ESPError) as e:
                logging.debug(e)
                logging.info(
                    "Skipping device on port %s, which didn't seem to be compatible",
                    n)
                # not possible to use this port? next one!
                continue
        else:
            raise HwError(
                "Failed to find a device on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #11
0
    def _sendCommand(self, cmd):
        """
        cmd (byte str): command to be sent to PMT Control unit.
        returns (byte str): answer received from the PMT Control unit
        raises:
            PMTControlError: if an ERROR is returned by the PMT Control firmware.
            HwError: in case the of connection timeout
        """
        cmd = cmd + b"\n"
        with self._ser_access:
            logging.debug("Sending command %s", to_str_escape(cmd))
            try:
                self._serial.write(cmd)
            except IOError:
                logging.warn("Failed to send command to PMT Control firmware, "
                             "trying to reconnect.")
                if self._recovering:
                    raise
                else:
                    self._tryRecover()
                    # send command again
                    logging.debug(
                        "Sending command %s again after auto-reconnect" %
                        to_str_escape(cmd))
                    return self._sendCommand(cmd[:-1])  # cmd without \n

            ans = b''
            char = None
            while char != b'\n':
                try:
                    char = self._serial.read()
                except IOError:
                    logging.warn("Failed to read from PMT Control firmware, "
                                 "trying to reconnect.")
                    if self._recovering:
                        raise
                    else:
                        self._tryRecover()
                        # don't send command again
                        raise IOError(
                            "Failed to read from PMT Control firmware, "
                            "restarted serial connection.")

                if not char:
                    logging.error("Timeout after receiving %s",
                                  to_str_escape(ans))
                    # TODO: See how you should handle a timeout before you raise
                    # an HWError
                    raise HwError(
                        "PMT Control Unit connection timeout. "
                        "Please turn off and on the power to the box.")
                # Handle ERROR coming from PMT control unit firmware
                ans += char

            logging.debug("Received answer %s", to_str_escape(ans))
            if ans.startswith(b"ERROR"):
                raise PMTControlError(ans.split(b' ', 1)[1])

            return ans.rstrip()
Beispiel #12
0
    def _openConnection(cls, address, sn=None):
        """
        return (Accesser)
        """
        if address == "fake":
            host, port = "fake", 23
        elif address == "autoip":
            conts = cls._scanOverIP()
            if sn is not None:
                for hn, host, port in conts:
                    # Open connection to each controller and ask for their SN
                    dev = cls(None,
                              None,
                              address=host,
                              axes=["a"],
                              stepsize=[1e-6])
                    _, _, devsn = dev.GetIdentification()
                    if sn == devsn:
                        break
                else:
                    raise HwError(
                        "Failed to find New Focus controller %s over the "
                        "network. Ensure it is turned on and connected to "
                        "the network." % (sn, ))
            else:
                # just pick the first one
                # TODO: only pick the ones of model 8742
                try:
                    hn, host, port = conts[0]
                    logging.info("Connecting to New Focus %s", hn)
                except IndexError:
                    raise HwError(
                        "Failed to find New Focus controller over the "
                        "network. Ensure it is turned on and connected to "
                        "the network.")

        else:
            # split the (IP) port, separated by a :
            if ":" in address:
                host, ipport_str = address.split(":")
                port = int(ipport_str)
            else:
                host = address
                port = 23  # default

        return IPAccesser(host, port)
Beispiel #13
0
    def __init__(self,
                 name,
                 role,
                 sn=None,
                 max_power=0.1,
                 inversed=False,
                 **kwargs):
        """
        sn (None or str): serial number.
           If None, it will pick the first device found.
        max_power (0<float): maxium power emitted in W.
        """
        model.Emitter.__init__(self, name, role, **kwargs)

        self._sn = sn
        self._max_power = max_power

        # Just find the first BlinkStick led controller
        if sn is None:
            self._bstick = blinkstick.find_first()
        else:
            # Note: doesn't work with v1.1.7:
            # need fix on get_string(), reported here: https://github.com/arvydas/blinkstick-python/pull/35
            logging.warning(
                "Using sn to select the device doesn't currently work")
            self._bstick = blinkstick.find_by_serial(sn)
        if self._bstick is None:
            raise HwError(
                "Failed to find a Blinkstick for component %s. "
                "Check that the device is connected to the computer." %
                (name, ))

        self._bstick.set_inverse(inversed)
        time.sleep(0.1)  # Device apparently needs some time to recover

        self._shape = ()
        # list of 5-tuples of floats
        self.spectra = model.ListVA([(380e-9, 390e-9, 560e-9, 730e-9, 740e-9)],
                                    unit="m",
                                    readonly=True)

        self.power = model.ListContinuous([
            0.,
        ], ((0., ), (max_power, )),
                                          unit="W",
                                          cls=(int, long, float),
                                          setter=self._setPower)
        self.power.subscribe(self._updatePower, init=True)

        self._swVersion = "Blinkstick v%s" % (blinkstick.__version__, )
        # These functions report wrong values on Linux with v1.1.7
        #         man = self._bstick.get_manufacturer()
        #         desc = self._bstick.get_description()
        #         rsn = self._bstick.get_serial()
        man = self._bstick.device.manufacturer
        desc = self._bstick.device.product
        rsn = self._bstick.device.serial_number
        self._hwVersion = "%s %s (s/n: %s)" % (man, desc, rsn)
Beispiel #14
0
    def _findDevice(self, ports, baudrate=9600):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        baudrate (0<int)
        midn (str or None): regex to match the *IDN answer
        return:
           (str): the name of the port used
           (str): the identification string
           Note: will also update ._file and ._serial
        raises:
            IOError: if no device are found
        """
        # For debugging purpose
        if ports == "/dev/fake":
            self._serial = RemconSimulator(timeout=1)
            self._file = None
            idn = self.GetVersion()
            return ports, idn

        if os.name == "nt":
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)
        for n in names:
            try:
                # Ensure no one will talk to it simultaneously, and we don't talk to devices already in use
                self._file = open(n)  # Open in RO, just to check for lock
                try:
                    fcntl.flock(
                        self._file.fileno(), fcntl.LOCK_EX
                        | fcntl.LOCK_NB)  # Raises IOError if cannot lock
                except IOError:
                    logging.info("Port %s is busy, will not use", n)
                    continue

                self._serial = self._openSerialPort(n, baudrate)
                try:
                    self.NullCommand(
                    )  # stop if it's not the right hardware before disturbing it
                    idn = self.GetVersion()
                except RemconError:
                    # Can happen if the device has received some weird characters
                    # => try again (now that it's flushed)
                    logging.info("Device answered by an error, will try again")
                    idn = self.GetVersion()
                return n, idn
            except (IOError, RemconError):
                logging.info(
                    "Skipping device on port %s, which didn't seem to be compatible",
                    n)
                # not possible to use this port? next one!
                continue
        else:
            raise HwError(
                "Check that Remcon32 is running, and check the connection "
                "to the SEM PC. No Zeiss SEM found on ports %s" % (ports, ))
Beispiel #15
0
    def _findDevice(self, ports):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        baudrate (0<int)
        return:
           (str): the name of the port used
           Note: will also update ._file and ._serial
        raises:
            IOError: if no devices are found
        """
        # For debugging purpose
        if ports == "/dev/fake":
            self._serial = LakeshoreSimulator(timeout=1)
            self._file = None
            return ports

        if os.name == "nt":
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                # Ensure no one will talk to it simultaneously, and we don't talk to devices already in use
                self._file = open(n)  # Open in RO, just to check for lock
                try:
                    fcntl.flock(
                        self._file.fileno(), fcntl.LOCK_EX
                        | fcntl.LOCK_NB)  # Raises IOError if cannot lock
                except IOError:
                    logging.info("Port %s is busy, will not use", n)
                    continue

                self._serial = self._openSerialPort(n)
                manufacturer, md, _, _ = self.GetIdentifier(
                )  # if value is incorrect, will throw an exception wile unpacking

                if manufacturer != "LSCI":
                    raise IOError("Invalid device manufacturer")
                if md != "MODEL335":
                    raise IOError("The model is %s, not MODEL335." % (md, ))
                return n

            except (IOError, LakeshoreError) as e:
                logging.debug(e)
                logging.info(
                    "Skipping device on port %s, which didn't seem to be compatible",
                    n)
                # not possible to use this port? next one!
                continue
        else:
            raise HwError(
                "Failed to find a device on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #16
0
    def _findDevice(self, ports):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        return (str): the name of the port used
        It also sets ._serial and ._idn to contain the opened serial port, and
        the identification string.
        raises:
            IOError: if no device are found
        """
        # For debugging purpose
        if ports == "/dev/fake":
            self._serial = PowerControlSimulator(timeout=1)
            return ports

        if os.name == "nt":
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                self._file = open(n)  # Open in RO, just to check for lock
                try:
                    fcntl.flock(self._file.fileno(),
                                fcntl.LOCK_EX | fcntl.LOCK_NB)
                except IOError:
                    logging.info("Port %s is busy, will wait and retry", n)
                    time.sleep(11)
                    fcntl.flock(self._file.fileno(),
                                fcntl.LOCK_EX | fcntl.LOCK_NB)
                self._serial = self._openSerialPort(n)

                try:
                    idn = self._getIdentification()
                except PowerControlError:
                    # Can happen if the device has received some weird characters
                    # => try again (now that it's flushed)
                    logging.info("Device answered by an error, will try again")
                    idn = self._getIdentification()
                # Check that we connect to the right device
                if not idn.startswith("Delmic Analog Power"):
                    logging.info("Connected to wrong device on %s, skipping.",
                                 n)
                    continue
                return n
            except (IOError, PowerControlError):
                # not possible to use this port? next one!
                logging.debug(
                    "Skipping port %s which doesn't seem the right device", n)
                continue
        else:
            raise HwError(
                "Failed to find a Power Control device on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #17
0
 def _getIdentities(self):
     """
     Return the ids of connected EEPROMs
     """
     try:
         ans = self._sendCommand("SID")
     except PowerControlError as e:
         # means there is no power provided
         raise HwError("There is no power provided to the Power Control Unit. "
                       "Please make sure the board is turned on.")
     return [x for x in ans.split(',') if x != '']
Beispiel #18
0
    def _findDevice(self, ports):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        return (str): the name of the port used
        It also sets ._serial and ._idn to contain the opened serial port, and
        the identification string.
        raises:
            IOError: if no device are found
        """
        # TODO: For debugging purpose
        #         if ports == "/dev/fake":
        #             self._serial = DPSSSimulator(timeout=1)
        #             return ports

        if os.name == "nt":
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                # Ensure no one will talk to it simultaneously, and we don't talk to devices already in use
                self._file = open(n)  # Open in RO, just to check for lock
                try:
                    fcntl.flock(
                        self._file.fileno(), fcntl.LOCK_EX
                        | fcntl.LOCK_NB)  # Raises IOError if cannot lock
                except IOError:
                    logging.info("Port %s is busy, will not use", n)
                    continue

                self._serial = self._openSerialPort(n)

                try:
                    sn = self.GetSerialNumber()
                except DPSSError:
                    # Can happen if the device has received some weird characters
                    # => try again (now that it's flushed)
                    logging.info("Device answered by an error, will try again")
                    sn = self.GetSerialNumber()
                return n
            except (IOError, DPSSError):
                logging.info(
                    "Skipping device on port %s, which didn't seem to be a Cobolt",
                    n)
                # not possible to use this port? next one!
                continue
        else:
            raise HwError(
                "Failed to find a Cobolt device on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #19
0
    def _findDevice(cls, ports):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        return serial, port:
            serial: serial port found, and open
            port (str): the name of the port used
        raises:
            IOError: if no device are found
        """
        # We are called very early, so no attribute is to be expected
        if os.name == "nt":
            # TODO
            #ports = ["COM" + str(n) for n in range (15)]
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                ser = cls.openSerialPort(n)
                dev = LLE(None, None, port=None, sources=None, _serial=ser)
            except serial.SerialException:
                # not possible to use this port? next one!
                continue

            # Try to connect and get back some answer.
            # The LLE only answers back for the temperature
            try:
                for i in range(3):  # 3 times in a row good answer?
                    temp = dev.GetTemperature()
                    # avoid 0 and 255 (= only 000's or 1111's), which is bad sign
                    if not (0 < temp < 250):
                        raise IOError()
            except Exception:
                logging.debug(
                    "Port %s doesn't seem to have a LLE device connected", n)
                continue
            return ser, n  # found it!
        else:
            raise HwError(
                "Failed to find a Lumencor Light Engine on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #20
0
    def _findDevice(self, ports):
        """
        Look for a compatible device
        ports (str): pattern for the port name
        return (str): the name of the port used
        It also sets ._serial and ._idn to contain the opened serial port, and
        the identification string.
        raises:
            IOError: if no device are found
        """
        if os.name == "nt":
            # TODO
            # ports = ["COM" + str(n) for n in range(15)]
            raise NotImplementedError("Windows not supported")
        else:
            names = glob.glob(ports)

        for n in names:
            try:
                self._serial = self._openSerialPort(n)
            except serial.SerialException:
                # not possible to use this port? next one!
                continue

            # check whether it looks like a FW102C
            try:
                # If any garbage was previously received, make it discarded.
                self._serial.write("\r")
                # can have some \x00 bytes at the beginning + "CMD_NOT_DEFINED"
                self._flushInput()
                idn = self.GetIdentification()
                if re.match(self.re_idn, idn):
                    self._idn = idn
                    return n  # found it!
            except Exception:
                logging.debug(
                    "Port %s doesn't seem to have a FW102C device connected",
                    n)
        else:
            raise HwError(
                "Failed to find a filter wheel FW102C on ports '%s'. "
                "Check that the device is turned on and connected to "
                "the computer." % (ports, ))
Beispiel #21
0
    def __init__(self, name, role, children, address, daemon=None, **kwargs):
        """
        Parameters
        ----------
        address: str
            server address and port of the Microscope server, e.g. "PYRO:Microscope@localhost:4242"
        timeout: float
            Time in seconds the client should wait for a response from the server.
        """

        model.HwComponent.__init__(self, name, role, daemon=daemon, **kwargs)
        self._proxy_access = threading.Lock()
        try:
            self.server = Proxy(address)
            self.server._pyroTimeout = 30  # seconds
            self._swVersion = self.server.get_software_version()
            self._hwVersion = self.server.get_hardware_version()
        except Exception as err:
            raise HwError(
                "Failed to connect to XT server '%s'. Check that the "
                "uri is correct and XT server is"
                " connected to the network. %s" % (address, err))

        # create the scanner child
        try:
            kwargs = children["scanner"]
        except (KeyError, TypeError):
            raise KeyError("SEM was not given a 'scanner' child")
        self._scanner = Scanner(parent=self, daemon=daemon, **kwargs)
        self.children.value.add(self._scanner)

        # create the stage child, if requested
        if "stage" in children:
            ckwargs = children["stage"]
            self._stage = Stage(parent=self, daemon=daemon, **ckwargs)
            self.children.value.add(self._stage)

        # create a focuser, if requested
        if "focus" in children:
            ckwargs = children["focus"]
            self._focus = Focus(parent=self, daemon=daemon, **ckwargs)
            self.children.value.add(self._focus)
Beispiel #22
0
    def _getSerialPort(self, sn):
        """
        sn (str): serial number of the device
        return (str): serial port name (eg: "/dev/ttyUSB0" on Linux)
        """
        if sys.platform.startswith('linux'):
            # Look for each USB device, if the serial number is good
            sn_paths = glob.glob('/sys/bus/usb/devices/*/serial')
            for p in sn_paths:
                try:
                    f = open(p)
                    snp = f.read().strip()
                except IOError:
                    logging.debug("Failed to read %s, skipping device", p)
                if snp == sn:
                    break
            else:
                # There is a known problem with the APT devices that prevent
                # them from connecting to USB if they are connected via a hub
                # and powered on before the host PC.
                raise HwError("No USB device with S/N %s. "
                              "Check that the Thorlabs filter flipper was "
                              "turned on *after* the host computer." % sn)

            # Deduce the tty:
            # .../3-1.2/serial => .../3-1.2/3-1.2:1.0/ttyUSB1
            sys_path = os.path.dirname(p)
            usb_num = os.path.basename(sys_path)
            tty_paths = glob.glob("%s/%s/ttyUSB?*" %
                                  (sys_path, usb_num + ":1.0"))
            if not tty_paths:
                raise ValueError("Failed to find tty for device with S/N %s" %
                                 sn)
            tty = os.path.basename(tty_paths[0])

            # Convert to /dev
            # Note: that works because udev rules create a dev with the same name
            # otherwise, we would need to check the char numbers
            return "/dev/%s" % (tty, )
        else:
            # TODO: Windows version
            raise NotImplementedError("OS not yet supported")
Beispiel #23
0
    def __init__(self, name, role, ports, **kwargs):
        """
        ports (string): pattern of the name of the serial ports to try to connect to
          find the devices. It can have a "glob", for example: "/dev/ttyUSB*"
        """
        model.Emitter.__init__(self, name, role, **kwargs)
        self._ports = ports
        self._devices = self._getAvailableDevices(ports)
        if not self._devices:
            raise HwError("No Omicron xX device found for ports '%s', check "
                          "they are turned on and connected to the computer."
                          % ports)
        
        spectra = [] # list of tuples: 99% low, 25% low, centre, 25% high, 99% high in m
        max_power = [] # list of float (W)
        for d in self._devices:
            wl = d.wavelength
            # Lasers => spectrum is almost just one wl, but make it 2 nm wide
            # to avoid a bandwidth of exactly 0.
            spectra.append((wl - 1e-9, wl - 0.5e-9, wl, wl + 0.5e-9, wl + 1e-9))
            max_power.append(d.max_power)

        self._shape = ()

        # power of the whole device (=> max power of the device with max power)
        self.power = model.FloatContinuous(0., (0., max(max_power)), unit="W")
        self.power.subscribe(self._updatePower)

        # ratio of power per device
        # => if some device don't support max power, clamped before 1
        self.emissions = model.ListVA([0.] * len(self._devices), unit="", 
                                      setter=self._setEmissions)
        # info on what device is which wavelength 
        self.spectra = model.ListVA(spectra, unit="m", readonly=True)
        
        # make sure everything is off
        self._updateIntensities(self.power.value, self.emissions.value)
        
        # set HW and SW version
        driver_name = driver.getSerialDriver(self._devices[0].port)
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__, driver_name)
        self._hwVersion = "Omicron xX" # TODO: get version from GetFirmware()
Beispiel #24
0
    def __init__(self,
                 name,
                 role,
                 port,
                 turret=None,
                 calib=None,
                 _noinit=False,
                 dependencies=None,
                 **kwargs):
        """
        port (string): name of the serial port to connect to.
        turret (None or 1<=int<=3): turret number set-up. If None, consider that
          the current turret known by the device is correct.
        calib (None or list of (int, int and 5 x (float or str))):
          calibration data, as saved by Winspec. Data can be either in float
          or as an hexadecimal value "hex:9a,99,99,99,99,79,40,40"
           blaze in nm, groove gl/mm, center adjust, slope adjust,
           focal length, inclusion angle, detector angle
        inverted (None): it is not allowed to invert the axes
        dependencies (dict str -> Component): "ccd" should be the CCD used to acquire
         the spectrum.
        _noinit (boolean): for internal use only, don't try to initialise the device
        """
        if kwargs.get("inverted", None):
            raise ValueError("Axis of spectrograph cannot be inverted")

        # start with this opening the port: if it fails, we are done
        try:
            self._serial = self.openSerialPort(port)
        except serial.SerialException:
            raise HwError(
                "Failed to find spectrograph %s (on port '%s'). "
                "Check the device is turned on and connected to the "
                "computer. You might need to turn it off and on again." %
                (name, port))
        self._port = port

        # to acquire before sending anything on the serial port
        self._ser_access = threading.Lock()

        self._try_recover = False
        if _noinit:
            return

        self._initDevice()
        self._try_recover = True

        try:
            self._ccd = dependencies["ccd"]
        except (TypeError, KeyError):
            # TODO: only needed if there is calibration info (for the pixel size)
            # otherwise it's fine without CCD.
            raise ValueError("Spectrograph needs a dependency 'ccd'")

        # according to the model determine how many gratings per turret
        model_name = self.GetModel()
        self.max_gratings = MAX_GRATINGS_NUM.get(model_name, 3)

        if turret is not None:
            if turret < 1 or turret > self.max_gratings:
                raise ValueError(
                    "Turret number given is %s, while expected a value between 1 and %d"
                    % (turret, self.max_gratings))
            self.SetTurret(turret)
            self._turret = turret
        else:
            self._turret = self.GetTurret()

        # for now, it's fixed (and it's unlikely to be useful to allow less than the max)
        max_speed = 1000e-9 / 10  # about 1000 nm takes 10s => max speed in m/s
        self.speed = model.MultiSpeedVA(max_speed,
                                        range=[max_speed, max_speed],
                                        unit="m/s",
                                        readonly=True)

        gchoices = self.GetGratingChoices()
        # remove the choices which are not valid for the current turret
        for c in gchoices:
            t = 1 + (c - 1) // self.max_gratings
            if t != self._turret:
                del gchoices[c]

        # TODO: report the grating with its wavelength range (possible to compute from groove density + blaze wl?)
        # range also depends on the max grating angle (40°, CCD pixel size, CCD horizontal size, focal length,+ efficienty curve?)
        # cf http://www.roperscientific.de/gratingcalcmaster.html

        # TODO: a more precise way to find the maximum wavelength (looking at the available gratings?)
        # TODO: what's the min? 200nm seems the actual min working, although wavelength is set to 0 by default !?
        axes = {
            "wavelength":
            model.Axis(unit="m",
                       range=(0, 2400e-9),
                       speed=(max_speed, max_speed)),
            "grating":
            model.Axis(choices=gchoices)
        }
        # provides a ._axes
        model.Actuator.__init__(self,
                                name,
                                role,
                                axes=axes,
                                dependencies=dependencies,
                                **kwargs)

        # First step of parsing calib parmeter: convert to (int, int) -> ...
        calib = calib or ()
        if not isinstance(calib, collections.Iterable):
            raise ValueError("calib parameter must be in the format "
                             "[blz, gl, ca, sa, fl, ia, da], "
                             "but got %s" % (calib, ))
        dcalib = {}
        for c in calib:
            if not isinstance(c, collections.Iterable) or len(c) != 7:
                raise ValueError("calib parameter must be in the format "
                                 "[blz, gl, ca, sa, fl, ia, da], "
                                 "but got %s" % (c, ))
            gt = (c[0], c[1])
            if gt in dcalib:
                raise ValueError(
                    "calib parameter contains twice calibration for "
                    "grating (%d nm, %d gl/mm)" % gt)
            dcalib[gt] = c[2:]

        # store calibration for pixel -> wavelength conversion and wavelength offset
        # int (grating number 1 -> 9) -> center adjust, slope adjust,
        #     focal length, inclusion angle/2, detector angle
        self._calib = {}
        # TODO: read the info from MONO-EESTATUS (but it's so
        # huge that it's not fun to parse). There is also detector angle.
        dfl = FOCAL_LENGTH_OFFICIAL[model_name]  # m
        dia = math.radians(INCLUSION_ANGLE_OFFICIAL[model_name])  # rad
        for i in gchoices:
            # put default values
            self._calib[i] = (0, 0, dfl, dia, 0)
            try:
                blz = self._getBlaze(i)  # m
                gl = self._getGrooveDensity(i)  # gl/m
            except ValueError:
                logging.warning("Failed to parse info of grating %d" % i,
                                exc_info=True)
                continue

            # parse calib info
            gt = (int(blz * 1e9), int(gl * 1e-3))
            if gt in dcalib:
                calgt = dcalib[gt]
                ca = self._readCalibVal(calgt[0])  # ratio
                sa = self._readCalibVal(calgt[1])  # ratio
                fl = self._readCalibVal(calgt[2]) * 1e-3  # mm -> m
                ia = math.radians(self._readCalibVal(calgt[3]))  # ° -> rad
                da = math.radians(self._readCalibVal(calgt[4]))  # ° -> rad
                self._calib[i] = ca, sa, fl, ia, da
                logging.info(
                    "Calibration data for grating %d (%d nm, %d gl/mm) "
                    "-> %s" % (i, gt[0], gt[1], self._calib[i]))
            else:
                logging.warning("No calibration data for grating %d "
                                "(%d nm, %d gl/mm)" % (i, gt[0], gt[1]))

        # set HW and SW version
        self._swVersion = "%s (serial driver: %s)" % (
            odemis.__version__, driver.getSerialDriver(port))
        self._hwVersion = "%s (s/n: %s)" % (model_name,
                                            (self.GetSerialNumber()
                                             or "Unknown"))

        # will take care of executing axis move asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time

        # for storing the latest calibrated wavelength value
        self._wl = (None, None, None
                    )  # grating id, raw center wl, calibrated center wl
        # RO, as to modify it the client must use .moveRel() or .moveAbs()
        self.position = model.VigilantAttribute({}, unit="m", readonly=True)
        self._updatePosition()
Beispiel #25
0
    def __init__(self, name, role, address, axes, stepsize, sn=None, **kwargs):
        """
        address (str): ip address (use "autoip" to automatically scan and find the
          controller, "fake" for a simulator)
        axes (list of str): names of the axes, from the 1st to the 4th, if present.
          if an axis is not connected, put a "".
        stepsize (list of float): size of a step in m (the smaller, the
          bigger will be a move for a given distance in m)
        sn (str or None): serial number of the device (eg, "11500"). If None, the
          driver will use whichever controller is first found.
        inverted (set of str): names of the axes which are inverted (IOW, either
         empty or the name of the axis)
        """
        if not 1 <= len(axes) <= 4:
            raise ValueError(
                "Axes must be a list of 1 to 4 axis names (got %s)" % (axes, ))
        if len(axes) != len(stepsize):
            raise ValueError("Expecting %d stepsize (got %s)" %
                             (len(axes), stepsize))
        self._name_to_axis = {}  # str -> int: name -> axis number
        for i, n in enumerate(axes):
            if n == "":  # skip this non-connected axis
                continue
            self._name_to_axis[n] = i + 1

        for sz in stepsize:
            if sz > 10e-3:  # sz is typically ~1µm, so > 1 cm is very fishy
                raise ValueError("stepsize should be in meter, but got %g" %
                                 (sz, ))
        self._stepsize = stepsize

        self._address = address
        self._sn = sn
        self._accesser = self._openConnection(address, sn)
        self._recover = False

        self._resynchonise()

        if name is None and role is None:  # For scan only
            return

        # Seems to really be the device, so handle connection errors fully
        self._recover = True

        modl, fw, sn = self.GetIdentification()
        if modl != "8742":
            logging.warning("Controller %s is not supported, will try anyway",
                            modl)

        # will take care of executing axis move asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time

        # Let the controller check the actuators are connected
        self.MotorCheck()

        axes_def = {}
        speed = {}
        for n, i in self._name_to_axis.items():
            sz = self._stepsize[i - 1]
            # TODO: allow to pass the range in m in the arguments
            # Position supports ±2³¹, probably not that much in reality, but
            # there is no info.
            rng = [(-2**31) * sz, (2**31 - 1) * sz]

            # Check the actuator is connected
            mt = self.GetMotorType(i)
            if mt in {MT_NONE, MT_UNKNOWN}:
                raise HwError(
                    "Controller failed to detect motor %d, check the "
                    "actuator is connected to the controller" % (i, ))
            max_stp_s = {MT_STANDARD: 2000, MT_TINY: 1750}[mt]
            srng = (0, self._speedToMS(i, max_stp_s))
            speed[n] = self._speedToMS(i, self.GetVelocity(i))

            axes_def[n] = model.Axis(range=rng, speed=srng, unit="m")

        model.Actuator.__init__(self, name, role, axes=axes_def, **kwargs)

        self._swVersion = "%s (IP connection)" % (odemis.__version__, )
        self._hwVersion = "New Focus %s (firmware %s, S/N %s)" % (modl, fw, sn)

        # Note that the "0" position is just the position at which the
        # controller turned on
        self.position = model.VigilantAttribute({}, unit="m", readonly=True)
        self._updatePosition()

        max_speed = max(a.speed[1] for a in axes_def.values())
        self.speed = model.MultiSpeedVA(speed,
                                        range=(0, max_speed),
                                        unit="m/s",
                                        setter=self._setSpeed)
Beispiel #26
0
    def __init__(self,
                 name,
                 role,
                 port,
                 pin_map=None,
                 delay=None,
                 init=None,
                 ids=None,
                 termination=None,
                 **kwargs):
        '''
        port (str): port name
        pin_map (dict of str -> int): names of the components
          and the pin where the component is connected.
        delay (dict str -> float): time to wait for each component after it is
            turned on.
        init (dict str -> boolean): turn on/off the corresponding component upon
            initialization.
        ids (list str): EEPROM ids expected to be detected during initialization.
        termination (dict str -> bool/None): indicate for every component
            if it should be turned off on termination (False), turned on (True)
            or left as-is (None).
        Raise an exception if the device cannot be opened
        '''
        if pin_map:
            self.powered = list(pin_map.keys())
        else:
            self.powered = []
        model.PowerSupplier.__init__(self, name, role, **kwargs)

        # TODO: catch errors and convert to HwError
        self._ser_access = threading.Lock()

        self._file = None
        self._port = self._findDevice(port)  # sets ._serial and ._file
        logging.info("Found Power Control device on port %s", self._port)

        # Get identification of the Power control device
        self._idn = self._getIdentification()

        driver_name = driver.getSerialDriver(self._port)
        self._swVersion = "serial driver: %s" % (driver_name, )
        self._hwVersion = "%s" % (self._idn, )

        pin_map = pin_map or {}
        self._pin_map = pin_map

        delay = delay or {}
        # fill the missing pairs with 0 values
        self._delay = dict.fromkeys(pin_map, 0)
        self._delay.update(delay)
        self._last_start = dict.fromkeys(self._delay, time.time())

        # only keep components that should be changed on termination
        termination = termination or {}
        self._termination = {
            k: v
            for k, v in termination.items() if v is not None
        }
        for comp in self._termination:
            if comp not in pin_map:
                raise ValueError(
                    "Component %s in termination not found in pin_map." % comp)

        # will take care of executing switch asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time

        self._supplied = {}
        self.supplied = model.VigilantAttribute(self._supplied, readonly=True)
        self._updateSupplied()

        init = init or {}
        # Remove all None's from the dict, so it can be passed as-is to _doSupply()
        init = {k: v for k, v in init.items() if v is not None}
        for comp in init:
            if comp not in pin_map:
                raise ValueError("Component %s in init not found in pin_map." %
                                 comp)
        try:
            self._doSupply(init, apply_delay=False)
        except IOError as ex:
            # This is in particular to handle some cases where the device resets
            # when turning on the power. One or more trials and the
            logging.exception("Failure during turning on initial power.")
            raise HwError(
                "Device error when initialising power: %s. "
                "Try again or contact support if the problem persists." %
                (ex, ))

        self.memoryIDs = model.VigilantAttribute(None,
                                                 readonly=True,
                                                 getter=self._getIdentities)

        if ids:
            mem_ids = self.memoryIDs.value
            for eid in ids:
                if eid not in mem_ids:
                    raise HwError("EEPROM id %s was not detected. Make sure "
                                  "all EEPROM components are connected." %
                                  (eid, ))
Beispiel #27
0
 def __init__(self, errcode):
     if errcode in ERRCODE:
         text = "Error %d: %s" % (errcode, ERRCODE[errcode])
     else:
         text = "Unknown error code."
     HwError.__init__(self, text)
Beispiel #28
0
    def __init__(self, name, role, dependencies, settle_time=0, **kwargs):
        '''
        dependencies (dict string->model.HwComponent): the dependencies
            There must a dependency "detector" and at least one of the dependencies
            "pmt-control" and "pmt-signal".
            "pmt-control" takes a PMTControl or spectrograph object, "pmt-signal" an
            extra detector to be activated during the acquisition
            (eg, for shutter control)
        settle_time (0 < float): time to wait after turning on the gain to have
          it fully working.
        Raise an ValueError exception if the dependencies are not compatible
        '''
        # we will fill the set of dependencies with Components later in ._dependencies
        model.Detector.__init__(self,
                                name,
                                role,
                                dependencies=dependencies,
                                **kwargs)

        if settle_time < 0:
            raise ValueError("Settle time of %g s for '%s' is negative" %
                             (settle_time, name))
        elif settle_time > 10:
            # a large value is a sign that the user mistook in units
            raise ValueError("Settle time of %g s for '%s' is too long" %
                             (settle_time, name))
        self._settle_time = settle_time

        # Check the dependencies
        pmt = dependencies["detector"]
        if not isinstance(pmt, ComponentBase):
            raise ValueError("Dependency detector is not a component.")
        if not hasattr(pmt, "data") or not isinstance(pmt.data, DataFlowBase):
            raise ValueError(
                "Dependency detector is not a Detector component.")
        self._pmt = pmt
        self._shape = pmt.shape
        # copy all the VAs and Events from the PMT to here (but .state and .dependencies).
        pmtVAs = model.getVAs(pmt)
        for key, value in pmtVAs.items():
            if not hasattr(self, key):
                setattr(self, key, value)
        pmtEvents = model.getEvents(pmt)
        for key, value in pmtEvents.items():
            setattr(self, key, value)

        if "pmt-control" in dependencies:
            ctrl = dependencies["pmt-control"]
            self._control = ctrl
            if not isinstance(ctrl, ComponentBase):
                raise ValueError("Dependency pmt-control is not a component.")
            # Duplicate control unit VAs
            # In case of counting PMT these VAs are not available since a
            # spectrograph is given instead of the control unit.
            try:
                if model.hasVA(ctrl, "gain"):
                    gain = ctrl.gain.range[0]
                    self.gain = model.FloatContinuous(gain,
                                                      ctrl.gain.range,
                                                      unit="V",
                                                      setter=self._setGain)
                    self._last_gain = gain
                    self._setGain(gain)  # Just start with no gain
                if model.hasVA(ctrl, "powerSupply"):
                    self.powerSupply = ctrl.powerSupply
                    # Turn on the controller
                    self.powerSupply.value = True
            except IOError:
                # FIXME: needs to be handled directly by PMTControl (at least automatic reconnect)
                raise HwError(
                    "PMT Control Unit connection timeout. "
                    "Please turn off and on the power to the box and "
                    "then restart Odemis.")
                # Protection VA should be available anyway
            if not model.hasVA(ctrl, "protection"):
                raise ValueError(
                    "Given component appears to be neither a PMT control "
                    "unit or a spectrograph since protection VA is not "
                    "available.")
        else:
            self._control = None

        if "pmt-signal" in dependencies:
            self._signal = dependencies["pmt-signal"]
            if not isinstance(self._signal, ComponentBase):
                raise ValueError("Dependency pmt-signal is not a component.")
            if not hasattr(self._signal, "data") or not isinstance(
                    self._signal.data, model.DataFlowBase):
                raise ValueError(
                    "Dependency pmt-signal doesn't have an attribute .data of type DataFlow."
                )
        else:
            self._signal = None

        self.data = PMTDataFlow(self, self._pmt, self._control, self._signal)
Beispiel #29
0
 def __init__(self, errcode):
     if errcode in ERRCODE:
         text = "Error %d: %s" % (errcode, ERRCODE[errcode])
     else:
         text = "Unknown error code."
     HwError.__init__(self, text)
Beispiel #30
0
    def __init__(self, name, role, children=None, sn=None, port=None, axis="rz",
                 inverted=None, positions=None, **kwargs):
        """
        children (dict string->model.HwComponent): they are not actually used.
            This is currently in place just to enforce PMT control to be
            initialized before the Fiber Flipper since we need the relay reset
            to happen before the flipper is turned on.
        sn (str): serial number (recommended)
        port (str): port name (only if sn is not specified)
        axis (str): name of the axis
        inverted (set of str): names of the axes which are inverted (IOW, either
         empty or the name of the axis)
        positions (None, or list of 2 tuples (value, str)): positions values and
         their corresponding name. If None: 0 and Pi/2 are used, without names.
        """
        if (sn is None and port is None) or (sn is not None and port is not None):
            raise ValueError("sn or port argument must be specified (but not both)")
        if sn is not None:
            if not sn.startswith(SN_PREFIX_MFF) or len(sn) != 8:
                logging.warning("Serial number '%s' is unexpected for a MFF "
                                "device (should be 8 digits starting with %s).",
                                sn, SN_PREFIX_MFF)
            self._port = self._getSerialPort(sn)
            self._sn = sn
        else:
            self._port = port
            # The MFF returns no serial number from GetInfo(), so find via USB
            try:
                self._sn = self._getSerialNumber(port)
                logging.info("Found serial number %s for device %s", self._sn, name)
            except LookupError:
                self._sn = None

        self._serial = self._openSerialPort(self._port)
        self._ser_access = threading.RLock()  # reentrant, so that recovery can keep sending messages
        self._recover = False
        self._initHw()

        # will take care of executing axis move asynchronously
        self._executor = CancellableThreadPoolExecutor(max_workers=1) # one task at a time

        if positions is None:
            positions = ((0, None), (math.radians(90), None))
        else:
            if len(positions) != 2 or any(len(p) != 2 for p in positions):
                raise ValueError("Positions must be exactly 2 tuples of 2 values")

        # TODO: have the standard inverted Actuator functions work on enumerated axis
        if inverted and axis in inverted:
            positions = (positions[1], positions[0])

        self._pos_to_jog = {positions[0][0]: 1,
                            positions[1][0]: 2}
        self._status_to_pos = {STA_FWD_HLS: positions[0][0],
                               STA_RVS_HLS: positions[1][0]}

        if positions[0][1] is None:
            choices = set(p[0] for p in positions)
        else:
            choices = dict(positions)

        # TODO: add support for speed
        axes = {axis: model.Axis(unit="rad", choices=choices)}
        model.Actuator.__init__(self, name, role, axes=axes, **kwargs)

        driver_name = driver.getSerialDriver(self._port)
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__, driver_name)
        try:
            snd, modl, typ, fmv, notes, hwv, state, nc = self.GetInfo()
        except IOError:
            # This is the first communication with the hardware, if it fails
            # it can be a sign the device is in a bad state. (it is known to
            # fail when turned on and plugged in before the host computer is
            # turned on)
            logging.exception("GetInfo() failed.")
            raise HwError("USB device with S/N %s seems in bad state. "
                          "Check that the Thorlabs filter flipper was "
                          "turned on *after* the host computer." % sn)
        self._hwVersion = "%s v%d (firmware %s)" % (modl, hwv, fmv)

        # It has worked at least once, so if it fails, there are hopes
        self._recover = True

        self.position = model.VigilantAttribute({}, readonly=True)
        self._updatePosition()
Beispiel #31
0
    def __init__(self,
                 name,
                 role,
                 sn=None,
                 port=None,
                 axis="rz",
                 inverted=None,
                 **kwargs):
        """
        sn (str): serial number (recommended)
        port (str): port name (only if sn is not specified)
        axis (str): name of the axis
        inverted (set of str): names of the axes which are inverted (IOW, either
         empty or the name of the axis) 
        """
        if (sn is None and port is None) or (sn is not None
                                             and port is not None):
            raise ValueError(
                "sn or port argument must be specified (but not both)")
        if sn is not None:
            if not sn.startswith(SN_PREFIX_MFF) or len(sn) != 8:
                logging.warning(
                    "Serial number '%s' is unexpected for a MFF "
                    "device (should be 8 digits starting with %s).", sn,
                    SN_PREFIX_MFF)
            self._port = self._getSerialPort(sn)
        else:
            self._port = port

        self._serial = self._openSerialPort(self._port)
        self._ser_access = threading.Lock()

        # Ensure we don't receive anything
        self.SendMessage(HW_STOP_UPDATEMSGS)
        self._serial.flushInput()

        # Documentation says it should be done first, though it doesn't seem
        # required
        self.SendMessage(HW_NO_FLASH_PROGRAMMING)

        # will take care of executing axis move asynchronously
        self._executor = CancellableThreadPoolExecutor(
            max_workers=1)  # one task at a time

        # TODO: have the standard inverted Actuator functions work on enumerated
        # use a different format than the standard Actuator
        if inverted and axis in inverted:
            self._pos_to_jog = {POS_UP: 2, POS_DOWN: 1}
            self._status_to_pos = {STA_RVS_HLS: POS_UP, STA_FWD_HLS: POS_DOWN}
        else:
            self._pos_to_jog = {POS_UP: 1, POS_DOWN: 2}
            self._status_to_pos = {STA_FWD_HLS: POS_UP, STA_RVS_HLS: POS_DOWN}

        # TODO: add support for speed
        axes = {
            axis: model.Axis(unit="rad", choices=set(self._pos_to_jog.keys()))
        }
        model.Actuator.__init__(self, name, role, axes=axes, **kwargs)

        driver_name = driver.getSerialDriver(self._port)
        self._swVersion = "%s (serial driver: %s)" % (odemis.__version__,
                                                      driver_name)
        try:
            snd, modl, typ, fmv, notes, hwv, state, nc = self.GetInfo()
        except IOError:
            # This is the first communication with the hardware, if it fails
            # it can be a sign the device is in a bad state. (it is known to
            # fail when turned on and plugged in before the host computer is
            # turned on)
            raise HwError("No USB device with S/N %s. "
                          "Check that the Thorlabs filter flipper was "
                          "turned on *after* the host computer." % sn)
        self._hwVersion = "%s v%d (firmware %s)" % (modl, hwv, fmv)

        self.position = model.VigilantAttribute({}, readonly=True)
        self._updatePosition()