def main(): # parse command line arguments args = parse_args() try: log_port = int(args['logport']) except IndexError: raise IndexError( 'Please provide command line arguments in the form\n"' 'python launch_gui.py --logport 1234 --serverport 5678 --server servername' ) if 'serverport' in args: server_port = int(args['serverport']) else: server_port = None if 'server' in args: server = args['server'] else: raise IndexError( 'Please provide command line arguments in the form\n"' 'python launch_gui.py --logport 1234 --serverport 5678 --server servername' ) if 'logip' in args: log_ip = args['logip'] else: log_ip = 'localhost' # If pylabnet.server is launched directly, it might not use a configs flag. if 'config' in args: config = args['config'] else: config = None device_id = args['device_id'] logger_tag = server + '_server' + '_' + device_id # Instantiate logger. This creates a client_data entry in the LogServer # that is populated with the server name, port. server_logger = LogClient(host=log_ip, port=log_port, module_tag=logger_tag, server_port=server_port) # Add device ID of server to LogClient data dict server_logger.update_data(data=dict(device_id=device_id)) # Retrieve debug flag. debug = int(args['debug']) # Halt execution and wait for debugger connection if debug flag is up. if debug: # 5678 is the default attach port in the VS Code debug configurations server_logger.info( f"Waiting for debugger to attach to PID {os.getpid()} (pylabnet_server)" ) ptvsd.enable_attach(address=('localhost', 5678)) ptvsd.wait_for_attach() breakpoint() # Register new exception hook. def log_exceptions(exc_type, exc_value, exc_traceback): """Handler for unhandled exceptions that will write to the logs""" error_msg = ''.join( traceback.format_exception(exc_type, exc_value, exc_traceback)) server_logger.error(f"Uncaught exception: {error_msg}") sys.excepthook = log_exceptions # Instantiate module try: mod_inst = importlib.import_module(f'servers.{server}') except ModuleNotFoundError: server_logger.error( f'No module found in pylabnet.launchers.servers named {server}.py') raise tries = 0 update_flag = False while tries < 10: if server_port is None: server_port = np.random.randint(1024, 49151) update_flag = True try: mod_inst.launch(logger=server_logger, port=server_port, device_id=device_id, config=config) if update_flag: server_logger.update_data(data=dict(port=server_port)) tries = 10 except OSError: server_logger.warn( f'Failed to launch server at port: {server_port}') tries += 1 if tries == 10: raise hide_console()
class RNG: def __init__(self, mean=0, amp=1, log_host=None, log_port=None, log_tag='RNG', log_level='INFO'): self._mean = mean self._amp = amp self.test_array = np.zeros(1) self.test_array_dict = dict() self.log = LogClient(host=log_host, port=log_port, module_tag=log_tag, level_str=log_level) self.log.info('Started RNG: mean={0}, amp={1}'.format( self._mean, self._amp)) def get_params(self): param_dict = dict(mean=self._mean, amp=self._amp) return param_dict def set_params(self, mean=None, amp=None): self.log.info('set_params(mean={}, amp={}) called'.format(mean, amp)) if mean is not None: self._mean = mean if amp is not None: self._amp = amp return 0 def get_value(self, size=None): values = self._mean + self._amp * (np.random.random(size=size) - 0.5) return values # Timing test def generate_test_array(self, size): self.test_array = np.random.random(size=size) self.log.info( 'Successfully generated test_array with size={}'.format(size)) def get_test_array(self): return self.test_array # def get_test_array_pickle(self): # return pickle.dumps(self.test_array) # # def get_test_array_tostring(self): # return self.test_array.tostring() # Many active clients test def build_test_array(self, client_number, size=1000): test_ar = client_number + (np.random.random(size=size) - 0.5) self.test_array_dict[str(client_number)] = test_ar self.log.info('Successfully built test_array. \n' 'client_number={}, size={}'.format(client_number, size)) return 0 def ret_test_array(self, client_number): return self.test_array_dict[str(client_number)] # Multi-level traceback/stability test def divide(self, a, b): try: c = a / b return c except Exception as exc_obj: self.log.exception('divide(a={}, b={})'.format(a, b)) raise exc_obj
class Controller: """ Class for log system controller """ LOGGER_UI = 'logger_remote' GUI_NAME = 'logger_GUI' # When kept as None, random port numbers will be used # use these values to override and set manual port numbers if desired LOG_PORT = None GUI_PORT = None def __init__(self, proxy=False, master=False, staticproxy=False): """ Initializes launch control GUI """ self.operating_system = get_os() self.app = QtWidgets.QApplication(sys.argv) self.app.setWindowIcon( QtGui.QIcon( os.path.join(os.path.dirname(os.path.realpath(__file__)), 'devices.ico'))) # Instantiate GUI application if self.operating_system == 'Windows': ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID( 'pylabnet') self.main_window = LaunchWindow(self.app, self, gui_template=self.LOGGER_UI) self.main_window.stop_button.clicked.connect(self._kill) if self.operating_system not in ['Linux', 'Windows']: raise UnsupportedOSException try: if sys.argv[1] == '-m' or master: self.master = True else: self.master = False except IndexError: if master: self.master = True else: self.master = False try: if sys.argv[1] == '-p' or proxy: self.proxy = True else: self.proxy = False except IndexError: if proxy: self.proxy = True else: self.proxy = False try: if sys.argv[1] == '-sp' or staticproxy: self.staticproxy = True else: self.staticproxy = False except IndexError: if staticproxy: self.staticproxy = True else: self.staticproxy = False self.host = get_ip() self.update_index = 0 # Retrieve static port info. if self.master: try: static_proxy_dict = load_config('static_proxy') except: print('No config found named static_proxy.json') time.sleep(10) raise self.log_port = static_proxy_dict['master_log_port'] self.gui_port = static_proxy_dict['master_gui_port'] hide_console() elif self.proxy: popup = ParameterPopup(host=str, log_port=str, gui_port=str) self.waiting_flag = True popup.parameters.connect(self.fill_parameters) while self.waiting_flag: self.app.processEvents() elif self.staticproxy: try: static_proxy_dict = load_config('static_proxy') except: print('No config found named static_proxy.json') time.sleep(10) raise self.host = static_proxy_dict['master_ip'] self.log_port = static_proxy_dict['master_log_port'] self.gui_port = static_proxy_dict['master_gui_port'] self.proxy = True hide_console() else: self.log_port = self.LOG_PORT self.gui_port = self.GUI_PORT self.log_service = None self.log_server = None self.gui_client = None self.gui_logger = None self.gui_service = None self.gui_server = None self.client_list = {} self.port_list = {} self.script_list = {} self.client_data = {} self.disconnection = False self.debug = False self.debug_level = None self.autoscroll_off = False # date string is None if not logging to file, and gives today's date if logging to file. # For day-chopping purposes self.logfile_date_str = None self.filenamepath = None self.MAX_LOG_FILE_SIZE = 5000000 # 5MB self.last_seen_buffer = "" # setting selection mode for server list to multi-select self.main_window.client_list.setSelectionMode( QtWidgets.QAbstractItemView.ExtendedSelection) def fill_parameters(self, params): """ Called when parameters have been entered into a popup """ self.host = params['host'] self.log_port = params['log_port'] self.gui_port = params['gui_port'] self.waiting_flag = False def start_gui_server(self): """ Starts the launch controller GUI server, or connects to the server and updates GUI""" module_str = '' if self.proxy: module_str = '_proxy' # connect to the logger try: self.gui_logger = LogClient(host=self.host, port=self.log_port, module_tag=self.GUI_NAME + module_str, ui=self.LOGGER_UI) except ConnectionRefusedError: self.main_window.terminal.setText( 'Failed to connect to master. Shutting down') self.main_window.force_update() time.sleep(10) raise # if lab name is specified: add to gui_logger try: lab_name_dict = load_config("lab_name") lab_name = lab_name_dict['lab_name'] except: lab_name = 'NO_LAB' self.gui_logger.update_data(data=dict(lab_name=lab_name)) # Instantiate GUI server and update GUI with port details self.gui_service = Service() self.gui_service.assign_module(module=self.main_window) self.gui_service.assign_logger(logger=self.gui_logger) if self.gui_port is None: self.gui_server, self.gui_port = create_server( self.gui_service, logger=self.gui_logger, host=get_ip()) my_port = self.gui_port self.main_window.gui_label.setText(f'GUI Port: {my_port}') elif self.proxy: self.gui_server, my_port = create_server(self.gui_service, logger=self.gui_logger, host=get_ip()) self.main_window.gui_label.setText( f'Master (Local) GUI Port: {self.gui_port} ({my_port})') else: try: self.gui_server = GenericServer(service=self.gui_service, host=get_ip(), port=self.gui_port) my_port = self.gui_port self.main_window.gui_label.setText(f'GUI Port: {my_port}') except ConnectionRefusedError: self.gui_logger.error( f'Failed to instantiate GUI Server at port {self.gui_port}' ) raise self.gui_server.start() self.gui_logger.update_data(data=dict(port=my_port)) if self.proxy: # Connect to the GUI server try: self.gui_client = Client(host=self.host, port=self.gui_port) except ConnectionRefusedError: self.gui_logger.error( f'Failed to connect to GUI Server with IP address: {self.host}, ' f'Port: {self:gui_port}') raise # Now update GUI to mirror clients self._copy_master() # Get the latest update index buffer = self.gui_client.get_text('buffer') try: self.update_index = int( re.findall(r'\d+', re.findall(r'!~\d+~!', buffer)[-1])[0]) except IndexError: self.update_index = 0 self.gui_service = Service() self.gui_service.assign_module(module=self.main_window) self.gui_service.assign_logger(logger=self.gui_logger) else: # Update internal attributes and add to list of log clients self.client_list[self.GUI_NAME] = QtWidgets.QListWidgetItem( self.GUI_NAME) self.port_list[self.GUI_NAME] = [ port for port in self.log_server._server.clients ][0] self.main_window.client_list.addItem( self.client_list[self.GUI_NAME]) self.client_list[self.GUI_NAME].setToolTip( dict_to_str(self.log_service.client_data[self.GUI_NAME])) self.client_data[self.GUI_NAME + module_str] = self.log_service.client_data[ self.GUI_NAME] def update_terminal(self, text): """ Updates terminal output on GUI """ self.main_window.terminal.append(text) if not self.autoscroll_off: try: self.main_window.terminal.moveCursor(QtGui.QTextCursor.End) except TypeError: pass # Update buffer terminal buffer_str = f'!~{self.update_index}~!{text}' self.main_window.buffer_terminal.append(buffer_str) self.update_index += 1 def check_disconnection(self, text): """ Checks if a client has disconnected and raises a flag if so""" if 'Client disconnected' in text or self.disconnection: self.disconnection = True def disconnect(self): """ Handles case where client has disconnected """ to_del = [ client for (client, port) in self.port_list.items() if port not in self.log_server._server.clients ] for client in to_del: print('[INFO] {} disconnected at {}'.format( client, time.strftime("%Y-%m-%d, %H:%M:%S", time.gmtime()))) self.main_window.client_list.takeItem( self.main_window.client_list.row(self.client_list[client])) del self.client_list[client] del self.port_list[client] del self.log_service.client_data[client] del self.client_data[client] self.disconnection = False def update_connection(self): """ Checks if new/updated connections have been made and updates accordingly""" # Figure out ports/clients to add port_to_add = [ port for port in self.log_server._server.clients if port not in self.port_list.values() ] client_to_add = [ client for client in self.log_service.client_data if client not in self.client_list ] # Add client and update relevant directories + GUI if len(client_to_add) > 0: client = client_to_add[0] self.client_list[client] = QtWidgets.QListWidgetItem(client) self.main_window.client_list.addItem(self.client_list[client]) try: self.main_window.client_list.moveCursor(QtGui.QTextCursor.End) except TypeError: pass self.client_list[client].setToolTip( dict_to_str(self.log_service.client_data[client])) if len(port_to_add) > 0: self.port_list[client] = port_to_add[0] self.client_data[client] = self.log_service.client_data[client] # Check for updates to client data while len(self.log_service.data_updated) > 0: self.client_list[self.log_service.data_updated[0]].setToolTip( dict_to_str(self.log_service.client_data[ self.log_service.data_updated[0]])) del self.log_service.data_updated[0] def start_logger(self): """ Starts the log server """ self.log_service = LogService() if self.LOG_PORT is None and not self.master: self.log_server, self.log_port = create_server(self.log_service, host=get_ip()) else: try: self.log_server = GenericServer(service=self.log_service, host=get_ip(), port=self.log_port) except ConnectionRefusedError: print( f'Failed to insantiate Log Server at port {self.LOG_PORT}') raise self.log_server.start() self.log_service.logger.info('log service succesfully started') def initialize_gui(self): """ Initializes basic GUI display """ ip_str, ip_str_2, log_str = '', '', '' if self.master: self.main_window.setWindowTitle('Launch Control (Master)') if self.proxy: if self.staticproxy: self.main_window.setWindowTitle('Launch Control (Staticproxy)') else: self.main_window.setWindowTitle('Launch Control (Proxy)') ip_str = 'Master (Local) ' ip_str_2 = f' ({get_ip()})' log_str = 'Master' self.main_window.ip_label.setText(f'{ip_str}IP Address: {self.host}' + ip_str_2) self.main_window.logger_label.setText( f'{log_str} Logger Port: {self.log_port}') if self.proxy: self.main_window.terminal.setText( 'Connected to master Log Server. \n') self.main_window.terminal.setText( 'Log messages will be displayed below \n') self.main_window.buffer_terminal.document().setMaximumBlockCount(1000) # Assign widgets for remote access self.main_window.assign_container('client_list', 'clients') self.main_window.assign_label('buffer_terminal', 'buffer') self.main_window.assign_event_button('debug_radio_button', 'debug') # Hide some buttons self.main_window.file_viewer.setHidden(True) self.main_window.logfile_status_button.setHidden(True) self.main_window.debug_label.setHidden(True) self.main_window.debug_comboBox.setHidden(True) self.main_window.logfile_status_button.setHidden(True) self.main_window.log_previous.setHidden(True) self.main_window.logfile_status_indicator.setEnabled(False) # Configure list of scripts to run and clicking actions self._load_scripts() self._configure_clicks() self._configure_client_search() self._configure_lab_name_select() self._configure_debug() self._configure_debug_combo_select() self._configure_logfile() self._configure_logging() self._configure_autoscroll_off() self.main_window.force_update() def update_proxy(self, new_msg): """ Updates the proxy with new content using the buffer terminal continuously""" # Remove the !~ bookmark from the message self.main_window.terminal.append(re.sub(r'!~\d+~!', '', new_msg)) if not self.autoscroll_off: try: self.main_window.terminal.moveCursor(QtGui.QTextCursor.End) except TypeError: pass # New update index is the last !~ index found in the message indices_found = re.findall(r'!~\d+~!', new_msg) if len(indices_found) != 0: self.update_index = int(re.findall(r'\d+', indices_found[-1])[0]) else: self.update_index = None def kill_servers(self): """ Kills all servers connected to the logger, including the Log GUI and Log Server""" client_data = copy.deepcopy(self.client_data) del client_data['logger_GUI'] for server_data in client_data.values(): if 'port' in server_data: stop_client = ClientBase(host=server_data['ip'], port=server_data['port']) stop_client.close_server() self.gui_server.stop() self.log_server.stop() def update(self, text): """ Runs an update when new text comes through """ self.main_window.configure_widgets() self.main_window.update_widgets() #Check for disconnection events self.check_disconnection(text) # Handle new connections self.update_connection() # Update terminal self.update_terminal(text) # Chop log file if date has changed self.chop_log_file() if self.disconnection: self.disconnect() def chop_log_file(self): """ Checks if date has changed, and chops logfile accordingly""" if self.logfile_date_str is not None: # if date has changed, move to new log file with new date if self.logfile_date_str != datetime.now().strftime("%Y_%m_%d"): self.log_service.logger.info('Starting new logging file!') self.start_stop_logging(master_log=True) if os.stat(self.filenamepath).st_size > self.MAX_LOG_FILE_SIZE: self.log_service.logger.info('Starting new logging file!') self.start_stop_logging(master_log=True) def _configure_client_search(self): self.main_window.client_search.textChanged.connect( self._update_displayed_client_list) def _configure_lab_name_select(self): self.main_window.lab_name_select.currentIndexChanged.connect( self._update_displayed_client_list) def _configure_clicks(self): """ Configures what to do upon clicks """ self.main_window.close_server.pressed.connect(self._stop_server) def _update_displayed_client_list(self): search_str = self.main_window.client_search.text() lab_name = self.main_window.lab_name_select.currentText() clients = self.gui_client.get_container_info('clients') self.main_window.client_list.clear() self.client_list.clear() if lab_name == "ALL LABS": # if ALL LABS is selected, don't filter by lab name if search_str != "": for client, info in clients.items(): self.client_list[client] = QtWidgets.QListWidgetItem( client) # look for clients that have name or ip address containing search string if search_str in client or search_str in self.client_data[ client]['ip']: self.main_window.client_list.addItem( self.client_list[client]) self.client_list[client].setToolTip(info) else: # if search string is empty, don't use it to filter clients for client, info in clients.items(): self.client_list[client] = QtWidgets.QListWidgetItem( client) self.main_window.client_list.addItem( self.client_list[client]) self.client_list[client].setToolTip(info) else: # filter by lab name if search_str != "": for client, info in clients.items(): self.client_list[client] = QtWidgets.QListWidgetItem( client) # look for clients that have name or ip address containing search string, and that have the selected lab name if (search_str in client or search_str in self.client_data[client]['ip'] ) and (self.client_data[client]['lab_name'] == lab_name): self.main_window.client_list.addItem( self.client_list[client]) self.client_list[client].setToolTip(info) else: # if search string is empty, don't use it to filter clients for client, info in clients.items(): self.client_list[client] = QtWidgets.QListWidgetItem( client) # look for clients that have the selected lab name if self.client_data[client]['lab_name'] == lab_name: self.main_window.client_list.addItem( self.client_list[client]) self.client_list[client].setToolTip(info) def _stop_server(self): """ Stops the highlighted server, if applicable """ # Retrieve all selected servers. clients_to_stop = [ client.text() for client in self.main_window.client_list.selectedItems() ] for client_to_stop in clients_to_stop: server_data = self.client_data[client_to_stop] if 'port' in server_data: try: stop_client = ClientBase(host=server_data['ip'], port=server_data['port']) stop_client.close_server() except: self.gui_logger.warn( f'Failed to shutdown server {client_to_stop}' f'on host: {server_data["ip"]}, port: {server_data["port"]}' ) self.gui_logger.info( 'Attempting to remove from LogClients manually') self._close_dangling(client_to_stop) else: self._close_dangling(client_to_stop) def _close_dangling(self, client_to_stop): # Cannot connect to the server and close, must remove. # WARNING: might result in dangling threads try: client_port_to_stop = self.port_list[client_to_stop] port_found = True except KeyError: port_found = False if port_found and client_port_to_stop in self.log_server._server.clients: c = self.port_list[client_to_stop] c.close() closing(c) self.log_server._server.clients.discard(c) self.main_window.client_list.takeItem( self.main_window.client_list.row( self.client_list[client_to_stop])) del self.port_list[client_to_stop] del self.client_list[client_to_stop] del self.log_service.client_data[client_to_stop] del self.client_data[client_to_stop] self.gui_logger.info(f'Client disconnected: {client_to_stop}') # If we can't find the client connected to the server, just remove it else: self.gui_logger.warn( f'No matching client connected to LogServer: {client_to_stop}') try: # The following two member variables don't exist for a proxy. if not self.proxy: self.main_window.client_list.takeItem( self.main_window.client_list.row( self.client_list[client_to_stop])) del self.port_list[client_to_stop] del self.log_service.client_data[client_to_stop] del self.client_list[client_to_stop] del self.client_data[client_to_stop] else: self.gui_client.remove_client_list_entry(client_to_stop) self.gui_logger.info( f'Hard kill of {client_to_stop} successfull.') except: pass def _device_clicked(self, index): """ Configures behavior for device double click :param index: (QModelIndex) index of file clicked on """ # clear the client search bar and display all clients self.main_window.client_search.setText("") #self._search_clients() filepath = self.main_window.devices.model().filePath(index) # Check if it is an actual config file if not os.path.isdir(filepath): # Find the name of the server and device config file device_server = os.path.basename(os.path.dirname(filepath)) device_config = os.path.basename(filepath)[:-5] self.gui_logger.info(f'Launching device {device_server} ' f'with configuration {device_config}') # Initial configurations: All flags down. server_debug_flag = '0' # Raise flags if selected in combobox if self.debug and self.debug_level == "pylabnet_server": server_debug_flag = '1' server_port = np.random.randint(1024, 49151) launch_device_server(server=device_server, dev_config=device_config, log_ip=self.host, log_port=self.log_port, server_port=server_port, debug=server_debug_flag, logger=self.gui_logger) def _script_clicked(self, index): """ Configures behavior for script double click :param index: (QModelIndex) index of file clicked on """ # clear the client search bar and display all clients self.main_window.client_search.setText("") #self._search_clients() filepath = self.main_window.scripts.model().filePath(index) # Check if it is an actual config file if not os.path.isdir(filepath): # Find the name of the config file script_name = os.path.basename(os.path.dirname(filepath)) script_config = os.path.basename(filepath)[:-5] self.gui_logger.info(f'Launching device {script_name} ' f'with configuration {script_config}') # Initial configurations: All flags down. debug_flag, server_debug_flag = '0', '0' # Raise flags if selected in combobox if self.debug: if self.debug_level == "launcher": debug_flag = '1' elif self.debug_level == "pylabnet_server": server_debug_flag = '1' # # Build client list cmdline arg # client_index = 1 # bash_cmd = '' # for client in self.client_list: # bash_cmd += ' --client{} {} --ip{} {}'.format( # client_index, remove_spaces(client), client_index, self.client_data[client]['ip'] # ) # # Add device ID of client's corresponding hardware, if applicable # if 'device_id' in self.client_data[client]: # bash_cmd += ' --device_id{} {}'.format(client_index, self.client_data[client]['device_id']) # # Add port of client's server, if applicable # if 'port' in self.client_data[client]: # bash_cmd += ' --port{} {}'.format(client_index, self.client_data[client]['port']) # # If this client has relevant .ui file, pass this info # if 'ui' in self.client_data[client]: # bash_cmd += ' --ui{} {}'.format(client_index, self.client_data[client]['ui']) # client_index += 1 launch_script(script=script_name, config=script_config, log_ip=self.host, log_port=self.log_port, debug_flag=debug_flag, server_debug_flag=server_debug_flag, num_clients=len(self.client_list), logger=self.gui_logger) def _load_scripts(self): """ Loads all relevant scripts/devices from filesystem""" # Load scripts with configuraitons script_dir = os.path.join(get_config_directory(), 'scripts') if os.path.isdir(script_dir): model = QtWidgets.QFileSystemModel() model.setRootPath(script_dir) self.main_window.scripts.setModel(model) self.main_window.scripts.setRootIndex(model.index(script_dir)) self.main_window.scripts.hideColumn(1) self.main_window.scripts.hideColumn(2) self.main_window.scripts.hideColumn(3) self.main_window.scripts.doubleClicked.connect(self._script_clicked) # Load device config files device_dir = os.path.join(get_config_directory(), 'devices') if os.path.isdir(device_dir): model = QtWidgets.QFileSystemModel() model.setRootPath(device_dir) self.main_window.devices.setModel(model) self.main_window.devices.setRootIndex(model.index(device_dir)) self.main_window.devices.hideColumn(1) self.main_window.devices.hideColumn(2) self.main_window.devices.hideColumn(3) self.main_window.devices.doubleClicked.connect(self._device_clicked) def _copy_master(self): """ Updates the GUI to copy the GUI of the master GUI server """ # Get a dictionary of all client names and tooltip info clients = self.gui_client.get_container_info('clients') # Update the proxy GUI to reflect the client list of the main GUI for client, info in clients.items(): self.client_list[client] = QtWidgets.QListWidgetItem(client) self.main_window.client_list.addItem(self.client_list[client]) self.client_list[client].setToolTip(info) # Add client data self.client_data[client] = {} if 'ip: ' in info: self.client_data[client]['ip'] = info.split('ip: ')[1].split( '\n')[0] if 'timestamp: ' in info: self.client_data[client]['timestamp'] = info.split( 'timestamp: ')[1].split('\n')[0] if 'ui: ' in info: self.client_data[client]['ui'] = info.split('ui: ')[1].split( '\n')[0] if 'port: ' in info: self.client_data[client]['port'] = info.split( 'port: ')[1].split('\n')[0] if 'device_id: ' in info: self.client_data[client]['device_id'] = info.split( 'device_id: ')[1].split('\n')[0] if 'lab_name: ' in clients[client]: self.client_data[client]['lab_name'] = clients[client].split( 'lab_name: ')[1].split('\n')[0] else: # if no lab name is specified self.client_data[client]['lab_name'] = "NO_LAB" def _pull_connections(self): """ Updates the proxy's client list """ # Get a dictionary of all client names and tooltip info clients = self.gui_client.get_container_info('clients') # Update the proxy GUI to reflect the client list of the main GUI add_clients = list(set(clients.keys()) - set(self.client_list.keys())) remove_clients = list( set(self.client_list.keys()) - set(clients.keys())) other_clients = list( set(clients.keys()) - set(add_clients) - set(remove_clients)) # Add clients for client in add_clients: self.client_list[client] = QtWidgets.QListWidgetItem(client) self.main_window.client_list.addItem(self.client_list[client]) self.client_list[client].setToolTip(clients[client]) # Add client data self.client_data[client] = {} print('Client: ' + client) if 'ip: ' in clients[client]: self.client_data[client]['ip'] = clients[client].split( 'ip: ')[1].split('\n')[0] if 'timestamp: ' in clients[client]: self.client_data[client]['timestamp'] = clients[client].split( 'timestamp: ')[1].split('\n')[0] if 'ui: ' in clients[client]: self.client_data[client]['ui'] = clients[client].split( 'ui: ')[1].split('\n')[0] if 'port: ' in clients[client]: self.client_data[client]['port'] = clients[client].split( 'port: ')[1].split('\n')[0] if 'device_id: ' in clients[client]: self.client_data[client]['device_id'] = clients[client].split( 'device_id: ')[1].split('\n')[0] if 'lab_name: ' in clients[client]: self.client_data[client]['lab_name'] = clients[client].split( 'lab_name: ')[1].split('\n')[0] else: # if no lab name is specified self.client_data[client]['lab_name'] = "NO_LAB" # Remove clients for client in remove_clients: self.main_window.client_list.takeItem( self.main_window.client_list.row(self.client_list[client])) del self.client_list[client] # Update any other changes for client in other_clients: if self.client_list[client].toolTip() != clients[client]: self.client_list[client].setToolTip(clients[client]) if 'ip: ' in clients[client]: self.client_data[client]['ip'] = clients[client].split( 'ip: ')[1].split('\n')[0] if 'timestamp: ' in clients[client]: self.client_data[client]['timestamp'] = clients[ client].split('timestamp: ')[1].split('\n')[0] if 'ui: ' in clients[client]: self.client_data[client]['ui'] = clients[client].split( 'ui: ')[1].split('\n')[0] if 'port: ' in clients[client]: self.client_data[client]['port'] = clients[client].split( 'port: ')[1].split('\n')[0] if 'device_id: ' in clients[client]: self.client_data[client]['device_id'] = clients[ client].split('device_id: ')[1].split('\n')[0] if 'lab_name: ' in clients[client]: self.client_data[client]['lab_name'] = clients[ client].split('lab_name: ')[1].split('\n')[0] else: # if no lab name is specified self.client_data[client]['lab_name'] = "NO_LAB" def _configure_autoscroll_off(self): self.main_window.autoscroll_off_check.toggled.connect( self._update_autoscroll_setting) # Defines what to do if debug radio button is clicked. def _configure_debug(self): self.main_window.debug_radio_button.toggled.connect( self._update_debug_settings) def _configure_logging(self): """ Defines what to do if the Start/Stop Logging button is clicked """ self.main_window.logfile_status_button.toggled.connect( lambda: self.start_stop_logging(master_log=False)) def _configure_logfile(self): """ Defines what to do if the logfile radio button is clicked """ self.main_window.log_file_button.toggled.connect( self._update_logfile_status) # Defines what to do if combobox is changed. def _configure_debug_combo_select(self): self.main_window.debug_comboBox.currentIndexChanged.connect( self._update_debug_level) def _update_debug_settings(self): if self.main_window.debug_radio_button.isChecked(): self.debug = True # Enable and show combobox. self.main_window.debug_comboBox.setEnabled(True) self.main_window.debug_label.setHidden(False) self.main_window.debug_comboBox.setHidden(False) else: self.debug = False # Disable and hide combobox. self.main_window.debug_comboBox.setEnabled(False) self.main_window.debug_label.setHidden(True) self.main_window.debug_comboBox.setHidden(True) # Update debug level. self._update_debug_level() def _update_logfile_status(self): """ Updates the status of whether or not we are using a logfile """ if self.main_window.log_file_button.isChecked(): # Enable and show file browser self.main_window.file_viewer.setEnabled(True) self.main_window.file_viewer.setHidden(False) self.main_window.logfile_status_button.setEnabled(True) self.main_window.logfile_status_button.setHidden(False) self.main_window.log_previous.setEnabled(True) self.main_window.log_previous.setHidden(False) # Assign a file system model if we're not already logging if not self.main_window.logfile_status_button.isChecked(): model = QtWidgets.QFileSystemModel() model.setRootPath(QtCore.QDir.rootPath()) self.main_window.file_viewer.setModel(model) self.main_window.file_viewer.setRootIndex( model.index(QtCore.QDir.homePath())) self.main_window.file_viewer.setColumnWidth(0, 200) else: # Disable and hide file browser self.main_window.file_viewer.setHidden(True) self.main_window.file_viewer.setEnabled(False) self.main_window.logfile_status_button.setHidden(True) self.main_window.logfile_status_button.setEnabled(False) self.main_window.log_previous.setEnabled(False) self.main_window.log_previous.setHidden(True) def _update_debug_level(self, i=0): # Set debug level according to combo-box selection. # Levels are: # pylabnet_server, pylabnet_gui, launcher self.debug_level = self.main_window.debug_comboBox.currentText() def _update_autoscroll_setting(self): if self.main_window.autoscroll_off_check.isChecked(): self.autoscroll_off = True else: self.autoscroll_off = False def _kill(self): """ Kills launch control and all child servers if master """ if not self.proxy: self.kill_servers() self.main_window.close() def start_stop_logging(self, master_log=False): """ Starts or stops logging to file depending on situation :master_log: (bool) If True, this function is called as initial setup function of filesaving for the master launch control. In this case a log path as specified in the config file is chosen. """ # check if there's already an open log file and close it if self.logfile_date_str is not None: self.log_service.stop_latest_logfile() if self.main_window.logfile_status_button.isChecked() or master_log: date_str = datetime.now().strftime("%Y_%m_%d") time_str = datetime.now().strftime("%H_%M_%S") # Actually start logging filename = f'logfile_{time_str}' # Get logging file from json. filepath = None if master_log: try: config_dict = load_config('static_proxy') filepath = config_dict['logger_path'] except: self.main_window.terminal.setText( 'Critical error: ' 'no logger_path found in static_proxy.json') self.main_window.force_update() time.sleep(10) raise # Or from filepath selector. else: filepath = self.main_window.file_viewer.model().filePath( self.main_window.file_viewer.selectionModel().currentIndex( )) try: self.log_service.add_logfile(name=filename, dir_path=filepath) except Exception as error_msg: print( f'Failed to start logging to file {os.path.join(filepath, filename)}.\n{error_msg}' ) self.log_service.logger.info( f'Started logging to file {os.path.join(filepath, filename)}.') # Change button color and text self.main_window.logfile_status_button.setStyleSheet( "background-color: red") self.main_window.logfile_status_button.setText( 'Stop logging to file') self.main_window.logfile_status_indicator.setChecked(True) # Add previous text to logfile if self.main_window.log_previous.isChecked(): self.log_service.logger.info( f'Previous log terminal content: \n{self.main_window.terminal.toPlainText()}' f'\n---------------------------') # Pass current date of logfile for day-chopping purposes self.logfile_date_str = date_str # pass log file name and path to access filesize for chopping purposes self.filenamepath = config_dict[ 'logger_path'] + '\\' + date_str[:4] + '\\' + date_str[ 5:7] + '\\' + date_str[8:] + '\\logfile_' + time_str else: # Change button color and text self.main_window.logfile_status_button.setStyleSheet( "background-color: green") self.main_window.logfile_status_button.setText( 'Start logging to file') self.main_window.logfile_status_indicator.setChecked(False) # Actually stop logging self.log_service.stop_latest_logfile() # Set date string to None so that logfile does not get updated anymore self.logfile_date_str = None