kopia lustrzana https://github.com/projecthorus/chasemapper
Better support of supplied heading data. Handle identical sequential positions better. Optionally display heading on display
rodzic
25f5510b64
commit
e03e6d15e1
|
@ -8,4 +8,4 @@
|
||||||
|
|
||||||
# Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH
|
# Now using Semantic Versioning (https://semver.org/) MAJOR.MINOR.PATCH
|
||||||
|
|
||||||
__version__ = "1.3.2"
|
__version__ = "1.3.3"
|
||||||
|
|
|
@ -43,6 +43,7 @@ class GenericTrack(object):
|
||||||
self.is_descending = False
|
self.is_descending = False
|
||||||
|
|
||||||
self.supplied_heading = False
|
self.supplied_heading = False
|
||||||
|
self.heading_status = None
|
||||||
|
|
||||||
|
|
||||||
self.prev_heading = 0.0
|
self.prev_heading = 0.0
|
||||||
|
@ -82,6 +83,9 @@ class GenericTrack(object):
|
||||||
self.heading = data_dict["heading"]
|
self.heading = data_dict["heading"]
|
||||||
self.supplied_heading = True
|
self.supplied_heading = True
|
||||||
|
|
||||||
|
if "heading_status" in data_dict:
|
||||||
|
self.heading_status = data_dict["heading_status"]
|
||||||
|
|
||||||
self.update_states()
|
self.update_states()
|
||||||
|
|
||||||
return self.get_latest_state()
|
return self.get_latest_state()
|
||||||
|
@ -105,6 +109,7 @@ class GenericTrack(object):
|
||||||
"landing_rate": self.landing_rate,
|
"landing_rate": self.landing_rate,
|
||||||
"heading": self.heading,
|
"heading": self.heading,
|
||||||
"heading_valid": self.heading_valid,
|
"heading_valid": self.heading_valid,
|
||||||
|
"heading_status": "Unknown",
|
||||||
"turn_rate": self.turn_rate,
|
"turn_rate": self.turn_rate,
|
||||||
"speed": self.speed,
|
"speed": self.speed,
|
||||||
}
|
}
|
||||||
|
@ -163,9 +168,13 @@ class GenericTrack(object):
|
||||||
self.prev_time = _pos_1[0]
|
self.prev_time = _pos_1[0]
|
||||||
|
|
||||||
# Calculate new heading
|
# Calculate new heading
|
||||||
_pos_info = position_info(
|
try:
|
||||||
(_pos_1[1], _pos_1[2], _pos_1[3]), (_pos_2[1], _pos_2[2], _pos_2[3])
|
_pos_info = position_info(
|
||||||
)
|
(_pos_1[1], _pos_1[2], _pos_1[3]), (_pos_2[1], _pos_2[2], _pos_2[3])
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
logging.debug("Math Domain Error in heading calculation - Identical Sequential Positions")
|
||||||
|
return self.heading
|
||||||
|
|
||||||
self.heading = _pos_info["bearing"]
|
self.heading = _pos_info["bearing"]
|
||||||
|
|
||||||
|
@ -183,9 +192,14 @@ class GenericTrack(object):
|
||||||
_pos_1 = self.track_history[-2]
|
_pos_1 = self.track_history[-2]
|
||||||
_pos_2 = self.track_history[-1]
|
_pos_2 = self.track_history[-1]
|
||||||
|
|
||||||
_pos_info = position_info(
|
|
||||||
(_pos_1[1], _pos_1[2], _pos_1[3]), (_pos_2[1], _pos_2[2], _pos_2[3])
|
try:
|
||||||
)
|
_pos_info = position_info(
|
||||||
|
(_pos_1[1], _pos_1[2], _pos_1[3]), (_pos_2[1], _pos_2[2], _pos_2[3])
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
logging.debug("Math Domain Error in speed calculation - Identical Sequential Positions")
|
||||||
|
return 0.0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_speed = _pos_info["great_circle_distance"] / _time_delta
|
_speed = _pos_info["great_circle_distance"] / _time_delta
|
||||||
|
|
|
@ -57,6 +57,9 @@ class SerialGPS(object):
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.uberdebug = uberdebug
|
self.uberdebug = uberdebug
|
||||||
|
|
||||||
|
# Indication of what the last expected string is.
|
||||||
|
self.last_string = "GGA"
|
||||||
|
|
||||||
# Current GPS state, in a format which matches the Horus UDP
|
# Current GPS state, in a format which matches the Horus UDP
|
||||||
# 'Chase Car Position' message.
|
# 'Chase Car Position' message.
|
||||||
# Note that these packets do not contain a timestamp.
|
# Note that these packets do not contain a timestamp.
|
||||||
|
@ -66,6 +69,8 @@ class SerialGPS(object):
|
||||||
"longitude": 0.0,
|
"longitude": 0.0,
|
||||||
"altitude": 0.0,
|
"altitude": 0.0,
|
||||||
"speed": 0.0,
|
"speed": 0.0,
|
||||||
|
"fix_status": 0,
|
||||||
|
"heading": None,
|
||||||
"valid": False,
|
"valid": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +212,8 @@ class SerialGPS(object):
|
||||||
gpgga_latns = gpgga[3]
|
gpgga_latns = gpgga[3]
|
||||||
gpgga_lon = self.dm_to_sd(gpgga[4])
|
gpgga_lon = self.dm_to_sd(gpgga[4])
|
||||||
gpgga_lonew = gpgga[5]
|
gpgga_lonew = gpgga[5]
|
||||||
gpgga_fixstatus = gpgga[6]
|
gpgga_fixstatus = int(gpgga[6])
|
||||||
|
self.gps_state["fix_status"] = gpgga_fixstatus
|
||||||
self.gps_state["altitude"] = float(gpgga[9])
|
self.gps_state["altitude"] = float(gpgga[9])
|
||||||
|
|
||||||
if gpgga_latns == "S":
|
if gpgga_latns == "S":
|
||||||
|
@ -224,7 +230,49 @@ class SerialGPS(object):
|
||||||
self.gps_state["valid"] = False
|
self.gps_state["valid"] = False
|
||||||
else:
|
else:
|
||||||
self.gps_state["valid"] = True
|
self.gps_state["valid"] = True
|
||||||
|
|
||||||
|
if self.last_string == "GGA":
|
||||||
self.send_to_callback()
|
self.send_to_callback()
|
||||||
|
|
||||||
|
elif ("$GPTHS" in data) or ("$GNTHS" in data):
|
||||||
|
# Very basic handling of the uBlox NEO-M8U-provided True heading data.
|
||||||
|
# This data *appears* to be the output of the fused solution, once the system
|
||||||
|
# has self-calibrated.
|
||||||
|
# The GNTHS message can be enabled on the USB port by sending: $PUBX,40,THS,0,0,0,1,0,0*55\r\n
|
||||||
|
# to the GPS.
|
||||||
|
logging.debug("SerialGPS - Got Heading Info (GNTHS).")
|
||||||
|
gnths = data.split(",")
|
||||||
|
try:
|
||||||
|
if len(gnths[1]) > 0:
|
||||||
|
# Data is present in the heading field, try and parse it.
|
||||||
|
gnths_heading = float(gnths[1])
|
||||||
|
# Get the heading validity field.
|
||||||
|
gnths_valid = gnths[2]
|
||||||
|
|
||||||
|
if gnths_valid != "V":
|
||||||
|
# Treat anything other than 'V' as a valid heading
|
||||||
|
self.gps_state["heading"] = gnths_heading
|
||||||
|
else:
|
||||||
|
self.gps_state["heading"] = None
|
||||||
|
else:
|
||||||
|
# Blank field, which means data is not valid.
|
||||||
|
self.gps_state["heading"] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Assume that if we are receiving GNTHS strings, that they are the last in the batch.
|
||||||
|
# Stop sending data when we get a GGA string.
|
||||||
|
self.last_string = "THS"
|
||||||
|
|
||||||
|
# Send to callback if we have lock.
|
||||||
|
if self.gps_state["fix_status"] != 0:
|
||||||
|
self.send_to_callback()
|
||||||
|
|
||||||
|
except:
|
||||||
|
# Failed to parse field, which probably means an invalid heading.
|
||||||
|
logging.debug(f"Failed to parse GNTHS: {data}")
|
||||||
|
# Invalidate the heading data, and revert to emitting messages on GGA strings.
|
||||||
|
self.gps_state["heading"] = None
|
||||||
|
self.last_string = "GGA"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Discard all other lines
|
# Discard all other lines
|
||||||
|
@ -238,6 +286,9 @@ class SerialGPS(object):
|
||||||
# Generate a copy of the gps state
|
# Generate a copy of the gps state
|
||||||
_state = self.gps_state.copy()
|
_state = self.gps_state.copy()
|
||||||
|
|
||||||
|
if _state["heading"] is None:
|
||||||
|
_state.pop("heading")
|
||||||
|
|
||||||
# Attempt to pass it onto the callback function.
|
# Attempt to pass it onto the callback function.
|
||||||
if self.callback != None:
|
if self.callback != None:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -835,15 +835,19 @@ def udp_listener_car_callback(data):
|
||||||
"alt": _alt,
|
"alt": _alt,
|
||||||
"comment": _comment,
|
"comment": _comment,
|
||||||
}
|
}
|
||||||
# Add in true heading data if we have been supplied it
|
# Add in true heading data if we have been supplied it (e.g. from a uBlox NEO-M8U device)
|
||||||
# (Which will be the case once I end up building a better car GPS...)
|
|
||||||
if "heading" in data:
|
if "heading" in data:
|
||||||
_car_position_update["heading"] = data["heading"]
|
_car_position_update["heading"] = data["heading"]
|
||||||
|
|
||||||
|
if "heading_status" in data:
|
||||||
|
_car_position_update["heading_status"] = data["heading_status"]
|
||||||
|
|
||||||
car_track.add_telemetry(_car_position_update)
|
car_track.add_telemetry(_car_position_update)
|
||||||
|
|
||||||
_state = car_track.get_latest_state()
|
_state = car_track.get_latest_state()
|
||||||
_heading = _state["heading"]
|
_heading = _state["heading"]
|
||||||
|
_heading_status = _state["heading_status"]
|
||||||
|
_heading_valid = _state["heading_valid"]
|
||||||
_speed = _state["speed"]
|
_speed = _state["speed"]
|
||||||
|
|
||||||
# Push the new car position to the web client
|
# Push the new car position to the web client
|
||||||
|
@ -854,6 +858,8 @@ def udp_listener_car_callback(data):
|
||||||
"position": [_lat, _lon, _alt],
|
"position": [_lat, _lon, _alt],
|
||||||
"vel_v": 0.0,
|
"vel_v": 0.0,
|
||||||
"heading": _heading,
|
"heading": _heading,
|
||||||
|
"heading_valid": _heading_valid,
|
||||||
|
"heading_status": _heading_status,
|
||||||
"speed": _speed,
|
"speed": _speed,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -239,6 +239,7 @@ function handleTelemetry(data){
|
||||||
// Update car position.
|
// Update car position.
|
||||||
chase_car_position.latest_data = data.position;
|
chase_car_position.latest_data = data.position;
|
||||||
chase_car_position.heading = data.heading; // degrees true
|
chase_car_position.heading = data.heading; // degrees true
|
||||||
|
chase_car_position.heading_valid = data.heading_valid;
|
||||||
chase_car_position.speed = data.speed; // m/s
|
chase_car_position.speed = data.speed; // m/s
|
||||||
|
|
||||||
// Update range rings, if they are enabled.
|
// Update range rings, if they are enabled.
|
||||||
|
@ -257,6 +258,19 @@ function handleTelemetry(data){
|
||||||
$("#chase_car_speed_header").text("");
|
$("#chase_car_speed_header").text("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update heading information
|
||||||
|
if (document.getElementById("showCarHeading").checked){
|
||||||
|
if(chase_car_position.heading_valid){
|
||||||
|
$("#chase_car_heading").text(chase_car_position.heading.toFixed(0) + "˚");
|
||||||
|
}else{
|
||||||
|
$("#chase_car_heading").text("---˚");
|
||||||
|
}
|
||||||
|
$("#chase_car_heading_header").text("Heading");
|
||||||
|
} else {
|
||||||
|
$("#chase_car_heading").text("");
|
||||||
|
$("#chase_car_heading_header").text("");
|
||||||
|
}
|
||||||
|
|
||||||
if (chase_car_position.marker == 'NONE'){
|
if (chase_car_position.marker == 'NONE'){
|
||||||
// Create marker!
|
// Create marker!
|
||||||
chase_car_position.marker = L.marker(chase_car_position.latest_data,{title:"Chase Car", icon: carIcon, rotationOrigin: "center center"})
|
chase_car_position.marker = L.marker(chase_car_position.latest_data,{title:"Chase Car", icon: carIcon, rotationOrigin: "center center"})
|
||||||
|
@ -270,8 +284,8 @@ function handleTelemetry(data){
|
||||||
chase_car_position.path.addLatLng(chase_car_position.latest_data);
|
chase_car_position.path.addLatLng(chase_car_position.latest_data);
|
||||||
chase_car_position.marker.setLatLng(chase_car_position.latest_data).update();
|
chase_car_position.marker.setLatLng(chase_car_position.latest_data).update();
|
||||||
}
|
}
|
||||||
// Rotate car icon based on heading, but only if we're going faster than 20kph (5.5m/s).
|
|
||||||
if(chase_car_position.speed > 5.5){ // TODO: Remove magic number!
|
if(chase_car_position.heading_valid){
|
||||||
var _car_heading = chase_car_position.heading - 90.0;
|
var _car_heading = chase_car_position.heading - 90.0;
|
||||||
if (_car_heading<=90.0){
|
if (_car_heading<=90.0){
|
||||||
chase_car_position.marker.setIcon(carIcon);
|
chase_car_position.marker.setIcon(carIcon);
|
||||||
|
|
|
@ -382,6 +382,20 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.addTo(map);
|
.addTo(map);
|
||||||
|
// Chase Car Heading Display
|
||||||
|
L.control.custom({
|
||||||
|
position: 'bottomleft',
|
||||||
|
content : "<div class='dataAgeHeader' id='chase_car_heading_header'></div><div id='chase_car_heading' class='chaseCarSpeed'></div>",
|
||||||
|
classes : 'btn-group-vertical btn-group-sm',
|
||||||
|
id: 'heading_display',
|
||||||
|
style :
|
||||||
|
{
|
||||||
|
margin: '5px',
|
||||||
|
padding: '0px 0 0 0',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addTo(map);
|
||||||
|
|
||||||
// Time-to-landing display - shows the time until landing for the currently tracked payload.
|
// Time-to-landing display - shows the time until landing for the currently tracked payload.
|
||||||
L.control.custom({
|
L.control.custom({
|
||||||
|
@ -856,10 +870,13 @@
|
||||||
<b>Custom Color</b><input type="text" class="paramEntry" id="ringCustomColor" value="#FF0000"><br/>
|
<b>Custom Color</b><input type="text" class="paramEntry" id="ringCustomColor" value="#FF0000"><br/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3>Speed Display</h3>
|
<h3>Speed/Heading Display</h3>
|
||||||
<div class="paramRow">
|
<div class="paramRow">
|
||||||
<b>Show Chase Car Speed:</b> <input type="checkbox" class="paramSelector" id="showCarSpeed">
|
<b>Show Chase Car Speed:</b> <input type="checkbox" class="paramSelector" id="showCarSpeed">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="paramRow">
|
||||||
|
<b>Show Chase Car Heading:</b> <input type="checkbox" class="paramSelector" id="showCarHeading">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Ładowanie…
Reference in New Issue