diff --git a/gulpfile.js b/gulpfile.js index 91a168095..1fc549d9e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -89,6 +89,7 @@ sources.js = [ './js/msp/MSPCodes.js', './js/msp/MSPHelper.js', './js/msp/MSPchainer.js', + './js/ublox/UBLOX.js', './js/port_handler.js', './js/connection/connection.js', './js/connection/connectionBle.js', diff --git a/js/configurator_main.js b/js/configurator_main.js index 4ca94c2d6..aec07083b 100644 --- a/js/configurator_main.js +++ b/js/configurator_main.js @@ -78,12 +78,16 @@ $(function() { $('a', activeTab).trigger('click'); } + globalSettings.store = store; globalSettings.unitType = store.get('unit_type', UnitType.none); globalSettings.mapProviderType = store.get('map_provider_type', 'osm'); globalSettings.mapApiKey = store.get('map_api_key', ''); + globalSettings.assistnowApiKey = store.get('assistnow_api_key', ''); globalSettings.proxyURL = store.get('proxyurl', 'http://192.168.1.222/mapproxy/service?'); globalSettings.proxyLayer = store.get('proxylayer', 'your_proxy_layer_name'); globalSettings.showProfileParameters = store.get('show_profile_parameters', 1); + globalSettings.assistnowOfflineData = store.get('assistnow_offline_data', []); + globalSettings.assistnowOfflineDate = store.get('assistnow_offline_date', 0); updateProfilesHighlightColours(); var cliAutocomplete = store.get('cli_autocomplete', true); @@ -340,6 +344,7 @@ $(function() { $('#proxylayer').val(globalSettings.proxyLayer); $('#showProfileParameters').prop('checked', globalSettings.showProfileParameters); $('#cliAutocomplete').prop('checked', globalSettings.cliAutocomplete); + $('#assistnow-api-key').val(globalSettings.assistnowApiKey); i18n.getLanguages().forEach(lng => { $('#languageOption').append("".format(lng, i18n.getMessage("language_" + lng))); @@ -383,6 +388,11 @@ $(function() { store.set('proxylayer', $(this).val()); globalSettings.proxyLayer = $(this).val(); }); + $('#assistnow-api-key').on('change', function () { + store.set('assistnow_api_key', $(this).val()); + globalSettings.assistnowApiKey = $(this).val(); + }); + $('#demoModeReset').on('click', function () { SITLProcess.deleteEepromFile('demo.bin'); }); diff --git a/js/globalSettings.js b/js/globalSettings.js index 1a58268ce..cc28ad852 100644 --- a/js/globalSettings.js +++ b/js/globalSettings.js @@ -21,6 +21,14 @@ var globalSettings = { // tree target for documents docsTreeLocation: 'master', cliAutocomplete: true, + assistnowApiKey: null, + assistnowOfflineData: [], + assistnowOfflineDate: 0, + store: null, + saveAssistnowData: function() { + this.store.set('assistnow_offline_data', this.assistnowOfflineData); + this.store.set('assistnow_offline_date', this.assistnowOfflineDate); + } }; module.exports = { globalSettings, UnitType }; \ No newline at end of file diff --git a/js/msp/MSPCodes.js b/js/msp/MSPCodes.js index 613f0f6c3..abe54465b 100644 --- a/js/msp/MSPCodes.js +++ b/js/msp/MSPCodes.js @@ -225,6 +225,8 @@ var MSPCodes = { MSP2_INAV_FW_APPROACH: 0x204A, MSP2_INAV_SET_FW_APPROACH: 0x204B, + MSP2_INAV_GPS_UBLOX_COMMAND: 0x2050, + MSP2_INAV_RATE_DYNAMICS: 0x2060, MSP2_INAV_SET_RATE_DYNAMICS: 0x2061, diff --git a/js/msp/MSPHelper.js b/js/msp/MSPHelper.js index 383b99bbd..e872d571c 100644 --- a/js/msp/MSPHelper.js +++ b/js/msp/MSPHelper.js @@ -1588,11 +1588,14 @@ var mspHelper = (function () { FC.OSD_CUSTOM_ELEMENTS .items.push(customElement) } break; + case MSPCodes.MSP2_INAV_GPS_UBLOX_COMMAND: + // Just and ACK from the fc. + break; default: - console.log('Unknown code detected: ' + dataHandler.code); + console.log('Unknown code detected: 0x' + dataHandler.code.toString(16)); } else { - console.log('FC reports unsupported message error: ' + dataHandler.code); + console.log('FC reports unsupported message error: 0x' + dataHandler.code.toString(16)); } // trigger callbacks, cleanup/remove callback after trigger @@ -3383,6 +3386,10 @@ var mspHelper = (function () { MSP.send_message(MSPCodes.MSP2_SET_CF_SERIAL_CONFIG, mspHelper.crunch(MSPCodes.MSP2_SET_CF_SERIAL_CONFIG), false, callback); }; + self.sendUbloxCommand = function (ubloxData, callback) { + MSP.send_message(MSPCodes.MSP2_INAV_GPS_UBLOX_COMMAND, ubloxData, false, callback); + }; + return self; })(); diff --git a/js/ublox/UBLOX.js b/js/ublox/UBLOX.js new file mode 100644 index 000000000..438f55451 --- /dev/null +++ b/js/ublox/UBLOX.js @@ -0,0 +1,239 @@ +'use strict'; + +const semver = require('semver'); + +require('./../injected_methods'); +const jBox = require('./../libraries/jBox/jBox.min'); +const i18n = require('./../localization'); +const { GUI } = require('./../gui'); +const { globalSettings } = require('../globalSettings'); +const Store = require('electron-store'); + + +var ublox = (function () { + var self = {}; + var assistnowOnline = null; + var assistnowOffline = null; + + // m7 = aid, not supported + // m8+ = mga + const fmt="mga";; + const gnss="gps,gal,bds,glo,qzss"; + + const onlineServers = [ + 'online-live1.services.u-blox.com', + 'online-live2.services.u-blox.com', + ]; + + const period=5 + + const offline_gnss="gps,gal,bds,glo"; + const offline_alm="gps,gal,bds,glo"; + + const offlineServers = [ + 'offline-live1.services.u-blox.com', + 'offline-live2.services.u-blox.com' + ]; + + self.init = function() { + + }; + + var hasFirstHeader; + var hasSecondHeader; + var ubxClass; + var ubxId; + var lenLow; + var lenHigh; + var payloadLen; + var skipped; + var currentCommand; + + function resetUbloxState() { + //console.log("Reset ublox state"); + hasFirstHeader = false; + hasSecondHeader = false; + ubxClass = false; + ubxId = false; + lenLow = false; + lenHigh = false; + payloadLen = 0; + skipped = 0; + currentCommand = []; + } + + function splitUbloxData(ubxBytesBuffer) { + //console.log("type of data: " +typeof(ubxBytesBuffer)); + //console.log("splitUbloxData: " + ubxBytesBuffer.byteLength); + let ubxBytes = new DataView(ubxBytesBuffer); + + var ubxCommands = [] + resetUbloxState() + + for(var i = 0; i < ubxBytes.byteLength;++i) { + let c = ubxBytes.getUint8(i); + //console.log("byte: 0x" + c.toString(16)); + if (!hasFirstHeader) { + if (c == 0xb5) { + //console.log("First header"); + hasFirstHeader = true; + currentCommand.push(c); + continue; + } + else + { + resetUbloxState(); + continue; + } + } + if (!hasSecondHeader) { + if (c == 0x62) { + //console.log("Second header"); + hasSecondHeader = true; + currentCommand.push(c); + continue; + } + else + { + resetUbloxState(); + continue; + } + } + if (!ubxClass) { + ubxClass = true; + //console.log("ubxClass: 0x"+ (c).toString(16)); + currentCommand.push(c) + continue; + } + if (!ubxId) { + ubxId = true; + //console.log("ubxId: 0x"+ (c).toString(16)); + currentCommand.push(c); + continue; + } + if (!lenLow) { + //console.log("Len low"); + lenLow = true; + //(int) c + payloadLen = c; + currentCommand.push(c); + continue; + } + if (!lenHigh) { + //console.log("Len high"); + lenHigh = true; + // (int)c + payloadLen = (c << 8) | payloadLen; + //console.log("Payload len " + payloadLen); + payloadLen += 2; // add crc bytes; + currentCommand.push(c); + continue + } + if (skipped < payloadLen - 1) { + //console.log("payload + crc"); + skipped = skipped + 1; + currentCommand.push(c); + continue; + } + if (skipped == payloadLen - 1) { + skipped = skipped + 1; + currentCommand.push(c); + ubxCommands.push(currentCommand); + //console.log("Adding command"); + resetUbloxState(); + continue; + } + } + return ubxCommands + } + + function getBinaryData(url, successCallback, failCallback) { + const req = new XMLHttpRequest(); + req.open("GET", url, true); + req.responseType = "arraybuffer"; + + if (successCallback != null) { + req.onload = (event) => { + successCallback(req.response); + }; + } + + if (failCallback != null) { + req.onerror = (event) => { + failCallback(event); + } + } + + req.send(null); + } + + + function loadError(event) { + GUI.alert(i18n.getMessage("gpsAssistnowLoadDataError")); + console.log(i18n.getMessage("gpsAssistnowLoadDataError") + ':' + event.toString()); + } + + // For more info on assistnow, check: + // https://developer.thingstream.io/guides/location-services/assistnow-user-guide + // Currently only supported for M8+ units + self.loadAssistnowOffline = function(callback) { + + let url = `https://${ offlineServers[0] }/GetOfflineData.ashx?token=${globalSettings.assistnowApiKey};gnss=${offline_gnss};format=${fmt};period=${period};resolution=1;alm=${offline_alm};` + //console.log(url); + + function processOfflineData(data) { + if(globalSettings.assistnowOfflineData == null || ((Date.now() / 1000)-globalSettings.assistnowOfflineDate) > (60*60*24*3)) { + console.log("AssistnowOfflineData older than 3 days, refreshing."); + globalSettings.assistnowOfflineData = splitUbloxData(data); + globalSettings.assistnowOfflineDate = Math.floor(Date.now() / 1000); + globalSettings.saveAssistnowData(); + } else { + console.log("AssitnowOfflineData newer than 3 days. Re-using."); + } + //console.log("Assitnow offline commands:" + globalSettings.assistnowOfflineData.length); + callback(globalSettings.assistnowOfflineData); + } + + getBinaryData(url, processOfflineData, loadError); + //$.get(url, processOfflineData).fail(function() {GUI.alert("Error loading Offline data")}); + }; + + self.loadAssistnowOnline = function(callback) { + //url = "https://online-live1.services.u-blox.com/GetOnlineData.ashx?token=" + online_token + ";gnss=" + gnss + ";datatype=eph,alm,aux,pos;format=" + fmt + ";" + let url = `https://${ onlineServers[0] }/GetOnlineData.ashx?token=${globalSettings.assistnowApiKey};gnss=${ gnss };datatype=eph,alm,aux,pos;format=${ fmt }`; + + function processOnlineData(data) { + assistnowOnline = splitUbloxData(data); + + //console.log("Assitnow online commands:" + assistnowOnline.length); + callback(assistnowOnline); + } + + //$.get(url, processOnlineData).fail(function() {GUI.alert("Error loading Offline data")}); + getBinaryData(url, processOnlineData, loadError); + } + + self.isAssistnowDataRelevant = function(ubxMessage, cy, cm, cd) { + if ((ubxMessage[2] == 0x13 /*UBX_CLASS_MGA*/) && (ubxMessage[3] == 0x20 /*UBX_MGA_ANO*/)) + { + // UBX-MGA-ANO + const payloadOffset = 6; + if (((ubxMessage[payloadOffset + 4] + 2000) == cy) && (ubxMessage[payloadOffset + 5] == cm) && (ubxMessage[payloadOffset + 6] == cd)) + { + //console.log("UBX-MGA_ANO date matches"); + return true; + } + } else { + //console.log("UBX-CMD: class: 0x" + ubxMessage[2].toString(16) + " id: 0x" + ubxMessage[3].toString(16)); + return true; + } + + return false; + } + + + return self; +})(); + + +module.exports = ublox; diff --git a/locale/en/messages.json b/locale/en/messages.json index 88d111470..bba5c8e2d 100644 --- a/locale/en/messages.json +++ b/locale/en/messages.json @@ -5911,5 +5911,29 @@ }, "maintenanceFlushSettingsCache": { "message": "Flush settings cache" + }, + "gpsOptions": { + "message": "GPS Options" + }, + "gpsOptionsAssistnowToken": { + "message": "AssitNow Token" + }, + "gpsLoadAssistnowOfflineButton": { + "message": "Load AssistNow Offline" + }, + "gpsLoadAssistnowOnlineButton": { + "message": "Load AssistNow Online" + }, + "gpsAssistnowStart": { + "message": "AssistNow data transfer starting..." + }, + "gpsAssistnowDone": { + "message": "AssistNow data transfer complete." + }, + "gpsAssistnowUpdate": { + "message": "AssistNow messages sent." + }, + "gpsAssistnowLoadDataError": { + "message": "Error loading AssistNow data." } } diff --git a/tabs/gps.html b/tabs/gps.html index 3cf66f9d4..c217b7b9c 100644 --- a/tabs/gps.html +++ b/tabs/gps.html @@ -159,5 +159,11 @@
+
+ +
+
+ +
\ No newline at end of file diff --git a/tabs/gps.js b/tabs/gps.js index 87aee1637..bf0dad53f 100644 --- a/tabs/gps.js +++ b/tabs/gps.js @@ -18,6 +18,7 @@ const features = require('./../js/feature_framework'); const { globalSettings } = require('./../js/globalSettings'); const jBox = require('./../js/libraries/jBox/jBox.min'); const SerialBackend = require('../js/serial_backend'); +const ublox = require('../js/ublox/UBLOX'); TABS.gps = {}; @@ -418,6 +419,64 @@ TABS.gps.initialize = function (callback) { }); }); + function processUbloxData(data) { + if(data != null) { + //console.log("processing data type: " + typeof(data)); + let totalSent = 0; + let total = data.length; + + var ubloxChainer = MSPChainerClass(); + var chain = []; + let d = new Date(); + + GUI.log(i18n.getMessage('gpsAssistnowStart')); + data.forEach((item) => { + chain.push(function (callback) { + //console.log("UBX command: " + item.length); + let callCallback = false; + if (ublox.isAssistnowDataRelevant(item, d.getUTCFullYear(), d.getUTCMonth()+1, d.getUTCDate()+1)) { + mspHelper.sendUbloxCommand(item, callback); + } else { + // Ignore msp command, but keep counter going. + callCallback = true; + } + totalSent++; + if((totalSent % 100) == 0) { + GUI.log(totalSent + '/' + total + ' ' + i18n.getMessage('gpsAssistnowUpdate')); + } + if(callCallback) { + callback(); + } + }); + }); + ubloxChainer.setChain(chain); + ubloxChainer.setExitPoint(function () { + if ((totalSent % 100) != 0) { + GUI.log(totalSent + '/' + total + ' ' + i18n.getMessage('gpsAssistnowUpdate')); + } + GUI.log(i18n.getMessage('gpsAssistnowDone')); + }); + + ubloxChainer.execute(); + } + } + + $('a.loadAssistnowOnline').on('click', function () { + if(globalSettings.assistnowApiKey != null && globalSettings.assistnowApiKey != '') { + ublox.loadAssistnowOnline(processUbloxData); + } else { + GUI.alert("Assistnow Token not set!"); + } + }); + + $('a.loadAssistnowOffline').on('click', function () { + if(globalSettings.assistnowApiKey != null && globalSettings.assistnowApiKey != '') { + ublox.loadAssistnowOffline(processUbloxData); + } else { + GUI.alert("Assistnow Token not set!"); + } + }); + GUI.content_ready(callback); } diff --git a/tabs/options.html b/tabs/options.html index 7c21b29d2..d7e6c2f9f 100644 --- a/tabs/options.html +++ b/tabs/options.html @@ -97,5 +97,17 @@ +
+
+
+
+ +
+
+ + +
+
+
\ No newline at end of file