def connect(self):
     # Connect to pyboard.
     try:
         self.status_text.setText('Connecting...')
         self.board_select.setEnabled(False)
         self.variables_button.setEnabled(False)
         self.connect_button.setEnabled(False)
         self.repaint()
         port = self.GUI_main.setups_tab.get_port(
             self.board_select.currentText())
         self.board = Pycboard(port,
                               print_func=self.print_to_log,
                               data_logger=self.data_logger)
         self.connected = True
         self.config_button.setEnabled(True)
         self.task_groupbox.setEnabled(True)
         self.connect_button.setEnabled(True)
         self.connect_button.setText('Disconnect')
         self.status_text.setText('Connected')
     except SerialException:
         self.status_text.setText('Connection failed')
         self.print_to_log('Connection failed.')
         self.connect_button.setEnabled(True)
     if self.connected and not self.board.status['framework']:
         self.board.load_framework()
Exemple #2
0
 def connect(self):
     '''Instantiate pyboard object, opening serial connection to board.'''
     self.print('Connecting to board.')
     try: 
         self.board = Pycboard(self.port, print_func=self.setups_tab.print_to_log)
     except PyboardError:
         self.print('Unable to connect.')
Exemple #3
0
 def connect(self):
     # Connect to pyboard.
     try:
         self.status_text.setText('Connecting...')
         self.board_select.setEnabled(False)
         self.variables_button.setEnabled(False)
         self.connect_button.setEnabled(False)
         self.repaint()
         port = self.GUI_main.setups_tab.get_port(
             self.board_select.currentText())
         self.board = Pycboard(port,
                               print_func=self.print_to_log,
                               data_logger=self.data_logger)
         self.connected = True
         self.config_button.setEnabled(True)
         self.connect_button.setEnabled(True)
         self.connect_button.setText('Disconnect')
         self.connect_button.setIcon(
             QtGui.QIcon("gui/icons/disconnect.svg"))
         self.status_text.setText('Connected')
         if self.board.status['framework']:
             self.task_groupbox.setEnabled(True)
         else:
             self.print_to_log(
                 "\nLoad pyControl framework using 'Config' button.")
     except SerialException:
         self.status_text.setText('Connection failed')
         self.print_to_log('Connection failed.')
         self.connect_button.setEnabled(True)
         self.board_select.setEnabled(True)
Exemple #4
0
def run_task():
    data_logger = Data_logger(print_func=print)
    board = None
    while not board:
        i = input('Enter serial port of board or board number, or [s] to scan for pyboards: ')
        if i == 's':
            pyboard_serials = {j+1: b for (j,b) in 
                enumerate([c[0] for c in list_ports.comports() if 'Pyboard' in c[1]])}
            unknown_usb_serials = {j+len(pyboard_serials)+1: b for (j,b) in 
                enumerate([c[0] for c in list_ports.comports() if 'USB Serial Device' in c[1]])}
            if not (pyboard_serials or unknown_usb_serials):
                print('\nNo Pyboards found.\n' )
                continue
            else:
                if pyboard_serials:
                    print('\nPyboards found on the following serial ports:\n')
                    for b in pyboard_serials.keys():
                        print('{}: {}\n'.format(b, pyboard_serials[b]))
                if unknown_usb_serials:
                    print('\nPossible Pyboards found on the following serial ports:\n')
                    for b in unknown_usb_serials.keys():
                        print('{}: {}\n'.format(b, unknown_usb_serials[b]))
                pyboard_serials.update(unknown_usb_serials)
                while True:
                    k = input('Select Pyboard:')
                    try:
                        port = pyboard_serials[int(k)]
                        break
                    except (KeyError, ValueError):
                        print('\nInput not recognised, valid inputs: {}\n'.format(list(pyboard_serials.keys())))
        else:
            try: # Check if input is an integer corresponding to a setup number.
                port = config.board_serials[int(i)]
            except (KeyError, ValueError):
                port = i
        try:
            board = Pycboard(port, verbose=False, data_logger=data_logger)
        except SerialException:
            print('\nUnable to open serial connection {}, Check serial port is correct.\n'
                  'If port is correct, try resetting pyboard with reset button.\n'.format(port))

    print('\nSerial connection OK. Micropython version: {}'.format(board.micropython_version))

    if not board.status['framework']:
        board.load_framework()

    task_select_menu(board)
Exemple #5
0
 def connect_to_board(self, i):
     '''Connect to the i-th board.'''
     subject = self.subjects[i]
     setup = self.experiment['subjects'][subject]['setup']
     print_func = self.subjectboxes[i].print_to_log
     serial_port = self.GUI_main.setups_tab.get_port(setup)
     try:
         board = Pycboard(serial_port, print_func=print_func)
     except SerialException:
         print_func('\nConnection failed.')
         self.setup_failed[i] = True
         return
     if not board.status['framework']:
         print_func('\nInstall pyControl framework on board before running experiment.')
         self.setup_failed[i] = True
         self.subjectboxes[i].error()
     board.subject = subject
     board.setup_ID = setup
     return board
 def setup_experiment(self, experiment):
     '''Called when an experiment is loaded.'''
     # Setup tabs.
     self.status_text.setText('Loading')
     self.status_text.setStyleSheet('color: black;')
     self.experiment = experiment
     self.GUI_main.tab_widget.setTabEnabled(0,
                                            False)  # Disable run task tab.
     self.GUI_main.tab_widget.setTabEnabled(2, False)  # Disable setups tab.
     self.GUI_main.experiments_tab.setCurrentWidget(self)
     self.startstopclose_button.setText('Start')
     self.experiment_plot.setup_experiment(experiment)
     self.state = 'pre_run'
     self.logs_visible = True
     self.logs_button.setText('Hide logs')
     # Setup controls box.
     self.name_text.setText(experiment['name'])
     self.time_text.setText('')
     self.startstopclose_button.setEnabled(False)
     self.logs_button.setEnabled(False)
     self.plots_button.setEnabled(False)
     # Setup subjectboxes
     for setup in sorted(experiment['subjects'].keys()):
         self.subjectboxes.append(
             Subjectbox(
                 '{} : {}'.format(setup, experiment['subjects'][setup]),
                 self))
         self.boxes_layout.addWidget(self.subjectboxes[-1])
     # Create data folder if needed.
     if not os.path.exists(self.experiment['data_dir']):
         os.mkdir(self.experiment['data_dir'])
     # Load persistent variables if they exist.
     self.pv_path = os.path.join(self.experiment['data_dir'],
                                 'persistent_variables.json')
     if os.path.exists(self.pv_path):
         with open(self.pv_path, 'r') as pv_file:
             persistent_variables = json.loads(pv_file.read())
     else:
         persistent_variables = {}
     # Setup boards.
     self.GUI_main.app.processEvents()
     self.boards = []
     for i, setup in enumerate(sorted(experiment['subjects'].keys())):
         print_func = self.subjectboxes[i].print_to_log
         serial_port = self.GUI_main.setups_tab.get_port(setup)
         # Connect to boards.
         print_func('Connecting to board.. ')
         try:
             self.boards.append(Pycboard(serial_port,
                                         print_func=print_func))
         except SerialException:
             print_func('Connection failed.')
             self.abort_experiment()
             return
         self.boards[i].subject = experiment['subjects'][setup]
     # Hardware test.
     if experiment['hardware_test'] != ' no hardware test':
         reply = QtGui.QMessageBox.question(
             self, 'Hardware test', 'Run hardware test?',
             QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
         if reply == QtGui.QMessageBox.Yes:
             try:
                 for i, board in enumerate(self.boards):
                     board.setup_state_machine(experiment['hardware_test'])
                     board.print('\nStarting hardware test.')
                     board.start_framework(data_output=False)
                     time.sleep(0.01)
                     board.process_data()
                 QtGui.QMessageBox.question(
                     self, 'Hardware test',
                     'Press OK when finished with hardware test.',
                     QtGui.QMessageBox.Ok)
                 for i, board in enumerate(self.boards):
                     board.stop_framework()
                     time.sleep(0.01)
                     board.process_data()
             except PyboardError as e:
                 board.print('\n' + str(e))
                 self.subjectboxes[i].error()
                 self.abort_experiment()
                 return
     # Setup state machines.
     for i, board in enumerate(self.boards):
         try:
             board.data_logger = Data_logger(
                 print_func=board.print,
                 data_consumers=[
                     self.experiment_plot.subject_plots[i],
                     self.subjectboxes[i]
                 ])
             board.setup_state_machine(experiment['task'])
         except PyboardError:
             self.abort_experiment()
             return
         # Set variables.
         board.subject_variables = [
             v for v in experiment['variables']
             if v['subject'] in ('all', board.subject)
         ]
         if board.subject_variables:
             board.print('\nSetting variables.\n')
             board.variables_set_pre_run = []
             try:
                 try:
                     subject_pv_dict = persistent_variables[board.subject]
                 except KeyError:
                     subject_pv_dict = {}
                 for v in board.subject_variables:
                     if v['persistent'] and v[
                             'name'] in subject_pv_dict.keys(
                             ):  # Use stored value.
                         v_value = subject_pv_dict[v['name']]
                         board.variables_set_pre_run.append(
                             (v['name'], str(v_value),
                              '(persistent value)'))
                     else:
                         if v['value'] == '':
                             continue
                         v_value = eval(v['value'], variable_constants
                                        )  # Use value from variables table.
                         board.variables_set_pre_run.append(
                             (v['name'], v['value'], ''))
                     board.set_variable(v['name'], v_value)
                 # Print set variables to log.
                 if board.variables_set_pre_run:
                     name_len = max(
                         [len(v[0]) for v in board.variables_set_pre_run])
                     value_len = max(
                         [len(v[1]) for v in board.variables_set_pre_run])
                     for v_name, v_value, pv_str in board.variables_set_pre_run:
                         self.subjectboxes[i].print_to_log(
                             v_name.ljust(name_len + 4) +
                             v_value.ljust(value_len + 4) + pv_str)
             except PyboardError as e:
                 board.print('Setting variable failed. ' + str(e))
                 self.subjectboxes[i].error()
                 self.abort_experiment()
                 return
     for i, board in enumerate(self.boards):
         self.subjectboxes[i].assign_board(board)
     self.experiment_plot.set_state_machine(board.sm_info)
     self.startstopclose_button.setEnabled(True)
     self.logs_button.setEnabled(True)
     self.plots_button.setEnabled(True)
     self.status_text.setText('Ready')
class Run_task_tab(QtGui.QWidget):
    def __init__(self, parent=None):
        super(QtGui.QWidget, self).__init__(parent)

        # Variables.
        self.GUI_main = self.parent()
        self.board = None  # Pycboard class instance.
        self.task = None  # Task currently uploaded on pyboard.
        self.task_hash = None  # Used to check if file has changed.
        self.data_dir = None
        self.connected = False  # Whether gui is conencted to pyboard.
        self.uploaded = False  # Whether selected task file is on board.
        self.fresh_task = None  # Whether task has been run or variables edited.
        self.running = False
        self.subject_changed = False
        self.variables_dialog = None

        # GUI groupbox.

        self.status_groupbox = QtGui.QGroupBox('Status')

        self.status_text = QtGui.QLineEdit('Not connected')
        self.status_text.setReadOnly(True)

        self.guigroup_layout = QtGui.QHBoxLayout()
        self.guigroup_layout.addWidget(self.status_text)
        self.status_groupbox.setLayout(self.guigroup_layout)

        # Board groupbox

        self.board_groupbox = QtGui.QGroupBox('Setup')

        self.board_label = QtGui.QLabel('Select:')
        self.board_select = QtGui.QComboBox()
        self.board_select.setEditable(True)
        self.board_select.setFixedWidth(100)
        self.connect_button = QtGui.QPushButton('Connect')
        self.config_button = QtGui.QPushButton('Config')

        self.boardgroup_layout = QtGui.QHBoxLayout()
        self.boardgroup_layout.addWidget(self.board_label)
        self.boardgroup_layout.addWidget(self.board_select)
        self.boardgroup_layout.addWidget(self.connect_button)
        self.boardgroup_layout.addWidget(self.config_button)
        self.board_groupbox.setLayout(self.boardgroup_layout)

        self.connect_button.clicked.connect(
            lambda: self.disconnect() if self.connected else self.connect())
        self.config_button.clicked.connect(self.open_config_dialog)

        # File groupbox

        self.file_groupbox = QtGui.QGroupBox('Data file')

        self.data_dir_label = QtGui.QLabel('Data dir:')
        self.data_dir_text = QtGui.QLineEdit(data_dir)
        self.data_dir_button = QtGui.QPushButton('...')
        self.data_dir_button.setFixedWidth(30)
        self.subject_label = QtGui.QLabel('Subject ID:')
        self.subject_text = QtGui.QLineEdit()
        self.subject_text.setFixedWidth(80)
        self.subject_text.setMaxLength(12)

        self.filegroup_layout = QtGui.QHBoxLayout()
        self.filegroup_layout.addWidget(self.data_dir_label)
        self.filegroup_layout.addWidget(self.data_dir_text)
        self.filegroup_layout.addWidget(self.data_dir_button)
        self.filegroup_layout.addWidget(self.subject_label)
        self.filegroup_layout.addWidget(self.subject_text)
        self.file_groupbox.setLayout(self.filegroup_layout)

        self.data_dir_text.textChanged.connect(self.test_data_path)
        self.data_dir_button.clicked.connect(self.select_data_dir)
        self.subject_text.textChanged.connect(self.test_data_path)

        # Task groupbox

        self.task_groupbox = QtGui.QGroupBox('Task')

        self.task_label = QtGui.QLabel('Task:')
        self.task_select = QtGui.QComboBox()
        self.upload_button = QtGui.QPushButton('Upload')
        self.variables_button = QtGui.QPushButton('Variables')

        self.taskgroup_layout = QtGui.QHBoxLayout()
        self.taskgroup_layout.addWidget(self.task_label)
        self.taskgroup_layout.addWidget(self.task_select)
        self.taskgroup_layout.addWidget(self.upload_button)
        self.taskgroup_layout.addWidget(self.variables_button)
        self.task_groupbox.setLayout(self.taskgroup_layout)

        self.task_select.currentTextChanged.connect(self.task_changed)
        self.upload_button.clicked.connect(self.setup_task)

        # Session groupbox.

        self.session_groupbox = QtGui.QGroupBox('Session')

        self.start_button = QtGui.QPushButton('Start')
        self.stop_button = QtGui.QPushButton('Stop')

        self.sessiongroup_layout = QtGui.QHBoxLayout()
        self.sessiongroup_layout.addWidget(self.start_button)
        self.sessiongroup_layout.addWidget(self.stop_button)
        self.session_groupbox.setLayout(self.sessiongroup_layout)

        self.start_button.clicked.connect(self.start_task)
        self.stop_button.clicked.connect(self.stop_task)

        # Log text and task plots.

        self.log_textbox = QtGui.QTextEdit()
        self.log_textbox.setFont(QtGui.QFont('Courier', 9))
        self.log_textbox.setReadOnly(True)

        self.task_plot = Task_plot()
        self.data_logger = Data_logger(print_func=self.print_to_log,
                                       data_consumers=[self.task_plot])

        # Main layout

        self.vertical_layout = QtGui.QVBoxLayout()
        self.horizontal_layout_1 = QtGui.QHBoxLayout()
        self.horizontal_layout_2 = QtGui.QHBoxLayout()
        self.horizontal_layout_3 = QtGui.QHBoxLayout()

        self.horizontal_layout_1.addWidget(self.status_groupbox)
        self.horizontal_layout_1.addWidget(self.board_groupbox)
        self.horizontal_layout_2.addWidget(self.file_groupbox)
        self.horizontal_layout_3.addWidget(self.task_groupbox)
        self.horizontal_layout_3.addWidget(self.session_groupbox)
        self.vertical_layout.addLayout(self.horizontal_layout_1)
        self.vertical_layout.addLayout(self.horizontal_layout_2)
        self.vertical_layout.addLayout(self.horizontal_layout_3)
        self.vertical_layout.addWidget(self.log_textbox, 20)
        self.vertical_layout.addWidget(self.task_plot, 80)
        self.setLayout(self.vertical_layout)

        # Create timers

        self.update_timer = QtCore.QTimer(
        )  # Timer to regularly call update() during run.
        self.update_timer.timeout.connect(self.update)

        # Initial setup.

        self.disconnect()  # Set initial state as disconnected.

    # General methods

    def print_to_log(self, print_string, end='\n'):
        self.log_textbox.moveCursor(QtGui.QTextCursor.End)
        self.log_textbox.insertPlainText(print_string + end)
        self.log_textbox.moveCursor(QtGui.QTextCursor.End)
        self.GUI_main.app.processEvents(
        )  # To update gui during long operations that print progress.

    def test_data_path(self):
        # Checks whether data dir and subject ID are valid.
        self.data_dir = self.data_dir_text.text()
        subject_ID = self.subject_text.text()
        if os.path.isdir(self.data_dir) and subject_ID:
            self.start_button.setText('Record')
            return True
        else:
            self.start_button.setText('Start')
            return False

    def refresh(self):
        # Called regularly when framework not running.
        if self.GUI_main.setups_tab.available_setups_changed:
            self.board_select.clear()
            self.board_select.addItems(self.GUI_main.setups_tab.setup_names)
        if self.GUI_main.available_tasks_changed:
            self.task_select.clear()
            self.task_select.addItems(sorted(self.GUI_main.available_tasks))
        if self.task:
            try:
                task_path = os.path.join(tasks_dir, self.task + '.py')
                if not self.task_hash == _djb2_file(
                        task_path):  # Task file modified.
                    self.task_changed()
            except FileNotFoundError:
                pass

    def open_config_dialog(self):
        '''Open the config dialog and update GUI as required by chosen config.'''
        self.GUI_main.config_dialog.exec_(self.board)
        self.task_changed()
        if self.GUI_main.config_dialog.disconnect:
            self.disconnect()

    # Widget methods.

    def connect(self):
        # Connect to pyboard.
        try:
            self.status_text.setText('Connecting...')
            self.board_select.setEnabled(False)
            self.variables_button.setEnabled(False)
            self.connect_button.setEnabled(False)
            self.repaint()
            port = self.GUI_main.setups_tab.get_port(
                self.board_select.currentText())
            self.board = Pycboard(port,
                                  print_func=self.print_to_log,
                                  data_logger=self.data_logger)
            self.connected = True
            self.config_button.setEnabled(True)
            self.task_groupbox.setEnabled(True)
            self.connect_button.setEnabled(True)
            self.connect_button.setText('Disconnect')
            self.status_text.setText('Connected')
        except SerialException:
            self.status_text.setText('Connection failed')
            self.print_to_log('Connection failed.')
            self.connect_button.setEnabled(True)
        if self.connected and not self.board.status['framework']:
            self.board.load_framework()

    def disconnect(self):
        # Disconnect from pyboard.
        if self.board: self.board.close()
        self.board = None
        self.task_groupbox.setEnabled(False)
        self.file_groupbox.setEnabled(False)
        self.session_groupbox.setEnabled(False)
        self.config_button.setEnabled(False)
        self.board_select.setEnabled(True)
        self.connect_button.setText('Connect')
        self.status_text.setText('Not connected')
        self.task_changed()
        self.connected = False

    def task_changed(self):
        self.uploaded = False
        self.upload_button.setText('Upload')
        self.start_button.setEnabled(False)

    def setup_task(self):
        try:
            task = self.task_select.currentText()
            if self.uploaded:
                self.status_text.setText('Resetting task..')
            else:
                self.status_text.setText('Uploading..')
                self.task_hash = _djb2_file(
                    os.path.join(tasks_dir, task + '.py'))
            self.start_button.setEnabled(False)
            self.variables_button.setEnabled(False)
            self.repaint()
            self.board.setup_state_machine(task, uploaded=self.uploaded)
            if self.variables_dialog:
                self.variables_button.clicked.disconnect()
                self.variables_dialog.deleteLater()
            self.variables_dialog = Variables_dialog(self, self.board)
            self.variables_button.clicked.connect(self.variables_dialog.exec_)
            self.variables_button.setEnabled(True)
            self.task_plot.set_state_machine(self.board.sm_info)
            self.file_groupbox.setEnabled(True)
            self.session_groupbox.setEnabled(True)
            self.start_button.setEnabled(True)
            self.stop_button.setEnabled(False)
            self.status_text.setText('Uploaded : ' + task)
            self.task = task
            self.fresh_task = True
            self.uploaded = True
            self.upload_button.setText('Reset')
        except PyboardError:
            self.status_text.setText('Error setting up state machine.')

    def select_data_dir(self):
        self.data_dir_text.setText(
            QtGui.QFileDialog.getExistingDirectory(self, 'Select data folder',
                                                   data_dir))

    def start_task(self):
        recording = self.test_data_path()
        if recording:
            if not self.fresh_task:
                reset_task = QtGui.QMessageBox.question(
                    self, 'Reset task',
                    'Task has already been run, variables may not have default values.\n\nReset task?',
                    QtGui.QMessageBox.Yes, QtGui.QMessageBox.No)
                if reset_task == QtGui.QMessageBox.Yes:
                    self.setup_task()
                    return
            subject_ID = str(self.subject_text.text())
            self.data_logger.open_data_file(self.data_dir, 'run_task',
                                            subject_ID)
        self.fresh_task = False
        self.running = True
        self.board.start_framework()
        self.task_plot.run_start(recording)
        self.task_select.setEnabled(False)
        self.upload_button.setEnabled(False)
        self.file_groupbox.setEnabled(False)
        self.start_button.setEnabled(False)
        self.board_groupbox.setEnabled(False)
        self.stop_button.setEnabled(True)
        self.print_to_log('\nRun started at: {}\n'.format(
            datetime.now().strftime('%Y/%m/%d %H:%M:%S')))
        self.update_timer.start(update_interval)
        self.GUI_main.refresh_timer.stop()
        self.status_text.setText('Running: ' + self.task)
        self.GUI_main.tab_widget.setTabEnabled(
            1, False)  # Disable experiments tab.
        self.GUI_main.tab_widget.setTabEnabled(2, False)  # Disable setups tab.

    def stop_task(self, error=False, stopped_by_task=False):
        self.running = False
        self.update_timer.stop()
        self.GUI_main.refresh_timer.start(self.GUI_main.refresh_interval)
        if not (error or stopped_by_task):
            self.board.stop_framework()
            QtCore.QTimer.singleShot(
                100, self.update)  # Catch output after framework stops.
        self.data_logger.close_files()
        self.task_plot.run_stop()
        self.board_groupbox.setEnabled(True)
        self.file_groupbox.setEnabled(True)
        self.start_button.setEnabled(True)
        self.task_select.setEnabled(True)
        self.upload_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        self.status_text.setText('Uploaded : ' + self.task)
        self.GUI_main.tab_widget.setTabEnabled(1, True)  # Enable setups tab.
        self.GUI_main.tab_widget.setTabEnabled(2, True)  # Enable setups tab.

    # Timer updates

    def update(self):
        # Called regularly during run to process data from board and update plots.
        try:
            self.board.process_data()
            if not self.board.framework_running:
                self.stop_task(stopped_by_task=True)
        except PyboardError:
            self.print_to_log('\nError during framework run.')
            self.stop_task(error=True)
        self.task_plot.update()

    # Cleanup.

    def closeEvent(self, event):
        # Called when GUI window is closed.
        if self.board:
            self.board.stop_framework()
            self.board.close()
        event.accept()

    # Exception handling.

    def excepthook(self, ex_type, ex_value, ex_traceback):
        # Called whenever an uncaught exception occurs.
        if ex_type in (SerialException, SerialTimeoutException):
            self.print_to_log('\nError: Serial connection with board lost.')
        elif ex_type == PyboardError:
            self.print_to_log('\nError: Unable to execute command.')
        else:
            self.print_to_log(
                '\nError: uncaught exception of type: {}'.format(ex_type))
        if self.running:
            self.stop_task(error=True)
        self.disconnect()
Exemple #8
0
class Setup():
    '''Class representing one setup in the setups table.'''

    def __init__(self, serial_port, setups_tab):
        '''Setup is intilised when board is plugged into computer.'''

        try:
            self.name = setups_tab.saved_names[serial_port]
        except KeyError:
            self.name = serial_port

        self.port = serial_port
        self.setups_tab = setups_tab
        self.board = None

        self.port_item = QtGui.QTableWidgetItem()
        self.port_item.setText(serial_port)
        self.port_item.setFlags(QtCore.Qt.ItemIsEnabled)

        self.name_item = QtGui.QTableWidgetItem()
        self.name_item.changed = self.name_edited
        if self.name != self.port: 
            self.name_item.setText(self.name)

        self.select_checkbox = TableCheckbox()
        self.config_button = QtGui.QPushButton('Configure')
        self.config_button.clicked.connect(self.open_config_dialog)

        self.setups_tab.setups_table.insertRow(0)
        self.setups_tab.setups_table.setItem(0, 0, self.port_item)
        self.setups_tab.setups_table.setItem(0, 1, self.name_item)
        self.setups_tab.setups_table.setCellWidget(0, 2, self.select_checkbox)
        self.setups_tab.setups_table.setCellWidget(0, 3, self.config_button)

    def name_edited(self):
        '''If name entry in table is blank setup name is set to serial port.'''
        name = str(self.name_item.text())
        self.name = name if name else self.port
        self.setups_tab.update_available_setups()
        self.setups_tab.update_saved_setups(self)

    def open_config_dialog(self):
        '''Open the config dialog and update board status as required.'''
        if not self.board: self.connect()
        if self.board:
            self.setups_tab.GUI_main.config_dialog.exec_(self.board)
            if self.setups_tab.GUI_main.config_dialog.disconnect:
                self.disconnect()

    def print(self, print_string):
        ''' Print a string to the log prepended with the setup name.'''
        self.setups_tab.print_to_log('\n{}: '.format(self.name) + print_string)

    def connect(self):
        '''Instantiate pyboard object, opening serial connection to board.'''
        self.print('Connecting to board.')
        try: 
            self.board = Pycboard(self.port, print_func=self.setups_tab.print_to_log)
        except PyboardError:
            self.print('Unable to connect.')

    def disconnect(self):
        
        if self.board:
            self.board.close()
            self.board = None

    def unplugged(self):
        '''Called when a board is physically unplugged from computer. 
        Closes serial connection and removes row from setups table.'''
        if self.board: self.board.close()
        self.setups_tab.setups_table.removeRow(self.port_item.row())
        del(self.setups_tab.setups[self.port])

    def load_framework(self):
        if not self.board: self.connect()
        if self.board:
            self.print('Loading framework.')
            self.board.load_framework()

    def load_hardware_definition(self, hwd_path):
        if not self.board: self.connect()
        if self.board:
            self.print('Loading hardware definition.')
            self.board.load_hardware_definition(hwd_path)

    def enable_flashdrive(self):
        if not self.board: self.connect()
        if self.board:
            self.print('Enabling flashdrive.')
            self.board.enable_mass_storage()
            self.board.close()
            self.board = None

    def disable_flashdrive(self):
        if not self.board: self.connect()
        if self.board:
            self.print('Disabling flashdrive.')
            self.board.disable_mass_storage()
            self.board.close()
            self.board = None
Exemple #9
0
class Run_task_tab(QtGui.QWidget):
    def __init__(self, parent=None):
        super(QtGui.QWidget, self).__init__(parent)

        # Variables.
        self.GUI_main = self.parent()
        self.board = None  # Pycboard class instance.
        self.task = None  # Task currently uploaded on pyboard.
        self.task_hash = None  # Used to check if file has changed.
        self.data_dir = None  # Folder to save data files.
        self.custom_dir = False  # True if data_dir field has been changed from default.
        self.connected = False  # Whether gui is conencted to pyboard.
        self.uploaded = False  # Whether selected task file is on board.
        self.fresh_task = None  # Whether task has been run or variables edited.
        self.running = False
        self.subject_changed = False
        self.variables_dialog = None

        # GUI groupbox.

        self.status_groupbox = QtGui.QGroupBox("Status")

        self.status_text = QtGui.QLineEdit("Not connected")
        self.status_text.setReadOnly(True)

        self.guigroup_layout = QtGui.QHBoxLayout()
        self.guigroup_layout.addWidget(self.status_text)
        self.status_groupbox.setLayout(self.guigroup_layout)

        # Board groupbox

        self.board_groupbox = QtGui.QGroupBox("Setup")

        self.board_select = QtGui.QComboBox()
        self.board_select.addItems(["No setups found"])
        self.board_select.setSizeAdjustPolicy(0)
        self.connect_button = QtGui.QPushButton("Connect")
        self.connect_button.setIcon(QtGui.QIcon("gui/icons/connect.svg"))
        self.connect_button.setEnabled(False)
        self.config_button = QtGui.QPushButton("Config")
        self.config_button.setIcon(QtGui.QIcon("gui/icons/settings.svg"))

        self.boardgroup_layout = QtGui.QHBoxLayout()
        self.boardgroup_layout.addWidget(self.board_select)
        self.boardgroup_layout.addWidget(self.connect_button)
        self.boardgroup_layout.addWidget(self.config_button)
        self.board_groupbox.setLayout(self.boardgroup_layout)

        self.connect_button.clicked.connect(
            lambda: self.disconnect() if self.connected else self.connect())
        self.config_button.clicked.connect(self.open_config_dialog)

        # File groupbox

        self.file_groupbox = QtGui.QGroupBox("Data file")

        self.data_dir_label = QtGui.QLabel("Data dir:")
        self.data_dir_label.setAlignment(QtCore.Qt.AlignRight
                                         | QtCore.Qt.AlignVCenter)
        self.data_dir_text = QtGui.QLineEdit(dirs["data"])
        self.data_dir_button = QtGui.QPushButton()
        self.data_dir_button.setIcon(QtGui.QIcon("gui/icons/folder.svg"))
        self.data_dir_button.setFixedWidth(30)
        self.subject_label = QtGui.QLabel("Subject ID:")
        self.subject_text = QtGui.QLineEdit()

        self.filegroup_layout = QtGui.QGridLayout()
        self.filegroup_layout.addWidget(self.data_dir_label, 0, 0)
        self.filegroup_layout.addWidget(self.data_dir_text, 0, 1)
        self.filegroup_layout.addWidget(self.data_dir_button, 0, 2)
        self.filegroup_layout.addWidget(self.subject_label, 1, 0)
        self.filegroup_layout.addWidget(self.subject_text, 1, 1)
        self.file_groupbox.setLayout(self.filegroup_layout)

        self.data_dir_text.textChanged.connect(self.test_data_path)
        self.data_dir_text.textEdited.connect(
            lambda: setattr(self, "custom_dir", True))
        self.data_dir_button.clicked.connect(self.select_data_dir)
        self.subject_text.textChanged.connect(self.test_data_path)

        # Task groupbox

        self.task_groupbox = QtGui.QGroupBox("Task")

        self.task_select = TaskSelectMenu("select task")
        self.task_select.set_callback(self.task_changed)
        self.upload_button = QtGui.QPushButton("Upload")
        self.upload_button.setIcon(
            QtGui.QIcon("gui/icons/circle-arrow-up.svg"))
        self.variables_button = QtGui.QPushButton("Variables")
        self.variables_button.setIcon(QtGui.QIcon("gui/icons/filter.svg"))

        self.taskgroup_layout = QtGui.QGridLayout()
        self.taskgroup_layout.addWidget(self.task_select, 0, 0, 1, 2)
        self.taskgroup_layout.addWidget(self.upload_button, 1, 0)
        self.taskgroup_layout.addWidget(self.variables_button, 1, 1)
        self.task_groupbox.setLayout(self.taskgroup_layout)

        self.upload_button.clicked.connect(self.setup_task)

        # Session groupbox.

        self.session_groupbox = QtGui.QGroupBox("Session")

        self.start_button = QtGui.QPushButton("Start")
        self.start_button.setIcon(QtGui.QIcon("gui/icons/play.svg"))
        self.stop_button = QtGui.QPushButton("Stop")
        self.stop_button.setIcon(QtGui.QIcon("gui/icons/stop.svg"))

        self.task_info = TaskInfo()

        self.sessiongroup_layout = QtGui.QGridLayout()
        self.sessiongroup_layout.addWidget(self.task_info.print_label, 0, 1)
        self.sessiongroup_layout.addWidget(self.task_info.print_text, 0, 2, 1,
                                           3)
        self.sessiongroup_layout.addWidget(self.task_info.state_label, 1, 1)
        self.sessiongroup_layout.addWidget(self.task_info.state_text, 1, 2)
        self.sessiongroup_layout.addWidget(self.task_info.event_label, 1, 3)
        self.sessiongroup_layout.addWidget(self.task_info.event_text, 1, 4)
        self.sessiongroup_layout.addWidget(self.start_button, 0, 0)
        self.sessiongroup_layout.addWidget(self.stop_button, 1, 0)
        self.session_groupbox.setLayout(self.sessiongroup_layout)

        self.start_button.clicked.connect(self.start_task)
        self.stop_button.clicked.connect(self.stop_task)

        # Log text and task plots.

        self.log_textbox = QtGui.QTextEdit()
        self.log_textbox.setFont(QtGui.QFont("Courier New", log_font_size))
        self.log_textbox.setReadOnly(True)

        self.task_plot = Task_plot()
        self.data_logger = Data_logger(
            print_func=self.print_to_log,
            data_consumers=[self.task_plot, self.task_info])

        # Main layout

        self.vertical_layout = QtGui.QVBoxLayout()
        self.horizontal_layout_1 = QtGui.QHBoxLayout()
        self.horizontal_layout_2 = QtGui.QHBoxLayout()
        self.horizontal_layout_3 = QtGui.QHBoxLayout()

        self.horizontal_layout_1.addWidget(self.status_groupbox)
        self.horizontal_layout_1.addWidget(self.board_groupbox)
        self.horizontal_layout_2.addWidget(self.task_groupbox)
        self.horizontal_layout_2.addWidget(self.file_groupbox)
        self.horizontal_layout_3.addWidget(self.session_groupbox)
        self.vertical_layout.addLayout(self.horizontal_layout_1)
        self.vertical_layout.addLayout(self.horizontal_layout_2)
        self.vertical_layout.addLayout(self.horizontal_layout_3)
        self.vertical_layout.addWidget(self.log_textbox, 20)
        self.vertical_layout.addWidget(self.task_plot, 80)
        self.setLayout(self.vertical_layout)

        # Create timers

        self.update_timer = QtCore.QTimer(
        )  # Timer to regularly call update() during run.
        self.update_timer.timeout.connect(self.update)

        # Keyboard Shortcuts

        shortcut_dict = {
            "t":
            lambda: self.task_select.showMenu(),
            "u":
            lambda: self.setup_task(),
            "Space": (lambda: self.stop_task()
                      if self.running else self.start_task()
                      if self.uploaded else None),
        }

        init_keyboard_shortcuts(self, shortcut_dict)

        # Initial setup.

        self.disconnect()  # Set initial state as disconnected.

    # General methods

    def print_to_log(self, print_string, end="\n"):
        self.log_textbox.moveCursor(QtGui.QTextCursor.End)
        self.log_textbox.insertPlainText(print_string + end)
        self.log_textbox.moveCursor(QtGui.QTextCursor.End)
        self.GUI_main.app.processEvents(
        )  # To update gui during long operations that print progress.

    def test_data_path(self):
        # Checks whether data dir and subject ID are valid.
        self.data_dir = self.data_dir_text.text()
        subject_ID = self.subject_text.text()
        if os.path.isdir(self.data_dir) and subject_ID:
            self.start_button.setText("Record")
            self.start_button.setIcon(QtGui.QIcon("gui/icons/record.svg"))
            return True
        else:
            self.start_button.setText("Start")
            self.start_button.setIcon(QtGui.QIcon("gui/icons/play.svg"))
            return False

    def refresh(self):
        # Called regularly when framework not running.
        if self.GUI_main.setups_tab.available_setups_changed:
            self.board_select.clear()
            if self.GUI_main.setups_tab.setup_names:
                self.board_select.addItems(
                    self.GUI_main.setups_tab.setup_names)
                if not self.connected:
                    self.connect_button.setEnabled(True)
            else:  # No setups available to connect to.
                self.board_select.addItems(["No setups found"])
                self.connect_button.setEnabled(False)
        if self.GUI_main.available_tasks_changed:
            self.task_select.update_menu(dirs["tasks"])
        if self.GUI_main.data_dir_changed and not self.custom_dir:
            self.data_dir_text.setText(dirs["data"])
        if self.task:
            try:
                task_path = os.path.join(dirs["tasks"], self.task + ".py")
                if not self.task_hash == _djb2_file(
                        task_path):  # Task file modified.
                    self.task_changed()
            except FileNotFoundError:
                pass

    def open_config_dialog(self):
        """Open the config dialog and update GUI as required by chosen config."""
        self.GUI_main.config_dialog.exec_(self.board)
        self.task_changed()
        if self.GUI_main.config_dialog.disconnect:
            time.sleep(0.5)
            self.GUI_main.refresh()
            self.disconnect()
        if self.connected and self.board.status["framework"]:
            self.task_groupbox.setEnabled(True)

    # Widget methods.

    def connect(self):
        # Connect to pyboard.
        try:
            self.status_text.setText("Connecting...")
            self.board_select.setEnabled(False)
            self.variables_button.setEnabled(False)
            self.connect_button.setEnabled(False)
            self.repaint()
            port = self.GUI_main.setups_tab.get_port(
                self.board_select.currentText())
            self.board = Pycboard(port,
                                  print_func=self.print_to_log,
                                  data_logger=self.data_logger)
            self.connected = True
            self.config_button.setEnabled(True)
            self.connect_button.setEnabled(True)
            self.connect_button.setText("Disconnect")
            self.connect_button.setIcon(
                QtGui.QIcon("gui/icons/disconnect.svg"))
            self.status_text.setText("Connected")
            if self.board.status["framework"]:
                self.task_groupbox.setEnabled(True)
            else:
                self.print_to_log(
                    "\nLoad pyControl framework using 'Config' button.")
        except (SerialException, PyboardError):
            self.status_text.setText("Connection failed")
            self.print_to_log("Connection failed.")
            self.connect_button.setEnabled(True)
            self.board_select.setEnabled(True)

    def disconnect(self):
        # Disconnect from pyboard.
        if self.board:
            self.board.close()
        self.board = None
        self.task_groupbox.setEnabled(False)
        self.file_groupbox.setEnabled(False)
        self.session_groupbox.setEnabled(False)
        self.config_button.setEnabled(False)
        self.board_select.setEnabled(True)
        self.connect_button.setText("Connect")
        self.connect_button.setIcon(QtGui.QIcon("gui/icons/connect.svg"))
        self.status_text.setText("Not connected")
        self.task_changed()
        self.connected = False

    def task_changed(self, *args):
        self.uploaded = False
        self.upload_button.setText("Upload")
        self.upload_button.setIcon(
            QtGui.QIcon("gui/icons/circle-arrow-up.svg"))
        self.start_button.setEnabled(False)
        self.variables_button.setEnabled(False)

    def setup_task(self):
        task = self.task_select.text()
        if task == "select task":
            return
        try:
            if self.uploaded:
                self.status_text.setText("Resetting task..")
            else:
                self.status_text.setText("Uploading..")
                self.task_hash = _djb2_file(
                    os.path.join(dirs["tasks"], task + ".py"))
            self.start_button.setEnabled(False)
            self.variables_button.setEnabled(False)
            self.repaint()
            self.board.setup_state_machine(task, uploaded=self.uploaded)
            if self.variables_dialog:
                self.variables_button.clicked.disconnect()
                self.variables_dialog.deleteLater()
            self.task = task
            if "custom_variables_dialog" in self.board.sm_info["variables"]:
                custom_variables_name = eval(
                    self.board.sm_info["variables"]["custom_variables_dialog"])
                potential_dialog = Custom_variables_dialog(
                    self, custom_variables_name)
                if potential_dialog.using_custom_gui:
                    self.variables_dialog = potential_dialog
                    self.using_custom_gui = True
                else:
                    self.variables_dialog = Variables_dialog(self, self.board)
                    self.using_custom_gui = False
            else:
                self.variables_dialog = Variables_dialog(self, self.board)
                self.using_custom_gui = False
            self.variables_button.clicked.connect(self.variables_dialog.exec_)
            self.variables_button.setEnabled(True)
            self.task_plot.set_state_machine(self.board.sm_info)
            self.task_info.set_state_machine(self.board.sm_info)
            self.file_groupbox.setEnabled(True)
            self.session_groupbox.setEnabled(True)
            self.start_button.setEnabled(True)
            self.stop_button.setEnabled(False)
            self.status_text.setText("Uploaded : " + task)
            self.fresh_task = True
            self.uploaded = True
            self.upload_button.setText("Reset")
            self.upload_button.setIcon(QtGui.QIcon("gui/icons/refresh.svg"))

        except PyboardError:
            self.status_text.setText("Error setting up state machine.")

    def select_data_dir(self):
        new_path = QtGui.QFileDialog.getExistingDirectory(
            self, "Select data folder", dirs["data"])
        if new_path:
            self.data_dir_text.setText(new_path)
            self.custom_dir = True

    def start_task(self):
        recording = self.test_data_path()
        if recording:
            if not self.fresh_task:
                reset_task = QtGui.QMessageBox.question(
                    self,
                    "Reset task",
                    "Task has already been run, variables may not have default values.\n\nReset task?",
                    QtGui.QMessageBox.Yes,
                    QtGui.QMessageBox.No,
                )
                if reset_task == QtGui.QMessageBox.Yes:
                    self.setup_task()
                    return
            subject_ID = str(self.subject_text.text())
            setup_ID = str(self.board_select.currentText())
            self.data_logger.open_data_file(self.data_dir, "run_task",
                                            setup_ID, subject_ID)
            self.data_logger.copy_task_file(self.data_dir, dirs["tasks"],
                                            "run_task-task_files")
        self.fresh_task = False
        self.running = True
        self.board.start_framework()
        self.task_plot.run_start(recording)
        self.task_select.setEnabled(False)
        self.upload_button.setEnabled(False)
        self.file_groupbox.setEnabled(False)
        self.start_button.setEnabled(False)
        self.board_groupbox.setEnabled(False)
        self.stop_button.setEnabled(True)
        if self.using_custom_gui:
            self.variables_dialog.edit_action.setEnabled(False)
        self.print_to_log(
            f"\nRun started at: {datetime.now().strftime('%Y/%m/%d %H:%M:%S')}\n"
        )
        self.update_timer.start(update_interval)
        self.GUI_main.refresh_timer.stop()
        self.status_text.setText("Running: " + self.task)
        self.GUI_main.tab_widget.setTabEnabled(
            1, False)  # Disable experiments tab.
        self.GUI_main.tab_widget.setTabEnabled(2, False)  # Disable setups tab.

    def stop_task(self, error=False, stopped_by_task=False):
        self.running = False
        self.update_timer.stop()
        self.GUI_main.refresh_timer.start(self.GUI_main.refresh_interval)
        if not (error or stopped_by_task):
            self.board.stop_framework()
            time.sleep(0.05)
            self.board.process_data()
        self.data_logger.close_files()
        self.task_plot.run_stop()
        self.board_groupbox.setEnabled(True)
        self.file_groupbox.setEnabled(True)
        self.start_button.setEnabled(True)
        self.task_select.setEnabled(True)
        self.upload_button.setEnabled(True)
        self.stop_button.setEnabled(False)
        if self.using_custom_gui:
            self.variables_dialog.edit_action.setEnabled(True)
        self.status_text.setText("Uploaded : " + self.task)
        self.GUI_main.tab_widget.setTabEnabled(1, True)  # Enable setups tab.
        self.GUI_main.tab_widget.setTabEnabled(2, True)  # Enable setups tab.

    # Timer updates

    def update(self):
        # Called regularly during run to process data from board and update plots.
        try:
            self.board.process_data()
            if not self.board.framework_running:
                self.stop_task(stopped_by_task=True)
        except PyboardError:
            self.print_to_log("\nError during framework run.")
            self.stop_task(error=True)
        self.task_plot.update()

    # Cleanup.

    def closeEvent(self, event):
        # Called when GUI window is closed.
        if self.board:
            self.board.stop_framework()
            self.board.close()
        event.accept()

    # Exception handling.

    def excepthook(self, ex_type, ex_value, ex_traceback):
        # Called whenever an uncaught exception occurs.
        if ex_type in (SerialException, SerialTimeoutException):
            self.print_to_log("\nError: Serial connection with board lost.")
        elif ex_type == PyboardError:
            self.print_to_log("\nError: Unable to execute command.")
        else:
            self.print_to_log(
                f"\nError: uncaught exception of type: {ex_type}")
        if self.running:
            self.stop_task(error=True)
        self.disconnect()