diff --git a/src/blackcore/airspace_monitor.cpp b/src/blackcore/airspace_monitor.cpp new file mode 100644 index 000000000..0ef70af08 --- /dev/null +++ b/src/blackcore/airspace_monitor.cpp @@ -0,0 +1,518 @@ +/* Copyright (C) 2013 VATSIM Community / contributors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "airspace_monitor.h" +#include "blackmisc/project.h" +#include "blackmisc/indexvariantmap.h" +#include "network_vatlib.h" // for createFsipiCustomPacketData + +namespace BlackCore +{ + + using namespace BlackMisc; + using namespace BlackMisc::Aviation; + using namespace BlackMisc::Network; + using namespace BlackMisc::Geo; + using namespace BlackMisc::PhysicalQuantities; + + CAirspaceMonitor::CAirspaceMonitor(QObject *parent, INetwork *network, CVatsimBookingReader *bookings, CVatsimDataFileReader *dataFile) + : QObject(parent), m_network(network), m_vatsimBookingReader(bookings), m_vatsimDataFileReader(dataFile) + { + this->connect(this->m_network, &INetwork::atcPositionUpdate, this, &CAirspaceMonitor::atcPositionUpdate); + this->connect(this->m_network, &INetwork::atisReplyReceived, this, &CAirspaceMonitor::atisReceived); + this->connect(this->m_network, &INetwork::atisVoiceRoomReplyReceived, this, &CAirspaceMonitor::atisVoiceRoomReceived); + this->connect(this->m_network, &INetwork::atisLogoffTimeReplyReceived, this, &CAirspaceMonitor::atisLogoffTimeReceived); + this->connect(this->m_network, &INetwork::metarReplyReceived, this, &CAirspaceMonitor::metarReceived); + this->connect(this->m_network, &INetwork::flightPlanReplyReceived, this, &CAirspaceMonitor::flightplanReceived); + this->connect(this->m_network, &INetwork::realNameReplyReceived, this, &CAirspaceMonitor::realNameReplyReceived); + this->connect(this->m_network, &INetwork::icaoCodesReplyReceived, this, &CAirspaceMonitor::icaoCodesReceived); + this->connect(this->m_network, &INetwork::pilotDisconnected, this, &CAirspaceMonitor::pilotDisconnected); + this->connect(this->m_network, &INetwork::atcDisconnected, this, &CAirspaceMonitor::atcControllerDisconnected); + this->connect(this->m_network, &INetwork::aircraftPositionUpdate, this, &CAirspaceMonitor::aircraftUpdateReceived); + this->connect(this->m_network, &INetwork::frequencyReplyReceived, this, &CAirspaceMonitor::frequencyReceived); + this->connect(this->m_network, &INetwork::capabilitiesReplyReceived, this, &CAirspaceMonitor::capabilitiesReplyReceived); + this->connect(this->m_network, &INetwork::customPacketReceived, this, &CAirspaceMonitor::customPacketReceived); + this->connect(this->m_network, &INetwork::serverReplyReceived, this, &CAirspaceMonitor::serverReplyReceived); + + this->connect(this->m_vatsimBookingReader, &CVatsimBookingReader::dataRead, this, &CAirspaceMonitor::receivedBookings); + } + + CFlightPlan CAirspaceMonitor::loadFlightPlanFromNetwork(const CCallsign &callsign) + { + CFlightPlan plan; + + // use cache, but not for own callsign (always reload) + if (this->m_flightPlanCache.contains(callsign)) plan = this->m_flightPlanCache[callsign]; + if (!plan.wasSentOrLoaded() || plan.timeDiffSentOrLoadedMs() > 30 * 1000) + { + // outdated, or not in cache at all + this->m_network->sendFlightPlanQuery(callsign); + + // with this little trick we try to make an asynchronous signal / slot + // based approach a synchronous return value + QTime waitForFlightPlan = QTime::currentTime().addMSecs(1000); + while (QTime::currentTime() < waitForFlightPlan) + { + // process some other events and hope network answer is received already + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + if (m_flightPlanCache.contains(callsign)) + { + plan = this->m_flightPlanCache[callsign]; + break; + } + } + } + return plan; + } + + CUserList CAirspaceMonitor::getUsers() const + { + CUserList users; + foreach(CAtcStation station, this->m_atcStationsOnline) + { + CUser user = station.getController(); + users.push_back(user); + } + foreach(CAircraft aircraft, this->m_aircraftsInRange) + { + CUser user = aircraft.getPilot(); + users.push_back(user); + } + return users; + } + + CUserList CAirspaceMonitor::getUsersForCallsigns(const CCallsignList &callsigns) const + { + CUserList users; + if (callsigns.isEmpty()) return users; + CCallsignList searchList(callsigns); + + // myself, which is not in the lists below + CAircraft ownAircraft = this->m_ownAircraft; + if (!ownAircraft.getCallsign().isEmpty() && searchList.contains(ownAircraft.getCallsign())) + { + searchList.remove(ownAircraft.getCallsign()); + users.push_back(ownAircraft.getPilot()); + } + + // do aircrafts first, this will handle most callsigns + foreach(CAircraft aircraft, this->m_aircraftsInRange) + { + if (searchList.isEmpty()) break; + CCallsign callsign = aircraft.getCallsign(); + if (searchList.contains(callsign)) + { + CUser user = aircraft.getPilot(); + users.push_back(user); + searchList.remove(callsign); + } + } + + foreach(CAtcStation station, this->m_atcStationsOnline) + { + if (searchList.isEmpty()) break; + CCallsign callsign = station.getCallsign(); + if (searchList.contains(callsign)) + { + CUser user = station.getController(); + users.push_back(user); + searchList.remove(callsign); + } + } + + // we might have unresolved callsigns + // these are the ones not in range + foreach(CCallsign callsign, searchList) + { + CUserList usersByCallsign = this->m_vatsimDataFileReader->getUsersForCallsign(callsign); + if (usersByCallsign.isEmpty()) + { + CUser user; + user.setCallsign(callsign); + users.push_back(user); + } + else + { + users.push_back(usersByCallsign[0]); + } + } + return users; + } + + CClientList CAirspaceMonitor::getOtherClientsForCallsigns(const CCallsignList &callsigns) const + { + CClientList clients; + if (callsigns.isEmpty()) return clients; + foreach(CCallsign callsign, callsigns) + { + clients.push_back(this->m_otherClients.findBy(&CClient::getCallsign, callsign)); + } + return clients; + } + + BlackMisc::Aviation::CInformationMessage CAirspaceMonitor::getMetar(const BlackMisc::Aviation::CAirportIcao &airportIcaoCode) + { + CInformationMessage metar; + if (airportIcaoCode.isEmpty()) return metar; + if (this->m_metarCache.contains(airportIcaoCode)) metar = this->m_metarCache[airportIcaoCode]; + if (metar.isEmpty() || metar.timeDiffReceivedMs() > 10 * 1000) + { + // outdated, or not in cache at all + this->m_network->sendMetarQuery(airportIcaoCode); + + // with this little trick we try to make an asynchronous signal / slot + // based approach a synchronous return value + QTime waitForMetar = QTime::currentTime().addMSecs(1000); + while (QTime::currentTime() < waitForMetar) + { + // process some other events and hope network answer is received already + QCoreApplication::processEvents(QEventLoop::AllEvents, 100); + if (m_metarCache.contains(airportIcaoCode)) + { + metar = this->m_metarCache[airportIcaoCode]; + break; + } + } + } + return metar; + } + + CAtcStation CAirspaceMonitor::getAtcStationForComUnit(const CComSystem &comSystem) + { + CAtcStation station; + CAtcStationList stations = this->m_atcStationsOnline.findIfComUnitTunedIn25KHz(comSystem); + if (!stations.isEmpty()) + { + stations.sortBy(&CAtcStation::getDistanceToPlane); + station = stations.front(); + } + return station; + } + + void CAirspaceMonitor::requestDataUpdates() + { + if (!this->m_network->isConnected()) return; + + foreach(CAircraft aircraft, this->m_aircraftsInRange) + { + this->m_network->sendFrequencyQuery(aircraft.getCallsign()); + this->m_network->sendIcaoCodesQuery(aircraft.getCallsign()); + } + } + + void CAirspaceMonitor::requestAtisUpdates() + { + if (!this->m_network->isConnected()) return; + + foreach(CAtcStation station, this->m_atcStationsOnline) + { + this->m_network->sendAtisQuery(station.getCallsign()); + } + } + + void CAirspaceMonitor::realNameReplyReceived(const CCallsign &callsign, const QString &realname) + { + if (realname.isEmpty()) return; + CIndexVariantMap vm(CAtcStation::IndexControllerRealName, realname); + this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); + + vm = CIndexVariantMap(CAircraft::IndexPilotRealName, realname); + this->m_aircraftsInRange.applyIf(&CAircraft::getCallsign, callsign, vm); + + vm = CIndexVariantMap(CClient::IndexRealName, realname); + this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); + } + + void CAirspaceMonitor::capabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags) + { + if (callsign.isEmpty()) return; + CIndexVariantMap capabilities; + capabilities.addValue(CClient::FsdAtisCanBeReceived, (flags & INetwork::AcceptsAtisResponses)); + capabilities.addValue(CClient::FsdWithInterimPositions, (flags & INetwork::SupportsInterimPosUpdates)); + capabilities.addValue(CClient::FsdWithModelDescription, (flags & INetwork::SupportsModelDescriptions)); + CIndexVariantMap vm(CClient::IndexCapabilities, capabilities.toQVariant()); + this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); + } + + void CAirspaceMonitor::customPacketReceived(const CCallsign &callsign, const QString &packet, const QStringList &data) + { + if (callsign.isEmpty() || data.isEmpty()) return; + if (packet.startsWith("FSIPIR", Qt::CaseInsensitive)) + { + // Request of other client, I can get the other's model from that + // FsInn response is usually my model + QString model = data.last(); + if (model.isEmpty()) return; + CIndexVariantMap vm(CClient::IndexQueriedModelString, QVariant(model)); + if (!this->m_otherClients.contains(&CClient::getCallsign, callsign)) + { + // with custom packets it can happen, + //the packet is received before any other packet + this->m_otherClients.push_back(CClient(callsign)); + } + this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); + this->sendFsipiCustomPacket(callsign); // response + } + } + + void CAirspaceMonitor::serverReplyReceived(const CCallsign &callsign, const QString &server) + { + if (callsign.isEmpty() || server.isEmpty()) return; + CIndexVariantMap vm(CClient::IndexServer, QVariant(server)); + this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); + } + + void CAirspaceMonitor::metarReceived(const QString &metarMessage) + { + if (metarMessage.length() < 10) return; // invalid + const QString icaoCode = metarMessage.left(4).toUpper(); + const QString icaoCodeTower = icaoCode + "_TWR"; + CCallsign callsignTower(icaoCodeTower); + CInformationMessage metar(CInformationMessage::METAR, metarMessage); + + // add METAR to existing stations + CIndexVariantMap vm(CAtcStation::IndexMetar, metar.toQVariant()); + this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsignTower, vm); + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsignTower, vm); + this->m_metarCache.insert(icaoCode, metar); + if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsignTower)) emit this->changedAtcStationsOnline(); + if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsignTower)) emit this->changedAtcStationsBooked(); + } + + void CAirspaceMonitor::flightplanReceived(const CCallsign &callsign, const CFlightPlan &flightPlan) + { + CFlightPlan plan(flightPlan); + plan.setWhenLastSentOrLoaded(QDateTime::currentDateTimeUtc()); + this->m_flightPlanCache.insert(callsign, plan); + } + + void CAirspaceMonitor::sendFsipiCustomPacket(const CCallsign &recipientCallsign) const + { + QStringList data = this->createFsipiCustomPacketData(); + this->m_network->sendCustomPacket(recipientCallsign.asString(), "FSIPI", data); + } + + void CAirspaceMonitor::sendFsipirCustomPacket(const CCallsign &recipientCallsign) const + { + QStringList data = this->createFsipiCustomPacketData(); + this->m_network->sendCustomPacket(recipientCallsign.asString(), "FSIPIR", data); + } + + QStringList CAirspaceMonitor::createFsipiCustomPacketData() const + { + CAircraft me = this->m_ownAircraft; + CAircraftIcao icao = me.getIcaoInfo(); + QString modelString; + // FIXME (MS) simulator or ownaircraft context should send an ownAircraftModelChanged signal, so we wouldn't need to interrogate the simulator context here. + //if (this->getIContextSimulator()) + //{ + // if (this->getIContextSimulator()->isConnected()) modelString = this->getIContextSimulator()->getOwnAircraftModel().getQueriedModelString(); + //} + if (modelString.isEmpty()) modelString = CProject::systemNameAndVersion(); + QStringList data = CNetworkVatlib::createFsipiCustomPacketData( + "0", icao.getAirlineDesignator(), icao.getAircraftDesignator(), + "", "", "", "", + icao.getAircraftCombinedType(), modelString); + return data; + } + + void CAirspaceMonitor::receivedBookings(const CAtcStationList &bookedStations) + { + this->m_atcStationsBooked.clear(); + foreach(CAtcStation bookedStation, bookedStations) + { + // complete by VATSIM data file data + this->m_vatsimDataFileReader->getAtcStations().updateFromVatsimDataFileStation(bookedStation); + // exchange booking and online data + this->m_atcStationsOnline.mergeWithBooking(bookedStation); + // into list + this->m_atcStationsBooked.push_back(bookedStation); + } + } + + void CAirspaceMonitor::atcPositionUpdate(const CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency, const CCoordinateGeodetic &position, const BlackMisc::PhysicalQuantities::CLength &range) + { + CAtcStationList stationsWithCallsign = this->m_atcStationsOnline.findByCallsign(callsign); + if (stationsWithCallsign.isEmpty()) + { + // new station + CAtcStation station; + station.setCallsign(callsign); + station.setRange(range); + station.setFrequency(frequency); + station.setPosition(position); + station.setOnline(true); + station.calculcateDistanceToPlane(this->m_ownAircraft.getPosition()); + this->m_vatsimDataFileReader->getAtcStations().updateFromVatsimDataFileStation(station); // prefill + this->m_atcStationsOnline.push_back(station); + + if (this->m_network->isConnected()) + { + emit this->m_network->sendRealNameQuery(callsign); + emit this->m_network->sendAtisQuery(callsign); // request ATIS and voice rooms + emit this->m_network->sendServerQuery(callsign); + } + + emit this->changedAtcStationsOnline(); + // Remark: this->changedAtcStationOnlineConnectionStatus(station, true); + // will be sent in psFsdAtisVoiceRoomReceived + } + else + { + // update + CIndexVariantMap values; + values.addValue(CAtcStation::IndexFrequency, frequency); + values.addValue(CAtcStation::IndexPosition, position); + values.addValue(CAtcStation::IndexRange, range); + this->m_atcStationsOnline.applyIf(BlackMisc::Predicates::MemberEqual(&CAtcStation::getCallsign, callsign), values); + emit this->changedAtcStationsOnline(); + } + } + + void CAirspaceMonitor::atcControllerDisconnected(const CCallsign &callsign) + { + if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) + { + CAtcStation removeStation = this->m_atcStationsOnline.findByCallsign(callsign).front(); + this->m_atcStationsOnline.removeIf(&CAtcStation::getCallsign, callsign); + emit this->changedAtcStationsOnline(); + emit this->changedAtcStationOnlineConnectionStatus(removeStation, false); + } + + // booked + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, CIndexVariantMap(CAtcStation::IndexIsOnline, QVariant(false))); + } + + void CAirspaceMonitor::atisReceived(const CCallsign &callsign, const CInformationMessage &atisMessage) + { + if (callsign.isEmpty()) return; + CIndexVariantMap vm(CAtcStation::IndexAtis, atisMessage.toQVariant()); + this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); + if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); + if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); + } + + void CAirspaceMonitor::atisVoiceRoomReceived(const CCallsign &callsign, const QString &url) + { + QString trimmedUrl = url.trimmed(); + CIndexVariantMap vm(CAtcStation::IndexVoiceRoomUrl, trimmedUrl); + if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) + { + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); + emit this->changedAtcStationsBooked(); + } + if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) + { + this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); + CAtcStation station = this->m_atcStationsOnline.findFirstByCallsign(callsign); + emit this->changedAtcStationsBooked(); + emit this->changedAtcStationOnlineConnectionStatus(station, true); + } + vm = CIndexVariantMap(CClient::IndexVoiceCapabilities, CVoiceCapabilities(CVoiceCapabilities::Voice).toQVariant()); + this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); + } + + void CAirspaceMonitor::atisLogoffTimeReceived(const CCallsign &callsign, const QString &zuluTime) + { + if (zuluTime.length() == 4) + { + // Logic to set logoff time + bool ok; + int h = zuluTime.left(2).toInt(&ok); + if (!ok) return; + int m = zuluTime.right(2).toInt(&ok); + if (!ok) return; + QDateTime logoffDateTime = QDateTime::currentDateTimeUtc(); + logoffDateTime.setTime(QTime(h, m)); + CIndexVariantMap vm(CAtcStation::IndexBookedUntil, logoffDateTime); + this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); + this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); + if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); + if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); + } + } + + void CAirspaceMonitor::icaoCodesReceived(const CCallsign &callsign, const CAircraftIcao &icaoData) + { + // update + CIndexVariantMap vm(CAircraft::IndexIcao, icaoData.toQVariant()); + if (!icaoData.hasAircraftDesignator()) + { + // empty so far, try to fetch from data file + qDebug() << "Empty ICAO info for " << callsign << icaoData; + CAircraftIcao icaoDataDataFile = this->m_vatsimDataFileReader->getIcaoInfo(callsign); + if (!icaoDataDataFile.hasAircraftDesignator()) return; // give up! + vm = CIndexVariantMap(CAircraft::IndexIcao, icaoData.toQVariant()); + } + this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); + emit this->changedAircraftsInRange(); + } + + void CAirspaceMonitor::aircraftUpdateReceived(const CCallsign &callsign, const CAircraftSituation &situation, const CTransponder &transponder) + { + CAircraftList list = this->m_aircraftsInRange.findByCallsign(callsign); + if (list.isEmpty()) + { + // new aircraft + CAircraft aircraft; + aircraft.setCallsign(callsign); + aircraft.setSituation(situation); + aircraft.setTransponder(transponder); + aircraft.setCalculcatedDistanceToPosition(this->m_ownAircraft.getPosition()); // distance from myself + this->m_vatsimDataFileReader->updateWithVatsimDataFileData(aircraft); + this->m_aircraftsInRange.push_back(aircraft); + + // and new client, there is a chance it has been created by + // custom packet first + if (!this->m_otherClients.contains(&CClient::getCallsign, callsign)) + { + CClient c(callsign); + this->m_otherClients.push_back(c); // initial, will be filled by data later + } + + if (this->m_network->isConnected()) + { + // only if still connected + this->m_network->sendFrequencyQuery(callsign); + this->m_network->sendRealNameQuery(callsign); + this->m_network->sendIcaoCodesQuery(callsign); + this->m_network->sendCapabilitiesQuery(callsign); + this->m_network->sendServerQuery(callsign); + this->sendFsipirCustomPacket(callsign); // own aircraft model + } + } + else + { + // update + CLength distance = this->m_ownAircraft.calculcateDistanceToPosition(situation.getPosition()); + distance.switchUnit(CLengthUnit::NM()); + CIndexVariantMap vm; + vm.addValue(CAircraft::IndexTransponder, transponder); + vm.addValue(CAircraft::IndexSituation, situation); + vm.addValue(CAircraft::IndexDistance, distance); + this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); + } + + emit this->changedAircraftsInRange(); + emit changedAircraftSituation(callsign, situation); + } + + void CAirspaceMonitor::pilotDisconnected(const CCallsign &callsign) + { + this->m_aircraftsInRange.removeIf(&CAircraft::getCallsign, callsign); + this->m_otherClients.removeIf(&CClient::getCallsign, callsign); + } + + void CAirspaceMonitor::frequencyReceived(const CCallsign &callsign, const CFrequency &frequency) + { + // update + CIndexVariantMap vm(CAircraft::IndexFrequencyCom1, frequency.toQVariant()); + this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); + emit this->changedAircraftsInRange(); + } + +} // namespace diff --git a/src/blackcore/airspace_monitor.h b/src/blackcore/airspace_monitor.h new file mode 100644 index 000000000..a7b980545 --- /dev/null +++ b/src/blackcore/airspace_monitor.h @@ -0,0 +1,146 @@ +/* Copyright (C) 2013 VATSIM Community / contributors + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BLACKCORE_AIRSPACE_MONITOR_H +#define BLACKCORE_AIRSPACE_MONITOR_H + +#include "blackmisc/avatcstationlist.h" +#include "blackmisc/avaircraftlist.h" +#include "blackmisc/nwclientlist.h" +#include "blackmisc/avflightplan.h" +#include "blackmisc/nwuserlist.h" +#include "blackmisc/avcallsignlist.h" +#include "network.h" +#include "vatsimbookingreader.h" +#include "vatsimdatafilereader.h" + +//! \file + +namespace BlackCore +{ + + /*! + * Keeps track of other entities in the airspace: aircraft, ATC stations, etc. + */ + class CAirspaceMonitor : public QObject + { + Q_OBJECT + + public: + //! Constructor + CAirspaceMonitor(QObject *parent, INetwork *network, CVatsimBookingReader *bookings, CVatsimDataFileReader *dataFile); + + //! Returns the list of users we know about + BlackMisc::Network::CUserList getUsers() const; + + //! Returns a list of the users corresponding to the given callsigns + BlackMisc::Network::CUserList getUsersForCallsigns(const BlackMisc::Aviation::CCallsignList &callsigns) const; + + //! Returns the flightplan for the given callsign + BlackMisc::Aviation::CFlightPlan loadFlightPlanFromNetwork(const BlackMisc::Aviation::CCallsign &callsign); + + //! Returns this list of other clients we know about + BlackMisc::Network::CClientList getOtherClients() const { return m_otherClients; } + + //! Returns a list of other clients corresponding to the given callsigns + BlackMisc::Network::CClientList getOtherClientsForCallsigns(const BlackMisc::Aviation::CCallsignList &callsigns) const; + + //! Returns a METAR for the given airport, if available + BlackMisc::Aviation::CInformationMessage getMetar(const BlackMisc::Aviation::CAirportIcao &airportIcaoCode); + + //! Returns the current online ATC stations + BlackMisc::Aviation::CAtcStationList getAtcStationsOnline() const { return m_atcStationsOnline; } + + //! Returns the current booked ATC stations + BlackMisc::Aviation::CAtcStationList getAtcStationsBooked() const { return m_atcStationsBooked; } + + //! Returns the current aircraft in range + BlackMisc::Aviation::CAircraftList getAircraftInRange() const { return m_aircraftsInRange; } + + //! Returns the closest ATC station operating on the given frequency, if any + BlackMisc::Aviation::CAtcStation getAtcStationForComUnit(const BlackMisc::Aviation::CComSystem &comSystem); + + //! Request to update other clients' data from the network + void requestDataUpdates(); + + //! Request to update ATC stations' ATIS data from the network + void requestAtisUpdates(); + + signals: + //! Online ATC stations were changed + void changedAtcStationsOnline(); + + //! Booked ATC stations were changed + void changedAtcStationsBooked(); + + //! Connection status of an ATC station was changed + void changedAtcStationOnlineConnectionStatus(const BlackMisc::Aviation::CAtcStation &station, bool isConnected); + + //! Aircrafts were changed + void changedAircraftsInRange(); + + //! An aircraft's situation was changed + void changedAircraftSituation(const BlackMisc::Aviation::CCallsign, const BlackMisc::Aviation::CAircraftSituation &situation); + + //! Sent a status message + void statusMessage(const BlackMisc::CStatusMessage &msg); + + public slots: + //! Own aircraft updated + void setOwnAircraft(const BlackMisc::Aviation::CAircraft &ownAircraft) { m_ownAircraft = ownAircraft; } + + public: + //! Clear the contents + void clear() + { + m_atcStationsOnline.clear(); + m_atcStationsBooked.clear(); + m_aircraftsInRange.clear(); + m_otherClients.clear(); + m_metarCache.clear(); + m_flightPlanCache.clear(); + } + + private: + BlackMisc::Aviation::CAtcStationList m_atcStationsOnline; + BlackMisc::Aviation::CAtcStationList m_atcStationsBooked; + BlackMisc::Aviation::CAircraftList m_aircraftsInRange; + BlackMisc::Network::CClientList m_otherClients; + QMap m_metarCache; + QMap m_flightPlanCache; + + BlackMisc::Aviation::CAircraft m_ownAircraft; + + INetwork *m_network; + CVatsimBookingReader *m_vatsimBookingReader; + CVatsimDataFileReader *m_vatsimDataFileReader; + + // FIXME (MS) should be in INetwork + void sendFsipiCustomPacket(const BlackMisc::Aviation::CCallsign &recipientCallsign) const; + void sendFsipirCustomPacket(const BlackMisc::Aviation::CCallsign &recipientCallsign) const; + QStringList createFsipiCustomPacketData() const; + + private slots: + void realNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname); + void capabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags); + void customPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &packet, const QStringList &data); + void serverReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &server); + void metarReceived(const QString &metarMessage); + void flightplanReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CFlightPlan &flightPlan); + void receivedBookings(const BlackMisc::Aviation::CAtcStationList &bookedStations); + void atcPositionUpdate(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::Geo::CCoordinateGeodetic &position, const BlackMisc::PhysicalQuantities::CLength &range); + void atcControllerDisconnected(const BlackMisc::Aviation::CCallsign &callsign); + void atisReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CInformationMessage &atisMessage); + void atisVoiceRoomReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &url); + void atisLogoffTimeReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &zuluTime); + void icaoCodesReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftIcao &icaoData); + void aircraftUpdateReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); + void pilotDisconnected(const BlackMisc::Aviation::CCallsign &callsign); + void frequencyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency); + }; + +} // namespace + +#endif // guard diff --git a/src/blackcore/context_network_aircraft.cpp b/src/blackcore/context_network_aircraft.cpp deleted file mode 100644 index afc77f707..000000000 --- a/src/blackcore/context_network_aircraft.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* Copyright (C) 2013 VATSIM Community / authors - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "context_network_impl.h" -#include "context_runtime.h" -#include "vatsimbookingreader.h" -#include "vatsimdatafilereader.h" - -#include "blackmisc/predicates.h" - -using namespace BlackMisc; -using namespace BlackMisc::PhysicalQuantities; -using namespace BlackMisc::Aviation; -using namespace BlackMisc::Network; -using namespace BlackMisc::Geo; - -namespace BlackCore -{ - - /* - * Aircraft info received - */ - void CContextNetwork::psFsdIcaoCodesReceived(const CCallsign &callsign, const CAircraftIcao &icaoData) - { - // update - CIndexVariantMap vm(CAircraft::IndexIcao, icaoData.toQVariant()); - if (!icaoData.hasAircraftDesignator()) - { - // empty so far, try to fetch from data file - qDebug() << "Empty ICAO info for " << callsign << icaoData; - CAircraftIcao icaoDataDataFile = this->m_vatsimDataFileReader->getIcaoInfo(callsign); - if (!icaoDataDataFile.hasAircraftDesignator()) return; // give up! - vm = CIndexVariantMap(CAircraft::IndexIcao, icaoData.toQVariant()); - } - this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); - emit this->changedAircraftsInRange(); - } - - /* - * Aircraft update received - */ - void CContextNetwork::psFsdAircraftUpdateReceived(const CCallsign &callsign, const CAircraftSituation &situation, const CTransponder &transponder) - { - CAircraftList list = this->m_aircraftsInRange.findByCallsign(callsign); - if (list.isEmpty()) - { - // new aircraft - CAircraft aircraft; - aircraft.setCallsign(callsign); - aircraft.setSituation(situation); - aircraft.setTransponder(transponder); - aircraft.setCalculcatedDistanceToPosition(this->ownAircraft().getPosition()); // distance from myself - this->m_vatsimDataFileReader->updateWithVatsimDataFileData(aircraft); - this->m_aircraftsInRange.push_back(aircraft); - - // and new client, there is a chance it has been created by - // custom packet first - if (!this->m_otherClients.contains(&CClient::getCallsign, callsign)) - { - CClient c(callsign); - this->m_otherClients.push_back(c); // initial, will be filled by data later - } - - if (this->isConnected()) - { - // only if still connected - this->m_network->sendFrequencyQuery(callsign); - this->m_network->sendRealNameQuery(callsign); - this->m_network->sendIcaoCodesQuery(callsign); - this->m_network->sendCapabilitiesQuery(callsign); - this->m_network->sendServerQuery(callsign); - this->sendFsipirCustomPacket(callsign); // own aircraft model - } - } - else - { - // update - CLength distance = this->ownAircraft().calculcateDistanceToPosition(situation.getPosition()); - distance.switchUnit(CLengthUnit::NM()); - CIndexVariantMap vm; - vm.addValue(CAircraft::IndexTransponder, transponder); - vm.addValue(CAircraft::IndexSituation, situation); - vm.addValue(CAircraft::IndexDistance, distance); - this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); - } - - emit this->changedAircraftsInRange(); - emit changedAircraftSituation(callsign, situation); - } - - /* - * Pilot disconnected - */ - void CContextNetwork::psFsdPilotDisconnected(const CCallsign &callsign) - { - this->m_aircraftsInRange.removeIf(&CAircraft::getCallsign, callsign); - this->m_otherClients.removeIf(&CClient::getCallsign, callsign); - } - - /* - * Frequency received - */ - void CContextNetwork::psFsdFrequencyReceived(const CCallsign &callsign, const CFrequency &frequency) - { - // update - CIndexVariantMap vm(CAircraft::IndexFrequencyCom1, frequency.toQVariant()); - this->m_aircraftsInRange.applyIf(BlackMisc::Predicates::MemberEqual(&CAircraft::getCallsign, callsign), vm); - emit this->changedAircraftsInRange(); - } - -} // namespace diff --git a/src/blackcore/context_network_atc.cpp b/src/blackcore/context_network_atc.cpp index 2f23adeaa..8aa284258 100644 --- a/src/blackcore/context_network_atc.cpp +++ b/src/blackcore/context_network_atc.cpp @@ -43,19 +43,8 @@ namespace BlackCore /* * Update bookings */ - void CContextNetwork::psReceivedBookings(const CAtcStationList &bookedStations) + void CContextNetwork::psReceivedBookings(const CAtcStationList &) { - this->m_atcStationsBooked.clear(); - foreach(CAtcStation bookedStation, bookedStations) - { - // complete by VATSIM data file data - this->m_vatsimDataFileReader->getAtcStations().updateFromVatsimDataFileStation(bookedStation); - // exchange booking and online data - this->m_atcStationsOnline.mergeWithBooking(bookedStation); - // into list - this->m_atcStationsBooked.push_back(bookedStation); - } - // TODO (MS) no test for if (this->getIContextApplication()) here? this->getIContextApplication()->sendStatusMessage(CStatusMessage::getInfoMessage("Read bookings from network", CStatusMessage::TypeTrafficNetwork)); } @@ -67,14 +56,9 @@ namespace BlackCore { Q_ASSERT(this->m_network); if (!this->isConnected()) return; - this->requestAtisUpdates(); - // other updates - foreach(CAircraft aircraft, this->m_aircraftsInRange) - { - this->m_network->sendFrequencyQuery(aircraft.getCallsign()); - this->m_network->sendIcaoCodesQuery(aircraft.getCallsign()); - } + this->requestAtisUpdates(); + this->m_airspace->requestDataUpdates(); } /* @@ -84,10 +68,8 @@ namespace BlackCore { Q_ASSERT(this->m_network); if (!this->isConnected()) return; - foreach(CAtcStation station, this->m_atcStationsOnline) - { - this->m_network->sendAtisQuery(station.getCallsign()); - } + + this->m_airspace->requestAtisUpdates(); } /* @@ -96,29 +78,7 @@ namespace BlackCore BlackMisc::Aviation::CInformationMessage CContextNetwork::getMetar(const BlackMisc::Aviation::CAirportIcao &airportIcaoCode) { this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO, airportIcaoCode.toQString()); - CInformationMessage metar; - if (airportIcaoCode.isEmpty()) return metar; - if (this->m_metarCache.contains(airportIcaoCode)) metar = this->m_metarCache[airportIcaoCode]; - if (metar.isEmpty() || metar.timeDiffReceivedMs() > 10 * 1000) - { - // outdated, or not in cache at all - this->m_network->sendMetarQuery(airportIcaoCode); - - // with this little trick we try to make an asynchronous signal / slot - // based approach a synchronous return value - QTime waitForMetar = QTime::currentTime().addMSecs(1000); - while (QTime::currentTime() < waitForMetar) - { - // process some other events and hope network answer is received already - QCoreApplication::processEvents(QEventLoop::AllEvents, 100); - if (m_metarCache.contains(airportIcaoCode)) - { - metar = this->m_metarCache[airportIcaoCode]; - break; - } - } - } - return metar; + return m_airspace->getMetar(airportIcaoCode); } /* @@ -126,22 +86,8 @@ namespace BlackCore */ CAtcStationList CContextNetwork::getSelectedAtcStations() const { - CAtcStation com1Station; - CAtcStation com2Station; - - CAtcStationList stationsCom1 = this->m_atcStationsOnline.findIfComUnitTunedIn25KHz(this->ownAircraft().getCom1System()); - CAtcStationList stationsCom2 = this->m_atcStationsOnline.findIfComUnitTunedIn25KHz(this->ownAircraft().getCom2System()); - if (!stationsCom1.isEmpty()) - { - stationsCom1.sortBy(&CAtcStation::getDistanceToPlane); - com1Station = stationsCom1.front(); - } - - if (!stationsCom2.isEmpty()) - { - stationsCom2.sortBy(&CAtcStation::getDistanceToPlane); - com2Station = stationsCom2.front(); - } + CAtcStation com1Station = this->m_airspace->getAtcStationForComUnit(this->ownAircraft().getCom1System()); + CAtcStation com2Station = this->m_airspace->getAtcStationForComUnit(this->ownAircraft().getCom2System()); CAtcStationList selectedStations; selectedStations.push_back(com1Station); @@ -169,122 +115,4 @@ namespace BlackCore return rooms; } - - /* - * ATC Position update - */ - void CContextNetwork::psFsdAtcPositionUpdate(const CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency, const CCoordinateGeodetic &position, const BlackMisc::PhysicalQuantities::CLength &range) - { - CAtcStationList stationsWithCallsign = this->m_atcStationsOnline.findByCallsign(callsign); - if (stationsWithCallsign.isEmpty()) - { - // new station - CAtcStation station; - station.setCallsign(callsign); - station.setRange(range); - station.setFrequency(frequency); - station.setPosition(position); - station.setOnline(true); - station.calculcateDistanceToPlane(this->ownAircraft().getPosition()); - this->m_vatsimDataFileReader->getAtcStations().updateFromVatsimDataFileStation(station); // prefill - this->m_atcStationsOnline.push_back(station); - - if (this->isConnected()) - { - emit this->m_network->sendRealNameQuery(callsign); - emit this->m_network->sendAtisQuery(callsign); // request ATIS and voice rooms - emit this->m_network->sendServerQuery(callsign); - } - - emit this->changedAtcStationsOnline(); - // Remark: this->changedAtcStationOnlineConnectionStatus(station, true); - // will be sent in psFsdAtisVoiceRoomReceived - } - else - { - // update - CIndexVariantMap values; - values.addValue(CAtcStation::IndexFrequency, frequency); - values.addValue(CAtcStation::IndexPosition, position); - values.addValue(CAtcStation::IndexRange, range); - this->m_atcStationsOnline.applyIf(BlackMisc::Predicates::MemberEqual(&CAtcStation::getCallsign, callsign), values); - emit this->changedAtcStationsOnline(); - } - } - - /* - * ATC Controller disconnected - */ - void CContextNetwork::psFsdAtcControllerDisconnected(const CCallsign &callsign) - { - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) - { - CAtcStation removeStation = this->m_atcStationsOnline.findByCallsign(callsign).front(); - this->m_atcStationsOnline.removeIf(&CAtcStation::getCallsign, callsign); - emit this->changedAtcStationsOnline(); - emit this->changedAtcStationOnlineConnectionStatus(removeStation, false); - } - - // booked - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, CIndexVariantMap(CAtcStation::IndexIsOnline, QVariant(false))); - } - - /* - * ATIS received - */ - void CContextNetwork::psFsdAtisReceived(const CCallsign &callsign, const CInformationMessage &atisMessage) - { - if (callsign.isEmpty()) return; - CIndexVariantMap vm(CAtcStation::IndexAtis, atisMessage.toQVariant()); - this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); - if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); - } - - /* - * ATIS (voice room part) received - */ - void CContextNetwork::psFsdAtisVoiceRoomReceived(const CCallsign &callsign, const QString &url) - { - QString trimmedUrl = url.trimmed(); - CIndexVariantMap vm(CAtcStation::IndexVoiceRoomUrl, trimmedUrl); - if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) - { - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); - emit this->changedAtcStationsBooked(); - } - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) - { - this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); - CAtcStation station = this->m_atcStationsOnline.findFirstByCallsign(callsign); - emit this->changedAtcStationsBooked(); - emit this->changedAtcStationOnlineConnectionStatus(station, true); - } - vm = CIndexVariantMap(CClient::IndexVoiceCapabilities, CVoiceCapabilities(CVoiceCapabilities::Voice).toQVariant()); - this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); - } - - /* - * ATIS (logoff time part) received - */ - void CContextNetwork::psFsdAtisLogoffTimeReceived(const CCallsign &callsign, const QString &zuluTime) - { - if (zuluTime.length() == 4) - { - // Logic to set logoff time - bool ok; - int h = zuluTime.left(2).toInt(&ok); - if (!ok) return; - int m = zuluTime.right(2).toInt(&ok); - if (!ok) return; - QDateTime logoffDateTime = QDateTime::currentDateTimeUtc(); - logoffDateTime.setTime(QTime(h, m)); - CIndexVariantMap vm(CAtcStation::IndexBookedUntil, logoffDateTime); - this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); - if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsign)) emit this->changedAtcStationsBooked(); - } - } } // namespace diff --git a/src/blackcore/context_network_impl.cpp b/src/blackcore/context_network_impl.cpp index 393249870..b110526d8 100644 --- a/src/blackcore/context_network_impl.cpp +++ b/src/blackcore/context_network_impl.cpp @@ -24,6 +24,7 @@ using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::Aviation; using namespace BlackMisc::Network; using namespace BlackMisc::Geo; +using namespace BlackMisc::Audio; namespace BlackCore { @@ -32,7 +33,7 @@ namespace BlackCore * Init this context */ CContextNetwork::CContextNetwork(CRuntimeConfig::ContextMode mode, CRuntime *runtime) : - IContextNetwork(mode, runtime), m_network(nullptr), m_vatsimBookingReader(nullptr), m_vatsimDataFileReader(nullptr), m_dataUpdateTimer(nullptr) + IContextNetwork(mode, runtime), m_network(nullptr), m_airspace(nullptr), m_vatsimBookingReader(nullptr), m_vatsimDataFileReader(nullptr), m_dataUpdateTimer(nullptr) { Q_ASSERT(this->getRuntime()); Q_ASSERT(this->getRuntime()->getIContextSettings()); @@ -40,22 +41,7 @@ namespace BlackCore // 1. Init by "network driver" this->m_network = new CNetworkVatlib(this); this->connect(this->m_network, &INetwork::connectionStatusChanged, this, &CContextNetwork::psFsdConnectionStatusChanged); - this->connect(this->m_network, &INetwork::atcPositionUpdate, this, &CContextNetwork::psFsdAtcPositionUpdate); - this->connect(this->m_network, &INetwork::atisReplyReceived, this, &CContextNetwork::psFsdAtisReceived); - this->connect(this->m_network, &INetwork::atisVoiceRoomReplyReceived, this, &CContextNetwork::psFsdAtisVoiceRoomReceived); - this->connect(this->m_network, &INetwork::atisLogoffTimeReplyReceived, this, &CContextNetwork::psFsdAtisLogoffTimeReceived); - this->connect(this->m_network, &INetwork::metarReplyReceived, this, &CContextNetwork::psFsdMetarReceived); - this->connect(this->m_network, &INetwork::flightPlanReplyReceived, this, &CContextNetwork::psFsdFlightplanReceived); - this->connect(this->m_network, &INetwork::realNameReplyReceived, this, &CContextNetwork::psFsdRealNameReplyReceived); - this->connect(this->m_network, &INetwork::icaoCodesReplyReceived, this, &CContextNetwork::psFsdIcaoCodesReceived); - this->connect(this->m_network, &INetwork::pilotDisconnected, this, &CContextNetwork::psFsdPilotDisconnected); - this->connect(this->m_network, &INetwork::atcDisconnected, this, &CContextNetwork::psFsdAtcControllerDisconnected); - this->connect(this->m_network, &INetwork::aircraftPositionUpdate, this, &CContextNetwork::psFsdAircraftUpdateReceived); - this->connect(this->m_network, &INetwork::frequencyReplyReceived, this, &CContextNetwork::psFsdFrequencyReceived); this->connect(this->m_network, &INetwork::textMessagesReceived, this, &CContextNetwork::psFsdTextMessageReceived); - this->connect(this->m_network, &INetwork::capabilitiesReplyReceived, this, &CContextNetwork::psFsdCapabilitiesReplyReceived); - this->connect(this->m_network, &INetwork::customPacketReceived, this, &CContextNetwork::psFsdCustomPacketReceived); - this->connect(this->m_network, &INetwork::serverReplyReceived, this, &CContextNetwork::psFsdServerReplyReceived); // 2. VATSIM bookings this->m_vatsimBookingReader = new CVatsimBookingReader(this->getRuntime()->getIContextSettings()->getNetworkSettings().getBookingServiceUrl(), this); @@ -75,6 +61,15 @@ namespace BlackCore this->connect(this->m_dataUpdateTimer, &QTimer::timeout, this, &CContextNetwork::requestDataUpdates); this->m_dataUpdateTimer->start(30 * 1000); + // 5. Airspace contents + this->m_airspace = new CAirspaceMonitor(this, this->m_network, this->m_vatsimBookingReader, this->m_vatsimDataFileReader); + this->connect(this->m_airspace, &CAirspaceMonitor::changedAtcStationsOnline, this, &CContextNetwork::changedAtcStationsOnline); + this->connect(this->m_airspace, &CAirspaceMonitor::changedAtcStationsBooked, this, &CContextNetwork::changedAtcStationsBooked); + this->connect(this->m_airspace, &CAirspaceMonitor::changedAtcStationOnlineConnectionStatus, this, &CContextNetwork::changedAtcStationOnlineConnectionStatus); + this->connect(this->m_airspace, &CAirspaceMonitor::changedAircraftsInRange, this, &CContextNetwork::changedAircraftsInRange); + this->connect(this->m_airspace, &CAirspaceMonitor::changedAircraftSituation, this, &CContextNetwork::changedAircraftSituation); + this->connect(this->getIContextOwnAircraft(), &IContextOwnAircraft::changedAircraft, this->m_airspace, &CAirspaceMonitor::setOwnAircraft); + // FIXME (MS) conditional increases the number of scenarios which must be considered and continuously tested if (this->getIContextApplication()) { @@ -144,11 +139,7 @@ namespace BlackCore if (this->m_network->isConnected()) { this->m_network->terminateConnection(); - this->m_aircraftsInRange.clear(); - this->m_atcStationsBooked.clear(); - this->m_atcStationsOnline.clear(); - this->m_otherClients.clear(); - this->m_metarCache.clear(); + this->m_airspace->clear(); msgs.push_back(CStatusMessage(CStatusMessage::TypeTrafficNetwork, CStatusMessage::SeverityInfo, "Connection terminating")); } else @@ -189,30 +180,7 @@ namespace BlackCore CFlightPlan CContextNetwork::loadFlightPlanFromNetwork(const BlackMisc::Aviation::CCallsign &callsign) const { this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO); - CFlightPlan plan; - - // use cache, but not for own callsign (always reload) - if (this->m_flightPlanCache.contains(callsign)) plan = this->m_flightPlanCache[callsign]; - if (!plan.wasSentOrLoaded() || plan.timeDiffSentOrLoadedMs() > 30 * 1000) - { - // outdated, or not in cache at all - this->m_network->sendFlightPlanQuery(callsign); - - // with this little trick we try to make an asynchronous signal / slot - // based approach a synchronous return value - QTime waitForFlightPlan = QTime::currentTime().addMSecs(1000); - while (QTime::currentTime() < waitForFlightPlan) - { - // process some other events and hope network answer is received already - QCoreApplication::processEvents(QEventLoop::AllEvents, 100); - if (m_flightPlanCache.contains(callsign)) - { - plan = this->m_flightPlanCache[callsign]; - break; - } - } - } - return plan; + return this->m_airspace->loadFlightPlanFromNetwork(callsign); } /* @@ -220,18 +188,7 @@ namespace BlackCore */ CUserList CContextNetwork::getUsers() const { - CUserList users; - foreach(CAtcStation station, this->m_atcStationsOnline) - { - CUser user = station.getController(); - users.push_back(user); - } - foreach(CAircraft aircraft, this->m_aircraftsInRange) - { - CUser user = aircraft.getPilot(); - users.push_back(user); - } - return users; + return this->m_airspace->getUsers(); } /* @@ -241,58 +198,7 @@ namespace BlackCore { CUserList users; if (callsigns.isEmpty()) return users; - CCallsignList searchList(callsigns); - - // myself, which is not in the lists below - CAircraft ownAircraft = this->ownAircraft(); - if (!ownAircraft.getCallsign().isEmpty() && searchList.contains(ownAircraft.getCallsign())) - { - searchList.remove(ownAircraft.getCallsign()); - users.push_back(ownAircraft.getPilot()); - } - - // do aircrafts first, this will handle most callsigns - foreach(CAircraft aircraft, this->m_aircraftsInRange) - { - if (searchList.isEmpty()) break; - CCallsign callsign = aircraft.getCallsign(); - if (searchList.contains(callsign)) - { - CUser user = aircraft.getPilot(); - users.push_back(user); - searchList.remove(callsign); - } - } - - foreach(CAtcStation station, this->m_atcStationsOnline) - { - if (searchList.isEmpty()) break; - CCallsign callsign = station.getCallsign(); - if (searchList.contains(callsign)) - { - CUser user = station.getController(); - users.push_back(user); - searchList.remove(callsign); - } - } - - // we might have unresolved callsigns - // these are the ones not in range - foreach(CCallsign callsign, searchList) - { - CUserList usersByCallsign = this->m_vatsimDataFileReader->getUsersForCallsign(callsign); - if (usersByCallsign.isEmpty()) - { - CUser user; - user.setCallsign(callsign); - users.push_back(user); - } - else - { - users.push_back(usersByCallsign[0]); - } - } - return users; + return this->m_airspace->getUsersForCallsigns(callsigns); } /* @@ -312,7 +218,7 @@ namespace BlackCore */ CClientList CContextNetwork::getOtherClients() const { - return this->m_otherClients; + return this->m_airspace->getOtherClients(); } /* @@ -320,13 +226,7 @@ namespace BlackCore */ CClientList CContextNetwork::getOtherClientsForCallsigns(const CCallsignList &callsigns) const { - CClientList clients; - if (callsigns.isEmpty()) return clients; - foreach(CCallsign callsign, callsigns) - { - clients.push_back(this->m_otherClients.findBy(&CClient::getCallsign, callsign)); - } - return clients; + return this->m_airspace->getOtherClientsForCallsigns(callsigns); } /* @@ -360,24 +260,6 @@ namespace BlackCore emit this->connectionStatusChanged(from, to, message); } - /* - * Name query - */ - void CContextNetwork::psFsdRealNameReplyReceived(const CCallsign &callsign, const QString &realname) - { - this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO, { callsign.toQString(), realname }); - if (realname.isEmpty()) return; - CIndexVariantMap vm(CAtcStation::IndexControllerRealName, realname); - this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsign, vm); - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsign, vm); - - vm = CIndexVariantMap(CAircraft::IndexPilotRealName, realname); - this->m_aircraftsInRange.applyIf(&CAircraft::getCallsign, callsign, vm); - - vm = CIndexVariantMap(CClient::IndexRealName, realname); - this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); - } - /* * Data file (VATSIM) has been read */ @@ -397,115 +279,6 @@ namespace BlackCore this->textMessagesReceived(messages); // relay } - /* - * Capabilities - */ - void CContextNetwork::psFsdCapabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags) - { - if (callsign.isEmpty()) return; - CIndexVariantMap capabilities; - capabilities.addValue(CClient::FsdAtisCanBeReceived, (flags & CNetworkVatlib::AcceptsAtisResponses)); - capabilities.addValue(CClient::FsdWithInterimPositions, (flags & CNetworkVatlib::SupportsInterimPosUpdates)); - capabilities.addValue(CClient::FsdWithModelDescription, (flags & CNetworkVatlib::SupportsModelDescriptions)); - CIndexVariantMap vm(CClient::IndexCapabilities, capabilities.toQVariant()); - this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); - } - - /* - * Custom packets - */ - void CContextNetwork::psFsdCustomPacketReceived(const CCallsign &callsign, const QString &packet, const QStringList &data) - { - if (callsign.isEmpty() || data.isEmpty()) return; - if (packet.startsWith("FSIPIR", Qt::CaseInsensitive)) - { - // Request of other client, I can get the other's model from that - // FsInn response is usually my model - QString model = data.last(); - if (model.isEmpty()) return; - CIndexVariantMap vm(CClient::IndexQueriedModelString, QVariant(model)); - if (!this->m_otherClients.contains(&CClient::getCallsign, callsign)) - { - // with custom packets it can happen, - //the packet is received before any other packet - this->m_otherClients.push_back(CClient(callsign)); - } - this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); - this->sendFsipiCustomPacket(callsign); // response - } - } - - /* - * Host - */ - void CContextNetwork::psFsdServerReplyReceived(const CCallsign &callsign, const QString &server) - { - if (callsign.isEmpty() || server.isEmpty()) return; - CIndexVariantMap vm(CClient::IndexServer, QVariant(server)); - this->m_otherClients.applyIf(&CClient::getCallsign, callsign, vm); - } - - /* - * Metar received - */ - void CContextNetwork::psFsdMetarReceived(const QString &metarMessage) - { - if (metarMessage.length() < 10) return; // invalid - const QString icaoCode = metarMessage.left(4).toUpper(); - const QString icaoCodeTower = icaoCode + "_TWR"; - CCallsign callsignTower(icaoCodeTower); - CInformationMessage metar(CInformationMessage::METAR, metarMessage); - - // add METAR to existing stations - CIndexVariantMap vm(CAtcStation::IndexMetar, metar.toQVariant()); - this->m_atcStationsOnline.applyIf(&CAtcStation::getCallsign, callsignTower, vm); - this->m_atcStationsBooked.applyIf(&CAtcStation::getCallsign, callsignTower, vm); - this->m_metarCache.insert(icaoCode, metar); - if (this->m_atcStationsOnline.contains(&CAtcStation::getCallsign, callsignTower)) emit this->changedAtcStationsOnline(); - if (this->m_atcStationsBooked.contains(&CAtcStation::getCallsign, callsignTower)) emit this->changedAtcStationsBooked(); - } - - /* - * Flight plan received - */ - void CContextNetwork::psFsdFlightplanReceived(const CCallsign &callsign, const CFlightPlan &flightPlan) - { - CFlightPlan plan(flightPlan); - plan.setWhenLastSentOrLoaded(QDateTime::currentDateTimeUtc()); - this->m_flightPlanCache.insert(callsign, plan); - } - - - void CContextNetwork::sendFsipiCustomPacket(const CCallsign &recipientCallsign) const - { - QStringList data = this->createFsipiCustomPacketData(); - this->m_network->sendCustomPacket(recipientCallsign.asString(), "FSIPI", data); - } - - void CContextNetwork::sendFsipirCustomPacket(const CCallsign &recipientCallsign) const - { - QStringList data = this->createFsipiCustomPacketData(); - this->m_network->sendCustomPacket(recipientCallsign.asString(), "FSIPIR", data); - } - - QStringList CContextNetwork::createFsipiCustomPacketData() const - { - CAircraft me = this->ownAircraft(); - CAircraftIcao icao = me.getIcaoInfo(); - QString modelString; - // FIXME (MS) simulator context should send model string to own aircraft context, so we wouldn't need to interrogate the simulator context here. - if (this->getIContextSimulator()) - { - if (this->getIContextSimulator()->isConnected()) modelString = this->getIContextSimulator()->getOwnAircraftModel().getQueriedModelString(); - } - if (modelString.isEmpty()) modelString = CProject::systemNameAndVersion(); - QStringList data = CNetworkVatlib::createFsipiCustomPacketData( - "0", icao.getAirlineDesignator(), icao.getAircraftDesignator(), - "", "", "", "", - icao.getAircraftCombinedType(), modelString); - return data; - } - const CAircraft &CContextNetwork::ownAircraft() const { Q_ASSERT(this->getRuntime()); diff --git a/src/blackcore/context_network_impl.h b/src/blackcore/context_network_impl.h index ab414ac48..e8a070bd5 100644 --- a/src/blackcore/context_network_impl.h +++ b/src/blackcore/context_network_impl.h @@ -11,6 +11,7 @@ #include "blackcore/context_runtime.h" #include "blackcore/dbus_server.h" #include "blackcore/network.h" +#include "blackcore/airspace_monitor.h" #include "blackmisc/avatcstationlist.h" #include "blackmisc/setnetwork.h" #include "blackmisc/nwclientlist.h" @@ -45,21 +46,21 @@ namespace BlackCore virtual const BlackMisc::Aviation::CAtcStationList getAtcStationsOnline() const override { this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO); - return m_atcStationsOnline; + return this->m_airspace->getAtcStationsOnline(); } //! \copydoc IContextNetwork::getAtcStationsBooked() virtual const BlackMisc::Aviation::CAtcStationList getAtcStationsBooked() const override { this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO); - return m_atcStationsBooked; + return this->m_airspace->getAtcStationsBooked(); } //! \copydoc IContextNetwork::getAircraftsInRange() virtual const BlackMisc::Aviation::CAircraftList getAircraftsInRange() const override { this->getRuntime()->logSlot(c_logContext, Q_FUNC_INFO); - return m_aircraftsInRange; + return this->m_airspace->getAircraftInRange(); } //! \copydoc IContextNetwork::connectToNetwork() @@ -125,13 +126,8 @@ namespace BlackCore private: static const auto c_logContext = CRuntime::LogForNetwork; - BlackMisc::Aviation::CAtcStationList m_atcStationsOnline; - BlackMisc::Aviation::CAtcStationList m_atcStationsBooked; - BlackMisc::Aviation::CAircraftList m_aircraftsInRange; - BlackMisc::Network::CClientList m_otherClients; + CAirspaceMonitor *m_airspace; BlackCore::INetwork *m_network; - QMap m_metarCache /*!< Keep METARs for a while */; - QMap m_flightPlanCache /*!< Keep flight plans for a while */; // for reading XML and VATSIM data files CVatsimBookingReader *m_vatsimBookingReader; @@ -146,15 +142,6 @@ namespace BlackCore return this->getRuntime()->getIContextSettings()->getNetworkSettings(); } - //! Send FsInn custom packet - void sendFsipiCustomPacket(const BlackMisc::Aviation::CCallsign &recipientCallsign) const; - - //! Send FsInn custom packet - void sendFsipirCustomPacket(const BlackMisc::Aviation::CCallsign &recipientCallsign) const; - - //! Custom packet data based on own aircraft / model - QStringList createFsipiCustomPacketData() const; - //! Own aircraft const BlackMisc::Aviation::CAircraft &ownAircraft() const; @@ -175,64 +162,8 @@ namespace BlackCore */ void psFsdConnectionStatusChanged(INetwork::ConnectionStatus from, INetwork::ConnectionStatus to, const QString &message); - //! ATC position update - void psFsdAtcPositionUpdate(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::Geo::CCoordinateGeodetic &position, const BlackMisc::PhysicalQuantities::CLength &range); - - /*! - * \brief Controller disconnected - * \param callsign callsign of controller - */ - void psFsdAtcControllerDisconnected(const BlackMisc::Aviation::CCallsign &callsign); - - //! ATIS received - void psFsdAtisReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CInformationMessage &atisMessage); - - /*! - * \brief ATIS received (voice room part) - * \param callsign station callsign - * \param url voice room's URL - */ - void psFsdAtisVoiceRoomReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &url); - - /*! - * \brief ATIS received (logoff time part) - * \param callsign station callsign - * \param zuluTime UTC time, when controller will logoff - */ - void psFsdAtisLogoffTimeReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &zuluTime); - - //! METAR received - void psFsdMetarReceived(const QString &metarMessage); - - //! Flight plan received - void psFsdFlightplanReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CFlightPlan &flightplan); - - //! Realname recevied - void psFsdRealNameReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &realname); - - //! Plane ICAO codes received - void psFsdIcaoCodesReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftIcao &icaoData); - - //! Aircraft position update received - void psFsdAircraftUpdateReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::Aviation::CAircraftSituation &situation, const BlackMisc::Aviation::CTransponder &transponder); - - //! Pilot disconnected - void psFsdPilotDisconnected(const BlackMisc::Aviation::CCallsign &callsign); - - //! Frequency received - void psFsdFrequencyReceived(const BlackMisc::Aviation::CCallsign &callsign, const BlackMisc::PhysicalQuantities::CFrequency &frequency); - //! Radio text messages received void psFsdTextMessageReceived(const BlackMisc::Network::CTextMessageList &messages); - - //! Capabilities received - void psFsdCapabilitiesReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, quint32 flags); - - //! Custom packet - void psFsdCustomPacketReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &package, const QStringList &data); - - //! Server reply received - void psFsdServerReplyReceived(const BlackMisc::Aviation::CCallsign &callsign, const QString &server); }; }