From 742176d7eb4197b9c3987a791a60724bbd211823 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Wed, 6 Mar 2024 21:23:55 +0100 Subject: [PATCH 01/12] Decode LTM telemetry protocol --- gulpfile.js | 3 +- js/ltmDecoder.js | 246 +++++++++++++++++++++++++++++++++++++++++++ js/serial_backend.js | 1 + 3 files changed, 249 insertions(+), 1 deletion(-) create mode 100644 js/ltmDecoder.js diff --git a/gulpfile.js b/gulpfile.js index a7db937c5..f30da72da 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -139,7 +139,8 @@ sources.js = [ './js/libraries/plotly-latest.min.js', './js/sitl.js', './js/CliAutoComplete.js', - './node_modules/jquery-textcomplete/dist/jquery.textcomplete.js' + './node_modules/jquery-textcomplete/dist/jquery.textcomplete.js', + './js/ltmDecoder.js', ]; sources.receiverCss = [ diff --git a/js/ltmDecoder.js b/js/ltmDecoder.js new file mode 100644 index 000000000..e204889e6 --- /dev/null +++ b/js/ltmDecoder.js @@ -0,0 +1,246 @@ +'use strict'; + +var helper = helper || {}; + +helper.ltmDecoder = (function () { + + let TELEMETRY = { + //A frame + pitch: null, + roll: null, + heading: null, + + //S frame + voltage: null, + currectDrawn: null, + rssi: null, + airspeed: null, + flightmode: null, + flightmodeName: null, + + armed: null, + failsafe: null, + + //G frame + latitude: null, + longitude: null, + altitude: null, + groundSpeed: null, + gpsFix: null, + gpsSats: null, + + + //X frame + hdop: null, + sensorStatus: null, + frameCounter: null, + disarmReason: null, + disarmReasonName: null + + }; + + let publicScope = {}, + privateScope = {}; + + const LTM_TIMEOUT_MS = 5000; + const LTM_FRAME_TIMEOUT_MS = 700; + const LTM_HEADER_START_1 = '$'; + const LTM_HEADER_START_2 = 'T'; + const LTM_FRAMELENGTH = { + 'G': 18, + 'A': 10, + 'S': 11, + 'O': 18, + 'N': 10, + 'X': 10 + }; + + const LTM_FLIGHT_MODE_NAMES = [ + "MANUAL", + "RATE", + "ANGLE", + "HORIZON", + "ACRO", + "STABALIZED1", + "STABALIZED2", + "STABILIZED3", + "ALTHOLD", + "GPSHOLD", + "WAYPOINTS", + "HEADHOLD", + "CIRCLE", + "RTH", + "FOLLOWME", + "LAND", + "FLYBYWIRE1", + "FLYBYWIRE2", + "CRUISE", + "UNKNOWN", + "LAUNCH", + "AUTOTUNE" + ]; + + const LTM_DISARM_REASON_NAMES = [ + "NONE", + "TIMEOUT", + "STICKS", + "SWITCH_3D", + "SWITCH", + "KILLSWITCH", + "FAILSAFE", + "NAVIGATION", + "LANDING" + ]; + + const LTM_STATE_IDLE = 0; + const LTM_STATE_HEADER_START_1 = 1; + const LTM_STATE_HEADER_START_2 = 2; + const LTM_STATE_MSGTYPE = 3; + + privateScope.protocolState = LTM_STATE_IDLE; + privateScope.lastFrameReceivedMs = null; + privateScope.frameType = null; + privateScope.frameLength = null; + privateScope.receiverIndex = 0; + privateScope.serialBuffer = []; + privateScope.frameProcessingStartedAtMs = 0; + + privateScope.readByte = function (offset) { + return privateScope.serialBuffer[offset]; + }; + + privateScope.readInt = function (offset) { + return privateScope.serialBuffer[offset] + (privateScope.serialBuffer[offset + 1] << 8); + } + + privateScope.readInt32 = function (offset) { + return privateScope.serialBuffer[offset] + (privateScope.serialBuffer[offset + 1] << 8) + (privateScope.serialBuffer[offset + 2] << 16) + (privateScope.serialBuffer[offset + 3] << 24); + } + + privateScope.push = function (data) { + let charCode = String.fromCharCode(data); + + //If frame is processed for too long, reset protocol state + if (privateScope.protocolState != LTM_STATE_IDLE && new Date().getTime() - privateScope.frameProcessingStartedAtMs > LTM_FRAME_TIMEOUT_MS) { + privateScope.protocolState = LTM_STATE_IDLE; + privateScope.frameProcessingStartedAtMs = new Date().getTime(); + console.log('LTM privateScope.protocolState: TIMEOUT, forcing into IDLE, processed frame: ' + privateScope.frameType); + } + + if (privateScope.protocolState == LTM_STATE_IDLE) { + if (charCode == LTM_HEADER_START_1) { + privateScope.protocolState = LTM_STATE_HEADER_START_1; + privateScope.frameProcessingStartedAtMs = new Date().getTime(); + } + return; + } else if (privateScope.protocolState == LTM_STATE_HEADER_START_1) { + if (charCode == LTM_HEADER_START_2) { + privateScope.protocolState = LTM_STATE_HEADER_START_2; + } else { + privateScope.protocolState = LTM_STATE_IDLE; + } + return; + } else if (privateScope.protocolState == LTM_STATE_HEADER_START_2) { + + //Check if incoming frame type is a known one + if (LTM_FRAMELENGTH[charCode] == undefined) { + //Unknown frame type, reset protocol state + privateScope.protocolState = LTM_STATE_IDLE; + console.log('Unknown frame type, reset protocol state'); + } else { + //Known frame type, store it and move to next state + privateScope.frameType = charCode; + privateScope.frameLength = LTM_FRAMELENGTH[charCode]; + privateScope.receiverIndex = 0; + privateScope.serialBuffer = []; + privateScope.protocolState = LTM_STATE_MSGTYPE; + console.log('privateScope.protocolState: LTM_STATE_MSGTYPE', 'will expext frame ' + privateScope.frameType, 'expected length: ' + privateScope.frameLength); + } + return; + + } else if (privateScope.protocolState == LTM_STATE_MSGTYPE) { + + /* + * Check if last payload byte has been received. + */ + if (privateScope.receiverIndex == privateScope.frameLength - 4) { + /* + * If YES, check checksum and execute data processing + */ + if (privateScope.frameType == 'A') { + TELEMETRY.pitch = privateScope.readInt(0); + TELEMETRY.roll = privateScope.readInt(2); + TELEMETRY.heading = privateScope.readInt(4); + } + + if (privateScope.frameType == 'S') { + TELEMETRY.voltage = privateScope.readInt(0); + TELEMETRY.currectDrawn = privateScope.readInt(2); + TELEMETRY.rssi = privateScope.readByte(4); + + TELEMETRY.airspeed = privateScope.readByte(5); + + let fm = privateScope.readByte(6); + TELEMETRY.flightmode = fm >> 2; + TELEMETRY.flightmodeName = LTM_FLIGHT_MODE_NAMES[TELEMETRY.flightmode]; + + TELEMETRY.armed = (fm & 0x02) >> 1; + TELEMETRY.failsafe = fm & 0x01; + } + + if (privateScope.frameType == 'G') { + TELEMETRY.latitude = privateScope.readInt32(0); + TELEMETRY.longitude = privateScope.readInt32(4); + TELEMETRY.groundSpeed = privateScope.readByte(8); + TELEMETRY.altitude = privateScope.readInt32(9); + + let raw = privateScope.readByte(13); + TELEMETRY.gpsSats = raw >> 2; + TELEMETRY.gpsFix = raw & 0x03; + } + + if (privateScope.frameType == 'X') { + TELEMETRY.hdop = privateScope.readInt(0); + TELEMETRY.sensorStatus = privateScope.readByte(2); + TELEMETRY.frameCounter = privateScope.readByte(3); + TELEMETRY.disarmReason = privateScope.readByte(4); + TELEMETRY.disarmReasonName = LTM_DISARM_REASON_NAMES[TELEMETRY.disarmReason]; + } + + privateScope.protocolState = LTM_STATE_IDLE; + privateScope.serialBuffer = []; + privateScope.lastFrameReceivedMs = new Date().getTime(); + privateScope.receiverIndex = 0; + + } else { + /* + * If no, put data into buffer + */ + privateScope.serialBuffer.push(data); + privateScope.receiverIndex++; + } + } + } + + publicScope.read = function (readInfo) { + var data = new Uint8Array(readInfo.data); + + for (var i = 0; i < data.length; i++) { + privateScope.push(data[i]); + } + }; + + publicScope.isReceiving = function () { + return privateScope.lastFrameReceivedMs !== null && (new Date().getTime() - privateScope.lastFrameReceivedMs) < LTM_TIMEOUT_MS; + }; + + publicScope.wasEverReceiving = function () { + return privateScope.lastFrameReceivedMs !== null; + }; + + publicScope.get = function () { + return TELEMETRY; + }; + + return publicScope; +})(); \ No newline at end of file diff --git a/js/serial_backend.js b/js/serial_backend.js index 827169f63..e81daf7f7 100755 --- a/js/serial_backend.js +++ b/js/serial_backend.js @@ -322,6 +322,7 @@ function onOpen(openInfo) { chrome.storage.local.set({wireless_mode_enabled: $('#wireless-mode').is(":checked")}); CONFIGURATOR.connection.addOnReceiveListener(read_serial); + CONFIGURATOR.connection.addOnReceiveListener(helper.ltmDecoder.read); // disconnect after 10 seconds with error if we don't get IDENT data helper.timeout.add('connecting', function () { From 16b097cf8e236e43e33eb396adcddeea210b0242 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Wed, 6 Mar 2024 21:36:12 +0100 Subject: [PATCH 02/12] Compute and verify the LTM checksum --- js/ltmDecoder.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/js/ltmDecoder.js b/js/ltmDecoder.js index e204889e6..ef72c69a0 100644 --- a/js/ltmDecoder.js +++ b/js/ltmDecoder.js @@ -154,7 +154,7 @@ helper.ltmDecoder = (function () { privateScope.receiverIndex = 0; privateScope.serialBuffer = []; privateScope.protocolState = LTM_STATE_MSGTYPE; - console.log('privateScope.protocolState: LTM_STATE_MSGTYPE', 'will expext frame ' + privateScope.frameType, 'expected length: ' + privateScope.frameLength); + console.log('protocolState: LTM_STATE_MSGTYPE', 'will expext frame ' + privateScope.frameType, 'expected length: ' + privateScope.frameLength); } return; @@ -167,6 +167,20 @@ helper.ltmDecoder = (function () { /* * If YES, check checksum and execute data processing */ + + let checksum = 0; + for (let i = 0; i < privateScope.serialBuffer.length; i++) { + checksum ^= privateScope.serialBuffer[i]; + } + + if (checksum != data) { + console.log('LTM checksum error, frame type: ' + privateScope.frameType + ' rejected'); + privateScope.protocolState = LTM_STATE_IDLE; + privateScope.serialBuffer = []; + privateScope.receiverIndex = 0; + return; + } + if (privateScope.frameType == 'A') { TELEMETRY.pitch = privateScope.readInt(0); TELEMETRY.roll = privateScope.readInt(2); From 5e4e9294f837187f6c35b9205761d225a825ff5e Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 7 Mar 2024 12:07:22 +0100 Subject: [PATCH 03/12] Add option to check if MSP is receiving --- js/msp.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/msp.js b/js/msp.js index 38ef7ab01..a2046c493 100644 --- a/js/msp.js +++ b/js/msp.js @@ -78,6 +78,8 @@ var MSP = { last_received_timestamp: null, analog_last_received_timestamp: null, + lastFrameReceivedMs: 0, + read: function (readInfo) { var data = new Uint8Array(readInfo.data); @@ -236,6 +238,7 @@ var MSP = { if (this.message_checksum == expected_checksum) { // message received, process mspHelper.processData(this); + this.lastFrameReceivedMs = Date.now(); } else { console.log('code: ' + this.code + ' - crc failed'); this.packet_error++; @@ -378,6 +381,12 @@ var MSP = { this.packet_error = 0; // reset CRC packet error counter for next session this.callbacks_cleanup(); + }, + isReceiving: function () { + return Date.now() - this.lastFrameReceivedMs < 5000; + }, + wasEverReceiving: function () { + return this.lastFrameReceivedMs > 0; } }; From c7b924e67be2494420fac25d8b2723468e79cf89 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 7 Mar 2024 20:51:15 +0100 Subject: [PATCH 04/12] When LTM stream is detected, the app will automatically switch to groundstation mode. --- _locales/en/messages.json | 6 +++++ gulpfile.js | 2 ++ js/groundstation.js | 54 +++++++++++++++++++++++++++++++++++++++ js/serial_backend.js | 11 +++++++- main.html | 1 + src/css/groundstation.css | 5 ++++ 6 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 js/groundstation.js create mode 100644 src/css/groundstation.css diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ed334aff0..7d1a13a64 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -5603,5 +5603,11 @@ }, "ezTuneNote": { "message": "Important Ez Tune is enabled. All settings on this tab are set and controlled by the Ez Tune. To use PID Tuning tab you have to disable Ez Tune. To do it, uncheck the Enabled checkbox on the Ez Tune tab." + }, + "gsActivated": { + "message": "Ground station mode activated" + }, + "gsDeactivated": { + "message": "Ground station mode deactivated" } } \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index f30da72da..b482299f5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -55,6 +55,7 @@ sources.css = [ './node_modules/openlayers/dist/ol.css', './src/css/logic.css', './src/css/defaults_dialog.css', + './src/css/groundstation.css', ]; sources.js = [ @@ -141,6 +142,7 @@ sources.js = [ './js/CliAutoComplete.js', './node_modules/jquery-textcomplete/dist/jquery.textcomplete.js', './js/ltmDecoder.js', + './js/groundstation.js' ]; sources.receiverCss = [ diff --git a/js/groundstation.js b/js/groundstation.js new file mode 100644 index 000000000..d4d7e7477 --- /dev/null +++ b/js/groundstation.js @@ -0,0 +1,54 @@ +'use strict'; + +var helper = helper || {}; + +helper.groundstation = (function () { + + let publicScope = {}, + privateScope = {}; + + privateScope.activated = false; + privateScope.$gsViewport = null; + + publicScope.isActivated = function () { + return privateScope.activated; + }; + + publicScope.activate = function ($viewport) { + + if (privateScope.activated) { + return; + } + + helper.interval.add('gsUpdateGui', privateScope.updateGui, 200); + + $viewport.find(".tab_container").hide(); + $viewport.find('#content').hide(); + $viewport.find('#status-bar').hide(); + + privateScope.$gsViewport = $viewport.find('#view-groundstation'); + + privateScope.$gsViewport.show(); + + privateScope.activated = true; + GUI.log(chrome.i18n.getMessage('gsActivated')); + } + + publicScope.deactivate = function () { + + if (!privateScope.activated) { + return; + } + + helper.interval.remove('gsUpdateGui'); + + privateScope.activated = false; + GUI.log(chrome.i18n.getMessage('gsDeactivated')); + } + + privateScope.updateGui = function () { + console.log('updateGui'); + }; + + return publicScope; +})(); \ No newline at end of file diff --git a/js/serial_backend.js b/js/serial_backend.js index e81daf7f7..f4aa2b4a4 100755 --- a/js/serial_backend.js +++ b/js/serial_backend.js @@ -326,7 +326,9 @@ function onOpen(openInfo) { // disconnect after 10 seconds with error if we don't get IDENT data helper.timeout.add('connecting', function () { - if (!CONFIGURATOR.connectionValid) { + + //As we add LTM listener, we need to invalidate connection only when both protocols are not listening! + if (!CONFIGURATOR.connectionValid && !helper.ltmDecoder.isReceiving()) { GUI.log(chrome.i18n.getMessage('noConfigurationReceived')); helper.mspQueue.flush(); @@ -338,6 +340,13 @@ function onOpen(openInfo) { } }, 10000); + //Add a timer that every 1s will check if LTM stream is receiving data and display alert if so + helper.interval.add('ltm-connection-check', function () { + if (helper.ltmDecoder.isReceiving()) { + helper.groundstation.activate($('#main-wrapper')); + } + }, 1000); + FC.resetState(); // request configuration data. Start with MSPv1 and diff --git a/main.html b/main.html index 92c89a58d..c55b28616 100755 --- a/main.html +++ b/main.html @@ -174,6 +174,7 @@

+
    diff --git a/src/css/groundstation.css b/src/css/groundstation.css new file mode 100644 index 000000000..75c3f633f --- /dev/null +++ b/src/css/groundstation.css @@ -0,0 +1,5 @@ +#view-groundstation { + background-color: #2e2e2e; + width: 100%; + height: 100%; +} \ No newline at end of file From 1f718efe07e1d392e3d31faef159ef456f0d4fa9 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Thu, 7 Mar 2024 21:03:45 +0100 Subject: [PATCH 05/12] Activate / deactivate groundstation mode --- js/groundstation.js | 21 +++++++++++++++++---- js/serial_backend.js | 5 +++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/js/groundstation.js b/js/groundstation.js index d4d7e7477..33c29e619 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -8,6 +8,7 @@ helper.groundstation = (function () { privateScope = {}; privateScope.activated = false; + privateScope.$viewport = null; privateScope.$gsViewport = null; publicScope.isActivated = function () { @@ -22,12 +23,14 @@ helper.groundstation = (function () { helper.interval.add('gsUpdateGui', privateScope.updateGui, 200); - $viewport.find(".tab_container").hide(); - $viewport.find('#content').hide(); - $viewport.find('#status-bar').hide(); + privateScope.$viewport = $viewport; - privateScope.$gsViewport = $viewport.find('#view-groundstation'); + privateScope.$viewport.find(".tab_container").hide(); + privateScope.$viewport.find('#content').hide(); + privateScope.$viewport.find('#status-bar').hide(); + privateScope.$viewport.find('#connectbutton a.connect_state').text(chrome.i18n.getMessage('disconnect')).addClass('active'); + privateScope.$gsViewport = $viewport.find('#view-groundstation'); privateScope.$gsViewport.show(); privateScope.activated = true; @@ -42,6 +45,16 @@ helper.groundstation = (function () { helper.interval.remove('gsUpdateGui'); + if (privateScope.$viewport !== null) { + privateScope.$viewport.find(".tab_container").show(); + privateScope.$viewport.find('#content').show(); + privateScope.$viewport.find('#status-bar').show(); + } + + if (privateScope.$gsViewport !== null) { + privateScope.$gsViewport.hide(); + } + privateScope.activated = false; GUI.log(chrome.i18n.getMessage('gsDeactivated')); } diff --git a/js/serial_backend.js b/js/serial_backend.js index f4aa2b4a4..6a85819a1 100755 --- a/js/serial_backend.js +++ b/js/serial_backend.js @@ -129,6 +129,11 @@ $(document).ready(function () { }); $('div.connect_controls a.connect').click(function () { + + if (helper.groundstation.isActivated()) { + helper.groundstation.deactivate(); + } + if (GUI.connect_lock != true) { // GUI control overrides the user control var clicks = $(this).data('clicks'); From 6bfbe29756b3322e0ff4c8b6c83d1e4cd7f3f7de Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Fri, 15 Mar 2024 08:53:45 +0100 Subject: [PATCH 06/12] Initiate the map layer in groundstation mode --- js/groundstation.js | 41 +++++++++++++++++++++++++++++++++++++++++ main.html | 4 +++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/js/groundstation.js b/js/groundstation.js index 33c29e619..f4e922dbb 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -10,6 +10,9 @@ helper.groundstation = (function () { privateScope.activated = false; privateScope.$viewport = null; privateScope.$gsViewport = null; + privateScope.mapHandler = null; + privateScope.mapLayer = null; + privateScope.mapView = null; publicScope.isActivated = function () { return privateScope.activated; @@ -33,10 +36,48 @@ helper.groundstation = (function () { privateScope.$gsViewport = $viewport.find('#view-groundstation'); privateScope.$gsViewport.show(); + setTimeout(privateScope.initMap, 200); + privateScope.activated = true; GUI.log(chrome.i18n.getMessage('gsActivated')); } + privateScope.initMap = function () { + + //initialte layers + if (globalSettings.mapProviderType == 'bing') { + privateScope.mapLayer = new ol.source.BingMaps({ + key: globalSettings.mapApiKey, + imagerySet: 'AerialWithLabels', + maxZoom: 19 + }); + } else if (globalSettings.mapProviderType == 'mapproxy') { + privateScope.mapLayer = new ol.source.TileWMS({ + url: globalSettings.proxyURL, + params: { 'LAYERS': globalSettings.proxyLayer } + }) + } else { + privateScope.mapLayer = new ol.source.OSM(); + } + + //initiate view + privateScope.mapView = new ol.View({ + center: ol.proj.fromLonLat([0, 0]), + zoom: 3 + }); + + //initiate map handler + privateScope.mapHandler = new ol.Map({ + target: document.getElementById('groundstation-map'), + layers: [ + new ol.layer.Tile({ + source: privateScope.mapLayer + }) + ], + view: privateScope.mapView + }); + }; + publicScope.deactivate = function () { if (!privateScope.activated) { diff --git a/main.html b/main.html index c55b28616..e0e9f2f8b 100755 --- a/main.html +++ b/main.html @@ -174,7 +174,9 @@

- +
    From 95dbc87a5b440c324aa04a7c02d45b3646fd574b Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Fri, 15 Mar 2024 09:16:21 +0100 Subject: [PATCH 07/12] Center map to current position and set initial zoom --- js/groundstation.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/js/groundstation.js b/js/groundstation.js index f4e922dbb..27ea43cf8 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -14,6 +14,8 @@ helper.groundstation = (function () { privateScope.mapLayer = null; privateScope.mapView = null; + privateScope.mapInitiated = false; + publicScope.isActivated = function () { return privateScope.activated; }; @@ -35,6 +37,7 @@ helper.groundstation = (function () { privateScope.$gsViewport = $viewport.find('#view-groundstation'); privateScope.$gsViewport.show(); + privateScope.mapInitiated = false; setTimeout(privateScope.initMap, 200); @@ -101,7 +104,21 @@ helper.groundstation = (function () { } privateScope.updateGui = function () { - console.log('updateGui'); + + let telemetry = helper.ltmDecoder.get(); + + if (telemetry.gpsFix) { + let position = ol.proj.fromLonLat([telemetry.longitude / 10000000, telemetry.latitude / 10000000]); + privateScope.mapView.setCenter(position); + + //On first initiation, set zoom to 15 + if (!privateScope.mapInitiated) { + privateScope.mapView.setZoom(17); + privateScope.mapInitiated = true; + } + + } + }; return publicScope; From c27d5e03bd9b57f8378f10e9910ba04709fb74e1 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Fri, 15 Mar 2024 16:24:09 +0100 Subject: [PATCH 08/12] Place cursor on UAV position --- js/groundstation.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/js/groundstation.js b/js/groundstation.js index 27ea43cf8..f2a263015 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -13,6 +13,12 @@ helper.groundstation = (function () { privateScope.mapHandler = null; privateScope.mapLayer = null; privateScope.mapView = null; + + privateScope.cursorStyle = null; + privateScope.cursorPosition = null; + privateScope.cursorFeature = null; + privateScope.cursorVector = null; + privateScope.cursorLayer = null; privateScope.mapInitiated = false; @@ -108,15 +114,50 @@ helper.groundstation = (function () { let telemetry = helper.ltmDecoder.get(); if (telemetry.gpsFix) { - let position = ol.proj.fromLonLat([telemetry.longitude / 10000000, telemetry.latitude / 10000000]); - privateScope.mapView.setCenter(position); + + let lat = telemetry.latitude / 10000000; + let lon = telemetry.longitude / 10000000; //On first initiation, set zoom to 15 if (!privateScope.mapInitiated) { + + //Place UAV on the map + privateScope.cursorStyle = new ol.style.Style({ + image: new ol.style.Icon(({ + anchor: [0.5, 0.5], + opacity: 1, + scale: 0.6, + src: '../images/icons/icon_mission_airplane.png' + })) + }); + privateScope.cursorPosition = new ol.geom.Point(ol.proj.fromLonLat([lon, lat])); + + privateScope.cursorFeature = new ol.Feature({ + geometry: privateScope.cursorPosition + }); + + privateScope.cursorFeature.setStyle(privateScope.cursorStyle); + + privateScope.cursorVector = new ol.source.Vector({ + features: [privateScope.cursorFeature] + }); + privateScope.cursorLayer = new ol.layer.Vector({ + source: privateScope.cursorVector + }); + + privateScope.mapHandler.addLayer(privateScope.cursorLayer); + privateScope.mapView.setZoom(17); privateScope.mapInitiated = true; } + //Update map center + let position = ol.proj.fromLonLat([lon, lat]); + privateScope.mapView.setCenter(position); + + //Update position of cursor + privateScope.cursorPosition.setCoordinates(position); + } }; From aeac6842527cce69f4ceae11964796e2af94ba85 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Fri, 15 Mar 2024 16:29:43 +0100 Subject: [PATCH 09/12] Update orientation of UAV cursor on map --- js/groundstation.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/groundstation.js b/js/groundstation.js index f2a263015..dce1710e6 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -157,6 +157,8 @@ helper.groundstation = (function () { //Update position of cursor privateScope.cursorPosition.setCoordinates(position); + //Update orientation of cursor + privateScope.cursorStyle.getImage().setRotation((telemetry.heading / 360.0) * 6.28318); } From b31c0ab376ecde502d4dfb6655f724100eaf5799 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Sat, 23 Mar 2024 13:48:10 +0100 Subject: [PATCH 10/12] Make map full screen --- src/css/groundstation.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/css/groundstation.css b/src/css/groundstation.css index 75c3f633f..b0f22f52d 100644 --- a/src/css/groundstation.css +++ b/src/css/groundstation.css @@ -2,4 +2,9 @@ background-color: #2e2e2e; width: 100%; height: 100%; +} + +#groundstation-map { + width: 100%; + height: 100%; } \ No newline at end of file From 6db05efefbb12da719610ba89ca95bbc1ddf56c6 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Sat, 23 Mar 2024 14:49:01 +0100 Subject: [PATCH 11/12] Minor updates --- js/groundstation.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/js/groundstation.js b/js/groundstation.js index dce1710e6..7cd99b3e7 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -13,13 +13,18 @@ helper.groundstation = (function () { privateScope.mapHandler = null; privateScope.mapLayer = null; privateScope.mapView = null; - + privateScope.cursorStyle = null; privateScope.cursorPosition = null; privateScope.cursorFeature = null; privateScope.cursorVector = null; privateScope.cursorLayer = null; + privateScope.textGeometry = null; + privateScope.textFeature = null; + privateScope.textVector = null; + privateScope.textSource = null; + privateScope.mapInitiated = false; publicScope.isActivated = function () { @@ -45,7 +50,7 @@ helper.groundstation = (function () { privateScope.$gsViewport.show(); privateScope.mapInitiated = false; - setTimeout(privateScope.initMap, 200); + setTimeout(privateScope.initMap, 100); privateScope.activated = true; GUI.log(chrome.i18n.getMessage('gsActivated')); @@ -68,7 +73,7 @@ helper.groundstation = (function () { } else { privateScope.mapLayer = new ol.source.OSM(); } - + //initiate view privateScope.mapView = new ol.View({ center: ol.proj.fromLonLat([0, 0]), @@ -148,6 +153,7 @@ helper.groundstation = (function () { privateScope.mapHandler.addLayer(privateScope.cursorLayer); privateScope.mapView.setZoom(17); + privateScope.mapInitiated = true; } @@ -159,7 +165,6 @@ helper.groundstation = (function () { privateScope.cursorPosition.setCoordinates(position); //Update orientation of cursor privateScope.cursorStyle.getImage().setRotation((telemetry.heading / 360.0) * 6.28318); - } }; From 542a307338d34e3c2d410ec9168c6f974fe782c8 Mon Sep 17 00:00:00 2001 From: "Pawel Spychalski (DzikuVx)" Date: Sat, 23 Mar 2024 18:41:13 +0100 Subject: [PATCH 12/12] Show telemetry data in the groundstation --- _locales/en/messages.json | 27 ++++++++++++++++++++++++++ js/groundstation.js | 23 +++++++++++++++++++++- main.html | 40 +++++++++++++++++++++++++++++++++++++++ src/css/groundstation.css | 27 ++++++++++++++++++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 7d1a13a64..c21050c81 100755 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -5609,5 +5609,32 @@ }, "gsDeactivated": { "message": "Ground station mode deactivated" + }, + "gsTelemetry": { + "message": "Telemetry" + }, + "gsTelemetryLatitude": { + "message": "Latitude" + }, + "gsTelemetryLongitude": { + "message": "Longitude" + }, + "gsTelemetryAltitude": { + "message": "Altitude" + }, + "gsTelemetryAltitudeShort": { + "message": "Alt" + }, + "gsTelemetryVoltageShort": { + "message": "Vbat" + }, + "gsTelemetrySats": { + "message": "Sats" + }, + "gsTelemetryFix": { + "message": "Fix" + }, + "gsTelemetrySpeed": { + "message": "Speed" } } \ No newline at end of file diff --git a/js/groundstation.js b/js/groundstation.js index 7cd99b3e7..37d1af52f 100644 --- a/js/groundstation.js +++ b/js/groundstation.js @@ -118,7 +118,7 @@ helper.groundstation = (function () { let telemetry = helper.ltmDecoder.get(); - if (telemetry.gpsFix) { + if (telemetry.gpsFix && telemetry.gpsFix > 1) { let lat = telemetry.latitude / 10000000; let lon = telemetry.longitude / 10000000; @@ -165,8 +165,29 @@ helper.groundstation = (function () { privateScope.cursorPosition.setCoordinates(position); //Update orientation of cursor privateScope.cursorStyle.getImage().setRotation((telemetry.heading / 360.0) * 6.28318); + + + + //Update text + privateScope.$viewport.find("#gs-telemetry-latitude").html(lat); + privateScope.$viewport.find("#gs-telemetry-longitude").html(lon); + } + + privateScope.$viewport.find("#gs-telemetry-altitude").html(telemetry.altitude / 100.0 + 'm'); + privateScope.$viewport.find("#gs-telemetry-voltage").html(telemetry.voltage / 100.0 + 'V'); + privateScope.$viewport.find("#gs-telemetry-sats").html(telemetry.gpsSats); + privateScope.$viewport.find("#gs-telemetry-speed").html(telemetry.groundSpeed * 100 + 'm/s'); + + let fixText = ''; + if (telemetry.gpsFix == 3) { + fixText = '3D'; + } else if (telemetry.gpsFix == 2) { + fixText = '2D'; + } else { + fixText = 'No fix'; } + privateScope.$viewport.find("#gs-telemetry-fix").html(fixText); }; return publicScope; diff --git a/main.html b/main.html index e0e9f2f8b..bc44efc9a 100755 --- a/main.html +++ b/main.html @@ -175,6 +175,46 @@

diff --git a/src/css/groundstation.css b/src/css/groundstation.css index b0f22f52d..abc4a75b8 100644 --- a/src/css/groundstation.css +++ b/src/css/groundstation.css @@ -2,9 +2,36 @@ background-color: #2e2e2e; width: 100%; height: 100%; + display: flex; } #groundstation-map { width: 100%; height: 100%; +} + +#groundstation-telemetry { + width: 20%; + color: #ddd; +} + +.groundstation-telemetry__header { + margin: 1em; + text-align: center; +} + +.groundstation-telemetry__row { + display: flex; + justify-content: space-between; + margin: 1em; + font-size: 1.1em; +} + +.groundstation-telemetry__row--big { + font-size: 2.2em; +} + +.groundstation-telemetry__label { + color: #aaa; + font-weight: bold; } \ No newline at end of file