Add spectra plot, orientation info to web GUI. Add logging of telemetry data

picamera2
Mark Jessop 2022-12-12 16:31:15 +10:30
rodzic c1a4224c11
commit a6759c79a5
13 zmienionych plików z 440 dodań i 101 usunięć

Wyświetl plik

@ -11,6 +11,7 @@ RUN apt-get update && \
git \
libusb-1.0-0-dev \
pkg-config \
libatlas-base \
python3 \
python3-dev \
python3-pip \

Wyświetl plik

@ -441,7 +441,7 @@ def image_telemetry_decoder(packet):
data = struct.unpack('>BH7pBHIBffffffBBBBBBBBBbfffffff', packet)
image_data['sequence_number'] = data[1]
image_data['callsign'] = data[2]
image_data['callsign'] = data[2].decode()
image_data['image_id'] = data[3]
image_data['week'] = data[4]
image_data['iTOW'] = data[5]/1000.0 # iTOW provided as milliseconds, convert to seconds.

Wyświetl plik

@ -31,7 +31,10 @@ class FSKDemodStats(object):
def __init__(self,
averaging_time = 5.0,
peak_hold = False,
decoder_id = ""
decoder_id = "",
freq = 441200000,
sample_rate = 921416,
real = False
):
"""
@ -45,6 +48,9 @@ class FSKDemodStats(object):
self.averaging_time = float(averaging_time)
self.peak_hold = peak_hold
self.decoder_id = str(decoder_id)
self.freq = freq
self.sample_rate = sample_rate
self.real = real
# Input data stores.
self.in_times = np.array([])
@ -56,6 +62,8 @@ class FSKDemodStats(object):
self.snr = -999.0
self.fest = [0.0,0.0]
self.fft = []
self.fft_db = []
self.fft_freq = []
self.ppm = 0.0
@ -97,10 +105,18 @@ class FSKDemodStats(object):
# Now we can process the data.
_time = time.time()
self.fft = _data['samp_fft']
self.fft = np.array(_data['samp_fft'])
self.fest[0] = _data['f1_est']
self.fest[1] = _data['f2_est']
#self.fft = self.fft[self.fft>0.0]
try:
self.fft_db = list(np.around(10*np.log10(self.fft+0.000000001),1))
self.fft_freq = list(np.around(np.linspace(0, self.sample_rate/2, len(self.fft)) + self.freq, 1))
except:
pass
# Time-series data
self.in_times = np.append(self.in_times, _time)
self.in_snr = np.append(self.in_snr, _data['EbNodB'])
@ -164,11 +180,14 @@ if __name__ == "__main__":
# Command line arguments.
parser = argparse.ArgumentParser()
parser.add_argument("--rate", default=2, type=int, help="Update Rate (Hz)")
parser.add_argument("--freq", default=441200000, type=float, help="IQ Centre Frequency (Hz)")
parser.add_argument("--samplerate", default=921416, type=float, help="Sample rate (Hz)")
parser.add_argument("--real", default=False, action="store_true", help="Real Samples (not IQ)")
args = parser.parse_args()
_averaging_time = 1.0/args.rate
stats_parser = FSKDemodStats(averaging_time=_averaging_time, peak_hold=True)
stats_parser = FSKDemodStats(averaging_time=_averaging_time, peak_hold=True, freq=args.freq, sample_rate=args.samplerate, real=args.real)
_last_update_time = time.time()
@ -189,8 +208,11 @@ if __name__ == "__main__":
_stats = {
'snr': stats_parser.snr,
'ppm': stats_parser.ppm,
'fft': stats_parser.fft,
'fest': stats_parser.fest
#'fft': stats_parser.fft,
'fft_db': stats_parser.fft_db,
'fft_freq': stats_parser.fft_freq,
'fest': stats_parser.fest,
'freq': stats_parser.freq
}
send_modem_stats(_stats)

Wyświetl plik

@ -3,13 +3,14 @@
# SSDV Packet Receiver & Parser
# Decodes SSDV packets passed via stdin.
#
# Copyright (C) 2018 Mark Jessop <vk5qi@rfhead.net>
# Copyright (C) 2022 Mark Jessop <vk5qi@rfhead.net>
# Released under GNU GPL v3 or later
#
# Requires: ssdv (https://github.com/fsphil/ssdv)
#
import codecs
import datetime
import json
import logging
import os
@ -20,9 +21,6 @@ import argparse
import socket
from WenetPackets import *
# Check if we are running in Python 2 or 3
PY3 = sys.version_info[0] == 3
parser = argparse.ArgumentParser()
@ -30,8 +28,10 @@ parser.add_argument("--hex", action="store_true", help="Take Hex strings as inpu
parser.add_argument("--partialupdate", default=0, help="Push partial updates every N packets to GUI.")
parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Verbose output")
parser.add_argument("--headless", action='store_true', default=False, help="Headless mode - broadcasts additional data via UDP.")
parser.add_argument("--rximages", default="./rx_images/", help="Location to save RX images and telemetry to.")
args = parser.parse_args()
RX_IMAGES_DIR = args.rximages
# Set up log output.
if args.verbose:
@ -44,6 +44,9 @@ logging.getLogger("requests").setLevel(logging.CRITICAL)
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
LOG_FILENAME = os.path.join(args.rximages,datetime.datetime.utcnow().strftime("%Y%m%d-%H%MZ"))
# GUI updates are only sent locally.
def trigger_gui_update(filename, text = "None"):
message = {'filename': filename,
@ -87,6 +90,54 @@ def broadcast_telemetry_packet(data, headless=False):
gui_socket.close()
def log_telemetry_packet(packet):
global START_DATETIME
packet_type = decode_packet_type(packet)
if packet_type == WENET_PACKET_TYPES.IDLE:
return
elif packet_type == WENET_PACKET_TYPES.TEXT_MESSAGE:
decoded = decode_text_message(packet)
_log_f = open(LOG_FILENAME+"_text.log",'a')
_log_f.write(json.dumps(decoded)+"\n")
_log_f.close()
elif packet_type == WENET_PACKET_TYPES.SEC_PAYLOAD_TELEMETRY:
decoded = sec_payload_decode(packet)
# Convert payload (bytes) into a hexadecimal string so we can serialise it.
decoded['payload'] = codecs.encode(decoded['payload'],'hex').decode()
_log_f = open(LOG_FILENAME+"_secondary.log",'a')
_log_f.write(json.dumps(decoded)+"\n")
_log_f.close()
elif packet_type == WENET_PACKET_TYPES.GPS_TELEMETRY:
decoded = gps_telemetry_decoder(packet)
_log_f = open(LOG_FILENAME+"_gps.log",'a')
_log_f.write(json.dumps(decoded)+"\n")
_log_f.close()
elif packet_type == WENET_PACKET_TYPES.ORIENTATION_TELEMETRY:
decoded = orientation_telemetry_decoder(packet)
_log_f = open(LOG_FILENAME+"_orientation.log",'a')
_log_f.write(json.dumps(decoded)+"\n")
_log_f.close()
elif packet_type == WENET_PACKET_TYPES.IMAGE_TELEMETRY:
decoded = image_telemetry_decoder(packet)
_log_f = open(LOG_FILENAME+"_imagetelem.log",'a')
_log_f.write(json.dumps(decoded)+"\n")
_log_f.close()
# State variables
current_image = -1
current_callsign = ""
@ -108,92 +159,97 @@ while True:
data = codecs.decode(data, 'hex')
else:
# If we are receiving raw binary data via stdin, we need
# to use the buffer interface under Python 3, and the 'regular' interface
# under python t.
if PY3:
data = sys.stdin.buffer.read(256)
else:
data = sys.stdin.read(256)
# to use the buffer interface under Python 3.
data = sys.stdin.buffer.read(256)
packet_type = decode_packet_type(data)
try:
packet_type = decode_packet_type(data)
if packet_type == WENET_PACKET_TYPES.IDLE:
continue
elif packet_type == WENET_PACKET_TYPES.TEXT_MESSAGE:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
elif packet_type == WENET_PACKET_TYPES.SEC_PAYLOAD_TELEMETRY:
broadcast_telemetry_packet(data)
logging.info(packet_to_string(data))
elif packet_type == WENET_PACKET_TYPES.GPS_TELEMETRY:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
elif packet_type == WENET_PACKET_TYPES.ORIENTATION_TELEMETRY:
broadcast_telemetry_packet(data)
logging.info(packet_to_string(data))
elif packet_type == WENET_PACKET_TYPES.IMAGE_TELEMETRY:
broadcast_telemetry_packet(data)
logging.info(packet_to_string(data))
elif packet_type == WENET_PACKET_TYPES.SSDV:
# Extract packet information.
packet_info = ssdv_packet_info(data)
packet_as_string = ssdv_packet_string(data)
# Only proceed if there are no decode errors.
if packet_info['error'] != 'None':
logging.error(message['error'])
if packet_type == WENET_PACKET_TYPES.IDLE:
continue
elif packet_type == WENET_PACKET_TYPES.TEXT_MESSAGE:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
log_telemetry_packet(data)
if (packet_info['image_id'] != current_image) or (packet_info['callsign'] != current_callsign) :
# Attempt to decode current image if we have enough packets.
logging.info("New image - ID #%d" % packet_info['image_id'])
if current_packet_count > 0:
# Attempt to decode current image, and write out to a file.
temp_f.close()
# Run SSDV
returncode = os.system("ssdv -d rxtemp.bin ./rx_images/%s_%s_%d.jpg 2>/dev/null > /dev/null" % (current_packet_time,current_callsign,current_image))
if returncode == 1:
logging.error("ERROR: SSDV Decode failed!")
elif packet_type == WENET_PACKET_TYPES.SEC_PAYLOAD_TELEMETRY:
broadcast_telemetry_packet(data)
logging.info(packet_to_string(data))
log_telemetry_packet(data)
elif packet_type == WENET_PACKET_TYPES.GPS_TELEMETRY:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
log_telemetry_packet(data)
elif packet_type == WENET_PACKET_TYPES.ORIENTATION_TELEMETRY:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
log_telemetry_packet(data)
elif packet_type == WENET_PACKET_TYPES.IMAGE_TELEMETRY:
broadcast_telemetry_packet(data, args.headless)
logging.info(packet_to_string(data))
log_telemetry_packet(data)
elif packet_type == WENET_PACKET_TYPES.SSDV:
# Extract packet information.
packet_info = ssdv_packet_info(data)
packet_as_string = ssdv_packet_string(data)
# Only proceed if there are no decode errors.
if packet_info['error'] != 'None':
logging.error(message['error'])
continue
if (packet_info['image_id'] != current_image) or (packet_info['callsign'] != current_callsign) :
# Attempt to decode current image if we have enough packets.
logging.info("New image - ID #%d" % packet_info['image_id'])
if current_packet_count > 0:
# Attempt to decode current image, and write out to a file.
temp_f.close()
# Run SSDV
_dessdv_filename = os.path.join(RX_IMAGES_DIR,f"{current_packet_time}_{current_callsign}_{current_image}")
returncode = os.system(f"ssdv -d rxtemp.bin {_dessdv_filename}.jpg 2>/dev/null > /dev/null")
if returncode == 1:
logging.error("ERROR: SSDV Decode failed!")
else:
logging.debug("SSDV Decoded OK!")
# Make a copy of the raw binary data.
os.system(f"mv rxtemp.bin {_dessdv_filename}.bin")
# Update live displays here.
trigger_gui_update(os.path.abspath(_dessdv_filename+".jpg"), packet_as_string)
# Trigger upload to habhub here.
else:
logging.debug("SSDV Decoded OK!")
# Make a copy of the raw binary data.
os.system("mv rxtemp.bin ./rx_images/%s_%s_%d.bin" % (current_packet_time,current_callsign,current_image))
logging.debug("Not enough packets to decode previous image.")
# Update live displays here.
trigger_gui_update(os.path.abspath("./rx_images/%s_%s_%d.jpg" % (current_packet_time,current_callsign,current_image)), packet_as_string)
# Now set up for the new image.
current_image = packet_info['image_id']
current_callsign = packet_info['callsign']
current_packet_count = 1
current_packet_time = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ")
# Open file and write in first packet.
temp_f = open("rxtemp.bin" , "wb")
temp_f.write(data)
# Trigger upload to habhub here.
else:
logging.debug("Not enough packets to decode previous image.")
# Now set up for the new image.
current_image = packet_info['image_id']
current_callsign = packet_info['callsign']
current_packet_count = 1
current_packet_time = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ")
# Open file and write in first packet.
temp_f = open("rxtemp.bin" , "wb")
temp_f.write(data)
# Write current packet into temp file.
temp_f.write(data)
current_packet_count += 1
if args.partialupdate != 0:
if current_packet_count % int(args.partialupdate) == 0:
# Run the SSDV decoder and push a partial update to the GUI.
temp_f.flush()
returncode = os.system("ssdv -d rxtemp.bin rxtemp.jpg 2>/dev/null > /dev/null")
if returncode == 0:
logging.debug("Wrote out partial update of image ID #%d" % current_image)
trigger_gui_update(os.path.abspath("rxtemp.jpg"), packet_as_string)
else:
# Write current packet into temp file.
temp_f.write(data)
current_packet_count += 1
if args.partialupdate != 0:
if current_packet_count % int(args.partialupdate) == 0:
# Run the SSDV decoder and push a partial update to the GUI.
temp_f.flush()
returncode = os.system("ssdv -d rxtemp.bin rxtemp.jpg 2>/dev/null > /dev/null")
if returncode == 0:
logging.debug("Wrote out partial update of image ID #%d" % current_image)
trigger_gui_update(os.path.abspath("rxtemp.jpg"), packet_as_string)
else:
logging.debug("Unknown Packet Format.")
logging.debug("Unknown Packet Format.")
except Exception as e:
logging.error("Error handling packet - " + str(e))

Wyświetl plik

@ -1,7 +1,7 @@
#!/bin/bash
#
# Wenet RX-side Initialisation Script - HEADLESS DOCKER VERSION
# 2019 Mark Jessop <vk5qi@rfhead.net>
# 2022 Mark Jessop <vk5qi@rfhead.net>
#
# This code mostly assumes an RTLSDR will be used for RX.
#
@ -52,7 +52,7 @@ fi
# Start up the receive chain.
echo "Using Complex Samples."
rtl_sdr -d "$DEVICE" -s "$SDR_RATE" -f "$RX_SSB_FREQ" -g "$GAIN" - | \
./fsk_demod --cu8 -s --stats=100 2 "$SDR_RATE" "$BAUD_RATE" - - 2> >(python3 fskstatsudp.py --rate 1) | \
./fsk_demod --cu8 -s --stats=100 2 "$SDR_RATE" "$BAUD_RATE" - - 2> >(python3 fskstatsudp.py --rate 1 --freq $RX_SSB_FREQ --samplerate $SDR_RATE) | \
./drs232_ldpc - - -vv 2> /dev/null | \
python3 rx_ssdv.py --partialupdate 16 --headless

1
rx/static/css/c3.min.css vendored 100644
Wyświetl plik

@ -0,0 +1 @@
.c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc rect{stroke:#fff;stroke-width:1}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle{fill:currentColor}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-region text{fill-opacity:1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip .value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1}.c3-drag-zoom.enabled{pointer-events:all!important;visibility:visible}.c3-drag-zoom.disabled{pointer-events:none!important;visibility:hidden}.c3-drag-zoom .extent{fill-opacity:.1}

Wyświetl plik

@ -62,12 +62,20 @@ body {
.gps-display {
padding: .25rem 0;
font-weight: 600;
font-weight: 400;
color: rgba(255, 255, 255, .8);
background-color: transparent;
float: right;
}
.orientation-display {
padding: .25rem 0;
font-weight: 400;
color: rgba(255, 255, 255, .8);
background-color: transparent;
float: left;
}
@media (min-width: 48em) {
.masthead-brand {
float: left;
@ -83,3 +91,21 @@ body {
.mastfoot {
color: rgba(255, 255, 255, .5);
}
/*
* FFT plot
*/
#fft_plot .c3-circles-Spectra {
display: none;
}
#fft_plot .c3-target-Peaks {
fill: #ff7f0e;
stroke: #ff7f0e;
}
#fft_plot .c3-tooltip-container {
width: 200px;
color: black;
}

2
rx/static/js/c3.min.js vendored 100644

File diff suppressed because one or more lines are too long

2
rx/static/js/d3.min.js vendored 100644

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -0,0 +1,60 @@
// Scan Result Chart Setup
var scan_chart_spectra;
var scan_chart_fest;
var scan_chart_obj;
function setup_fft_plot(){
scan_chart_spectra = {
xs: {
'Spectra': 'x_spectra'
},
columns: [
['x_spectra',0,0],
['Spectra',0,0]
],
type:'line'
};
scan_chart_peaks = {
xs: {
'Tone Estimates': 'x_fest'
},
columns: [
['x_fest',0],
['Tone Estimates',0]
],
type:'scatter'
};
scan_chart_obj = c3.generate({
bindto: '#fft_plot',
data: scan_chart_spectra,
tooltip: {
format: {
title: function (d) { return (d / 1000000).toFixed(3) + " MHz"; },
value: function (value) { return value + " dB"; }
}
},
axis:{
x:{
tick:{
culling: {
max: window.innerWidth > 1100 ? 10 : 4
},
format: function (x) { return (x/1000000).toFixed(3); }
},
label:"Frequency (MHz)"
},
y:{
label:"Power (dB - Uncalibrated)"
}
},
point:{r:10}
});
$('.c3-axis-y').css('fill', 'white')
$('.c3-axis-x').css('fill', 'white')
$('.c3-legend-item text').css('fill', 'white')
}

Wyświetl plik

@ -6,8 +6,12 @@
<link href="{{ url_for('static', filename='css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/wenet.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='css/c3.min.css') }}" rel="stylesheet">
<script src="{{ url_for('static', filename='js/jquery-3.3.1.min.js')}}"></script>
<script src="{{ url_for('static', filename='js/socket.io-4.1.2.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/c3.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/d3.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/fft_plot.js') }}"></script>
<script type="text/javascript" charset="utf-8">
@ -20,6 +24,7 @@
// http[s]://<domain>:<port>[/<namespace>]
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
setup_fft_plot();
// Handle an image update.
socket.on('image_update', function(msg) {
@ -44,6 +49,29 @@
var _new_desc = "SNR: " + snr + " dB"
$('#snr-data').html(_new_desc);
// Update the Spectra data.
scan_chart_spectra.columns[0] = ['x_spectra'].concat(msg.fft_freq);
scan_chart_spectra.columns[1] = ['Spectra'].concat(msg.fft_db);
scan_chart_obj.load(scan_chart_spectra);
var max_fft_level = Math.max(...msg.fft_db);
// Update the Frequency Estimate data.
scan_chart_peaks.columns[0] = ['x_fest'].concat([msg.fest[0]+msg.freq, msg.fest[1]+msg.freq]);
scan_chart_peaks.columns[1] = ['Tone Estimates'].concat([max_fft_level,max_fft_level]);
scan_chart_obj.load(scan_chart_peaks);
// Set the chart axes
scan_chart_obj.axis.max({'y':max_fft_level});
scan_chart_obj.axis.min({'y':max_fft_level-20});
// Ensure colors are set correctly.
$('.c3-axis-y').css('fill', 'white')
$('.c3-axis-x').css('fill', 'white')
$('.c3-legend-item text').css('fill', 'white')
});
socket.on('gps_update', function(msg) {
@ -62,12 +90,110 @@
}
});
socket.on('orientation_update', function(msg) {
//console.log(msg);
// {
// "week": 2240,
// "iTOW": 8220,
// "leapS": 18,
// "timestamp": "2022-12-11T02:16:42",
// "sys_status": 5,
// "sys_error": 0,
// "sys_cal": 0,
// "gyro_cal": 3,
// "accel_cal": 1,
// "magnet_cal": 0,
// "temp": 29,
// "euler_heading": 1.5,
// "euler_roll": 1.125,
// "euler_pitch": 8.125,
// "quaternion_x": -0.0714111328125,
// "quaternion_y": -0.00933837890625,
// "quaternion_z": -0.01348876953125,
// "quaternion_w": 0.997314453125,
// "error": "None"
// }
if(msg.sys_cal==3){
var sys_cal = "OK";
} else {
var sys_cal = "Uncal (S" + msg.sys_cal.toFixed(0) + " G" + msg.gyro_cal.toFixed(0) + " A" + msg.accel_cal.toFixed(0) + " M" + msg.magnet_cal.toFixed(0) + ")";
}
var heading = msg.euler_heading.toFixed(1);
var roll = msg.euler_roll.toFixed(1);
var pitch = msg.euler_pitch.toFixed(1)
var _ori_desc = "Heading " + heading + "˚ Roll " + roll + "˚ Pitch " + pitch + "˚ Cal: " + sys_cal;
$('#orientation-data').html(_ori_desc);
});
socket.on('image_telem_update', function(msg) {
console.log(msg);
// {
// "week": 2240,
// "iTOW": 8220,
// "leapS": 18,
// "timestamp": "2022-12-11T02:16:42",
// "sys_status": 5,
// "sys_error": 0,
// "sys_cal": 0,
// "gyro_cal": 3,
// "accel_cal": 1,
// "magnet_cal": 0,
// "temp": 29,
// "euler_heading": 1.5,
// "euler_roll": 1.125,
// "euler_pitch": 8.125,
// "quaternion_x": -0.0714111328125,
// "quaternion_y": -0.00933837890625,
// "quaternion_z": -0.01348876953125,
// "quaternion_w": 0.997314453125,
// "error": "None"
// }
_img_number = msg.image_id.toFixed(0);
_timestamp = msg.timestamp;
if (msg.numSV < 3){
_gps_desc = "No GPS Lock";
} else {
var lat = msg.latitude.toFixed(5);
var lon = msg.longitude.toFixed(5);
var alt = msg.altitude.toFixed(0);
var ascent = msg.ascent_rate.toFixed(1);
var _gps_desc = lat + ", " + lon + " " + alt + "m " + ascent + " m/s"
}
if(msg.sys_cal==3){
var sys_cal = "OK";
} else {
var sys_cal = "Uncal (S" + msg.sys_cal.toFixed(0) + " G" + msg.gyro_cal.toFixed(0) + " A" + msg.accel_cal.toFixed(0) + " M" + msg.magnet_cal.toFixed(0) + ")";
}
var heading = msg.euler_heading.toFixed(1);
var roll = msg.euler_roll.toFixed(1);
var pitch = msg.euler_pitch.toFixed(1)
var _ori_desc = "Heading " + heading + "˚ Roll " + roll + "˚ Pitch " + pitch + "˚ Cal: " + sys_cal;
var _image_telem_desc = "Img Number: " + _img_number + " Time: " + _timestamp + " " + _gps_desc + " " + _ori_desc;
$('#image_telem_data').html(_image_telem_desc);
});
var text_messages = [];
socket.on('text_update', function(msg) {
var _text = "Msg #" + msg.id + ": " + msg.text;
text_messages.push(_text);
if(text_messages.length > 6){
if(text_messages.length > 10){
text_messages.shift();
}
@ -90,16 +216,22 @@
<body>
<div class="container-fluid">
<div class="row">
<div class="col-4">
<div class="col-10">
<h3 class="masthead-brand">Wenet Dashboard</h3>
</div>
<div class="col-6">
<h5 class="gps-display" id="gps-data"></h5>
</div>
<div class="col-2">
<h4 class="snr-display" id="snr-data">SNR: 0 dB</h4>
</div>
</div>
<div class="row">
<div class="col-6">
<h5 class="orientation-display" id="orientation-data"></h5>
</div>
<div class="col-6">
<h5 class="gps-display" id="gps-data"></h5>
</div>
</div>
<div class="row">
<div class="col-12">
<img src="{{ url_for('static', filename='horus.png') }}" id="wenet_image" class="center-block wenet-image"/>
@ -113,11 +245,31 @@
<div id="uploader_data">No uploader status data received yet.</div>
</div>
</div>
<div class="row">
<div class='col-12'>
<div id="image_telem_data"></div>
</div>
</div>
<div class="row">
<div class='col-12'>
<h5>Log Messages</h5>
</div>
</div>
<div class="row">
<div class='col-12'>
<div id="log_data">No log messages received yet.</div>
</div>
</div>
<div class="row">
<div class='col-12'>
<h5>Spectrum Plot</h5>
</div>
</div>
<div class="row">
<div class='col-12'>
<div id="fft_plot"></div>
</div>
</div>
</div>
</body>
</html>

Wyświetl plik

@ -119,6 +119,19 @@ def handle_telemetry(packet):
text_data = decode_text_message(packet)
if text_data['error'] == 'None':
flask_emit_event('text_update', data=text_data)
elif packet_type == WENET_PACKET_TYPES.ORIENTATION_TELEMETRY:
# Orientation data from the payload
orientation_data = orientation_telemetry_decoder(packet)
if orientation_data['error'] == 'None':
flask_emit_event('orientation_update', data=orientation_data)
elif packet_type == WENET_PACKET_TYPES.IMAGE_TELEMETRY:
# image data from the payload
image_data = image_telemetry_decoder(packet)
if image_data['error'] == 'None':
flask_emit_event('image_telem_update', data=image_data)
else:
# Discard any other packet type.
pass
@ -164,7 +177,7 @@ def udp_rx_thread():
udp_listener_running = True
while udp_listener_running:
try:
m = s.recvfrom(2048)
m = s.recvfrom(8192)
except socket.timeout:
m = None
@ -196,6 +209,10 @@ if __name__ == "__main__":
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=log_level)
logging.getLogger("werkzeug").setLevel(logging.ERROR)
logging.getLogger("socketio").setLevel(logging.ERROR)
logging.getLogger("engineio").setLevel(logging.ERROR)
logging.getLogger("geventwebsocket").setLevel(logging.ERROR)
t = Thread(target=udp_rx_thread)
t.start()

Wyświetl plik

@ -126,7 +126,7 @@ if [ "$RX_FLOW" = "IQ" ]; then
echo "Using Complex Samples."
rtl_sdr -s $SDR_RATE -f $RX_SSB_FREQ -g $GAIN - | \
./fsk_demod --cu8 -s --stats=100 2 $SDR_RATE $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1) | \
./fsk_demod --cu8 -s --stats=100 2 $SDR_RATE $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1 --freq $RX_SSB_FREQ --samplerate $SDR_RATE) | \
./drs232_ldpc - - -vv 2> /dev/null | \
python rx_ssdv.py --partialupdate 16 --headless
elif [ "$RX_FLOW" = "GQRX" ]; then
@ -136,7 +136,7 @@ elif [ "$RX_FLOW" = "GQRX" ]; then
# Might need to try: nc -l -u -p 7355 localhost
echo "Receiving samples from GQRX on UDP:localhost:7355"
nc -l -u localhost 7355 | \
./fsk_demod -s --stats=100 -b 1 -u 23500 2 48000 $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1) | \
./fsk_demod -s --stats=100 -b 1 -u 23500 2 48000 $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1 --freq $RX_SSB_FREQ --samplerate $SDR_RATE --real) | \
./drs232_ldpc - - -vv 2> /dev/null | \
python rx_ssdv.py --partialupdate 4 --headless
else
@ -147,7 +147,7 @@ else
rtl_sdr -s $SDR_RATE -f $RX_SSB_FREQ -g $GAIN - | csdr convert_u8_f | \
csdr bandpass_fir_fft_cc 0.05 0.45 0.05 | csdr realpart_cf | \
csdr gain_ff 0.5 | csdr convert_f_s16 | \
./fsk_demod -s --stats=100 2 $SDR_RATE $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1) | \
./fsk_demod -s --stats=100 2 $SDR_RATE $BAUD_RATE - - 2> >(python fskstatsudp.py --rate 1 --freq $RX_SSB_FREQ --samplerate $SDR_RATE --real) | \
./drs232_ldpc - - -vv 2> /dev/null | \
python rx_ssdv.py --partialupdate 16 --headless