kopia lustrzana https://github.com/projecthorus/wenet
Add spectra plot, orientation info to web GUI. Add logging of telemetry data
rodzic
c1a4224c11
commit
a6759c79a5
|
@ -11,6 +11,7 @@ RUN apt-get update && \
|
|||
git \
|
||||
libusb-1.0-0-dev \
|
||||
pkg-config \
|
||||
libatlas-base \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
222
rx/rx_ssdv.py
222
rx/rx_ssdv.py
|
@ -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))
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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}
|
|
@ -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;
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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')
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue