def showfor(icao: list):
    """
    Show ATIS info for a specific airfield
    """
    icao_str = ''.join(icao).upper()
    try:
        info = get_info_for_icao(icao_str)
    except KeyError:
        LOGGER.error('ICAO not found in the list of currently active ATIS:  %s', icao_str)
        return
    if core.Status.metar == 'unknown':
        LOGGER.error('no weather information available at this time')
        return
    running = 'running' if URVoiceService.is_running() else 'not running'
    # type: ignore
    _weather = core.Status.metar.as_str()  # pylint: disable=no-member
    # type: ignore
    _metar = core.Status.metar.raw_metar_str  # pylint: disable=no-member
    info_str = f'UR voice service is {running}\n\n' \
               f'METAR:\n{_metar}\n\n' \
               f'Weather:\n{_weather}\n\n' \
               f'Active runway: {info.active_runway}\n' \
               f'Information ID: {info.info_letter}\n' \
               f'ATIS speech: {core.CTX.atis_speech}'
    LOGGER.info(info_str)
예제 #2
0
def get_latest_mission_from_github():
    """
    Downloads the latest mission from a Github repository

    The repository needs to have releases (tagged)
    The function will download the first MIZ file found in the latest release
    """
    if core.CTX.dcs_auto_mission:
        LOGGER.debug('getting latest mission from Github')
        commands.DCS.block_start('loading mission')
        if DCSConfig.DCS_AUTO_MISSION_GH_OWNER(
        ) and DCSConfig.DCS_AUTO_MISSION_GH_REPO():
            LOGGER.debug('looking for newer mission file')
            latest_version, asset_name, download_url = utils.get_latest_release(
                DCSConfig.DCS_AUTO_MISSION_GH_OWNER(),
                DCSConfig.DCS_AUTO_MISSION_GH_REPO())
            LOGGER.debug('latest release: %s', latest_version)
            local_file = MissionPath(Path(_get_mission_folder(), asset_name))
            if not local_file:
                LOGGER.info('downloading new mission: %s', asset_name)
                req = requests.get(download_url)
                if req.ok:
                    local_file.path.write_bytes(req.content)
                    local_file.set_as_active()
                else:
                    LOGGER.error('failed to download latest mission')
        else:
            LOGGER.warning('no config values given for [auto mission]')
        commands.DCS.unblock_start('loading mission')
    else:
        LOGGER.debug('skipping mission update')
예제 #3
0
def _write_dedi_config():
    dedi_cfg_path = Path(FS.variant_saved_games_path, 'Config/dedicated.lua')
    if not dedi_cfg_path.exists():
        LOGGER.info('writing %s', dedi_cfg_path)
        dedi_cfg_path.write_text(DEDI_CFG)
    else:
        LOGGER.debug('file already exists: %s', dedi_cfg_path)
def show():
    """
    Shows ICAO & frequencies for the ATIS
    """
    output = ['List of ATIS frequencies:']
    for airfield in ALL_AIRFIELDS:
        output.append(f'{airfield.icao} {airfield.name}: {airfield.atis_freq.long_freq()}')
    LOGGER.info('\n'.join(output))
예제 #5
0
 def _queue_kill(queue: Queue):
     while DCS.there_are_connected_players():
         if not queue.empty():
             queue.get_nowait()
             LOGGER.debug('queued DCS kill has been cancelled')
             return
         time.sleep(5)
     LOGGER.info('executing planned DCS restart')
     DCS.kill()
예제 #6
0
 def start_service():
     """
     Starts UR voice service
     """
     exe_path = Path(FS.ur_install_path, PROC_NAME)
     if not exe_path.exists():
         raise FileNotFoundError(exe_path)
     LOGGER.info('starting UR voice service: %s', exe_path)
     os.startfile(str(exe_path))  # nosec
     URVoiceService.is_running()
예제 #7
0
def sigint_handler(*_):
    """
    Catches exit signal (triggered byu CTRL+C)

    Args:
        *_: frame

    """
    from esst import LOGGER, core
    LOGGER.info('ESST has been interrupted by user request, shutting down')
    core.CTX.exit = True
예제 #8
0
def init_atis_module():
    """
    Initialize the ATIS module
    """
    LOGGER.info('initializing ATIS module')
    discover_ur_install_path()
    if CTX.sentry:
        LOGGER.debug('registering ATIS contexts for Sentry')
        CTX.sentry.register_context(context_name='ATIS', context_provider=_ATISStatus)
        CTX.sentry.register_context(context_name='UR', context_provider=_URStatus)
    elib_wx.Config.dummy_icao_code = ATISConfig.DEFAULT_ICAO()
예제 #9
0
 def __init__(self):
     LOGGER.info('initializing Sentry')
     # noinspection SpellCheckingInspection
     dsn = 'https://*****:*****@sentry.io/206995'
     self.registered_contexts = {}
     raven.Client.__init__(
         self,
         f'{dsn}?ca_certs={certifi.where()}',
         release=__version__,
     )
     if self.is_enabled():
         LOGGER.info('Sentry is ready')
예제 #10
0
def initial_setup():
    """
    Runs at the start of the DCS loop, to initialize the first mission
    """
    LOGGER.debug('initializing first mission')
    mission = get_running_mission()
    if isinstance(mission, MissionPath):
        LOGGER.info('building METAR for initial mission: %s',
                    mission.orig_name)
        weather = elib_wx.Weather(str(mission.path))
        core.Status.metar = weather
        esst.atis.create.generate_atis(weather)
    else:
        LOGGER.error('no initial mission found')
예제 #11
0
    async def kill_running_app(self):  # noqa: C901
        """
        Kills the running DCS.exe process
        """
        async def _ask_politely():
            if not self.app or not self.app.is_running():
                return True
            LOGGER.debug('sending socket command to DCS for graceful exit')
            commands.LISTENER.exit_dcs()
            await asyncio.sleep(1)
            LOGGER.debug(
                'waiting on DCS to close itself (grace period: %s seconds)',
                DCSConfig.DCS_CLOSE_GRACE_PERIOD())
            now_ = utils.now()
            while self.app.is_running():
                await asyncio.sleep(1)
                if utils.now() - now_ > DCSConfig.DCS_CLOSE_GRACE_PERIOD():
                    LOGGER.debug('grace period time out!')
                    return False

            LOGGER.info('DCS closed itself, nice')
            return True

        async def _no_more_mr_nice_guy():
            if not self.app or not self.app.is_running():
                return True
            LOGGER.debug('killing dcs.exe application')
            self.app.kill()
            now_ = utils.now()
            while self.app.is_running():
                await asyncio.sleep(1)
                if utils.now() - now_ > 10:
                    return False

            return True

        core.CTX.dcs_do_kill = False
        await self._check_if_dcs_is_running()
        if not self.app or not self.app.is_running():
            LOGGER.debug('DCS process was not running')
            return
        LOGGER.info('closing DCS')
        if not await _ask_politely():
            LOGGER.info('DCS will not exit gracefully, killing it')
            if not await _no_more_mr_nice_guy():
                LOGGER.error('I was not able to kill DCS, something is wrong')
                raise RuntimeError()
        await self._check_if_dcs_is_running()
예제 #12
0
def delete(mission: MissionPath):
    """
    Removes a mission from the filesystem.

    Also removes leftover AUTO artifacts

    Args:
        mission: MissionPath instance to remove

    """
    if mission:
        LOGGER.info('removing: %s', mission.path)
        mission.path.unlink()
    if mission.auto:
        LOGGER.info('removing: %s', mission.auto.path)
        mission.auto.path.unlink()
예제 #13
0
 def kill():
     """
     Kills UR voice service
     """
     if URVoiceService.is_running():
         try:
             proc = psutil.Process(URVoiceService.pid)
             LOGGER.info('killing UR voice service')
             proc.terminate()
             while URVoiceService.is_running():
                 LOGGER.debug('waiting on UR voice service to close')
                 time.sleep(1)
         except FileNotFoundError:
             pass
         except psutil.NoSuchProcess:
             pass
예제 #14
0
        async def _ask_politely():
            if not self.app or not self.app.is_running():
                return True
            LOGGER.debug('sending socket command to DCS for graceful exit')
            commands.LISTENER.exit_dcs()
            await asyncio.sleep(1)
            LOGGER.debug(
                'waiting on DCS to close itself (grace period: %s seconds)',
                DCSConfig.DCS_CLOSE_GRACE_PERIOD())
            now_ = utils.now()
            while self.app.is_running():
                await asyncio.sleep(1)
                if utils.now() - now_ > DCSConfig.DCS_CLOSE_GRACE_PERIOD():
                    LOGGER.debug('grace period time out!')
                    return False

            LOGGER.info('DCS closed itself, nice')
            return True
예제 #15
0
 async def restart(self):
     """
     Restarts DCS application
     """
     if core.CTX.exit:
         LOGGER.debug('restart interrupted by exit signal')
         return
     core.CTX.dcs_do_restart = False
     LOGGER.info('restarting DCS')
     await self.kill_running_app()
     core.Status.mission_file = 'unknown'
     core.Status.server_age = 'unknown'
     core.Status.mission_time = 'unknown'
     core.Status.paused = 'unknown'
     core.Status.mission_name = 'unknown'
     core.Status.players = []
     self._app = None
     self.process_pid = None
     cmd_chain = [
         self._start_new_dcs_application_if_needed,
     ]
     await self._execute_cmd_chain(cmd_chain)
예제 #16
0
    def set_as_active(self, weather: elib_wx.Weather = None):
        """
        Write the settings file to set this mission as active

        :param weather: current weather; if not provided,  will be inferred from MIZ file
        :type weather: elib_wx.Weather
        """

        LOGGER.info('setting active mission to: %s', self.name)
        if not self:
            LOGGER.error('mission file not found: %s', self.path)
            return

        write_server_settings(str(self.path).replace('\\', '/'))

        if weather is None:
            LOGGER.debug('building metar from mission: %s', self.name)
            # noinspection SpellCheckingInspection
            weather = elib_wx.Weather(str(self.path))
            LOGGER.info('metar for %s:\n%s', self.name, weather)
        else:
            esst.atis.create.generate_atis(weather)
        core.Status.metar = weather
예제 #17
0
def download_mission_from_discord(discord_attachment,
                                  overwrite: bool = False,
                                  load: bool = False,
                                  force: bool = False):
    """
    Downloads a mission from a discord message attachment

    Args:
        force: force restart even with players connected
        discord_attachment: url to download the mission from
        overwrite: whether or not to overwrite an existing file
        load: whether or not to restart the server with the downloaded mission
    """
    url = discord_attachment['url']
    size = discord_attachment['size']
    filename = discord_attachment['filename']
    local_file = MissionPath(Path(_get_mission_folder(), filename))

    overwriting = ''
    if local_file:
        if overwrite:
            overwriting = ' (replacing existing file)'
        else:
            LOGGER.warning(
                'this mission already exists: %s\nuse "overwrite" to replace it',
                local_file.path)
            return

    LOGGER.info(
        'downloading: %s (%s) %s',
        filename,
        humanize.naturalsize(size),
        overwriting,
    )
    with requests.get(url) as response:
        local_file.path.write_bytes(response.content)

    if load:
        if commands.DCS.there_are_connected_players() and not force:
            LOGGER.error(
                'there are connected players; cannot restart the server now (use "force" to kill anyway)'
            )
            return
        LOGGER.info('restarting the server with this mission')
        local_file.set_as_active()
        commands.DCS.restart(force=force)
    else:
        LOGGER.info('download successful, mission is now available')
예제 #18
0
    def _parse_ping(self, data: dict):
        if Status.paused != data.get('paused'):
            if not data.get('paused'):
                LOGGER.info('DCS server is ready!')
        players = data.get('players', set())
        if players != Status.players:
            players, old_players = set(players), set(Status.players)
            joined = players - old_players
            left = old_players - players
            if joined:
                LOGGER.info('player(s) joined: %s', ', '.join(joined))
            if left:
                LOGGER.info('player(s) left: %s', ', '.join(left))
        self.last_ping = time.time()
        Status.server_age = data.get('time', 'unknown')
        Status.mission_time = data.get('model_time', 'unknown')
        Status.paused = data.get('paused', 'unknown')
        Status.mission_file = data.get('mission_filename', 'unknown')
        Status.mission_name = data.get('mission_name', 'unknown')
        Status.players = data.get('players', set())

        CTX.players_history.append((now(), len(Status.players)))
def status():
    """
    Show UR voice service status
    """
    status_ = 'running' if URVoiceService.is_running() else 'not running'
    LOGGER.info('UniversRadio voice service is  %s', status_)
예제 #20
0
def generate_atis(weather: elib_wx.Weather,
                  include_icao: typing.List[str] = None,
                  exclude_icao: typing.List[str] = None):
    """
    Create MP3 from METAR
    """
    if not ATISConfig.ATIS_CREATE():
        LOGGER.info('skipping ATIS creation as per config')
        return
    atis_dir = Path('atis').absolute()
    if not atis_dir.exists():
        LOGGER.info('creating ATIS dir: %s', atis_dir)
        atis_dir.mkdir()
    URVoiceService.kill()

    LOGGER.info('creating ATIS from METAR: %s', weather.raw_metar_str)

    wind_dir = int(weather.wind_direction.value())
    LOGGER.debug('wind direction: %s', wind_dir)

    speech_atis = weather.as_speech()
    core.CTX.atis_speech = speech_atis
    LOGGER.debug('ATIS speech: %s', speech_atis)

    ur_settings = URVoiceServiceSettings()

    if include_icao:
        include_icao = [icao.upper() for icao in include_icao]

    if exclude_icao:
        exclude_icao = [icao.upper() for icao in exclude_icao]

    threads = []
    atis_queue: queue.Queue = queue.Queue()
    for airfield in ALL_AIRFIELDS:
        if core.CTX.exit:
            break
        if include_icao and airfield.icao.upper() not in include_icao:
            LOGGER.debug('%s: skipping (not included)', airfield.icao)
            continue
        if exclude_icao and airfield.icao.upper() in exclude_icao:
            LOGGER.debug('%s: skipping (excluded)', airfield.icao)
            continue

        thread = threading.Thread(target=_build_speech_for_airfield,
                                  args=(airfield, wind_dir, speech_atis,
                                        ur_settings, atis_queue))
        threads.append(thread)
        thread.start()
    for job in threads:
        job.join()

    _update_status(atis_queue)

    list_of_active_icao = ', '.join(list(Status.active_atis.keys()))
    LOGGER.debug('generated %s ATIS for: %s)', len(Status.active_atis),
                 list_of_active_icao)

    LOGGER.debug('writing UR settings')
    ur_settings.write_settings_file()
    URVoiceService.start_service()
예제 #21
0
def _load(name, metar_or_icao, time, max_wind, min_wind, force):  # noqa: C901
    if max_wind or min_wind:
        LOGGER.warning(
            '"min_wind" and "max_wind" have been disabled for the time being')
    if name is None:
        mission = missions_manager.get_running_mission()
        if not mission:
            LOGGER.error('unable to retrieve current mission')
            return
    else:
        try:
            LOGGER.debug('trying to cast mission name into an int: %s', name)
            mission_number = int(name)
        except ValueError:
            LOGGER.debug('loading mission name: %s', name)
            mission = missions_manager.MissionPath(name)
            if not mission:
                LOGGER.debug('mission path not found: %s', mission.path)
                LOGGER.error('mission file not found: %s', mission.name)
                return
        else:
            LOGGER.debug('loading mission number: %s', mission_number)
            mission = _mission_index_to_mission_name(mission_number)
            if not mission:
                LOGGER.error(
                    'invalid mission index: %s; use "!mission  show" to see available indices',
                    mission_number)
                return

    LOGGER.info('loading mission file: %s', mission.path)
    if time:
        try:
            mission_time = elib_miz.MissionTime.from_string(time)
            LOGGER.info('setting mission time: %s', mission_time.iso_format)
        except elib_miz.exc.InvalidDateTimeString:
            LOGGER.error('invalid date-time string: %s', time)
            return
        except ValueError as err:
            LOGGER.error(err)
            return
    else:
        mission_time = None
    if metar_or_icao:
        LOGGER.info('analyzing METAR string: %s', metar_or_icao)
        try:
            weather_ = elib_wx.Weather(metar_or_icao)
            LOGGER.info('setting mission weather: %s', weather_.as_str())
        except elib_wx.BadStationError:
            LOGGER.error('wrong ICAO code: %s', metar_or_icao)
            return
        LOGGER.info('METAR: %s', weather_.raw_metar_str)
    else:
        LOGGER.info('building METAR from mission file')
        # noinspection SpellCheckingInspection
        weather_ = elib_wx.Weather(str(mission.path))
        LOGGER.info('METAR: %s', weather_.as_str())

    commands.DCS.block_start('loading mission')
    commands.DCS.kill(force=force)
    try:
        LOGGER.debug('waiting on DCS application to close')
        while core.Status.dcs_application != 'not running':
            sleep(1)
        LOGGER.debug('DCS has closed, carrying on')
        active_mission = mission
        if time:
            mission_time.apply_to_miz(str(mission.path),
                                      str(mission.auto.path),
                                      overwrite=True)
            active_mission = mission.auto
        if metar_or_icao:
            weather_.apply_to_miz(str(mission.path),
                                  str(mission.auto.path),
                                  overwrite=True)
            active_mission = mission.auto
        active_mission.set_as_active(weather_)
    finally:
        commands.DCS.unblock_start('loading mission')
예제 #22
0
 async def _update_application_status(self, status: str):
     if core.Status.dcs_application != status:
         core.Status.dcs_application = status
         LOGGER.info('DCS application is %s', status)
         if status == 'starting':
             commands.LISTENER.monitor_server_startup_start()