def runwayTurnOffs(self, rwy, maxangle=90, minroll=0): ''' Returns a list tuple (L1, L2, L3, L4) containing turn-offs after a landing roll down the given RWY, in preferred order: - L1 is the list of preferred turn-offs, i.e. respecting maximum turn-off angle and ending off all runways - L2 is the list of sharper turn-offs down the runway and ending on taxiways - L3 is the list of turn-offs requiring a backtrack - L4 is the list of turn-offs ending on a runway In all cases a turn-off is a tuple (n1, n2, d, a) where: - n1 is the turn-off node on the runway - n2 is the arrival node where the runway is cleared - d is the distance from threshold to n1 - a is the turn angle, between runway and (n1, n2) heading All lists are sorted by distance from current point. ''' res = [] for rwy_node in self.nodes(lambda n: self.nodeIsOnRunway(n, rwy.name)): for n in self.neighbours(rwy_node): if not self.nodeIsOnRunway(n, rwy.name): rwy_point = self.nodePosition(rwy_node) thr_dist = rwy.threshold().distanceTo(rwy_point) turn_angle = rwy_point.headingTo( self.nodePosition(n)).diff(rwy.orientation()) res.append((rwy_node, n, thr_dist, turn_angle)) res.sort(key=(lambda t: t[2])) # sort by distance to THR res_worst = pop_all(res, lambda t: len(self.connectedRunways(t[1])) > 0 ) # turn off on a RWY res_worse = [(n1, n2, d, (a + 180) % 360) for n1, n2, d, a in pop_all(res, lambda t: t[2] < minroll) ] # need backtrack res_worse.reverse() res_bad = pop_all( res, lambda t: abs(t[3]) > max_rwy_turn_off_angle) # sharp turn-off return res, res_bad, res_worse, res_worst
def new_arrival_GND(self): acft_type = choice(self.parkable_aircraft_types) rwy = rnd_rwy([ rwy for rwy in env.airport_data.allRunways() if rwy.use_for_arrivals ], lambda rwy: rwy.acceptsAcftType(acft_type)) if rwy == None: return None turn_off_lists = l1, l2, l3, l4 = env.airport_data.ground_net.runwayTurnOffs( rwy, minroll=(rwy.length(dthr=True) * 2 / 3)) for lst in turn_off_lists: pop_all( lst, lambda t: not self.groundPositionFullySeparated( env.airport_data.ground_net.nodePosition(t[1]), acft_type)) if all(lst == [] for lst in turn_off_lists): return None else: turn_off_choice = choice(l1) if l1 != [] else (l2 + l3)[0] pos = env.airport_data.ground_net.nodePosition(turn_off_choice[1]) hdg = rwy.orientation() + turn_off_choice[3] params = SoloParams(Status(Status.TAXIING), pos, env.groundStdPressureAlt(pos), hdg, Speed(0)) params.XPDR_code = env.nextSquawkCodeAssignment(XPDR_range_IFR_ARR) pk_request = choice( env.airport_data.ground_net.parkingPositions(acftType=acft_type)) return self.mkAiAcft(acft_type, params, pk_request)
def readRunwaysInUse(self): if self.airport_data == None: return 'N/A' dep = [ rwy.name for rwy in self.airport_data.allRunways() if rwy.use_for_departures ] arr = [ rwy.name for rwy in self.airport_data.allRunways() if rwy.use_for_arrivals ] if dep + arr == []: return 'N/A' both = pop_all(dep, lambda rwy: rwy in arr) pop_all(arr, lambda rwy: rwy in both) res = '' if both == [] else '/'.join(both) + ' for dep+arr' if dep != []: if res != '': res += ', ' res += '%s for departures' % '/'.join(dep) if arr != []: if res != '': res += ', ' res += '%s for arrivals' % '/'.join(arr) return res
def clearFPLs(self, pred=None): self.beginResetModel() if pred == None: self.FPL_list.clear() else: pop_all(self.FPL_list, lambda fpl: pred(fpl)) self.endResetModel() return True
def killAircraft(self, acft): if env.cpdlc.isConnected(acft.identifier): env.cpdlc.endDataLink(acft.identifier) pop_all(self.aircraft_list, lambda a: a is acft) if acft.spawned and self.studentConnected(): self.student.sendMessage( TeachingMsg(TeachingMsg.ACFT_KILLED, data=acft.identifier)) signals.aircraftKilled.emit(acft)
def tickSessionOnce(self): if self.controlledAcftNeeded() and not self.spawn_timer.isActive(): delay = randint(int(settings.solo_min_spawn_delay.total_seconds()), int(settings.solo_max_spawn_delay.total_seconds())) self.spawn_timer.start(1000 * delay) self.adjustDistractorCount() pop_all( self.controlled_traffic, lambda a: a.released or not env. pointInRadarRange(a.params.position)) pop_all(self.uncontrolled_traffic, lambda a: a.ticks_to_live == 0) for acft in self.getAircraft(): acft.tickOnce() send_packet_to_views(acft.fgmsLivePositionPacket())
def tickSessionOnce(self): pop_all(self.aircraft_list, lambda a: not env.pointInRadarRange(a.params.position)) send_traffic_this_tick = self.studentConnected( ) and self.noACK_traffic_count < max_noACK_traffic for acft in self.aircraft_list: acft.tickOnce() fgms_packet = acft.fgmsLivePositionPacket() send_packet_to_views(fgms_packet) if send_traffic_this_tick and acft.spawned: self.student.sendMessage( TeachingMsg(TeachingMsg.TRAFFIC, data=fgms_packet)) self.noACK_traffic_count += 1
def setupRwyLists(self): if env.airport_data != None: dep_rwys = [rwy for rwy in env.airport_data.allRunways() if rwy.use_for_departures] arr_rwys = [rwy for rwy in env.airport_data.allRunways() if rwy.use_for_arrivals] if selection.strip != None: acft_type = selection.strip.lookup(FPL.ACFT_TYPE, fpl=True) if acft_type != None: pop_all(dep_rwys, lambda rwy: not rwy.acceptsAcftType(acft_type)) pop_all(arr_rwys, lambda rwy: not rwy.acceptsAcftType(acft_type)) dep_lst = sorted([rwy.name for rwy in dep_rwys] if dep_rwys != [] else env.airport_data.runwayNames()) arr_lst = sorted([rwy.name for rwy in arr_rwys] if arr_rwys != [] else env.airport_data.runwayNames()) self.reportReadyRWY_select.clear() self.expectArrRWY_select.clear() self.reportReadyRWY_select.addItems(dep_lst) self.expectArrRWY_select.addItems(arr_lst)
def __init__(self, gui): SessionManager.__init__(self, gui) self.session_type = SessionType.SOLO self.session_ticker = Ticker(self.tickSessionOnce, parent=gui) self.weather_ticker = Ticker(self.setNewWeather, parent=gui) self.spawn_timer = QTimer(gui) self.spawn_timer.setSingleShot(True) self.voice_instruction_recogniser = None self.speech_synthesiser = None self.msg_is_from_session_manager = False # set to True before sending to avoid chat msg being rejected if speech_recognition_available: try: self.voice_instruction_recogniser = InstructionRecogniser(gui) except RuntimeError as err: settings.solo_voice_instructions = False QMessageBox.critical(self.gui, 'Sphinx error', \ 'Error setting up the speech recogniser (check log): %s\nVoice instructions disabled.' % err) if speech_synthesis_available: try: self.speech_synthesiser = SpeechSynthesiser(gui) except Exception as err: settings.solo_voice_readback = False QMessageBox.critical(self.gui, 'Pyttsx error', \ 'Error setting up the speech synthesiser: %s\nPilot read-back disabled.' % err) self.controlled_traffic = [] self.uncontrolled_traffic = [] self.current_local_weather = None self.simulation_paused_at = None # start time if session is paused; None otherwise self.spawn_timer.timeout.connect( lambda: self.spawnNewControlledAircraft(isSessionStart=False)) self.playable_aircraft_types = settings.solo_aircraft_types[:] self.uncontrolled_aircraft_types = [ t for t in known_aircraft_types() if cruise_speed(t) != None ] pop_all(self.playable_aircraft_types, lambda t: t not in known_aircraft_types()) pop_all(self.playable_aircraft_types, lambda t: cruise_speed(t) == None)
def mkAiAcft(self, acft_type, params, goal): ''' goal=None for UncontrolledAircraft; otherwise ControlledAircraft returns None if something prevented fresh ACFT creation, e.g. CallsignGenerationError. ''' params.XPDR_mode = 'S' if acft_cat(acft_type) in ['jets', 'heavy' ] else 'C' airlines = known_airline_codes() if env.airport_data != None: # might be rendering in tower view, prefer ACFT with known liveries liveries_for_acft = FGFS_model_liveries.get(acft_type, {}) if len(liveries_for_acft ) > 0 and settings.solo_restrict_to_available_liveries: pop_all(airlines, lambda al: al not in liveries_for_acft) try: callsign = self.generateCallsign(acft_type, airlines) if goal == None: ms_to_live = 1000 * 60 * randint(10, 60 * 3) return UncontrolledAircraft(callsign, acft_type, params, ms_to_live // solo_ticker_interval) else: return ControlledAircraft(callsign, acft_type, params, goal) except CallsignGenerationError: return None
def new_arrival_APP(self, entry_point): type_choice = self.parkable_aircraft_types if self.parkable_aircraft_types != [] else self.playable_aircraft_types # must be landable too rwy_choice = [ rwy for rwy in env.airport_data.allRunways() if rwy.use_for_arrivals ] if rwy_choice == []: rwy_choice = env.airport_data.allRunways() pop_all( type_choice, lambda t: all(not rwy.acceptsAcftType(t) for rwy in rwy_choice)) if type_choice == []: return None acft_type = choice(type_choice) ils = any(rwy.hasILS() for rwy in rwy_choice) and random() >= settings.solo_ILSvsVisual_balance if entry_point == None: hdg = Heading(randint(1, 360), True) pos = env.radarPos().moved( hdg.opposite(), uniform(.33 * settings.radar_range, .75 * settings.radar_range)) else: pos = entry_point.coordinates hdg = pos.headingTo(env.radarPos()) alt = StdPressureAlt.fromFL( 10 * randint(settings.solo_APP_ceiling_FL_min // 10, settings.solo_APP_ceiling_FL_max // 10)) if not self.airbornePositionFullySeparated(pos, alt): return None ias = restrict_speed_under_ceiling( cruise_speed(acft_type), alt, StdPressureAlt.fromFL(150)) # 5000-ft anticipation params = SoloParams(Status(Status.AIRBORNE), pos, alt, hdg, ias) params.XPDR_code = env.nextSquawkCodeAssignment(XPDR_range_IFR_ARR) return self.mkAiAcft(acft_type, params, ils)
def __init__(self, gui): SoloSessionManager.__init__(self, gui) pop_all(self.playable_aircraft_types, lambda t: acft_cat(t) not in ['jets', 'heavy'])
def forgetStrips(self, pred): self.beginResetModel() pop_all(self.discarded_strips, lambda elt: pred(elt[0])) self.endResetModel()
def deleteClosedWindows(self): for w in pop_all(self.child_windows, CpdlcConnectionWidget.closed): w.deleteLater()
def dieSignal(self, signal_key): pop_all(self.received_signals, lambda sig: sig[0] == signal_key) self.signalChanged.emit()
def killAircraft(self, acft): if env.cpdlc.isConnected(acft.identifier): env.cpdlc.endDataLink(acft.identifier) if len(pop_all(self.controlled_traffic, lambda a: a is acft)) == 0: pop_all(self.uncontrolled_traffic, lambda a: a is acft) signals.aircraftKilled.emit(acft)
def scan(self): visible_aircraft = { a.identifier: a for a in settings.session_manager.getAircraft() if a.isRadarVisible() } ## UPDATE AIRCRAFT LIST lost_contacts = [] for got_acft in self.aircraft_list: try: ignore = visible_aircraft.pop(got_acft.identifier) got_acft.saveRadarSnapshot() self.blips_invisible[got_acft.identifier] = 0 except KeyError: count = self.blips_invisible[got_acft.identifier] if count < settings.invisible_blips_before_contact_lost: self.blips_invisible[got_acft.identifier] = count + 1 else: lost_contacts.append(got_acft.identifier) # Remove lost aircraft for acft in pop_all(self.aircraft_list, lambda acft: acft.identifier in lost_contacts): strip = env.linkedStrip(acft) del self.blips_invisible[acft.identifier] self.known_EMG_squawkers.discard(acft.identifier) self.lostContact.emit(acft) if strip != None: signals.controlledContactLost.emit(strip, acft.coords()) # Add newly visible aircraft for new_acft in visible_aircraft.values(): new_acft.saveRadarSnapshot() self.aircraft_list.append(new_acft) self.blips_invisible[new_acft.identifier] = 0 self.newContact.emit(new_acft) ## CHECK FOR NEW EMERGENCIY SQUAWKS for acft in self.aircraft_list: if acft.xpdrCode() in XPDR_emergency_codes: if acft.identifier not in self.known_EMG_squawkers: self.known_EMG_squawkers.add(acft.identifier) self.emergencySquawk.emit(acft) else: self.known_EMG_squawkers.discard(acft.identifier) ## CHECK FOR NEW/LOST RADAR IDENTIFICATIONS if settings.traffic_identification_assistant: found_S_links = [] found_A_links = [] for strip in env.strips.listStrips( lambda s: s.linkedAircraft() == None): mode_S_found = False # Try mode S identification if strip.lookup(FPL.CALLSIGN) != None: scs = strip.lookup(FPL.CALLSIGN).upper() if env.strips.count( lambda s: s.lookup(FPL.CALLSIGN) != None and s. lookup(FPL.CALLSIGN).upper() == scs) == 1: candidates = [ acft for acft in self.aircraft_list if acft.xpdrCallsign() != None and acft.xpdrCallsign().upper() == scs ] if len(candidates) == 1: found_S_links.append((strip, candidates[0])) mode_S_found = True # Try mode A identification if not mode_S_found: ssq = strip.lookup(assigned_SQ_detail) if ssq != None and env.strips.count(lambda s: \ s.lookup(assigned_SQ_detail) == ssq and s.linkedAircraft() == None) == 1: # only one non-linked strip with this SQ candidates = [acft for acft in self.aircraft_list if not any(a is acft for s, a in found_S_links) \ and acft.xpdrCode() == ssq and env.linkedStrip(acft) == None] if len(candidates) == 1: # only one aircraft matching found_A_links.append((strip, candidates[0])) for s, a in pop_all( self.soft_links, lambda sl: not any(sl[0] is s and sl[ 1] is a for s, a in found_S_links + found_A_links)): s.writeDetail(soft_link_detail, None) for s, a, m in [(s, a, True) for s, a in found_S_links ] + [(s, a, False) for s, a in found_A_links]: if not any(sl[0] is s and sl[1] is a for sl in self.soft_links): # new found soft link if strip.lookup( received_from_detail ) != None and settings.strip_autolink_on_ident and ( m or settings.strip_autolink_include_modeC): s.linkAircraft(a) else: # strip not automatically linked; notify of a new identification self.soft_links.append((s, a)) s.writeDetail(soft_link_detail, a) signals.aircraftIdentification.emit(s, a, m) ## UPDATE POSITION/ROUTE WARNINGS conflicts = { acft.identifier: Conflict.NO_CONFLICT for acft in self.aircraft_list } traffic_for_route_checks = [] for strip in env.strips.listStrips( ): # check for position conflicts and build list of traffic to check for routes later acft = strip.linkedAircraft() if acft != None and acft.identifier in conflicts: # controlled traffic with radar contact for other in self.aircraft_list: if other is not acft and position_conflict_test( acft, other ) == Conflict.NEAR_MISS: # positive separation loss detected conflicts[acft.identifier] = conflicts[ other.identifier] = Conflict.NEAR_MISS if not bypass_route_conflict_check(strip): traffic_for_route_checks.append(acft) if settings.route_conflict_warnings: # check for route conflicts while traffic_for_route_checks != []: # progressively emptying the list acft = traffic_for_route_checks.pop() for other in traffic_for_route_checks: c = path_conflict_test(acft, other) conflicts[acft.identifier] = max( conflicts[acft.identifier], c) conflicts[other.identifier] = max( conflicts[other.identifier], c) # now update aircraft conflicts and emit signals if any are new new_near_miss = new_path_conflict = False for contact in self.aircraft_list: new_conflict = conflicts[contact.identifier] if new_conflict > contact.conflict: new_near_miss |= new_conflict == Conflict.NEAR_MISS new_path_conflict |= new_conflict in [ Conflict.DEPENDS_ON_ALT, Conflict.PATH_CONFLICT ] contact.conflict = new_conflict if new_path_conflict: self.pathConflict.emit() if new_near_miss: self.nearMiss.emit() ## UPDATE RUNWAY OCCUPATION for phrwy in self.runway_occupation: new_occ = [] if settings.monitor_runway_occupation: rwy1, rwy2 = env.airport_data.physicalRunway(phrwy) width_metres = env.airport_data.physicalRunwayData(phrwy)[0] thr1 = rwy1.threshold().toRadarCoords() thr2 = rwy2.threshold().toRadarCoords() w = m2NM * width_metres for acft in self.aircraft_list: if acft.considerOnGround(): if acft.coords().toRadarCoords().isBetween( thr1, thr2, w / 2): # ACFT is on RWY new_occ.append(acft) if not any( a is acft for a in self.runway_occupation[phrwy] ): # just entered the RWY: check if alarm must sound try: boxed_link = env.strips.findStrip( lambda strip: strip.lookup( runway_box_detail) == phrwy ).linkedAircraft() except StopIteration: # no strip boxed on this runway if rwy1.inUse() or rwy2.inUse( ): # entering a non-reserved but active RWY self.runwayIncursion.emit(phrwy, acft) else: # RWY is reserved if boxed_link == None and env.linkedStrip( acft ) == None or boxed_link is acft: # entering ACFT is the one cleared to enter, or can be if self.runway_occupation[ phrwy] != []: # some ACFT was/were already on RWY call_guilty = acft if boxed_link == None else self.runway_occupation[ phrwy][0] self.runwayIncursion.emit( phrwy, call_guilty) else: # entering ACFT is known to be different from the one cleared to enter self.runwayIncursion.emit(phrwy, acft) self.runway_occupation[phrwy] = new_occ # Finished aircraft stuff self.last_sweep = now() self.blip.emit()
def silentlyForgetContact(self, killed): for popped in pop_all( self.aircraft_list, lambda acft: acft is killed): # there should only be one del self.blips_invisible[killed.identifier] self.known_EMG_squawkers.discard(killed.identifier)
def receiveMsgFromTeacher(self, msg): #DEBUG if msg.type != TeachingMsg.TRAFFIC:# #DEBUG print('=== STUDENT RECEIVES ===\n%s\n=== End ===' % msg.data) if msg.type == TeachingMsg.ACFT_KILLED: callsign = msg.strData() if env.cpdlc.isConnected(callsign): env.cpdlc.endDataLink(callsign) for acft in pop_all(self.traffic, lambda a: a.identifier == callsign): signals.aircraftKilled.emit(acft) elif msg.type == TeachingMsg.TRAFFIC: # traffic update; contains FGMS packet fgms_packet = msg.binData() update_FgmsAircraft_list(self.traffic, fgms_packet) send_packet_to_views(fgms_packet) self.teacher.sendMessage(TeachingMsg(TeachingMsg.TRAFFIC)) elif msg.type == TeachingMsg.SIM_PAUSED: self.teacher_paused_at = now() signals.sessionPaused.emit() elif msg.type == TeachingMsg.SIM_RESUMED: pause_delay = now() - self.teacher_paused_at for acft in self.traffic: acft.moveHistoryTimesForward(pause_delay) self.teacher_paused_at = None signals.sessionResumed.emit() elif msg.type == TeachingMsg.ATC_TEXT_CHAT: lines = msg.strData().split('\n') if len(lines) == 2: signals.incomingAtcTextMsg.emit( ChatMessage(lines[0], lines[1], recipient=student_callsign, private=True)) else: print( 'ERROR: Invalid format in received ATC text chat from teacher.' ) elif msg.type == TeachingMsg.STRIP_EXCHANGE: line_sep = msg.strData().split('\n', maxsplit=1) fromATC = line_sep[0] strip = Strip.fromEncodedDetails( '' if len(line_sep) < 2 else line_sep[1]) strip.writeDetail(received_from_detail, fromATC) signals.receiveStrip.emit(strip) elif msg.type == TeachingMsg.SX_LIST: to_remove = set(env.ATCs.knownATCs()) to_remove.discard(teacher_callsign) for line in msg.strData().split('\n'): if line != '': # last line is empty lst = line.rsplit('\t', maxsplit=1) try: frq = CommFrequency(lst[1]) if len(lst) == 2 else None except ValueError: frq = None env.ATCs.updateATC(lst[0], None, None, frq) to_remove.discard(lst[0]) for atc in to_remove: env.ATCs.removeATC(atc) env.ATCs.refreshViews() elif msg.type == TeachingMsg.WEATHER: metar = msg.strData() station = metar.split(' ', maxsplit=1)[0] if station == settings.primary_METAR_station and metar != self.known_METAR: self.known_METAR = metar signals.newWeather.emit(station, Weather(metar)) elif msg.type == TeachingMsg.PTT: # msg format: "b acft" where b is '1' or '0' for PTT on/off; acft is caller's identifier line_sep = msg.strData().split(' ', maxsplit=1) try: ptt = bool(int(line_sep[0])) caller = next(acft for acft in self.getAircraft() if acft.identifier == line_sep[1]) if ptt: env.rdf.receiveSignal(caller.identifier, lambda acft=caller: acft.coords()) else: env.rdf.dieSignal(caller.identifier) except StopIteration: print('Ignored PTT message from teacher (unknown ACFT %s).' % line_sep[1]) except (ValueError, IndexError): print('Error decoding PTT message value from teacher') elif msg.type == TeachingMsg.CPDLC: try: acft_callsign, line2 = msg.strData().split('\n', maxsplit=1) if line2 == '0': # ACFT is disconnecting env.cpdlc.endDataLink(acft_callsign) elif line2 == '1': # ACFT is connecting (CAUTION: teacher needs confirmation of accepted connection) if settings.controller_pilot_data_link: env.cpdlc.beginDataLink(acft_callsign, student_callsign) self.teacher.sendMessage( TeachingMsg( TeachingMsg.CPDLC, data=('%s\n%d' % (acft_callsign, settings.controller_pilot_data_link)))) elif line2.startswith(CPDLC_transfer_cmd_prefix ): # ACFT being transferred to me if settings.controller_pilot_data_link: # CAUTION: teacher needs confirmation of accepted connection xfr_auth = line2[len(CPDLC_transfer_cmd_prefix):] env.cpdlc.beginDataLink(acft_callsign, student_callsign, transferFrom=xfr_auth) self.teacher.sendMessage(TeachingMsg(TeachingMsg.CPDLC, \ data=('%s\n%s%s' % (acft_callsign, CPDLC_transfer_cmd_prefix, xfr_auth)))) else: self.teacher.sendMessage( TeachingMsg(TeachingMsg.CPDLC, data=('%s\n0' % acft_callsign))) elif line2.startswith( CPDLC_message_cmd_prefix): # ACFT sending a message encoded_msg = line2[len(CPDLC_message_cmd_prefix):] link = env.cpdlc.currentDataLink(acft_callsign) if link == None: print( 'Ignored CPDLC message sent from %s while not connected.' % acft_callsign) else: link.appendMessage( CpdlcMessage.fromText(False, encoded_msg)) else: print('Error decoding CPDLC command from teacher:', line2) except (IndexError, ValueError): print('Error decoding CPDLC message value from teacher') else: print('Unhandled message type from teacher: %s' % msg.type)