"""
Implements each step defined in the YAML config file.

This module implements a single step.
A step consists of a very small function.
One string sent over the Serial port, a few I2C commands,
or executing a tcl file.

| author     = "Seth Miers"
| copyright  = "Copyright, Xilinx Inc. All rights reserved."
| credits    = ["Seth Miers"]
| license    = "GPL"
| version    = "3.0"
| maintainer = "Seth Miers"
| email      = "sethm@xilinx.com"
| status     = "Development"


"""

##########################################
#               Imports                  #
##########################################

# --------------Packages---------------- #
import os           # only used for IPA command
import re           # finding serial/iic ports
import logging      # General Logging
import string       # used for valid_data
import time         # used for delays
import threading
import serial       # Serial Class
try:
    import queue
except ImportError:
    import Queue as queue

# --------------Local------------------- #
import boardframework  # for globals
import iiccomm      # Iic Class
import passfail     # for passfail history
import ethernetcomm

# -------------Globals------------------ #
VERSION = boardframework.VERSION
FILE_MANAGER = boardframework.FILE_MANAGER
SERIAL_MANAGER = boardframework.SERIAL_MANAGER

##########################################
#              Main Code                 #
##########################################


class Step(passfail.PassFail, passfail.CheckPassFail):
    '''
    Runs through a single step checking for pass and fail
    along the way. A single step is the most basic form
    of testing and can include a single IIC command, a
    string to send over the serial port, a period of time to wait,
    a TCL script to run, etc...

    Args:
        timeout (int): The time in seconds that should be
            used for the timeout condition
        command (string): The command for this step. A
            path to a tcl file, a serial port string to send, or
            even an IIC command.
        continue_on_fail (bool): Should the step keep
            running after a fail condition is found
            or should it return immediately

    Attributes:
        stop (bool): An indicator that is imbedded in
            all loops. When this is set to True the
            step will safely exit out.
        executor_logger (logging): The logger that is used
            throughout the executor portion
            of the program.

    Todo:
        * Is there a good way to reduce the number of
          arguments?
        * Add the find_and_replace_vars function to the
          command
        * Add the find_and_replace_vars function to the
          timeout
        * Add the find_and_replace_vars function to the
          continue_on_fail
        * History is currently references as [0] consider
          making this a varible so we can run
          a particular step a number of times and keep
          track of it.
    '''
    def __init__(self, pass_conditions, fail_conditions,
                 timeout, command,
                 continue_on_fail):
        # pylint: disable=too-many-arguments
        # Six argumetns seems like a resonable amount
        # for what this class has to do
        passfail.PassFail.__init__(self)
        passfail.CheckPassFail.__init__(self, pass_conditions, fail_conditions)
        self.timeout = timeout
        self.command = command
        self.continue_on_fail = continue_on_fail
        self.stop = False
        self.executor_logger = logging.getLogger("BoardFramework.Executor")
        self.text = ''

    def update_text(self, text):
        '''
        All steps will return some form of text. This text
        is appended to the text attribute and also posted
        to the executor_logger.
        '''
        self.text += text
        self.executor_logger.info(repr(text))

    def start_step(self):
        '''
        This method is likely overrided by another subclass.
        It is used to start whatever step is being run.
        '''
        while not self.stop:
            break

    def reset_step(self):
        '''
        Reset the step to it's default state so that it
        can be run again.
        '''
        self.stop = False
        self.text = ''

    def run_step(self):
        '''
        This method is likely overridden by anotehr subclass.
        This method should always be run in the subclass
        by running super().run_step() at the very end of the
        overridden method. Here we check to make sure
        all of the pass conditions are found and none of the
        fail conditions are found.

        Attributes:
            found_fail_conditions (list): A list of all the
                fail conditions that were found, returned
                back as the original regex.
            missing_pass_conditions (list): A list of all the
                pass conditions that were not found, returned
                back as the original regex.
        '''
        found_fail_conditions = self.check_for_fail()
        missing_pass_conditions = self.check_for_pass()
        if found_fail_conditions or missing_pass_conditions:
            for found_fail_condition in found_fail_conditions:
                self.history[0].fail_cause.append(['Found: ',
                                                   found_fail_condition])
            for missing_pass_condition in missing_pass_conditions:
                self.history[0].fail_cause.append(['Missing: ',
                                                   missing_pass_condition])
        # There are no missing pass conditions and no
        # found fail conditions. This means that the test passed.
        else:
            self.history[0].passed = True

    def stop_step(self):
        '''
        This method is likely overridden by another subclass.
        This method is used to completely stop the step.
        Likely all that is done is self.stop being set to true.
        If overridden, then this method should always be
        back referenced by using super().stop_step().
        '''
        self.stop = True


class Serial(Step):
    '''
    The Serial Step subclass sends data over a serial port (com/tty).
    The step doesn't exit until all pass conditions are met,
    timeout occurs, or a fail condition is fou nd (unless continue_on_fail
    is set to true). A port is connected to by looking at all
    available serial ports and connecting to the one that matches
    the port_regex.

    Note:
        serial port can be used to connect to a linux system
        and simply delay execution until the login prompt is found.
        It can be used to send a set of menu options to a
        serial port and ignore all responses.

    Args:
        port_regex (string): The regular expression used
            to connect to a certain serial port.
        baud (int): The baud rate to use when connecting
            to the serial port.
        stopbits (int): How many stop bits should be used
            for this particular connection?

    Attributes:
        serial_comm (SerialComm): handles all the connections
            to the serial port.

    Todo:
        * Add the find_and_replace_vars function to the
          port_regex
        * Add the find_and_replace_vars function to the
          baud
        * Add the find_and_replace_vars function to the
          stopbits
        * Is there a good way to reduce the number
          of arguments?
    '''
    def __init__(self, pass_conditions=None, fail_conditions=None,
                 timeout=30, command="",
                 continue_on_fail=False, port_regex="", baud=115200,
                 stopbits=serial.STOPBITS_ONE):
        # pylint: disable=too-many-arguments
        # Nine argumetns seems like a resonable amount
        # for what this class has to do
        Step.__init__(self, pass_conditions, fail_conditions,
                      timeout, command,
                      continue_on_fail)
        # self.port_regex = find_replace_vars(port_regex)
        self.serial_comm = SERIAL_MANAGER.get_serial_comm(self.timeout,
                                                          port_regex,
                                                          baud, stopbits)
        self.executor_logger.debug("Created Serial class")

    # def start_step(self):
    #     '''
    #     Starting the step simply consists of starting the
    #     serial_comm, which handles all the connections and
    #     reading/writing to the serial port.
    #     '''
    #     return super().start_step()
    #     # return self.serial_comm.start_serial()

    def run_step(self):
        '''
        Continously read lines from the serial port. Put these lines into
        the class' text attribute so that it can be used later.
        Every time a new line is received, run the pass checks
        and the fail checks.

        Attributes:
            read_data (string): The line that was just read by
                the connected serial port.

        Todo:
            * Create some kind of break for continue_on_fail
        '''
        while not self.stop:
            read_data = self.serial_comm.readline(1, 80)
            if read_data is not None:
                self.executor_logger.debug("Serial step read the \
                                           following data: %s",
                                           read_data)
                self.update_text(read_data)
            else:
                self.executor_logger.error("Timeout occured...")
                break
            if not self.check_for_pass():
                self.executor_logger.error("Pass conditions found")
                break
            if self.check_for_fail():
                self.executor_logger.error("Fail condition found")
                break
        super().run_step()

    def stop_step(self):
        '''
        Issue a stop to the serial comm and call
        the super's stop method to indicate we are completely
        finished with this step's execution
        '''
        self.serial_comm.stop_serial()
        super().stop_step()

    def reset_step(self):
        self.serial_comm.reset_serial()
        super().reset_step()


class SerialToIicBridge(Step):
    '''
    The SerialToIicBridge is only here to support old versions of the
    System Controller that was typically used in 7-Series and
    Ultrascale FPGA development boards. This step is typically used
    in conjunction with the old 'BoardUI'. This step simply connects
    a specified serial port with an I2C port by using commands such as
    'IW0\\r74\\r\\04\\r\\t' or 'IR0\\r74\\r01\\r\\t'. Sending the escape
    character over the serial port will cause this step to end. The
    serial port connects with 115200 baud and 1 stop bit. This
    is standard for the system controller protocol.

    Args:
        port_regex (string): The regular expression used
            to connect to a certain serial port

    Attributes:
        serial_comm (SerialComm): Handles all the connections
            to the serial port.
        iic_comm (IicComm): Handles all the connections
            to the IIC ports.
        scratchpad (FileComm): The scratchpad area
        runonboot (FileComm): The run on boot area
        input_text (string): all text received by the connected
            serial port
        input_text (string): All text received by and sent to
            the connected serial port

    TODO:
        * Should the serial port be stopped when the step finishes?
    '''
    # pylint: disable=too-many-instance-attributes
    # This sectionw ill be depricated and 9 is a reasonable amount
    def __init__(self, pass_conditions=None, fail_conditions=None,
                 timeout=30, command="",
                 continue_on_fail=False, port_regex=""):
        # pylint: disable=too-many-arguments
        # Seven argumetns seems like a resonable amount
        # for what this class has to do
        Step.__init__(self, pass_conditions, fail_conditions,
                      timeout, command,
                      continue_on_fail)
        self.ethernet_comm = ethernetcomm.EthernetComm(self.executor_logger, 5,
                                                       16339, 16340)
        self.serial_comm = SERIAL_MANAGER.get_serial_comm(self.timeout,
                                                          port_regex,
                                                          115200, 1)
        self.ethernet_comm_thread = None
        self.serial_comm.add_fake_input(command)
        self.iic_comm = iiccomm.IicComm(0)
        self.scratchpad = FILE_MANAGER.get_file_comm('scratchpad')
        self.runonboot = FILE_MANAGER.get_file_comm('runonboot')
        self.input_text = ""
        self.all_text = ""

        self.receive_queue = queue.Queue()

    def read_single(self):
        '''
        read a single character from either the
        ethernet or the uart.

        Attributes:
            full_read: The ethernet can return
                a whole string so it needs to be broken
                apart into single characters
            read_char: A single character
        '''
        try:
            full_read = self.ethernet_comm.receive_queue.get(False)
            if full_read[1] == 'sysc':
                for read_char in full_read[2]:
                    self.receive_queue.put(read_char)
        except queue.Empty:
            pass

        try:
            return self.receive_queue.get(False)
        except queue.Empty:
            return self.serial_comm.read(self.timeout)

    def write_out(self, data):
        '''
        write something out to both the uart
        and the ethernet comm
        '''
        self.serial_comm.outqueue.put(data)
        self.ethernet_comm.send_queue.put((1, 'sysc', data))

    # def start_step(self):
    #     '''
    #     Starting the step simply consists of starting the
    #     serial_comm, which handles all the connections and
    #     reading/writing to the serial port.
    #     '''
    #     return super().start_step()
    #     # return self.serial_comm.start_serial()

    def run_step(self):
        '''
        Run get command in a continuious loop.
        Get command only returns if an escape is seen
        on the serial port.
        '''
        self.ethernet_comm_thread = threading.Thread(
            target=self.ethernet_comm.start_server, name='X_THREAD')
        self.ethernet_comm_thread.setDaemon(True)
        self.ethernet_comm_thread.start()
        time_val = os.popen("cat /proc/uptime | \
                             awk '{print $1 }'").read().strip()
        self.write_out('[ ' + str(time_val) + ' ]\t')
        self.all_text += str('[ ' + str(time_val) + " ]\t")
        self.write_out("DONE\r")
        self.all_text += str("DONE\r")
        while not self.stop:
            self.get_cmd()

        self.ethernet_comm.stop = True
        self.ethernet_comm_thread.join()

    def failure_read(self):
        '''
        This method is called when a command wasn't
        properly formatted. It reads characters from
        the serial port until a tab is found.

        Attributes:
            char (Char): the character to read into. If
                no character is found after timeout seconds, then
                we assume that everything has been read and
                the user is ready to type in new commands
        '''
        char = ''
        while not self.stop:
            char = self.read_single()
            self.input_text += str(char)
            self.all_text += str(char)
            if char == '\t':
                break
            self.write_out(char)
        self.write_out(":R\r")
        self.all_text += str(":R\r")

    def read_chunk(self, valid_data):
        '''
        Read and return a chunk of data. A chunk is defined
        as everything up until a return (\\r) character. If
        no characters are found for timeout seconds then we
        assume that a chunk has completed and return.

        Attributes:
            read_data (String): The place where the chunk
                is stored until it is returned.
            char (Char): the character to read into. If
                no character is found after timeout seconds, then
                we assume that everything has been read.
        '''
        read_data = ''
        while not self.stop:
            char = self.read_single()
            self.input_text += str(char)
            self.all_text += str(char)
            if char == '\t':
                break
            if char == '\x1b':
                return None
            self.write_out(char)
            if char == '\r':
                break
            read_data += char
            if char == '\b':
                return '\b'
        for char in read_data:
            if char not in valid_data:
                self.failure_read()
                return None
        return read_data

    def process_iic_ping(self, cmd):
        '''
        Interpret the command as a ping command. This
        is simply to look for an ack or a nack on a particular
        address to confirm if a address exists.

        Attributes:
            confirm_sent (list): Was the message sent. If
                this value is 'None' then a Nack was received.

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 2:
            confirm = self.iic_comm.iic_ping(cmd[1])
            if confirm is None:
                return "Nack"
            return "Ack"
        return None

    def process_iic_continious_write(self, cmd):
        '''
        Interpret the command as an iic continious write command.
        This method pulls out the data into a list and
        if the write data happens to be odd, it will add a '0'
        at the most significant bit. If there are not equal write
        commands as specified in the command, then this method
        will return an error.

        Attributes:
            wr_length (int): how many write commands are going
                to be done consecutively.
            confirm_sent (list): What was the message sent. If
                this value is 'None' then a Nack was received.

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        try:
            wr_length = int(cmd[2], 16)
        except ValueError:
            return None
        if len(cmd) == 3 + wr_length:
            for wr_index in range(1, wr_length+1):
                if len(cmd[2+wr_index]) % 2:
                    cmd[2+wr_index] = '0' + cmd[2+wr_index]
                cmd[2+wr_index] = re.findall('..', cmd[2+wr_index])
                try:
                    cmd[2+wr_index] = [int(wr_data, 16)
                                       for wr_data in cmd[2+wr_index]]
                except ValueError:
                    return None
                confirm_sent = self.iic_comm.iic_send(cmd[1], cmd[2+wr_index])
                if confirm_sent is None:
                    return "Nack"
            return "Ack"
        return None

    def process_iic_read(self, cmd):
        '''
        Interpret the command as an iic read command. This method
        will put the received characters on the outqueue of the
        serial port so it can be seen what was read from the
        iic.

        Attributes:
            read_data (list): A liste containing each of the bytes
                read off of the iic bus.

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 3:
            try:
                cmd[2] = int(cmd[2], 16)
            except ValueError:
                return None
            read_data = self.iic_comm.iic_recv(cmd[1], cmd[2])
            if read_data is None:
                return "Nack"
            self.write_out(':P\r')
            self.all_text += str(":P\r")
            for read_byte in read_data:
                self.write_out(str(format(read_byte,
                                          '02x')).upper())
                self.all_text += str(format(read_byte, '02x')).upper()
            return "Done"
        return None

    def process_iic_repeated_start(self, cmd):
        '''
        Interpret the command as an iic repeated start
        command. If the write command happens to be odd,
        then this method will add a '0' to the MSB of
        that string. If the command is not of length
        4 then the method will return 'None'

        Attributes:
            read_data (list): A liste containing each of the bytes
                read off of the iic bus.

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 4:
            try:
                cmd[3] = int(cmd[3], 16)
            except ValueError:
                return None
            if len(cmd[2]) % 2:
                cmd[2] = '0' + cmd[2]
            cmd[2] = re.findall('..', cmd[2])
            try:
                cmd[2] = [int(wr_data, 16) for wr_data in cmd[2]]
            except ValueError:
                return None
            read_data = self.iic_comm.iic_send_rs_recv(cmd[1],
                                                       cmd[2],
                                                       cmd[3])
            if read_data is None:
                return "Nack"
            self.write_out(':P\r')
            self.all_text += str(":P\r")
            for read_byte in read_data:
                self.write_out(str(format(read_byte,
                                          '02x')).upper())
                self.all_text += str(format(read_byte, '02x')).upper()
            return "Done"
        return None

    def process_iic_write(self, cmd):
        '''
        Interpret the command as an iic write command.
        This method pulls out the data into a list and
        if the data happens to be odd, it will add a '0'
        at the most significant bit.

        Attributes:
            confirm_sent (list): What was the message sent. If
                this value is 'None' then a Nack was received.

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 3:
            if len(cmd[2]) % 2:
                cmd[2] = '0' + cmd[2]
            cmd[2] = re.findall('..', cmd[2])
            try:
                cmd[2] = [int(wr_data, 16) for wr_data in cmd[2]]
            except ValueError:
                return None
            confirm_sent = self.iic_comm.iic_send(cmd[1], cmd[2])
            if confirm_sent is None:
                return "Nack"
            return "Ack"
        return None

    def process_iic(self, cmd):
        '''
        The iic commands only have a set amount of options.
        This method interprets those options and calls
        the apropriate methods based on the results. This
        method also sets the correct iic port that this
        command will be processed on

        Returns:
            string: "Ack", "Nack", or None for pass, nack, and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        self.iic_comm.set_iic_port(int(cmd[0][2]))
        if cmd[0][1] == 'P':
            return self.process_iic_ping(cmd)
        if cmd[0][1] == 'R':
            return self.process_iic_read(cmd)
        if cmd[0][1] == 'W':
            return self.process_iic_write(cmd)
        if cmd[0][1] == 'C':
            return self.process_iic_continious_write(cmd)
        if cmd[0][1] == 'S':
            return self.process_iic_repeated_start(cmd)
        return None

    def process_scratchpad(self, cmd):
        '''
        MWS and MRS commands are interpreted here. This
        uses a scratchpad to save non-volatile data.

        Attributes:
            read_data (string): the data that is read
                out of the scratchpad

        Returns:
            string: "Ack" or None for pass and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 3:
            if cmd[0][1] == 'W':
                self.write_out(':P')
                self.all_text += str(':P')
                self.scratchpad.update(int(cmd[1], 16), cmd[2])
            else:
                return None
        elif len(cmd) == 2:
            if cmd[0][1] == 'R':
                read_data = ':P\r' + str(self.scratchpad.get(int(cmd[1], 16)))
                self.write_out(read_data)
                self.all_text += str(read_data)
            else:
                return None
        else:
            return None

        return "Done"

    def process_runonboot(self, cmd):
        '''
        MWB and MDB commands are interpreted here. This
        uses a runonboot to save non-volatile data. When
        MWB is called, the get_cmd method will be run
        and all data from that command will be
        recorded into a buffer, so that the program
        knows what to run on boot.

        Attributes:
            current_text_length (int): How much text has already
                been recorded by get_cmd. We want to dump
                all of this data later, we don't want to record
                what has already happened.
            result (bool): get_cmd returns a true or a false based
                on if the backspace character was hit or not

        Returns:
            string: "Ack" or None for pass and
                fail. If the command isn't formatted properly
                then it will return a None.
        '''
        if len(cmd) == 2:
            if cmd[0][1] == 'D':
                self.write_out(':P')
                self.all_text += str(':P')
                self.runonboot.delete(int(cmd[1], 16))
            elif cmd[0][1] == 'W':
                self.write_out(":P\r{")
                self.all_text += str(":P\r{")
                current_text_length = len(self.input_text)
                while not self.stop:
                    result = self.get_cmd()
                    if not result:
                        break
                self.runonboot.update(int(cmd[1], 16),
                                      self.input_text[current_text_length:-1] +
                                      'EXT\r\t')
                self.write_out("}:P")
                self.all_text += str("}:P")
            else:
                return None
        else:
            return None
        return "Done"

    def erase_nonvolatile(self):
        '''
        Completely erase both the runonboot and
        the scratchpad.
        '''
        for index in self.runonboot.get_keys():
            self.runonboot.delete(index)
        for index in self.scratchpad.get_keys():
            self.scratchpad.delte(index)

    def get_cmd(self):
        '''
        Read a command string from the serial port (up until
        a tab chracter '\t' is found). The command string is
        then interpreted and what needs to run is processed.
        This all follows the Serial to I2C Bridge Protocol.
        When 'EXT\r\t' is found on the Serial port this step will
        exit.

        Attributes:
            cmd (list): the place where the command is stored.
                parts of this list change from string to
                int throghout this method.
            chunk (string): A string that is read from the
                serial port up until a return chracter is
                found.
            result (string): After a command is interpreted
                it can result in a pass, a fail, or a nack.

        Todo:
            * This method has too many statements and needs to be
              refactored into smaller methods.
            * This method has too many branches and needs to be
              refactored into smaller methods

        '''
        # pylint: disable=too-many-branches
        # There is a reasonable amount of branches for now
        # but this function does need to be refactored
        # pylint: disable=too-many-statements
        # There is a reasonable amount of statements for now
        # but this function does need to be refactored
        cmd = []
        while not self.stop:
            chunk = self.read_chunk(string.printable+'\b')
            if chunk is None:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
                return True
            if chunk == '':
                break
            if chunk == '\b':
                return False
            if chunk == '\x03':
                self.stop_step()
            cmd.append(chunk)

        cmd = [x.upper() for x in cmd]

        if not cmd:
            self.write_out(":R\r")
            self.all_text += str(":R\r")
            return True

        if len(cmd[0]) != 3:
            self.write_out(":R\r")
            self.all_text += str(":R\r")
            return True

        if cmd[0][0] == 'M':
            if cmd[0][2] == 'S':
                result = self.process_scratchpad(cmd)
            elif cmd[0][2] == 'B':
                result = self.process_runonboot(cmd)
            else:
                result = None

            if result is None:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
            elif result == "Done":
                self.write_out("\r")
                self.all_text += str("\r")
        elif cmd[0] == 'IPA':
            if len(cmd) == 1:
                ipv4 = os.popen('ip addr show eth0 | \
                                 grep "\\<inet\\>" | \
                                 awk \'{ print $2 }\' | \
                                 awk -F "/" \'{ print $1 }\'').read().strip()
                self.write_out(ipv4+":P\r")
                self.all_text += str(ipv4+":P\r")
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        elif cmd[0][0] == 'I':
            if len(cmd) < 2:
                result = None
            elif len(cmd[1]) != 2:
                result = None
            else:
                try:
                    cmd[1] = int(cmd[1], 16)
                    result = self.process_iic(cmd)
                except ValueError:
                    result = None
            if result is None:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
            elif result == "Nack":
                self.write_out(":N\r")
                self.all_text += str(":N\r")
            elif result == "Ack":
                self.write_out(":P\r")
                self.all_text += str(":P\r")
            elif result == "Done":
                self.write_out("\r")
                self.all_text += str("\r")
        elif cmd[0] == 'VRN':
            if len(cmd) == 1:
                self.write_out(":P\r")
                self.all_text += str(":P\r")
                self.write_out("Linux " + VERSION + " I2C Bridge v0.95\r")
                self.all_text += str("Linux " + VERSION +
                                     " I2C Bridge v0.95\r")
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        elif cmd[0] == '~~~':
            if len(cmd) == 1:
                self.write_out(":P\r")
                self.all_text += str(":P\r")
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        elif cmd[0] == 'EXT':
            if len(cmd) == 1:
                self.write_out(":P\r")
                self.all_text += str(":P\r")
                self.stop_step()
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        elif cmd[0] == 'ERS':
            if len(cmd) == 1:
                self.erase_nonvolatile()
                self.write_out(":P\r")
                self.all_text += str(":P\r")
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        elif cmd[0] == 'DLY':
            if len(cmd) == 2:
                try:
                    cmd[1] = int(cmd[1], 16)
                    self.write_out(":P\r")
                    self.all_text += str(":P\r")
                    time.sleep(cmd[1]/100000)
                except ValueError:
                    result = None
                    self.write_out(":R\r")
                    self.all_text += str(":R\r")
            else:
                self.write_out(":R\r")
                self.all_text += str(":R\r")
        else:
            self.failure_read()

        return True


##########################################
#             Unit Testing               #
##########################################

if __name__ == "__main__":
    X = SerialToIicBridge(port_regex="/dev/ttyPS0")
    X.start_step()
    X.run_step()
    # X = Serial(pass_conditions=['pass'],
    #          fail_conditions=['fail'],
    #          port_regex=r"COM44")
    # X.start_step()
    # X.run_step()
    # X.stop_step()
