class MFWorker(QObject): def __init__(self, func, args=None): super().__init__(None) self.func = func self.args = args self.thread = QThread() self.moveToThread(self.thread) self.thread.started.connect(self.run) pass def isRunning(self): return self.thread.isRunning() def start(self): self.thread.start() pass def terminate(self): self.thread.exit(0) pass def run(self): if self.args: self.func(*self.args) else: self.func() self.thread.exit(0) pass pass
class AdbDeviceDialog(Ui_Dialog, QtWidgets.QDialog): def __init__(self, deviceInfo=None, deviceCode=None, parent=None): super(AdbDeviceDialog, self).__init__(parent) self.setupUi(self) self.setWindowTitle(deviceInfo) self.setObjectName(deviceCode) self.image = QImage() self.imageShow.resize(450, 800) self.obj = WorkerScreenCap(deviceCode) self.thread = QThread(self) self.obj.image.connect(self.pathImageScreenCap) self.obj.moveToThread(self.thread) self.thread.started.connect(partial(self.obj.runScreenCap)) self.thread.start() def clear(self): print("CLEAR - THREAD") self.obj.readData = False self.thread.exit() def checkCode(self, code): return code == self.objectName() @pyqtSlot(QImage) def pathImageScreenCap(self, img): # pixmap = QPixmap(path) # self.image = QPixmap.toImage(img) if img: # image = img.scaled(450, 800, Qt.KeepAspectRatio) self.imageShow.setPixmap(QPixmap.fromImage(img)) else: print("Empty Image") if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) dialog = AdbDeviceDialog() dialog.show() app.exec_()
class MainWindow(QMainWindow, Ui_MainWindow): STAGE_TIMEOUT = 300 def __init__(self, config: Config, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.setupUi(self) self._config = config self.tvAgents.setItemDelegateForColumn(0, CheckBoxDelegate(None)) self._selected_agent = None self.registry = Registry() self.registry_model = RegistryModel(self, self.registry) self.tvAgents.setModel(self.registry_model) # file menu self._current_session_file_path = None self.signals = MainUISignals() # populate the TimeOfDay picker for time_of_day in TimeOfDay._meta.enum.__members__.values(): self.cbTimeOfDay.addItem(time_of_day.lower_name) self.show() # https://stackoverflow.com/questions/35527439/pyqt4-wait-in-thread-for-user-input-from-gui/35534047#35534047 self._agent_checker_thread = QThread() self.agent_checker_worker = AgentCheckerWorker() self.agent_checker_worker.moveToThread(self._agent_checker_thread) self._agent_checker_thread.started.connect(self.agent_checker_worker.run) listener = Listener() atexit.register(listener.stop) listener.signals.zeroconf_agent_found.connect(self.registry.handle_zeroconf_agent_found) listener.signals.zeroconf_agent_found.connect(self.agent_checker_worker.registry.handle_zeroconf_agent_found) listener.signals.zeroconf_agent_removed.connect(self.registry.handle_zeroconf_agent_removed) listener.signals.zeroconf_agent_removed.connect(self.agent_checker_worker.registry.handle_zeroconf_agent_removed) listener.run() # our registry self.signals.agent_manually_added.connect(self.registry.handle_agent_manually_added) self.signals.agent_manually_removed.connect(self.registry.handle_agent_manually_removed) # thread's registry self.signals.agent_manually_added.connect(self.agent_checker_worker.registry.handle_agent_manually_added) self.signals.agent_manually_removed.connect(self.agent_checker_worker.registry.handle_agent_manually_removed) self.agent_checker_worker.signals.agents_changed.connect(self.update_agent_view) self.agent_checker_worker.signals.agent_info_updated.connect(self.registry.handle_agent_info_updated) self._primary_candidates = [] self.agent_checker_worker.signals.primary_candidate_add.connect(self.handle_primary_candidate_add) self.agent_checker_worker.signals.primary_candidate_remove.connect(self.handle_primary_candidate_remove) self.agent_checker_worker.signals.agent_status_changed.connect(self.handle_agent_state_changed) # connect UI signals and worker signals before starting agent checker thread self._agent_checker_thread.start() self._state = DirectorState.IDLE self.registry.signals.registry_updated.connect(self.update_agent_view) self.registry.signals.taint_agent_status.connect(self.agent_checker_worker.handle_taint_agent_status) self.signals.agent_custom_settings_updated.connect(self.registry.handle_agent_custom_settings_updated) self._ai_scenarios = [] self._cancel_requested = None self._stage_watchdog_timer = None # hostnames / strings only self._selected_agent_hostnames = None self._selected_primary = None self._selected_secondary_hostnames = None self._wait_list = [] self._stage_started_datetime = None self._stage_count = None self._stages_passed = None # statusbar self._status_label = QLabel() self._status_progress_bar = QProgressBar() self._status_timer_label = QLabel() self.statusbar.addPermanentWidget(self._status_label) self.statusbar.addPermanentWidget(self._status_progress_bar) self.statusbar.addPermanentWidget(self._status_timer_label) self._selected_aircraft_directory = None self._last_session_path = Path(config.director_dir, 'last_session.yml') if self._last_session_path.exists(): self.load_scenario(self._last_session_path) else: self._set_defaults() aircraft_data.do_web_panel_report(config.aircraft_db) self._retrieve_web_panels() self.controls_enabled = True self.parking_cache_loaded = True self.parking_cache_threadpool = QThreadPool() def _retrieve_web_panels(self): current_aircraft = self.pbAircraft.text() self.web_panels = aircraft_data.get_web_panels(self._config.aircraft_db, current_aircraft) self.pbWebPanels.setText(f'{len(self.web_panels)} Web Panels') def load_scenario(self, path: Path): memo = yaml.load(path.read_text(), Loader=yaml.UnsafeLoader) logging.info(f"Loading {path}") scenario_settings = ScenarioSettings.from_dict(memo['scenario']) self.cbAircraftVariant.clear() self._map_scenario_settings_to_form(scenario_settings) self._retrieve_aircraft_variants() self.registry.load_dict(memo['agents']) self.agent_checker_worker.load_registry_from_save(memo['agents']) self.update_agent_view() self._retrieve_web_panels() def _retrieve_aircraft_variants(self): aircraft_name = self.pbAircraft.text() variants = aircraft_data.get_variants(self._config.aircraft_db, aircraft_name) current = self.cbAircraftVariant.currentText() if current in variants: variants.remove(current) self.cbAircraftVariant.addItems(variants) @pyqtSlot() def on_pbAircraft_clicked(self): selected_aircraft, selected_aircraft_directory, okPressed = SelectAircraftDialog.getValues(self._config.aircraft_db) if okPressed: self.pbAircraft.setText(selected_aircraft) self._selected_aircraft_directory = selected_aircraft_directory self.cbAircraftVariant.clear() self._retrieve_aircraft_variants() self._retrieve_web_panels() def save_scenario(self, path: Path): primary_hostname, selected_agent_hostnames, selected_secondary_hostnames = self._figure_out_primary_and_secondaries() scenario = self._map_form_to_scenario_settings() scenario.primary = primary_hostname scenario.secondaries = selected_secondary_hostnames memo = {'scenario': scenario.to_dict(), 'agents': self.registry.to_dict()} memo_yaml = yaml.dump(memo) logging.info(f"Writing {path}") logging.debug(f"save_scenario: data to save:\n\n {memo}\n\n") logging.debug(f"save_scenario: yaml serialisation: {memo_yaml}") path.write_text(memo_yaml) def _set_defaults(self): # Basics tab default_aircraft = 'c172p' self.pbAircraft.setText(default_aircraft) self._selected_aircraft_directory = 'c172p' self.cbAircraftVariant.clear() self._retrieve_aircraft_variants() self.cbTimeOfDay.setCurrentIndex(0) if self.cbPrimaryAgent.count() > 0: self.cbPrimaryAgent.setCurrentIndex(0) else: self.cbPrimaryAgent.setCurrentIndex(-1) self.cbAutoCoordination.setChecked(True) self.rbDefaultAirport.setChecked(True) self.leAirport.setText('YMML') self.leCarrier.clear() self.rbDefaultRunway.setChecked(True) self.leRunway.setText('09') self.leParking.clear() # Advanced tab self.leTSEndpoint.setText('http://flightgear.sourceforge.net/scenery') self.leCeiling.clear() self.cbAutoCoordination.setChecked(True) self.leVisibilityMeters.clear() # Clear AI scenarios self._ai_scenarios = [] # Reset open tab self.tabScenarioSettings.setCurrentIndex(0) # Reset agent custom settings self.registry.reset_all_custom_agent_settings_to_default() self._state = DirectorState.IDLE @pyqtSlot() def on_pbSelectAirport_clicked(self): selected_airport, selected_runway, okPressed = SelectAirportDialog.getValues(self._config.nav_db) if okPressed: if selected_airport: self.leAirport.setText(selected_airport) worker = ParkingCacheUpdaterWorker(selected_airport, self._config.parking_cache_dir) worker.signals.parking_cache_ready.connect(self.process_parking_cache) self.parking_cache_threadpool.start(worker) if selected_runway: self.leRunway.setText(selected_runway) self.rbAirport.setChecked(True) self.rbRunway.setChecked(True) self.leParking.setText("") def process_parking_cache(self, airport_code: str, records: typing.List[parking_record.ParkingRecord]): logging.info(f"Processing {len(records)} parking records for {airport_code}") if len(records) > 0: parking_data.save_parking_records( self._config.nav_db, airport_code, records ) current_airport_code = self.leAirport.text() if current_airport_code == airport_code: self.parking_cache_loaded = True if self.controls_enabled: self.pbSelectParking.setEnabled(True) @pyqtSlot() def on_pbSelectParking_clicked(self): current_airport_code = self.leAirport.text() records = parking_data.get_parking_records(self._config.nav_db, current_airport_code) logging.info(f"{len(records)} parking records for {current_airport_code}") selected_parking, okPressed = SelectParkingLocationDialog.getValues(records) if okPressed and selected_parking: self.leParking.setText(selected_parking) self.rbParking.setChecked(True) @pyqtSlot() def on_actionNew_Scenario_triggered(self): # reset the state self._set_defaults() self.setWindowTitle("FlightGear Orchestrator") self._current_session_file_path = None self.actionSave_As.setEnabled(False) self.pbLaunch.setEnabled(True) @pyqtSlot() def on_actionSave_As_triggered(self): file_name, _filter_type = QFileDialog.getSaveFileName( self, 'Save Scenario', str(self._config.director_dir), "Scenario Files (*.yml);;All Files (*.*)" ) logging.debug(f"on_actionSave_As_triggered QFileDialog result {file_name}") if file_name != "": file_path = Path(file_name) if not file_path.suffix == '.yml': file_path = Path(f'{file_name}.yml') self._current_session_file_path = file_path self.save_scenario(file_path) self.setWindowTitle(f"FlightGear Orchestrator {file_path.name}") self.actionSave_As.setEnabled(True) @pyqtSlot() def on_actionSave_Scenario_triggered(self): if self._current_session_file_path is None: return self.on_actionSave_As_triggered() self.save_scenario(self._current_session_file_path) self.statusbar.showMessage("File saved") @pyqtSlot() def on_actionLoad_Secnario_triggered(self): file_name, _filter_type = QFileDialog.getOpenFileName( self, 'Load Scenario', str(self._config.director_dir), "Scenario Files (*.yml);;All Files (*.*)" ) logging.debug(f"on_actionLoad_Secnario_triggered QFileDialog result {file_name}") if file_name != "": file_path = Path(file_name) self._current_session_file_path = file_path self.load_scenario(file_path) self.setWindowTitle(f"FlightGear Orchestrator {file_path.name}") self.actionSave_As.setEnabled(True) @pyqtSlot() def on_pbWebPanels_clicked(self): current_primary = self._selected_primary panel_list = '\n'.join([f"<li>{panel.generate_link_tag(current_primary)}</li>" for panel in self.web_panels]) content = f''' <p>Web panels:</p> <ul> {panel_list} </ul> ''' print(content) QMessageBox.information( self, "Web Panels", content, buttons=QMessageBox.Ok ) @pyqtSlot() def on_actionExit_triggered(self): self._agent_checker_thread.exit() QApplication.exit(0) @pyqtSlot() def update_agent_view(self): logging.debug("update_agent_view called") self.registry_model.updateModel() self.tvAgents.resizeColumnsToContents() @pyqtSlot(str) def handle_primary_candidate_add(self, host): if host not in self._primary_candidates: logging.info(f"Adding primary candidate {host}") self.cbPrimaryAgent.addItem(host) self._primary_candidates.append(host) @pyqtSlot(str) def handle_primary_candidate_remove(self, host): if host in self._primary_candidates: logging.info(f"Removing primary candidate {host}") index = self._primary_candidates.index(host) self.cbPrimaryAgent.removeItem(index) self._primary_candidates.remove(host) @pyqtSlot(QModelIndex) def on_tvAgents_clicked(self, index): logging.debug(f"agent selected {index}") row = index.row() host_index = self.registry_model.createIndex(row, 1) uuid_index = self.registry_model.createIndex(row, 4) self._selected_agent = { 'host': self.registry_model.data(host_index, Qt.DisplayRole), 'uuid': self.registry_model.data(uuid_index, Qt.DisplayRole) } logging.debug(f"self._selected_agent={self._selected_agent}") @pyqtSlot(QModelIndex) def on_tvAgents_doubleClicked(self, index): self.on_tvAgents_clicked(index) self._show_custom_agent_settings(self._selected_agent['host']) return @pyqtSlot(QPoint) def on_tvAgents_customContextMenuRequested(self, position: QPoint): menu = QMenu() customise_host_action = menu.addAction("Custom Host Settings") manage_directories_action = menu.addAction("Manage Directories") menu.addSeparator() open_webserver_action = menu.addAction("Open Web server") open_webserver_action.setEnabled(False) open_telnet_action = menu.addAction("Open Telnet") open_telnet_action.setEnabled(False) menu.addSeparator() rescan_environment_action = menu.addAction("Rescan environment") rescan_environment_action.setEnabled(False) reset_fail_count_action = menu.addAction("Reset fail count") reset_fail_count_action.setEnabled(False) show_errors_action = menu.addAction("Show errors") show_errors_action.setEnabled(False) menu.addSeparator() stop_flightgear_action = menu.addAction("Stop Flightgear") stop_flightgear_action.setEnabled(False) menu.addSeparator() remove_host_action = menu.addAction("Remove Host") if not self._selected_agent: customise_host_action.setEnabled(False) manage_directories_action.setEnabled(False) remove_host_action.setEnabled(False) open_webserver_action.setEnabled(False) open_telnet_action.setEnabled(False) else: hostname = self._selected_agent['host'] if self.registry.is_agent_failed(hostname): reset_fail_count_action.setEnabled(True) if self.registry.is_agent_online(hostname): rescan_environment_action.setEnabled(True) manage_directories_action.setEnabled(True) if self.registry.agent_has_errors(hostname): show_errors_action.setEnabled(True) if self.registry.is_agent_running_fgfs(hostname): stop_flightgear_action.setEnabled(True) if self.registry.is_web_server_available(hostname) or \ (self._state in [DirectorState.WAITING_FOR_SECONDARIES, DirectorState.IN_SESSION] and hostname == self._selected_primary): open_webserver_action.setEnabled(True) if self.registry.is_telnet_available(hostname): open_telnet_action.setEnabled(True) res = menu.exec_(self.tvAgents.mapToGlobal(position)) if res == remove_host_action: self.signals.agent_manually_removed.emit( self._selected_agent.get('host', None), self._selected_agent.get('uuid', None) ) self._selected_agent = None self.registry_model.updateModel() if res == reset_fail_count_action: self.registry.reset_failed_count(hostname) self.agent_checker_worker.registry.reset_failed_count(hostname) if res == stop_flightgear_action: self.registry.stop_fgfs(hostname) self.registry_model.updateModel() if res == rescan_environment_action: self.registry.rescan_environment(hostname) if res == customise_host_action: self._show_custom_agent_settings(hostname) return if res == show_errors_action: ShowErrorsDialog(hostname, self.registry.get_errors_for_agent(hostname)).exec_() if res == manage_directories_action: original_directories = self.registry.get_directories_for_agent(hostname) if original_directories is None: return updated_directories, ok_pressed = ConfigureAgentPathsDialog.getValues(original_directories, self.registry, hostname) if ok_pressed and updated_directories != original_directories: logging.info(f'Main.handle_agents_context_menu_requested applying updated directories for {hostname}: {updated_directories}') ok, error_str = self.registry.apply_directory_changes_to_agent(hostname, updated_directories) self.registry.rescan_environment(hostname) if ok: QMessageBox.information( self, "Settings applied", "Agent updated successfully", buttons=QMessageBox.Ok ) else: ShowErrorsDialog(hostname, error_str).exec_() if res == open_telnet_action: webbrowser.open(f"telnet://{hostname}:8081") if res == open_webserver_action: webbrowser.open(f"http://{hostname}:8080") self.update_agent_view() def _show_custom_agent_settings(self, hostname: str): custom_settings = self.registry.get_custom_settings_for_agent(hostname) if custom_settings: updated_custom_settings, ok_pressed = CustomSettingsDialog.getValues(custom_settings) if ok_pressed: self.signals.agent_custom_settings_updated.emit( hostname, updated_custom_settings.to_update_dict() ) self.update_agent_view() @pyqtSlot() def on_actionAddHost_triggered(self): text, okPressed = QInputDialog.getText( self, 'Add host', 'Enter IP Address or hostname:', QLineEdit.Normal, '' ) if okPressed and len(text.strip()) > 0: self.signals.agent_manually_added.emit(text.strip()) self.registry_model.updateModel() @pyqtSlot(int) def on_cbPrimaryAgent_currentIndexChanged(self, index: int): if index == -1: self.pbLaunch.setEnabled(False) self.pbManageAIScenarios.setEnabled(False) self.pbLaunch.setEnabled(False) else: self.pbLaunch.setEnabled(True) self.pbManageAIScenarios.setEnabled(True) @pyqtSlot() def on_pbManageAIScenarios_clicked(self): current_primary = self._selected_primary current_ai_scenarios = self._ai_scenarios or [] all_ai_scenarios = self.registry.get_ai_scenarios_from_host(current_primary) selected_scenarios, okPressed = AiScenariosDialog.getValues(all_ai_scenarios, current_ai_scenarios) if okPressed: self._ai_scenarios = selected_scenarios @pyqtSlot() def on_pbLaunch_clicked(self): logging.info("Preparing to launch a session") primary_hostname, selected_agent_hostnames, selected_secondary_hostnames = self._figure_out_primary_and_secondaries() self._selected_agent_hostnames = copy.copy(selected_agent_hostnames) self._wait_list = copy.copy(selected_agent_hostnames) self._selected_primary = primary_hostname self._selected_secondary_hostnames = selected_secondary_hostnames # do pre-checks here! failed = False msg = '' if primary_hostname == '': failed = True msg = "Please select a primary" else: # status check selected_agents = [agent for agent in self.registry.all_agents if agent.host in selected_agent_hostnames] failed = any([agent.status != 'READY' for agent in selected_agents]) if failed: msg = "One or more agents are not ready and we cannot launch a session" if failed: msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Critical) msg_box.setText(msg) msg_box.setWindowTitle("Agents not ready") msg_box.setStandardButtons(QMessageBox.Ok) msg_box.setDefaultButton(QMessageBox.Ok) msg_box.setEscapeButton(QMessageBox.Ok) msg_box.exec_() return # version mismatch check versions = set([agent.version for agent in selected_agents]) if len(versions) > 1: # yes/no continue dialog box msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Question) msg_box.setText("A mix of different versions of FlightGear was detected. Continuing may lead to unexpected behaviour.\n\nAre you sure you wish to continue?") msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) msg_box.setDefaultButton(QMessageBox.Yes) msg_box.setEscapeButton(QMessageBox.No) msg_box.setWindowTitle("Conflicting FlightGear versions") res = msg_box.exec_() if res == QMessageBox.No: logging.info('User abort at version notification') return logging.info('Preparing UI for launch') self._state = DirectorState.START_SEQUENCE_REQUESTED self._cancel_requested = False self.pbStop.setEnabled(True) self._lock_scenario_controls() logging.info('Constructing a scenario settings object') scenario_settings = self._map_form_to_scenario_settings() scenario_settings.primary = primary_hostname scenario_settings.secondaries = selected_secondary_hostnames self.registry.scenario_settings = scenario_settings self.save_scenario(self._last_session_path) self._stage_started_datetime = datetime.now() self._status_timer_label.setText(f"{self.STAGE_TIMEOUT}") self._start_stage_timeout_watchdog() self._stages_passed = 0 self._status_progress_bar.setValue(0) self._cancel_requested = False if scenario_settings.skip_aircraft_install or scenario_settings.aircraft in [None, "c172p"]: self._stage_count = 1 + len(self._selected_secondary_hostnames) self._wait_list = [primary_hostname] self._state = DirectorState.WAITING_FOR_PRIMARY self.registry.start_primary() else: self._stage_count = 2 + 2 * len(self._selected_secondary_hostnames) self._wait_list = copy.deepcopy(selected_agent_hostnames) self._state = DirectorState.INSTALLING_AIRCRAFT self.registry.install_aircraft() self._status_label.setText(self._state.name) def _figure_out_primary_and_secondaries(self): primary_hostname = self.cbPrimaryAgent.currentText() logging.info(f"Primary is: {primary_hostname}") selected_agent_hostnames = [agent.host for agent in self.registry.all_agents if agent.selected] # add primary to selected_agents if it's not in the collection if primary_hostname not in selected_agent_hostnames: selected_agent_hostnames.append(primary_hostname) logging.info(f"Selected agent hostnames are: {selected_agent_hostnames}") selected_secondary_hostnames = [hostname for hostname in selected_agent_hostnames if hostname != primary_hostname] logging.debug(f"Selected secondaries are:{selected_secondary_hostnames}") return primary_hostname, selected_agent_hostnames, selected_secondary_hostnames def _start_stage_timeout_watchdog(self): if self._stage_watchdog_timer is not None: self._stage_watchdog_timer.stop() self._stage_watchdog_timer = QTimer() self._stage_watchdog_timer.timeout.connect(self._check_stage_timeout) self._stage_watchdog_timer.start(1000) def _check_stage_timeout(self): seconds_run = (datetime.now() - self._stage_started_datetime).seconds seconds_remaining = self.STAGE_TIMEOUT - seconds_run if seconds_remaining <= 0: seconds_remaining = 0 self.on_pbStop_clicked() QMessageBox.critical( self, "Timeout", "One or more agents timed out while launching a session", buttons=QMessageBox.Close ) self._status_timer_label.setText(f"{seconds_remaining}") @pyqtSlot() def on_pbStop_clicked(self): self._cancel_requested = True self.pbStop.setEnabled(False) if self._stage_watchdog_timer is not None: self._stage_watchdog_timer.stop() current_state = self._state if current_state in [ DirectorState.IN_SESSION, DirectorState.WAITING_FOR_SECONDARIES, DirectorState.WAITING_FOR_PRIMARY ]: self.registry.stop_fgfs() self._state = DirectorState.IDLE self._status_label.setText(self._state.name) self.pbWebPanels.setEnabled(False) self._unlock_scenario_controls() def handle_agent_state_changed(self, hostname, agent_previous_state, agent_next_state): logging.info(f"handle_agent_state_changed {hostname}, next: {agent_next_state}, prev: {agent_previous_state}") self.registry.set_agent_state(hostname, agent_next_state) if self._cancel_requested: return current_state = self._state def state_transition(prev, next_): logging.debug(f"handle_agent_state_changed.state_transition prev: {prev}, next: {next_}") logging.debug(f"first test: {(agent_previous_state in prev or agent_previous_state == 'PENDING')}") logging.debug(f"second test {next_ == agent_next_state}") return (agent_previous_state in prev or agent_previous_state == 'PENDING') and next_ == agent_next_state def advance_stage(hostname_): logging.debug(f"handle_agent_state_changed.advance_stage hostname: {hostname_}") self._stages_passed += 1 self._wait_list.remove(hostname_) self._status_progress_bar.setValue(int((self._stages_passed / self._stage_count) * 100)) next_state = copy.copy(self._state) if len(self._wait_list) == 0: logging.debug(f"handle_agent_state_changed.advance_stage wait list drained") self._stage_started_datetime = datetime.now() self._status_timer_label.setText(f"{self.STAGE_TIMEOUT}") if current_state == DirectorState.INSTALLING_AIRCRAFT: self._wait_list = [self._selected_primary] self.registry.start_primary() next_state = DirectorState.WAITING_FOR_PRIMARY if current_state == DirectorState.WAITING_FOR_PRIMARY: self.labelPhiLink.setText( '<a href="http://%s:8080/">Open Phi Web Interface on %s</a>' % (hostname_, hostname_)) if len(self.web_panels) > 0: self.pbWebPanels.setEnabled(True) if len(self._selected_secondary_hostnames) > 0: self._wait_list = copy.deepcopy(self._selected_secondary_hostnames) self.registry.start_secondaries() next_state = DirectorState.WAITING_FOR_SECONDARIES else: next_state = DirectorState.IN_SESSION if self._stage_watchdog_timer is not None: self._stage_watchdog_timer.stop() self._status_progress_bar.setValue(100) if current_state == DirectorState.WAITING_FOR_SECONDARIES: self._stage_watchdog_timer.stop() next_state = DirectorState.IN_SESSION self._status_progress_bar.setValue(100) self._status_label.setText(next_state.name) self._state = next_state if hostname in self._wait_list: if agent_next_state == 'ERROR': QMessageBox.critical( self, "Agent state transition error", f"The agent {hostname} entered error state while {current_state.name.lower()}.\n\nPlease check its errors, rescan the environment, modify your settings and try again", QMessageBox.Close ) self._stage_watchdog_timer.stop() agent_next_state = DirectorState.IDLE self._status_progress_bar.setValue(0) self.on_pbStop_clicked() if current_state == DirectorState.INSTALLING_AIRCRAFT and state_transition(['INSTALLING_AIRCRAFT', 'PENDING'], 'READY'): advance_stage(hostname) if current_state == DirectorState.WAITING_FOR_PRIMARY and state_transition(['FGFS_START_REQUESTED', 'FGFS_STARTING', 'PENDING'], 'FGFS_RUNNING'): advance_stage(hostname) if current_state == DirectorState.WAITING_FOR_SECONDARIES and state_transition(['FGFS_START_REQUESTED', 'FGFS_STARTING', 'PENDING'], 'FGFS_RUNNING'): advance_stage(hostname) def _lock_scenario_controls(self): self._set_scenario_controls_enabled_state(False) def _unlock_scenario_controls(self): self._set_scenario_controls_enabled_state(True) def _set_scenario_controls_enabled_state(self, enabled: bool): self.labelPhiLink.clear() self.pbLaunch.setEnabled(enabled) self.pbAircraft.setEnabled(enabled) self.cbAircraftVariant.setEnabled(enabled) self.cbTimeOfDay.setEnabled(enabled) self.cbPrimaryAgent.setEnabled(enabled) self.rbDefaultAirport.setEnabled(enabled) self.rbAirport.setEnabled(enabled) self.pbSelectAirport.setEnabled(enabled) self.leAirport.setEnabled(enabled) self.rbCarrier.setEnabled(enabled) self.leCarrier.setEnabled(enabled) self.rbDefaultRunway.setEnabled(enabled) self.rbRunway.setEnabled(enabled) self.leRunway.setEnabled(enabled) self.rbParking.setEnabled(enabled) self.leParking.setEnabled(enabled) self.leTSEndpoint.setEnabled(enabled) self.leCeiling.setEnabled(enabled) self.cbAutoCoordination.setEnabled(enabled) self.leVisibilityMeters.setEnabled(enabled) self.pbManageAIScenarios.setEnabled(enabled) if not enabled: self.pbSelectParking.setEnabled(enabled) elif self.parking_cache_loaded: self.pbSelectParking.setEnabled(enabled) self.controls_enabled = enabled def _map_form_to_scenario_settings(self): ''' reads form values and returns a Registry.ScenarioSettings object ''' s = ScenarioSettings() s.time_of_day = self.cbTimeOfDay.currentText() # s.aircraft = self.pbAircraft.text() self._apply_text_input_if_set(self.pbAircraft, s, 'aircraft') s.aircraft_directory = self._selected_aircraft_directory s.aircraft_variant = self.cbAircraftVariant.currentText() # self._apply_text_input_if_set(self.cbAircraftVariant, s, 'aircraft_variant') self._apply_text_input_if_set(self.leAirport, s, 'airport') self._apply_text_input_if_set(self.leCarrier, s, 'carrier') self._apply_text_input_if_set(self.leRunway, s, 'runway') self._apply_text_input_if_set(self.leParking, s, 'parking') self._apply_text_input_if_set(self.leTSEndpoint, s, 'terra_sync_endpoint') self._apply_text_input_if_set(self.leCeiling, s, 'ceiling') s.enable_auto_coordination = self.cbAutoCoordination.isChecked() s.skip_aircraft_install = self.cbSkipAircraftInstall.isChecked() self._apply_integer_input_if_set(self.leVisibilityMeters, s, 'visibility_in_meters') s.ai_scenarios = self._ai_scenarios selected_airport_option = 0 if self.rbAirport.isChecked(): selected_airport_option = 1 elif self.rbCarrier.isChecked(): selected_airport_option = 2 s.selected_airport_option = selected_airport_option selected_runway_option = 0 if self.rbRunway.isChecked(): selected_runway_option = 1 elif self.rbParking.isChecked(): selected_runway_option = 2 s.selected_runway_option = selected_runway_option logging.info(f"Constructed scenario settings {s}") return s def _map_scenario_settings_to_form(self, scenario_settings: ScenarioSettings): ''' Loads given ScenarioSettings into the presently displayed form ''' idx = self.cbTimeOfDay.findText(scenario_settings.time_of_day, Qt.MatchFixedString) self.cbTimeOfDay.setCurrentIndex(idx) self._selected_primary = scenario_settings.primary self._set_text_field_safe(self.pbAircraft, scenario_settings.aircraft) if scenario_settings.aircraft_directory is None: logging.warn("Scenario Settings missing aircraft_directory key!") self._selected_aircraft_directory = scenario_settings.aircraft else: self._selected_aircraft_directory = scenario_settings.aircraft_directory # self._set_text_field_safe(self.cbAircraftVariant, scenario_settings.aircraft_variant) if scenario_settings.aircraft_variant is not None: self.cbAircraftVariant.addItem(scenario_settings.aircraft_variant) self.cbAircraftVariant.setCurrentText(scenario_settings.aircraft_variant) self._set_text_field_safe(self.leAirport, scenario_settings.airport) self._set_text_field_safe(self.leCarrier, scenario_settings.carrier) self._set_text_field_safe(self.leRunway, scenario_settings.runway) self._set_text_field_safe(self.leParking, scenario_settings.parking) self._set_text_field_safe(self.leTSEndpoint, scenario_settings.terra_sync_endpoint) self._set_text_field_safe(self.leCeiling, scenario_settings.ceiling) self._set_boolean_safe(self.cbAutoCoordination, scenario_settings.enable_auto_coordination) self._set_boolean_safe(self.cbSkipAircraftInstall, scenario_settings.skip_aircraft_install) if scenario_settings.visibility_in_meters is not None: self._set_text_field_safe(self.leVisibilityMeters, f"{scenario_settings.visibility_in_meters}") self._ai_scenarios = scenario_settings.ai_scenarios selected_airport_option = scenario_settings.selected_airport_option if selected_airport_option == 2: self.rbCarrier.setChecked(True) elif selected_airport_option == 1: self.rbAirport.setChecked(True) else: self.rbDefaultAirport.setChecked(True) selected_runway_option = scenario_settings.selected_runway_option if selected_runway_option == 2: self.rbParking.setChecked(True) elif selected_runway_option == 1: self.rbRunway.setChecked(True) else: self.rbDefaultRunway.setChecked(True) logging.info(f"Loaded scenario settings {scenario_settings}") @staticmethod def _set_boolean_safe(widget: QCheckBox, val): if val is not None: widget.setChecked(val) @staticmethod def _set_text_field_safe(widget: QLineEdit, val, fallback=""): if val is None: val = fallback widget.setText(val) @staticmethod def _apply_text_input_if_set(widget: QLineEdit, s: ScenarioSettings, prop: str): val = widget.text().strip() if val != "": setattr(s, prop, val) @staticmethod def _apply_integer_input_if_set(widget: QLineEdit, s: ScenarioSettings, prop: str): val = widget.text().strip() logging.debug(f"_apply_integer_input_if_set widget: {widget}, \ns: {s}, \nprop: {prop}\nval: {val}") if val is not None and val != "": try: setattr(s, prop, int(val)) except ValueError as _: pass
class DAQ_Move(Ui_Form, QObject): """ | DAQ_Move object is a module used to control one motor from a specified list. | | Preset is an optional list of dicts used to managers programatically settings such as the name of the controller from the list of possible controllers, COM address... | | Init is a boolean to tell the programm to initialize the controller at the start of the programm given the managers options ========================= ================================================= **Attributes** **Type** *command_stage* instance of pyqtSignal *move_done_signal* instance of pyqtSignal *update_settings_signal* instance of pyqtSignal *status_signal* instance of pyqtSignal *bounds_signal* instance of pyqtSignal *params* dictionnary list *ui* instance of UI_Form *parent* QObject *title* string *wait_time* int *initialized_state* boolean *Move_done* boolean *controller* instance of the specific controller object *stage* instance of the stage (axis or wathever) object *current_position* float *target_position* float *wait_position_flag* boolean *stage_types* string list ========================= ================================================= See Also -------- set_enabled_move_buttons, set_setting_tree, stage_changed, quit_fun, ini_stage_fun, move_Abs, move_Rel, move_Home, get_position, stop_Motion, show_settings, show_fine_tuning References ---------- QLocale, QObject, pyqtSignal, QStatusBar, ParameterTree """ init_signal = pyqtSignal(bool) command_stage = pyqtSignal(ThreadCommand) command_tcpip = pyqtSignal(ThreadCommand) move_done_signal = pyqtSignal( str, float ) # to be used in external program to make sure the move has been done, export the current position. str refer to the unique title given to the module update_settings_signal = pyqtSignal(edict) status_signal = pyqtSignal(str) bounds_signal = pyqtSignal(bool) params = daq_move_params def __init__(self, parent, title="pymodaq Move", preset=None, init=False, controller_ID=-1): """DAQ_Move object is a module used to control one motor from a specified list. managers is an optional list of dicts used to managers programatically settings such as the name of the controller from the list of possible controllers, COM address... init is a boolean to tell the programm to initialize the controller at the start of the programm given the managers options controller_ID is a unique random integer generated by the parent caller. To differenciate various instance of this class """ self.logger = utils.set_logger(f'{logger.name}.{title}') self.logger.info(f'Initializing DAQ_Move: {title}') QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates)) super().__init__() here = Path(__file__).parent splash = QtGui.QPixmap(str(here.parent.joinpath('splash.png'))) self.splash_sc = QtWidgets.QSplashScreen(splash, Qt.WindowStaysOnTopHint) self.ui = Ui_Form() self.ui.setupUi(parent) self.ui.Moveto_pb_bis_2.setVisible(False) self.parent = parent self.ui.title_label.setText(title) self.title = title self.ui.statusbar = QtWidgets.QStatusBar(parent) self.ui.StatusBarLayout.addWidget(self.ui.statusbar) self.ui.statusbar.setMaximumHeight(20) self.send_to_tcpip = False self.tcpclient_thread = None self.wait_time = 1000 self.ui.Ini_state_LED self.ui.Ini_state_LED.clickable = False self.ui.Ini_state_LED.set_as_false() self.ui.Move_Done_LED.clickable = False self.ui.Move_Done_LED.set_as_false() self.initialized_state = False self.ui.Current_position_sb.setReadOnly(False) self.move_done_bool = True # ###########IMPORTANT############################ self.controller = None # the hardware controller/set after initialization and to be used by other modules # ################################################ self.current_position = 0 self.target_position = 0 self.wait_position_flag = True self.ui.Current_position_sb.setValue(self.current_position) self.set_enabled_move_buttons(enable=False) self.ui.groupBox.hide() self.parent.resize(150, 200) # #Setting stages types self.stage_types = [mov['name'] for mov in DAQ_Move_Stage_type] self.ui.Stage_type_combo.clear() self.ui.Stage_type_combo.addItems(self.stage_types) # create main parameter tree self.ui.settings_tree = ParameterTree() self.ui.verticalLayout_2.addWidget(self.ui.settings_tree) self.ui.settings_tree.setMinimumWidth(300) self.settings = Parameter.create(name='Settings', type='group', children=self.params) self.ui.settings_tree.setParameters(self.settings, showTop=False) # connecting from tree self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed ) # any changes on the settings will update accordingly the detector self.ui.settings_tree.setVisible(False) self.set_setting_tree() self.settings.child('main_settings', 'controller_ID').setValue(controller_ID) QtWidgets.QApplication.processEvents() # #Connecting buttons: self.ui.Stage_type_combo.currentIndexChanged.connect( self.set_setting_tree) self.ui.Stage_type_combo.currentIndexChanged.connect( self.stage_changed) self.ui.Quit_pb.clicked.connect(self.quit_fun) self.ui.IniStage_pb.clicked.connect(self.ini_stage_fun) self.update_status("Ready", wait_time=self.wait_time) self.ui.Move_Abs_pb.clicked.connect( lambda: self.move_Abs(self.ui.Abs_position_sb.value())) self.ui.Move_Rel_plus_pb.clicked.connect( lambda: self.move_Rel(self.ui.Rel_position_sb.value())) self.ui.Move_Rel_minus_pb.clicked.connect( lambda: self.move_Rel(-self.ui.Rel_position_sb.value())) self.ui.Find_Home_pb.clicked.connect(self.move_Home) self.ui.Get_position_pb.clicked.connect(self.get_position) self.ui.Stop_pb.clicked.connect(self.stop_Motion) self.ui.parameters_pb.clicked.connect(self.show_settings) self.ui.fine_tuning_pb.clicked.connect(self.show_fine_tuning) self.ui.Abs_position_sb.valueChanged.connect( self.ui.Abs_position_sb_bis.setValue) self.ui.Abs_position_sb_bis.valueChanged.connect( self.ui.Abs_position_sb.setValue) self.ui.Moveto_pb_bis.clicked.connect( lambda: self.move_Abs(self.ui.Abs_position_sb_bis.value())) # set managers options if preset is not None: for preset_dict in preset: # fo instance preset_dict=dict(object='Stage_type_combo',method='setCurrentIndex',value=1) if hasattr(self.ui, preset_dict['object']): obj = getattr(self.ui, preset_dict['object']) if hasattr(obj, preset_dict['method']): setattr(obj, preset_dict['method'], preset_dict['value']) QtWidgets.QApplication.processEvents() # initialize the controller if init=True if init: self.ui.IniStage_pb.click() def ini_stage_fun(self): """ Init : * a DAQ_move_stage instance if not exists * a linked thread connected by signal to the DAQ_move_main instance See Also -------- set_enabled_move_buttons, DAQ_utils.ThreadCommand, DAQ_Move_stage, DAQ_Move_stage.queue_command, thread_status, DAQ_Move_stage.update_settings, update_status """ try: if not self.ui.IniStage_pb.isChecked(): try: self.set_enabled_move_buttons(enable=False) self.ui.Stage_type_combo.setEnabled(True) self.ui.Ini_state_LED.set_as_false() self.command_stage.emit(ThreadCommand(command="close")) except Exception as e: self.logger.exception(str(e)) else: self.stage_name = self.ui.Stage_type_combo.currentText() stage = DAQ_Move_stage(self.stage_name, self.current_position, self.title) self.stage_thread = QThread() stage.moveToThread(self.stage_thread) self.command_stage[ThreadCommand].connect(stage.queue_command) stage.status_sig[ThreadCommand].connect(self.thread_status) self.update_settings_signal[edict].connect( stage.update_settings) self.stage_thread.stage = stage self.stage_thread.start() self.ui.Stage_type_combo.setEnabled(False) self.command_stage.emit( ThreadCommand(command="ini_stage", attributes=[ self.settings.child( ('move_settings')).saveState(), self.controller ])) except Exception as e: self.logger.exception(str(e)) self.set_enabled_move_buttons(enable=False) def get_position(self): """ Get the current position from the launched thread via the "check_position" Thread Command. See Also -------- update_status, DAQ_utils.ThreadCommand """ try: self.command_stage.emit(ThreadCommand(command="check_position")) except Exception as e: self.logger.exception(str(e)) def move_Abs(self, position, send_to_tcpip=False): """ | Make the move from an absolute position. | | The move is made if target is in bounds, sending the thread command "Reset_Stop_Motion" and "move_Abs". =============== ========== =========================================== **Parameters** **Type** **Description** *position* float The absolute target position of the move =============== ========== =========================================== See Also -------- update_status, check_out_bounds, DAQ_utils.ThreadCommand """ try: self.send_to_tcpip = send_to_tcpip if not (position == self.current_position and self.stage_name == "Thorlabs_Flipper"): self.ui.Move_Done_LED.set_as_false() self.move_done_bool = False self.target_position = position self.update_status("Moving", wait_time=self.wait_time) # self.check_out_bounds(position) self.command_stage.emit( ThreadCommand(command="Reset_Stop_Motion")) self.command_stage.emit( ThreadCommand(command="move_Abs", attributes=[position])) except Exception as e: self.logger.exception(str(e)) def move_Home(self, send_to_tcpip=False): """ Send the thread commands "Reset_Stop_Motion" and "move_Home" and update the status. See Also -------- update_status, DAQ_utils.ThreadCommand """ self.send_to_tcpip = send_to_tcpip try: self.ui.Move_Done_LED.set_as_false() self.move_done_bool = False self.update_status("Moving", wait_time=self.wait_time) self.command_stage.emit(ThreadCommand(command="Reset_Stop_Motion")) self.command_stage.emit(ThreadCommand(command="move_Home")) except Exception as e: self.logger.exception(str(e)) def move_Rel_p(self): self.ui.Move_Rel_plus_pb.click() def move_Rel_m(self, send_to_tcpip=False): self.ui.Move_Rel_minus_pb.click() def move_Rel(self, rel_position, send_to_tcpip=False): """ | Make a move from the given relative psition and the current one. | | The move is done if (current position + relative position) is in bounds sending Threads Commands "Reset_Stop_Motion" and "move_done" =============== ========== =================================================== **Parameters** **Type** **Description** *position* float The relative target position from the current one =============== ========== =================================================== See Also -------- update_status, check_out_bounds, DAQ_utils.ThreadCommand """ try: self.send_to_tcpip = send_to_tcpip self.ui.Move_Done_LED.set_as_false() self.move_done_bool = False self.target_position = self.current_position + rel_position self.update_status("Moving", wait_time=self.wait_time) # self.check_out_bounds(self.target_position) self.command_stage.emit(ThreadCommand(command="Reset_Stop_Motion")) self.command_stage.emit( ThreadCommand(command="move_Rel", attributes=[rel_position])) except Exception as e: self.logger.exception(str(e)) def parameter_tree_changed(self, param, changes): """ | Check eventual changes in the changes list parameter. | | In case of changed values, emit the signal containing the current path and parameter via update_settings_signal to the connected hardware. =============== ==================================== ================================================== **Parameters** **Type** **Description** *param* instance of pyqtgraph parameter The parameter to be checked *changes* (parameter,change,infos) tuple list The (parameter,change,infos) list to be treated =============== ==================================== ================================================== """ for param, change, data in changes: path = self.settings.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() if change == 'childAdded': if 'main_settings' not in path: self.update_settings_signal.emit( edict(path=path, param=data[0].saveState(), change=change)) elif change == 'value': if param.name() == 'connect_server': if param.value(): self.connect_tcp_ip() else: self.command_tcpip.emit(ThreadCommand('quit')) elif param.name() == 'ip_address' or param.name == 'port': self.command_tcpip.emit( ThreadCommand( 'update_connection', dict(ipaddress=self.settings.child( 'main_settings', 'tcpip', 'ip_address').value(), port=self.settings.child( 'main_settings', 'tcpip', 'port').value()))) if path is not None: if 'main_settings' not in path: self.update_settings_signal.emit( edict(path=path, param=param, change=change)) if self.settings.child('main_settings', 'tcpip', 'tcp_connected').value(): self.command_tcpip.emit( ThreadCommand('send_info', dict(path=path, param=param))) elif change == 'parent': if param.name() not in putils.iter_children( self.settings.child('main_settings'), []): self.update_settings_signal.emit( edict(path=['move_settings'], param=param, change=change)) def connect_tcp_ip(self): if self.settings.child('main_settings', 'tcpip', 'connect_server').value(): self.tcpclient_thread = QThread() tcpclient = TCPClient(self.settings.child('main_settings', 'tcpip', 'ip_address').value(), self.settings.child('main_settings', 'tcpip', 'port').value(), self.settings.child(('move_settings')), client_type="ACTUATOR") tcpclient.moveToThread(self.tcpclient_thread) self.tcpclient_thread.tcpclient = tcpclient tcpclient.cmd_signal.connect(self.process_tcpip_cmds) self.command_tcpip[ThreadCommand].connect(tcpclient.queue_command) self.tcpclient_thread.start() tcpclient.init_connection() @pyqtSlot(ThreadCommand) def process_tcpip_cmds(self, status): if 'move_abs' in status.command: self.move_Abs(status.attributes[0], send_to_tcpip=True) elif 'move_rel' in status.command: self.move_Rel(status.attributes[0], send_to_tcpip=True) elif 'move_home' in status.command: self.move_Home(send_to_tcpip=True) elif 'check_position' in status.command: self.send_to_tcpip = True self.command_stage.emit(ThreadCommand('check_position')) elif status.command == 'connected': self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(True) elif status.command == 'disconnected': self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(False) elif status.command == 'Update_Status': self.thread_status(status) elif status.command == 'set_info': param_dict = ioxml.XML_string_to_parameter(status.attributes[1])[0] param_tmp = Parameter.create(**param_dict) param = self.settings.child('move_settings', *status.attributes[0][1:]) param.restoreState(param_tmp.saveState()) def quit_fun(self): """ Leave the current instance of DAQ_Move_Main closing the parent widget. """ # insert anything that needs to be closed before leaving try: if self.initialized_state: self.ui.IniStage_pb.click() self.parent.close() # close the parent widget try: self.parent.parent().parent().close( ) # the dock parent (if any) except Exception as e: self.logger.info('No dock parent to close') except Exception as e: icon = QtGui.QIcon() icon.addPixmap( QtGui.QPixmap(":/Labview_icons/Icon_Library/close2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) msgBox = QtWidgets.QMessageBox(parent=None) msgBox.addButton(QtWidgets.QMessageBox.Yes) msgBox.addButton(QtWidgets.QMessageBox.No) msgBox.setWindowTitle("Error") msgBox.setText( str(e) + " error happened when uninitializing the stage.\nDo you still want to quit?" ) msgBox.setDefaultButton(QtWidgets.QMessageBox.Yes) ret = msgBox.exec() if ret == QtWidgets.QMessageBox.Yes: self.parent.close() @pyqtSlot() def raise_timeout(self): """ Update status with "Timeout occured" statement. See Also -------- update_status """ self.update_status("Timeout occured", wait_time=self.wait_time) self.wait_position_flag = False def set_enabled_move_buttons(self, enable=False): """ Set the move buttons enabled (or not) in User Interface from the gridLayout_buttons course. =============== ========== ================================================ **Parameters** **Type** **Description** *enable* boolean The parameter making enable or not the buttons =============== ========== ================================================ """ Nchildren = self.ui.gridLayout_buttons.count() for ind in range(Nchildren): widget = self.ui.gridLayout_buttons.itemAt(ind).widget() if widget is not None: widget.setEnabled(enable) self.ui.Moveto_pb_bis.setEnabled(enable) self.ui.Abs_position_sb_bis.setEnabled(enable) self.ui.Current_position_sb.setEnabled(enable) @pyqtSlot(int) def set_setting_tree(self, index=0): """ Set the move settings parameters tree, clearing the current tree and setting the 'move_settings' node. See Also -------- update_status """ self.stage_name = self.ui.Stage_type_combo.currentText() self.settings.child('main_settings', 'move_type').setValue(self.stage_name) try: for child in self.settings.child(('move_settings')).children(): child.remove() parent_module = utils.find_dict_in_list_from_key_val( DAQ_Move_Stage_type, 'name', self.stage_name) class_ = getattr( getattr(parent_module['module'], 'daq_move_' + self.stage_name), 'DAQ_Move_' + self.stage_name) params = getattr(class_, 'params') move_params = Parameter.create(name='move_settings', type='group', children=params) self.settings.child( ('move_settings')).addChildren(move_params.children()) except Exception as e: self.logger.exception(str(e)) def show_fine_tuning(self): """ Make GroupBox visible if User Interface corresponding attribute is checked to show fine tuning in. """ if self.ui.fine_tuning_pb.isChecked(): self.ui.groupBox.show() else: self.ui.groupBox.hide() def show_settings(self): """ Make settings tree visible if User Interface corresponding attribute is checked to show the settings tree in. """ if self.ui.parameters_pb.isChecked(): self.ui.settings_tree.setVisible(True) else: self.ui.settings_tree.setVisible(False) @pyqtSlot(int) def stage_changed(self, index=0): """ See Also -------- move_Abs """ pass def stop_Motion(self): """ stop any motion via the launched thread with the "stop_Motion" Thread Command. See Also -------- update_status, DAQ_utils.ThreadCommand """ try: self.command_stage.emit(ThreadCommand(command="stop_Motion")) except Exception as e: self.logger.exception(str(e)) @pyqtSlot(ThreadCommand) def thread_status( self, status ): # general function to get datas/infos from all threads back to the main """ | General function to get datas/infos from all threads back to the main0 | Interpret a command from the command given by the ThreadCommand status : * In case of **'Update_status'** command, call the update_status method with status attributes as parameters * In case of **'ini_stage'** command, initialise a Stage from status attributes * In case of **'close'** command, close the launched stage thread * In case of **'check_position'** command, set the Current_position value from status attributes * In case of **'move_done'** command, set the Current_position value, make profile of move_done and send the move done signal with status attributes * In case of **'Move_Not_Done'** command, set the current position value from the status attributes, make profile of Not_Move_Done and send the Thread Command "Move_abs" * In case of **'update_settings'** command, create child "Move Settings" from status attributes (if possible) ================ ================= ====================================================== **Parameters** **Type** **Description** *status* ThreadCommand() instance of ThreadCommand containing two attributes : * *command* str * *attributes* list ================ ================= ====================================================== See Also -------- update_status, set_enabled_move_buttons, get_position, DAQ_utils.ThreadCommand, parameter_tree_changed, raise_timeout """ if status.command == "Update_Status": if len(status.attributes) > 2: self.update_status(status.attributes[0], wait_time=self.wait_time, log_type=status.attributes[1]) else: self.update_status(status.attributes[0], wait_time=self.wait_time) elif status.command == "ini_stage": # status.attributes[0]=edict(initialized=bool,info="", controller=) self.update_status("Stage initialized: {:} info: {:}".format( status.attributes[0]['initialized'], status.attributes[0]['info']), wait_time=self.wait_time) if status.attributes[0]['initialized']: self.controller = status.attributes[0]['controller'] self.set_enabled_move_buttons(enable=True) self.ui.Ini_state_LED.set_as_true() self.initialized_state = True else: self.initialized_state = False if self.initialized_state: self.get_position() self.init_signal.emit(self.initialized_state) elif status.command == "close": try: self.update_status(status.attributes[0], wait_time=self.wait_time) self.stage_thread.exit() self.stage_thread.wait() finished = self.stage_thread.isFinished() if finished: pass delattr(self, 'stage_thread') else: self.update_status('thread is locked?!', self.wait_time, 'log') except Exception as e: self.logger.exception(str(e)) self.initialized_state = False self.init_signal.emit(self.initialized_state) elif status.command == "check_position": self.ui.Current_position_sb.setValue(status.attributes[0]) self.current_position = status.attributes[0] if self.settings.child( 'main_settings', 'tcpip', 'tcp_connected').value() and self.send_to_tcpip: self.command_tcpip.emit( ThreadCommand('position_is', status.attributes)) elif status.command == "move_done": self.ui.Current_position_sb.setValue(status.attributes[0]) self.current_position = status.attributes[0] self.move_done_bool = True self.ui.Move_Done_LED.set_as_true() self.move_done_signal.emit(self.title, status.attributes[0]) if self.settings.child( 'main_settings', 'tcpip', 'tcp_connected').value() and self.send_to_tcpip: self.command_tcpip.emit( ThreadCommand('move_done', status.attributes)) elif status.command == "Move_Not_Done": self.ui.Current_position_sb.setValue(status.attributes[0]) self.current_position = status.attributes[0] self.move_done_bool = False self.ui.Move_Done_LED.set_as_false() self.command_stage.emit( ThreadCommand(command="move_Abs", attributes=[self.target_position])) elif status.command == 'update_main_settings': # this is a way for the plugins to update main settings of the ui (solely values, limits and options) try: if status.attributes[2] == 'value': self.settings.child('main_settings', *status.attributes[0]).setValue( status.attributes[1]) elif status.attributes[2] == 'limits': self.settings.child('main_settings', *status.attributes[0]).setLimits( status.attributes[1]) elif status.attributes[2] == 'options': self.settings.child( 'main_settings', *status.attributes[0]).setOpts(**status.attributes[1]) except Exception as e: self.logger.exception(str(e)) elif status.command == 'update_settings': # ThreadCommand(command='update_settings',attributes=[path,data,change])) try: self.settings.sigTreeStateChanged.disconnect( self.parameter_tree_changed ) # any changes on the settings will update accordingly the detector except Exception: pass try: if status.attributes[2] == 'value': self.settings.child('move_settings', *status.attributes[0]).setValue( status.attributes[1]) elif status.attributes[2] == 'limits': self.settings.child('move_settings', *status.attributes[0]).setLimits( status.attributes[1]) elif status.attributes[2] == 'options': self.settings.child( 'move_settings', *status.attributes[0]).setOpts(**status.attributes[1]) elif status.attributes[2] == 'childAdded': child = Parameter.create(name='tmp') child.restoreState(status.attributes[1][0]) self.settings.child('move_settings', *status.attributes[0]).addChild( status.attributes[1][0]) except Exception as e: self.logger.exception(str(e)) self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed ) # any changes on the settings will update accordingly the detector elif status.command == 'raise_timeout': self.raise_timeout() elif status.command == 'outofbounds': self.bounds_signal.emit(True) elif status.command == 'show_splash': self.ui.settings_tree.setEnabled(False) self.splash_sc.show() self.splash_sc.raise_() self.splash_sc.showMessage(status.attributes[0], color=Qt.white) elif status.command == 'close_splash': self.splash_sc.close() self.ui.settings_tree.setEnabled(True) def update_status(self, txt, wait_time=0): """ Show the given txt message in the status bar with a delay of wait_time ms if specified (0 by default). ================ ========== ================================= **Parameters** **Type** **Description** *txt* string The message to show *wait_time* int The delay time of showing ================ ========== ================================= """ self.ui.statusbar.showMessage(txt, wait_time) self.status_signal.emit(txt) self.logger.info(txt)
class SoundPlayer(QObject): def __init__(self, app=None, db=None, **kwargs): super().__init__() self._app = app self._db = db self._threads_workers = {} self._thread = QThread() self._worker = SoundPlayerWorker() self._worker.moveToThread(self._thread) # self._worker.playlist.mediaInserted.connect(self._worker.play_sound) # self._worker.media_player.stateChanged.connect(self._player_state_changed) self._worker.playlist.currentIndexChanged.connect( self._worker.check_playlist_index) self._worker.media_player.stateChanged.connect( self._worker.state_changed) # self._thread.started.connect(self._worker.run) # self._thread.finished.connect(self._thread_finished) self._thread.start() @pyqtSlot() def stop_thread(self): """ Method to stop the thread when the application is being shutdown :return: """ if self._thread.isRunning(): self._worker.stop() self._thread.exit() @pyqtSlot(str, int, bool, name="playSound") def play_sound(self, sound_name, priority=0, override=False): """ Method to play a sound :param sound_name: str - name indicating which action was taken and thus the associated sound to play :param priority: int :param override: bool - whether to override any existing playing sounds or not - True/False :return: """ if not isinstance(sound_name, str) or sound_name == "": return sound_file = "" if sound_name == "takeSubsample": sound_file = "resources/sounds/weighbaskets_buzzer.wav" elif sound_name == "takeWeight": sound_file = "resources/sounds/weighbaskets_donuts.wav" elif sound_name == "takeLength": sound_file = "resources/sounds/fishsampling_phonering.wav" elif sound_name == "takeWidth": sound_file = "resources/sounds/fishsampling_coffeebreak.wav" elif sound_name == "ageWeightSpecimen": sound_file = "resources/sounds/fishsampling_vikinghorn.wav" elif sound_name == "takeBarcode": sound_file = "resources/sounds/fishsampling_shotgun.wav" elif sound_name == "takeSudmantBarcode": sound_file = "resources/sounds/specialproject_sudmant.wav" elif sound_name == "deleteItem": sound_file = "resources/sounds/delete_item.wav" elif sound_name == "error": sound_file = "resources/sounds/fishsampling_undoundo.wav" elif sound_name == "jukebox": sound_file = f"resources/sounds/jukebox/{file_name}" elif sound_name == "hlHookMatrix15secs": sound_file = "resources/sounds/hookmatrix_15secs.wav" elif sound_name == "hlCutterStationNextFish": sound_file = "resources/sounds/cutterstation_nextfish.wav" if sound_file == "" or sound_file is None: return # logging.info(f"playing sound = {sound_file}, priority={priority}, override={override}") try: self._worker.play_sound(file=sound_file, priority=priority, override=override) except Exception as ex: logging.info( f"Exception occurred when attempting to play a sound: {ex}") def _player_state_changed(self): """ Method called when the media player state is changed. :param key: :return: """ if self._worker.media_player.state() == 0: logging.info('clearing playlist') self._worker.playlist.clear() return if key in self._threads_workers: item = self._threads_workers[key] if item["worker"].media_player.state() == 0: if item["thread"].isRunning(): item["thread"].exit() def _thread_finished(self, key): """ Method called when the thread is finished :param key: str - key to remove from the self._threads_worker dictionary :return: """ if key in self._threads_workers: item = self._threads_workers[key] self._threads_workers.pop(key, None) def _play_sound_thread(self, sound=None, loop=False): """ Method to play a sound in the background :param sound: :param loop: :return: """ try: while loop: if self.stop_sound: winsound.PlaySound(None) self.stop_sound = False break winsound.PlaySound(sound, winsound.SND_FILENAME) else: if self.stop_sound: winsound.PlaySound(None) self.stop_sound = False return winsound.PlaySound(sound, winsound.SND_FILENAME) except Exception as ex: print('Playing sound exception:', str(ex))
class DAQ_PID(QObject): """ """ log_signal = pyqtSignal(str) #look for eventual model files command_pid = pyqtSignal(ThreadCommand) command_stage = pyqtSignal(ThreadCommand) move_done_signal = pyqtSignal(str, float) models = [] try: model_mod = importlib.import_module('pymodaq_pid_models') for ind_file, entry in enumerate(os.scandir(os.path.join(model_mod.__path__[0], 'models'))): if not entry.is_dir() and entry.name != '__init__.py': try: file, ext = os.path.splitext(entry.name) importlib.import_module('.'+file, model_mod.__name__+'.models') models.append(file) except Exception as e: print(e) if 'PIDModelMock' in models: mods = models mods.pop(models.index('PIDModelMock')) models = ['PIDModelMock'] models.extend(mods) except Exception as e: print(e) if len(models) == 0: logger.warning('No valid installed models') def __init__(self,area, detector_modules = [], actuator_modules =[]): QLocale.setDefault(QLocale(QLocale.English, QLocale.UnitedStates)) super(DAQ_PID,self).__init__() self.settings = Parameter.create(title='PID settings', name='pid_settings', type='group', children=params) self.title = 'PyMoDAQ PID' self.Initialized_state = False self.model_class = None self.detector_modules = detector_modules self.actuator_modules = actuator_modules self.dock_area = area self.overshoot = None self.check_moving = False self.preset_manager = PresetManager() self.setupUI() self.command_stage.connect(self.move_Abs) #to be compatible with actuator modules within daq scan self.enable_controls_pid(False) self.enable_controls_pid_run(False) def ini_PID(self): if self.ini_PID_action.isChecked(): output_limits =[None,None] if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value(): output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value() if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value(): output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value() self.PIDThread = QThread() pid_runner = PIDRunner(self.model_class, [mod.move_done_signal for mod in self.actuator_modules], [mod.grab_done_signal for mod in self.detector_modules], [mod.command_stage for mod in self.actuator_modules], [mod.command_detector for mod in self.detector_modules], dict(Kp=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value(), Ki=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value(), Kd=self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value(), setpoint=self.settings.child('main_settings', 'pid_controls', 'set_point').value(), sample_time=self.settings.child('main_settings', 'pid_controls', 'sample_time').value()/1000, output_limits=output_limits, auto_mode=False), filter=dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(), value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value()), det_averaging=[mod.settings.child('main_settings', 'Naverage').value() for mod in self.detector_modules], ) self.PIDThread.pid_runner = pid_runner pid_runner.pid_output_signal.connect(self.process_output) pid_runner.status_sig.connect(self.thread_status) self.command_pid.connect(pid_runner.queue_command) pid_runner.moveToThread(self.PIDThread) self.PIDThread.start() self.pid_led.set_as_true() self.enable_controls_pid_run(True) else: if hasattr(self,'PIDThread'): if self.PIDThread.isRunning(): try: self.PIDThread.quit() except: pass self.pid_led.set_as_false() self.enable_controls_pid_run(False) self.Initialized_state = True pyqtSlot(dict) def process_output(self, datas): self.output_viewer.show_data([[dat] for dat in datas['output']]) self.input_viewer.show_data([[dat] for dat in datas['input']]) self.currpoint_sb.setValue(np.mean(datas['input'])) if self.check_moving: if np.abs(np.mean(datas['input'])-self.settings.child('main_settings', 'pid_controls', 'set_point').value()) < \ self.settings.child('main_settings', 'epsilon').value(): self.move_done_signal.emit(self.title, np.mean(datas['input'])) self.check_moving = False print('Move from {:s} is done: {:f}'.format('PID', np.mean(datas['input']))) @pyqtSlot(ThreadCommand) def move_Abs(self, command=ThreadCommand()): """ """ if command.command == "move_Abs": self.check_moving = True self.setpoint_sb.setValue(command.attributes[0]) QtWidgets.QApplication.processEvents() def enable_controls_pid(self,enable = False): self.ini_PID_action.setEnabled(enable) self.setpoint_sb.setOpts(enabled = enable) def enable_controls_pid_run(self,enable = False): self.run_action.setEnabled(enable) self.pause_action.setEnabled(enable) def setupUI(self): self.dock_pid = Dock('PID controller', self.dock_area) self.dock_area.addDock(self.dock_pid) #%% create logger dock self.logger_dock=Dock("Logger") self.logger_list=QtWidgets.QListWidget() self.logger_list.setMinimumWidth(300) self.logger_dock.addWidget(self.logger_list) self.dock_area.addDock(self.logger_dock,'right') self.logger_dock.setVisible(True) widget = QtWidgets.QWidget() widget_toolbar = QtWidgets.QWidget() verlayout = QtWidgets.QVBoxLayout() widget.setLayout(verlayout) toolbar_layout = QtWidgets.QGridLayout() widget_toolbar.setLayout(toolbar_layout) iconquit = QtGui.QIcon() iconquit.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/close2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.quit_action = QtWidgets.QPushButton(iconquit, "Quit") self.quit_action.setToolTip('Quit the application') toolbar_layout.addWidget(self.quit_action,0,0,1,2) self.quit_action.clicked.connect(self.quit_fun) iconini = QtGui.QIcon() iconini.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/ini.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.ini_model_action = QtWidgets.QPushButton(iconini, "Init Model") self.ini_model_action.setToolTip('Initialize the chosen model') toolbar_layout.addWidget(self.ini_model_action,2,0) self.ini_model_action.clicked.connect(self.ini_model) self.model_led = QLED() toolbar_layout.addWidget(self.model_led, 2,1) self.ini_PID_action = QtWidgets.QPushButton(iconini, "Init PID") self.ini_PID_action.setToolTip('Initialize the PID loop') toolbar_layout.addWidget(self.ini_PID_action,2,2) self.ini_PID_action.setCheckable(True) self.ini_PID_action.clicked.connect(self.ini_PID) self.pid_led = QLED() toolbar_layout.addWidget(self.pid_led, 2,3) self.iconrun = QtGui.QIcon() self.iconrun.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/run2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.icon_stop = QtGui.QIcon() self.icon_stop.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/stop.png")) self.run_action = QtWidgets.QPushButton(self.iconrun, "", None) self.run_action.setToolTip('Start PID loop') self.run_action.setCheckable(True) toolbar_layout.addWidget(self.run_action,0,2) self.run_action.clicked.connect(self.run_PID) iconpause = QtGui.QIcon() iconpause.addPixmap(QtGui.QPixmap(":/icons/Icon_Library/pause.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.pause_action = QtWidgets.QPushButton(iconpause, "", None) self.pause_action.setToolTip('Pause PID') self.pause_action.setCheckable(True) toolbar_layout.addWidget(self.pause_action,0,3) self.pause_action.setChecked(True) self.pause_action.clicked.connect(self.pause_PID) lab = QtWidgets.QLabel('Set Point:') toolbar_layout.addWidget(lab, 3,0,1,2) self.setpoint_sb = custom_tree.SpinBoxCustom() self.setpoint_sb.setMinimumHeight(40) font = self.setpoint_sb.font() font.setPointSizeF(20) self.setpoint_sb.setFont(font) self.setpoint_sb.setDecimals(6) toolbar_layout.addWidget(self.setpoint_sb,3,2,1,2) self.setpoint_sb.valueChanged.connect(self.settings.child('main_settings', 'pid_controls', 'set_point').setValue) lab1 = QtWidgets.QLabel('Current Point:') toolbar_layout.addWidget(lab1, 4,0,1,2) self.currpoint_sb = custom_tree.SpinBoxCustom() self.currpoint_sb.setMinimumHeight(40) self.currpoint_sb.setReadOnly(True) self.currpoint_sb.setDecimals(6) self.currpoint_sb.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) font = self.currpoint_sb.font() font.setPointSizeF(20) self.currpoint_sb.setFont(font) toolbar_layout.addWidget(self.currpoint_sb,4,2,1,2) #create main parameter tree self.settings_tree = ParameterTree() self.settings_tree.setParameters(self.settings, showTop=False) verlayout.addWidget(widget_toolbar) verlayout.addWidget(self.settings_tree) self.dock_output = Dock('PID output') widget_output = QtWidgets.QWidget() self.output_viewer = Viewer0D(widget_output) self.dock_output.addWidget(widget_output) self.dock_area.addDock(self.dock_output, 'right') self.dock_input = Dock('PID input') widget_input = QtWidgets.QWidget() self.input_viewer = Viewer0D(widget_input) self.dock_input.addWidget(widget_input) self.dock_area.addDock(self.dock_input, 'bottom',self.dock_output) if len(self.models) != 0: self.get_set_model_params(self.models[0]) #connecting from tree self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)#any changes on the settings will update accordingly the detector self.dock_pid.addWidget(widget) def get_set_model_params(self, model_file): self.settings.child('models', 'model_params').clearChildren() model = importlib.import_module('.' + model_file, self.model_mod.__name__+'.models') model_class = getattr(model, model_file) params = getattr(model_class, 'params') self.settings.child('models', 'model_params').addChildren(params) def run_PID(self): if self.run_action.isChecked(): self.run_action.setIcon(self.icon_stop) self.command_pid.emit(ThreadCommand('start_PID', [self.model_class.curr_input])) QtWidgets.QApplication.processEvents() QtWidgets.QApplication.processEvents() self.command_pid.emit(ThreadCommand('run_PID', [self.model_class.curr_output])) else: self.run_action.setIcon(self.iconrun) self.command_pid.emit(ThreadCommand('stop_PID')) QtWidgets.QApplication.processEvents() def pause_PID(self): self.command_pid.emit(ThreadCommand('pause_PID', [self.pause_action.isChecked()])) def update_status(self,txt,log_type=None): """ Show the txt message in the status bar with a delay of wait_time ms. =============== =========== ======================= **Parameters** **Type** **Description** *txt* string The message to show *wait_time* int the delay of showing *log_type* string the type of the log =============== =========== ======================= """ try: if log_type is not None: self.log_signal.emit(txt) logging.info(txt) except Exception as e: pass @pyqtSlot(str) def add_log(self,txt): """ Add the QListWisgetItem initialized with txt informations to the User Interface logger_list and to the save_parameters.logger array. =============== =========== ====================== **Parameters** **Type** **Description** *txt* string the log info to add. =============== =========== ====================== """ try: now=datetime.datetime.now() new_item=QtWidgets.QListWidgetItem(now.strftime('%Y/%m/%d %H:%M:%S')+": "+txt) self.logger_list.addItem(new_item) except: pass def set_file_preset(self,model): """ Set a file managers from the converted xml file given by the filename parameter. =============== =========== =================================================== **Parameters** **Type** **Description** *filename* string the name of the xml file to be converted/treated =============== =========== =================================================== Returns ------- (Object list, Object list) tuple The updated (Move modules list, Detector modules list). See Also -------- custom_tree.XML_file_to_parameter, set_param_from_param, stop_moves, update_status,DAQ_Move_main.daq_move, DAQ_viewer_main.daq_viewer """ filename = os.path.join(get_set_pid_path(), model + '.xml') self.preset_file = filename self.preset_manager.set_file_preset(filename, show=False) self.move_docks = [] self.det_docks_settings = [] self.det_docks_viewer = [] move_forms = [] actuator_modules = [] detector_modules = [] move_types = [] ################################################################# ###### sort plugins by IDs and within the same IDs by Master and Slave status plugins=[{'type': 'move', 'value': child} for child in self.preset_manager.preset_params.child(('Moves')).children()]+[{'type': 'det', 'value': child} for child in self.preset_manager.preset_params.child(('Detectors')).children()] for plug in plugins: plug['ID']=plug['value'].child('params','main_settings','controller_ID').value() if plug["type"]=='det': plug['status']=plug['value'].child('params','detector_settings','controller_status').value() else: plug['status']=plug['value'].child('params','move_settings', 'multiaxes', 'multi_status').value() IDs=list(set([plug['ID'] for plug in plugins])) #%% plugins_sorted=[] for id in IDs: plug_Ids=[] for plug in plugins: if plug['ID']==id: plug_Ids.append(plug) plug_Ids.sort(key=lambda status: status['status']) plugins_sorted.append(plug_Ids) ################################################################# ####################### ind_move=-1 ind_det=-1 for plug_IDs in plugins_sorted: for ind_plugin, plugin in enumerate(plug_IDs): plug_name=plugin['value'].child(('name')).value() plug_init=plugin['value'].child(('init')).value() plug_settings=plugin['value'].child(('params')) if plugin['type'] == 'move': ind_move+=1 plug_type=plug_settings.child('main_settings','move_type').value() self.move_docks.append(Dock(plug_name, size=(150,250))) if ind_move==0: self.dock_area.addDock(self.move_docks[-1], 'top',self.logger_dock) else: self.dock_area.addDock(self.move_docks[-1], 'above',self.move_docks[-2]) move_forms.append(QtWidgets.QWidget()) mov_mod_tmp=DAQ_Move(move_forms[-1],plug_name) mov_mod_tmp.ui.Stage_type_combo.setCurrentText(plug_type) mov_mod_tmp.ui.Quit_pb.setEnabled(False) QtWidgets.QApplication.processEvents() set_param_from_param(mov_mod_tmp.settings,plug_settings) QtWidgets.QApplication.processEvents() mov_mod_tmp.bounds_signal[bool].connect(self.stop_moves) self.move_docks[-1].addWidget(move_forms[-1]) actuator_modules.append(mov_mod_tmp) try: if ind_plugin==0: #should be a master type plugin if plugin['status']!="Master": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: actuator_modules[-1].ui.IniStage_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_type: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() master_controller=actuator_modules[-1].controller else: if plugin['status']!="Slave": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: actuator_modules[-1].controller=master_controller actuator_modules[-1].ui.IniStage_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_type: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() except Exception as e: self.update_status(getLineInfo()+ str(e),'log') else: ind_det+=1 plug_type=plug_settings.child('main_settings','DAQ_type').value() plug_subtype=plug_settings.child('main_settings','detector_type').value() self.det_docks_settings.append(Dock(plug_name+" settings", size=(150,250))) self.det_docks_viewer.append(Dock(plug_name+" viewer", size=(350,350))) if ind_det==0: self.logger_dock.area.addDock(self.det_docks_settings[-1], 'bottom', self.dock_input) #dock_area of the logger dock else: self.dock_area.addDock(self.det_docks_settings[-1], 'bottom',self.det_docks_settings[-2]) self.dock_area.addDock(self.det_docks_viewer[-1],'right',self.det_docks_settings[-1]) det_mod_tmp=DAQ_Viewer(self.dock_area,dock_settings=self.det_docks_settings[-1], dock_viewer=self.det_docks_viewer[-1],title=plug_name, DAQ_type=plug_type, parent_scan=self) detector_modules.append(det_mod_tmp) detector_modules[-1].ui.Detector_type_combo.setCurrentText(plug_subtype) detector_modules[-1].ui.Quit_pb.setEnabled(False) set_param_from_param(det_mod_tmp.settings,plug_settings) QtWidgets.QApplication.processEvents() try: if ind_plugin==0: #should be a master type plugin if plugin['status']!="Master": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_subtype: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() master_controller=detector_modules[-1].controller else: if plugin['status']!="Slave": raise Exception('error in the master/slave type for plugin {}'.format(plug_name)) if plug_init: detector_modules[-1].controller=master_controller detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() if 'Mock' in plug_subtype: QThread.msleep(500) else: QThread.msleep(4000) # to let enough time for real hardware to init properly QtWidgets.QApplication.processEvents() except Exception as e: self.update_status(getLineInfo()+ str(e),'log') detector_modules[-1].settings.child('main_settings','overshoot').show() detector_modules[-1].overshoot_signal[bool].connect(self.stop_moves) QtWidgets.QApplication.processEvents() return actuator_modules,detector_modules pyqtSlot(bool) def stop_moves(self,overshoot): """ Foreach module of the move module object list, stop motion. See Also -------- stop_scan, DAQ_Move_main.daq_move.stop_Motion """ self.overshoot = overshoot for mod in self.actuator_modules: mod.stop_Motion() def set_default_preset(self): actuators = self.model_class.actuators actuator_names = self.model_class.actuators_name detectors_type = self.model_class.detectors_type detectors = self.model_class.detectors detectors_name = self.model_class.detectors_name detector_modules = [] for ind_det, det in enumerate(detectors): detector_modules.append(DAQ_Viewer(area, title=detectors_name[ind_det], DAQ_type=detectors_type[ind_det])) #self.detector_modules[-1].ui.IniDet_pb.click() QtWidgets.QApplication.processEvents() detector_modules[-1].ui.Detector_type_combo.setCurrentText(detectors[ind_det]) detector_modules[-1].ui.Quit_pb.setEnabled(False) self.dock_area.addDock(self.dock_output, 'bottom') self.dock_area.moveDock(self.dock_input, 'bottom', self.dock_output) self.dock_area.addDock(self.dock_pid, 'left') dock_moves = [] actuator_modules = [] for ind_act, act in enumerate(actuators): form = QtWidgets.QWidget() dock_moves.append(Dock(actuator_names[ind_act])) area.addDock(dock_moves[-1], 'bottom', self.dock_pid) dock_moves[-1].addWidget(form) actuator_modules.append(DAQ_Move(form)) QtWidgets.QApplication.processEvents() actuator_modules[-1].ui.Stage_type_combo.setCurrentText(actuators[ind_act]) actuator_modules[-1].ui.Quit_pb.setEnabled(False) #self.actuator_modules[-1].ui.IniStage_pb.click() #QThread.msleep(1000) QtWidgets.QApplication.processEvents() return actuator_modules, detector_modules def ini_model(self): try: model_name = self.settings.child('models', 'model_class').value() model = importlib.import_module('.' +model_name, self.model_mod.__name__+'.models') self.model_class = getattr(model, model_name)(self) #try to get corresponding managers file filename = os.path.join(get_set_pid_path(), model_name + '.xml') if os.path.isfile(filename): self.actuator_modules, self.detector_modules = self.set_file_preset(model_name) else: self.actuator_modules, self.detector_modules = self.set_default_preset() # # connecting to logger # for mov in self.actuator_modules: # mov.log_signal[str].connect(self.add_log) # for det in self.detector_modules: # det.log_signal[str].connect(self.add_log) # self.log_signal[str].connect(self.add_log) self.model_class.ini_model() self.enable_controls_pid(True) self.model_led.set_as_true() self.ini_model_action.setEnabled(False) except Exception as e: self.update_status(getLineInfo() + str(e), log_type='log') def quit_fun(self): """ """ try: try: self.PIDThread.exit() except Exception as e: print(e) for module in self.actuator_modules: try: module.quit_fun() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() except Exception as e: print(e) for module in self.detector_modules: try: module.stop_all() QtWidgets.QApplication.processEvents() module.quit_fun() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() except Exception as e: print(e) areas=self.dock_area.tempAreas[:] for area in areas: area.win.close() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() self.dock_area.parent().close() except Exception as e: print(e) def parameter_tree_changed(self,param,changes): """ Foreach value changed, update : * Viewer in case of **DAQ_type** parameter name * visibility of button in case of **show_averaging** parameter name * visibility of naverage in case of **live_averaging** parameter name * scale of axis **else** (in 2D pymodaq type) Once done emit the update settings signal to link the commit. =============== =================================== ================================================================ **Parameters** **Type** **Description** *param* instance of ppyqtgraph parameter the parameter to be checked *changes* tuple list Contain the (param,changes,info) list listing the changes made =============== =================================== ================================================================ See Also -------- change_viewer, daq_utils.custom_parameter_tree.iter_children """ for param, change, data in changes: path = self.settings.childPath(param) if change == 'childAdded': pass elif change == 'value': if param.name() == 'model_class': self.get_set_model_params(param.value()) elif param.name() == 'module_settings': if param.value(): self.settings.sigTreeStateChanged.disconnect( self.parameter_tree_changed) param.setValue(False) self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed) self.preset_manager.set_PID_preset(self.settings.child('models','model_class').value()) elif param.name() == 'refresh_plot_time' or param.name() == 'timeout': self.command_pid.emit(ThreadCommand('update_timer', [param.name(),param.value()])) elif param.name() == 'set_point': if self.pid_led.state: self.command_pid.emit(ThreadCommand('update_options', dict(setpoint=param.value()))) else: output = self.model_class.convert_output(param.value(),0, stab=False) for ind_act, act in enumerate(self.actuator_modules): act.move_Abs(output[ind_act]) elif param.name() == 'sample_time': self.command_pid.emit(ThreadCommand('update_options', dict(sample_time=param.value()))) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'output_limits'), []): output_limits = [None, None] if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min_enabled').value(): output_limits[0] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_min').value() if self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max_enabled').value(): output_limits[1] = self.settings.child('main_settings', 'pid_controls', 'output_limits', 'output_limit_max').value() self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits))) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'filter'), []): self.command_pid.emit(ThreadCommand('update_filter', [dict(enable=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_enable').value(), value=self.settings.child('main_settings', 'pid_controls', 'filter', 'filter_step').value())])) elif param.name() in custom_tree.iter_children(self.settings.child('main_settings', 'pid_controls', 'pid_constants'), []): Kp = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kp').value() Ki = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'ki').value() Kd = self.settings.child('main_settings', 'pid_controls', 'pid_constants', 'kd').value() self.command_pid.emit(ThreadCommand('update_options', dict(tunings= (Kp, Ki, Kd)))) elif param.name() in custom_tree.iter_children(self.settings.child('models', 'model_params'),[]): self.model_class.update_settings(param) elif param.name() == 'detector_modules': self.model_class.update_detector_names() elif change == 'parent': pass @pyqtSlot(list) def thread_status(self,status): # general function to get datas/infos from all threads back to the main """ | General function to get datas/infos from all threads back to the main. | Switch the status with : * *"Update status"* : Update the status bar with the status attribute txt message """ if status[0]=="Update_Status": self.update_status(status[1],log_type=status[2])
class Sweep2D(BaseSweep, QObject): """ A 2-D Sweep of QCoDeS Parameters. This class runs by setting an outside parameter, then running an inner Sweep1D object. The inner sweep handles all data saving and communications through the Thread objects. Attributes --------- in_params: List defining the inner sweep [parameter, start, stop, step]. out_params: List defining the outer sweep [parameter, start, stop, step]. inter_delay: Time (in seconds) to wait between data points on inner sweep. outer_delay: Time (in seconds) to wait between data points on outer sweep. save_data: Flag used to determine if the data should be saved or not. plot_data: Flag to determine whether or not to live-plot data. complete_func: Sets a function to be executed upon completion of the outer sweep. update_func: Sets a function to be executed upon completion of the inner sweep. plot_bin: Defaults to 1. Controls amount of data stored in the data_queue list in the Plotter Thread. runner: Assigns the Runner Thread. plotter: Assigns the Plotter Thread. back_multiplier: Factor to scale the step size after flipping directions. heatmap_plotter: Uses color to represent values of a third parameter plotted against two sweeping parameters. Methods --------- follow_param(*p) Saves parameters to be tracked, for both saving and plotting data. follow_srs(self, l, name, gain=1.0) Adds an SRS lock-in to Sweep1D to ensure that the range is kept correctly. _create_measurement() Creates the measurement object for the sweep. start(ramp_to_start=True, persist_data=None) Extends the start() function of BaseSweep. stop() Stops the sweeping of both the inner and outer sweep. resume() Resumes the inner and outer sweeps. update_values() Updates plots and heatmap based on data from the inner and outer sweeps. get_param_setpoint() Obtains the current value of the setpoint. set_update_rule(func) Sets a function to be called upon completion of each inner sweep. send_updates(no_sp=False) Passed in Sweep2D. kill() Ends all threads and closes any active plots. ramp_to(value, start_on_finish=False, multiplier=1) Ramps the set_param to a given value, at a rate specified by multiplier. ramp_to_zero() done_ramping(start_on_finish=False) """ add_heatmap_lines = pyqtSignal(list) clear_heatmap_plot = pyqtSignal() def __init__(self, in_params, out_params, outer_delay=1, update_func=None, *args, **kwargs): """ Initializes the sweep. The inner sweep parameters('in_params') and outer sweep parameters ('out_params') MUST be a list, conforming to the following standard: [ <QCoDeS Parameter>, <start value>, <stop value>, <step size> ] Parameters --------- in_params: A list conforming to above standard for the inner sweep. out_params: A list conforming to above standard for the outer sweep. inter_delay: Time (in seconds) to wait between data points on inner sweep. outer_delay: Time (in seconds) to wait between data points on outer sweep. save_data: Flag used to determine if the data should be saved or not. plot_data: Flag to determine whether or not to live-plot data. complete_func: Sets a function to be executed upon completion of the outer sweep. update_func: Sets a function to be executed upon completion of the inner sweep. plot_bin: Defaults to 1. Controls amount of data stored in the data_queue list in the Plotter Thread. runner: Assigns the Runner Thread. plotter: Assigns the Plotter Thread. back_multiplier: Factor to scale the step size after flipping directions. heatmap_plotter: Uses color to represent values of a third parameter plotted against two sweeping parameters. """ # Ensure that the inputs were passed (at least somewhat) correctly if len(in_params) != 4 or len(out_params) != 4: raise TypeError('For 2D Sweep, must pass list of 4 object for each sweep parameter, \ in order: [ <QCoDeS Parameter>, <start value>, <stop value>, <step size> ]') # Save our input variables self.in_param = in_params[0] self.in_start = in_params[1] self.in_stop = in_params[2] self.in_step = in_params[3] # Ensure that the step has the right sign if (self.in_stop - self.in_start) > 0: self.in_step = abs(self.in_step) else: self.in_step = (-1) * abs(self.in_step) self.set_param = out_params[0] self.out_start = out_params[1] self.out_stop = out_params[2] self.out_step = out_params[3] self.out_setpoint = self.out_start if (self.out_stop - self.out_start) > 0: self.out_step = abs(self.out_step) else: self.out_step = (-1) * abs(self.out_step) # Initialize the BaseSweep QObject.__init__(self) BaseSweep.__init__(self, set_param=self.set_param, *args, **kwargs) # Create the inner sweep object self.in_sweep = Sweep1D(self.in_param, self.in_start, self.in_stop, self.in_step, bidirectional=True, inter_delay=self.inter_delay, save_data=self.save_data, x_axis_time=0, plot_data=self.plot_data, back_multiplier=self.back_multiplier) # We set our outer sweep parameter as a follow param for the inner sweep, so that # it is always read and saved with the rest of our data self.in_sweep.follow_param(self.set_param) # Our update_values() function iterates the outer sweep, so when the inner sweep # is done, call that function automatically self.in_sweep.set_complete_func(self.update_values) self.outer_delay = outer_delay # Flags for ramping self.inner_ramp = False self.outer_ramp = False self.ramp_sweep = None # Set the function to call when the inner sweep finishes if update_func is None: self.update_rule = self.no_change # Initialize our heatmap plotting thread self.heatmap_plotter = None def __str__(self): return f"2D Sweep of {self.set_param.label} from {self.out_start} to {self.out_stop} with step {self.out_step}," \ f"while sweeping {self.in_param.label} from {self.in_start} to {self.in_stop} with step {self.in_step}." def __repr__(self): return f"Sweep2D([{self.set_param.label}, {self.out_start}, {self.out_stop}, {self.out_step}], " \ f"[{self.in_param.label}, {self.in_start}, {self.in_stop}, {self.in_step}])" def follow_param(self, *p): """ Saves parameters to be tracked, for both saving and plotting data. Since the data saving is always handled by the inner Sweep1D object, all parameters are registered in the inner Sweep1D object. The parameters must be followed before '_create_measurement()' is called. Parameters --------- *p: Variable number of arguments, each of which must be a QCoDeS Parameter, or a list of QCoDeS Parameters, for the sweep to follow. """ for param in p: if isinstance(param, list): for l in param: self.in_sweep._params.append(l) else: self.in_sweep._params.append(param) self._params = self.in_sweep._params def follow_srs(self, l, name, gain=1.0): """ Adds an SRS lock-in to Sweep1D to ensure that the range is kept correctly. Parameters --------- l: The lock-in instrument. name: The user-defined name of the instrument. gain: The current gain value. """ self.in_sweep.follow_srs((l, name, gain)) def _create_measurement(self): """ Creates the measurement object for the sweep. The measurement object is created through the inner Sweep1D object. Returns --------- self.meas: The QCoDeS Measurement object responsible for running the sweep. """ self.meas = self.in_sweep._create_measurement() return self.meas def start(self, ramp_to_start=True, persist_data=None): """ Extends the start() function of BaseSweep. The first outer sweep setpoint is set, and the inner sweep is started. Parameters --------- ramp_to_start: Sets a sweep to gently iterate the parameter to its starting value. persist_data: Sets the outer parameter for Sweep2D. """ if self.is_running: print("Can't start the sweep, we're already running!") return elif self.outer_ramp: print("Can't start the sweep, we're currently ramping the outer sweep parameter!") return if self.meas is None: self._create_measurement() if ramp_to_start: self.ramp_to(self.out_setpoint, start_on_finish=True) else: print( f'Starting the 2D Sweep. Ramping {self.set_param.label} to {self.out_stop} {self.set_param.unit}, ' f'while sweeping {self.in_param.label} between {self.in_start} {self.in_param.unit} and {self.in_stop} ' f'{self.in_param.unit}') self.set_param.set(self.out_setpoint) time.sleep(self.outer_delay) self.is_running = True if self.heatmap_plotter is None: # Initialize our heatmap self.heatmap_plotter = Heatmap(self) self.heatmap_thread = QThread() self.heatmap_plotter.moveToThread(self.heatmap_thread) self.heatmap_plotter.create_figs() self.add_heatmap_lines.connect(self.heatmap_plotter.add_lines) self.clear_heatmap_plot.connect(self.heatmap_plotter.clear) self.heatmap_thread.start() self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint)) self.plotter = self.in_sweep.plotter self.runner = self.in_sweep.runner def stop(self): """ Stops the sweeping of both the inner and outer sweep. """ self.is_running = False self.in_sweep.stop() def resume(self): """ Resumes the inner and outer sweeps. """ self.is_running = True self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint), ramp_to_start=False) def update_values(self): """ Updates plots and heatmap based on data from the inner and outer sweeps. This function is automatically called upon completion of the inner sweep. The outer parameter is iterated and the inner sweep is restarted. If the stop condition is reached, the completed signal is emitted and the sweeps are stopped. """ # If this function was called from a ramp down to 0, a special case of sweeping, deal with that # independently if self.in_sweep.is_ramping: # We are no longer ramping to zero self.inner_ramp = False # Check if our outer ramp to zero is still going, and if not, then officially end # our ramping to zero if not self.outer_ramp: self.is_running = False self.inner_sweep.is_running = False print("Done ramping both parameters to zero") # Stop the function from running any further, as we don't want to check anything else return # print("trying to update heatmap") # Update our heatmap! lines = self.in_sweep.plotter.axes[1].get_lines() self.add_heatmap_lines.emit(lines) # Check our update condition self.update_rule(self.in_sweep, lines) # self.in_sweep.ramp_to(self.in_sweep.begin, start_on_finish=False) # while self.in_sweep.is_ramping == True: # time.sleep(0.5) # If we aren't at the end, keep going if abs(self.out_setpoint - self.out_stop) - abs(self.out_step / 2) > abs(self.out_step) * 1e-4: self.out_setpoint = self.out_setpoint + self.out_step time.sleep(self.outer_delay) print(f"Setting {self.set_param.label} to {self.out_setpoint} {self.set_param.unit}") self.set_param.set(self.out_setpoint) time.sleep(self.outer_delay) # Reset our plots self.in_sweep.reset_plots() self.in_sweep.start(persist_data=(self.set_param, self.out_setpoint)) # If neither of the above are triggered, it means we are at the end of the sweep else: self.is_running = False print(f"Done with the sweep, {self.set_param.label}={self.out_setpoint}") self.in_sweep.kill() self.completed.emit() def get_param_setpoint(self): """ Obtains the current value of the setpoint. """ s = f"{self.set_param.label} = {self.set_param.get()} {self.set_param.unit} \ \n{self.inner_sweep.set_param.label} = {self.inner_sweep.set_param.get()} {self.inner_sweep.set_param.unit}" return s def set_update_rule(self, func): """ Sets a function to be called upon completion of each inner sweep. Parameters --------- func: The function handle desired to set the update function. """ self.update_rule = func def send_updates(self, no_sp=False): pass def kill(self): """ Ends all threads and closes any active plots. """ self.in_sweep.kill() # Gently shut down the heatmap if self.heatmap_plotter is not None: self.clear_heatmap_plot.emit() self.heatmap_thread.exit() if not self.heatmap_thread.wait(1000): self.heatmap_thread.terminate() print('forced heatmap to terminate') self.heatmap_plotter = None def ramp_to(self, value, start_on_finish=False, multiplier=1): """ Ramps the set_param to a given value, at a rate specified by the multiplier. Parameter --------- value: The setpoint for the sweep to ramp to. start_on_finish: Flag to determine whether to begin the sweep when ramping is finished. multiplier: The multiplier for the step size, to ramp quicker than the sweep speed. """ # Ensure we aren't currently running if self.outer_ramp: print(f"Currently ramping. Finish current ramp before starting another.") return if self.is_running: print(f"Already running. Stop the sweep before ramping.") return # Check if we are already at the value curr_value = self.set_param.get() if abs(value - curr_value) <= self.out_step / 2: # print(f"Already within {self.step} of the desired ramp value. Current value: {curr_value}, # ramp setpoint: {value}.\nSetting our setpoint directly to the ramp value.") self.set_param.set(value) self.done_ramping(start_on_finish=True) return # Create a new sweep to ramp our outer parameter to zero self.ramp_sweep = Sweep1D(self.set_param, curr_value, value, multiplier * self.out_step, inter_delay=self.inter_delay, complete_func=partial(self.done_ramping, start_on_finish), save_data=False, plot_data=True) self.is_running = False self.outer_ramp = True self.ramp_sweep.start(ramp_to_start=False) print(f'Ramping {self.set_param.label} to {value} . . . ') def ramp_to_zero(self): """Ramps the set_param to 0, at the same rate as already specified. """ print("Ramping both parameters to 0.") # Ramp our inner sweep parameter to zero self.inner_ramp = True self.in_sweep.ramp_to(0) # Check our step sign if self.out_setpoint > 0: self.out_step = (-1) * abs(self.out_step) else: self.out_step = abs(self.out_step) # Create a new sweep to ramp our outer parameter to zero zero_sweep = Sweep1D(self.set_param, self.set_param.get(), 0, self.step, inter_delay=self.inter_delay, complete_func=self.done_ramping) self.is_running = True self.outer_ramp = True zero_sweep.start() def done_ramping(self, start_on_finish=False): """ Alerts the sweep that the ramp is finished. Parameters --------- start_on_finish: Sweep will be called to start immediately after ramping when set to True. """ # Our outer parameter has finished ramping self.outer_ramp = False if self.ramp_sweep is not None: self.ramp_sweep.kill() self.ramp_sweep = None # Check if our inner parameter has finished while self.in_sweep.is_ramping: time.sleep(0.5) # If so, tell the system we are done self.is_running = False print("Done ramping!") if start_on_finish: self.start(ramp_to_start=False) def estimate_time(self, verbose=True): in_time = self.in_sweep.estimate_time() n_lines = abs((self.out_start-self.out_stop)/self.out_step)+1 out_time = self.outer_delay * self.out_step t_est = in_time*n_lines + out_time hours = int(t_est / 3600) minutes = int((t_est % 3600) / 60) seconds = t_est % 60 if verbose is True: print(f'Estimated time for {repr(self)} to run: {hours}h:{minutes:2.0f}m:{seconds:2.0f}s') return t_est
class NetworkPanelController(QObject): scanSignal = pyqtSignal() listenSignal = pyqtSignal() stopSignal = pyqtSignal() devicesBroadcastingSignal = pyqtSignal(object) def __init__(self, ui_scanPanel): super().__init__() self.ui_scanPanel = ui_scanPanel self.ui_connectBtn = ui_scanPanel.connectBtn self.ui_connectBtn.clicked.connect(self.scan) self.ui_disconnectBtn = ui_scanPanel.disconnectBtn self.ui_disconnectBtn.clicked.connect(self.disconnect) self.ui_resetBtn = ui_scanPanel.resetBtn self.ui_resetBtn.clicked.connect(self.reset) self.ui_devicesListBox = ui_scanPanel.devicesListBox self.ui_devicesListBox.itemDoubleClicked.connect(self.renameDevice) self.ui_sessionText = ui_scanPanel.sessionText self.antDriver = ANTDriver() self.scanWorker = ScanWorker(self.antDriver) self.scanThread = QThread() self.scanWorker.moveToThread(self.scanThread) self.scanWorker.devicesFound.connect(self.devicesFound) self.scanSignal.connect(self.scanWorker.scan) self.listenWorker = ListenWorker(self.antDriver) self.listenThread = QThread() self.listenWorker.moveToThread(self.listenThread) self.listenWorker.messageReceived.connect(self.updateNetworkStatistics) self.listenSignal.connect(self.listenWorker.listen) self.broadcastingDevices = [] self.broadcastingProfiles = [] self.sessionTime = 0 self.sessionStartTime = 0 self.numMessagesReceived = 0 self.messageFrequency = 0 self.ui_connectBtn.setEnabled(True) self.ui_disconnectBtn.setEnabled(False) self.ui_resetBtn.setEnabled(False) def scan(self): self.ui_connectBtn.setEnabled(False) self.ui_disconnectBtn.setEnabled(False) self.ui_resetBtn.setEnabled(False) self.broadcastingDevices = [] if not self.scanThread.isRunning(): self.scanThread.start() self.scanSignal.emit() def disconnect(self): self.ui_connectBtn.setEnabled(True) self.ui_disconnectBtn.setEnabled(False) self.ui_resetBtn.setEnabled(False) self.antDriver.disconnect() self.listenWorker.messageReceived.disconnect( self.updateNetworkStatistics) if self.scanThread.isRunning(): self.scanThread.exit() if self.listenThread.isRunning(): self.listenThread.exit() self.clear() self.stopSignal.emit() def reset(self): self.ui_connectBtn.setEnabled(False) self.ui_disconnectBtn.setEnabled(True) self.ui_resetBtn.setEnabled(True) self.sessionTime = 0 self.sessionStartTime = time.time() self.messageFrequency = 0 self.numMessagesReceived = 0 self.antDriver.reset() def renameDevice(self, selectedDevice): if selectedDevice is None: return if not selectedDevice.name: return inputDialog = InputDialog() name, okSelected = inputDialog.getText(inputDialog, 'Rename Device', 'Enter Desired Device Name:') if okSelected: xmlWriter = XMLWriter() xmlWriter.addDevice(name, selectedDevice.number) def clear(self): self.ui_devicesListBox.clear() self.ui_sessionText.clear() self.sessionTime = 0 self.messageFrequency = 0 self.numMessagesReceived = 0 self.broadcastingDevices = [] def devicesFound(self, devices): if self.scanThread.isRunning(): self.scanThread.exit() if not devices: self.ui_connectBtn.setEnabled(True) self.ui_disconnectBtn.setEnabled(False) self.ui_resetBtn.setEnabled(False) return self.sessionStartTime = time.time() self.ui_connectBtn.setEnabled(False) self.ui_disconnectBtn.setEnabled(True) self.ui_resetBtn.setEnabled(True) if not self.listenThread.isRunning(): self.listenThread.start() self.listenSignal.emit() xmlReader = XMLReader() deviceNumber2Name = xmlReader.getDeviceNumber2Name() for deviceNumber in devices: deviceName = "Unknown Device" + str(deviceNumber) if deviceNumber in deviceNumber2Name: deviceName = deviceNumber2Name[deviceNumber] elif deviceNumber in ANTConstants.DeviceNumber2Name: deviceName = ANTConstants.DeviceNumber2Name[deviceNumber] antDevice = ANTDevice(deviceName, deviceNumber) self.broadcastingDevices.append(antDevice) antDevice.setText(antDevice.name) self.ui_devicesListBox.addItem(antDevice) self.devicesBroadcastingSignal.emit(self.broadcastingDevices) def updateNetworkStatistics(self, profileMessage): if profileMessage.msg.deviceType not in self.broadcastingProfiles: self.broadcastingProfiles.append(profileMessage.msg.deviceType) self.sessionTime = time.time() - self.sessionStartTime self.numMessagesReceived += 1 self.messageFrequency = self.numMessagesReceived / self.sessionTime self.ui_sessionText.setText('Connection Duration (s): ' + str(round(self.sessionTime))) self.ui_sessionText.append('Devices Broadcasting: ' + str(len(self.broadcastingDevices))) self.ui_sessionText.append('Profiles Received: ' + str(len(self.broadcastingProfiles))) self.ui_sessionText.append('Messages Received: ' + str(self.numMessagesReceived)) self.ui_sessionText.append('Message Frequency (Hz): ' + str(round(self.messageFrequency)))
class SettingsMenu(QtWidgets.QWidget): def __init__(self, parent=None): super().__init__() self.setup_ui() self.thread = QThread() self.controller = SettingsController(self) #Connect slots self.controller.status_updated.connect(self.update_status) self.controller.ip_updated.connect(self.update_ip) self.controller.update_btn_updated.connect(self.update_update_btn) self.btn_wifi__connect.clicked.connect(self.connect_to_wifi) self.btn_update.clicked.connect(self.check_for_update) self.btn_home.clicked.connect(self.go_home) #Start Background Worker self.controller.moveToThread(self.thread) self.thread.start() self.controller.finished.connect(self.thread.quit) self.update_status(self.controller.get_status()) self.update_ip(self.controller.get_ip()) def go_home(self): self.thread.exit(0) self.nativeParentWidget().show_main_menu() def connect_to_wifi(self): self.thread.start() QtCore.QMetaObject.invokeMethod( self.controller, "connect_wifi", Qt.QueuedConnection, QtCore.Q_ARG(str, self.ssid_input.text()), QtCore.Q_ARG(str, self.wifi_pwd_input.text())) def check_for_update(self): QtCore.QMetaObject.invokeMethod(self.controller, "update_software", Qt.QueuedConnection) #Will not excecute if restarted after update was necessary self.btn_update.setEnabled(False) def update_status(self, status): self.label_status2.setText(status) def update_ip(self, ip): self.label_ip2.setText(ip) def update_update_btn(self, text): self.btn_update.setText(text) def setup_ui(self): self.setEnabled(True) font = QtGui.QFont() font.setPointSize(12) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(":/icons/radio.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(icon) self.setToolTipDuration(-5) self.setStyleSheet("background-color: rgb(255, 255, 255);") self.centralwidget = QtWidgets.QWidget(self) self.centralwidget.setGeometry(QtCore.QRect(0, 0, 1024, 768)) self.label_logo = QtWidgets.QLabel(self.centralwidget) self.label_logo.setGeometry(QtCore.QRect(100, 10, 221, 81)) self.label_logo.setStyleSheet("image: url(:/icons/logo.png);") self.label_logo.setText("") self.label_logo.setObjectName("label_logo") self.btn_home = QtWidgets.QToolButton(self.centralwidget) self.btn_home.setGeometry(QtCore.QRect(10, 10, 81, 71)) self.btn_home.setStyleSheet("QToolButton{\n" "border: none ;\n" "background: transparent ;\n" "}\n" "\n" "QToolButton:pressed{\n" "background-color : rgb(255, 255, 255)\n" "}\n" "\n" "") icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(":icons/home.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btn_home.setIcon(icon) self.btn_home.setIconSize(QtCore.QSize(40, 40)) self.btn_home.setObjectName("toolButton_home") ###WIFI Section self.wifisection = QtWidgets.QWidget(self.centralwidget) self.wifisection.setGeometry(QtCore.QRect(10, 100, 300, 200)) self.wifisection.setObjectName("wifisection") self.wifiLayout = QtWidgets.QGridLayout(self.wifisection) self.wifiLayout.setContentsMargins(0, 0, 0, 0) self.wifiLayout.setObjectName("wifiLayout") self.wifi_icon_label = QtWidgets.QLabel(self.wifisection) self.wifi_icon_label.setWordWrap(False) self.wifi_icon_label.setObjectName("wifi_icon_label") pixmap = QtGui.QPixmap(":/icons/wlan.png") self.wifi_icon_label.resize(40, 40) self.wifi_icon_label.setPixmap( pixmap.scaled(self.wifi_icon_label.size(), QtCore.Qt.KeepAspectRatio)) self.wifiLayout.addWidget(self.wifi_icon_label, 0, 0, 1, 2, QtCore.Qt.AlignHCenter) #Connection State Labels self.label_status1 = QtWidgets.QLabel("Status:", self.wifisection) self.label_status2 = QtWidgets.QLabel("", self.wifisection) self.label_ip1 = QtWidgets.QLabel("IP Adresse:", self.wifisection) self.label_ip2 = QtWidgets.QLabel("", self.wifisection) self.label_status1.setObjectName("label_status1") self.label_status2.setObjectName("label_status2") self.label_ip1.setObjectName("label_ip1") self.label_ip2.setObjectName("label_ip2") self.label_status1.setFont(font) self.label_status2.setFont(font) self.label_ip1.setFont(font) self.label_ip2.setFont(font) self.wifiLayout.addWidget(self.label_status1, 1, 0, 1, 1) self.wifiLayout.addWidget(self.label_status2, 1, 1, 1, 1) self.wifiLayout.addWidget(self.label_ip1, 2, 0, 1, 1) self.wifiLayout.addWidget(self.label_ip2, 2, 1, 1, 1) #SSID Input self.ssid_input = QtWidgets.QLineEdit(self.wifisection) self.ssid_input.setFont(font) self.ssid_input.setStyleSheet( "QLineEdit\n" "{\n" " color:black;\n" " border-color: rgba(94, 136, 161, 200);\n" " border-width: 1px;\n" " border-style: solid;\n" "}\n" "@label->setStyleSheet(\"font: 18pt;\");\n" "") self.ssid_input.setText("") self.ssid_input.setFrame(True) self.ssid_input.setEchoMode(QtWidgets.QLineEdit.Normal) self.ssid_input.setClearButtonEnabled(False) self.ssid_input.setObjectName("lineEdit_2") self.wifiLayout.addWidget(self.ssid_input, 3, 1, 1, 1) self.ssid_label = QtWidgets.QLabel("SSID", self.wifisection) self.ssid_label.setFont(font) self.wifiLayout.addWidget(self.ssid_label, 3, 0, 1, 1) #Password Input self.wifi_pwd_input = QtWidgets.QLineEdit(self.wifisection) self.wifi_pwd_input.setFont(font) self.wifi_pwd_input.setStyleSheet( "QLineEdit\n" "{\n" " color:black;\n" " border-color: rgba(94, 136, 161, 200);\n" " border-width: 1px;\n" " border-style: solid;\n" "}\n" "@label->setStyleSheet(\"font: 18pt;\");\n" "") self.wifi_pwd_input.setText("") self.wifi_pwd_input.setEchoMode(QtWidgets.QLineEdit.Password) self.wifi_pwd_input.setPlaceholderText("") self.wifi_pwd_input.setObjectName("lineEdit_3") self.wifiLayout.addWidget(self.wifi_pwd_input, 4, 1, 1, 1) self.password_label = QtWidgets.QLabel("Passwort", self.wifisection) self.password_label.setObjectName("label_2") self.password_label.setFont(font) self.wifiLayout.addWidget(self.password_label, 4, 0, 1, 1) self.btn_wifi__connect = QtWidgets.QToolButton(self.wifisection) self.btn_wifi__connect.setStyleSheet( "QToolButton{\n" "border: none ;\n" "background: transparent ;\n" "}\n" "\n" "QToolButton:pressed{\n" "background-color : rgb(255, 255, 255)\n" "}\n" "\n" "") icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap(":/icons/play.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.btn_wifi__connect.setIcon(icon2) self.btn_wifi__connect.setIconSize(QtCore.QSize(40, 40)) self.btn_wifi__connect.setObjectName("btn_wifi__connect") self.wifiLayout.addWidget(self.btn_wifi__connect, 6, 0, 1, 2, QtCore.Qt.AlignRight) self.btn_update = QtWidgets.QPushButton(self.centralwidget) self.btn_update.setGeometry(QtCore.QRect(780, 10, 231, 41)) self.btn_update.setStyleSheet( "QPushButton\n" "{\n" " color:black;\n" " border-color: rgba(94, 136, 161, 200);\n" " border-width: 1px;\n" " border-style: solid;\n" "}\n" "QPushButton:hover\n" "{\n" " background-color:rgba(94, 136, 161, 200);\n" "}\n" "") self.btn_update.setObjectName("btn_update") self.btn_update.setText("Updates installieren") self.btn_update.setFont(font)
class ProcessDialog(QDialog): def __init__(self, port, **kwargs): super().__init__() self.setWindowTitle('Cooking your TinyGS station...') self.setFixedWidth(400) self.exception = None esptool.sw.progress.connect(self.update_progress) self.nam = QNetworkAccessManager() self.nrBinFile = QNetworkRequest() self.bin_data = b'' self.setLayout(VLayout(5, 5)) self.actions_layout = QFormLayout() self.actions_layout.setSpacing(5) self.layout().addLayout(self.actions_layout) self._actions = [] self._action_widgets = {} self.port = port self.auto_reset = kwargs.get('auto_reset', False) self.file_path = kwargs.get('file_path') if self.file_path and self.file_path.startswith('http'): self._actions.append('download') self.backup = kwargs.get('backup') if self.backup: self._actions.append('backup') self.backup_size = kwargs.get('backup_size') self.erase = kwargs.get('erase') if self.erase: self._actions.append('erase') if self.file_path: self._actions.append('write') self.create_ui() self.start_process() def create_ui(self): for action in self._actions: pb = QProgressBar() pb.setFixedHeight(35) self._action_widgets[action] = pb self.actions_layout.addRow(action.capitalize(), pb) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.btns.rejected.connect(self.abort) self.layout().addWidget(self.btns) self.sb = QStatusBar() self.layout().addWidget(self.sb) def appendBinFile(self): self.bin_data += self.bin_reply.readAll() def saveBinFile(self): if self.bin_reply.error() == QNetworkReply.NoError: self.file_path = self.file_path.split('/')[-1] with open(self.file_path, 'wb') as f: f.write(self.bin_data) self.run_esp() else: raise NetworkError def updateBinProgress(self, recv, total): self._action_widgets['download'].setValue(recv // total * 100) def download_bin(self): self.nrBinFile.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) self.nrBinFile.setUrl(QUrl(self.file_path)) self.bin_reply = self.nam.get(self.nrBinFile) self.bin_reply.readyRead.connect(self.appendBinFile) self.bin_reply.downloadProgress.connect(self.updateBinProgress) self.bin_reply.finished.connect(self.saveBinFile) def show_connection_state(self, state): self.sb.showMessage(state, 0) def run_esp(self): params = { 'file_path': self.file_path, 'auto_reset': self.auto_reset, 'erase': self.erase } if self.backup: backup_size = f'0x{2 ** self.backup_size}00000' params['backup_size'] = backup_size self.esp_thread = QThread() self.esp = ESPWorker(self.port, self._actions, **params) esptool.sw.connection_state.connect(self.show_connection_state) self.esp.waiting.connect(self.wait_for_user) self.esp.done.connect(self.accept) self.esp.error.connect(self.error) self.esp.moveToThread(self.esp_thread) self.esp_thread.started.connect(self.esp.run) self.esp_thread.start() def start_process(self): if 'download' in self._actions: self.download_bin() self._actions = self._actions[1:] else: self.run_esp() def update_progress(self, action, value): self._action_widgets[action].setValue(value) @pyqtSlot() def wait_for_user(self): dlg = QMessageBox.information( self, 'User action required', 'Please power cycle the device, wait a moment and press OK', QMessageBox.Ok | QMessageBox.Cancel) if dlg == QMessageBox.Ok: self.esp.continue_ok() elif dlg == QMessageBox.Cancel: self.esp.abort() self.esp.continue_ok() self.abort() def stop_thread(self): self.esp_thread.wait(2000) self.esp_thread.exit() def accept(self): self.stop_thread() self.done(QDialog.Accepted) def abort(self): self.sb.showMessage('Aborting...', 0) QApplication.processEvents() self.esp.abort() self.stop_thread() self.reject() def error(self, e): self.exception = e self.abort() def closeEvent(self, e): self.stop_thread()
class SoundPlayer(QObject): def __init__(self, **kwargs): super().__init__() self._threads_workers = {} self._thread = QThread() self._worker = SoundPlayerWorker() self._worker.moveToThread(self._thread) # self._worker.playlist.mediaInserted.connect(self._worker.play_sound) # self._worker.media_player.stateChanged.connect(self._player_state_changed) self._worker.playlist.currentIndexChanged.connect( self._worker.check_playlist_index) self._worker.media_player.stateChanged.connect( self._worker.state_changed) # self._thread.started.connect(self._worker.run) # self._thread.finished.connect(self._thread_finished) self._thread.start() @pyqtSlot() def stop_thread(self): """ Method to stop the thread when the application is being shutdown :return: """ if self._thread.isRunning(): self._worker.stop() self._thread.exit() @pyqtSlot(str, bool) def play_sound(self, sound_name, priority=0, override=True): """ Method to play a sound :param sound_name: str - name indicating which action was taken and thus the associated sound to play :param priority: priority :param override: bool - whether to override any existing playing sounds or not - True/False :return: """ if not isinstance(sound_name, str) or sound_name == "": return sounds = { 'keyInput': 'resources/sounds/minimal-ui-sounds/clack.wav', 'numpadInput': 'resources/sounds/minimal-ui-sounds/clack.wav', 'numpadDecimal': 'resources/sounds/minimal-ui-sounds/tinyclick.wav', 'numpadBack': 'resources/sounds/minimal-ui-sounds/click3.wav', 'numpadOK': 'resources/sounds/minimal-ui-sounds/beep.mp3', 'matrixWtSel': 'resources/sounds/minimal-ui-sounds/beep.mp3', 'matrixSel': 'resources/sounds/minimal-ui-sounds/beep.mp3', 'keyOK': 'resources/sounds/minimal-ui-sounds/beep.mp3', 'click': 'resources/sounds/minimal-ui-sounds/click2.wav', 'saveRecord': 'resources/sounds/minimal-ui-sounds/funnyclick.wav', 'login': '******', 'noCount': 'resources/sounds/minimal-ui-sounds/spaceyclick.wav', 'reject': 'resources/sounds/minimal-ui-sounds/coarse.wav' } sound_file = sounds.get(sound_name, None) if sound_file: self._worker.play_sound(file=sound_file, priority=priority, override=override) else: logging.warning(f'Sound not found: {sound_name}') def _player_state_changed(self): """ Method called when the media player state is changed. :param key: :return: """ if self._worker.media_player.state() == 0: logging.info('clearing playlist') self._worker.playlist.clear() return if key in self._threads_workers: item = self._threads_workers[key] if item["worker"].media_player.state() == 0: if item["thread"].isRunning(): item["thread"].exit() def _thread_finished(self, key): """ Method called when the thread is finished :param key: str - key to remove from the self._threads_worker dictionary :return: """ if key in self._threads_workers: item = self._threads_workers[key] self._threads_workers.pop(key, None)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) # self.ui = CandyWindow.createWindow(self.ui, 'blueGreen',"串口调试助手",'1.ico') self.setWindowIcon(QIcon('1.ico')) self.__curveChart = CurveChart() self.__curveChart.legend().hide() self.__chartView = QChartView(self.__curveChart) self.__chartView.setRenderHint(QPainter.Antialiasing) #反走样 # self.__chartView.setMargins(100,0,0,0) self.__layOut = QtWidgets.QVBoxLayout(self.ui.widget_dynamic_curve) self.__layOut.addWidget(self.__chartView) # self.__layOut.setContentsMargins(0,0,0,0) self.__uartState = OFF # 常规串口 self.__serialPort = QtSerialPort.QSerialPort() self.__uart2State = OFF # PID调参串口 self.__serialPort2 = QtSerialPort.QSerialPort() self.__receiveThread = QThread(self) self.__uartReceive = Receive(self.__serialPort2) self.__uartReceive.moveToThread(self.__receiveThread) self.__plotThread = QThread(self) self.ui.widget_dynamic_curve.moveToThread(self.__plotThread) self.__plotThread.start() self.uart_init() self.uart2_init() # PID滑动条初始化 self.__maxP = 10.0 self.__maxI = 0.1 self.__maxD = 1.0 self.ui.doubleSpinBox_max_P.setValue(self.__maxP) self.ui.doubleSpinBox_max_I.setValue(self.__maxI) self.ui.doubleSpinBox_max_D.setValue(self.__maxD) self.ui.doubleSpinBox_P.setDecimals(1) self.ui.doubleSpinBox_I.setDecimals(3) self.ui.doubleSpinBox_D.setDecimals(2) # self.__curveDataS = CurveDataS(self.ui.widget_dynamic_curve) # signal and slot self.ui.pushButton_uart_sw.clicked.connect(self.change_uart_state) self.ui.pushButton_uart_rfresh.clicked.connect(self.refresh_uart_info) self.ui.comboBox_com.currentTextChanged.connect(self.change_combox_tooltip) self.ui.radioButton_show_ascii.toggled.connect(self.display_ascii) self.ui.radioButton_show_hex.toggled.connect(self.display_hex) self.ui.pushButton_clean.clicked.connect(self.clean_display) self.ui.pushButton_save.clicked.connect(self.save_receive_data) self.ui.radioButton_tx_hex.toggled.connect(self.change_tx_mode) # self.ui.radioButton_tx_ascii.toggled.connect(self.change_tx_mode) self.ui.pushButton_tx.clicked.connect(self.transmit_data) self.ui.pushButton_uart_sw_2.clicked.connect(self.change_uart2_state) self.ui.pushButton_uart_rfresh_2.clicked.connect(self.refresh_uart2_info) self.ui.comboBox_com_2.currentTextChanged.connect(self.change_combox_tooltip) self.ui.doubleSpinBox_max_P.valueChanged.connect(self.set_p_max_value) self.ui.doubleSpinBox_max_I.valueChanged.connect(self.set_i_max_value) self.ui.doubleSpinBox_max_D.valueChanged.connect(self.set_d_max_value) self.ui.verticalSlider_P.valueChanged.connect(self.change_p_para_display) self.ui.verticalSlider_I.valueChanged.connect(self.change_i_para_display) self.ui.verticalSlider_D.valueChanged.connect(self.change_d_para_display) self.ui.doubleSpinBox_P.valueChanged.connect(self.change_p_slider_value) self.ui.doubleSpinBox_I.valueChanged.connect(self.change_i_slider_value) self.ui.doubleSpinBox_D.valueChanged.connect(self.change_d_slider_value) # 滑动条改变曲线图X大小 # self.ui.horizontalSlider.valueChanged.connect(self.ui.widget_dynamic_curve.change_the_radio) self.ui.horizontalSlider.valueChanged.connect(self.__curveChart.change_the_radio) self.ui.checkBox_curve_show_random.toggled.connect(self.plot_random_data) # for i in range(len(self.__curveData)): # self.__curveData[i].plot_data.connect(self.ui.widget_dynamic_curve.plot) # self.__curveData[i].Id = i # 设置标号 对应显示通道数目 # self.__curveDataS.plot_data.connect(self.ui.widget_dynamic_curve.plot) self.__uartReceive.receive_success.connect(self.add_plot_data) def uart_init(self): self.__uartState = OFF self.ui.comboBox_com.clear() for info in QtSerialPort.QSerialPortInfo.availablePorts(): self.ui.comboBox_com.addItem(info.portName() + ":" + info.description()) self.ui.comboBox_com.setCurrentIndex(0) self.ui.comboBox_com.setToolTip(self.ui.comboBox_com.currentText()) def uart2_init(self): self.__uart2State = OFF self.ui.comboBox_com_2.clear() for info in QtSerialPort.QSerialPortInfo.availablePorts(): self.ui.comboBox_com_2.addItem(info.portName() + ":" + info.description()) self.ui.comboBox_com_2.setCurrentIndex(0) self.ui.comboBox_com_2.setToolTip(self.ui.comboBox_com.currentText()) def change_uart_state(self): if self.__uartState: self.__serialPort.close() self.__uartState = OFF self.ui.pushButton_uart_sw.setText("打开串口") self.ui.comboBox_com.setEnabled(True) self.ui.comboBox_baud.setEnabled(True) else: self.__serialPort.setPortName(self.ui.comboBox_com.currentText().split(':')[0]) self.__serialPort.setBaudRate(int(self.ui.comboBox_baud.currentText())) self.__serialPort.setFlowControl(QtSerialPort.QSerialPort.NoFlowControl) self.__serialPort.setDataBits(QtSerialPort.QSerialPort.Data8) self.__serialPort.setStopBits(QtSerialPort.QSerialPort.OneStop) self.__serialPort.setParity(QtSerialPort.QSerialPort.NoParity) self.__serialPort.setReadBufferSize(10) if self.__serialPort.open(QtSerialPort.QSerialPort.ReadWrite): self.__uartState = ON # 常规模式 self.__serialPort.readyRead.connect(self.receive_uart_data) self.ui.pushButton_uart_sw.setText("关闭串口") self.ui.comboBox_baud.setEnabled(False) self.ui.comboBox_com.setEnabled(False) else: QMessageBox.critical(self,"Error","Fail to turn on this device!") print(self.__serialPort.error()) def change_uart2_state(self): if self.__uart2State: self.__serialPort2.close() self.__uart2State = OFF if self.__receiveThread.isRunning(): self.__receiveThread.quit() self.__receiveThread.exit() self.ui.pushButton_uart_sw_2.setText("打开串口") self.ui.comboBox_com_2.setEnabled(True) self.ui.comboBox_baud_2.setEnabled(True) # 开启随机点 self.ui.checkBox_curve_show_random.setEnabled(True) # self.__curveDataS.stop_plot() else: self.__serialPort2.setPortName(self.ui.comboBox_com_2.currentText().split(':')[0]) self.__serialPort2.setBaudRate(int(self.ui.comboBox_baud_2.currentText())) self.__serialPort2.setFlowControl(QtSerialPort.QSerialPort.NoFlowControl) self.__serialPort2.setDataBits(QtSerialPort.QSerialPort.Data8) self.__serialPort2.setStopBits(QtSerialPort.QSerialPort.OneStop) self.__serialPort2.setParity(QtSerialPort.QSerialPort.NoParity) self.__serialPort2.setReadBufferSize(10) if self.__serialPort2.open(QtSerialPort.QSerialPort.ReadWrite): self.__uart2State = ON self.__receiveThread.start() # PID调参模式 self.__serialPort2.readyRead.connect(self.__uartReceive.receive_uart_data) self.ui.pushButton_uart_sw_2.setText("关闭串口") self.ui.comboBox_baud_2.setEnabled(False) self.ui.comboBox_com_2.setEnabled(False) # 关闭随机点 self.ui.checkBox_curve_show_random.setChecked(False) self.ui.checkBox_curve_show_random.setEnabled(False) self.__curveChart.start_plot() else: QMessageBox.critical(self,"Error","Fail to turn on this device!") # print(self.__serialPort2.error()) def refresh_uart_info(self): if self.__uartState == ON: QToolTip.showText(QCursor.pos(),"Please turn off the current device first.") else: self.uart_init() def refresh_uart2_info(self): if self.__uart2State == ON: QToolTip.showText(QCursor.pos(),"Please turn off the current device first.") else: self.uart2_init() def change_combox_tooltip(self): self.ui.comboBox_com.setToolTip(self.ui.comboBox_com.currentText()) self.ui.comboBox_com_2.setToolTip(self.ui.comboBox_com_2.currentText()) def receive_uart_data(self): if self.__serialPort.isReadable(): data = self.__serialPort.readAll() # _bytes = bytearray(data) # print(data) # print(data.length()) if data.isEmpty(): return self.ui.plainTextEdit_rx.moveCursor(QtGui.QTextCursor.End) if self.ui.radioButton_show_ascii.isChecked(): # _bytes.decode('utf8') # print(chardet.detect(_bytes)) try: self.ui.plainTextEdit_rx.insertPlainText(str(data, encoding='gbk')) except : self.ui.plainTextEdit_rx.insertPlainText(str(data)) else: hexText = data.toHex().toUpper() # print(hexText) # print(hexText.length()) for i in range(0,hexText.length(),2): self.ui.plainTextEdit_rx.insertPlainText(' '+str(hexText.mid(i,2),encoding='gbk')) def display_ascii(self,checked): if checked: hexText = self.ui.plainTextEdit_rx.toPlainText().replace(" ","")#encode('gbk') # print(hexText) text =bytes.fromhex(hexText) # print(text) try: self.ui.plainTextEdit_rx.setPlainText(str(text, encoding='gbk')) except: self.ui.plainTextEdit_rx.setPlainText(str(text)) def display_hex(self,checked): if checked: asciiText = self.ui.plainTextEdit_rx.toPlainText() hexText = bytes(asciiText,encoding='gbk').hex().upper() #print(hexText) self.ui.plainTextEdit_rx.setPlainText("") #print(len(hexText)) for i in range(0,len(hexText),2): self.ui.plainTextEdit_rx.insertPlainText(' ' + hexText[i:i+2]) def clean_display(self): self.ui.plainTextEdit_rx.clear() def save_receive_data(self): filename = QFileDialog.getSaveFileName(self,"Save as","/", "*.txt;;*.log") print(filename) print(type(filename)) if len(filename[0]) > 0: file = QFile(filename[0]) if file.open(QFile.WriteOnly|QFile.Text): os = QtCore.QTextStream(file) os << self.ui.plainTextEdit_rx.toPlainText() def change_tx_mode(self,checked): data = self.ui.plainTextEdit_tx.toPlainText() if checked: if len(data) > 0: hexText = bytes(data, encoding='gbk').hex().upper() self.ui.plainTextEdit_tx.setPlainText("") for i in range(0, len(hexText), 2): self.ui.plainTextEdit_tx.insertPlainText(' ' + hexText[i:i + 2]) else: if len(data) > 0: hexText = self.ui.plainTextEdit_tx.toPlainText().replace(" ", "") text = bytes.fromhex(hexText) try: self.ui.plainTextEdit_tx.setPlainText(str(text, encoding='gbk')) except: self.ui.plainTextEdit_tx.setPlainText(str(text)) def transmit_data(self): data = self.ui.plainTextEdit_tx.toPlainText() if self.ui.radioButton_tx_hex.isChecked(): data = bytes.fromhex(data.replace(" ", "")) # print(data) # data = str(text, encoding='gbk') else: data = bytes(data, encoding='gbk') self.__serialPort.write(data) # 关闭窗口关闭线程 def closeEvent(self, e): # print(1) b=QMessageBox.question(self, "滑稽", "陈奇是不是你爸爸", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) while b == QMessageBox.No: b = QMessageBox.question(self, "滑稽", "陈奇是不是你爸爸", QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) # 关闭串口 if self.__uartState: self.__serialPort.close() if self.__uart2State: self.__serialPort2.close() # 关闭数据接收线程 if self.__receiveThread.isRunning(): self.__receiveThread.quit() self.__receiveThread.exit() # 关闭随机模拟曲线数据生成线程 # for i in range(len(self.__curveData)): # if self.__curveData[i].isRun == True: # self.__curveData[i].release_plot() # if self.__curveDataS.isRandomRun: # self.__curveDataS.release_random_plot() # self.__curveDataS.stop_plot() self.__curveChart.stop_random_plot() # print('k88888') # else: # e.ignore() # 滑动条部分操作 def set_p_max_value(self,max): self.__maxP = max if self.__maxP < 0.1: self.__maxP = 0.1 self.ui.doubleSpinBox_max_P.setValue(0.1) if self.__maxP >= 100: self.ui.doubleSpinBox_P.setDecimals(0) self.ui.doubleSpinBox_P.setSingleStep(1) elif self.__maxP >= 10: self.ui.doubleSpinBox_P.setDecimals(1) self.ui.doubleSpinBox_P.setSingleStep(0.1) elif self.__maxP >= 1: self.ui.doubleSpinBox_P.setDecimals(2) self.ui.doubleSpinBox_P.setSingleStep(0.01) else: self.ui.doubleSpinBox_P.setDecimals(3) self.ui.doubleSpinBox_P.setSingleStep(0.001) # self.ui.doubleSpinBox_P.setValue(self.ui.verticalSlider_P.value() * self.__maxP / 100) def set_i_max_value(self,max): self.__maxI = max if self.__maxI < 0.1: self.__maxI = 0.1 self.ui.doubleSpinBox_max_I.setValue(0.1) if self.__maxI >= 100: self.ui.doubleSpinBox_I.setDecimals(0) self.ui.doubleSpinBox_I.setSingleStep(1) elif self.__maxI >=10: self.ui.doubleSpinBox_I.setDecimals(1) self.ui.doubleSpinBox_I.setSingleStep(0.1) elif self.__maxI >= 1: self.ui.doubleSpinBox_I.setDecimals(2) self.ui.doubleSpinBox_I.setSingleStep(0.01) else: self.ui.doubleSpinBox_I.setDecimals(3) self.ui.doubleSpinBox_I.setSingleStep(0.001) # print(self.ui.verticalSlider_I.value(),1,self.__maxI) # self.ui.doubleSpinBox_I.setValue(self.ui.verticalSlider_I.value() * self.__maxI / 100) # print(self.ui.verticalSlider_I.value(),self.__maxI) def set_d_max_value(self,max): self.__maxD = max if self.__maxD < 0.1: self.__maxD = 0.1 self.ui.doubleSpinBox_max_D.setValue(0.1) if self.__maxD >= 100: self.ui.doubleSpinBox_D.setDecimals(0) self.ui.doubleSpinBox_D.setSingleStep(1) elif self.__maxD >= 10: self.ui.doubleSpinBox_D.setDecimals(1) self.ui.doubleSpinBox_D.setSingleStep(0.1) elif self.__maxD >= 1: self.ui.doubleSpinBox_D.setDecimals(2) self.ui.doubleSpinBox_D.setSingleStep(0.01) else: self.ui.doubleSpinBox_D.setDecimals(3) self.ui.doubleSpinBox_D.setSingleStep(0.001) # self.ui.doubleSpinBox_D.setValue(self.ui.verticalSlider_D.value() * self.__maxD / 100) def change_p_para_display(self,para): self.ui.doubleSpinBox_P.setValue(para*self.__maxP/100) def change_i_para_display(self,para): self.ui.doubleSpinBox_I.setValue(para * self.__maxI / 100) def change_d_para_display(self,para): self.ui.doubleSpinBox_D.setValue(para * self.__maxD / 100) self.send_pid_para('d', para * self.__maxD / 100) def change_p_slider_value(self, value): if value > self.__maxP: value = self.__maxP self.ui.doubleSpinBox_P.setValue(value) self.send_pid_para('p', value) value = value * 100 / self.__maxP self.ui.verticalSlider_P.setValue(value) def change_i_slider_value(self, value): if value > self.__maxI: value = self.__maxI self.ui.doubleSpinBox_I.setValue(value) self.send_pid_para('i', value) value = value * 100 / self.__maxI self.ui.verticalSlider_I.setValue(value) def change_d_slider_value(self, value): if value > self.__maxD: value = self.__maxD self.ui.doubleSpinBox_D.setValue(value) self.send_pid_para('d', value) value = value * 100 / self.__maxD self.ui.verticalSlider_D.setValue(value) # cmd = ‘p' 'i' 'd' # sign = '-'/'+' # para_float --> para_8*4 # sum = cmd + sign + para_8*4 def send_pid_para(self, cmd, para): para_8 = struct.pack('f',para) # float to byte send = bytearray(3) send[0] = ord('$') send[1] = ord(cmd) if cmd == 'p': if self.ui.checkBox_P.isChecked(): send[2] = ord('-') else: send[2] = ord('+') elif cmd == 'i': if self.ui.checkBox_I.isChecked(): send[2] = ord('-') else: send[2] = ord('+') elif cmd == 'd': if self.ui.checkBox_D.isChecked(): send[2] = ord('-') else: send[2] = ord('+') # for i in range(len(para_8)): # send[2+i] = para_8[i] send.extend(para_8) sum = 0 for i in range(len(send)): sum += send[i] # print(sum|0xff) send.append(sum&0xff) # print(send.hex()) # print(send) if not self.__uart2State: QToolTip.showText(QCursor.pos(), "请打开串口") else: self.__serialPort2.write(send) def which_channel_show(self): num = [] if self.ui.checkBox_1.isChecked(): num.append(1) if self.ui.checkBox_2.isChecked(): num.append(2) if self.ui.checkBox_3.isChecked(): num.append(3) if self.ui.checkBox_4.isChecked(): num.append(4) if self.ui.checkBox_5.isChecked(): num.append(5) if self.ui.checkBox_6.isChecked(): num.append(6) return num def plot_random_data(self, checked): if checked: # if 1 in self.which_channel_show(): # self.__curveDataS.start_random_plot(1) if len(self.which_channel_show()) > 0: self.__curveChart.start_random_plot(self.which_channel_show()[0]) else: self.__curveChart.start_random_plot(0) else: self.__curveChart.stop_random_plot() def add_plot_data(self, data, num): # start = time.time() # print('add_plot_data_start', start) # print(1, data, num) data = round(data,3) if num == 0: self.ui.lineEdit_7.setText(str(data)) elif num ==1: self.ui.lineEdit_8.setText(str(data)) elif num ==2: self.ui.lineEdit_9.setText(str(data)) elif num ==3: self.ui.lineEdit_10.setText(str(data)) elif num ==4: self.ui.lineEdit_11.setText(str(data)) elif num ==5: self.ui.lineEdit_12.setText(str(data)) if num+1 in self.which_channel_show(): # print(2, data, num) self.__curveChart.add_data(data,num) else: self.__curveChart.clear_data(num)
class ProcessDialog(QDialog): def __init__(self, port, **kwargs): super().__init__() self.setWindowTitle('Preparando seu Satélite Educacional...') self.setFixedWidth(600) self.exception = None esptool.sw.progress.connect(self.update_progress) self.nam = QNetworkAccessManager() self.nrDownloads = 0 self.nrBinFile1 = QNetworkRequest() self.bin_data1 = b'' self.nrBinFile2 = QNetworkRequest() self.bin_data2 = b'' self.nrBinFile3 = QNetworkRequest() self.bin_data3 = b'' self.nrBinFile4 = QNetworkRequest() self.bin_data4 = b'' self.setLayout(VLayout(5, 5)) self.actions_layout = QFormLayout() self.actions_layout.setSpacing(5) self.layout().addLayout(self.actions_layout) self._actions = [] self._action_widgets = {} self.port = port self.file_path = kwargs.get('file_url') self.file_pathBoot = kwargs.get('file_urlBoot') self.file_pathBootloader = kwargs.get('file_urlBootloader') self.file_pathPart = kwargs.get('file_urlPart') self._actions.append('download') self.erase = kwargs.get('erase') if self.erase: self._actions.append('erase') if self.file_path: self._actions.append('write') self.create_ui() self.start_process() def create_ui(self): for action in self._actions: pb = QProgressBar() pb.setFixedHeight(35) self._action_widgets[action] = pb self.actions_layout.addRow(action.capitalize(), pb) self.btns = QDialogButtonBox(QDialogButtonBox.Abort) self.btns.rejected.connect(self.abort) self.layout().addWidget(self.btns) self.sb = QStatusBar() self.layout().addWidget(self.sb) def appendBinFile1(self): self.bin_data1 += self.bin_reply1.readAll() def appendBinFile2(self): self.bin_data2 += self.bin_reply2.readAll() def appendBinFile3(self): self.bin_data3 += self.bin_reply3.readAll() def appendBinFile4(self): self.bin_data4 += self.bin_reply4.readAll() def downloadsFinished(self): if self.nrDownloads == 4: self.run_esp() def saveBinFile1(self): if self.bin_reply1.error() == QNetworkReply.NoError: self.file_path = self.file_path.split('/')[-1] with open(self.file_path, 'wb') as f: f.write(self.bin_data1) self.nrDownloads += 1 self.downloadsFinished() else: raise NetworkError def saveBinFile2(self): if self.bin_reply2.error() == QNetworkReply.NoError: self.file_pathBoot = self.file_pathBoot.split('/')[-1] with open(self.file_pathBoot, 'wb') as f: f.write(self.bin_data2) self.nrDownloads += 1 self.downloadsFinished() else: raise NetworkError def saveBinFile3(self): if self.bin_reply3.error() == QNetworkReply.NoError: self.file_pathBootloader = self.file_pathBootloader.split('/')[-1] with open(self.file_pathBootloader, 'wb') as f: f.write(self.bin_data3) self.nrDownloads += 1 self.downloadsFinished() else: raise NetworkError def saveBinFile4(self): if self.bin_reply4.error() == QNetworkReply.NoError: self.file_pathPart = self.file_pathPart.split('/')[-1] with open(self.file_pathPart, 'wb') as f: f.write(self.bin_data4) self.nrDownloads += 1 self.downloadsFinished() else: raise NetworkError def updateBinProgress(self, recv, total): self._action_widgets['download'].setValue(recv // total * 100) def download_bin(self): self.nrBinFile1.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) self.nrBinFile1.setUrl(QUrl(self.file_path)) self.bin_reply1 = self.nam.get(self.nrBinFile1) self.bin_reply1.readyRead.connect(self.appendBinFile1) self.bin_reply1.downloadProgress.connect(self.updateBinProgress) self.bin_reply1.finished.connect(self.saveBinFile1) self.nrBinFile2.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) self.nrBinFile2.setUrl(QUrl(self.file_pathBoot)) self.bin_reply2 = self.nam.get(self.nrBinFile2) self.bin_reply2.readyRead.connect(self.appendBinFile2) self.bin_reply2.finished.connect(self.saveBinFile2) self.nrBinFile3.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) self.nrBinFile3.setUrl(QUrl(self.file_pathBootloader)) self.bin_reply3 = self.nam.get(self.nrBinFile3) self.bin_reply3.readyRead.connect(self.appendBinFile3) self.bin_reply3.finished.connect(self.saveBinFile3) self.nrBinFile4.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) self.nrBinFile4.setUrl(QUrl(self.file_pathPart)) self.bin_reply4 = self.nam.get(self.nrBinFile4) self.bin_reply4.readyRead.connect(self.appendBinFile4) self.bin_reply4.finished.connect(self.saveBinFile4) def show_connection_state(self, state): self.sb.showMessage(state, 0) def run_esp(self): params = { 'file_path': self.file_path, 'file_pathBoot': self.file_pathBoot, 'file_pathBootloader': self.file_pathBootloader, 'file_pathPart': self.file_pathPart, 'erase': self.erase } self.esp_thread = QThread() self.esp = ESPWorker(self.port, self._actions, **params) esptool.sw.connection_state.connect(self.show_connection_state) self.esp.done.connect(self.accept) self.esp.error.connect(self.error) self.esp.moveToThread(self.esp_thread) self.esp_thread.started.connect(self.esp.run) self.esp_thread.start() def start_process(self): if 'download' in self._actions: self.download_bin() self._actions = self._actions[1:] else: self.run_esp() def update_progress(self, action, value): self._action_widgets[action].setValue(value) @pyqtSlot() def stop_thread(self): self.esp_thread.wait(2000) self.esp_thread.exit() def accept(self): self.stop_thread() self.done(QDialog.Accepted) def abort(self): self.sb.showMessage('Aborting...', 0) QApplication.processEvents() self.esp.abort() self.stop_thread() self.reject() def error(self, e): self.exception = e self.abort() def closeEvent(self, e): self.stop_thread()
class MainWindow(QtWidgets.QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent=parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.setWindowTitle("Youtube downloader") self.checked_all = False # declares which state should be setted by check_all-button # thread self.thread = None self.downloader = None # videos self.videos = [] # slots self.ui.addButton.clicked.connect(self.search) self.ui.downloadButton.clicked.connect(self.download) self.ui.checkAllButton.clicked.connect(self.check_all) self.ui.cleanButton.clicked.connect(self.clean_list) # ui self.ui.listWidget.setSelectionMode( QAbstractItemView.SelectionMode.NoSelection) self.ui.mp3CheckBox.setChecked(True) self.ui.labelInformation.setVisible(False) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.mp3_options = { 'format': 'bestaudio', # 'bestaudio/best', 'postprocessors': [{ 'key': 'FFmpegExtractAudio', 'preferredcodec': 'mp3', 'preferredquality': '320', # 192', }], 'logger': MainWindow.MyLogger(), 'progress_hooks': [self.my_hook], } # TODO don't work video downloading # self.video_options = { # 'format': 'bestvideo+bestaudio', # 'noplaylist': True, # # 'ext': '3gp', # 'bestaudio/best', # 'logger': MainWindow.MyLogger(), # 'progress_hooks': [self.my_hook], # } # TODO test # self.ui.searchLineEdit.setText( # "https://www.youtube.com/watch?v=8GW6sLrK40k&list=PLit8GzUQ7d7F0Lf2WNNL754TYQHb23b8t&t=0s&index=2") self.ui.searchLineEdit.setText( "https://www.youtube.com/watch?v=kfchvCyHmsc") # TODO test self.search() class MyLogger: # TODO def debug(self, msg): #if msg.startswith("[download]"): print("XD") def warning(self, msg): print("XDD12" + msg) def error(self, msg): print("XDD123" + msg) def my_hook(self, d): if d['status'] == 'finished': print('Done downloading, now converting ...') @pyqtSlot() def search(self): self.ui.labelInformation.setVisible(False) if self.ui.searchLineEdit.text(): try: # TODO so bad it is very slow, but this is a youtube-dl problem with yt.YoutubeDL(self.get_options()) as ydl: result = ydl.extract_info( self.ui.searchLineEdit.text(), download=False # We just want to extract the info ) # TODO test pp = pprint.PrettyPrinter(indent=4) pp.pprint(result) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.update_list(result) except yt.DownloadError: self.print_error( str(yt.DownloadError) ) # ("Download is not possible.\nCheck you internet connection or link.") @pyqtSlot() def download(self): self.ui.labelInformation.setVisible(False) if self.videos: try: # TODO test # path = QtWidgets.QFileDialog.getExistingDirectory( # self, # "Otwórz", # expanduser("~"), # QtWidgets.QFileDialog.ShowDirsOnly # ) # TODO test path = '/home/marek/Desktop' if path and self.videos: self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.thread = QThread() self.downloader = Downloader(path, self.videos, self.get_options()) self.downloader.moveToThread(self.thread) self.thread.started.connect(self.downloader.download) self.download_is_running(True) self.downloader.finished.connect( lambda: self.download_is_running(False)) self.downloader.error_occured.connect(self.print_error) self.thread.start() except yt.DownloadError: self.print_error( "Download is not possible.\nCheck you internet connection or link." ) def update_list(self, videos): if 'entries' in videos: for video in videos['entries']: if video['title'] not in (v.original_title for v in self.videos): record = Video(video['url'], video['title'], video['duration'], video['thumbnail']) record.is_checked = True record.filesize = self.get_filesize( video['formats'], 'best') # TODO now its only best quality self.videos.append(record) else: if videos['title'] not in (v.original_title for v in self.videos): record = Video(videos['url'], videos['title'], videos['duration'], videos['thumbnail']) record.is_checked = True record.filesize = self.get_filesize( videos['formats'], 'best') # TODO now its only best quality self.videos.append(record) self.show_list() @pyqtSlot() def check_all(self): self.ui.labelInformation.setVisible(False) if self.videos: for v in self.videos: v.is_checked = not self.checked_all self.checked_all = not self.checked_all text = 'Odznacz' if self.checked_all else 'Zaznacz' self.ui.checkAllButton.setText(text) def show_list(self): self.ui.listWidget.clear() for video in self.videos: # create an item item = QtWidgets.QListWidgetItem(self.ui.listWidget) # create a custom widget item_widget = ListItem() # set thumbnail url = video.thumbnail data = urllib.request.urlopen(url).read() image = QtGui.QImage() image.loadFromData(data) image = image.scaled(100, 100, QtCore.Qt.KeepAspectRatio) item_widget.setPixmap(QtGui.QPixmap(image)) item_widget.setTitle(video.title) item_widget.setDuration(video.duration) item_widget.setChecked(video.is_checked) # self.checkboxes.append(Item_Widget.getCheckBox()) # set the size from the item to the same of the widget item.setSizeHint(item_widget.sizeHint()) # Item.setFlags(Item.flags() | QtCore.Qt.ItemIsSelectable) # TODO not working # Item. # I add it to the list self.ui.listWidget.addItem(item) # self.items.append(Item) video.checkbox = item_widget.getCheckBox() video.line_edit = item_widget.getLineEdit() self.ui.listWidget.setItemWidget(item, item_widget) @pyqtSlot() def clean_list(self): self.videos = [] self.checked_all = False self.ui.labelInformation.setVisible(False) self.ui.progressBarSingle.setVisible(False) self.ui.progressBarMultiple.setVisible(False) self.show_list() def download_is_running(self, is_running): self.ui.downloadButton.setEnabled(not is_running) self.ui.progressBarSingle.setVisible(is_running) self.ui.progressBarMultiple.setVisible(is_running) if self.thread: self.thread.exit() def get_options(self): if self.ui.mp3CheckBox.isChecked(): return self.mp3_options else: return self.video_options @pyqtSlot(str) def print_error(self, msg): self.ui.labelInformation.setVisible(True) self.ui.labelInformation.setText(msg) def get_filesize(self, formats, choosen_format): # TODO it's fake in case of MP3 files. Here is only video filesize if choosen_format == 'best': filesizes = [frm['filesize'] for frm in formats] print('----------------' + str(max(filesizes))) return max(filesizes) else: # TODO there is a problem in downloading videos at all raise NotImplementedError
class WorkerManager(QObject, IProjectChangeNotify): onPushTask = pyqtSignal(object, object) onStartWorker = pyqtSignal() def __init__(self, main_window): super(WorkerManager, self).__init__() self.main_window = main_window self.project = None self.queue = [] self.queue_identify = [] self.running = None self.worker = AnalysisWorker(self) self.execution_thread = QThread() self.execution_thread.exit() self.worker.moveToThread(self.execution_thread) self.execution_thread.start() self.onPushTask.connect(self.worker.push_task) self.onStartWorker.connect(self.worker.run_worker) self.worker.signals.sign_create_progress_bar.connect(self.main_window.concurrent_task_viewer.add_task) self.worker.signals.sign_remove_progress_bar.connect(self.main_window.concurrent_task_viewer.remove_task) self.worker.signals.sign_task_manager_progress.connect(self.main_window.concurrent_task_viewer.update_progress) self.worker.signals.analysisStarted.connect(self.main_window.pipeline_toolbar.progress_widget.on_start_analysis) self.worker.signals.analysisEnded.connect(self.main_window.pipeline_toolbar.progress_widget.on_stop_analysis) self.reset() def push(self, project, analysis, targets, parameters, fps, class_objs): identify = (targets, class_objs, analysis.__class__) if identify in self.queue_identify: log_info("Job is already in Queue:", identify) return self.queue.append((analysis, (project, targets, fps, class_objs))) self.queue_identify.append(identify) if self.running is None: self._start() def _start(self): if len(self.queue) > 0: print("Queue:", len(self.queue), [q[0].__class__ for q in self.queue]) analysis, params = self.queue.pop(0) self.queue_identify.pop(0) self.running = analysis args = analysis.prepare(*params) if analysis.multiple_result: for arg in args: self.onPushTask.emit(analysis, arg) else: self.onPushTask.emit(analysis, args) self.onStartWorker.emit() else: self.running = None @pyqtSlot(object) def on_worker_finished(self, finished_tasks): for task_id, result in finished_tasks.items(): try: if isinstance(result, list): for r in result: self.running.modify_project(self.project, r, main_window=self.main_window) self.project.add_analysis(r, dispatch=False) r.unload_container() else: self.running.modify_project(self.project, result, main_window=self.main_window) self.project.add_analysis(result) result.unload_container() except Exception as e: print("Exception in AnalysisWorker.analysis_result", str(e)) self.project.dispatch_changed(item=self.project) self._start() def on_loaded(self, project): self.project = project def on_closed(self): self.reset() @pyqtSlot(tuple) def on_signal_error(self, error): print("*********ERROR**IN**WORKER***********") print(error) print("*************************************") @pyqtSlot(tuple) def on_signal_progress(self, progress): print("on_signal_progress", progress) # total = self.concurrent_task_viewer.update_progress(progress[0], progress[1]) # self.main_window.progress_bar.set_progress(float(total) / 100) @pyqtSlot() def reset(self): self.worker.abort()
class FixateGUI(QtWidgets.QMainWindow, layout.Ui_FixateUI): """ GUI Main window """ # QT Signals # These are the thread safe signals to update UI elements # Multiple Choices/ OK signal sig_choices_input = pyqtSignal(str, tuple) # Updates the test Information above the image sig_label_update = pyqtSignal(str, str) # Signal for the text user input sig_text_input = pyqtSignal(str) # Timer for abort cleanup. TODO Rethink? sig_timer = pyqtSignal() # Tree Events sig_tree_init = pyqtSignal(list) sig_tree_update = pyqtSignal(str, str) # Active Window sig_active_update = pyqtSignal(str) sig_active_clear = pyqtSignal() # History Window sig_history_update = pyqtSignal(str) sig_history_clear = pyqtSignal(str) # Error Window sig_error_update = pyqtSignal(str) sig_error_clear = pyqtSignal(str) # Image Window sig_image_update = pyqtSignal(str) sig_image_clear = pyqtSignal() # Progress Signals sig_indicator_start = pyqtSignal() sig_indicator_stop = pyqtSignal() sig_working = pyqtSignal() sig_progress = pyqtSignal() sig_finish = pyqtSignal() # Deprecated Replace with Active , History and Error Window signals output_signal = pyqtSignal(str, str) # Deprecated replace with Image Window signals update_image = pyqtSignal(str, bool) """Class Constructor and destructor""" def __init__(self, worker, application): super(FixateGUI, self).__init__(None) self.application = application self.register_events() self.setupUi(self) self.treeSet = False self.blocked = False self.closing = False # Extra GUI setup not supported in the designer self.TestTree.setColumnWidth(1, 90) self.TestTree.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch) self.TestTree.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed) self.base_image = "" self.dialog = None self.image_scene = QtWidgets.QGraphicsScene() self.ImageView.set_scene(self.image_scene) self.ImageView.setScene(self.image_scene) self.working_indicator = QtGui.QMovie(QT_GUI_WORKING_INDICATOR) self.WorkingIndicator.setMovie(self.working_indicator) self.start_indicator() self.status_code = -1 # Default status code used to check for unusual exit # Timers and Threads self.input_queue = Queue() self.abort_timer = QtCore.QTimer(self) self.abort_timer.timeout.connect(self.abort_check) self.worker = SequencerThread(worker) self.worker_thread = QThread() self.worker.moveToThread(self.worker_thread) self.worker_thread.started.connect(self.worker.run_thread) self.user_action_queue = None self.abort_queue = None # UI Binds self.Button_1.clicked.connect(self.button_1_click) self.Button_2.clicked.connect(self.button_2_click) self.Button_3.clicked.connect(self.button_3_click) self.UserInputBox.submit.connect(self.text_input_submit) self.bind_qt_signals() sys.excepthook = exception_hook # TODO DEBUG REMOVE def run_sequencer(self): self.worker_thread.start() def closeEvent(self, event): """ This function overrides closeEvent from the MainWindow class, called in case of unusual termination""" event.ignore() self.hide() self.clean_up() def bind_qt_signals(self): """ Binds the qt signals to the appropriate handlers :return: """ # Signal Binds self.sig_finish.connect(self.clean_up) # Normal termination self.sig_choices_input.connect(self.get_input) self.sig_label_update.connect(self.display_test) self.sig_text_input.connect(self.open_text_input) self.sig_timer.connect(self.start_timer) self.sig_tree_init.connect(self.display_tree) self.sig_tree_update.connect(self.update_tree) self.sig_progress.connect(self.progress_update) # New Binds self.sig_indicator_start.connect(self._start_indicator) self.sig_indicator_stop.connect(self._stop_indicator) self.sig_active_update.connect(self._active_update) self.sig_active_clear.connect(self._active_clear) self.sig_history_update.connect(self.history_update) self.sig_history_clear.connect(self.history_clear) self.sig_error_update.connect(self.error_update) self.sig_error_clear.connect(self.error_clear) self.sig_image_update.connect(self._image_update) self.sig_image_clear.connect(self._image_clear) # Deprecated # self.update_image.connect(self.display_image) # self.output_signal.connect(self.display_output) # self.working.connect(self.start_indicator) """Pubsub handlers for setup and teardown These are run in the main thread""" def register_events(self): pub.subscribe(self._seq_abort, "Sequence_Abort") pub.subscribe(self._user_ok, 'UI_req') pub.subscribe(self._user_choices, "UI_req_choices") pub.subscribe(self._user_input, 'UI_req_input') pub.subscribe(self._user_display, 'UI_display') pub.subscribe(self._user_display_important, "UI_display_important") pub.subscribe(self._user_action, 'UI_action') pub.subscribe(self._completion_code, 'Finish') # Image Window pub.subscribe(self.image_update, "UI_image") pub.subscribe(self.image_clear, "UI_image_clear") pub.subscribe(self.image_clear, "UI_block_end") # Active Window pub.subscribe(self.active_clear, "UI_block_end") # Multi Window pub.subscribe(self._print_test_start, 'Test_Start') pub.subscribe(self._print_test_seq_start, 'TestList_Start') pub.subscribe(self._print_test_complete, 'Test_Complete') pub.subscribe(self._print_comparisons, 'Check') pub.subscribe(self._print_errors, "Test_Exception") pub.subscribe(self._print_sequence_end, "Sequence_Complete") pub.subscribe(self._print_test_skip, 'Test_Skip') pub.subscribe(self._print_test_retry, 'Test_Retry') # Error Window # Working Indicator pub.subscribe(self.start_indicator, 'Test_Start') pub.subscribe(self.start_indicator, 'UI_block_end') pub.subscribe(self.stop_indicator, 'UI_block_start') return def unregister_events(self): pub.unsubAll() return """Slot handlers for thread-gui interaction These are run in the main thread""" def start_timer(self): self.abort_timer.start(100) def abort_check(self): if self.abort_queue is None: return try: self.abort_queue.get_nowait() self.abort_queue = None self.button_reset(True) self.abort_timer.stop() except Empty: return def open_text_input(self, message): self.ActiveEvent.append(message) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) self.Events.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) self.UserInputBox.setPlaceholderText("Input:") self.UserInputBox.setEnabled(True) self.UserInputBox.setFocus() def start_indicator(self, **kwargs): self.sig_indicator_start.emit() def _start_indicator(self): self.WorkingIndicator.show() self.working_indicator.start() def stop_indicator(self): self.sig_indicator_stop.emit() def _stop_indicator(self): self.working_indicator.stop() self.WorkingIndicator.hide() def retrieve_packaged_data(self, path): try: return pkgutil.get_data("module.loaded_tests", path) except FileNotFoundError: return b"" def image_update(self, path): self.sig_image_update.emit(path) def _image_update(self, path): """ Adds an image to the image viewer. These images can be stacked with transparent layers to form overlays :param path: Relative path to image within the test scripts package :return: None """ image = QtGui.QPixmap() image.loadFromData(self.retrieve_packaged_data(path)) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) def image_clear(self): self.sig_image_clear.emit() def _image_clear(self): self.image_scene.clear() def display_image(self, path="", overlay=False): if path == "" or not overlay: self.image_scene.clear() if overlay: image = QtGui.QPixmap() image.loadFromData(self.base_image) if image.isNull(): self.file_not_found(self.base_image) elif path == "": self.base_image = path return else: self.base_image = self.retrieve_packaged_data(path) image = QtGui.QPixmap() image.loadFromData(self.base_image) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) return image = QtGui.QPixmap() image.loadFromData(self.retrieve_packaged_data(path)) if image.isNull(): self.file_not_found(path) self.image_scene.addPixmap(image) self.ImageView.fitInView(0, 0, self.image_scene.width(), self.image_scene.height(), QtCore.Qt.KeepAspectRatio) return def file_not_found(self, path): """ Display warning box for an invalid image path :param path: :return: """ self.dialog = QtWidgets.QMessageBox() self.dialog.setText("Warning: Image not Found") self.dialog.setInformativeText("Filename: {}".format(path)) self.dialog.setStandardButtons(QtWidgets.QMessageBox.Ok) self.dialog.setDefaultButton(QtWidgets.QMessageBox.Ok) self.dialog.setIcon(QtWidgets.QMessageBox.Warning) self.dialog.exec() def display_tree(self, tree): # Make sure this function is only run once if self.treeSet: return self.treeSet = True level_stack = [] for item in tree: # Check Level if item[0].count('.') + 1 <= len(level_stack): # Case 1: Going out one or more levels or same level for _ in range(len(level_stack) - item[0].count('.')): level_stack.pop() elif item[0].count('.') + 1 > len(level_stack): # Case 2: Going in one or more levels for index in range(item[0].count('.') + 1 - len(level_stack), 0, -1): split_index = item[0].split('.') if index > 1: # More than one level, append dummy items as required dummy = QtWidgets.QTreeWidgetItem() dummy.setText(0, '.'.join(split_index[:-(index - 1)])) dummy.setText(1, 'Queued') dummy.setTextAlignment(1, QtCore.Qt.AlignRight) level_stack.append(dummy.clone()) tree_item = QtWidgets.QTreeWidgetItem() tree_item.setText(0, item[0] + '. ' + item[1]) tree_item.setTextAlignment(1, QtCore.Qt.AlignRight) tree_item.setText(1, 'Queued') level_stack.append(tree_item.clone()) if len(level_stack) > 1: # Child Add level_stack[-2].addChild(level_stack[-1]) else: # Top Level self.TestTree.addTopLevelItem(level_stack[-1]) def update_tree(self, test_index, status): if len(test_index) == 0: return colours = get_status_colours(status) test_index = test_index.split('.') # Find the test in the tree current_test = self.TestTree.findItems(test_index[0], QtCore.Qt.MatchStartsWith, 0)[0] while len(test_index) > 1: test_index[0:2] = [''.join(test_index[0] + '.' + test_index[1])] for child_index in range(current_test.childCount()): if current_test.child(child_index).text(0).startswith(test_index[0]): current_test = current_test.child(child_index) break # Update the test if status not in ["Aborted"]: for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, status) current_test.setExpanded(True) # In case of an abort, update all remaining tests else: self.active_update("Aborting, please wait...") sub_finish = False original_test = current_test while current_test is not None: if current_test.text(1) in ["Queued"]: for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, status) current_test.setExpanded(False) if current_test.childCount() > 0 and not sub_finish: # Go in a level current_test = current_test.child(0) sub_finish = False elif current_test.parent() is not None: if current_test.parent().indexOfChild( current_test) >= current_test.parent().childCount() - 1: # Come out a level sub_finish = True current_test = current_test.parent() else: current_test = current_test.parent().child( current_test.parent().indexOfChild(current_test) + 1) # Same level sub_finish = False else: # Top level test, go to next test current_test = self.TestTree.topLevelItem(self.TestTree.indexOfTopLevelItem(current_test) + 1) sub_finish = False current_test = original_test # Check for last test in group while current_test.parent() is not None and (current_test.parent().indexOfChild( current_test) >= current_test.parent().childCount() - 1 or status in ["Aborted"]): parent_status = current_test.text(1) current_test = current_test.parent() for child_index in range(current_test.childCount()): # Check status of all child tests check_status = current_test.child(child_index).text(1) if list(STATUS_PRIORITY.keys()).index(check_status) < list(STATUS_PRIORITY.keys()).index(parent_status): parent_status = check_status colours = get_status_colours(parent_status) for i in range(2): current_test.setBackground(i, colours[0]) current_test.setForeground(i, colours[1]) current_test.setText(1, parent_status) if parent_status not in ["In Progress"]: current_test.setExpanded(False) def display_test(self, test_index, description): self.ActiveTest.setText("Test {}:".format(test_index)) self.TestDescription.setText("{}".format(description)) def active_update(self, msg, **kwargs): self.sig_active_update.emit(msg) def _active_update(self, message): self.ActiveEvent.append(message) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def active_clear(self, **kwargs): self.sig_active_clear.emit() def _active_clear(self): self.ActiveEvent.clear() self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def history_update(self, message): self.Events.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) def history_clear(self): self.Events.clear() self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) def error_update(self, message): self.Errors.append(message) self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) def error_clear(self): self.Errors.clear() self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) # def display_output(self, message, status): # self.Events.append(message) # self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) # # if status == "False": # Print errors # self.Errors.append(self.ActiveTest.text() + ' - ' + message[1:]) # self.Errors.verticalScrollBar().setValue(self.Errors.verticalScrollBar().maximum()) # # if status in ["Active", "False"]: # self.ActiveEvent.append(message) # self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) def progress_update(self): self.ActiveEvent.clear() self.ProgressBar.setValue(self.worker.worker.get_current_task()) if self.worker.worker.sequencer.tests_failed > 0 or self.worker.worker.sequencer.tests_errored > 0: self.ProgressBar.setStyleSheet(ERROR_STYLE) def get_input(self, message, choices): self.Events.append(message) self.ActiveEvent.append(message) self.Events.verticalScrollBar().setValue(self.Events.verticalScrollBar().maximum()) self.ActiveEvent.verticalScrollBar().setValue(self.ActiveEvent.verticalScrollBar().maximum()) if isinstance(choices, bool): pass elif len(choices) == 1: self.Button_2.setText(choices[0]) self.Button_2.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_2.setEnabled(True) self.Button_2.setDefault(True) self.Button_2.setFocus() elif len(choices) == 2: self.Button_1.setText(choices[0]) self.Button_1.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_1.setEnabled(True) self.Button_1.setDefault(True) self.Button_1.setFocus() self.Button_3.setText(choices[1]) self.Button_3.setShortcut(QtGui.QKeySequence(choices[1][0:1])) self.Button_3.setEnabled(True) else: self.Button_1.setText(choices[0]) self.Button_1.setShortcut(QtGui.QKeySequence(choices[0][0:1])) self.Button_1.setEnabled(True) self.Button_1.setDefault(True) self.Button_1.setFocus() self.Button_2.setText(choices[1]) self.Button_2.setShortcut(QtGui.QKeySequence(choices[1][0:1])) self.Button_2.setEnabled(True) self.Button_3.setText(choices[2]) self.Button_3.setShortcut(QtGui.QKeySequence(choices[2][0:1])) self.Button_3.setEnabled(True) def _seq_abort(self, exception=None): """ This function ensures that sequence aborting is handled correctly if the sequencer is blocked waiting for input """ # Release user input waiting loops if self.user_action_queue is not None: self.user_action_queue.put(False) self.user_action_queue = None if self.abort_queue is not None: self.abort_queue.put(True) self.abort_queue = None # Release sequence blocking calls if self.blocked: self.input_queue.put("ABORT_FORCE") def clean_up(self): """ This function is the second one called for normal termination, and the first one called for unusual termination. Check for abnormal termination, and stop the sequencer if required; then stop and delete the thread """ if self.worker_thread is None: # This function has already run, therefore main already has the status code return # The following actions must be done in a specific order, be careful when making changes to this section self.abort_timer.stop() self.closing = True if self.status_code == -1: # Unusual termination - The sequencer hasn't finished yet, stop it self.status_code = self.worker.worker.stop() self.unregister_events() # Prevent interruption by pubsub messages self.worker.deleteLater() # Schedule the thread worker for deletion self.worker = None # Remove the reference to allow the GC to clean up self.worker_thread.exit(self.status_code) # Exit the thread self.worker_thread.wait(2000) # 2 seconds for the thread to exit self.worker_thread.terminate() # Force quit the thread if it is still running, if so, this will throw a warning self.worker_thread.deleteLater() # Schedule the thread for deletion self.worker_thread = None # Remove the reference to allow the GC to clean up # Now close the GUI thread, return to the controller in main self.application.exit(self.status_code) """User IO handlers, emit signals to trigger main thread updates via slots. These are run in the sequencer thread""" def event_output(self, message, status="True"): self.output_signal.emit(message, str(status)) def gui_text_input(self, message): self.sig_text_input.emit(message) self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() return result def gui_choices(self, message, choices): self.sig_choices_input.emit(message, choices) self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() return result def gui_user_action_pass_fail(self, message, q, abort): """ Non blocking user call :param message: :param q: :param abort: :return: """ self.sig_choices_input.emit(message, ["PASS", "FAIL"]) self.sig_timer.emit() self.user_action_queue = q self.abort_queue = abort def gui_user_action_fail(self, message, q, abort): self.sig_choices_input.emit(message, ["FAIL"]) self.sig_timer.emit() self.user_action_queue = q self.abort_queue = abort def gui_user_input(self, message, choices=None, blocking=True): result = None if choices is not None: # Button Prompt if blocking: self.sig_choices_input.emit(message, choices) else: self.sig_choices_input.emit(message, (choices[0],)) self.sig_timer.emit() else: # Text Prompt self.sig_text_input.emit(message) if blocking: # Block sequencer until user responds self.blocked = True result = self.input_queue.get(True) self.blocked = False self.sig_working.emit() else: self.user_action_queue = choices[1] self.abort_queue = choices[2] return result """UI Event Handlers, process actions taken by the user on the GUI. These are run in the main thread """ def text_input_submit(self): self.input_queue.put(self.UserInputBox.toPlainText()) self.UserInputBox.clear() self.UserInputBox.setPlaceholderText("") self.UserInputBox.setEnabled(False) def button_1_click(self): self.input_queue.put(self.Button_1.text()) self.button_reset() def button_2_click(self): if self.user_action_queue is not None: self.user_action_queue.put(self.Button_2.text()) self.user_action_queue = None self.abort_timer.stop() self.abort_queue = None else: self.input_queue.put(self.Button_2.text()) self.button_reset() def button_3_click(self): self.input_queue.put(self.Button_3.text()) self.button_reset() def button_reset(self, fail_only=False): self.Button_2.setText("") self.Button_2.setEnabled(False) self.Button_2.setDefault(False) if not fail_only: self.Button_1.setText("") self.Button_3.setText("") self.Button_1.setEnabled(False) self.Button_3.setEnabled(False) self.Button_1.setDefault(False) self.Button_3.setDefault(False) """Thread listener, called from the sequencer thread""" def _completion_code(self, code): """This function is the first one called when the sequencer completes normally. Set the exit code, and signal the main thread.""" self.status_code = code self.sig_finish.emit() """UI Callables, called from the sequencer thread""" def reformat_text(self, text_str, first_line_fill="", subsequent_line_fill=""): lines = [] wrapper.initial_indent = first_line_fill wrapper.subsequent_indent = subsequent_line_fill for ind, line in enumerate(text_str.splitlines()): if ind != 0: wrapper.initial_indent = subsequent_line_fill lines.append(wrapper.fill(line)) return '\n'.join(lines) # def _image(self, path, overlay): # if self.closing: # return # self.update_image.emit(path, overlay) def _user_action(self, msg, q, abort): """ This is for tests that aren't entirely dependant on the automated system. This works by monitoring a queue to see if the test completed successfully. Also while doing this it is monitoring if the escape key is pressed to signal to the system that the test fails. Use this in situations where you want the user to do something (like press all the keys on a keypad) where the system is automatically monitoring for success but has no way of monitoring failure. :param msg: Information for the user :param q: The queue object to put false if the user fails the test :param abort: The queue object to abort this monitoring as the test has already passed. :return: None """ if self.closing: q.put(False) abort.put(True) return self.gui_user_input(self.reformat_text(msg), ("Fail", q, abort), False) def _user_ok(self, msg, q): """ This can be replaced anywhere in the project that needs to implement the user driver The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object :param msg: Message for the user to understand what to do :param q: The result queue of type queue.Queue :return: """ if self.closing: q.put("Result", None) return self.gui_user_input(msg, ("Continue",)) q.put("Result", None) def _user_choices(self, msg, q, choices, target, attempts=5): """ This can be replaced anywhere in the project that needs to implement the user driver Temporarily a simple input function. The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object This needs to be compatible with forced exit. Look to user action for how it handles a forced exit :param msg: Message for the user to understand what to input :param q: The result queue of type queue.Queue :param target: Optional Validation function to check if the user response is valid :param attempts: :return: """ if self.closing: q.put(("Result", "ABORT_FORCE")) return for _ in range(attempts): # This will change based on the interface ret_val = self.gui_user_input(self.reformat_text(msg), choices) ret_val = target(ret_val, choices) if ret_val: q.put(('Result', ret_val)) return q.put('Exception', UserInputError("Maximum number of attempts {} reached".format(attempts))) def _user_input(self, msg, q, target=None, attempts=5, kwargs=None): """ This can be replaced anywhere in the project that needs to implement the user driver Temporarily a simple input function. The result needs to be put in the queue with the first part of the tuple as 'Exception' or 'Result' and the second part is the exception object or response object This needs to be compatible with forced exit. Look to user action for how it handles a forced exit :param msg: Message for the user to understand what to input :param q: The result queue of type queue.Queue :param target: Optional Validation function to check if the user response is valid :param attempts: :param kwargs: :return: """ if self.closing: q.put(('Result', "ABORT_FORCE")) return msg = self.reformat_text(msg) wrapper.initial_indent = "" wrapper.subsequent_indent = "" for _ in range(attempts): # This will change based on the interface ret_val = self.gui_user_input(msg, None, True) if target is None or ret_val == "ABORT_FORCE": q.put(ret_val) return ret_val = target(ret_val, **kwargs) if ret_val: q.put(('Result', ret_val)) return q.put('Exception', UserInputError("Maximum number of attempts {} reached".format(attempts))) def _user_display(self, msg): """ :param msg: :return: """ if self.closing: return self.history_update(self.reformat_text(msg)) def _user_display_important(self, msg): """ :param msg: :return: """ if self.closing: return self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) self.history_update("") self.history_update(self.reformat_text(msg)) self.active_update(self.reformat_text(msg)) self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) def _print_sequence_end(self, status, passed, failed, error, skipped, sequence_status): if self.closing: return self.history_update("#" * wrapper.width) self.history_update(self.reformat_text("Sequence {}".format(sequence_status))) # self.history_update("Sequence {}".format(sequence_status)) post_sequence_info = [] if status == "PASSED": post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info_pass", [])) elif status == "FAILED" or status == "ERROR": post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info_fail", [])) post_sequence_info.extend(RESOURCES["SEQUENCER"].context_data.get("_post_sequence_info", [])) if post_sequence_info: self.history_update("-" * wrapper.width) self.history_update("IMPORTANT INFORMATION") for itm in post_sequence_info: self.history_update(self.reformat_text(itm)) self.history_update("-" * wrapper.width) self.history_update(self.reformat_text("Status: {}".format(status))) self.history_update("#" * wrapper.width) def _print_test_start(self, data, test_index): if self.closing: return self.sig_progress.emit() self.history_update("*" * wrapper.width) self.history_update(self.reformat_text("Test {}: {}".format(test_index, data.test_desc))) self.history_update("-" * wrapper.width) self.sig_label_update.emit(test_index, data.test_desc) self.sig_tree_update.emit(test_index, "In Progress") def _print_test_seq_start(self, data, test_index): if self.closing: return self.ProgressBar.setMaximum(self.worker.worker.get_task_count()) self.sig_tree_init.emit(self.worker.worker.get_test_tree()) self.sig_progress.emit() self._print_test_start(data, test_index) def _print_test_complete(self, data, test_index, status): if self.closing: return sequencer = RESOURCES["SEQUENCER"] self.history_update("-" * wrapper.width) self.history_update( self.reformat_text("Checks passed: {}, Checks failed: {}".format(sequencer.chk_pass, sequencer.chk_fail))) # self.history_update("Checks passed: {}, Checks failed: {}".format(sequencer.chk_pass, sequencer.chk_fail)) self.history_update(self.reformat_text("Test {}: {}".format(test_index, status.upper()))) # self.history_update("Test {}: {}".format(test_index, status.upper())) self.history_update("-" * wrapper.width) if status.upper() in ["ERROR", "SKIPPED"]: return if sequencer.chk_fail == 0: self.sig_tree_update.emit(test_index, "Passed") else: self.sig_tree_update.emit(test_index, "Failed") def _print_test_skip(self, data, test_index): if self.closing: return self.history_update("\nTest Marked as skip") self.sig_tree_update.emit(test_index, "Skipped") def _print_test_retry(self, data, test_index): if self.closing: return self.history_update(self.reformat_text("\nTest {}: Retry".format(test_index))) def _print_errors(self, exception, test_index): if self.closing: return if isinstance(exception, SequenceAbort): self.sig_tree_update.emit(test_index, "Aborted") status = True else: status = False self.sig_tree_update.emit(test_index, "Error") self.history_update("") self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) self.history_update( self.reformat_text("Test {}: Exception Occurred, {} {}".format(test_index, type(exception), exception))) self.active_update( self.reformat_text("Test {}: Exception Occurred, {} {}".format(test_index, type(exception), exception))) self.history_update("!" * wrapper.width) self.active_update("!" * wrapper.width) # TODO self.history_update traceback into a debug log file if fixate.config.DEBUG: traceback.print_tb(exception.__traceback__, file=sys.stderr) def round_to_3_sig_figures(self, chk): """ Tries to round elements to 3 significant figures for formatting :param chk: :return: """ ret_dict = {} for element in ["_min", "_max", "test_val", "nominal", "tol"]: ret_dict[element] = getattr(chk, element, None) try: ret_dict[element] = "{:.3g}".format(ret_dict[element]) except: pass return ret_dict def _print_comparisons(self, passes, chk, chk_cnt, context): if passes: status = "PASS" else: status = "FAIL" format_dict = self.round_to_3_sig_figures(chk) if chk._min is not None and chk._max is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {_min} - {_max} : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) elif chk.nominal is not None and chk.tol is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {nominal} +- {tol}% : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) elif chk._min is not None or chk._max is not None or chk.nominal is not None: # Grabs the first value that isn't none. Nominal takes priority comp_val = next(format_dict[item] for item in ["nominal", "_min", "_max"] if format_dict[item] is not None) msg = self.reformat_text("\nCheck {chk_cnt}: {status} when comparing {test_val} {comparison} {comp_val} : " "{description}".format( status=status, comparison=chk.target.__name__[1:].replace('_', ' '), comp_val=comp_val, chk_cnt=chk_cnt, description=chk.description, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) else: if chk.test_val is not None: msg = self.reformat_text( "\nCheck {chk_cnt}: {status}: {test_val} : {description}".format( chk_cnt=chk_cnt, description=chk.description, status=status, **format_dict)) self.history_update(msg) if status == "FAIL": self.active_update(msg) else: msg = self.reformat_text( "\nCheck {chk_cnt} : {status}: {description}".format(description=chk.description, chk_cnt=chk_cnt, status=status)) self.history_update(msg) if status == "FAIL": self.active_update(msg)
class Window(QMainWindow, Ui_MainWindow): final_duration = 0 final_clip = None video_backend = None video_forend = None bgm = None current_clip = None cutter_start = 0 withaudio = True product_filename = None def __init__(self): super(Window, self).__init__() self.setupUi(self) self.mediaPlayer = QMediaPlayer() self.mediaPlayer.setVideoOutput(self.widget) self.listWidget.setViewMode(QListView.ListMode) self.listWidget.setFlow(QListView.LeftToRight) self.listWidget.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.listWidget.setHorizontalScrollMode(QListWidget.ScrollPerPixel) self.listWidget.setFixedHeight(50) self.pushButton_3.setEnabled(False) self.pushButton_2.setEnabled(False) self.pushButton_4.setEnabled(False) self.pushButton_5.setEnabled(False) self.pushButton_8.setEnabled(False) self.pushButton_9.setEnabled(False) self.pushButton_2.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.horizontalSlider.setRange(0, 0) self.horizontalSlider.sliderMoved.connect(self.setPosition) self.progressBar = QProgressBar() self.progressBar.setVisible(False) self.progressBar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.statusbar.addPermanentWidget(self.progressBar) self.horizontalSlider.setTickPosition(QSlider.TicksBothSides) self.connecctSignals() def connecctSignals(self): self.pushButton_2.clicked.connect(self.play) self.pushButton_4.clicked.connect(self.setStart) self.pushButton_5.clicked.connect(self.changeChannel) self.pushButton.clicked.connect(self.openFore) self.pushButton_7.clicked.connect(self.openBack) self.pushButton_6.clicked.connect(self.openBgm) self.pushButton_3.clicked.connect(self.process) self.pushButton_8.clicked.connect(self.cut) self.pushButton_9.clicked.connect(self.tiktok) self.pushButton_10.clicked.connect(self.add) self.mediaPlayer.stateChanged.connect(self.mediaStateChanged) self.mediaPlayer.positionChanged.connect(self.positionChanged) self.mediaPlayer.durationChanged.connect(self.durationChanged) self.mediaPlayer.error.connect(self.handleError) def add(self): self.listWidget.addItem("item") def cut(self): end = self.mediaPlayer.position() / 1000 if end <= Window.cutter_start: QMessageBox.critical(self, "警告", "请在起始时间之后切分") else: Window.cutter_end = end self.progressBar.setVisible(True) self.threadObject = ProcessObject() self.subThread = QThread() self.threadObject.moveToThread(self.subThread) self.subThread.started.connect(self.threadObject.process_cut) self.threadObject.finish_cut.connect(self.finishCut) self.threadObject.message.connect(self.thread_message) self.threadObject.progress.connect(self.thread_progress) self.subThread.start() self.blockCommand() def fitState(self, state): if state == State.Audio_loaded: self.pushButton_2.setEnabled(True) self.pushButton_3.setEnabled(True) self.pushButton_4.setEnabled(True) self.pushButton_8.setEnabled(True) self.pushButton_5.setEnabled(False) self.pushButton_9.setEnabled(False) elif state == State.Video_loaded: self.pushButton_5.setEnabled(True) self.pushButton_9.setEnabled(True) self.pushButton_2.setEnabled(True) self.pushButton_3.setEnabled(True) self.pushButton_4.setEnabled(True) self.pushButton_8.setEnabled(True) def refreshCommand(self): self.progressBar.setVisible(False) self.pushButton_3.setEnabled(True) self.pushButton_8.setEnabled(True) self.pushButton_4.setEnabled(True) self.pushButton_5.setEnabled(True) self.pushButton_9.setEnabled(True) def blockCommand(self): if self.subThread.isRunning(): self.pushButton_3.setEnabled(False) self.pushButton_8.setEnabled(False) self.pushButton_4.setEnabled(False) self.pushButton_5.setEnabled(False) self.pushButton_9.setEnabled(False) def changeChannel(self): Window.withaudio = not Window.withaudio self.pushButton_5.setText("有声") if Window.withaudio else self.pushButton_5.setText("无声") def setStart(self): start = self.mediaPlayer.position()/1000 Window.cutter_start = start currentTime = QTime((start/3600) % 60, (start/60) % 60, start % 60, (start*1000) % 1000) format = 'hh:mm:ss' if self.mediaPlayer.duration()/1000 > 3600 else 'mm:ss' tStr = currentTime.toString(format) self.statusbar.showMessage("设置起始时间%s" % tStr) def process(self): if Window.video_forend is not None and Window.video_backend is not None and Window.bgm is not None: self.progressBar.setVisible(True) self.threadObject = ProcessObject() self.subThread = QThread() self.threadObject.moveToThread(self.subThread) self.subThread.started.connect(self.threadObject.process_work) self.threadObject.finish_process.connect(self.finishProcess) self.threadObject.message.connect(self.thread_message) self.threadObject.progress.connect(self.thread_progress) self.subThread.start() self.blockCommand() def finishProcess(self): self.cutter_start = 0 self.refreshCommand() self.subThread.exit(0) self.initiateTime() def thread_message(self, value): self.statusbar.showMessage(value) def thread_progress(self, value): self.progressBar.setValue(value) def finishCut(self): self.cutter_start = 0 self.refreshCommand() self.subThread.exit(0) def openFore(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie", QDir.homePath(),"Video(*.mp4;*.wmv;*.rmvb;*.avi)") if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) Window.video_forend = VideoFileClip(fileName) Window.current_clip = Window.video_forend self.fitState(State.Video_loaded) self.initiateTime() def openBack(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Movie", QDir.homePath(), "Video(*.mp4;*.wmv;*.rmvb;*.avi)") if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) Window.video_backend = VideoFileClip(fileName) Window.current_clip = Window.video_backend self.fitState(State.Video_loaded) self.initiateTime() def openBgm(self): fileName, _ = QFileDialog.getOpenFileName(self, "Open Audio", QDir.homePath(), "Audio(*.mp3;*.wav)") if fileName != '': self.mediaPlayer.setMedia( QMediaContent(QUrl.fromLocalFile(fileName))) Window.bgm = AudioFileClip(fileName) Window.current_clip = Window.bgm self.fitState(State.Audio_loaded) self.initiateTime() def play(self): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.mediaPlayer.pause() else: self.mediaPlayer.play() def mediaStateChanged(self, state): if self.mediaPlayer.state() == QMediaPlayer.PlayingState: self.pushButton_2.setIcon( self.style().standardIcon(QStyle.SP_MediaPause)) else: self.pushButton_2.setIcon( self.style().standardIcon(QStyle.SP_MediaPlay)) def initiateTime(self): duration = self.current_clip.duration currentTime = QTime(0,0,0,0) totalTime = QTime((duration / 3600) % 60, (duration / 60) % 60, duration % 60, (duration * 1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) self.label.setText(tStr) self.mediaPlayer.pause() def positionChanged(self, position): self.horizontalSlider.setValue(position) self.updateDurationInfo(position / 1000) def durationChanged(self, duration): self.horizontalSlider.setRange(0, duration) def setPosition(self, position): self.mediaPlayer.setPosition(position) def updateDurationInfo(self, currentInfo): duration = self.mediaPlayer.duration()/1000 if currentInfo or duration: currentTime = QTime((currentInfo/3600) % 60, (currentInfo/60) % 60, currentInfo % 60, (currentInfo*1000) % 1000) totalTime = QTime((duration/3600) % 60, (duration/60) % 60, duration % 60, (duration*1000) % 1000) format = 'hh:mm:ss' if duration > 3600 else 'mm:ss' tStr = currentTime.toString(format) + " / " + totalTime.toString(format) else: tStr = "" self.label.setText(tStr) def tiktok(self): if isinstance(self.current_clip,VideoFileClip): if self.current_clip == Window.video_forend: Window.current_clip = Window.current_clip.fl_image(tiktok_effect) Window.video_forend = Window.current_clip elif self.current_clip == Window.video_backend: Window.current_clip = Window.current_clip.fl_image(tiktok_effect) Window.video_backend = Window.current_clip print("success") def handleError(self): self.statusbar.showMessage(self.mediaPlayer.errorString())
class CorpusClient(QObject): onConnectionEstablished = pyqtSignal(object) onConnectionFailed = pyqtSignal(object) onCommitStarted = pyqtSignal(object, object) onCommitProgress = pyqtSignal(float, str) onCommitFinished = pyqtSignal(object) onCommitFailed = pyqtSignal(object) onDisconnect = pyqtSignal(object) def __init__(self): super(CorpusClient, self).__init__() self.corpus_interface = None self.execution_thread = None self.is_connected = False def mode(self): if isinstance(self.corpus_interface, WebAppCorpusInterface): return "webapp" elif isinstance(self.corpus_interface, LocalCorpusInterface): return "local" else: return None def connect_signals(self): if self.corpus_interface is not None: self.corpus_interface = LocalCorpusInterface @pyqtSlot(object, str) def connect_local(self, user: Contributor, filepath): """ Connects the user to a local Database of VIAN Projects at given database.db file :param user: the user object :param filepath: the file path to the sqlite file :return: """ self.corpus_interface = LocalCorpusInterface() self.execution_thread = QThread() self.corpus_interface.moveToThread(self.execution_thread) self.execution_thread.start() pass @pyqtSlot(object) def connect_webapp(self, user: Contributor): try: if self.execution_thread is not None: self.execution_thread.exit() self.corpus_interface = WebAppCorpusInterface() self.execution_thread = QThread() self.corpus_interface.moveToThread(self.execution_thread) self.execution_thread.start() ret = self.corpus_interface.login(user) if ret['success'] == True: self.onCommitStarted.connect( self.corpus_interface.commit_project) self.is_connected = True return ret except Exception as e: print("Exception in connect webapp", e) return False @pyqtSlot(object) def on_connect_finished(self, result): if result is not None: r = dict(corpus_name=result['corpus_name']) self.onConnectionEstablished.emit(r) else: self.onConnectionFailed.emit(result) pass @pyqtSlot(object, object) def commit(self, project: VIANProject, contributor: Contributor): if self.mode() is not None: self.onCommitStarted.emit(project, contributor) else: self.onCommitFailed.emit(None) def check_project_exists(self, project: VIANProject): return self.corpus_interface.check_project_exists(project) @pyqtSlot(object) def on_commit_finished(self): pass @pyqtSlot(object) def download(self, desc): pass @pyqtSlot(object) def on_download_finished(self): pass def disconnect_corpus(self): if self.corpus_interface is not None: self.corpus_interface.logout() self.is_connected = False self.onDisconnect.emit(dict()) pass
def start_download(self): while True: text = "Die Datenbank wird heruntergeladen.\n\nDies kann einige Minuten dauern ..." Dialog_download = QDialog() ui = Ui_Dialog_processing() ui.setupUi(Dialog_download, text) thread = QThread(Dialog_download) worker = Worker_DownloadDatabase() worker.finished.connect(Dialog_download.close) worker.moveToThread(thread) rsp = thread.started.connect(worker.task) thread.start() thread.exit() Dialog_download.exec() # download_successfull = True if worker.download_successfull == True: text = "Die Datenbank wurde erfolgreich heruntergeladen. LaMA kann ab sofort verwendet werden!" msg = QMessageBox() msg.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) msg.setWindowTitle("Datenbank heruntergeladen") msg.setIcon(QMessageBox.Information) # msg.setWindowIcon(QtGui.QIcon(logo_path)) msg.setText(text) # msg.setDetailedText(detailed_text) # msg.setInformativeText(informative_text) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() break else: text = """ Datenbank konnte nicht heruntergeladen werden. Stellen Sie sicher, dass eine Verbindung zum Internet besteht und versuchen Sie es erneut. Sollte das Problem weiterhin bestehen, melden Sie sich unter [email protected] """ msg = QMessageBox() msg.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) msg.setWindowTitle("Fehler") msg.setIcon(QMessageBox.Critical) # msg.setWindowIcon(QtGui.QIcon(logo_path)) msg.setText(text) # msg.setInformativeText(informative_text) msg.setDetailedText("Error: {}".format(worker.download_successfull)) msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) buttonRepeat = msg.button(QMessageBox.Ok) buttonRepeat.setText("Wiederholen") buttonX = msg.button(QMessageBox.Cancel) buttonX.setText("Abbrechen") rsp = msg.exec_() if rsp == QMessageBox.Cancel: sys.exit(0) else: continue # except Exception as e: # text = """ # Datenbank konnte nicht heruntergeladen werden. Stellen Sie sicher, dass eine Verbindung zum Internet besteht und versuchen Sie es erneut. # Sollte das Problem weiterhin bestehen, melden Sie sich unter [email protected] # """ # msg = QMessageBox() # msg.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint) # msg.setWindowTitle("Fehler") # msg.setIcon(QMessageBox.Critical) # # msg.setWindowIcon(QtGui.QIcon(logo_path)) # msg.setText(text) # # msg.setInformativeText(informative_text) # msg.setDetailedText("Error: {}".format(e)) # msg.setStandardButtons(QMessageBox.Ok) # msg.exec_() # continue print("LaMA wird gestartet ...") self.StartWindow.accept()