
"""
devices.py contains unified LabJack classes for the U3, U6 and T7.
A common set of methods is implemented, simplifying writing code for any
of the devices listed above.

The classes are built using the existing LabJack python packages:
    - LabJackPython
    - labjack-ljm-python

Note: Use code folding at the method level to improve readability.

Author: Eduardo Nigro
    rev 0.0.1
    2021-12-13
"""
import re
import time
import numpy as np

# Importing LJM and LJM classes used in
# the LabJackT7 class
from labjack.ljm import ljm, LJMError

# Importing LabJackPython classes and functions used in
# the LabJackU3 and LabJackU6 classes
import u3, u6
import ctypes
from LabJackPython import (
    LabJackException,
    _loadLibrary,
    ePut,
    eGet,
    AddRequest,
    GoOne
)

# Importing constants used in the LabJackU3 and LabJackU6 classes
# See LabJackPython.py for more details on each constant
from LabJackPython import (
    LJ_chALL_CHANNELS,
    LJ_chNUMBER_TIMERS_ENABLED,
    LJ_chSTREAM_BACKLOG_COMM,
    LJ_chSTREAM_BACKLOG_UD,
    LJ_chSTREAM_BUFFER_SIZE,
    LJ_chSTREAM_SCAN_FREQUENCY,
    LJ_chSTREAM_WAIT_MODE,
    LJ_chTIMER_CLOCK_BASE,
    LJ_chTIMER_CLOCK_DIVISOR,
    LJ_chTIMER_COUNTER_PIN_OFFSET,
    LJ_ioADD_STREAM_CHANNEL,
    LJ_ioCLEAR_STREAM_CHANNELS,
    LJ_ioGET_AIN,
    LJ_ioGET_AIN_DIFF,
    LJ_ioGET_ANALOG_ENABLE_PORT,
    LJ_ioGET_CONFIG,
    LJ_ioGET_DIGITAL_BIT,
    LJ_ioGET_DIGITAL_BIT_DIR,
    LJ_ioGET_STREAM_DATA,
    LJ_ioGET_TIMER,
    LJ_ioPIN_CONFIGURATION_RESET,
    LJ_ioPUT_AIN_RANGE,
    LJ_ioPUT_ANALOG_ENABLE_BIT,
    LJ_ioPUT_ANALOG_ENABLE_PORT,
    LJ_ioPUT_CONFIG,
    LJ_ioPUT_DAC,
    LJ_ioPUT_DIGITAL_BIT,
    LJ_ioPUT_TIMER_MODE,
    LJ_ioPUT_TIMER_VALUE,
    LJ_ioSTART_STREAM,
    LJ_ioSTOP_STREAM,
    LJ_rgBIP10V,
    LJ_rgBIP1V,
    LJ_rgBIPP1V,
    LJ_rgBIPP01V,
    LJ_swNONE,
    LJ_tc48MHZ,
    LJ_tc48MHZ_DIV,
    LJ_tmPWM16,
    LJ_tmQUAD,
)


class LabJackU3:
    """
    The class to represent the LabJack U3

    Ports that are made available with this class are listed below.
    The port names on the first column assume a U3-HV. The second column
    contains alternate names that are sometimes used interchangeably.

    Analog Output
    'DAC0'      'N.A.'
    'DAC1'      'N.A.'
    Analog Input
    'AIN0'	    'FIO0'
    'AIN1'	    'FIO1'
    'AIN2'	    'FIO2'
    'AIN3'	    'FIO3'
    Flexible I/O
    'FIO4'	    'AIN4'
    'FIO5'	    'AIN5'
    'FIO6'	    'AIN6'
    'FIO7'	    'AIN7'
    'EIO0'	    'AIN8'
    'EIO1'	    'AIN9'
    'EIO2'	    'AIN10'
    'EIO3'	    'AIN11'
    'EIO4'	    'AIN12'
    'EIO5'	    'AIN13'
    'EIO6'	    'AIN14'
    'EIO7'	    'AIN15'

    Properties
    ----------
    (none public)

    Methods
    -------
    close ............ Closes the LabJack device
    display_info ..... Displays summary info about the device
    set_digital ...... Writes digital value to specified port(s)
    get_digital ...... Reads digital value from specified port(s)
    set_analog ....... Writes analog value to specified port(s)
    get_analog ....... Reads analog value from specified port(s)
    get_labjacktemp .. Gets LabJack internal temperature
    set_stream ....... Sets LabJack configuration for data streaming
    get_stream ....... Gets streaming data
    stop_stream ...... Stops data streaming
    set_PWM .......... Sets LabJack configuration for PWM output
    set_dutycycle .... Sets duty cycle of PWM output (-100 to 100)
    set_quadrature ... Sets LabJack configuration for encoder A-B-Z input
    get_counter ...... Gets edge count from encoder A-B signals
    reset_counter .... Resets edge counter

    get_config ....... Gets port configuration
    reset_config ..... Resets port configuration to factory defaults
    get_bitdir ....... Gets digital port bit direction

    Example
    -------
    Connect to the first found U3.
    >>> from labjack_unified.devices import LabJackU3
    >>> lju3 = LabJackU3()
    >>> lju3.display_info()
    >>> lju3.close()

    You can also connect to a specific device using its serial number.
    >>> lju3 = LabJackU3(320012345)

    """
    # CONSTRUCTOR METHODS
    def __init__(self, serialnum=None):
        """
        Class constructor

        """
        self._staticlib = _loadLibrary()
        # Attempting to open the device
        try:
            if serialnum:
                # Opens LabJack with specific serial number
                self._commhandle = u3.U3(autoOpen=False)
                self._commhandle.open(serial=serialnum)
            else:
                # Opens first available LabJack
                self._commhandle = u3.U3(autoOpen=False)
                self._commhandle.open()
        except LabJackException as error:
            print(error)
        else:
            self._numports = 16
            self._assign_info(self._commhandle.configU3())
            self._assign_ports()
            self.reset_config()
            print('Opened LabJack', self._serialnum)

    def close(self):
        self._staticlib.Close(self._commhandle.handle)
        print('Closed LabJack', self._serialnum)

    def display_info(self):
        print('____________________________________________________________')
        print('Device Name........', self._type)
        print('Serial Number......', self._serialnum)
        print('Hardware Version...', self._hardware)
        print('Firmware Version...', self._firmware)
        print('Connection Type....', self._connection)
        print('____________________________________________________________')

    # I/O METHODS
    def set_config(self, name, config):
        """
        Set the LabJack flexible IO port configuration.

        Parameters
        ----------
        name : str
            The port name to configure.
        config : str, int
            The configuration option 
            If `name` is `ALL` a string of mask bits for all 16 ports is used.
            For single port, `config` can be either a string `ANALOG` or `DIGITAL`.
            Alternativelly 1 or 0 can be used.

        Returns
        -------
        None.

        Example
        -------
        Configure flexible port FIO4 as analog
        >>> lju3.set_config('FIO4', 'Analog')

        Configure flexible port FIO4 as analog
        >>> lju3.set_config('FIO4', 1)

        Configure flexible port EIO0 as digital
        >>> lju3.set_config('EIO0', 'Digital')

        Configure flexible port EIO0 as digital
        >>> lju3.set_config('EIO0', 0)

        Configure flexible port EI01 and EI03 as analog and
        ports AIN0 to AIN3 to analog if a U3-LV is used.
        Note 1: LSB is port 1 ('AIN0')
        Note 2: On a U3-HV, the first 4 ports are always analog
                and the first 4 LSB settings are ignored
        >>> lju3.set_config('ALL', '0000101000001111')

        """
        if name.lower() == 'all':
            # Checking for correct mask string length
            if len(config) == self._numports:
                ePut(self._commhandle.handle, LJ_ioPUT_ANALOG_ENABLE_PORT,
                     0, int(config, 2), self._numports)
            else:
                raise Exception("'ALL' ports CONFIG must be 16-bit string.")
        else:
            # Checking for valid input port names
            if not self._check_port(name, "AIN"):
                raise Exception('Invalid flexible IO port name.')
            # Checking for valid configuration options
            if type(config) == str:
                if config.lower() == 'analog':
                    config = 1
                elif config.lower() == 'digital':
                    config = 0
                else:
                    raise Exception(
                        "Port configuration must be either 'DIGITAL' or 'ANALOG'.")
            else:
                if (config != 0) and (config != 1):
                    raise Exception('Port configuration must be either 0 or 1')
            ePut(self._commhandle.handle, LJ_ioPUT_ANALOG_ENABLE_BIT,
                 self._get_AINnumber(name), config, 0)

    def get_config(self, name='ALL'):
        """
        Get the LabJack flexible IO port configuration.

        Parameters
        ----------
        name : str
            The port name to get the configuration
            If `name` is `ALL` a dictionary with keys `EIOAnalog` and
            `FIOAnalog` is returned.

        Returns
        -------
        config : int, dict
            For single channel name, an integer is returned, where 1=Analog
            and 0=Digital. If name is `ALL` a dictionary containing the bit 
            mask string for the EIO and FIO ports is returned. The least 
            significant bit (LSB) of the EIO and FIO bit strings correspond
            respectively to ports EIO0 and FIO0.

        Example
        -------
        Get flexible port configuration
        >>> lju3.get_config()

        Notes
        -----
        In the case of a LabJack U3-HV, the four LSBs of the FIO ports
        are always 1s. They correspond to the always analog ports AIN0
        to AIN3.

        """
        mask = eGet(self._commhandle.handle, LJ_ioGET_ANALOG_ENABLE_PORT,
                    0, 0, self._numports)
        mask = '{:016b}'.format(int(mask))
        if name.lower() == 'all':
            config = {
                'EIOAnalog': mask[0:8],
                'FIOAnalog': mask[8::]
            }
        else:
            if not self._check_port(name, "AIN"):
                raise Exception('Invalid flexible IO port name.')
            config = int(mask[self._numports - self._get_AINnumber(name) - 1])
        return config

    def reset_config(self):
        """
        Reset the flexible IO ports to default all digital.

        Parameters
        ----------
        None.

        Returns
        -------
        None.

        Example
        -------
        Reset flexible port configuration
        >>> lju3.reset_config()

        Notes
        -----
        On the LabJack U3-HV the first 4 ports AIN0 to AIN3 are always analog.

        """
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)

    def set_digital(self, name, state):
        """
        Write the digital state to an output port.
        It also sets the port direction to output.

        Parameters
        ----------
        name : str
            The port name to set the state.
        state : int
            The digital state 0 = Low, 1 = High.

        Returns
        -------
        None.

        Example
        -------
        Set port FIO4 output to high
        >>> lju3.set_digital('FIO4', 1)

        Set port FIO5 output to low
        >>> lju3.set_digital('FIO5', 0)

        """
        # Checking for valid inputs
        if not self._check_port(name, "AIN"):
            raise Exception('Invalid flexible IO port name.')
        if (state != 0) and (state != 1):
            raise Exception('Port state must be either 1 or 0')
        # Setting port state by port number
        ePut(self._commhandle.handle, LJ_ioPUT_DIGITAL_BIT,
             self._get_AINnumber(name), state, 0)

    def get_digital(self, name):
        """
        Read the digital state from an input port.
        It also sets the port direction to input.

        Parameters
        ----------
        name : str
            The port name to get the state.

        Returns
        -------
        state : int
            The state of the digital port. 0 = Low, 1 = High.

        Example
        -------
        Get port FIO6 input state
        >>> lju3.get_digital('FIO6')

        """
        # Checking for valid inputs
        if not self._check_port(name, "AIN"):
            raise Exception('Invalid flexible IO port name.')
        # Getting port state by port number
        state = int(eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT,
                         self._get_AINnumber(name), 0, 0))
        return state

    def get_bitdir(self, name):
        """
        Read the direction of the digital port.

        Parameters
        ----------
        name : str
            The port name to get the direction.

        Returns
        -------
        bitdir : str
            The direction of the digital port. `Input` or `Output`

        Example
        -------
        Get the direction of port FIO6
        >>> lju3.get_bitdir('FIO6')

        """
        # Checking for valid input port names
        if not self._check_port(name, "AIN"):
            raise Exception('Invalid analog input port name.')
        # Getting digital port bit direction
        value = eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT_DIR,
                     self._get_AINnumber(name), 0, 0)
        if value == 0:
            bitdir = 'input'
        else:
            bitdir = 'output'
        return bitdir

    def set_analog(self, name, value):
        """
        Set analog output voltage.

        Parameters
        ----------
        name : str
            The port name to set the output voltage.
            Available ports are 'DAC0' and 'DAC1'.
        value : float
            The output voltage between 0 and 5 V.

        Returns
        -------
        None.

        Example
        -------
        Set port DAC1 output voltage to 2.2 V.
        >>> lju3.set_analog('DAC1', 2.2)

        """
        # Checking for valid input port names
        if not self._check_port(name, "DAC"):
            raise Exception('Invalid analog output port name.')
        # Getting corresponding port number
        DACnumber = self._get_DACnumber(name)
        # Limiting analog values
        if value > 5: value = 5
        if value < 0: value = 0
        # Setting analog port output voltage
        ePut(self._commhandle.handle, LJ_ioPUT_DAC, DACnumber, value, 0)

    def get_analog(self, namepos, *args):
        """
        Get analog input voltage.

        Parameters
        ----------
        namepos : str
            The positive port name to get the voltage.
        *args : str
            Can be one of the three options:
            - `single-ended` (default value)
            - The negative port name to get a differential voltage.
            - `special` to get increased range on the voltage reading.

        Returns
        -------
        value : float
            The input voltage value
            - On a U3-HV, the range for ports AIN0 to AIN3 is +/-10 V
            - On a U3-HV, `special` enables a range of -10 to +20 V
            - FIO ports have a range of +/- 2.4 V
            - FIO ports using `special` have a range of 0 to 3.6 V

        Example
        -------
        Get single-ended voltage on port FIO2
        >>> lju3.get_analog('FIO2')

        Get differential voltage betweens ports AIN0 and AIN1
        >>> lju3.get_analog('AIN0', 'AIN1')

        Get special range voltage on port FIO3
        >>> lju3.get_analog('FIO3', 'special')

        """
        # Checking for differential measurement
        if len(args) > 0:
            nameneg = args[0]
        else:
            nameneg = 'single-ended'
        # Checking for valid input port names
        if not self._check_port([namepos, nameneg], "AIN"):
            raise Exception('Invalid analog input port name.')
        # Getting corresponding port numbers
        AINpos = self._get_AINnumber(namepos)
        if nameneg.lower() == 'single-ended':
            AINneg = 31
        elif nameneg.lower() == 'special':
            AINneg = 32
        else:
            AINneg = self._get_AINnumber(nameneg)
        # Getting analog input voltage
        value = eGet(self._commhandle.handle,
                        LJ_ioGET_AIN_DIFF, AINpos, 0, AINneg)
        return value

    # STREAMING METHODS
    def set_stream(self, names, scanrate=50000, readrate=0.5):
        """
        Set and start data streaming.

        Parameters
        ----------
        names : str, list
            The U3 channel name(or list of channel names) to be streamed.
        scanrate : int
            The scan rate (Hz) of the data streaming. The default (and maximum)
            value is 50000 Hz. The effective scan frequency of each channel is
            the scan rate divided by the number of scanned channels. 
        readrate : float
            The rate in seconds at which blocks of data are retrieved from the
            data buffer. The default value is 0.5 seconds.

        Returns
        -------
        None.

        Example
        -------
        Retrieves streaming data from port AIN0 at 25 kHz every 0.5 s
        >>> lju3.set_stream('AIN0', scanrate=25000, readrate=0.5)

        Retrieves streaming data from port AIN0 and AIN1 at 50 kHz
        every 1.0 s
        >>> lju3.get_analog(['AIN0', 'AIN1'], scanrate=50000, readrate=1.0)

        Notes
        -----
        Only analog input channels AIN0 to AIN3 can be streamed.
        Hence, a Labjack U3-HV has to be used. While it's possible to
        stream digital channels, that hasn't been implemented in this
        release.

        See also get_stream, stop_stream

        """
        # Assigning streamed data block read-in rate (s)
        self._streamreadrate = readrate
        # Assigning buffer size as 2 times block length (s)
        self._streambuffersize = 2*self._streamreadrate
        # Assigning data block scan rate (Hz)
        self._scanrate = scanrate
        # Checking for valid input port names
        if not self._check_port(names, "AIN"):
            raise Exception('Invalid input port name.')
        # Getting port numbers
        if type(names) != list:
            names = [names]
        portnum = []
        for name in names:
            portnum.append(self._get_AINnumber(name))
        # Assigning number of streaming channels
        self._streamnumchannels = len(portnum)
        # Updating scan frequency per channel (Hz)
        self._streamscanfreq = int(self._scanrate/self._streamnumchannels)
        # Clearing streaming channels
        ePut(self._commhandle.handle, LJ_ioCLEAR_STREAM_CHANNELS, 0, 0, 0)
        # Adding channels to scan list
        for num in portnum:
            ePut(self._commhandle.handle, LJ_ioADD_STREAM_CHANNEL, num, 0, 0)
        # Assigning scanning frequency
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
             LJ_chSTREAM_SCAN_FREQUENCY, self._streamscanfreq, 0)
        # Assigning buffer size
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG, LJ_chSTREAM_BUFFER_SIZE,
             self._scanrate*self._streambuffersize, 0)
        # Configuring reads to retrieve whatever data is available without waiting
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
             LJ_chSTREAM_WAIT_MODE, LJ_swNONE, 0)
        # Starting streaming
        ePut(self._commhandle.handle, LJ_ioSTART_STREAM, 0, 0, 0)

    def stop_stream(self):
        """
        Stop data streaming.

        Parameters
        ----------
        None.

        Returns
        -------
        None.

        Example
        -------
        Stopping data streaming
        >>> lju3.stop_stream()

        See also set_stream, get_stream

        """
        ePut(self._commhandle.handle, LJ_ioSTOP_STREAM, 0, 0, 0)

    def get_stream(self):
        """
        Get streaming data block.

        Parameters
        ----------
        None.

        Returns
        -------
        dt : float
            The sampling period (s) between each data point.
        data : ndarray
            The numpy m-by-n array containing the streamed data where
            m is the number of samples per channel in the block and n
            is the number of channels defined in set_stream()
        numscans : int
            The actual number of scans per channel in the data block.
        commbacklog : float
            The communication backlog in % (increasing values indicate
            that the computer cannot keep up with the data download from
            the U3 driver)
        devbacklog : float
            The U3 device backlog in % (increasing values indicate that
            the U3 device cannot keep up with the data streaming - usually
            not the case)

        Example
        -------
        Retrieving scan period, data, and scan info
        >>> dt, datablock, numscans, commbacklog, U3backlog = lju3.get_stream()

        See also set_stream, stop_stream

        """
        # Defining initial number of samples per channnel
        # (double the size for safety)
        numscans = 2 * self._streamreadrate * self._streamscanfreq
        # Preallocating output array
        datasamples = np.zeros(int(numscans)*self._streamnumchannels)
        numscansactual, datalong = self._eGetArray(
            self._commhandle.handle, LJ_ioGET_STREAM_DATA,
            LJ_chALL_CHANNELS, numscans, datasamples)
        # Separating channels
        numscansactual = int(numscansactual)
        nrow = numscansactual
        ncol = self._streamnumchannels
        data = np.array(datalong)
        data = data[0:nrow*ncol].reshape(nrow, ncol)
        # Calculating sample period (s)
        dt = 1/self._streamscanfreq
        # Calculating communication backlog (%)
        commbacklog = eGet(self._commhandle.handle,
                           LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_COMM, 0, 0)
        commbacklog = 100 * commbacklog/numscansactual
        # Calculating LabJack device backlog (%)
        devbacklog = eGet(self._commhandle.handle,
                          LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_UD, 0, 0)
        devbacklog = 100 * devbacklog/numscansactual
        # Returning streamed data block parameters
        return dt, data, numscansactual, commbacklog, devbacklog

    # PWM METHODS
    def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=366):
        """
        Configure PWM output.

        Parameters
        ----------
        pwmnum : int
            The number of PWM output signals. 1 or 2 PWMs can be used.
            For one PWM, the output port is FIO4. For two PWMs, the output
            ports are FIO4 and FIO5
        dirport1 : str
            The type of ports that control the PWM "direction" for electric
            motor control. There are three options:
            - None  - Default value (no direction ports are used)
            - 'DAC' - Uses analog ports DAC0 and DAC1
            - 'DIO' - Uses digital ports FIO6 and FIO7
            When using digital ports, a 10 kOhm resistor has to be connected
            from the LabJack VS port to each one of the DIO ports to ensure
            true 'high' and 'low' states.
        dirport2 : str
            Same as `dirport1`. It's used when two PWM outputs are enabled.
            The 'DAC' option can only be used for one set of direction ports,
            unless the two motors are running synchronously. For the 'DIO'
            option, digital ports EIO0 and EIO1 are used.
        frequency : int
            The PWM signal frequency in Hz. In the case of two PWMs, both will
            have the same frequency. Valid values are 183, 366 or 732.

        Returns
        -------
        None.

        Example
        -------
        Set 1 PWM for motor control on FIO4 with direction ports on DAC0 and
        DAC1. The PWM frequency is the default 366 Hz.
        >>> lju3.set_pwm(dirport1='DAC')

        Set 2 PWMs with a frequency of 183 Hz.
        >>> lju3.set_pwm(pwmnum=2, frequency=183)

        Set 2 PWMs for motor control on ports FIO4 and FIO5, using the digital
        ports FIO6 and FIO7 for motor 1 direction, and EIO0 and EIO1 for motor
        2 direction. The PWM frequency is 732 Hz
        >>> lju3.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=732)

        See also set_dutycycle

        """
        # Defining PWM frequency divisors and checking input
        pwmfreq = {
            '183': 4,
            '366': 2,
            '732': 1
        }
        if str(frequency) not in list(pwmfreq.keys()):
            raise Exception(
                "Valid PWM frequencies are 183, 366, and 732 Hz.")
        # Checking number of PWM outputs and direction ports
        dirport = [dirport1, dirport2]
        pwmname = ['FIO4']
        pwmdir = [None]
        self._pwmtype = [1]
        if dirport[0] == 'DAC':
            pwmdir[0] = ['DAC0', 'DAC1']
            self._pwmtype[0] = 0
        elif dirport[0] == 'DIO':
            pwmdir[0] = ['FIO6', 'FIO7']
        if pwmnum == 2:
            self._pwmtype.append(1)
            pwmname.append('FIO5')
            if dirport[1] is None:
                pwmdir.append(None)
            if dirport[1] == 'DAC':
                pwmdir.append(['DAC0', 'DAC1'])
                self._pwmtype[1] = 0
            elif dirport[1] == 'DIO':
                pwmdir.append(['EIO0', 'EIO1'])
        # Assigning PWM attributes
        self._pwmnum = pwmnum
        self._pwmname = pwmname
        self._pwmdir = pwmdir
        self._pwmfreq = frequency
        # Assinging frequency divisor
        divisor = pwmfreq[str(frequency)]
        # Assigning PWM pin offset
        # (always 4 since the timers always start on FIO4)
        pinoffset = 4
        # Resetting pin configuration
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
        # Setting the pin offset for the timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
        # Configuring the timer clock to 48 MHz
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
        # Setting clock Divisor
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_DIVISOR, divisor, 0, 0)
        # Enabling 1 or 2 timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chNUMBER_TIMERS_ENABLED, pwmnum, 0, 0)
        for num in range(pwmnum):
            # Setting timer to 16 bits
            # Frequency = (48MHz/Divisor ) / 2^16 Hz
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                    num, LJ_tmPWM16, 0, 0)
            # Setting timer duty cycle to 0%
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                    num, 0, 0, 0)
        # Sending command sequence
        GoOne(self._commhandle.handle)

    def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
        """
        Set PWM duty cycle value.

        Parameters
        ----------
        value1 : float
            The PWM 1 duty cycle percent value between -100 and 100.
        value2 : float
            The PWM 2 duty cycle percent value between -100 and 100.
        brake1 : bool
            The motor 1 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.
        brake2 : bool
            The motor 2 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.

        Returns
        -------
        None.

        Example
        -------
        Set duty cycle to 50% on PWM 1
        >>> lju3.set_dutycycle(value1=50)

        Set duty cycle to 25% (reverse rotation) on PWM 2.
        >>> lju3.set_dutycycle(value2=-25)

        Set duty cycle to 20% and 40% on PWMs 1 and 2.
        >>> lju3.set_dutycycle(value1=20, value2=40)

        Stop motor 2 and apply brake.
        >>> lju3.set_dutycycle(value2=0, brake2=True)

        Notes
        -----
        1.  Avoid suddenly switching the direction of the motor to avoid
            damaging the motor and any gear train.
        2.  You can use the brake option True to hold the motor in position.

        See also set_pwm
        
        """
        values = [value1, value2]
        brakes = [brake1, brake2]
        for pwmnum, (value, brake, pwmdir, pwmtype) in enumerate(zip(
            values, brakes, self._pwmdir, self._pwmtype)):

            if value is not None:
                # Applying bounds to inputs
                if value > 100:
                    value = 100
                if value < -100:
                    value = -100
                # Applying PWM direction
                if pwmdir:
                    if value > 0:
                        # Forward rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir[0], 4.5)
                            self.set_analog(pwmdir[1], 0)
                        else:
                            self.set_digital(pwmdir[0], 0)
                            self.get_digital(pwmdir[1])
                    elif value < 0:
                        # Reverse rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir[0], 0)
                            self.set_analog(pwmdir[1], 4.5)
                        else:
                            self.get_digital(pwmdir[0])
                            self.set_digital(pwmdir[1], 0)
                    elif value == 0:
                        # Brake stop
                        if brake:
                            if pwmtype == 0:
                                self.set_analog(pwmdir[0], 0)
                                self.set_analog(pwmdir[1], 0)
                            else:
                                self.set_digital(pwmdir[0], 0)
                                self.set_digital(pwmdir[1], 0)
                        else:
                            if pwmtype == 0:
                                self.set_analog(pwmdir[0], 4.5)
                                self.set_analog(pwmdir[1], 4.5)
                            else:
                                self.get_digital(pwmdir[0])
                                self.get_digital(pwmdir[1])
                # Calculating duty cycle
                dutycycle = np.ceil(65535*(1-np.abs(value)/100))

                # Setting timer duty cycle to desired value
                AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                            pwmnum, dutycycle, 0, 0)

        # Sending command sequence
        GoOne(self._commhandle.handle)

    # QUADRATURE ENCODER METHODS
    def set_quadrature(self, zphase1=False):
        """
        Configure quadrature encoder input on ports FIO4 and FIO5

        Parameters
        ----------
        zphase1 : bool
            The logic value indicating if a Z-phase reference pulse is used
            on port FIO6. The default value is False.

        Returns
        -------
        None.

        Example
        -------
        Set ports FIO4 and FIO5 for encoder phase A and B signals.
        >>> lju3.set_quadrature()

        Set ports FIO4 and FIO5 for encoder phase A and B signals
        and port FIO6 for the reference Z phase.
        >>> lju3.set_quadrature(zphase1=True)

        See also get_counter, reset_counter

        """
        # Checking input arguments
        quadnameAB = ['FIO4', 'FIO5']
        portnumZ = 0
        if zphase1:
            portnumZ = 32768 + self._get_AINnumber('FIO6')
        # Assigning A-B port names
        self._zphase1 = zphase1
        self._quadnameAB = quadnameAB
        # Assigning quadrature port pin offset
        pinoffset = self._get_AINnumber(quadnameAB[0])
        # Resetting pin configuration
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
        # Setting the pin offset for the timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
        # Configuring the timer clock to 48 MHz
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
        # Enabling 2 timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chNUMBER_TIMERS_ENABLED, 2, 0, 0)
        # Setting quadrature mode
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                   0, LJ_tmQUAD, 0, 0)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                   1, LJ_tmQUAD, 0, 0)
        # Setting timer values to add Z port (or reset in case there's no Z)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                    0, portnumZ, 0, 0)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                    1, portnumZ, 0, 0)
        # Sending command sequence
        GoOne(self._commhandle.handle)

    def get_counter(self):
        """
        Get current quadrature counter value.

        Parameters
        ----------
        None

        Returns
        -------
        value : int
            The counter value

        Example
        -------
        Get current counter value
        >>> lju3.get_counter()

        Notes
        -----
        1.  Because the qudrature counter counts rising and falling edges
            of phases A and B, a 1024 pulse/rev encoder will generate 4096
            counts for a full shaft turn.

        """
        value = int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 0, 0, 0))
        return value

    def reset_counter(self):
        """
        Reset quadrature counter value.

        Parameters
        ----------
        None

        Returns
        -------
        None

        Example
        -------
        Resets current counter value
        >>> lju3.reset_counter()

        Notes
        -----
        1.  The count is only reset when a Z phase isn't being used.

        """
        if not self._zphase1:
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                       0, 0, 0, 0)
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                       1, 0, 0, 0)
            # Sending command sequence
            GoOne(self._commhandle.handle)

    # OTHER METHODS
    def get_labjacktemp(self, unit='C'):
        """
        Get LabJack's temperature from internal sensor.

        Parameters
        ----------
        unit : str
            The temperature measurement unit.
            Valid values are `C` or `F'. Default unit is `C`.

        Returns
        -------
        value : float
            The internal sensor temperature reading

        Example
        -------
        Get temperature reading in deg. C
        >>> lju3.get_labjacktemp()

        Get temperature reading in deg. F
        >>> lju3.get_labjacktemp(unit='F')

        """
        tempabs = eGet(self._commhandle.handle, LJ_ioGET_AIN, 30, 0, 0)
        if unit == 'C':
            temp = tempabs - 273.15
        elif unit == 'F':
            temp = 9/5*(tempabs-273.15) + 32
        else:
            raise Exception(
                "Temperature units must be either 'degC' or 'degF'.")
        return temp

    # HELPER METHODS (PRIVATE)
    def _eGetArray(self, Handle, IOType, Channel, pValue, x1):
        """
        Perform one call to the LabJack Device returning a data array.
        
        This method was created to complement the eGet() function in
        LabJackPyhon. It's used primarily in data streaming and like all
        the other C library functions, it is limited to a Windows platform.

        """
        pv = ctypes.c_double(pValue)
        xv = (ctypes.c_double * len(x1))()
        ec = self._staticlib.eGet_DblArray(
            Handle, IOType, Channel, ctypes.byref(pv), ctypes.byref(xv))
        if ec != 0:
            raise LabJackException(ec)
        return pv.value, xv

    def _assign_info(self, info):
        # Assigning info collected from LabJack handle to private attributes
        self._type = info['DeviceName']
        self._serialnum = info['SerialNumber']
        self._connection = 'USB'
        self._hardware = info['HardwareVersion']
        self._firmware = info['FirmwareVersion']

    def _assign_ports(self):
        """
        Creates lists with valid LabJack U3 port names
        
        """
        # Assigning analog output port names
        self._portDAC = [
            'DAC0', 'DAC1']
        # Assigning analog / FIO inout port names
        self._portAIN = [
            'AIN0', 'AIN1', 'AIN2', 'AIN3', 'FIO4', 'FIO5', 'FIO6', 'FIO7',
            'EIO0', 'EIO1', 'EIO2', 'EIO3', 'EIO4', 'EIO5', 'EIO6', 'EIO7']
        self._portAINalt = [
            'FIO0', 'FIO1', 'FIO2', 'FIO3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
            'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14', 'AIN15']
        self._portAINsp = ['SINGLE-ENDED', 'SPECIAL']

    def _check_port(self, namelist, porttype, *args):
        """
        Checks if the port NAMES belong to the possible values in
        the correposnding PORTTYPE list of valid names. If all NAMES
        are valid then FLAGPORT = 1, otherwise FLAGPORT = 0

        A user defined list can be passed with porttype = 'USER'
        through an additional argument VARARGIN

        """
        # Making sure inputs are lists
        if type(namelist) != list:
            namelist = [namelist]
        # Assining valid names list based on port type
        if porttype == "DAC":
            validnames = self._portDAC
        elif porttype == "AIN":
            validnames = self._portAIN + self._portAINalt + self._portAINsp
        elif porttype == "QUAD":
            validnames = [
                'FIO4', 'FIO5', 'FIO6', 'FIO7',
                'AIN4', 'AIN5', 'AIN6', 'AIN7']
        elif porttype == "USER":
            validnames = args[0]
        # Returns true if all names in namelist are validnames
        return all([name.upper() in validnames for name in namelist])

    def _get_DACnumber(self, name):
        if name.upper() == 'DAC0':
            DACnumber = 0
        elif name.upper() == 'DAC1':
            DACnumber = 1
        return DACnumber

    def _get_AINnumber(self, name):
        try:
            AINnumber = self._portAIN.index(name)
        except ValueError:
            AINnumber = self._portAINalt.index(name)
        return AINnumber


class LabJackU6:
    """
    The class to represent the LabJack U6

    Ports that are made available with this class are:
    - Analog Output (0 to 5V) : 'DAC0' , 'DAC1'
    - Analog Input (+/-10V)   : 'AIN0' , 'AIN1' , ... , 'AIN13'
    - Digital I/O             : 'DIO0' , 'DIO1' , ... , 'DIO22'

    Properties
    ----------
    (none public)

    Methods
    -------
    close ............ Closes the LabJack device 
    display_info ..... Displays summary info about the device
    set_digital ...... Writes digital value to specified port(s)
    get_digital ...... Reads digital value from specified port(s)
    set_analog ....... Writes analog value to specified port(s)
    get_analog ....... Reads analog value from specified port(s)
    get_labjacktemp .. Gets LabJack internal temperature
    set_stream ....... Sets LabJack configuration for data streaming
    get_stream ....... Gets streaming data
    stop_stream ...... Stops data streaming
    set_pwm .......... Sets LabJack configuration for PWM output
    set_dutycycle .... Sets duty cycle of PWM output (-100 to 100)
    set_quadrature ... Sets LabJack configuration for encoder A-B-Z input
    get_counter ...... Gets edge count from encoder A-B signals
    reset_counter .... Resets edge counter

    get_bitdir ....... Gets digital port bit direction
    set_range ........ Sets analog input voltage range
    set_pwm_quad ..... Sets simultaneous PWM output and encoder input

    Example
    -------
    Connect to the first found U6.
    >>> from labjack_unified.devices import LabJackU6
    >>> lju6 = LabJackU6()
    >>> lju6.display_info()
    >>> lju6.close()

    You can also connect to a specific device using its serial number.
    >>> lju6 = LabJackT7(370012345)

    """
    # CONSTRUCTOR METHODS
    def __init__(self, serialnum=None):
        """
        Class constructor

        """
        self._staticlib = _loadLibrary()
        # Attempting to open the device
        try:
            if serialnum:
                # Opens LabJack with specific serial number
                self._commhandle = u6.U6(autoOpen=False)
                self._commhandle.open(serial=serialnum)
            else:
                # Opens first available LabJack
                self._commhandle = u6.U6(autoOpen=False)
                self._commhandle.open()
        except LabJackException as error:
            print(error)
        else:
            self._numports = 34
            self._assign_info(self._commhandle.configU6())
            self._assign_ports()
            self._reset_config()
            self.set_range('all', 10)
            print('Opened LabJack', self._serialnum)

    def close(self):
        self._staticlib.Close(self._commhandle.handle)
        print('Closed LabJack', self._serialnum)

    def display_info(self):
        print('____________________________________________________________')
        print('Device Name........', self._type)
        print('Serial Number......', self._serialnum)
        print('Hardware Version...', self._hardware)
        print('Firmware Version...', self._firmware)
        print('Connection Type....', self._connection)
        print('____________________________________________________________')

    # I/O METHODS
    def set_digital(self, name, state):
        """
        Write the digital state to an output port.
        It also sets the port direction to output.

        Parameters
        ----------
        name : str
            The port name to set the state.
        state : int
            The digital state 0 = Low, 1 = High.

        Returns
        -------
        None.

        Example
        -------
        Set port FIO0 output to high
        >>> lju6.set_digital('FIO0', 1)

        Set port FIO1 output to low
        >>> lju6.set_digital('FIO1', 0)

        """
        # Checking for valid inputs
        if not self._check_port(name, "DIO"):
            raise Exception('Invalid digital IO port name.')
        if (state != 0) and (state != 1):
            raise Exception('Port state must be either 1 or 0')
        # Setting port state by port number
        ePut(self._commhandle.handle, LJ_ioPUT_DIGITAL_BIT,
             self._get_DIOnumber(name), state, 0)

    def get_digital(self, name):
        """
        Read the digital state from an input port.
        It also sets the port direction to input.

        Parameters
        ----------
        name : str
            The port name to get the state.

        Returns
        -------
        state : int
            The state of the digital port. 0 = Low, 1 = High.

        Example
        -------
        Get port FIO2 input state
        >>> lju6.get_digital('FIO2')

        """
        # Checking for valid inputs
        if not self._check_port(name, "DIO"):
            raise Exception('Invalid digital IO port name.')
        # Getting port state by port number
        state = int(eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT,
                         self._get_DIOnumber(name), 0, 0))
        return state

    def get_bitdir(self, name):
        """
        Read the direction of the digital port.

        Parameters
        ----------
        name : str
            The port name to get the direction.

        Returns
        -------
        bitdir : str
            The direction of the digital port. `Input` or `Output`

        Example
        -------
            Get the direction of port FIO2
            >>> lju6.get_bitdir('FIO2')

        """
        # Checking for valid input port names
        if not self._check_port(name, "DIO"):
            raise Exception('Invalid digital I/O port name.')
        # Getting digital port bit direction
        value = eGet(self._commhandle.handle, LJ_ioGET_DIGITAL_BIT_DIR,
                     self._get_DIOnumber(name), 0, 0)
        if value == 0:
            bitdir = 'input'
        else:
            bitdir = 'output'
        return bitdir

    def set_analog(self, name, value):
        """
        Set analog output voltage.

        Parameters
        ----------
        name : str
            The port name to set the output voltage.
            Available ports are 'DAC0' and 'DAC1'.
        value : float
            The output voltage between 0 and 5 V.

        Returns
        -------
        None.

        Example
        -------
            Set port DAC1 output voltage to 2.2 V.
            >>> lju6.set_analog('DAC1', 2.2)

        """
        # Checking for valid input port names
        if not self._check_port(name, "DAC"):
            raise Exception('Invalid analog output port name.')
        # Getting corresponding port number
        DACnumber = self._get_DACnumber(name)
        # Limiting analog values
        if value > 5: value = 5
        if value < 0: value = 0
        # Setting analog port output voltage
        ePut(self._commhandle.handle, LJ_ioPUT_DAC, DACnumber, value, 0)

    def get_analog(self, name, mode='single-ended'):
        """
        Get analog input voltage.

        Parameters
        ----------
        namepos : str
            The positive port name to get the voltage.
        *mode : str
            Can be one of the two options:
            - `Single-Ended` (default value)
            - `Differential' sets the ports to get a differential voltage.

        Returns
        -------
        value : float
            The input voltage value

        Example
        -------
        Get single-ended voltage on port AIN3
        >>> lju6.get_analog('AIN3')

        Get differential voltage betweens ports AIN2 and AIN3
        >>> lju6.get_analog('AIN2', 'Differential')

        Notes
        -----
        Differential reading uses two consecutive even-odd ports.
        Valid ports for differential reading are AIN0/2/4/6/8/10/12.

        """
        # Checking for valid inputs
        if mode.lower() not in ['single-ended', 'differential']:
            raise Exception(
                "Valid reference types are 'Single-Ended' or 'Differential'.")
        # Assigning range values
        if not self._check_port(name, "AIN"):
            raise Exception('Invalid analog input port name(s).')
        # Getting corresponding port numbers
        AINpos = self._get_AINnumber(name)
        #
        if mode.lower() == 'single-ended':
            AINneg = 199
        else:
            flagport, _ = self._check_portdiff(name)
            if flagport:
                AINneg = AINpos + 1
            else:
                raise Exception('Valid analog ports are AIN0/2/4/6/8/10/12.')
        # Getting analog input voltage
        value = eGet(self._commhandle.handle,
                     LJ_ioGET_AIN_DIFF, AINpos, 0, AINneg)
        return value

    # STREAMING METHODS
    def set_stream(self, names, scanrate=50000, readrate=0.5):
        """
        Set and start data streaming.

        Parameters
        ----------
        names : str, list
            The U6 channel name(or list of channel names) to be streamed.
        scanrate : int
            The scan rate (Hz) of the data streaming. The default (and maximum)
            value is 50000 Hz. The effective scan frequency of each channel is
            the scan rate divided by the number of scanned channels. 
        readrate : float
            The rate in seconds at which blocks of data are retrieved from
            the data buffer. The default value is 0.5 seconds.

        Returns
        -------
        None.

        Example
        -------
        Set data streaming from port AIN0 at 25 kHz every 0.5 s
        >>> lju6.set_stream('AIN0', scanrate=25000, readrate=0.5)

        Set data streaming from port AIN0 and AIN1 at 50 kHz every 1.0 s
        (The effective scan frequency of each channel is 25 kHz)
        >>> lju6.set_stream(['AIN0', 'AIN1'], scanrate=50000, readrate=1.0)

        Notes
        -----
        Only analog input channels can be streamed. While it is possible to
        stream digital channels, that hasn't been implemented in this release.

        See also get_stream, stop_stream

        """
        # Assigning streamed data block read-in rate (s)
        self._streamreadrate = readrate
        # Assigning buffer size as 2 times block length (s)
        self._streambuffersize = 2*self._streamreadrate
        # Assigning data block scan rate (Hz)
        self._scanrate = scanrate
        # Checking for valid input port names
        if not self._check_port(names, "AIN"):
            raise Exception('Invalid input port name.')
        # Getting port numbers
        if type(names) != list:
            names = [names]
        portnum = []
        for name in names:
            portnum.append(self._get_AINnumber(name))
        # Assigning number of streaming channels
        self._streamnumchannels = len(portnum)
        # Updating scan frequency per channel (Hz)
        self._streamscanfreq = int(self._scanrate/self._streamnumchannels)
        # Clearing streaming channels
        ePut(self._commhandle.handle, LJ_ioCLEAR_STREAM_CHANNELS, 0, 0, 0)
        # Adding channels to scan list
        for num in portnum:
            ePut(self._commhandle.handle, LJ_ioADD_STREAM_CHANNEL, num, 0, 0)
        # Assigning scanning frequency
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
             LJ_chSTREAM_SCAN_FREQUENCY, self._streamscanfreq, 0)
        # Assigning buffer size
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG, LJ_chSTREAM_BUFFER_SIZE,
             self._scanrate*self._streambuffersize, 0)
        # Configuring reads to retrieve whatever data is available without waiting
        ePut(self._commhandle.handle, LJ_ioPUT_CONFIG,
             LJ_chSTREAM_WAIT_MODE, LJ_swNONE, 0)
        # Starting streaming
        ePut(self._commhandle.handle, LJ_ioSTART_STREAM, 0, 0, 0)

    def stop_stream(self):
        """
        Stop data streaming.

        Parameters
        ----------
        None.

        Returns
        -------
        None.

        Example
        -------
        Stopping data streaming
        >>> lju6.stop_stream()

        See also set_stream, get_stream

        """
        ePut(self._commhandle.handle, LJ_ioSTOP_STREAM, 0, 0, 0)

    def get_stream(self):
        """
        Get streaming data block.

        Parameters
        ----------
        None.

        Returns
        -------
        dt : float
            The sampling period (s) between each data point.
        data : ndarray
            The numpy m-by-n array containing the streamed data where
            m is the number of samples per channel in the block and n
            is the number of channels defined in set_stream()
        numscans : int
            The actual number of scans per channel in the data block.
        commbacklog : float
            The communication backlog in % (increasing values indicate
            that the computer cannot keep up with the data download from
            the U6 driver)
        devbacklog : float
            The U6 device backlog in % (increasing values indicate that
            the U6 device cannot keep up with the data streaming - usually
            not the case)

        Example
        -------
        Retrieving scan period, data, and scan info
        >>> dt, datablock, numscans, commbacklog, U6backlog = lju6.get_stream()

        See also set_stream, stop_stream

        """
        # Defining initial number of samples per channnel
        # (double the size for safety)
        numscans = 2 * self._streamreadrate * self._streamscanfreq
        # Preallocating output array
        datasamples = np.zeros(int(numscans)*self._streamnumchannels)
        numscansactual, datalong = self._eGetArray(
            self._commhandle.handle, LJ_ioGET_STREAM_DATA,
            LJ_chALL_CHANNELS, numscans, datasamples)
        # Separating channels
        numscansactual = int(numscansactual)
        nrow = numscansactual
        ncol = self._streamnumchannels
        data = np.array(datalong)
        data = data[0:nrow*ncol].reshape(nrow, ncol)
        # Calculating sample period (s)
        dt = 1/self._streamscanfreq
        # Calculating communication backlog (%)
        commbacklog = eGet(self._commhandle.handle,
                           LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_COMM, 0, 0)
        commbacklog = 100 * commbacklog/numscansactual
        # Calculating LabJack device backlog (%)
        devbacklog = eGet(self._commhandle.handle,
                          LJ_ioGET_CONFIG, LJ_chSTREAM_BACKLOG_UD, 0, 0)
        devbacklog = 100 * devbacklog/numscansactual
        # Returning streamed data block parameters
        return dt, data, numscansactual, commbacklog, devbacklog

    # PWM METHODS
    def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=366):
        """
        Configure PWM output.

        Parameters
        ----------
        pwmnum : int
            The number of PWM output signals. 1 or 2 PWMs can be used.
            For one PWM, the output port is FIO0. For two PWMs, the output
            ports are FIO0 and FIO1
        dirport1 : str
            The type of ports that control the PWM "direction" for electric
            motor control. There are three options:
            - None  - Defaul value (no direction ports are used)
            - 'DAC' - Uses analog ports DAC0 and DAC1
            - 'DIO' - Uses digital ports FIO2 and FIO3
            When using digital ports, a 10 kOhm resistor has to be connected
            from the LabJack VS port to each one of the DIO ports to ensure
            true 'high' and 'low' states.
        dirport2 : str
            Same as `dirport1`. It's used when two PWM outputs are enabled.
            The 'DAC' option can only be used for one set of direction ports,
            unless the two motors are running synchronously. For the 'DIO'
            option, digital ports FIO4 and FIO5 are used.
        frequency : int
            The PWM signal frequency in Hz. In the case of two PWMs, both will
            have the same frequency. Valid values are 183, 366 or 732.

        Returns
        -------
        None.

        Example
        -------
        Set 1 PWM for motor control on FIO0 with direction ports on DAC0 and
        DAC1. The PWM frequency is the default 366 Hz.
        >>> lju6.set_pwm(dirport1='DAC')

        Set 2 PWMs with a frequency of 183 Hz.
        >>> lju6.set_pwm(pwmnum=2, frequency=183)

        Set 2 PWMs for motor control on ports FIO0 and FIO1, using the digital
        ports FIO2 and FIO3 for motor 1 direction, and FIO4 and FIO5 for motor
        2 direction. The PWM frequency is 732 Hz
        >>> lju6.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=732)

        See also set_dutycycle

        """
        # Setting flag for simultaneous PWN and quadrature setup
        self._pwmquad = False
        # Defining PWM frequency divisors and checking input
        pwmfreq = {
            '183': 4,
            '366': 2,
            '732': 1
        }
        if str(frequency) not in list(pwmfreq.keys()):
            raise Exception(
                "Valid PWM frequencies are 183, 366, and 732 Hz.")
        # Checking number of PWM outputs and direction ports
        dirport = [dirport1, dirport2]
        pwmname = ['FIO0']
        pwmdir = [None]
        self._pwmtype = [1]
        if dirport[0] == 'DAC':
            pwmdir[0] = ['DAC0', 'DAC1']
            self._pwmtype[0] = 0
        elif dirport[0] == 'DIO':
            pwmdir[0] = ['FIO2', 'FIO3']
        if pwmnum == 2:
            self._pwmtype.append(1)
            pwmname.append('FIO1')
            if dirport[1] is None:
                pwmdir.append(None)
            if dirport[1] == 'DAC':
                pwmdir.append(['DAC0', 'DAC1'])
                self._pwmtype[1] = 0
            elif dirport[1] == 'DIO':
                pwmdir.append(['FIO4', 'FIO5'])
        # Assigning PWM attributes
        self._pwmnum = pwmnum
        self._pwmname = pwmname
        self._pwmdir = pwmdir
        self._pwmfreq = frequency
        # Assinging frequency divisor
        divisor = pwmfreq[str(frequency)]
        # Assigning PWM pin offset
        # (always 0 since the timers always start on FIO0)
        pinoffset = 0
        # Resetting pin configuration
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
        # Setting the pin offset for the timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
        # Configuring the timer clock to 48 MHz
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ_DIV, 0, 0)
        # Setting clock Divisor
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_DIVISOR, divisor, 0, 0)
        # Enabling 1 or 2 timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chNUMBER_TIMERS_ENABLED, pwmnum, 0, 0)
        for num in range(pwmnum):
            # Setting timer to 16 bits
            # Frequency = (48MHz/Divisor ) / 2^16 Hz
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                       num, LJ_tmPWM16, 0, 0)
            # Setting timer duty cycle to 0%
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                       num, 0, 0, 0)
        # Sending command sequence
        GoOne(self._commhandle.handle)

    def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
        """
        Set PWM duty cycle value.

        Parameters
        ----------
        value1 : float
            The PWM 1 duty cycle percent value between -100 and 100.
        value2 : float
            The PWM 2 duty cycle percent value between -100 and 100.
        brake1 : bool
            The motor 1 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.
        brake2 : bool
            The motor 2 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.

        Returns
        -------
        None.

        Example
        -------
        Set duty cycle to 50% on PWM 1
        >>> lju6.set_dutycycle(value1=50)

        Set duty cycle to 25% (reverse rotation) on PWM 2.
        >>> lju6.set_dutycycle(value2=-25)

        Set duty cycle to 20% and 40% on PWMs 1 and 2.
        >>> lju6.set_dutycycle(value1=20, value2=40)

        Stop motor 2 and apply brake.
        >>> lju6.set_dutycycle(value2=0, brake2=True)

        Notes
        -----
        1.  Avoid suddenly switching the direction of the motor to avoid
            damaging the motor and any gear train.
        2.  You can use the brake option True to hold the motor in position.

        See also set_pwm
        
        """
        values = [value1, value2]
        brakes = [brake1, brake2]
        for pwmnum, (value, brake, pwmdir, pwmtype) in enumerate(zip(
            values, brakes, self._pwmdir, self._pwmtype)):

            if value is not None:
                # Applying bounds to inputs
                if value > 100:
                    value = 100
                if value < -100:
                    value = -100
                # Applying PWM direction
                if pwmdir:
                    if value > 0:
                        # Forward rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir[0], 4.5)
                            self.set_analog(pwmdir[1], 0)
                        else:
                            self.set_digital(pwmdir[0], 0)
                            self.get_digital(pwmdir[1])
                    elif value < 0:
                        # Reverse rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir[0], 0)
                            self.set_analog(pwmdir[1], 4.5)
                        else:
                            self.get_digital(pwmdir[0])
                            self.set_digital(pwmdir[1], 0)
                    elif value == 0:
                        # Brake stop
                        if brake:
                            if pwmtype == 0:
                                self.set_analog(pwmdir[0], 0)
                                self.set_analog(pwmdir[1], 0)
                            else:
                                self.set_digital(pwmdir[0], 0)
                                self.set_digital(pwmdir[1], 0)
                        else:
                            if pwmtype == 0:
                                self.set_analog(pwmdir[0], 4.5)
                                self.set_analog(pwmdir[1], 4.5)
                            else:
                                self.get_digital(pwmdir[0])
                                self.get_digital(pwmdir[1])
                # Calculating duty cycle
                dutycycle = np.ceil(65535*(1-np.abs(value)/100))

                # Setting timer duty cycle to desired value
                if self._pwmquad:
                    AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                               2, dutycycle, 0, 0)
                else:
                    AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                               pwmnum, dutycycle, 0, 0)

        # Sending command sequence
        GoOne(self._commhandle.handle)

    # QUADRATURE ENCODER METHODS
    def set_quadrature(self, quadnum=1, zphase1=False, zphase2=False):
        """
        Configure quadrature encoder input.

        Parameters
        ----------
        quadnum : int
            The number of quadrature input signals. 1 or 2 encoders can be
            used. For one encoder, the input ports are FIO0 and FIO1. For two
            encoders, the input ports for the second one are FIO2 and FIO3.
        zphase1 : bool
            The logic value indicating if a Z-phase reference pulse is used
            for the first encoder. Port FIO2 is used if `quadnum=1`. Port FIO4
            is used if `quadnum=2`.
        zphase2 : bool
            The logic value indicating if a Z-phase reference pulse is used
            for the second encoder. Port FIO4 is used for the first encoder
            and port FIO5 is used for the second encoder. `zphase2` is ignored
            if `quadnum=1`.

        Returns
        -------
        None.

        Example
        -------
        Set ports FIO0 and FIO1 for encoder with phase A and B signals only.
        >>> lju6.set_quadrature()

        Set ports FIO0 and FIO1 for encoder phase A and B signals and port
        FIO2 for the reference Z phase.
        >>> lju6.set_quadrature(zphase1=True)

        Set 2 encoders with Z phase. A and B phases are on ports FIO0 and FIO1
        for encoder 1, and FIO2 and FIO3 for encoder 2. The Z phase ports are
        respectively FIO4 and FIO5.
        >>> lju6.set_quadrature(quadnum=2, zphase1=True, zphase2=True)

        See also get_counter, reset_counter

        """
        # Setting flag for simultaneous PWN and quadrature setup
        self._pwmquad = False
        # Checking input arguments
        if quadnum == 1:
            quadnameAB = ['FIO0', 'FIO1']
            portnumZ = [0]
            if zphase1:
                portnumZ[0] = 32768 + self._get_DIOnumber('FIO2')
        elif quadnum == 2:
            quadnameAB = ['FIO0', 'FIO1', 'FIO2', 'FIO3']
            portnumZ = [0, 0]
            if zphase1:
                portnumZ[0] = 32768 + self._get_DIOnumber('FIO4')
            if zphase2:
                portnumZ[1] = 32768 + self._get_DIOnumber('FIO5')
        else:
            raise Exception('Only 1 or 2 quadrature inputd can be assigned.')
        # Assigning quadrature attributes
        self._zphase = [zphase1, zphase2]
        self._quadnum = quadnum
        self._quadnameAB = quadnameAB
        # Assigning quadrature port pin offset
        pinoffset = 0
        # Resetting pin configuration
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
        # Setting the pin offset for the timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
        # Configuring the timer clock to 48 MHz
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
        # Enabling 2 or 4 timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chNUMBER_TIMERS_ENABLED, 2*quadnum, 0, 0)
        for num in range(quadnum):
            # Setting quadrature mode
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                    (2*num)+0, LJ_tmQUAD, 0, 0)
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                    (2*num)+1, LJ_tmQUAD, 0, 0)
            # Setting timer values to add Z port (or reset in case there's no Z)
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                    (2*num)+0, portnumZ[num], 0, 0)
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                    (2*num)+1, portnumZ[num], 0, 0)
        # Sending command sequence
        GoOne(self._commhandle.handle)

    def get_counter(self):
        """
        Get current quadrature counter value.

        Parameters
        ----------
        None

        Returns
        -------
        value : int, list
            The counter value or a list with 2 values for 2 encoders.

        Example
        -------
        Gets current counter value or list of values
        >>> lju6.get_counter()

        Notes
        -----
        1.  Because the qudrature counter counts rising and falling edges
            of phases A and B, a 1024 pulse/rev encoder will generate 4096
            counts for a full shaft turn.

        """
        value = [int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 0, 0, 0))]
        if self._quadnum == 2:
            value.append(int(eGet(self._commhandle.handle, LJ_ioGET_TIMER, 2, 0, 0)))
        else:
            value = value[0]
        return value

    def reset_counter(self, counter1=True, counter2=True):
        """
        Reset quadrature counter value.

        Parameters
        ----------
        counter1 : bool
            The flag indicating whether to reset counter 1 or not.
            The default value is True and it resets the counter.
        counter2 : bool
            The flag indicating whether to reset counter 2 or not.
            The default value is True and it resets the counter.

        Returns
        -------
        None

        Example
        -------
        Resets current counter value of all encoders.
        >>> lju6.reset_counter()

        Resets current counter value only for second encoder.
        >>> lju6.reset_counter(counter1=False)

        Notes
        -----
        1.  The count is only reset when a Z phase isn't being used.

        """
        if not self._zphase[0] and counter1:
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                       0, 0, 0, 0)
            AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                       1, 0, 0, 0)
            # Sending command sequence
            GoOne(self._commhandle.handle)
        if self._quadnum == 2:
            if not self._zphase[1] and counter2:
                AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                        2, 0, 0, 0)
                AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                        3, 0, 0, 0)
                # Sending command sequence
                GoOne(self._commhandle.handle)

    # OTHER METHODS
    def set_pwm_quad(self, dirport='DAC', zphase=False):
        """
        Configure 1 PWM output and 1 quadrature input.
        The PWM port is FIO2 and the phases A and B ports are respectively
        FIO0 and FIO1.

        Parameters
        ----------
        dirport : str
            The type of ports that control the PWM "direction" for electric
            motor control. There are three options:
            - None  - Defaul value (no direction ports are used)
            - 'DAC' - Uses analog ports DAC0 and DAC1
            - 'DIO' - Uses digital ports FIO4 and FIO5
            When using digital ports, a 10 kOhm resistor has to be connected
            from the LabJack VS port to each one of the DIO ports to ensure
            true 'high' and 'low' states.
        zphase : bool
            The logic value indicating if a Z-phase reference pulse is used
            for the encoder. The port used in this configuration is FIO3.

        Returns
        -------
        None.

        Example
        -------
        Set a PWM for motor control on FIO2 with direction ports on DAC0 and
        DAC1. The encoder A and B ports are FIO0 and FIO1.
        >>> lju6.set_pwm_quad(dirport='DAC')

        Set a PWM for motor control on FIO2 with direction ports on FIO4 and
        FIO5. The A-B-Z encoder A and B ports are FIO0 and FIO1. The Z-phase
        is on port FIO3
        >>> lju6.set_pwm_quad(dirport='DIO', zphase=True)

        Notes
        -----
        Due to limitations with internal clocks under this configuration, the
        PWM frequency is fixed at 732 Hz.

        See also set_dutycycle

        """
        # Setting flag for simultaneous PWN and quadrature setup
        self._pwmquad = True
        # Assigning A-B port names
        quadnameAB = ['FIO0', 'FIO1']
        # Assigning Z port number
        portnumZ = 0
        if zphase:
            portnumZ = 32768 + self._get_DIOnumber('FIO3')
        # Assigning PWM ports
        pwmname = ['FIO2']
        if dirport == 'DAC':
            self._pwmtype = [0]
            pwmdir = [['DAC0', 'DAC1']]
        elif dirport == 'DIO':
            self._pwmtype = [1]
            pwmdir = [['FIO4', 'FIO5']]
        else:
            raise Exception("Valid direction port types are 'DAC' or 'DIO'.")
        # Setting some attributes
        self._quadnum = 1
        self._zphase = [zphase]
        self._quadnameAB = quadnameAB
        self._pwmnum = 1
        self._pwmname = pwmname
        self._pwmdir = pwmdir
        #
        # Setting timers for quadrature and PWM
        #
        # Assigning timer pin offset
        pinoffset = 0
        # Resetting pin configuration
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)
        # Setting the pin offset for the timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_COUNTER_PIN_OFFSET, pinoffset, 0, 0)
        # Configuring the timer clock to 48 MHz
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chTIMER_CLOCK_BASE, LJ_tc48MHZ, 0, 0)
        # Enabling 3 timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_CONFIG,
                   LJ_chNUMBER_TIMERS_ENABLED, 3, 0, 0)
        #
        # Setting up quadrature input configuration
        #
        # Setting quadrature timers
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                   0, LJ_tmQUAD, 0, 0)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                   1, LJ_tmQUAD, 0, 0)
        # Setting timer values to add Z port (or reset in case there's no Z)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                   0, portnumZ, 0, 0)
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                   1, portnumZ, 0, 0)
        #
        # Setting up PWM output configuration
        #
        # Setting third timer to 16-bit PWM
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_MODE,
                   2, LJ_tmPWM16, 0, 0)
        # Setting timer duty cycle to 0%
        AddRequest(self._commhandle.handle, LJ_ioPUT_TIMER_VALUE,
                   2, 0, 0, 0)

        # Sending command sequence
        GoOne(self._commhandle.handle)

    def set_range(self, names, ranges):
        """
        Set analog input voltage range.

        Parameters
        ----------
        names : str, list
            The analog port(s) that will have their ranges modified.
            Use `all` to set all analog input ports to the same range.
        value : float, list
            The voltage range value to be used.
            Valid ranges are +/- 10, 1, 0.1, 0.01 V.
            If a single value is used, it will be applied to all
            channels in NAMES.

        Returns
        -------
        None.

        Example
        -------
        Set port AIN0 with a range of +/- 1 V.
        >>> lju6.set_range('AIN0', 1)

        Set port AIN0 and AIN2 with a range of +/- 0.1 and 0.01 V
        >>> lju6.set_range(['AIN0', 'AIN2'], [0.1, 0.01])

        Set all ports with a (default) range of +/- 10 V
        >>> lju6.set_range('ALL', 10)

        """
        #
        rcode = {
            '10': LJ_rgBIP10V,
            '1': LJ_rgBIP1V,
            '0.1': LJ_rgBIPP1V,
            '0.01': LJ_rgBIPP01V
        }
        # Checking for valid inputs
        if type(names) != list: names = [names]
        if type(ranges) != list: ranges = [ranges]
        if (len(names) != len(ranges)) and (len(ranges) > 1):
            raise Exception('Number of ports must match number of ranges.')
        if len(ranges) == 1:
            ranges = ranges * len(names)
        for r in ranges:
            if r not in [10, 1, 0.1, 0.01]:
                raise Exception('Valid ranges are 10, 1, 0.1, or 0.01 V.')
        if names[0].lower() == 'all':
            names = self._portAIN
        else:
            if not self._check_port(names, "AIN"):
                raise Exception('Invalid analog input port name(s).')
        # Assigning range values
        for name, r in zip(names, ranges):
            AddRequest(self._commhandle.handle, LJ_ioPUT_AIN_RANGE,
                       self._get_AINnumber(name), rcode[str(r)], 0, 0)
        # Sending command sequence
        GoOne(self._commhandle.handle)

    # HELPER METHODS (PRIVATE)
    def _eGetArray(self, Handle, IOType, Channel, pValue, x1):
        """
        Perform one call to the LabJack Device returning a data array.
        
        This method was created to complement the eGet() function in
        LabJackPyhon. It's used primarily in data streaming and like all
        the other C library functions, it is limited to a Windows platform.

        """
        pv = ctypes.c_double(pValue)
        xv = (ctypes.c_double * len(x1))()
        ec = self._staticlib.eGet_DblArray(
            Handle, IOType, Channel, ctypes.byref(pv), ctypes.byref(xv))
        if ec != 0:
            raise LabJackException(ec)
        return pv.value, xv

    def _assign_info(self, info):
        # Assigning info collected from LabJack handle to private attributes
        self._type = info['DeviceName']
        self._serialnum = info['SerialNumber']
        self._connection = 'USB'
        self._hardware = info['HardwareVersion']
        self._firmware = info['FirmwareVersion']

    def _assign_ports(self):
        """
        Creates lists with valid LabJAck T7 port names
        """
        # Assigning analog output port names
        self._portDAC = [
            'DAC0', 'DAC1']
        # Assigning analog inout port names
        self._portAIN = [
            'AIN0', 'AIN1', 'AIN2', 'AIN3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
            'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14']
        # Assigning digital I/O port names
        self._portDIO = [
            'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
            'DIO6', 'DIO7', 'DIO8', 'DIO9', 'DIO10', 'DIO11',
            'DIO12', 'DIO13', 'DIO14', 'DIO15', 'DI16', 'DI17',
            'DIO18', 'DIO19', 'DIO20', 'DIO21', 'DIO22']
        # Assigning alternate digital I/O port names
        self._portDIOalt = [
            'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5',
            'FIO6', 'FIO7', 'EIO0', 'EIO1', 'EIO2', 'EIO3',
            'EIO4', 'EIO5', 'EIO6', 'EIO7', 'CIO0', 'CIO1',
            'CIO2', 'CIO3', 'MIO0', 'MIO1', 'MIO2']

    def _reset_config(self):
        """
        Reset the flexible IO ports to default all digital.

        Parameters
        ----------
        None.

        Returns
        -------
        None.

        Example
        -------
        Reset flexible port configuration
        >>> lju3.reset_config()

        Notes
        -----
        On the LabJack U3-HV the first 4 ports AIN0 to AIN3 are always analog.

        """
        ePut(self._commhandle.handle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0)

    def _check_port(self, namelist, portype, *args):
        """
        Checks if the port NAMES belong to the possible values in
        the correposnding PORTTYPE list of valid names. If all NAMES
        are valid then FLAGPORT = 1, otherwise FLAGPORT = 0

        A user defined list can be passed with PORTYPE = 'USER'
        through an additional argument VARARGIN

        """
        # Making sure inputs are lists
        if type(namelist) != list:
            namelist = [namelist]
        # Assining valid names list based on port type
        if portype == "DAC":
            validnames = self._portDAC
        elif portype == "AIN":
            validnames = self._portAIN
        elif portype == "DIO":
            validnames = self._portDIO + self._portDIOalt
        elif portype == "ALLIN":
            validnames = self._portAIN + self._portDIO + self._portDIOalt
        elif portype == "QUAD":
            validnames = [
                'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5', 'DIO6', 'DIO7', 'DIO8',
                'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5', 'FIO6', 'FIO7', 'EIO0']
        elif portype == "USER":
            validnames = args[0]
        # Returns true if all names in namelist are validnames
        return all([name in validnames for name in namelist])

    def _check_portdiff(self, names):
        # Making sure inputs are lists
        if type(names) != list:
            names = [names]
        # Getting port name digits
        portnum = [int(re.findall(r'\d+', name)[0]) for name in names]
        # Checking for all even digits
        flagport = all([(num % 2)==0 for num in portnum])
        # Checking for AIN14
        if 14 in portnum:
            flagport = False
        # Returning flag and port numbers
        return flagport, portnum

    def _get_DACnumber(self, name):
        if name.upper() == 'DAC0':
            DACnumber = 0
        elif name.upper() == 'DAC1':
            DACnumber = 1
        return DACnumber

    def _get_AINnumber(self, name):
        try:
            AINnumber = self._portAIN.index(name)
        except ValueError:
            AINnumber = self._portAINalt.index(name)
        return AINnumber

    def _get_DIOnumber(self, name):
        try:
            DIOnumber = self._portDIO.index(name)
        except ValueError:
            DIOnumber = self._portDIOalt.index(name)
        return DIOnumber


class LabJackT7:
    """
    The class to represent the LabJack T7 multifunction DAQ

    Ports that are made available with this class are:
    - Analog Output (0 to 5V) : 'DAC0' , 'DAC1'
    - Analog Input (+/-10V)   : 'AIN0' , 'AIN1' , ... , 'AIN13'
    - Digital I/O             : 'DIO0' , 'DIO1' , ... , 'DIO22'

    Properties
    ----------
    (none public)

    Methods
    -------
    close ............ Closes the LabJack device 
    display_info ..... Displays summary info about the device
    set_digital ...... Writes digital value to specified port(s)
    get_digital ...... Reads digital value from specified port(s)
    set_analog ....... Writes analog value to specified port(s)
    get_analog ....... Reads analog value from specified port(s)
    get_labjacktemp .. Gets LabJack internal temperature
    set_stream ....... Sets LabJack configuration for data streaming
    get_stream ....... Gets streaming data
    stop_stream ...... Stops data streaming
    set_PWM .......... Sets LabJack configuration for PWM output
    set_dutycycle .... Sets duty cycle of PWM output (-100 to 100)
    set_quadrature ... Sets LabJack configuration for encoder A-B-Z input
    get_counter ...... Gets edge count from encoder A-B signals
    reset_counter .... Resets edge counter

    set_TC ........... Sets LabJack configuration for thermocouple input
    get_TCtemp ....... Gets thermocouple temperature reading
    set_reference .... Sets analog input reference voltage point
    set_range ........ Sets analog input voltage range

    Example
    -------
    Connect to the first found T7.
    >>> from labjack_unified.devices import LabJackT7
    >>> ljt7 = LabJackT7()
    >>> ljt7.display_info()
    >>> ljt7.close()

    You can also connect to a specific device using its serial number.
    >>> ljt7 = LabJackT7(370012345)

    """
    # CONSTRUCTOR METHODS
    def __init__(self, serialnum=None):
        """
        Class constructor

        """
        try:
            if serialnum:
                # Opens LabJack with specific serial number
                self._commhandle = ljm.openS("ANY", "ANY", serialnum)
            else:
                # Opens first available LabJack
                self._commhandle = ljm.openS("ANY", "ANY", "ANY")
        except LJMError as error:
            print(error)
        else:
            # Assigning internal attributes
            self._corefreq = 80e6
            self._clockdivisor = 1
            self._assign_info(ljm.getHandleInfo(self._commhandle))
            self._assign_ports()
            self.set_reference('all', mode='Single-Ended')
            self.set_range('all', 10)
            print('Opened LabJack', self._serialnum)

    def close(self):
        ljm.close(self._commhandle)
        print('Closed LabJack', self._serialnum)

    def display_info(self):
        print('____________________________________________________________')
        print('Device Name........', self._type)
        print('Serial Number......', self._serialnum)
        print('Hardware Version...', self._hardware)
        print('Firmware Version...', self._firmware)
        print('Connection Type....', self._connection)
        print('IP Address.........', self._ipaddress)
        print('Port...............', self._port)
        print('____________________________________________________________')

    # I/O METHODS
    def set_analog(self, names, values):
        """
        Set analog output voltage.

        Parameters
        ----------
        name : str, list
            The port name to set the output voltage.
            Available ports are 'DAC0' and 'DAC1'. Both ports can be set at
            the same time using a list containing the two names.
        value : float, list
            The output voltage between 0 and 5 V. A list containing values
            can be used in conjunction with a list with the two port names.

        Returns
        -------
        None.

        Example
        -------
        Set port DAC1 output voltage to 2.2 V.
        >>> ljt7.set_analog('DAC1', 2.2)

        Set port DAC0 output voltage to 2.5 V and DAC1 to 3.2 V
        >>> ljt7.set_analog(['DAC0', 'DAC1'], [2.5, 3.2])

        See also get_analog

        """
        # Making sure inputs are lists
        if type(names) != list:
            names = [names]
        # Making sure values are ndarray of float64
        values = np.double(values)
        if type(values) == np.float64:
            values = [values]
        # Doing other input checks
        if len(names) != len(values):
            raise Exception('Number of ports must match number of values.')
        if not self._check_port(names, "DAC"):
            raise Exception('Invalid analog output port name(s).')
        # Limiting analog values
        values = [max(value, 0) for value in values]
        values = [min(value, 5) for value in values]
        # Setting port values
        self._set_port_values(names, values)

    def get_analog(self, names):
        """
        Get analog input voltage

        Parameters
        ----------
        names : str, list
            A character string identifier or a list of identifier `names`
            can be used to read the voltage on the selected input.
            Ports AIN0 to AIN13 are possible names and can read a range
            between -10 and +10V.

        Returns
        -------
        value : float, list
            The input voltage value or a list of values.

        Example
        -------
        Get input voltage on port AIN0
        >>> ljt7.get_analog('AIN0')

        Get input voltages on ports AIN0, AIN2 and AIN3
        >>> ljt7.get_analog(['AIN0', 'AIN2', 'AIN3'])

        Notes
        -----
        1.  Ports that are not connected may have erratic readings.
        2.  See get_range and set_range to view and modify the input range
            of the analog input ports

        """
        # Making sure inputs are lists
        if type(names) != list:
            names = [names]
        # Checking for valid analog input names
        if not self._check_port(names, "AIN"):
            raise Exception('Invalid analog input port name(s).')
        # Getting values
        return self._get_port_values(names)

    def set_digital(self, names, values):
        """
        Write the digital state to an output port.
        It also sets the port direction to output.

        Parameters
        ----------
        name : str, list
            The port name (or a list of port names) to set the state.
        state : int
            The digital state (or list of states) 0 = Low, 1 = High.

        Returns
        -------
        None.

        Example
        -------
        Set port DIO0 bit to high
        >>> ljt7.set_digital('DIO0', 1)

        Set ports DIO0, DIO1 and DIO6' bits to high, low and high
        >>> ljt7.set_digital(['DIO0', 'DIO1', 'DIO6'], [1, 0, 1])

        """
        # Making sure inputs are lists
        if type(names) != list:
            names = [names]
        if type(values) != list:
            values = [values]
        # Doing other input checks
        if len(names) != len(values):
            raise Exception('Number of ports must match number of values.')
        if not self._check_port(names, "DIO"):
            raise Exception('Invalid digital I/O port name(s).')
        # Limiting digital values
        values = [max(value, 0) for value in values]
        values = [min(value, 1) for value in values]
        # Setting port values
        self._set_port_values(names, values)

    def get_digital(self, names):
        """
        Read the digital state from an input port.
        It also sets the port direction to input.

        Parameters
        ----------
        name : str, list
            The port name (or list of port names) to get the state.

        Returns
        -------
        state : int, list
            The state of the digital port. 0 = Low, 1 = High.

        Example
        -------
        Get logic state on port DIO2
        >>> ljt7.get_digital('DIO2')

        Get logic states on ports DIO2, DIO3 and DIO7
        >>> ljt7.get_digital(['DIO2', 'DIO3', 'DIO7'])

        """
        if type(names) != list:
            names = [names]
        # Checking for valid analog input names
        if not self._check_port(names, "DIO"):
            raise Exception('Invalid digital I/O port name(s).')
        # Getting values
        states = self._get_port_values(names)
        if len(names) > 1:
            state = [int(value) for value in states]
        else:
            state = int(states)
        return state

    # STREAMING METHODS
    def set_stream(self, names, scanrate=100000, readrate=0.5,
                   clocksource='INT', exttrigger=None):
        """
        Set and start data streaming.

        Parameters
        ----------
        names : str, list
            The port NAMES can be AIN0 to AIN13 and DIO0 to DIO22. 
        scanrate : int
            The scan rate (Hz) of the data streaming. The default (and maximum)
            value is 100000 Hz. The effective scan frequency of each channel is
            the scan rate divided by the number of scanned channels. When
            `clocksource` is equal to 'EXT', `scanrate` is interpreted as the
            number of pulses per block of data.
        readrate : float
            The size in seconds of the data blocks data are retrieved from the
            data buffer by get_stream. When `clocksource` is equal to 'EXT',
            `readrate` is interpreted as the number of blocks of data retrieved
            by get_stream. The default value is 0.5 seconds
        clocksource : str
            The source of the streaming clock. It indicates whether the LabJack
            internal clock or an external clock (pulse train) will be used. In 
            the case of an external clock, the digital signal must be connected
            to CIO3 (DIO16). The default value is 'INT'.
        exttrigger : str
            The DIO port name containing an external trigger used to start the
            streaming.

        Returns
        -------
        None.

        Example
        -------
        Configure 100000 Hz streaming with 0.5 s data blocks from port AIN0.
        >>> ljt7.set_stream('AIN0')

        Configure streaming for 1 s data blocks from analog and digital ports.
        >>> ljt7.set_stream(['AIN0', 'AIN1', 'DIO6', 'DIO7'], readrate=1)

        Configure streaming for 0.5 s data blocks from analog ports
        The scan rate is 50000 Samples/s (25000 S/s for each port)
        >>> ljt7.set_stream(['AIN0','AIN1'], scanrate=50000)

        Configure streaming for 1 block of 1024 Samples/port per data retrieve.
        The external clock signal must be connected to CIO3 (DIO16)
        >>> ljt7.set_stream(['AIN0','AIN1'],
                            scanrate=1024, readrate=1, clocksource='EXT')

        Configure streaming for 3 blocks of 600 Samples/port
        Port DIO1 is connected to an external trigger that will
        start the streaming
        The external clock signal must be connected to CIO3 (DIO16)
        >>> ljt7.set_stream(['AIN0','DIO0'],
                            scanrate=600, readrate=3, clocksource='EXT',
                            exttrigger='DIO1')

        Notes
        -----
        Data streaming starts immediatelly after set_stream is invoked,
        unless `exttrigger` is used.

        See also get_stream and stop_stream

        """
        # Making sure inputs are lists
        names = names.copy()
        if type(names) != list:
            names = [names]
        # Checking for valid analog input names
        if not self._check_port(names, "ALLIN"):
            raise Exception('Invalid input port name(s).')
        # Limiting and interpreting scan rate
        if clocksource.upper() == 'INT':
            maxrate = min(scanrate, 100000)
        elif clocksource.upper() == 'EXT':
            maxrate = min(scanrate*(len(names)+1), 100000)
        else:
            raise Exception(
                'Stream clock source must be either "INT" or "EXT"')
        # Adding core timer register name to shallow copy of port list
        if clocksource.upper() == "EXT":
            names.append("CORE_TIMER")
        # Assining external clock trigger parameters
        if exttrigger:
            if not self._check_port(exttrigger, "USER", self._portDIO):
                raise Exception('Trigger must use DIO port naming.')
            self._streamtrigger = exttrigger
            triggernum = 2000 + int(self._streamtrigger[-1])
        # Configuring clock source in LabJack
        if clocksource.upper() == "INT":
            ljm.eWriteName(self._commhandle, 'STREAM_CLOCK_SOURCE', 0)
        else:
            ljm.eWriteName(self._commhandle, 'STREAM_CLOCK_SOURCE', 2)
        # Doing additional configuration changes for triggered external clock
        if exttrigger:
            ljm.writeLibraryConfigS(
                'LJM_STREAM_SCANS_RETURN',
                ljm.constants.STREAM_SCANS_RETURN_ALL)
            ljm.writeLibraryConfigS(
                'LJM_STREAM_RECEIVE_TIMEOUT_MS', 0)
            ljm.eWriteName(
                self._commhandle, 'STREAM_TRIGGER_INDEX', triggernum)
            ljm.eWriteName(
                self._commhandle, self._streamtrigger + '_EF_ENABLE', 0)
            ljm.eWriteName(
                self._commhandle, self._streamtrigger + '_EF_INDEX', 3)
            ljm.eWriteName(
                self._commhandle, self._streamtrigger + '_EF_CONFIG_A', 2)
            ljm.eWriteName(
                self._commhandle, self._streamtrigger + '_EF_ENABLE', 1)
        else:
            ljm.writeLibraryConfigS(
                'LJM_STREAM_SCANS_RETURN',
                ljm.constants.STREAM_SCANS_RETURN_ALL)
            ljm.writeLibraryConfigS(
                'LJM_STREAM_RECEIVE_TIMEOUT_MODE',
                ljm.constants.STREAM_RECEIVE_TIMEOUT_MODE_CALCULATED)
            ljm.eWriteName(self._commhandle, 'STREAM_TRIGGER_INDEX', 0)
        # Making sure read period is an integer for external clock
        if clocksource.upper() == 'EXT':
            readrate = np.ceil(readrate)
        # Assigning streaming parameters
        self._streamclock = clocksource
        self._streamnumports = len(names)
        self._streamreadperiod = readrate
        self._streamscanrate = maxrate/self._streamnumports
        self._streamscansperread = int(
            self._streamreadperiod*self._streamscanrate)
        self._streamTs = 1/self._streamscanrate
        # Configuring and starting stream
        addresses = self._get_port_address(names)
        ljm.eStreamStart(self._commhandle, self._streamscansperread,
                         self._streamnumports, addresses, self._streamscanrate)
        # Ditching first block of data
        if clocksource.upper() == 'INT':
            time.sleep(readrate)
            _, _, _, _, _ = self.get_stream()

    def get_stream(self):
        """
        Get data streaming data block.

        Parameters
        ----------
        None.

        Returns
        -------
        dt : float, ndarray
            The sampling period (s) between each data point.
            When the streaming is configured with an external clock, DT
            contains the delta times between two consecutive samples.
            The first value of the array DT is NaN.
        value : ndarray
            The numpy m-by-n array containing the streamed data where
            m is the number of samples per channel in the block and n
            is the number of channels defined in set_stream()
        numscans : int
            The actual number of scans per channel in the data block.
        commbacklog : float
            The communication backlog in % (increasing values indicate
            that the computer cannot keep up with the data download from
            the U3 driver)
        devbacklog : float
            The U3 device backlog in % (increasing values indicate that
            the U3 device cannot keep up with the data streaming - usually
            not the case)

        Example
        -------
        Retrieve one data block
        >>> dt, datablock, commbacklog, T7backlog = ljt7.get_stream()

        Create the time array for the acquired block
        >>> t = dt * np.linspace(0, datablock.shape[0]-1, datablock.shape[0])

        See also set_stream and stop_stream

        """
        # Reading data block from LabJack
        data, commbacklog, devbacklog = ljm.eStreamRead(self._commhandle)
        # Creating array from data
        data = np.array(data)
        # Separating interweaved channels into columns
        ncol = self._streamnumports
        nrow = len(data)//ncol
        valueaux = data[0:nrow*ncol].reshape((nrow, ncol))
        # Checking for external clock
        if self._streamclock == "EXT":
            value = valueaux[:, 0:-1]
            dAux = np.diff(valueaux[:, -1])
            iaux = np.nonzero(dAux < 0)[0]
            dAux[iaux] = 65535+valueaux[iaux+1, -1] - valueaux[iaux, -1]
            dt = dAux/40e6
        else:
            value = valueaux
            dt = self._streamTs
        # Assigning number of actual scans
        numscans = nrow
        # Converting backlog outputs to % values
        commbacklog = 100*commbacklog/self._streamscanrate
        devbacklog = 100*devbacklog/self._streamscanrate
        # Returning outputs
        return dt, value, numscans, commbacklog, devbacklog

    def stop_stream(self):
        """
        Stop data streaming.

        Parameters
        ----------
        None.

        Returns
        -------
        None.

        Example
        -------
        Stopping data streaming
        >>> ljt7.stop_stream()

        See also set_stream, get_stream

        """
        ljm.eStreamStop(self._commhandle)

    # PWM METHODS
    def set_pwm(self, pwmnum=1, dirport1=None, dirport2=None, frequency=250):
        """
        Configure PWM output.

        Parameters
        ----------
        pwmnum : int
            The number of PWM output signals. 1 or 2 PWMs can be used.
            For one PWM, the output port is FIO0. For two PWMs, the output
            ports are FIO0 and FIO4
        dirport1 : str
            The type of ports that control the PWM "direction" for electric
            motor control. There are three options:
            - None  - Default value (no direction ports are used)
            - 'DAC' - Uses analog ports DAC0 and DAC1
            - 'DIO' - Uses digital ports EIO0 and EIO1
            When using digital ports, a 10 kOhm resistor has to be connected
            from the LabJack VS port to each one of the DIO ports to ensure
            true 'high' and 'low' states.
        dirport2 : str
            Same as `dirport1`. It is used when two PWM outputs are enabled.
            The 'DAC' option can only be used for one set of direction ports,
            unless the two motors are running synchronously. For the 'DIO'
            option, digital ports EIO2 and EIO3 are used.
        frequency : int
            The PWM signal frequency in Hz. In the case of two PWMs, both will
            have the same frequency.

        Returns
        -------
        None.

        Example
        -------
        Set 1 PWM for motor control on FIO0 with direction ports on DAC0 and
        DAC1. The PWM frequency is the default 500 Hz.
        >>> ljt7.set_pwm(dirport1='DAC')

        Set 2 PWMs with a frequency of 250 Hz.
        >>> ljt7.set_pwm(pwmnum=2, frequency=250)

        Set 2 PWMs for motor control on ports FIO0 and FIO4, using the digital
        ports EIO0 and EIO1 for motor 1 direction, and EIO2 and EIO3 for motor
        2 direction. The PWM frequency is 750 Hz
        >>> ljt7.set_pwm(pwmnum=2, dirport1='DIO', dirport2='DIO', frequency=750)

        See also set_dutycycle

        """
        # Checking number of PWM outputs and direction ports
        dirport = [dirport1, dirport2]
        pwmname = ['DIO0']
        pwmdir = [None]
        self._pwmtype = [1]
        if dirport[0] == 'DAC':
            pwmdir[0] = ['DAC0', 'DAC1']
            self._pwmtype[0] = 0
        elif dirport[0] == 'DIO':
            pwmdir[0] = ['EIO0', 'EIO1']
        if pwmnum == 2:
            self._pwmtype.append(1)
            pwmname.append('DIO4')
            if dirport[1] is None:
                pwmdir.append(None)
            if dirport[1] == 'DAC':
                pwmdir.append[0] = ['DAC0', 'DAC1']
                self._pwmtype[1] = 0
            elif dirport[1] == 'DIO':
                pwmdir.append(['EIO2', 'EIO3'])
        # Assigning PWM attributes
        self._pwmnum = pwmnum
        self._pwmname = pwmname
        self._pwmdir = pwmdir
        self._pwmfreq = frequency
        # Calculating clock roll value
        self._clockrollval = self._corefreq/(self._clockdivisor*self._pwmfreq)
        # Configuring clock registers
        ljm.eWriteName(
            self._commhandle, 'DIO_EF_CLOCK0_ENABLE', 0)
        ljm.eWriteName(
            self._commhandle, 'DIO_EF_CLOCK0_DIVISOR', self._clockdivisor)
        ljm.eWriteName(
            self._commhandle, 'DIO_EF_CLOCK0_ROLL_VALUE', self._clockrollval)
        ljm.eWriteName(
            self._commhandle, 'DIO_EF_CLOCK0_ENABLE', 1)
        # Configuring extended feature (EF) registers for selected PWM port
        for name in pwmname:
            # Disable the EF system for initial configuration
            ljm.eWriteName(self._commhandle, name + '_EF_ENABLE', 0)
            # Configure EF system for PWM
            ljm.eWriteName(self._commhandle, name + '_EF_INDEX', 0)
            # Configure what clock source to use: Clock0
            ljm.eWriteName(self._commhandle, name + '_EF_OPTIONS', 0)
            # Configure duty cycle to be: 0%
            ljm.eWriteName(self._commhandle, name + '_EF_CONFIG_A', 0)
            # Enable the EF system, PWM wave is now being outputted
            ljm.eWriteName(self._commhandle, name + '_EF_ENABLE', 1)

    def set_dutycycle(self, value1=None, value2=None, brake1=False, brake2=False):
        """
        Set PWM duty cycle value.

        Parameters
        ----------
        value1 : float
            The PWM 1 duty cycle percent value between -100 and 100.
        value2 : float
            The PWM 2 duty cycle percent value between -100 and 100.
        brake1 : bool
            The motor 1 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.
        brake2 : bool
            The motor 2 brake option used when dutycycle is zero.
            Brake is applied when True. Motor is floating when False.

        Returns
        -------
        None.

        Example
        -------
        Set duty cycle to 50% on PWM 1
        >>> ljt7.set_dutycycle(value1=50)

        Set duty cycle to 25% (reverse rotation) on PWM 2.
        >>> ljt7.set_dutycycle(value2=-25)

        Set duty cycle to 20% and 40% on PWMs 1 and 2.
        >>> ljt7.set_dutycycle(value1=20, value2=40)

        Stop motor 2 and apply brake.
        >>> ljt7.set_dutycycle(value2=0, brake2=True)

        Notes
        -----
        1.  Avoid suddenly switching the direction of the motor to avoid
            damaging the motor and any gear train.
        2.  You can use the brake option True to hold the motor in position.

        See also set_pwm
        
        """
        values = [value1, value2]
        brakes = [brake1, brake2]
        for value, brake, pwmdir, pwmtype, pwmname in zip(
            values, brakes, self._pwmdir, self._pwmtype, self._pwmname):

            if value is not None:
                # Applying bounds to inputs
                if value > 100:
                    value = 100
                if value < -100:
                    value = -100
                # Applying PWM direction
                if pwmdir:
                    if value > 0:
                        # Forward rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir, [4.5, 0])
                        else:
                            self.set_digital(pwmdir[0], 0)
                            self.get_digital(pwmdir[1])
                    elif value < 0:
                        # Reverse rotation
                        if pwmtype == 0:
                            self.set_analog(pwmdir, [0, 4.5])
                        else:
                            self.get_digital(pwmdir[0])
                            self.set_digital(pwmdir[1], 0)
                    elif value == 0:
                        # Brake stop
                        if brake:
                            if pwmtype == 0:
                                self.set_analog(pwmdir, [0, 0])
                            else:
                                self.set_digital(pwmdir[0], 0)
                                self.set_digital(pwmdir[1], 0)
                        else:
                            if pwmtype == 0:
                                self.set_analog(pwmdir, [4.5, 4.5])
                            else:
                                self.get_digital(pwmdir[0])
                                self.get_digital(pwmdir[1])
                # Calculating duty cycle
                dutycycle = round(abs(value)/100*self._clockrollval)
                # Setting timer duty cycle to desired value
                ljm.eWriteName(
                    self._commhandle, pwmname + '_EF_CONFIG_A', dutycycle)

    # QUADRATURE ENCODER METHODS
    def set_quadrature(self, quadnum=1, zphase1=False, zphase2=False):
        """
        Configure encoder quadrature input.

        Parameters
        ----------
        quadnum : int
            The number of quadrature input signals. 1 or 2 encoders can be
            used. For one encoder, the input ports are FIO2 and FIO3. For two
            encoders, the input ports for the second one are FIO6 and FIO7.
        zphase1 : bool
            The logic value indicating if a Z-phase reference pulse is used
            for the first encoder. Port FIO1 is used.
        zphase2 : bool
            The logic value indicating if a Z-phase reference pulse is used
            for the second encoder. Port FIO1 is used for the first encoder
            and port FIO5 is used for the second encoder. `zphase2` is ignored
            if `quadnum=1`.

        Returns
        -------
        None.

        Example
        -------
        Set ports FIO2 and FIO3 for encoder with phase A and B signals only.
        >>> ljt7.set_quadrature()

        Set ports FIO2 and FIO3 for encoder phase A and B signals and port
        FIO1 for the reference Z phase.
        >>> ljt7.set_quadrature(zphase1=True)

        Set two encoders with Z phase. A and B phases are  on ports FIO2 and
        FIO3 for encoder 1, and FIO6 and FIO7 for encoder 2. The Z phase ports
        are respectively FIO1 and FIO5.
        >>> ljt7.set_quadrature(quadnum=2, zphase1=True, zphase2=True)

        See also get_counter, reset_counter

        """
        # Selecting port numbers based on input options
        if quadnum == 1:
            quadnameAB = [['DIO2', 'DIO3']]
            portnumZ = [0]
            if zphase1:
                portnumZ[0] = 1
        elif quadnum == 2:
            quadnameAB = [['DIO2', 'DIO3'], ['DIO6', 'DIO7']]
            portnumZ = [0, 0]
            if zphase1:
                portnumZ[0] = 1
            if zphase2:
                portnumZ[1] = 5
        else:
            raise Exception('Only 1 or 2 quadrature inputd can be assigned.')
        # Assigning quadrature properties
        self._zphase = [zphase1, zphase2]
        self._quadnum = quadnum
        self._quadnameAB = quadnameAB
        #
        # Configuring extended feature (EF) registers for
        # selected A-B ports and optional Z phase port
        #
        # Disabling the EF system for initial configuration
        for name, num in zip(quadnameAB, portnumZ):
            ljm.eWriteName(self._commhandle, name[0] + '_EF_ENABLE', 0)
            ljm.eWriteName(self._commhandle, name[1] + '_EF_ENABLE', 0)
            # Configure EF system for qudrature
            ljm.eWriteName(self._commhandle, name[0] + '_EF_INDEX', 10)
            ljm.eWriteName(self._commhandle, name[1] + '_EF_INDEX', 10)
            # Configuring port Z
            if num > 0:
                ljm.eWriteName(
                    self._commhandle, name[0] + '_EF_CONFIG_A', 1)
                ljm.eWriteName(
                    self._commhandle, name[0] + '_EF_CONFIG_B', num)
                ljm.eWriteName(
                    self._commhandle, name[1] + '_EF_CONFIG_A', 1)
                ljm.eWriteName(
                    self._commhandle, name[1] + '_EF_CONFIG_B', num)
            else:
                ljm.eWriteName(
                    self._commhandle, name[0] + '_EF_CONFIG_A', 0)
                ljm.eWriteName(
                    self._commhandle, name[1] + '_EF_CONFIG_A', 0)
            # Enabling the EF system
            ljm.eWriteName(self._commhandle, name[0] + '_EF_ENABLE', 1)
            ljm.eWriteName(self._commhandle, name[1] + '_EF_ENABLE', 1)

    def get_counter(self):
        """
        Get current quadrature counter value.

        Parameters
        ----------
        None

        Returns
        -------
        value : int, list
            The counter value or a list with 2 values for 2 encoders.

        Example
        -------
        Gets current counter value or list of values
        >>> ljt7.get_counter()

        Notes
        -----
        1.  Because the qudrature counter counts rising and falling edges
            of phases A and B, a 1024 pulse/rev encoder will generate 4096
            counts for a full shaft turn.

        """
        value = [self._get_port_values([self._quadnameAB[0][0] + '_EF_READ_A_F'])]
        if self._quadnum == 2:
            value.append(self._get_port_values([self._quadnameAB[1][0] + '_EF_READ_A_F']))
        else:
            value = value[0]
        return value

    def reset_counter(self, counter1=True, counter2=True):
        """
        Reset quadrature counter value.

        Parameters
        ----------
        counter1 : bool
            The flag indicating whether to reset counter 1 or not.
            The default value is True and it resets the counter.
        counter2 : bool
            The flag indicating whether to reset counter 2 or not.
            The default value is True and it resets the counter.

        Returns
        -------
        None

        Example
        -------
        Resets current counter value of all encoders.
        >>> ljt7.reset_counter()

        Resets current counter value only for second encoder.
        >>> ljt7.reset_counter(counter1=False)

        Notes
        -----
        1.  The count is only reset when a Z phase isn't being used.

        """
        if not self._zphase[0] and counter1:
            self._get_port_values([self._quadnameAB[0][0] + '_EF_READ_A_AND_RESET'])
        if self._quadnum == 2:
            if not self._zphase[1] and counter2:
                self._get_port_values([self._quadnameAB[1][0] + '_EF_READ_A_AND_RESET'])

    # THERMOCOUPLE METHODS
    def get_labjacktemp(self, unit='C'):
        """
        Get LabJack's temperature from internal sensor.

        Parameters
        ----------
        unit : str
            The temperature measurement unit.
            Valid values are `C` or `F'. Default unit is `C`.

        Returns
        -------
        value : float
            The internal sensor temperature reading

        Example
        -------
        Get temperature reading in deg. C
        >>> ljt7.get_labjacktemp()

        Get temperature reading in deg. F
        >>> ljt7.get_labjacktemp(unit='F')

        """
        v = ljm.eReadName(self._commhandle, 'AIN14')
        tempabs = -92.6*v + 467.6
        if unit == 'K':
            temp = tempabs
        elif unit == 'C':
            temp = tempabs - 273.15
        elif unit == 'F':
            temp = 9/5*(tempabs-273.15) + 32
        else:
            raise Exception(
                "Temperature units must be either 'C' or 'F'.")
        return temp

    def set_TC(self, names, types, unit='C'):
        """
        Set configuration for thermocouple input.

        Parameters
        ----------
        names : str, list
            The analog port(s) that will be used for thermocouple input.
            Ports AIN0 to AIN3 are recommended for higher measurement accuracy.
            The negative thermocouple wire should be connected to GND.
        type : str, list
            The thermocouple type. It can be a single string or a list with
            same length as NAMES. Valid types are: 
            'B', 'E', 'J', 'K', 'N', 'R', 'S', 'T', and 'C'
        unit : str
            The temperature measurement unit.
            Valid values are `C` or `F'. Default unit is `C`.

        Returns
        -------
        None.

        Example
        -------
        Set port AIN0 for thermocouple type K.
        >>> ljt7.set_TC('AIN0', 'K')

        Set port AIN0, AIN2, and AIN3 for thermocouples type K and J with
        measurement in deg F.
        >>> ljt7.set_TC(['AIN0', 'AIN2', 'AIN3'], ['K', 'J', 'J'], unit='F')

        """
        self._TCoptions = ['B', 'E', 'J', 'K', 'N', 'R', 'S', 'T', 'C']
        # Checking for valid inputs
        if type(names) != list: names = [names]
        if type(types) != list: types = [types]
        if (len(names) != len(types)) and (len(types) > 1):
            raise Exception('Number of ports must match number of types.')
        if not self._check_port(names, "AIN"):
            raise Exception('Analog input port(s) are required for TC setup.')
        if (unit != 'C') and (unit != 'F'):
            raise Exception("Temperature units must be either 'degC' or 'degF'.")
        for tipe in types:
            if tipe not in self._TCoptions:
                raise Exception('Invalid TC type.')
        # Assining TC attributes
        self._TCnames = names
        if len(types) == 1:
            self._TCtypes = types * len(names)
        else:
            self._TCtypes = types
        self._TCunit = unit
        # Assigning TC type number
        self._TCtypenum = [6001+self._TCoptions.index(tipe) for tipe in types]
        # Setting TC analog port ranges to +/- 0.1V
        for name in names:
            ljm.eWriteName(self._commhandle, name + '_RANGE', 0.1)

    def get_TCtemp(self):
        """
        Get thermocouple temperature reading.

        Parameters
        ----------
        None.

        Returns
        -------
        value : float, list
            The temperature readings for the thermocouples defined
            using set_TC.

        Example
        -------
        Gets temperature readings from thermocouples.
        >>> ljt7.get_TCtemp()

        """
        # Getting LabJack cold joint temperature
        TCJ = self.get_labjacktemp(unit='K')
        # Getting TC voltage and convertng it to temperature
        v = self.get_analog(self._TCnames)
        if type(v) != list:
            v = [v]
        tempabs = []
        for typ, val in zip(self._TCtypenum, v):
            tempabs.append(ljm.tcVoltsToTemp(typ, val, TCJ))
        # Converting to TC unit
        tempabs = np.array(tempabs)
        if self._TCunit == 'K':
            temp = tempabs
        elif self._TCunit == 'C':
            temp = tempabs - 273.15
        elif self._TCunit == 'F':
            temp = 9/5*(tempabs-273.15) + 32
        if len(temp) == 1:
            temp = temp[0]
        else:
            temp = list(temp)
        return temp

    # OTHER METHODS
    def set_reference(self, names, mode='Single-Ended'):
        """
        Set reference point for analog input voltage.

        Parameters
        ----------
        names : str, list
            The analog port(s) that will that will have the voltage reference
            point modified.
        mode : str
            The reference mode for the analog channels. It and can be either
            `Single-Ended` or `Differential`. Default is `Single-Ended'.

        Returns
        -------
        None.

        Example
        -------
        Set port AIN0 for differential reading with port AIN1.
        >>> ljt7.set_reference('AIN0', 'Differential')

        Set ports AIN0, AIN2, and AIN6 for differential reading respecitvely
        with ports AIN1, AIN3, and AIN7.
        >>> ljt7.set_reference(['AIN0', 'AIN2', 'AIN6'], 'Differential')

        Set all ports for single-ended reading
        >>> ljt7.set_reference('ALL', 'Single-Ended')

        """
        # Checking for valid inputs
        if type(names) != list: names = [names]
        if mode.lower() not in ['single-ended', 'differential']:
            raise Exception(
                "Valid reference types are 'Single-Ended' or 'Differential'.")
        # Assigning range values
        if (names[0].lower() == 'all') and (len(names) == 1):
            numref = 199
            if mode.lower() == 'differential':
                numref = 1
            ljm.eWriteName(self._commhandle, 'AIN_ALL_NEGATIVE_CH', numref)
        else:
            if not self._check_port(names, "AIN"):
                raise Exception('Invalid analog input port name(s).')
            flagport, portnum = self._check_portdiff(names)
            if flagport:
                numref = 199
                for name, num in zip(names, portnum):
                    if mode.lower() == 'differential':
                        numref = num + 1
                    ljm.eWriteName(self._commhandle, name + '_NEGATIVE_CH', numref)
            else:
                raise Exception('Valid analog ports are AIN0/2/4/6/8/10/12.')

    def set_range(self, names, ranges):
        """
        Set analog input voltage range.

        Parameters
        ----------
        names : str, list
            The analog port(s) that will have their ranges modified.
            Use `all` to set all analog input ports to the same range.
        value : float, list
            The voltage range value to be used.
            Valid ranges are +/- 10, 1, 0.1, 0.01 V.
            If a single value is used, it will be applied to all
            channels in NAMES.

        Returns
        -------
        None.

        Example
        -------
        SSet port AIN0 with a range of +/- 1 V.
        >>> ljt7.set_range('AIN0', 1)

        Set port AIN0 and AIN2 with a range of +/- 0.1 and 0.01 V
        >>> ljt7.set_range(['AIN0', 'AIN2'], [0.1, 0.01])

        Set all ports with a (default) range of +/- 10 V
        >>> ljt7.set_range('ALL', 10)

        """
        # Checking for valid inputs
        if type(names) != list: names = [names]
        if type(ranges) != list: ranges = [ranges]
        if (len(names) != len(ranges)) and (len(ranges) > 1):
            raise Exception('Number of ports must match number of ranges.')
        if len(ranges) == 1:
            ranges = ranges * len(names)
        for r in ranges:
            if r not in [10, 1, 0.1, 0.01]:
                raise Exception('Valid ranges are 10, 1, 0.1, or 0.01 V.')
        # Assigning range values
        if (names[0].lower() == 'all') and (len(names) == 1):
            ljm.eWriteName(self._commhandle, 'AIN_ALL_RANGE', ranges[0])
        else:
            if not self._check_port(names, "AIN"):
                raise Exception('Invalid analog input port name(s).')
            for name, r in zip(names, ranges):
                ljm.eWriteName(self._commhandle, name + '_RANGE', r)

    # HELPER METHODS (PRIVATE)
    def _assign_info(self, info):
        # Assigning info collected from LabJack handle to private attributes
        self._type = "T" + str(info[0])
        self._serialnum = str(info[2])
        if info[1] == 1:
            self._connection = "USB"
            self._ipaddress = "N/A"
            self._port = "N/A"
        elif info[1] == 2:
            self._connection = "TCP"
            self._ipaddress = ljm.numberToIP(info[3])
            self._port = str(info[4])
        elif info[1] == 3:
            self._connection = "ETHERNET"
            self._ipaddress = "N/A"
            self._port = "N/A"
        elif info[1] == 4:
            self._connection = "WIFI"
            self._ipaddress = ljm.numberToIP(info[3])
            self._port = str(info[4])
        else:
            self._connection = "Unknown"
            self._ipaddress = "Unknown"
            self._port = "Unknown"
        # More stuff
        value = ljm.eReadName(self._commhandle, 'HARDWARE_VERSION')
        self._hardware = np.round(value, 2)
        value = ljm.eReadName(self._commhandle, 'FIRMWARE_VERSION')
        self._firmware = np.round(value, 3)

    def _assign_ports(self):
        """
        Creates lists with valid LabJAck T7 port names
        """
        # Assigning analog output port names
        self._portDAC = [
            'DAC0', 'DAC1']
        # Assigning analog inout port names
        self._portAIN = [
            'AIN0', 'AIN1', 'AIN2', 'AIN3', 'AIN4', 'AIN5', 'AIN6', 'AIN7',
            'AIN8', 'AIN9', 'AIN10', 'AIN11', 'AIN12', 'AIN13', 'AIN14']
        # Assigning digital I/O port names
        self._portDIO = [
            'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
            'DIO6', 'DIO7', 'DIO8', 'DIO9', 'DIO10', 'DIO11',
            'DIO12', 'DIO13', 'DIO14', 'DIO15', 'DI16', 'DI17',
            'DIO18', 'DIO19', 'DIO20', 'DIO21', 'DIO22']
        # Assigning alternate digital I/O port names
        self._portDIOAlt = [
            'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO4', 'FIO5',
            'FIO6', 'FIO7', 'EIO0', 'EIO1', 'EIO2', 'EIO3',
            'EIO4', 'EIO5', 'EIO6', 'EIO7', 'CIO0', 'CIO1',
            'CIO2', 'CIO3', 'MIO0', 'MIO1', 'MIO2']

    def _check_port(self, namelist, portype, *args):
        """
        Checks if the port NAMES belong to the possible values in
        the correposnding PORTTYPE list of valid names. If all NAMES
        are valid then FLAGPORT = 1, otherwise FLAGPORT = 0

        A user defined list can be passed with PORTYPE = 'USER'
        through an additional argument VARARGIN

        """
        # Making sure inputs are lists
        if type(namelist) != list:
            namelist = [namelist]
        # Assining valid names list based on port type
        if portype == "DAC":
            validnames = self._portDAC
        elif portype == "AIN":
            validnames = self._portAIN
        elif portype == "DIO":
            validnames = self._portDIO + self._portDIOAlt
        elif portype == "ALLIN":
            validnames = self._portAIN + self._portDIO + self._portDIOAlt
        elif portype == "PWM":
            validnames = [
                'DIO0', 'DIO2', 'DIO3', 'DIO4', 'DIO5',
                'FIO0', 'FIO2', 'FIO3', 'FIO4', 'FIO5']
        elif portype == "QUAD":
            validnames = [
                'DIO0', 'DIO1', 'DIO2', 'DIO3', 'DIO6', 'DIO7',
                'FIO0', 'FIO1', 'FIO2', 'FIO3', 'FIO6', 'FIO7']
        elif portype == "USER":
            validnames = args[0]
        # Returns true if all names in namelist are validnames
        return all([name in validnames for name in namelist])

    def _check_portdiff(self, names):
        # Making sure inputs are lists
        if type(names) != list:
            names = [names]
        # Getting port name digits
        portnum = [int(re.findall(r'\d+', name)[0]) for name in names]
        # Checking for all even digits
        flagport = all([(num % 2)==0 for num in portnum])
        # Checking for AIN14
        if 14 in portnum:
            flagport = False
        # Returning flag and port numbers
        return flagport, portnum

    def _get_portalt(self, name):
        """
        Returns the alternate DIO port name corresponding to the input NAME

        """
        # Making sure input is a not list
        if type(name) == list:
            name = name[0]
        try:
            portnum = self._portDIO.index(name)
            portalt = self._portDIOAlt[portnum]
        except ValueError:
            portalt = "none"
        return portalt

    def _set_port_values(self, names, values):
        """
        Calls LJM method to write name values to LabJack

        """
        ljm.eWriteNames(self._commhandle, len(names), names, values)

    def _get_port_values(self, names):
        """
        Calls LJM method to read name values from LabJack

        """
        values = ljm.eReadNames(self._commhandle, len(names), names)
        if len(values) == 1:
            values = values[0]
        return values

    def _get_port_address(self, names):
        """
        Gets port addresses corresponding to the ports in NAMES from LabJack

        """
        return ljm.namesToAddresses(len(names), names)[0]


