def __init__(self,
                 protocol_xml_path="default_config.xml",
                 command_xml_path="default_config.xml",
                 verbose=False):
        super(KilroyProtocols, self).__init__()

        # Initialize internal attributes
        self.verbose = verbose
        self.protocol_xml_path = protocol_xml_path
        self.command_xml_path = command_xml_path
        self.protocol_names = []
        self.protocol_commands = []  # [Instrument Type, command_info]
        self.protocol_durations = []
        self.num_protocols = 0
        self.status = [-1, -1]  # Protocol ID, command ID within protocol
        self.issued_command = []
        self.received_message = None

        print(
            "----------------------------------------------------------------------"
        )

        # Create instance of ValveCommands class
        print('creating valveCommands')
        self.valveCommands = ValveCommands(xml_file_path=self.command_xml_path,
                                           verbose=self.verbose)

        # Connect valve command issue signal
        self.valveCommands.change_command_signal.connect(
            self.issueValveCommand)
        print(self.issueValveCommand)

        # Create instance of PumpCommands class
        self.pumpCommands = PumpCommands(xml_file_path=self.command_xml_path,
                                         verbose=self.verbose)

        # Connect pump commands issue signal
        self.pumpCommands.change_command_signal.connect(self.issuePumpCommand)

        # Create GUI
        self.createGUI()

        # Load configurations
        self.loadProtocols(xml_file_path=self.protocol_xml_path)

        # Create protocol timer--controls when commands are issued
        self.protocol_timer = QtCore.QTimer()
        self.protocol_timer.setSingleShot(True)
        self.protocol_timer.timeout.connect(self.advanceProtocol)

        # Create elapsed time timer--determines time between command calls
        self.elapsed_timer = QtCore.QElapsedTimer()
        self.poll_elapsed_time_timer = QtCore.QTimer()
        self.poll_elapsed_time_timer.setInterval(1000)
        self.poll_elapsed_time_timer.timeout.connect(self.updateElapsedTime)
Example #2
0
    def __init__(self,
                 protocol_xml_path = "default_config.xml",
                 command_xml_path = "default_config.xml",
                 verbose = False):
        super(KilroyProtocols, self).__init__()

        # Initialize internal attributes
        self.verbose = verbose
        self.protocol_xml_path = protocol_xml_path
        self.command_xml_path = command_xml_path
        self.protocol_names = []
        self.protocol_commands = [] # [Instrument Type, command_info]
        self.protocol_durations = []
        self.num_protocols = 0
        self.status = [-1, -1] # Protocol ID, command ID within protocol
        self.issued_command = []
        self.received_message = None

        print "----------------------------------------------------------------------"
        
        # Create instance of ValveCommands class
        self.valveCommands = ValveCommands(xml_file_path = self.command_xml_path,
                                           verbose = self.verbose)

        # Connect valve command issue signal
        self.valveCommands.change_command_signal.connect(self.issueValveCommand)

        # Create instance of PumpCommands class
        self.pumpCommands = PumpCommands(xml_file_path = self.command_xml_path,
                                         verbose = self.verbose)

        # Connect pump commands issue signal
        self.pumpCommands.change_command_signal.connect(self.issuePumpCommand)
        
        # Create GUI
        self.createGUI()

        # Load configurations
        self.loadProtocols(xml_file_path = self.protocol_xml_path)

        # Create protocol timer--controls when commands are issued
        self.protocol_timer = QtCore.QTimer()
        self.protocol_timer.setSingleShot(True)
        self.protocol_timer.timeout.connect(self.advanceProtocol)

        # Create elapsed time timer--determines time between command calls
        self.elapsed_timer = QtCore.QElapsedTimer()
        self.poll_elapsed_time_timer = QtCore.QTimer()
        self.poll_elapsed_time_timer.setInterval(1000)
        self.poll_elapsed_time_timer.timeout.connect(self.updateElapsedTime)
Example #3
0
class KilroyProtocols(QtGui.QMainWindow):

    # Define custom command ready signal
    command_ready_signal = QtCore.pyqtSignal(
    )  # A command is ready to be issued
    status_change_signal = QtCore.pyqtSignal(
    )  # A protocol status change occured
    completed_protocol_signal = QtCore.pyqtSignal(
        object)  # Name of completed protocol
    error_signal = QtCore.pyqtSignal(
        object
    )  # An error has occurred (CURRENTLY UNUSED! USE ME FOR PROTOCOL ERRORS)

    def __init__(self,
                 protocol_xml_path="default_config.xml",
                 command_xml_path="default_config.xml",
                 verbose=False):
        super(KilroyProtocols, self).__init__()

        # Initialize internal attributes
        self.verbose = verbose
        self.protocol_xml_path = protocol_xml_path
        self.command_xml_path = command_xml_path
        self.protocol_names = []
        self.protocol_commands = []  # [Instrument Type, command_info]
        self.protocol_durations = []
        self.num_protocols = 0
        self.status = [-1, -1]  # Protocol ID, command ID within protocol
        self.issued_command = []
        self.received_message = None

        print "----------------------------------------------------------------------"

        # Create instance of ValveCommands class
        self.valveCommands = ValveCommands(xml_file_path=self.command_xml_path,
                                           verbose=self.verbose)

        # Connect valve command issue signal
        self.valveCommands.change_command_signal.connect(
            self.issueValveCommand)

        # Create instance of PumpCommands class
        self.pumpCommands = PumpCommands(xml_file_path=self.command_xml_path,
                                         verbose=self.verbose)

        # Connect pump commands issue signal
        self.pumpCommands.change_command_signal.connect(self.issuePumpCommand)

        # Create GUI
        self.createGUI()

        # Load configurations
        self.loadProtocols(xml_file_path=self.protocol_xml_path)

        # Create protocol timer--controls when commands are issued
        self.protocol_timer = QtCore.QTimer()
        self.protocol_timer.setSingleShot(True)
        self.protocol_timer.timeout.connect(self.advanceProtocol)

        # Create elapsed time timer--determines time between command calls
        self.elapsed_timer = QtCore.QElapsedTimer()
        self.poll_elapsed_time_timer = QtCore.QTimer()
        self.poll_elapsed_time_timer.setInterval(1000)
        self.poll_elapsed_time_timer.timeout.connect(self.updateElapsedTime)

    # ------------------------------------------------------------------------------------
    # Advance the protocol to the next command and issue it
    # ------------------------------------------------------------------------------------
    def advanceProtocol(self):
        status = self.status
        protocol_ID = self.status[0]
        command_ID = self.status[1] + 1
        if command_ID < len(self.protocol_commands[protocol_ID]):
            command_name = self.protocol_commands[protocol_ID][command_ID]
            command_duration = self.protocol_durations[protocol_ID][command_ID]
            self.status = [protocol_ID, command_ID]
            self.issueCommand(command_name, command_duration)

            self.elapsed_timer.start()

            self.protocolDetailsList.setCurrentRow(command_ID)
        else:
            self.stopProtocol()

    # ------------------------------------------------------------------------------------
    # Close
    # ------------------------------------------------------------------------------------
    def close(self):
        self.stopProtocol()
        if self.verbose: print "Closing valve protocols"
        self.valveCommands.close()

    # ------------------------------------------------------------------------------------
    # Create display and control widgets
    # ------------------------------------------------------------------------------------
    def createGUI(self):
        self.mainWidget = QtGui.QGroupBox()
        self.mainWidget.setTitle("Protocols")
        self.mainWidgetLayout = QtGui.QVBoxLayout(self.mainWidget)

        self.fileLabel = QtGui.QLabel()
        self.fileLabel.setText("")

        self.protocolListWidget = QtGui.QListWidget()
        self.protocolListWidget.currentItemChanged.connect(
            self.updateProtocolDescriptor)

        self.elapsedTimeLabel = QtGui.QLabel()
        self.elapsedTimeLabel.setText("Elapsed Time: ")

        self.protocolDetailsList = QtGui.QListWidget()

        self.startProtocolButton = QtGui.QPushButton("Start Protocol")
        self.startProtocolButton.clicked.connect(self.startProtocolLocally)
        self.skipCommandButton = QtGui.QPushButton("Skip Command")
        self.skipCommandButton.clicked.connect(self.skipCommand)
        self.stopProtocolButton = QtGui.QPushButton("Stop Protocol")
        self.stopProtocolButton.clicked.connect(self.stopProtocol)

        self.protocolStatusGroupBox = QtGui.QGroupBox()
        self.protocolStatusGroupBox.setTitle("Command In Progress")
        self.protocolStatusGroupBoxLayout = QtGui.QVBoxLayout(
            self.protocolStatusGroupBox)

        self.mainWidgetLayout.addWidget(self.fileLabel)
        self.mainWidgetLayout.addWidget(self.protocolListWidget)
        self.mainWidgetLayout.addWidget(self.elapsedTimeLabel)
        self.mainWidgetLayout.addWidget(self.protocolDetailsList)
        self.mainWidgetLayout.addWidget(self.startProtocolButton)
        self.mainWidgetLayout.addWidget(self.skipCommandButton)
        self.mainWidgetLayout.addWidget(self.stopProtocolButton)
        self.mainWidgetLayout.addStretch(1)

        # Configure menu items
        self.load_fullconfig_action = QtGui.QAction("Load Full Configuration",
                                                    self)
        self.load_fullconfig_action.triggered.connect(
            self.loadFullConfiguration)

        self.load_protocols_action = QtGui.QAction("Load New Protocols", self)
        self.load_protocols_action.triggered.connect(self.loadProtocols)

        self.load_commands_action = self.valveCommands.load_commands_action

        self.menu_names = ["File"]
        self.menu_items = [[
            self.load_fullconfig_action, self.load_protocols_action,
            self.load_commands_action
        ]]

        # Disable buttons
        self.skipCommandButton.setEnabled(False)
        self.stopProtocolButton.setEnabled(False)

    # ------------------------------------------------------------------------------------
    # Return current command
    # ------------------------------------------------------------------------------------
    def getCurrentCommand(self):
        return self.issued_command

    # ------------------------------------------------------------------------------------
    # Return the number of loaded protocols
    # ------------------------------------------------------------------------------------
    def getNumProtocols(self):
        return self.num_protocols

    # ------------------------------------------------------------------------------------
    # Return protocol status
    # ------------------------------------------------------------------------------------
    def getStatus(self):
        return self.status  # [protocol_ID, command_ID] -1 = no active protocol

    # ------------------------------------------------------------------------------------
    # Return a protocol index by name
    # ------------------------------------------------------------------------------------
    def getProtocolByName(self, command_name):
        try:
            command_ID = self.command_names.index(command_name)
            return self.commands[command_ID]
        except:
            print "Did not find " + command_name
            return [-1] * self.num_valves  # Return no change command

    # ------------------------------------------------------------------------------------
    # Return loaded protocol names
    # ------------------------------------------------------------------------------------
    def getProtocolNames(self):
        return self.protocol_names

    # ------------------------------------------------------------------------------------
    # Issue a command: load current command, send command ready signal
    # ------------------------------------------------------------------------------------
    def issueCommand(self, command_data, command_duration=-1):
        if command_data[0] == "pump":
            self.issued_command = [
                "pump",
                self.pumpCommands.getCommandByName(command_data[1])
            ]
        elif command_data[0] == "valve":
            self.issued_command = [
                "valve",
                self.valveCommands.getCommandByName(command_data[1])
            ]
        if self.verbose:
            text = "Issued " + command_data[0] + ": " + command_data[1]
            if command_duration > 0:
                text += ": " + str(command_duration) + " s"
            print text

        self.command_ready_signal.emit()

        if command_duration >= 0:
            self.protocol_timer.start(command_duration * 1000)

    # ------------------------------------------------------------------------------------
    # Handle Issue Command Request from Pump Commands
    # ------------------------------------------------------------------------------------
    def issuePumpCommand(self, command_name):
        self.issueCommand(["pump", command_name])

    # ------------------------------------------------------------------------------------
    # Handle Issue Command Request from Valve Commands
    # ------------------------------------------------------------------------------------
    def issueValveCommand(self, command_name):
        self.issueCommand(["valve", command_name])

    # ------------------------------------------------------------------------------------
    # Check to see if protocol name is in the list of protocols
    # ------------------------------------------------------------------------------------
    def isValidProtocol(self, protocol_name):
        try:
            self.protocol_names.index(protocol_name)
            return True
        except ValueError:
            if self.verbose:
                print protocol_name + " is not a valid protocol"
            return False

    # ------------------------------------------------------------------------------------
    # Check to see if protocol name is in the list of protocols
    # ------------------------------------------------------------------------------------
    def isRunningProtocol(self):
        return self.status[0] >= 0

    # ------------------------------------------------------------------------------------
    # Load a protocol xml file
    # ------------------------------------------------------------------------------------
    def loadProtocols(self, xml_file_path=""):
        # Set Configuration XML (load if needed)
        if not xml_file_path:
            xml_file_path = QtGui.QFileDialog.getOpenFileName(
                self, "Open File", "\home")
            if not os.path.isfile(xml_file_path):
                xml_file_path = "default_config.xml"
                print "Not a valid path. Restoring: " + xml_file_path

        self.protocol_xml_path = xml_file_path

        # Parse XML
        self.parseProtocolXML()

        # Update GUI
        self.updateGUI()

        # Display if desired
        if self.verbose:
            self.printProtocols()

    # ------------------------------------------------------------------------------------
    # Short function to load both commands and protocols in a single file
    # ------------------------------------------------------------------------------------
    def loadFullConfiguration(self, xml_file_path=""):
        print "----------------------------------------------------------------------"

        # Set Configuration XML (load if needed)
        if not xml_file_path:
            xml_file_path = QtGui.QFileDialog.getOpenFileName(
                self, "Open File", "\home")
            if not os.path.isfile(xml_file_path):
                xml_file_path = "default_config.xml"
                print "Not a valid path. Restoring: " + xml_file_path

        self.protocol_xml_path = xml_file_path
        self.command_xml_path = xml_file_path

        # Update valveCommands
        self.valveCommands.loadCommands(xml_file_path=self.command_xml_path)

        # Update pumpCommands
        self.pumpCommands.loadCommands(xml_file_path=self.command_xml_path)

        # Parse XML
        self.parseProtocolXML()

        # Update GUI
        self.updateGUI()

        # Display if desired
        if self.verbose:
            self.printProtocols()

    # ------------------------------------------------------------------------------------
    # Parse loaded xml file: load protocols
    # ------------------------------------------------------------------------------------
    def parseProtocolXML(self):
        try:
            print "Parsing for protocols: " + self.protocol_xml_path
            self.xml_tree = elementTree.parse(self.protocol_xml_path)
            self.kilroy_configuration = self.xml_tree.getroot()
        except:
            print "Valid xml file not loaded"
            return

        # Clear previous commands
        self.protocol_names = []
        self.protocol_commands = []
        self.protocol_durations = []
        self.num_protocols = 0

        # Load commands
        for kilroy_protocols in self.kilroy_configuration.findall(
                "kilroy_protocols"):
            protocol_list = kilroy_protocols.findall("protocol")
            for protocol in protocol_list:
                self.protocol_names.append(protocol.get("name"))
                new_protocol_commands = []
                new_protocol_durations = []
                for command in protocol:  # Get all children
                    new_protocol_durations.append(int(command.get("duration")))
                    new_protocol_commands.append(
                        [command.tag,
                         command.text])  # [Instrument Type, Command Name]
                    if (not (command.tag == "pump")) and (not (command.tag
                                                               == "valve")):
                        print "Unknown command tag: " + command.tag
                self.protocol_commands.append(new_protocol_commands)
                self.protocol_durations.append(new_protocol_durations)

        # Record number of configs
        self.num_protocols = len(self.protocol_names)

    # ------------------------------------------------------------------------------------
    # Display loaded protocols
    # ------------------------------------------------------------------------------------
    def printProtocols(self):
        print "Current protocols:"
        for protocol_ID in range(self.num_protocols):
            print self.protocol_names[protocol_ID]
            for command_ID, command in enumerate(
                    self.protocol_commands[protocol_ID]):
                textString = "    " + command[0] + ": " + command[1] + ": "
                textString += str(
                    self.protocol_durations[protocol_ID][command_ID]) + " s"
                print textString

    # ------------------------------------------------------------------------------------
    # Display loaded protocols
    # ------------------------------------------------------------------------------------
    def requiredTime(self, protocol_name):
        protocol_ID = self.protocol_names.index(protocol_name)
        total_time = 0.0
        for time in self.protocol_durations[protocol_ID]:
            total_time += time

        return total_time

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol and issue first command
    # ------------------------------------------------------------------------------------
    def skipCommand(self):
        self.protocol_timer.stop()
        self.advanceProtocol()

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol and issue first command
    # ------------------------------------------------------------------------------------
    def startProtocol(self):
        protocol_ID = self.protocolListWidget.currentRow()

        # Get first command in protocol
        command_data = self.protocol_commands[protocol_ID][0]
        command_duration = self.protocol_durations[protocol_ID][0]

        # Set protocol status: [protocol_ID, command_ID]
        self.status = [protocol_ID, 0]
        self.status_change_signal.emit()  # emit status change signal

        if self.verbose:
            print "Starting " + self.protocol_names[protocol_ID]

        # Issue command signal
        self.issueCommand(command_data, command_duration)

        # Start elapsed time timer
        self.elapsed_timer.start()
        self.poll_elapsed_time_timer.start()

        # Change enable status of GUI items
        self.startProtocolButton.setEnabled(False)
        self.skipCommandButton.setEnabled(True)
        self.stopProtocolButton.setEnabled(True)
        self.protocolListWidget.setEnabled(False)
        self.protocolDetailsList.setCurrentRow(0)
        self.valveCommands.setEnabled(False)
        self.pumpCommands.setEnabled(False)

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol specified by name
    # ------------------------------------------------------------------------------------
    def startProtocolByName(self, protocol_name):
        if self.isValidProtocol(protocol_name):
            if self.isRunningProtocol():
                if self.verbose:
                    print "Stopped In Progress: " + self.protocol_names[
                        self.status[0]]
                self.stopProtocol()  # Abort protocol in progress

            # Find protocol and set as active element
            protocol_ID = self.protocol_names.index(protocol_name)
            self.protocolListWidget.setCurrentRow(protocol_ID)

            # Run protocol
            self.startProtocol()

    # ------------------------------------------------------------------------------------
    # Handle the local start button
    # ------------------------------------------------------------------------------------
    def startProtocolLocally(self):
        # Run protocol
        self.received_message = None  # Remove existing messages
        self.startProtocol()

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol specified by a TCP message
    # ------------------------------------------------------------------------------------
    def startProtocolRemotely(self, message):
        protocol_name = message.getData("name")
        if self.isValidProtocol(protocol_name):
            if self.isRunningProtocol():
                if self.verbose:
                    print "Stopped In Progress: " + self.protocol_names[
                        self.status[0]]
                self.stopProtocol()  # Abort protocol in progress

            # Find protocol and set as active element
            protocol_ID = self.protocol_names.index(protocol_name)
            self.protocolListWidget.setCurrentRow(protocol_ID)

            # Run protocol
            self.received_message = message
            self.startProtocol()

    # ------------------------------------------------------------------------------------
    # Stop a running protocol either on completion or early
    # ------------------------------------------------------------------------------------
    def stopProtocol(self):
        # Get name of current protocol
        if self.status[0] >= 0:
            if self.verbose: print "Stopped Protocol"
            self.completed_protocol_signal.emit(self.received_message)

        # Reset status and emit status change signal
        self.status = [-1, -1]
        self.status_change_signal.emit()
        self.received_message = None

        # Stop timer
        self.protocol_timer.stop()

        # Re-enable GUI
        self.startProtocolButton.setEnabled(True)
        self.protocolListWidget.setEnabled(True)
        self.skipCommandButton.setEnabled(False)
        self.stopProtocolButton.setEnabled(False)
        self.valveCommands.setEnabled(True)
        self.pumpCommands.setEnabled(True)

        # Unselect all
        self.protocolDetailsList.setCurrentRow(0)
        self.protocolDetailsList.item(0).setSelected(False)

        # Stop timers
        self.poll_elapsed_time_timer.stop()
        self.elapsedTimeLabel.setText("Elapsed Time:")

    # ------------------------------------------------------------------------------------
    # Display time elapsed since previous command was issued
    # ------------------------------------------------------------------------------------
    def updateElapsedTime(self):
        ms_count = self.elapsed_timer.elapsed()
        elapsed_seconds = int(float(ms_count) / float(1000))

        text_string = "Elapsed Time: "
        text_string += str(elapsed_seconds)
        text_string += " s"
        self.elapsedTimeLabel.setText(text_string)

    # ------------------------------------------------------------------------------------
    # Update GUI based on protocols
    # ------------------------------------------------------------------------------------
    def updateGUI(self):
        self.protocolListWidget.clear()  # Remove previous items
        for name in self.protocol_names:
            self.protocolListWidget.addItem(name)

        if len(self.protocol_names) > 0:
            self.protocolListWidget.setCurrentRow(0)  # Set to default

        drive, path_and_file = os.path.splitdrive(str(self.protocol_xml_path))
        path_name, file_name = os.path.split(str(path_and_file))
        self.fileLabel.setText(file_name)
        self.fileLabel.setToolTip(self.protocol_xml_path)

    # ------------------------------------------------------------------------------------
    # Update protocol description widget
    # ------------------------------------------------------------------------------------
    def updateProtocolDescriptor(self):
        protocol_ID = self.protocolListWidget.currentRow()
        current_protocol_name = self.protocol_names[protocol_ID]
        current_protocol_commands = self.protocol_commands[protocol_ID]
        current_protocol_durations = self.protocol_durations[protocol_ID]

        self.protocolDetailsList.clear()
        for ID in range(len(current_protocol_commands)):
            text_string = current_protocol_commands[ID][0]
            text_string += ": "
            text_string += current_protocol_commands[ID][1]
            text_string += ": "
            text_string += str(current_protocol_durations[ID]) + " s"

            wid = QtGui.QListWidgetItem(text_string)
            wid.setFlags(wid.flags() & QtCore.Qt.ItemIsSelectable)
            self.protocolDetailsList.insertItem(ID, wid)
Example #4
0
class KilroyProtocols(QtGui.QMainWindow):

    # Define custom command ready signal
    command_ready_signal = QtCore.pyqtSignal() # A command is ready to be issued
    status_change_signal = QtCore.pyqtSignal() # A protocol status change occured
    completed_protocol_signal = QtCore.pyqtSignal(object) # Name of completed protocol
        
    def __init__(self,
                 protocol_xml_path = "default_config.xml",
                 command_xml_path = "default_config.xml",
                 verbose = False):
        super(KilroyProtocols, self).__init__()

        # Initialize internal attributes
        self.verbose = verbose
        self.protocol_xml_path = protocol_xml_path
        self.command_xml_path = command_xml_path
        self.protocol_names = []
        self.protocol_commands = [] # [Instrument Type, command_info]
        self.protocol_durations = []
        self.num_protocols = 0
        self.status = [-1, -1] # Protocol ID, command ID within protocol
        self.issued_command = []
        self.received_message = None

        print "----------------------------------------------------------------------"
        
        # Create instance of ValveCommands class
        self.valveCommands = ValveCommands(xml_file_path = self.command_xml_path,
                                           verbose = self.verbose)

        # Connect valve command issue signal
        self.valveCommands.change_command_signal.connect(self.issueValveCommand)

        # Create instance of PumpCommands class
        self.pumpCommands = PumpCommands(xml_file_path = self.command_xml_path,
                                         verbose = self.verbose)

        # Connect pump commands issue signal
        self.pumpCommands.change_command_signal.connect(self.issuePumpCommand)
        
        # Create GUI
        self.createGUI()

        # Load configurations
        self.loadProtocols(xml_file_path = self.protocol_xml_path)

        # Create protocol timer--controls when commands are issued
        self.protocol_timer = QtCore.QTimer()
        self.protocol_timer.setSingleShot(True)
        self.protocol_timer.timeout.connect(self.advanceProtocol)

        # Create elapsed time timer--determines time between command calls
        self.elapsed_timer = QtCore.QElapsedTimer()
        self.poll_elapsed_time_timer = QtCore.QTimer()
        self.poll_elapsed_time_timer.setInterval(1000)
        self.poll_elapsed_time_timer.timeout.connect(self.updateElapsedTime)

    # ------------------------------------------------------------------------------------
    # Advance the protocol to the next command and issue it
    # ------------------------------------------------------------------------------------       
    def advanceProtocol(self):
        status = self.status
        protocol_ID = self.status[0]
        command_ID = self.status[1] + 1
        if command_ID < len(self.protocol_commands[protocol_ID]):
            command_name = self.protocol_commands[protocol_ID][command_ID]
            command_duration = self.protocol_durations[protocol_ID][command_ID]
            self.status = [protocol_ID, command_ID]
            self.issueCommand(command_name, command_duration)

            self.elapsed_timer.start()

            self.protocolDetailsList.setCurrentRow(command_ID)
        else:
            self.stopProtocol()

    # ------------------------------------------------------------------------------------
    # Close
    # ------------------------------------------------------------------------------------                                                
    def close(self):
        self.stopProtocol()
        if self.verbose: print "Closing valve protocols"
        self.valveCommands.close()
        
    # ------------------------------------------------------------------------------------
    # Create display and control widgets
    # ------------------------------------------------------------------------------------                                                
    def createGUI(self):
        self.mainWidget = QtGui.QGroupBox()
        self.mainWidget.setTitle("Protocols")
        self.mainWidgetLayout = QtGui.QVBoxLayout(self.mainWidget)

        self.fileLabel = QtGui.QLabel()
        self.fileLabel.setText("")

        self.protocolListWidget = QtGui.QListWidget()
        self.protocolListWidget.currentItemChanged.connect(self.updateProtocolDescriptor)

        self.elapsedTimeLabel = QtGui.QLabel()
        self.elapsedTimeLabel.setText("Elapsed Time: ")

        self.protocolDetailsList =  QtGui.QListWidget()
        
        self.startProtocolButton = QtGui.QPushButton("Start Protocol")
        self.startProtocolButton.clicked.connect(self.startProtocolLocally)
        self.skipCommandButton = QtGui.QPushButton("Skip Command")
        self.skipCommandButton.clicked.connect(self.skipCommand)
        self.stopProtocolButton = QtGui.QPushButton("Stop Protocol")
        self.stopProtocolButton.clicked.connect(self.stopProtocol)
        
        self.protocolStatusGroupBox = QtGui.QGroupBox()
        self.protocolStatusGroupBox.setTitle("Command In Progress")
        self.protocolStatusGroupBoxLayout = QtGui.QVBoxLayout(self.protocolStatusGroupBox)
        
        self.mainWidgetLayout.addWidget(self.fileLabel)
        self.mainWidgetLayout.addWidget(self.protocolListWidget)
        self.mainWidgetLayout.addWidget(self.elapsedTimeLabel)
        self.mainWidgetLayout.addWidget(self.protocolDetailsList)
        self.mainWidgetLayout.addWidget(self.startProtocolButton)
        self.mainWidgetLayout.addWidget(self.skipCommandButton)
        self.mainWidgetLayout.addWidget(self.stopProtocolButton)
        self.mainWidgetLayout.addStretch(1)

        # Configure menu items
        self.load_fullconfig_action = QtGui.QAction("Load Full Configuration", self)
        self.load_fullconfig_action.triggered.connect(self.loadFullConfiguration)
        
        self.load_protocols_action = QtGui.QAction("Load New Protocols", self)
        self.load_protocols_action.triggered.connect(self.loadProtocols)

        self.load_commands_action = self.valveCommands.load_commands_action
        
        self.menu_names = ["File"]
        self.menu_items = [[self.load_fullconfig_action,
                            self.load_protocols_action,
                            self.load_commands_action]]

        # Disable buttons
        self.skipCommandButton.setEnabled(False)
        self.stopProtocolButton.setEnabled(False)

    # ------------------------------------------------------------------------------------
    # Return current command
    # ------------------------------------------------------------------------------------                                    
    def getCurrentCommand(self):
        return self.issued_command

    # ------------------------------------------------------------------------------------
    # Return the number of loaded protocols
    # ------------------------------------------------------------------------------------                                        
    def getNumProtocols(self):
        return self.num_protocols

    # ------------------------------------------------------------------------------------
    # Return protocol status
    # ------------------------------------------------------------------------------------                                        
    def getStatus(self):
        return self.status # [protocol_ID, command_ID] -1 = no active protocol

    # ------------------------------------------------------------------------------------
    # Return a protocol index by name
    # ------------------------------------------------------------------------------------                                        
    def getProtocolByName(self, command_name):
        try:
            command_ID = self.command_names.index(command_name)
            return self.commands[command_ID]
        except:
            print "Did not find " + command_name
            return [-1]*self.num_valves # Return no change command

    # ------------------------------------------------------------------------------------
    # Return loaded protocol names
    # ------------------------------------------------------------------------------------                                        
    def getProtocolNames(self):
        return self.protocol_names

    # ------------------------------------------------------------------------------------
    # Issue a command: load current command, send command ready signal
    # ------------------------------------------------------------------------------------                       
    def issueCommand(self, command_data, command_duration=-1):
        if command_data[0] == "pump":
            self.issued_command = ["pump", self.pumpCommands.getCommandByName(command_data[1])]
        elif command_data[0] == "valve":
            self.issued_command = ["valve", self.valveCommands.getCommandByName(command_data[1])]
        elif command_data[0] == "notify":
            command_duration.execute()
            self.advanceProtocol()
            return
        if self.verbose:
            text = "Issued " + command_data[0] + ": " + command_data[1]
            if command_duration > 0:
                text += ": " + str(command_duration) + " s"
            print text
            
        self.command_ready_signal.emit()

        if command_duration >= 0:
            self.protocol_timer.start(command_duration*1000)

    # ------------------------------------------------------------------------------------
    # Handle Issue Command Request from Pump Commands
    # ------------------------------------------------------------------------------------                       
    def issuePumpCommand(self, command_name):
        self.issueCommand(["pump", command_name])

    # ------------------------------------------------------------------------------------
    # Handle Issue Command Request from Valve Commands
    # ------------------------------------------------------------------------------------                       
    def issueValveCommand(self, command_name):
        self.issueCommand(["valve", command_name])
        
    # ------------------------------------------------------------------------------------
    # Check to see if protocol name is in the list of protocols
    # ------------------------------------------------------------------------------------                       
    def isValidProtocol(self, protocol_name):
        try:
            self.protocol_names.index(protocol_name)
            return True
        except ValueError:
            if self.verbose:
                print protocol_name + " is not a valid protocol"
            return False

    # ------------------------------------------------------------------------------------
    # Check to see if protocol name is in the list of protocols
    # ------------------------------------------------------------------------------------                       
    def isRunningProtocol(self):
        return self.status[0] >= 0

    # ------------------------------------------------------------------------------------
    # Load a protocol xml file
    # ------------------------------------------------------------------------------------                        
    def loadProtocols(self, xml_file_path = ""):
        # Set Configuration XML (load if needed)
        if not xml_file_path:
            xml_file_path = QtGui.QFileDialog.getOpenFileName(self, "Open File", "\home")
            if not os.path.isfile(xml_file_path):
                xml_file_path = "default_config.xml"
                print "Not a valid path. Restoring: " + xml_file_path
                
        self.protocol_xml_path = xml_file_path
        
        # Parse XML
        self.parseProtocolXML()

        # Update GUI
        self.updateGUI()

        # Display if desired
        if self.verbose:
            self.printProtocols()
            
    # ------------------------------------------------------------------------------------
    # Short function to load both commands and protocols in a single file
    # ------------------------------------------------------------------------------------                        
    def loadFullConfiguration(self, xml_file_path = ""):
        print "----------------------------------------------------------------------"

        # Set Configuration XML (load if needed)
        if not xml_file_path:
            xml_file_path = QtGui.QFileDialog.getOpenFileName(self, "Open File", "\home")
            if not os.path.isfile(xml_file_path):
                xml_file_path = "default_config.xml"
                print "Not a valid path. Restoring: " + xml_file_path

        self.protocol_xml_path = xml_file_path
        self.command_xml_path = xml_file_path

        # Update valveCommands
        self.valveCommands.loadCommands(xml_file_path = self.command_xml_path)

        # Update pumpCommands
        self.pumpCommands.loadCommands(xml_file_path = self.command_xml_path)
      
        # Parse XML
        self.parseProtocolXML()

        # Update GUI
        self.updateGUI()

        # Display if desired
        if self.verbose:
            self.printProtocols()

    # ------------------------------------------------------------------------------------
    # Parse loaded xml file: load protocols
    # ------------------------------------------------------------------------------------                                        
    def parseProtocolXML(self):
        try:
            print "Parsing for protocols: " + self.protocol_xml_path
            self.xml_tree = elementTree.parse(self.protocol_xml_path)
            self.kilroy_configuration = self.xml_tree.getroot()
        except:
            print "Valid xml file not loaded"
            return

        # Clear previous commands
        self.protocol_names = []
        self.protocol_commands = []
        self.protocol_durations = []
        self.num_protocols = 0
        
        # Load commands
        for kilroy_protocols in self.kilroy_configuration.findall("kilroy_protocols"):
            protocol_list = kilroy_protocols.findall("protocol")
            for protocol in protocol_list:
                self.protocol_names.append(protocol.get("name"))
                new_protocol_commands = []
                new_protocol_durations = []
                for command in protocol: # Get all children
                    if command.tag == "pump" or command.tag == "valve":
                        new_protocol_durations.append(int(command.get("duration")))
                        new_protocol_commands.append([command.tag,command.text]) # [Instrument Type, Command Name]
                    elif command.tag == "notify":
                        notification = Notification(command)
                        new_protocol_durations.append(notification) # hack the duration list to contain the notification
                                                                    # object for now
                        new_protocol_commands.append([command.tag, "Notify User"])
                    else:
                        print "Unknown command tag: " + command.tag
                self.protocol_commands.append(new_protocol_commands)
                self.protocol_durations.append(new_protocol_durations)
        # Record number of configs
        self.num_protocols = len(self.protocol_names)

    # ------------------------------------------------------------------------------------
    # Display loaded protocols
    # ------------------------------------------------------------------------------------                                                
    def printProtocols(self):
        print "Current protocols:"
        for protocol_ID in range(self.num_protocols):
            print self.protocol_names[protocol_ID]
            for command_ID, command in enumerate(self.protocol_commands[protocol_ID]):
                textString = "    " + command[0] + ": " + command[1] + ": "
                textString += str(self.protocol_durations[protocol_ID][command_ID]) + " s"
                print textString
    # ------------------------------------------------------------------------------------
    # Display loaded protocols
    # ------------------------------------------------------------------------------------                                                
    def requiredTime(self, protocol_name):
        protocol_ID = self.protocol_names.index(protocol_name)
        total_time = 0.0
        for time in self.protocol_durations[protocol_ID]:
            total_time += time

        return total_time
        
    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol and issue first command
    # ------------------------------------------------------------------------------------
    def skipCommand(self):
        self.protocol_timer.stop()
        self.advanceProtocol()

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol and issue first command
    # ------------------------------------------------------------------------------------
    def startProtocol(self):
        protocol_ID = self.protocolListWidget.currentRow()
        
        # Get first command in protocol
        command_data = self.protocol_commands[protocol_ID][0]
        command_duration = self.protocol_durations[protocol_ID][0]

        # Set protocol status: [protocol_ID, command_ID]
        self.status = [protocol_ID, 0]
        self.status_change_signal.emit() # emit status change signal
        
        if self.verbose:
            print "Starting " + self.protocol_names[protocol_ID]

        # Issue command signal
        self.issueCommand(command_data, command_duration)
        
        # Start elapsed time timer
        self.elapsed_timer.start()
        self.poll_elapsed_time_timer.start()

        # Change enable status of GUI items
        self.startProtocolButton.setEnabled(False)
        self.skipCommandButton.setEnabled(True)
        self.stopProtocolButton.setEnabled(True)
        self.protocolListWidget.setEnabled(False)
        self.protocolDetailsList.setCurrentRow(0)
        self.valveCommands.setEnabled(False)
        self.pumpCommands.setEnabled(False)
        
    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol specified by name
    # ------------------------------------------------------------------------------------
    def startProtocolByName(self, protocol_name):
        if self.isValidProtocol(protocol_name):
            if self.isRunningProtocol():
                if self.verbose:
                    print "Stopped In Progress: " + self.protocol_names[self.status[0]]
                self.stopProtocol() # Abort protocol in progress

            # Find protocol and set as active element 
            protocol_ID = self.protocol_names.index(protocol_name)
            self.protocolListWidget.setCurrentRow(protocol_ID)

            # Run protocol
            self.startProtocol()

    # ------------------------------------------------------------------------------------
    # Handle the local start button
    # ------------------------------------------------------------------------------------
    def startProtocolLocally(self):
        # Run protocol
        self.received_message = None # Remove existing messages
        self.startProtocol()

    # ------------------------------------------------------------------------------------
    # Initialize and start a protocol specified by a TCP message
    # ------------------------------------------------------------------------------------
    def startProtocolRemotely(self, message):
        protocol_name = message.getData("name")
        if self.isValidProtocol(protocol_name):
            if self.isRunningProtocol():
                if self.verbose:
                    print "Stopped In Progress: " + self.protocol_names[self.status[0]]
                self.stopProtocol() # Abort protocol in progress

            # Find protocol and set as active element 
            protocol_ID = self.protocol_names.index(protocol_name)
            self.protocolListWidget.setCurrentRow(protocol_ID)

            # Run protocol
            self.received_message = message
            self.startProtocol()
            
    # ------------------------------------------------------------------------------------
    # Stop a running protocol either on completion or early
    # ------------------------------------------------------------------------------------               
    def stopProtocol(self):
        # Get name of current protocol
        if self.status[0] >= 0:
            if self.verbose: print "Stopped Protocol"
            self.completed_protocol_signal.emit(self.received_message)
        
        # Reset status and emit status change signal
        self.status = [-1,-1]
        self.status_change_signal.emit()
        self.received_message = None
        
        # Stop timer
        self.protocol_timer.stop()

        # Re-enable GUI
        self.startProtocolButton.setEnabled(True)
        self.protocolListWidget.setEnabled(True)
        self.skipCommandButton.setEnabled(False)
        self.stopProtocolButton.setEnabled(False)
        self.valveCommands.setEnabled(True)
        self.pumpCommands.setEnabled(True)
        
        # Unselect all
        self.protocolDetailsList.setCurrentRow(0)
        self.protocolDetailsList.item(0).setSelected(False)
    
        # Stop timers
        self.poll_elapsed_time_timer.stop()
        self.elapsedTimeLabel.setText("Elapsed Time:")

    # ------------------------------------------------------------------------------------
    # Display time elapsed since previous command was issued
    # ------------------------------------------------------------------------------------                       
    def updateElapsedTime(self):
        ms_count = self.elapsed_timer.elapsed()
        elapsed_seconds = int ( float(ms_count) / float(1000) )
        
        text_string = "Elapsed Time: "
        text_string += str(elapsed_seconds)
        text_string += " s"
        self.elapsedTimeLabel.setText(text_string)

    # ------------------------------------------------------------------------------------
    # Update GUI based on protocols
    # ------------------------------------------------------------------------------------                                                
    def updateGUI(self):
        self.protocolListWidget.clear() # Remove previous items
        for name in self.protocol_names:
            self.protocolListWidget.addItem(name)

        if len(self.protocol_names) > 0:
            self.protocolListWidget.setCurrentRow(0) # Set to default

        drive, path_and_file = os.path.splitdrive(str(self.protocol_xml_path))
        path_name, file_name = os.path.split(str(path_and_file))
        self.fileLabel.setText(file_name)
        self.fileLabel.setToolTip(self.protocol_xml_path)
        
    # ------------------------------------------------------------------------------------
    # Update protocol description widget
    # ------------------------------------------------------------------------------------                                                        
    def updateProtocolDescriptor(self):
        protocol_ID = self.protocolListWidget.currentRow()
        current_protocol_name = self.protocol_names[protocol_ID]
        current_protocol_commands = self.protocol_commands[protocol_ID]
        current_protocol_durations = self.protocol_durations[protocol_ID]

        self.protocolDetailsList.clear()
        for ID in range(len(current_protocol_commands)):
            text_string = current_protocol_commands[ID][0]
            text_string += ": "
            text_string += current_protocol_commands[ID][1]
            text_string += ": "
            text_string += str(current_protocol_durations[ID]) \
                + (" s" if current_protocol_commands[ID][0] != "notify" else "")

            wid = QtGui.QListWidgetItem(text_string)
            wid.setFlags(wid.flags() & QtCore.Qt.ItemIsSelectable)
            self.protocolDetailsList.insertItem(ID, wid)