From 103895fc8ac18e43c343b6efa45f027d5b92c7a3 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Sat, 8 Jan 2022 16:56:05 +1030 Subject: [PATCH 01/25] Added initial XDATA parser, with OIF411 support --- build.sh | 1 + js/xdata.js | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 js/xdata.js diff --git a/build.sh b/build.sh index 99c8903..b6ac176 100755 --- a/build.sh +++ b/build.sh @@ -25,6 +25,7 @@ java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations - java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge tracker.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge app.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge colour-map.js >> mobile.js +java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge xdata.js >> mobile.js #compile plot lib and config java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge _jquery.flot.js >> init_plot.js diff --git a/js/xdata.js b/js/xdata.js new file mode 100644 index 0000000..744168c --- /dev/null +++ b/js/xdata.js @@ -0,0 +1,125 @@ +/* SondeHub XDATA Parser Library + * + * Author: Mark Jessop + */ + +function parseOIF411(xdata){ + // Attempt to parse an XDATA string from an OIF411 Ozone Sounder + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // https://www.vaisala.com/sites/default/files/documents/Ozone%20Sounding%20with%20Vaisala%20Radiosonde%20RS41%20User%27s%20Guide%20M211486EN-C.pdf + // + // Sample data: 0501036402B958B07500 (length = 20 characters) + // More sample data: 0501R20234850000006EI (length = 21 characters) + + // Cast to string if not already + xdata = String(xdata); + + // Run some checks over the input + if(xdata.length < 20){ + // Invalid OIF411 dataset + return {}; + } + + if(xdata.substr(0,2) !== '05'){ + // Not an OIF411 (shouldn't get here) + return {}; + } + _output = {'xdata_instrument': 'OIF411'}; + + // Instrument number is common to all XDATA types. + _output['oif411_instrument_number'] = parseInt(xdata.substr(2,2),16); + + + if(xdata.length == 21){ + // ID Data (Table 19) + // Serial number + _output['oif411_serial'] = xdata.substr(4,8); + + // Diagnostics word. + _diagnostics_word = xdata.substr(12,4); + if(_diagnostics_word == '0000'){ + _output['oif411_diagnostics'] = "All OK"; + }else if(_diagnostics_word == '0004'){ + _output['oif411_diagnostics'] = 'Ozone pump temperature below −5 °C.'; + }else if(_diagnostics_word == '0400'){ + _output['oif411_diagnostics'] = 'Ozone pump battery voltage (+VBatt) is not connected to OIF411'; + }else if (_diagnostics_word == '0404'){ + _output['oif411_diagnostics'] = 'Ozone pump temp low, and +VBatt not connected.'; + }else { + _output['oif411_diagnostics'] = 'Unknown State: ' + _diagnostics_word; + } + + // Version number + _output['oif411_version'] = (parseInt(xdata.substr(16,4),16)/100).toFixed(2); + + return _output + } else if (xdata.length == 20){ + // Measurement Data (Table 18) + // Ozone pump temperature - signed int16 + _ozone_pump_temp = parseInt(xdata.substr(4,4),16); + if ((_ozone_pump_temp & 0x8000) > 0) { + _ozone_pump_temp = _ozone_pump_temp - 0x10000; + } + _output['oif411_ozone_pump_temp'] = _ozone_pump_temp*0.01; // Degrees C + + // Ozone Pump Current + _output['oif411_ozone_current_uA'] = parseInt(xdata.substr(8,5),16)*0.0001; // micro-Amps + + // Battery Voltage + _output['oif411_ozone_battery_v'] = parseInt(xdata.substr(13,2),16)*0.1; // Volts + + // Ozone Pump Current + _output['oif411_ozone_pump_curr_mA'] = parseInt(xdata.substr(15,3),16); // mA + + // External Voltage + _output['oif411_ext_voltage'] = parseInt(xdata.substr(18,2),16)*0.1; // Volts + + + return _output + + } else { + return {} + } +} + +function parseXDATA(data){ + // Accept an XDATA string, or multiple XDATA entries, delimited by '#' + // Attempt to parse each one, and return an object + // Test datasets: + // "0501034F02C978A06300" + // "0501R20234850000006EI" + // "0501034F02CA08B06700#800261FCA6F80012F6F40A75" + // "800262358C080012FE6C0A70#0501035902BA08908400" + + // Split apart any contatenated xdata. + if(data.includes('#')){ + data_split = data.split('#'); + } else { + data_split = [data]; + } + + _output = {}; + for(xdata_i = 0; xdata_i < data_split.length; xdata_i++){ + _current_xdata = data_split[xdata_i]; + + // Get Instrument ID + _instrument = _current_xdata.substr(0,2); + + if(_instrument === '05'){ + // OIF411 + _xdata_temp = parseOIF411(_current_xdata); + _output = Object.assign(_output,_xdata_temp); + } else if (_instrument === '80'){ + // Unknown! + //console.log("Saw unknown XDATA instrument 0x80.") + } else { + // Unknown! + + } + } + + return _output + +} \ No newline at end of file From c82d14329328f58a09d7fddfe135c6c4e16b67b7 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sat, 8 Jan 2022 18:57:10 +1100 Subject: [PATCH 02/25] initial XDATA decoding support --- js/tracker.js | 188 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 166 insertions(+), 22 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index 1d04a06..da468bc 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -1759,7 +1759,15 @@ function habitat_data(jsondata, alternative) { "manufacturer": "Manufacturer", "type": "Sonde Type", "burst_timer": "Burst Timer", - "xdata": "XDATA" + "xdata": "XDATA", + "xdata_instrument": "XDATA Instrument", + "oif411_ozone_battery_v": "OIF411 Battery", + "oif411_ozone_current_uA": "Ozone Current", + "oif411_ozone_pump_curr_mA": "Ozone Pump Current", + "oif411_ozone_pump_temp": "Ozone Pump Temperature", + "oif411_serial": "OIF411 Serial Number", + "oif411_diagnostics": "OIF411 Diagnostics", + "oif411_version": "OIF411 Version", }; var tooltips = { @@ -1797,7 +1805,11 @@ function habitat_data(jsondata, alternative) { "humidity": " %", "frequency": " MHz", "frequency_tx": " MHz", - "spam": "" + "spam": "", + "oif411_ozone_battery_v": " V", + "oif411_ozone_current_uA": " uA", + "oif411_ozone_pump_curr_mA": " mA", + "oif411_ozone_pump_temp": "°C", }; try @@ -1808,6 +1820,7 @@ function habitat_data(jsondata, alternative) { var array = []; var output = ""; var txFreq = false + var xdataFound = false if(Object.keys(data).length === 0) return ""; @@ -1815,15 +1828,19 @@ function habitat_data(jsondata, alternative) { txFreq = true } + if ("xdata_instrument" in data) { + xdataFound = true + } + for(var key in data) { - if (key === "frequency" && txFreq) {} else { + if ((key === "frequency" && txFreq) || (key === "xdata" && xdataFound)) {} else { array.push([key, data[key]]); } } - array.sort(function(a, b) { - return a[0].localeCompare(b[0]); - }); + //array.sort(function(a, b) { + // return a[0].localeCompare(b[0]); + //}); for(var i = 0, ii = array.length; i < ii; i++) { var k = array[i][0]; // key @@ -3113,6 +3130,9 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { if (data.hasOwnProperty("manufacturer")) { html += "
Manufacturer: " + data.manufacturer + "
"; }; + if (data.hasOwnProperty("pressure")) { + html += "
Pressure: " + data.pressure + " Pa
"; + }; if (data.hasOwnProperty("sats")) { html += "
Satellites: " + data.sats + "
"; }; @@ -3124,11 +3144,35 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { } else if (data.hasOwnProperty("type")) { html += "
Sonde Type: " + data.type + "
"; }; - if (data.hasOwnProperty("pressure")) { - html += "
Pressure: " + data.pressure + " Pa
"; - }; if (data.hasOwnProperty("xdata")) { + html += "
"; + html += "
" html += "
XDATA: " + data.xdata + "
"; + var tempXDATA = parseXDATA(data.xdata); + if (tempXDATA.hasOwnProperty('xdata_instrument')) { + html += "
XDATA Instrument: " + tempXDATA.xdata_instrument + "
"; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { + html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v + " V
"; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { + html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA + " uA
"; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { + html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA + " mA
"; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { + html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp + "°C
"; + } + if (tempXDATA.hasOwnProperty('oif411_serial')) { + html += "
OIF411 Serial Number: " + tempXDATA.oif411_serial + "
"; + } + if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { + html += "
OIF411 Diagnostics: " + tempXDATA.oif411_diagnostics + "
"; + } + if (tempXDATA.hasOwnProperty('oif411_version')) { + html += "
OIF411 Version: " + tempXDATA.oif411_version + "
"; + } }; html += "
"; @@ -3851,7 +3895,7 @@ function addPosition(position) { // Graph Stuff -var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer']; +var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_current_uA', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version']; function updateGraph(vcallsign, reset_selection) { if(!plot || !plot_open) return; @@ -4167,6 +4211,9 @@ function formatData(data, live) { if (data[entry].manufacturer) { dataTempEntry.data.manufacturer = data[entry].manufacturer; } + if (data[entry].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[entry].pressure; + } if (data[entry].sats) { dataTempEntry.data.sats = data[entry].sats; } @@ -4181,11 +4228,33 @@ function formatData(data, live) { dataTempEntry.data.type = data[entry].subtype; dataTempEntry.type = data[entry].subtype; } - if (data[entry].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[entry].pressure; - } if (data[entry].xdata) { dataTempEntry.data.xdata = data[entry].xdata; + var tempXDATA = parseXDATA(data[entry].xdata); + if (tempXDATA.hasOwnProperty('xdata_instrument')) { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { + dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { + dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { + dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { + dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; + } + if (tempXDATA.hasOwnProperty('oif411_serial')) { + dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; + } + if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { + dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + } + if (tempXDATA.hasOwnProperty('oif411_version')) { + dataTempEntry.oif411_version = tempXDATA.oif411_version; + } } if (data[entry].serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4255,6 +4324,9 @@ function formatData(data, live) { if (data.manufacturer) { dataTempEntry.data.manufacturer = data.manufacturer; } + if (data.hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data.pressure; + } if (data.sats) { dataTempEntry.data.sats = data.sats; } @@ -4269,11 +4341,33 @@ function formatData(data, live) { dataTempEntry.data.type = data.subtype; dataTempEntry.type = data.subtype; } - if (data.hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data.pressure; - } if (data.xdata) { dataTempEntry.data.xdata = data.xdata; + var tempXDATA = parseXDATA(data.xdata); + if (tempXDATA.hasOwnProperty('xdata_instrument')) { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { + dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { + dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { + dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { + dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; + } + if (tempXDATA.hasOwnProperty('oif411_serial')) { + dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; + } + if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { + dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + } + if (tempXDATA.hasOwnProperty('oif411_version')) { + dataTempEntry.oif411_version = tempXDATA.oif411_version; + } } if (data.serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4326,6 +4420,9 @@ function formatData(data, live) { if (data[key][i].manufacturer) { dataTempEntry.data.manufacturer = data[key][i].manufacturer; } + if (data[key][i].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[key][i].pressure; + } if (data[key][i].sats) { dataTempEntry.data.sats = data[key][i].sats; } @@ -4340,11 +4437,33 @@ function formatData(data, live) { dataTempEntry.data.type = data[key][i].subtype; dataTempEntry.type = data[key][i].subtype; } - if (data[key][i].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[key][i].pressure; - } if (data[key][i].xdata) { dataTempEntry.data.xdata = data[key][i].xdata; + var tempXDATA = parseXDATA(data[key][i].xdata); + if (tempXDATA.hasOwnProperty('xdata_instrument')) { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { + dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { + dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { + dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { + dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; + } + if (tempXDATA.hasOwnProperty('oif411_serial')) { + dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; + } + if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { + dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + } + if (tempXDATA.hasOwnProperty('oif411_version')) { + dataTempEntry.oif411_version = tempXDATA.oif411_version; + } } if (data[key][i].serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4418,6 +4537,9 @@ function formatData(data, live) { if (data[i].manufacturer) { dataTempEntry.data.manufacturer = data[i].manufacturer; } + if (data[i].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[i].pressure; + } if (data[i].sats) { dataTempEntry.data.sats = data[i].sats; } @@ -4442,11 +4564,33 @@ function formatData(data, live) { dataTempEntry.data.type = data[i].subtype; dataTempEntry.type = data[i].subtype; } - if (data[i].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[i].pressure; - } if (data[i].xdata) { dataTempEntry.data.xdata = data[i].xdata; + var tempXDATA = parseXDATA(data[i].xdata); + if (tempXDATA.hasOwnProperty('xdata_instrument')) { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { + dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { + dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { + dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; + } + if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { + dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; + } + if (tempXDATA.hasOwnProperty('oif411_serial')) { + dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; + } + if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { + dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + } + if (tempXDATA.hasOwnProperty('oif411_version')) { + dataTempEntry.oif411_version = tempXDATA.oif411_version; + } } dataTemp.push(dataTempEntry); } From 03644003ded159b06a8dde77b9701b03da8afe0c Mon Sep 17 00:00:00 2001 From: Luke Prior <22492406+LukePrior@users.noreply.github.com> Date: Sat, 8 Jan 2022 19:49:02 +1100 Subject: [PATCH 03/25] Update tracker.js --- js/tracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/tracker.js b/js/tracker.js index da468bc..e4f2ebf 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -3895,7 +3895,7 @@ function addPosition(position) { // Graph Stuff -var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_current_uA', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version']; +var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version']; function updateGraph(vcallsign, reset_selection) { if(!plot || !plot_open) return; From 7eeb2172d3e9c405903f463236459971340abe60 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sat, 8 Jan 2022 21:06:19 +1100 Subject: [PATCH 04/25] XDATA + single recovery --- js/tracker.js | 17 +++++++++++++++++ js/xdata.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index da468bc..44ea120 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -4767,6 +4767,7 @@ function refreshSingle(serial) { success: function(data, textStatus) { response = formatData(data, false); update(response); + singleRecovery(serial); $("#stText").text(""); }, error: function() { @@ -4896,6 +4897,22 @@ function refreshNewReceivers(initial, serial) { }); } +function singleRecovery(serial) { + + var datastr = "serial=" + serial; + + $.ajax({ + type: "GET", + url: recovered_sondes_url, + data: datastr, + dataType: "json", + success: function(response, textStatus) { + updateRecoveries(response); + } + }); + +} + function refreshRecoveries() { $.ajax({ diff --git a/js/xdata.js b/js/xdata.js index 744168c..d8d132d 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -105,16 +105,57 @@ function parseXDATA(data){ _current_xdata = data_split[xdata_i]; // Get Instrument ID + // https://gml.noaa.gov/aftp/user/jordan/XDATA%20Instrument%20ID%20Allocation.pdf + // https://www.gruan.org/gruan/editor/documents/gruan/GRUAN-TN-11_GruanToolRs92_v1.0_2020-10-01.pdf _instrument = _current_xdata.substr(0,2); - if(_instrument === '05'){ + if (_instrument === '01') { + // V7 + _output = {'xdata_instrument': 'V7'}; + } else if (_instrument === '05'){ // OIF411 _xdata_temp = parseOIF411(_current_xdata); _output = Object.assign(_output,_xdata_temp); + } else if (_instrument === '08'){ + // CFH + _output = {'xdata_instrument': 'CFH'}; + } else if (_instrument === '10'){ + // FPH + _output = {'xdata_instrument': 'FPH'}; + } else if (_instrument === '18'){ + // COBALD + _output = {'xdata_instrument': 'COBALD'}; + } else if (_instrument === '28'){ + // SLW + _output = {'xdata_instrument': 'SLW'}; + } else if (_instrument === '38'){ + // POPS + _output = {'xdata_instrument': 'POPS'}; + } else if (_instrument === '39'){ + // OPC + _output = {'xdata_instrument': 'OPC'}; + } else if (_instrument === '3C'){ + // PCFH + _output = {'xdata_instrument': 'PCFH'}; + } else if (_instrument === '3D'){ + // FLASH-B + _output = {'xdata_instrument': 'FLASH-B'}; + } else if (_instrument === '3E'){ + // TRAPS + _output = {'xdata_instrument': 'TRAPS'}; + } else if (_instrument === '3F'){ + // SKYDEW + _output = {'xdata_instrument': 'SKYDEW'}; + } else if (_instrument === '41'){ + // CICANUM + _output = {'xdata_instrument': 'CICANUM'}; + } else if (_instrument === '45'){ + // POPS + _output = {'xdata_instrument': 'POPS'}; } else if (_instrument === '80'){ // Unknown! //console.log("Saw unknown XDATA instrument 0x80.") - } else { + }else { // Unknown! } From 69066b31081c7e714456885016c327744daac9ee Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sat, 8 Jan 2022 21:15:53 +1100 Subject: [PATCH 05/25] single historical working --- js/tracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/tracker.js b/js/tracker.js index 44ea120..a6635a8 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -4899,7 +4899,7 @@ function refreshNewReceivers(initial, serial) { function singleRecovery(serial) { - var datastr = "serial=" + serial; + var datastr = "serials=" + serial; $.ajax({ type: "GET", From fc7cc4e7cc5de07eae600cf024f7dfecb6907d00 Mon Sep 17 00:00:00 2001 From: Luke Prior <22492406+LukePrior@users.noreply.github.com> Date: Sun, 9 Jan 2022 10:12:32 +1100 Subject: [PATCH 06/25] Revert "single historical working" --- js/tracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/tracker.js b/js/tracker.js index 2283f03..f07f096 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -4899,7 +4899,7 @@ function refreshNewReceivers(initial, serial) { function singleRecovery(serial) { - var datastr = "serials=" + serial; + var datastr = "serial=" + serial; $.ajax({ type: "GET", From ca6ccde820bb9d8454ecd70190621e0db3b7acba Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Sun, 9 Jan 2022 15:39:08 +1030 Subject: [PATCH 07/25] Initial test of O3 partial pressure calculations --- js/tracker.js | 66 ++++++++++++++++++++++++++++++++++++++++++--------- js/xdata.js | 52 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index f07f096..01a2661 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -1768,11 +1768,13 @@ function habitat_data(jsondata, alternative) { "oif411_serial": "OIF411 Serial Number", "oif411_diagnostics": "OIF411 Diagnostics", "oif411_version": "OIF411 Version", + "oif411_O3_partial_pressure": "Ozone Partial Pressure" }; var tooltips = { "burst_timer": "If active, this indicates the time (HH:MM:SS) until the radiosonde will automatically power-off.", - "xdata": "Raw auxiliary data (as hexadecimal) from an external sensor package (often an Ozone sensor)." + "xdata": "Raw auxiliary data (as hexadecimal) from an external sensor package (often an Ozone sensor).", + "oif411_O3_partial_pressure": "Estimated O3 partial pressure, using nominal calibration values. +/- 1 mPa." } var hide_keys = { @@ -1810,6 +1812,7 @@ function habitat_data(jsondata, alternative) { "oif411_ozone_current_uA": " uA", "oif411_ozone_pump_curr_mA": " mA", "oif411_ozone_pump_temp": "°C", + "oif411_O3_partial_pressure": " mPa (+/- 1)" }; try @@ -3148,21 +3151,29 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { html += "
"; html += "
" html += "
XDATA: " + data.xdata + "
"; - var tempXDATA = parseXDATA(data.xdata); + if (data.hasOwnProperty("pressure")) { + xdata_pressure = data.pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data.xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { html += "
XDATA Instrument: " + tempXDATA.xdata_instrument + "
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v + " V
"; + html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v.toFixed(1) + " V
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA + " uA
"; + html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA.toFixed(4) + " uA
"; + } + if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { + html += "
Ozone Partial Presure: " + tempXDATA.oif411_O3_partial_pressure.toFixed(3) + " mPa (+/- 1)
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA + " mA
"; + html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA.toFixed(1) + " mA
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp + "°C
"; + html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp.toFixed(1) + "°C
"; } if (tempXDATA.hasOwnProperty('oif411_serial')) { html += "
OIF411 Serial Number: " + tempXDATA.oif411_serial + "
"; @@ -3895,7 +3906,7 @@ function addPosition(position) { // Graph Stuff -var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version']; +var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version', 'oif411_ozone_current_uA']; function updateGraph(vcallsign, reset_selection) { if(!plot || !plot_open) return; @@ -4230,7 +4241,13 @@ function formatData(data, live) { } if (data[entry].xdata) { dataTempEntry.data.xdata = data[entry].xdata; - var tempXDATA = parseXDATA(data[entry].xdata); + + if (data[entry].hasOwnProperty("pressure")) { + xdata_pressure = data[entry].pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data[entry].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; } @@ -4255,6 +4272,9 @@ function formatData(data, live) { if (tempXDATA.hasOwnProperty('oif411_version')) { dataTempEntry.oif411_version = tempXDATA.oif411_version; } + if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { + dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + } } if (data[entry].serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4343,7 +4363,12 @@ function formatData(data, live) { } if (data.xdata) { dataTempEntry.data.xdata = data.xdata; - var tempXDATA = parseXDATA(data.xdata); + if (data.hasOwnProperty("pressure")) { + xdata_pressure = data.pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data.xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; } @@ -4368,6 +4393,9 @@ function formatData(data, live) { if (tempXDATA.hasOwnProperty('oif411_version')) { dataTempEntry.oif411_version = tempXDATA.oif411_version; } + if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { + dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + } } if (data.serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4439,7 +4467,12 @@ function formatData(data, live) { } if (data[key][i].xdata) { dataTempEntry.data.xdata = data[key][i].xdata; - var tempXDATA = parseXDATA(data[key][i].xdata); + if (data[key][i].hasOwnProperty("pressure")) { + xdata_pressure = data[key][i].pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data[key][i].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; } @@ -4464,6 +4497,9 @@ function formatData(data, live) { if (tempXDATA.hasOwnProperty('oif411_version')) { dataTempEntry.oif411_version = tempXDATA.oif411_version; } + if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { + dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + } } if (data[key][i].serial.toLowerCase() != "xxxxxxxx") { dataTemp.push(dataTempEntry); @@ -4566,7 +4602,12 @@ function formatData(data, live) { } if (data[i].xdata) { dataTempEntry.data.xdata = data[i].xdata; - var tempXDATA = parseXDATA(data[i].xdata); + if (data[i].hasOwnProperty("pressure")) { + xdata_pressure = data[i].pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data[i].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; } @@ -4591,6 +4632,9 @@ function formatData(data, live) { if (tempXDATA.hasOwnProperty('oif411_version')) { dataTempEntry.oif411_version = tempXDATA.oif411_version; } + if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { + dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + } } dataTemp.push(dataTempEntry); } diff --git a/js/xdata.js b/js/xdata.js index d8d132d..73e9445 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -3,7 +3,45 @@ * Author: Mark Jessop */ -function parseOIF411(xdata){ +// Pump Efficiency Correction Parameters for ECC-6A Ozone Sensor, with 3.0cm^3 volume. +// We are using these as a nominal correction value for pump efficiency vs pressure +// +OIF411_Cef_Pressure = [ 0, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500, 1000, 1100]; +OIF411_Cef = [ 1.171, 1.171, 1.131, 1.092, 1.055, 1.032, 1.022, 1.015, 1.011, 1.008, 1.006, 1.004, 1, 1]; + + +function lerp(x, y, a){ + // Helper function for linear interpolation between two points + return x * (1 - a) + y * a +} + + +function get_oif411_Cef(pressure){ + // Get the Pump efficiency correction value for a given pressure. + + // Off-scale use bottom-end value + if (pressure <= OIF411_Cef_Pressure[0]){ + return OIF411_Cef[0]; + } + + // Off-scale top, use top-end value + if (pressure >= OIF411_Cef_Pressure[OIF411_Cef_Pressure.length-1]){ + return OIF411_Cef[OIF411_Cef.length-1]; + } + + + // Within the correction range, perform linear interpolation. + for(i= 1; i Date: Sun, 9 Jan 2022 18:01:58 +1100 Subject: [PATCH 08/25] XDATA fixes --- js/tracker.js | 34 +++++++++++++++++----------------- js/xdata.js | 22 ++++++++++++++-------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index 01a2661..7f34dfb 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -3161,19 +3161,19 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { html += "
XDATA Instrument: " + tempXDATA.xdata_instrument + "
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v.toFixed(1) + " V
"; + html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v + " V
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA.toFixed(4) + " uA
"; + html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA + " uA
"; } if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - html += "
Ozone Partial Presure: " + tempXDATA.oif411_O3_partial_pressure.toFixed(3) + " mPa (+/- 1)
"; + html += "
Ozone Partial Presure: " + tempXDATA.oif411_O3_partial_pressure + " mPa (+/- 1)
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA.toFixed(1) + " mA
"; + html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA + " mA
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp.toFixed(1) + "°C
"; + html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp + "°C
"; } if (tempXDATA.hasOwnProperty('oif411_serial')) { html += "
OIF411 Serial Number: " + tempXDATA.oif411_serial + "
"; @@ -4267,13 +4267,13 @@ function formatData(data, live) { dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; } if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; } if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.oif411_version = tempXDATA.oif411_version; + dataTempEntry.data.oif411_version = tempXDATA.oif411_version; } if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; } } if (data[entry].serial.toLowerCase() != "xxxxxxxx") { @@ -4388,13 +4388,13 @@ function formatData(data, live) { dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; } if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; } if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.oif411_version = tempXDATA.oif411_version; + dataTempEntry.data.oif411_version = tempXDATA.oif411_version; } if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; } } if (data.serial.toLowerCase() != "xxxxxxxx") { @@ -4492,13 +4492,13 @@ function formatData(data, live) { dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; } if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; } if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.oif411_version = tempXDATA.oif411_version; + dataTempEntry.data.oif411_version = tempXDATA.oif411_version; } if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; } } if (data[key][i].serial.toLowerCase() != "xxxxxxxx") { @@ -4627,13 +4627,13 @@ function formatData(data, live) { dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; } if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.oif411_diagnostics = tempXDATA.oif411_diagnostics; + dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; } if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.oif411_version = tempXDATA.oif411_version; + dataTempEntry.data.oif411_version = tempXDATA.oif411_version; } if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; + dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; } } dataTemp.push(dataTempEntry); diff --git a/js/xdata.js b/js/xdata.js index 73e9445..07ae13c 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -80,7 +80,7 @@ function parseOIF411(xdata, pressure){ if(_diagnostics_word == '0000'){ _output['oif411_diagnostics'] = "All OK"; }else if(_diagnostics_word == '0004'){ - _output['oif411_diagnostics'] = 'Ozone pump temperature below −5 °C.'; + _output['oif411_diagnostics'] = 'Ozone pump temperature below -5 °C.'; }else if(_diagnostics_word == '0400'){ _output['oif411_diagnostics'] = 'Ozone pump battery voltage (+VBatt) is not connected to OIF411'; }else if (_diagnostics_word == '0404'){ @@ -100,19 +100,24 @@ function parseOIF411(xdata, pressure){ if ((_ozone_pump_temp & 0x8000) > 0) { _ozone_pump_temp = _ozone_pump_temp - 0x10000; } - _output['oif411_ozone_pump_temp'] = _ozone_pump_temp*0.01; // Degrees C + _ozone_pump_temp = _ozone_pump_temp*0.01; // Degrees C + _output['oif411_ozone_pump_temp'] = Math.round(_ozone_pump_temp * 10) / 10; // 1 DP - // Ozone Pump Current - _output['oif411_ozone_current_uA'] = parseInt(xdata.substr(8,5),16)*0.0001; // micro-Amps + // Ozone Current + _ozone_current_uA = parseInt(xdata.substr(8,5),16)*0.0001; // micro-Amps + _output['oif411_ozone_current_uA'] = Math.round(_ozone_current_uA * 10000) / 10000; // 4 DP // Battery Voltage - _output['oif411_ozone_battery_v'] = parseInt(xdata.substr(13,2),16)*0.1; // Volts + _ozone_battery_v = parseInt(xdata.substr(13,2),16)*0.1; // Volts + _output['oif411_ozone_battery_v'] = Math.round(_ozone_battery_v * 10) / 10; // 1 DP // Ozone Pump Current - _output['oif411_ozone_pump_curr_mA'] = parseInt(xdata.substr(15,3),16); // mA + _ozone_pump_curr_mA = parseInt(xdata.substr(15,3),16); // mA + _output['oif411_ozone_pump_curr_mA'] = Math.round(_ozone_pump_curr_mA * 10) / 10; // 1 DP // External Voltage - _output['oif411_ext_voltage'] = parseInt(xdata.substr(18,2),16)*0.1; // Volts + _ext_voltage = parseInt(xdata.substr(18,2),16)*0.1; // Volts + _output['oif411_ext_voltage'] = Math.round(_ext_voltage * 10) / 10; // 1 DP // Now attempt to calculate the O3 partial pressure @@ -121,7 +126,8 @@ function parseOIF411(xdata, pressure){ Cef = get_oif411_Cef(pressure); // Calculate the pump efficiency correction. FlowRate = 28.5; // Use a 'nominal' value for Flow Rate (seconds per 100mL). - _output['oif411_O3_partial_pressure'] = (4.30851e-4)*(_output['oif411_ozone_current_uA'] - Ibg)*(_output['oif411_ozone_pump_temp']+273.15)*FlowRate*Cef; + _O3_partial_pressure = (4.30851e-4)*(_output['oif411_ozone_current_uA'] - Ibg)*(_output['oif411_ozone_pump_temp']+273.15)*FlowRate*Cef; // mPa + _output['oif411_O3_partial_pressure'] = Math.round(_O3_partial_pressure * 1000) / 1000; // 3 DP return _output From 6265d643950411d232babe1aab5f27a8e652a2f0 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sun, 9 Jan 2022 21:17:29 +1100 Subject: [PATCH 09/25] Initial CFH support --- js/xdata.js | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/js/xdata.js b/js/xdata.js index 07ae13c..39a54fa 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -64,6 +64,7 @@ function parseOIF411(xdata, pressure){ // Not an OIF411 (shouldn't get here) return {}; } + _output = {'xdata_instrument': 'OIF411'}; // Instrument number is common to all XDATA types. @@ -136,6 +137,60 @@ function parseOIF411(xdata, pressure){ } } +function parseCFH(xdata) { + // Attempt to parse an XDATA string from an CFH Cryogenic Frostpoint Hygrometer + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // https://eprints.lib.hokudai.ac.jp/dspace/bitstream/2115/72249/1/GRUAN-TD-5_MeiseiRadiosondes_v1_20180221.pdf + // + // Sample data: 0802E21FFD85C8CE078A0193 (length = 24 characters) + + // Cast to string if not already + xdata = String(xdata); + + // Run some checks over the input + if(xdata.length != 24){ + // Invalid CFH dataset + return {}; + } + + if(xdata.substr(0,2) !== '08'){ + // Not an CFH (shouldn't get here) + return {}; + } + + _output = {'xdata_instrument': 'CFH'}; + + // Instrument number is common to all XDATA types. + _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Mirror temperature + _mirror_temperature = parseInt(xdata.substr(4,6),16); + if ((_mirror_temperature & 0x80000) > 0) { + _mirror_temperature = _mirror_temperature - 0x1000000; + } + _mirror_temperature = _mirror_temperature*0.00001; // Degrees C + _output['cfh_mirror_temperature'] = Math.round(_mirror_temperature*100000) / 100000; // 5 DP + + // Optics voltage + _optics_voltage = parseInt(xdata.substr(10,6),16)*0.000001; // Volts + _output['cfh_optics_voltage'] = Math.round(_optics_voltage*1000000) / 1000000; // 6 DP + + // Optics temperature + _optics_temperature = parseInt(xdata.substr(16,4),16)*0.01; // Degrees C + if ((_optics_temperature & 0x8000) > 0) { + _optics_temperature = _optics_temperature - 0x10000; + } + _output['cfh_optics_temperature'] = Math.round(_optics_temperature*100) / 100; // 2 DP + + // CFH battery + _battery = parseInt(xdata.substr(20,4),16)*0.01; // Volts + _output['cfh_battery'] = Math.round(_battery*100) / 100; // 2 DP + + return _output +} + function parseXDATA(data, pressure){ // Accept an XDATA string, or multiple XDATA entries, delimited by '#' // Attempt to parse each one, and return an object @@ -144,6 +199,7 @@ function parseXDATA(data, pressure){ // "0501R20234850000006EI" // "0501034F02CA08B06700#800261FCA6F80012F6F40A75" // "800262358C080012FE6C0A70#0501035902BA08908400" + // "0802AC83D88AB61107A30175" // Split apart any contatenated xdata. if(data.includes('#')){ @@ -163,6 +219,8 @@ function parseXDATA(data, pressure){ if (_instrument === '01') { // V7 + // 0102 time=1001 cnt=0 rpm=0 + // 0102 time=1001 cnt=7 rpm=419 _output = {'xdata_instrument': 'V7'}; } else if (_instrument === '05'){ // OIF411 @@ -170,7 +228,8 @@ function parseXDATA(data, pressure){ _output = Object.assign(_output,_xdata_temp); } else if (_instrument === '08'){ // CFH - _output = {'xdata_instrument': 'CFH'}; + _xdata_temp = parseCFH(_current_xdata); + _output = Object.assign(_output,_xdata_temp); } else if (_instrument === '10'){ // FPH _output = {'xdata_instrument': 'FPH'}; @@ -188,6 +247,10 @@ function parseXDATA(data, pressure){ _output = {'xdata_instrument': 'OPC'}; } else if (_instrument === '3C'){ // PCFH + // 3c010000184b4b5754 + // 3c0103ce7b58647a98748befff + // 3c010148719fff8e54b9af627e249fe0 + // 3c01028d696fff8db4b7865980cdbbb3 _output = {'xdata_instrument': 'PCFH'}; } else if (_instrument === '3D'){ // FLASH-B From 98a407f1a15d05bca4e70791b13e3c0cd140a828 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 11:51:47 +1100 Subject: [PATCH 10/25] XDATA COBALD support --- js/xdata.js | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index 39a54fa..d5cf328 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -1,6 +1,6 @@ /* SondeHub XDATA Parser Library * - * Author: Mark Jessop + * Authors: Mark Jessop & Luke Prior */ // Pump Efficiency Correction Parameters for ECC-6A Ozone Sensor, with 3.0cm^3 volume. @@ -191,6 +191,76 @@ function parseCFH(xdata) { return _output } +function parseCOBALD(xdata) { + // Attempt to parse an XDATA string from a Compact Optical Backscatter Aerosol Detector + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // https://hobbydocbox.com/Radio/83430839-Cobald-operating-instructions-imet-configuration.html + // + // Sample data: 190213fffe005fcf00359943912cca (length = 30 characters) + + // Cast to string if not already + xdata = String(xdata); + + // Run some checks over the input + if(xdata.length != 30){ + // Invalid COBALD dataset + return {}; + } + + if(xdata.substr(0,2) !== '19'){ + // Not a COBALD (shouldn't get here) + return {}; + } + + _output = {'xdata_instrument': 'COBALD'}; + + // Instrument number is common to all XDATA types. + _output['cobald_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Sonde number + _output['cobald_sonde_number'] = parseInt(xdata.substr(4,3),16); + + // Internal temperature + _internal_temperature = parseInt(xdata.substr(7,3),16); + if ((_internal_temperature & 0x800) > 0) { + _internal_temperature = _internal_temperature - 0x1000; + } + _internal_temperature = _internal_temperature/8; // Degrees C + _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 10) / 10; // 1 DP + + // Blue backskatter + _blue_backskatter = parseInt(xdata.substr(10,6),16); + if ((_blue_backskatter & 0x800000) > 0) { + _blue_backskatter = _blue_backskatter - 0x1000000; + } + _output['cobald_blue_backskatter'] = _blue_backskatter; + + // Red backskatter + _red_backskatter = parseInt(xdata.substr(16,6),16); + if ((_red_backskatter & 0x800000) > 0) { + _red_backskatter = _red_backskatter - 0x1000000; + } + _output['cobald_red_backskatter'] = _red_backskatter; + + // Blue monitor + _blue_monitor = parseInt(xdata.substr(22,4),16); + if ((_blue_monitor & 0x8000) > 0) { + _blue_monitor = _blue_monitor - 0x10000; + } + _output['cobald_blue_monitor'] = _blue_monitor; + + // Red monitor + _red_monitor = parseInt(xdata.substr(26,4),16); + if ((_red_monitor & 0x8000) > 0) { + _red_monitor = _red_monitor - 0x10000; + } + _output['cobald_red_monitor'] = _red_monitor; + + return _output +} + function parseXDATA(data, pressure){ // Accept an XDATA string, or multiple XDATA entries, delimited by '#' // Attempt to parse each one, and return an object @@ -233,9 +303,10 @@ function parseXDATA(data, pressure){ } else if (_instrument === '10'){ // FPH _output = {'xdata_instrument': 'FPH'}; - } else if (_instrument === '18'){ + } else if (_instrument === '19'){ // COBALD - _output = {'xdata_instrument': 'COBALD'}; + _xdata_temp = parseCOBALD(_current_xdata); + _output = Object.assign(_output,_xdata_temp); } else if (_instrument === '28'){ // SLW _output = {'xdata_instrument': 'SLW'}; From 295d4517cec449b946801468ca7aa38a7a9933d2 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:07:46 +1100 Subject: [PATCH 11/25] fix multiple xdata --- js/xdata.js | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index d5cf328..de6619e 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -65,7 +65,7 @@ function parseOIF411(xdata, pressure){ return {}; } - _output = {'xdata_instrument': 'OIF411'}; + var _output = {'xdata_instrument': 'OIF411'}; // Instrument number is common to all XDATA types. _output['oif411_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -160,7 +160,7 @@ function parseCFH(xdata) { return {}; } - _output = {'xdata_instrument': 'CFH'}; + var _output = {'xdata_instrument': 'CFH'}; // Instrument number is common to all XDATA types. _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -214,7 +214,7 @@ function parseCOBALD(xdata) { return {}; } - _output = {'xdata_instrument': 'COBALD'}; + var _output = {'xdata_instrument': 'COBALD'}; // Instrument number is common to all XDATA types. _output['cobald_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -230,19 +230,19 @@ function parseCOBALD(xdata) { _internal_temperature = _internal_temperature/8; // Degrees C _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 10) / 10; // 1 DP - // Blue backskatter - _blue_backskatter = parseInt(xdata.substr(10,6),16); - if ((_blue_backskatter & 0x800000) > 0) { - _blue_backskatter = _blue_backskatter - 0x1000000; + // Blue backscatter + _blue_backscatter = parseInt(xdata.substr(10,6),16); + if ((_blue_backscatter & 0x800000) > 0) { + _blue_backscatter = _blue_backscatter - 0x1000000; } - _output['cobald_blue_backskatter'] = _blue_backskatter; + _output['cobald_blue_backscatter'] = _blue_backscatter; - // Red backskatter - _red_backskatter = parseInt(xdata.substr(16,6),16); - if ((_red_backskatter & 0x800000) > 0) { - _red_backskatter = _red_backskatter - 0x1000000; + // Red backckatter + _red_backscatter = parseInt(xdata.substr(16,6),16); + if ((_red_backscatter & 0x800000) > 0) { + _red_backscatter = _red_backscatter - 0x1000000; } - _output['cobald_red_backskatter'] = _red_backskatter; + _output['cobald_red_backscatter'] = _red_backscatter; // Blue monitor _blue_monitor = parseInt(xdata.substr(22,4),16); @@ -291,7 +291,7 @@ function parseXDATA(data, pressure){ // V7 // 0102 time=1001 cnt=0 rpm=0 // 0102 time=1001 cnt=7 rpm=419 - _output = {'xdata_instrument': 'V7'}; + _output['xdata_instrument'] = 'V7'; } else if (_instrument === '05'){ // OIF411 _xdata_temp = parseOIF411(_current_xdata, pressure); @@ -302,42 +302,42 @@ function parseXDATA(data, pressure){ _output = Object.assign(_output,_xdata_temp); } else if (_instrument === '10'){ // FPH - _output = {'xdata_instrument': 'FPH'}; + _output['xdata_instrument'] = 'FPH'; } else if (_instrument === '19'){ // COBALD _xdata_temp = parseCOBALD(_current_xdata); _output = Object.assign(_output,_xdata_temp); } else if (_instrument === '28'){ // SLW - _output = {'xdata_instrument': 'SLW'}; + _output['xdata_instrument'] = 'SLW'; } else if (_instrument === '38'){ // POPS - _output = {'xdata_instrument': 'POPS'}; + _output['xdata_instrument'] = 'POPS'; } else if (_instrument === '39'){ // OPC - _output = {'xdata_instrument': 'OPC'}; + _output['xdata_instrument'] = 'OPC'; } else if (_instrument === '3C'){ // PCFH // 3c010000184b4b5754 // 3c0103ce7b58647a98748befff // 3c010148719fff8e54b9af627e249fe0 // 3c01028d696fff8db4b7865980cdbbb3 - _output = {'xdata_instrument': 'PCFH'}; + _output['xdata_instrument'] = 'PCFH'; } else if (_instrument === '3D'){ // FLASH-B - _output = {'xdata_instrument': 'FLASH-B'}; + _output['xdata_instrument'] = 'FLASH-B'; } else if (_instrument === '3E'){ // TRAPS - _output = {'xdata_instrument': 'TRAPS'}; + _output['xdata_instrument'] = 'TRAPS'; } else if (_instrument === '3F'){ // SKYDEW - _output = {'xdata_instrument': 'SKYDEW'}; + _output['xdata_instrument'] = 'SKYDEW'; } else if (_instrument === '41'){ // CICANUM - _output = {'xdata_instrument': 'CICANUM'}; + _output['xdata_instrument'] = 'CICANUM'; } else if (_instrument === '45'){ // POPS - _output = {'xdata_instrument': 'POPS'}; + _output['xdata_instrument'] = 'POPS'; } else if (_instrument === '80'){ // Unknown! //console.log("Saw unknown XDATA instrument 0x80.") From c0106b57d88fc83442f4f1a951da3673b2656a3a Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 12:21:29 +1100 Subject: [PATCH 12/25] HEX to Int helper function --- js/xdata.js | 53 +++++++++++++++++++++-------------------------------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index de6619e..fa4c390 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -9,6 +9,19 @@ OIF411_Cef_Pressure = [ 0, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500, 1000, 1100]; OIF411_Cef = [ 1.171, 1.171, 1.131, 1.092, 1.055, 1.032, 1.022, 1.015, 1.011, 1.008, 1.006, 1.004, 1, 1]; +// https://stackoverflow.com/a/34679269/9389353 +function hexToInt(hex) { + // Helper function to convert a signed hex value to an integer + if (hex.length % 2 != 0) { + hex = "0" + hex; + } + var num = parseInt(hex, 16); + var maxVal = Math.pow(2, hex.length / 2 * 8); + if (num > maxVal / 2 - 1) { + num = num - maxVal + } + return num; +} function lerp(x, y, a){ // Helper function for linear interpolation between two points @@ -97,10 +110,7 @@ function parseOIF411(xdata, pressure){ } else if (xdata.length == 20){ // Measurement Data (Table 18) // Ozone pump temperature - signed int16 - _ozone_pump_temp = parseInt(xdata.substr(4,4),16); - if ((_ozone_pump_temp & 0x8000) > 0) { - _ozone_pump_temp = _ozone_pump_temp - 0x10000; - } + _ozone_pump_temp = hexToInt(xdata.substr(4,4)); _ozone_pump_temp = _ozone_pump_temp*0.01; // Degrees C _output['oif411_ozone_pump_temp'] = Math.round(_ozone_pump_temp * 10) / 10; // 1 DP @@ -166,10 +176,7 @@ function parseCFH(xdata) { _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); // Mirror temperature - _mirror_temperature = parseInt(xdata.substr(4,6),16); - if ((_mirror_temperature & 0x80000) > 0) { - _mirror_temperature = _mirror_temperature - 0x1000000; - } + _mirror_temperature = hexToInt(xdata.substr(4,6)); _mirror_temperature = _mirror_temperature*0.00001; // Degrees C _output['cfh_mirror_temperature'] = Math.round(_mirror_temperature*100000) / 100000; // 5 DP @@ -178,10 +185,7 @@ function parseCFH(xdata) { _output['cfh_optics_voltage'] = Math.round(_optics_voltage*1000000) / 1000000; // 6 DP // Optics temperature - _optics_temperature = parseInt(xdata.substr(16,4),16)*0.01; // Degrees C - if ((_optics_temperature & 0x8000) > 0) { - _optics_temperature = _optics_temperature - 0x10000; - } + _optics_temperature = hexToInt(xdata.substr(16,4))*0.01; // Degrees C _output['cfh_optics_temperature'] = Math.round(_optics_temperature*100) / 100; // 2 DP // CFH battery @@ -223,39 +227,24 @@ function parseCOBALD(xdata) { _output['cobald_sonde_number'] = parseInt(xdata.substr(4,3),16); // Internal temperature - _internal_temperature = parseInt(xdata.substr(7,3),16); - if ((_internal_temperature & 0x800) > 0) { - _internal_temperature = _internal_temperature - 0x1000; - } + _internal_temperature = hexToInt(xdata.substr(7,3)); _internal_temperature = _internal_temperature/8; // Degrees C _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 10) / 10; // 1 DP // Blue backscatter - _blue_backscatter = parseInt(xdata.substr(10,6),16); - if ((_blue_backscatter & 0x800000) > 0) { - _blue_backscatter = _blue_backscatter - 0x1000000; - } + _blue_backscatter = hexToInt(xdata.substr(10,6)); _output['cobald_blue_backscatter'] = _blue_backscatter; // Red backckatter - _red_backscatter = parseInt(xdata.substr(16,6),16); - if ((_red_backscatter & 0x800000) > 0) { - _red_backscatter = _red_backscatter - 0x1000000; - } + _red_backscatter = hexToInt(xdata.substr(16,6)); _output['cobald_red_backscatter'] = _red_backscatter; // Blue monitor - _blue_monitor = parseInt(xdata.substr(22,4),16); - if ((_blue_monitor & 0x8000) > 0) { - _blue_monitor = _blue_monitor - 0x10000; - } + _blue_monitor = hexToInt(xdata.substr(22,4)); _output['cobald_blue_monitor'] = _blue_monitor; // Red monitor - _red_monitor = parseInt(xdata.substr(26,4),16); - if ((_red_monitor & 0x8000) > 0) { - _red_monitor = _red_monitor - 0x10000; - } + _red_monitor = hexToInt(xdata.substr(26,4)); _output['cobald_red_monitor'] = _red_monitor; return _output From 573dc84bd82173941a26058cfe518feedcf00ac0 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:23:33 +1100 Subject: [PATCH 13/25] list all XDATA instruments --- js/tracker.js | 10 +++++----- js/xdata.js | 38 ++++++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index 7f34dfb..df5a7a6 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -3158,7 +3158,7 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { } var tempXDATA = parseXDATA(data.xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { - html += "
XDATA Instrument: " + tempXDATA.xdata_instrument + "
"; + html += "
XDATA Instrument: " + tempXDATA.xdata_instrument.join(', ') + "
"; } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v + " V
"; @@ -4249,7 +4249,7 @@ function formatData(data, live) { } var tempXDATA = parseXDATA(data[entry].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; @@ -4370,7 +4370,7 @@ function formatData(data, live) { } var tempXDATA = parseXDATA(data.xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; @@ -4474,7 +4474,7 @@ function formatData(data, live) { } var tempXDATA = parseXDATA(data[key][i].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; @@ -4609,7 +4609,7 @@ function formatData(data, live) { } var tempXDATA = parseXDATA(data[i].xdata, xdata_pressure); if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument; + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); } if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; diff --git a/js/xdata.js b/js/xdata.js index fa4c390..c55fa84 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -78,7 +78,7 @@ function parseOIF411(xdata, pressure){ return {}; } - var _output = {'xdata_instrument': 'OIF411'}; + var _output = {}; // Instrument number is common to all XDATA types. _output['oif411_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -148,7 +148,7 @@ function parseOIF411(xdata, pressure){ } function parseCFH(xdata) { - // Attempt to parse an XDATA string from an CFH Cryogenic Frostpoint Hygrometer + // Attempt to parse an XDATA string from a CFH Cryogenic Frostpoint Hygrometer // Returns an object with parameters to be added to the sondes telemetry. // // References: @@ -170,7 +170,7 @@ function parseCFH(xdata) { return {}; } - var _output = {'xdata_instrument': 'CFH'}; + var _output = {}; // Instrument number is common to all XDATA types. _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -196,7 +196,7 @@ function parseCFH(xdata) { } function parseCOBALD(xdata) { - // Attempt to parse an XDATA string from a Compact Optical Backscatter Aerosol Detector + // Attempt to parse an XDATA string from a COBALD Compact Optical Backscatter Aerosol Detector // Returns an object with parameters to be added to the sondes telemetry. // // References: @@ -218,7 +218,7 @@ function parseCOBALD(xdata) { return {}; } - var _output = {'xdata_instrument': 'COBALD'}; + var _output = {}; // Instrument number is common to all XDATA types. _output['cobald_instrument_number'] = parseInt(xdata.substr(2,2),16); @@ -268,6 +268,7 @@ function parseXDATA(data, pressure){ } _output = {}; + _instruments = []; for(xdata_i = 0; xdata_i < data_split.length; xdata_i++){ _current_xdata = data_split[xdata_i]; @@ -280,53 +281,56 @@ function parseXDATA(data, pressure){ // V7 // 0102 time=1001 cnt=0 rpm=0 // 0102 time=1001 cnt=7 rpm=419 - _output['xdata_instrument'] = 'V7'; + if (!_instruments.includes("V7")) _instruments.push('V7'); } else if (_instrument === '05'){ // OIF411 _xdata_temp = parseOIF411(_current_xdata, pressure); _output = Object.assign(_output,_xdata_temp); + if (!_instruments.includes("OIF411")) _instruments.push('OIF411'); } else if (_instrument === '08'){ // CFH _xdata_temp = parseCFH(_current_xdata); _output = Object.assign(_output,_xdata_temp); + if (!_instruments.includes("CFH")) _instruments.push('CFH'); } else if (_instrument === '10'){ // FPH - _output['xdata_instrument'] = 'FPH'; + if (!_instruments.includes("FPH")) _instruments.push('FPH'); } else if (_instrument === '19'){ // COBALD _xdata_temp = parseCOBALD(_current_xdata); _output = Object.assign(_output,_xdata_temp); + if (!_instruments.includes("COBALD")) _instruments.push('COBALD'); } else if (_instrument === '28'){ // SLW - _output['xdata_instrument'] = 'SLW'; + if (!_instruments.includes("SLW")) _instruments.push('SLW'); } else if (_instrument === '38'){ // POPS - _output['xdata_instrument'] = 'POPS'; + if (!_instruments.includes("POPS")) _instruments.push('POPS'); } else if (_instrument === '39'){ // OPC - _output['xdata_instrument'] = 'OPC'; + if (!_instruments.includes("OPC")) _instruments.push('OPC'); } else if (_instrument === '3C'){ // PCFH // 3c010000184b4b5754 // 3c0103ce7b58647a98748befff // 3c010148719fff8e54b9af627e249fe0 // 3c01028d696fff8db4b7865980cdbbb3 - _output['xdata_instrument'] = 'PCFH'; + if (!_instruments.includes("PCFH")) _instruments.push('PCFH'); } else if (_instrument === '3D'){ // FLASH-B - _output['xdata_instrument'] = 'FLASH-B'; + if (!_instruments.includes("FLASH-B")) _instruments.push('FLASH-B'); } else if (_instrument === '3E'){ // TRAPS - _output['xdata_instrument'] = 'TRAPS'; + if (!_instruments.includes("TRAPS")) _instruments.push('TRAPS'); } else if (_instrument === '3F'){ // SKYDEW - _output['xdata_instrument'] = 'SKYDEW'; + if (!_instruments.includes("SKYDEW")) _instruments.push('SKYDEW'); } else if (_instrument === '41'){ // CICANUM - _output['xdata_instrument'] = 'CICANUM'; + if (!_instruments.includes("CICANUM")) _instruments.push('CICANUM'); } else if (_instrument === '45'){ // POPS - _output['xdata_instrument'] = 'POPS'; + if (!_instruments.includes("POPS")) _instruments.push('POPS'); } else if (_instrument === '80'){ // Unknown! //console.log("Saw unknown XDATA instrument 0x80.") @@ -336,6 +340,8 @@ function parseXDATA(data, pressure){ } } + _output["xdata_instrument"] = _instruments; + return _output } \ No newline at end of file From 5921a8e811f8719fbb95d62d15a6cdf8743da635 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 17:41:56 +1100 Subject: [PATCH 14/25] Delete predictions button --- js/tracker.js | 87 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 3 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index df5a7a6..14714bb 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -31,6 +31,9 @@ var recovery_names = []; var recoveries = []; var launchPredictions = {}; +var stationMarkerLookup = {}; +var markerStationLookup = {}; +var predictionAjax = []; var stationHistoricalData = {}; var historicalPlots = {}; @@ -670,6 +673,28 @@ function load() { L.control.historicalontrol({ position: 'topleft' }).addTo(map); + L.Control.PredictionControl = L.Control.extend({ + onAdd: function(map) { + var div = L.DomUtil.create('div'); + + div.innerHTML = ''; + div.id = "predictionControlButton"; + div.style.display = "none"; + + return div; + }, + + onRemove: function(map) { + // Nothing to do here + } + }); + + L.control.predictionontrol = function(opts) { + return new L.Control.PredictionControl(opts); + } + + L.control.predictionontrol({ position: 'topleft' }).addTo(map); + // update current position if we geolocation is available if(currentPosition) updateCurrentPosition(currentPosition.lat, currentPosition.lon); @@ -1043,9 +1068,15 @@ function deleteHistoricalButton() { for (let serial in historicalPlots[station].sondes) { map.removeLayer(historicalPlots[station].sondes[serial].marker); } + var realpopup = launches.getLayer(stationMarkerLookup[station]).getPopup(); var popup = $("#popup" + station); var deleteHistorical = popup.find("#deleteHistorical"); deleteHistorical.hide(); + if (!realpopup.isOpen()) { + var tempContent = $(realpopup.getContent()); + tempContent.find("#deleteHistorical").hide(); + realpopup.setContent("
" + tempContent.html() + "
"); + } } } @@ -1061,6 +1092,44 @@ function deleteHistoricalButton() { } +function deletePredictionButton() { + var predictionDelete = $("#predictionControlButton"); + + for (var marker in launchPredictions) { + if (launchPredictions.hasOwnProperty(marker)) { + for (var prediction in launchPredictions[marker]) { + if (launchPredictions[marker].hasOwnProperty(prediction)) { + for (var object in launchPredictions[marker][prediction]) { + if (launchPredictions[marker][prediction].hasOwnProperty(object)) { + map.removeLayer(launchPredictions[marker][prediction][object]); + } + } + } + } + var realpopup = launches.getLayer(marker).getPopup(); + var popup = $("#popup" + markerStationLookup[marker]); + var deletePrediction = popup.find("#predictionDeleteButton"); + deletePrediction.hide(); + if (!realpopup.isOpen()) { + var tempContent = $(realpopup.getContent()); + tempContent.find("#predictionDeleteButton").hide(); + realpopup.setContent("
" + tempContent.html() + "
"); + } + } + } + + launchPredictions = {}; + + for (i=0; i < predictionAjax.length; i++) { + predictionAjax[i].abort(); + } + + predictionAjax = []; + + predictionDelete.hide(); + +} + // Master function to display historic summaries function showHistorical (station, marker) { var popup = $("#popup" + station); @@ -1368,6 +1437,8 @@ function launchSitePredictions(times, station, properties, marker, id) { dates.push(date.toISOString().split('.')[0]+"Z"); } var completed = 0; + var predictionDelete = $("#predictionControlButton"); + predictionDelete.show(); for (var i = 0; i < dates.length; i++) { var lon = ((360 + (position[1] % 360)) % 360); //var url = "https://predict.cusf.co.uk/api/v1/?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1]; @@ -1378,7 +1449,7 @@ function launchSitePredictions(times, station, properties, marker, id) { completed += 1; plotPrediction(data, dates, marker, properties); if (completed == dates.length) { - predictionDeleteButton.show(); + if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); predictionButton.show(); predictionButtonLoading.hide(); if (!realpopup.isOpen()) { @@ -1389,7 +1460,7 @@ function launchSitePredictions(times, station, properties, marker, id) { function handleError(error) { completed += 1; if (completed == dates.length) { - predictionDeleteButton.show(); + if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); predictionButton.show(); predictionButtonLoading.hide(); if (!realpopup.isOpen()) { @@ -1474,14 +1545,17 @@ function plotPrediction (data, dates, marker, properties) { } function showPrediction(url) { - return $.ajax({ + var ajaxReq = $.ajax({ type: "GET", url: url, dataType: "json", }); + predictionAjax.push(ajaxReq); + return ajaxReq; } function deletePredictions(marker, station) { + var predictionDelete = $("#predictionControlButton"); if (launchPredictions.hasOwnProperty(marker)) { for (var prediction in launchPredictions[marker]) { if (launchPredictions[marker].hasOwnProperty(prediction)) { @@ -1492,12 +1566,14 @@ function deletePredictions(marker, station) { } } } + delete launchPredictions[marker]; } var popup = $("#popup" + station); var predictionDeleteButton = popup.find("#predictionDeleteButton"); if (predictionDeleteButton.is(':visible')) { predictionDeleteButton.hide(); } + if (Object.keys(launchPredictions).length == 0) predictionDelete.hide(); } function getLaunchSites() { @@ -1652,6 +1728,11 @@ function generateLaunchSites() { div.innerHTML = popupContent; popup.setContent(div.innerHTML); + + var leafletID = launches.getLayerId(marker); + + stationMarkerLookup[key] = leafletID; + markerStationLookup[leafletID] = key; } } if (focusID != 0) { From 734dd3f2a4aa6cab993cf20e2c56285bc46763b6 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:04:30 +1100 Subject: [PATCH 15/25] Split station functions to seperate file + fixes --- build.sh | 2 + js/format.js | 322 ++++++++++ js/station.js | 963 ++++++++++++++++++++++++++++ js/tracker.js | 1671 ++++--------------------------------------------- js/xdata.js | 57 +- 5 files changed, 1445 insertions(+), 1570 deletions(-) create mode 100644 js/format.js create mode 100644 js/station.js diff --git a/build.sh b/build.sh index b6ac176..fd6e6d2 100755 --- a/build.sh +++ b/build.sh @@ -26,6 +26,8 @@ java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations - java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge app.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge colour-map.js >> mobile.js java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge xdata.js >> mobile.js +java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge station.js >> mobile.js +java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge format.js >> mobile.js #compile plot lib and config java -jar "../tools/yuicompressor-2.4.8.jar" --type=js --disable-optimizations --nomunge _jquery.flot.js >> init_plot.js diff --git a/js/format.js b/js/format.js new file mode 100644 index 0000000..11cfe8b --- /dev/null +++ b/js/format.js @@ -0,0 +1,322 @@ +/* SondeHub Tracker Format Incoming Data + * + * Author: Luke Prior + */ + +function formatData(data, live) { + var response = {}; + response.positions = {}; + var dataTemp = []; + if (live) { // Websockets + for (let entry in data) { + var dataTempEntry = {}; + var station = data[entry].uploader_callsign; + dataTempEntry.callsign = {}; + //check if other stations also received this packet + if (vehicles.hasOwnProperty(data[entry].serial)) { + if (data[entry].datetime == vehicles[data[entry].serial].curr_position.gps_time) { + for (let key in vehicles[data[entry].serial].curr_position.callsign) { + if (vehicles[data[entry].serial].curr_position.callsign.hasOwnProperty(key)) { + if (key != station) { + dataTempEntry.callsign[key] = {}; + if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("snr")) { + dataTempEntry.callsign[key].snr = vehicles[data[entry].serial].curr_position.callsign[key].snr; + } + if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("rssi")) { + dataTempEntry.callsign[key].rssi = vehicles[data[entry].serial].curr_position.callsign[key].rssi; + } + if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("frequency")) { + dataTempEntry.callsign[key].frequency = vehicles[data[entry].serial].curr_position.callsign[key].frequency; + } + } + } + } + } + } + dataTempEntry.callsign[station] = {}; + if (data[entry].snr) { + dataTempEntry.callsign[station].snr = data[entry].snr; + } + if (data[entry].rssi) { + dataTempEntry.callsign[station].rssi = data[entry].rssi; + } + if (data[entry].frequency) { + dataTempEntry.callsign[station].frequency = data[entry].frequency; + } + dataTempEntry.gps_alt = data[entry].alt; + dataTempEntry.gps_lat = data[entry].lat; + dataTempEntry.gps_lon = data[entry].lon; + if (data[entry].heading) { + dataTempEntry.gps_heading = data[entry].heading; + } + dataTempEntry.gps_time = data[entry].datetime; + dataTempEntry.server_time = data[entry].datetime; + dataTempEntry.vehicle = data[entry].serial; + dataTempEntry.position_id = data[entry].serial + "-" + data[entry].datetime; + dataTempEntry.data = {}; + if (data[entry].batt) { + dataTempEntry.data.batt = data[entry].batt; + } + if (data[entry].burst_timer) { + dataTempEntry.data.burst_timer = data[entry].burst_timer; + } + if (data[entry].frequency) { + dataTempEntry.data.frequency = data[entry].frequency; + } + if (data[entry].tx_frequency) { + dataTempEntry.data.frequency_tx = data[entry].tx_frequency; + } + if (data[entry].hasOwnProperty("humidity")) { + dataTempEntry.data.humidity = data[entry].humidity; + } + if (data[entry].manufacturer) { + dataTempEntry.data.manufacturer = data[entry].manufacturer; + } + if (data[entry].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[entry].pressure; + } + if (data[entry].sats) { + dataTempEntry.data.sats = data[entry].sats; + } + if (data[entry].hasOwnProperty("temp")) { + dataTempEntry.data.temperature_external = data[entry].temp; + } + if (data[entry].type) { + dataTempEntry.data.type = data[entry].type; + dataTempEntry.type = data[entry].type; + } + if (data[entry].subtype) { + dataTempEntry.data.type = data[entry].subtype; + dataTempEntry.type = data[entry].subtype; + } + if (data[entry].xdata) { + dataTempEntry.data.xdata = data[entry].xdata; + + if (data[entry].hasOwnProperty("pressure")) { + xdata_pressure = data[entry].pressure; + } else { + xdata_pressure = 1100.0; + } + + var tempXDATA = parseXDATA(data[entry].xdata, xdata_pressure); + for (let field in tempXDATA) { + if (tempXDATA.hasOwnProperty(field)) { + if (field == "xdata_instrument") { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); + } else { + dataTempEntry.data[field] = tempXDATA[field]; + } + } + } + } + if (data[entry].serial.toLowerCase() != "xxxxxxxx") { + dataTemp.push(dataTempEntry); + } + } + } else if (data.length == null) { // Elasticsearch + for (let key in data) { + if (data.hasOwnProperty(key)) { + if (typeof data[key] === 'object') { + for (let i in data[key]) { + var dataTempEntry = {}; + var station = data[key][i].uploader_callsign; + dataTempEntry.callsign = {}; + dataTempEntry.callsign[station] = {}; + if (data[key][i].snr) { + dataTempEntry.callsign[station].snr = data[key][i].snr; + } + if (data[key][i].rssi) { + dataTempEntry.callsign[station].rssi = data[key][i].rssi; + } + if (data[key][i].frequency) { + dataTempEntry.callsign[station].frequency = data[key][i].frequency; + } + dataTempEntry.gps_alt = data[key][i].alt; + dataTempEntry.gps_lat = data[key][i].lat; + dataTempEntry.gps_lon = data[key][i].lon; + if (data[key][i].heading) { + dataTempEntry.gps_heading = data[key][i].heading; + } + dataTempEntry.gps_time = data[key][i].datetime; + dataTempEntry.server_time = data[key][i].datetime; + dataTempEntry.vehicle = data[key][i].serial; + dataTempEntry.position_id = data[key][i].serial + "-" + data[key][i].datetime; + dataTempEntry.data = {}; + if (data[key][i].batt) { + dataTempEntry.data.batt = data[key][i].batt; + } + if (data[key][i].burst_timer) { + dataTempEntry.data.burst_timer = data[key][i].burst_timer; + } + if (data[key][i].frequency) { + dataTempEntry.data.frequency = data[key][i].frequency; + } + if (data[key][i].tx_frequency) { + dataTempEntry.data.frequency_tx = data[key][i].tx_frequency; + } + if (data[key][i].hasOwnProperty("humidity")) { + dataTempEntry.data.humidity = data[key][i].humidity; + } + if (data[key][i].manufacturer) { + dataTempEntry.data.manufacturer = data[key][i].manufacturer; + } + if (data[key][i].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[key][i].pressure; + } + if (data[key][i].sats) { + dataTempEntry.data.sats = data[key][i].sats; + } + if (data[key][i].hasOwnProperty("temp")) { + dataTempEntry.data.temperature_external = data[key][i].temp; + } + if (data[key][i].type) { + dataTempEntry.data.type = data[key][i].type; + dataTempEntry.type = data[key][i].type; + } + if (data[key][i].subtype) { + dataTempEntry.data.type = data[key][i].subtype; + dataTempEntry.type = data[key][i].subtype; + } + if (data[key][i].xdata) { + dataTempEntry.data.xdata = data[key][i].xdata; + if (data[key][i].hasOwnProperty("pressure")) { + xdata_pressure = data[key][i].pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data[key][i].xdata, xdata_pressure); + for (let field in tempXDATA) { + if (tempXDATA.hasOwnProperty(field)) { + if (field == "xdata_instrument") { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); + } else { + dataTempEntry.data[field] = tempXDATA[field]; + } + } + } + } + if (data[key][i].serial.toLowerCase() != "xxxxxxxx") { + dataTemp.push(dataTempEntry); + } + } + } + } + } + } else { // AWS + for (var i = data.length - 1; i >= 0; i--) { + if (data[i].hasOwnProperty('subtype') && data[i].subtype == "SondehubV1") { // SondeHub V1 + var dataTempEntry = {}; + var station = data[i].uploader_callsign; + dataTempEntry.callsign = {}; + dataTempEntry.callsign[station] = {}; + dataTempEntry.gps_alt = parseFloat(data[i].alt); + dataTempEntry.gps_lat = parseFloat(data[i].lat); + dataTempEntry.gps_lon = parseFloat(data[i].lon); + dataTempEntry.gps_time = data[i].time_received; + dataTempEntry.server_time = data[i].time_received; + dataTempEntry.vehicle = data[i].serial; + dataTempEntry.position_id = data[i].serial + "-" + data[i].time_received; + dataTempEntry.data = {}; + if (data[i].humidity) { + dataTempEntry.data.humidity = parseFloat(data[i].humidity); + } + if (data[i].temp) { + dataTempEntry.data.temperature_external = parseFloat(data[i].temp); + } + dataTemp.push(dataTempEntry); + } else { // SondeHub V2 + var dataTempEntry = {}; + var station = data[i].uploader_callsign; + dataTempEntry.callsign = {}; + dataTempEntry.callsign[station] = {}; + if (data[i].snr) { + dataTempEntry.callsign[station].snr = data[i].snr; + } + if (data[i].rssi) { + dataTempEntry.callsign[station].rssi = data[i].rssi; + } + if (data[i].frequency) { + dataTempEntry.callsign[station].frequency = data[i].frequency; + } + dataTempEntry.gps_alt = data[i].alt; + dataTempEntry.gps_lat = data[i].lat; + dataTempEntry.gps_lon = data[i].lon; + if (data[i].heading) { + dataTempEntry.gps_heading = data[i].heading; + } + dataTempEntry.gps_time = data[i].datetime; + dataTempEntry.server_time = data[i].datetime; + dataTempEntry.vehicle = data[i].serial; + dataTempEntry.position_id = data[i].serial + "-" + data[i].datetime; + dataTempEntry.data = {}; + if (data[i].batt) { + dataTempEntry.data.batt = data[i].batt; + } + if (data[i].burst_timer) { + dataTempEntry.data.burst_timer = data[i].burst_timer; + } + if (data[i].frequency) { + dataTempEntry.data.frequency = data[i].frequency; + } + if (data[i].tx_frequency) { + dataTempEntry.data.frequency_tx = data[i].tx_frequency; + } + if (data[i].hasOwnProperty("humidity")) { + dataTempEntry.data.humidity = data[i].humidity; + } + if (data[i].manufacturer) { + dataTempEntry.data.manufacturer = data[i].manufacturer; + } + if (data[i].hasOwnProperty("pressure")) { + dataTempEntry.data.pressure = data[i].pressure; + } + if (data[i].sats) { + dataTempEntry.data.sats = data[i].sats; + } + if (data[i].hasOwnProperty("temp")) { + dataTempEntry.data.temperature_external = data[i].temp; + } + if (data[i].type && data[i].type == "payload_telemetry") { // SondeHub V1.5 data? + var comment = data[i].comment.split(" "); + if (v1types.hasOwnProperty(comment[0])) { + dataTempEntry.data.type = v1types[comment[0]]; + dataTempEntry.type = v1types[comment[0]]; + if (v1manufacturers.hasOwnProperty(dataTempEntry.type)) { + dataTempEntry.data.manufacturer = v1manufacturers[dataTempEntry.type]; + } + } + dataTempEntry.data.frequency = comment[2]; + } else if (data[i].type) { + dataTempEntry.data.type = data[i].type; + dataTempEntry.type = data[i].type; + } + if (data[i].subtype) { + dataTempEntry.data.type = data[i].subtype; + dataTempEntry.type = data[i].subtype; + } + if (data[i].xdata) { + dataTempEntry.data.xdata = data[i].xdata; + if (data[i].hasOwnProperty("pressure")) { + xdata_pressure = data[i].pressure; + } else { + xdata_pressure = 1100.0; + } + var tempXDATA = parseXDATA(data[i].xdata, xdata_pressure); + for (let field in tempXDATA) { + if (tempXDATA.hasOwnProperty(field)) { + if (field == "xdata_instrument") { + dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); + } else { + dataTempEntry.data[field] = tempXDATA[field]; + } + } + } + } + dataTemp.push(dataTempEntry); + } + } + } + response.positions.position = dataTemp; + response.fetch_timestamp = Date.now(); + return response; +} \ No newline at end of file diff --git a/js/station.js b/js/station.js new file mode 100644 index 0000000..20ea504 --- /dev/null +++ b/js/station.js @@ -0,0 +1,963 @@ +/* SondeHub Tracker Station Popup Functions + * + * Author: Luke Prior + */ + +var launches_url = "https://api.v2.sondehub.org/sites"; +var sites = null; + +// Calculate the number of historical records for selected date range +// Set selectable months for selected year +function getSelectedNumber (station) { + var popup = $("#popup" + station); + var targetyear = popup.find("#yearList option:selected").val(); + var targetmonth = popup.find("#monthList option:selected").val(); + var count = 0; + var data = stationHistoricalData[station]; + + // Calculate count + for (let year in data) { + if (data.hasOwnProperty(year)) { + if (year == targetyear || targetyear == "all") { + for (let month in data[year]) { + if (data[year].hasOwnProperty(month)) { + if (month == targetmonth || targetmonth == "all" || targetyear == "all") { + count += data[year][month].length; + } + } + } + } + } + } + + // Update selected field & hide months if no data + var months = ["all", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; + popup.find('#yearList option').each(function() { + if ($(this).is(':selected')) { + $(this).attr("selected", "selected"); + var selectedYear = $(this).val(); + if (selectedYear != "all") { + months = Object.keys(data[selectedYear]); + months.push("all"); + } + } else { + $(this).attr("selected", false); + } + }); + + popup.find('#monthList option').each(function() { + if (!months.includes($(this).val())) { + $(this).hide(); + } else { + $(this).show(); + } + if ($(this).is(':selected')) { + $(this).attr("selected", "selected"); + } else { + $(this).attr("selected", false); + } + }); + + // Update popup + popup.find("#launchCount").text(count); +} + +// Get initial summary data for station, courtesy of TimMcMahon +function getHistorical (id, callback, continuation) { + var prefix = 'launchsites/' + id + '/'; + var params = { + Prefix: prefix, + }; + + if (typeof continuation !== 'undefined') { + params.ContinuationToken = continuation; + } else { + tempLaunchData = {}; + } + + s3.makeUnauthenticatedRequest('listObjectsV2', params, function(err, data) { + if (err) { + console.log(err, err.stack); + } else { + var tempSerials = []; + for (var i = 0; i < data.Contents.length; i++) { + // Sort data into year and month groups + var date = data.Contents[i].Key.substring(prefix.length).substring(0,10); + var year = date.substring(0,4); + var month = date.substring(5,7); + var serial = data.Contents[i].Key.substring(prefix.length+11).slice(0, -5); + if (tempLaunchData.hasOwnProperty(year)) { + if (tempLaunchData[year].hasOwnProperty(month)) { + if (!tempSerials.includes(serial)) { + tempSerials.push(serial) + tempLaunchData[year][month].push(data.Contents[i].Key); + } + } else { + tempLaunchData[year][month] = []; + tempSerials = []; + tempSerials.push(serial) + tempLaunchData[year][month].push(data.Contents[i].Key); + } + } else { + tempLaunchData[year] = {}; + tempLaunchData[year][month] = []; + tempSerials = []; + tempSerials.push(serial) + tempLaunchData[year][month].push(data.Contents[i].Key); + } + } + if (data.IsTruncated == true) { + // Requests are limited to 1000 entries so multiple may be required + getHistorical(id, callback, data.NextContinuationToken); + } else { + callback(tempLaunchData); + } + } + }); +} + +// Download summary data from AWS S3 +function downloadHistorical (suffix) { + var url = "https://sondehub-history.s3.amazonaws.com/" + suffix; + var ajaxReq = $.ajax({ + type: "GET", + url: url, + dataType: "json", + tryCount : 0, + retryLimit : 3, // Retry max of 3 times + error : function(xhr, textStatus, errorThrown ) { + if (textStatus == 'timeout') { + this.tryCount++; + if (this.tryCount <= this.retryLimit) { + //try again + $.ajax(this); + return; + } + return; + } + } + }); + historicalAjax.push(ajaxReq); + return ajaxReq; +} + +// Draw historic summaries to map +function drawHistorical (data, station) { + var landing = data[2]; + var serial = landing.serial; + var time = landing.datetime; + + if (!historicalPlots[station].sondes.hasOwnProperty(serial)) { + + historicalPlots[station].sondes[serial] = {}; + + // Using last known alt to detmine colour + var minAlt = 0; + var actualAlt = landing.alt; + var maxAlt = 10000; + + if (actualAlt > maxAlt) { + actualAlt = maxAlt; + } else if (actualAlt < minAlt) { + actualAlt = minAlt; + } + + var normalisedAlt = ((actualAlt-minAlt)/(maxAlt-minAlt)); + var iconColour = ConvertRGBtoHex(evaluate_cmap(normalisedAlt, 'turbo')); + + // Check if we have recovery data for it + var recovered = false; + if (historicalPlots[station].data.hasOwnProperty("recovered")) { + if (historicalPlots[station].data.recovered.hasOwnProperty(serial)) { + var recovery_info = historicalPlots[station].data.recovered[serial]; + recovered = true; + } + } + + var popup = L.popup(); + + html = "
"; + html += "
"+serial+" ("+time+")
"; + html += "
"; + html += "
 "+roundNumber(landing.lat, 5) + ', ' + roundNumber(landing.lon, 5)+"
"; + + var imp = offline.get('opt_imperial'); + var text_alt = Number((imp) ? Math.floor(3.2808399 * parseInt(landing.alt)) : parseInt(landing.alt)).toLocaleString("us"); + text_alt += " " + ((imp) ? 'ft':'m'); + + html += "
Altitude: "+text_alt+"
"; + html += "
Time: "+formatDate(stringToDateUTC(time))+"
"; + + if (landing.hasOwnProperty("type")) { + html += "
Sonde Type: " + landing.type + "
"; + }; + + html += "
"; + + if (recovered) { + html += "
"+(recovery_info.recovered ? "Recovered by " : "Not Recovered by ")+recovery_info.recovered_by+"
"; + html += "
Recovery time: "+formatDate(stringToDateUTC(recovery_info.datetime))+"
"; + html += "
Recovery location: "+recovery_info.position[1]+", "+recovery_info.position[0] + "
"; + html += "
Recovery notes: "+recovery_info.description+"
"; + + html += "
"; + } + + html += "
Show Full Flight Path: " + serial + "
"; + + html += "
"; + html += "
" + + if (landing.hasOwnProperty("uploader_callsign")) { + html += "
Last received by: " + landing.uploader_callsign.toLowerCase() + "
"; + }; + + popup.setContent(html); + + if (!recovered) { + var marker = L.circleMarker([landing.lat, landing.lon], {fillColor: "white", color: iconColour, weight: 3, radius: 5, fillOpacity:1}); + } else { + var marker = L.circleMarker([landing.lat, landing.lon], {fillColor: "grey", color: iconColour, weight: 3, radius: 5, fillOpacity:1}); + } + + marker.bindPopup(popup); + + marker.addTo(map); + marker.bringToBack(); + historicalPlots[station].sondes[serial].marker = marker; + } +} + +// Delete historic summaries from map +function deleteHistorical (station) { + var popup = $("#popup" + station); + var deleteHistorical = popup.find("#deleteHistorical"); + var historicalDelete = $("#historicalControlButton"); + + deleteHistorical.hide(); + + if (historicalPlots.hasOwnProperty(station)) { + for (let serial in historicalPlots[station].sondes) { + map.removeLayer(historicalPlots[station].sondes[serial].marker); + } + } + + delete historicalPlots[station]; + + var otherSondes = false; + + for (station in historicalPlots) { + if (historicalPlots.hasOwnProperty(station)) { + if (Object.keys(historicalPlots[station].sondes).length > 1) { + otherSondes = true; + } + } + } + + if (!otherSondes) historicalDelete.hide(); +} + +// Delete all historic sondes from map +function deleteHistoricalButton() { + var historicalDelete = $("#historicalControlButton"); + + for (station in historicalPlots) { + if (historicalPlots.hasOwnProperty(station)) { + historicalPlots[station].data.drawing = false; + for (let serial in historicalPlots[station].sondes) { + map.removeLayer(historicalPlots[station].sondes[serial].marker); + } + // Hide delete historical button from popup + var realpopup = launches.getLayer(stationMarkerLookup[station]).getPopup(); + var popup = $("#popup" + station); + var deleteHistorical = popup.find("#deleteHistorical"); + deleteHistorical.hide(); + // Required if popup is closed + if (!realpopup.isOpen()) { + var tempContent = $(realpopup.getContent()); + tempContent.find("#deleteHistorical").hide(); + realpopup.setContent("
" + tempContent.html() + "
"); + } + } + } + + // Cancel any queued or ongoing historical requests + for (i=0; i < historicalAjax.length; i++) { + historicalAjax[i].abort(); + } + + historicalAjax = []; + + historicalPlots = {}; + + historicalDelete.hide(); + +} + +// Delete all launch site predictions from map +function deletePredictionButton() { + var predictionDelete = $("#predictionControlButton"); + + for (var marker in launchPredictions) { + if (launchPredictions.hasOwnProperty(marker)) { + for (var prediction in launchPredictions[marker]) { + if (launchPredictions[marker].hasOwnProperty(prediction)) { + for (var object in launchPredictions[marker][prediction]) { + if (launchPredictions[marker][prediction].hasOwnProperty(object)) { + map.removeLayer(launchPredictions[marker][prediction][object]); + } + } + } + } + // Hide delete historical prediction button from popup + var realpopup = launches.getLayer(marker).getPopup(); + var popup = $("#popup" + markerStationLookup[marker]); + var deletePrediction = popup.find("#predictionDeleteButton"); + deletePrediction.hide(); + // Required if popup is closed + if (!realpopup.isOpen()) { + var tempContent = $(realpopup.getContent()); + tempContent.find("#predictionDeleteButton").hide(); + realpopup.setContent("
" + tempContent.html() + "
"); + } + } + } + + launchPredictions = {}; + + // Cancel any queued or ongoing launch site prediction requests + for (i=0; i < predictionAjax.length; i++) { + predictionAjax[i].abort(); + } + + predictionAjax = []; + + predictionDelete.hide(); + +} + +// Master function to display historic summaries +function showHistorical (station, marker) { + var popup = $("#popup" + station); + var realpopup = launches.getLayer(marker).getPopup(); + var submit = popup.find("#submit"); + var submitLoading = popup.find("#submitLoading"); + var deleteHistorical = popup.find("#deleteHistorical"); + var targetyear = popup.find("#yearList option:selected").val(); + var targetmonth = popup.find("#monthList option:selected").val(); + + submit.hide(); + submitLoading.show(); + deleteHistorical.hide(); + + var sondes = []; + var data = stationHistoricalData[station]; + + // Generate list of serial URLs + for (let year in data) { + if (data.hasOwnProperty(year)) { + if (year == targetyear || targetyear == "all") { + for (let month in data[year]) { + if (data[year].hasOwnProperty(month)) { + if (month == targetmonth || targetmonth == "all" || targetyear == "all") { + sondes = sondes.concat(data[year][month]); + } + } + } + } + } + } + + // Generate date range for station + dateNow = new Date(); + dateNow.setDate(dateNow.getDate() + 2); + + if (!historicalPlots.hasOwnProperty(station)) { + historicalPlots[station] = {}; + historicalPlots[station].sondes = {}; + historicalPlots[station].data = {}; + } + + // Get station location to fetch recoveries + if (!historicalPlots[station].data.hasOwnProperty("recovered")) { + historicalPlots[station].data.recovered = {}; + + var station_position = sites[station].position; + var data_str = "lat=" + station_position[0] + "&lon=" + station_position[1] + "&distance=400000&last=0"; + + $.ajax({ + type: "GET", + url: recovered_sondes_url, + data: data_str, + dataType: "json", + success: function(json) { + for (var i = 0; i < json.length; i++) { + historicalPlots[station].data.recovered[json[i].serial] = json[i]; + } + processHistorical() + }, + error: function() { + processHistorical(); + } + }); + } else { + processHistorical(); + } + + function processHistorical() { + var historicalDelete = $("#historicalControlButton"); + historicalDelete.show(); + + historicalPlots[station].data.drawing = true; + + for (let i = 0; i < sondes.length; i++) { + downloadHistorical(sondes[i]).done(handleData).fail(handleError); + } + + var completed = 0; + + function handleData(data) { + completed += 1; + try { + drawHistorical(data, station); + } catch(e) {}; + if (completed == sondes.length) { + submit.show(); + submitLoading.hide(); + if (historicalPlots[station].data.drawing) deleteHistorical.show(); + // If modal is closed the contents needs to be forced updated + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + historicalPlots[station].data.drawing = false; + } + } + + function handleError(error) { + completed += 1; + if (completed == sondes.length) { + submit.show(); + submitLoading.hide(); + if (historicalPlots[station].data.drawing) deleteHistorical.show(); + // If modal is closed the contents needs to be forced updated + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + historicalPlots[station].data.drawing = false; + } + } + } +} + +// Used to generate the content for station modal +function historicalLaunchViewer(station, marker) { + var realpopup = launches.getLayer(marker).getPopup(); + var popup = $("#popup" + station); + var historical = popup.find("#historical"); + function populateDropDown(data) { + // Save data + stationHistoricalData[station] = data; + + // Check if data exists + if (Object.keys(data).length == 0) { + historical.html("


No historical data
"); + historical.show(); + historicalButton.show(); + historicalButtonLoading.hide(); + // If modal is closed the contents needs to be forced updated + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + return; + } + + // Find latest year + var latestYear = "0"; + var latestYears = Object.keys(data); + for (var i=0; i < latestYears.length; i++) { + if (parseInt(latestYears[i]) > parseInt(latestYear)) { + latestYear = latestYears[i]; + } + } + + // Generate year drop down + var yearList = document.createElement("select"); + yearList.name = "year" + yearList.id = "yearList"; + var option = document.createElement("option"); + option.value = "all"; + option.text = "All"; + yearList.appendChild(option); + for (let year in data) { + if (data.hasOwnProperty(year)) { + var option = document.createElement("option"); + option.value = year; + option.text = year; + if (year == latestYear) { + option.setAttribute("selected", "selected"); + } + yearList.appendChild(option); + } + } + + // Find latest month + var latestMonth = "0"; + var latestMonths = Object.keys(data[latestYear]); + for (var i=0; i < latestMonths.length; i++) { + if (parseInt(latestMonths[i]) > parseInt(latestMonth)) { + latestMonth = latestMonths[i]; + } + } + + // Generate month drop down + var monthList = document.createElement("select"); + monthList.name = "month" + monthList.id = "monthList"; + var option = document.createElement("option"); + option.value = "all"; + option.text = "All"; + monthList.appendChild(option); + var months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; + var monthsText = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]; + for (var i=0; i < months.length; i++) { + var option = document.createElement("option"); + option.value = months[i]; + option.text = monthsText[i]; + if (months[i] == latestMonth) { + option.setAttribute("selected", "selected"); + } + monthList.appendChild(option); + } + + + // Calculate total launches + var totalLaunches = 0; + for (let year in data) { + if (data.hasOwnProperty(year)) { + for (let month in data[year]) { + if (data[year].hasOwnProperty(month)) { + totalLaunches += data[year][month].length; + } + } + } + } + + // Generate HTML + var popupContent = "


Launches Selected: " + totalLaunches + "
"; + popupContent += "
" + yearList.outerHTML; + popupContent += "" + monthList.outerHTML + "
"; + popupContent += "
"; + historical.html(popupContent); + historical.show(); + historicalButton.show(); + historicalButtonLoading.hide(); + // If modal is closed the contents needs to be forced updated + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + getSelectedNumber(station); + } + if (historical.is(":visible")) { + // Don't regenerate if already in memory + historical.hide(); + } else { + if (stationHistoricalData.hasOwnProperty(station) && popup.find("#launchCount").length) { + // Don't regenerate if already in memory + historical.show(); + } else { + var historicalButton = popup.find("#historicalButton"); + var historicalButtonLoading = popup.find("#historicalButtonLoading"); + historicalButton.hide(); + historicalButtonLoading.show(); + getHistorical(station, populateDropDown); + } + } +} + +function launchSitePredictions(times, station, properties, marker, id) { + var realpopup = launches.getLayer(marker).getPopup(); + var popup = $("#popup" + id); + var predictionButton = popup.find("#predictionButton"); + var predictionButtonLoading = popup.find("#predictionButtonLoading"); + var predictionDeleteButton = popup.find("#predictionDeleteButton"); + + predictionButton.hide(); + predictionButtonLoading.show(); + + if (predictionDeleteButton.is(':visible')) { + deletePredictions(marker, id); + predictionDeleteButton.hide(); + } + position = station.split(","); + properties = properties.split(":"); + var now = new Date(); + if (times.length > 0) { + times = times.split(","); + var maxCount = 24 + var count = 0; + var day = 0; + var dates = []; + while (day < 8) { + for (var i = 0; i < times.length; i++) { + var date = new Date(); + var time = times[i].split(":"); + if (time[0] != 0) { + date.setDate(date.getDate() + (7 + time[0] - date.getDay()) % 7); + } + date.setUTCHours(time[1]); + date.setUTCMinutes(time[2]); + date.setSeconds(0); + date.setMilliseconds(0); + // launch time 45 minutes before target time + date.setMinutes( date.getMinutes() - 45 ); + while (date < now) { + if (time[0] == 0) { + date.setDate(date.getDate() + 1); + } else { + date.setDate(date.getDate() + 7); + } + } + if (day > 0) { + if (time[0] == 0) { + date.setDate(date.getDate() + day); + } else { + date.setDate(date.getDate() + (7*day)); + } + } + if (count < maxCount) { + if (((date - now) / 36e5) < 170) { + if (!dates.includes(date.toISOString().split('.')[0]+"Z")) { + dates.push(date.toISOString().split('.')[0]+"Z"); + count += 1; + } + } + } + } + day += 1; + } + dates.sort(); + } else { + var date = new Date(); + var dates = []; + dates.push(date.toISOString().split('.')[0]+"Z"); + } + var completed = 0; + var predictionDelete = $("#predictionControlButton"); + predictionDelete.show(); + for (var i = 0; i < dates.length; i++) { + var lon = ((360 + (position[1] % 360)) % 360); + //var url = "https://predict.cusf.co.uk/api/v1/?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1]; + var url = "https://api.v2.sondehub.org/tawhiri?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1]; + showPrediction(url).done(handleData).fail(handleError); + } + function handleData(data) { + completed += 1; + plotPrediction(data, dates, marker, properties); + if (completed == dates.length) { + if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); + predictionButton.show(); + predictionButtonLoading.hide(); + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + } + } + function handleError(error) { + completed += 1; + if (completed == dates.length) { + if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); + predictionButton.show(); + predictionButtonLoading.hide(); + if (!realpopup.isOpen()) { + realpopup.setContent("
" + popup.html() + "
"); + } + } + } +} + +function plotPrediction (data, dates, marker, properties) { + if (!launchPredictions.hasOwnProperty(marker)) { + launchPredictions[marker] = {}; + } + launchPredictions[marker][dates.indexOf(data.request.launch_datetime)+1] = {}; + plot = launchPredictions[marker][dates.indexOf(data.request.launch_datetime)+1]; + + ascent = data.prediction[0].trajectory; + descent = data.prediction[1].trajectory; + var predictionPath = []; + for (var i = 0; i < ascent.length; i++) { + if (ascent[i].longitude > 180.0) { + var longitude = ascent[i].longitude - 360.0; + } else { + var longitude = ascent[i].longitude; + } + predictionPath.push([ascent[i].latitude, longitude]); + }; + for (var x = 0; x < descent.length; x++) { + if (descent[x].longitude > 180.0) { + var longitude = descent[x].longitude - 360.0; + } else { + var longitude = descent[x].longitude; + } + predictionPath.push([descent[x].latitude, longitude]); + }; + var burstPoint = ascent[ascent.length-1]; + var landingPoint = descent[descent.length-1]; + + plot.predictionPath = new L.polyline(predictionPath, {color: 'red'}).addTo(map); + + burstIconImage = host_url + markers_url + "balloon-pop.png"; + + burstIcon = new L.icon({ + iconUrl: burstIconImage, + iconSize: [20,20], + iconAnchor: [10, 10], + }); + + if (burstPoint.longitude > 180.0) { + var burstLongitude = burstPoint.longitude - 360.0; + } else { + var burstLongitude = burstPoint.longitude; + } + + plot.burstMarker = new L.marker([burstPoint.latitude, burstLongitude], { + icon: burstIcon + }).addTo(map); + + var burstTime = new Date(burstPoint.datetime); + var burstTooltip = "Time: " + burstTime.toLocaleString() + "
Altitude: " + Math.round(burstPoint.altitude) + "m"; + plot.burstMarker.bindTooltip(burstTooltip, {offset: [5,0]}); + + if (landingPoint.longitude > 180.0) { + var landingLongitude = landingPoint.longitude - 360.0; + } else { + var landingLongitude = landingPoint.longitude; + } + + plot.landingMarker = new L.marker([landingPoint.latitude, landingLongitude], { + icon: new L.NumberedDivIcon({number: dates.indexOf(data.request.launch_datetime)+1}) + }).addTo(map); + + var landingTime = new Date(landingPoint.datetime); + if (properties[3] != "" && properties[4] != "") { + var landingTooltip = "Time: " + landingTime.toLocaleString() + "
Model Dataset: " + data.request.dataset + + "
Model Assumptions:
- " + data.request.ascent_rate + "m/s ascent
- " + data.request.burst_altitude + "m burst altitude (" + properties[3] + " samples)
- " + data.request.descent_rate + "m/s descent (" + properties[4] + " samples)"; + } else { + var landingTooltip = "Time: " + landingTime.toLocaleString() + "
Model Dataset: " + data.request.dataset + + "
Model Assumptions:
- " + data.request.ascent_rate + "m/s ascent
- " + data.request.burst_altitude + "m burst altitude
- " + data.request.descent_rate + "m/s descent"; + } + plot.landingMarker.bindTooltip(landingTooltip, {offset: [13,-28]}); +} + +function showPrediction(url) { + var ajaxReq = $.ajax({ + type: "GET", + url: url, + dataType: "json", + }); + predictionAjax.push(ajaxReq); + return ajaxReq; +} + +function deletePredictions(marker, station) { + var predictionDelete = $("#predictionControlButton"); + if (launchPredictions.hasOwnProperty(marker)) { + for (var prediction in launchPredictions[marker]) { + if (launchPredictions[marker].hasOwnProperty(prediction)) { + for (var object in launchPredictions[marker][prediction]) { + if (launchPredictions[marker][prediction].hasOwnProperty(object)) { + map.removeLayer(launchPredictions[marker][prediction][object]); + } + } + } + } + delete launchPredictions[marker]; + } + var popup = $("#popup" + station); + var predictionDeleteButton = popup.find("#predictionDeleteButton"); + if (predictionDeleteButton.is(':visible')) { + predictionDeleteButton.hide(); + } + if (Object.keys(launchPredictions).length == 0) predictionDelete.hide(); +} + +function getLaunchSites() { + $.ajax({ + type: "GET", + url: launches_url, + dataType: "json", + success: function(json) { + sites = json; + generateLaunchSites(); + } + }); +} + +function generateLaunchSites() { + for (var key in sites) { + if (sites.hasOwnProperty(key)) { + var latlon = [sites[key].position[1], sites[key].position[0]]; + var sondesList = ""; + var popupContent = "
"; + var div = document.createElement('div'); + div.id = "popup" + key; + var ascent_rate = 5; + var descent_rate = 6; + var burst_altitude = 26000; + var burst_samples = ""; + var descent_samples = ""; + var marker = new L.circleMarker(latlon, {color: '#696969', fillColor: "white", radius: 8}); + var popup = new L.popup({ autoClose: false, closeOnClick: false }); + marker.title = key; + marker.bindPopup(popup); + launches.addLayer(marker); + + // Match sonde codes + if (sites[key].hasOwnProperty('rs_types')) { + var sondes = sites[key].rs_types; + for (var y = 0; y < sondes.length; y++) { + if (Array.isArray(sondes[y]) == false) { + sondes[y] = [sondes[y]]; + } + if (sondeCodes.hasOwnProperty(sondes[y][0])) { + sondesList += sondeCodes[sondes[y][0]] + if (sondes[y].length > 1) { + sondesList += " (" + sondes[y][1] + " MHz)"; + } + } else if (unsupportedSondeCodes.hasOwnProperty(sondes[y][0])) { + sondesList += unsupportedSondeCodes[sondes[y][0]]; + sondesList += " (cannot track)"; + } else { + sondesList += sondes[y][0] + " (unknown WMO code)"; + } + if (y < sondes.length-1) { + sondesList += ", "; + } + } + if (sondes.includes("11") || sondes.includes("82")) { //LMS6 + ascent_rate = 5; + descent_rate = 2.5; + burst_altitude = 33500; + } + popupContent += "" + sites[key].station_name + "

Sondes launched: " + sondesList; + } + + // Generate prefilled suggestion form + var popupLink = "https://docs.google.com/forms/d/e/1FAIpQLSfIbBSQMZOXpNE4VpK4BqUbKDPCWCDgU9QxYgmhh-JD-JGSsQ/viewform?usp=pp_url&entry.796606853=Modify+Existing+Site"; + popupLink += "&entry.749833526=" + key; + if (sites[key].hasOwnProperty('station_name')) { + popupLink += "&entry.675505431=" + sites[key].station_name.replace(/\s/g, '+'); + } + if (sites[key].hasOwnProperty('position')) { + popupLink += "&entry.1613779787=" + sites[key].position.reverse().toString(); + } + if (sites[key].hasOwnProperty('alt')) { + popupLink += "&entry.753148337=" + sites[key].alt; + } + if (sites[key].hasOwnProperty('ascent_rate')) { + popupLink += "&entry.509146334=" + sites[key]["ascent_rate"]; + } + if (sites[key].hasOwnProperty('burst_altitude')) { + popupLink += "&entry.1897602989=" + sites[key]["burst_altitude"]; + } + if (sites[key].hasOwnProperty('descent_rate')) { + popupLink += "&entry.267462486=" + sites[key]["descent_rate"]; + } + if (sites[key].hasOwnProperty('notes')) { + popupLink += "&entry.197384117=" + sites[key]["notes"].replace(/\s/g, '+'); + } + + // Update prediction data if provided + if (sites[key].hasOwnProperty('ascent_rate')) { + ascent_rate = sites[key]["ascent_rate"]; + } + if (sites[key].hasOwnProperty('descent_rate')) { + descent_rate = sites[key]["descent_rate"]; + } + if (sites[key].hasOwnProperty('burst_altitude')) { + burst_altitude = sites[key]["burst_altitude"]; + } + if (sites[key].hasOwnProperty('burst_samples')) { + burst_samples = sites[key]["burst_samples"]; + } + if (sites[key].hasOwnProperty('descent_samples')) { + descent_samples = sites[key]["descent_samples"]; + } + + // Process launch schedule if provided + if (sites[key].hasOwnProperty('times')) { + popupContent += "
Launch schedule:"; + for (var x = 0; x < sites[key]['times'].length; x++) { + popupContent += "
- "; + var day = sites[key]['times'][x].split(":")[0]; + if (day == 0) { + popupContent += "Everyday at "; + } else if (day == 1) { + popupContent += "Monday at "; + } else if (day == 2) { + popupContent += "Tuesday at "; + } else if (day == 3) { + popupContent += "Wednesday at "; + } else if (day == 4) { + popupContent += "Thursday at "; + } else if (day == 5) { + popupContent += "Friday at "; + } else if (day == 6) { + popupContent += "Saturday at "; + } else if (day == 7) { + popupContent += "Sunday at "; + } + popupContent += sites[key]['times'][x].split(":")[1] + ":" + sites[key]['times'][x].split(":")[2] + " UTC"; + } + } + + // Show notes if provided + if (sites[key].hasOwnProperty('notes')) { + popupContent += "
Notes: " + sites[key]["notes"]; + } + + popupContent += "
Know when this site launches? Contribute here"; + + // Generate view historical button + popupContent += "
"; + + // Create prediction button + if (sites[key].hasOwnProperty('times')) { + popupContent += ""; + } else { + popupContent += ""; + } + + popupContent += ""; + + div.innerHTML = popupContent; + + popup.setContent(div.innerHTML); + + var leafletID = launches.getLayerId(marker); + + stationMarkerLookup[key] = leafletID; + markerStationLookup[leafletID] = key; + } + } + if (focusID != 0) { + gotoSite(); + } +} + +// URL parameter redirect +function gotoSite() { + if (sites != null) { + if (sites.hasOwnProperty(focusID)) { + var site = sites[focusID]; + var latlng = new L.LatLng(site["position"][0], site["position"][1]); + map.setView(latlng, 9); + for (var i in launches._layers) { + marker = launches._layers + if (marker[i].title == focusID) { + marker[i].openPopup(); + } + } + } + } +} \ No newline at end of file diff --git a/js/tracker.js b/js/tracker.js index 14714bb..bf35a58 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -6,7 +6,6 @@ var predictions_url = "https://api.v2.sondehub.org/predictions?vehicles="; var launch_predictions_url = "https://api.v2.sondehub.org/predictions/reverse"; var recovered_sondes_url = "https://api.v2.sondehub.org/recovered"; var recovered_sondes_stats_url = "https://api.v2.sondehub.org/recovered/stats"; -var launches_url = "https://api.v2.sondehub.org/sites"; var livedata = "wss://ws-reader.v2.sondehub.org/"; var clientID = "SondeHub-Tracker-" + Math.floor(Math.random() * 10000000000); @@ -41,9 +40,7 @@ var historicalAjax = []; var skewtdata = []; -var sites = null; var launches = new L.LayerGroup(); -var showLaunches = false; var focusID = 0; var receiverCanvas = null; @@ -166,6 +163,98 @@ var v1manufacturers = { "M20": "Meteomodem" } +var globalKeys = { + "ascentrate": "Ascent Rate", + "battery_percent": "Battery", + "temperature_external": "Temperature, External", + "pressure_internal": "Pressure, Internal", + "voltage_solar_1": "Voltage, Solar 1", + "voltage_solar_2": "Voltage, Solar 2", + "light_red": "Light (Red)", + "light_green": "Light (Green)", + "light_blue": "Light (Blue)", + "gas_a": "Gas (A)", + "gas_b": "Gas (B)", + "gas_co2": "Gas (CO)", + "gas_combustible": "Gas (Combustible)", + "radiation": "Radiation (CPM)", + "temperature_radio": "Temperature, Radio", + "uplink_rssi": "Uplink RSSI", + "light_intensity": "Light Intensity", + "pred_lat": "Onboard Prediction (Lat)", + "pred_lon": "Onboard Prediction (Lon)", + "batt": "Battery Voltage", + "sats": "GNSS SVs Used", + "humidity": "Relative Humidity", + "subtype": "Sonde Sub-type", + "frequency": "Frequency", + "frequency_tx": "TX Frequency", + "manufacturer": "Manufacturer", + "type": "Sonde Type", + "burst_timer": "Burst Timer", + "xdata": "XDATA", + "xdata_instrument": "XDATA Instrument", + "oif411_instrument_number": "OIF411 Instrument Number", + "oif411_ext_voltage": "OIF411 External Voltage", + "oif411_ozone_battery_v": "OIF411 Battery", + "oif411_ozone_current_uA": "Ozone Current", + "oif411_ozone_pump_curr_mA": "Ozone Pump Current", + "oif411_ozone_pump_temp": "Ozone Pump Temperature", + "oif411_O3_partial_pressure": "Ozone Partial Pressure", + "oif411_serial": "OIF411 Serial Number", + "oif411_diagnostics": "OIF411 Diagnostics", + "oif411_version": "OIF411 Version", + "cfh_instrument_number": "CFH Instrument Number", + "cfh_mirror_temperature": "CFH Mirror Temperature", + "cfh_optics_voltage": "CFH Optics Voltage", + "cfh_optics_temperature": "CFH Optics Temperature", + "cfh_battery": "CFH Battery", + "cobald_instrument_number": "COBALD Instrument Number", + "cobald_sonde_number": "COBALD Sonde Number", + "cobald_internal_temperature": "COBALD Internal Temperature", + "cobald_blue_backscatter": "COBALD Blue Backscatter", + "cobald_red_backscatter": "COBALD Red Backscatter", + "cobald_blue_monitor": "COBALD Blue Monitor", + "cobald_red_monitor": "COBALD Red Monitor" +}; + +var globalSuffixes = { + "current": " A", + "battery": " V", + "batt": " V", + "solar_panel": " V", + "temperature": "°C", + "temperature_internal": "°C", + "temperature_external": "°C", + "temperature_radio": "°C", + "pressure": " hPa", + "voltage_solar_1": " V", + "voltage_solar_2": " V", + "battery_percent": "%", + "uplink_rssi": " dBm", + "rssi_last_message": " dBm", + "rssi_floor": " dBm", + "bearing": "°", + "iss_azimuth": "°", + "iss_elevation": "°", + "light_intensity": " lx", + "humidity": " %", + "frequency": " MHz", + "frequency_tx": " MHz", + "spam": "", + "oif411_ext_voltage": " V", + "oif411_ozone_battery_v": " V", + "oif411_ozone_current_uA": " uA", + "oif411_ozone_pump_curr_mA": " mA", + "oif411_ozone_pump_temp": "°C", + "oif411_O3_partial_pressure": " mPa (+/- 1)", + "cfh_mirror_temperature": "°C", + "cfh_optics_voltage": " V", + "cfh_optics_temperature": "°C", + "cfh_battery": " V", + "cobald_internal_temperature": "°C" +}; + // localStorage vars var ls_receivers = false; var ls_pred = false; @@ -850,910 +939,18 @@ function load() { } else if($("#lookanglesbox span:first").is(":hidden")) { $("#lookanglesbox .nofollow").fadeIn(500, "easeOut"); } - }); - - // if we there is enough screen space open aboutbox on startup - if(!is_mobile && !offline.get('opt_nowelcome') && $(window).width() > 900) $('.nav li.about').click(); - - }, 500); -} - -function setTimeValue() { - setTimeout(function() { - document.getElementById("timeperiod").value = wvar.mode; - }, 100); -} - -function getSelectedNumber (station) { - var popup = $("#popup" + station); - var targetyear = popup.find("#yearList option:selected").val(); - var targetmonth = popup.find("#monthList option:selected").val(); - var count = 0; - var data = stationHistoricalData[station]; - - // Calculate count - for (let year in data) { - if (data.hasOwnProperty(year)) { - if (year == targetyear || targetyear == "all") { - for (let month in data[year]) { - if (data[year].hasOwnProperty(month)) { - if (month == targetmonth || targetmonth == "all" || targetyear == "all") { - count += data[year][month].length; - } - } - } - } - } - } - - // Update selected field & hide months if no data - var months = ["all", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; - popup.find('#yearList option').each(function() { - if ($(this).is(':selected')) { - $(this).attr("selected", "selected"); - var selectedYear = $(this).val(); - if (selectedYear != "all") { - months = Object.keys(data[selectedYear]); - months.push("all"); - } - } else { - $(this).attr("selected", false); - } - }); - - popup.find('#monthList option').each(function() { - if (!months.includes($(this).val())) { - $(this).hide(); - } else { - $(this).show(); - } - if ($(this).is(':selected')) { - $(this).attr("selected", "selected"); - } else { - $(this).attr("selected", false); - } - }); - - // Update popup - popup.find("#launchCount").text(count); -} - -// Download summary data from AWS S3 -function downloadHistorical (suffix) { - var url = "https://sondehub-history.s3.amazonaws.com/" + suffix; - var ajaxReq = $.ajax({ - type: "GET", - url: url, - dataType: "json", - tryCount : 0, - retryLimit : 3, // Retry max of 3 times - error : function(xhr, textStatus, errorThrown ) { - if (textStatus == 'timeout') { - this.tryCount++; - if (this.tryCount <= this.retryLimit) { - //try again - $.ajax(this); - return; - } - return; - } - } - }); - historicalAjax.push(ajaxReq); - return ajaxReq; -} - -// Draw historic summaries to map -function drawHistorical (data, station) { - var landing = data[2]; - var serial = landing.serial; - var time = landing.datetime; - - if (!historicalPlots[station].sondes.hasOwnProperty(serial)) { - - historicalPlots[station].sondes[serial] = {}; - - // Using last known alt to detmine colour - var minAlt = 0; - var actualAlt = landing.alt; - var maxAlt = 10000; - - if (actualAlt > maxAlt) { - actualAlt = maxAlt; - } else if (actualAlt < minAlt) { - actualAlt = minAlt; - } - - var normalisedAlt = ((actualAlt-minAlt)/(maxAlt-minAlt)); - var iconColour = ConvertRGBtoHex(evaluate_cmap(normalisedAlt, 'turbo')); - - // Check if we have recovery data for it - var recovered = false; - if (historicalPlots[station].data.hasOwnProperty("recovered")) { - if (historicalPlots[station].data.recovered.hasOwnProperty(serial)) { - var recovery_info = historicalPlots[station].data.recovered[serial]; - recovered = true; - } - } - - var popup = L.popup(); - - html = "
"; - html += "
"+serial+" ("+time+")
"; - html += "
"; - html += "
 "+roundNumber(landing.lat, 5) + ', ' + roundNumber(landing.lon, 5)+"
"; - - var imp = offline.get('opt_imperial'); - var text_alt = Number((imp) ? Math.floor(3.2808399 * parseInt(landing.alt)) : parseInt(landing.alt)).toLocaleString("us"); - text_alt += " " + ((imp) ? 'ft':'m'); - - html += "
Altitude: "+text_alt+"
"; - html += "
Time: "+formatDate(stringToDateUTC(time))+"
"; - - if (landing.hasOwnProperty("type")) { - html += "
Sonde Type: " + landing.type + "
"; - }; - - html += "
"; - - if (recovered) { - html += "
"+(recovery_info.recovered ? "Recovered by " : "Not Recovered by ")+recovery_info.recovered_by+"
"; - html += "
Recovery time: "+formatDate(stringToDateUTC(recovery_info.datetime))+"
"; - html += "
Recovery location: "+recovery_info.position[1]+", "+recovery_info.position[0] + "
"; - html += "
Recovery notes: "+recovery_info.description+"
"; - - html += "
"; - } - - html += "
Show Full Flight Path: " + serial + "
"; - - html += "
"; - html += "
" - - if (landing.hasOwnProperty("uploader_callsign")) { - html += "
Last received by: " + landing.uploader_callsign.toLowerCase() + "
"; - }; - - popup.setContent(html); - - if (!recovered) { - var marker = L.circleMarker([landing.lat, landing.lon], {fillColor: "white", color: iconColour, weight: 3, radius: 5, fillOpacity:1}); - } else { - var marker = L.circleMarker([landing.lat, landing.lon], {fillColor: "grey", color: iconColour, weight: 3, radius: 5, fillOpacity:1}); - } - - marker.bindPopup(popup); - - marker.addTo(map); - marker.bringToBack(); - historicalPlots[station].sondes[serial].marker = marker; - } -} - -// Delete historic summaries from map -function deleteHistorical (station) { - var popup = $("#popup" + station); - var deleteHistorical = popup.find("#deleteHistorical"); - var historicalDelete = $("#historicalControlButton"); - - deleteHistorical.hide(); - - if (historicalPlots.hasOwnProperty(station)) { - for (let serial in historicalPlots[station].sondes) { - map.removeLayer(historicalPlots[station].sondes[serial].marker); - } - } - - delete historicalPlots[station]; - - var otherSondes = false; - - for (station in historicalPlots) { - if (historicalPlots.hasOwnProperty(station)) { - if (Object.keys(historicalPlots[station].sondes).length > 1) { - otherSondes = true; - } - } - } - - if (!otherSondes) historicalDelete.hide(); -} - -function deleteHistoricalButton() { - var historicalDelete = $("#historicalControlButton"); - - for (station in historicalPlots) { - if (historicalPlots.hasOwnProperty(station)) { - historicalPlots[station].data.drawing = false; - for (let serial in historicalPlots[station].sondes) { - map.removeLayer(historicalPlots[station].sondes[serial].marker); - } - var realpopup = launches.getLayer(stationMarkerLookup[station]).getPopup(); - var popup = $("#popup" + station); - var deleteHistorical = popup.find("#deleteHistorical"); - deleteHistorical.hide(); - if (!realpopup.isOpen()) { - var tempContent = $(realpopup.getContent()); - tempContent.find("#deleteHistorical").hide(); - realpopup.setContent("
" + tempContent.html() + "
"); - } - } - } - - for (i=0; i < historicalAjax.length; i++) { - historicalAjax[i].abort(); - } - - historicalAjax = []; - - historicalPlots = {}; - - historicalDelete.hide(); - -} - -function deletePredictionButton() { - var predictionDelete = $("#predictionControlButton"); - - for (var marker in launchPredictions) { - if (launchPredictions.hasOwnProperty(marker)) { - for (var prediction in launchPredictions[marker]) { - if (launchPredictions[marker].hasOwnProperty(prediction)) { - for (var object in launchPredictions[marker][prediction]) { - if (launchPredictions[marker][prediction].hasOwnProperty(object)) { - map.removeLayer(launchPredictions[marker][prediction][object]); - } - } - } - } - var realpopup = launches.getLayer(marker).getPopup(); - var popup = $("#popup" + markerStationLookup[marker]); - var deletePrediction = popup.find("#predictionDeleteButton"); - deletePrediction.hide(); - if (!realpopup.isOpen()) { - var tempContent = $(realpopup.getContent()); - tempContent.find("#predictionDeleteButton").hide(); - realpopup.setContent("
" + tempContent.html() + "
"); - } - } - } - - launchPredictions = {}; - - for (i=0; i < predictionAjax.length; i++) { - predictionAjax[i].abort(); - } - - predictionAjax = []; - - predictionDelete.hide(); - -} - -// Master function to display historic summaries -function showHistorical (station, marker) { - var popup = $("#popup" + station); - var realpopup = launches.getLayer(marker).getPopup(); - var submit = popup.find("#submit"); - var submitLoading = popup.find("#submitLoading"); - var deleteHistorical = popup.find("#deleteHistorical"); - var targetyear = popup.find("#yearList option:selected").val(); - var targetmonth = popup.find("#monthList option:selected").val(); - - submit.hide(); - submitLoading.show(); - deleteHistorical.hide(); - - var sondes = []; - var data = stationHistoricalData[station]; - - // Generate list of serial URLs - for (let year in data) { - if (data.hasOwnProperty(year)) { - if (year == targetyear || targetyear == "all") { - for (let month in data[year]) { - if (data[year].hasOwnProperty(month)) { - if (month == targetmonth || targetmonth == "all" || targetyear == "all") { - sondes = sondes.concat(data[year][month]); - } - } - } - } - } - } - - // Generate date range for station - // TODO make this reactive? - dateNow = new Date(); - dateNow.setDate(dateNow.getDate() + 2); - - if (!historicalPlots.hasOwnProperty(station)) { - historicalPlots[station] = {}; - historicalPlots[station].sondes = {}; - historicalPlots[station].data = {}; - } - - // Get station location to fetch recoveries - if (!historicalPlots[station].data.hasOwnProperty("recovered")) { - historicalPlots[station].data.recovered = {}; - - var station_position = sites[station].position; - var data_str = "lat=" + station_position[0] + "&lon=" + station_position[1] + "&distance=400000&last=0"; - - $.ajax({ - type: "GET", - url: recovered_sondes_url, - data: data_str, - dataType: "json", - success: function(json) { - for (var i = 0; i < json.length; i++) { - historicalPlots[station].data.recovered[json[i].serial] = json[i]; - } - processHistorical() - }, - error: function() { - processHistorical(); - } - }); - } else { - processHistorical(); - } - - function processHistorical() { - var historicalDelete = $("#historicalControlButton"); - historicalDelete.show(); - - historicalPlots[station].data.drawing = true; - - for (let i = 0; i < sondes.length; i++) { - downloadHistorical(sondes[i]).done(handleData).fail(handleError); - } - - var completed = 0; - - function handleData(data) { - completed += 1; - try { - drawHistorical(data, station); - } catch(e) {}; - if (completed == sondes.length) { - submit.show(); - submitLoading.hide(); - if (historicalPlots[station].data.drawing) deleteHistorical.show(); - // If modal is closed the contents needs to be forced updated - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - historicalPlots[station].data.drawing = false; - } - } - - function handleError(error) { - completed += 1; - if (completed == sondes.length) { - submit.show(); - submitLoading.hide(); - if (historicalPlots[station].data.drawing) deleteHistorical.show(); - // If modal is closed the contents needs to be forced updated - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - historicalPlots[station].data.drawing = false; - } - } - } -} - -// Used to generate the content for station modal -function historicalLaunchViewer(station, marker) { - var realpopup = launches.getLayer(marker).getPopup(); - var popup = $("#popup" + station); - var historical = popup.find("#historical"); - function populateDropDown(data) { - // Save data - stationHistoricalData[station] = data; - - // Check if data exists - if (Object.keys(data).length == 0) { - historical.html("


No historical data
"); - historical.show(); - historicalButton.show(); - historicalButtonLoading.hide(); - // If modal is closed the contents needs to be forced updated - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - return; - } - - // Find latest year - var latestYear = "0"; - var latestYears = Object.keys(data); - for (var i=0; i < latestYears.length; i++) { - if (parseInt(latestYears[i]) > parseInt(latestYear)) { - latestYear = latestYears[i]; - } - } - - // Generate year drop down - var yearList = document.createElement("select"); - yearList.name = "year" - yearList.id = "yearList"; - var option = document.createElement("option"); - option.value = "all"; - option.text = "All"; - yearList.appendChild(option); - for (let year in data) { - if (data.hasOwnProperty(year)) { - var option = document.createElement("option"); - option.value = year; - option.text = year; - if (year == latestYear) { - option.setAttribute("selected", "selected"); - } - yearList.appendChild(option); - } - } - - // Find latest month - var latestMonth = "0"; - var latestMonths = Object.keys(data[latestYear]); - for (var i=0; i < latestMonths.length; i++) { - if (parseInt(latestMonths[i]) > parseInt(latestMonth)) { - latestMonth = latestMonths[i]; - } - } - - // Generate month drop down - var monthList = document.createElement("select"); - monthList.name = "month" - monthList.id = "monthList"; - var option = document.createElement("option"); - option.value = "all"; - option.text = "All"; - monthList.appendChild(option); - var months = ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"]; - var monthsText = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]; - for (var i=0; i < months.length; i++) { - var option = document.createElement("option"); - option.value = months[i]; - option.text = monthsText[i]; - if (months[i] == latestMonth) { - option.setAttribute("selected", "selected"); - } - monthList.appendChild(option); - } - - - // Calculate total launches - var totalLaunches = 0; - for (let year in data) { - if (data.hasOwnProperty(year)) { - for (let month in data[year]) { - if (data[year].hasOwnProperty(month)) { - totalLaunches += data[year][month].length; - } - } - } - } - - // Generate HTML - var popupContent = "


Launches Selected: " + totalLaunches + "
"; - popupContent += "
" + yearList.outerHTML; - popupContent += "" + monthList.outerHTML + "
"; - popupContent += "
"; - historical.html(popupContent); - historical.show(); - historicalButton.show(); - historicalButtonLoading.hide(); - // If modal is closed the contents needs to be forced updated - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - getSelectedNumber(station); - } - if (historical.is(":visible")) { - // Don't regenerate if already in memory - historical.hide(); - } else { - if (stationHistoricalData.hasOwnProperty(station) && popup.find("#launchCount").length) { - // Don't regenerate if already in memory - historical.show(); - } else { - var historicalButton = popup.find("#historicalButton"); - var historicalButtonLoading = popup.find("#historicalButtonLoading"); - historicalButton.hide(); - historicalButtonLoading.show(); - getHistorical(station, populateDropDown); - } - } -} - -function launchSitePredictions(times, station, properties, marker, id) { - var realpopup = launches.getLayer(marker).getPopup(); - var popup = $("#popup" + id); - var predictionButton = popup.find("#predictionButton"); - var predictionButtonLoading = popup.find("#predictionButtonLoading"); - var predictionDeleteButton = popup.find("#predictionDeleteButton"); - - predictionButton.hide(); - predictionButtonLoading.show(); - - if (predictionDeleteButton.is(':visible')) { - deletePredictions(marker, id); - predictionDeleteButton.hide(); - } - position = station.split(","); - properties = properties.split(":"); - var now = new Date(); - if (times.length > 0) { - times = times.split(","); - var maxCount = 24 - var count = 0; - var day = 0; - var dates = []; - while (day < 8) { - for (var i = 0; i < times.length; i++) { - var date = new Date(); - var time = times[i].split(":"); - if (time[0] != 0) { - date.setDate(date.getDate() + (7 + time[0] - date.getDay()) % 7); - } - date.setUTCHours(time[1]); - date.setUTCMinutes(time[2]); - date.setSeconds(0); - date.setMilliseconds(0); - // launch time 45 minutes before target time - date.setMinutes( date.getMinutes() - 45 ); - while (date < now) { - if (time[0] == 0) { - date.setDate(date.getDate() + 1); - } else { - date.setDate(date.getDate() + 7); - } - } - if (day > 0) { - if (time[0] == 0) { - date.setDate(date.getDate() + day); - } else { - date.setDate(date.getDate() + (7*day)); - } - } - if (count < maxCount) { - if (((date - now) / 36e5) < 170) { - if (!dates.includes(date.toISOString().split('.')[0]+"Z")) { - dates.push(date.toISOString().split('.')[0]+"Z"); - count += 1; - } - } - } - } - day += 1; - } - dates.sort(); - } else { - var date = new Date(); - var dates = []; - dates.push(date.toISOString().split('.')[0]+"Z"); - } - var completed = 0; - var predictionDelete = $("#predictionControlButton"); - predictionDelete.show(); - for (var i = 0; i < dates.length; i++) { - var lon = ((360 + (position[1] % 360)) % 360); - //var url = "https://predict.cusf.co.uk/api/v1/?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1]; - var url = "https://api.v2.sondehub.org/tawhiri?launch_latitude=" + position[0] + "&launch_longitude=" + lon + "&launch_datetime=" + dates[i] + "&ascent_rate=" + properties[0] + "&burst_altitude=" + properties[2] + "&descent_rate=" + properties[1]; - showPrediction(url).done(handleData).fail(handleError); - } - function handleData(data) { - completed += 1; - plotPrediction(data, dates, marker, properties); - if (completed == dates.length) { - if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); - predictionButton.show(); - predictionButtonLoading.hide(); - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - } - } - function handleError(error) { - completed += 1; - if (completed == dates.length) { - if (Object.keys(launchPredictions).length != 0) predictionDeleteButton.show(); - predictionButton.show(); - predictionButtonLoading.hide(); - if (!realpopup.isOpen()) { - realpopup.setContent("
" + popup.html() + "
"); - } - } - } -} - -function plotPrediction (data, dates, marker, properties) { - if (!launchPredictions.hasOwnProperty(marker)) { - launchPredictions[marker] = {}; - } - launchPredictions[marker][dates.indexOf(data.request.launch_datetime)+1] = {}; - plot = launchPredictions[marker][dates.indexOf(data.request.launch_datetime)+1]; - - ascent = data.prediction[0].trajectory; - descent = data.prediction[1].trajectory; - var predictionPath = []; - for (var i = 0; i < ascent.length; i++) { - if (ascent[i].longitude > 180.0) { - var longitude = ascent[i].longitude - 360.0; - } else { - var longitude = ascent[i].longitude; - } - predictionPath.push([ascent[i].latitude, longitude]); - }; - for (var x = 0; x < descent.length; x++) { - if (descent[x].longitude > 180.0) { - var longitude = descent[x].longitude - 360.0; - } else { - var longitude = descent[x].longitude; - } - predictionPath.push([descent[x].latitude, longitude]); - }; - var burstPoint = ascent[ascent.length-1]; - var landingPoint = descent[descent.length-1]; - - plot.predictionPath = new L.polyline(predictionPath, {color: 'red'}).addTo(map); - - burstIconImage = host_url + markers_url + "balloon-pop.png"; - - burstIcon = new L.icon({ - iconUrl: burstIconImage, - iconSize: [20,20], - iconAnchor: [10, 10], - }); - - if (burstPoint.longitude > 180.0) { - var burstLongitude = burstPoint.longitude - 360.0; - } else { - var burstLongitude = burstPoint.longitude; - } - - plot.burstMarker = new L.marker([burstPoint.latitude, burstLongitude], { - icon: burstIcon - }).addTo(map); - - var burstTime = new Date(burstPoint.datetime); - var burstTooltip = "Time: " + burstTime.toLocaleString() + "
Altitude: " + Math.round(burstPoint.altitude) + "m"; - plot.burstMarker.bindTooltip(burstTooltip, {offset: [5,0]}); - - if (landingPoint.longitude > 180.0) { - var landingLongitude = landingPoint.longitude - 360.0; - } else { - var landingLongitude = landingPoint.longitude; - } - - plot.landingMarker = new L.marker([landingPoint.latitude, landingLongitude], { - icon: new L.NumberedDivIcon({number: dates.indexOf(data.request.launch_datetime)+1}) - }).addTo(map); - - var landingTime = new Date(landingPoint.datetime); - if (properties[3] != "" && properties[4] != "") { - var landingTooltip = "Time: " + landingTime.toLocaleString() + "
Model Dataset: " + data.request.dataset + - "
Model Assumptions:
- " + data.request.ascent_rate + "m/s ascent
- " + data.request.burst_altitude + "m burst altitude (" + properties[3] + " samples)
- " + data.request.descent_rate + "m/s descent (" + properties[4] + " samples)"; - } else { - var landingTooltip = "Time: " + landingTime.toLocaleString() + "
Model Dataset: " + data.request.dataset + - "
Model Assumptions:
- " + data.request.ascent_rate + "m/s ascent
- " + data.request.burst_altitude + "m burst altitude
- " + data.request.descent_rate + "m/s descent"; - } - plot.landingMarker.bindTooltip(landingTooltip, {offset: [13,-28]}); -} - -function showPrediction(url) { - var ajaxReq = $.ajax({ - type: "GET", - url: url, - dataType: "json", - }); - predictionAjax.push(ajaxReq); - return ajaxReq; -} - -function deletePredictions(marker, station) { - var predictionDelete = $("#predictionControlButton"); - if (launchPredictions.hasOwnProperty(marker)) { - for (var prediction in launchPredictions[marker]) { - if (launchPredictions[marker].hasOwnProperty(prediction)) { - for (var object in launchPredictions[marker][prediction]) { - if (launchPredictions[marker][prediction].hasOwnProperty(object)) { - map.removeLayer(launchPredictions[marker][prediction][object]); - } - } - } - } - delete launchPredictions[marker]; - } - var popup = $("#popup" + station); - var predictionDeleteButton = popup.find("#predictionDeleteButton"); - if (predictionDeleteButton.is(':visible')) { - predictionDeleteButton.hide(); - } - if (Object.keys(launchPredictions).length == 0) predictionDelete.hide(); -} - -function getLaunchSites() { - $.ajax({ - type: "GET", - url: launches_url, - dataType: "json", - success: function(json) { - sites = json; - generateLaunchSites(); - } - }); -} - -function generateLaunchSites() { - for (var key in sites) { - if (sites.hasOwnProperty(key)) { - var latlon = [sites[key].position[1], sites[key].position[0]]; - var sondesList = ""; - var popupContent = "
"; - var div = document.createElement('div'); - div.id = "popup" + key; - var ascent_rate = 5; - var descent_rate = 6; - var burst_altitude = 26000; - var burst_samples = ""; - var descent_samples = ""; - var marker = new L.circleMarker(latlon, {color: '#696969', fillColor: "white", radius: 8}); - var popup = new L.popup({ autoClose: false, closeOnClick: false }); - marker.title = key; - marker.bindPopup(popup); - launches.addLayer(marker); - - // Match sonde codes - if (sites[key].hasOwnProperty('rs_types')) { - var sondes = sites[key].rs_types; - for (var y = 0; y < sondes.length; y++) { - if (Array.isArray(sondes[y]) == false) { - sondes[y] = [sondes[y]]; - } - if (sondeCodes.hasOwnProperty(sondes[y][0])) { - sondesList += sondeCodes[sondes[y][0]] - if (sondes[y].length > 1) { - sondesList += " (" + sondes[y][1] + " MHz)"; - } - } else if (unsupportedSondeCodes.hasOwnProperty(sondes[y][0])) { - sondesList += unsupportedSondeCodes[sondes[y][0]]; - sondesList += " (cannot track)"; - } else { - sondesList += sondes[y][0] + " (unknown WMO code)"; - } - if (y < sondes.length-1) { - sondesList += ", "; - } - } - if (sondes.includes("11") || sondes.includes("82")) { //LMS6 - ascent_rate = 5; - descent_rate = 2.5; - burst_altitude = 33500; - } - popupContent += "" + sites[key].station_name + "

Sondes launched: " + sondesList; - } - - // Generate prefilled suggestion form - var popupLink = "https://docs.google.com/forms/d/e/1FAIpQLSfIbBSQMZOXpNE4VpK4BqUbKDPCWCDgU9QxYgmhh-JD-JGSsQ/viewform?usp=pp_url&entry.796606853=Modify+Existing+Site"; - popupLink += "&entry.749833526=" + key; - if (sites[key].hasOwnProperty('station_name')) { - popupLink += "&entry.675505431=" + sites[key].station_name.replace(/\s/g, '+'); - } - if (sites[key].hasOwnProperty('position')) { - popupLink += "&entry.1613779787=" + sites[key].position.reverse().toString(); - } - if (sites[key].hasOwnProperty('alt')) { - popupLink += "&entry.753148337=" + sites[key].alt; - } - if (sites[key].hasOwnProperty('ascent_rate')) { - popupLink += "&entry.509146334=" + sites[key]["ascent_rate"]; - } - if (sites[key].hasOwnProperty('burst_altitude')) { - popupLink += "&entry.1897602989=" + sites[key]["burst_altitude"]; - } - if (sites[key].hasOwnProperty('descent_rate')) { - popupLink += "&entry.267462486=" + sites[key]["descent_rate"]; - } - if (sites[key].hasOwnProperty('notes')) { - popupLink += "&entry.197384117=" + sites[key]["notes"].replace(/\s/g, '+'); - } - - // Update prediction data if provided - if (sites[key].hasOwnProperty('ascent_rate')) { - ascent_rate = sites[key]["ascent_rate"]; - } - if (sites[key].hasOwnProperty('descent_rate')) { - descent_rate = sites[key]["descent_rate"]; - } - if (sites[key].hasOwnProperty('burst_altitude')) { - burst_altitude = sites[key]["burst_altitude"]; - } - if (sites[key].hasOwnProperty('burst_samples')) { - burst_samples = sites[key]["burst_samples"]; - } - if (sites[key].hasOwnProperty('descent_samples')) { - descent_samples = sites[key]["descent_samples"]; - } - - // Process launch schedule if provided - if (sites[key].hasOwnProperty('times')) { - popupContent += "
Launch schedule:"; - for (var x = 0; x < sites[key]['times'].length; x++) { - popupContent += "
- "; - var day = sites[key]['times'][x].split(":")[0]; - if (day == 0) { - popupContent += "Everyday at "; - } else if (day == 1) { - popupContent += "Monday at "; - } else if (day == 2) { - popupContent += "Tuesday at "; - } else if (day == 3) { - popupContent += "Wednesday at "; - } else if (day == 4) { - popupContent += "Thursday at "; - } else if (day == 5) { - popupContent += "Friday at "; - } else if (day == 6) { - popupContent += "Saturday at "; - } else if (day == 7) { - popupContent += "Sunday at "; - } - popupContent += sites[key]['times'][x].split(":")[1] + ":" + sites[key]['times'][x].split(":")[2] + " UTC"; - } - } - - // Show notes if provided - if (sites[key].hasOwnProperty('notes')) { - popupContent += "
Notes: " + sites[key]["notes"]; - } - - popupContent += "
Know when this site launches? Contribute here"; - - // Generate view historical button - popupContent += "
"; - - // Create prediction button - if (sites[key].hasOwnProperty('times')) { - popupContent += ""; - } else { - popupContent += ""; - } - - popupContent += ""; - - div.innerHTML = popupContent; - - popup.setContent(div.innerHTML); + }); - var leafletID = launches.getLayerId(marker); + // if we there is enough screen space open aboutbox on startup + if(!is_mobile && !offline.get('opt_nowelcome') && $(window).width() > 900) $('.nav li.about').click(); - stationMarkerLookup[key] = leafletID; - markerStationLookup[leafletID] = key; - } - } - if (focusID != 0) { - gotoSite(); - } + }, 500); } -function gotoSite() { - if (sites != null) { - if (sites.hasOwnProperty(focusID)) { - var site = sites[focusID]; - var latlng = new L.LatLng(site["position"][0], site["position"][1]); - map.setView(latlng, 9); - for (var i in launches._layers) { - marker = launches._layers - if (marker[i].title == focusID) { - marker[i].openPopup(); - } - } - } - } +function setTimeValue() { + setTimeout(function() { + document.getElementById("timeperiod").value = wvar.mode; + }, 100); } function shareVehicle(callsign) { @@ -1811,46 +1008,7 @@ function guess_name(key) { } function habitat_data(jsondata, alternative) { - var keys = { - "ascentrate": "Ascent Rate", - "battery_percent": "Battery", - "temperature_external": "Temperature, External", - "pressure_internal": "Pressure, Internal", - "voltage_solar_1": "Voltage, Solar 1", - "voltage_solar_2": "Voltage, Solar 2", - "light_red": "Light (Red)", - "light_green": "Light (Green)", - "light_blue": "Light (Blue)", - "gas_a": "Gas (A)", - "gas_b": "Gas (B)", - "gas_co2": "Gas (CO)", - "gas_combustible": "Gas (Combustible)", - "radiation": "Radiation (CPM)", - "temperature_radio": "Temperature, Radio", - "uplink_rssi": "Uplink RSSI", - "light_intensity": "Light Intensity", - "pred_lat": "Onboard Prediction (Lat)", - "pred_lon": "Onboard Prediction (Lon)", - "batt": "Battery Voltage", - "sats": "GNSS SVs Used", - "humidity": "Relative Humidity", - "subtype": "Sonde Sub-type", - "frequency": "Frequency", - "frequency_tx": "TX Frequency", - "manufacturer": "Manufacturer", - "type": "Sonde Type", - "burst_timer": "Burst Timer", - "xdata": "XDATA", - "xdata_instrument": "XDATA Instrument", - "oif411_ozone_battery_v": "OIF411 Battery", - "oif411_ozone_current_uA": "Ozone Current", - "oif411_ozone_pump_curr_mA": "Ozone Pump Current", - "oif411_ozone_pump_temp": "Ozone Pump Temperature", - "oif411_serial": "OIF411 Serial Number", - "oif411_diagnostics": "OIF411 Diagnostics", - "oif411_version": "OIF411 Version", - "oif411_O3_partial_pressure": "Ozone Partial Pressure" - }; + var keys = globalKeys; var tooltips = { "burst_timer": "If active, this indicates the time (HH:MM:SS) until the radiosonde will automatically power-off.", @@ -1862,39 +1020,10 @@ function habitat_data(jsondata, alternative) { "spam": true, "battery_millivolts": true, "temperature_internal_x10": true, - "uplink_rssi_raw": true + "uplink_rssi_raw": true, }; - var suffixes = { - "current": " A", - "battery": " V", - "batt": " V", - "solar_panel": " V", - "temperature": "°C", - "temperature_internal": "°C", - "temperature_external": "°C", - "temperature_radio": "°C", - "pressure": " hPa", - "voltage_solar_1": " V", - "voltage_solar_2": " V", - "battery_percent": "%", - "uplink_rssi": " dBm", - "rssi_last_message": " dBm", - "rssi_floor": " dBm", - "bearing": "°", - "iss_azimuth": "°", - "iss_elevation": "°", - "light_intensity": " lx", - "humidity": " %", - "frequency": " MHz", - "frequency_tx": " MHz", - "spam": "", - "oif411_ozone_battery_v": " V", - "oif411_ozone_current_uA": " uA", - "oif411_ozone_pump_curr_mA": " mA", - "oif411_ozone_pump_temp": "°C", - "oif411_O3_partial_pressure": " mPa (+/- 1)" - }; + var suffixes = globalSuffixes; try { @@ -3238,32 +2367,27 @@ function mapInfoBox_handle_path_new(data, vehicle, date) { xdata_pressure = 1100.0; } var tempXDATA = parseXDATA(data.xdata, xdata_pressure); - if (tempXDATA.hasOwnProperty('xdata_instrument')) { - html += "
XDATA Instrument: " + tempXDATA.xdata_instrument.join(', ') + "
"; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - html += "
OIF411 Battery: " + tempXDATA.oif411_ozone_battery_v + " V
"; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - html += "
Ozone Current: " + tempXDATA.oif411_ozone_current_uA + " uA
"; - } - if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - html += "
Ozone Partial Presure: " + tempXDATA.oif411_O3_partial_pressure + " mPa (+/- 1)
"; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - html += "
Ozone Pump Current: " + tempXDATA.oif411_ozone_pump_curr_mA + " mA
"; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - html += "
Ozone Pump Temperature: " + tempXDATA.oif411_ozone_pump_temp + "°C
"; - } - if (tempXDATA.hasOwnProperty('oif411_serial')) { - html += "
OIF411 Serial Number: " + tempXDATA.oif411_serial + "
"; - } - if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - html += "
OIF411 Diagnostics: " + tempXDATA.oif411_diagnostics + "
"; - } - if (tempXDATA.hasOwnProperty('oif411_version')) { - html += "
OIF411 Version: " + tempXDATA.oif411_version + "
"; + for (let field in tempXDATA) { + if (tempXDATA.hasOwnProperty(field)) { + if (field == "xdata_instrument") { + html += "
XDATA Instrument: " + tempXDATA.xdata_instrument.join(', ') + "
"; + } else { + if (globalKeys.hasOwnProperty(field)) { + if (globalSuffixes.hasOwnProperty(field)) { + html += "
" + globalKeys[field] + ": " + tempXDATA[field] + globalSuffixes[field] + "
"; + } else { + html += "
" + globalKeys[field] + ": " + tempXDATA[field] + "
"; + } + + } else { + if (globalSuffixes.hasOwnProperty(field)) { + html += "
" + guess_name(field) + ": " + tempXDATA[field] + globalSuffixes[field] + "
"; + } else { + html += "
" + guess_name(field) + ": " + tempXDATA[field] + "
"; + } + } + } + } } }; @@ -4233,499 +3357,6 @@ function graphAddPosition(vcallsign, new_data) { } } -function formatData(data, live) { - var response = {}; - response.positions = {}; - var dataTemp = []; - if (live) { - if (data.length) { - for (let entry in data) { - var dataTempEntry = {}; - var station = data[entry].uploader_callsign; - dataTempEntry.callsign = {}; - //check if other stations also received this packet - if (vehicles.hasOwnProperty(data[entry].serial)) { - if (data[entry].datetime == vehicles[data[entry].serial].curr_position.gps_time) { - for (let key in vehicles[data[entry].serial].curr_position.callsign) { - if (vehicles[data[entry].serial].curr_position.callsign.hasOwnProperty(key)) { - if (key != station) { - dataTempEntry.callsign[key] = {}; - if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("snr")) { - dataTempEntry.callsign[key].snr = vehicles[data[entry].serial].curr_position.callsign[key].snr; - } - if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("rssi")) { - dataTempEntry.callsign[key].rssi = vehicles[data[entry].serial].curr_position.callsign[key].rssi; - } - if (vehicles[data[entry].serial].curr_position.callsign[key].hasOwnProperty("frequency")) { - dataTempEntry.callsign[key].frequency = vehicles[data[entry].serial].curr_position.callsign[key].frequency; - } - } - } - } - } - } - dataTempEntry.callsign[station] = {}; - if (data[entry].snr) { - dataTempEntry.callsign[station].snr = data[entry].snr; - } - if (data[entry].rssi) { - dataTempEntry.callsign[station].rssi = data[entry].rssi; - } - if (data[entry].frequency) { - dataTempEntry.callsign[station].frequency = data[entry].frequency; - } - dataTempEntry.gps_alt = data[entry].alt; - dataTempEntry.gps_lat = data[entry].lat; - dataTempEntry.gps_lon = data[entry].lon; - if (data[entry].heading) { - dataTempEntry.gps_heading = data[entry].heading; - } - dataTempEntry.gps_time = data[entry].datetime; - dataTempEntry.server_time = data[entry].datetime; - dataTempEntry.vehicle = data[entry].serial; - dataTempEntry.position_id = data[entry].serial + "-" + data[entry].datetime; - dataTempEntry.data = {}; - if (data[entry].batt) { - dataTempEntry.data.batt = data[entry].batt; - } - if (data[entry].burst_timer) { - dataTempEntry.data.burst_timer = data[entry].burst_timer; - } - if (data[entry].frequency) { - dataTempEntry.data.frequency = data[entry].frequency; - } - if (data[entry].tx_frequency) { - dataTempEntry.data.frequency_tx = data[entry].tx_frequency; - } - if (data[entry].hasOwnProperty("humidity")) { - dataTempEntry.data.humidity = data[entry].humidity; - } - if (data[entry].manufacturer) { - dataTempEntry.data.manufacturer = data[entry].manufacturer; - } - if (data[entry].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[entry].pressure; - } - if (data[entry].sats) { - dataTempEntry.data.sats = data[entry].sats; - } - if (data[entry].hasOwnProperty("temp")) { - dataTempEntry.data.temperature_external = data[entry].temp; - } - if (data[entry].type) { - dataTempEntry.data.type = data[entry].type; - dataTempEntry.type = data[entry].type; - } - if (data[entry].subtype) { - dataTempEntry.data.type = data[entry].subtype; - dataTempEntry.type = data[entry].subtype; - } - if (data[entry].xdata) { - dataTempEntry.data.xdata = data[entry].xdata; - - if (data[entry].hasOwnProperty("pressure")) { - xdata_pressure = data[entry].pressure; - } else { - xdata_pressure = 1100.0; - } - var tempXDATA = parseXDATA(data[entry].xdata, xdata_pressure); - if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); - } - if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; - } - if (tempXDATA.hasOwnProperty('oif411_serial')) { - dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; - } - if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; - } - if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.data.oif411_version = tempXDATA.oif411_version; - } - if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; - } - } - if (data[entry].serial.toLowerCase() != "xxxxxxxx") { - dataTemp.push(dataTempEntry); - } - } - } else { - var dataTempEntry = {}; - var station = data.uploader_callsign; - dataTempEntry.callsign = {}; - //check if other stations also received this packet - if (vehicles.hasOwnProperty(data.serial)) { - if (data.datetime == vehicles[data.serial].curr_position.gps_time) { - for (let key in vehicles[data.serial].curr_position.callsign) { - if (vehicles[data.serial].curr_position.callsign.hasOwnProperty(key)) { - if (key != station) { - dataTempEntry.callsign[key] = {}; - if (vehicles[data.serial].curr_position.callsign[key].hasOwnProperty("snr")) { - dataTempEntry.callsign[key].snr = vehicles[data.serial].curr_position.callsign[key].snr; - } - if (vehicles[data.serial].curr_position.callsign[key].hasOwnProperty("rssi")) { - dataTempEntry.callsign[key].rssi = vehicles[data.serial].curr_position.callsign[key].rssi; - } - if (vehicles[data.serial].curr_position.callsign[key].hasOwnProperty("frequency")) { - dataTempEntry.callsign[key].frequency = vehicles[data.serial].curr_position.callsign[key].frequency; - } - } - } - } - } - } - dataTempEntry.callsign[station] = {}; - if (data.snr) { - dataTempEntry.callsign[station].snr = data.snr; - } - if (data.rssi) { - dataTempEntry.callsign[station].rssi = data.rssi; - } - if (data.frequency) { - dataTempEntry.callsign[station].frequency = data.frequency; - } - dataTempEntry.gps_alt = data.alt; - dataTempEntry.gps_lat = data.lat; - dataTempEntry.gps_lon = data.lon; - if (data.heading) { - dataTempEntry.gps_heading = data.heading; - } - dataTempEntry.gps_time = data.datetime; - dataTempEntry.server_time = data.datetime; - dataTempEntry.vehicle = data.serial; - dataTempEntry.position_id = data.serial + "-" + data.datetime; - dataTempEntry.data = {}; - if (data.batt) { - dataTempEntry.data.batt = data.batt; - } - if (data.burst_timer) { - dataTempEntry.data.burst_timer = data.burst_timer; - } - if (data.frequency) { - dataTempEntry.data.frequency = data.frequency; - } - if (data.tx_frequency) { - dataTempEntry.data.frequency_tx = data.tx_frequency; - } - if (data.hasOwnProperty("humidity")) { - dataTempEntry.data.humidity = data.humidity; - } - if (data.manufacturer) { - dataTempEntry.data.manufacturer = data.manufacturer; - } - if (data.hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data.pressure; - } - if (data.sats) { - dataTempEntry.data.sats = data.sats; - } - if (data.hasOwnProperty("temp")) { - dataTempEntry.data.temperature_external = data.temp; - } - if (data.type) { - dataTempEntry.data.type = data.type; - dataTempEntry.type = data.type; - } - if (data.subtype) { - dataTempEntry.data.type = data.subtype; - dataTempEntry.type = data.subtype; - } - if (data.xdata) { - dataTempEntry.data.xdata = data.xdata; - if (data.hasOwnProperty("pressure")) { - xdata_pressure = data.pressure; - } else { - xdata_pressure = 1100.0; - } - var tempXDATA = parseXDATA(data.xdata, xdata_pressure); - if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); - } - if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; - } - if (tempXDATA.hasOwnProperty('oif411_serial')) { - dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; - } - if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; - } - if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.data.oif411_version = tempXDATA.oif411_version; - } - if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; - } - } - if (data.serial.toLowerCase() != "xxxxxxxx") { - dataTemp.push(dataTempEntry); - } - } - } else if (data.length == null) { - for (let key in data) { - if (data.hasOwnProperty(key)) { - if (typeof data[key] === 'object') { - for (let i in data[key]) { - var dataTempEntry = {}; - var station = data[key][i].uploader_callsign; - dataTempEntry.callsign = {}; - dataTempEntry.callsign[station] = {}; - if (data[key][i].snr) { - dataTempEntry.callsign[station].snr = data[key][i].snr; - } - if (data[key][i].rssi) { - dataTempEntry.callsign[station].rssi = data[key][i].rssi; - } - if (data[key][i].frequency) { - dataTempEntry.callsign[station].frequency = data[key][i].frequency; - } - dataTempEntry.gps_alt = data[key][i].alt; - dataTempEntry.gps_lat = data[key][i].lat; - dataTempEntry.gps_lon = data[key][i].lon; - if (data[key][i].heading) { - dataTempEntry.gps_heading = data[key][i].heading; - } - dataTempEntry.gps_time = data[key][i].datetime; - dataTempEntry.server_time = data[key][i].datetime; - dataTempEntry.vehicle = data[key][i].serial; - dataTempEntry.position_id = data[key][i].serial + "-" + data[key][i].datetime; - dataTempEntry.data = {}; - if (data[key][i].batt) { - dataTempEntry.data.batt = data[key][i].batt; - } - if (data[key][i].burst_timer) { - dataTempEntry.data.burst_timer = data[key][i].burst_timer; - } - if (data[key][i].frequency) { - dataTempEntry.data.frequency = data[key][i].frequency; - } - if (data[key][i].tx_frequency) { - dataTempEntry.data.frequency_tx = data[key][i].tx_frequency; - } - if (data[key][i].hasOwnProperty("humidity")) { - dataTempEntry.data.humidity = data[key][i].humidity; - } - if (data[key][i].manufacturer) { - dataTempEntry.data.manufacturer = data[key][i].manufacturer; - } - if (data[key][i].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[key][i].pressure; - } - if (data[key][i].sats) { - dataTempEntry.data.sats = data[key][i].sats; - } - if (data[key][i].hasOwnProperty("temp")) { - dataTempEntry.data.temperature_external = data[key][i].temp; - } - if (data[key][i].type) { - dataTempEntry.data.type = data[key][i].type; - dataTempEntry.type = data[key][i].type; - } - if (data[key][i].subtype) { - dataTempEntry.data.type = data[key][i].subtype; - dataTempEntry.type = data[key][i].subtype; - } - if (data[key][i].xdata) { - dataTempEntry.data.xdata = data[key][i].xdata; - if (data[key][i].hasOwnProperty("pressure")) { - xdata_pressure = data[key][i].pressure; - } else { - xdata_pressure = 1100.0; - } - var tempXDATA = parseXDATA(data[key][i].xdata, xdata_pressure); - if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); - } - if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; - } - if (tempXDATA.hasOwnProperty('oif411_serial')) { - dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; - } - if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; - } - if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.data.oif411_version = tempXDATA.oif411_version; - } - if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; - } - } - if (data[key][i].serial.toLowerCase() != "xxxxxxxx") { - dataTemp.push(dataTempEntry); - } - } - } - } - } - } else { - for (var i = data.length - 1; i >= 0; i--) { - if (data[i].hasOwnProperty('subtype') && data[i].subtype == "SondehubV1") { - var dataTempEntry = {}; - var station = data[i].uploader_callsign; - dataTempEntry.callsign = {}; - dataTempEntry.callsign[station] = {}; - dataTempEntry.gps_alt = parseFloat(data[i].alt); - dataTempEntry.gps_lat = parseFloat(data[i].lat); - dataTempEntry.gps_lon = parseFloat(data[i].lon); - dataTempEntry.gps_time = data[i].time_received; - dataTempEntry.server_time = data[i].time_received; - dataTempEntry.vehicle = data[i].serial; - dataTempEntry.position_id = data[i].serial + "-" + data[i].time_received; - dataTempEntry.data = {}; - if (data[i].humidity) { - dataTempEntry.data.humidity = parseFloat(data[i].humidity); - } - if (data[i].temp) { - dataTempEntry.data.temperature_external = parseFloat(data[i].temp); - } - dataTemp.push(dataTempEntry); - } else { - var dataTempEntry = {}; - var station = data[i].uploader_callsign; - dataTempEntry.callsign = {}; - dataTempEntry.callsign[station] = {}; - if (data[i].snr) { - dataTempEntry.callsign[station].snr = data[i].snr; - } - if (data[i].rssi) { - dataTempEntry.callsign[station].rssi = data[i].rssi; - } - if (data[i].frequency) { - dataTempEntry.callsign[station].frequency = data[i].frequency; - } - dataTempEntry.gps_alt = data[i].alt; - dataTempEntry.gps_lat = data[i].lat; - dataTempEntry.gps_lon = data[i].lon; - if (data[i].heading) { - dataTempEntry.gps_heading = data[i].heading; - } - dataTempEntry.gps_time = data[i].datetime; - dataTempEntry.server_time = data[i].datetime; - dataTempEntry.vehicle = data[i].serial; - dataTempEntry.position_id = data[i].serial + "-" + data[i].datetime; - dataTempEntry.data = {}; - if (data[i].batt) { - dataTempEntry.data.batt = data[i].batt; - } - if (data[i].burst_timer) { - dataTempEntry.data.burst_timer = data[i].burst_timer; - } - if (data[i].frequency) { - dataTempEntry.data.frequency = data[i].frequency; - } - if (data[i].tx_frequency) { - dataTempEntry.data.frequency_tx = data[i].tx_frequency; - } - if (data[i].hasOwnProperty("humidity")) { - dataTempEntry.data.humidity = data[i].humidity; - } - if (data[i].manufacturer) { - dataTempEntry.data.manufacturer = data[i].manufacturer; - } - if (data[i].hasOwnProperty("pressure")) { - dataTempEntry.data.pressure = data[i].pressure; - } - if (data[i].sats) { - dataTempEntry.data.sats = data[i].sats; - } - if (data[i].hasOwnProperty("temp")) { - dataTempEntry.data.temperature_external = data[i].temp; - } - if (data[i].type && data[i].type == "payload_telemetry") { // SondeHub V1 data - var comment = data[i].comment.split(" "); - if (v1types.hasOwnProperty(comment[0])) { - dataTempEntry.data.type = v1types[comment[0]]; - dataTempEntry.type = v1types[comment[0]]; - if (v1manufacturers.hasOwnProperty(dataTempEntry.type)) { - dataTempEntry.data.manufacturer = v1manufacturers[dataTempEntry.type]; - } - } - dataTempEntry.data.frequency = comment[2]; - } else if (data[i].type) { - dataTempEntry.data.type = data[i].type; - dataTempEntry.type = data[i].type; - } - if (data[i].subtype) { - dataTempEntry.data.type = data[i].subtype; - dataTempEntry.type = data[i].subtype; - } - if (data[i].xdata) { - dataTempEntry.data.xdata = data[i].xdata; - if (data[i].hasOwnProperty("pressure")) { - xdata_pressure = data[i].pressure; - } else { - xdata_pressure = 1100.0; - } - var tempXDATA = parseXDATA(data[i].xdata, xdata_pressure); - if (tempXDATA.hasOwnProperty('xdata_instrument')) { - dataTempEntry.data.xdata_instrument = tempXDATA.xdata_instrument.join(', '); - } - if (tempXDATA.hasOwnProperty('oif411_ozone_battery_v')) { - dataTempEntry.data.oif411_ozone_battery_v = tempXDATA.oif411_ozone_battery_v; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_current_uA')) { - dataTempEntry.data.oif411_ozone_current_uA = tempXDATA.oif411_ozone_current_uA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_curr_mA')) { - dataTempEntry.data.oif411_ozone_pump_curr_mA = tempXDATA.oif411_ozone_pump_curr_mA; - } - if (tempXDATA.hasOwnProperty('oif411_ozone_pump_temp')) { - dataTempEntry.data.oif411_ozone_pump_temp = tempXDATA.oif411_ozone_pump_temp; - } - if (tempXDATA.hasOwnProperty('oif411_serial')) { - dataTempEntry.data.oif411_serial = tempXDATA.oif411_serial; - } - if (tempXDATA.hasOwnProperty('oif411_diagnostics')) { - dataTempEntry.data.oif411_diagnostics = tempXDATA.oif411_diagnostics; - } - if (tempXDATA.hasOwnProperty('oif411_version')) { - dataTempEntry.data.oif411_version = tempXDATA.oif411_version; - } - if (tempXDATA.hasOwnProperty('oif411_O3_partial_pressure')) { - dataTempEntry.data.oif411_O3_partial_pressure = tempXDATA.oif411_O3_partial_pressure; - } - } - dataTemp.push(dataTempEntry); - } - } - } - response.positions.position = dataTemp; - response.fetch_timestamp = Date.now(); - return response; -} - var ajax_positions = null; var ajax_positions_single = null; var ajax_positions_single_new = null; @@ -5115,60 +3746,6 @@ function refreshPredictions() { }); } -// Get initial summary data for station, courtesy of TimMcMahon -function getHistorical (id, callback, continuation) { - var prefix = 'launchsites/' + id + '/'; - var params = { - Prefix: prefix, - }; - - if (typeof continuation !== 'undefined') { - params.ContinuationToken = continuation; - } else { - tempLaunchData = {}; - } - - s3.makeUnauthenticatedRequest('listObjectsV2', params, function(err, data) { - if (err) { - console.log(err, err.stack); - } else { - var tempSerials = []; - for (var i = 0; i < data.Contents.length; i++) { - // Sort data into year and month groups - var date = data.Contents[i].Key.substring(prefix.length).substring(0,10); - var year = date.substring(0,4); - var month = date.substring(5,7); - var serial = data.Contents[i].Key.substring(prefix.length+11).slice(0, -5); - if (tempLaunchData.hasOwnProperty(year)) { - if (tempLaunchData[year].hasOwnProperty(month)) { - if (!tempSerials.includes(serial)) { - tempSerials.push(serial) - tempLaunchData[year][month].push(data.Contents[i].Key); - } - } else { - tempLaunchData[year][month] = []; - tempSerials = []; - tempSerials.push(serial) - tempLaunchData[year][month].push(data.Contents[i].Key); - } - } else { - tempLaunchData[year] = {}; - tempLaunchData[year][month] = []; - tempSerials = []; - tempSerials.push(serial) - tempLaunchData[year][month].push(data.Contents[i].Key); - } - } - if (data.IsTruncated == true) { - // Requests are limited to 1000 entries so multiple may be required - getHistorical(id, callback, data.NextContinuationToken); - } else { - callback(tempLaunchData); - } - } - }); -} - var periodical, periodical_focus, periodical_focus_new, periodical_receivers, periodical_listeners; var periodical_predictions = null; var timer_seconds = 5; diff --git a/js/xdata.js b/js/xdata.js index c55fa84..7fb9b82 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -9,20 +9,6 @@ OIF411_Cef_Pressure = [ 0, 2, 3, 5, 10, 20, 30, 50, 100, 200, 300, 500, 1000, 1100]; OIF411_Cef = [ 1.171, 1.171, 1.131, 1.092, 1.055, 1.032, 1.022, 1.015, 1.011, 1.008, 1.006, 1.004, 1, 1]; -// https://stackoverflow.com/a/34679269/9389353 -function hexToInt(hex) { - // Helper function to convert a signed hex value to an integer - if (hex.length % 2 != 0) { - hex = "0" + hex; - } - var num = parseInt(hex, 16); - var maxVal = Math.pow(2, hex.length / 2 * 8); - if (num > maxVal / 2 - 1) { - num = num - maxVal - } - return num; -} - function lerp(x, y, a){ // Helper function for linear interpolation between two points return x * (1 - a) + y * a @@ -110,7 +96,10 @@ function parseOIF411(xdata, pressure){ } else if (xdata.length == 20){ // Measurement Data (Table 18) // Ozone pump temperature - signed int16 - _ozone_pump_temp = hexToInt(xdata.substr(4,4)); + _ozone_pump_temp = parseInt(xdata.substr(4,4),16); + if ((_ozone_pump_temp & 0x8000) > 0) { + _ozone_pump_temp = _ozone_pump_temp - 0x10000; + } _ozone_pump_temp = _ozone_pump_temp*0.01; // Degrees C _output['oif411_ozone_pump_temp'] = Math.round(_ozone_pump_temp * 10) / 10; // 1 DP @@ -176,7 +165,10 @@ function parseCFH(xdata) { _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); // Mirror temperature - _mirror_temperature = hexToInt(xdata.substr(4,6)); + _mirror_temperature = parseInt(xdata.substr(4,6),16); + if ((_mirror_temperature & 0x80000) > 0) { + _mirror_temperature = _mirror_temperature - 0x1000000; + } _mirror_temperature = _mirror_temperature*0.00001; // Degrees C _output['cfh_mirror_temperature'] = Math.round(_mirror_temperature*100000) / 100000; // 5 DP @@ -185,7 +177,11 @@ function parseCFH(xdata) { _output['cfh_optics_voltage'] = Math.round(_optics_voltage*1000000) / 1000000; // 6 DP // Optics temperature - _optics_temperature = hexToInt(xdata.substr(16,4))*0.01; // Degrees C + _optics_temperature = parseInt(xdata.substr(16,4),16); + if ((_optics_temperature & 0x8000) > 0) { + _optics_temperature = _optics_temperature - 0x10000; + } + _optics_temperature = _optics_temperature*0.01; // Degrees C _output['cfh_optics_temperature'] = Math.round(_optics_temperature*100) / 100; // 2 DP // CFH battery @@ -227,24 +223,39 @@ function parseCOBALD(xdata) { _output['cobald_sonde_number'] = parseInt(xdata.substr(4,3),16); // Internal temperature - _internal_temperature = hexToInt(xdata.substr(7,3)); + _internal_temperature = parseInt(xdata.substr(7,3),16); + if ((_internal_temperature & 0x800) > 0) { + _internal_temperature = _internal_temperature - 0x1000; + } _internal_temperature = _internal_temperature/8; // Degrees C _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 10) / 10; // 1 DP // Blue backscatter - _blue_backscatter = hexToInt(xdata.substr(10,6)); + _blue_backscatter = parseInt(xdata.substr(10,6),16); + if ((_blue_backscatter & 0x800000) > 0) { + _blue_backscatter = _blue_backscatter - 0x1000000; + } _output['cobald_blue_backscatter'] = _blue_backscatter; // Red backckatter - _red_backscatter = hexToInt(xdata.substr(16,6)); + _red_backscatter = parseInt(xdata.substr(16,6),16); + if ((_red_backscatter & 0x800000) > 0) { + _red_backscatter = _red_backscatter - 0x1000000; + } _output['cobald_red_backscatter'] = _red_backscatter; // Blue monitor - _blue_monitor = hexToInt(xdata.substr(22,4)); + _blue_monitor = parseInt(xdata.substr(22,4),16); + if ((_blue_monitor & 0x8000) > 0) { + _blue_monitor = _blue_monitor - 0x10000; + } _output['cobald_blue_monitor'] = _blue_monitor; // Red monitor - _red_monitor = hexToInt(xdata.substr(26,4)); + _red_monitor = parseInt(xdata.substr(26,4),16); + if ((_red_monitor & 0x8000) > 0) { + _red_monitor = _red_monitor - 0x10000; + } _output['cobald_red_monitor'] = _red_monitor; return _output @@ -267,7 +278,7 @@ function parseXDATA(data, pressure){ data_split = [data]; } - _output = {}; + _output = {"xdata_instrument": []}; _instruments = []; for(xdata_i = 0; xdata_i < data_split.length; xdata_i++){ _current_xdata = data_split[xdata_i]; From c60216ae7a01bbd78891fd1a8c06b839746fb031 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 10 Jan 2022 20:26:01 +1100 Subject: [PATCH 16/25] fixes --- js/tracker.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index bf35a58..4dd6104 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -1021,6 +1021,11 @@ function habitat_data(jsondata, alternative) { "battery_millivolts": true, "temperature_internal_x10": true, "uplink_rssi_raw": true, + "oif411_instrument_number": true, + "oif411_ext_voltage": true, + "cfh_instrument_number": true, + "cobald_instrument_number": true, + "cobald_sonde_number": true }; var suffixes = globalSuffixes; @@ -3111,7 +3116,7 @@ function addPosition(position) { // Graph Stuff -var graph_inhibited_fields = ['frequency', 'frequency_tx', 'burst_timer', 'xdata', 'oif411_ozone_pump_temp', 'oif411_ozone_battery_v', 'oif411_ozone_pump_curr_mA', 'oif411_serial', 'oif411_version', 'oif411_ozone_current_uA']; +var graph_fields = ['altitude', 'pred.alt', 'batt', 'humidity', 'pressure', 'sats', 'temperature_external', 'oif411_O3_partial_pressure']; function updateGraph(vcallsign, reset_selection) { if(!plot || !plot_open) return; @@ -3285,7 +3290,7 @@ function graphAddPosition(vcallsign, new_data) { i = (k in vehicle.graph_data_map) ? vehicle.graph_data_map[k] : data.length; // Disable plotting of a few fields. - if (graph_inhibited_fields.includes(k)){ + if (!(graph_fields.includes(k))){ return; } From cbfbb00c22cbc6d1fc732e08dda9c345158d6a85 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Tue, 11 Jan 2022 12:42:58 +1100 Subject: [PATCH 17/25] handle single sonde --- js/format.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/js/format.js b/js/format.js index 11cfe8b..a1ee090 100644 --- a/js/format.js +++ b/js/format.js @@ -8,6 +8,9 @@ function formatData(data, live) { response.positions = {}; var dataTemp = []; if (live) { // Websockets + if (!data.length) { // handle single sonde + data = {"entry": data}; + } for (let entry in data) { var dataTempEntry = {}; var station = data[entry].uploader_callsign; From 17732bb52d772595ec5a2c77119cb72f77b103b7 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Tue, 11 Jan 2022 17:18:15 +1100 Subject: [PATCH 18/25] xdata updates --- js/xdata.js | 96 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 37 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index 7fb9b82..cf738f4 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -100,19 +100,19 @@ function parseOIF411(xdata, pressure){ if ((_ozone_pump_temp & 0x8000) > 0) { _ozone_pump_temp = _ozone_pump_temp - 0x10000; } - _ozone_pump_temp = _ozone_pump_temp*0.01; // Degrees C - _output['oif411_ozone_pump_temp'] = Math.round(_ozone_pump_temp * 10) / 10; // 1 DP + _ozone_pump_temp = _ozone_pump_temp*0.01; // Degrees C (5 - 35) + _output['oif411_ozone_pump_temp'] = Math.round(_ozone_pump_temp * 100) / 100; // 2 DP // Ozone Current - _ozone_current_uA = parseInt(xdata.substr(8,5),16)*0.0001; // micro-Amps + _ozone_current_uA = parseInt(xdata.substr(8,5),16)*0.0001; // micro-Amps (0.05 - 30) _output['oif411_ozone_current_uA'] = Math.round(_ozone_current_uA * 10000) / 10000; // 4 DP // Battery Voltage - _ozone_battery_v = parseInt(xdata.substr(13,2),16)*0.1; // Volts + _ozone_battery_v = parseInt(xdata.substr(13,2),16)*0.1; // Volts (14 - 19) _output['oif411_ozone_battery_v'] = Math.round(_ozone_battery_v * 10) / 10; // 1 DP // Ozone Pump Current - _ozone_pump_curr_mA = parseInt(xdata.substr(15,3),16); // mA + _ozone_pump_curr_mA = parseInt(xdata.substr(15,3),16); // mA (30 - 110) _output['oif411_ozone_pump_curr_mA'] = Math.round(_ozone_pump_curr_mA * 10) / 10; // 1 DP // External Voltage @@ -164,30 +164,6 @@ function parseCFH(xdata) { // Instrument number is common to all XDATA types. _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); - // Mirror temperature - _mirror_temperature = parseInt(xdata.substr(4,6),16); - if ((_mirror_temperature & 0x80000) > 0) { - _mirror_temperature = _mirror_temperature - 0x1000000; - } - _mirror_temperature = _mirror_temperature*0.00001; // Degrees C - _output['cfh_mirror_temperature'] = Math.round(_mirror_temperature*100000) / 100000; // 5 DP - - // Optics voltage - _optics_voltage = parseInt(xdata.substr(10,6),16)*0.000001; // Volts - _output['cfh_optics_voltage'] = Math.round(_optics_voltage*1000000) / 1000000; // 6 DP - - // Optics temperature - _optics_temperature = parseInt(xdata.substr(16,4),16); - if ((_optics_temperature & 0x8000) > 0) { - _optics_temperature = _optics_temperature - 0x10000; - } - _optics_temperature = _optics_temperature*0.01; // Degrees C - _output['cfh_optics_temperature'] = Math.round(_optics_temperature*100) / 100; // 2 DP - - // CFH battery - _battery = parseInt(xdata.substr(20,4),16)*0.01; // Volts - _output['cfh_battery'] = Math.round(_battery*100) / 100; // 2 DP - return _output } @@ -227,37 +203,80 @@ function parseCOBALD(xdata) { if ((_internal_temperature & 0x800) > 0) { _internal_temperature = _internal_temperature - 0x1000; } - _internal_temperature = _internal_temperature/8; // Degrees C - _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 10) / 10; // 1 DP + _internal_temperature = _internal_temperature/8; // Degrees C (-40 - 50) + _output['cobald_internal_temperature'] = Math.round(_internal_temperature * 100) / 100; // 2 DP // Blue backscatter _blue_backscatter = parseInt(xdata.substr(10,6),16); if ((_blue_backscatter & 0x800000) > 0) { _blue_backscatter = _blue_backscatter - 0x1000000; } - _output['cobald_blue_backscatter'] = _blue_backscatter; + _output['cobald_blue_backscatter'] = _blue_backscatter; // (0 - 1000000) // Red backckatter _red_backscatter = parseInt(xdata.substr(16,6),16); if ((_red_backscatter & 0x800000) > 0) { _red_backscatter = _red_backscatter - 0x1000000; } - _output['cobald_red_backscatter'] = _red_backscatter; + _output['cobald_red_backscatter'] = _red_backscatter; // (0 - 1000000) // Blue monitor _blue_monitor = parseInt(xdata.substr(22,4),16); if ((_blue_monitor & 0x8000) > 0) { _blue_monitor = _blue_monitor - 0x10000; } - _output['cobald_blue_monitor'] = _blue_monitor; + _output['cobald_blue_monitor'] = _blue_monitor; // (-32768 - 32767) // Red monitor _red_monitor = parseInt(xdata.substr(26,4),16); if ((_red_monitor & 0x8000) > 0) { _red_monitor = _red_monitor - 0x10000; } - _output['cobald_red_monitor'] = _red_monitor; + _output['cobald_red_monitor'] = _red_monitor; // (-32768 - 32767) + + return _output +} + +function parseSKYDEW(xdata) { + // Attempt to parse an XDATA string from a SKYDEW Peltier-based chilled-mirror hygrometer + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // https://www.gruan.org/gruan/editor/documents/meetings/icm-12/pres/pres_306_Sugidachi_SKYDEW.pdf + // + // Sample data: 3F0141DF73B940F600150F92FF27D5C8304102 (length = 38 characters) + + // Cast to string if not already + xdata = String(xdata); + + // Run some checks over the input + if(xdata.length != 38){ + // Invalid SKYDEW dataset + return {}; + } + if(xdata.substr(0,2) !== '3F'){ + // Not a SKYDEW (shouldn't get here) + return {}; + } + + var _output = {}; + + // Instrument number is common to all XDATA types. + _output['skydew_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Other fields may include + // Serial number + // Mirror temperature (-120 - 30) + // Mixing ratio V (ppmV) + // PT100 (Ohm 60 - 120) + // SCA light + // SCA base + // PLT current + // HS temp + // CB temp + // PID + // Battery return _output } @@ -269,7 +288,7 @@ function parseXDATA(data, pressure){ // "0501R20234850000006EI" // "0501034F02CA08B06700#800261FCA6F80012F6F40A75" // "800262358C080012FE6C0A70#0501035902BA08908400" - // "0802AC83D88AB61107A30175" + // "0501092C000000000000#190214f0df03e82e03660048d73683#0803DC5EF086C244078601A5#3F04475A4B0D415900160D510C270200465900" // Split apart any contatenated xdata. if(data.includes('#')){ @@ -322,6 +341,7 @@ function parseXDATA(data, pressure){ if (!_instruments.includes("OPC")) _instruments.push('OPC'); } else if (_instrument === '3C'){ // PCFH + // SRNO, H0, H1, F0, F1 // 3c010000184b4b5754 // 3c0103ce7b58647a98748befff // 3c010148719fff8e54b9af627e249fe0 @@ -335,6 +355,8 @@ function parseXDATA(data, pressure){ if (!_instruments.includes("TRAPS")) _instruments.push('TRAPS'); } else if (_instrument === '3F'){ // SKYDEW + _xdata_temp = parseSKYDEW(_current_xdata); + _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("SKYDEW")) _instruments.push('SKYDEW'); } else if (_instrument === '41'){ // CICANUM @@ -351,7 +373,7 @@ function parseXDATA(data, pressure){ } } - _output["xdata_instrument"] = _instruments; + if (_instrument.length > 0) _output["xdata_instrument"] = _instruments; return _output From df8a5b228b23a987cb8340c6b0d07f198c40030c Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:26:50 +1100 Subject: [PATCH 19/25] PCFH Support --- js/tracker.js | 60 ++++++++++++++- js/xdata.js | 209 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 242 insertions(+), 27 deletions(-) diff --git a/js/tracker.js b/js/tracker.js index 4dd6104..5dba81c 100644 --- a/js/tracker.js +++ b/js/tracker.js @@ -215,7 +215,42 @@ var globalKeys = { "cobald_blue_backscatter": "COBALD Blue Backscatter", "cobald_red_backscatter": "COBALD Red Backscatter", "cobald_blue_monitor": "COBALD Blue Monitor", - "cobald_red_monitor": "COBALD Red Monitor" + "cobald_red_monitor": "COBALD Red Monitor", + "pcfh_instrument_number": "PCFH Instrument Number", + "pcfh_6v_analog_supply_battery_voltage": "PCFH 6V Analog Battery Voltage", + "pcfh_45v_logic_supply_battery_voltage": "PCFH 4.5V Supply Battery Voltage", + "pcfh_45v_peltier_and_heater_supply_battery_voltage": "PCFH 4.5V Peltier Battery Voltage", + "pcfh_air_temperature_01": "PCFH Air Temperature #1", + "pcfh_air_temperature_02": "PCFH Air Temperature #2", + "pcfh_anticipated_frost_point_mirror_temperature_01": "PCFH Anticipated Frost Point Mirror Temperature #1", + "pcfh_anticipated_frost_point_mirror_temperature_02": "PCFH Anticipated Frost Point Mirror Temperature #2", + "pcfh_clean_frost_point_mirror_reflectance_01": "PCFH Clean Frost Point Mirror Reflectance #1", + "pcfh_clean_frost_point_mirror_reflectance_02": "PCFH Clean Frost Point Mirror Reflectance #2", + "pcfh_clean_reference_surface_reflectance_01": "PCFH Clean Reference Surface Reflectance #1", + "pcfh_clean_reference_surface_reflectance_02": "PCFH Clean Reference Surface Reflectance #2", + "pcfh_frost_point_mirror_reflectance_01": "PCFH Frost Point Mirror Reflectance #1", + "pcfh_frost_point_mirror_reflectance_02": "PCFH Frost Point Mirror Reflectance #2", + "pcfh_reference_surface_reflectance_01": "PCFH Reference Surface Reflectance #1", + "pcfh_reference_surface_reflectance_01": "PCFH Reference Surface Reflectance #2", + "pcfh_heat_sink_temperature_01": "PCFH Heat Sink Temperature #1", + "pcfh_heat_sink_temperature_02": "PCFH Heat Sink Temperature #2", + "pcfh_frost_point_mirror_temperature_01": "PCFH Frost Point Mirror Temperature #1", + "pcfh_frost_point_mirror_temperature_02": "PCFH Frost Point Mirror Temperature #2", + "pcfh_peltier_hot_side_temperature_01": "PCFH Peltier Hot Side Temperature #1", + "pcfh_peltier_hot_side_temperature_02": "PCFH Peltier Hot Side Temperature #2", + "pcfh_reference_surface_heating_current_01": "PCFH Reference Surface Heating Current #1", + "pcfh_reference_surface_heating_current_02": "PCFH Reference Surface Heating Current #2", + "pcfh_reference_surface_temperature_01": "PCFH Reference Surface Temperature #1", + "pcfh_reference_surface_temperature_02": "PCFH Reference Surface Temperature #2", + "pcfh_peltier_current_01": "PCFH Peltier Current #1", + "pcfh_peltier_current_02": "PCFH Peltier Current #2", + "pcfh_reserved_temperature": "PCFH Reserved Temperature", + "pcfh_thermocouple_reference_temperature": "PCFH Reference Thermocouple Temperature", + "pcfh_serial_number": "PCFH Serial Number", + "pcfh_controller_fw_date": "PCFH Controller Firmware Date", + "pcfh_fpga_fw_date": "PCFH FPGA Firmware Date", + "pcfh_temperature_pcb_date": "PCFH Temperature PCB Manufacture Date", + "pcfh_main_pcb_date": "PCFH Main PCB Manufacture Date" }; var globalSuffixes = { @@ -252,7 +287,28 @@ var globalSuffixes = { "cfh_optics_voltage": " V", "cfh_optics_temperature": "°C", "cfh_battery": " V", - "cobald_internal_temperature": "°C" + "cobald_internal_temperature": "°C", + "pcfh_6v_analog_supply_battery_voltage": " V", + "pcfh_45v_logic_supply_battery_voltage": " V", + "pcfh_45v_peltier_and_heater_supply_battery_voltage": " V", + "pcfh_air_temperature_01": "°C", + "pcfh_air_temperature_02": "°C", + "pcfh_anticipated_frost_point_mirror_temperature_01": "°C", + "pcfh_anticipated_frost_point_mirror_temperature_02": "°C", + "pcfh_heat_sink_temperature_01": "°C", + "pcfh_heat_sink_temperature_02": "°C", + "pcfh_frost_point_mirror_temperature_01": "°C", + "pcfh_frost_point_mirror_temperature_02": "°C", + "pcfh_peltier_hot_side_temperature_01": "°C", + "pcfh_peltier_hot_side_temperature_02": "°C", + "pcfh_reference_surface_heating_current_01": " A", + "pcfh_reference_surface_heating_current_02": " A", + "pcfh_reference_surface_temperature_01": "°C", + "pcfh_reference_surface_temperature_02": "°C", + "pcfh_peltier_current_01": " A", + "pcfh_peltier_current_02": " A", + "pcfh_reserved_temperature": "°C", + "pcfh_thermocouple_reference_temperature": "°C", }; // localStorage vars diff --git a/js/xdata.js b/js/xdata.js index cf738f4..21fa736 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -48,10 +48,7 @@ function parseOIF411(xdata, pressure){ // https://www.vaisala.com/sites/default/files/documents/Ozone%20Sounding%20with%20Vaisala%20Radiosonde%20RS41%20User%27s%20Guide%20M211486EN-C.pdf // // Sample data: 0501036402B958B07500 (length = 20 characters) - // More sample data: 0501R20234850000006EI (length = 21 characters) - - // Cast to string if not already - xdata = String(xdata); + // More sample data: 0501R20234850000006EI (length = 21 characters) // Run some checks over the input if(xdata.length < 20){ @@ -91,8 +88,6 @@ function parseOIF411(xdata, pressure){ // Version number _output['oif411_version'] = (parseInt(xdata.substr(16,4),16)/100).toFixed(2); - - return _output } else if (xdata.length == 20){ // Measurement Data (Table 18) // Ozone pump temperature - signed int16 @@ -128,12 +123,9 @@ function parseOIF411(xdata, pressure){ _O3_partial_pressure = (4.30851e-4)*(_output['oif411_ozone_current_uA'] - Ibg)*(_output['oif411_ozone_pump_temp']+273.15)*FlowRate*Cef; // mPa _output['oif411_O3_partial_pressure'] = Math.round(_O3_partial_pressure * 1000) / 1000; // 3 DP + } - return _output - - } else { - return {} - } + return _output } function parseCFH(xdata) { @@ -145,9 +137,6 @@ function parseCFH(xdata) { // // Sample data: 0802E21FFD85C8CE078A0193 (length = 24 characters) - // Cast to string if not already - xdata = String(xdata); - // Run some checks over the input if(xdata.length != 24){ // Invalid CFH dataset @@ -176,9 +165,6 @@ function parseCOBALD(xdata) { // // Sample data: 190213fffe005fcf00359943912cca (length = 30 characters) - // Cast to string if not already - xdata = String(xdata); - // Run some checks over the input if(xdata.length != 30){ // Invalid COBALD dataset @@ -246,9 +232,6 @@ function parseSKYDEW(xdata) { // // Sample data: 3F0141DF73B940F600150F92FF27D5C8304102 (length = 38 characters) - // Cast to string if not already - xdata = String(xdata); - // Run some checks over the input if(xdata.length != 38){ // Invalid SKYDEW dataset @@ -280,6 +263,183 @@ function parseSKYDEW(xdata) { return _output } +function getPCFHdate(code) { + // months reference list + var PCFHmonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + + // Get year from first character + var year = parseInt(code.charAt(0),16); + year = year + 2016; + + // Get month from second character + var month = parseInt(code.charAt(1),16); + month = PCFHmonths[month-1]; + + // Generate string + _part_date = month + " " + year; + return _part_date; +} + +function parsePCFH(xdata) { + // Attempt to parse an XDATA string from a Peltier Cooled Frost point Hygrometer (PCFH) + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // Peltier Cooled Frost point Hygrometer (PCFH) Telemetry Interface PDF + // + // Sample data: 3c0101434a062c5cd4a5747b81486c93 (length = 32 characters) + // 3c0103456076175ec5fc9df9b1 (length = 26 characters) + // 3c0104a427104e203a9861a8ab6a65 (length = 30 characters) + // 3c010000011b062221 (length = 18 characters) + + // Run some checks over the input + if(xdata.length > 32){ + // Invalid PCFH dataset + return {}; + } + + if(xdata.substr(0,2) !== '3C'){ + // Not a PCFH (shouldn't get here) + return {}; + } + + var _output = {}; + + // Instrument number is common to all XDATA types. + _output['pcfh_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Packet ID + var packetID = xdata.substr(4,2); + + // Packet type + if (packetID == "00") { // Individual instrument identification (10 s) + // Serial number + _output["pcfh_serial_number"] = parseInt(xdata.substr(6,4)); + + // Temperature PCB date + _output["pcfh_temperature_pcb_date"] = getPCFHdate(xdata.substr(10,2)); + + // Main PCB date + _output["pcfh_main_pcb_date"] = getPCFHdate(xdata.substr(12,2)); + + // Controller FW date + _output["pcfh_controller_fw_date"] = getPCFHdate(xdata.substr(14,2)); + + // FPGA FW date + _output["pcfh_fpga_fw_date"] = getPCFHdate(xdata.substr(16,2)); + } else if (packetID == "01" || packetID == "02") { // Regular one second data, sub-sensor 1/2 + // Frost point mirror temperature + _frost_point_mirror_temperature = parseInt(xdata.substr(8,3),16); + _frost_point_mirror_temperature = (_frost_point_mirror_temperature*0.05) - 125; + _output['pcfh_frost_point_mirror_temperature_' + packetID] = Math.round(_frost_point_mirror_temperature * 100) / 100; // 2 DP + + // Peltier hot side temperature + _peltier_hot_side_temperature = parseInt(xdata.substr(11,3),16); + _peltier_hot_side_temperature = (_peltier_hot_side_temperature*0.05) - 125; + _output['pcfh_peltier_hot_side_temperature_' + packetID] = Math.round(_peltier_hot_side_temperature * 100) / 100; // 2 DP + + // Air temperature + _air_temperature = parseInt(xdata.substr(14,3),16); + _air_temperature = (_air_temperature*0.05) - 125; + _output['pcfh_air_temperature_' + packetID] = Math.round(_air_temperature * 100) / 100; // 2 DP + + // Anticipated frost point mirror temperature + _anticipated_frost_point_mirror_temperature = parseInt(xdata.substr(17,3),16); + _anticipated_frost_point_mirror_temperature = (_anticipated_frost_point_mirror_temperature*0.05) - 125; + _output['pcfh_anticipated_frost_point_mirror_temperature_' + packetID] = Math.round(_anticipated_frost_point_mirror_temperature * 100) / 100; // 2 DP + + // Frost point mirror reflectance + _frost_point_mirror_reflectance = parseInt(xdata.substr(20,4),16); + _frost_point_mirror_reflectance = _frost_point_mirror_reflectance/32768; + _output['pcfh_frost_point_mirror_reflectance_' + packetID] = Math.round(_frost_point_mirror_reflectance * 1000) / 1000; // 3 DP + + // Reference surface reflectance + _reference_surface_reflectance = parseInt(xdata.substr(24,4),16); + _reference_surface_reflectance = _reference_surface_reflectance/32768; + _output['pcfh_reference_surface_reflectance_' + packetID] = Math.round(_reference_surface_reflectance * 1000) / 1000; // 3 DP + + // Reference surface heating current + _reference_surface_heating_current = parseInt(xdata.substr(28,2),16); + _reference_surface_heating_current = _reference_surface_heating_current/2.56; + _output['pcfh_reference_surface_heating_current_' + packetID] = Math.round(_reference_surface_heating_current * 100) / 100; // 2 DP + + // Peltier current + _peltier_current = parseInt(xdata.substr(30,2),16); + if ((_peltier_current & 0x80) > 0) { + _peltier_current = _peltier_current - 0x100; + } + _peltier_current = _peltier_current/64; + _output['pcfh_peltier_current_' + packetID] = Math.round(_peltier_current * 1000) / 1000; // 3 DP + } else if (packetID == "03") { // Regular five second data + // Heat sink temperature 1 + _heat_sink_temperature = parseInt(xdata.substr(8,3),16); + _heat_sink_temperature = (_heat_sink_temperature*0.05) - 125; + _output['pcfh_heat_sink_temperature_01'] = Math.round(_heat_sink_temperature * 100) / 100; // 2 DP + + // Reference surface temperature 1 + _reference_surface_temperature = parseInt(xdata.substr(11,3),16); + _reference_surface_temperature = (_reference_surface_temperature*0.05) - 125; + _output['pcfh_reference_surface_temperature_01'] = Math.round(_reference_surface_temperature * 100) / 100; // 2 DP + + // Heat sink temperature 2 + _heat_sink_temperature = parseInt(xdata.substr(14,3),16); + _heat_sink_temperature = (_heat_sink_temperature*0.05) - 125; + _output['pcfh_heat_sink_temperature_02'] = Math.round(_heat_sink_temperature * 100) / 100; // 2 DP + + // Reference surface temperature 2 + _reference_surface_temperature = parseInt(xdata.substr(17,3),16); + _reference_surface_temperature = (_reference_surface_temperature*0.05) - 125; + _output['pcfh_reference_surface_temperature_02'] = Math.round(_reference_surface_temperature * 100) / 100; // 2 DP + + // Thermocouple reference temperature + _thermocouple_reference_temperature = parseInt(xdata.substr(20,3),16); + _thermocouple_reference_temperature = (_thermocouple_reference_temperature*0.05) - 125; + _output['pcfh_thermocouple_reference_temperature'] = Math.round(_thermocouple_reference_temperature * 100) / 100; // 2 DP + + // Reserved temperature + _reserved_temperature = parseInt(xdata.substr(23,3),16); + _reserved_temperature = (_reserved_temperature*0.05) - 125; + _output['pcfh_reserved_temperature'] = Math.round(_reserved_temperature * 100) / 100; // 2 DP + } else if (packetID == "04") { // Instrument status (10 s) + // Clean frost point mirror reflectance 1 + _clean_frost_point_mirror_reflectance = parseInt(xdata.substr(8,4),16); + _clean_frost_point_mirror_reflectance = _clean_frost_point_mirror_reflectance*0.001; + _output['pcfh_clean_frost_point_mirror_reflectance_01'] = Math.round(_clean_frost_point_mirror_reflectance * 1000) / 1000; // 3 DP + + // Clean reference surface reflectance 1 + _clean_reference_surface_reflectance = parseInt(xdata.substr(12,4),16); + _clean_reference_surface_reflectance = _clean_reference_surface_reflectance*0.001; + _output['pcfh_clean_reference_surface_reflectance_01'] = Math.round(_clean_reference_surface_reflectance * 1000) / 1000; // 3 DP + + // Clean frost point mirror reflectance 2 + _clean_frost_point_mirror_reflectance = parseInt(xdata.substr(16,4),16); + _clean_frost_point_mirror_reflectance = _clean_frost_point_mirror_reflectance*0.001; + _output['pcfh_clean_frost_point_mirror_reflectance_02'] = Math.round(_clean_frost_point_mirror_reflectance * 1000) / 1000; // 3 DP + + // Clean reference surface reflectance 2 + _clean_reference_surface_reflectance = parseInt(xdata.substr(20,4),16); + _clean_reference_surface_reflectance = _clean_reference_surface_reflectance*0.001; + _output['pcfh_clean_reference_surface_reflectance_02'] = Math.round(_clean_reference_surface_reflectance * 1000) / 1000; // 3 DP + + // 6V Analog supply battery voltage + _6v_analog_supply_battery_voltage = parseInt(xdata.substr(24,2),16); + _6v_analog_supply_battery_voltage = (_6v_analog_supply_battery_voltage*0.02) + 2.5; + _output['pcfh_6v_analog_supply_battery_voltage'] = Math.round(_6v_analog_supply_battery_voltage * 100) / 100; // 2 DP + + // 4.5V Logic supply battery voltage + _45v_logic_supply_battery_voltage = parseInt(xdata.substr(26,2),16); + _45v_logic_supply_battery_voltage = (_45v_logic_supply_battery_voltage*0.02) + 2.5; + _output['pcfh_45v_logic_supply_battery_voltage'] = Math.round(_45v_logic_supply_battery_voltage * 100) / 100; // 2 DP + + // 4.5V Peltier and heater supply battery voltage + _45v_peltier_and_heater_supply_battery_voltage = parseInt(xdata.substr(28,2),16); + _45v_peltier_and_heater_supply_battery_voltage = (_45v_peltier_and_heater_supply_battery_voltage*0.02) + 2.5; + _output['pcfh_45v_peltier_and_heater_supply_battery_voltage'] = Math.round(_45v_peltier_and_heater_supply_battery_voltage * 100) / 100; // 2 DP + } + + return _output +} + function parseXDATA(data, pressure){ // Accept an XDATA string, or multiple XDATA entries, delimited by '#' // Attempt to parse each one, and return an object @@ -302,6 +462,8 @@ function parseXDATA(data, pressure){ for(xdata_i = 0; xdata_i < data_split.length; xdata_i++){ _current_xdata = data_split[xdata_i]; + _current_xdata = String(_current_xdata).toUpperCase(); + // Get Instrument ID // https://gml.noaa.gov/aftp/user/jordan/XDATA%20Instrument%20ID%20Allocation.pdf // https://www.gruan.org/gruan/editor/documents/gruan/GRUAN-TN-11_GruanToolRs92_v1.0_2020-10-01.pdf @@ -341,11 +503,8 @@ function parseXDATA(data, pressure){ if (!_instruments.includes("OPC")) _instruments.push('OPC'); } else if (_instrument === '3C'){ // PCFH - // SRNO, H0, H1, F0, F1 - // 3c010000184b4b5754 - // 3c0103ce7b58647a98748befff - // 3c010148719fff8e54b9af627e249fe0 - // 3c01028d696fff8db4b7865980cdbbb3 + _xdata_temp = parsePCFH(_current_xdata); + _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("PCFH")) _instruments.push('PCFH'); } else if (_instrument === '3D'){ // FLASH-B From 2083b34d495142a7480b4e1398da47b3f9ccc831 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Fri, 14 Jan 2022 13:32:15 +1100 Subject: [PATCH 20/25] typo --- js/xdata.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/xdata.js b/js/xdata.js index 21fa736..18533cd 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -532,7 +532,7 @@ function parseXDATA(data, pressure){ } } - if (_instrument.length > 0) _output["xdata_instrument"] = _instruments; + if (_instruments.length > 0) _output["xdata_instrument"] = _instruments; return _output From a9a7d4ed71951b887b191a6798e968fc7fa22688 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sat, 15 Jan 2022 10:15:23 +1100 Subject: [PATCH 21/25] FLASH-B WIP --- js/xdata.js | 152 +++++++++++++++++++++++++++------------------------- 1 file changed, 78 insertions(+), 74 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index 18533cd..dba0310 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -117,7 +117,7 @@ function parseOIF411(xdata, pressure){ // Now attempt to calculate the O3 partial pressure // Calibration values - Ibg = 0.0; // The BOM appear to use a Ozone background current value of 0 uA + Ibg = 12.0; // The BOM appear to use a Ozone background current value of 12 uA (+- 0.6) Cef = get_oif411_Cef(pressure); // Calculate the pump efficiency correction. FlowRate = 28.5; // Use a 'nominal' value for Flow Rate (seconds per 100mL). @@ -128,34 +128,6 @@ function parseOIF411(xdata, pressure){ return _output } -function parseCFH(xdata) { - // Attempt to parse an XDATA string from a CFH Cryogenic Frostpoint Hygrometer - // Returns an object with parameters to be added to the sondes telemetry. - // - // References: - // https://eprints.lib.hokudai.ac.jp/dspace/bitstream/2115/72249/1/GRUAN-TD-5_MeiseiRadiosondes_v1_20180221.pdf - // - // Sample data: 0802E21FFD85C8CE078A0193 (length = 24 characters) - - // Run some checks over the input - if(xdata.length != 24){ - // Invalid CFH dataset - return {}; - } - - if(xdata.substr(0,2) !== '08'){ - // Not an CFH (shouldn't get here) - return {}; - } - - var _output = {}; - - // Instrument number is common to all XDATA types. - _output['cfh_instrument_number'] = parseInt(xdata.substr(2,2),16); - - return _output -} - function parseCOBALD(xdata) { // Attempt to parse an XDATA string from a COBALD Compact Optical Backscatter Aerosol Detector // Returns an object with parameters to be added to the sondes telemetry. @@ -223,46 +195,6 @@ function parseCOBALD(xdata) { return _output } -function parseSKYDEW(xdata) { - // Attempt to parse an XDATA string from a SKYDEW Peltier-based chilled-mirror hygrometer - // Returns an object with parameters to be added to the sondes telemetry. - // - // References: - // https://www.gruan.org/gruan/editor/documents/meetings/icm-12/pres/pres_306_Sugidachi_SKYDEW.pdf - // - // Sample data: 3F0141DF73B940F600150F92FF27D5C8304102 (length = 38 characters) - - // Run some checks over the input - if(xdata.length != 38){ - // Invalid SKYDEW dataset - return {}; - } - - if(xdata.substr(0,2) !== '3F'){ - // Not a SKYDEW (shouldn't get here) - return {}; - } - - var _output = {}; - - // Instrument number is common to all XDATA types. - _output['skydew_instrument_number'] = parseInt(xdata.substr(2,2),16); - - // Other fields may include - // Serial number - // Mirror temperature (-120 - 30) - // Mixing ratio V (ppmV) - // PT100 (Ohm 60 - 120) - // SCA light - // SCA base - // PLT current - // HS temp - // CB temp - // PID - // Battery - return _output -} - function getPCFHdate(code) { // months reference list var PCFHmonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; @@ -440,7 +372,81 @@ function parsePCFH(xdata) { return _output } -function parseXDATA(data, pressure){ +function calculateFLASHBWaterVapour(S, B, P, T) { + var K1 = 0; + var K2 = 0; + var U = 0; + + var F = S - B + K2*(S-B) + + if (P < 36) { + U = K1*F*0.956*(1+((0.00781*(T+273.16))/P)); + } else if (36 <= 36 < 300) { + U = K1*F*(1 + 0.00031*P) + } + + return U; +} + +function parseFLASHB(xdata, pressure, temperature) { + // Attempt to parse an XDATA string from a Fluorescent Lyman-Alpha Stratospheric Hygrometer for Balloon (FLASH-B) + // Returns an object with parameters to be added to the sondes telemetry. + // + // + // Sample data: 3D0204E20001407D00E4205DC24406B1012 (length = 35 characters) + + // Run some checks over the input + if(xdata.length != 35){ + // Invalid FLASH-B dataset + return {}; + } + + if(xdata.substr(0,2) !== '3D'){ + // Not a FLASH-B (shouldn't get here) + return {}; + } + + var _output = {}; + + // Instrument number is common to all XDATA types. + _output['flashb_instrument_number'] = parseInt(xdata.substr(2,2),16); + + _photomultiplier_counts = parseInt(xdata.substr(5,4),16); + + _photomultiplier_background_counts = parseInt(xdata.substr(9,4),16); + _output['flashb_photomultiplier_background_counts'] = _photomultiplier_background_counts + + //_photomultiplier_counts = calculateFLASHBWaterVapour(_photomultiplier_counts, _photomultiplier_background_counts, pressure, temperature); + _output['flashb_photomultiplier_counts'] = _photomultiplier_counts; + + _photomultiplier_temperature = parseInt(xdata.substr(13,4),16); + _photomultiplier_temperature = (-21.103*Math.log((_photomultiplier_temperature*0.0183)/(2.49856 - (_photomultiplier_temperature*0.00061)))) + 97.106; + _output['flashb_photomultiplier_temperature'] = Math.round(_photomultiplier_temperature * 100) / 100; // 2 DP + + _battery_voltage = parseInt(xdata.substr(17,4),16); + _battery_voltage = _battery_voltage*0.005185; + _output['flashb_battery_voltage'] = Math.round(_battery_voltage * 100) / 100; // 2 DP + + _yuv_current = parseInt(xdata.substr(21,4),16); + _yuv_current = _yuv_current*0.0101688; + _output['flashb_yuv_current'] = Math.round(_yuv_current * 100) / 100; // 2 DP + + _pmt_voltage = parseInt(xdata.substr(25,4),16); + _pmt_voltage = _pmt_voltage*0.36966; + _output['flashb_pmt_voltage'] = Math.round(_pmt_voltage * 10) / 10; // 1 DP + + _firmware_version = parseInt(xdata.substr(29,2),16); + _firmware_version = _firmware_version*0.1; + _output['flashb_firmware_version'] = Math.round(_firmware_version * 10) / 10; // 1 DP + + _output['flashb_production_year'] = parseInt(xdata.substr(31,2),16); + + _output['flashb_hardware_version'] = parseInt(xdata.substr(33,2),16); + + return _output +} + +function parseXDATA(data, pressure, temperature){ // Accept an XDATA string, or multiple XDATA entries, delimited by '#' // Attempt to parse each one, and return an object // Test datasets: @@ -481,8 +487,6 @@ function parseXDATA(data, pressure){ if (!_instruments.includes("OIF411")) _instruments.push('OIF411'); } else if (_instrument === '08'){ // CFH - _xdata_temp = parseCFH(_current_xdata); - _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("CFH")) _instruments.push('CFH'); } else if (_instrument === '10'){ // FPH @@ -508,14 +512,14 @@ function parseXDATA(data, pressure){ if (!_instruments.includes("PCFH")) _instruments.push('PCFH'); } else if (_instrument === '3D'){ // FLASH-B + _xdata_temp = parseFLASHB(_current_xdata, pressure, temperature); + _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("FLASH-B")) _instruments.push('FLASH-B'); } else if (_instrument === '3E'){ // TRAPS if (!_instruments.includes("TRAPS")) _instruments.push('TRAPS'); } else if (_instrument === '3F'){ // SKYDEW - _xdata_temp = parseSKYDEW(_current_xdata); - _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("SKYDEW")) _instruments.push('SKYDEW'); } else if (_instrument === '41'){ // CICANUM From d51e5be99d84b0600462e8892efdae004b4f5610 Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Mon, 17 Jan 2022 22:26:22 +1100 Subject: [PATCH 22/25] SKYDEW initial --- js/xdata.js | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/js/xdata.js b/js/xdata.js index dba0310..a299a84 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -446,6 +446,94 @@ function parseFLASHB(xdata, pressure, temperature) { return _output } +function parseSKYDEW(xdata) { + // Attempt to parse an XDATA string from a Peltier-based chilled-mirror hygrometer SKYDEW + // Returns an object with parameters to be added to the sondes telemetry. + // + // References: + // Peltier-based chilled-mirror hygrometer “SKYDEW” XDATA protocol (draft) + // + // Sample data: 3F0144A75446416100160ECAFFFF6EC8000006 (length = 38 characters) + + // Run some checks over the input + if(xdata.length != 38){ + // Invalid SKYDEW dataset + return {}; + } + + if(xdata.substr(0,2) !== '3F'){ + // Not a SKYDEW (shouldn't get here) + return {}; + } + + var _output = {}; + + // Instrument number is common to all XDATA types. + _output['skydew_instrument_number'] = parseInt(xdata.substr(2,2),16); + + // Mirror temperature value + // This requires the four coefficients to actually get a value + _output['skydew_mirror_temperature_value'] = parseInt(xdata.substr(4,4),16); + + // Scattered light level + _scattered_light_value = parseInt(xdata.substr(8,4),16); + _scattered_light_value = _scattered_light_value*0.0000625 // V + _output['skydew_scattered_light'] = Math.round(_scattered_light_value * 10000) / 10000; // 4 DP + + // Reference resistance + // Used to calculate mirror temperature + _reference_resistance = parseInt(xdata.substr(12,4),16); + + // Offset value + // Used to calculate mirror temperature + _offset_value = parseInt(xdata.substr(16,4),16); + + // Peltier current + _peltier_current_value = parseInt(xdata.substr(20,4),16); + _peltier_current_value = (_peltier_current_value*0.00040649414 - 1.5)*2; // A + _output['skydew_peltier_current'] = Math.round(_peltier_current_value * 10000) / 10000; // 4 DP + + // Heatsink temperature + _heatsink_temperature = parseInt(xdata.substr(24,2),16); + _heatsink_temperature = (Math.pow((((Math.log(((_heatsink_temperature/8192)*141.9)/(3.3-(_heatsink_temperature/8192)*3.3)/6))/3390)+1)/273.16, -1) -276.16); // Degrees C + _output['skydew_heatsink_temperature'] = Math.round(_heatsink_temperature * 100) / 100; // 2 DP + + // Circuit board temperature + _circuit_board_temperature = parseInt(xdata.substr(26,2),16); + _circuit_board_temperature = (Math.pow((((Math.log(((_circuit_board_temperature/8192)*39.6)/(3.3-(_circuit_board_temperature/8192)*3.3)/6))/3390)+1)/273.16, -1) -276.16); // Degrees C + _output['skydew_circuit_board_temperature'] = Math.round(_circuit_board_temperature * 100) / 100; // 2 DP + + // Battery + _output['skydew_battery'] = parseInt(xdata.substr(28,2),16); + + // PID + _output['skydew_pid'] = parseInt(xdata.substr(30,2),16); + + // Parameter + var parameter = parseInt(xdata.substr(32,4),16); + + // Coefficent type + var parameterType = parseInt(xdata.substr(36,2),16); + + // Parameter Type + switch(parameterType) { + case 0: + _output['skydew_serial_number'] = parameter; + case 1: + _output['skydew_coefficient_b'] = parameter; + case 2: + _output['skydew_coefficient_c'] = parameter; + case 3: + _output['skydew_coefficient_d'] = parameter; + case 4: + _output['skydew_coefficient_e'] = parameter; + case 5: + _output['skydew_firmware_version'] = parameter; + } + + return _output +} + function parseXDATA(data, pressure, temperature){ // Accept an XDATA string, or multiple XDATA entries, delimited by '#' // Attempt to parse each one, and return an object @@ -487,6 +575,10 @@ function parseXDATA(data, pressure, temperature){ if (!_instruments.includes("OIF411")) _instruments.push('OIF411'); } else if (_instrument === '08'){ // CFH + // 0803B922067F1D0707CD0144 + // 0803ABD602800F7907D9015D + // 08038AB16A7FBF4908E50161 + // https://www.en-sci.com/cryogenic-frost-point-hygrometer/ if (!_instruments.includes("CFH")) _instruments.push('CFH'); } else if (_instrument === '10'){ // FPH @@ -520,6 +612,8 @@ function parseXDATA(data, pressure, temperature){ if (!_instruments.includes("TRAPS")) _instruments.push('TRAPS'); } else if (_instrument === '3F'){ // SKYDEW + _xdata_temp = parseSKYDEW(_current_xdata); + _output = Object.assign(_output,_xdata_temp); if (!_instruments.includes("SKYDEW")) _instruments.push('SKYDEW'); } else if (_instrument === '41'){ // CICANUM From 58fa3c1f1c2b75ba8991f331a17aa05fc4a4f06d Mon Sep 17 00:00:00 2001 From: Luke Prior <22492406+LukePrior@users.noreply.github.com> Date: Wed, 19 Jan 2022 08:47:06 +1100 Subject: [PATCH 23/25] Fix --- js/xdata.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index a299a84..b91f50e 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -117,7 +117,7 @@ function parseOIF411(xdata, pressure){ // Now attempt to calculate the O3 partial pressure // Calibration values - Ibg = 12.0; // The BOM appear to use a Ozone background current value of 12 uA (+- 0.6) + Ibg = 0.0; // The BOM appear to use a Ozone background current value of 0 uA Cef = get_oif411_Cef(pressure); // Calculate the pump efficiency correction. FlowRate = 28.5; // Use a 'nominal' value for Flow Rate (seconds per 100mL). @@ -634,4 +634,4 @@ function parseXDATA(data, pressure, temperature){ return _output -} \ No newline at end of file +} From 88c5d8ea08f1d058a2d486c971629d5256c791be Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Sat, 29 Jan 2022 13:32:45 +1100 Subject: [PATCH 24/25] comments --- js/xdata.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/js/xdata.js b/js/xdata.js index a299a84..acf7bf3 100644 --- a/js/xdata.js +++ b/js/xdata.js @@ -373,6 +373,7 @@ function parsePCFH(xdata) { } function calculateFLASHBWaterVapour(S, B, P, T) { + // This code is incomplete as I don't have reference values var K1 = 0; var K2 = 0; var U = 0; @@ -420,19 +421,19 @@ function parseFLASHB(xdata, pressure, temperature) { _output['flashb_photomultiplier_counts'] = _photomultiplier_counts; _photomultiplier_temperature = parseInt(xdata.substr(13,4),16); - _photomultiplier_temperature = (-21.103*Math.log((_photomultiplier_temperature*0.0183)/(2.49856 - (_photomultiplier_temperature*0.00061)))) + 97.106; + _photomultiplier_temperature = (-21.103*Math.log((_photomultiplier_temperature*0.0183)/(2.49856 - (_photomultiplier_temperature*0.00061)))) + 97.106; // Degrees C _output['flashb_photomultiplier_temperature'] = Math.round(_photomultiplier_temperature * 100) / 100; // 2 DP _battery_voltage = parseInt(xdata.substr(17,4),16); - _battery_voltage = _battery_voltage*0.005185; + _battery_voltage = _battery_voltage*0.005185; // V _output['flashb_battery_voltage'] = Math.round(_battery_voltage * 100) / 100; // 2 DP _yuv_current = parseInt(xdata.substr(21,4),16); - _yuv_current = _yuv_current*0.0101688; + _yuv_current = _yuv_current*0.0101688; // mA _output['flashb_yuv_current'] = Math.round(_yuv_current * 100) / 100; // 2 DP _pmt_voltage = parseInt(xdata.substr(25,4),16); - _pmt_voltage = _pmt_voltage*0.36966; + _pmt_voltage = _pmt_voltage*0.36966; // V _output['flashb_pmt_voltage'] = Math.round(_pmt_voltage * 10) / 10; // 1 DP _firmware_version = parseInt(xdata.substr(29,2),16); From bc7d949d1e1d5cd46c1b5702e56ab3bd89a98aab Mon Sep 17 00:00:00 2001 From: Uskompuf <22492406+Uskompuf@users.noreply.github.com> Date: Thu, 3 Feb 2022 14:24:36 +1100 Subject: [PATCH 25/25] jump to user location --- js/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/app.js b/js/app.js index 573f312..62a976d 100644 --- a/js/app.js +++ b/js/app.js @@ -860,7 +860,7 @@ $(window).ready(function() { // open map $('.nav .home').click(); // pan map to our current location - map.flyTo(new L.LatLng(currentPosition.lat, currentPosition.lon)); + map.panTo(new L.LatLng(currentPosition.lat, currentPosition.lon)); } else { alert("No position available"); }