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)
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')
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))
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()
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()
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
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()
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')
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')
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()
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()
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
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 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)
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
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')
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_)
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()
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')
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()