From ea8887ebf02bc0b47e22062e168fae5244528cf8 Mon Sep 17 00:00:00 2001 From: Neal Probert Date: Wed, 16 Jun 2021 12:11:23 -0400 Subject: [PATCH 1/2] Started my own version of virtual cones --- gps_vcones/MainWindow.py | 229 ++++++++++ gps_vcones/alert.wav | Bin 0 -> 61830 bytes gps_vcones/home-block.csv | 7 + gps_vcones/mainwindow.ui | 716 ++++++++++++++++++++++++++++++++ gps_vcones/ublox-6h-250ms.txt | 71 ++++ gps_vcones/ublox-6h-500ms.txt | 71 ++++ gps_vcones/virtual_cones.py | 278 +++++++++++++ gps_vcones/virtual_cones_gui.py | 505 ++++++++++++++++++++++ 8 files changed, 1877 insertions(+) create mode 100644 gps_vcones/MainWindow.py create mode 100644 gps_vcones/alert.wav create mode 100644 gps_vcones/home-block.csv create mode 100644 gps_vcones/mainwindow.ui create mode 100644 gps_vcones/ublox-6h-250ms.txt create mode 100644 gps_vcones/ublox-6h-500ms.txt create mode 100755 gps_vcones/virtual_cones.py create mode 100755 gps_vcones/virtual_cones_gui.py diff --git a/gps_vcones/MainWindow.py b/gps_vcones/MainWindow.py new file mode 100644 index 0000000..c73ee52 --- /dev/null +++ b/gps_vcones/MainWindow.py @@ -0,0 +1,229 @@ +# -*- 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(772, 445) + 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(389, 60, 81, 16)) + self.listCones2 = QListWidget(self.centralwidget) + self.listCones2.setObjectName(u"listCones2") + self.listCones2.setGeometry(QRect(200, 110, 180, 220)) + self.label = QLabel(self.centralwidget) + self.label.setObjectName(u"label") + self.label.setGeometry(QRect(389, 40, 81, 16)) + self.label_3 = QLabel(self.centralwidget) + self.label_3.setObjectName(u"label_3") + self.label_3.setGeometry(QRect(579, 40, 61, 16)) + self.txtLongitude = QLabel(self.centralwidget) + self.txtLongitude.setObjectName(u"txtLongitude") + self.txtLongitude.setGeometry(QRect(470, 60, 91, 20)) + self.txtLongitude.setFrameShape(QFrame.Panel) + self.txtLongitude.setFrameShadow(QFrame.Sunken) + self.txtDist4 = QLabel(self.centralwidget) + self.txtDist4.setObjectName(u"txtDist4") + self.txtDist4.setGeometry(QRect(690, 90, 70, 18)) + self.txtDist4.setFrameShape(QFrame.Panel) + self.txtDist4.setFrameShadow(QFrame.Sunken) + self.txtDist2 = QLabel(self.centralwidget) + self.txtDist2.setObjectName(u"txtDist2") + self.txtDist2.setGeometry(QRect(310, 90, 70, 18)) + self.txtDist2.setFrameShape(QFrame.Panel) + self.txtDist2.setFrameShadow(QFrame.Sunken) + self.txtSpeed = QLabel(self.centralwidget) + self.txtSpeed.setObjectName(u"txtSpeed") + self.txtSpeed.setGeometry(QRect(650, 40, 81, 18)) + self.txtSpeed.setFrameShape(QFrame.Panel) + self.txtSpeed.setFrameShadow(QFrame.Sunken) + self.txtDist1 = QLabel(self.centralwidget) + self.txtDist1.setObjectName(u"txtDist1") + self.txtDist1.setGeometry(QRect(120, 90, 70, 18)) + self.txtDist1.setFrameShape(QFrame.Panel) + self.txtDist1.setFrameShadow(QFrame.Sunken) + self.lblList2 = QLabel(self.centralwidget) + self.lblList2.setObjectName(u"lblList2") + self.lblList2.setGeometry(QRect(200, 90, 141, 16)) + self.lblList1 = QLabel(self.centralwidget) + self.lblList1.setObjectName(u"lblList1") + self.lblList1.setGeometry(QRect(10, 90, 141, 16)) + self.txtLatitude = QLabel(self.centralwidget) + self.txtLatitude.setObjectName(u"txtLatitude") + self.txtLatitude.setGeometry(QRect(470, 40, 91, 20)) + 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(579, 60, 61, 16)) + self.listCones4 = QListWidget(self.centralwidget) + self.listCones4.setObjectName(u"listCones4") + self.listCones4.setGeometry(QRect(580, 110, 180, 220)) + self.label_5 = QLabel(self.centralwidget) + self.label_5.setObjectName(u"label_5") + self.label_5.setGeometry(QRect(20, 10, 281, 16)) + font = QFont() + font.setPointSize(14) + self.label_5.setFont(font) + self.exitButton = QPushButton(self.centralwidget) + self.exitButton.setObjectName(u"exitButton") + self.exitButton.setGeometry(QRect(680, 380, 84, 32)) + self.lblList4 = QLabel(self.centralwidget) + self.lblList4.setObjectName(u"lblList4") + self.lblList4.setGeometry(QRect(580, 90, 141, 16)) + self.listCones3 = QListWidget(self.centralwidget) + self.listCones3.setObjectName(u"listCones3") + self.listCones3.setGeometry(QRect(390, 110, 180, 220)) + self.listCones1 = QListWidget(self.centralwidget) + self.listCones1.setObjectName(u"listCones1") + self.listCones1.setGeometry(QRect(10, 110, 180, 220)) + self.txtDist3 = QLabel(self.centralwidget) + self.txtDist3.setObjectName(u"txtDist3") + self.txtDist3.setGeometry(QRect(500, 90, 70, 18)) + self.txtDist3.setFrameShape(QFrame.Panel) + self.txtDist3.setFrameShadow(QFrame.Sunken) + self.txtHeading = QLabel(self.centralwidget) + self.txtHeading.setObjectName(u"txtHeading") + self.txtHeading.setGeometry(QRect(650, 60, 81, 18)) + self.txtHeading.setFrameShape(QFrame.Panel) + self.txtHeading.setFrameShadow(QFrame.Sunken) + self.lblList3 = QLabel(self.centralwidget) + self.lblList3.setObjectName(u"lblList3") + self.lblList3.setGeometry(QRect(390, 90, 141, 16)) + self.openFiles = QPushButton(self.centralwidget) + self.openFiles.setObjectName(u"openFiles") + self.openFiles.setGeometry(QRect(40, 40, 111, 32)) + self.txtTime = QLabel(self.centralwidget) + self.txtTime.setObjectName(u"txtTime") + self.txtTime.setGeometry(QRect(460, 10, 241, 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(390, 10, 49, 16)) + self.logEnabled = QCheckBox(self.centralwidget) + self.logEnabled.setObjectName(u"logEnabled") + self.logEnabled.setGeometry(QRect(170, 50, 181, 20)) + self.addPoint1 = QPushButton(self.centralwidget) + self.addPoint1.setObjectName(u"addPoint1") + self.addPoint1.setGeometry(QRect(60, 340, 41, 32)) + self.delPoint1 = QPushButton(self.centralwidget) + self.delPoint1.setObjectName(u"delPoint1") + self.delPoint1.setGeometry(QRect(100, 340, 41, 32)) + self.addPoint2 = QPushButton(self.centralwidget) + self.addPoint2.setObjectName(u"addPoint2") + self.addPoint2.setGeometry(QRect(250, 340, 41, 32)) + self.delPoint2 = QPushButton(self.centralwidget) + self.delPoint2.setObjectName(u"delPoint2") + self.delPoint2.setGeometry(QRect(290, 340, 41, 32)) + self.addPoint3 = QPushButton(self.centralwidget) + self.addPoint3.setObjectName(u"addPoint3") + self.addPoint3.setGeometry(QRect(440, 340, 41, 32)) + self.delPoint3 = QPushButton(self.centralwidget) + self.delPoint3.setObjectName(u"delPoint3") + self.delPoint3.setGeometry(QRect(480, 340, 41, 32)) + self.addPoint4 = QPushButton(self.centralwidget) + self.addPoint4.setObjectName(u"addPoint4") + self.addPoint4.setGeometry(QRect(630, 340, 41, 32)) + self.delPoint4 = QPushButton(self.centralwidget) + self.delPoint4.setObjectName(u"delPoint4") + self.delPoint4.setGeometry(QRect(670, 340, 41, 32)) + self.saveList1 = QPushButton(self.centralwidget) + self.saveList1.setObjectName(u"saveList1") + self.saveList1.setGeometry(QRect(150, 340, 41, 32)) + self.saveList2 = QPushButton(self.centralwidget) + self.saveList2.setObjectName(u"saveList2") + self.saveList2.setGeometry(QRect(340, 340, 41, 32)) + self.saveList3 = QPushButton(self.centralwidget) + self.saveList3.setObjectName(u"saveList3") + self.saveList3.setGeometry(QRect(530, 340, 41, 32)) + self.saveList4 = QPushButton(self.centralwidget) + self.saveList4.setObjectName(u"saveList4") + self.saveList4.setGeometry(QRect(720, 340, 41, 32)) + self.resetList1 = QPushButton(self.centralwidget) + self.resetList1.setObjectName(u"resetList1") + self.resetList1.setGeometry(QRect(10, 340, 41, 32)) + self.resetList2 = QPushButton(self.centralwidget) + self.resetList2.setObjectName(u"resetList2") + self.resetList2.setGeometry(QRect(200, 340, 41, 32)) + self.resetList3 = QPushButton(self.centralwidget) + self.resetList3.setObjectName(u"resetList3") + self.resetList3.setGeometry(QRect(390, 340, 41, 32)) + self.resetList4 = QPushButton(self.centralwidget) + self.resetList4.setObjectName(u"resetList4") + self.resetList4.setGeometry(QRect(580, 340, 41, 32)) + self.saveConfig = QPushButton(self.centralwidget) + self.saveConfig.setObjectName(u"saveConfig") + self.saveConfig.setGeometry(QRect(340, 380, 84, 32)) + MainWindow.setCentralWidget(self.centralwidget) + self.menubar = QMenuBar(MainWindow) + self.menubar.setObjectName(u"menubar") + self.menubar.setGeometry(QRect(0, 0, 772, 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"TOSCo Virtual Cones V1.0", 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.txtDist4.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtDist2.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtSpeed.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtDist1.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.lblList2.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) + self.lblList1.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) + self.txtLatitude.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.label_4.setText(QCoreApplication.translate("MainWindow", u"Heading:", None)) + self.label_5.setText(QCoreApplication.translate("MainWindow", u"TOSCo Virtual Cones", None)) + self.exitButton.setText(QCoreApplication.translate("MainWindow", u"Exit", None)) + self.lblList4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) + self.txtDist3.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.txtHeading.setText(QCoreApplication.translate("MainWindow", u"0", None)) + self.lblList3.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) + self.openFiles.setText(QCoreApplication.translate("MainWindow", u"Open Files", None)) + self.txtTime.setText("") + self.label_7.setText(QCoreApplication.translate("MainWindow", u"Time:", None)) + self.logEnabled.setText(QCoreApplication.translate("MainWindow", u"Logging Enabled", None)) + self.addPoint1.setText(QCoreApplication.translate("MainWindow", u"Add", None)) + self.delPoint1.setText(QCoreApplication.translate("MainWindow", u"Del", None)) + self.addPoint2.setText(QCoreApplication.translate("MainWindow", u"Add", None)) + self.delPoint2.setText(QCoreApplication.translate("MainWindow", u"Del", None)) + self.addPoint3.setText(QCoreApplication.translate("MainWindow", u"Add", None)) + self.delPoint3.setText(QCoreApplication.translate("MainWindow", u"Del", None)) + self.addPoint4.setText(QCoreApplication.translate("MainWindow", u"Add", None)) + self.delPoint4.setText(QCoreApplication.translate("MainWindow", u"Del", None)) + self.saveList1.setText(QCoreApplication.translate("MainWindow", u"Save", None)) + self.saveList2.setText(QCoreApplication.translate("MainWindow", u"Save", None)) + self.saveList3.setText(QCoreApplication.translate("MainWindow", u"Save", None)) + self.saveList4.setText(QCoreApplication.translate("MainWindow", u"Save", None)) + self.resetList1.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) + self.resetList2.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) + self.resetList3.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) + self.resetList4.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) + self.saveConfig.setText(QCoreApplication.translate("MainWindow", u"SaveConfig", None)) + # retranslateUi + diff --git a/gps_vcones/alert.wav b/gps_vcones/alert.wav new file mode 100644 index 0000000000000000000000000000000000000000..268ffc039c83e01a521ad20086b0d9e8c643de7a GIT binary patch literal 61830 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

brACbQ&pI(3! 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

dN)Ui74WhT=WXQ81#gdq=%H*G%fuw)52QJ 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 literal 0 HcmV?d00001 diff --git a/gps_vcones/home-block.csv b/gps_vcones/home-block.csv new file mode 100644 index 0000000..48d89f4 --- /dev/null +++ b/gps_vcones/home-block.csv @@ -0,0 +1,7 @@ + 42.5218830, -83.2373390, 5.0 + 42.5210100, -83.2361230, 5.0 + 42.5211590, -83.2337800, 5.0 + 42.5220680, -83.2337250, 5.0 + 42.5232330, -83.2339910, 5.0 + 42.5226510, -83.2360890, 5.0 + 42.5221890, -83.2371260, 5.0 diff --git a/gps_vcones/mainwindow.ui b/gps_vcones/mainwindow.ui new file mode 100644 index 0000000..6df0fe8 --- /dev/null +++ b/gps_vcones/mainwindow.ui @@ -0,0 +1,716 @@ + + + MainWindow + + + + 0 + 0 + 772 + 445 + + + + NTCNA GPS Virtual Cones V1.0 + + + + + + 389 + 60 + 81 + 16 + + + + Longitude: + + + + + + 200 + 110 + 180 + 220 + + + + + + + 389 + 40 + 81 + 16 + + + + Latitude: + + + + + + 579 + 40 + 61 + 16 + + + + Speed: + + + + + + 470 + 60 + 91 + 20 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 690 + 90 + 70 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 310 + 90 + 70 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 650 + 40 + 81 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 120 + 90 + 70 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 200 + 90 + 141 + 16 + + + + TextLabel + + + + + + 10 + 90 + 141 + 16 + + + + TextLabel + + + + + + 470 + 40 + 91 + 20 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 579 + 60 + 61 + 16 + + + + Heading: + + + + + + 580 + 110 + 180 + 220 + + + + + + + 20 + 10 + 281 + 16 + + + + + 14 + + + + NTCNA GPS Virtual Cones + + + + + + 680 + 380 + 84 + 32 + + + + Exit + + + + + + 580 + 90 + 141 + 16 + + + + TextLabel + + + + + + 390 + 110 + 180 + 220 + + + + + + + 10 + 110 + 180 + 220 + + + + + + + 500 + 90 + 70 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 650 + 60 + 81 + 18 + + + + QFrame::Panel + + + QFrame::Sunken + + + 0 + + + + + + 390 + 90 + 141 + 16 + + + + TextLabel + + + + + + 40 + 40 + 111 + 32 + + + + Open Files + + + + + + 460 + 10 + 241 + 20 + + + + QFrame::Panel + + + QFrame::Sunken + + + + + + + + + 390 + 10 + 49 + 16 + + + + Time: + + + + + + 170 + 50 + 131 + 20 + + + + Logging Enabled + + + + + + 60 + 330 + 41 + 32 + + + + Add + + + + + + 100 + 340 + 41 + 32 + + + + Del + + + + + + 250 + 330 + 41 + 32 + + + + Add + + + + + + 290 + 340 + 41 + 32 + + + + Del + + + + + + 440 + 330 + 41 + 32 + + + + Add + + + + + + 480 + 340 + 41 + 32 + + + + Del + + + + + + 630 + 330 + 41 + 32 + + + + Add + + + + + + 670 + 340 + 41 + 32 + + + + Del + + + + + + 150 + 340 + 41 + 32 + + + + Save + + + + + + 340 + 340 + 41 + 32 + + + + Save + + + + + + 530 + 340 + 41 + 32 + + + + Save + + + + + + 720 + 340 + 41 + 32 + + + + Save + + + + + + 10 + 340 + 41 + 32 + + + + Reset + + + + + + 200 + 340 + 41 + 32 + + + + Reset + + + + + + 390 + 340 + 41 + 32 + + + + Reset + + + + + + 580 + 340 + 41 + 32 + + + + Reset + + + + + + 340 + 380 + 84 + 32 + + + + SaveConfig + + + + + + 60 + 360 + 41 + 32 + + + + Ins + + + + + + 250 + 360 + 41 + 32 + + + + Ins + + + + + + 440 + 360 + 41 + 32 + + + + Ins + + + + + + 630 + 360 + 41 + 32 + + + + Ins + + + + + + 310 + 10 + 41 + 30 + + + + 3.0 + + + + + + 250 + 20 + 61 + 16 + + + + Range: + + + + + + + 0 + 0 + 772 + 28 + + + + + + + + diff --git a/gps_vcones/ublox-6h-250ms.txt b/gps_vcones/ublox-6h-250ms.txt new file mode 100644 index 0000000..95f8ac3 --- /dev/null +++ b/gps_vcones/ublox-6h-250ms.txt @@ -0,0 +1,71 @@ +MON-VER - 0A 04 28 00 37 2E 30 33 20 28 34 35 39 36 39 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 30 30 34 30 30 30 37 00 00 +CFG-ANT - 06 13 04 00 1B 00 8B A9 +CFG-DAT - 06 06 02 00 00 00 +CFG-FXN - 06 0E 24 00 0C 00 00 00 00 00 00 00 00 00 00 00 10 27 00 00 10 27 00 00 D0 07 00 00 18 FC FF FF 00 00 00 00 00 00 00 00 +CFG-INF - 06 02 0A 00 00 00 00 00 00 00 00 00 00 00 +CFG-INF - 06 02 0A 00 01 00 00 00 87 87 87 87 87 87 +CFG-INF - 06 02 0A 00 03 00 00 00 00 00 00 00 00 00 +CFG-ITFM - 06 39 08 00 F3 AC 62 2D 1E 03 00 00 +CFG-MSG - 06 01 08 00 0B 30 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 32 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 33 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 31 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 00 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 0B 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 09 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 02 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 07 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 21 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 08 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 60 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 22 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 31 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 04 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 02 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 32 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 30 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 20 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 21 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 11 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 12 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 02 20 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 00 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 01 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 02 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 03 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 04 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 05 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 07 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 08 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 09 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 0A 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 00 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 04 00 00 00 00 00 00 +CFG-NAV5 - 06 24 24 00 FF FF 00 03 00 00 00 00 10 27 00 00 05 00 FA 00 FA 00 64 00 2C 01 00 3C 00 00 00 00 00 00 00 00 00 00 00 00 +CFG-NAVX5 - 06 23 28 00 00 00 FF FF 43 00 00 00 03 02 03 10 07 00 00 01 00 00 43 06 00 00 00 00 01 01 00 00 00 64 78 00 00 00 00 00 00 00 00 00 +CFG-NMEA - 06 17 04 00 00 23 00 02 +CFG-PM - 06 32 18 00 00 06 00 00 04 90 00 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00 +CFG-PM2 - 06 3B 2C 00 01 06 00 00 00 90 02 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00 2C 01 00 00 4F C1 03 00 86 02 00 00 FE 00 00 00 64 40 01 00 +CFG-PRT - 06 00 14 00 00 00 00 00 84 00 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-PRT - 06 00 14 00 01 00 00 00 C0 08 00 00 00 C2 01 00 07 00 03 00 00 00 00 00 +CFG-PRT - 06 00 14 00 02 00 00 00 C0 08 00 00 80 25 00 00 00 00 00 00 00 00 00 00 +CFG-PRT - 06 00 14 00 03 00 00 00 00 00 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-PRT - 06 00 14 00 04 00 00 00 00 32 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-RATE - 06 08 06 00 FA 00 01 00 01 00 +CFG-RINV - 06 34 18 00 00 4E 6F 74 69 63 65 3A 20 6E 6F 20 64 61 74 61 20 73 61 76 65 64 21 00 +CFG-RXM - 06 11 02 00 08 00 +CFG-SBAS - 06 16 08 00 01 03 03 00 51 62 06 00 +CFG-TP - 06 07 14 00 40 42 0F 00 A0 86 01 00 01 01 00 00 32 00 00 00 00 00 00 00 +CFG-TP5 - 06 31 20 00 00 07 40 00 32 00 00 00 40 42 0F 00 40 42 0F 00 00 00 00 00 A0 86 01 00 00 00 00 00 F7 00 00 00 +CFG-TP5 - 06 31 20 00 01 07 40 00 32 00 00 00 04 00 00 00 01 00 00 00 48 E8 01 00 A0 86 01 00 00 00 00 00 FE 00 00 00 +CFG-USB - 06 1B 6C 00 46 15 A6 01 00 00 00 00 64 00 00 00 75 2D 62 6C 6F 78 20 41 47 20 2D 20 77 77 77 2E 75 2D 62 6C 6F 78 2E 63 6F 6D 00 00 00 00 00 00 75 2D 62 6C 6F 78 20 36 20 20 2D 20 20 47 50 53 20 52 65 63 65 69 76 65 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 diff --git a/gps_vcones/ublox-6h-500ms.txt b/gps_vcones/ublox-6h-500ms.txt new file mode 100644 index 0000000..f435b71 --- /dev/null +++ b/gps_vcones/ublox-6h-500ms.txt @@ -0,0 +1,71 @@ +MON-VER - 0A 04 28 00 37 2E 30 33 20 28 34 35 39 36 39 29 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 30 30 30 34 30 30 30 37 00 00 +CFG-ANT - 06 13 04 00 1B 00 8B A9 +CFG-DAT - 06 06 02 00 00 00 +CFG-FXN - 06 0E 24 00 0C 00 00 00 00 00 00 00 00 00 00 00 10 27 00 00 10 27 00 00 D0 07 00 00 18 FC FF FF 00 00 00 00 00 00 00 00 +CFG-INF - 06 02 0A 00 00 00 00 00 00 00 00 00 00 00 +CFG-INF - 06 02 0A 00 01 00 00 00 87 87 87 87 87 87 +CFG-INF - 06 02 0A 00 03 00 00 00 00 00 00 00 00 00 +CFG-ITFM - 06 39 08 00 F3 AC 62 2D 1E 03 00 00 +CFG-MSG - 06 01 08 00 0B 30 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 32 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 33 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 31 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0B 00 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 0B 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 09 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 02 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 07 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 21 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0A 08 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 60 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 22 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 31 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 04 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 02 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 32 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 30 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 20 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 21 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 11 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 01 12 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 02 20 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 01 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 0D 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 00 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 01 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 02 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 03 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 04 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 05 01 01 01 01 01 01 +CFG-MSG - 06 01 08 00 F0 06 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 07 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 08 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 09 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F0 0A 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 00 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 03 00 00 00 00 00 00 +CFG-MSG - 06 01 08 00 F1 04 00 00 00 00 00 00 +CFG-NAV5 - 06 24 24 00 FF FF 00 03 00 00 00 00 10 27 00 00 05 00 FA 00 FA 00 64 00 2C 01 00 3C 00 00 00 00 00 00 00 00 00 00 00 00 +CFG-NAVX5 - 06 23 28 00 00 00 FF FF 43 00 00 00 03 02 03 10 07 00 00 01 00 00 43 06 00 00 00 00 01 01 00 00 00 64 78 00 00 00 00 00 00 00 00 00 +CFG-NMEA - 06 17 04 00 00 23 00 02 +CFG-PM - 06 32 18 00 00 06 00 00 04 90 00 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00 +CFG-PM2 - 06 3B 2C 00 01 06 00 00 00 90 02 00 E8 03 00 00 10 27 00 00 00 00 00 00 02 00 00 00 2C 01 00 00 4F C1 03 00 86 02 00 00 FE 00 00 00 64 40 01 00 +CFG-PRT - 06 00 14 00 00 00 00 00 84 00 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-PRT - 06 00 14 00 01 00 00 00 C0 08 00 00 00 C2 01 00 07 00 03 00 00 00 00 00 +CFG-PRT - 06 00 14 00 02 00 00 00 C0 08 00 00 80 25 00 00 00 00 00 00 00 00 00 00 +CFG-PRT - 06 00 14 00 03 00 00 00 00 00 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-PRT - 06 00 14 00 04 00 00 00 00 32 00 00 00 00 00 00 07 00 07 00 00 00 00 00 +CFG-RATE - 06 08 06 00 F4 01 01 00 01 00 +CFG-RINV - 06 34 18 00 00 4E 6F 74 69 63 65 3A 20 6E 6F 20 64 61 74 61 20 73 61 76 65 64 21 00 +CFG-RXM - 06 11 02 00 08 00 +CFG-SBAS - 06 16 08 00 01 03 03 00 51 62 06 00 +CFG-TP - 06 07 14 00 40 42 0F 00 A0 86 01 00 01 01 00 00 32 00 00 00 00 00 00 00 +CFG-TP5 - 06 31 20 00 00 07 40 00 32 00 00 00 40 42 0F 00 40 42 0F 00 00 00 00 00 A0 86 01 00 00 00 00 00 F7 00 00 00 +CFG-TP5 - 06 31 20 00 01 07 40 00 32 00 00 00 04 00 00 00 01 00 00 00 48 E8 01 00 A0 86 01 00 00 00 00 00 FE 00 00 00 +CFG-USB - 06 1B 6C 00 46 15 A6 01 00 00 00 00 64 00 00 00 75 2D 62 6C 6F 78 20 41 47 20 2D 20 77 77 77 2E 75 2D 62 6C 6F 78 2E 63 6F 6D 00 00 00 00 00 00 75 2D 62 6C 6F 78 20 36 20 20 2D 20 20 47 50 53 20 52 65 63 65 69 76 65 72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 diff --git a/gps_vcones/virtual_cones.py b/gps_vcones/virtual_cones.py new file mode 100755 index 0000000..65083e5 --- /dev/null +++ b/gps_vcones/virtual_cones.py @@ -0,0 +1,278 @@ +#! /usr/bin/python3 + +import os +import sys +import getopt +import csv + +from math import * +from gps import * +from time import strftime, sleep + +import os +from sys import platform + +############################################################################## +# audio stuff +############################################################################## + +sound_args = "" + +def play_sound(file): + if platform == "win32" or platform == "win64" or platform == "cygwin": + cmd = "wmplayer " + '"' + file + '"' + else: + cmd = "aplay -N " + sound_args + " " + file + " &" +# print(cmd) + os.system(cmd) + +############################################################################## +# buzzer stuff +############################################################################## + +# https://www.instructables.com/Raspberry-Pi-Tutorial-How-to-Use-a-Buzzer/ + +try: + import RPi.GPIO as GPIO + + buzzer=23 + seconds=3 + + def buzzer_init(): + GPIO.setwarnings(0) + GPIO.setmode(GPIO.BCM) + GPIO.setup(buzzer, GPIO.OUT) + + def buzzer_on(): + GPIO.output(buzzer, GPIO.HIGH) + + def buzzer_off(): + GPIO.output(buzzer, GPIO.LOW) + + def buzzer_run(): + global seconds + sec = seconds + while (sec): + buzzer_on() + sleep(0.5) + buzzer_off() + sleep(0.5) + sec = sec - 1 + + buzzer_init() +except: + print("Not a Raspberry Pi! No buzzer support.") + +############################################################################## +# gps stuff +############################################################################## + +# https://ozzmaker.com/using-python-with-a-gps-receiver-on-a-raspberry-pi/ + +gpsd = gps(mode=WATCH_ENABLE|WATCH_NEWSTYLE) + +# +# GPS receiver is ublox from V2V-MD at 10x seconds +# + +# http://www.movable-type.co.uk/scripts/latlong.html + +earth_flatening = 1.0/298.257223563 +earth_radius = 6378137.0 + +def roydistance(lat1, lon1, lat2, lon2): + """ + Calculate the great circle distance between two points + on the earth (specified in decimal degrees) + """ + # convert decimal degrees to radians + lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) + + # Roy's method + f1 = (1.0 - earth_flatening) + + top = (pow((lon2-lon1), 2) * pow(cos(lat1), 2)) + pow(lat2-lat1, 2) + bot = pow(sin(lat1), 2) + (pow(f1, 2) * pow(cos(lat1), 2)) + + return f1 * earth_radius * sqrt(top/bot) + +############################################################################## +# log +############################################################################## + +debug_on = 0 + +if not os.path.isdir("logs"): + os.mkdir("logs") +log = open(strftime("logs/way-%Y%m%d-%H%M.log"), 'a') +log.write("Time,Latitude,Longitude,Heading,Speed,Description\n") + +############################################################################## +# virtual cones +############################################################################## + +# node from csv (each line is lat, long, distance, next code# (optional)): +# 42.522040, -83.237594, 5, 1 + + +############################################################################## +# class +############################################################################## + +class VirtualCones(): + def __init__(self): + self.filename = "" + self.cones = [] + self.num_cones = 0 + self.current_cone = 0 + self.distance = 0.0 + self.buzz_no = 0 + self.max_buzz = 1 + + def clr_cones(self): + self.cones = [] + self.num_cones = 0 + self.current_cone = 0 + + def add_cone(self, lat, lon, rad): + self.cones.append( [float(lat), float(lon), float(rad)] ) + self.num_cones += 1 + + def read_csv(self, csv_file): + file_csv = open(csv_file) + data_csv = csv.reader(file_csv) + return list(data_csv) + + def read_list(self, file): + self.filename = file + self.clr_cones() + self.cones = self.read_csv(file) + if len(self.cones) == 0: + return + for i in range(0, len(self.cones)): + for j in range(0, 3): + self.cones[i][j] = float(self.cones[i][j]) + self.num_cones = len(self.cones) + return self.cones + + def save_list(self, filename): + with open(filename, "w") as file: + for i in range(0, self.num_cones): + file.write("%11.7f, %11.7f, %4.1f\n" % (self.cones[i][0], self.cones[i][1], self.cones[i][2])) + file.close() + + def check_list(self, lat, lon, heading, speed): + trig = 0 + if self.num_cones == 0: + return (0, 999.0, 0) + + # end of list? + if self.current_cone >= self.num_cones: + self.current_cone = 0 + self.buzz_no = 0 + + # check first node for reset + if self.current_cone: + # node from csv (each line is lat, long, distance) + row = self.cones[0] + cone_lat = row[0] + cone_lon = row[1] + cone_dist = row[2] + dist = roydistance(lat, lon, cone_lat, cone_lon) + if dist < cone_dist: + self.current_cone = 0 + + # node from csv (each line is lat, long, distance, next code#) + row = self.cones[self.current_cone] + cone_lat = row[0] + cone_lon = row[1] + cone_dist = row[2] + + # next cone + cone_next = self.current_cone + 1 + try: + cone_next = int(row[3]) + except: + pass + + # distance between vehicle and cone + self.distance = roydistance(lat, lon, cone_lat, cone_lon) + + # check distance trigger + if self.distance < cone_dist: +# out = "Waypoint %s:%d reached: %f, %f, %f, %d" % (self.filename, self.current_cone, lat, lon, heading, speed) +# print(out) + if self.buzz_no < self.max_buzz: + play_sound("alert.wav") + self.buzz_no = self.buzz_no + 1 + self.current_cone = cone_next + trig = 1 + else: + self.buzz_no = 0 + + return (self.current_cone, self.distance, trig) + +############################################################################## +# main +############################################################################## + +opt_list = "d" + +def main(argv): + global log, debug_on + virtual_list = [[],[],[],[],[],[],[],[],[]] + virtual_count = 0 + + try: + opts, args = getopt.getopt(argv, opt_list, ["",""]) + except getopt.GetoptError: + print(sys.argv[0], '[-d] ') + sys.exit(2) + + for opt, arg in opts: + if opt in ('-h'): + print("virtual_cone_alert.py " + opt_list + " ") + sys.exit() + elif opt in ('-d'): + debug_on = 1 + else: + print("Argument error!") + print("virtual_cone_alert.py " + opt_list + " ") + sys.exit() + + # slurp in files for each list + for arg in args: + try: + cones = VirtualCones() + cones.read_list(arg) + virtual_list[virtual_count] = cones + virtual_count += 1 + except: + print("File open/read failed: ", arg) + sys.exit(1) + + try: + while True: + report = gpsd.next() # + if report['class'] == 'TPV': + + time = getattr(report, 'time', 0.0) + lat = getattr(report, 'lat', 0.0) + lon = getattr(report, 'lon', 0.0) + heading = getattr(report, 'track', 0.0) + speed = getattr(report, 'speed', 0.0) + + log.write("%s,%f,%f,%f,%f\n" % (time, lat, lon, heading, speed)) +# print("Position: %f, %f, %d" % (lat, lon, speed)) + + # walk through each list + for i in range(0, virtual_count): + virtual_list[i].check_list(lat, lon, heading, speed) + +# time.sleep(0.2) + + except (KeyboardInterrupt, SystemExit): #when you press ctrl+c + log.close() + print("Done.\nExiting.") + +main(sys.argv[1:]) diff --git a/gps_vcones/virtual_cones_gui.py b/gps_vcones/virtual_cones_gui.py new file mode 100755 index 0000000..00c8b47 --- /dev/null +++ b/gps_vcones/virtual_cones_gui.py @@ -0,0 +1,505 @@ +#! /usr/bin/python3 + +from sys import platform +from os import path, mkdir, system + +from math import * +from gps import * +from datetime import datetime +from time import time, strftime + +import gpxpy +import csv +import yaml + +from PySide2.QtGui import * +from PySide2.QtWidgets import QApplication, QMainWindow, QFileDialog +from PySide2.QtCore import QObject, Slot, Signal, QThread, QDir + +from MainWindow import Ui_MainWindow + +############################################################################## +# audio stuff +############################################################################## + +sound_args = "" + +def play_sound(file): + if platform == "win32" or platform == "win64" or platform == "cygwin": + cmd = "wmplayer " + '"' + file + '"' + else: + cmd = "aplay -N " + sound_args + " " + file + " &" +# print(cmd) + system(cmd) + +############################################################################## +# gps stuff +############################################################################## + +# https://ozzmaker.com/using-python-with-a-gps-receiver-on-a-raspberry-pi/ + +# +# GPS receiver is ublox from V2V-MD at 10x seconds +# + +# http://www.movable-type.co.uk/scripts/latlong.html + +earth_flatening = 1.0/298.257223563 +earth_radius = 6378137.0 + +def roydistance(lat1, lon1, lat2, lon2): + """ + Calculate the great circle distance between two points + on the earth (specified in decimal degrees) + """ + # convert decimal degrees to radians + lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2]) + + # Roy's method + f1 = (1.0 - earth_flatening) + + top = (pow((lon2-lon1), 2) * pow(cos(lat1), 2)) + pow(lat2-lat1, 2) + bot = pow(sin(lat1), 2) + (pow(f1, 2) * pow(cos(lat1), 2)) + + return f1 * earth_radius * sqrt(top/bot) + +############################################################################## +# log +############################################################################## + +if not path.isdir("logs"): + mkdir("logs") + +############################################################################## +# virtual cones +############################################################################## + +# node from csv (each line is lat, long, distance, next code# (optional)): +# 42.522040, -83.237594, 5, 1 + +############################################################################## +# class +############################################################################## + +class VirtualCones(): + def __init__(self): + self.filename = "" + self.cones = [] + self.num_cones = 0 + self.current_cone = 0 + self.distance = 0.0 + self.buzz_no = 0 + self.max_buzz = 1 + + def clr_cones(self): + self.cones = [] + self.num_cones = 0 + self.current_cone = 0 + + def add_cone(self, lat, lon, rad): + self.cones.append( [float(lat), float(lon), float(rad)] ) + self.num_cones += 1 + + def read_csv(self, csv_file): + file_csv = open(csv_file) + data_csv = csv.reader(file_csv) + return list(data_csv) + + def read_list(self, file): + self.filename = file + self.clr_cones() + self.cones = self.read_csv(file) + if len(self.cones) == 0: + return + for i in range(0, len(self.cones)): + for j in range(0, 3): + self.cones[i][j] = float(self.cones[i][j]) + self.num_cones = len(self.cones) + return self.cones + + def save_list(self, filename): + with open(filename, "w") as file: + for i in range(0, self.num_cones): + file.write("%11.7f, %11.7f, %4.1f\n" % (self.cones[i][0], self.cones[i][1], self.cones[i][2])) + file.close() + + def check_list(self, lat, lon, heading, speed): + trig = 0 + if self.num_cones == 0: + return (0, 999.0, 0) + + # end of list? + if self.current_cone >= self.num_cones: + self.current_cone = 0 + self.buzz_no = 0 + + # check first node for reset + if self.current_cone: + # node from csv (each line is lat, long, distance) + row = self.cones[0] + cone_lat = row[0] + cone_lon = row[1] + cone_dist = row[2] + dist = roydistance(lat, lon, cone_lat, cone_lon) + if dist < cone_dist: + self.current_cone = 0 + + # node from csv (each line is lat, long, distance, next code#) + row = self.cones[self.current_cone] + cone_lat = row[0] + cone_lon = row[1] + cone_dist = row[2] + + # next cone + cone_next = self.current_cone + 1 + try: + cone_next = int(row[3]) + except: + pass + + # distance between vehicle and cone + self.distance = roydistance(lat, lon, cone_lat, cone_lon) + + # check distance trigger + if self.distance < cone_dist: +# out = "Waypoint %s:%d reached: %f, %f, %f, %d" % (self.filename, self.current_cone, lat, lon, heading, speed) +# print(out) + if self.buzz_no < self.max_buzz: + play_sound("alert.wav") + self.buzz_no = self.buzz_no + 1 + self.current_cone = cone_next + trig = 1 + else: + self.buzz_no = 0 + + return (self.current_cone, self.distance, trig) + +############################################################################## +# thread +############################################################################## + +gpsd_host = "localhost" +gpsd_report = () + +class DataSignal(QObject): + sig = Signal(int) + +class DataThread(QThread): + def __init__(self, parent=None): + super(DataThread, self).__init__(parent) + self.new_data = DataSignal() +# self.new_data.sig.connect(parent.update_data) + + def run(self): + global 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() + +############################################################################## +# main +############################################################################## + +class MainWindow(QMainWindow, Ui_MainWindow): + def __init__(self, *args, obj=None, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + self.setupUi(self) + +class ConesWindow(MainWindow): + def __init__(self, *args, obj=None, **kwargs): + super(ConesWindow, self).__init__(*args, **kwargs) + + self.setWindowTitle("NTCNA GPS Virtual Cones V1.6") + + self.vlist1 = VirtualCones() + self.vlist2 = VirtualCones() + self.vlist3 = VirtualCones() + self.vlist4 = VirtualCones() + self.vlists = [self.vlist1, self.vlist2, self.vlist3, self.vlist4] + + # button connections + self.openFiles.clicked.connect(self.read_dialog) + self.exitButton.clicked.connect(self.exit_button) + self.resetList1.clicked.connect(self.reset_list1) + self.resetList2.clicked.connect(self.reset_list2) + self.resetList3.clicked.connect(self.reset_list3) + self.resetList4.clicked.connect(self.reset_list4) + self.addPoint1.clicked.connect(self.add_point1) + self.delPoint1.clicked.connect(self.del_point1) + self.addPoint2.clicked.connect(self.add_point2) + self.delPoint2.clicked.connect(self.del_point2) + self.addPoint3.clicked.connect(self.add_point3) + self.delPoint3.clicked.connect(self.del_point3) + self.addPoint4.clicked.connect(self.add_point4) + self.delPoint4.clicked.connect(self.del_point4) + self.saveList1.clicked.connect(self.save_list1) + self.saveList2.clicked.connect(self.save_list2) + self.saveList3.clicked.connect(self.save_list3) + self.saveList4.clicked.connect(self.save_list4) + self.saveConfig.clicked.connect(self.save_config) + + # makes life easier, avoid redundant code + self.labels = [self.lblList1, self.lblList2, self.lblList3, self.lblList4] + self.lists = [self.listCones1, self.listCones2, self.listCones3, self.listCones4] + self.dists = [self.txtDist1, self.txtDist2, self.txtDist3, self.txtDist4] + self.saves = [self.saveList1, self.saveList2, self.saveList3, self.saveList4] + + for i in range(0, 4): + self.saves[i].hide() + + # txtLatitude + self.latitude = 0.0 + self.longitude = 0.0 + + # 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.num_points = 0 + self.my_thread = DataThread() + + def read_dialog(self): + filedialog = QFileDialog(self) + filedialog.setDirectory(QDir.currentPath()) + filedialog.setDefaultSuffix("csv") + filedialog.setNameFilter("CSV (*.csv);; all (*.*)") + filedialog.setFileMode(QFileDialog.ExistingFiles) + selected = filedialog.exec() + if selected: + self.load_cones( filedialog.selectedFiles()[0:4] ) + + def write_dialog(self): + filedialog = QFileDialog(self) + filedialog.setDirectory(QDir.currentPath()) + filedialog.setDefaultSuffix("csv") + filedialog.setNameFilter("CSV (*.csv);; all (*.*)") + selected = filedialog.exec() + if selected: + filename = filedialog.selectedFiles()[0] + return filename + return "" + + # slurp in files for each list + def load_cones(self, args): + count = 0 + for i in range(0, 4): + self.labels[i].setText("") + self.lists[i].clear() + self.saves[i].hide() + self.vlists[i].clr_cones() + + for arg in args: + try: + cones = VirtualCones() + cones.read_list(arg) + self.vlists[count] = cones + self.labels[count].setText(path.basename(arg)) + self.lists[count].clear() + + for i in range(0, cones.num_cones): + self.lists[count].addItem("%11.7f, %11.7f" % (cones.cones[i][0], cones.cones[i][1])) + self.lists[count].setCurrentRow(0) + count += 1 + self.show() + except Exception as e: + print(e) + sys.exit(1) + + 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() + + @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.latitude = lat + self.longitude = lon + +# 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)) + + for i in range(0, 4): + if self.lists[i].count() == 0: + continue + (cone, dist, trig) = self.vlists[i].check_list(lat, lon, heading, speed) + if trig: + self.lists[i].setCurrentRow(cone) + self.dists[i].setText("%6.2f" % (dist)) + + if self.logEnabled.isChecked(): + point = gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=elev) + point.time = datetime.now() + self.gpx_segment.points.append(point) + self.num_points += 1 + + self.show() + + def reset_list1(self): + self.listCones1.current_cone = 0 + self.lists[0].setCurrentRow(0) + + def reset_list2(self): + self.listCones2.current_cone = 0 + self.lists[1].setCurrentRow(0) + + def reset_list3(self): + self.listCones3.current_cone = 0 + self.lists[2].setCurrentRow(0) + + def reset_list4(self): + self.listCones4.current_cone = 0 + self.lists[3].setCurrentRow(0) + + def update_list(self, list): + self.vlists[list].clr_cones() + for i in range(0, self.lists[list].count()): + item = self.lists[list].item(i) + txt = item.text() + lat, lon = txt.split(", ") + self.vlists[list].add_cone(lat, lon, 3.0) + + def add_point(self, list): + self.lists[list].insertItem(self.lists[list].currentRow()+1, "%11.7f, %11.7f" % (self.latitude, self.longitude)) + self.saves[list].show() + self.update_list(list) + + def del_point(self, list): + listItems = self.lists[list].selectedItems() + if not listItems: return + for item in listItems: + self.lists[list].takeItem(self.lists[list].row(item)) + self.saves[list].show() + self.update_list(list) + + def save_list(self, list): + filename = self.labels[list].text() + if filename == "": + filename = self.write_dialog() + if filename == "": + return + filename = path.basename(filename) + self.labels[list].setText(filename) + self.vlists[list].save_list(filename) + self.saves[list].hide() + + def add_point1(self): + self.add_point(0) + + def del_point1(self): + self.del_point(0) + + def save_list1(self): + self.save_list(0) + + def add_point2(self): + self.add_point(1) + + def del_point2(self): + self.del_point(1) + + def save_list2(self): + self.save_list(1) + + def add_point3(self): + self.add_point(2) + + def del_point3(self): + self.del_point(2) + + def save_list3(self): + self.save_list(2) + + def add_point4(self): + self.add_point(3) + + def del_point4(self): + self.del_point(3) + + def save_list4(self): + self.save_list(3) + + def save_config(self): + with open('config.yaml', 'w') as f: + config = {} + if gpsd_host != "localhost": + config['gpsd_host'] = gpsd_host + config['lists'] = [] + for i in range(0, 4): + if self.labels[i].text() != "": + self.save_list(i) + config['lists'].append( self.labels[i].text() ) + f.write(yaml.dump(config)) + f.close() + + def save_log(self): + if self.num_points: + log = open(strftime("logs/way-%Y%m%d-%H%M.gpx"), 'w') + log.write(self.gpx.to_xml()) + log.close() + + def closeEvent(self, event): + self.stop_thread() + event.accept() + sys.exit(0) + + def exit_button(self): + self.stop_thread() + sys.exit(0) + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + window = ConesWindow() + window.show() + + config = [] + if path.isfile('config.yaml'): + with open('config.yaml') as f: + config = yaml.load(f, Loader=yaml.FullLoader) + if 'gpsd_host' in config: + gpsd_host = config['gpsd_host'] + + if len(sys.argv[1:]): + window.load_cones(sys.argv[1:]) + elif 'lists' in config: + window.load_cones(config['lists']) + + window.start_thread() + ret = app.exec_() + sys.exit(ret) From ef7a4ee33b2970ce7fba5e29ea1c9d71cc737c81 Mon Sep 17 00:00:00 2001 From: Neal Probert Date: Thu, 17 Jun 2021 12:42:57 -0400 Subject: [PATCH 2/2] Added insert buttons and range field --- gps_vcones/MainWindow.py | 50 ++++++++++++++++++++++++--------- gps_vcones/mainwindow.ui | 30 ++++++++++---------- gps_vcones/virtual_cones_gui.py | 35 +++++++++++++++++------ 3 files changed, 79 insertions(+), 36 deletions(-) diff --git a/gps_vcones/MainWindow.py b/gps_vcones/MainWindow.py index c73ee52..4d0ac1a 100644 --- a/gps_vcones/MainWindow.py +++ b/gps_vcones/MainWindow.py @@ -17,7 +17,7 @@ class Ui_MainWindow(object): def setupUi(self, MainWindow): if not MainWindow.objectName(): MainWindow.setObjectName(u"MainWindow") - MainWindow.resize(772, 445) + MainWindow.resize(772, 451) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.label_2 = QLabel(self.centralwidget) @@ -39,12 +39,12 @@ class Ui_MainWindow(object): self.txtLongitude.setFrameShadow(QFrame.Sunken) self.txtDist4 = QLabel(self.centralwidget) self.txtDist4.setObjectName(u"txtDist4") - self.txtDist4.setGeometry(QRect(690, 90, 70, 18)) + self.txtDist4.setGeometry(QRect(709, 90, 51, 20)) self.txtDist4.setFrameShape(QFrame.Panel) self.txtDist4.setFrameShadow(QFrame.Sunken) self.txtDist2 = QLabel(self.centralwidget) self.txtDist2.setObjectName(u"txtDist2") - self.txtDist2.setGeometry(QRect(310, 90, 70, 18)) + self.txtDist2.setGeometry(QRect(329, 90, 51, 20)) self.txtDist2.setFrameShape(QFrame.Panel) self.txtDist2.setFrameShadow(QFrame.Sunken) self.txtSpeed = QLabel(self.centralwidget) @@ -54,7 +54,7 @@ class Ui_MainWindow(object): self.txtSpeed.setFrameShadow(QFrame.Sunken) self.txtDist1 = QLabel(self.centralwidget) self.txtDist1.setObjectName(u"txtDist1") - self.txtDist1.setGeometry(QRect(120, 90, 70, 18)) + self.txtDist1.setGeometry(QRect(139, 90, 51, 20)) self.txtDist1.setFrameShape(QFrame.Panel) self.txtDist1.setFrameShadow(QFrame.Sunken) self.lblList2 = QLabel(self.centralwidget) @@ -94,7 +94,7 @@ class Ui_MainWindow(object): self.listCones1.setGeometry(QRect(10, 110, 180, 220)) self.txtDist3 = QLabel(self.centralwidget) self.txtDist3.setObjectName(u"txtDist3") - self.txtDist3.setGeometry(QRect(500, 90, 70, 18)) + self.txtDist3.setGeometry(QRect(519, 90, 51, 20)) self.txtDist3.setFrameShape(QFrame.Panel) self.txtDist3.setFrameShadow(QFrame.Sunken) self.txtHeading = QLabel(self.centralwidget) @@ -110,7 +110,7 @@ class Ui_MainWindow(object): self.openFiles.setGeometry(QRect(40, 40, 111, 32)) self.txtTime = QLabel(self.centralwidget) self.txtTime.setObjectName(u"txtTime") - self.txtTime.setGeometry(QRect(460, 10, 241, 20)) + self.txtTime.setGeometry(QRect(470, 10, 261, 20)) self.txtTime.setFrameShape(QFrame.Panel) self.txtTime.setFrameShadow(QFrame.Sunken) self.label_7 = QLabel(self.centralwidget) @@ -118,28 +118,28 @@ class Ui_MainWindow(object): self.label_7.setGeometry(QRect(390, 10, 49, 16)) self.logEnabled = QCheckBox(self.centralwidget) self.logEnabled.setObjectName(u"logEnabled") - self.logEnabled.setGeometry(QRect(170, 50, 181, 20)) + self.logEnabled.setGeometry(QRect(170, 50, 131, 20)) self.addPoint1 = QPushButton(self.centralwidget) self.addPoint1.setObjectName(u"addPoint1") - self.addPoint1.setGeometry(QRect(60, 340, 41, 32)) + self.addPoint1.setGeometry(QRect(60, 330, 41, 32)) self.delPoint1 = QPushButton(self.centralwidget) self.delPoint1.setObjectName(u"delPoint1") self.delPoint1.setGeometry(QRect(100, 340, 41, 32)) self.addPoint2 = QPushButton(self.centralwidget) self.addPoint2.setObjectName(u"addPoint2") - self.addPoint2.setGeometry(QRect(250, 340, 41, 32)) + self.addPoint2.setGeometry(QRect(250, 330, 41, 32)) self.delPoint2 = QPushButton(self.centralwidget) self.delPoint2.setObjectName(u"delPoint2") self.delPoint2.setGeometry(QRect(290, 340, 41, 32)) self.addPoint3 = QPushButton(self.centralwidget) self.addPoint3.setObjectName(u"addPoint3") - self.addPoint3.setGeometry(QRect(440, 340, 41, 32)) + self.addPoint3.setGeometry(QRect(440, 330, 41, 32)) self.delPoint3 = QPushButton(self.centralwidget) self.delPoint3.setObjectName(u"delPoint3") self.delPoint3.setGeometry(QRect(480, 340, 41, 32)) self.addPoint4 = QPushButton(self.centralwidget) self.addPoint4.setObjectName(u"addPoint4") - self.addPoint4.setGeometry(QRect(630, 340, 41, 32)) + self.addPoint4.setGeometry(QRect(630, 330, 41, 32)) self.delPoint4 = QPushButton(self.centralwidget) self.delPoint4.setObjectName(u"delPoint4") self.delPoint4.setGeometry(QRect(670, 340, 41, 32)) @@ -170,6 +170,24 @@ class Ui_MainWindow(object): self.saveConfig = QPushButton(self.centralwidget) self.saveConfig.setObjectName(u"saveConfig") self.saveConfig.setGeometry(QRect(340, 380, 84, 32)) + self.insPoint1 = QPushButton(self.centralwidget) + self.insPoint1.setObjectName(u"insPoint1") + self.insPoint1.setGeometry(QRect(60, 360, 41, 32)) + self.insPoint2 = QPushButton(self.centralwidget) + self.insPoint2.setObjectName(u"insPoint2") + self.insPoint2.setGeometry(QRect(250, 360, 41, 32)) + self.insPoint3 = QPushButton(self.centralwidget) + self.insPoint3.setObjectName(u"insPoint3") + self.insPoint3.setGeometry(QRect(440, 360, 41, 32)) + self.insPoint4 = QPushButton(self.centralwidget) + self.insPoint4.setObjectName(u"insPoint4") + self.insPoint4.setGeometry(QRect(630, 360, 41, 32)) + self.txtRange = QLineEdit(self.centralwidget) + self.txtRange.setObjectName(u"txtRange") + self.txtRange.setGeometry(QRect(310, 10, 41, 30)) + self.label_6 = QLabel(self.centralwidget) + self.label_6.setObjectName(u"label_6") + self.label_6.setGeometry(QRect(250, 20, 61, 16)) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") @@ -185,7 +203,7 @@ class Ui_MainWindow(object): # setupUi def retranslateUi(self, MainWindow): - MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"TOSCo Virtual Cones V1.0", None)) + MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"NTCNA GPS Virtual Cones V1.0", 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)) @@ -198,7 +216,7 @@ class Ui_MainWindow(object): self.lblList1.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) self.txtLatitude.setText(QCoreApplication.translate("MainWindow", u"0", None)) self.label_4.setText(QCoreApplication.translate("MainWindow", u"Heading:", None)) - self.label_5.setText(QCoreApplication.translate("MainWindow", u"TOSCo Virtual Cones", None)) + self.label_5.setText(QCoreApplication.translate("MainWindow", u"NTCNA GPS Virtual Cones", None)) self.exitButton.setText(QCoreApplication.translate("MainWindow", u"Exit", None)) self.lblList4.setText(QCoreApplication.translate("MainWindow", u"TextLabel", None)) self.txtDist3.setText(QCoreApplication.translate("MainWindow", u"0", None)) @@ -225,5 +243,11 @@ class Ui_MainWindow(object): self.resetList3.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) self.resetList4.setText(QCoreApplication.translate("MainWindow", u"Reset", None)) self.saveConfig.setText(QCoreApplication.translate("MainWindow", u"SaveConfig", None)) + self.insPoint1.setText(QCoreApplication.translate("MainWindow", u"Ins", None)) + self.insPoint2.setText(QCoreApplication.translate("MainWindow", u"Ins", None)) + self.insPoint3.setText(QCoreApplication.translate("MainWindow", u"Ins", None)) + self.insPoint4.setText(QCoreApplication.translate("MainWindow", u"Ins", None)) + self.txtRange.setText(QCoreApplication.translate("MainWindow", u"3.0", None)) + self.label_6.setText(QCoreApplication.translate("MainWindow", u"Range:", None)) # retranslateUi diff --git a/gps_vcones/mainwindow.ui b/gps_vcones/mainwindow.ui index 6df0fe8..ac3b8b4 100644 --- a/gps_vcones/mainwindow.ui +++ b/gps_vcones/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 772 - 445 + 451 @@ -85,10 +85,10 @@ - 690 + 709 90 - 70 - 18 + 51 + 20 @@ -104,10 +104,10 @@ - 310 + 329 90 - 70 - 18 + 51 + 20 @@ -142,10 +142,10 @@ - 120 + 139 90 - 70 - 18 + 51 + 20 @@ -293,10 +293,10 @@ - 500 + 519 90 - 70 - 18 + 51 + 20 @@ -357,9 +357,9 @@ - 460 + 470 10 - 241 + 261 20 diff --git a/gps_vcones/virtual_cones_gui.py b/gps_vcones/virtual_cones_gui.py index 00c8b47..e9b1317 100755 --- a/gps_vcones/virtual_cones_gui.py +++ b/gps_vcones/virtual_cones_gui.py @@ -232,12 +232,16 @@ class ConesWindow(MainWindow): self.resetList4.clicked.connect(self.reset_list4) self.addPoint1.clicked.connect(self.add_point1) self.delPoint1.clicked.connect(self.del_point1) + self.insPoint1.clicked.connect(self.ins_point1) self.addPoint2.clicked.connect(self.add_point2) self.delPoint2.clicked.connect(self.del_point2) + self.insPoint2.clicked.connect(self.ins_point2) self.addPoint3.clicked.connect(self.add_point3) self.delPoint3.clicked.connect(self.del_point3) + self.insPoint3.clicked.connect(self.ins_point3) self.addPoint4.clicked.connect(self.add_point4) self.delPoint4.clicked.connect(self.del_point4) + self.insPoint4.clicked.connect(self.ins_point4) self.saveList1.clicked.connect(self.save_list1) self.saveList2.clicked.connect(self.save_list2) self.saveList3.clicked.connect(self.save_list3) @@ -359,7 +363,10 @@ class ConesWindow(MainWindow): (cone, dist, trig) = self.vlists[i].check_list(lat, lon, heading, speed) if trig: self.lists[i].setCurrentRow(cone) - self.dists[i].setText("%6.2f" % (dist)) + if dist < 10000.0: + self.dists[i].setText("%6.1f" % (dist)) + else + self.dists[i].setText("OOR") if self.logEnabled.isChecked(): point = gpxpy.gpx.GPXTrackPoint(lat, lon, elevation=elev) @@ -391,10 +398,10 @@ class ConesWindow(MainWindow): item = self.lists[list].item(i) txt = item.text() lat, lon = txt.split(", ") - self.vlists[list].add_cone(lat, lon, 3.0) + self.vlists[list].add_cone(lat, lon, float(self.txtRange.text())) - def add_point(self, list): - self.lists[list].insertItem(self.lists[list].currentRow()+1, "%11.7f, %11.7f" % (self.latitude, self.longitude)) + def add_point(self, list, add): + self.lists[list].insertItem(self.lists[list].currentRow()+add, "%11.7f, %11.7f" % (self.latitude, self.longitude)) self.saves[list].show() self.update_list(list) @@ -418,38 +425,50 @@ class ConesWindow(MainWindow): self.saves[list].hide() def add_point1(self): - self.add_point(0) + self.add_point(0, 1) def del_point1(self): self.del_point(0) + + def ins_point1(self): + self.add_point(0, 0) def save_list1(self): self.save_list(0) def add_point2(self): - self.add_point(1) + self.add_point(1, 1) def del_point2(self): self.del_point(1) + def ins_point2(self): + self.add_point(1, 0) + def save_list2(self): self.save_list(1) def add_point3(self): - self.add_point(2) + self.add_point(2, 1) def del_point3(self): self.del_point(2) + def ins_point3(self): + self.add_point(2, 0) + def save_list3(self): self.save_list(2) def add_point4(self): - self.add_point(3) + self.add_point(3, 1) def del_point4(self): self.del_point(3) + def ins_point4(self): + self.add_point(3, 0) + def save_list4(self): self.save_list(3)