示例#1
0
    def __init__(self, com, baud=9600, timeout=2.0, **kwargs):
        super().__init__(**kwargs)
        self.connection = serial.Serial(
            port=com,
            baudrate=baud,
            timeout=timeout,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            parity=serial.PARITY_NONE,
        )
        # If the laser is currently on, then we need to use 7-byte mode; otherwise we need to
        # use 16-byte mode.
        self._write(b"S?")
        response = self._readline()
        _logger.info("Current laser state: [%s]", response.decode())

        self._write(b"STAT3")
        option_codes = self._readline()
        if not option_codes.startswith(b"OC "):
            raise microscope.DeviceError("Failed to get option codes '%s'" %
                                         option_codes.decode())
        if option_codes[9:12] == b"AP1":
            self._has_apc = True
        else:
            _logger.warning("Laser is missing APC option.  Will return set"
                            " power instead of actual power")
            self._has_apc = False
示例#2
0
 def _do_shutdown(self):
     status = TMC.SBC_Close(self.serial_number)
     if status:
         message = "Error"
         if status in TMC.errors_dict.keys():
             message = TMC.errors_dict[status]
         raise microscope.DeviceError(message)
示例#3
0
    def __init__(self, shared_serial: microscope._utils.SharedSerial) -> None:
        self._serial = shared_serial

        self._serial.readlines()  # discard anything that may be on the line

        if self.get_system_type() != "iChrome-MLE":
            raise microscope.DeviceError("not an iChrome MLE device")
示例#4
0
 def _send_command(self, command, param=0, max_length=16, timeout_ms=100):
     """Send a command to the Clarity and return its response"""
     if not self._hid:
         self.open()
     with self._lock:
         # The device expects a list of 16 integers
         buffer = [0x00] * max_length  # The 0th element must be 0.
         buffer[1] = command  # The 1st element is the command
         buffer[2] = param  # The 2nd element is any command argument.
         result = self._hid.write(buffer)
         if result == -1:
             # Nothing to read back. Check hid error state.
             err = self._hid.error()
             if err != "":
                 self.close()
                 raise microscope.DeviceError(err)
             else:
                 return None
         while True:
             # Read responses until we see the response to our command.
             # (We should get the correct response on the first read.)
             response = self._hid.read(result - 1, timeout_ms)
             if not response:
                 # No response
                 return None
             elif response[0] == command:
                 break
         bytes = self._resultlen.get(command, None)
         if bytes is None:
             return response[1:]
         elif bytes == 1:
             return response[1]
         else:
             return response[1:]
示例#5
0
 def show_power_uW(self) -> float:
     """Returns actual laser power in µW."""
     answer = self.command(b"show power")
     if not answer.startswith(b"PIC  = ") and not answer.endswith(b" uW  "):
         raise microscope.DeviceError(
             "failed to parse power from answer: %s" % answer)
     return float(answer[7:-5])
示例#6
0
 def move_by(self, delta):
     status = TMC.SBC_MoveRelative(self.serial_number, self.axis, delta)
     if status:
         message = "Error"
         if status in TMC.errors_dict.keys():
             message = TMC.errors_dict[status]
         raise microscope.DeviceError(message)
示例#7
0
    def command_and_answer(self, *TX_tokens: bytes) -> bytes:
        # Command contains two or more tokens.  The first token for a
        # TX (transmitted) command string is one of the two keywords
        # GET, SET (to query or to set).  The second token is the
        # command name.
        assert len(TX_tokens) >= 2, "invalid command with less than two tokens"
        assert TX_tokens[0] in (b"GET", b"SET"), "invalid command (not SET/GET)"

        TX_command = b" ".join(TX_tokens) + b"\n"
        with self._serial.lock:
            self._serial.write(TX_command)
            answer = self._serial.readline()
        RX_tokens = answer.split(maxsplit=2)
        # A received answer has at least two tokens.  The first token
        # is A or E (for success or failure).  The second token is the
        # command name (second token of the transmitted command).
        if (
            len(RX_tokens) < 2
            or RX_tokens[0] != b"A"
            or RX_tokens[1] != TX_tokens[1]
        ):
            raise microscope.DeviceError(
                "command %s failed: %s" % (TX_command, answer)
            )
        return answer
示例#8
0
    def command(self, command: bytes) -> bytes:
        """Run command and return answer after minimal validation.

        The output of a command has the format::

        \r\nANSWER\r\n[OK]\r\n

        The returned bytes only include `ANSWER` without its own final
        `\r\n`.  This means that the return value might be an empty
        array of bytes.
        """
        # We expect to be on 'talk usual' mode without prompt so each
        # command will end with [OK] on its own line.
        with self._serial.lock:
            self._serial.write(command + b"\r\n")
            # An answer always starts with \r\n so there will be one
            # before [OK] even if this command is not a query.
            answer = self._serial.read_until(b"\r\n[OK]\r\n")

        if not answer.startswith(b"\r\n"):
            raise microscope.DeviceError(
                "answer to command %s does not start with CRLF."
                " This may be leftovers from a previous command:"
                " %s" % (command, answer)
            )
        if not answer.endswith(b"\r\n[OK]\r\n"):
            raise microscope.DeviceError(
                "Command %s failed or failed to read answer: %s"
                % (command, answer)
            )

        # If an error occurred, the answer still ends in [OK].  We
        # need to check if the second line (first line is \r\n) is an
        # error code with the format "%SYS-L-XXX, error description"
        # where L is the error level (I for Information, W for
        # Warning, E for Error, and F for Fatal), and XXX is the error
        # code number.
        if answer[2:7] == b"%SYS-" and answer[7] != ord(b"I"):
            # Errors of level I (information) should not raise an
            # exception since they can be replies to normal commands.
            raise microscope.DeviceError(
                "Command %s failed: %s" % (command, answer)
            )

        # Exclude the first \r\n, the \r\n from a possible answer, and
        # the final [OK]\r\n
        return answer[2:-8]
示例#9
0
 def param_set(self, name: bytes, value: bytes) -> None:
     """Change parameter (`param-set!` operator)."""
     answer = self._param_command(b"(param-set! '%s %s)" % (name, value))
     status = int(answer)
     if status < 0:
         raise microscope.DeviceError(
             "Failed to set parameter %s (return value %d)" %
             (name.decode(), status))
示例#10
0
 def move_to(self, pos):
     # pos: int
     status = TMC.SBC_MoveToPosition(self.serial_number, self.axis, pos)
     if status:
         message = "Error"
         if status in TMC.errors_dict.keys():
             message = TMC.errors_dict[status]
         raise microscope.DeviceError(message)
示例#11
0
 def set_slide_position(self, position, blocking=True):
     """Set the slide position"""
     result = self._send_command(__SETSLIDE, position)
     if result is None:
         raise microscope.DeviceError("Slide position error.")
     while blocking and self.moving():
         pass
     return result
示例#12
0
 def _do_set_position(self, pos, blocking=True):
     """Set the filter position"""
     result = self._send_command(__SETFILT, pos)
     if result is None:
         raise microscope.DeviceError("Filter position error.")
     while blocking and self.moving():
         pass
     return result
示例#13
0
 def _readline(self):
     """Read a line from connection without leading and trailing whitespace.
     We override from SerialDeviceMixin
     """
     response = self.connection.readline().strip()
     if self.connection.readline().strip() != b"OK":
         raise microscope.DeviceError(
             "Did not get a proper answer from the laser serial comm.")
     return response
示例#14
0
 def get_is_on(self) -> bool:
     state = self._conn.status_laser()
     if state == b"ON":
         return True
     elif state == b"OFF":
         return False
     else:
         raise microscope.DeviceError("Unexpected laser status: %s" %
                                      state.decode())
示例#15
0
 def show_max_power(self) -> float:
     # There should be a cleaner way to get these, right?  We can
     # query the current limits (mA) but how do we go from there to
     # the power limits (mW)?
     table = self.command(b"show satellite")
     key = _get_table_value(table, b"Pmax")
     if not key.endswith(b" mW"):
         raise microscope.DeviceError("failed to parse power from %s" % key)
     return float(key[:-3])
示例#16
0
 def get_css(self) -> bytes:
     """Get the global channel status map."""
     with self._serial.lock:
         self._serial.write(b"CSS?\n")
         answer = self._serial.readline()
     if not answer.startswith(b"CSS"):
         raise microscope.DeviceError(
             "answer to 'CSS?' should start with 'CSS'"
             " but got '%s' instead" % answer.decode)
     return answer[3:-2]  # remove initial b'CSS' and final b'\r\n'
示例#17
0
 def set_css(self, css: bytes) -> None:
     """Set status for any number of channels."""
     assert len(css) % 6 == 0, "css must be multiple of 6 (6 per channel)"
     with self._serial.lock:
         self._serial.write(b"CSS" + css + b"\n")
         answer = self._serial.readline()
     if not answer.startswith(b"CSS"):
         raise microscope.DeviceError(
             "answer to 'CSS?' should start with 'CSS'"
             " but got '%s' instead" % answer.decode)
示例#18
0
 def enable(self):
     # homing
     # !!! Might need to wait for homing to finish axis by axis
     for channel_nr in range(self.n_axes):
         status = TMC.SBC_Home(self.serial_number, channel_nr + 1)
         if status:
             message = "Error"
             if status in TMC.errors_dict.keys():
                 message = TMC.errors_dict[status]
             raise microscope.DeviceError(message)
示例#19
0
 def get_light_state(self) -> bool:
     """On (True) or off (False) state"""
     # We use CHACT (actual light state) instead of CH (light
     # state) because CH checks both the TTL inputs and channel
     # state switches.
     state = self._conn.get_command(b"CHACT", self._index_bytes)
     if state == b"1":
         return True
     elif state == b"0":
         return False
     else:
         raise microscope.DeviceError("unexpected answer")
示例#20
0
    def __init__(self, conn: _iChromeConnection, laser_number: int) -> None:
        self._conn = conn
        self._param_prefix = b"laser%d:" % laser_number

        # We Need to confirm that indeed there is a laser at this
        # position.  There is no command to check this, we just try to
        # read a parameter and check if it works.
        try:
            self.get_label()
        except microscope.DeviceError as ex:
            raise microscope.DeviceError(
                "failed to get label, probably no laser %d" %
                laser_number) from ex
示例#21
0
    def wait_until_idle(self, timeout: float = 10.0) -> None:
        """Wait, or error, until device is idle.

        A device is busy if *any* of its axis is busy.
        """
        sleep_interval = 0.1
        for _ in range(int(timeout / sleep_interval)):
            if not self.is_busy():
                break
            time.sleep(sleep_interval)
        else:
            raise microscope.DeviceError("device still busy after %f seconds" %
                                         timeout)
示例#22
0
def _get_table_value(table: bytes, key: bytes) -> bytes:
    """Get the value for a key in a table/multiline output.

    Some commands return something like a table of key/values.  There
    may be even empty lines on this table.  This searches for the
    first line with a specific key (hopefully there's only one line
    with such key) and returns the associated value.
    """
    # Key might be the first line, hence '(?:^|\r\n)'
    match = re.search(b"(?:^|\r\n) *" + key + b": (.*)\r\n", table)
    if match is None:
        raise microscope.DeviceError("failed to find key %s on table: %s" %
                                     (key, table))
    return match.group(1)
示例#23
0
    def __init__(self, port: str):
        # From the Toptica iBeam SMART manual:
        # Direct connection via COMx with 115200,8,N,1 and serial
        # interface handshake "none". That means that no hardware
        # handshake (DTR, RTS) and no software handshake (XON,XOFF) of
        # the underlying operating system is supported.
        serial_conn = serial.Serial(
            port=port,
            baudrate=115200,
            timeout=1.0,
            bytesize=serial.EIGHTBITS,
            stopbits=serial.STOPBITS_ONE,
            parity=serial.PARITY_NONE,
            xonxoff=False,
            rtscts=False,
            dsrdtr=False,
        )
        self._serial = _SharedSerial(serial_conn)

        # We don't know what is the current verbosity state and so we
        # don't know yet what we should be reading back.  So blindly
        # set to the level we want, flush all output, and then check
        # if indeed this is a Toptica iBeam device.
        with self._serial.lock:
            self._serial.write(b"echo off\r\n")
            self._serial.write(b"prompt off\r\n")
            # The talk level we want is 'usual'.  In theory we should
            # be able to use 'quiet' which only answers queries but in
            # practice 'quiet' does not answer some queries like 'show
            # serial'.
            self._serial.write(b"talk usual\r\n")
            self._serial.readlines()  # discard all pending lines

        # Empty command does nothing and returns nothing extra so we
        # use it to ensure this at least behaves like a Toptica iBeam.
        try:
            self.command(b"")
        except microscope.DeviceError as e:
            raise microscope.InitialiseError(
                "Failed to confirm Toptica iBeam on %s" % (port)
            ) from e

        answer = self.command(b"show serial")
        if not answer.startswith(b"SN: "):
            raise microscope.DeviceError(
                "Failed to parse serial from %s" % answer
            )
        _logger.info("got connection to Toptica iBeam %s", answer.decode())
示例#24
0
    def _do_trigger(self) -> None:
        """Convenience fallback.

        This only provides a convenience fallback for devices that
        don't support queuing multiple patterns and software trigger,
        i.e., devices that take only one pattern at a time.  This is
        not the case of most devices.

        Devices that support queuing patterns, should override this
        method.

        .. todo:: instead of a convenience fallback, we should have a
           separate mixin for this.
        """
        if self._patterns is None:
            raise microscope.DeviceError("no pattern queued to apply")
        self._pattern_idx += 1
        self.apply_pattern(self._patterns[self._pattern_idx, :])
示例#25
0
    def _do_get_power(self) -> float:
        if not self.get_is_on():
            return 0.0
        if self._has_apc:
            query = b"P"
            scale = 0xCCC
        else:
            query = b"PP"
            scale = 0xFFF

        self._write(query + b"?")
        answer = self._readline()
        if not answer.startswith(query):
            raise microscope.DeviceError("failed to read power from '%s'" %
                                         answer.decode())

        level = int(answer[len(query):], 16)
        return float(level) / float(scale)
示例#26
0
 def _fetch_data(self):
     if self._acquiring and self._triggered > 0:
         if random.randint(0, 100) < self._error_percent:
             _logger.info("Raising exception")
             raise microscope.DeviceError(
                 "Exception raised in TestCamera._fetch_data"
             )
         _logger.info("Sending image")
         time.sleep(self._exposure_time)
         self._triggered -= 1
         # Create an image
         dark = int(32 * np.random.rand())
         light = int(255 - 128 * np.random.rand())
         width = self._roi.width // self._binning.h
         height = self._roi.height // self._binning.v
         image = self._image_generator.get_image(
             width, height, dark, light, index=self._sent
         )
         self._sent += 1
         return image
示例#27
0
    def _param_command(self, command: bytes) -> bytes:
        """Run command and return raw answer (minus prompt and echo)."""
        command = command + b"\r\n"
        with self._serial.lock:
            self._serial.write(command)
            answer = self._serial.read_until(b"\r\n> ")

        # When we read, we are reading the whole command console
        # including the prompt and even the command is echoed back.
        assert answer[:len(command)] == command and answer[-4:] == b"\r\n> "

        # Errors are indicated by the string "Error: " at the
        # beginning of a new line.
        if answer[len(command):len(command) + 7] == b"Error: ":
            base_command = command[:-2]
            error_msg = answer[len(command) + 8:-4]
            raise microscope.DeviceError(
                "error on command '%s': %s" %
                (base_command.decode(), error_msg.decode()))

        # Return the answer minus the "echoed" command and the prompt
        # for the next command.
        return answer[len(command):-4]
示例#28
0
 def _raise_status(self, func: typing.Callable) -> None:
     error_code = self._status.contents.value
     raise microscope.DeviceError(
         "mro_%s() failed (error code %d)" % (func.__name__, error_code)
     )
示例#29
0
 def get_slide_position(self):
     """Get the current slide position"""
     result = self._send_command(__GETSLIDE)
     if result is None:
         raise microscope.DeviceError("Slide position error.")
     return result
示例#30
0
 def _do_get_position(self):
     """Return the current filter position"""
     result = self._send_command(__GETFILT)
     if result == __FLTERR:
         raise microscope.DeviceError("Filter position error.")
     return result