From af5a1011650fb6d437a851e6879d911f69469183 Mon Sep 17 00:00:00 2001 From: Mark Jessop Date: Fri, 28 Jul 2023 19:58:42 +0930 Subject: [PATCH] Add sondehub-amateur upload for GPS telemetry --- Dockerfile | 3 +- rx/WenetPackets.py | 3 +- rx/requirements.txt | 7 +++++ rx/rx_ssdv.py | 9 +++--- rx/start_rx_docker.sh | 2 +- rx/wenetserver.py | 70 +++++++++++++++++++++++++++++++++++++++++-- start_rx_headless.sh | 2 +- tx/ublox.py | 2 +- 8 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 rx/requirements.txt diff --git a/Dockerfile b/Dockerfile index 1cbfd2f..1ece121 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,7 +45,8 @@ RUN --mount=type=cache,target=/root/.cache/pip pip3 install \ flask \ flask-socketio \ simple-websocket \ - requests + requests \ + sondehub # Copy in wenet. COPY . /root/wenet diff --git a/rx/WenetPackets.py b/rx/WenetPackets.py index f7f25af..e006c8a 100644 --- a/rx/WenetPackets.py +++ b/rx/WenetPackets.py @@ -19,8 +19,7 @@ from base64 import b64encode # Check if we are running in Python 2 or 3 PY3 = sys.version_info[0] == 3 - - +WENET_VERSION = "1.1.0" WENET_IMAGE_UDP_PORT = 7890 WENET_TELEMETRY_UDP_PORT = 55672 diff --git a/rx/requirements.txt b/rx/requirements.txt new file mode 100644 index 0000000..d293ce1 --- /dev/null +++ b/rx/requirements.txt @@ -0,0 +1,7 @@ +numpy +crcmod +flask +flask-socketio +simple-websocket +requests +sondehub \ No newline at end of file diff --git a/rx/rx_ssdv.py b/rx/rx_ssdv.py index 6d07c24..ffb1608 100644 --- a/rx/rx_ssdv.py +++ b/rx/rx_ssdv.py @@ -49,9 +49,10 @@ LOG_FILENAME = os.path.join(args.rximages,datetime.datetime.utcnow().strftime("% # GUI updates are only sent locally. -def trigger_gui_update(filename, text = "None"): +def trigger_gui_update(filename, text = "None", metadata = None): message = {'filename': filename, - 'text': text} + 'text': text, + 'metadata': metadata} gui_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) gui_socket.sendto(json.dumps(message).encode('ascii'),("127.0.0.1",WENET_IMAGE_UDP_PORT)) @@ -231,7 +232,7 @@ while True: 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_gui_update(os.path.abspath(_dessdv_filename+".jpg"), packet_as_string, packet_info) # Trigger upload to habhub here. else: @@ -258,7 +259,7 @@ while True: 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) + trigger_gui_update(os.path.abspath("rxtemp.jpg"), packet_as_string, packet_info) else: logging.debug("Unknown Packet Format.") diff --git a/rx/start_rx_docker.sh b/rx/start_rx_docker.sh index a1b4983..9f5d8ba 100755 --- a/rx/start_rx_docker.sh +++ b/rx/start_rx_docker.sh @@ -29,7 +29,7 @@ python3 ssdvuploader.py "$MYCALL" & SSDV_UPLOAD_PID=$! # Start the Web Interface Server -python3 wenetserver.py & +python3 wenetserver.py "$MYCALL" & WEB_VIEWER_PID=$! # Calculate the SDR sample rate required. diff --git a/rx/wenetserver.py b/rx/wenetserver.py index 63ae916..8caedff 100644 --- a/rx/wenetserver.py +++ b/rx/wenetserver.py @@ -27,6 +27,8 @@ from io import BytesIO from WenetPackets import * +from sondehub.amateur import Uploader + # Define Flask Application, and allow automatic reloading of templates for dev app = flask.Flask(__name__) app.config['SECRET_KEY'] = 'secret!' @@ -36,12 +38,20 @@ app.jinja_env.auto_reload = True # SocketIO instance socketio = SocketIO(app) +# PySondeHub Uploader, instantiated later. +sondehub = None # Latest Image latest_image = None latest_image_lock = Lock() +# Data we need for uploading telemetry to SondeHub-Amateur +my_callsign = "N0CALL" +current_callsign = None +current_modem_stats = None + + # # Flask Routes # @@ -69,14 +79,12 @@ def serve_latest_image(): as_attachment=False) - def flask_emit_event(event_name="none", data={}): """ Emit a socketio event to any clients. """ socketio.emit(event_name, data, namespace='/update_status') # SocketIO Handlers - @socketio.on('client_connected', namespace='/update_status') def update_client_display(data): pass @@ -102,6 +110,47 @@ def update_image(filename, description): logging.error("Error loading new image %s - %s" % (filename, str(e))) + +def handle_gps_telemetry(gps_data): + global current_callsign, current_modem_stats + + if current_callsign is None: + # No callsign yet, can't do anything with the GPS data + return + + if current_modem_stats is None: + # No modem stats, don't want to upload without that info. + return + + # Only upload telemetry if we have GPS lock. + if gps_data['gpsFix'] != 3: + logging.debug("No GPS lock - discarding GPS telemetry.") + return + + + if sondehub: + # Add to the SondeHub-Amateur uploader! + sondehub.add_telemetry( + current_callsign + "-Wenet", + gps_data['timestamp'] + "Z", + round(gps_data['latitude'],6), + round(gps_data['longitude'],6), + round(gps_data['altitude'],1), + sats = gps_data['numSV'], + heading = round(gps_data['heading'],1), + extra_fields = { + 'ascent_rate': round(gps_data['ascent_rate'],1), + 'speed': round(gps_data['ground_speed'],1) + }, + modulation = "Wenet", + frequency = round(current_modem_stats['fcentre']/1e6, 5), + snr = round(current_modem_stats['snr'],1) + ) + + # TODO - Emit as a Horus UDP Payload Summary packet. + + + def handle_telemetry(packet): """ Handle GPS and Text message packets from the wenet receiver """ @@ -114,6 +163,8 @@ def handle_telemetry(packet): if gps_data['error'] == 'None': flask_emit_event('gps_update', data=gps_data) + handle_gps_telemetry(gps_data) + elif packet_type == WENET_PACKET_TYPES.TEXT_MESSAGE: # A text message from the payload. text_data = decode_text_message(packet) @@ -138,6 +189,7 @@ def handle_telemetry(packet): def process_udp(packet): + global current_callsign, current_modem_stats packet_dict = json.loads(packet.decode('ascii')) @@ -145,6 +197,11 @@ def process_udp(packet): # New image to load update_image(packet_dict['filename'], packet_dict['text']) + new_callsign = packet_dict['metadata']['callsign'] + if current_callsign != new_callsign: + logging.info(f"Received new payload callsign data: {new_callsign}") + current_callsign = new_callsign + elif 'uploader_status' in packet_dict: # Information from the uploader process. flask_emit_event('uploader_update', data=packet_dict) @@ -152,6 +209,7 @@ def process_udp(packet): elif 'snr' in packet_dict: # Modem statistics packet flask_emit_event('modem_stats', data=packet_dict) + current_modem_stats = packet_dict elif 'type' in packet_dict: # Generic telemetry packet from the wenet RX. @@ -197,8 +255,10 @@ if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() + parser.add_argument("callsign", help="SondeHub-Amateur Uploader Callsign") parser.add_argument("-l", "--listen_port",default=5003,help="Port to run Web Server on. (Default: 5003)") parser.add_argument("-v", "--verbose", action='store_true', help="Enable debug output.") + parser.add_argument("--no_sondehub", action='store_true', help="Disable SondeHub-Amateur position upload.") args = parser.parse_args() @@ -209,6 +269,12 @@ if __name__ == "__main__": logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=log_level) + my_callsign = args.callsign + + # Instantiate the SondeHub-Amateur Uploader + if not args.no_sondehub: + sondehub = Uploader(my_callsign, software_name="pysondehub-wenet", software_version=WENET_VERSION) + logging.getLogger("werkzeug").setLevel(logging.ERROR) logging.getLogger("socketio").setLevel(logging.ERROR) logging.getLogger("engineio").setLevel(logging.ERROR) diff --git a/start_rx_headless.sh b/start_rx_headless.sh index 6c527a3..86d2b6b 100755 --- a/start_rx_headless.sh +++ b/start_rx_headless.sh @@ -94,7 +94,7 @@ python ssdvuploader.py $MYCALL & SSDV_UPLOAD_PID=$! # Start the Web Interface Server -python wenetserver.py & +python wenetserver.py $MYCALL & WEB_VIEWER_PID=$! diff --git a/tx/ublox.py b/tx/ublox.py index f9a946b..69b376e 100644 --- a/tx/ublox.py +++ b/tx/ublox.py @@ -1139,7 +1139,7 @@ class UBloxGPS(object): msg_name = msg.name() #print(msg_name) except Exception as e: - self.debug_message("WARNING: GPS Failure. Attempting to reconnect.") + self.debug_message("WARNING: GPS Failure - " + str(e)) self.write_state('numSV',0) # Attempt to re-open GPS. time.sleep(5)