Esempio n. 1
0
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 _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)
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 8
0
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
Esempio n. 9
0
    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)
Esempio n. 10
0
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
Esempio n. 11
0
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
Esempio n. 12
0
 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))
Esempio n. 13
0
    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()