diff --git a/src/MissionManager/AirMapManager.cc b/src/MissionManager/AirMapManager.cc index 29f16589a36b806b8a66c67c512391257db5ecdd..cd06ea97e8c954d5f851122bbda1417591aecdc7 100644 --- a/src/MissionManager/AirMapManager.cc +++ b/src/MissionManager/AirMapManager.cc @@ -817,9 +817,66 @@ void AirMapTelemetry::stopTelemetryStream() _state = State::EndCommunication; } +void AirMapTrafficAlertClient::startConnection(const QString& flightID, const QString& password) +{ + _flightID = flightID; + + setClientId("qgc-client-id"); // TODO: random? + setUsername(flightID); + setPassword(password); + connectToHost(); +} + +void AirMapTrafficAlertClient::onError(const QMQTT::ClientError error) +{ + // TODO: user-facing warning + qWarning() << "QMQTT error" << (int)error; +} + +void AirMapTrafficAlertClient::onConnected() +{ + // see https://developers.airmap.com/v2.0/docs/traffic-alerts + subscribe("uav/traffic/sa/" + _flightID, 0); // situational awareness topic +} + +void AirMapTrafficAlertClient::onSubscribed(const QString& topic) +{ + qCDebug(AirMapManagerLog) << "subscribed" << topic; +} + +void AirMapTrafficAlertClient::onReceived(const QMQTT::Message& message) +{ + qCDebug(AirMapManagerLog) << "traffic publish received:" << QString::fromUtf8(message.payload()); + // looks like this: + // "{"traffic":[{"id":"OAW380-1501565193-airline-0081","direction":-41.20024491976423,"altitude":"5675", + // "latitude":"47.50335","longitude":"8.40912","recorded_time":"1501774066","ground_speed_kts":"199", + // "true_heading":"241","properties":{"aircraft_id":"OAW380"},"timestamp":1501774079064}]}" + + QJsonParseError parseError; + QJsonDocument responseJson = QJsonDocument::fromJson(message.payload(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + qWarning() << "QMQTT JSON parsing error" << parseError.errorString(); + return; + } + QJsonObject rootObject = responseJson.object(); + const QJsonArray& trafficArray = rootObject["traffic"].toArray(); + for (int i = 0; i < trafficArray.count(); i++) { + const QJsonObject& trafficObject = trafficArray[i].toObject(); + QString traffic_id = trafficObject["id"].toString(); + float altitude = trafficObject["altitude"].toString().toFloat() * 0.3048f; // feet to meters + double lat = trafficObject["latitude"].toString().toDouble(); + double lon = trafficObject["longitude"].toString().toDouble(); + float heading = trafficObject["true_heading"].toString().toDouble(); // in deg + QString vehicle_id = trafficObject["properties"].toObject()["aircraft_id"].toString(); + emit trafficUpdate(traffic_id, vehicle_id, QGeoCoordinate(lat, lon, altitude), heading); + + } +} + + AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) : QGCTool(app, toolbox), _airspaceRestrictionManager(_networkingData), _flightManager(_networkingData), - _telemetry(_networkingData) + _telemetry(_networkingData), _trafficAlerts("mqtt-prod.airmap.io", 8883) { _updateTimer.setInterval(2000); _updateTimer.setSingleShot(true); @@ -829,7 +886,9 @@ AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) connect(&_flightManager, &AirMapFlightManager::networkError, this, &AirMapManager::_networkError); connect(&_telemetry, &AirMapTelemetry::networkError, this, &AirMapManager::_networkError); - connect(&_flightManager, &AirMapFlightManager::flightPermitStatusChanged, this, &AirMapManager::flightPermitStatusChanged); + connect(&_flightManager, &AirMapFlightManager::flightPermitStatusChanged, this, &AirMapManager::_flightPermitStatusChanged); + + connect(&_trafficAlerts, &AirMapTrafficAlertClient::trafficUpdate, this, &AirMapManager::trafficUpdate); qmlRegisterUncreatableType("QGroundControl", 1, 0, "AirspaceAuthorization", "Reference only"); @@ -840,6 +899,18 @@ AirMapManager::AirMapManager(QGCApplication* app, QGCToolbox* toolbox) GOOGLE_PROTOBUF_VERIFY_VERSION; } +void AirMapManager::_flightPermitStatusChanged() +{ + // activate traffic alerts + if (_flightManager.flightPermitStatus() == AirspaceAuthorization::PermitAccepted) { + qCDebug(AirMapManagerLog) << "Subscribing to Traffic Alerts"; + // since we already created the flight, we know that we have a valid login token + _trafficAlerts.startConnection(_flightManager.flightID(), _networkingData.login.JWTToken()); + } + + emit flightPermitStatusChanged(); +} + AirMapManager::~AirMapManager() { @@ -885,6 +956,7 @@ void AirMapManager::_vehicleArmedChanged(bool armed) // end the flight if we were in mission mode during arming or disarming // TODO: needs to be improved. for instance if we do RTL and then want to continue the mission... if (_vehicleWasInMissionMode || _vehicle->flightMode() == _vehicle->missionFlightMode()) { + _trafficAlerts.disconnectFromHost(); _flightManager.endFlight(); } } diff --git a/src/MissionManager/AirMapManager.h b/src/MissionManager/AirMapManager.h index df2407763f763feb61e6bcefda296d25e7b8120a..8c11e1bb5db441eff280de81e8b1d455824c84a9 100644 --- a/src/MissionManager/AirMapManager.h +++ b/src/MissionManager/AirMapManager.h @@ -16,6 +16,8 @@ #include "MissionItem.h" #include "MultiVehicleManager.h" +#include + #include #include #include @@ -319,6 +321,43 @@ private: float _lastHdop = 1.f; }; + +class AirMapTrafficAlertClient : public QMQTT::Client +{ + Q_OBJECT +public: + AirMapTrafficAlertClient(const QString& host, const quint16 port, QObject* parent = NULL) + : QMQTT::Client(host, port, QSslConfiguration::defaultConfiguration(), true, parent) + { + connect(this, &AirMapTrafficAlertClient::connected, this, &AirMapTrafficAlertClient::onConnected); + connect(this, &AirMapTrafficAlertClient::subscribed, this, &AirMapTrafficAlertClient::onSubscribed); + connect(this, &AirMapTrafficAlertClient::received, this, &AirMapTrafficAlertClient::onReceived); + connect(this, &AirMapTrafficAlertClient::error, this, &AirMapTrafficAlertClient::onError); + } + virtual ~AirMapTrafficAlertClient() = default; + + void startConnection(const QString& flightID, const QString& password); + +signals: + void trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + +private slots: + + void onError(const QMQTT::ClientError error); + + void onConnected(); + + void onSubscribed(const QString& topic); + + void onReceived(const QMQTT::Message& message); + +private: + QString _flightID; +}; + + + + /// AirMap server communication support. class AirMapManager : public QGCTool { @@ -347,6 +386,8 @@ public: signals: void flightPermitStatusChanged(); + void trafficUpdate(QString traffic_id, QString vehicle_id, QGeoCoordinate location, float heading); + private slots: void _updateToROI(void); void _networkError(QNetworkReply::NetworkError code, const QString& errorString, const QString& serverErrorMessage); @@ -354,6 +395,8 @@ private slots: void _activeVehicleChanged(Vehicle* activeVehicle); void _vehicleArmedChanged(bool armed); + void _flightPermitStatusChanged(); + private: bool _hasAPIKey() const { return _networkingData.airmapAPIKey != ""; } @@ -366,6 +409,7 @@ private: AirspaceRestrictionManager _airspaceRestrictionManager; AirMapFlightManager _flightManager; AirMapTelemetry _telemetry; + AirMapTrafficAlertClient _trafficAlerts; QGeoCoordinate _roiCenter; double _roiRadius;