diff --git a/apps/spacer/ChangeLog b/apps/spacer/ChangeLog index 263d4078d2..88e8a64fc0 100644 --- a/apps/spacer/ChangeLog +++ b/apps/spacer/ChangeLog @@ -1 +1,2 @@ 0.01: attempt to import +0.10: major updates, display satellites, display per-system satellite info diff --git a/apps/spacer/README.md b/apps/spacer/README.md index 5a8725a966..b3fc437ba7 100644 --- a/apps/spacer/README.md +++ b/apps/spacer/README.md @@ -4,5 +4,17 @@ Compare GPS with Baido and Glonass This turns GNSS receiver into mode with all three systems enabled, and displays debug info from all of them. Click into top left corner to -switch navigation systems. +switch navigation systems. Clicks in bottom half of screen switch info +pages. + +GNSS acquisition has few phases, and this software is assuming you are +not doing a cold start. GNSS fix needs 4 or 5 satellites with +reasonable signal strength, and then holding the same place for 40 or +so seconds. + +Uxx -- satellites are known but not being received + +S1..S4 -- not enough satellites being decoded (5 needed) + +XXdB -- this is strength of 5th strongest satellite diff --git a/apps/spacer/app-icon.js b/apps/spacer/app-icon.js index 903d14af51..53dbfa31ee 100644 --- a/apps/spacer/app-icon.js +++ b/apps/spacer/app-icon.js @@ -1,2 +1,2 @@ -require("heatshrink").decompress(atob("mEwgIQNgQFEj/gAof+jgECgeAAIIFBgwCBuACBhgCEjAOEAoQ6CmAhCDwItDoEB4AFCsEBFgUEkEDG4XEJYcL8gFCgUP+gxCAoP8DIIFBhfsiEIAoMJAogCBAoYlBiBMBAoUwrA0B////ALECI0QAocgAolgApVADolAHYnAAomAAoqdBAoKVBMoRvCOIQDCRIIFBYwKVBAoKqC4AFBVQVggTRDn0CYgQcBN4LpDV4T7IAooAJA=")) +require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AGMAF34v/F34EBAAIvrFwQxnEoIsFGEyNHF9ZgNGrheMF4guJHDAvLEhCdbLyLLdL5ImFfRo6SLpjGOCgw5TdZRhKHwhEPFxjOJCwwOGF6zOJBxpjFAB4TKR6aEJDpgwKd5rzUYBrkVMxwtVF7CYHeBxGRF5LwMR9YvdPiJfjBhYv/F/4v/F/4v/F/4v/F/4v/F/4vfAH4Ad")) diff --git a/apps/spacer/app.png b/apps/spacer/app.png index ce7ac239fd..6743d6e592 100644 Binary files a/apps/spacer/app.png and b/apps/spacer/app.png differ diff --git a/apps/spacer/metadata.json b/apps/spacer/metadata.json index 55598f9535..e114d8dd12 100644 --- a/apps/spacer/metadata.json +++ b/apps/spacer/metadata.json @@ -1,6 +1,6 @@ { "id": "spacer", "name": "Space Race", - "version": "0.01", + "version": "0.10", "description": "Compare GPS with Baido and Glonass", "icon": "app.png", "readme": "README.md", diff --git a/apps/spacer/spacer.app.js b/apps/spacer/spacer.app.js index 13812b2a59..99453e79a8 100644 --- a/apps/spacer/spacer.app.js +++ b/apps/spacer/spacer.app.js @@ -1,20 +1,6 @@ /* Space race */ -/* - -Performance Assessment of GNSS Signals in -terms of Time to First Fix for -Cold, Warm and Hot Start -Matteo Paonni, Marco Anghileri, Stefan Wallner, José-Ángel Ávila-Rodríguez, Bernd Eissfeller -Institute of Geodesy and Navigation, University FAF Munich, Germany - -=> 22 to 26 dB -- long time / no fix / ... -=> 26db + -- basically strength no longer matters - -apps/assistedgps/custom.html - -https://github.com/espruino/EspruinoDocs/blob/master/info/Bangle.js2%20Technical.md#gps - +/* gsa mi rika 2d/3d fix, a taky pdop/vdop/hdop CFG-NAVX z CASIC_en -- umoznuje nastavit chodec / auto / letadlo @@ -68,12 +54,12 @@ let ui = { touchHandler: function(d) { let x = Math.floor(d.x); let y = Math.floor(d.y); - + if (d.b != 1 || this.last_b != 0) { this.last_b = d.b; return; } - + print("touch", x, y, this.h, this.w); if ((x= this.snrLim) { + this.sats_used++; + print(sat.id, sat.snr); + } + this.sats[this.snum++] = sat; + } + }, + + snrSort: function() { + return this.sats.slice(0, this.snum).sort((a, b) => b.snr - a.snr); + }, + getSatSNR: function(n) { /* Get n-th strongest sat */ + if (n <= 0 || n > this.sats.length) + return -1; + + // Sort the satellites by snr in descending order + let sortedSats = this.snrSort(); + + // Return the SNR of the n-th strongest satellite + return sortedSats[n - 1].snr; + }, + qualest: function() { + // Sort the satellites by snr in descending order + let sortedSats = this.snrSort(); + if (sortedSats[4] && sortedSats[4].snr) { + return "" + sortedSats[4].snr + "dB"; + } + for (let i=4; i>=0; i--) { + if (sortedSats[i] && sortedSats[i].snr) + return "S" + (i+1); + } + return "U" + this.snum; + }, + satVisibility: [], + trackSatelliteVisibility: function() { + const threshold = this.snrLim; // SNR threshold + const now = getTime(); + let newVisibility = []; + //this.satVisibility = []; + for (let i = 0; i < this.snum; i++) { + let sat = this.sats[i]; + let existingSat = this.satVisibility[sat.id]; + if (sat.snr >= threshold) { + if (!existingSat) { + // New satellite starts visibility + newVisibility[sat.id] = { start: now, visible: true }; + } else + newVisibility[sat.id] = this.satVisibility[sat.id]; + } + } + this.satVisibility = newVisibility; + }, + getnthLowestStartTimeSat: function(n) { + // Collect all satellites from visibility + let satellites = Object.values(this.satVisibility); + + // Ensure we have at least 5 satellites + if (satellites.length < n) + return -1; + + // Sort satellites by start time in ascending order + satellites.sort((a, b) => a.start - b.start); + + // Return the satellite with the 5th lowest start time + return satellites[n-1]; // 0-based index, so 5th is index 4 + }, + goodest: function () { + let s = this.getnthLowestStartTimeSat(5); + if (s==-1) + return ""; + let t = getTime() - s.start; + return "" + t + "s"; + }, + summary: function () { + let s = this.goodest(); + if (s != "") + return s; + return this.qualest(); + }, + onEnd: function () { + this.trackSatelliteVisibility(); + if (this.sats_used < 5) + this.sky_start = getTime(); + this.reset(); + }, +}; + +function deepCopy(obj) { + if (obj === null || typeof obj !== "object") { + return obj; // Return primitive values as-is + } + + if (Array.isArray(obj)) { + return obj.map(deepCopy); // Handle arrays recursively + } + + const copy = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + copy[key] = deepCopy(obj[key]); // Recursively copy properties + } + } + return copy; +} + +let sky = { this_usable: 0, + debug: 0, + all: skys, /* Sattelites from all systems */ + split: 1, + + init: function () { + if (this.split) { + this.s_gp = deepCopy(skys); + this.s_gl = deepCopy(skys); + this.s_bd = deepCopy(skys); + } + }, drawGrid: function() { g.setColor(0,0,0); @@ -140,7 +258,8 @@ let sky = { ui.radCircle(1.0); }, - snrLim: 28, + /* 18.. don't get reliable fix in 40s */ + snrLim: 22, drawSat: function(s) { let a = s.azi / 360; let e = ((90 - s.ele) / 90); @@ -148,7 +267,7 @@ let sky = { let y = ui.radY(a, e); if (s.snr == 0) - g.setColor(1, 0.25, 0.25); + g.setColor(1, 0.25, 0.25); else if (s.snr < this.snrLim) g.setColor(0.25, 0.5, 0.25); else @@ -160,8 +279,6 @@ let sky = { // https://in-the-sky.org//satmap_radar.php?year=2023&month=10&day=24&skin=1 decorate: function() {}, drawSats: function(sats) { - if (ui.display != 0) - return; g.reset() .setColor(1, 1, 1) .fillRect(0, ui.wi, ui.w, ui.y2) @@ -172,45 +289,36 @@ let sky = { if (fix && fix.fix && fix.lat) { g.setColor(0, 0, 0) - .setFontAlign(-1, 1); + .setFontAlign(-1, 1); g.drawString(fix.satellites + "/" + fix.hdop, 5, ui.y2); } this.decorate(); }, - parseSats: function(s) { - let view = 1 * s[3]; - let k = Math.min(4, view - this.snum); - for (let i = 4, j = 0; j < k; j++) { - let sat = { id: s[i++], ele: 1 * s[i++], azi: 1 * s[i++], snr: s[i++] }; - if (sat.snr === "") - sat.snr = 0; - if (sat.snr >= this.snrLim) { - this.sats_used++; - print(sat.id, sat.snr); - } - this.sats[this.snum++] = sat; - } - }, - old_msg: {}, msg: {}, - tof: function(v) { let i = (1*v); return i.toFixed(0); }, - fmtSys: function(sys) { - if (sys && sys.sent !== undefined && sys.d23 !== undefined) - return sys.sent + "." + sys.d23 + "D "+ this.tof(sys.pdop) + " " + this.tof(sys.vdop) + "\n"; - else - return "(no data)\n"; + tof: function(v, n) { let i = (1*v); return i.toFixed(n); }, + tof0: function(v) { return this.tof(v, 0); }, + tof1: function(v) { return this.tof(v, 1); }, + fmtSys: function(sys, sats) { + if (!sys.sent) + return " off\n"; + let r = sys.sent + " "; + // r+= sys.d23 + "D "; + if (sats) + // r += sats.sats_used + "/" + sats.snum; + r += sats.summary(); + return r + "\n"; }, - display: function() { - if (ui.display != 1) - return; + drawRace: function() { let m = this.old_msg; - let msg = "" + this.tof(m.time) + "\n" + - "q" + m.quality + " " + m.in_view + " " + m.hdop + "\n" + - "gp"+ this.fmtSys(m.gp) + - "bd" + this.fmtSys(m.bd) + - "gl" + this.fmtSys(m.gl); + let msg = "gmt" + this.tof0(m.time) + "\n" + + "q" + m.quality + " S" + m.in_view + " h" + this.tof0(m.hdop) + "m\n" + + /* "v" + this.tof0(m.vdop) + "m " + "p" + this.tof0(m.pdop) + "m\n" + */ + this.all.summary() + "\n" + + "gp"+ this.fmtSys(m.gp, this.s_gp) + + "bd" + this.fmtSys(m.bd, this.s_bd) + + "gl" + this.fmtSys(m.gl, this.s_gl); if (this.msg.finished != 1) msg += "!"; g.reset().clear().setFont("Vector", 30) @@ -218,37 +326,68 @@ let sky = { .setFontAlign(-1, -1) .drawString(msg, 0, 0); }, + drawEstimates: function() { + /* + Performance Assessment of GNSS Signals in terms of Time to + First Fix for Cold, Warm and Hot Start Matteo Paonni, Marco Anghileri, + Stefan Wallner, José-Ángel Ávila-Rodríguez, Bernd Eissfeller Institute + of Geodesy and Navigation, University FAF Munich, Germany + + => 22 to 26 dB -- long time / no fix / ... + => 26db + -- basically strength no longer matters + */ + let r = this.all.qualest(); + let r1 = this.all.goodest(); + print(r, r1, this.old_msg.hdop, this.old_msg.quality); + ui.drawMsg(r + "\n" + r1 + "\n" + this.old_msg.hdop + "-" + this.old_msg.quality + "d\n" + (getTime() - this.all.sky_start)); + }, + onMessageEnd: function() {}, + messageEnd: function() { + this.old_msg = this.msg; + this.msg = {}; + this.msg.gp = {}; + this.msg.bd = {}; + this.msg.gl = {}; + this.onMessageEnd(); + //print(this.sats); + this.all.onEnd(); + if (this.split) { + this.s_gp.onEnd(); + this.s_gl.onEnd(); + this.s_bd.onEnd(); + } + }, parseRaw: function(msg, lost) { + //print(msg); if (lost) print("## data lost"); let s = msg.split(","); + // print(getTime(), s[0]); + //return; let cmd = s[0].slice(3); //print("cmd", cmd); + if (cmd === "RMC") { + /* Repeat of position/speed/course */ + this.messageEnd(); + return; + } if (cmd === "GGA") { - this.old_msg = this.msg; - this.msg = {}; this.msg.time = s[1]; this.msg.quality = s[6]; this.msg.in_view = s[7]; this.msg.hdop = s[8]; - this.msg.gp = {}; - this.msg.bd = {}; - this.msg.gl = {}; - print("-----------------------------------------------"); - print("GGA Time", s[1], "fix quality", s[4], "sats in view ", s[5]); - this.drawSats(this.sats); - if (this.sats_used < 5) - this.sky_start = getTime(); - this.snum = 0; - this.sats = []; - this.sats_used = 0; + + if (this.debug > 0) { + print("-----------------------------------------------"); + print("GGA Time", s[1], "fix quality", s[4], "sats in view ", s[5]); + } return; } if (cmd === "GLL") return; /* Position lat/lon */ if (cmd === "GSA") { /* - $GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,4*04 - 0 1 2 15 16 17 18 - */ + $GNGSA,A,1,,,,,,,,,,,,,25.5,25.5,25.5,4*04 + 0 1 2 15 16 17 18 + */ /* Satelites used, fix type! INTERESTING */ let sys = s[18]; let add = {}; @@ -256,41 +395,48 @@ let sky = { add.pdop = s[15]; add.hdop = s[16]; add.vdop = s[17]; + sys = sys[0]; /* FIXME -- should really add to the sentence */ if (sys == 1) { this.msg.gp = add; } else if (sys == 2) { this.msg.gl = add; } else if (sys == 4) { this.msg.bd = add; } - else print("GSA Unknown system\n"); - - print(msg); + else { + print("GSA Unknown system -- ", sys, "\n"); + print(msg); + } return; } if (s[0] === "$GPGSV") { - print("Have gps sentences", s[1], "/", s[2]); - this.parseSats(s); + if (this.debug > 0) + print("Have gps sentences", s[1], "/", s[2]); + this.all.parseSats(s); + if (this.split) + this.s_gp.parseSats(s); this.msg.gp.sent = ""+s[2]; return; } if (s[0] === "$BDGSV") { - print("Have baidu sentences", s[1], "/", s[2]); - this.parseSats(s); + if (this.debug > 0) + print("Have baidu sentences", s[1], "/", s[2]); + this.all.parseSats(s); + if (this.split) + this.s_bd.parseSats(s); this.msg.bd.sent = ""+s[2]; return; } if (s[0] === "$GLGSV") { - print("Have glonass sentences", s[1], "/", s[2]); - this.parseSats(s); + if (this.debug > 0) + print("Have glonass sentences", s[1], "/", s[2]); + this.all.parseSats(s); + if (this.split) + this.s_gl.parseSats(s); this.msg.gl.sent = ""+s[2]; return; } - if (cmd === "RMC") return; /* Repeat of position/speed/course */ + if (cmd === "VTG") return; /* Speeds in knots/kph */ if (cmd === "ZDA") return; /* Time + timezone */ - if (cmd === "TXT") { - this.msg.finished = 1; - return; /* Misc text? antena open */ - } - + if (cmd === "TXT") return; /* Misc text? antena open */ print(msg); }, casic_cmd: function (cmd) { @@ -314,20 +460,32 @@ let sky = { function start() { Bangle.setGPSPower(1); - Bangle.on('GPS-raw', function(a, b) { sky.parseRaw(a, b); }); + Bangle.on('GPS-raw', (a, b) => sky.parseRaw(a, b)); setTimeout(function() { Bangle.removeAllListeners('GPS-raw'); }, 1000000); - setInterval(function() { sky.display(); }, 1000); +} + +function onMessage() { + /* quality.updateGps(); /* FIXME -- for skyspy + if (ui.display == 4) + sky.drawEstimates(); + */ + if (ui.display == 0) + sky.drawSats(sky.all.sats); + if (ui.display == 1) + sky.drawRace(); } // CASIC_CMD("$PCAS06,0"); /* Query product information */ setTimeout(() => sky.casic_cmd("$PCAS04,7"), 1000); /* Enable gps + beidou + glonass */ -setTimeout(() => sky.casic_cmd("$PCAS03,1,1,1,1,1,1,1,1"), 1000); /* Enable gps + beidou + glonass */ +setTimeout(() => sky.casic_cmd("$PCAS03,1,1,1,1,1,1,1,1"), 1500); /* Enable all messages */ //setTimeout(() => sky.casic_cmd("$PCAS10,2"), 1200); /* 2: cold start, 1 warm start, 0: hot start */ ui.init(); ui.topLeft = () => sky.selectSpace(); Bangle.on("drag", (b) => ui.touchHandler(b)); +sky.onMessageEnd = onMessage; +sky.init(); start();