def testGetIPOnTarget(base_fixture, mocker): from waqd.base.system import RuntimeSystem # TODO this would be really cool in Docker with an actual system mock_run_on_target(mocker) cur_system = RuntimeSystem() ip4_ref = "192.168.1.274" ip6_ref = "2001:db8:85a3:8d3:1319:8a2e:370:7348" mock_call = mocker.Mock() # check only ip4 mock_call.return_value = ip4_ref.encode("utf-8") mocker.patch('subprocess.check_output', mock_call) [ip4, ip6] = cur_system.get_ip() assert ip4 == ip4_ref assert ip6 == "" # check only ip6 mock_call.return_value = ip6_ref.encode("utf-8") mocker.patch('subprocess.check_output', mock_call) [ip4, ip6] = cur_system.get_ip() assert ip4 == "" assert ip6 == ip6_ref # check both ip4 and ip6 mock_call.return_value = (ip4_ref + " " + ip6_ref).encode("utf-8") mocker.patch('subprocess.check_output', mock_call) [ip4, ip6] = cur_system.get_ip() assert ip4 == ip4_ref assert ip6 == ip6_ref
def testRestart(base_fixture, mocker): from waqd.base.system import RuntimeSystem mock_run_on_target(mocker) mocker.patch('os.system') cur_system = RuntimeSystem() cur_system.restart() # not ideal: confidence through duplication os.system.assert_called_once_with('shutdown -r now') # pylint: disable=no-member
def testInitOnNonTarget(base_fixture, mocker): mock_run_on_non_target(mocker) from waqd.base.system import RuntimeSystem # This test expects, that it is NOT run on a Raspberry Pi cur_system = RuntimeSystem() assert not cur_system.is_target_system if platform.system == "Linux": # works only on linux assert not cur_system.platform is None [ip4, _] = cur_system.get_ip() assert not ip4 is None
def _call_ow_api(self, command: str) -> Dict[str, Any]: """ Call the REST like API of OpenWeatherMap. Return response. """ if self._disabled: return {} # wait a little bit for connection is_connected = RuntimeSystem().wait_for_network() if not is_connected: # TODO error message return {} response = [] if self._cw_json_file and command == self.CURRENT_WEATHER_BY_CITY_ID_API_CMD: response.append(self._cw_json_file) elif self._fc_json_file and command == self.FORECAST_BY_CITY_ID_API_CMD: response.append(self._fc_json_file) else: try: response = urllib.request.urlretrieve( command.format(cid=self._city_id) + self.API_POSTFIX.format(apikey=self._api_key)) except Exception as error: self._logger.error( f"Can't get current weather for {self._city_id} : {str(error)}" ) if not response or not response or not os.path.exists(response[0]): return {} with open(response[0]) as response_json: return json.load(response_json)
def testInitOnTarget(base_fixture, mocker): # Mocks call to get info of Rpi mock_run_on_target(mocker) from waqd.base.system import RuntimeSystem cur_system = RuntimeSystem() assert cur_system.is_target_system # Actual string is platform dependent assert cur_system.platform == "RASPBERRY_PI_4B"
def main(settings_path: Optional[Path] = None): """ Main function, calling setup, loading components and safe shutdown. :param settings_path: Only used for testing to load a settings file. """ # Create user config dir if not config.user_config_dir.exists(): os.makedirs(config.user_config_dir) # System is first, is_target_system is the most basic check runtime_system = RuntimeSystem() if not runtime_system.is_target_system: setup_on_non_target_system() # All other classes depend on settings if not settings_path: settings_path = config.user_config_dir settings = Settings(ini_folder=settings_path) handle_cmd_args(settings) # cmd args set Debug level for logger # to be able to remote debug as much as possible, this call is being done early start_remote_debug() Logger( output_path=config.user_config_dir) # singleton, no assigment needed comp_ctrl = ComponentController(settings) config.comp_ctrl = comp_ctrl #if config.DEBUG_LEVEL > 0: # disable startup sound # comp_ctrl.components.tts.say_internal("startup", [WAQD_VERSION]) display_type = settings.get(DISPLAY_TYPE) if display_type in [DISP_TYPE_RPI, DISP_TYPE_WAVESHARE_5_LCD]: config.qt_app = qt_app_setup(settings) # main_ui must be held in this context, otherwise the gc will destroy the gui loading_sequence(comp_ctrl, settings) try: config.qt_app.exec_() except: # pylint:disable=bare-except trace_back = traceback.format_exc() Logger().error("Application crashed: \n%s", trace_back) elif display_type == DISP_TYPE_WAVESHARE_EPAPER_2_9: pass elif display_type == DISP_TYPE_HEADLESS: comp_ctrl.init_all() comp_ctrl._stop_event.wait() # unload modules - wait for every thread to quit if runtime_system.is_target_system: Logger().info("Prepare to exit...") if comp_ctrl: comp_ctrl.unload_all() while not comp_ctrl.all_unloaded: time.sleep(.1)
def __init__(self, components: "ComponentRegistry" = None, settings: Settings = None): self._comps = components self._settings = settings # TODO remove self._logger = Logger() self._runtime_system = RuntimeSystem() self._reload_forbidden = False # must be set manually in the child class self._disabled = False self._ready = True
def start_remote_debug(): """ Start remote debugging from level 2 and wait on it from level 3""" runtime_system = RuntimeSystem() if config.DEBUG_LEVEL > 1 and runtime_system.is_target_system: import debugpy # pylint: disable=import-outside-toplevel port = 3003 debugpy.listen(("0.0.0.0", port)) if config.DEBUG_LEVEL > 2: print("Waiting to attach on port %s", port) debugpy.wait_for_client( ) # blocks execution until client is attached
def _watch_components(self): """ Checks existence of global variable of each module and starts it. """ # check and restart wifi if RuntimeSystem().is_target_system: RuntimeSystem().check_internet_connection() for comp_name in self._components.get_names(): component = self._components.get(comp_name) if not component: break if issubclass(type(component), CyclicComponent): if component.is_ready and not component.is_alive and not component.is_disabled: # call stop, so it will be initialized in the next cycle self._components.stop_component(comp_name) for sensor_name in self._components.get_sensors(): sensor = self._components.get_sensors()[sensor_name] if not sensor: break if isinstance(sensor, CyclicComponent ) and sensor.is_ready and not sensor.is_alive: self._components.get_sensors().pop(sensor_name)
def testMH_Z19(base_fixture, target_mockup_fixture, mocker): mock_run_on_non_target(mocker) assert not RuntimeSystem().is_target_system settings = Settings(base_fixture.testdata_path / "integration") sensor = sensors.MH_Z19(settings) measure_points = 2 sensor._co2_impl._values_capacity = measure_points time.sleep(1) assert sensor.is_alive assert sensor.is_ready # wait until all measurement points are filled up, so that mean value equals the constant value # -> takes too long, every call spawns a new python process tgis takes a few seconds time.sleep(sensor.UPDATE_TIME + (measure_points + 1)) from mh_z19 import CO2 assert sensor.get_co2() == CO2
def loading_sequence(comp_ctrl: ComponentController, settings: Settings): """ Load modules with watchdog and display Splashscreen until loading is finished. """ # init speech module - this takes a while, so the output will be effectively heard, # when the splash screen is already loading. It is a non-blocking call. # start init for all components comp_ctrl.init_all() app_main_ui = main_ui.WeatherMainUi(comp_ctrl, settings) if RuntimeSystem().is_target_system: # only remove titlebar on RPi app_main_ui.setWindowFlags(Qt.CustomizeWindowHint) # show splash screen splash_screen = SplashScreen() splash_screen.show() # start gui init in separate qt thread thread = QtCore.QThread(app_main_ui) thread.started.connect(app_main_ui.init_gui) thread.start() # wait for finishing loading - processEvents is needed for animations to work (loader) loading_minimum_time = 10 # seconds start = time.time() while (not app_main_ui.ready or not comp_ctrl.all_ready) \ or (time.time() < start + loading_minimum_time) \ and config.DEBUG_LEVEL <= 3: QtWidgets.QApplication.processEvents() # splash screen can be disabled - with fader app_main_ui.show() fade_length = 1 # second fader_widget = FaderWidget( # pylint: disable=unused-variable splash_screen, app_main_ui, length=fade_length * 1000) splash_screen.finish(app_main_ui) return app_main_ui
def _call_tts(self, text, lang): """ Download tts as mp3 and play with VLC. Blocks execution. To be used in a separate thread. """ # remap, if given as setting if lang in LANGS_MAP: lang = LANGS_MAP.get(lang) try: # remove most likeable pitfalls. Is not comprehensive! normalized_text = text.replace(' ', '_').replace( '.', '_').replace('/', '').replace(',', '_').strip() audio_file = self._save_dir / f"{normalized_text}_{lang}.mp3" # only download, if file does not exist if not audio_file.is_file(): RuntimeSystem().wait_for_network() gtts = gTTS(text, lang=lang) gtts.save(audio_file) if self._comps: self._comps.sound.play(audio_file) self._logger.debug("Speech: Finished: %s", text) except Exception as error: self._logger.warning("Speech: Cannot use text to speech engine.: %s", str(error))
def __init__(self, main_ui: "WeatherMainUi", comp_ctrl: ComponentController, settings: Settings): super().__init__() self._main_ui = main_ui # reference to main ui instance self._comp_ctrl = comp_ctrl self._comps = comp_ctrl.components self._settings = settings self._previous_scaling = self._settings.get(FONT_SCALING) self._runtime_system = RuntimeSystem() # create qt base objects self.setWindowFlags( Qt.WindowType(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)) ui_type = uic.loadUiType(config.base_path / "ui" / "qt" / "options.ui") self._ui = ui_type[0]() # 0th element is always the UI class self._ui.setupUi(self) self.setGeometry(main_ui.geometry()) # start fader - variable must be held otherwise gc will claim it fader_widget = FaderWidget(main_ui, self) # pylint: disable=unused-variable # set version label self._ui.version_label.setText(WAQD_VERSION) # set up fix background image self._ui.background_label.setPixmap( QtGui.QPixmap(str(get_asset_file("gui_base", "background-full")))) # display current options self.display_options() # connect buttons on main tab self._ui.ok_button.clicked.connect(self.apply_options) self._ui.cancel_button.clicked.connect(self.close_ui) self._ui.shutdown_button.clicked.connect(self.call_shutdown) self._ui.restart_button.clicked.connect(self.call_restart) self._ui.connect_wlan_button.clicked.connect(self.connect_wlan) self._ui.lang_cbox.currentTextChanged.connect( self._update_language_cbox) self._ui.forecast_background_cbox.currentTextChanged.connect( self._update_preview_forecast) self._ui.interior_background_cbox.currentTextChanged.connect( self._update_preview_interior) self._ui.font_cbox.currentFontChanged.connect(self._update_font) # connect elements on energy saver tab self._ui.night_mode_begin_slider.valueChanged.connect( self._update_night_mode_slider) self._ui.night_mode_begin_slider.sliderMoved.connect( self._update_night_mode_slider) self._ui.night_mode_end_slider.valueChanged.connect( self._update_night_mode_slider) self._ui.night_mode_end_slider.sliderMoved.connect( self._update_night_mode_slider) self._ui.brightness_slider.valueChanged.connect( self._update_brightness_slider) self._ui.brightness_slider.sliderMoved.connect( self._update_brightness_slider) self._ui.motion_sensor_enable_toggle.toggled.connect( self._update_motion_sensor_enabled) # set starting tab to first tab self._ui.options_tabs.setCurrentIndex(0) # set to normal brightness self._comps.display.set_brightness(self._settings.get_int(BRIGHTNESS)) common.scale_gui_elements( self, self._settings.get_float(FONT_SCALING) * self.EXTRA_SCALING) # initialize splash screen for the closing of the UI and make a screenshot self._splash_screen = SplashScreen(background=False) # minimal wait to show the button feedback time.sleep(0.3) self.show()
class OptionMainUi(QtWidgets.QDialog): """ Base class of the options qt ui. Holds the option SubUi element. """ EXTRA_SCALING = 1.15 # make items bigger in this menu # matches strings to seconds in dropdown of timeouts TIME_CBOX_VALUES = [5, 30, 120, 600, 1800] # seconds FONT_SCALING_VALUES = [1, 1.2, 1.4] DHT_PIN_VALUES = ["Disabled", "4", "8", "22"] def __init__(self, main_ui: "WeatherMainUi", comp_ctrl: ComponentController, settings: Settings): super().__init__() self._main_ui = main_ui # reference to main ui instance self._comp_ctrl = comp_ctrl self._comps = comp_ctrl.components self._settings = settings self._previous_scaling = self._settings.get(FONT_SCALING) self._runtime_system = RuntimeSystem() # create qt base objects self.setWindowFlags( Qt.WindowType(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)) ui_type = uic.loadUiType(config.base_path / "ui" / "qt" / "options.ui") self._ui = ui_type[0]() # 0th element is always the UI class self._ui.setupUi(self) self.setGeometry(main_ui.geometry()) # start fader - variable must be held otherwise gc will claim it fader_widget = FaderWidget(main_ui, self) # pylint: disable=unused-variable # set version label self._ui.version_label.setText(WAQD_VERSION) # set up fix background image self._ui.background_label.setPixmap( QtGui.QPixmap(str(get_asset_file("gui_base", "background-full")))) # display current options self.display_options() # connect buttons on main tab self._ui.ok_button.clicked.connect(self.apply_options) self._ui.cancel_button.clicked.connect(self.close_ui) self._ui.shutdown_button.clicked.connect(self.call_shutdown) self._ui.restart_button.clicked.connect(self.call_restart) self._ui.connect_wlan_button.clicked.connect(self.connect_wlan) self._ui.lang_cbox.currentTextChanged.connect( self._update_language_cbox) self._ui.forecast_background_cbox.currentTextChanged.connect( self._update_preview_forecast) self._ui.interior_background_cbox.currentTextChanged.connect( self._update_preview_interior) self._ui.font_cbox.currentFontChanged.connect(self._update_font) # connect elements on energy saver tab self._ui.night_mode_begin_slider.valueChanged.connect( self._update_night_mode_slider) self._ui.night_mode_begin_slider.sliderMoved.connect( self._update_night_mode_slider) self._ui.night_mode_end_slider.valueChanged.connect( self._update_night_mode_slider) self._ui.night_mode_end_slider.sliderMoved.connect( self._update_night_mode_slider) self._ui.brightness_slider.valueChanged.connect( self._update_brightness_slider) self._ui.brightness_slider.sliderMoved.connect( self._update_brightness_slider) self._ui.motion_sensor_enable_toggle.toggled.connect( self._update_motion_sensor_enabled) # set starting tab to first tab self._ui.options_tabs.setCurrentIndex(0) # set to normal brightness self._comps.display.set_brightness(self._settings.get_int(BRIGHTNESS)) common.scale_gui_elements( self, self._settings.get_float(FONT_SCALING) * self.EXTRA_SCALING) # initialize splash screen for the closing of the UI and make a screenshot self._splash_screen = SplashScreen(background=False) # minimal wait to show the button feedback time.sleep(0.3) self.show() def display_options(self): """ Set all elements to the display the current values and set up sliders """ settings = self._settings # read events events_json_path = config.user_config_dir / "events.json" if events_json_path.exists(): with open(events_json_path, "r") as fp: events_text = fp.read() self._ui.event_config_text_browser.setText(events_text) self._ui.night_mode_begin_slider.setMaximum(24) self._ui.night_mode_begin_slider.setTickInterval(1) self._ui.night_mode_begin_value.setText( str(settings.get(NIGHT_MODE_BEGIN))) self._ui.night_mode_begin_slider.setSliderPosition( settings.get(NIGHT_MODE_BEGIN)) self._ui.night_mode_end_slider.setMaximum(24) self._ui.night_mode_end_slider.setTickInterval(1) self._ui.night_mode_end_value.setText(str( settings.get(NIGHT_MODE_END))) self._ui.night_mode_end_slider.setSliderPosition( settings.get(NIGHT_MODE_END)) self._ui.brightness_slider.setMaximum(100) self._ui.brightness_slider.setMinimum(30) self._ui.brightness_slider.setTickInterval(5) self._ui.brightness_value.setText(str(settings.get(BRIGHTNESS))) self._ui.brightness_slider.setSliderPosition(settings.get(BRIGHTNESS)) self._ui.events_enable_toggle.setChecked(settings.get(EVENTS_ENABLED)) self._ui.auto_updater_enable_toggle.setChecked( settings.get(AUTO_UPDATER_ENABLED)) self._ui.sensor_logging_enable_toggle.setChecked( settings.get(LOG_SENSOR_DATA)) self._ui.updater_beta_channel_toggle.setChecked( settings.get(UPDATER_USER_BETA_CHANNEL)) # themes # set background images bgr_path = config.assets_path / "gui_bgrs" for bgr_file in bgr_path.iterdir(): self._ui.interior_background_cbox.addItem(bgr_file.name) self._ui.forecast_background_cbox.addItem(bgr_file.name) self._ui.interior_background_cbox.setCurrentText( settings.get(INTERIOR_BG)) self._ui.forecast_background_cbox.setCurrentText( settings.get(FORECAST_BG)) self._ui.font_cbox.setCurrentText(settings.get(FONT_NAME)) # try to get index - font-scaling can be set to anything try: scaling_index = self.FONT_SCALING_VALUES.index( self._previous_scaling) except Exception: # normal scaling_index = 1 self._ui.font_scaling_cbox.setCurrentIndex(scaling_index) # hw feature toggles self._ui.sound_enable_toggle.setChecked(settings.get(SOUND_ENABLED)) self._ui.motion_sensor_enable_toggle.setChecked( settings.get(MOTION_SENSOR_ENABLED)) self._ui.ccs811_enable_toggle.setChecked(settings.get(CCS811_ENABLED)) self._ui.bmp280_enable_toggle.setChecked(settings.get(BMP_280_ENABLED)) self._ui.bme280_enable_toggle.setChecked(settings.get(BME_280_ENABLED)) self._ui.mh_z19_enable_toggle.setChecked(settings.get(MH_Z19_ENABLED)) # set up DHT22 combo box self._ui.dht22_pin_cbox.addItems(self.DHT_PIN_VALUES) # 0 is not in the list and means it is disabled, so it maps to 'Disabled' if str(settings.get(DHT_22_PIN)) not in self.DHT_PIN_VALUES: selected_pin = 0 else: selected_pin = self.DHT_PIN_VALUES.index( str(settings.get(DHT_22_PIN))) self._ui.dht22_pin_cbox.setCurrentIndex(selected_pin) # set up display type combo box display_dict = { DISP_TYPE_RPI: "Org. RPi 7\" display", DISP_TYPE_WAVESHARE_5_LCD: "Waveshare touch LCD" } self._ui.display_type_cbox.setDisabled(True) self._ui.display_type_cbox.addItems(display_dict.values()) try: self._ui.display_type_cbox.setCurrentIndex( list(display_dict.values()).index( display_dict.get(settings.get(DISPLAY_TYPE)))) self._ui.day_standby_timeout_cbox.setCurrentIndex( self.TIME_CBOX_VALUES.index(settings.get(DAY_STANDBY_TIMEOUT))) self._ui.night_standby_timeout_cbox.setCurrentIndex( self.TIME_CBOX_VALUES.index( settings.get(NIGHT_STANDBY_TIMEOUT))) except Exception: # leave default pass # enable / disable standby based on motion sensor self._ui.day_standby_timeout_cbox.setEnabled( settings.get(MOTION_SENSOR_ENABLED)) self._ui.night_standby_timeout_cbox.setEnabled( settings.get(MOTION_SENSOR_ENABLED)) # populate location dropdown- only ow for now for city in settings.get(OW_CITY_IDS): self._ui.location_combo_box.addItem(city) # set info labels self._ui.system_value.setText(self._runtime_system.platform) [ipv4, _] = self._runtime_system.get_ip() self._ui.ip_address_value.setText(ipv4) self._ui.location_combo_box.setCurrentText(settings.get(LOCATION)) self._ui.lang_cbox.setCurrentText(settings.get(LANG)) # set to normal brightness - again, in case it was modified self._comps.display.set_brightness(self._settings.get(BRIGHTNESS)) def close_ui(self): """ Transition back to Main Ui. Restart unloaded components and re-init main Gui. Shows splashscreen. """ # minimal wait to show the button feedback time.sleep(0.3) self.hide() # this splash screen works only in full screen under RPI - god knows why... if self._runtime_system.is_target_system: self._splash_screen.showFullScreen() self._splash_screen.setFocus() else: self._splash_screen.show() loading_minimum_time = 3 # seconds start = time.time() while not self._comp_ctrl.all_unloaded or ( time.time() < start + loading_minimum_time): config.qt_app.processEvents() self._comp_ctrl.init_all() while not self._comp_ctrl.all_ready: config.qt_app.processEvents() self._main_ui.init_gui() start = time.time() while not self._main_ui.ready or (time.time() < start + loading_minimum_time): config.qt_app.processEvents() self._splash_screen.finish(self._main_ui) self.close() def apply_options(self): """ Callback of Apply Options button. Write back every option and close ui. """ settings = self._settings settings.set(LOCATION, self._ui.location_combo_box.currentText()) settings.set(LANG, self._ui.lang_cbox.currentText()) settings.set( DAY_STANDBY_TIMEOUT, self.TIME_CBOX_VALUES[ self._ui.day_standby_timeout_cbox.currentIndex()]) settings.set( NIGHT_STANDBY_TIMEOUT, self.TIME_CBOX_VALUES[ self._ui.night_standby_timeout_cbox.currentIndex()]) settings.set(EVENTS_ENABLED, self._ui.events_enable_toggle.isChecked()) settings.set(LOG_SENSOR_DATA, self._ui.sensor_logging_enable_toggle.isChecked()) settings.set(UPDATER_USER_BETA_CHANNEL, self._ui.updater_beta_channel_toggle.isChecked()) settings.set(AUTO_UPDATER_ENABLED, self._ui.auto_updater_enable_toggle.isChecked()) settings.set(SOUND_ENABLED, self._ui.sound_enable_toggle.isChecked()) settings.set(NIGHT_MODE_BEGIN, self._ui.night_mode_begin_slider.sliderPosition()) settings.set(NIGHT_MODE_END, self._ui.night_mode_end_slider.sliderPosition()) settings.set(BRIGHTNESS, self._ui.brightness_slider.sliderPosition()) settings.set(INTERIOR_BG, self._ui.interior_background_cbox.currentText()) settings.set(FORECAST_BG, self._ui.forecast_background_cbox.currentText()) settings.set(FONT_NAME, self._ui.font_cbox.currentText()) if self._ui.dht22_pin_cbox.currentText( ) == self.DHT_PIN_VALUES[0]: # "Disabled" settings.set(DHT_22_PIN, DHT_22_DISABLED) else: settings.set(DHT_22_PIN, int(self._ui.dht22_pin_cbox.currentText())) settings.set(MOTION_SENSOR_ENABLED, self._ui.motion_sensor_enable_toggle.isChecked()) settings.set(BME_280_ENABLED, self._ui.bme280_enable_toggle.isChecked()) settings.set(BMP_280_ENABLED, self._ui.bmp280_enable_toggle.isChecked()) settings.set(MH_Z19_ENABLED, self._ui.mh_z19_enable_toggle.isChecked()) settings.set(CCS811_ENABLED, self._ui.ccs811_enable_toggle.isChecked()) font_scaling = self.FONT_SCALING_VALUES[ self._ui.font_scaling_cbox.currentIndex()] self._settings.set(FONT_SCALING, font_scaling) # write them to file settings.save_all_options() self.close_ui() def _update_language_cbox(self): """ Change language in the current ui on value change. """ self._settings.set(LANG, self._ui.lang_cbox.currentText()) common.set_ui_language(config.qt_app, self._settings) self._ui.retranslateUi(self) self.display_options() # redraw values def _update_font(self): font = self._ui.font_cbox.currentFont() self.setFont(font) config.qt_app.setFont(font) def _update_preview_interior(self): bgr_path = config.assets_path / "gui_bgrs" / self._ui.interior_background_cbox.currentText( ) self._ui.preview_label.setPixmap(QtGui.QPixmap(str(bgr_path))) def _update_preview_forecast(self): bgr_path = config.assets_path / "gui_bgrs" / self._ui.forecast_background_cbox.currentText( ) self._ui.preview_label.setPixmap(QtGui.QPixmap(str(bgr_path))) def _update_night_mode_slider(self): """ Callback to update value shown on night mode time silder. """ self._ui.night_mode_begin_value.setText( str(self._ui.night_mode_begin_slider.sliderPosition())) self._ui.night_mode_end_value.setText( str(self._ui.night_mode_end_slider.sliderPosition())) def _update_brightness_slider(self): """ Callback to update value shown on brightness silder. """ self._ui.brightness_value.setText( str(self._ui.brightness_slider.sliderPosition())) self._comps.display.set_brightness( self._ui.brightness_slider.sliderPosition()) def _update_motion_sensor_enabled(self): """ Callback to disable the timeout checkboxes, if motion sensor is checked. """ value = bool(self._ui.motion_sensor_enable_toggle.isChecked()) self._ui.day_standby_timeout_cbox.setEnabled(value) self._ui.night_standby_timeout_cbox.setEnabled(value) def call_shutdown(self): """ Callback to shut down the target system. """ self._runtime_system.shutdown() self.close_ui() def call_restart(self): """ Callback to restart the target system. """ self._runtime_system.restart() self.close_ui() def show_updater_ui(self): if self._runtime_system.is_target_system: # this is the default updater on RaspberryPi OS os.system( "sudo apt update") # TODO this takes a while, but is necessary os.system("pi-gpk-update-viewer&") def connect_wlan(self): ssid_name = "Connect_WAQD" msg = QtWidgets.QMessageBox(parent=self) msg.setStandardButtons(QtWidgets.QMessageBox.Ok) msg.setWindowFlags(Qt.WindowType(Qt.CustomizeWindowHint)) try: check_call(f'sudo wifi-connect -s "{ssid_name}"') msg.setIcon(QtWidgets.QMessageBox.Information) msg.setWindowTitle("Connect to WLAN") msg.setText( f"Connect to WLAN '{ssid_name}'' on your phone or pc, where you can select your network and enter your password!" ) except Exception as e: msg.setIcon(QtWidgets.QMessageBox.Warning) msg.setWindowTitle("Error while opening connection to WLAN") msg.setText(f"Cannot start WLAN connection portal: {str(e)}") # needed because of CustomizeWindowHint msg.move(int((self._main_ui.geometry().width() - self.height()) / 2), int((self._main_ui.geometry().height() - msg.height()) / 2)) msg.exec_() def _cyclic_update(self): pass