#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on 17/10/2019 Tom Mladenov, European Space Agency """ __author__ = 'Tom Mladenov, European Space Operations Centre ESA/ESOC' __email__ = 'Tom.Mladenov@esa.int' import os import struct import zmq import sys import time from threading import Thread, Lock from PyQt5.QtCore import (QCoreApplication, QObject, QRunnable, QThread, pyqtSignal, QEvent, Qt, QVariant) from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog, QWidget, QListWidgetItem, QFileDialog, QTableWidgetItem from PyQt5.uic import loadUi from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5.QtGui import QImage, QIcon, QPixmap, QFont, QSyntaxHighlighter, QTextCharFormat, QColor, QBrush import numpy as np import argparse import datetime import collections from crccheck.crc import Crc32c import logging NODES = { "1": "Nanomind", "2": "EPS dock", "3": "EPS ACU1", "4": "EPS ACU2", "5": "Nanocom", "6": "EPS PDU1", "7": "EPS PDU2" } class Main(QMainWindow): def __init__(self, parent=None): super(Main, self).__init__(parent) eventLogger.info('Starting OPS-SAT UHF Desktop') gui = path + '/gui/gui.ui' eventLogger.info('Loading GUI file {GUI}'.format(GUI=gui)) loadUi(gui, self) self.setWindowTitle('OPS-SAT Telemetry Desktop') self.setFixedSize(self.size()) self.logo.setScaledContents(True) ops_sat_logo = QPixmap(path + '/gui/img/ESA_OPS_SAT.png') self.logo.setPixmap(ops_sat_logo) self.clear_tm_button.clicked.connect(self.clearTM) self.packet_history_table.setColumnCount(10) self.packet_history_table.setHorizontalHeaderLabels(['RX Time (UTC)', 'Type','Length', 'CRC OK', 'pri', 'src', 'dst', 'dst port', 'src port', 'flags']) packet_history_header = self.packet_history_table.horizontalHeader() packet_history_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(4, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(5, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(6, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(7, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(8, QtWidgets.QHeaderView.ResizeToContents) packet_history_header.setSectionResizeMode(9, QtWidgets.QHeaderView.ResizeToContents) self.raw_history_table.setColumnCount(3) self.raw_history_table.setHorizontalHeaderLabels(['RX Time (UTC)', 'RAW data', 'Bytes']) raw_history_table_header = self.raw_history_table.horizontalHeader() raw_history_table_header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) raw_history_table_header.setSectionResizeMode(1, QtWidgets.QHeaderView.ResizeToContents) raw_history_table_header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) self.adapter = TMadapter(self, "127.0.0.1", 38211) self.adapter.link.connect(self.updateLink) self.adapter.packet.connect(self.update) self.adapter.packet_count.connect(self.updatePacketCounter) self.adapter.start() self.host_label.setText(self.adapter.host) self.packet_counter_label.setText('Received packets: 0') eventLogger.info('GUI Thread started') def clearTM(self): self.adapter.recvd_packets = 0 self.packet_history_table.clearContents() self.packet_history_table.setRowCount(0) self.raw_history_table.clearContents() self.raw_history_table.setRowCount(0) eventLogger.info('Telemetry counter has been reset to 0') def updatePacketCounter(self, count): self.packet_counter_label.setText('Received packets: ' + str(count)) def updateLink(self, up): if up: self.link_label.setText('TM FLOW') self.link_label.setStyleSheet('background-color: rgb(0, 255, 0)') else: self.link_label.setText('NO TM') self.link_label.setStyleSheet('background-color: rgb(255, 0, 0)') def update(self, csp, crc): try: csp_crc = csp.getCRC32C() #calculate the CRC of the CSP packet sent_crc = int.from_bytes(crc, byteorder='big') #Check what was attached as a CRC if csp_crc == sent_crc: #Compare crc_ok = True if csp.isBeacon(): temp_brd,\ temp_pa,\ last_rssi,\ last_rferr,\ tx_count,\ rx_count,\ tx_bytes,\ rx_bytes,\ active_conf,\ boot_count,\ boot_cause,\ last_contact,\ bgnd_rssi,\ tx_duty,\ tot_tx_count,\ tot_rx_count,\ tot_tx_bytes,\ tot_rx_bytes = csp.getBeaconContents() self.temp_brd_label.setText(str(temp_brd/10.0)) self.temp_pa_label.setText(str(temp_pa/10.0)) self.last_rssi_label.setText(str(last_rssi)) self.last_rferr_label.setText(str(last_rferr)) self.tx_count_label.setText(str(tx_count)) self.rx_count_label.setText(str(rx_count)) self.tx_bytes_label.setText(str(tx_bytes)) self.rx_bytes_label.setText(str(rx_bytes)) self.active_conf_label.setText(str(active_conf)) self.boot_count_label.setText(str(boot_count)) self.boot_cause_label.setText(str(boot_cause)) self.last_contact_label.setText(str(last_contact)) self.bgnd_rssi_label.setText(str(bgnd_rssi)) self.tx_duty_label.setText(str(tx_duty)) self.tot_tx_count_label.setText(str(tot_tx_count)) self.tot_rx_count_label.setText(str(tot_rx_count)) self.tot_tx_bytes_label.setText(str(tot_tx_bytes)) self.tot_rx_bytes_label.setText(str(tot_rx_bytes)) current_packet_count = self.packet_history_table.rowCount() self.packet_history_table.insertRow(current_packet_count) self.packet_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.packet_history_table.setItem(current_packet_count, 1, QTableWidgetItem('Beacon')) self.packet_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.packet_history_table.setItem(current_packet_count, 3, QTableWidgetItem(str(crc_ok))) self.packet_history_table.setItem(current_packet_count, 4, QTableWidgetItem(str(csp.priority))) self.packet_history_table.setItem(current_packet_count, 5, QTableWidgetItem(NODES[str(csp.source)])) self.packet_history_table.setItem(current_packet_count, 6, QTableWidgetItem(str(csp.destination))) self.packet_history_table.setItem(current_packet_count, 7, QTableWidgetItem(str(csp.dest_port))) self.packet_history_table.setItem(current_packet_count, 8, QTableWidgetItem(str(csp.source_port))) self.packet_history_table.setItem(current_packet_count, 9, QTableWidgetItem(str(csp.flags))) self.packet_history_table.item(current_packet_count, 3).setBackground(QtGui.QColor(0, 255, 0)) self.packet_history_table.scrollToBottom() self.raw_history_table.insertRow(current_packet_count) self.raw_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.raw_history_table.setItem(current_packet_count, 1, QTableWidgetItem(str(csp.getHex()))) self.raw_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.raw_history_table.scrollToBottom() parsed_data = '{iso_time},{temp_brd},{temp_pa},{last_rssi},{last_rferr},{tx_count},{rx_count},{tx_bytes},{rx_bytes},{active_conf},{boot_count},{boot_cause},{last_contact},{bgnd_rssi},{tx_duty},{tot_tx_count},{tot_rx_count},{tot_tx_bytes},{tot_rx_bytes}'.format(iso_time=datetime.datetime.utcnow().isoformat(),\ temp_brd=temp_brd,\ temp_pa=temp_pa,\ last_rssi=last_rssi,\ last_rferr=last_rferr,\ tx_count=tx_count,\ rx_count=rx_count,\ tx_bytes=tx_bytes,\ rx_bytes=rx_bytes,\ active_conf=active_conf,\ boot_count=boot_count,\ boot_cause=boot_cause,\ last_contact=last_contact,\ bgnd_rssi=bgnd_rssi,\ tx_duty=tx_duty,\ tot_tx_count=tot_tx_count,\ tot_rx_count=tot_rx_count,\ tot_tx_bytes=tot_tx_bytes,\ tot_rx_bytes=tot_rx_bytes) eventLogger.info('Received CSP beacon packet of length {LEN} bytes + 4 byte CRC32C check: OK'.format(LEN=csp.getLength())) raw_data = str(csp.getHex()) + str(crc.hex()) rawLogger.info('{iso_time},{data}'.format(iso_time=datetime.datetime.utcnow().isoformat(), data=raw_data)) parsedBeaconLogger.info(parsed_data) else: current_packet_count = self.packet_history_table.rowCount() self.packet_history_table.insertRow(current_packet_count) self.packet_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.packet_history_table.setItem(current_packet_count, 1, QTableWidgetItem('SPP over CSP')) self.packet_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.packet_history_table.setItem(current_packet_count, 3, QTableWidgetItem(str(crc_ok))) self.packet_history_table.setItem(current_packet_count, 4, QTableWidgetItem(str(csp.priority))) self.packet_history_table.setItem(current_packet_count, 5, QTableWidgetItem(NODES[str(csp.source)])) self.packet_history_table.setItem(current_packet_count, 6, QTableWidgetItem(str(csp.destination))) self.packet_history_table.setItem(current_packet_count, 7, QTableWidgetItem(str(csp.dest_port))) self.packet_history_table.setItem(current_packet_count, 8, QTableWidgetItem(str(csp.source_port))) self.packet_history_table.setItem(current_packet_count, 9, QTableWidgetItem(str(csp.flags))) self.packet_history_table.item(current_packet_count, 3).setBackground(QtGui.QColor(0, 255, 0)) self.packet_history_table.scrollToBottom() self.raw_history_table.insertRow(current_packet_count) self.raw_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.raw_history_table.setItem(current_packet_count, 1, QTableWidgetItem(str(csp.getHex()))) self.raw_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.raw_history_table.scrollToBottom() eventLogger.info('Received SPP over CSP packet of length {LEN} bytes + 4 byte CRC32C check: OK'.format(LEN=csp.getLength())) data = str(csp.getHex()) + str(crc.hex()) rawLogger.info('{iso_time},{data}'.format(iso_time=datetime.datetime.utcnow().isoformat(), data=data)) else: crc_ok = False current_packet_count = self.packet_history_table.rowCount() self.packet_history_table.insertRow(current_packet_count) self.packet_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.packet_history_table.setItem(current_packet_count, 1, QTableWidgetItem('--')) self.packet_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.packet_history_table.setItem(current_packet_count, 3, QTableWidgetItem(str(crc_ok))) self.packet_history_table.setItem(current_packet_count, 4, QTableWidgetItem(str(csp.priority))) self.packet_history_table.setItem(current_packet_count, 5, QTableWidgetItem(str(csp.source))) self.packet_history_table.setItem(current_packet_count, 6, QTableWidgetItem(str(csp.destination))) self.packet_history_table.setItem(current_packet_count, 7, QTableWidgetItem(str(csp.dest_port))) self.packet_history_table.setItem(current_packet_count, 8, QTableWidgetItem(str(csp.source_port))) self.packet_history_table.setItem(current_packet_count, 9, QTableWidgetItem(str(csp.flags))) self.packet_history_table.item(current_packet_count, 3).setBackground(QtGui.QColor(255, 0, 0)) self.packet_history_table.scrollToBottom() self.raw_history_table.insertRow(current_packet_count) self.raw_history_table.setItem(current_packet_count, 0, QTableWidgetItem(str(datetime.datetime.utcnow()))) self.raw_history_table.setItem(current_packet_count, 1, QTableWidgetItem(str(csp.getHex()))) self.raw_history_table.setItem(current_packet_count, 2, QTableWidgetItem(str(csp.getLength()))) self.raw_history_table.scrollToBottom() eventLogger.error('Received CSP packet of length {LEN} bytes - Failed CRC32C!'.format(LEN=str(csp.getLength()))) #Don't write to any logs. except Exception as e: eventLogger.error('Error occured: {ERR}'.format(ERR=e)) class CSP(object): """ Reused from: https://github.com/daniestevez/gr-satellites/blob/master/python/csp_header.py """ def __init__(self, csp_bytes): if len(csp_bytes) < 4: raise ValueError("Malformed CSP packet (too short)") self.csp_bytes = csp_bytes csp = struct.unpack(">I", csp_bytes[0:4])[0] self.priority = (csp >> 30) & 0x3 self.source = (csp >> 25) & 0x1f self.destination = (csp >> 20) & 0x1f self.dest_port = (csp >> 14) & 0x3f self.source_port = (csp >> 8) & 0x3f self.reserved = (csp >> 4) & 0xf self.hmac = (csp >> 3) & 1 self.xtea = (csp >> 2) & 1 self.rdp = (csp >> 1) & 1 self.crc = csp & 1 self.flags=int('{HMAC}{XTEA}{RDP}{CRC}'.format(HMAC=self.hmac, XTEA=self.xtea, RDP=self.rdp, CRC=self.crc)) def toString(self): return ("""CSP header: Priority:\t\t{} Source:\t\t\t{} Destination:\t\t{} Destination port:\t{} Source port:\t\t{} Reserved field:\t\t{} HMAC:\t\t\t{} XTEA:\t\t\t{} RDP:\t\t\t{} CRC:\t\t\t{}""".format( self.priority, self.source, self.destination, self.dest_port, self.source_port, self.reserved, self.hmac, self.xtea, self.rdp, self.crc)) def isBeacon(self): if self.priority == 3 and self.source == 5 and self.destination == 10 and self.dest_port == 31 and self.source_port==0 and self.getLength() == 58: return True else: return False def getCRC32C(self): calculated_CRC32C = Crc32c.calc(self.csp_bytes) return calculated_CRC32C def getLength(self): return len(self.csp_bytes) def getHex(self): return self.csp_bytes.hex() def getBeaconContents(self): if self.isBeacon(): payload = struct.unpack('>4h 4I B H 2I h B 4I', self.csp_bytes[4:]) #nanocom beacon format return payload class TMadapter(QThread): packet = pyqtSignal(object, object) link = pyqtSignal(bool) packet_count = pyqtSignal(int) recvd_packets = 0 active = False host = None def __init__(self, parent, ip, port): QtCore.QThread.__init__(self, parent) self.parent = parent self.active = True self.context = zmq.Context() self.socket = self.context.socket(zmq.SUB) self.host = 'tcp://' + ip + ':' + str(port) self.socket.connect(self.host) self.socket.setsockopt_string(zmq.SUBSCRIBE, "") self.socket.setsockopt(zmq.RCVTIMEO, 15000) #Timeout set to 15s, beacon frame is radiated every 10s by the spacecraft eventLogger.info('Started listening on {HOST} for incoming packets from GR flowgraph'.format(HOST=self.host)) def run(self): while self.active: try: data = self.socket.recv() crc = data[-4:] #4 last bytes are CRC32-C csp = CSP(data[:(len(data)-4)])#Everything but the last 4 bytes belongs to the CSP packet self.packet.emit(csp, crc) self.recvd_packets = self.recvd_packets + 1 self.packet_count.emit(self.recvd_packets) self.link.emit(True) except Exception as e: eventLogger.warning('No packets received in the last 15 seconds...{ERR}'.format(ERR=e)) self.link.emit(False) def setup_logger(name, log_file, formatter, level=logging.INFO): fileHandler = logging.FileHandler(log_file) streamHandler = logging.StreamHandler() fileHandler.setFormatter(formatter) streamHandler.setFormatter(formatter) logger = logging.getLogger(name) logger.setLevel(level) logger.addHandler(fileHandler) logger.addHandler(streamHandler) return logger if __name__ == '__main__': path = os.path.dirname(os.path.abspath(__file__)) formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') eventLogger = setup_logger('first_logger', path + '/log/gui_event.log', formatter, level=logging.INFO) plainFormatter = logging.Formatter('%(message)s') rawLogger = setup_logger('second_logger', path + '/log/raw.log', plainFormatter, level=logging.INFO) parsedBeaconLogger = setup_logger('third_logger', path + '/log/parsed_beacon.log', plainFormatter, level=logging.INFO) a = QApplication(sys.argv) a.setWindowIcon(QIcon(path + '/gui/img/1200px-ESA_logo_simple.png')) app = Main() app.show() a.exec_() os._exit(0)