Esempio n. 1
0
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
Esempio n. 2
0
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_()
Esempio n. 3
0
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
Esempio n. 4
0
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)
Esempio n. 5
0
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))
Esempio n. 6
0
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])
Esempio n. 7
0
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
Esempio n. 8
0
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)))
Esempio n. 9
0
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)
Esempio n. 10
0
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)
Esempio n. 13
0
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
Esempio n. 15
0
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()
Esempio n. 16
0
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())
Esempio n. 18
0
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
Esempio n. 19
0
    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()