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 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 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)
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)
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()
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
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()