Ejemplo n.º 1
0
    def _get_cache_file_path(self, text: str, use_cache: bool) -> str:
        """
        Return file path to where the synthesized audio file will be saved / cached, if cache is
        enabled, otherwise return random file path.
        """
        # TODO: Add cron job which auto purges old recording cached files
        file_hash = hashlib.md5(
            text.encode("utf-8") +
            self.implementation_id.encode("utf-8")).hexdigest()

        file_name = "%s%s" % (file_hash, self.file_extension)

        cache_generated_audio_files = get_config_option("tts",
                                                        "enable_cache",
                                                        "bool",
                                                        fallback=False)

        if cache_generated_audio_files:
            cached_audio_files_path = get_config_option(
                "tts", "cache_directory")
            file_path = os.path.join(cached_audio_files_path, file_name)
        else:
            file_path = os.path.join("/tmp", file_name)

        if self._is_valid_cached_file(file_path=file_path,
                                      use_cache=use_cache):
            return file_path

        return file_path
Ejemplo n.º 2
0
    def test_run_success_sequence_1(self):
        set_config_option("tts",
                          "implementation",
                          "invalid",
                          write_to_disk=True)
        self.assertEqual(get_config_option("tts", "implementation", "str"),
                         "invalid")
        self.assertConfigFileContainsValue(self._config_path, "tts",
                                           "implementation", "invalid")

        plugin = ChangeTTSImplementationAdminPluginForTest()
        self.assertEqual(len(plugin.mock_said_text), 0)

        plugin.initialize(config={})
        plugin.run(sequence="1")

        self.assertEqual(get_config_option("tts", "implementation", "str"),
                         "gtts")
        self.assertConfigFileContainsValue(self._config_path, "tts",
                                           "implementation", "gtts")

        expected_text = "TTS mode changed to online."

        self.assertEqual(len(plugin.mock_said_text), 1)
        self.assertEqual(plugin.mock_said_text[0], expected_text)
Ejemplo n.º 3
0
    def __init__(
        self,
        implementation: str = "gtts",
        **implementation_kwargs: Any,
    ):
        cache_generated_audio_files = get_config_option("tts",
                                                        "enable_cache",
                                                        "bool",
                                                        fallback=False)
        cached_audio_files_path = get_config_option("tts",
                                                    "cache_directory",
                                                    "str",
                                                    fallback=None)

        if cache_generated_audio_files:
            os.makedirs(cached_audio_files_path, exist_ok=True)

        self._implementation = implementation
        self._implementation_kwargs = implementation_kwargs

        if implementation not in self.implementations:
            raise ValueError(
                "Invalid implementation: %s. Valid implementation are: %s" %
                (implementation, ",".join(self.implementations)))

        self._tts = self.implementations[implementation]()  # type: ignore
Ejemplo n.º 4
0
    def __init__(self):
        self._started = False

        self._all_plugins: Dict[str, BasePlugin] = {}
        self._dtmf_plugins: Dict[str, BaseDTMFPlugin] = {}
        self._sequence_to_plugin_map: Dict[str, BaseDTMFPlugin] = {}

        self._emulator_mode = False
        self._dev_mode = False
        self._offline_mode = False

        self._dtmf_decoder = DTMFDecoder()
        self._rx = RX(
            input_device_index=get_config_option("audio", "input_device_index",
                                                 "int"),
            rate=get_config_option("audio", "sample_rate", "int"),
        )
        self._plugin_executor = PluginExecutor(
            implementation=get_config_option("plugins", "executor"))

        self._scheduler = BackgroundScheduler()

        self._cron_jobs_to_run_lock = threading.Lock()
        # Holds a list of ids for cron jobs which should run during the next iteration of the main
        # loop
        self._cron_jobs_to_run: List[str] = []
Ejemplo n.º 5
0
    def __init__(self):
        self._callsign = get_config_option("tx", "callsign")
        self._tx_mode = get_config_option("tx", "mode")
        self._gpio_pin = get_config_option("tx", "gpio_pin", fallback=None)
        self._audio_player = AudioPlayer()

        self._config = {}
Ejemplo n.º 6
0
    def _get_text_format_context(self) -> Dict[str, str]:
        """
        Return a string format context with which each cron job test is rendered with.

        This allows user to reference various dynamic values in this text such as current time,
        date, etc.
        """
        station_id = self._config.get("weather_station_id", "default")
        now_dt = datetime.datetime.utcnow()
        observation_pb = get_weather_observation_for_date(
            station_id=station_id, date=now_dt)

        context = {}
        context["callsign"] = get_config_option("tx",
                                                "callsign",
                                                "str",
                                                fallback="unknown")
        context["day_of_week"] = now_dt.strftime("%A")
        context["date"] = now_dt.strftime("%Y-%m-%d")
        context["time_utc"] = datetime.datetime.utcnow().strftime("%H:%M")
        context["time_local"] = datetime.datetime.now().strftime("%H:%M")

        if observation_pb:
            context["weather_data"] = MessageToDict(observation_pb)
        else:
            context["weather_data"] = {}

        return context
    def test_get_config_option(self):
        os.environ["RADIO_BRIDGE_CONFIG_PATH"] = CONFIG_PATH_1

        value = get_config_option("tx", "callsign", "str")
        self.assertEqual(value, "ABCD")

        value = get_config_option("tx", "invalid", "str", fallback="default")
        self.assertEqual(value, "default")

        value = get_config_option("invalid_section", "callsign", "str", fallback="default1")
        self.assertEqual(value, "default1")

        # fallback not provided
        expected_msg = "Section invalid_section is empty or missing"
        self.assertRaisesRegex(
            ValueError, expected_msg, get_config_option, "invalid_section", "callsign", "str"
        )
Ejemplo n.º 8
0
def write_otps_to_disk(otps: List[str]) -> bool:
    """
    Write provided OTPs to a local db file on disk, overwriting any existing content.
    """
    otps_file_path = get_config_option("plugins", "admin_otps_file_path")

    with open(otps_file_path, "w") as fp:
        fp.write("\n".join(sorted(list(set(otps)))))

    return True
Ejemplo n.º 9
0
    def _is_valid_cached_file(self, file_path: str, use_cache: bool = True):
        cache_generated_audio_files = get_config_option("tts",
                                                        "enable_cache",
                                                        "bool",
                                                        fallback=False)

        if not cache_generated_audio_files or not use_cache:
            return False

        return os.path.isfile(file_path) and os.stat(file_path).st_size > 0
Ejemplo n.º 10
0
    def _parse_and_validate_config(self,
                                   config) -> Dict[str, CronSayItemConfig]:
        result = {}
        for job_id, job_specs in config.items():
            split = job_specs.split(JOB_SPEC_DELIMITER)

            if len(split) != 4:
                raise ValueError('Plugin job specification "%s" is invalid' %
                                 (job_specs))

            job_trigger = split[0]
            job_kwargs_str = split[1]
            job_type = split[2]
            job_value = split[3]

            if job_type not in ["text", "text_to_morse", "morse", "file"]:
                raise ValueError('Unknown job type "%s" for job %s' %
                                 (job_type, job_id))

            trigger_instance = self._get_job_trigger_for_job_spec(
                job_id, job_trigger, job_kwargs_str)
            item = CronSayItemConfig(job_id=job_id,
                                     trigger_instance=trigger_instance,
                                     type=job_type,
                                     value=job_value)

            trigger_interval_seconds = self._get_interval_in_seconds(
                item.trigger_instance)
            dev_mode = get_config_option("main",
                                         "dev_mode",
                                         "bool",
                                         fallback=False)

            if (not dev_mode and trigger_interval_seconds
                    and trigger_interval_seconds < MINIMUM_TRIGGER_INTERVAL):
                raise ValueError(
                    "Requested interval for job %s is %s seconds, but minimum "
                    "allowed value is %s seconds" %
                    (job_id, trigger_interval_seconds,
                     MINIMUM_TRIGGER_INTERVAL))

            if not dev_mode and item.duration > MAXIMUM_PLAYBACK_DURATION:
                raise ValueError(
                    "Calculated audio duration for job %s is longer than maximum "
                    "allowed (%s seconds > %s seconds)" %
                    (job_id, item.duration, MAXIMUM_PLAYBACK_DURATION))

            result[job_id] = item

        return result
Ejemplo n.º 11
0
def get_valid_otps() -> List[str]:
    """
    Return a list of all the OTPs which are still valid (unused) from a local db file on disk.
    """
    otps_file_path = get_config_option("plugins", "admin_otps_file_path")

    valid_otps = []

    if otps_file_path and os.path.isfile(otps_file_path):
        with open(otps_file_path, "r") as fp:
            content = fp.read().strip()

        if content:
            valid_otps = content.splitlines()

    return sorted(valid_otps)
Ejemplo n.º 12
0
 def __init__(self):
     self._max_run_time = get_config_option("plugins",
                                            "max_run_time",
                                            "int",
                                            fallback=None)
Ejemplo n.º 13
0
    def initialize(
        self,
        dev_mode: bool = False,
        emulator_mode: bool = False,
        offline_mode: bool = False,
        debug: bool = False,
    ):
        # 1. Configure logging
        configure_logging(get_config_option("main", "logging_config"),
                          debug=debug)
        wx_server_load_and_parse_config(WX_SERVER_CONFIG_PATH)

        if dev_mode:
            LOG.info("Development mode is active")
            set_config_option("main", "dev_mode", "True")

        if emulator_mode:
            LOG.info("Running in emulator mode")
            set_config_option("main", "emulator_mode", "True")

        if offline_mode:
            LOG.info(
                "Running in offline mode. Only plugins which don't require internet "
                'connection will be available and using "espeak" TTS engine.')
            set_config_option("main", "offline_mode", "True")
            set_config_option("tts", "implementation", "espeak")

        self._emulator_mode = get_config_option("main",
                                                "emulator_mode",
                                                "bool",
                                                fallback=False)
        self._dev_mode = get_config_option("main",
                                           "dev_mode",
                                           "bool",
                                           fallback=False)
        self._offline_mode = get_config_option("main",
                                               "offline_mode",
                                               "bool",
                                               fallback=False)

        # 2. Load and register plugins
        self._all_plugins = get_available_plugins()
        self._dtmf_plugins = get_plugins_with_dtmf_sequence()
        self._sequence_to_plugin_map = self._dtmf_plugins

        # Filter out plugins in offline mode
        # TODO: Move that functionality to get_ methods
        if self._offline_mode:
            self._all_plugins = {
                name: instance
                for (name, instance) in self._all_plugins.items()
                if instance.REQUIRES_INTERNET_CONNECTION is False
            }
            self._dtmf_plugins = {
                name: instance
                for (name, instance) in self._dtmf_plugins.items()
                if instance.REQUIRES_INTERNET_CONNECTION is False
            }

        # 3. Generate OTPs
        all_otps, _ = generate_and_write_otps()
        LOG.info("Generated and unused OTPs for admin commands", otps=all_otps)
Ejemplo n.º 14
0
 def _tts(self):
     # NOTE: We instantiate this object lazily on demand so any changes to the config state made
     # during the program life cycle are reflected here.
     return TextToSpeech(implementation=get_config_option("tts", "implementation"))