diff --git a/QGCExternalLibs.pri b/QGCExternalLibs.pri index ace9afcd190..4067627fa5e 100644 --- a/QGCExternalLibs.pri +++ b/QGCExternalLibs.pri @@ -264,3 +264,9 @@ contains (DEFINES, DISABLE_ZEROCONF) { } else { message("Skipping support for Zeroconf (unsupported platform)") } + +# UTM Adapter Enabled +contains (DEFINES, CONFIG_UTM_ADAPTER){ + INCLUDEPATH += $$PWD/libs/libevents/libevents/libs/cpp/parse/nlohmann_json/include + LIBS += -lboost_system -lboost_thread -lssl -lcrypto +} diff --git a/qgroundcontrol.pro b/qgroundcontrol.pro index 1f34758b4b9..7a42e8f18d6 100644 --- a/qgroundcontrol.pro +++ b/qgroundcontrol.pro @@ -1451,3 +1451,49 @@ LinuxBuild { INSTALLS += target share_qgroundcontrol share_icons share_metainfo share_applications } + +# UTM Adapter Enabled +contains (DEFINES, CONFIG_UTM_ADAPTER) { + + #-- To test with UTM Adapter Enabled Flag + LIBS += -lboost_system -lboost_thread -lssl -lcrypto + INCLUDEPATH += \ + src/UTMSP \ + + RESOURCES += \ + src/UTMSP/utmsp.qrc + + HEADERS += \ + src/UTMSP/UTMSPLogger.h \ + src/UTMSP/UTMSPRestInterface.h \ + src/UTMSP/UTMSPBlenderRestInterface.h \ + src/UTMSP/UTMSPAuthorization.h \ + src/UTMSP/UTMSPNetworkRemoteIDManager.h \ + src/UTMSP/UTMSPAircraft.h \ + src/UTMSP/UTMSPFlightDetails.h \ + src/UTMSP/UTMSPOperator.h \ + src/UTMSP/UTMSPFlightPlanManager.h \ + src/UTMSP/UTMSPServiceController.h \ + src/UTMSP/UTMSPVehicle.h \ + src/UTMSP/UTMSPManager.h + + SOURCES += \ + src/UTMSP/UTMSPRestInterface.cpp \ + src/UTMSP/UTMSPBlenderRestInterface.cpp \ + src/UTMSP/UTMSPAuthorization.cpp \ + src/UTMSP/UTMSPNetworkRemoteIDManager.cpp \ + src/UTMSP/UTMSPAircraft.cpp \ + src/UTMSP/UTMSPFlightDetails.cpp \ + src/UTMSP/UTMSPOperator.cpp \ + src/UTMSP/UTMSPFlightPlanManager.cpp \ + src/UTMSP/UTMSPServiceController.cpp \ + src/UTMSP/UTMSPVehicle.cpp \ + src/UTMSP/UTMSPManager.cpp +} +else { + #-- Dummy UTM Adapter resource file created to override UTM adapter qml files + INCLUDEPATH += \ + src/UTMSP/dummy + RESOURCES += \ + src/UTMSP/dummy/utmsp_dummy.qrc +} diff --git a/src/FlightDisplay/FlyView.qml b/src/FlightDisplay/FlyView.qml index 7a879cbabab..b9999fe6de7 100644 --- a/src/FlightDisplay/FlyView.qml +++ b/src/FlightDisplay/FlyView.qml @@ -34,6 +34,9 @@ Item { property var planController: _planController property var guidedController: _guidedController + // Properties of UTM adapter + property bool utmspSendActTrigger + PlanMasterController { id: _planController flyView: true @@ -83,6 +86,7 @@ Item { parentToolInsets: _toolInsets mapControl: _mapControl visible: !QGroundControl.videoManager.fullScreen + utmspActTrigger: utmspSendActTrigger } diff --git a/src/FlightDisplay/FlyViewWidgetLayer.qml b/src/FlightDisplay/FlyViewWidgetLayer.qml index 013ccfba349..a0feec9fcf3 100644 --- a/src/FlightDisplay/FlyViewWidgetLayer.qml +++ b/src/FlightDisplay/FlyViewWidgetLayer.qml @@ -48,6 +48,8 @@ Item { property real _rightPanelWidth: ScreenTools.defaultFontPixelWidth * 30 property alias _gripperMenu: gripperOptions + property bool utmspActTrigger + QGCToolInsets { id: _totalToolInsets leftEdgeTopInset: toolStrip.leftEdgeTopInset @@ -113,6 +115,7 @@ Item { z: QGroundControl.zOrderTopMost guidedController: _guidedController guidedValueSlider: _guidedValueSlider + utmspSliderTrigger: utmspActTrigger } FlyViewInstrumentPanel { diff --git a/src/FlightDisplay/GuidedActionConfirm.qml b/src/FlightDisplay/GuidedActionConfirm.qml index 6441b36fe44..07b4e5b86b4 100644 --- a/src/FlightDisplay/GuidedActionConfirm.qml +++ b/src/FlightDisplay/GuidedActionConfirm.qml @@ -22,7 +22,7 @@ Rectangle { height: mainLayout.height + (_margins * 2) radius: ScreenTools.defaultFontPixelWidth / 2 color: qgcPal.window - visible: false + visible: _utmspEnabled === true ? utmspSliderTrigger: false property var guidedController property var guidedValueSlider @@ -38,6 +38,10 @@ Rectangle { property real _margins: ScreenTools.defaultFontPixelWidth / 2 property bool _emergencyAction: action === guidedController.actionEmergencyStop + // Properties of UTM adapter + property bool utmspSliderTrigger + property bool _utmspEnabled: QGroundControl.utmspSupported + Component.onCompleted: guidedController.confirmDialog = this onVisibleChanged: { @@ -112,6 +116,8 @@ Rectangle { id: slider confirmText: ScreenTools.isMobile ? qsTr("Slide to confirm") : qsTr("Slide or hold spacebar") Layout.fillWidth: true + enabled: _utmspEnabled === true? utmspSliderTrigger : true + opacity: if(_utmspEnabled){utmspSliderTrigger === true ? 1 : 0.5} else{1} onAccept: { _root.visible = false diff --git a/src/MissionManager/GeoFenceController.cc b/src/MissionManager/GeoFenceController.cc index 7092d1f1053..7fb0a2809b4 100644 --- a/src/MissionManager/GeoFenceController.cc +++ b/src/MissionManager/GeoFenceController.cc @@ -602,3 +602,48 @@ bool GeoFenceController::isEmpty(void) const return _polygons.count() == 0 && _circles.count() == 0 && !_breachReturnPoint.isValid(); } + +#ifdef CONFIG_UTM_ADAPTER +void GeoFenceController::loadFlightPlanData() +{ + QJsonArray jsonPolygonArray; + QJsonDocument doc; + QList geoCoordinates ; + + for (int i = 0; i < _polygons.count(); i++) { + QJsonObject jsonPolygon; + QGCFencePolygon* fencePolygon = _polygons.value(i); + fencePolygon->saveToJson(jsonPolygon); + jsonPolygonArray.append(jsonPolygon); + } + doc.setArray(jsonPolygonArray); + QString dataToString(doc.toJson()); + + // Parse the JSON string into a QJsonArray + QJsonDocument polygonDoc = QJsonDocument::fromJson(dataToString.toUtf8()); + QJsonArray jsonArray = polygonDoc.array(); + QJsonObject jsonObject = jsonArray.at(0).toObject(); + QJsonArray polygonArray = jsonObject.value("polygon").toArray(); + + for (int i = 0; i < polygonArray.size(); ++i) { + QJsonArray pointArray = polygonArray.at(i).toArray(); + double latitude = pointArray.at(0).toDouble(); + double longitude = pointArray.at(1).toDouble(); + QGeoCoordinate geoCoordinate(latitude, longitude); + geoCoordinates.append(geoCoordinate); + } + + // Append the first coordinate again to the end of the list + if (!geoCoordinates.isEmpty()) { + geoCoordinates.append(geoCoordinates.first()); + } + + emit polygonBoundarySent(geoCoordinates); +} +bool GeoFenceController::loadUploadFlag() +{ + emit uploadFlagSent(true); + + return true; +} +#endif diff --git a/src/MissionManager/GeoFenceController.h b/src/MissionManager/GeoFenceController.h index 19f06433b2b..3aec1260ed5 100644 --- a/src/MissionManager/GeoFenceController.h +++ b/src/MissionManager/GeoFenceController.h @@ -59,6 +59,11 @@ class GeoFenceController : public PlanElementController /// Clears the interactive bit from all fence items Q_INVOKABLE void clearAllInteractive(void); +#ifdef CONFIG_UTM_ADAPTER + Q_INVOKABLE void loadFlightPlanData(void); + Q_INVOKABLE bool loadUploadFlag(void); +#endif + double paramCircularFence (void); Fact* breachReturnAltitude(void) { return &_breachReturnAltitudeFact; } @@ -90,6 +95,11 @@ class GeoFenceController : public PlanElementController void loadComplete (void); void paramCircularFenceChanged (void); +#ifdef CONFIG_UTM_ADAPTER + void uploadFlagSent (bool flag); + void polygonBoundarySent (QList coords); +#endif + private slots: void _polygonDirtyChanged (bool dirty); void _setDirty (void); diff --git a/src/MissionManager/QGCMapPolygonVisuals.qml b/src/MissionManager/QGCMapPolygonVisuals.qml index 05102f3740e..9b3269daff4 100644 --- a/src/MissionManager/QGCMapPolygonVisuals.qml +++ b/src/MissionManager/QGCMapPolygonVisuals.qml @@ -42,6 +42,7 @@ Item { property string _instructionText: _polygonToolsText property var _savedVertices: [ ] property bool _savedCircleMode + property bool _utmspEnabled: QGroundControl.utmspSupported property real _zorderDragHandle: QGroundControl.zOrderMapItems + 3 // Highest to prevent splitting when items overlap property real _zorderSplitHandle: QGroundControl.zOrderMapItems + 2 @@ -575,8 +576,15 @@ Item { z: QGroundControl.zOrderMapItems + 1 // Over item indicators onClicked: (mouse) => { - if (mouse.button === Qt.LeftButton && _root.interactive) { - mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)) + if(_utmspEnabled){ + if (mouse.button === Qt.LeftButton) { + mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)) + } + } + else{ + if (mouse.button === Qt.LeftButton && _root.interactive) { + mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)) + } } } } diff --git a/src/PlanView/PlanToolBarIndicators.qml b/src/PlanView/PlanToolBarIndicators.qml index 6b394e0fe54..2c30cff7410 100644 --- a/src/PlanView/PlanToolBarIndicators.qml +++ b/src/PlanView/PlanToolBarIndicators.qml @@ -68,6 +68,22 @@ Item { readonly property real _margins: ScreenTools.defaultFontPixelWidth + // Properties of UTM adapter + property var _utmspController: _planMasterController.geoFenceController + property bool _utmspEnabled: QGroundControl.utmspSupported + property bool responseFlag + // Dummy object when utm adapter flag is not enabled + QtObject { + id: dummyTarget + signal uploadFlagSent(bool flag) + } + Connections { + target: _utmspEnabled ? _utmspController: dummyTarget + onUploadFlagSent: function(flag) { + responseFlag = flag + } + } + function getMissionTime() { if (!_missionTime) { return "00:00:00" @@ -244,10 +260,18 @@ Item { QGCButton { id: uploadButton text: _controllerDirty ? qsTr("Upload Required") : qsTr("Upload") - enabled: !_controllerSyncInProgress + enabled: _utmspEnabled ? !_controllerSyncInProgress && responseFlag : !_controllerSyncInProgress visible: !_controllerOffline && !_controllerSyncInProgress && !uploadCompleteText.visible primary: _controllerDirty - onClicked: _planMasterController.upload() + onClicked: { + if(_utmspEnabled){ + _planMasterController.upload() + QGroundControl.utmspManager.utmspVehicle.triggerUploadButton(true) + } + else{ + _planMasterController.upload() + } + } PropertyAnimation on opacity { easing.type: Easing.OutQuart diff --git a/src/PlanView/PlanView.qml b/src/PlanView/PlanView.qml index 2c7bbe90aba..2f1203e91f0 100644 --- a/src/PlanView/PlanView.qml +++ b/src/PlanView/PlanView.qml @@ -24,6 +24,9 @@ import QGroundControl.FactControls import QGroundControl.Palette import QGroundControl.Controllers import QGroundControl.ShapeFileHelper +import QGroundControl.FlightDisplay +import QGroundControl.UTMSP + Item { id: _root @@ -46,19 +49,27 @@ Item { property bool _lightWidgetBorders: editorMap.isSatelliteMap property bool _addROIOnClick: false property bool _singleComplexItem: _missionController.complexMissionItemNames.length === 1 - property int _editingLayer: layerTabBar.currentIndex ? _layers[layerTabBar.currentIndex] : _layerMission + property int _editingLayer: {if(!_utmspEnabled){layerTabBar.currentIndex ? _layers[layerTabBar.currentIndex] : _layerMission}else{layerTabBarUTMSP.currentIndex ? _layersUTMSP[layerTabBarUTMSP.currentIndex] : _layerMission}} property int _toolStripBottom: toolStrip.height + toolStrip.y property var _appSettings: QGroundControl.settingsManager.appSettings property var _planViewSettings: QGroundControl.settingsManager.planViewSettings property bool _promptForPlanUsageShowing: false + property bool _utmspEnabled: QGroundControl.utmspSupported + property bool _resetTrigered: false //Reset the Geofence Polygon + property var _vehicleID + property bool _triggerSubmit - readonly property var _layers: [_layerMission, _layerGeoFence, _layerRallyPoints] + readonly property var _layers: [_layerMission, _layerGeoFence, _layerRallyPoints] + readonly property var _layersUTMSP: [_layerMission, _layerRallyPoints, _layerUTMSP] //Adds additional UTMSP layer readonly property int _layerMission: 1 readonly property int _layerGeoFence: 2 readonly property int _layerRallyPoints: 3 + readonly property int _layerUTMSP: 4 // Additional Tab button when UTMSP is enabled readonly property string _armedVehicleUploadPrompt: qsTr("Vehicle is currently armed. Do you want to upload the mission to the vehicle?") + signal activationParamsSent(string startTime, bool activate, string flightID) + function mapCenter() { var coordinate = editorMap.center coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) @@ -359,6 +370,9 @@ Item { coordinate.latitude = coordinate.latitude.toFixed(_decimalPlaces) coordinate.longitude = coordinate.longitude.toFixed(_decimalPlaces) coordinate.altitude = coordinate.altitude.toFixed(_decimalPlaces) + if(_utmspEnabled){ + QGroundControl.utmspManager.utmspVehicle.updateLastCoordinates(coordinate.latitude, coordinate.longitude) + } switch (_editingLayer) { case _layerMission: @@ -368,8 +382,17 @@ Item { insertROIAfterCurrent(coordinate) _addROIOnClick = false } + break + case _layerUTMSP: + if (addWaypointRallyPointAction.checked) { + insertSimpleItemAfterCurrent(coordinate) + } else if (_addROIOnClick) { + insertROIAfterCurrent(coordinate) + _addROIOnClick = false + } break + case _layerRallyPoints: if (_rallyPointController.supported && addWaypointRallyPointAction.checked) { _rallyPointController.addPoint(coordinate) @@ -385,8 +408,8 @@ Item { delegate: MissionItemMapVisual { map: editorMap onClicked: _missionController.setCurrentPlanViewSeqNum(sequenceNumber, false) - opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity - interactive: _editingLayer == _layerMission + opacity: _editingLayer == _layerMission || _editingLayer == _layerUTMSP ? 1 : editorMap._nonInteractiveOpacity + interactive: _editingLayer == _layerMission || _editingLayer == _layerUTMSP vehicle: _planMasterController.controllerVehicle } } @@ -395,12 +418,12 @@ Item { MissionLineView { showSpecialVisual: _missionController.isROIBeginCurrentItem model: _missionController.simpleFlightPathSegments - opacity: _editingLayer == _layerMission ? 1 : editorMap._nonInteractiveOpacity + opacity: _editingLayer == _layerMission || _editingLayer == _layerUTMSP ? 1 : editorMap._nonInteractiveOpacity } // Direction arrows in waypoint lines MapItemView { - model: _editingLayer == _layerMission ? _missionController.directionArrows : undefined + model: _editingLayer == _layerMission ||_editingLayer == _layerUTMSP ? _missionController.directionArrows : undefined delegate: MapLineArrow { fromCoord: object ? object.coordinate1 : undefined @@ -429,7 +452,7 @@ Item { anchorPoint.x: sourceItem.width / 2 anchorPoint.y: sourceItem.height / 2 z: QGroundControl.zOrderWaypointLines + 1 - visible: _editingLayer == _layerMission + visible: _editingLayer == _layerMission || _editingLayer == _layerUTMSP sourceItem: SplitIndicator { onClicked: _missionController.insertSimpleMissionItem(splitSegmentItem.coordinate, @@ -487,6 +510,35 @@ Item { planView: true opacity: _editingLayer != _layerRallyPoints ? editorMap._nonInteractiveOpacity : 1 } + + UTMSPMapVisuals { + id: utmspvisual + enabled: _utmspEnabled + map: editorMap + currentMissionItems: _visualItems + myGeoFenceController: _geoFenceController + interactive: _editingLayer == _layerUTMSP + homePosition: _missionController.plannedHomePosition + planView: true + opacity: _editingLayer != _layerUTMSP ? editorMap._nonInteractiveOpacity : 1 + resetCheck: _resetTrigered + } + + Connections { + target: utmspEditor + function onResetTriggered() { + resetTimer.start() + } + } + Timer { + id: resetTimer + interval: 2500 + running: false + repeat: false + onTriggered: { + _resetTrigered = true + } + } } //----------------------------------------------------------- @@ -511,6 +563,7 @@ Item { property bool _isRallyLayer: _editingLayer == _layerRallyPoints property bool _isMissionLayer: _editingLayer == _layerMission + property bool _isUtmspLayer: _editingLayer == _layerUTMSP ToolStripActionList { id: toolStripActionList @@ -533,10 +586,11 @@ Item { text: qsTr("Takeoff") iconSource: "/res/takeoff.svg" enabled: _missionController.isInsertTakeoffValid - visible: toolStrip._isMissionLayer && !_planMasterController.controllerVehicle.rover + visible: toolStrip._isMissionLayer && !_planMasterController.controllerVehicle.rover || toolStrip._isUtmspLayer && !_planMasterController.controllerVehicle.rover onTriggered: { toolStrip.allAddClickBoolsOff() insertTakeItemAfterCurrent() + _triggerSubmit = true } }, ToolStripAction { @@ -544,7 +598,7 @@ Item { text: _editingLayer == _layerRallyPoints ? qsTr("Rally Point") : qsTr("Waypoint") iconSource: "/qmlimages/MapAddMission.svg" enabled: toolStrip._isRallyLayer ? true : _missionController.flyThroughCommandsAllowed - visible: toolStrip._isRallyLayer || toolStrip._isMissionLayer + visible: toolStrip._isRallyLayer || toolStrip._isMissionLayer || toolStrip._isUtmspLayer checkable: true }, ToolStripAction { @@ -580,7 +634,7 @@ Item { text: _planMasterController.controllerVehicle.multiRotor ? qsTr("Return") : qsTr("Land") iconSource: "/res/rtl.svg" enabled: _missionController.isInsertLandValid - visible: toolStrip._isMissionLayer + visible: toolStrip._isMissionLayer || toolStrip._isUtmspLayer onTriggered: { toolStrip.allAddClickBoolsOff() insertLandItemAfterCurrent() @@ -611,7 +665,14 @@ Item { Rectangle { id: rightPanel height: parent.height - width: _rightPanelWidth + width:{ + if(_utmspEnabled){ + _rightPanelWidth + ScreenTools.defaultFontPixelWidth * 21.667 + } + else{ + _rightPanelWidth + } + } color: qgcPal.window opacity: layerTabBar.visible ? 0.2 : 0 anchors.bottom: parent.bottom @@ -637,7 +698,7 @@ Item { QGCTabBar { id: layerTabBar width: parent.width - visible: QGroundControl.corePlugin.options.enablePlanViewSelector + visible: QGroundControl.corePlugin.options.enablePlanViewSelector && !_utmspEnabled Component.onCompleted: currentIndex = 0 QGCTabButton { text: qsTr("Mission") @@ -651,6 +712,24 @@ Item { enabled: _rallyPointController.supported } } + + QGCTabBar { + id: layerTabBarUTMSP + width: parent.width + visible: (!planControlColapsed || !_airspaceEnabled) && QGroundControl.corePlugin.options.enablePlanViewSelector && _utmspEnabled + QGCTabButton { + text: qsTr("Mission") + } + QGCTabButton { + text: qsTr("Rally") + enabled: _rallyPointController.supported + } + QGCTabButton { + id: utmspbutton + text: qsTr("UTM-Adapter") + visible: _utmspEnabled + } + } } //------------------------------------------------------- // Mission Item Editor @@ -725,6 +804,40 @@ Item { rallyPoint: _rallyPointController.currentRallyPoint controller: _rallyPointController } + UTMSPAdapterEditor{ + id: utmspEditor + enabled: _utmspEnabled + anchors.top: rightControls.bottom + anchors.topMargin: ScreenTools.defaultFontPixelHeight * 0.25 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + currentMissionItems: _visualItems + myGeoFenceController: _geoFenceController + flightMap: editorMap + visible: _editingLayer == _layerUTMSP + triggerSubmitButton: _triggerSubmit + } + } + + Connections { + target: utmspEditor + function onResponseSent(response, responseFlag) { + if(responseFlag===true){ + successPopup.opacity = 1 + successPopup.visible = true + success_notify.text = "Flight Plan for Vehicle:" + _vehicleID +" is successfully Registered..." + var disappearTimer1 = Qt.createQmlObject("import QtQuick 2.0; Timer { interval: 3000; onTriggered: {successPopup.visible = false; successPopup.opacity = 0;} }", parent, "disappearTimer"); + disappearTimer1.start() + } + else{ + failPopup.opacity = 1 + failPopup.visible = true + fail_notify.text = "Error in Flightblender Response..." //TODO->Will pass the response message + var disappearTimer4 = Qt.createQmlObject("import QtQuick 2.0; Timer { interval: 3000; onTriggered: {failPopup.visible = false; failPopup.opacity = 0;} }", parent, "disappearTimer"); + disappearTimer4.start() + } + } } QGCLabel { @@ -1052,4 +1165,136 @@ Item { } } } + + Rectangle { + id: successPopup + x: Math.round((mainWindow.width - width) * 0.5) + y: ScreenTools.defaultFontPixelHeight + width: mainWindow.width * 0.55 + height: ScreenTools.defaultFontPixelHeight * 2.944 + color: _utmspEnabled? qgcPal.successNotifyUTMSP: qgcPal.buttonText + radius: ScreenTools.defaultFontPixelHeight * 0.5 + border.color: qgcPal.alertBorder + border.width: 1 + opacity: 0 + visible: false + Text{ + id: success_notify + width: successPopup.width - (ScreenTools.defaultFontPixelHeight * 2) + x: ScreenTools.defaultFontPixelWidth * 2 + y: ScreenTools.defaultFontPixelHeight * 0.667 + textFormat: TextEdit.RichText + font.pointSize: ScreenTools.defaultFontPointSize + font.family: ScreenTools.demiboldFontFamily + wrapMode: TextEdit.WordWrap + color: qgcPal.alertText + } + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: -(height / 2) + color: _utmspEnabled? qgcPal.successNotifyUTMSP: qgcPal.buttonText + radius: ScreenTools.defaultFontPixelHeight * 0.25 + border.color: qgcPal.alertBorder + border.width: 1 + width: statusLabel.contentWidth + _margins + height: statusLabel.contentHeight + _margins + + property real _margins: ScreenTools.defaultFontPixelHeight * 0.25 + + QGCLabel { + id: statusLabel + anchors.centerIn: parent + text: qsTr("Status") + font.pointSize: ScreenTools.smallFontPointSize + color: qgcPal.alertText + } + } + Behavior on opacity { + NumberAnimation { duration: 1000 } + } + function hidesuccessPopup() { + successPopup.visible = false; + successPopup.opacity = 0; + } + Behavior on visible { + SequentialAnimation { + NumberAnimation { from: 2.5; to: 3.5; duration: 500 } + PauseAnimation { duration: 2000 } + NumberAnimation { from: 3.5; to: 2.5; duration: 500 } + } + } + } + // Failure Notification Popup + Rectangle { + id: failPopup + x: Math.round((mainWindow.width - width) * 0.5) + y: ScreenTools.defaultFontPixelHeight + width: mainWindow.width * 0.55 + height: ScreenTools.defaultFontPixelHeight * 2.944 + color: qgcPal.alertBackground + radius: ScreenTools.defaultFontPixelHeight * 0.5 + opacity: 0 + visible: false + Text{ + id:fail_notify + width: successPopup.width - (ScreenTools.defaultFontPixelHeight * 2) + x: ScreenTools.defaultFontPixelWidth * 2 + y: ScreenTools.defaultFontPixelHeight * 0.667 + textFormat: TextEdit.RichText + font.pointSize: ScreenTools.defaultFontPointSize + font.family: ScreenTools.demiboldFontFamily + wrapMode: TextEdit.WordWrap + color: qgcPal.alertText + } + Rectangle { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: -(height / 2) + color: qgcPal.alertBackground + radius: ScreenTools.defaultFontPixelHeight * 0.25 + border.color: qgcPal.alertBorder + border.width: 1 + width: warningLabel.contentWidth + _margins + height: warningLabel.contentHeight + _margins + + property real _margins: ScreenTools.defaultFontPixelHeight * 0.25 + + QGCLabel { + id: warningLabel + anchors.centerIn: parent + text: qsTr("Status Error") + font.pointSize: ScreenTools.smallFontPointSize + color: qgcPal.alertText + } + } + Behavior on opacity { + NumberAnimation { duration: 1000 } + } + function hidefailPopup() { + failPopup.visible = false; + failPopup.opacity = 0; + } + Behavior on visible { + SequentialAnimation { + NumberAnimation { from: 2.5; to: 3.5; duration: 500 } + PauseAnimation { duration: 2000 } + NumberAnimation { from: 3.5; to: 2.5; duration: 500 } + } + } + } + Connections{ + target: utmspEditor + function onTimeStampSent(timestamp, activateflag, id){ + activationParamsSent(timestamp,activateflag, id) + } + } + + Connections { + target: utmspEditor + function onVehicleIDSent(id) { + _vehicleID = id + } + } + } diff --git a/src/QGCPalette.cc b/src/QGCPalette.cc index ddf26fd54e5..ce1814d7980 100644 --- a/src/QGCPalette.cc +++ b/src/QGCPalette.cc @@ -95,6 +95,13 @@ void QGCPalette::_buildMap() DECLARE_QGC_SINGLE_COLOR(mapMissionTrajectory, "#be781c") DECLARE_QGC_SINGLE_COLOR(surveyPolygonInterior, "green") DECLARE_QGC_SINGLE_COLOR(surveyPolygonTerrainCollision, "red") + +// Colors for UTM Adapter +#ifdef CONFIG_UTM_ADAPTER + DECLARE_QGC_COLOR(switchUTMSP, "#b0e0e6", "#b0e0e6", "#b0e0e6", "#b0e0e6"); + DECLARE_QGC_COLOR(sliderUTMSP, "#9370db", "#9370db", "#9370db", "#9370db"); + DECLARE_QGC_COLOR(successNotifyUTMSP, "#3cb371", "#3cb371", "#3cb371", "#3cb371"); +#endif } void QGCPalette::setColorGroupEnabled(bool enabled) diff --git a/src/QGCPalette.h b/src/QGCPalette.h index 93149cf9d96..30502864f53 100644 --- a/src/QGCPalette.h +++ b/src/QGCPalette.h @@ -153,6 +153,12 @@ class QGCPalette : public QObject DEFINE_QGC_COLOR(toolStripFGColor, setToolStripFGColor) DEFINE_QGC_COLOR(toolStripHoverColor, setToolStripHoverColor) +#ifdef CONFIG_UTM_ADAPTER + DEFINE_QGC_COLOR(switchUTMSP, setSwitchUTMSP) + DEFINE_QGC_COLOR(sliderUTMSP, setSliderUTMSP) + DEFINE_QGC_COLOR(successNotifyUTMSP, setSuccessNotifyUTMSP) +#endif + QGCPalette(QObject* parent = nullptr); ~QGCPalette(); diff --git a/src/QGCToolbox.cc b/src/QGCToolbox.cc index 8be03d3c1a7..ea76da606f1 100644 --- a/src/QGCToolbox.cc +++ b/src/QGCToolbox.cc @@ -43,6 +43,10 @@ #if defined(QGC_CUSTOM_BUILD) #include CUSTOMHEADER +#endif + +#ifdef CONFIG_UTM_ADAPTER +#include "UTMSPManager.h" #endif /** @@ -97,6 +101,22 @@ QGCToolbox::QGCToolbox(QGCApplication* app) #if defined(QGC_GST_MICROHARD_ENABLED) _microhardManager = new MicrohardManager (app, this); #endif +#ifdef CONFIG_UTM_ADAPTER + _utmspManager = new UTMSPManager (app, this); +#endif +} + +/** + * @brief Helper function to register a type with QML that is not creatable from QML + * @param uri The URI to register the type under + * @param majorVersion The major version of the type + * @param minorVersion The minor version of the type + * @param qmlName The name of the type in QML + */ +template +void registerUncreatableQmlType(const char *uri, int majorVersion, int minorVersion, const char *qmlName) +{ + qmlRegisterUncreatableType(uri, majorVersion, minorVersion, qmlName, "Reference only"); } void QGCToolbox::setChildToolboxes(void) @@ -133,6 +153,9 @@ void QGCToolbox::setChildToolboxes(void) #if defined(QGC_ENABLE_PAIRING) _pairingManager->setToolbox(this); #endif +#ifdef CONFIG_UTM_ADAPTER + _utmspManager->setToolbox(this); +#endif } void QGCToolbox::_scanAndLoadPlugins(QGCApplication* app) diff --git a/src/QGCToolbox.h b/src/QGCToolbox.h index 44b87fe1ec7..5a559b0add0 100644 --- a/src/QGCToolbox.h +++ b/src/QGCToolbox.h @@ -43,6 +43,10 @@ class TaisyncManager; class MicrohardManager; #endif +#ifdef CONFIG_UTM_ADAPTER +class UTMSPManager; +#endif + /// This is used to manage all of our top level services/tools class QGCToolbox : public QObject { Q_OBJECT @@ -79,6 +83,9 @@ class QGCToolbox : public QObject { #if defined(QGC_GST_MICROHARD_ENABLED) MicrohardManager* microhardManager () { return _microhardManager; } #endif +#ifdef CONFIG_UTM_ADAPTER + UTMSPManager* utmspManager () { return _utmspManager; } +#endif private: void setChildToolboxes(void); @@ -114,6 +121,9 @@ class QGCToolbox : public QObject { #endif #if defined(QGC_GST_MICROHARD_ENABLED) MicrohardManager* _microhardManager = nullptr; +#endif +#ifdef CONFIG_UTM_ADAPTER + UTMSPManager* _utmspManager = nullptr; #endif friend class QGCApplication; }; diff --git a/src/QmlControls/QGroundControlQmlGlobal.cc b/src/QmlControls/QGroundControlQmlGlobal.cc index 2a32c717825..b7cb55c7a6f 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.cc +++ b/src/QmlControls/QGroundControlQmlGlobal.cc @@ -88,6 +88,9 @@ void QGroundControlQmlGlobal::setToolbox(QGCToolbox* toolbox) #if defined(QGC_GST_MICROHARD_ENABLED) _microhardManager = toolbox->microhardManager(); #endif +#ifdef CONFIG_UTM_ADAPTER + _utmspManager = toolbox->utmspManager(); +#endif } void QGroundControlQmlGlobal::saveGlobalSetting (const QString& key, const QString& value) diff --git a/src/QmlControls/QGroundControlQmlGlobal.h b/src/QmlControls/QGroundControlQmlGlobal.h index 6a8ba466cf4..8634e9bf35d 100644 --- a/src/QmlControls/QGroundControlQmlGlobal.h +++ b/src/QmlControls/QGroundControlQmlGlobal.h @@ -31,6 +31,9 @@ class TaisyncManager; #else class MicrohardManager; #endif +#ifdef CONFIG_UTM_ADAPTER +#include "UTMSPManager.h" +#endif #ifdef QT_DEBUG #include "MockLink.h" @@ -130,6 +133,12 @@ class QGroundControlQmlGlobal : public QGCTool Q_PROPERTY(PairingManager* pairingManager READ pairingManager CONSTANT) #endif + Q_PROPERTY(bool utmspSupported READ utmspSupported CONSTANT) + +#ifdef CONFIG_UTM_ADAPTER + Q_PROPERTY(UTMSPManager* utmspManager READ utmspManager CONSTANT) +#endif + Q_INVOKABLE void saveGlobalSetting (const QString& key, const QString& value); Q_INVOKABLE QString loadGlobalSetting (const QString& key, const QString& defaultValue); Q_INVOKABLE void saveBoolGlobalSetting (const QString& key, bool value); @@ -201,6 +210,10 @@ class QGroundControlQmlGlobal : public QGCTool bool microhardSupported () { return false; } #endif +#ifdef CONFIG_UTM_ADAPTER + UTMSPManager* utmspManager () {return _utmspManager;} +#endif + qreal zOrderTopMost () { return 1000; } qreal zOrderWidgets () { return 100; } qreal zOrderMapItems () { return 50; } @@ -244,6 +257,12 @@ class QGroundControlQmlGlobal : public QGCTool QString qgcVersion (void) const; +#ifdef CONFIG_UTM_ADAPTER + bool utmspSupported() { return true; } +#else + bool utmspSupported() { return false; } +#endif + // Overrides from QGCTool virtual void setToolbox(QGCToolbox* toolbox); @@ -276,6 +295,9 @@ class QGroundControlQmlGlobal : public QGCTool #if defined(QGC_ENABLE_PAIRING) PairingManager* _pairingManager = nullptr; #endif +#ifdef CONFIG_UTM_ADAPTER + UTMSPManager* _utmspManager; +#endif bool _skipSetupPage = false; QStringList _altitudeModeEnumString; diff --git a/src/UTMSP/CMakeLists.txt b/src/UTMSP/CMakeLists.txt new file mode 100644 index 00000000000..f8448ba662d --- /dev/null +++ b/src/UTMSP/CMakeLists.txt @@ -0,0 +1,59 @@ +find_package(Threads REQUIRED) + +add_library(UTMSP + UTMSPLogger.h + UTMSPRestInterface.h + UTMSPRestInterface.cpp + UTMSPBlenderRestInterface.h + UTMSPBlenderRestInterface.cpp + UTMSPAuthorization.h + UTMSPAuthorization.cpp + UTMSPNetworkRemoteIDManager.h + UTMSPNetworkRemoteIDManager.cpp + UTMSPAircraft.h + UTMSPAircraft.cpp + UTMSPFlightDetails.h + UTMSPFlightDetails.cpp + UTMSPOperator.h + UTMSPOperator.cpp + UTMSPFlightPlanManager.h + UTMSPFlightPlanManager.cpp + UTMSPServiceController.h + UTMSPServiceController.cpp + UTMSPVehicle.h + UTMSPVehicle.cpp + UTMSPManager.h + UTMSPManager.cpp + utmsp.qrc + QGroundControl/UTMSP/UTMSPAdapterEditor.qml + QGroundControl/UTMSP/UTMSPMapVisuals.qml + QGroundControl/UTMSP/UTMSPActivationStatusBar.qml + ) + + add_custom_target(UTMSPQml + SOURCES + UTMSPAdapterEditor.qml + UTMSPMapVisuals.qml + ) + +else() + add_library(UTMSP + utmsp_dummy.qrc + ) + target_include_directories(UTMSP PUBLIC dummy) +endif() +target_include_directories(UTMSP PUBLIC services ) + +target_link_libraries(UTMSP + +PRIVATE + libevents_parser +PUBLIC + Qt5::Core + Qt5::Location + Qt5::Widgets + Threads::Threads + qgc +) + +target_include_directories(USSP PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/UTMSP/UTMSPActivationStatusBar.qml b/src/UTMSP/UTMSPActivationStatusBar.qml new file mode 100644 index 00000000000..fe25178d214 --- /dev/null +++ b/src/UTMSP/UTMSPActivationStatusBar.qml @@ -0,0 +1,199 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.11 +import QtQuick.Layouts 1.2 +import QtQuick.Dialogs 1.2 +import QtQuick.Extras 1.4 +import QtQuick.Controls 2.15 +import QtPositioning 5.2 +import QtLocation 5.12 +import QtGraphicalEffects 1.0 + +import QGroundControl 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controllers 1.0 +import QGroundControl.ShapeFileHelper 1.0 +import QGroundControl.UTMSP 1.0 +import QGroundControl.FlightDisplay 1.0 +import QGroundControl.MultiVehicleManager 1.0 + +Item { + id: _root + // Activation window for UTM Adapter + property date targetTimestamp : new Date(activationStartTimestamp) + property date localTimestamp : new Date(targetTimestamp.getTime() - 4 * 60 * 60 * 1000) + property date currentTime : new Date() + property bool _utmspEnabled : QGroundControl.utmspSupported + property string activationStartTimestamp + property bool activationApproval + property string flightID + property string timeDifference + property bool activationErrorFlag + + signal activationTriggered(bool value) + + Timer { + interval: 1000 + running: activationApproval + repeat: activationApproval + onTriggered: { + currentTime = new Date() + var diff = localTimestamp - currentTime; + if(diff <= 0) { + timeDifference = "0 hours 0 minutes 0 seconds" + running = false; + var activationErrorFlag = QGroundControl.utmspManager.utmspVehicle.activationFlag + if(activationErrorFlag === true){ + QGroundControl.utmspManager.utmspVehicle.loadTelemetryFlag(true) + approvetag.visible = false + activatetag.visible = true + activationTriggered(true) + hideTimer.start() + }else{ + approvetag.visible = false + failtag.visible = true + } + } else { + var hours = Math.floor(diff / 3600000) + var minutes = Math.floor((diff % 3600000) / 60000) + var seconds = Math.floor((diff % 60000) / 1000) + timeDifference = hours + " hours " + minutes + " minutes " + seconds + " seconds" + } + } + } + + Timer { + id: hideTimer + interval: 5000 + running: false + onTriggered: activationBar.visible = false + } + + Rectangle { + id: activationBar + color: qgcPal.textFieldText + border.color: qgcPal.textFieldText + width: ScreenTools.defaultFontPixelWidth * 83.33 + height: ScreenTools.defaultFontPixelHeight * 8.33 + y: ScreenTools.defaultFontPixelHeight * 10 + anchors.right: parent.right + opacity: 0.9 + visible: activationApproval + radius: ScreenTools.defaultFontPixelWidth * 0.833 + + QGCColoredImage { + id: closeButton + width: ScreenTools.defaultFontPixelWidth * 5 + height: ScreenTools.defaultFontPixelWidth * 5 + x: 460 + y: 5 + source: "/res/XDelete.svg" + fillMode: Image.PreserveAspectFit + color: qgcPal.text + } + QGCMouseArea { + fillItem: closeButton + onClicked: { + activationBar.visible = false + activationApproval = false + } + } + + Column{ + spacing: ScreenTools.defaultFontPixelWidth * 1.667 + anchors.centerIn: parent + Text{ + text: "Remaining time for activation!!!" + color: qgcPal.buttonText + font.pixelSize: ScreenTools.defaultFontPixelWidth * 2.5 + font.bold: true + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + visible: true + } + + Text { + text: timeDifference + color: qgcPal.buttonText + font.pixelSize: ScreenTools.defaultFontPixelWidth * 3.667 + font.bold: true + horizontalAlignment: Text.AlignHCenter + anchors.horizontalCenter: parent.horizontalCenter + } + + Row{ + spacing: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.horizontalCenter: parent.horizontalCenter + + Text { + text: "FLIGHT ID: " + color: qgcPal.buttonText + font.pixelSize: ScreenTools.defaultFontPixelWidth * 2.5 + font.bold: true + horizontalAlignment: Text.AlignHCenter + } + + Text { + text: flightID + color: qgcPal.buttonText + font.pixelSize: ScreenTools.defaultFontPixelWidth * 2.5 + horizontalAlignment: Text.AlignHCenter + } + } + + Row{ + spacing: ScreenTools.defaultFontPixelWidth * 0.5 + anchors.horizontalCenter: parent.horizontalCenter + + Text { + text: "STATUS: " + color: qgcPal.buttonText + font.pixelSize: ScreenTools.defaultFontPixelWidth * 2.5 + font.bold: true + horizontalAlignment: Text.AlignHCenter + } + + Text { + id: approvetag + text: "Approved" + color: qgcPal.colorOrange + font.pixelSize: ScreenTools.defaultFontPixelWidth * 3 + horizontalAlignment: Text.AlignHCenter + visible: true + font.bold: true + } + + Text { + id: activatetag + text: "Activated" + color: qgcPal.colorGreen + font.pixelSize: ScreenTools.defaultFontPixelWidth * 3 + horizontalAlignment: Text.AlignHCenter + visible: false + font.bold: true + } + Text { + id: failtag + text: "Activation Failed" + color: qgcPal.colorRed + font.pixelSize: ScreenTools.defaultFontPixelWidth * 3 + horizontalAlignment: Text.AlignHCenter + visible: false + font.bold: true + } + } + } + } +} diff --git a/src/UTMSP/UTMSPAdapterEditor.qml b/src/UTMSP/UTMSPAdapterEditor.qml new file mode 100644 index 00000000000..eb94594570f --- /dev/null +++ b/src/UTMSP/UTMSPAdapterEditor.qml @@ -0,0 +1,1324 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.11 +import QtQuick 2.15 +import QtQuick.Controls 2.4 +import QtPositioning 5.2 +import QtLocation 5.12 +import QtGraphicalEffects 1.0 +import QtLocation 5.3 +import QtPositioning 5.3 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.11 +import QtQuick.Extras 1.4 +import QtQuick.Controls 2.15 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FactSystem 1.0 +import QGroundControl.FactControls 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.MultiVehicleManager 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.ShapeFileHelper 1.0 +import QGroundControl.UTMSP 1.0 + +QGCFlickable { + + id: _root + contentHeight: utmspEditorRect.height + clip: true + + property var myGeoFenceController + property var mapPolygon + property var mapControl + property var flightMap + property var currentMissionItems + property int currentYear : new Date().getFullYear() + property int currentMonth : new Date().getMonth() + 1 + property int currentHour : (new Date()).getHours() + property int currentMin : (new Date()).getMinutes() + property int currentDate : (new Date()).getDay() + property var myActiveVehicle : QGroundControl.multiVehicleManager.activeVehicle + property var flightID + property var startTimeStamp + property bool submissionFlag + property bool approvalFlag + property bool triggerSubmitButton + + readonly property real _editFieldWidth: Math.min(width - _margin * 2, ScreenTools.defaultFontPixelWidth * 15) + readonly property real _margin: ScreenTools.defaultFontPixelWidth / 2 + readonly property real _radius: ScreenTools.defaultFontPixelWidth / 2 + + // Send parameters to PlanView qml + signal responseSent(string response, bool responseFlag) // Send the flight blender response to PlanVeiw + signal vehicleIDSent(string id) // Send Vehcile ID to PlanView + signal resetTriggered() // Send FlightPlan Trigger Value to PlanView + signal timeStampSent(string timestamp, bool activateflag, string id) // Send the flight timestamp to UTMSPActivationStatusBar + + // Set default Geofence Polygon + function defaultPolygon(){ + var rect = Qt.rect(flightMap.centerViewport.x, flightMap.centerViewport.y, flightMap.centerViewport.width, flightMap.centerViewport.height) + var topLeftCoord = flightMap.toCoordinate(Qt.point(rect.x, rect.y),false) + var bottomRightCoord = flightMap.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height),false) + myGeoFenceController.addInclusionPolygon(topLeftCoord,bottomRightCoord) + } + + Rectangle { + id: utmspEditorRect + anchors.left: parent.left + anchors.right: parent.right + height: utmspFenceItems.y + utmspFenceItems.height + (_margin * 2) + radius: _radius + color: qgcPal.missionItemEditor + + QGCLabel { + id: utmspFenceLabel + anchors.margins: _margin + anchors.left: parent.left + anchors.top: parent.top + text: qsTr("UTM Service Editor") + anchors.leftMargin: ScreenTools.defaultFontPixelWidth + } + + Rectangle { + id: utmspFenceItems + anchors.margins: _margin + anchors.left: parent.left + anchors.right: parent.right + anchors.top: utmspFenceLabel.bottom + height: fenceColumn.y + fenceColumn.height + (_margin * 2) + color: qgcPal.windowShadeDark + radius: _radius + + Column { + id: fenceColumn + anchors.margins: _margin + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + spacing: _margin + + // Login Interface + QGCLabel { + anchors.left: parent.left + anchors.right: parent.right + Text{ + text: "Login / Registration" + color: qgcPal.buttonText + font.pointSize: ScreenTools.smallFontPointSize + } + } + + // LED Indicator with status + Row{ + spacing: _margin * 25 + + Switch { + id: login_switch + text: login_switch.checked ? qsTr("Enabled"):qsTr("Disabled") + visible: trigger.visible + indicator: Rectangle { + implicitWidth: ScreenTools.defaultFontPixelWidth * 8 + implicitHeight: ScreenTools.defaultFontPixelHeight * 1.44 + x: login_switch.leftPadding + y: parent.height / 2 - height / 2 + radius: ScreenTools.defaultFontPixelHeight * 0.722 + color: login_switch.checked ? qgcPal.switchUTMSP : qgcPal.buttonText + border.color: login_switch.checked ? qgcPal.switchUTMSP : qgcPal.colorGrey + + Rectangle { + x: login_switch.checked ? parent.width - width : 0 + width: ScreenTools.defaultFontPixelWidth * 4.33 + height: ScreenTools.defaultFontPixelHeight * 1.44 + radius: ScreenTools.defaultFontPixelHeight * 0.722 + color: login_switch.down ? qgcPal.colorGrey : qgcPal.buttonText + border.color: login_switch.checked ? (login_switch.down ? qgcPal.switchUTMSP : qgcPal.switchUTMSP) : qgcPal.colorGrey + } + } + + contentItem: Text { + text: login_switch.text + font: login_switch.font + opacity: enabled ? 1.0 : 0.3 + color: login_switch.checked ? qgcPal.colorGreen : qgcPal.buttonText + verticalAlignment: Text.AlignVCenter + leftPadding: login_switch.indicator.width + login_switch.spacing + } + } + + // Logout button + QGCButton{ + width: ScreenTools.defaultFontPixelWidth * 7.5 + height: ScreenTools.defaultFontPixelHeight * 1.389 + text: "Logout" + visible: !trigger.visible + onClicked:{ + trigger.visible = !trigger.visible + login_switch.checked =!login_switch.checked + loading.visible = false + loginButton.opacity = 1 + } + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 0.1 + height: ScreenTools.defaultFontPixelHeight * 0.83 + color: qgcPal.windowShadeDark + visible: !trigger.visible + } + + Row{ + spacing: _margin * 5 + + QGCLabel{ + id: notifyText + x: ScreenTools.defaultFontPixelWidth * 16.667 + y: ScreenTools.defaultFontPixelWidth * 0.833 + text: "Status:" + color: qgcPal.buttonText + } + + Image { + width: ScreenTools.defaultFontPixelWidth * 5 + height: ScreenTools.defaultFontPixelHeight * 1.667 + source: trigger.visible? "qrc:/utmsp/red.png" : "qrc:/utmsp/green.png" + PropertyAnimation on opacity { + easing.type: Easing.OutQuart + from: 0.5 + to: 1 + loops: Animation.Infinite + running: trigger.visible + alwaysRunToEnd: true + duration: 2000 + } + } + } + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 0.5 + height: ScreenTools.defaultFontPixelHeight * 0.833 + color: qgcPal.windowShadeDark + visible: trigger.visible + } + + Column{ + spacing: _margin * 3.33 + visible: trigger.visible + + Column{ + spacing:_margin * 1.667 + + QGCLabel { text: qsTr("User ID") } + FactTextField { + id: userName + width: ScreenTools.defaultFontPixelWidth * 50 + height: ScreenTools.defaultFontPixelHeight * 1.667 + enabled: login_switch.checked + visible: true + Layout.fillWidth: true + Layout.minimumWidth: _editFieldWidth + placeholderText: "Enter your user ID" + } + } + + Column{ + spacing: _margin * 1.5 + + QGCLabel { text: qsTr("Password:") } + FactTextField { + id: password + width: ScreenTools.defaultFontPixelWidth * 50 + height: ScreenTools.defaultFontPixelHeight * 1.667 + enabled: login_switch.checked + visible: true + echoMode: TextInput.Password + Layout.fillWidth: true + Layout.minimumWidth: _editFieldWidth + placeholderText: "Enter your password" + } + } + + Row{ + spacing: _margin * 30 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 0.5 + height: ScreenTools.defaultFontPixelHeight * 0.833 + color: qgcPal.windowShadeDark + visible: trigger.visible + } + QGCButton { + id: loginButton + text: qsTr("Login") + enabled: login_switch.checked + width: ScreenTools.defaultFontPixelWidth * 18 + height: ScreenTools.defaultFontPixelHeight * 1.667 + x: ScreenTools.defaultFontPixelWidth * 18 + AnimatedImage{ + id: loading + width: ScreenTools.defaultFontPixelWidth * 4.5 + height: ScreenTools.defaultFontPixelHeight * 1.5 + source: "qrc:/utmsp/load.gif" + visible: false + } + onClicked:{ + var validToken = QGroundControl.utmspManager.utmspAuthorization.requestOAuth2Client(userName.text,password.text) + if(validToken === true){ + loginButton.opacity = 0.5 + loading.visible = true + delayTimer.interval = 2500 + delayTimer.repeat = false + delayTimer.start() + } + else{ + errorLogin.visible = true + } + } + } + + Timer { + id: delayTimer + running: false + onTriggered: { + trigger.visible = !trigger.visible + } + } + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 0.5 + height: ScreenTools.defaultFontPixelHeight * 0.833 + color: qgcPal.windowShadeDark + visible: trigger.visible + } + } + Label{ + id: errorLogin + text: "Incorrect Username or Password" + color: qgcPal.colorRed + visible: false + anchors.horizontalCenter: parent.horizontalCenter + } + + Row{ + spacing: _margin * 23.33 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height: ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + QGCLabel { + text: qsTr("Forgot Your Password?") + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 3 + } + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height: ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + + Row{ + spacing:_margin * 23.33 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height: ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + QGCLabel { + text: qsTr("New User? Register Now") //TODO-->Will include register process + Layout.alignment: Qt.AlignHCenter + Layout.columnSpan: 3 + enabled: true + } + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height: ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 0.5 + height: ScreenTools.defaultFontPixelHeight * 0.833 + color: qgcPal.windowShadeDark + } + + SectionHeader { + id: flightinformation + anchors.left: parent.left + anchors.right: parent.right + Text{ + text: "Flight Plan Information" + font.bold: true + color: qgcPal.buttonText + } + visible: !trigger.visible + } + + // Geofence Interface + SectionHeader { + id: insertFence + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Insert Geofence") + checked: false + visible: !trigger.visible + PropertyAnimation on opacity { + easing.type: Easing.OutQuart + from: 0.5 + to: 1 + loops: Animation.Infinite + running: !insertFence.checked + alwaysRunToEnd: true + duration: 2000 + } + } + + Column{ + id: trigger + spacing: _margin * 3.33 + } + + Column { + spacing: _margin * 3.33 + visible: insertFence.checked && !trigger.visible + Switch { + id: geo_switch + text: geo_switch.checked ? qsTr("Enabled") : qsTr("Disabled") + indicator: Rectangle { + implicitWidth: ScreenTools.defaultFontPixelWidth * 8 + implicitHeight: ScreenTools.defaultFontPixelHeight * 1.44 + x: geo_switch.leftPadding + y: parent.height / 2 - height / 2 + radius: ScreenTools.defaultFontPixelHeight * 0.722 + color: geo_switch.checked ? qgcPal.switchUTMSP : qgcPal.buttonText + border.color: geo_switch.checked ? qgcPal.switchUTMSP : qgcPal.colorGrey + Rectangle { + x: geo_switch.checked ? parent.width - width : 0 + width: ScreenTools.defaultFontPixelWidth * 4.33 + height: ScreenTools.defaultFontPixelHeight * 1.44 + radius: ScreenTools.defaultFontPixelHeight * 0.722 + color: geo_switch.down ? qgcPal.colorGrey : qgcPal.buttonText + border.color: geo_switch.checked ? (geo_switch.down ? qgcPal.switchUTMSP : qgcPal.switchUTMSP) : qgcPal.colorGrey + } + } + contentItem: Text { + text: geo_switch.text + font: geo_switch.font + opacity: enabled ? 1.0 : 0.3 + color: geo_switch.checked ? qgcPal.colorGreen : qgcPal.buttonText + verticalAlignment: Text.AlignVCenter + leftPadding: geo_switch.indicator.width + geo_switch.spacing + } + onCheckedChanged: { + if (checked) { + defaultPolygon() + } + } + } + + Row{ + Label{ + text: qsTr("Min Altitude") + color: qgcPal.buttonText + } + } + + Row { + spacing: _margin * 0.667 + + Row{ + spacing: _margin * 0.667 + + Row{ + spacing: _margin * 0.667 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 15 + height: ScreenTools.defaultFontPixelHeight * 1.667 + border.width: ScreenTools.defaultFontPixelWidth * 0.167 + border.color: qgcPal.buttonText + color: qgcPal.windowShadeDark + Text { + anchors.centerIn: parent + color: qgcPal.buttonText + text: qsTr(minSlider.value.toFixed(0)+ " m") + } + } + } + } + + Slider { + id: minSlider + from: 0 + value: 0 + to: 10 + background: Rectangle { + x: minSlider.leftPadding + y: minSlider.topPadding + minSlider.availableHeight / 2 - height / 2 + implicitWidth: ScreenTools.defaultFontPixelWidth * 30 + implicitHeight: ScreenTools.defaultFontPixelHeight * 0.22 + width: minSlider.availableWidth + height: implicitHeight + radius: ScreenTools.defaultFontPixelHeight * 0.1111 + color: qgcPal.colorGrey + Rectangle { + width: minSlider.visualPosition * parent.width + height: parent.height + color: qgcPal.sliderUTMSP + radius: ScreenTools.defaultFontPixelHeight * 0.1111 + } + } + handle: Rectangle { + x: minSlider.leftPadding + minSlider.visualPosition * (minSlider.availableWidth - width) + y: minSlider.topPadding + minSlider.availableHeight / 2 - height / 2 + implicitWidth: ScreenTools.defaultFontPixelWidth * 4.33 + implicitHeight: ScreenTools.defaultFontPixelHeight * 1.44 + radius: ScreenTools.defaultFontPixelHeight * 0.722 + color: minSlider.pressed ? qgcPal.colorGrey : qgcPal.buttonText + border.color: qgcPal.colorGrey + } + } + } + + Row{ + Label{ + text: qsTr("Max Altitude") + color: qgcPal.buttonText + } + } + + Row{ + spacing: _margin * 0.667 + + Row{ + spacing: _margin * 0.667 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 15 + height: ScreenTools.defaultFontPixelHeight * 1.667 + border.width: ScreenTools.defaultFontPixelWidth * 0.1667 + border.color: qgcPal.buttonText + color: qgcPal.windowShadeDark + Text { + anchors.centerIn: parent + color: qgcPal.buttonText + text: qsTr(maxSlider.value.toFixed(0)+ " m") + } + } + } + Slider { + id: maxSlider + from: 0 + value: 0 + to: 120 + + background: Rectangle { + x: maxSlider.leftPadding + y: maxSlider.topPadding + maxSlider.availableHeight / 2 - height / 2 + implicitWidth: ScreenTools.defaultFontPixelWidth * 30 + implicitHeight: ScreenTools.defaultFontPixelHeight * 0.22 + width: maxSlider.availableWidth + height: implicitHeight + radius: ScreenTools.defaultFontPixelHeight * 0.1111 + color: qgcPal.colorGrey + Rectangle { + width: maxSlider.visualPosition * parent.width + height: parent.height + color: qgcPal.sliderUTMSP + radius: ScreenTools.defaultFontPixelHeight * 0.1111 + } + } + handle: Rectangle { + x: maxSlider.leftPadding + maxSlider.visualPosition * (maxSlider.availableWidth - width) + y: maxSlider.topPadding + maxSlider.availableHeight / 2 - height / 2 + implicitWidth: ScreenTools.defaultFontPixelWidth * 4.33 + implicitHeight: ScreenTools.defaultFontPixelHeight * 1.44 + radius: ScreenTools.defaultFontPixelHeight * 0.7222 + color: maxSlider.pressed ? qgcPal.colorGrey : qgcPal.buttonText + border.color: qgcPal.colorGrey + } + } + } + + Row { + ListView { + model: myGeoFenceController.polygons + delegate: QGCButton { + text: qsTr("Delete") + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 1.667 + x: ScreenTools.defaultFontPixelWidth * 43 + y: ScreenTools.defaultFontPixelHeight * 2 + onClicked: { + myGeoFenceController.deletePolygon(index) + geo_switch.checked =false + } + } + } + } + } + + // Date Interface + SectionHeader { + id: dateandTime + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Date & Time") + checked: false + visible: !trigger.visible + + PropertyAnimation on opacity { + easing.type: Easing.OutQuart + from: 0.5 + to: 1 + loops: Animation.Infinite + running: !dateandTime.checked + alwaysRunToEnd: true + duration: 2000 + } + } + + TabBar { + visible: dateandTime.checked && !trigger.visible + anchors.left: parent.left + anchors.right: parent.right + background: Rectangle { + color: qgcPal.buttonText + } + + TabButton { + id: dateButton + text: qsTr("Date") + checked: !timeButton.checked + + Image { + width: ScreenTools.defaultFontPixelWidth * 5 + height: ScreenTools.defaultFontPixelHeight * 1.667 + source: "qrc:/utmsp/date.svg" + anchors.horizontalCenter: dateButton.horizontalCenter + anchors.verticalCenter: dateButton.verticalCenter + anchors.horizontalCenterOffset: -40 + } + } + + TabButton { + id: timeButton + text: qsTr("Time") + checked: false + + Image { + width: ScreenTools.defaultFontPixelWidth * 5 + height: ScreenTools.defaultFontPixelHeight * 1.667 + source: "qrc:/utmsp/time.svg" + anchors.horizontalCenter: timeButton.horizontalCenter + anchors.verticalCenter: timeButton.verticalCenter + anchors.horizontalCenterOffset: -40 + } + } + } + + Column{ + spacing: _margin * 3.33 + + Row{ + visible: dateButton.checked && dateandTime.checked && !trigger.visible + spacing: _margin * 10 + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height: ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + Text{ + text: "Month" + color: qgcPal.buttonText + } + Text{ + text: " Day" + color: qgcPal.buttonText + } + Text{ + text: " Year" + color: qgcPal.buttonText + } + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + + Row{ + spacing: _margin * 6.667 + visible: dateButton.checked && dateandTime.checked && !trigger.visible + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + Tumbler { + id: scrollMonth + model: { + var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"] + return monthNames; + } + currentIndex: (new Date()).getMonth() + background: Item { + Rectangle { + opacity: scrollMonth.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: scrollMonth.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: scrollMonth.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (scrollMonth.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: scrollMonth.horizontalCenter + y: scrollMonth.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: scrollMonth.horizontalCenter + y: scrollMonth.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: scrollDate + model: { + var modelArray = [] + for (var i = 1; i <= 31; i++) { + modelArray.push(i.toString()) + } + return modelArray; + } + currentIndex: (new Date()).getDate() - 1 + background: Item { + Rectangle { + opacity: scrollDate.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: scrollDate.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: scrollDate.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (scrollDate.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: scrollDate.horizontalCenter + y: scrollDate.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: scrollDate.horizontalCenter + y: scrollDate.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: scrollYear + model: { + var modelArray = [] + var currentYear = (new Date()).getFullYear() + for (var i = currentYear - 5; i <= currentYear + 10; i++) { + modelArray.push(i.toString()) + } + return modelArray; + } + currentIndex: 5 + background: Item { + Rectangle { + opacity: scrollYear.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: scrollYear.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: scrollYear.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (scrollYear.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: scrollYear.horizontalCenter + y: scrollYear.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: scrollYear.horizontalCenter + y: scrollYear.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + } + + // Start and End Time Interface + Column{ + spacing: _margin * 3.33 + + Row{ + visible: timeButton.checked && dateandTime.checked && !trigger.visible + spacing: _margin * 10 + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + + Text{ + text: " Hour" + color: qgcPal.buttonText + } + + Text{ + text: " Minute" + color: qgcPal.buttonText + } + + Text{ + text: " Second" + color: qgcPal.buttonText + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + + Row{ + spacing: _margin * 6.667 + visible: timeButton.checked && startID.checked && dateandTime.checked && !trigger.visible + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + + Tumbler { + id: starthours + model: { + var modelArray = [] + for (var i = 0; i <= 23; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getHours() + + background: Item { + Rectangle { + opacity: starthours.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + + Rectangle { + opacity: starthours.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + + delegate: Text { + text: modelData + font: starthours.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (starthours.visibleItemCount / 2) + } + + Rectangle { + anchors.horizontalCenter: starthours.horizontalCenter + y: starthours.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + + Rectangle { + anchors.horizontalCenter: starthours.horizontalCenter + y: starthours.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: startMinutes + model: { + var modelArray = [] + for (var i = 0; i <= 59; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getMinutes()+1 + background: Item { + Rectangle { + opacity: startMinutes.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: startMinutes.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: startMinutes.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (startMinutes.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: startMinutes.horizontalCenter + y: startMinutes.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: startMinutes.horizontalCenter + y: startMinutes.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: startSeconds + model: { + var modelArray = [] + for (var i = 0; i <= 59; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getSeconds() + background: Item { + Rectangle { + opacity: startSeconds.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + + Rectangle { + opacity: startSeconds.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: startSeconds.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (startSeconds.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: startSeconds.horizontalCenter + y: startSeconds.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: startSeconds.horizontalCenter + y: startSeconds.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + + Row{ + spacing: _margin * 6.667 + visible: timeButton.checked && stopID.checked && dateandTime.checked && !trigger.visible + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + + Tumbler { + id: endhours + model: { + var modelArray = [] + for (var i = 0; i <= 23; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getHours() +1 + background: Item { + Rectangle { + opacity: endhours.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: endhours.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: endhours.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (endhours.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: endhours.horizontalCenter + y: endhours.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: endhours.horizontalCenter + y: endhours.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: endMinutes + model: { + var modelArray = [] + for (var i = 0; i <= 59; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getMinutes() + background: Item { + Rectangle { + opacity: endMinutes.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: endMinutes.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: endMinutes.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (endMinutes.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: endMinutes.horizontalCenter + y: endMinutes.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: endMinutes.horizontalCenter + y: endMinutes.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Tumbler { + id: endSeconds + model: { + var modelArray = [] + for (var i = 0; i <= 59; i++) { + modelArray.push(i.toString().padStart(2, '0')) + } + return modelArray; + } + currentIndex: (new Date()).getSeconds() + background: Item { + Rectangle { + opacity: endSeconds.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.top: parent.top + } + Rectangle { + opacity: endSeconds.enabled ? 0.2 : 0.1 + border.color: qgcPal.textFieldText + width: parent.width + height: ScreenTools.defaultFontPixelHeight * 0.056 + anchors.bottom: parent.bottom + } + } + delegate: Text { + text: modelData + font: endSeconds.font + color: qgcPal.buttonText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity: 1.0 - Math.abs(Tumbler.displacement) / (endSeconds.visibleItemCount / 2) + } + Rectangle { + anchors.horizontalCenter: endSeconds.horizontalCenter + y: endSeconds.height * 0.4 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + Rectangle { + anchors.horizontalCenter: endSeconds.horizontalCenter + y: endSeconds.height * 0.6 + width: ScreenTools.defaultFontPixelWidth * 6.667 + height: ScreenTools.defaultFontPixelHeight * 0.056 + color: qgcPal.colorGreen + } + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 2.5 + height:ScreenTools.defaultFontPixelHeight * 0.278 + color: qgcPal.windowShadeDark + } + } + + Row{ + spacing: _margin * 10 + visible: !trigger.visible + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 5 + height:ScreenTools.defaultFontPixelHeight * 0.556 + color: qgcPal.windowShadeDark + } + + QGCCheckBox { + id: startID + text: qsTr("Start Time") + checked: true + enabled: !stopID.checked + visible: timeButton.checked + } + + QGCCheckBox { + id: stopID + text: qsTr("End Time") + checked: false + visible: timeButton.checked + } + + Rectangle{ + width: ScreenTools.defaultFontPixelWidth * 5 + height:ScreenTools.defaultFontPixelHeight * 0.556 + color: qgcPal.windowShadeDark + } + } + } + + // Mission Altitude + QGCLabel { + text: qsTr("Mission Altitude") + visible: !trigger.visible + } + FactTextField { + Layout.preferredWidth: ScreenTools.defaultFontPixelWidth * 6.667 + fact: QGroundControl.settingsManager.appSettings.defaultMissionItemAltitude + visible: !trigger.visible + } + + Connections { + target: myGeoFenceController + onPolygonBoundarySent: function(coords) { + QGroundControl.utmspManager.utmspVehicle.updatePolygonBoundary(coords) + } + } + + // Request Flight approval Interface + SectionHeader { + anchors.left: parent.left + anchors.right: parent.right + } + + QGCButton { + id: submitFlightPlan + text: qsTr("Register Flight Plan") + visible: !trigger.visible + anchors.left: parent.left + anchors.right: parent.right + enabled: geo_switch.checked /*&& triggerSubmitButton*/ + + onClicked: { + submissionTimer.interval = 2500 + submissionTimer.repeat = false + submissionTimer.start() + var minAltitude = minSlider.value.toFixed(0) + var maxAltitude = maxSlider.value.toFixed(0) + var sday = scrollDate.model[scrollDate.currentIndex] + var smonth = scrollMonth.currentIndex + 1 + var syear = scrollYear.model[scrollYear.currentIndex] + var shour = starthours.model[starthours.currentIndex] + var sminute = startMinutes.model[startMinutes.currentIndex] + var ssecond = startSeconds.model[startSeconds.currentIndex] + var ehour = endhours.model[endhours.currentIndex] + var eminute = endMinutes.model[endMinutes.currentIndex] + var esecond = endSeconds.model[endSeconds.currentIndex] + var st = syear + "-" + smonth + "-" + sday + "T" + shour + ":" + sminute + ":" + ssecond+ "." + "000000" + "Z" + var et = syear + "-" + smonth + "-" + sday + "T" + ehour + ":" + eminute + ":" + esecond+ "." + "000000" + "Z" + var activateTD = syear + "-" + String(smonth).padStart(2, '0') + "-" + String(sday).padStart(2, '0') + "T" + String(shour).padStart(2, '0') + ":" + String(sminute).padStart(2, '0') + ":" + String(ssecond).padStart(2, '0') + "." + "000000" + "Z" + resetTriggered() + myGeoFenceController.min_alt = minAltitude; + myGeoFenceController.max_alt = maxAltitude; + myGeoFenceController.startDT = st.toString() + myGeoFenceController.endDT = et.toString() + myGeoFenceController.loadFlightPlanData() + QGroundControl.utmspManager.utmspVehicle.updateStartDateTime(st.toString()) + QGroundControl.utmspManager.utmspVehicle.updateEndDateTime(et.toString()) + QGroundControl.utmspManager.utmspVehicle.updateMinAltitude(minAltitude) + QGroundControl.utmspManager.utmspVehicle.updateMaxAltitude(maxAltitude) + QGroundControl.utmspManager.utmspVehicle.triggerFlightAuthorization() + var Response_flightID = QGroundControl.utmspManager.utmspVehicle.responseFlightID + var Response_flag = QGroundControl.utmspManager.utmspVehicle.responseFlag + var Response_Json = QGroundControl.utmspManager.utmspVehicle.responseJson + var serialNumber = QGroundControl.utmspManager.utmspVehicle.vehicleSerialNumber + vehicleIDSent(serialNumber) + flightID = Response_flightID + startTimeStamp = activateTD + submissionFlag = Response_flag + responseSent(Response_Json,Response_flag) + } + } + + Timer { + id: submissionTimer + running: false + onTriggered: { + if(submissionFlag === true) + { + approvalFlag = myGeoFenceController.loadUploadFlag() + timeStampSent(startTimeStamp,approvalFlag,flightID) + } + else{ + submitFlightPlan.enabled = true + } + } + } + } + } + } +} diff --git a/src/UTMSP/UTMSPAircraft.cpp b/src/UTMSP/UTMSPAircraft.cpp new file mode 100644 index 00000000000..7d282a597ff --- /dev/null +++ b/src/UTMSP/UTMSPAircraft.cpp @@ -0,0 +1,84 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPAircraft.h" + +UTMSPAircraft::UTMSPAircraft() +{ + +} + +std::vector UTMSPAircraft::_mavTypes= { + "Generic micro air vehicle", + "Fixed wing aircraft", + "Quadrotor", + "Coaxial helicopter", + "Normal helicopter with tail rotor", + "Ground installation", + "Operator control unit / ground control station", + "Airship, controlled", + "Free balloon, uncontrolled", + "Rocket", + "Ground rover", + "Surface vessel, boat, ship", + "Submarine", + "Hexarotor", + "Octorotor", + "Tricopter", + "Flapping wing", + "Kite" +}; + +std::string UTMSPAircraft::aircraftSerialNo(const mavlink_message_t &message) +{ + if (message.msgid == MAVLINK_MSG_ID_AUTOPILOT_VERSION) { + mavlink_autopilot_version_t autopilot_version; + mavlink_msg_autopilot_version_decode(&message, &autopilot_version); + + _mavSerialNumber = autopilot_version.uid; + } + std::string serialNo = std::to_string(_mavSerialNumber); + + return serialNo; +} + +std::string UTMSPAircraft::aircraftModel() +{ + //TODO--> Get the Aircraft Model of pixhawk board + std::string model = "Multi-rotor"; // Dummy model + + return model; +} + +std::string UTMSPAircraft::aircraftClass() +{ + //TODO--> Get the category class + std::string aircraftClass = "Group1"; // Dummy class + + return aircraftClass; +} + +std::string UTMSPAircraft::aircraftType(const mavlink_message_t &message) +{ + switch (message.msgid) + { + case MAVLINK_MSG_ID_HEARTBEAT: + mavlink_heartbeat_t heartbeat; + mavlink_msg_heartbeat_decode(&message, &heartbeat); + _mavType = static_cast(heartbeat.type); + break; + } + + if (_mavType >= 0 && _mavType < static_cast(_mavTypes.size())) { + return _mavTypes[_mavType]; + } + + return "Unknown Type"; +} + diff --git a/src/UTMSP/UTMSPAircraft.h b/src/UTMSP/UTMSPAircraft.h new file mode 100644 index 00000000000..cff34d168f8 --- /dev/null +++ b/src/UTMSP/UTMSPAircraft.h @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "MAVLinkProtocol.h" +#include "MAVLinkStreamConfig.h" +#include + +class UTMSPAircraft { +public: + UTMSPAircraft(); + std::string aircraftSerialNo(const mavlink_message_t &message); + std::string aircraftModel(); + std::string aircraftType(const mavlink_message_t& message); + std::string aircraftClass(); + +private: + int _mavType; + uint64_t _mavSerialNumber; + + static std::vector _mavTypes; +}; diff --git a/src/UTMSP/UTMSPAuthorization.cpp b/src/UTMSP/UTMSPAuthorization.cpp new file mode 100644 index 00000000000..26773cd5028 --- /dev/null +++ b/src/UTMSP/UTMSPAuthorization.cpp @@ -0,0 +1,92 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include +#include + +#include "UTMSPLogger.h" +#include "UTMSPAuthorization.h" + +using json = nlohmann::ordered_json; + +std::string UTMSPAuthorization::_clientID; +std::string UTMSPAuthorization::_clientSecret; +std::string UTMSPAuthorization::_clientToken; + +UTMSPAuthorization::UTMSPAuthorization(): + UTMSPRestInterface("passport.utm.dev.airoplatform.com") +{ + +} + +UTMSPAuthorization::~UTMSPAuthorization() +{ + +} + +bool UTMSPAuthorization::requestOAuth2Client(const QString &clientID, const QString &clientSecret) +{ + // Convert QString to std::string + _clientID = clientID.toStdString(); + _clientSecret = clientSecret.toStdString(); + + // Generate the basic Token + std::string combinedCredential = _clientID + ":" + _clientSecret; + BIO *bio, *b64; + BUF_MEM *bufferPtr; + + b64 = BIO_new(BIO_f_base64()); + bio = BIO_new(BIO_s_mem()); + bio = BIO_push(b64, bio); + + BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); + BIO_write(bio, combinedCredential.c_str(), combinedCredential.length()); + BIO_flush(bio); + BIO_get_mem_ptr(bio, &bufferPtr); + BIO_set_close(bio, BIO_NOCLOSE); + BIO_free_all(bio); + + std::string encodedBasicToken(bufferPtr->data, bufferPtr->length); + BUF_MEM_free(bufferPtr); + setBasicToken(encodedBasicToken); + + // Get the Access Token + setHost("AuthClient"); + connectNetwork(); + const std::string target = "/oauth/token/"; + std::string body = "grant_type=client_credentials&scope=blender.write blender.read&audience=blender.utm.dev.airoplatform.com&client_id=" + _clientID + "&client_secret=" + _clientSecret + "\r\n\r\n"; + modifyRequest(target, http::verb::post, body); + auto [status, response] = executeRequest(); + UTMSP_LOG_INFO() << "UTMSPAuthorization: Authorization Response: " << response; + + if(status == 200) + { + try { + json responseJson = json::parse(response); + _clientToken = responseJson["access_token"]; + _isValidToken = true; + } + catch (const json::parse_error& e) { + UTMSP_LOG_ERROR() << "UTMSPAuthorization: Invalid Token: " << e.what(); + _isValidToken = false; + } + } + else + { + UTMSP_LOG_ERROR() << "UTMSPAuthorization: Invalid Status Code "; + _isValidToken = false; + } + + return _isValidToken; +} + +std::string UTMSPAuthorization::getOAuth2Token() +{ + return _clientToken; +} diff --git a/src/UTMSP/UTMSPAuthorization.h b/src/UTMSP/UTMSPAuthorization.h new file mode 100644 index 00000000000..4262473fd43 --- /dev/null +++ b/src/UTMSP/UTMSPAuthorization.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include + +#include "UTMSPRestInterface.h" + +class UTMSPAuthorization: public QObject, public UTMSPRestInterface +{ + Q_OBJECT +public: + UTMSPAuthorization(); + ~UTMSPAuthorization(); + + std::string getOAuth2Token(); + +protected slots: + bool requestOAuth2Client(const QString& clientID, const QString& clientSecret); + +protected: + http::request _request; + +private: + bool _isValidToken; + + static std::string _clientID; + static std::string _clientSecret; + static std::string _clientToken; +}; diff --git a/src/UTMSP/UTMSPBlenderRestInterface.cpp b/src/UTMSP/UTMSPBlenderRestInterface.cpp new file mode 100644 index 00000000000..68c02b456ed --- /dev/null +++ b/src/UTMSP/UTMSPBlenderRestInterface.cpp @@ -0,0 +1,42 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPBlenderRestInterface.h" + +UTMSPBlenderRestInterface::UTMSPBlenderRestInterface(): + UTMSPRestInterface("blender.utm.dev.airoplatform.com") +{ + +} + +std::pair UTMSPBlenderRestInterface::setFlightPlan(const std::string& body) +{ + // Post Flight plan + const std::string setFlightPlanTarget = "/flight_declaration_ops/set_flight_declaration"; + modifyRequest(setFlightPlanTarget, http::verb::post, body); + + return executeRequest(); +} + +std::pair UTMSPBlenderRestInterface::requestTelemetry(const std::string& body) +{ + // Post RID data + const std::string target = "/flight_stream/set_telemetry"; + modifyRequest(target, http::verb::put, body); + + return executeRequest(); +} + +std::pair UTMSPBlenderRestInterface::ping() +{ + const std::string target = "/ping"; + modifyRequest(target, http::verb::get); + + return executeRequest(); +} diff --git a/src/UTMSP/UTMSPBlenderRestInterface.h b/src/UTMSP/UTMSPBlenderRestInterface.h new file mode 100644 index 00000000000..841d65dcdd1 --- /dev/null +++ b/src/UTMSP/UTMSPBlenderRestInterface.h @@ -0,0 +1,25 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "UTMSPRestInterface.h" + +class UTMSPBlenderRestInterface: public UTMSPRestInterface +{ +public: + UTMSPBlenderRestInterface(); + + std::pair setFlightPlan(const std::string& body); + std::pair requestTelemetry(const std::string& body); + std::pair ping(); + +private: + http::request _request; +}; diff --git a/src/UTMSP/UTMSPFlightDetails.cpp b/src/UTMSP/UTMSPFlightDetails.cpp new file mode 100644 index 00000000000..f2a08ced76e --- /dev/null +++ b/src/UTMSP/UTMSPFlightDetails.cpp @@ -0,0 +1,24 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPFlightDetails.h" + +UTMSPFlightDetails::UTMSPFlightDetails() +{ + +} + +void UTMSPFlightDetails::flightID() +{ + //TODO--> Add the functionality for flightID +} +void UTMSPFlightDetails::status() +{ + //TODO--> Add the functionality for status +} diff --git a/src/UTMSP/UTMSPFlightDetails.h b/src/UTMSP/UTMSPFlightDetails.h new file mode 100644 index 00000000000..1e2dee0c859 --- /dev/null +++ b/src/UTMSP/UTMSPFlightDetails.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include + +class UTMSPFlightDetails{ +public: + UTMSPFlightDetails(); + void flightID(); + void status(); +}; diff --git a/src/UTMSP/UTMSPFlightPlanManager.cpp b/src/UTMSP/UTMSPFlightPlanManager.cpp new file mode 100644 index 00000000000..1f7faccb917 --- /dev/null +++ b/src/UTMSP/UTMSPFlightPlanManager.cpp @@ -0,0 +1,155 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPFlightPlanManager.h" +#include "UTMSPLogger.h" + +std::string UTMSPFlightPlanManager::_responseJSON; +std::string UTMSPFlightPlanManager::_flightResponseID; +bool UTMSPFlightPlanManager::_responseStatus; + +UTMSPFlightPlanManager::UTMSPFlightPlanManager(): + _currentState(FlightState::Idle) +{ + +} + +UTMSPFlightPlanManager::~UTMSPFlightPlanManager() +{ + +} + +void UTMSPFlightPlanManager::getCapability() +{ + //TODO +} + +void UTMSPFlightPlanManager::preRegisterFlightPlan() +{ + //TODO preRegisterFlightPlan is bit unclear now +} + +void UTMSPFlightPlanManager::preRegisterFlightPlanNotification() +{ + //TODO +} + +void UTMSPFlightPlanManager::registerFlightPlan(const std::string &token, + const json &boundaryPolygons, + const int &minAltitude, + const int &maxAltitude, + const std::string &startDateTime, + const std::string &endDateTime) +{ + // Generate Flight declaretion JSON + + this->_flightData.user = "user@example.com"; //TODO + this->_flightData.operation = 1; + this->_flightData.party = "Flight 1023"; //TODO + this->_flightData.startDateTime = startDateTime; + this->_flightData.endDateTime = endDateTime; + this->_flightData.flightDeclaration.type = "FeatureCollection"; + this->_flightData.flightDeclaration.features.type = "Feature"; + this->_flightData.flightDeclaration.features.properties.min_Altitude.meters = minAltitude; + this->_flightData.flightDeclaration.features.properties.min_Altitude.datum = "agl"; + this->_flightData.flightDeclaration.features.properties.max_Altitude.meters = maxAltitude; + this->_flightData.flightDeclaration.features.properties.max_Altitude.datum = "agl"; + this->_flightData.flightDeclaration.features.geometry.type = "Polygon"; + this->_flightData.flightDeclaration.features.geometry.coordinates = boundaryPolygons; + + _flightDataJson.clear(); + + // Generate GeoJson for flight plan + + _flightDataJson["submitted_by"] = this->_flightData.user; + _flightDataJson["type_of_operation"]= this->_flightData.operation; + _flightDataJson["originating_party"]= this->_flightData.party; + _flightDataJson["start_datetime"]= this->_flightData.startDateTime; + _flightDataJson["end_datetime"] = this->_flightData.endDateTime; + _flightDataJson["flight_declaration_geo_json"]["type"] = this->_flightData.flightDeclaration.type; + _flightDataJson["flight_declaration_geo_json"]["features"] ={{ + {"type", this->_flightData.flightDeclaration.features.type}, + {"properties",{ + + {"min_altitude",{ + {"meters",this->_flightData.flightDeclaration.features.properties.min_Altitude.meters}, + {"datum",this->_flightData.flightDeclaration.features.properties.min_Altitude.datum} + }}, + {"max_altitude",{ + {"meters",this->_flightData.flightDeclaration.features.properties.max_Altitude.meters}, + {"datum",this->_flightData.flightDeclaration.features.properties.max_Altitude.datum} + }}}}, + + {"geometry", { + + {"type", this->_flightData.flightDeclaration.features.geometry.type}, + + {"coordinates",{this->_flightData.flightDeclaration.features.geometry.coordinates}} + + }}}}; + + + json data = _flightDataJson; + setHost("BlenderClient"); + connectNetwork(); + setBearerToken(token); + auto [statusCode, response] = setFlightPlan(data.dump(4)); + UTMSP_LOG_INFO() << "UTMSPFlightPlanManager: Register Response -->" << response; + + if(statusCode == 201) + { + try { + json jsonData = json::parse(response); + std::string flightID = jsonData["id"]; + + _flightResponseID = flightID; + _responseJSON = response; + _responseStatus = true; + } + catch (const json::parse_error& e) { + UTMSP_LOG_ERROR() << "UTMSPFlightPlanManager: Error parsing the response: " << e.what(); + _responseJSON = "Response is Invalid"; + _responseStatus = false; + } + } + else + { + UTMSP_LOG_ERROR() << "UTMSPFlightPlanManager: Invalid Status Code"; + _responseJSON = "Response is Invalid"; + _responseStatus = false; + } +} + +std::tuple UTMSPFlightPlanManager::registerFlightPlanNotification() +{ + return std::make_tuple(_responseJSON, _flightResponseID, _responseStatus); +} + +void UTMSPFlightPlanManager::activateFlightPlan() +{ + //TODO : Plan for conformance Monitering phase 2 +} + +void UTMSPFlightPlanManager::activateFlightPlanNotification() +{ + //TODO : Plan for conformance Monitering phase 2 +} + +UTMSPFlightPlanManager::FlightState UTMSPFlightPlanManager::getFlightPlanState() +{ + UTMSP_LOG_INFO() << "UTMSPFlightPlanManager: Fetch state -->" << static_cast(_currentState); + + return _currentState; +} + +void UTMSPFlightPlanManager::updateFlightPlanState(FlightState state) +{ + UTMSP_LOG_INFO() << "UTMSPFlightPlanManager: State updated to -->" << static_cast(state); + _currentState = state; +} diff --git a/src/UTMSP/UTMSPFlightPlanManager.h b/src/UTMSP/UTMSPFlightPlanManager.h new file mode 100644 index 00000000000..435b375d4c0 --- /dev/null +++ b/src/UTMSP/UTMSPFlightPlanManager.h @@ -0,0 +1,101 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "UTMSPBlenderRestInterface.h" + +using json = nlohmann::ordered_json; + +class UTMSPFlightPlanManager: public UTMSPBlenderRestInterface +{ +public: + UTMSPFlightPlanManager(); + ~UTMSPFlightPlanManager(); + + enum class FlightState { + Idle, + Register, + Activated, + StartTelemetryStreaming, + StopTelemetryStreaming + }; + + + void getCapability(); + void preRegisterFlightPlan(); + void preRegisterFlightPlanNotification(); + void registerFlightPlan(const std::string& token, + const json& boundaryPolygons, + const int& minAltitude, + const int& maxAltitude, + const std::string& startDateTime, + const std::string& endDateTime); + std::tuple registerFlightPlanNotification(); + void activateFlightPlan(); + void activateFlightPlanNotification(); + void updateFlightPlanState(FlightState state); + FlightState getFlightPlanState(); + + // Structs for RegisterFlightPlan JSON + struct Min_Altitude{ + int meters; + std::string datum; + }; + + struct Max_Altitude{ + int meters; + std::string datum; + }; + + struct Properties{ + Min_Altitude min_Altitude; + Max_Altitude max_Altitude; + }; + + struct Geometry{ + std::string type; + json coordinates; + }; + + + struct Features{ + std::string type; + Properties properties; + Geometry geometry; + + }; + + struct FlightDeclaration{ + std::string type; + Features features; + }; + + struct FlightData{ + std::string user; + int operation; + std::string party; + std::string startDateTime; + std::string endDateTime; + FlightDeclaration flightDeclaration; + }; + +private: + FlightData _flightData; + json _flightDataJson; + FlightState _currentState; + + static std::string _responseJSON; + static bool _responseStatus; + static std::string _flightResponseID; +}; diff --git a/src/UTMSP/UTMSPLogger.h b/src/UTMSP/UTMSPLogger.h new file mode 100644 index 00000000000..3c27333ea08 --- /dev/null +++ b/src/UTMSP/UTMSPLogger.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#if defined (CONFIG_UTM_ADAPTER) +#include + +inline QDebug operator<<(QDebug debug, const std::string &s) { + return debug << QString::fromStdString(s); +} + +#define UTMSP_LOG_DEBUG if(isDebugMode()) {} else QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).debug +#define UTMSP_LOG_INFO QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).info +#define UTMSP_LOG_WARNING QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).warning +#define UTMSP_LOG_ERROR QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).critical +#define UTMSP_LOG_FATAL QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).fatal + +#else +#include +#include +#include +class UTMSPLogger { +public: + UTMSPLogger(const std::string& level) : logLevel(level) {} + + ~UTMSPLogger() { + if (logLevel != "Debug" || isDebugMode()) { + std::cout << logLevel << ": " << ss.str() << std::endl; + } + } + + template + UTMSPLogger& operator<<(const T& msg) { + ss << msg; + return *this; + } + +private: + std::string logLevel; + std::stringstream ss; +}; + +#define UTMSP_LOG_DEBUG() UTMSPLogger("Debug") +#define UTMSP_LOG_INFO() UTMSPLogger("Info") +#define UTMSP_LOG_WARNING() UTMSPLogger("Warning") +#define UTMSP_LOG_ERROR() UTMSPLogger("Error") +#define UTMSP_LOG_FATAL() UTMSPLogger("Fatal"), std::abort() + +#endif + +inline bool isDebugMode() { +#if defined(QT_DEBUG) + return false; +#else + return true; +#endif +} diff --git a/src/UTMSP/UTMSPManager.cpp b/src/UTMSP/UTMSPManager.cpp new file mode 100644 index 00000000000..87d1deacc55 --- /dev/null +++ b/src/UTMSP/UTMSPManager.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "Vehicle.h" + +#include "UTMSPVehicle.h" +#include "UTMSPManager.h" +#include "UTMSPLogger.h" + +UTMSPManager::UTMSPManager(QGCApplication* app, QGCToolbox* toolbox) : + QGCTool(app, toolbox), + _dispatcher(std::make_shared()) +{ + _utmspAuthorization = new UTMSPAuthorization(); + qmlRegisterUncreatableType ("QGroundControl.UTMSP", 1, 0, "UTMSPManager", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.UTMSP", 1, 0, "UTMSPVehicle", "Reference only"); + qmlRegisterUncreatableType ("QGroundControl.UTMSP", 1, 0, "UTMSPAuthorization", "Reference only"); +} + +UTMSPManager::~UTMSPManager() +{ + UTMSP_LOG_DEBUG() << "UTMSPManager: Destructor called"; + delete _utmspAuthorization; + _utmspAuthorization = nullptr; +} + +void UTMSPManager::setToolbox(QGCToolbox* toolbox) +{ + UTMSP_LOG_INFO() << "UTMSPManager: ToolBox Set" ; + QGCTool::setToolbox(toolbox); +} + +UTMSPVehicle* UTMSPManager::instantiateVehicle(const Vehicle& vehicle) +{ + // TODO: Investigate safe deletion of pointer in this modification of having a member pointer + _vehicle = new UTMSPVehicle(_dispatcher,vehicle); + + return _vehicle; +} + +UTMSPAuthorization* UTMSPManager::instantiateUTMSPAuthorization(){ + + return _utmspAuthorization; +} diff --git a/src/UTMSP/UTMSPManager.h b/src/UTMSP/UTMSPManager.h new file mode 100644 index 00000000000..11a4e35d80b --- /dev/null +++ b/src/UTMSP/UTMSPManager.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "QGCLoggingCategory.h" +#include "QGCToolbox.h" +#include +#include +#include + +#include "UTMSPVehicle.h" +#include "services/dispatcher.h" +#include "UTMSPAuthorization.h" + +class QGCToolbox; +class UTMSPVehicle; +class Vehicle; + +class UTMSPManager : public QGCTool +{ + Q_OBJECT + +public: + UTMSPManager(QGCApplication* app, QGCToolbox* toolbox); + virtual ~UTMSPManager(); + + Q_PROPERTY(UTMSPVehicle* utmspVehicle READ utmspVehicle CONSTANT) + Q_PROPERTY(UTMSPAuthorization* utmspAuthorization READ utmspAuthorization CONSTANT) + + void setToolbox (QGCToolbox* toolbox); + + UTMSPVehicle* instantiateVehicle (const Vehicle& vehicle); + UTMSPAuthorization* instantiateUTMSPAuthorization (void); + UTMSPAuthorization* utmspAuthorization (void) { return _utmspAuthorization;} + UTMSPVehicle* utmspVehicle (void ) {return _vehicle;}; + +private: + UTMSPVehicle* _vehicle = nullptr; + UTMSPAuthorization* _utmspAuthorization = nullptr; + std::shared_ptr _dispatcher; +}; diff --git a/src/UTMSP/UTMSPMapPolygonVisuals.qml b/src/UTMSP/UTMSPMapPolygonVisuals.qml new file mode 100644 index 00000000000..5847792a57b --- /dev/null +++ b/src/UTMSP/UTMSPMapPolygonVisuals.qml @@ -0,0 +1,708 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import QtLocation 5.3 +import QtPositioning 5.3 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.11 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FlightMap 1.0 +import QGroundControl.ShapeFileHelper 1.0 + +Item { + id: _root + property var mapControl + property var mapPolygon + property var missionItems + property bool interactive : mapPolygon.interactive + property color interiorColor : "transparent" + property color altColor : "transparent" + property real interiorOpacity : 1 + property int borderWidth : 0 + property color borderColor : "black" + property string _instructionText : _polygonToolsText + property var _StoreCoordinates : [] + property real _zorderDragHandle : QGroundControl.zOrderMapItems + 3 + property real _zorderSplitHandle : QGroundControl.zOrderMapItems + 2 + property real _zorderCenterHandle : QGroundControl.zOrderMapItems + 1 + readonly property string _polygonToolsText : qsTr("Option") + readonly property string _fenceText : qsTr("Click on the map to add vertices. Click 'Done Fencing' when finished.") + property string someParameter : "defaultParameter" + property real radius : ScreenTools.defaultFontPixelHeight * 4.44 //Automatic Geofence Radius + property real centerX //Initial Drone X-Position + property real centerY //Initial Drone Y-Position + property bool resetChecked + + //Create Geofence Polygon Visuals + function addCommonVisuals() { + if (_objMgrCommonVisuals.empty) { + _objMgrCommonVisuals.createObject(polygonComponent, mapControl, true) + } + } + + //Delete Geofence Polygon Visuals + function removeCommonVisuals() { + _objMgrCommonVisuals.destroyObjects() + } + + //Create Editing tools for Polygon Visuals + function addEditingVisuals() { + if (_objMgrEditingVisuals.empty) { + _objMgrEditingVisuals.createObjects([ dragHandlesComponent, splitHandlesComponent, centerDragHandleComponent ], mapControl, false /* addToMap */) + } + } + + //Delete Editing tools for Polygon Visuals + function removeEditingVisuals() { + _objMgrEditingVisuals.destroyObjects() + } + + // Create a Toolbar for Polygon Visuals + function addToolbarVisuals() { + if (_objMgrToolVisuals.empty) { + var toolbar = _objMgrToolVisuals.createObject(toolbarComponent, mapControl) + toolbar.z = QGroundControl.zOrderWidgets + } + } + + // Delete a Toolbar for Polygon Visuals + function removeToolVisuals() { + _objMgrToolVisuals.destroyObjects() + } + + /// Calculate the default/initial 4 sided polygon + function defaultPolygonVertices() { + // Initial polygon is inset to take 2/3rds space + var rect = Qt.rect(mapControl.centerViewport.x, mapControl.centerViewport.y, mapControl.centerViewport.width, mapControl.centerViewport.height) + rect.x += (rect.width * 0.25) / 2 + rect.y += (rect.height * 0.25) / 2 + rect.width *= 0.75 + rect.height *= 0.75 + + var centerCoord = mapControl.toCoordinate(Qt.point(rect.x + (rect.width / 2), rect.y + (rect.height / 2)), false /* clipToViewPort */) + var topLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y), false /* clipToViewPort */) + var topRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y), false /* clipToViewPort */) + var bottomLeftCoord = mapControl.toCoordinate(Qt.point(rect.x, rect.y + rect.height), false /* clipToViewPort */) + var bottomRightCoord = mapControl.toCoordinate(Qt.point(rect.x + rect.width, rect.y + rect.height), false /* clipToViewPort */) + + // Initial polygon has max width and height of 3000 meters + var halfWidthMeters = Math.min(topLeftCoord.distanceTo(topRightCoord), 3000) / 2 + var halfHeightMeters = Math.min(topLeftCoord.distanceTo(bottomLeftCoord), 3000) / 2 + topLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 0) + topRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 0) + bottomLeftCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, -90).atDistanceAndAzimuth(halfHeightMeters, 180) + bottomRightCoord = centerCoord.atDistanceAndAzimuth(halfWidthMeters, 90).atDistanceAndAzimuth(halfHeightMeters, 180) + console.log([topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord]) + return [topLeftCoord, topRightCoord, bottomRightCoord, bottomLeftCoord] + } + + // Generate Automatic Geofence + function optimalPolygonVertices() { + + var firstPoints = [] + var lastPoints = [] + var topCoordList = [] + var bottomCoordList = [] + var arrayPolygon = [] + var newPolyCoords = [] + var Mt = [] + var Ct = [] + var Mb = [] + var Cb = [] + var thetaList = [] + var Xt = [] + var Yt = [] + var Xb = [] + var Yb = [] + var appendedListx =[] + var appendedListy =[] + var X_Top = [] + var Y_Top = [] + var X_Bottom = [] + var Y_Bottom = [] + var coordinateListx = [] + var coordinateListy = [] + + centerX = Math.round(mapControl.fromCoordinate(missionItems.get(1).coordinate,false).x) + centerY = Math.round(mapControl.fromCoordinate(missionItems.get(1).coordinate,false).y) + for (var r = 0; r < missionItems.count - 2; r++) { + var item = missionItems.get(r + 2) + var point = mapControl.fromCoordinate(item.coordinate,false) + coordinateListx.push(Math.round(point.x)) + coordinateListy.push(Math.round(point.y)) + } + + var lastcenterX = coordinateListx[coordinateListx.length - 1] + var lastcenterY = coordinateListy[coordinateListy.length - 1] + coordinateListx.splice(0, 0, centerX) + coordinateListy.splice(0, 0, centerY) + + for (var m = 1; m < coordinateListx.length - 1; m++){ + appendedListx.push(coordinateListx[m]) + appendedListx.push(coordinateListx[m]) + appendedListy.push(coordinateListy[m]) + appendedListy.push(coordinateListy[m]) + } + + appendedListx.splice(0, 0, centerX) + appendedListx.splice(appendedListx.length, 0, coordinateListx[coordinateListx.length - 1]) + appendedListy.splice(0, 0, centerY) + appendedListy.splice(appendedListy.length, 0, coordinateListy[coordinateListy.length - 1]) + + for (var n = 0; n < coordinateListx.length - 1; n++){ + + var theta = Math.atan((coordinateListy[n + 1] - coordinateListy[n]) / (coordinateListx[n + 1] - coordinateListx[n])) + + if(coordinateListx[n + 1] > coordinateListx[n] && coordinateListy[n + 1] < coordinateListy[n] ){ + thetaList.push(theta) + } + + else if(coordinateListx[n + 1] > coordinateListx[n] && coordinateListy[n + 1] > coordinateListy[n] && theta > 0){ + thetaList.push(theta) + } + + else if(coordinateListx[n + 1] < coordinateListx[n] && coordinateListy[n + 1] > coordinateListy[n] && theta > 0){ + var newtheta1 = ((theta * (180 / Math.PI)) - 180) * (Math.PI / 180) + thetaList.push(newtheta1) + } + + else { + var newtheta2 = ((theta * (180 / Math.PI)) + 180) * (Math.PI / 180) + thetaList.push(newtheta2) + } + } + + for (var o = 0; o < appendedListx.length-1; o += 2){ + + var xt1 = appendedListx[o] - 80 * Math.sin(thetaList[o / 2]) + var yt1 = appendedListy[o] + 80 * Math.cos(thetaList[o / 2]) + + var xt2 = appendedListx[o + 1] - 80 * Math.sin(thetaList[o / 2]) + var yt2 = appendedListy[o + 1] + 80 * Math.cos(thetaList[o / 2]) + + var xb1 = appendedListx[o] + 80 * Math.sin(thetaList[o / 2]) + var yb1 = appendedListy[o] - 80 * Math.cos(thetaList[o / 2]) + + var xb2 = appendedListx[o+1] + 80 * Math.sin(thetaList[o / 2]) + var yb2 = appendedListy[o+1] - 80 * Math.cos(thetaList[o / 2]) + + Xt.push(xt1) + Xt.push(xt2) + Xb.push(xb1) + Xb.push(xb2) + + Yt.push(yt1) + Yt.push(yt2) + Yb.push(yb1) + Yb.push(yb2) + } + + for (var q = 0; q < Xt.length - 1; q += 2){ + + var mt = (Yt[q] - Yt[q + 1]) / (Xt[q] - Xt[q + 1]) + var ct = (-mt * Xt[q + 1]) + Yt[q + 1] + Mt.push(mt) + Ct.push(ct) + + var mb = (Yb[q] - Yb[q + 1]) / (Xb[q] - Xb[q + 1]) + var cb = (-mb * Xb[q + 1]) + Yb[q + 1] + Mb.push(mb) + Cb.push(cb) + } + + for (var p = 0; p < Mt.length - 1; p++){ + + var xTop = (Ct[p + 1] - Ct[p]) / (Mt[p] - Mt[p + 1]) + var yTop = Mt[p] * xTop + Ct[p] + + var xBottom = (Cb[p + 1] - Cb[p]) / (Mb[p] - Mb[p + 1]) + var yBottom = Mb[p] * xBottom + Cb[p] + + X_Top.push(xTop) + Y_Top.push(yTop) + X_Bottom.push(xBottom) + Y_Bottom.push(yBottom) + } + + X_Top.splice(0, 0, Xt[0]) + Y_Top.splice(0, 0, Yt[0]) + + X_Bottom.splice(0, 0, Xb[0]) + Y_Bottom.splice(0, 0, Yb[0]) + + X_Top.splice(Xt.length - 1, 0, Xt[Xt.length - 1]) + Y_Top.splice(Yt.length - 1, 0, Yt[Yt.length - 1]) + + X_Bottom.splice(Yb.length - 1, 0, Xb[Xb.length - 1]) + Y_Bottom.splice(Yb.length - 1, 0, Yb[Yb.length - 1]) + + + for (var i = 90 + (thetaList[0] * (180 / Math.PI)); i <= 270+(thetaList[0] * (180 / Math.PI)); i += 9) { + var anglef = i * Math.PI / 180.0 + var xf = centerX + radius * Math.cos(anglef) + var yf = centerY + radius * Math.sin(anglef) + firstPoints.push(mapControl.toCoordinate(Qt.point(xf, yf))) + } + + for (var j = 270 + (thetaList[thetaList.length-1] * (180 / Math.PI)); j <= 450 + (thetaList[thetaList.length - 1]*(180/Math.PI)); j += 9) { + var anglel = j * Math.PI / 180.0 + var xl = lastcenterX + radius * Math.cos(anglel) + var yl = lastcenterY + radius * Math.sin(anglel) + lastPoints.push(mapControl.toCoordinate(Qt.point(xl, yl))) + } + + for (var k = 0; k <= X_Top.length - 1; k++) { + var x_top = X_Top[k] + var y_top = Y_Top[k] + + var x_bottom = X_Bottom[k] + var y_bottom = Y_Bottom[k] + + var topCoord = mapControl.toCoordinate(Qt.point(x_top, y_top), false /* clipToViewPort */) + var bottomCoord = mapControl.toCoordinate(Qt.point(x_bottom, y_bottom), false /* clipToViewPort */) + topCoordList.push(topCoord) + bottomCoordList.push(bottomCoord) + } + + arrayPolygon.push(topCoordList) + arrayPolygon.push(lastPoints.reverse()) + arrayPolygon.push(bottomCoordList.reverse()) + arrayPolygon.push(firstPoints.reverse()) + + for (var l = 0; l < arrayPolygon.length; l++) { + newPolyCoords = newPolyCoords.concat(arrayPolygon[l]) + } + + return newPolyCoords; + } + + /// Reset polygon back to initial default + function _resetPolygon() { + mapPolygon.beginReset() + mapPolygon.clear() + mapPolygon.appendVertices(optimalPolygonVertices()) + mapPolygon.endReset() + } + + function _handleInteractiveChanged() { + if (interactive) { + addEditingVisuals() + addToolbarVisuals() + } else { + mapPolygon.traceMode = false + removeEditingVisuals() + removeToolVisuals() + } + } + + function _saveCurrentVertices() { + _StoreCoordinates = [ ] + for (var i=0; i 3 && menu._editingVertexIndex >= 0) + menu.popup() + } + + function popupCenter() { + menu.popup() + } + + QGCMenuItem { + id: removeVertexItem + text: qsTr("Remove vertex") + onTriggered: { + if (menu._editingVertexIndex >= 0) { + mapPolygon.removeVertex(menu._editingVertexIndex) + } + } + } + + QGCMenuSeparator { + visible: removeVertexItem.visible + } + + QGCMenuItem { + text: qsTr("Edit position..." ) + onTriggered: editCenterPositionDialog.createObject(mainWindow).open() + } + + QGCMenuItem { + text: qsTr("Edit position..." ) + visible: menu._editingVertexIndex >= 0 + onTriggered: editVertexPositionDialog.createObject(mainWindow).open() + } + } + + Component { + id: polygonComponent + + MapPolygon { + color: mapPolygon.showAltColor ? altColor : interiorColor + opacity: interiorOpacity + border.color: borderColor + border.width: borderWidth + path: mapPolygon.path + } + } + + Component { + id: splitHandleComponent + + MapQuickItem { + id: mapQuickItem + anchorPoint.x: sourceItem.width / 2 + anchorPoint.y: sourceItem.height / 2 + property int vertexIndex + + sourceItem: SplitIndicator { + z: _zorderSplitHandle + onClicked: if(_root.interactive) mapPolygon.splitPolygonSegment(mapQuickItem.vertexIndex) + } + } + } + + Component { + id: splitHandlesComponent + + Repeater { + model: mapPolygon.path + + delegate: Item { + property var _splitHandle + property var _vertices: mapPolygon.path + + function _setHandlePosition() { + var nextIndex = index + 1 + if (nextIndex > _vertices.length - 1) { + nextIndex = 0 + } + var distance = _vertices[index].distanceTo(_vertices[nextIndex]) + var azimuth = _vertices[index].azimuthTo(_vertices[nextIndex]) + _splitHandle.coordinate = _vertices[index].atDistanceAndAzimuth(distance / 2, azimuth) + } + + Component.onCompleted: { + _splitHandle = splitHandleComponent.createObject(mapControl) + _splitHandle.vertexIndex = index + _setHandlePosition() + mapControl.addMapItem(_splitHandle) + } + + Component.onDestruction: { + if (_splitHandle) { + _splitHandle.destroy() + } + } + } + } + } + + // Control which is used to drag polygon vertices + Component { + id: dragAreaComponent + + MissionItemIndicatorDrag { + id: dragArea + mapControl: _root.mapControl + z: _zorderDragHandle + onDragStop: mapPolygon.verifyClockwiseWinding() + + property int polygonVertex + + property bool _creationComplete: false + + Component.onCompleted: _creationComplete = true + + onItemCoordinateChanged: { + if (_creationComplete) { + // During component creation some bad coordinate values got through which screws up draw + mapPolygon.adjustVertex(polygonVertex, itemCoordinate) + } + } + + onClicked: if(_root.interactive) menu.popupVertex(polygonVertex) + } + } + + Component { + id: centerDragHandle + MapQuickItem { + id: mapQuickItem + anchorPoint.x: dragHandle.width * 0.5 + anchorPoint.y: dragHandle.height * 0.5 + z: _zorderDragHandle + sourceItem: Rectangle { + id: dragHandle + width: ScreenTools.defaultFontPixelHeight * 1.5 + height: width + radius: width * 0.5 + color: Qt.rgba(1,1,1,0.8) + border.color: Qt.rgba(0,0,0,0.25) + border.width: 1 + QGCColoredImage { + width: parent.width + height: width + color: Qt.rgba(0,0,0,1) + mipmap: true + fillMode: Image.PreserveAspectFit + source: "/qmlimages/MapCenter.svg" + sourceSize.height: height + anchors.centerIn: parent + } + } + } + } + + Component { + id: dragHandleComponent + + MapQuickItem { + id: mapQuickItem + anchorPoint.x: dragHandle.width / 2 + anchorPoint.y: dragHandle.height / 2 + z: _zorderDragHandle + + + property int polygonVertex + + sourceItem: Rectangle { + id: dragHandle + width: ScreenTools.defaultFontPixelHeight * 1.5 + height: width + radius: width * 0.5 + color: Qt.rgba(1,1,1,0.8) + border.color: Qt.rgba(0,0,0,0.25) + border.width: 1 + } + } + } + + // Add all polygon vertex drag handles to the map + Component { + id: dragHandlesComponent + + Repeater { + model: mapPolygon.pathModel + + delegate: Item { + property var _visuals: [ ] + + Component.onCompleted: { + var dragHandle = dragHandleComponent.createObject(mapControl) + dragHandle.coordinate = Qt.binding(function() { return object.coordinate }) + dragHandle.polygonVertex = Qt.binding(function() { return index }) + mapControl.addMapItem(dragHandle) + var dragArea = dragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": object.coordinate }) + dragArea.polygonVertex = Qt.binding(function() { return index }) + _visuals.push(dragHandle) + _visuals.push(dragArea) + } + + Component.onDestruction: { + for (var i=0; i<_visuals.length; i++) { + _visuals[i].destroy() + } + _visuals = [ ] + } + } + } + } + + Component { + id: editCenterPositionDialog + + EditPositionDialog { + title: qsTr("Edit Center Position") + coordinate: mapPolygon.center + onCoordinateChanged: { + // Prevent spamming signals on vertex changes by setting centerDrag = true when changing center position. + // This also fixes a bug where Qt gets confused by all the signalling and draws a bad visual. + mapPolygon.centerDrag = true + mapPolygon.center = coordinate + mapPolygon.centerDrag = false + } + } + } + + Component { + id: editVertexPositionDialog + + EditPositionDialog { + title: qsTr("Edit Vertex Position") + coordinate: mapPolygon.vertexCoordinate(menu._editingVertexIndex) + onCoordinateChanged: { + mapPolygon.adjustVertex(menu._editingVertexIndex, coordinate) + mapPolygon.verifyClockwiseWinding() + } + } + } + + Component { + id: centerDragAreaComponent + + MissionItemIndicatorDrag { + mapControl: _root.mapControl + z: _zorderCenterHandle + onItemCoordinateChanged: mapPolygon.center = itemCoordinate + onDragStart: mapPolygon.centerDrag = true + onDragStop: mapPolygon.centerDrag = false + } + } + + Component { + id: centerDragHandleComponent + + Item { + property var dragHandle + property var dragArea + + Component.onCompleted: { + dragHandle = centerDragHandle.createObject(mapControl) + dragHandle.coordinate = Qt.binding(function() { return mapPolygon.center }) + mapControl.addMapItem(dragHandle) + dragArea = centerDragAreaComponent.createObject(mapControl, { "itemIndicator": dragHandle, "itemCoordinate": mapPolygon.center }) + } + + Component.onDestruction: { + dragHandle.destroy() + dragArea.destroy() + } + } + } + + Component { + id: toolbarComponent + + PlanEditToolbar { + anchors.horizontalCenter: mapControl.left + anchors.horizontalCenterOffset: mapControl.centerViewport.left + (mapControl.centerViewport.width / 2) + y: mapControl.centerViewport.top + availableWidth: mapControl.centerViewport.width + + QGCButton { + _horizontalPadding: 0 + text: qsTr("Automatic") + visible: !mapPolygon.traceMode + onClicked: _resetPolygon() + } + + + QGCButton { + _horizontalPadding: 0 + text: mapPolygon.traceMode ? qsTr("Done fencing") : qsTr("Mannual") + onClicked: { + if (mapPolygon.traceMode) { + if (mapPolygon.count < 3) { + _restorePreviousVertices() + } + mapPolygon.traceMode = false + } else { + _saveCurrentVertices() + mapPolygon.traceMode = true + mapPolygon.clear() + } + } + } + + } + } + + + // Mouse area to capture clicks for tracing a polygon + Component { + id: traceMouseAreaComponent + + MouseArea { + anchors.fill: mapControl + z: QGroundControl.zOrderMapItems + 1 // Over item indicators + + onClicked: { + if (mouse.button === Qt.LeftButton) { + mapPolygon.appendVertex(mapControl.toCoordinate(Qt.point(mouse.x, mouse.y), false /* clipToViewPort */)) + + } + } + } + } + + Component { + id: radiusDragHandleComponent + + MapQuickItem { + id: mapQuickItem + anchorPoint.x: dragHandle.width / 2 + anchorPoint.y: dragHandle.height / 2 + z: QGroundControl.zOrderMapItems + 2 + + sourceItem: Rectangle { + id: dragHandle + width: ScreenTools.defaultFontPixelHeight * 1.5 + height: width + radius: width / 2 + color: "white" + opacity: interiorOpacity * .90 + } + } + } +} diff --git a/src/UTMSP/UTMSPMapVisuals.qml b/src/UTMSP/UTMSPMapVisuals.qml new file mode 100644 index 00000000000..896b4e65a2d --- /dev/null +++ b/src/UTMSP/UTMSPMapVisuals.qml @@ -0,0 +1,61 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtLocation 5.3 +import QtPositioning 5.3 + +import QGroundControl 1.0 +import QGroundControl.ScreenTools 1.0 +import QGroundControl.Palette 1.0 +import QGroundControl.Controls 1.0 +import QGroundControl.FlightMap 1.0 + +/// UTMSPGeoFence map visuals +Item { + id: _root + z: QGroundControl.zOrderMapItems + + property var map + property var myGeoFenceController + property var currentMissionItems + property bool interactive: false ///< true: user can interact with items + property bool planView: false ///< true: visuals showing in plan view + property var homePosition + property var _breachReturnPointComponent + property var _breachReturnDragComponent + property var _paramCircleFenceComponent + property var _utmspPolygon: myGeoFenceController.polygons + property color _borderColor: "green" + property int _borderWidthInclusion: 2 + property int _borderWidthExclusion: 0 + property color _interiorColorExclusion: "orange" + property color _interiorColorInclusion: "transparent" + property real _interiorOpacityExclusion: 0.2 * opacity + property real _interiorOpacityInclusion: 1 * opacity + property bool resetCheck + + Instantiator { + model: _utmspPolygon + + delegate : UTMSPMapPolygonVisuals { + parent: _root + mapControl: map + mapPolygon: object + borderWidth: object.inclusion ? _borderWidthInclusion : _borderWidthExclusion + borderColor: _borderColor + interiorColor: object.inclusion ? _interiorColorExclusion : _interiorColorInclusion + interiorOpacity: object.inclusion ? _interiorOpacityExclusion : _interiorOpacityInclusion + interactive: _root.interactive && mapPolygon && mapPolygon.interactive + resetChecked: resetCheck + missionItems: currentMissionItems + } + } +} diff --git a/src/UTMSP/UTMSPNetworkRemoteIDManager.cpp b/src/UTMSP/UTMSPNetworkRemoteIDManager.cpp new file mode 100644 index 00000000000..bf4f3ecadb0 --- /dev/null +++ b/src/UTMSP/UTMSPNetworkRemoteIDManager.cpp @@ -0,0 +1,190 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPNetworkRemoteIDManager.h" +#include "UTMSPLogger.h" + +UTMSPNetworkRemoteIDManager::UTMSPNetworkRemoteIDManager(std::shared_ptr dispatcher): + _dispatcher(dispatcher) +{ + +} + +UTMSPNetworkRemoteIDManager::~UTMSPNetworkRemoteIDManager() +{ + +} + +void UTMSPNetworkRemoteIDManager::getCapabilty(const std::string &token) +{ + connectNetwork(); + setBearerToken(token); +} + +void UTMSPNetworkRemoteIDManager::startTelemetry(const double &latitude, + const double &longitude, + const double &altitude, + const double &heading, + const double &velocityX, + const double &velocityY, + const double &velocityZ, + const double &relativeAltitude, + const std::string &aircraftSerialNumber, + const std::string &operatorID, + const std::string &flightID) +{ + // Generate RID JSON + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + std::ostringstream oss; + + if(_j==0){ + _initLatitude = latitude; + _initLongitude = longitude; + _j++; + } + _ridData = {}; + _ridData.timestamp_accuracy = 0.0; + _ridData.position.accuracy_h = "HAUnknown"; + _ridData.position.accuracy_v = "VAUnknown"; + _ridData.position.extrpolated = true; + _ridData.speed_accuracy = "SAUnknown"; + _ridData.height.reference = "TakeoffLocation"; + _ridData.operational_status = "Undeclared"; + oss << std::put_time(std::gmtime(&now_c), "%FT%T") << "Z"; + _ridData.timestamp.value = oss.str(); + _ridData.timestamp.format = "RFC3339"; + _ridData.position.lat = latitude; + _ridData.position.lng = longitude; + _ridData.position.alt = altitude / 1000; + _ridData.track = heading; + _ridData.speed = std::sqrt(velocityX*velocityX + velocityY*velocityY); + _ridData.vertical_speed = velocityZ; + _ridData.height.distance = relativeAltitude; + + this->_flightDetails.rid_details.id = flightID; + this->_flightDetails.rid_details.operator_id = operatorID; + this->_flightDetails.rid_details.operation_description = "Delivery operation, see more details at https://deliveryops.com/operation"; + this->_flightDetails.eu_classification.category = "EUCategoryUndefined"; + this->_flightDetails.eu_classification.clasS = "EUClassUndefined"; + this->_flightDetails.uas_id.serial_number = aircraftSerialNumber; + this->_flightDetails.uas_id.registration_number =operatorID; + this->_flightDetails.uas_id.utm_id = aircraftSerialNumber; //TODO->Will be taken care as part of registration service integration + this->_flightDetails.uas_id.specific_session_id = "Unknown"; + this->_flightDetails.operator_location_lng = _initLongitude; + this->_flightDetails.operator_location_lat = _initLatitude; + this->_flightDetails.operator_location_accuracyH ="HAUnknown"; + this->_flightDetails.operator_location_accuracyV ="VAUnknown"; + this->_flightDetails.auth_data_format = "string"; + this->_flightDetails.auth_data_data = 0; //TODO->Needs to be updated + this->_flightDetails.serial_number = aircraftSerialNumber; + this->_flightDetails.registration_number = operatorID; + + _flightDetailsJson["rid_details"]["id"] = this->_flightDetails.rid_details.id; + _flightDetailsJson["rid_details"]["operator_id"] = this->_flightDetails.rid_details.operator_id; + _flightDetailsJson["rid_details"]["operation_description"] = this->_flightDetails.rid_details.operation_description; + _flightDetailsJson["eu_classification"]["category"] = this->_flightDetails.eu_classification.category; + _flightDetailsJson["eu_classification"]["class"] = this->_flightDetails.eu_classification.clasS; + _flightDetailsJson["uas_id"]["serial_number"] = this->_flightDetails.uas_id.serial_number; + _flightDetailsJson["uas_id"]["registration_number"] = this->_flightDetails.uas_id.registration_number; + _flightDetailsJson["uas_id"]["utm_id"] = this->_flightDetails.uas_id.utm_id; + _flightDetailsJson["uas_id"]["specific_session_id"] = this->_flightDetails.uas_id.specific_session_id ; + _flightDetailsJson["operator_location"]["position"] = { + {"lng",this->_flightDetails.operator_location_lng}, + {"lat",this->_flightDetails.operator_location_lat}, + {"accuracy_h",this->_flightDetails.operator_location_accuracyH}, + {"accuracy_v",this->_flightDetails.operator_location_accuracyV}}; + _flightDetailsJson["operator_location"]["altitude"] = 0; //TODO-> Will get the operator location + _flightDetailsJson["operator_location"]["altitude_type"] = "Takeoff"; + _flightDetailsJson["auth_data"] = { + {"format", this->_flightDetails.auth_data_format}, + {"data", this->_flightDetails.auth_data_data} + }; + _flightDetailsJson["serial_number"] = this->_flightDetails.uas_id.serial_number;; + _flightDetailsJson["registration_number"] = this->_flightDetails.uas_id.registration_number; + + _ridDataJson.clear(); + _ridDataJson["timestamp"]["value"] = _ridData.timestamp.value; + _ridDataJson["timestamp"]["format"] = _ridData.timestamp.format; + _ridDataJson["timestamp_accuracy"] = _ridData.timestamp_accuracy; + _ridDataJson["operational_status"] = _ridData.operational_status; + _ridDataJson["position"]["lat"] = _ridData.position.lat; + _ridDataJson["position"]["lng"] = _ridData.position.lng; + _ridDataJson["position"]["alt"] = _ridData.position.alt; + _ridDataJson["position"]["accuracy_h"] = _ridData.position.accuracy_h; + _ridDataJson["position"]["accuracy_v"] = _ridData.position.accuracy_v; + _ridDataJson["position"]["extrapolated"] = _ridData.position.extrpolated; + _ridDataJson["position"]["pressure_altitude"] = _ridData.position.pressure_altitude; + _ridDataJson["track"] = _ridData.track; + _ridDataJson["speed"] = _ridData.speed; + _ridDataJson["speed_accuracy"] = _ridData.speed_accuracy; + _ridDataJson["vertical_speed"] = _ridData.vertical_speed; + _ridDataJson["height"]["distance"] = _ridData.height.distance; + _ridDataJson["height"]["reference"] = _ridData.height.reference ; + _ridDataJson["group_radius"] = _ridData.group_radius; + _ridDataJson["group_ceiling"] = _ridData.group_ceiling; + _ridDataJson["group_floor"] = _ridData.group_floor; + _ridDataJson["group_count"] = 1; + _ridDataJson["group_time_start"] = _ridData.timestamp.value; + _ridDataJson["group_time_end"] = _ridData.timestamp.value; + + json current_states = json::array(); + current_states.push_back(_ridDataJson); + json mix; + mix["current_states"] = current_states; + mix["flight_details"] = _flightDetailsJson; + json final_array = json::array(); + final_array.push_back(mix); + json final_format; + final_format["observations"] = final_array; + json data = final_format; + + // Get the RID response + _dispatcher->add_task([data,this]() { + auto [statusCode, response] = requestTelemetry(data.dump(4)); + _statusCode = statusCode; + _response = response; + }); + + if(!_response.empty()){ + + if(_statusCode == 201) + { + try { + json responseJson = json::parse(_response); + _response.clear(); + if (responseJson.contains("message")){ + if(responseJson["message"] == "Telemetry data succesfully submitted"){ + + auto now = std::chrono::system_clock::now(); + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + char buffer[20]; + std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now_c)); + UTMSP_LOG_DEBUG() <<"The Telemetry RID data submitted at " << buffer; + UTMSP_LOG_DEBUG() << "--------------Telemetry Submitted Successfully---------------"; + } + } + } + catch (const json::parse_error& e) { + UTMSP_LOG_ERROR() << "UTMSPNetworkRemoteManager: Error parsing the response: " << e.what(); + } + } + else + { + UTMSP_LOG_ERROR() << "UTMSPNetworkRemoteManager: Invalid Status Code"; + } + } +} + +bool UTMSPNetworkRemoteIDManager::stopTelemetry() +{ + UTMSP_LOG_INFO() << "--------------Telemetry Stopped Successfully---------------"; + + return true; +} diff --git a/src/UTMSP/UTMSPNetworkRemoteIDManager.h b/src/UTMSP/UTMSPNetworkRemoteIDManager.h new file mode 100644 index 00000000000..2be6c4f5d97 --- /dev/null +++ b/src/UTMSP/UTMSPNetworkRemoteIDManager.h @@ -0,0 +1,121 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include + +#include "UTMSPBlenderRestInterface.h" +#include "services/dispatcher.h" + +using json = nlohmann::ordered_json; + +class UTMSPNetworkRemoteIDManager:public UTMSPBlenderRestInterface +{ +public: + UTMSPNetworkRemoteIDManager(std::shared_ptr dispatcher); + ~UTMSPNetworkRemoteIDManager(); + + void getCapabilty(const std::string& token); + void startTelemetry(const double& latitude, + const double& longitude, + const double& altitude, + const double& heading, + const double& velocityX, + const double& velocityY, + const double& velocityZ, + const double& relativeAltitude, + const std::string& aircraftSerialNumber, + const std::string& operatorID, + const std::string& flightID); + bool stopTelemetry(); + + struct Rid_details{ + std::string id; + std::string operator_id; + std::string operation_description; + }; + + struct Eu_classification{ + std::string category; + std::string clasS; + }; + + struct Uas_id{ + std::string serial_number; + std::string registration_number; + std::string utm_id; + std::string specific_session_id; + }; + + struct FlightDetails + { + Rid_details rid_details; + Eu_classification eu_classification; + Uas_id uas_id; + double operator_location_lng; + double operator_location_lat; + std::string operator_location_accuracyH; + std::string operator_location_accuracyV; + std::string auth_data_format; + int auth_data_data; + std::string serial_number; + std::string registration_number; + }; + + struct Position { + double lat; ///< The latitude of the position [°]. + double lng; ///< The longitude of the position in [°]. + double alt; ///< The altitude above mean sea level of the position in [m]. + std::string accuracy_h; ///< The horizontal accuracy of the position in [m]. + std::string accuracy_v; + bool extrpolated; + double pressure_altitude; + }; + + struct Height{ + double distance; + std::string reference; + }; + + struct Timestamp{ + std::string value; + std::string format; + }; + + struct RidData { + Timestamp timestamp; + double timestamp_accuracy; + std::string operational_status; + Position position; + double track; + double speed; + std::string speed_accuracy; + double vertical_speed; + Height height; + double group_radius; + double group_ceiling; + double group_floor; + int group_count; + std::string group_time_start; + std::string group_time_end; + }; + +private: + FlightDetails _flightDetails; + json _flightDetailsJson; + RidData _ridData; + json _ridDataJson; + int _statusCode; + std::string _response; + std::shared_ptr _dispatcher; + double _initLatitude; + double _initLongitude; + int _j=0; +}; diff --git a/src/UTMSP/UTMSPOperator.cpp b/src/UTMSP/UTMSPOperator.cpp new file mode 100644 index 00000000000..2084103920b --- /dev/null +++ b/src/UTMSP/UTMSPOperator.cpp @@ -0,0 +1,29 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPOperator.h" + +UTMSPOperator::UTMSPOperator() +{ + +} + +std::string UTMSPOperator::operatorID() +{ + //TODO--> Get the operator ID from QGC UI + std::string operatorID = "test.123"; + return operatorID; +} + +std::string UTMSPOperator::operatorClass() +{ + //TODO--> Get the operator class + std::string operatorClass = ""; + return operatorClass; +} diff --git a/src/UTMSP/UTMSPOperator.h b/src/UTMSP/UTMSPOperator.h new file mode 100644 index 00000000000..25175806b74 --- /dev/null +++ b/src/UTMSP/UTMSPOperator.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include + +class UTMSPOperator { +public: + UTMSPOperator(); + std::string operatorID(); + std::string operatorClass(); +}; diff --git a/src/UTMSP/UTMSPRestInterface.cpp b/src/UTMSP/UTMSPRestInterface.cpp new file mode 100644 index 00000000000..1035a6ddf55 --- /dev/null +++ b/src/UTMSP/UTMSPRestInterface.cpp @@ -0,0 +1,200 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include +#include + +#include "UTMSPRestInterface.h" +#include "UTMSPLogger.h" + +UTMSPRestInterface::UTMSPRestInterface(std::string host, std::string port): + _ssl_ctx{boost::asio::ssl::context::tlsv13_client}, + _host(host), + _port(port) +{ + +} + +UTMSPRestInterface::~UTMSPRestInterface() +{ + +} + + +http::request UTMSPRestInterface::_request; + +bool isNetworkAvailable() +{ + bool hasConnectivity = false; + + QList interfaces = QNetworkInterface::allInterfaces(); + for (const QNetworkInterface& interface : interfaces) { + + // Check if the interface is up and not loopback + if (interface.isValid() && interface.flags().testFlag(QNetworkInterface::IsUp) + && !interface.flags().testFlag(QNetworkInterface::IsLoopBack)) + { + hasConnectivity = true; + break; + } + } + + if (!hasConnectivity) + { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: No network/internet connectivity"; + } + return hasConnectivity; +} + +void UTMSPRestInterface::setHost(std::string target) +{ + _request.set(http::field::content_length, 0); + _request.version(11); + _request.set(boost::beast::http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + if(target == "AuthClient"){ + _request.set(http::field::host, "passport.utm.dev.airoplatform.com"); + _request.set(http::field::content_type, "application/x-www-form-urlencoded"); + } + else if(target == "BlenderClient"){ + _request.set(http::field::host, "blender.utm.dev.airoplatform.com"); + _request.set(http::field::content_type, "application/json"); + } + + _request.set(http::field::accept, "*/*"); + _request.set(http::field::accept_encoding, "gzip, deflate, br"); + _request.set(http::field::connection, "keep-alive"); +} + +void UTMSPRestInterface::setBasicToken(const std::string &basicToken){ + _basicToken = basicToken; + _request.set(http::field::authorization, "Basic "+ _basicToken); +} + +bool UTMSPRestInterface::connectNetwork() +{ + if (!isNetworkAvailable()) + return ""; + + _ssl_ctx.set_default_verify_paths(); + _stream = QSharedPointer>::create(_ioc, _ssl_ctx); + + if (!SSL_set_tlsext_host_name(_stream->native_handle(), _host.c_str())) + { + boost::system::error_code ec{ static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category() }; + throw boost::system::system_error{ ec }; + } + + boost::system::error_code ec; + + const auto results = _resolver.resolve(_host, _port, ec); + + if (ec) + { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during resolving: " << ec.message(); + return false; + } + else + { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: Resolving successfull"; + } + + boost::asio::connect(_stream->lowest_layer(), results.begin(), results.end(), ec); + + if (ec) + { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during connection: " << ec.message(); + return false; + } + else + { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: Connection successfull"; + } + + _stream->set_verify_mode(boost::asio::ssl::verify_peer, ec); + if (ec) + { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during set_verify_mode: " << ec.message(); + return false; + } + else + { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: set_verify_mode successfull"; + } + + _stream->handshake(boost::asio::ssl::stream_base::client, ec); + if (ec) + { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during handshake: " << ec.message(); + return false; + } + else + { + UTMSP_LOG_INFO() << "UTMSPRestInterfaceLog: Handshake successfull"; + } + + return true; + +} + +void UTMSPRestInterface::modifyRequest(std::string target, http::verb method, std::string body) +{ + _request.target(target); + _request.method(method); + _request.body() = body; + _request.prepare_payload(); +} + +std::pair UTMSPRestInterface::executeRequest() +{ + if (!isNetworkAvailable()) + return std::pair(0,""); + + // sendRequest + std::lock_guard lock_guard(_mutex); + boost::system::error_code ec; + beast::http::write(*_stream.data(), _request, ec); + + if (ec) { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during connection: " << ec.message(); + } else { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: Write successfull"; + } + + // ReceivedResponse + beast::flat_buffer buffer; + http::response response; + http::read(*_stream.data(), buffer, response, ec); + + if (ec) { + UTMSP_LOG_ERROR() << "UTMSPRestInterfaceLog: Error during connection: " << ec.message(); + } else { + UTMSP_LOG_DEBUG() << "UTMSPRestInterfaceLog: Read successfull"; + } + + if (response.result() == beast::http::status::ok) + { + // Handle successful response + UTMSP_LOG_INFO() << "UTMSPRestInterfaceLog: Received OK response."; + } + else + { + UTMSP_LOG_INFO() << "UTMSPRestInterfaceLog: Received response with status code: "<< response.result_int(); + } + if (response.body().size() == 0) { + return std::pair(response.result_int(),""); + } + + return std::pair(response.result_int(), boost::beast::buffers_to_string(response.body().data())); +} + +void UTMSPRestInterface::setBearerToken(const std::string& token) +{ + _request.set(http::field::authorization, "Bearer " + token); +} diff --git a/src/UTMSP/UTMSPRestInterface.h b/src/UTMSP/UTMSPRestInterface.h new file mode 100644 index 00000000000..c7f300d4e61 --- /dev/null +++ b/src/UTMSP/UTMSPRestInterface.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; +using tcp = net::ip::tcp; + +class UTMSPRestInterface { + +public: + UTMSPRestInterface(std::string host, std::string port = "443"); + virtual ~UTMSPRestInterface(); + + bool connectNetwork(); + void setBearerToken(const std::string& token); + std::pair executeRequest(); + void modifyRequest(std::string target, http::verb method, std::string body = ""); + void setBasicToken(const std::string& basicToken); + void setHost(std::string target); + +private: + net::io_context _ioc; + net::ssl::context _ssl_ctx; + net::ip::tcp::resolver _resolver{_ioc}; + QSharedPointer> _stream; + std::mutex _mutex; + std::string _basicToken; + std::string _host; + std::string _port; + + static http::request _request; +}; diff --git a/src/UTMSP/UTMSPServiceController.cpp b/src/UTMSP/UTMSPServiceController.cpp new file mode 100644 index 00000000000..2a8e84c860a --- /dev/null +++ b/src/UTMSP/UTMSPServiceController.cpp @@ -0,0 +1,150 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "UTMSPServiceController.h" +#include "UTMSPLogger.h" + +UTMSPServiceController::UTMSPServiceController(QObject *parent): + QObject(parent), + UTMSPNetworkRemoteIDManager(std::make_shared()) +{ + +} + +UTMSPServiceController::~UTMSPServiceController() +{ + +} + +QString UTMSPServiceController::_responseJson; +QString UTMSPServiceController::_responseFlightID; +bool UTMSPServiceController::_responseFlag; +bool UTMSPServiceController::_activationFlag; +std::string UTMSPServiceController::_clientID; +std::string UTMSPServiceController::_clientPassword; +std::string UTMSPServiceController::_blenderToken; +QList UTMSPServiceController::_boundaryPolygon; +std::string UTMSPServiceController::_startDateTime; +std::string UTMSPServiceController::_endDateTime; +int UTMSPServiceController::_minAltitude; +int UTMSPServiceController::_maxAltitude; +double UTMSPServiceController::_lastLatitude; +double UTMSPServiceController::_lastLongitude; +double UTMSPServiceController::_lastAltitude = 0; + +std::string UTMSPServiceController::flightPlanAuthorization() +{ + _currentState = getFlightPlanState(); + + //Get the Token + _blenderToken = _utmspAuth.getOAuth2Token(); + + // State 1 --> Register + getFlightPlanState(); + _currentState = UTMSPFlightPlanManager::FlightState::Register; + + _coordinateList = json::array(); + for (const auto& coordinates: _boundaryPolygon) + { + json coordinateJson ={coordinates.latitude(),coordinates.longitude()}; + _coordinateList.push_back(coordinateJson); + } + registerFlightPlan(_blenderToken, _coordinateList,_minAltitude,_maxAltitude,_startDateTime, _endDateTime); + //TODO-> Find a correct way to assign min and max altitude for UTM mission + auto [responseJson, flightID, flag] = registerFlightPlanNotification(); + QString _Json = QString::fromStdString(responseJson); + QString _flightID = QString::fromStdString(flightID); + _responseFlightID = _flightID; + emit responseFlightIDChanged(); + _responseJson = _Json; + emit responseJsonChanged(); + _responseFlag = flag; + emit responseFlagChanged(); + updateFlightPlanState(_currentState); + + // State 2 --> Activate Flight Plan + getFlightPlanState(); + if(flag == true){ + _currentState = UTMSPFlightPlanManager::FlightState::Activated; + _activationFlag = true; + emit activationFlagChanged(); + getCapabilty(_blenderToken); + updateFlightPlanState(_currentState); + getFlightPlanState(); + } + + + return _responseFlightID.toStdString(); +} +bool UTMSPServiceController::networkRemoteID(const mavlink_message_t& message, + const std::string &serialNumber, + const std::string &operatorID, + const std::string &flightID) +{ + double vehicleLatitude; + double vehicleLongitude; + double lastLatitude; + double lastLongitude; + + switch (message.msgid) + { + case MAVLINK_MSG_ID_GLOBAL_POSITION_INT: + + // rate-limit updates to 3 Hz + if (!_timerLastSent.hasExpired(3000)) { + return false; + } + _timerLastSent.restart(); + + mavlink_global_position_int_t globalPosition; + mavlink_msg_global_position_int_decode(&message, &globalPosition); + _vehicleLatitude = (static_cast(globalPosition.lat /1e7)); + _vehicleLongitude = (static_cast(globalPosition.lon / 1e7)); + _vehicleAltitude = static_cast(globalPosition.alt) / 1000; + _vehicleHeading = static_cast(globalPosition.hdg); + _vehicleVelocityX = (globalPosition.vx / 100.f); + _vehicleVelocityY = (globalPosition.vy / 100.f); + _vehicleVelocityZ = (globalPosition.vz / 100.f); + _vehicleRelativeAltitude = static_cast(globalPosition.relative_alt) / 1000.0; + + vehicleLatitude = std::round(_vehicleLatitude* 1e4)/ 1e4; + vehicleLongitude = std::round(_vehicleLongitude* 1e4)/ 1e4; + lastLatitude = std::round(_lastLatitude* 1e4)/ 1e4; + lastLongitude = std::round(_lastLongitude* 1e4)/ 1e4; + + if((std::abs(vehicleLatitude-lastLatitude) > 0.0001) || (std::abs(vehicleLongitude-lastLongitude) > 0.0001)) + { + // State 3 --> Start telemetry + _currentState = UTMSPFlightPlanManager::FlightState::StartTelemetryStreaming; + startTelemetry(_vehicleLatitude, _vehicleLongitude, _vehicleAltitude, _vehicleHeading, _vehicleVelocityX, _vehicleVelocityY, _vehicleVelocityZ, _vehicleRelativeAltitude, serialNumber, operatorID, flightID); + updateFlightPlanState(_currentState); + _streamingFlag = false; + } + else + { + // State 4 --> Stop telemetry + bool stopflag = stopTelemetry(); + emit stopTelemetryFlagChanged(stopflag); + _streamingFlag = true; + } + break; + + case MAVLINK_MSG_ID_GPS_RAW_INT: + mavlink_gps_raw_int_t gps_raw; + mavlink_msg_gps_raw_int_decode(&message, &gps_raw); + if (gps_raw.eph == UINT16_MAX) { + _lastHdop = 1.f; + } else { + _lastHdop = gps_raw.eph / 100.f; + } + break; + } + + return _streamingFlag; +} diff --git a/src/UTMSP/UTMSPServiceController.h b/src/UTMSP/UTMSPServiceController.h new file mode 100644 index 00000000000..37110b2ec1b --- /dev/null +++ b/src/UTMSP/UTMSPServiceController.h @@ -0,0 +1,92 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "qelapsedtimer.h" +#include "qgeocoordinate.h" +#include +#include + +#include "UTMSPAuthorization.h" +#include "UTMSPFlightPlanManager.h" +#include "UTMSPNetworkRemoteIDManager.h" + +class UTMSPServiceController : public QObject, public UTMSPFlightPlanManager, public UTMSPNetworkRemoteIDManager +{ + Q_OBJECT + +public: + UTMSPServiceController(QObject *parent = nullptr); + ~UTMSPServiceController(); + + Q_PROPERTY(bool responseFlag READ responseFlag NOTIFY responseFlagChanged) + Q_PROPERTY(QString responseFlightID READ responseFlightID NOTIFY responseFlightIDChanged) + Q_PROPERTY(QString responseJson READ responseJson NOTIFY responseJsonChanged) + Q_PROPERTY(bool activationFlag READ activationFlag NOTIFY activationFlagChanged) + + std::string flightPlanAuthorization(); + bool networkRemoteID(const mavlink_message_t& message, + const std::string& serialNumber, + const std::string& operatorID, + const std::string& flightID); + + bool responseFlag () const {return _responseFlag;}; + QString responseFlightID() const {return _responseFlightID;}; + QString responseJson () const {return _responseJson;}; + bool activationFlag () const {return _activationFlag;}; + +signals: + void responseFlagChanged (void); + void responseFlightIDChanged (void); + void responseJsonChanged (void); + void activationFlagChanged (void); + void stopTelemetryFlagChanged (bool value); + +public slots: + void updatePolygonBoundary (const QList &boundary) {_boundaryPolygon = boundary;}; + void updateStartDateTime (const QString &startDateTime) {_startDateTime = startDateTime.toStdString();}; + void updateEndDateTime (const QString &endDateTime) {_endDateTime = endDateTime.toStdString();}; + void updateMinAltitude (const int &minAltitude) {_minAltitude = minAltitude;}; + void updateMaxAltitude (const int &maxAltitude) {_minAltitude = maxAltitude;}; + void updateLastCoordinates (const double &lastLatitude, const double &lastLongitude) {_lastLatitude = lastLatitude;_lastLongitude = lastLongitude;}; + +private: + UTMSPAuthorization _utmspAuth; + UTMSPFlightPlanManager::FlightState _currentState; + float _lastHdop = 1.f; + QElapsedTimer _timerLastSent; + double _vehicleLatitude; + double _vehicleLongitude; + double _vehicleAltitude; + double _vehicleHeading; + double _vehicleVelocityX; + double _vehicleVelocityY; + double _vehicleVelocityZ; + double _vehicleRelativeAltitude; + json _coordinateList; + bool _streamingFlag; + + static QList _boundaryPolygon; + static std::string _startDateTime; + static std::string _endDateTime; + static int _minAltitude; + static int _maxAltitude; + static bool _responseFlag; + static QString _responseFlightID; + static QString _responseJson; + static bool _uploadFlag; + static bool _activationFlag; + static std::string _clientID; + static std::string _clientPassword; + static std::string _blenderToken; + static double _lastLatitude; + static double _lastLongitude; + static double _lastAltitude; +}; diff --git a/src/UTMSP/UTMSPVehicle.cpp b/src/UTMSP/UTMSPVehicle.cpp new file mode 100644 index 00000000000..e155113604b --- /dev/null +++ b/src/UTMSP/UTMSPVehicle.cpp @@ -0,0 +1,72 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "Vehicle.h" + +#include + +#include "UTMSPVehicle.h" +#include "UTMSPLogger.h" + +UTMSPVehicle::UTMSPVehicle(std::shared_ptr dispatcher, const Vehicle& vehicle): + _dispatcher(dispatcher) +{ + UTMSP_LOG_INFO() << "UTMSPManagerLog: UTMSPVehicle Contructor"; + connect(&vehicle, &Vehicle::mavlinkMessageReceived, this, &UTMSPVehicle::triggerNetworkRemoteID); + UTMSP_LOG_INFO() << "UTMSPManagerLog: UTMSPVehicle MAvlink msg slot connected"; + _aircraftModel = aircraftModel(); + _aircraftClass = aircraftClass(); + _operatorID = operatorID(); + _operatorClass = operatorClass(); +} + +bool UTMSPVehicle::_remoteIDFlag = false; +bool UTMSPVehicle::_stopFlag; +std::string UTMSPVehicle::_flightID; +QString UTMSPVehicle::_vehicleSerialNumber; +bool UTMSPVehicle::_vehicleActivation; + +void UTMSPVehicle::loadTelemetryFlag(bool value){ + _remoteIDFlag = value; +} + +void UTMSPVehicle::triggerFlightAuthorization() +{ + UTMSP_LOG_INFO() << "Registration process Initiated Successfully"; + _flightID = flightPlanAuthorization(); +} + +void UTMSPVehicle::triggerNetworkRemoteID(const mavlink_message_t &message) +{ + _aircraftType = aircraftType(message); + _aircraftSerialNumber = aircraftSerialNo(message); + _i++; + + if(_i>20 && _i<22) // First 20 Mavlink message are NIL + { + _vehicleSerialNumber = QString::fromStdString(_aircraftSerialNumber); + UTMSP_LOG_DEBUG() << "Serial Number" << _vehicleSerialNumber; + UTMSP_LOG_DEBUG() << "Aircraft Type" << _aircraftType; + emit vehicleSerialNumberChanged(); + } + + if (_remoteIDFlag) { + if (_stopFlag) { + updateFlightPlanState(UTMSPFlightPlanManager::FlightState::StopTelemetryStreaming); + _remoteIDFlag = false; + } else { + _stopFlag = networkRemoteID(message, _aircraftSerialNumber, _operatorID, _flightID); + } + } +} + +void UTMSPVehicle::triggerUploadButton(bool flag){ + _vehicleActivation = flag; + emit vehicleActivationChanged(); +} diff --git a/src/UTMSP/UTMSPVehicle.h b/src/UTMSP/UTMSPVehicle.h new file mode 100644 index 00000000000..b5d922e44aa --- /dev/null +++ b/src/UTMSP/UTMSPVehicle.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include "UTMSPServiceController.h" +#include "services/dispatcher.h" +#include "UTMSPAircraft.h" +#include "UTMSPOperator.h" +#include "UTMSPFlightDetails.h" + +// UTM-Adapter per vehicle management class. +class Vehicle; +class UTMSPVehicle : public UTMSPServiceController, public UTMSPAircraft, public UTMSPOperator, UTMSPFlightDetails +{ + Q_OBJECT + Q_PROPERTY(QString vehicleSerialNumber READ vehicleSerialNumber NOTIFY vehicleSerialNumberChanged) + Q_PROPERTY(bool vehicleActivation READ vehicleActivation NOTIFY vehicleActivationChanged) + +public: + UTMSPVehicle (std::shared_ptr dispatcher,const Vehicle& vehicle); + ~UTMSPVehicle () override = default; + + Q_INVOKABLE void loadTelemetryFlag(bool value); + + QString vehicleSerialNumber(void) const {return _vehicleSerialNumber;}; + bool vehicleActivation(void) const {return _vehicleActivation;}; + +signals: + void vehicleSerialNumberChanged (void); + void vehicleActivationChanged (void); + +protected slots: + void triggerFlightAuthorization (void); + void triggerNetworkRemoteID (const mavlink_message_t &message) ; + void triggerUploadButton (bool flag); + +private: + std::string _aircraftSerialNumber; + std::string _aircraftModel; + std::string _aircraftType; + std::string _aircraftClass; + std::string _operatorID; + std::string _operatorClass; + std::shared_ptr _dispatcher; + int _i=0; + + static bool _remoteIDFlag; + static bool _stopFlag; + static std::string _flightID; + static QString _vehicleSerialNumber; + static bool _vehicleActivation; +}; diff --git a/src/UTMSP/dummy/UTMSPActivationStatusBar.qml b/src/UTMSP/dummy/UTMSPActivationStatusBar.qml new file mode 100644 index 00000000000..b08c0ca075d --- /dev/null +++ b/src/UTMSP/dummy/UTMSPActivationStatusBar.qml @@ -0,0 +1,11 @@ +import QtQuick 2.3 +import QGroundControl.UTMSP 1.0 + +Item { + property string activationStartTimestamp + property bool activationApproval + property string flightID + property string timeDifference + + signal activationTriggered(bool value) +} diff --git a/src/UTMSP/dummy/UTMSPAdapterEditor.qml b/src/UTMSP/dummy/UTMSPAdapterEditor.qml new file mode 100644 index 00000000000..ed6675bd46c --- /dev/null +++ b/src/UTMSP/dummy/UTMSPAdapterEditor.qml @@ -0,0 +1,16 @@ +import QtQuick 2.3 +import QGroundControl.UTMSP 1.0 + +Item { + // Dummy Properties + property var myGeoFenceController + property var flightMap + property var currentMissionItems + property bool triggerSubmitButton + // Dummy Signals + signal responseSent(string response) + signal vehicleIDSent(int id) + signal resetTriggered() + signal timeStampSent(string timestamp, bool activateflag, string id) + signal approvalSent(bool approval) +} diff --git a/src/UTMSP/dummy/UTMSPMapPolygonVisuals.qml b/src/UTMSP/dummy/UTMSPMapPolygonVisuals.qml new file mode 100644 index 00000000000..5cf0e98bbd1 --- /dev/null +++ b/src/UTMSP/dummy/UTMSPMapPolygonVisuals.qml @@ -0,0 +1,5 @@ +import QtQuick 2.3 + +Item { + +} diff --git a/src/UTMSP/dummy/UTMSPMapVisuals.qml b/src/UTMSP/dummy/UTMSPMapVisuals.qml new file mode 100644 index 00000000000..6e92e7455a1 --- /dev/null +++ b/src/UTMSP/dummy/UTMSPMapVisuals.qml @@ -0,0 +1,22 @@ +import QtQuick 2.3 + +Item { + // Dummy Properties + property var map + property var myGeoFenceController + property var currentMissionItems + property bool interactive: false + property bool planView: false + property var homePosition + property bool resetCheck: false + property var _dummy: myGeoFenceController.polygons + + Instantiator { + model: _dummy + + delegate : UTMSPMapPolygonVisuals { + + } + } + +} diff --git a/src/UTMSP/dummy/qmldir b/src/UTMSP/dummy/qmldir new file mode 100644 index 00000000000..aff750760de --- /dev/null +++ b/src/UTMSP/dummy/qmldir @@ -0,0 +1,7 @@ +Module QGroundControl.UTMSP + +UTMSPAdapterEditor 1.0 UTMSPAdapterEditor.qml +UTMSPMapVisuals 1.0 UTMSPMapVisuals.qml +UTMSPActivationStatusBar 1.0 UTMSPActivationStatusBar.qml +UTMSPMapPolygonVisuals 1.0 UTMSPMapPolygonVisuals.qml + diff --git a/src/UTMSP/dummy/utmsp_dummy.qrc b/src/UTMSP/dummy/utmsp_dummy.qrc new file mode 100644 index 00000000000..cf2f0c0c076 --- /dev/null +++ b/src/UTMSP/dummy/utmsp_dummy.qrc @@ -0,0 +1,9 @@ + + + UTMSPMapVisuals.qml + UTMSPAdapterEditor.qml + qmldir + UTMSPActivationStatusBar.qml + UTMSPMapPolygonVisuals.qml + + diff --git a/src/UTMSP/images/date.svg b/src/UTMSP/images/date.svg new file mode 100644 index 00000000000..cbc5fcf9ff1 --- /dev/null +++ b/src/UTMSP/images/date.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/UTMSP/images/green.png b/src/UTMSP/images/green.png new file mode 100644 index 00000000000..d3d47728de5 Binary files /dev/null and b/src/UTMSP/images/green.png differ diff --git a/src/UTMSP/images/load.gif b/src/UTMSP/images/load.gif new file mode 100644 index 00000000000..4301102d388 Binary files /dev/null and b/src/UTMSP/images/load.gif differ diff --git a/src/UTMSP/images/red.png b/src/UTMSP/images/red.png new file mode 100644 index 00000000000..ac44099bd2f Binary files /dev/null and b/src/UTMSP/images/red.png differ diff --git a/src/UTMSP/images/time.svg b/src/UTMSP/images/time.svg new file mode 100644 index 00000000000..bb3b9197274 --- /dev/null +++ b/src/UTMSP/images/time.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/UTMSP/images/timer.gif b/src/UTMSP/images/timer.gif new file mode 100644 index 00000000000..594172d6981 Binary files /dev/null and b/src/UTMSP/images/timer.gif differ diff --git a/src/UTMSP/qmldir b/src/UTMSP/qmldir new file mode 100644 index 00000000000..aff750760de --- /dev/null +++ b/src/UTMSP/qmldir @@ -0,0 +1,7 @@ +Module QGroundControl.UTMSP + +UTMSPAdapterEditor 1.0 UTMSPAdapterEditor.qml +UTMSPMapVisuals 1.0 UTMSPMapVisuals.qml +UTMSPActivationStatusBar 1.0 UTMSPActivationStatusBar.qml +UTMSPMapPolygonVisuals 1.0 UTMSPMapPolygonVisuals.qml + diff --git a/src/UTMSP/services/dispatcher.h b/src/UTMSP/services/dispatcher.h new file mode 100644 index 00000000000..4abe7fbcee6 --- /dev/null +++ b/src/UTMSP/services/dispatcher.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * + * (c) 2023 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include +#include + +class Dispatcher +{ +public: + Dispatcher() : _running(true) + { + _workerThread = std::thread([this]() { + while (_running) { + std::function task; + { + std::unique_lock lock(_queueMutex); + _conditionVariable.wait(lock, [this] { return !_tasks.empty() || !_running; }); + if (!_running && _tasks.empty()) { + return; + } + task = std::move(_tasks.front()); + _tasks.pop_front(); + } + task(); + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + }); + } + + ~Dispatcher() + { + std::cout<<" DESTRUCTOR is the problem "< lock(_queueMutex); + _running = false; + } + _conditionVariable.notify_all(); + _workerThread.join(); + } + + void add_task(std::function task) + { + { + std::unique_lock lock(_queueMutex); + _tasks.push_back(std::move(task)); + } + _conditionVariable.notify_one(); + } + +private: + std::deque> _tasks; + std::mutex _queueMutex; + std::condition_variable _conditionVariable; + std::thread _workerThread; + bool _running; +}; diff --git a/src/UTMSP/utmsp.qrc b/src/UTMSP/utmsp.qrc new file mode 100644 index 00000000000..3662af2b830 --- /dev/null +++ b/src/UTMSP/utmsp.qrc @@ -0,0 +1,17 @@ + + + UTMSPAdapterEditor.qml + UTMSPMapVisuals.qml + qmldir + UTMSPMapPolygonVisuals.qml + UTMSPActivationStatusBar.qml + + + images/red.png + images/green.png + images/time.svg + images/date.svg + images/load.gif + images/timer.gif + + diff --git a/src/Vehicle/Vehicle.cc b/src/Vehicle/Vehicle.cc index 6c13da4e167..ce2de18e828 100644 --- a/src/Vehicle/Vehicle.cc +++ b/src/Vehicle/Vehicle.cc @@ -214,6 +214,13 @@ Vehicle::Vehicle(LinkInterface* link, _settingsManager->videoSettings()->lowLatencyMode()->setRawValue(true); } +#ifdef CONFIG_UTM_ADAPTER + UTMSPManager* utmspManager = _toolbox->utmspManager(); + if (utmspManager) { + _utmspVehicle = utmspManager->instantiateVehicle(*this); + } +#endif + _autopilotPlugin = _firmwarePlugin->autopilotPlugin(this); _autopilotPlugin->setParent(this); @@ -511,6 +518,10 @@ Vehicle::~Vehicle() delete _mav; _mav = nullptr; + +#ifdef CONFIG_UTM_ADAPTER + delete _utmspVehicle; +#endif } void Vehicle::prepareDelete() diff --git a/src/Vehicle/Vehicle.h b/src/Vehicle/Vehicle.h index 436b969078b..b7a8e1abdb4 100644 --- a/src/Vehicle/Vehicle.h +++ b/src/Vehicle/Vehicle.h @@ -52,6 +52,11 @@ #include "VehicleGeneratorFactGroup.h" #include "VehicleEFIFactGroup.h" +#ifdef CONFIG_UTM_ADAPTER +#include "UTMSPVehicle.h" +#include "UTMSPManager.h" +#endif + class Actuators; class EventHandler; class UAS; @@ -78,6 +83,9 @@ class LinkManager; class InitialConnectStateMachine; class Autotune; class RemoteIDManager; +#ifdef CONFIG_UTM_ADAPTER +class UTMSPVehicle; +#endif #ifndef OPAQUE_PTR_Vehicle #define OPAQUE_PTR_Vehicle @@ -545,20 +553,20 @@ class Vehicle : public FactGroup /** * @brief Send MAV_CMD_DO_GRIPPER command to trigger specified action in the vehicle - * + * * @param gripperAction Gripper action to trigger */ enum GRIPPER_OPTIONS { - Gripper_release = GRIPPER_ACTION_RELEASE, + Gripper_release = GRIPPER_ACTION_RELEASE, Gripper_grab = GRIPPER_ACTION_GRAB, Invalid_option = GRIPPER_ACTIONS_ENUM_END, - }; + }; Q_ENUM(GRIPPER_OPTIONS) void setGripperAction(GRIPPER_ACTIONS gripperAction); - Q_INVOKABLE void sendGripperAction(GRIPPER_OPTIONS gripperOption); + Q_INVOKABLE void sendGripperAction(GRIPPER_OPTIONS gripperOption); bool fixedWing() const; bool multiRotor() const; @@ -1221,6 +1229,10 @@ private slots: VehicleObjectAvoidance* _objectAvoidance = nullptr; Autotune* _autotune = nullptr; +#ifdef CONFIG_UTM_ADAPTER + UTMSPVehicle* _utmspVehicle = nullptr; +#endif + bool _armed = false; ///< true: vehicle is armed uint8_t _base_mode = 0; ///< base_mode from HEARTBEAT uint32_t _custom_mode = 0; ///< custom_mode from HEARTBEAT diff --git a/src/ui/MainRootWindow.qml b/src/ui/MainRootWindow.qml index ce4ce779623..2d2cb4c11a9 100644 --- a/src/ui/MainRootWindow.qml +++ b/src/ui/MainRootWindow.qml @@ -20,6 +20,8 @@ import QGroundControl.ScreenTools import QGroundControl.FlightDisplay import QGroundControl.FlightMap +import QGroundControl.UTMSP 1.0 + /// @brief Native QML top level window /// All properties defined here are visible to all QML pages. ApplicationWindow { @@ -28,6 +30,12 @@ ApplicationWindow { minimumHeight: ScreenTools.isMobile ? Screen.height : Math.min(ScreenTools.defaultFontPixelWidth * 50, Screen.height) visible: true + property string _startTimeStamp + property bool _showVisible + property string _flightID + property bool _utmspSendActTrigger + property bool _utmspStartTelemetry + Component.onCompleted: { //-- Full screen on mobile or tiny screens if (ScreenTools.isMobile || Screen.height / ScreenTools.realPixelDensity < 120) { @@ -448,6 +456,7 @@ ApplicationWindow { FlyView { id: flightView anchors.fill: parent + utmspSendActTrigger: _utmspSendActTrigger } PlanView { @@ -736,4 +745,27 @@ ApplicationWindow { } } } + + Connections{ + target: planView + function onActivationParamsSent(timestamp,activate,flightID){ + _startTimeStamp = timestamp + _showVisible = activate + _flightID = flightID + } + } + Connections{ + target: activationbar + function onActivationTriggered(value){ + _utmspSendActTrigger= value + } + } + + UTMSPActivationStatusBar{ + id: activationbar + activationStartTimestamp: _startTimeStamp + activationApproval: _showVisible && planView.visible == false && QGroundControl.utmspManager.utmspVehicle.vehicleActivation + flightID: _flightID + anchors.fill: parent + } }