diff --git a/gps_tool/MainWindow.py b/gps_tool/MainWindow.py new file mode 100644 index 0000000..633ed75 --- /dev/null +++ b/gps_tool/MainWindow.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'mainwindow.ui' +## +## Created by: Qt User Interface Compiler version 5.15.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide2.QtCore import * +from PySide2.QtGui import * +from PySide2.QtWidgets import * + + +class Ui_MainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + MainWindow.resize(737, 359) + self.centralwidget = QWidget(MainWindow) + self.centralwidget.setObjectName(u"centralwidget") + self.label_2 = QLabel(self.centralwidget) + self.label_2.setObjectName(u"label_2") + self.label_2.setGeometry(QRect(10, 100, 61, 16)) + self.label = QLabel(self.centralwidget) + self.label.setObjectName(u"label") + self.label.setGeometry(QRect(10, 80, 61, 16)) + self.label_3 = QLabel(self.centralwidget) + self.label_3.setObjectName(u"label_3") + self.label_3.setGeometry(QRect(10, 160, 51, 16)) + self.txtLongitude = QLabel(self.centralwidget) + self.txtLongitude.setObjectName(u"txtLongitude") + self.txtLongitude.setGeometry(QRect(70, 100, 81, 18)) + self.txtLongitude.setFrameShape(QFrame.Panel) + self.txtLongitude.setFrameShadow(QFrame.Sunken) + self.txtSpeed = QLabel(self.centralwidget) + self.txtSpeed.setObjectName(u"txtSpeed") + self.txtSpeed.setGeometry(QRect(70, 160, 61, 18)) + self.txtSpeed.setFrameShape(QFrame.Panel) + self.txtSpeed.setFrameShadow(QFrame.Sunken) + self.txtLatitude = QLabel(self.centralwidget) + self.txtLatitude.setObjectName(u"txtLatitude") + self.txtLatitude.setGeometry(QRect(70, 80, 81, 18)) + self.txtLatitude.setFrameShape(QFrame.Panel) + self.txtLatitude.setFrameShadow(QFrame.Sunken) + self.label_4 = QLabel(self.centralwidget) + self.label_4.setObjectName(u"label_4") + self.label_4.setGeometry(QRect(10, 140, 61, 16)) + self.exitButton = QPushButton(self.centralwidget) + self.exitButton.setObjectName(u"exitButton") + self.exitButton.setGeometry(QRect(640, 300, 84, 32)) + self.txtHeading = QLabel(self.centralwidget) + self.txtHeading.setObjectName(u"txtHeading") + self.txtHeading.setGeometry(QRect(70, 140, 61, 18)) + self.txtHeading.setFrameShape(QFrame.Panel) + self.txtHeading.setFrameShadow(QFrame.Sunken) + self.txtTime = QLabel(self.centralwidget) + self.txtTime.setObjectName(u"txtTime") + self.txtTime.setGeometry(QRect(70, 60, 151, 20)) + self.txtTime.setFrameShape(QFrame.Panel) + self.txtTime.setFrameShadow(QFrame.Sunken) + self.label_7 = QLabel(self.centralwidget) + self.label_7.setObjectName(u"label_7") + self.label_7.setGeometry(QRect(10, 60, 51, 16)) + self.label_5 = QLabel(self.centralwidget) + self.label_5.setObjectName(u"label_5") + self.label_5.setGeometry(QRect(10, 120, 61, 16)) + self.label_8 = QLabel(self.centralwidget) + self.label_8.setObjectName(u"label_8") + self.label_8.setGeometry(QRect(10, 40, 51, 16)) + self.txtStatus = QLabel(self.centralwidget) + self.txtStatus.setObjectName(u"txtStatus") + self.txtStatus.setGeometry(QRect(70, 40, 61, 16)) + self.txtStatus.setFrameShape(QFrame.Panel) + self.txtStatus.setFrameShadow(QFrame.Sunken) + self.label_10 = QLabel(self.centralwidget) + self.label_10.setObjectName(u"label_10") + self.label_10.setGeometry(QRect(10, 180, 49, 20)) + self.label_11 = QLabel(self.centralwidget) + self.label_11.setObjectName(u"label_11") + self.label_11.setGeometry(QRect(10, 200, 49, 16)) + self.txtHDOP = QLabel(self.centralwidget) + self.txtHDOP.setObjectName(u"txtHDOP") + self.txtHDOP.setGeometry(QRect(70, 180, 61, 16)) + self.txtHDOP.setFrameShape(QFrame.Panel) + self.txtHDOP.setFrameShadow(QFrame.Sunken) + self.txtVDOP = QLabel(self.centralwidget) + self.txtVDOP.setObjectName(u"txtVDOP") + self.txtVDOP.setGeometry(QRect(70, 200, 61, 16)) + self.txtVDOP.setFrameShape(QFrame.Panel) + self.txtVDOP.setFrameShadow(QFrame.Sunken) + self.txtAltitude = QLabel(self.centralwidget) + self.txtAltitude.setObjectName(u"txtAltitude") + self.txtAltitude.setGeometry(QRect(70, 120, 61, 16)) + self.txtAltitude.setFrameShape(QFrame.Panel) + self.txtAltitude.setFrameShadow(QFrame.Sunken) + self.label_9 = QLabel(self.centralwidget) + self.label_9.setObjectName(u"label_9") + self.label_9.setGeometry(QRect(140, 120, 49, 16)) + self.label_13 = QLabel(self.centralwidget) + self.label_13.setObjectName(u"label_13") + self.label_13.setGeometry(QRect(140, 160, 49, 16)) + self.txtMode = QLabel(self.centralwidget) + self.txtMode.setObjectName(u"txtMode") + self.txtMode.setGeometry(QRect(130, 40, 49, 16)) + self.txtMode.setFrameShape(QFrame.Panel) + self.txtMode.setFrameShadow(QFrame.Sunken) + self.label_6 = QLabel(self.centralwidget) + self.label_6.setObjectName(u"label_6") + self.label_6.setGeometry(QRect(10, 220, 49, 16)) + self.txtGrid = QLabel(self.centralwidget) + self.txtGrid.setObjectName(u"txtGrid") + self.txtGrid.setGeometry(QRect(70, 220, 61, 16)) + self.txtGrid.setFrameShape(QFrame.Panel) + self.txtGrid.setFrameShadow(QFrame.Sunken) + self.txtSatellites = QTextEdit(self.centralwidget) + self.txtSatellites.setObjectName(u"txtSatellites") + self.txtSatellites.setGeometry(QRect(230, 10, 220, 280)) + font = QFont() + font.setFamily(u"Courier") + self.txtSatellites.setFont(font) + self.toggleLogging = QPushButton(self.centralwidget) + self.toggleLogging.setObjectName(u"toggleLogging") + self.toggleLogging.setGeometry(QRect(130, 250, 84, 32)) + self.label_12 = QLabel(self.centralwidget) + self.label_12.setObjectName(u"label_12") + self.label_12.setGeometry(QRect(10, 250, 111, 20)) + self.viewSatellites = QGraphicsView(self.centralwidget) + self.viewSatellites.setObjectName(u"viewSatellites") + self.viewSatellites.setGeometry(QRect(450, 10, 280, 280)) + self.label_14 = QLabel(self.centralwidget) + self.label_14.setObjectName(u"label_14") + self.label_14.setGeometry(QRect(10, 10, 221, 16)) + font1 = QFont() + font1.setPointSize(10) + font1.setBold(True) + font1.setWeight(75) + self.label_14.setFont(font1) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QMenuBar(MainWindow) + self.menubar.setObjectName(u"menubar") + self.menubar.setGeometry(QRect(0, 0, 737, 28)) + MainWindow.setMenuBar(self.menubar) + self.statusbar = QStatusBar(MainWindow) + self.statusbar.setObjectName(u"statusbar") + MainWindow.setStatusBar(self.statusbar) + + self.retranslateUi(MainWindow) + + QMetaObject.connectSlotsByName(MainWindow) + # setupUi + + def retranslateUi(self, MainWindow): + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"QtGPS", None)) + self.label_2.setText(QCoreApplication.translate("MainWindow", u"Longitude:", None)) + self.label.setText(QCoreApplication.translate("MainWindow", u"Latitude:", None)) + self.label_3.setText(QCoreApplication.translate("MainWindow", u"Speed:", None)) + self.txtLongitude.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtSpeed.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtLatitude.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.label_4.setText(QCoreApplication.translate("MainWindow", u"Heading:", None)) + self.exitButton.setText(QCoreApplication.translate("MainWindow", u"Exit", None)) + self.txtHeading.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtTime.setText("") + self.label_7.setText(QCoreApplication.translate("MainWindow", u"Time:", None)) + self.label_5.setText(QCoreApplication.translate("MainWindow", u"Altitude:", None)) + self.label_8.setText(QCoreApplication.translate("MainWindow", u"Fix:", None)) + self.txtStatus.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.label_10.setText(QCoreApplication.translate("MainWindow", u"HDOP:", None)) + self.label_11.setText(QCoreApplication.translate("MainWindow", u"VDOP:", None)) + self.txtHDOP.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtVDOP.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtAltitude.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.label_9.setText(QCoreApplication.translate("MainWindow", u"meters", None)) + self.label_13.setText(QCoreApplication.translate("MainWindow", u"m/sec", None)) + self.txtMode.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.label_6.setText(QCoreApplication.translate("MainWindow", u"Grid:", None)) + self.txtGrid.setText("") + self.toggleLogging.setText(QCoreApplication.translate("MainWindow", u"Start", None)) + self.label_12.setText(QCoreApplication.translate("MainWindow", u"GPX Data Logging:", None)) + self.label_14.setText(QCoreApplication.translate("MainWindow", u"GPS Tool (gpsd client)", None)) + # retranslateUi + diff --git a/gps_tool/Makefile b/gps_tool/Makefile new file mode 100644 index 0000000..f927421 --- /dev/null +++ b/gps_tool/Makefile @@ -0,0 +1,3 @@ + +MainWindow.py : mainwindow.ui + pyside2-uic $< -o $@ diff --git a/gps_tool/README.md b/gps_tool/README.md new file mode 100644 index 0000000..bb8a193 --- /dev/null +++ b/gps_tool/README.md @@ -0,0 +1,2 @@ +# QtGPS +Qt5 Version of xgps (from gpsd clients) diff --git a/gps_tool/gps_tool.py b/gps_tool/gps_tool.py new file mode 100755 index 0000000..c82ad67 --- /dev/null +++ b/gps_tool/gps_tool.py @@ -0,0 +1,303 @@ +#! /usr/bin/python3 + +import os +import sys +import yaml + +from math import radians +from datetime import datetime +from time import strftime, time + +from gps import * +import gpxpy + + +from PySide2.QtGui import * +from PySide2.QtWidgets import QApplication, QMainWindow, QGraphicsEllipseItem, QFileDialog, QGraphicsScene, QGraphicsProxyWidget +from PySide2.QtCore import QObject, Slot, Signal, QThread, QDir, QRect + +import matplotlib as mp +import matplotlib.pyplot as plot +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +from matplotlib.backends.qt_compat import QtCore, QtWidgets +from matplotlib import cm + +from MainWindow import Ui_MainWindow + +import maidenhead as mh + +############################################################################## +# log +############################################################################## + +#if not os.path.isdir("logs"): +# os.mkdir("logs") + +fix_status = [ + 'Normal', 'DGPS', 'RTK FixPt', 'RTK Float', 'DR Fix', 'GNSSDR', 'Surveyed', 'Simulated', 'P(Y) Fix' + ]; + +fix_mode = [ + '0=None', '1=No', '2=2D', '3=3D' + ]; + +gpsd_host="127.0.0.1" +gpsd_report = () + +############################################################################## +# thread +############################################################################## + +class DataSignal(QObject): + sig = Signal(int) + +class DataThread(QThread): + def __init__(self, parent=None): + super(DataThread, self).__init__(parent) + self.new_data = DataSignal() + + def run(self): + global gpsd_host, gpsd_report + gpsd = gps(host=gpsd_host, mode=WATCH_ENABLE|WATCH_NEWSTYLE) + + while not self.isInterruptionRequested(): + gpsd_report = gpsd.next() # wait for next packet + self.new_data.sig.emit(1) + + def stop(self): + self.requestInterruption() + self.wait() + +############################################################################## +# plot +############################################################################## + +############################################################################## +# main +############################################################################## + +class MainWindow(QMainWindow, Ui_MainWindow): + def __init__(self, *args, obj=None, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + self.setupUi(self) + +class QtGPSWindow(MainWindow): + def __init__(self, *args, obj=None, **kwargs): + super(QtGPSWindow, self).__init__(*args, **kwargs) + + self.exitButton.clicked.connect(self.exit_button) + self.toggleLogging.clicked.connect(self.toggle_logging) + + # GPS log + self.gpx = gpxpy.gpx.GPX() + # Create first track in our GPX: + self.gpx_track = gpxpy.gpx.GPXTrack() + self.gpx.tracks.append(self.gpx_track) + # Create first segment in our GPX track: + self.gpx_segment = gpxpy.gpx.GPXTrackSegment() + self.gpx_track.segments.append(self.gpx_segment) + + self.log_enabled = 0 + self.dirname = "" + self.num_points = 0 + + self.my_thread = DataThread() + + self.txtSatellites.setReadOnly(True) + self.black = QColor(0,0,0) + self.gray = QColor(128,128,128) + self.red = QColor(255,0,0) + self.orange = QColor(255,80,0) + self.yellow = QColor(255,211,0) + self.green = QColor(0,255,0) + self.green = QColor(0,0,255) + + self.scene = QtWidgets.QGraphicsScene() + self.viewSatellites.setScene(self.scene) + self.viewSatellites.scale(0.65, 0.65) + + @Slot(list) + def update_data(self, x): + global gpsd_report + report = gpsd_report + + if report['class'] == 'TPV': + status = getattr(report, 'status', 0) + mode = getattr(report, 'mode', 0) + time = getattr(report, 'time', 0) + lat = getattr(report, 'lat', 0.0) + lon = getattr(report, 'lon', 0.0) + elev = getattr(report, 'altHAE', 0.0) # meters + heading = getattr(report, 'track', 0.0) + speed = getattr(report, 'speed', 0.0) # m/sec + + self.txtStatus.setText(fix_status[status]) + self.txtMode.setText(fix_mode[mode]) + self.txtTime.setText(time) + self.txtLatitude.setText("%10.7f" %(lat)) + self.txtLongitude.setText("%10.7f" % (lon)) + self.txtAltitude.setText("%7.2f" % (elev)) + self.txtHeading.setText("%7.2f" % (heading)) + self.txtSpeed.setText("%7.2f" % (speed)) + + self.txtGrid.setText(mh.to_maiden(lat, lon)) + + if self.log_enabled: + point = gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=elev) + point.time = datetime.now() + self.gpx_segment.points.append(point) + self.num_points += 1 + + elif report['class'] == 'SKY': + self.txtHDOP.setText(str(getattr(report, 'hdop', 0.0))) + self.txtVDOP.setText(str(getattr(report, 'vdop', 0.0))) + satellites = getattr(report, 'satellites') + + txt = "%4s %4s %4s %3s %1s" % ('PRN', 'Azim', 'Elev', 'SNR', 'Used') + self.txtSatellites.setTextColor(self.black) + self.txtSatellites.setFontWeight(QFont.Bold) + self.txtSatellites.setText(txt) + + prns = [] + radius = [] + theta = [] + colors = [] + markers = [] + + for (i, satellite) in enumerate(satellites): + prn = satellite['PRN'] + azim = satellite['az'] + elev = satellite['el'] + snr = satellite['ss'] + used = satellite['used'] +# health = satellite['health'] + + # don't show if not visible + if azim >= 360 or elev < -10 or elev > 90: + continue; + + cname = 'black' + color = self.black + if snr < 12: + cname = 'gray' + color = self.gray + elif snr < 30: + cname = 'red' + color = self.red + elif snr < 36: + cname = 'yellow' + color = self.yellow + elif snr < 42: + color = 'green' + color = self.green + else: + color = 'blue' + color = self.blue + + txt = "%4u %4u %4u %3u %1u" % (prn, azim, elev, snr, used) + self.txtSatellites.setTextColor(color) + if used: + self.txtSatellites.setFontWeight(QFont.Bold) + else: + self.txtSatellites.setFontWeight(QFont.Normal) + self.txtSatellites.append(txt) + + prns.append(prn) + theta.append(radians(float(azim))) + radius.append(float(elev)) + colors.append(cname) + if used: + markers.append(ord('P')) + else: + markers.append(ord('+')) + + area = 100 + figure = Figure() + polar = figure.add_subplot(111, projection='polar') + polar.scatter(theta, radius, c=colors, s=area, cmap=None, alpha=0.75) + polar.set_rorigin(90.0) + polar.set_yticklabels([]) + for i, prn in enumerate(prns): + polar.annotate(str(prn), (theta[i], radius[i])) + + polar.set_theta_direction(-1) + polar.set_theta_zero_location('N', offset=0) + + canvas = FigureCanvas(figure) + self.scene.addWidget(canvas) + h = float(self.scene.height()) / 2.0 + w = float(self.scene.width()) / 2.0 + self.viewSatellites.centerOn(w,h) + self.viewSatellites.show() + + self.show() + + + def toggle_logging(self): + if self.log_enabled: + self.log_enabled = 0 + self.dir_dialog() + self.save_log() + self.toggleLogging.setText("Start") + else: + self.log_enabled = 1 + self.toggleLogging.setText("Stop") + + def dir_dialog(self): + dirdialog = QFileDialog(self) + dirdialog.setDirectory(QDir.currentPath()) + dirdialog.setDefaultSuffix("gpx") + dirdialog.setFileMode(QFileDialog.Directory) + dirdialog.setOption(QFileDialog.ShowDirsOnly, True) + selected = dirdialog.exec() + if selected: + dirname = dirdialog.selectedFiles()[0] +# self.outputDir.setText(dirname) + self.dirname = dirname + + def save_log(self): + if self.num_points: + self.num_points = 0 + log = open(strftime(self.dirname + "/track-%Y%m%d-%H%M.gpx"), 'w') + log.write(self.gpx.to_xml()) + log.close() + self.gpx_segment = gpxpy.gpx.GPXTrackSegment() + + def start_thread(self): + if not self.my_thread.isRunning(): + self.my_thread.new_data.sig.connect(self.update_data) + self.my_thread.start() + self.my_thread.setTerminationEnabled(True) + + def stop_thread(self): + self.my_thread.stop() + self.my_thread.wait(3) + self.save_log() + + def closeEvent(self, event): + self.stop_thread() + event.accept() + sys.exit(0) + + def exit_button(self): + self.stop_thread() + if self.log_enabled: + self.toggle_logging() + self.log_enabled = 0 + sys.exit(0) + +if os.path.isfile('config.yaml'): + with open('config.yaml') as f: + config = yaml.load(f, Loader=yaml.FullLoader) + if 'host' in config: + gpsd_host = config['host'] + +if __name__ == '__main__': + app = QApplication(sys.argv) + + window = QtGPSWindow() + window.show() + window.start_thread() + ret = app.exec_() + sys.exit(ret) diff --git a/gps_tool/mainwindow.ui b/gps_tool/mainwindow.ui new file mode 100644 index 0000000..9229d7b --- /dev/null +++ b/gps_tool/mainwindow.ui @@ -0,0 +1,481 @@ + + + MainWindow + + + + 0 + 0 + 737 + 359 + + + + QtGPS + + + + + + 10 + 100 + 61 + 16 + + + + Longitude: + + + + + + 10 + 80 + 61 + 16 + + + + Latitude: + + + + + + 10 + 160 + 51 + 16 + + + + Speed: + + + + + + 70 + 100 + 81 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 70 + 160 + 61 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 70 + 80 + 81 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 10 + 140 + 61 + 16 + + + + Heading: + + + + + + 640 + 300 + 84 + 32 + + + + Exit + + + + + + 70 + 140 + 61 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 70 + 60 + 151 + 20 + + + + QFrame::Panel + + + QFrame::Sunken + + + + + + + + + 10 + 60 + 51 + 16 + + + + Time: + + + + + + 10 + 120 + 61 + 16 + + + + Altitude: + + + + + + 10 + 40 + 51 + 16 + + + + Fix: + + + + + + 70 + 40 + 61 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 10 + 180 + 49 + 20 + + + + HDOP: + + + + + + 10 + 200 + 49 + 16 + + + + VDOP: + + + + + + 70 + 180 + 61 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 70 + 200 + 61 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 70 + 120 + 61 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 140 + 120 + 49 + 16 + + + + meters + + + + + + 140 + 160 + 49 + 16 + + + + m/sec + + + + + + 130 + 40 + 49 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 10 + 220 + 49 + 16 + + + + Grid: + + + + + + 70 + 220 + 61 + 16 + + + + QFrame::Panel + + + QFrame::Sunken + + + + + + + + + 230 + 10 + 220 + 280 + + + + + Courier + + + + + + + 130 + 250 + 84 + 32 + + + + Start + + + + + + 10 + 250 + 111 + 20 + + + + GPX Data Logging: + + + + + + 450 + 10 + 280 + 280 + + + + + + + 10 + 10 + 221 + 16 + + + + + 10 + 75 + true + + + + GPS Tool (gpsd client) + + + + + + + 0 + 0 + 737 + 28 + + + + + + + + diff --git a/gps_tool/setup.sh b/gps_tool/setup.sh new file mode 100755 index 0000000..f6ca1f7 --- /dev/null +++ b/gps_tool/setup.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Python 3 +echo "Python 3 packages:" +sudo apt-get -y --ignore-missing install python3-all python3-dev python3-tk idle3 python3-pip python3-pyqt5 python3-serial python3-can python3-protobuf python3-numpy python3-pil.imagetk python3-gi spyder3 python3-pyside2.* + +# Python addons +sudo -H pip install --upgrade pip +sudo -H pip install configparser pyside2 +sudo -H pip install --upgrade pyserial numpy scipy scapy gps gpxpy maidenhead virtualenv virtualenvwrapper