# OxfordInstruments_ILM200.py class, to perform the communication between the Wrapper and the device
# Copyright (c) 2017 QuTech (Delft)
# Code is available under the available under the `MIT open-source license <https://opensource.org/licenses/MIT>`__
#
# Pieter Eendebak <pieter.eendebak@tno.nl>, 2017
# Takafumi Fujita <t.fujita@tudelft.nl>, 2016
# Guenevere Prawiroatmodjo <guen@vvtp.tudelft.nl>, 2009
# Pieter de Groot <pieterdegroot@gmail.com>, 2009


from time import sleep
import visa
import logging
from qcodes import VisaInstrument


class OxfordInstruments_ILM200(VisaInstrument):
    """
    This is the qcodes driver for the Oxford Instruments ILM 200 Helium Level Meter.

    Usage:
    Initialize with
    <name> = instruments.create('name', 'OxfordInstruments_ILM200', address='<Instrument address>')
    <Instrument address> = ASRL4::INSTR

    Note: Since the ISOBUS allows for several instruments to be managed in parallel, the command
    which is sent to the device starts with '@n', where n is the ISOBUS instrument number.

    """

    def __init__(self, name, address, number=1, **kwargs):
        """
        Initializes the Oxford Instruments ILM 200 Helium Level Meter.

        Args:
            name (str): name of the instrument
            address (str): instrument address
            number (int): ISOBUS instrument number (number=1 is specific to the ILM in F008)

        Returns:
            None
        """
        logging.debug(__name__ + ' : Initializing instrument')
        super().__init__(name, address, **kwargs)

        self.visa_handle.set_visa_attribute(visa.constants.VI_ATTR_ASRL_STOP_BITS,
                                            visa.constants.VI_ASRL_STOP_TWO)
        self._address = address
        self._number = number
        self._values = {}

        self.add_parameter('level',
                           label='level',
                           get_cmd=self._do_get_level,
                           unit='%')
        self.add_parameter('status',
                           get_cmd=self._do_get_status)
        self.add_parameter('rate',
                           get_cmd=self._do_get_rate,
                           set_cmd=self._do_set_rate)

        # a dummy command to avoid the initial error
        try:
            self.get_idn()
            sleep(70e-3)  # wait for the device to be able to respond
            self._read()  # to flush the buffer
        except Exception as ex:
            logging.debug(ex)

    def _execute(self, message):
        """
        Write a command to the device and read answer. This function writes to
        the buffer by adding the device number at the front, instead of 'ask'.

        Args:
            message (str) : write command for the device

        Returns:
            None
        """
        logging.info(
            __name__ + ' : Send the following command to the device: %s' % message)
        self.visa_handle.write('@%s%s' % (self._number, message))
        sleep(70e-3)  # wait for the device to be able to respond
        result = self._read()
        if result.find('?') >= 0:
            print("Error: Command %s not recognized" % message)
        else:
            return result

    def _read(self):
        """
        Reads the total bytes in the buffer and outputs as a string.

        Args:
            None

        Returns:
            message (str)
        """
        # because protocol has no termination chars the read reads the number
        # of bytes in the buffer
        bytes_in_buffer = self.visa_handle.bytes_in_buffer
        # a workaround for a timeout error in the pyvsia read_raw() function
        with(self.visa_handle.ignore_warning(visa.constants.VI_SUCCESS_MAX_CNT)):
            mes = self.visa_handle.visalib.read(
                self.visa_handle.session, bytes_in_buffer)
        # cannot be done on same line for some reason
        mes = str(mes[0].decode())
        return mes

    def get_idn(self):
        """
        Overrides the function of Instrument since ILM does not support `*IDN?`

        This string is supposed to be a
        comma-separated list of vendor, model, serial, and firmware, but
        semicolon and colon are also common separators so we accept them here
        as well.

        Returns:
            A dict containing vendor, model, serial, and firmware.
        """
        try:
            idstr = ''  # in case self.ask fails
            idstr = self._get_version().split()
            # form is supposed to be comma-separated, but we've seen
            # other separators occasionally
            idparts = [idstr[3] + ' ' + idstr[4], idstr[0], idstr[5],
                       idstr[1] + ' ' + idstr[2]]
            # in case parts at the end are missing, fill in None
            if len(idparts) < 4:
                idparts += [None] * (4 - len(idparts))
        except Exception as ex:
            logging.warn('Error getting or interpreting *IDN?: ' + repr(idstr))
            logging.debug(ex)
            idparts = [None, None, None, None]

        return dict(zip(('vendor', 'model', 'serial', 'firmware'), idparts))

    def get_all(self):
        """
        Reads all implemented parameters from the instrument,
        and updates the wrapper.
        """
        logging.info(__name__ + ' : reading all settings from instrument')
        self.level.get()
        self.status.get()
        self.rate.get()

    def close(self):
        """
        Safely close connection
        """
        logging.info(__name__ + ' : Closing ILM200 connection')
        self.local()
        super().close()

    # Functions: Monitor commands
    def _get_version(self):
        """
        Identify the device

        Args:
            None

        Returns:
            identification (str): should be 'ILM200 Version 1.08 (c) OXFORD 1994\r'
        """
        logging.info(__name__ + ' : Identify the device')
        return self._execute('V')

    def _do_get_level(self):
        """
        Get Helium level of channel 1.

        Args:
            None

        Returns:
            result (float) : Helium level
        """
        logging.info(__name__ + ' : Read level of channel 1')
        result = self._execute('R1')
        return float(result.replace("R", "")) / 10

    def _do_get_status(self):
        """
        Get status of the device.
        """
        logging.info(__name__ + ' : Get status of the device.')
        result = self._execute('X')
        usage = {
            0: "Channel not in use",
            1: "Channel used for Nitrogen level",
            2: "Channel used for Helium Level (Normal pulsed operation)",
            3: "Channel used for Helium Level (Continuous measurement)",
            9: "Error on channel (Usually means probe unplugged)"
        }
        # current_flowing = {
        # 0 : "Curent not flowing in Helium Probe Wire",
        # 1 : "Curent not flowing in Helium Probe Wire"
        # }
        # auto_fill_status = {
        # 00 : "End Fill (Level > FULL)",
        # 01 : "Not Filling (Level < FULL, Level > FILL)",
        # 10 : "Filling (Level < FULL, Level > FILL)",
        # 11 : "Start Filling (Level < FILL)"
        # }
        return usage.get(int(result[1]), "Unknown")

    def _do_get_rate(self):
        """
        Get helium meter channel 1 probe rate

        Input:
            None

        Output:
            rate(int) :
            0 : "SLOW"
            1 : "FAST"
        """
        rate = {
            1: "1 : Helium Probe in FAST rate",
            0: "0 : Helium Probe in SLOW rate"
        }
        result = self._execute('X')
        return rate.get(int(format(int(result[5:7]), '08b')[6]), "Unknown")

    def remote(self):
        """
        Set control to remote & locked
        """
        logging.info(__name__ + ' : Set control to remote & locked')
        self.set_remote_status(1)

    def local(self):
        """
        Set control to local & locked
        """
        logging.info(__name__ + ' : Set control to local & locked')
        self.set_remote_status(0)

    def set_remote_status(self, mode):
        """
        Set remote control status.

        Args:
            mode(int) :
            0 : "Local and locked",
            1 : "Remote and locked",
            2 : "Local and unlocked",
            3 : "Remote and unlocked",

        Returns:
            None
        """
        status = {
            0: "Local and locked",
            1: "Remote and locked",
            2: "Local and unlocked",
            3: "Remote and unlocked",
        }
        logging.info(__name__ + ' : Setting remote control status to %s' %
                     status.get(mode, "Unknown"))
        self._execute('C%s' % mode)

    # Functions: Control commands (only recognised when in REMOTE control)
    def set_to_slow(self):
        """
        Set helium meter channel 1 to slow mode.
        """
        self.set_remote_status(1)
        logging.info(__name__ + ' : Setting Helium Probe in SLOW rate')
        self._execute('S1')
        self.set_remote_status(3)

    def set_to_fast(self):
        """
        Set helium meter channel 1 to fast mode.
        """
        self.set_remote_status(1)
        logging.info(__name__ + ' : Setting Helium Probe in FAST rate')
        self._execute('T1')
        self.set_remote_status(3)

    def _do_set_rate(self, rate):
        """
        Set helium meter channel 1 probe rate

        Args:
            rate(int) :
            0 : "SLOW"
            1 : "FAST"
        """
        self.set_remote_status(1)
        if rate == 0:
            self.set_to_slow()
        elif rate == 1:
            self.set_to_fast()
        self.set_remote_status(3)
        logging.info(self._do_get_rate())

[((43, 8, 43, 62), 'logging.debug', 'logging.debug', ({(43, 22, 43, 61): "(__name__ + ' : Initializing instrument')"}, {}), "(__name__ + ' : Initializing instrument')", False, 'import logging\n'), ((81, 8, 82, 83), 'logging.info', 'logging.info', ({(82, 12, 82, 82): "(__name__ + ' : Send the following command to the device: %s' % message)"}, {}), "(__name__ + ' : Send the following command to the device: %s' %\n    message)", False, 'import logging\n'), ((84, 8, 84, 20), 'time.sleep', 'sleep', ({(84, 14, 84, 19): '(0.07)'}, {}), '(0.07)', False, 'from time import sleep\n'), ((146, 8, 146, 74), 'logging.info', 'logging.info', ({(146, 21, 146, 73): "(__name__ + ' : reading all settings from instrument')"}, {}), "(__name__ + ' : reading all settings from instrument')", False, 'import logging\n'), ((155, 8, 155, 63), 'logging.info', 'logging.info', ({(155, 21, 155, 62): "(__name__ + ' : Closing ILM200 connection')"}, {}), "(__name__ + ' : Closing ILM200 connection')", False, 'import logging\n'), ((170, 8, 170, 57), 'logging.info', 'logging.info', ({(170, 21, 170, 56): "(__name__ + ' : Identify the device')"}, {}), "(__name__ + ' : Identify the device')", False, 'import logging\n'), ((183, 8, 183, 61), 'logging.info', 'logging.info', ({(183, 21, 183, 60): "(__name__ + ' : Read level of channel 1')"}, {}), "(__name__ + ' : Read level of channel 1')", False, 'import logging\n'), ((191, 8, 191, 63), 'logging.info', 'logging.info', ({(191, 21, 191, 62): "(__name__ + ' : Get status of the device.')"}, {}), "(__name__ + ' : Get status of the device.')", False, 'import logging\n'), ((235, 8, 235, 68), 'logging.info', 'logging.info', ({(235, 21, 235, 67): "(__name__ + ' : Set control to remote & locked')"}, {}), "(__name__ + ' : Set control to remote & locked')", False, 'import logging\n'), ((242, 8, 242, 67), 'logging.info', 'logging.info', ({(242, 21, 242, 66): "(__name__ + ' : Set control to local & locked')"}, {}), "(__name__ + ' : Set control to local & locked')", False, 'import logging\n'), ((275, 8, 275, 71), 'logging.info', 'logging.info', ({(275, 21, 275, 70): "(__name__ + ' : Setting Helium Probe in SLOW rate')"}, {}), "(__name__ + ' : Setting Helium Probe in SLOW rate')", False, 'import logging\n'), ((284, 8, 284, 71), 'logging.info', 'logging.info', ({(284, 21, 284, 70): "(__name__ + ' : Setting Helium Probe in FAST rate')"}, {}), "(__name__ + ' : Setting Helium Probe in FAST rate')", False, 'import logging\n'), ((65, 12, 65, 24), 'time.sleep', 'sleep', ({(65, 18, 65, 23): '(0.07)'}, {}), '(0.07)', False, 'from time import sleep\n'), ((68, 12, 68, 29), 'logging.debug', 'logging.debug', ({(68, 26, 68, 28): 'ex'}, {}), '(ex)', False, 'import logging\n'), ((136, 12, 136, 29), 'logging.debug', 'logging.debug', ({(136, 26, 136, 28): 'ex'}, {}), '(ex)', False, 'import logging\n')]