#!/usr/bin/env python2.7 # # Wenet IMU Data Viewer # # Copyright (C) 2018 Mark Jessop # Released under GNU GPL v3 or later # # NOTE: This is somewhat deprecated as we no longer fly IMUs on Wenet flights. # It may be re-visited in the future if/when required. # import argparse import traceback import socket import json import sys import datetime from WenetPackets import * import numpy as np from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg import pyqtgraph.opengl as gl from threading import Thread try: # Python 2 from Queue import Queue except ImportError: # Python 3 from queue import Queue parser = argparse.ArgumentParser() parser.add_argument("--imu", action="store_true", help="Show IMU panels.") parser.add_argument("--callsign", type=str, default="N0CALL", help="User callsign for Image Telemetry uploads.") parser.add_argument("-v", "--verbose", action='store_true', default=False, help="Verbose output") args = parser.parse_args() # Various GUI Settings imu_plot_history_size = 60 # Seconds. user_callsign = args.callsign app = QtGui.QApplication([]) # Configure PyQtGraph to use black plots on a transparent background. pg.setConfigOption('background',(0,0,0,0)) pg.setConfigOption('foreground', 'k') # # GPS Telemetry Data Frame # gpsFrame = QtGui.QFrame() gpsFrame.setFixedSize(250,250) gpsFrame.setFrameStyle(QtGui.QFrame.Box) gpsFrame.setLineWidth(2) gpsFrameTitle = QtGui.QLabel("GPS Data") gpsTime = QtGui.QLabel("Time:") gpsTimeValue = QtGui.QLabel("00:00:00") gpsLatitude = QtGui.QLabel("Latitude:") gpsLatitudeValue = QtGui.QLabel("-00.00000") gpsLongitude = QtGui.QLabel("Longitude:") gpsLongitudeValue = QtGui.QLabel("000.00000") gpsAltitude = QtGui.QLabel("Altitude:") gpsAltitudeValue = QtGui.QLabel("00000m") gpsSpeed = QtGui.QLabel("Speed") gpsSpeedValue = QtGui.QLabel("H 000kph V 0.0m/s") gpsHeading = QtGui.QLabel("Heading:") gpsHeadingValue = QtGui.QLabel("-") gpsSats = QtGui.QLabel("Satellites:") gpsSatsValue = QtGui.QLabel("0") gpsFixValid = QtGui.QLabel("Fix Status:") gpsFixValidValue = QtGui.QLabel("-") gpsFrameLayout = QtGui.QGridLayout() gpsFrameLayout.addWidget(gpsFrameTitle,0,0,1,2) gpsFrameLayout.addWidget(gpsTime,1,0) gpsFrameLayout.addWidget(gpsTimeValue,1,1) gpsFrameLayout.addWidget(gpsLatitude,2,0) gpsFrameLayout.addWidget(gpsLatitudeValue,2,1) gpsFrameLayout.addWidget(gpsLongitude,3,0) gpsFrameLayout.addWidget(gpsLongitudeValue,3,1) gpsFrameLayout.addWidget(gpsAltitude,4,0) gpsFrameLayout.addWidget(gpsAltitudeValue,4,1) gpsFrameLayout.addWidget(gpsSpeed,5,0) gpsFrameLayout.addWidget(gpsSpeedValue,5,1) gpsFrameLayout.addWidget(gpsHeading,6,0) gpsFrameLayout.addWidget(gpsHeadingValue,6,1) gpsFrameLayout.addWidget(gpsSats,7,0) gpsFrameLayout.addWidget(gpsSatsValue,7,1) gpsFrameLayout.addWidget(gpsFixValid,8,0) gpsFrameLayout.addWidget(gpsFixValidValue,8,1) gpsFrame.setLayout(gpsFrameLayout) def updateGpsFrame(gps_data): """ Update GPS Data Frame with a WenetPackets GPS Data dictionary """ try: gpsTimeValue.setText(gps_data['timestamp']) gpsLatitudeValue.setText("%.5f" % gps_data['latitude']) gpsLongitudeValue.setText("%.5f" % gps_data['longitude']) gpsAltitudeValue.setText("%d m" % int(gps_data['altitude'])) gpsSpeedValue.setText("H %d kph V %.1f m/s" % ( int(gps_data['ground_speed']), gps_data['ascent_rate'])) gpsHeadingValue.setText("%.1f deg" % gps_data['heading']) gpsSatsValue.setText("%d" % gps_data['numSV']) gpsFixValidValue.setText(gps_data['gpsFix_str']) except: traceback.print_exc() pass # # IMU Telemetry Data Frame # imuFrame = QtGui.QFrame() imuFrame.setFixedSize(250,250) imuFrame.setFrameStyle(QtGui.QFrame.Box) imuFrame.setLineWidth(2) imuFrameTitle = QtGui.QLabel("IMU Data") imuTime = QtGui.QLabel("Time:") imuTimeValue = QtGui.QLabel("00:00:00") imuTemp = QtGui.QLabel("Temp:") imuTempValue = QtGui.QLabel("0 deg C") imuCalStatus = QtGui.QLabel("Calibration Status") imuCalStatus_System = QtGui.QLabel("System") imuCalStatus_System_OK = QtGui.QLabel("?") imuCalStatus_Gyro = QtGui.QLabel("Gyro") imuCalStatus_Gyro_OK = QtGui.QLabel("?") imuCalStatus_Accel = QtGui.QLabel("Accel") imuCalStatus_Accel_OK =QtGui.QLabel("?") imuCalStatus_Magnet = QtGui.QLabel("Mag") imuCalStatus_Magnet_OK = QtGui.QLabel("?") imuEulerLabel = QtGui.QLabel("Euler Angles") imuEulerHeadingLabel = QtGui.QLabel("Heading") imuEulerHeadingValue = QtGui.QLabel("0 deg") imuEulerRollLabel = QtGui.QLabel("Roll") imuEulerRollValue = QtGui.QLabel("0 deg") imuEulerPitchLabel = QtGui.QLabel("Pitch") imuEulerPitchValue = QtGui.QLabel("0 deg") imuFrameLayout = QtGui.QGridLayout() imuFrameLayout.addWidget(imuFrameTitle,0,0,1,4) imuFrameLayout.addWidget(imuTime,1,0,1,1) imuFrameLayout.addWidget(imuTimeValue,1,2,1,3) imuFrameLayout.addWidget(imuTemp,2,0,1,1) imuFrameLayout.addWidget(imuTempValue,2,2,1,3) imuFrameLayout.addWidget(imuCalStatus,3,0,1,4) imuFrameLayout.addWidget(imuCalStatus_System,4,0,1,1) imuFrameLayout.addWidget(imuCalStatus_Gyro,4,1,1,1) imuFrameLayout.addWidget(imuCalStatus_Accel,4,2,1,1) imuFrameLayout.addWidget(imuCalStatus_Magnet,4,3,1,1) imuFrameLayout.addWidget(imuCalStatus_System_OK,5,0,1,1) imuFrameLayout.addWidget(imuCalStatus_Gyro_OK,5,1,1,1) imuFrameLayout.addWidget(imuCalStatus_Accel_OK,5,2,1,1) imuFrameLayout.addWidget(imuCalStatus_Magnet_OK,5,3,1,1) imuFrameLayout.addWidget(imuEulerLabel,6,0,1,4) imuFrameLayout.addWidget(imuEulerHeadingLabel,7,0,1,2) imuFrameLayout.addWidget(imuEulerHeadingValue,7,2,1,2) imuFrameLayout.addWidget(imuEulerRollLabel,8,0,1,2) imuFrameLayout.addWidget(imuEulerRollValue,8,2,1,2) imuFrameLayout.addWidget(imuEulerPitchLabel,9,0,1,2) imuFrameLayout.addWidget(imuEulerPitchValue,9,2,1,2) imuFrame.setLayout(imuFrameLayout) # # IMU Plots. # buffer_size = 60 heading_values =[0] roll_values = [0] pitch_values = [0] imu_times = [0] imuPlot = pg.GraphicsLayoutWidget() imuPlot_Heading = imuPlot.addPlot(title='Heading') imuPlot_Roll = imuPlot.addPlot(title='Roll') imuPlot_Pitch = imuPlot.addPlot(title='Pitch') imuPlotLayout = imuPlot.ci.layout imuPlotLayout.rowMaximumHeight(220) # Configure Plots imuPlot_Heading.setXRange(-1*imu_plot_history_size,0) imuPlot_Heading.setYRange(0,360) imuPlot_Roll.setXRange(-1*imu_plot_history_size,0) imuPlot_Roll.setYRange(-180,180) imuPlot_Pitch.setXRange(-1*imu_plot_history_size,0) imuPlot_Pitch.setYRange(-180,180) # Get curve objects so we can update them with new data. heading_curve = imuPlot_Heading.plot(x=imu_times, y=heading_values, pen=pg.mkPen('k',width=2)) roll_curve = imuPlot_Roll.plot(x=imu_times, y=roll_values, pen=pg.mkPen('k',width=2)) pitch_curve = imuPlot_Pitch.plot(x=imu_times, y=pitch_values, pen=pg.mkPen('k',width=2)) def updateIMUFrame(imu_data): """ Update IMU Frame and Plots with new IMU data """ global imu_times, heading_values, roll_values, pitch_values, heading_curve, roll_curve, pitch_curve try: imuTimeValue.setText(imu_data['timestamp']) imuTempValue.setText("%d" % imu_data['temp']) imuCalStatus_System_OK.setText("%d" % imu_data['sys_cal']) imuCalStatus_Gyro_OK.setText("%d" % imu_data['gyro_cal']) imuCalStatus_Accel_OK.setText("%d" % imu_data['accel_cal']) imuCalStatus_Magnet_OK.setText("%d" % imu_data['magnet_cal']) imuEulerHeadingValue.setText("%.1f" % imu_data['euler_heading']) imuEulerRollValue.setText("%.1f" % imu_data['euler_roll']) imuEulerPitchValue.setText("%.1f" % imu_data['euler_pitch']) latest_imu_time = imu_data['iTOW'] latest_euler_heading = imu_data['euler_heading'] latest_euler_roll = imu_data['euler_roll'] latest_euler_pitch = imu_data['euler_pitch'] if imu_times[0] == 0: # We need to initialise the arrays. imu_times[0] = latest_imu_time heading_values[0] = latest_euler_heading roll_values[0] = latest_euler_roll pitch_values[0] = latest_euler_pitch else: # Append, and crop arrays if they are too big. imu_times.append(latest_imu_time) heading_values.append(latest_euler_heading) roll_values.append(latest_euler_roll) pitch_values.append(latest_euler_pitch) if len(imu_times) > buffer_size: imu_times = imu_times[-1*buffer_size:] heading_values = heading_values[-1*buffer_size:] roll_values = roll_values[-1*buffer_size:] pitch_values = pitch_values[-1*buffer_size:] # Update plots heading_curve.setData(x=(np.array(imu_times)-latest_imu_time), y=heading_values) roll_curve.setData(x=(np.array(imu_times)-latest_imu_time), y = roll_values) pitch_curve.setData(x=(np.array(imu_times)-latest_imu_time), y=pitch_values) except: traceback.print_exc() pass # Telemetry Log packetSnifferFrame = QtGui.QFrame() packetSnifferFrame.setFixedSize(1200,150) packetSnifferFrame.setFrameStyle(QtGui.QFrame.Box) packetSnifferTitle = QtGui.QLabel("Telemetry Log") console = QtGui.QPlainTextEdit() console.setReadOnly(True) packetSnifferLayout = QtGui.QGridLayout() packetSnifferLayout.addWidget(packetSnifferTitle) packetSnifferLayout.addWidget(console) packetSnifferFrame.setLayout(packetSnifferLayout) # Habitat Upload Frame uploadFrame = QtGui.QFrame() uploadFrame.setFixedSize(200,150) uploadFrame.setFrameStyle(QtGui.QFrame.Box) uploadFrame.setLineWidth(1) uploadFrameTitle = QtGui.QLabel("Habitat Upload") uploadFrameHabitat = QtGui.QCheckBox("Habitat Upload") uploadFrameHabitat.setChecked(True) uploadFrameCallsignLabel = QtGui.QLabel("Your Callsign:") uploadFrameCallsign = QtGui.QLineEdit(user_callsign) uploadFrameCallsign.setMaxLength(10) uploadFrameHabitatStatus = QtGui.QLabel("Last Upload: ") uploadFrameLayout = QtGui.QGridLayout() uploadFrameLayout.addWidget(uploadFrameTitle,0,0,1,1) uploadFrameLayout.addWidget(uploadFrameCallsignLabel,1,0,1,1) uploadFrameLayout.addWidget(uploadFrameCallsign,1,1,1,1) uploadFrameLayout.addWidget(uploadFrameHabitat,2,0,1,2) uploadFrameLayout.addWidget(uploadFrameHabitatStatus,3,0,1,2) uploadFrame.setLayout(uploadFrameLayout) def imageTelemetryHandler(packet): (upload_ok, error) = image_telemetry_upload(packet, user_callsign = str(uploadFrameCallsign.text())) timestamp = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ") if upload_ok: uploadFrameHabitatStatus.setText("Last Upload: %s" % datetime.datetime.utcnow().strftime("%H:%M:%S")) console.appendPlainText("%s \tHabitat Upload: OK") else: uploadFrameHabitatStatus.setText("Last Upload: Failed!") console.appendPlainText("%s \tHabitat Upload: FAIL: %s" % (timestamp, error)) # Main Window main_widget = QtGui.QWidget() layout = QtGui.QGridLayout() main_widget.setLayout(layout) if args.imu: layout.addWidget(gpsFrame,0,0) layout.addWidget(imuFrame,0,1) layout.addWidget(imuPlot,0,2,1,2) layout.addWidget(packetSnifferFrame,1,0,1,3) layout.addWidget(uploadFrame,1,3,1,1) else: layout.addWidget(gpsFrame,0,0) packetSnifferFrame.setFixedSize(800,250) layout.addWidget(packetSnifferFrame,0,1) mainwin = QtGui.QMainWindow() mainwin.setWindowTitle("Wenet Payload Telemetry Console") mainwin.setCentralWidget(main_widget) if args.imu: mainwin.resize(1300,400) else: mainwin.resize(1050,250) mainwin.show() # # UDP Packet Handling Functions. # rxqueue = Queue(32) def process_udp(udp_packet): """ Process received UDP packets. """ # Grab timestamp. timestamp = datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%SZ") # Parse JSON, and extract packet. packet_dict = json.loads(udp_packet.decode('ascii')) # Discard all but Wenet packets if packet_dict['type'] == 'WENET': packet = packet_dict['packet'] else: return # 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 = gps_telemetry_decoder(packet) updateGpsFrame(gps_data) elif packet_type == WENET_PACKET_TYPES.ORIENTATION_TELEMETRY: orientation_data = orientation_telemetry_decoder(packet) updateIMUFrame(orientation_data) elif packet_type == WENET_PACKET_TYPES.IMAGE_TELEMETRY: # Print to console, then attempt to upload packet. console.appendPlainText("%s \t%s" % (timestamp,packet_to_string(packet))) imageTelemetryHandler(packet) else: # Convert to string, and print to terminal with timestamp. console.appendPlainText("%s \t%s" % (timestamp,packet_to_string(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_TELEMETRY_UDP_PORT)) print("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: rxqueue.put_nowait(m[0]) except: traceback.print_exc() pass print("Closing UDP Listener") s.close() t = Thread(target=udp_rx_thread) t.start() def read_queue(): try: if rxqueue.qsize()>0: packet = rxqueue.get_nowait() process_udp(packet) except: traceback.print_exc() pass # Start a timer to attempt to read a packet from the queue every 100ms. timer = QtCore.QTimer() timer.timeout.connect(read_queue) timer.start(100) ## Start Qt event loop unless running in interactive mode. if __name__ == '__main__': import sys if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_() udp_listener_running = False