diff --git a/headunit-desktop.pro b/headunit-desktop.pro index 9a7d6e4..aa86236 100644 --- a/headunit-desktop.pro +++ b/headunit-desktop.pro @@ -10,6 +10,7 @@ SUBDIRS = \ modules/welle-io \ # modules/navit \ # modules/fm-radio \ + modules/gpsd \ modules/hvac \ modules/sample \ modules/rpi \ diff --git a/modules/gpsd/config.json b/modules/gpsd/config.json new file mode 100644 index 0000000..7e446db --- /dev/null +++ b/modules/gpsd/config.json @@ -0,0 +1,41 @@ +{ + "name":"GPSD", + "label": "GPS", + "icon": "qrc:/GPSD/icons/icons8-satellite-100.png", + "config": { + "type": "loader", + "source": "qrc:/GPSD/settings.qml", + "settings": [ + { + "name": "host", + "type": "string", + "defaultValue": "127.0.0.1" + }, + { + "name": "port", + "type": "uint", + "defaultValue": 0 + }, + { + "name": "fence1pt1", + "type": "string", + "defaultValue": "-37.81377659455088, 144.9573499708269" + }, + { + "name": "fence1pt2", + "type": "string", + "defaultValue": "-37.81959883100442, 144.9600354951891" + }, + { + "name": "fence1pt3", + "type": "string", + "defaultValue": "-37.817398930901135, 144.96748872616857" + }, + { + "name": "fence1pt4", + "type": "string", + "defaultValue": "-37.81167088347648, 144.96478490480058" + } + ] + } +} diff --git a/modules/gpsd/gpsd.pro b/modules/gpsd/gpsd.pro new file mode 100644 index 0000000..1667295 --- /dev/null +++ b/modules/gpsd/gpsd.pro @@ -0,0 +1,27 @@ +TEMPLATE = lib +CONFIG += c++11 plugin link_pkgconfig +QT += quick +LIBS += -lgps +TARGET = $$qtLibraryTarget(gpsd-plugin) +DEFINES += QT_DEPRECATED_WARNINGS +INCLUDEPATH += $${PWD}/../../includes + +DESTDIR = $${OUT_PWD}/../../plugins + +include("../../config.pri") + +target.path = $${PREFIX}/plugins + +INSTALLS += target + +SOURCES += \ + gpsdplugin.cpp + +HEADERS += \ + gpsdplugin.h + +DISTFILES += \ + config.json + +RESOURCES += \ + qml.qrc diff --git a/modules/gpsd/gpsdplugin.cpp b/modules/gpsd/gpsdplugin.cpp new file mode 100644 index 0000000..4750df8 --- /dev/null +++ b/modules/gpsd/gpsdplugin.cpp @@ -0,0 +1,243 @@ +#include "gpsdplugin.h" +#define _USE_MATH_DEFINES +#include + +const int m_fence = 0; //only one fence +double fences[1][4][2]; // 1 Fence, 4 Points = 2 Measurements (Lat/Lon) + +GPSDPlugin::GPSDPlugin(QObject *parent) : QObject (parent) +{ + //m_pluginSettings.events = QStringList() << "MediaInput::position"; +} + +void GPSDPlugin::init() { + startWorker(); +} + +void GPSDPlugin::handleMode(const int& result) { + if(this->m_mode != result) { + this->m_mode = result; + emit modeUpdated(); + emit message("Mode", this->m_mode); + } +} + +void GPSDPlugin::handleLocation(const double& lat, const double& lon, const bool& inFence) { + if((this->m_latitude != lat || this->m_longitude != lon) && this->m_mode >= MODE_2D) { + this->m_latitude = lat; + this->m_longitude = lon; + emit latitudeUpdated(); + emit longitudeUpdated(); + + QVariantMap location; + location.insert("latitude", this->m_latitude); + location.insert("longitude", this->m_longitude); + location.insert("track", this->m_track); + location.insert("speed", this->m_speed); + location.insert("altitude", this->m_altitude); + location.insert("epx", this->m_epx); + location.insert("epy", this->m_epy); + location.insert("eph", this->m_eph); + emit message("Location", location); + + if(this->m_inFence != inFence) { + this->m_inFence = inFence; + emit inFenceUpdated(); + emit message("InFence", inFence); + } + } +} + +void GPSDPlugin::handleHERR(const double& epx, const double &epy, const double &eph) { + if(this->m_epx != epx) { + this->m_epx = epx; + emit epxUpdated(); + } + if(this->m_epy != epy) { + this->m_epy = epy; + emit epyUpdated(); + } + if(this->m_eph != eph) { + this->m_eph = eph; + emit ephUpdated(); + } +} + +void GPSDPlugin::handleTrack(const double& track) { + if(this->m_track != track) { + this->m_track = track; + emit trackUpdated(); + } +} + +void GPSDPlugin::handleSpeed(const double& speed) { + if(this->m_speed != speed) { + this->m_speed = speed; + emit speedUpdated(); + } +} + +void GPSDPlugin::handleAltitude(const double& altitude) { + if(this->m_altitude != altitude) { + this->m_altitude = altitude; + emit altitudeUpdated(); + } +} + +QObject *GPSDPlugin::getContextProperty(){ + return this; +} + +void GPSDPlugin::eventMessage(QString id, QVariant message) { +} + +GPSDPlugin::~GPSDPlugin() { + stopWorker(); +} + +void GPSDPlugin::stopWorker() { + workerThread.quit(); + workerThread.requestInterruption(); + workerThread.wait(); +} + +void GPSDPlugin::startWorker() { + m_host = m_settings.value("host").toString(); + m_port = m_settings.value("port").toUInt(); + + if (m_fence >= 0) { + //Get Fence Points + int fenceSize = sizeof(fences[m_fence])/sizeof(fences[m_fence][0]); + char buffer[11]; + for(int i = 0; i < fenceSize; i++) { + snprintf(buffer, 11, "fence%dpt%d", m_fence + 1, i + 1); + QString pt = m_settings.value(buffer).toString(); + QStringList pieces = pt.split(","); + if(pieces.length() == 2) { + fences[m_fence][i][0] = pieces.value(0).toDouble(); + fences[m_fence][i][1] = pieces.value(1).toDouble(); + } else { + fences[m_fence][i][0] = -91; //invalid latitude + fences[m_fence][i][1] = -181; //invalid longitude + } + } + } + + //Run the data colleciton in another thread + GPSDWorker* worker = new GPSDWorker; + worker->moveToThread(&workerThread); + connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); + connect(this, &GPSDPlugin::operate, worker, &GPSDWorker::connect); + connect(worker, &GPSDWorker::mode, this, &GPSDPlugin::handleMode); + connect(worker, &GPSDWorker::location, this, &GPSDPlugin::handleLocation); + connect(worker, &GPSDWorker::track, this, &GPSDPlugin::handleTrack); + connect(worker, &GPSDWorker::speed, this, &GPSDPlugin::handleSpeed); + connect(worker, &GPSDWorker::altitude, this, &GPSDPlugin::handleAltitude); + connect(worker, &GPSDWorker::herr, this, &GPSDPlugin::handleHERR); + workerThread.start(); + operate(m_host, m_port, m_fence); + qDebug() << "GPSD: Started worker thread: " << workerThread.isRunning(); +} + +void GPSDPlugin::settingsChanged(const QString &key, const QVariant &){ + stopWorker(); + startWorker(); + if(key == "host"){ + } + else if (key == "port") { + } +} + +void GPSDPlugin::PrintString(const char *message) { + qDebug() << "GPSD DEBUG : " << message; +} + +bool GPSDWorker::pointInPolygon(double lat, double lon) { + if (m_fence < 0) return false; + + int fenceSize = sizeof(fences[m_fence])/sizeof(fences[m_fence][0]); + double vectors[fenceSize][2]; + + for(int i = 0; i < fenceSize; i++){ + if(fences[m_fence][i][0] < -90 || fences[m_fence][i][1] < -180) { + fenceSize = i; + break; + } + vectors[i][0] = fences[m_fence][i][0] - lat; + vectors[i][1] = fences[m_fence][i][1] - lon; + } + + double angle = 0; + double num, den; + + for(int i = 0; i < fenceSize; i++){ + num = (vectors[i%fenceSize][0])*(vectors[(i+1)%fenceSize][0])+ (vectors[i%fenceSize][1])*(vectors[(i+1)%fenceSize][1]); + den = (sqrt(pow(vectors[i%fenceSize][0],2) + pow(vectors[i%fenceSize][1],2)))*(sqrt(pow(vectors[(i+1)%fenceSize][0],2) + pow(vectors[(i+1)%fenceSize][1],2))); + angle = angle + (180*acos(num/den)/M_PI); + } + + return (angle > 355 && angle < 365); +} + +void GPSDWorker::procData(struct gps_data_t * gps) { + if (gps->set & MODE_SET) { + emit mode(gps->fix.mode); + if(gps->fix.mode >= MODE_2D && gps->set & LATLON_SET) + emit location(gps->fix.latitude, gps->fix.longitude, pointInPolygon(gps->fix.latitude, gps->fix.longitude)); + } + + if(gps->set & TRACK_SET) + emit track(gps->fix.track); + + if(gps->set & ALTITUDE_SET) + emit altitude(gps->fix.altitude); + + if(gps->set & SPEED_SET) + emit speed(gps->fix.speed); + + if(gps->set & HERR_SET) + emit herr(gps->fix.epx, gps->fix.epy, gps->fix.eph); + +} + +void GPSDWorker::getData(std::string host, uint32_t port) { + gpsmm gps_rec(host.c_str(), DEFAULT_GPSD_PORT); + + if(port > 0 && port <= 65535) { + char buffer[6]; + snprintf(buffer, 6, "%d", port); + gpsmm gps_rec(host.c_str(), buffer); + } + + if (gps_rec.stream(WATCH_ENABLE|WATCH_JSON) == NULL) { + qDebug() << "GPSD: No GPSD running.\n"; + return; + } + + while(stopClient == false && QThread::currentThread()->isInterruptionRequested() == false) { + struct gps_data_t* newdata; + + if (!gps_rec.waiting(5000000)) + continue; + + if ((newdata = gps_rec.read()) == NULL) { + qDebug() << "GPSD: Read error\n"; + QThread::sleep(1); + } else { + procData(newdata); + } + } +} + +void GPSDWorker::connect(QString host, quint32 port) { + stopClient = false; + getData(host.toStdString(), port); +} + +void GPSDWorker::disconnect() { + stopClient = true; +} + +GPSDWorker::~GPSDWorker() { + stopClient = true; +} diff --git a/modules/gpsd/gpsdplugin.h b/modules/gpsd/gpsdplugin.h new file mode 100644 index 0000000..5e78cc4 --- /dev/null +++ b/modules/gpsd/gpsdplugin.h @@ -0,0 +1,112 @@ +#ifndef GPSDPLUGIN_H +#define GPSDPLUGIN_H + +#include +#include +#include +#include +#include +#include +#include + +#include "libgpsmm.h" + +using namespace std; + +class GPSDPlugin : public QObject, PluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.aselafernando.headunit.gpsd" FILE "config.json") + Q_INTERFACES(PluginInterface) + + //Q_PROPERTY(bool connected MEMBER m_connected NOTIFY connectedUpdated) + Q_PROPERTY(int mode MEMBER m_mode NOTIFY modeUpdated) + Q_PROPERTY(double longitude MEMBER m_longitude NOTIFY longitudeUpdated) + Q_PROPERTY(double latitude MEMBER m_latitude NOTIFY latitudeUpdated) + Q_PROPERTY(bool inFence MEMBER m_inFence NOTIFY inFenceUpdated) + Q_PROPERTY(double track MEMBER m_track NOTIFY trackUpdated) + Q_PROPERTY(double speed MEMBER m_speed NOTIFY speedUpdated) + Q_PROPERTY(double altitude MEMBER m_altitude NOTIFY altitudeUpdated) + Q_PROPERTY(double epx MEMBER m_epx NOTIFY epxUpdated) + Q_PROPERTY(double epy MEMBER m_epy NOTIFY epyUpdated) + Q_PROPERTY(double eph MEMBER m_eph NOTIFY ephUpdated) + +public: + explicit GPSDPlugin(QObject *parent = nullptr); + ~GPSDPlugin(); + + void init() override; + QObject *getContextProperty() override; + void PrintString(const char * message); + +public slots: + void eventMessage(QString id, QVariant message) override; + void handleMode(const int& result); + void handleLocation(const double& lat, const double& lon, const bool& inFence); + void handleTrack(const double& track); + void handleSpeed(const double& speed); + void handleAltitude(const double& altitude); + void handleHERR(const double& epx, const double& epy, const double& eph); + +signals: + void message(QString id, QVariant message); + void action(QString id, QVariant message); + void operate(QString host, quint32 port, int fence); + void modeUpdated(); + void longitudeUpdated(); + void latitudeUpdated(); + void inFenceUpdated(); + void trackUpdated(); + void speedUpdated(); + void altitudeUpdated(); + void epxUpdated(); + void epyUpdated(); + void ephUpdated(); + +private slots: + void settingsChanged(const QString &key, const QVariant &value); + +private: + QString m_host = ""; + quint32 m_port = 0; + QThread workerThread; + int m_mode = 0; + double m_latitude = -91; + double m_longitude = -181; + bool m_inFence = false; + double m_track = 0; + double m_speed = 0; + double m_altitude = 0; + double m_epx = 0; + double m_epy = 0; + double m_eph = 0; + void stopWorker(); + void startWorker(); + +}; + +class GPSDWorker : public QObject { + Q_OBJECT + +private: + void getData(std::string host, uint32_t port); + void procData(struct gps_data_t * gps); + bool stopClient = false; + bool pointInPolygon(double lat, double lon); + ~GPSDWorker(); + +public slots: + void connect(QString host, quint32 port); + void disconnect(); + +signals: + void mode(const int& result); + void location(const double& latitude, const double& longitude, const bool& inFence); + void track(const double& track); + void speed(const double& speed); + void altitude(const double& altitude); + void herr(const double& epx, const double& epy, const double& eph); +}; + + +#endif // GPSDPLUGIN_H diff --git a/modules/gpsd/icons/icons8-satellite-100.png b/modules/gpsd/icons/icons8-satellite-100.png new file mode 100644 index 0000000..1e95fc0 Binary files /dev/null and b/modules/gpsd/icons/icons8-satellite-100.png differ diff --git a/modules/gpsd/qml.qrc b/modules/gpsd/qml.qrc new file mode 100644 index 0000000..0da8083 --- /dev/null +++ b/modules/gpsd/qml.qrc @@ -0,0 +1,6 @@ + + + settings.qml + icons/icons8-satellite-100.png + + diff --git a/modules/gpsd/settings.qml b/modules/gpsd/settings.qml new file mode 100644 index 0000000..9b7226e --- /dev/null +++ b/modules/gpsd/settings.qml @@ -0,0 +1,121 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import HUDTheme 1.0 +import HUDSettingsPage 1.0 +import QtQuick.Layouts 1.3 + +ThemeRoot { + id:__root + + Flickable { + anchors.fill: parent + contentHeight: column.height + flickableDirection: Flickable.VerticalFlick + ScrollBar.vertical: ThemeScrollBar { } + Column { + id: column + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 0 + + SettingsPageItemTextfield { + id: host + width: parent.width + label: "Host" + onValueChanged: { + pluginSettings.host = value + } + value : pluginSettings.host + } + + SettingsPageItemTextfield { + id: port + width: parent.width + label: "Port" + onValueChanged: { + pluginSettings.port = value + } + value : pluginSettings.port + } + + SettingsPageItemTextfield { + id: current_mode + width: parent.width + label: "Current Fix Mode" + value : pluginContext.mode + } + + SettingsPageItemTextfield { + id: current_latitude + width: parent.width + label: "Current Latitude" + value : pluginContext.latitude + } + + SettingsPageItemTextfield { + id: current_longitude + width: parent.width + label: "Current Longitude" + value : pluginContext.longitude + } + + SettingsPageItemTextfield { + id: current_inFence + width: parent.width + label: "Currently inside fence?" + value : pluginContext.inFence + } + + SettingsPageItemTextfield { + id: fence1pt1 + width: parent.width + label: "Fence 1 Point 1" + onValueChanged: { + pluginSettings.fence1pt1 = value + } + value : pluginSettings.fence1pt1 + } + + SettingsPageItemTextfield { + id: fence1pt2 + width: parent.width + label: "Fence 1 Point 2" + onValueChanged: { + pluginSettings.fence1pt2 = value + } + value : pluginSettings.fence1pt2 + } + + SettingsPageItemTextfield { + id: fence1pt3 + width: parent.width + label: "Fence 1 Point 3" + onValueChanged: { + pluginSettings.fence1pt3 = value + } + value : pluginSettings.fence1pt3 + } + + SettingsPageItemTextfield { + id: fence1pt4 + width: parent.width + label: "Fence 1 Point 4" + onValueChanged: { + pluginSettings.fence1pt4 = value + } + value : pluginSettings.fence1pt4 + } + + SettingsPageItemHeader{ + width: parent.width + } + + } + } +} + +/*##^## +Designer { + D{i:0;autoSize:true;height:480;width:640} +} +##^##*/