Example #1
0
    def __init__(self, *args, **kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower()
                                              == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port = find_mettler_toledo_device_port(baudrate=kwargs['baudrate'],
                                                   try_ports=try_ports,
                                                   debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args, **kwargs)
        atexit.register(self._exit_mettler_toledo_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))
    def __init__(self, *args, **kwargs):
        if "debug" in kwargs:
            self.debug = kwargs["debug"]
        else:
            kwargs.update({"debug": DEBUG})
            self.debug = DEBUG
        if "try_ports" in kwargs:
            try_ports = kwargs.pop("try_ports")
        else:
            try_ports = None
        if "baudrate" not in kwargs:
            kwargs.update({"baudrate": BAUDRATE})
        elif (kwargs["baudrate"] is None) or (str(kwargs["baudrate"]).lower() == "default"):
            kwargs.update({"baudrate": BAUDRATE})
        if "timeout" not in kwargs:
            kwargs.update({"timeout": self._TIMEOUT})
        if "write_write_delay" not in kwargs:
            kwargs.update({"write_write_delay": self._WRITE_WRITE_DELAY})
        if ("port" not in kwargs) or (kwargs["port"] is None):
            port = find_mettler_toledo_device_port(
                baudrate=kwargs["baudrate"], try_ports=try_ports, debug=kwargs["debug"]
            )
            kwargs.update({"port": port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args, **kwargs)
        atexit.register(self._exit_mettler_toledo_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print("Initialization time =", (t_end - t_start))
    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        kwargs['debug'] = False
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_zaber_device_port(baudrate=kwargs['baudrate'],
                                           try_ports=try_ports,
                                           debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._debug_print("port = {0}".format(kwargs['port']))
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_zaber_device)
        time.sleep(self._RESET_DELAY)
        self._lock = threading.Lock()
        self._actuator_count = None
        self._zaber_response = ''
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))
    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_bioshake_device_port(baudrate=kwargs['baudrate'],
                                              try_ports=try_ports,
                                              debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_bioshake_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))
Example #5
0
        def thrproc():
            if self.connectButton.cget('relief') == 'raised':
                self.progressStart()
                try:
                    port = self.portName.get()
                    self.dev = SerialDevice(port=port)
                except Exception as e:
                    if self.downloadButton:
                        self.downloadButton.config(state=DISABLED)
                        self.uploadButton.config(state=DISABLED)
                        self.deleteButton.config(state=DISABLED)
                    showwarning("Connect",
                                "Problem opening serial.  Details:\n" + str(e))
                    return

                self.dev.baudrate = 115200
                self.connectButton.config(relief=SUNKEN)
                # send ctrl-c to break any potentially running python program
                ctrlc = chr(3)
                self.remote(ctrlc)
                self.populateFileView()
                self.termArea.insert(
                    END,
                    ">>> ")  # write out the initial prompt... purely cosmetic
                self.termArea.config(background='white')
                self.downloadButton.config(state=NORMAL)
                self.uploadButton.config(state=NORMAL)
                self.deleteButton.config(state=NORMAL)
                self.connected = True
            else:
                self.connected = False
                if self.dev is not None:
                    self.dev.close()
                self.dev = None
                self.connectButton.config(relief=RAISED)
                self.downloadButton.config(state=DISABLED)
                self.deleteButton.config(state=DISABLED)
                self.uploadButton.config(state=DISABLED)
                self.clearFileView()
                self.termArea.config(background='lightgrey')

            self.progressStop()
    def __init__(self,*args,**kwargs):
        model_number = None
        serial_number = None
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if 'model_number' in kwargs:
            model_number = kwargs.pop('model_number')
        if 'serial_number' in kwargs:
            serial_number = kwargs.pop('serial_number')
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_modular_device_port(baudrate=kwargs['baudrate'],
                                             model_number=model_number,
                                             serial_number=serial_number,
                                             try_ports=try_ports,
                                             debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_modular_device)
        time.sleep(self._RESET_DELAY)
        self._response_dict = None
        self._response_dict = self._get_response_dict()
        self._method_dict = self._get_method_dict()
        self._method_dict_inv = dict([(v,k) for (k,v) in self._method_dict.iteritems()])
        self._create_methods()
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))
Example #7
0
class MettlerToledoDevice(object):
    '''
    This Python package (mettler_toledo_device) creates a class named
    MettlerToledoDevice, which contains an instance of
    serial_device2.SerialDevice and adds methods to it to interface to
    Mettler Toledo balances and scales that use the Mettler Toledo
    Standard Interface Command Set (MT-SICS).

    Example Usage:

    dev = MettlerToledoDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = MettlerToledoDevice(port='/dev/ttyUSB0') # Linux specific port
    dev = MettlerToledoDevice(port='/dev/tty.usbmodem262471') # Mac OS X specific port
    dev = MettlerToledoDevice(port='COM3') # Windows specific port
    dev.get_serial_number()
    1126493049
    dev.get_balance_data()
    ['XS204', 'Excellence', '220.0090', 'g']
    dev.get_weight_stable()
    [-0.0082, 'g'] #if weight is stable
    None  #if weight is dynamic
    dev.get_weight()
    [-0.6800, 'g', 'S'] #if weight is stable
    [-0.6800, 'g', 'D'] #if weight is dynamic
    dev.zero_stable()
    True  #zeros if weight is stable
    False  #does not zero if weight is not stable
    dev.zero()
    'S'   #zeros if weight is stable
    'D'   #zeros if weight is dynamic
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0

    def __init__(self, *args, **kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower()
                                              == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port = find_mettler_toledo_device_port(baudrate=kwargs['baudrate'],
                                                   try_ports=try_ports,
                                                   debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args, **kwargs)
        atexit.register(self._exit_mettler_toledo_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_mettler_toledo_device(self):
        pass

    def _args_to_request(self, *args):
        request = ''.join(map(str, args))
        request = request + '\r\n'
        return request

    def _send_request(self, *args):
        '''Sends request to device over serial port and
        returns number of bytes written'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        bytes_written = self._serial_device.write_check_freq(request,
                                                             delay_write=True)
        return bytes_written

    def _send_request_get_response(self, *args):
        '''Sends request to device over serial port and
        returns response'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        response = self._serial_device.write_read(request,
                                                  use_readline=True,
                                                  check_write_freq=True)
        response = response.replace('"', '')
        response_list = response.split()
        if 'ES' in response_list[0]:
            raise MettlerToledoError('Syntax Error!')
        elif 'ET' in response_list[0]:
            raise MettlerToledoError('Transmission Error!')
        elif 'EL' in response_list[0]:
            raise MettlerToledoError('Logical Error!')
        return response_list

    def close(self):
        '''
        Close the device serial port.
        '''
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def get_commands(self):
        '''
        Inquiry of all implemented MT-SICS commands.
        '''
        response = self._send_request_get_response('I0')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'The list cannot be sent at present as another operation is taking place.'
            )
        return response[2:]

    def get_mtsics_level(self):
        '''
        Inquiry of MT-SICS level and MT-SICS versions.
        '''
        response = self._send_request_get_response('I1')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        return response[2:]

    def get_balance_data(self):
        '''
        Inquiry of balance data.
        '''
        response = self._send_request_get_response('I2')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        return response[2:]

    def get_software_version(self):
        '''
        Inquiry of balance SW version and type definition number.
        '''
        response = self._send_request_get_response('I3')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        return response[2:]

    def get_serial_number(self):
        '''
        Inquiry of serial number.
        '''
        response = self._send_request_get_response('I4')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        return response[2]

    def get_software_id(self):
        '''
        Inquiry of SW-Identification number.
        '''
        response = self._send_request_get_response('I5')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        return response[2]

    def get_weight_stable(self):
        '''
        Send the current stable net weight value.
        '''
        try:
            response = self._send_request_get_response('S')
            if 'I' in response[1]:
                raise MettlerToledoError(
                    'Command understood, not executable at present.')
            elif '+' in response[1]:
                raise MettlerToledoError('Balance in overload range.')
            elif '-' in response[1]:
                raise MettlerToledoError('Balance in underload range.')
            response[2] = float(response[2])
            return response[2:]
        except:
            pass

    def get_weight(self):
        '''
        Send the current net weight value, irrespective of balance stability.
        '''
        response = self._send_request_get_response('SI')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Command understood, not executable at present.')
        elif '+' in response[1]:
            raise MettlerToledoError('Balance in overload range.')
        elif '-' in response[1]:
            raise MettlerToledoError('Balance in underload range.')
        response.append(response[1])
        response[2] = float(response[2])
        return response[2:]

    def zero_stable(self):
        '''
        Zero the balance.
        '''
        try:
            response = self._send_request_get_response('Z')
            if 'I' in response[1]:
                raise MettlerToledoError(
                    'Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached).'
                )
            elif '+' in response[1]:
                raise MettlerToledoError(
                    'Upper limit of zero setting range exceeded.')
            elif '-' in response[1]:
                raise MettlerToledoError(
                    'Lower limit of zero setting range exceeded.')
            return True
        except:
            return False

    def zero(self):
        '''
        Zero the balance immediately regardless the stability of the balance.
        '''
        response = self._send_request_get_response('ZI')
        if 'I' in response[1]:
            raise MettlerToledoError(
                'Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached).'
            )
        elif '+' in response[1]:
            raise MettlerToledoError(
                'Upper limit of zero setting range exceeded.')
        elif '-' in response[1]:
            raise MettlerToledoError(
                'Lower limit of zero setting range exceeded.')
        return response[1]

    def reset(self):
        '''
        Resets the balance to the condition found after switching on, but without a zero setting being performed.
        '''
        self._send_request('@')
class ModularServer(object):
    '''
    ModularServer contains an instance of serial_device2.SerialDevice and
    adds methods to it, like auto discovery of available modular devices
    in Linux, Windows, and Mac OS X. This class automatically creates
    methods from available functions reported by the modular device when
    it is running the appropriate firmware.

    Example Usage:

    dev = ModularDevice() # Might automatically finds device if one available
    # if it is not found automatically, specify port directly
    dev = ModularDevice(port='/dev/ttyACM0') # Linux specific port
    dev = ModularDevice(port='/dev/tty.usbmodem262471') # Mac OS X specific port
    dev = ModularDevice(port='COM3') # Windows specific port
    dev.get_device_info()
    dev.get_methods()
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0
    _METHOD_ID_GET_DEVICE_INFO = 0
    _METHOD_ID_GET_METHOD_IDS = 1
    _METHOD_ID_GET_RESPONSE_CODES = 2

    def __init__(self,*args,**kwargs):
        model_number = None
        serial_number = None
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if 'model_number' in kwargs:
            model_number = kwargs.pop('model_number')
        if 'serial_number' in kwargs:
            serial_number = kwargs.pop('serial_number')
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_modular_device_port(baudrate=kwargs['baudrate'],
                                             model_number=model_number,
                                             serial_number=serial_number,
                                             try_ports=try_ports,
                                             debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_modular_device)
        time.sleep(self._RESET_DELAY)
        self._response_dict = None
        self._response_dict = self._get_response_dict()
        self._method_dict = self._get_method_dict()
        self._method_dict_inv = dict([(v,k) for (k,v) in self._method_dict.iteritems()])
        self._create_methods()
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_modular_device(self):
        pass

    def _args_to_request(self,*args):
        request_list = ['[', ','.join(map(str,args)), ']']
        request = ''.join(request_list)
        request += '\n';
        return request

    def _send_request(self,*args):
        '''
        Sends request to modular device over serial port and
        returns number of bytes written
        '''
        request = self._args_to_request(*args)
        self._debug_print('request', request)
        bytes_written = self._serial_device.write_check_freq(request,delay_write=True)
        return bytes_written

    def _send_request_get_response(self,*args):
        '''
        Sends request to device over serial port and
        returns response
        '''
        request = self._args_to_request(*args)
        self._debug_print('request', request)
        response = self._serial_device.write_read(request,use_readline=True,check_write_freq=True)
        if response is None:
            response_dict = {}
            return response_dict
        self._debug_print('response', response)
        try:
            response_dict = json_string_to_dict(response)
        except Exception, e:
            error_message = 'Unable to parse device response {0}.'.format(str(e))
            raise IOError, error_message
        try:
            status = response_dict.pop('status')
        except KeyError:
            error_message = 'Device response does not contain status.'
            raise IOError, error_message
        try:
            method_id  = response_dict.pop('method_id')
        except KeyError:
            error_message = 'Device response does not contain method_id.'
            raise IOError, error_message
        if not method_id == args[0]:
            raise IOError, 'Device method_id does not match that sent.'
        if self._response_dict is not None:
            if status == self._response_dict['response_error']:
                try:
                    dev_error_message = '(from device) {0}'.format(response_dict['error_message'])
                except KeyError:
                    dev_error_message = 'Error message missing.'
                error_message = '{0}'.format(dev_error_message)
                raise IOError, error_message
        return response_dict
class ZaberDevice(object):
    '''
    This Python package (zaber_device) creates a class named ZaberDevice,
    which contains an instance of serial_device2.SerialDevice and adds
    methods to it to interface to Zaber motorized linear slides.

    Example Usage:

    dev = ZaberDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = ZaberDevice(port='/dev/ttyUSB0') # Linux
    dev = ZaberDevice(port='/dev/tty.usbmodem262471') # Mac OS X
    dev = ZaberDevice(port='COM3') # Windows
    dev.get_actuator_count()
    2
    dev.get_position()
    [130000, 160000]
    dev.home()
    dev.moving()
    [True, True]
    dev.moving()
    [False, False]
    dev.get_position()
    [0, 0]
    dev.move_relative(10000)
    dev.get_position()
    [10000, 10000]
    dev.move_relative(10000,0)
    dev.moving()
    [True, False]
    dev.get_position()
    [20000, 10000]
    dev.store_position(0)
    dev.get_stored_position(0)
    [20000, 10000]
    dev.move_at_speed(1000)
    dev.stop()
    dev.get_position()
    [61679, 51679]
    dev.move_to_stored_position(0)
    dev.get_position()
    [20000, 10000]
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0

    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        kwargs['debug'] = False
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_zaber_device_port(baudrate=kwargs['baudrate'],
                                           try_ports=try_ports,
                                           debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._debug_print("port = {0}".format(kwargs['port']))
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_zaber_device)
        time.sleep(self._RESET_DELAY)
        self._lock = threading.Lock()
        self._actuator_count = None
        self._zaber_response = ''
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_zaber_device(self):
        pass

    def _args_to_request(self,*args):
        request = ''.join(map(chr,args))
        return request

    def _data_to_args_list(self,data):
        if data is None:
            return [0,0,0,0]
        # Handle negative data
        # If Cmd_Data < 0 then Cmd_Data = 256^4 + Cmd_Data
        # Cmd_Byte_6 = Cmd_Data / 256^3
        # Cmd_Data   = Cmd_Data - 256^3 * Cmd_Byte_6
        # Cmd_Byte_5 = Cmd_Data / 256^2
        # Cmd_Data   = Cmd_Data - 256^2 * Cmd_Byte_5
        # Cmd_Byte_4 = Cmd_Data / 256
        # Cmd_Data   = Cmd_Data - 256   * Cmd_Byte_4
        # Cmd_Byte 3 = Cmd_Data
        data = int(data)
        if data < 0:
            data += pow(256,4)
        arg3 = data // pow(256,3)
        data -= pow(256,3)*arg3
        arg2 = data // pow(256,2)
        data -= pow(256,2)*arg2
        arg1 = data // 256
        data -= 256*arg1
        arg0 = data
        return [arg0,arg1,arg2,arg3]

    def _response_to_data(self,response):
        self._debug_print('len(response)',len(response))
        actuator_count = len(response) // RESPONSE_LENGTH
        self._debug_print('actuator_count',actuator_count)
        if self._actuator_count is not None:
            if actuator_count != self._actuator_count:
                self._debug_print("actuator_count != self._actuator_count!!")
                raise ZaberNumberingError('')
        elif actuator_count > 0:
            self._actuator_count = actuator_count
        data_list = [None for d in range(actuator_count)]
        for actuator_n in range(actuator_count):
            actuator = ord(response[0+actuator_n*RESPONSE_LENGTH]) - 1
            self._debug_print('response_actuator',actuator)
            if (actuator >= actuator_count) or (actuator < 0):
                self._debug_print("invalid actuator number!!")
                raise ZaberNumberingError('')
            cmd = ord(response[1+actuator_n*RESPONSE_LENGTH])
            self._debug_print('response_command',cmd)
            response_copy = response[(2+actuator_n*RESPONSE_LENGTH):(6+actuator_n*RESPONSE_LENGTH)]
            # Reply_Data = 256^3 * Rpl_Byte 6 + 256^2 * Rpl_Byte_5 + 256 * Rpl_Byte_4 + Rpl_Byte_3
            # If Rpl_Byte_6 > 127 then Reply_Data = Reply_Data - 256^4
            data = pow(256,3)*ord(response_copy[3]) + pow(256,2)*ord(response_copy[2]) + 256*ord(response_copy[1]) + ord(response_copy[0])
            data_list[actuator] = data
        if any([data is None for data in data_list]):
            raise ZaberNumberingError('')
        return data_list

    def _send_request(self,command,actuator=None,data=None):

        '''Sends request to device over serial port and
        returns number of bytes written'''

        with self._lock:
            if actuator is None:
                actuator = 0
            elif actuator < 0:
                raise ZaberError('actuator must be >= 0')
            else:
                actuator = int(actuator)
                actuator += 1
            args_list = self._data_to_args_list(data)
            request = self._args_to_request(actuator,command,*args_list)
            self._debug_print('request', [ord(c) for c in request])
            bytes_written = self._serial_device.write_check_freq(request,delay_write=True)
            self._debug_print('bytes_written', bytes_written)
        return bytes_written

    def _send_request_get_response(self,command,actuator=None,data=None):

        '''Sends request to device over serial port and
        returns response'''

        request_successful = False
        with self._lock:
            if actuator is None:
                actuator = 0
            elif actuator < 0:
                raise ZaberError('actuator must be >= 0')
            else:
                actuator = int(actuator)
                actuator += 1
            args_list = self._data_to_args_list(data)
            request = self._args_to_request(actuator,command,*args_list)
            request_attempt = 0
            while (not request_successful) and (request_attempt < REQUEST_ATTEMPTS_MAX):
                try:
                    self._debug_print('request attempt: {0}'.format(request_attempt))
                    self._debug_print('request', [ord(c) for c in request])
                    request_attempt += 1
                    response = self._serial_device.write_read(request,use_readline=False,match_chars=True)
                    response_array = [ord(c) for c in response]
                    response_str = str(response_array)
                    self._debug_print('response', response_str)
                    self._zaber_response = response_str
                    data = self._response_to_data(response)
                    self._debug_print('data', data)
                    request_successful = True
                except ZaberNumberingError:
                    self._debug_print("request error!!")
        if not request_successful:
            raise ZaberError('Improper actuator response, may need to rearrange zaber cables or use renumber method to fix.')
        else:
            return data

    def close(self):
        '''
        Close the device serial port.
        '''
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def reset(self,actuator=None):
        '''
        Sets the actuator to its power-up condition.
        '''
        self._send_request(0,actuator)

    def home(self,actuator=None):
        '''
        Moves to the home position and resets the actuator's internal position.
        '''
        self._send_request(1,actuator)

    def renumber(self):
        '''
        Assigns new numbers to all the actuators in the order in which they are connected.
        '''
        self._send_request(2,None)

    def store_position(self,address,actuator=None):
        '''
        Saves the current absolute position of the actuator into the address.
        '''
        address = int(address)
        if (address < POSITION_ADDRESS_MIN) or (address > POSITION_ADDRESS_MAX):
            raise ZaberError('address must be between {0} and {1}'.format(POSITION_ADDRESS_MIN,POSITION_ADDRESS_MAX))
        self._send_request(16,actuator,address)

    def get_stored_position(self,address):
        '''
        Gets the current absolute position of the actuator into the address.
        '''
        address = int(address)
        if (address < POSITION_ADDRESS_MIN) or (address > POSITION_ADDRESS_MAX):
            raise ZaberError('address must be between {0} and {1}'.format(POSITION_ADDRESS_MIN,POSITION_ADDRESS_MAX))
        actuator = None
        response = self._send_request_get_response(17,actuator,address)
        return response

    def move_to_stored_position(self,address,actuator=None):
        '''
        Moves the actuator to the position stored in the specified address.
        '''
        address = int(address)
        if (address < POSITION_ADDRESS_MIN) or (address > POSITION_ADDRESS_MAX):
            raise ZaberError('address must be between {0} and {1}'.format(POSITION_ADDRESS_MIN,POSITION_ADDRESS_MAX))
        self._send_request(18,actuator,address)

    def move_absolute(self,position,actuator=None):
        '''
        Moves the actuator to the position specified in microsteps.
        '''
        if position < 0:
            return
        self._send_request(20,actuator,position)

    def get_actuator_count(self):
        '''
        Return the number of Zaber actuators connected in a chain.
        '''
        data = 123
        actuator = None
        response = self._send_request_get_response(55,actuator,data)
        try:
            actuator_count = len(response)
        except TypeError:
            actuator_count = 1
        return actuator_count

    def move_relative(self,position,actuator=None):
        '''
        Moves the actuator by the positive or negative number of microsteps specified.
        '''
        self._send_request(21,actuator,position)

    def move_at_speed(self,speed,actuator=None):
        '''
        Moves the actuator at a constant speed until stop is commanded or a limit is reached.
        '''
        self._send_request(22,actuator,speed)

    def stop(self,actuator=None):
        '''
        Stops the device from moving by preempting any move instruction.
        '''
        self._send_request(23,actuator)

    def restore_settings(self):
        '''
        Restores the device settings to the factory defaults.
        '''
        self._send_request(36,None)

    def get_actuator_id(self):
        '''
        Returns the id number for the type of actuator connected.
        '''
        actuator = None
        response = self._send_request_get_response(50,actuator)
        return response

    def _return_setting(self,setting,actuator):
        '''
        Returns the current value of the specified setting.
        '''
        response = self._send_request_get_response(53,actuator,setting)
        return response

    def _get_microstep_resolution(self):
        '''
        Returns the number of microsteps per step.
        '''
        actuator = None
        response = self._return_setting(37,actuator)
        return response

    def set_running_current(self,current,actuator=None):
        '''
        Sets the desired current to be used when the actuator is moving. (1-100)
        '''
        if (current < CURRENT_MIN) or (current > CURRENT_MAX):
            raise ZaberError('current must be between {0} and {1}'.format(CURRENT_MIN,CURRENT_MAX))
        zaber_current = self._map(current,CURRENT_MIN,CURRENT_MAX,ZABER_CURRENT_MIN,ZABER_CURRENT_MAX)
        self._send_request(38,actuator,zaber_current)

    def get_running_current(self):
        '''
        Returns the desired current to be used when the actuator is moving. (1-100)
        '''
        actuator = None
        response = self._return_setting(38,actuator)
        response = self._map_list(response,ZABER_CURRENT_MIN,ZABER_CURRENT_MAX,CURRENT_MIN,CURRENT_MAX)
        return response

    def set_hold_current(self,current,actuator=None):
        '''
        Sets the desired current to be used when the actuator is holding its position. (1-100)
        '''
        if (current < CURRENT_MIN) or (current > CURRENT_MAX):
            raise ZaberError('current must be between {0} and {1}'.format(CURRENT_MIN,CURRENT_MAX))
        zaber_current = self._map(current,CURRENT_MIN,CURRENT_MAX,ZABER_CURRENT_MIN,ZABER_CURRENT_MAX)
        self._send_request(39,actuator,zaber_current)

    def get_hold_current(self):
        '''
        Returns the desired current to be used when the actuator is holding its position. (1-100)
        '''
        actuator = None
        response = self._return_setting(39,actuator)
        response = self._map_list(response,ZABER_CURRENT_MIN,ZABER_CURRENT_MAX,CURRENT_MIN,CURRENT_MAX)
        return response

    def _set_actuator_mode(self,mode,actuator=None):
        '''
        Sets the mode for the given actuator.
        '''
        self._send_request(40,actuator,mode)

    def _get_actuator_mode(self):
        '''
        Returns the mode.
        '''
        actuator = None
        response = self._return_setting(40,actuator)
        return response

    def get_actuator_mode(self):
        '''
        Returns the mode as binary string.
        '''
        actuator = None
        response = self._return_setting(40,actuator)
        response = ["{0:b}".format(r) for r in response]
        return response

    def _set_actuator_mode_bit(self,bit,actuator=None):
        '''
        Sets the mode bit high, leaving all other mode bits unchanged.
        '''
        mode_list = self._get_actuator_mode()
        if actuator is None:
            mode = mode_list[0]
        else:
            actuator = int(actuator)
            mode = mode_list[actuator]
        mode |= 1 << bit
        self._set_actuator_mode(mode,actuator)

    def _clear_actuator_mode_bit(self,bit,actuator=None):
        '''
        Sets the mode bit low, leaving all other mode bits unchanged.
        '''
        mode_list = self._get_actuator_mode()
        if actuator is None:
            mode = mode_list[0]
        else:
            actuator = int(actuator)
            mode = mode_list[actuator]
        mode &= ~(1 << bit)
        self._set_actuator_mode(mode,actuator)

    def disable_potentiometer(self,actuator=None):
        '''
        Disables the potentiometer preventing manual adjustment.
        '''
        self._set_actuator_mode_bit(3,actuator)

    def enable_potentiometer(self,actuator=None):
        '''
        Enables the potentiometer allowing manual adjustment.
        '''
        self._clear_actuator_mode_bit(3,actuator)

    def disable_power_led(self,actuator=None):
        '''
        Disables the green power LED.
        '''
        self._set_actuator_mode_bit(14,actuator)

    def enable_power_led(self,actuator=None):
        '''
        Enables the green power LED.
        '''
        self._clear_actuator_mode_bit(14,actuator)

    def disable_serial_led(self,actuator=None):
        '''
        Disables the green serial LED.
        '''
        self._set_actuator_mode_bit(15,actuator)

    def enable_serial_led(self,actuator=None):
        '''
        Enables the green serial LED.
        '''
        self._clear_actuator_mode_bit(15,actuator)

    def homed(self):
        '''
        Returns home status.
        '''
        mode_list = self._get_actuator_mode()
        home_status_list = [((1 << 7) & mode) for mode in mode_list]
        return [bool(home_status) for home_status in home_status_list]

    def set_home_speed(self,speed,actuator=None):
        '''
        Sets the speed at which the actuator moves when using the "Home" command.
        '''
        self._send_request(41,actuator,speed)

    def get_home_speed(self):
        '''
        Returns the speed at which the actuator moves when using the "Home" command.
        '''
        actuator = None
        response = self._return_setting(41,actuator)
        return response

    def set_target_speed(self,speed,actuator=None):
        '''
        Sets the speed at which the actuator moves when using "move_absolute" or "move_relative" commands.
        '''
        self._send_request(42,actuator,speed)

    def get_target_speed(self):
        '''
        Returns the speed at which the actuator moves when using "move_absolute" or "move_relative" commands.
        '''
        actuator = None
        response = self._return_setting(42,actuator)
        return response

    def set_acceleration(self,acceleration,actuator=None):
        '''
        Sets the acceleration used by the movement commands.
        '''
        self._send_request(43,actuator,acceleration)

    def get_acceleration(self):
        '''
        Returns the acceleration used by the movement commands.
        '''
        actuator = None
        response = self._return_setting(43,actuator)
        return response

    def set_home_offset(self,offset,actuator=None):
        '''
        Sets the the new "Home" position which can then be used when the Home command is issued. 
        '''
        self._send_request(47,actuator,offset)

    def get_home_offset(self):
        '''
        Returns the offset to which the actuator moves when using the "Home" command.
        '''
        actuator = None
        response = self._return_setting(47,actuator)
        return response

    def get_alias(self):
        '''
        Returns the alternate device numbers for the actuators.
        '''
        actuator = None
        response = self._return_setting(48,actuator)
        response_corrected = []
        for r in response:
            if r > 0:
                response_corrected.append(r-1)
            else:
                response_corrected.append(None)
        return response_corrected

    def set_alias(self,actuator,alias):
        '''
        Sets the alternate device numbers for the actuator.
        '''
        actuator_count = self.get_actuator_count()
        if (actuator < 0) or (actuator > actuator_count):
            raise ZaberError('actuator must be between {0} and {1}'.format(0,actuator_count))
        if (alias < ALIAS_MIN) or (alias > ALIAS_MAX):
            raise ZaberError('alias must be between {0} and {1}'.format(ALIAS_MIN,ALIAS_MAX))
        self._send_request(48,actuator,alias+1)

    def remove_alias(self,actuator=None):
        '''
        Removes the alternate device number for the actuator.
        '''
        response = self._send_request_get_response(48,actuator,0)
        return response

    def moving(self):
        '''
        Returns True if actuator is moving, False otherwise
        '''
        actuator = None
        response = self._send_request_get_response(54,actuator)
        response = [bool(r) for r in response]
        return response

    def echo_data(self,data):
        '''
        Echoes back the same Command Data that was sent.
        '''
        actuator = None
        response = self._send_request_get_response(55,actuator,data)
        try:
            response = response[0]
        except TypeError:
            pass
        return response

    def get_position(self):
        '''
        Returns the current absolute position of the actuator in microsteps.
        '''
        actuator = None
        response = self._send_request_get_response(60,actuator)
        return response

    def set_serial_number(self,serial_number):
        '''
        Sets serial number. Useful for talking communicating with ZaberDevices on multiple serial ports.
        '''
        actuator = None
        # write
        data = 1 << 7
        address = SERIAL_NUMBER_ADDRESS
        data += address
        serial_number = int(serial_number)
        serial_number = serial_number << 8
        data += serial_number
        self._send_request(35,actuator,data)

    def get_serial_number(self):
        '''
        Gets serial number. Useful for talking communicating with ZaberDevices on multiple serial ports.
        '''
        actuator = None
        # read
        data = 0 << 7
        address = SERIAL_NUMBER_ADDRESS
        data += address
        response = self._send_request_get_response(35,actuator,data)
        response = response[0]
        response = response >> 8
        return response

    def get_zaber_response(self):
        return self._zaber_response

    def _map_list(self,x_list,in_min,in_max,out_min,out_max):
        return [int((x-in_min)*(out_max-out_min)/(in_max-in_min)+out_min) for x in x_list]

    def _map(self,x,in_min,in_max,out_min,out_max):
        return int((x-in_min)*(out_max-out_min)/(in_max-in_min)+out_min)
Example #10
0
class uPythonHelperUI:
    # ----------------------------------------------------------------------
    # | MainFrame
    # | ---------------------------------------  --------------------------- |
    # | | TermFrame                           |  | File Frame              | |
    # | | ---------------------------------   |  | ----------------------- | |
    # | | | PortFrame                     |   |  | | Transfer Frame      | | |
    # | | ---------------------------------   |  | ----------------------- | |
    # | | ---------------------------------   |  | ----------------------- | |
    # | | | TermView                      |   |  | | FileView            | | |
    # | | ---------------------------------   |  | ----------------------- | |
    # | ---------------------------------------  --------------------------- |
    # ----------------------------------------------------------------------
    def __init__(self):
        self.dev = None
        self.pauseUpdate = True
        self.fileView = None
        self.uploadButton = None
        self.downloadButton = None
        self.deleteButton = None
        self.connectButton = None
        self.portName = None
        self.termArea = None
        self.progress = None
        self.connected = False

        root = Tk()

        mainFrame = Frame(root)
        root.title("uPython Helper")
        mainFrame.pack(padx=10, pady=10)

        # Terminal Side
        termFrame = Frame(mainFrame)
        termFrame.pack(padx=1, pady=1, side=LEFT)

        portFrame = Frame(termFrame)
        portFrame.pack(padx=1, pady=1, side=TOP, fill=X)

        Label(portFrame, text='Port/Device').pack(side=LEFT)

        self.portName = Entry(portFrame)
        self.portName.pack(padx=1, pady=1, side=LEFT)

        #If not Windows, user might try the following:
        # '/dev/ttyUSB0' # Linux
        # '/dev/tty.usbmodem262471' # Mac OS X
        self.portName.insert(0, 'COM3')

        self.connectButton = Button(portFrame,
                                    text='Connect',
                                    command=self.onConnect)
        self.connectButton.pack(padx=1, pady=1, side=LEFT)

        self.progress = ttk.Progressbar(portFrame,
                                        orient="horizontal",
                                        length=200,
                                        mode="determinate")
        self.progress.pack()

        self.termArea = tkst.ScrolledText(master=termFrame,
                                          wrap=WORD,
                                          width=100,
                                          height=30,
                                          padx=10,
                                          pady=10,
                                          takefocus=True,
                                          background='lightgrey')
        self.termArea.bind('<Key>', self.handleInput)
        self.termArea.bind('<Return>', self.handleInput)
        self.termArea.pack(padx=2, pady=2, side=BOTTOM)

        # File area
        self.fileFrame = Frame(mainFrame)
        self.fileFrame.pack(padx=2, pady=2, side=RIGHT, fill=Y)

        transferFrame = Frame(self.fileFrame)
        transferFrame.pack(padx=1, pady=1, side=TOP, fill=X)

        self.uploadButton = Button(transferFrame,
                                   text='Upload File',
                                   command=self.uploadFile,
                                   state=DISABLED)
        self.uploadButton.pack(padx=1, pady=1, side=LEFT)

        self.downloadButton = Button(transferFrame,
                                     text='Download File',
                                     command=self.downloadFile,
                                     state=DISABLED)
        self.downloadButton.pack(padx=1, pady=1, side=LEFT)

        self.deleteButton = Button(transferFrame,
                                   text='Delete File',
                                   command=self.deleteFile,
                                   state=DISABLED)
        self.deleteButton.pack(padx=1, pady=1, side=LEFT)

        self.fileView = Treeview(self.fileFrame)
        self.fileView["columns"] = "bytes"
        self.fileView.heading("#0", text="filename", anchor=W)
        self.fileView.column("bytes", width=75)
        self.fileView.heading("bytes", text="bytes", anchor=W)
        self.fileView.pack(side=RIGHT, fill=Y)

        termUpdateTimer = None

        def updateDataIfNeeded():
            global termUpdateTimer
            if not self.pauseUpdate:
                if self.dev:
                    if self.dev.in_waiting:
                        resp = self.dev.read_all()
                        self.write(resp)
            termUpdateTimer = threading.Timer(0.5, updateDataIfNeeded)
            termUpdateTimer.start()

        termUpdateTimer = threading.Timer(0.5, updateDataIfNeeded)
        termUpdateTimer.start()

        def on_closing():
            global termUpdateTimer
            root.destroy()
            termUpdateTimer.cancel()

        def make_menu(w):
            global the_menu
            the_menu = Menu(w, tearoff=0)
            the_menu.add_command(label="Copy")
            the_menu.add_command(label="Paste")

        def show_menu(e):
            w = e.widget
            the_menu.entryconfigure(
                "Copy", command=lambda: w.event_generate("<<Copy>>"))
            the_menu.entryconfigure(
                "Paste", command=lambda: w.event_generate("<<Paste>>"))
            the_menu.tk.call("tk_popup", the_menu, e.x_root, e.y_root)

        make_menu(root)

        self.termArea.bind("<Button-3><ButtonRelease-3>", show_menu)

        root.protocol("WM_DELETE_WINDOW", on_closing)
        root.mainloop()

    def remote(self, cmd, ignoreResult=False):
        asc = cmd.encode('ascii', 'ignore')
        self.pauseUpdate = True
        self.dev.write(asc)
        if ignoreResult:
            self.pauseUpdate = False
            return
        sleep(0.5)
        res = self.dev.read_all()
        self.pauseUpdate = False
        return res

    def write(self, txt):
        self.termArea.insert(END, txt)

    def handleInput(self, inputData):
        if self.connected:
            self.remote(inputData.char, ignoreResult=True)
        return "break"

    @staticmethod
    def ascii(uni):
        return uni.encode('ascii', 'ignore')

    def getDirInfo(self):
        self.remote("import os\r\n")
        resp = self.remote("os.listdir()\r\n")
        resp = resp.decode().split("\r\n")[1]
        entries = eval(resp)
        result = []
        for entry in entries:
            resp = self.remote(
                "statinfo = os.stat('{}')\r\nstatinfo\r\n".format(entry))
            resp = resp.decode().split("\r\n")[2]
            statinfo = eval(resp)
            result.append({'name': entry, 'size': statinfo[6]})
        return result

    def uploadFile(self):
        def thrProc():
            filename = filedialog.askopenfile(mode='r')
            if filename is None:
                return
            self.progressStart()
            f = open(filename.name, 'rb')
            bytesRead = f.read()
            b64 = base64.b64encode(bytesRead)
            fn = os.path.split(filename.name)[1]
            self.remote('import ubinascii\r\n')
            self.remote('f = open("{}","w")\r\n'.format(fn))
            b64Len = len(b64)
            chunkSize = 1024
            for i in range(0, b64Len // chunkSize):
                sub = b64[chunkSize * i:(chunkSize * (i + 1))].decode()
                self.remote('s = "{}"\r\n'.format(sub))
                self.remote('s2 = ubinascii.a2b_base64(s)\r\n')
                self.remote('f.write(s2)\r\n', True)
            if b64Len % chunkSize:
                sub = b64[b64Len // chunkSize * chunkSize:b64Len].decode()
                self.remote('s = "{}"\r\n'.format(sub))
                self.remote('s2 = ubinascii.a2b_base64(s)\r\n')
                self.remote('f.write(s2)\r\n', True)
            self.remote('f.close()\r\n', True)
            self.clearFileView()
            self.populateFileView()
            self.progressStop()

        t1 = threading.Thread(target=thrProc)
        t1.start()

    def downloadFile(self):
        def thrProc():
            selected = self.fileView.selection()
            if not selected:
                showwarning("Download", "Please select an item.")
                return
            if len(selected) > 1:
                showwarning("Download", "Please select a single item.")
                return
            sel = self.fileView.item(selected[0])

            filename = filedialog.asksaveasfile(mode='w',
                                                initialfile=sel['text'])
            if filename is None:
                return

            self.progressStart()
            fLocal = open(filename.name, 'wb')
            remoteName = sel['text']
            remoteSize = sel['values'][0]
            self.remote('import ubinascii\r\n')
            self.remote('f = open("{}","r")\r\n'.format(remoteName))

            for i in range(0, remoteSize // 1024):
                self.remote("data = f.read(1024)\r\n")
                self.remote('s2 = ubinascii.b2a_base64(data)\r\n')
                resp = self.remote('print(s2)\r\n')
                resp = resp.decode().split("\r\n")[1][2:].split("\\")[0]
                chunkBin = base64.b64decode(resp)
                fLocal.write(chunkBin)

            remainder = remoteSize % 1024
            if remainder:
                self.remote("data = f.read({})\r\n".format(remainder))
                self.remote('s2 = ubinascii.b2a_base64(data)\r\n')
                resp = self.remote('print(s2)\r\n')
                resp = resp.decode().split("\r\n")[1][2:].split("\\")[0]
                tailBin = base64.b64decode(resp)
                fLocal.write(tailBin)
            fLocal.close()
            self.remote('f.close()\r\n')
            self.progressStop()

        t1 = threading.Thread(target=thrProc)
        t1.start()

    def deleteFile(self):
        def thrProc():
            selected = self.fileView.selection()
            if not selected:
                showwarning("Delete", "Please select an item to delete.")
                return
            if len(selected) > 1:
                showwarning("Delete", "Please select a single item.")
                return

            sel = self.fileView.item(selected[0])
            res = askquestion("Delete",
                              'Are you sure you want to delete "{}"?'.format(
                                  sel['text']),
                              icon='warning')
            if res != 'yes':
                return

            self.progressStart()
            self.remote('import os\r\n')
            self.remote('os.remove("{}")\r\n'.format(sel['text']))
            self.fileView.delete(selected[0])
            self.progressStop()

        t1 = threading.Thread(target=thrProc)
        t1.start()

    def populateFileView(self):
        info = self.getDirInfo()
        for item in info:
            values = item['size']
            self.fileView.insert('',
                                 'end',
                                 iid=None,
                                 text=item['name'],
                                 values=values)

    def clearFileView(self):
        self.fileView.delete(*self.fileView.get_children())

    def progressStart(self):
        self.progress["mode"] = "indeterminate"
        self.progress.start(10)

    def progressStop(self):
        self.progress.stop()
        self.progress["mode"] = "determinate"

    def onConnect(self):
        def thrproc():
            if self.connectButton.cget('relief') == 'raised':
                self.progressStart()
                try:
                    port = self.portName.get()
                    self.dev = SerialDevice(port=port)
                except Exception as e:
                    if self.downloadButton:
                        self.downloadButton.config(state=DISABLED)
                        self.uploadButton.config(state=DISABLED)
                        self.deleteButton.config(state=DISABLED)
                    showwarning("Connect",
                                "Problem opening serial.  Details:\n" + str(e))
                    return

                self.dev.baudrate = 115200
                self.connectButton.config(relief=SUNKEN)
                # send ctrl-c to break any potentially running python program
                ctrlc = chr(3)
                self.remote(ctrlc)
                self.populateFileView()
                self.termArea.insert(
                    END,
                    ">>> ")  # write out the initial prompt... purely cosmetic
                self.termArea.config(background='white')
                self.downloadButton.config(state=NORMAL)
                self.uploadButton.config(state=NORMAL)
                self.deleteButton.config(state=NORMAL)
                self.connected = True
            else:
                self.connected = False
                if self.dev is not None:
                    self.dev.close()
                self.dev = None
                self.connectButton.config(relief=RAISED)
                self.downloadButton.config(state=DISABLED)
                self.deleteButton.config(state=DISABLED)
                self.uploadButton.config(state=DISABLED)
                self.clearFileView()
                self.termArea.config(background='lightgrey')

            self.progressStop()

        t1 = threading.Thread(target=thrproc)
        t1.start()
class MightexDevice(object):
    '''
    This Python package (mightex_device) creates a class named MightexDevice,
    which contains an instance of serial_device2.SerialDevice and adds
    methods to it to interface to Mightex LED controllers.

    Example Usage:

    dev = MightexDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = MightexDevice(port='/dev/ttyUSB0') # Linux
    dev = MightexDevice(port='/dev/tty.usbmodem262471') # Mac OS X
    dev = MightexDevice(port='COM3') # Windows
    dev.get_serial_number()
    '04-150824-007'
    dev.get_channel_count()
    4
    channel = 0 # channel numbering starts at 0, not 1!
    dev.get_mode(channel)
    'disable'
    dev.set_normal_parameters(channel,1000,30)
    dev.get_normal_parameters(channel)
    {'current': 30, 'current_max': 1000}
    dev.set_mode_normal(channel)
    dev.get_load_voltage(channel)
    2622
    dev.set_normal_current(channel,200)
    dev.get_load_voltage(channel)
    3054
    dev.set_mode_disable(channel)
    dev.set_strobe_parameters(channel,100,1)
    dev.get_strobe_parameters(channel)
    {'current_max': 100, 'repeat': 1}
    dev.set_strobe_profile_set_point(channel,0,100,1000000)
    dev.set_strobe_profile_set_point(channel,1,10,500000)
    dev.set_strobe_profile_set_point(channel,2,0,0)
    profile = dev.get_strobe_profile(channel)
    profile
    [{'current': 100, 'duration': 1000000},
     {'current': 10, 'duration': 500000},
     {'current': 0, 'duration': 0}]
    dev.set_mode_strobe(channel)
    dev.get_strobe_profile(channel+1)
    [{'current': 20, 'duration': 1000},
     {'current': 10, 'duration': 1000},
     {'current': 0, 'duration': 0}]
    dev.set_strobe_profile(channel+1,profile)
    dev.get_strobe_profile(channel+1)
    dev.set_mode_strobe(channel+1)
    dev.set_trigger_parameters(channel,100,True)
    dev.get_trigger_parameters(channel)
    {'current_max': 100, 'falling_edge': True}
    dev.set_trigger_profile_set_point(channel,0,100,1000000)
    dev.set_trigger_profile_set_point(channel,1,10,500000)
    dev.set_trigger_profile_set_point(channel,2,0,0)
    dev.get_trigger_profile(channel)
    [{'current': 100, 'duration': 1000000},
     {'current': 10, 'duration': 500000},
     {'current': 0, 'duration': 0}]
    dev.set_mode_trigger(channel)
    dev.reset()
    dev.get_trigger_profile(channel)
    [{'current': 20, 'duration': 1000},
     {'current': 10, 'duration': 1000},
     {'current': 0, 'duration': 0}]
    dev.set_trigger_profile_set_point(channel,0,100,1000000)
    dev.set_trigger_profile_set_point(channel,1,10,500000)
    dev.set_trigger_profile_set_point(channel,2,0,0)
    dev.store_parameters()
    dev.reset()
    dev.get_trigger_profile(channel)
    [{'current': 100, 'duration': 1000000},
     {'current': 10, 'duration': 500000},
     {'current': 0, 'duration': 0}]
    dev.restore_factory_defaults()
    dev.store_parameters()
    dev.reset()
    dev.get_trigger_profile(channel)
    [{'current': 10, 'duration': 20},
     {'current': 0, 'duration': 0}]
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0

    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_mightex_device_port(baudrate=kwargs['baudrate'],
                                             try_ports=try_ports,
                                             debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_mightex_device)
        self._lock = threading.Lock()
        self._strict_error = False
        self._channel_count = -1
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_mightex_device(self):
        pass

    def _args_to_request(self,*args):
        request = ' '.join(map(str,args))
        request = request + '\n\r';
        return request

    def _send_request(self,*args):
        '''
        Sends request to device over serial port and
        returns number of bytes written.
        '''
        lock_acquired = self._lock.acquire()
        try:
            request = self._args_to_request(*args)
            self._debug_print('request', request)
            bytes_written = self._serial_device.write_check_freq(request,delay_write=True)
        finally:
            self._lock.release()
        return bytes_written

    def _send_request_get_response(self,*args):
        '''
        Sends request to device over serial port and
        returns response.
        '''
        lock_acquired = self._lock.acquire()
        try:
            request = self._args_to_request(*args)
            self._debug_print('request', request)
            response = self._serial_device.write_read(request,use_readline=True,check_write_freq=False,delay_write=True)
        finally:
            self._lock.release()
        response = response.strip()
        if '#!' in response:
            if self._strict_error:
                raise MightexError('The command is valid and executed, but an error occurred during execution.')
        elif '#?' in response:
            raise MightexError('The latest command is a valid command but the argument is NOT in valid range.')
        response = response.replace('#','')
        return response

    def close(self):
        '''
        Close the device serial port.
        '''
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def set_strict_error(self,strict_error):
        self._strict_error = strict_error

    def get_device_info(self):
        '''
        Get device_info.
        '''
        request = self._args_to_request('DEVICEINFO')
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        if 'Mightex' not in response:
            # try again just in case
            response = self._send_request_get_response(request)
            if 'Mightex' not in response:
                raise MightexError('"Mightex" not in device_info.')
        return response

    def get_serial_number(self):
        '''
        Get serial_number.
        '''
        device_info = self.get_device_info()
        serial_number_str = 'Serial No.:'
        p = re.compile(serial_number_str+'\d+-?\d+-?\d+')
        found_list = p.findall(device_info)
        if len(found_list) != 1:
            raise MightexError('serial_number not found in device_info.')
        else:
            serial_number = found_list[0]
            serial_number = serial_number.replace(serial_number_str,'')
            return serial_number

    def get_mode(self,channel):
        '''
        Get channel mode. Modes = ['DISABLE','NORMAL','STROBE','TRIGGER']
        '''
        channel = int(channel) + 1
        request = self._args_to_request('?MODE',channel)
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        if response == '0':
            return 'DISABLE'
        elif response == '1':
            return 'NORMAL'
        elif response == '2':
            return 'STROBE'
        elif response == '3':
            return 'TRIGGER'
        else:
            raise MightexError('Unknown response: {0}'.format(response))

    def get_all_modes(self):
        '''
        Get all channel modes.
        '''
        modes = []
        for channel in range(self.get_channel_count()):
            mode = self.get_mode(channel)
            modes.append(mode)
        return modes

    def get_channel_count(self):
        '''
        Get channel count.
        '''
        if self._channel_count > 0:
            return self._channel_count
        channel_count = 0
        while True:
            try:
                channel_count += 1
                mode = self.get_mode(channel_count)
            except MightexError:
                break
        channel_count -= 1
        self._channel_count = channel_count
        return channel_count

    def set_mode_disable(self,channel):
        '''
        Set DISABLE mode.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('MODE',channel,0)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_mode_disable(self):
        '''
        Set DISABLE mode for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_mode_disable(channel)

    def set_mode_normal(self,channel):
        '''
        Set NORMAL mode.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('MODE',channel,1)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_mode_normal(self):
        '''
        Set NORMAL mode for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_mode_normal(channel)

    def set_mode_strobe(self,channel):
        '''
        Set STROBE mode.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('MODE',channel,2)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_mode_strobe(self):
        '''
        Set STROBE mode for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_mode_strobe(channel)

    def set_mode_trigger(self,channel):
        '''
        Set TRIGGER mode.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('MODE',channel,3)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_mode_trigger(self):
        '''
        Set TRIGGER mode for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_mode_trigger(channel)

    def set_normal_parameters(self,channel,current_max,current):
        '''
        Set NORMAL mode parameters. current_max is the maximum current
        allowed for NORMAL mode, in mA. current is the working current
        for NORMAL mode, in mA.
        '''
        channel = int(channel) + 1
        current_max = int(current_max)
        current = int(current)
        request = self._args_to_request('NORMAL',channel,current_max,current)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_normal_parameters(self,current_max,current):
        '''
        Set normal parameters for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_normal_parameters(channel,current_max,current)

    def set_normal_current(self,channel,current):
        '''
        Set the working current for NORMAL mode, in mA.
        '''
        channel = int(channel) + 1
        current = int(current)
        request = self._args_to_request('CURRENT',channel,current)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_normal_current(self,current):
        '''
        Set normal current for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_normal_current(channel,current)

    def get_normal_parameters(self,channel):
        '''
        Get NORMAL mode parameters. current_max is the maximum current
        allowed for NORMAL mode, in mA. current is the working current
        for NORMAL mode, in mA.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('?CURRENT',channel)
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        response_list = response.split(' ')
        parameters = {}
        parameters['current_max'] = int(response_list[-2])
        parameters['current'] = int(response_list[-1])
        return parameters

    def get_all_normal_parameters(self):
        '''
        get normal parameters for all channels.
        '''
        parameters_list = []
        for channel in range(self.get_channel_count()):
            parameters = self.get_normal_parameters(channel)
            parameters_list.append(parameters)
        return parameters_list

    def set_strobe_parameters(self,channel,current_max,repeat):
        '''
        Set STROBE mode parameters. current_max is the maximum current
        allowed for STROBE mode, in mA. repeat is the Repeat Count for
        running the profile. It can be from 0 to 99999999. And the
        number 9999 is special, it means repeat forever. Note that
        when it is 0, the programmed wave form will output once, when
        it is 1, the wave form will be repeated once, which will be
        output twice and so on.
        '''
        channel = int(channel) + 1
        current_max = int(current_max)
        repeat = int(repeat)
        request = self._args_to_request('STROBE',channel,current_max,repeat)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_strobe_parameters(self,current_max,repeat):
        '''
        Set strobe parameters for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_strobe_parameters(channel,current_max,current)

    def set_strobe_profile_set_point(self,channel,set_point,current,duration):
        '''
        Each channel has a programmable profile for STROBE mode. The
        profile contains 128 set_point values (0-127), and each
        set_point has current(mA)/duration(us) pair. A ZERO/ZERO pair
        means it is the end of the profile. If user does not program
        the profile for a certain channel, the default is all
        Zero/Zero pairs, which means the channel is always OFF. Use
        this method over and over to set a customized profile and
        then enter STROBE mode with the set_mode_strobe method. The
        profile will be executed (repeatedly) after device enters (or
        reenters) the STROBE mode.
        '''
        channel = int(channel) + 1
        set_point = int(set_point)
        current = int(current)
        duration = int(duration)
        request = self._args_to_request('STRP',channel,set_point,current,duration)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def get_strobe_parameters(self,channel):
        '''
        Get STROBE mode parameters. current_max is the maximum current
        allowed for STROBE mode, in mA. repeat is the Repeat Count for
        running the profile. It can be from 0 to 99999999. And the
        number 9999 is special, it means repeat forever. Note that
        when it is 0, the programmed wave form will output once, when
        it is 1, the wave form will be repeated once, which will be
        output twice and so on.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('?STROBE',channel)
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        response_list = response.split(' ')
        parameters = {}
        parameters['current_max'] = int(response_list[0])
        parameters['repeat'] = int(response_list[1])
        return parameters

    def get_all_strobe_parameters(self):
        '''
        get strobe parameters for all channels.
        '''
        parameters_list = []
        for channel in range(self.get_channel_count()):
            parameters = self.get_strobe_parameters(channel)
            parameters_list.append(parameters)
        return parameters_list

    def get_strobe_profile(self,channel):
        '''
        Each channel has a programmable profile for STROBE mode. The
        profile contains 128 set_point values (0-127), and each
        set_point has current(mA)/duration(us) pair. A ZERO/ZERO pair
        means it is the end of the profile. If user does not program
        the profile for a certain channel, the default is all
        Zero/Zero pairs, which means the channel is always OFF.
        '''
        request = self._args_to_request('?STRP',channel)
        self._debug_print('request', request)
        self._send_request_get_response(request)
        current = None
        duration = None
        profile = []
        while not ((current == 0) and (duration == 0)):
            response = self._serial_device.readline()
            response = response.strip()
            response = response.replace('#','')
            response_list = response.split(' ')
            self._debug_print('strobe_profile set_point',response_list)
            try:
                current = int(response_list[0])
                duration = int(response_list[1])
                profile_set_point = {}
                profile_set_point['current'] = current
                profile_set_point['duration'] = duration
                profile.append(profile_set_point)
            except:
                pass
        return profile

    def set_strobe_profile(self,channel,profile):
        '''
        Each channel has a programmable profile for STROBE mode. The
        profile contains up to 128 set_point values (0-127), and each
        set_point has current(mA)/duration(us) pair. profile example:
        [{'current': 100, 'duration': 1000000},
         {'current': 10, 'duration': 500000},
         {'current': 0, 'duration': 0}]
        '''
        set_point = 0
        for set_point_parameters in profile:
            self.set_strobe_profile_set_point(channel,set_point,**set_point_parameters)
            set_point += 1

    def set_trigger_parameters(self,channel,current_max,falling_edge):
        '''
        Set TRIGGER mode parameters. current_max is the maximum current
        allowed for TRIGGER mode, in mA. When falling_edge is True,
        the falling edge of external trigger signal asserts. When
        falling_edge is False, the rising edge of the external trigger
        signal asserts.
        '''
        channel = int(channel) + 1
        current_max = int(current_max)
        polarity = int(bool(falling_edge))
        request = self._args_to_request('TRIGGER',channel,current_max,polarity)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def set_all_trigger_parameters(self,current_max,falling_edge):
        '''
        Set trigger parameters for all channels.
        '''
        for channel in range(self.get_channel_count()):
            self.set_trigger_parameters(channel,current_max,falling_edge)

    def set_trigger_profile_set_point(self,channel,set_point,current,duration):
        '''
        Each channel has a programmable profile for TRIGGER mode. The
        profile contains 128 set_point values (0-127), and each
        set_point has current(mA)/duration(us) pair. A ZERO/ZERO pair
        means it is the end of the profile. If user does not program
        the profile for a certain channel, the default is all
        Zero/Zero pairs, which means the channel is always OFF. Use
        this method over and over to set a customized profile and then
        enter TRIGGER mode with the set_mode_trigger method. The
        profile will be executed while an external trigger occurs and
        the device is in TRIGGER mode.
        '''
        channel = int(channel) + 1
        set_point = int(set_point)
        current = int(current)
        duration = int(duration)
        request = self._args_to_request('TRIGP',channel,set_point,current,duration)
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def get_trigger_parameters(self,channel):
        '''
        Get TRIGGER mode parameters. current_max is the maximum current
        allowed for TRIGGER mode, in mA. When falling_edge is True,
        the falling edge of external trigger signal asserts. When
        falling_edge is False, the rising edge of the external trigger
        signal asserts.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('?TRIGGER',channel)
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        response_list = response.split(' ')
        parameters = {}
        parameters['current_max'] = int(response_list[0])
        parameters['falling_edge'] = bool(int(response_list[1]))
        return parameters

    def get_all_trigger_parameters(self):
        '''
        get trigger parameters for all channels.
        '''
        parameters_list = []
        for channel in range(self.get_channel_count()):
            parameters = self.get_trigger_parameters(channel)
            parameters_list.append(parameters)
        return parameters_list

    def get_trigger_profile(self,channel):
        '''
        Each channel has a programmable profile for TRIGGER mode. The
        profile contains 128 set_point values (0-127), and each
        set_point has current(mA)/duration(us) pair. A ZERO/ZERO pair
        means it is the end of the profile. If user does not program
        the profile for a certain channel, the default is all
        Zero/Zero pairs, which means the channel is always OFF.
        '''
        request = self._args_to_request('?TRIGP',channel)
        self._debug_print('request', request)
        self._send_request_get_response(request)
        current = None
        duration = None
        profile = []
        while not ((current == 0) and (duration == 0)):
            response = self._serial_device.readline()
            response = response.strip()
            response = response.replace('#','')
            response_list = response.split(' ')
            self._debug_print('trigger_profile set_point',response_list)
            try:
                current = int(response_list[0])
                duration = int(response_list[1])
                profile_set_point = {}
                profile_set_point['current'] = current
                profile_set_point['duration'] = duration
                profile.append(profile_set_point)
            except:
                pass
        return profile

    def get_load_voltage(self,channel):
        '''
        For XV Module (e.g. AV04 or SV04), use this method to get the
        voltage on the load of the specified channel. It will return
        the voltage in mV.  Note: As the controller polls the load
        voltage in a 20ms interval, this feature is proper for NORMAL
        mode or slow STROBE mode only.
        '''
        channel = int(channel) + 1
        request = self._args_to_request('LoadVoltage',channel)
        self._debug_print('request', request)
        response = self._send_request_get_response(request)
        channel_str = "{0}:".format(channel)
        response = response.replace(channel_str,'')
        response = int(response)
        return response

    def get_all_load_voltages(self):
        '''
        get load voltages for all channels.
        '''
        voltages = []
        for channel in range(self.get_channel_count()):
            voltage = self.get_load_voltage(channel)
            voltages.append(parameters)
        return voltages

    def reset(self):
        '''
        Soft reset device.
        '''
        request = self._args_to_request('Reset')
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def restore_factory_defaults(self):
        '''
        This method will reset the device mode and all related parameters
        to the factory defaults. Note that these parameters become the
        current device settings in volatile memory, use the
        "store_parameters" method to save the current settings to
        non-volatile memory.
        '''
        request = self._args_to_request('RESTOREDEF')
        self._debug_print('request', request)
        self._send_request_get_response(request)

    def store_parameters(self):
        '''
        This method will store the current settings in volatile memory to
        non-volatile memory.
        '''
        request = self._args_to_request('STORE')
        self._debug_print('request', request)
        self._send_request_get_response(request)
class BioshakeDevice(object):
    '''
    This Python package (bioshake_device) creates a class named
    BioshakeDevice, which contains an instance of
    serial_device2.SerialDevice and adds methods to it to interface to
    Q.instruments BioShake devices.

    Example Usage:

    dev = BioshakeDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = BioshakeDevice(port='/dev/ttyUSB0') # Linux
    dev = BioshakeDevice(port='/dev/tty.usbmodem262471') # Mac OS X
    dev = BioshakeDevice(port='COM3') # Windows
    dev.get_description()
    dev.shake_on(speed_target=1000) # speed_target (rpm)
    dev.get_shake_actual_speed()
    dev.shake_off()
    dev.temp_on(temp_target=45) # temp_target (°C)
    dev.get_temp_actual()
    dev.temp_off()
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0
    _DEFAULT_SPEED_TARGET = 1000
    _SHAKE_STATE_DESCRIPTIONS = {
        0: 'Shaking is active',
        1: 'Shaker has a stop command detect',
        2: 'Shaker in the braking mode',
        3: 'Arrived in the home position',
        4: 'Manual mode',
        5: 'Acceleration',
        6: 'Deceleration',
        7: 'Deceleration with stopping',
        90: 'ECO mode',
        99: 'Boot process running',
        -1: '',
    }
    _ELM_STATE_DESCRIPTIONS = {
        1: 'Microplate is locked',
        3: 'Microplate is unlocked',
        9: 'Error',
        -1: '',
    }

    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_bioshake_device_port(baudrate=kwargs['baudrate'],
                                              try_ports=try_ports,
                                              debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_bioshake_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_bioshake_device(self):
        pass

    def _args_to_request(self,*args):
        request = ''.join(map(str,args))
        request = request + '\r';
        return request

    def _send_request(self,*args):

        '''Sends request to bioshake device over serial port and
        returns number of bytes written'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        bytes_written = self._serial_device.write_check_freq(request,delay_write=True)
        return bytes_written

    def _send_request_get_response(self,*args):

        '''Sends request to device over serial port and
        returns response'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        response = self._serial_device.write_read(request,use_readline=True,check_write_freq=True)
        response = response.strip()
        if (response == 'e'):
            request = request.strip()
            raise BioshakeError(request)
        return response

    def close(self):
        '''
        Close the device serial port.
        '''
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def info(self):
        '''
        Listing of general information.
        '''
        return self._send_request_get_response('info')

    def get_version(self):
        '''
        Send back the current version number.
        '''
        return self._send_request_get_response('getVersion')

    def get_description(self):
        '''
        Send back the current model information.
        '''
        return self._send_request_get_response('getDescription')

    def reset_device(self):
        '''
        Restart the controller.
        '''
        return self._send_request_get_response('resetDevice')

    def get_error_list(self):
        '''
        Return a semicolon separated list with warnings and errors that
        occurred.
        '''
        return self._send_request_get_response('getErrorList')

    def set_eco_mode(self):
        '''
        Switch the shaker into economical mode. It will reduce electricity
        consumption by switching off the solenoid for the home
        position and the deactivation of the ELM function. Warning:
        No home position!!! ELM is in locked position!!!
        '''
        return self._send_request_get_response('setEcoMode')

    def leave_eco_mode(self):
        '''
        Leave the economical mode and change in the normal operating state
        with finding the home position.
        '''
        return self._send_request_get_response('leaveEcoMode')

    def shake_on(self,speed_target=_DEFAULT_SPEED_TARGET):
        '''
        Start the shaking at the target speed (rpm) or with the default
        speed if no speed_target provided.
        '''
        response = self._set_shake_speed_target(speed_target)
        if response is not None:
            return self._send_request_get_response('shakeOn')

    def shake_on_with_runtime(self,runtime,speed_target=_DEFAULT_SPEED_TARGET):
        '''
        Shake for runtime duration (s) at the speed_target (rpm) or with
        the default speed_target if none provided. Allowable runtime
        range: 0 – 99999 seconds.
        '''
        response = self._set_shake_speed_target(speed_target)
        if response is not None:
            return self._send_request_get_response('shakeOnWithRuntime'+str(int(runtime)))

    def get_shake_remaining_time(self):
        '''
        Return the remaining time in seconds.
        '''
        return int(float(self._send_request_get_response('getShakeRemainingTime')))

    def shake_off(self):
        '''
        Stop the shaking and return to the home position.
        '''
        return self._send_request_get_response('shakeOff')

    def shake_emergency_off(self):
        '''
        High-Speed stop for the shaking. Warning: No defined home
        position !!!
        '''
        return self._send_request_get_response('shakeEmergencyOff')

    def shake_go_home(self):
        '''
        Shaker goes to the home position and lock in.
        '''
        return self._send_request_get_response('shakeGoHome')

    def get_shake_state(self):
        '''
        Return the state of shaking.
        '''
        shake_state_value = self._send_request_get_response('getShakeState')
        if len(shake_state_value) > 0:
            shake_state_value = int(shake_state_value)
        else:
            shake_state_value = -1
        return {'value': shake_state_value,
                'description': self._SHAKE_STATE_DESCRIPTIONS[shake_state_value]}

    def get_shake_speed_target(self):
        '''
        Return the target mixing speed. (rpm)
        '''
        return int(float(self._send_request_get_response('getShakeTargetSpeed')))

    def _set_shake_speed_target(self,speed_target=_DEFAULT_SPEED_TARGET):
        '''
        Set the target mixing speed. Allowable range: 200 – 3000 rpm
        '''
        if (speed_target >= 200) and (speed_target <= 3000):
            return self._send_request_get_response('setShakeTargetSpeed'+str(int(speed_target)))
        else:
            print(self._set_shake_speed_target.__doc__)

    def get_default_shake_speed_target(self):
        '''
        Get the default mixing speed. (rpm)
        '''
        return self._DEFAULT_SPEED_TARGET

    def get_shake_speed_actual(self):
        '''
        Return the current mixing speed. (rpm)
        '''
        return int(float(self._send_request_get_response('getShakeActualSpeed')))

    def get_shake_speed_min(self):
        '''
        Return the least shake_speed set point.
        '''
        return int(float(self._send_request_get_response('getShakeMinRpm')))

    def get_shake_speed_max(self):
        '''
        Return the biggest shake_speed set point.
        '''
        return int(float(self._send_request_get_response('getShakeMaxRpm')))

    def get_shake_acceleration(self):
        '''
        Return the acceleration/deceleration value. (seconds)
        '''
        return int(float(self._send_request_get_response('getShakeAcceleration')))

    def set_shake_acceleration(self,acceleration):
        '''
        Set the acceleration/deceleration value in seconds. Allowable
        range: 0 - 10 seconds
        '''
        if (acceleration >= 0) and (acceleration <= 10):
            return self._send_request_get_response('setShakeAcceleration'+str(acceleration))
        else:
            print(self.set_shake_acceleration.__doc__)

    def temp_on(self,temp_target):
        '''
        Activate the temperature control. temp_target allowed range: 0 – 99.0 (°C)
        '''
        response = self._set_temp_target(temp_target)
        if response is not None:
            return self._send_request_get_response('tempOn')

    def temp_off(self):
        '''
        Deactivate the temperature control.
        '''
        return self._send_request_get_response('tempOff')

    def get_temp_target(self):
        '''
        Return the target temperature. (°C)
        '''
        return float(self._send_request_get_response('getTempTarget'))

    def _set_temp_target(self,temp_target):
        '''
        Set the target temperature in °C allowed range: 0 – 99.0 in °C
        '''
        if (temp_target >= 0) and (temp_target <= 99):
            return self._send_request_get_response('setTempTarget'+str(int(round(temp_target*10))))
        else:
            print(self.set_temp_target.__doc__)

    def get_temp_actual(self):
        '''
        Return the actual temperature. (°C)
        '''
        return float(self._send_request_get_response('getTempActual'))

    def get_temp_min(self):
        '''
        Return the least set point of temperature. (°C)
        '''
        return float(self._send_request_get_response('getTempMin'))

    def get_temp_max(self):
        '''
        Return the biggest set point of temperature. (°C)
        '''
        return float(self._send_request_get_response('getTempMax'))

    def set_elm_lock_pos(self):
        '''
        Close the Edge Locking Mechanism (ELM).
        The microplate is locked.
        '''
        return self._send_request_get_response('setElmLockPos')

    def set_elm_unlock_pos(self):
        '''
        Opens the Edge Locking Mechanism (ELM) for gripping of microplates.
        The microplate is not locked.
        '''
        return self._send_request_get_response('setElmUnlockPos')

    def get_elm_state(self):
        '''
        Return the state of Edge Locking Mechanism (ELM).
        '''
        elm_state_value = self._send_request_get_response('getElmState')
        if len(elm_state_value) > 0:
            elm_state_value = int(elm_state_value)
        else:
            elm_state_value = -1
        return {'value': elm_state_value,
                'description': self._ELM_STATE_DESCRIPTIONS[elm_state_value]}
Example #13
0
    def __init__(self,config_file_path,quick_test=False,no_hardware=False,*args,**kwargs):
        self._TIMEOUT = 0.05
        self._WRITE_WRITE_DELAY = 0.05
        self._RESET_DELAY = 2.0
        self._RELAY_COUNT = 8
        self._CAMERA_TRIGGER_DUTY_CYCLE = 50
        self._BOARD_INDICATOR_LIGHT_FREQUENCY = 2
        self._BOARD_INDICATOR_LIGHT_DUTY_CYCLE = 50
        self._quick_test = quick_test
        if not self._quick_test:
            self._MILLISECONDS_PER_SECOND = 1000
        else:
            self._MILLISECONDS_PER_SECOND = 1000/3600
        self._SECONDS_PER_MINUTE = 60
        self._MINUTES_PER_HOUR = 60
        self._HOURS_PER_DAY = 24
        self._MILLISECONDS_PER_HOUR = self._MILLISECONDS_PER_SECOND*self._SECONDS_PER_MINUTE*self._MINUTES_PER_HOUR
        self._MILLISECONDS_PER_DAY = self._MILLISECONDS_PER_HOUR*self._HOURS_PER_DAY

        self._METHOD_ID_START_PWM = 0
        self._METHOD_ID_STOP_ALL_PULSES = 1
        self._METHOD_ID_GET_POWER = 2
        self._METHOD_ID_GET_PWM_STATUS = 3

        self._PWM_STOPPED = 0
        self._PWM_RUNNING = 1

        self._POWER_MAX = 255

        self._config_file_path = os.path.abspath(config_file_path)
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            # kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        with open(config_file_path,'r') as config_stream:
            self._config = yaml.load(config_stream)
        os_type = platform.system()
        if os_type == 'Linux':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['linux']
            except KeyError:
                raise RuntimeError('Must specify linux serial port in config file!')
        elif os_type == 'Windows':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['windows']
            except KeyError:
                raise RuntimeError('Must specify windows serial port in config file!')
        elif os_type == 'Darwin':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['osx']
            except KeyError:
                raise RuntimeError('Must specify osx serial port in config file!')
        t_start = time.time()
        self._no_hardware = no_hardware
        if not self._no_hardware:
            self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_sleep_assay)
        time.sleep(self._RESET_DELAY)
        self._csv_file_path = None
        self._csv_file = None
        self._csv_writer = None
        self._video_frame = -1
        self._state = 'initialization'
        self._header = ['video_frame',
                        'date_time',
                        'state',
                        'white_light_power',
                        'red_light_pwm_status',
                        'red_light_power']
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))
Example #14
0
class SleepAssay(object):
    '''
    '''
    def __init__(self,config_file_path,quick_test=False,no_hardware=False,*args,**kwargs):
        self._TIMEOUT = 0.05
        self._WRITE_WRITE_DELAY = 0.05
        self._RESET_DELAY = 2.0
        self._RELAY_COUNT = 8
        self._CAMERA_TRIGGER_DUTY_CYCLE = 50
        self._BOARD_INDICATOR_LIGHT_FREQUENCY = 2
        self._BOARD_INDICATOR_LIGHT_DUTY_CYCLE = 50
        self._quick_test = quick_test
        if not self._quick_test:
            self._MILLISECONDS_PER_SECOND = 1000
        else:
            self._MILLISECONDS_PER_SECOND = 1000/3600
        self._SECONDS_PER_MINUTE = 60
        self._MINUTES_PER_HOUR = 60
        self._HOURS_PER_DAY = 24
        self._MILLISECONDS_PER_HOUR = self._MILLISECONDS_PER_SECOND*self._SECONDS_PER_MINUTE*self._MINUTES_PER_HOUR
        self._MILLISECONDS_PER_DAY = self._MILLISECONDS_PER_HOUR*self._HOURS_PER_DAY

        self._METHOD_ID_START_PWM = 0
        self._METHOD_ID_STOP_ALL_PULSES = 1
        self._METHOD_ID_GET_POWER = 2
        self._METHOD_ID_GET_PWM_STATUS = 3

        self._PWM_STOPPED = 0
        self._PWM_RUNNING = 1

        self._POWER_MAX = 255

        self._config_file_path = os.path.abspath(config_file_path)
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            # kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        with open(config_file_path,'r') as config_stream:
            self._config = yaml.load(config_stream)
        os_type = platform.system()
        if os_type == 'Linux':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['linux']
            except KeyError:
                raise RuntimeError('Must specify linux serial port in config file!')
        elif os_type == 'Windows':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['windows']
            except KeyError:
                raise RuntimeError('Must specify windows serial port in config file!')
        elif os_type == 'Darwin':
            try:
                kwargs['port'] = self._config['relay_board_serial_port']['osx']
            except KeyError:
                raise RuntimeError('Must specify osx serial port in config file!')
        t_start = time.time()
        self._no_hardware = no_hardware
        if not self._no_hardware:
            self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_sleep_assay)
        time.sleep(self._RESET_DELAY)
        self._csv_file_path = None
        self._csv_file = None
        self._csv_writer = None
        self._video_frame = -1
        self._state = 'initialization'
        self._header = ['video_frame',
                        'date_time',
                        'state',
                        'white_light_power',
                        'red_light_pwm_status',
                        'red_light_power']
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_sleep_assay(self):
        self.stop()

    def _flatten(self,l):
        out = []
        for item in l:
            if isinstance(item, (list, tuple)):
                out.extend(self._flatten(item))
            else:
                out.append(item)
        return out

    def _args_to_request(self,*args):
        args = self._flatten(args)
        request = ['[', ','.join(map(str,args)), ']']
        request = ''.join(request)
        request = request + '\n';
        return request

    def _send_request(self,*args):

        '''Sends request to device over serial port and
        returns number of bytes written'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        bytes_written = 0
        if not self._no_hardware:
            bytes_written = self._serial_device.write_check_freq(request,
                                                                 delay_write=True)
        return bytes_written

    def _send_request_get_result(self,*args):
        '''
        Sends request to server over serial port and
        returns response result
        '''
        request = self._args_to_request(*args)
        self._debug_print('request', request)
        successful = False
        result = None
        while (not successful) and (not self._no_hardware):
            try:
                response = self._serial_device.write_read(request,use_readline=True,check_write_freq=False)
                self._debug_print('response', response)
                result = json.loads(response)
                successful = True
            except:
                print('Error!','\nrequest:',request)
                time.sleep(0.5)
        return result

    def _close(self):
        '''
        Close the device serial port.
        '''
        if not self._no_hardware:
            self._serial_device.close()

    def _get_port(self):
        if not self._no_hardware:
            return self._serial_device.port
        else:
            return None

    def _start_pwm(self,
                   relay,
                   power,
                   delay,
                   count,
                   pwm_level_count,
                   periods,
                   on_durations):
        '''
        '''
        if relay < 0:
            relay = 0
        elif relay > (self._RELAY_COUNT - 1):
            relay = self._RELAY_COUNT
        power = int(power)
        delay = int(delay)
        count = int(count)
        for pwm_level in range(pwm_level_count):
            periods[pwm_level] = int(periods[pwm_level])
            on_durations[pwm_level] = int(on_durations[pwm_level])
        pwm_info = zip(periods,on_durations)

        self._send_request(self._METHOD_ID_START_PWM,
                           relay,
                           power,
                           delay,
                           count,
                           pwm_level_count,
                           *pwm_info)

    def _stop_all_pulses(self):
        '''
        '''
        self._send_request(self._METHOD_ID_STOP_ALL_PULSES)

    def _get_power(self):
        '''
        '''
        result = self._send_request_get_result(self._METHOD_ID_GET_POWER)
        return result

    def _get_pwm_status(self):
        '''
        '''
        result = self._send_request_get_result(self._METHOD_ID_GET_PWM_STATUS)
        return result

    def _print_datetime(self,dt):
        print('    {0}-{1}-{2}-{3}-{4}-{5}'.format(dt.year,
                                                   dt.month,
                                                   dt.day,
                                                   dt.hour,
                                                   dt.minute,
                                                   dt.second))

    def _start_to_start_datetime(self,start):
        now_datetime = datetime.datetime.now()
        offset = datetime.timedelta(start['offset_days'])
        offset_datetime = now_datetime + offset
        start_datetime = datetime.datetime(offset_datetime.year,
                                           offset_datetime.month,
                                           offset_datetime.day,
                                           start['hour'],
                                           start['minute'])
        delta = start_datetime - now_datetime
        delay = int(1000*delta.total_seconds())
        if delay < 0:
            start_datetime = now_datetime
        return start_datetime

    def _start_datetime_to_delay(self,start_datetime):
        now_datetime = datetime.datetime.now()
        delta = start_datetime - now_datetime
        delay = int(1000*delta.total_seconds())
        if delay < 0:
            delay = 0
        return delay

    def _writerow(self,columns):
        if self._csv_writer is not None:
            utf8row = []
            for col in columns:
                utf8row.append(str(col).encode('utf8'))
            self._csv_writer.writerow(utf8row)

    def _get_date_str(self):
        today = datetime.date.today()
        date_str = "{year}-{month}-{day}".format(year=today.year,
                                                 month=today.month,
                                                 day=today.day)
        return date_str

    def _get_time_str(self):
        localtime = time.localtime()
        time_str = "{hour}-{min}-{sec}".format(hour=localtime.tm_hour,
                                               min=localtime.tm_min,
                                               sec=localtime.tm_sec)
        return time_str

    def _get_date_time_str(self):
        date_str = self._get_date_str()
        time_str = self._get_time_str()
        return date_str + '-' + time_str

    def start_board_indicator_light_cycle(self,relay):
        period = 1000/self._BOARD_INDICATOR_LIGHT_FREQUENCY
        on_duration = (self._BOARD_INDICATOR_LIGHT_DUTY_CYCLE/100)*period
        self._start_pwm(relay,
                        self._POWER_MAX,
                        0,
                        -1,
                        1,
                        [period],
                        [on_duration])

    def start_camera_trigger(self,
                             relay,
                             frame_rate,
                             delay):
        print('start_camera_trigger:')
        print('  relay = {0}'.format(relay))
        print('  frame_rate = {0}'.format(frame_rate))
        period = 1000/frame_rate
        on_duration = (self._CAMERA_TRIGGER_DUTY_CYCLE/100)*period
        self._start_pwm(relay,
                        self._POWER_MAX,
                        delay,
                        -1,
                        1,
                        [period],
                        [on_duration])

    def start_data_writer(self):
        if self._csv_writer is None:
            user_home_dir = os.path.expanduser('~')
            date_str = self._get_date_str()
            output_dir = os.path.join(user_home_dir,'sleep_assay_data',date_str)
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)
            date_time_str = self._get_date_time_str()
            self._csv_file_path = os.path.join(output_dir,date_time_str + '-data.txt')

            self._csv_file = open(self._csv_file_path, 'w')

            # Create a new csv writer object to use as the output formatter
            self._csv_writer = csv.writer(self._csv_file,quotechar='\"',quoting=csv.QUOTE_MINIMAL)
            self._writerow(self._header)
            return self._csv_file_path

    def stop(self):
        self._stop_all_pulses()

    def _duration_days_to_duration_datetime(self,duration_days):
        duration_datetime = datetime.timedelta(duration_days/(1000/self._MILLISECONDS_PER_SECOND))
        return duration_datetime

    def start_entrainment(self,start_datetime,config):
        print('entrainment:')
        print('  start:')
        self._print_datetime(start_datetime)
        duration_days = config['duration_days']

        if duration_days <= 0:
            end_datetime = start_datetime
            print('  end:')
            self._print_datetime(end_datetime)
            return end_datetime

        relay = self._config['relays']['white_light']
        power = config['white_light']['power']
        pwm0_on_duration_hours = config['white_light']['pwm0_on_duration_hours']
        pwm0_off_duration_hours = config['white_light']['pwm0_off_duration_hours']
        pwm0_period_hours = pwm0_on_duration_hours + pwm0_off_duration_hours
        pwm0_period = pwm0_period_hours*self._MILLISECONDS_PER_HOUR
        pwm0_on_duration = pwm0_on_duration_hours*self._MILLISECONDS_PER_HOUR
        pwm0_period_days = pwm0_period_hours/self._HOURS_PER_DAY
        count = duration_days/pwm0_period_days
        delay = self._start_datetime_to_delay(start_datetime)
        self._start_pwm(relay,
                        power,
                        delay,
                        count,
                        1,
                        [pwm0_period],
                        [pwm0_on_duration])
        duration_datetime = self._duration_days_to_duration_datetime(duration_days)
        end_datetime = start_datetime + duration_datetime
        print('  end:')
        self._print_datetime(end_datetime)
        return end_datetime

    def start_experiment_run(self,run,start_datetime,config):
        print('experiment run {0}:'.format(run))
        print('  start:')
        self._print_datetime(start_datetime)
        duration_days = config['duration_days']

        if duration_days <= 0:
            end_datetime = start_datetime
            print('  end:')
            self._print_datetime(end_datetime)
            return end_datetime

        # white light
        if 'white_light' in config:
            relay = self._config['relays']['white_light']
            power = config['white_light']['power']
            pwm0_on_duration_hours = config['white_light']['pwm0_on_duration_hours']
            pwm0_off_duration_hours = config['white_light']['pwm0_off_duration_hours']
            pwm0_period = (pwm0_on_duration_hours + pwm0_off_duration_hours)*self._MILLISECONDS_PER_HOUR
            pwm0_on_duration = pwm0_on_duration_hours*self._MILLISECONDS_PER_HOUR
            delay_days = 0
            if 'delay_days' in config['white_light']:
                delay_days = config['white_light']['delay_days']
            duration_datetime = self._duration_days_to_duration_datetime(delay_days)
            white_start_datetime = start_datetime + duration_datetime
            delay = self._start_datetime_to_delay(white_start_datetime)
            if ('pwm1_on_duration_days' in config['white_light']) and ('pwm1_off_duration_days' in config['white_light']):
                pwm1_on_duration_days = config['white_light']['pwm1_on_duration_days']
                pwm1_off_duration_days = config['white_light']['pwm1_off_duration_days']
                pwm1_period_days = pwm1_on_duration_days + pwm1_off_duration_days
                pwm1_period = pwm1_period_days*self._MILLISECONDS_PER_DAY
                pwm1_on_duration = pwm1_on_duration_days*self._MILLISECONDS_PER_DAY
                count = math.ceil((duration_days - delay_days)/pwm1_period_days)
                self._start_pwm(relay,
                                power,
                                delay,
                                count,
                                2,
                                [pwm0_period,pwm1_period],
                                [pwm0_on_duration,pwm1_on_duration])
            else:
                pwm0_period_days = (pwm0_on_duration_hours + pwm0_off_duration_hours)/self._HOURS_PER_DAY
                count = math.ceil((duration_days - delay_days)/pwm0_period_days)
                self._start_pwm(relay,
                                power,
                                delay,
                                count,
                                1,
                                [pwm0_period],
                                [pwm0_on_duration])

        # red light
        if 'red_light' in config:
            relay = self._config['relays']['red_light']
            power = config['red_light']['power']
            pwm0_frequency = config['red_light']['pwm0_frequency_hz']
            pwm0_duty_cycle = config['red_light']['pwm0_duty_cycle_percent']
            pwm0_period = 1000/pwm0_frequency
            pwm0_on_duration = (pwm0_duty_cycle/100)*pwm0_period
            pwm1_on_duration_hours = config['red_light']['pwm1_on_duration_hours']
            pwm1_off_duration_hours = config['red_light']['pwm1_off_duration_hours']
            pwm1_period_hours = pwm1_on_duration_hours + pwm1_off_duration_hours
            pwm1_period = pwm1_period_hours*self._MILLISECONDS_PER_HOUR
            pwm1_on_duration = pwm1_on_duration_hours*self._MILLISECONDS_PER_HOUR
            pwm1_period_days = pwm1_period_hours/self._HOURS_PER_DAY
            delay_days = 0
            if 'delay_days' in config['red_light']:
                delay_days = config['red_light']['delay_days']
            count = math.ceil((duration_days - delay_days)/pwm1_period_days)
            duration_datetime = self._duration_days_to_duration_datetime(delay_days)
            red_start_datetime = start_datetime + duration_datetime
            delay = self._start_datetime_to_delay(red_start_datetime)
            self._start_pwm(relay,
                            power,
                            delay,
                            count,
                            2,
                            [pwm0_period,pwm1_period],
                            [pwm0_on_duration,pwm1_on_duration])

        duration_datetime = self._duration_days_to_duration_datetime(duration_days)
        end_datetime = start_datetime + duration_datetime
        print('  end:')
        self._print_datetime(end_datetime)
        return end_datetime

    def start_recovery(self,start_datetime,config):
        print('recovery:')
        print('  start:')
        self._print_datetime(start_datetime)
        duration_days = config['duration_days']

        if duration_days <= 0:
            end_datetime = start_datetime
            print('  end:')
            self._print_datetime(end_datetime)
            return end_datetime

        relay = self._config['relays']['white_light']
        power = config['white_light']['power']
        pwm0_on_duration_hours = config['white_light']['pwm0_on_duration_hours']
        pwm0_off_duration_hours = config['white_light']['pwm0_off_duration_hours']
        pwm0_period_hours = pwm0_on_duration_hours + pwm0_off_duration_hours
        pwm0_period = pwm0_period_hours*self._MILLISECONDS_PER_HOUR
        pwm0_on_duration = pwm0_on_duration_hours*self._MILLISECONDS_PER_HOUR
        pwm0_period_days = pwm0_period_hours/self._HOURS_PER_DAY
        count = duration_days/pwm0_period_days
        delay = self._start_datetime_to_delay(start_datetime)
        self._start_pwm(relay,
                        power,
                        delay,
                        count,
                        1,
                        [pwm0_period],
                        [pwm0_on_duration])
        duration_datetime = self._duration_days_to_duration_datetime(duration_days)
        end_datetime = start_datetime + duration_datetime
        print('  end:')
        self._print_datetime(end_datetime)
        return end_datetime

    def _write_data(self):
        if self._no_hardware:
            time.sleep(1/self._config['camera_trigger']['frame_rate_hz'])
            return
        time_start = time.time()
        pwm_status = self._get_pwm_status()
        camera_trigger_on = pwm_status[self._config['relays']['camera_trigger']][1]
        if camera_trigger_on:
            power = self._get_power()
            white_light_pwm_status = pwm_status[self._config['relays']['white_light']][0:3]
            white_light_power = power[self._config['relays']['white_light']]
            red_light_pwm_status = pwm_status[self._config['relays']['red_light']][1]
            red_light_power = power[self._config['relays']['red_light']]
            self._video_frame += 1
            date_time = self._get_date_time_str()
            if ((self._white_light_power_prev != white_light_power) or
                (self._red_light_pwm_status_prev != red_light_pwm_status) or
                (self._red_light_power_prev != red_light_power) or
                (self._state_prev != self._state)):
                # if ((not self._prev_written) and
                #     (self._white_light_power_prev is not None) and
                #     (self._red_light_pwm_status_prev is not None) and
                #     (self._red_light_power_prev is not None) and
                #     (self._date_time_prev is not None)):
                #     row = []
                #     row.append(self._video_frame - 1)
                #     row.append(self._date_time_prev)
                #     row.append(self._state_prev)
                #     row.append(self._white_light_power_prev)
                #     row.append(self._red_light_pwm_status_prev)
                #     row.append(self._red_light_power_prev)
                #     self._writerow(row)
                row = []
                row.append(self._video_frame)
                row.append(date_time)
                row.append(self._state)
                row.append(white_light_power)
                row.append(red_light_pwm_status)
                row.append(red_light_power)
                self._writerow(row)
                self._prev_written = True
            else:
                self._prev_written = False
            self._white_light_power_prev = white_light_power
            self._red_light_pwm_status_prev = red_light_pwm_status
            self._red_light_power_prev = red_light_power
            self._state_prev = self._state
            self._date_time_prev = date_time

        time_stop = time.time()
        time_sleep = 1/self._config['camera_trigger']['frame_rate_hz'] - (time_stop - time_start)
        if time_sleep > 0:
            time.sleep(time_sleep)

    def plot_data(self,data_file_path):
        if self._no_hardware:
            return
        print('data_file_path: {0}'.format(data_file_path))
        fig = plt.figure()
        filename = os.path.split(data_file_path)[1]
        fig.suptitle(filename, fontsize=14, fontweight='bold')
        self._data_file_path = os.path.abspath(data_file_path)
        with open(self._data_file_path,'r') as fid:
            header = fid.readline().rstrip().split(',')

        dt = np.dtype({'names':header,'formats':['S25']*len(header)})
        self._numpy_data = np.loadtxt(self._data_file_path,dtype=dt,delimiter=",",skiprows=1)

        scale_factor = 1000/(self._config['camera_trigger']['frame_rate_hz']*self._MILLISECONDS_PER_DAY)
        t = np.uint32(self._numpy_data['video_frame'])*scale_factor

        # white light
        power = np.uint8(self._numpy_data['white_light_power'])
        y_max = 255
        plt.subplot(2, 1, 1)
        # plt.plot(t, power)
        plt.step(t, power, where='post')
        plt.ylim(-0.1, y_max+45)
        plt.ylabel('white light power')
        plt.xlabel('days')
        duration_days = int(t.max())
        plt.xticks(np.linspace(0, duration_days+1, 2*duration_days+3, endpoint=True))
        plt.grid(True)
        marker_half_thickness = 0.025
        start = 0
        entrainment_duration = self._config['entrainment']['duration_days']
        stop = entrainment_duration - marker_half_thickness
        if entrainment_duration > 0:
            plt.axvspan(start, stop, color='y', alpha=0.5, lw=0)
            plt.text(start + (stop-start)/2, y_max+25, 'entrainment', fontsize=15, horizontalalignment='center')
        start = stop
        stop = start + 2*marker_half_thickness
        plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)
        run = 0
        for run_config in self._config['experiment']:
            start = stop
            run_duration = run_config['duration_days']
            stop = start + run_duration - 2*marker_half_thickness
            if run_duration > 0:
                plt.axvspan(start, stop, color='g', alpha=0.5, lw=0)
                plt.text(start + (stop-start)/2, y_max+25, 'experiment run {0}'.format(run), fontsize=15, horizontalalignment='center')
            start = stop
            stop = start + 2*marker_half_thickness
            plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)
            run += 1
        start = stop
        recovery_duration = self._config['recovery']['duration_days']
        stop = start + recovery_duration - marker_half_thickness
        if recovery_duration > 0:
            plt.axvspan(start, stop, color='r', alpha=0.5, lw=0)
            plt.text(start + (stop-start)/2, y_max+25, 'recovery', fontsize=15, horizontalalignment='center')
        start = stop
        stop = start + marker_half_thickness
        plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)

        # red light
        red_light_pwm_status = np.uint8(self._numpy_data['red_light_pwm_status'])
        y_max = 1
        plt.subplot(2, 1, 2)
        # plt.plot(t, red_light_pwm_status)
        plt.step(t, red_light_pwm_status, where='post')
        plt.ylim(-0.1, y_max+0.25)
        plt.ylabel('red light pwm status')
        plt.xlabel('days')
        duration_days = int(t.max())
        plt.xticks(np.linspace(0, duration_days+1, 2*duration_days+3, endpoint=True))
        plt.grid(True)
        marker_half_thickness = 0.025
        start = 0
        entrainment_duration = self._config['entrainment']['duration_days']
        stop = entrainment_duration - marker_half_thickness
        if entrainment_duration > 0:
            plt.axvspan(start, stop, color='y', alpha=0.5, lw=0)
            plt.text(start + (stop-start)/2, y_max+0.1, 'entrainment', fontsize=15, horizontalalignment='center')
        start = stop
        stop = start + 2*marker_half_thickness
        plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)
        run = 0
        for run_config in self._config['experiment']:
            start = stop
            run_duration = run_config['duration_days']
            stop = start + run_duration - 2*marker_half_thickness
            if run_duration > 0:
                plt.axvspan(start, stop, color='g', alpha=0.5, lw=0)
                plt.text(start + (stop-start)/2, y_max+0.1, 'experiment run {0}'.format(run), fontsize=15, horizontalalignment='center')
            start = stop
            stop = start + 2*marker_half_thickness
            plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)
            run += 1
        start = stop
        recovery_duration = self._config['recovery']['duration_days']
        stop = start + recovery_duration - marker_half_thickness
        if recovery_duration > 0:
            plt.axvspan(start, stop, color='r', alpha=0.5, lw=0)
            plt.text(start + (stop-start)/2, y_max+0.1, 'recovery', fontsize=15, horizontalalignment='center')
        start = stop
        stop = start + marker_half_thickness
        plt.axvspan(start, stop, color='k', alpha=0.5, lw=0)

        plt.show()

    def run(self):
        self.stop()
        print('config_file_path:')
        print(self._config_file_path)
        print('data_file_path:')
        data_file_path = self.start_data_writer()
        print(data_file_path)
        self.start_board_indicator_light_cycle(self._config['relays']['board_indicator_light'])
        if self._quick_test:
            self._config['start']['offset_days'] = -1
            if self._config['camera_trigger']['frame_rate_hz'] <= 1:
                self._config['camera_trigger']['frame_rate_hz'] *= 10
        camera_trigger_start_datetime = self._start_to_start_datetime(self._config['start'])

        entrainment_duration_datetime = self._duration_days_to_duration_datetime(self._config['entrainment']['duration_days'])
        end_datetime = camera_trigger_start_datetime + entrainment_duration_datetime
        for run in range(len(self._config['experiment'])):
            run_duration_datetime = self._duration_days_to_duration_datetime(self._config['experiment'][run]['duration_days'])
            end_datetime += run_duration_datetime
        recovery_duration_datetime = self._duration_days_to_duration_datetime(self._config['recovery']['duration_days'])
        end_datetime += recovery_duration_datetime
        print('sleep assay will run until:')
        self._print_datetime(end_datetime)

        delay = self._start_datetime_to_delay(camera_trigger_start_datetime)
        self._video_frame = -1
        self.start_camera_trigger(self._config['relays']['camera_trigger'],
                                  self._config['camera_trigger']['frame_rate_hz'],
                                  delay)
        self._state = 'entrainment'
        entrainment_end_datetime = self.start_entrainment(camera_trigger_start_datetime,
                                                          self._config['entrainment'])
        self._white_light_power_prev = None
        self._red_light_pwm_status_prev = None
        self._red_light_power_prev = None
        self._state_prev = None
        self._prev_written = False
        self._date_time_prev = None
        while(datetime.datetime.now() < entrainment_end_datetime):
            self._write_data()

        run_start_datetime = entrainment_end_datetime
        for run in range(len(self._config['experiment'])):
            self._state = 'experiment_run{0}'.format(run)
            run_end_datetime = self.start_experiment_run(run,
                                                         run_start_datetime,
                                                         self._config['experiment'][run])
            while(datetime.datetime.now() < run_end_datetime):
                self._write_data()
            run_start_datetime = run_end_datetime

        self._state = 'recovery'
        recovery_start_datetime = run_end_datetime
        recovery_end_datetime = self.start_recovery(recovery_start_datetime,
                                                    self._config['recovery'])
        while(datetime.datetime.now() < recovery_end_datetime):
            self._write_data()

        self._csv_file.close()
        self._stop_all_pulses()
        self.plot_data(self._csv_file_path)
class MettlerToledoDevice(object):
    '''
    This Python package (mettler_toledo_device) creates a class named
    MettlerToledoDevice, which contains an instance of
    serial_device2.SerialDevice and adds methods to it to interface to
    Mettler Toledo balances and scales that use the Mettler Toledo
    Standard Interface Command Set (MT-SICS).

    Example Usage:

    dev = MettlerToledoDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = MettlerToledoDevice(port='/dev/ttyUSB0') # Linux specific port
    dev = MettlerToledoDevice(port='/dev/tty.usbmodem262471') # Mac OS X specific port
    dev = MettlerToledoDevice(port='COM3') # Windows specific port
    dev.get_serial_number()
    1126493049
    dev.get_balance_data()
    ['XS204', 'Excellence', '220.0090', 'g']
    dev.get_weight_stable()
    [-0.0082, 'g'] #if weight is stable
    None  #if weight is dynamic
    dev.get_weight()
    [-0.6800, 'g', 'S'] #if weight is stable
    [-0.6800, 'g', 'D'] #if weight is dynamic
    dev.zero_stable()
    True  #zeros if weight is stable
    False  #does not zero if weight is not stable
    dev.zero()
    'S'   #zeros if weight is stable
    'D'   #zeros if weight is dynamic
    '''
    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0

    def __init__(self,*args,**kwargs):
        if 'debug' in kwargs:
            self.debug = kwargs['debug']
        else:
            kwargs.update({'debug': DEBUG})
            self.debug = DEBUG
        if 'try_ports' in kwargs:
            try_ports = kwargs.pop('try_ports')
        else:
            try_ports = None
        if 'baudrate' not in kwargs:
            kwargs.update({'baudrate': BAUDRATE})
        elif (kwargs['baudrate'] is None) or (str(kwargs['baudrate']).lower() == 'default'):
            kwargs.update({'baudrate': BAUDRATE})
        if 'timeout' not in kwargs:
            kwargs.update({'timeout': self._TIMEOUT})
        if 'write_write_delay' not in kwargs:
            kwargs.update({'write_write_delay': self._WRITE_WRITE_DELAY})
        if ('port' not in kwargs) or (kwargs['port'] is None):
            port =  find_mettler_toledo_device_port(baudrate=kwargs['baudrate'],
                                                    try_ports=try_ports,
                                                    debug=kwargs['debug'])
            kwargs.update({'port': port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args,**kwargs)
        atexit.register(self._exit_mettler_toledo_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print('Initialization time =', (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_mettler_toledo_device(self):
        pass

    def _args_to_request(self,*args):
        request = ''.join(map(str,args))
        request = request + '\r\n';
        return request

    def _send_request(self,*args):

        '''Sends request to device over serial port and
        returns number of bytes written'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        bytes_written = self._serial_device.write_check_freq(request,delay_write=True)
        return bytes_written

    def _send_request_get_response(self,*args):

        '''Sends request to device over serial port and
        returns response'''

        request = self._args_to_request(*args)
        self._debug_print('request', request)
        response = self._serial_device.write_read(request,use_readline=True,check_write_freq=True)
        response = response.replace('"','')
        response_list = response.split()
        if 'ES' in response_list[0]:
            raise MettlerToledoError('Syntax Error!')
        elif 'ET' in response_list[0]:
            raise MettlerToledoError('Transmission Error!')
        elif 'EL' in response_list[0]:
            raise MettlerToledoError('Logical Error!')
        return response_list

    def close(self):
        '''
        Close the device serial port.
        '''
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def get_commands(self):
        '''
        Inquiry of all implemented MT-SICS commands.
        '''
        response = self._send_request_get_response('I0')
        if 'I' in response[1]:
            raise MettlerToledoError('The list cannot be sent at present as another operation is taking place.')
        return response[2:]

    def get_mtsics_level(self):
        '''
        Inquiry of MT-SICS level and MT-SICS versions.
        '''
        response = self._send_request_get_response('I1')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        return response[2:]

    def get_balance_data(self):
        '''
        Inquiry of balance data.
        '''
        response = self._send_request_get_response('I2')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        return response[2:]

    def get_software_version(self):
        '''
        Inquiry of balance SW version and type definition number.
        '''
        response = self._send_request_get_response('I3')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        return response[2:]

    def get_serial_number(self):
        '''
        Inquiry of serial number.
        '''
        response = self._send_request_get_response('I4')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        return response[2]

    def get_software_id(self):
        '''
        Inquiry of SW-Identification number.
        '''
        response = self._send_request_get_response('I5')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        return response[2]

    def get_weight_stable(self):
        '''
        Send the current stable net weight value.
        '''
        try:
            response = self._send_request_get_response('S')
            if 'I' in response[1]:
                raise MettlerToledoError('Command understood, not executable at present.')
            elif '+' in response[1]:
                raise MettlerToledoError('Balance in overload range.')
            elif '-' in response[1]:
                raise MettlerToledoError('Balance in underload range.')
            response[2] = float(response[2])
            return response[2:]
        except:
            pass

    def get_weight(self):
        '''
        Send the current net weight value, irrespective of balance stability.
        '''
        response = self._send_request_get_response('SI')
        if 'I' in response[1]:
            raise MettlerToledoError('Command understood, not executable at present.')
        elif '+' in response[1]:
            raise MettlerToledoError('Balance in overload range.')
        elif '-' in response[1]:
            raise MettlerToledoError('Balance in underload range.')
        response.append(response[1])
        response[2] = float(response[2])
        return response[2:]

    def zero_stable(self):
        '''
        Zero the balance.
        '''
        try:
            response = self._send_request_get_response('Z')
            if 'I' in response[1]:
                raise MettlerToledoError('Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached).')
            elif '+' in response[1]:
                raise MettlerToledoError('Upper limit of zero setting range exceeded.')
            elif '-' in response[1]:
                raise MettlerToledoError('Lower limit of zero setting range exceeded.')
            return True
        except:
            return False

    def zero(self):
        '''
        Zero the balance immediately regardless the stability of the balance.
        '''
        response = self._send_request_get_response('ZI')
        if 'I' in response[1]:
            raise MettlerToledoError('Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached).')
        elif '+' in response[1]:
            raise MettlerToledoError('Upper limit of zero setting range exceeded.')
        elif '-' in response[1]:
            raise MettlerToledoError('Lower limit of zero setting range exceeded.')
        return response[1]

    def reset(self):
        '''
        Resets the balance to the condition found after switching on, but without a zero setting being performed.
        '''
        self._send_request('@')
class MettlerToledoDevice(object):
    """
    This Python package (mettler_toledo_device) creates a class named
    MettlerToledoDevice, which contains an instance of
    serial_device2.SerialDevice and adds methods to it to interface to
    Mettler Toledo balances and scales that use the Mettler Toledo
    Standard Interface Command Set (MT-SICS).

    Example Usage:

    dev = MettlerToledoDevice() # Might automatically find device if one available
    # if it is not found automatically, specify port directly
    dev = MettlerToledoDevice(port='/dev/ttyUSB0') # Linux specific port
    dev = MettlerToledoDevice(port='/dev/tty.usbmodem262471') # Mac OS X specific port
    dev = MettlerToledoDevice(port='COM3') # Windows specific port
    dev.get_serial_number()
    1126493049
    dev.get_balance_data()
    ['XS204', 'Excellence', '220.0090', 'g']
    dev.get_weight_stable()
    [-0.0082, 'g'] #if weight is stable
    None  #if weight is dynamic
    dev.get_weight()
    [-0.6800, 'g', 'S'] #if weight is stable
    [-0.6800, 'g', 'D'] #if weight is dynamic
    dev.zero_stable()
    True  #zeros if weight is stable
    False  #does not zero if weight is not stable
    dev.zero()
    'S'   #zeros if weight is stable
    'D'   #zeros if weight is dynamic
    """

    _TIMEOUT = 0.05
    _WRITE_WRITE_DELAY = 0.05
    _RESET_DELAY = 2.0

    def __init__(self, *args, **kwargs):
        if "debug" in kwargs:
            self.debug = kwargs["debug"]
        else:
            kwargs.update({"debug": DEBUG})
            self.debug = DEBUG
        if "try_ports" in kwargs:
            try_ports = kwargs.pop("try_ports")
        else:
            try_ports = None
        if "baudrate" not in kwargs:
            kwargs.update({"baudrate": BAUDRATE})
        elif (kwargs["baudrate"] is None) or (str(kwargs["baudrate"]).lower() == "default"):
            kwargs.update({"baudrate": BAUDRATE})
        if "timeout" not in kwargs:
            kwargs.update({"timeout": self._TIMEOUT})
        if "write_write_delay" not in kwargs:
            kwargs.update({"write_write_delay": self._WRITE_WRITE_DELAY})
        if ("port" not in kwargs) or (kwargs["port"] is None):
            port = find_mettler_toledo_device_port(
                baudrate=kwargs["baudrate"], try_ports=try_ports, debug=kwargs["debug"]
            )
            kwargs.update({"port": port})

        t_start = time.time()
        self._serial_device = SerialDevice(*args, **kwargs)
        atexit.register(self._exit_mettler_toledo_device)
        time.sleep(self._RESET_DELAY)
        t_end = time.time()
        self._debug_print("Initialization time =", (t_end - t_start))

    def _debug_print(self, *args):
        if self.debug:
            print(*args)

    def _exit_mettler_toledo_device(self):
        pass

    def _args_to_request(self, *args):
        request = "".join(map(str, args))
        request = request + "\r\n"
        return request

    def _send_request(self, *args):

        """Sends request to device over serial port and
        returns number of bytes written"""

        request = self._args_to_request(*args)
        self._debug_print("request", request)
        bytes_written = self._serial_device.write_check_freq(request, delay_write=True)
        return bytes_written

    def _send_request_get_response(self, *args):

        """Sends request to device over serial port and
        returns response"""

        request = self._args_to_request(*args)
        self._debug_print("request", request)
        response = self._serial_device.write_read(request, use_readline=True, check_write_freq=True)
        response = response.decode().replace('"', "")
        response_list = response.split()
        if "ES" in response_list[0]:
            raise MettlerToledoError("Syntax Error!")
        elif "ET" in response_list[0]:
            raise MettlerToledoError("Transmission Error!")
        elif "EL" in response_list[0]:
            raise MettlerToledoError("Logical Error!")
        return response_list

    def close(self):
        """
        Close the device serial port.
        """
        self._serial_device.close()

    def get_port(self):
        return self._serial_device.port

    def get_commands(self):
        """
        Inquiry of all implemented MT-SICS commands.
        """
        response = self._send_request_get_response("I0")
        if "I" in response[1]:
            raise MettlerToledoError("The list cannot be sent at present as another operation is taking place.")
        return response[2:]

    def get_mtsics_level(self):
        """
        Inquiry of MT-SICS level and MT-SICS versions.
        """
        response = self._send_request_get_response("I1")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        return response[2:]

    def get_balance_data(self):
        """
        Inquiry of balance data.
        """
        response = self._send_request_get_response("I2")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        return response[2:]

    def get_software_version(self):
        """
        Inquiry of balance SW version and type definition number.
        """
        response = self._send_request_get_response("I3")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        return response[2:]

    def get_serial_number(self):
        """
        Inquiry of serial number.
        """
        response = self._send_request_get_response("I4")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        return response[2]

    def get_software_id(self):
        """
        Inquiry of SW-Identification number.
        """
        response = self._send_request_get_response("I5")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        return response[2]

    def get_weight_stable(self):
        """
        Send the current stable net weight value.
        """
        try:
            response = self._send_request_get_response("S")
            if "I" in response[1]:
                raise MettlerToledoError("Command understood, not executable at present.")
            elif "+" in response[1]:
                raise MettlerToledoError("Balance in overload range.")
            elif "-" in response[1]:
                raise MettlerToledoError("Balance in underload range.")
            response[2] = float(response[2])
            return response[2:]
        except:
            pass

    def get_weight(self):
        """
        Send the current net weight value, irrespective of balance stability.
        """
        response = self._send_request_get_response("SI")
        if "I" in response[1]:
            raise MettlerToledoError("Command understood, not executable at present.")
        elif "+" in response[1]:
            raise MettlerToledoError("Balance in overload range.")
        elif "-" in response[1]:
            raise MettlerToledoError("Balance in underload range.")
        response.append(response[1])
        response[2] = float(response[2])
        return response[2:]

    def zero_stable(self):
        """
        Zero the balance.
        """
        try:
            response = self._send_request_get_response("Z")
            if "I" in response[1]:
                raise MettlerToledoError(
                    "Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached)."
                )
            elif "+" in response[1]:
                raise MettlerToledoError("Upper limit of zero setting range exceeded.")
            elif "-" in response[1]:
                raise MettlerToledoError("Lower limit of zero setting range exceeded.")
            return True
        except:
            return False

    def zero(self):
        """
        Zero the balance immediately regardless the stability of the balance.
        """
        response = self._send_request_get_response("ZI")
        if "I" in response[1]:
            raise MettlerToledoError(
                "Zero setting not performed (balance is currently executing another command, e.g. taring, or timeout as stability was not reached)."
            )
        elif "+" in response[1]:
            raise MettlerToledoError("Upper limit of zero setting range exceeded.")
        elif "-" in response[1]:
            raise MettlerToledoError("Lower limit of zero setting range exceeded.")
        return response[1]

    def reset(self):
        """
        Resets the balance to the condition found after switching on, but without a zero setting being performed.
        """
        self._send_request("@")