diff --git a/README.md b/README.md index 1e20588..4b2760d 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Another option to obtain map tiles is [FoxtrotGPS](https://www.foxtrotgps.org/). To grab map tiles using FoxtrotGPS, we're going to use FoxtrotGPS's [Cached Maps](https://www.foxtrotgps.org/doc/foxtrotgps.html#Cached-Maps) feature. * Install FoxtrotGPS (Linux only unfortunately, works OK on a Pi!) either [from source](https://www.foxtrotgps.org/releases/), or via your system package manager (`sudo apt-get install foxtrotgps`). + * Warning - Installing foxtrotgps will also install gpsd, which may 'take over' your GPS receiver! If you aren't using GPSD, I'd recommend uninstalling it with: `sudo apt-get purge gpsd` * Load up FoxtrotGPS, and pan around the area you are intersted in caching. Pick the map layer you want, right-click on the map, and choose 'Map download'. You can then select how many zoom levels you want to cache, and start it downloading (this may take a while!) * Once you have a set of folders within your `~/Maps` cache directory, you can startup Chasemapper and start using them! Tiles will be served up as they become available. diff --git a/chasemapper/__init__.py b/chasemapper/__init__.py index 7640920..41977fd 100644 --- a/chasemapper/__init__.py +++ b/chasemapper/__init__.py @@ -8,4 +8,4 @@ # Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH -__version__ = "1.5.4" +__version__ = "1.5.7" diff --git a/chasemapper/config.py b/chasemapper/config.py index f16843c..6a49de3 100644 --- a/chasemapper/config.py +++ b/chasemapper/config.py @@ -50,6 +50,13 @@ default_config = { "bearing_weight": 1.0, "bearing_color": "black", "bearing_custom_color": "#FF0000", + "bearings_only_mode": False, + # TimeSync Hunting Settings (not in config file, but needs to be shared between clients) + "time_seq_enabled": False, + "time_seq_times": [0,0,0,0], + "time_seq_active": 25, + "time_seq_cycle": 120, + # History "reload_last_position": False, } @@ -182,6 +189,18 @@ def parse_config_file(filename): logging.info("Missing ascent_rate_averaging setting, using default (10)") chase_config["ascent_rate_averaging"] = 10 + try: + chase_config["bearings_only_mode"] = config.getboolean("bearings", "bearings_only_mode") + except: + logging.info("Missing bearing_only_mode setting, using default (False)") + chase_config["bearings_only_mode"] = False + + try: + chase_config["doa_confidence_threshold"] = config.getfloat("bearings", "doa_confidence_threshold") + except: + logging.info("Missing DoA Confidence Threshold Setting, using default (4.0)") + chase_config["doa_confidence_threshold"] = 4.0 + # Telemetry Source Profiles _profile_count = config.getint("profile_selection", "profile_count") diff --git a/chasemapper/gpsd.py b/chasemapper/gpsd.py index 2ea4a2b..c69194d 100644 --- a/chasemapper/gpsd.py +++ b/chasemapper/gpsd.py @@ -288,6 +288,7 @@ class DataStream(object): except (ValueError, KeyError) as error: logging.error("GPSD Parser - Other Error - %s" % str(error)) + print(gpsd_socket_response) return @@ -418,6 +419,11 @@ class GPSDAdaptor(object): if __name__ == "__main__": + # Little test script to print out received data from GPSD for debugging. + # Run with: python3 -m chasemapper.gpsd 127.0.0.1 + # or whatever IP your gpsd server is running on. + + import sys def print_dict(data): print(data) @@ -426,16 +432,7 @@ if __name__ == "__main__": format="%(asctime)s %(levelname)s:%(message)s", level=logging.DEBUG ) - _gpsd = GPSDAdaptor(callback=print_dict) - time.sleep(30) + _gpsd = GPSDAdaptor(callback=print_dict, hostname=sys.argv[1]) + time.sleep(3000) _gpsd.close() - # gpsd_socket = GPSDSocket() - # data_stream = DataStream() - # gpsd_socket.connect() - # gpsd_socket.watch() - # for new_data in gpsd_socket: - # if new_data: - # data_stream.unpack(new_data) - # print(data_stream.TPV) - # #print(data_stream.SKY) diff --git a/horusmapper.cfg.example b/horusmapper.cfg.example index 045387e..7134596 100644 --- a/horusmapper.cfg.example +++ b/horusmapper.cfg.example @@ -243,6 +243,9 @@ chase_car_speed = True # [bearings] +# If true, will hide balloon-related display elements from the map +bearings_only_mode = False + # Number of bearings to store max_bearings = 300 @@ -260,6 +263,10 @@ car_speed_gate = 10 # 4 degrees/second seems to work fairly well. turn_rate_threshold = 4.0 +# DoA Confidence Threshold +# Used to gate bearings provided by a KrakenSDR system +doa_confidence_threshold = 4.0 + # Visual Settings - these can be adjust in the Web GUI during runtime # Bearing length in km diff --git a/horusmapper.py b/horusmapper.py index 4a9b35a..025b939 100644 --- a/horusmapper.py +++ b/horusmapper.py @@ -110,6 +110,11 @@ def flask_bearing_entry(): """ Render bearing entry page """ return flask.render_template("bearing_entry.html") +@app.route("/oclock") +def flask_oclock(): + """ Render bearing o'clock page """ + return flask.render_template("oclock.html") + @app.route("/get_telemetry_archive") def flask_get_telemetry_archive(): return json.dumps(current_payloads) diff --git a/static/css/oclock.css b/static/css/oclock.css new file mode 100644 index 0000000..c53798f --- /dev/null +++ b/static/css/oclock.css @@ -0,0 +1,125 @@ +.circle { + position: relative; + left: 0; + border: 1px solid black; + padding: 0; + width: 20em; + height: 20em; + border-radius: 50%; + list-style: none; + overflow: hidden; +} +li { + overflow: hidden; + position: absolute; + top: 0; right: 0; + width: 50%; height: 50%; + transform-origin: 0% 100%; +} +.text { + position: absolute; + left: -100%; + width: 200%; height: 200%; + text-align: center; + -webkit-transform: skewY(60deg) rotate(15deg); + -ms-transform: skewY(60deg) rotate(15deg); + transform: skewY(60deg) rotate(15deg); + padding-top: 20px; +} + +li:first-child { + -webkit-transform: rotate(15deg) skewY(-60deg); + -ms-transform: rotate(15deg) skewY(-60deg); + transform: rotate(15deg) skewY(-60deg); +} +li:nth-child(2) { + -webkit-transform: rotate(45deg) skewY(-60deg); + -ms-transform: rotate(45deg) skewY(-60deg); + transform: rotate(45deg) skewY(-60deg); +} +li:nth-child(3) { + -webkit-transform: rotate(75deg) skewY(-60deg); + -ms-transform: rotate(75deg) skewY(-60deg); + transform: rotate(75deg) skewY(-60deg); +} +li:nth-child(4) { + -webkit-transform: rotate(105deg) skewY(-60deg); + -ms-transform: rotate(105deg) skewY(-60deg); + transform: rotate(105deg) skewY(-60deg); +} +li:nth-child(5) { + -webkit-transform: rotate(135deg) skewY(-60deg); + -ms-transform: rotate(135deg) skewY(-60deg); + transform: rotate(135deg) skewY(-60deg); +} +li:nth-child(6) { + -webkit-transform: rotate(165deg) skewY(-60deg); + -ms-transform: rotate(165deg) skewY(-60deg); + transform: rotate(165deg) skewY(-60deg); +} +li:nth-child(7) { + -webkit-transform: rotate(195deg) skewY(-60deg); + -ms-transform: rotate(195deg) skewY(-60deg); + transform: rotate(195deg) skewY(-60deg); +} +li:nth-child(8) { + -webkit-transform: rotate(225deg) skewY(-60deg); + -ms-transform: rotate(225deg) skewY(-60deg); + transform: rotate(225deg) skewY(-60deg); +} +li:nth-child(9) { + -webkit-transform: rotate(255deg) skewY(-60deg); + -ms-transform: rotate(255deg) skewY(-60deg); + transform: rotate(255deg) skewY(-60deg); +} +li:nth-child(10) { + -webkit-transform: rotate(285deg) skewY(-60deg); + -ms-transform: rotate(285deg) skewY(-60deg); + transform: rotate(285deg) skewY(-60deg); +} +li:nth-child(11) { + -webkit-transform: rotate(315deg) skewY(-60deg); + -ms-transform: rotate(315deg) skewY(-60deg); + transform: rotate(315deg) skewY(-60deg); +} +li:nth-child(12) { + -webkit-transform: rotate(345deg) skewY(-60deg); + -ms-transform: rotate(345deg) skewY(-60deg); + transform: rotate(345deg) skewY(-60deg); +} +li:first-child .text { + background: green; +} +li:nth-child(2) .text { + background: tomato; +} +li:nth-child(3) .text { + background: aqua; +} +li:nth-child(4) .text { + background: yellow; +} +li:nth-child(5) .text { + background: orange; +} +li:nth-child(6) .text { + background: purple; +} +li:nth-child(7) .text { + background: cyan; +} +li:nth-child(8) .text { + background: brown; +} +li:nth-child(9) .text { + background: gray; +} +li:nth-child(10) .text { + background: pink; +} +li:nth-child(11) .text { + background: maroon; +} +li:nth-child(12) .text { + background: gold; +} diff --git a/static/js/balloon.js b/static/js/balloon.js index 882f994..04ede73 100644 --- a/static/js/balloon.js +++ b/static/js/balloon.js @@ -247,7 +247,17 @@ function handleTelemetry(data){ // Update Detailed GPS / Heading Info if(data.hasOwnProperty('heading_status')){ - $("#headingStatus").text(data.heading_status); + if(data.heading_status != null){ + $("#headingStatus").text(data.heading_status); + + if(data.heading_status.includes("Ongoing")){ + $('#car_warning').text("IMU Not Aligned") + $('#car_warning').removeClass(); + $('#car_warning').addClass('dataAgeBad'); + } else { + $('#car_warning').text("") + } + } } if(data.hasOwnProperty('numSV')){ diff --git a/static/js/bearings.js b/static/js/bearings.js index f230aeb..2b92214 100644 --- a/static/js/bearings.js +++ b/static/js/bearings.js @@ -36,6 +36,16 @@ var bearing_large_plot = false; // Start out with just our own local timestamp. var latest_server_timestamp = Date.now()/1000.0; +// Time-Sequenced Transmitter Code +// ... which is entirely specific to one event at the Mt Gambier Convention, +// yet took me ages to write. + +// These values are set to a instantaneous time when a button is clicked. +var timeSeqEnabled = false; +var timeSeqActive = 25; +var timeSeqCycle = 120; +var timeSeqTimes = [0,0,0,0]; + function updateBearingSettings(){ // Update bearing settings, but do *not* redraw. @@ -69,7 +79,7 @@ function destroyAllBearings(){ }); bearing_store = {}; - bearing_sources = []; + //bearing_sources = []; } @@ -114,6 +124,15 @@ function addBearing(timestamp, bearing, live){ bearing_store[timestamp] = bearing; + if (timeSeqEnabled){ + // Check if this bearing is from the current time-sequenced transmitter. + var _current_seq = getCurrentSeqNumber(); + if (_current_seq >= 0){ + bearing.source = bearing.source + "_Fox" + _current_seq; + } + updateTimeSeqStatus(); + } + if ( !bearing_sources.includes(bearing.source)){ bearing_sources.push(bearing.source); _new_bearing_div_name = "bearing_source_" + bearing.source; @@ -297,7 +316,7 @@ function toggleBearingsOnlyMode(){ var _bearings_only_enabled = document.getElementById("bearingsOnlyMode").checked; - if ((_bearings_only_enabled == true) && (bearings_only_mode == false)){ + if ((_bearings_only_enabled == true) ){//} && (bearings_only_mode == false)){ // The user had just enabled the bearings_only_mode, so hide things that are not relevant. $("#summary_table").hide(); @@ -309,7 +328,7 @@ function toggleBearingsOnlyMode(){ bearings_only_mode = true; - } else if ((_bearings_only_enabled == false) && (bearings_only_mode == true)){ + } else if ((_bearings_only_enabled == false)){//} && (bearings_only_mode == true)){ // Un-hide balloon stuff $("#summary_table").show(); @@ -473,3 +492,125 @@ function manualBearing(){ socket.emit('add_manual_bearing', _bearing_info); } + + + +function updateTimeSeqStatus(){ + // Update text indicating which sequence number is active. + var _current_seq = getCurrentSeqNumber(); + if(_current_seq >= 0 ){ + var _timeseqtext = "Current Active: " + _current_seq + "
"; + } else { + var _timeseqtext = "Current Active: None
"; + } + for (var n=0; n<4; n++){ + if(timeSeqTimes[n] > 0){ + timeseq_hms = new Date(timeSeqTimes[n]); + _timeseqtext += "Fox "+n+": " + timeseq_hms.toLocaleTimeString() + "
"; + $("#timeSeqSet" + n).css("background-color", "#00FF00"); + }else if (timeSeqTimes[n] < 0){ + _timeseqtext += "Fox "+n+": Not Set
"; + $("#timeSeqSet" + num).css("background-color", "#FF0000"); + } else { + _timeseqtext += "Fox "+n+": Not Set
"; + $("#timeSeqSet" + n).css("background-color", "buttonface"); + } + } + + $("#timeSeqStatus").html(_timeseqtext); +} + +function updateTimeSeqClock(){ + if(timeSeqEnabled == true){ + var _current_seq = getCurrentSeqNumber(); + if( _current_seq >= 0 ){ + + var _current_time = Date.now(); + var _seqtime = timeSeqActive - ((_current_time - timeSeqTimes[_current_seq]) % (timeSeqCycle*1000))/1000.0; + + $('#timeseq_notice').text("Fox " + _current_seq + ": " + _seqtime.toFixed(1)); + + } else { + $('#timeseq_notice').text(""); + } + + } else { + $('#timeseq_notice').text(""); + } +} + +function getCurrentSeqNumber(offset_seconds){ + // Determine the current transmitter number, based on current time and the timeSeqTimes. + // Optional offset_seconds argument, to enable testing times slightly into the future. + + if (typeof offset_seconds === 'undefined') { + offset_seconds = 0; + } + + var _current_time = Date.now() + offset_seconds*1000; + + if(timeSeqTimes[0] > 0){ + if ((_current_time - timeSeqTimes[0]) % (timeSeqCycle*1000) < timeSeqActive*1000){ + return 0 + } + } + if(timeSeqTimes[1] > 0){ + if ((_current_time - timeSeqTimes[1]) % (timeSeqCycle*1000) < timeSeqActive*1000){ + return 1 + } + } + if(timeSeqTimes[2] > 0){ + if ((_current_time - timeSeqTimes[2]) % (timeSeqCycle*1000) < timeSeqActive*1000){ + return 2 + } + } + if(timeSeqTimes[3] > 0){ + if ((_current_time - timeSeqTimes[3]) % (timeSeqCycle*1000) < timeSeqActive*1000){ + return 3 + } + } + return -1; +} + +function setTimeSeq(num){ + + if (num>= 0){ + timeSeqEnabled = true; + $("#timeSeqEnabled").prop('checked', true); + // Check we arent currently in the middle of a transmit period + if (getCurrentSeqNumber() < 0 && getCurrentSeqNumber(timeSeqActive)){ + // Update + timeSeqTimes[num] = Date.now(); + // Set button color to green. + $("#timeSeqSet" + num).css("background-color", "#00FF00"); + } else { + timeSeqTimes[num] = -1; + // Set button color to red. + $("#timeSeqSet" + num).css("background-color", "#FF0000"); + } + } else { + timeSeqEnabled = false; + $("#timeSeqEnabled").prop('checked', false); + timeSeqTimes = [0,0,0,0]; + $("#timeSeqSet0").css("background-color", "buttonface"); + $("#timeSeqSet1").css("background-color", "buttonface"); + $("#timeSeqSet2").css("background-color", "buttonface"); + $("#timeSeqSet3").css("background-color", "buttonface"); + } + updateTimeSeqStatus(); + clientSettingsUpdate(); +} + +function toggleTimeSeqEnabled(){ + // Enable-disable time sequenced transmitters. + var _time_seq_enabled = document.getElementById("timeSeqEnabled").checked; + + if (_time_seq_enabled == true){ + // Enable time-sequenced transmitters. + timeSeqEnabled = true; + } else { + // Disable time-sequenced transmitters. + timeSeqEnabled = false; + } + clientSettingsUpdate(); +} \ No newline at end of file diff --git a/static/js/settings.js b/static/js/settings.js index e3f5e3a..d09c699 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -65,6 +65,19 @@ function serverSettingsUpdate(data){ $('#bearingColorSelect').val(chase_config.bearing_color); $('#bearingCustomColor').val(chase_config.bearing_custom_color); $('#bearingMaximumAge').val((chase_config.max_bearing_age/60.0).toFixed(0)); + $('#bearingConfidenceThreshold').val(chase_config.doa_confidence_threshold.toFixed(1)); + + $('#bearingsOnlyMode').prop('checked', chase_config.bearings_only_mode); + toggleBearingsOnlyMode() + // Add new time sync bearing settings here + + timeSeqEnabled = chase_config.time_seq_enabled; + $("#timeSeqEnabled").prop('checked', timeSeqEnabled); + timeSeqActive = chase_config.time_seq_active; + timeSeqCycle = chase_config.time_seq_cycle; + timeSeqTimes = chase_config.time_seq_times; + updateTimeSeqStatus(); + // Clear and populate the profile selection. $('#profileSelect').children('option:not(:first)').remove(); @@ -108,6 +121,12 @@ function clientSettingsUpdate(){ chase_config.habitat_update_rate = _habitat_update_rate } + // Add in a selection of the bearing settings here. + // These don't change anything on the backend, but need to be propagated to other clients. + chase_config.time_seq_times = timeSeqTimes; + chase_config.time_seq_enabled = timeSeqEnabled; + chase_config.time_seq_active = timeSeqActive; + chase_config.time_seq_cycle = timeSeqCycle; socket.emit('client_settings_update', chase_config); }; \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 80ee9dc..e4a5845 100644 --- a/templates/index.html +++ b/templates/index.html @@ -358,7 +358,7 @@ // Data age display - shows how old the various datasets are. L.control.custom({ position: 'topleft', - content : "
Data Age
", + content : "
Data Age
", classes : 'btn-group-vertical btn-group-sm', id: 'age_display', style : @@ -414,6 +414,20 @@ }) .addTo(map); + // Time-to-landing display - shows the time until landing for the currently tracked payload. + L.control.custom({ + position: 'bottomcenter', + content : "
", + classes : 'btn-group-vertical btn-group-sm', + id: 'ttl_display', + style : + { + margin: '5px', + padding: '0px 0 0 0', + cursor: 'pointer', + } + }) + .addTo(map); L.control.custom({ position: 'bottomright', @@ -675,6 +689,8 @@ }else{ $("#pred_age").text("Predictions: " + pred_data_age.toFixed(1)+"s"); } + + updateTimeSeqClock(); }, age_update_rate); // Start connection to Sondehub Websockets. @@ -705,6 +721,9 @@ }); } },10000); + + // Enable bearings only mode if it's set. + toggleBearingsOnlyMode(); }); @@ -1014,7 +1033,28 @@


- +
+ EasyBearing
O'Clock Entry +
+

Mt Gambier

+
+ + +
+
+ Active Time (s)
+
+
+ Cycle Time (s)
+
+
+
+
+
+
+
diff --git a/templates/oclock.html b/templates/oclock.html new file mode 100644 index 0000000..fd7a97b --- /dev/null +++ b/templates/oclock.html @@ -0,0 +1,270 @@ + + + + O'Clock Bearing Entry + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

O'Clock Entry

+
+
+
+
+
    +
  • 1
  • +
  • 2
  • +
  • 3
  • +
  • 4
  • +
  • 5
  • +
  • 6
  • +
  • 7
  • +
  • 8
  • +
  • 9
  • +
  • 10
  • +
  • 11
  • +
  • 12
  • +
      +
+
+
+
+
Last Bearing:
Last Time:
+
+
+
+
+
+
+
+
+ + +