#!/usr/bin/env python # # Wenet Web GUI # # Copyright (C) 2019 Mark Jessop # Released under GNU GPL v3 or later # # A really hacky first attempt at a live-updating web interface that displays wenet imagery. # # Run this instead of rx_gui in the startup scripts, and then access at http://localhost:5003/ # # TODO: # [ ] Automatic re-scaling of images in web browser. # [ ] Add Display of GPS telemetry and text messages. # import json import logging import flask from flask_socketio import SocketIO import time import traceback import socket import sys import datetime from threading import Thread, Lock from io import BytesIO from WenetPackets import * # Define Flask Application, and allow automatic reloading of templates for dev app = flask.Flask(__name__) app.config['SECRET_KEY'] = 'secret!' app.config['TEMPLATES_AUTO_RELOAD'] = True app.jinja_env.auto_reload = True # SocketIO instance socketio = SocketIO(app) # Latest Image latest_image = None latest_image_lock = Lock() # # Flask Routes # @app.route("/") def flask_index(): """ Render main index page """ return flask.render_template('index.html') @app.route("/latest.jpg") def serve_latest_image(): global latest_image, latest_image_lock if latest_image == None: flask.abort(404) else: # Grab image bytes. latest_image_lock.acquire() _temp_image = bytes(latest_image) latest_image_lock.release() return flask.send_file( BytesIO(_temp_image), mimetype='image/jpeg', 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 def update_image(filename, description): global latest_image, latest_image_lock try: with open(filename, 'rb') as _new_image: _data = _new_image.read() latest_image_lock.acquire() latest_image = bytes(_data) latest_image_lock.release() # Trigger the clients to update. flask_emit_event('image_update', data={'text':description}) logging.debug("Loaded new image: %s" % filename) except Exception as e: logging.error("Error loading new image %s - %s" % (filename, str(e))) def handle_telemetry(packet): """ Handle GPS and Text message packets from the wenet receiver """ # Decode GPS and IMU packets, and pass onto their respective GUI update functions. packet_type = decode_packet_type(packet) if packet_type == WENET_PACKET_TYPES.GPS_TELEMETRY: # GPS data from the payload gps_data = gps_telemetry_decoder(packet) if gps_data['error'] == 'None': flask_emit_event('gps_update', data=gps_data) elif packet_type == WENET_PACKET_TYPES.TEXT_MESSAGE: # A text message from the payload. text_data = decode_text_message(packet) if text_data['error'] == 'None': flask_emit_event('text_update', data=text_data) else: # Discard any other packet type. pass def process_udp(packet): packet_dict = json.loads(packet.decode('ascii')) if 'filename' in packet_dict: # New image to load update_image(packet_dict['filename'], packet_dict['text']) elif 'uploader_status' in packet_dict: # Information from the uploader process. flask_emit_event('uploader_update', data=packet_dict) elif 'snr' in packet_dict: # Modem statistics packet flask_emit_event('modem_stats', data=packet_dict) elif 'type' in packet_dict: # Generic telemetry packet from the wenet RX. # This could be GPS telemetry, text data, or something else.. if packet_dict['type'] == 'WENET': handle_telemetry(packet_dict['packet']) udp_listener_running = False def udp_rx_thread(): """ Listen on a port for UDP broadcast packets, and pass them onto process_udp()""" global udp_listener_running s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) s.settimeout(1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) except: pass s.bind(('',WENET_IMAGE_UDP_PORT)) logging.info("Started UDP Listener Thread.") udp_listener_running = True while udp_listener_running: try: m = s.recvfrom(2048) except socket.timeout: m = None if m != None: try: process_udp(m[0]) except: traceback.print_exc() pass logging.info("Closing UDP Listener") s.close() if __name__ == "__main__": import argparse parser = argparse.ArgumentParser() 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.") args = parser.parse_args() if args.verbose: log_level = logging.DEBUG else: log_level = logging.ERROR logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=log_level) t = Thread(target=udp_rx_thread) t.start() # Run the Flask app, which will block until CTRL-C'd. socketio.run(app, host='0.0.0.0', port=args.listen_port) udp_listener_running = False