From c517f8fb9a996b8da85493578997d014c01b7573 Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sun, 17 Dec 2023 04:06:15 -0500 Subject: [PATCH 01/12] Basic ADSB Structure Refactor --- QOpenHD.pro | 8 + app/adsb/adsb.cpp | 416 +++++++++++++++++ app/adsb/adsb.h | 83 ++++ app/adsb/adsbvehicle.cpp | 98 ++++ app/adsb/adsbvehicle.h | 114 +++++ app/adsb/adsbvehiclemanager.cpp | 647 +++++++++++++++++++++++++++ app/adsb/adsbvehiclemanager.h | 167 +++++++ app/adsb/qmlobjectlistmodel.cpp | 300 +++++++++++++ app/adsb/qmlobjectlistmodel.h | 86 ++++ app/main.cpp | 15 + app/telemetry/models/markermodel.cpp | 215 +++++++++ app/telemetry/models/markermodel.h | 89 ++++ app/telemetry/telemetry.pri | 2 + 13 files changed, 2240 insertions(+) create mode 100644 app/adsb/adsb.cpp create mode 100644 app/adsb/adsb.h create mode 100644 app/adsb/adsbvehicle.cpp create mode 100644 app/adsb/adsbvehicle.h create mode 100644 app/adsb/adsbvehiclemanager.cpp create mode 100644 app/adsb/adsbvehiclemanager.h create mode 100644 app/adsb/qmlobjectlistmodel.cpp create mode 100644 app/adsb/qmlobjectlistmodel.h create mode 100644 app/telemetry/models/markermodel.cpp create mode 100644 app/telemetry/models/markermodel.h diff --git a/QOpenHD.pro b/QOpenHD.pro index b8a5680fd..6ad929c31 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -101,6 +101,10 @@ LinuxBuild { # All Generic files / files that literally have 0!! dependencies other than qt SOURCES += \ + app/adsb/adsb.cpp \ + app/adsb/adsbvehicle.cpp \ + app/adsb/adsbvehiclemanager.cpp \ + app/adsb/qmlobjectlistmodel.cpp \ app/logging/hudlogmessagesmodel.cpp \ app/logging/logmessagesmodel.cpp \ app/util/mousehelper.cpp \ @@ -111,6 +115,10 @@ SOURCES += \ app/main.cpp \ HEADERS += \ + app/adsb/adsb.h \ + app/adsb/adsbvehicle.h \ + app/adsb/adsbvehiclemanager.h \ + app/adsb/qmlobjectlistmodel.h \ app/common/util_fs.h \ app/common/StringHelper.hpp \ app/common/TimeHelper.hpp \ diff --git a/app/adsb/adsb.cpp b/app/adsb/adsb.cpp new file mode 100644 index 000000000..90a1c684d --- /dev/null +++ b/app/adsb/adsb.cpp @@ -0,0 +1,416 @@ +#include "adsb.h" +#include +#include +#include + +#ifndef __windows__ +#include +#endif + +#include +#include +#include + +#include + +#include +#include + + +#include +#include +#include +#include + +//#include +#include + + +#include +//#include +#include "../telemetry/models/markermodel.h" +#include +//#include "openhd.h" +//#include "localmessage.h" + +static Adsb* _instance = nullptr; + +Adsb* Adsb::instance() { + if (_instance == nullptr) { + _instance = new Adsb(); + } + return _instance; +} + +Adsb::Adsb(QObject *parent): QObject(parent) { + qDebug() << "Adsb::Adsb()"; +} + +void Adsb::onStarted() { + qDebug() << "------------------Adsb::onStarted()"; + +#if defined(__rasp_pi__) + groundAddress = "127.0.0.1"; +#endif + + auto markerModel = MarkerModel::instance(); + connect(this, &Adsb::addMarker, markerModel, &MarkerModel::addMarker); + connect(this, &Adsb::doneAddingMarkers, markerModel, &MarkerModel::doneAddingMarkers); + connect(this, &Adsb::removeAllMarkers, markerModel, &MarkerModel::removeAllMarkers); + + QNetworkAccessManager * manager = new QNetworkAccessManager(this); + + m_manager = manager; + + connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))) ; + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &Adsb::requestData); + // How frequently data is requested + timer->start(timer_interval); +} + +void Adsb::setGroundIP(QString address) { + groundAddress = address; +} + +/* +void Adsb::mapBoundsChanged(double map_lat, double map_lon) { + //center_lat= center_coord.latitude(); + //center_lon= center_coord.longitude(); + + // api_request_center_lat=setLatitude(center_lat); + //api_request_center_lon=setLongitude(center_lon); + + + need to limit the map bounds to a distance and + when map orients to drone it breaks api as it expects a box oriented north. + So defining our own bound box for the api... + + + auto adsb_distance_limit = settings.value("adsb_distance_limit").toInt(); + + + QGeoCoordinate qgeo_upper_left; + QGeoCoordinate qgeo_lower_right; + + qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); + qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); + + upperl_lat= QString::number(qgeo_upper_left.latitude()); + upperl_lon= QString::number(qgeo_upper_left.longitude()); + lowerr_lat= QString::number(qgeo_lower_right.latitude()); + lowerr_lon= QString::number(qgeo_lower_right.longitude()); + + + qDebug() << "Adsb::lower right=" << lowerr_lat << " " << lowerr_lon; + qDebug() << "Adsb::upper left=" << upperl_lat << " " << upperl_lon; + qDebug() << "Adsb::Center=" << center_lat << " " << center_lon; + + +} +*/ +void Adsb::setAdsbLat(double map_lat){ + m_adsb_lat=map_lat; + //qDebug() << "adsb_api_coord=" << m_adsb_api_coord; + emit adsbLatChanged(map_lat); +} + +void Adsb::setAdsbLon(double map_lon){ + m_adsb_lon=map_lon; + //qDebug() << "adsb_api_coord=" << m_adsb_api_coord; + emit adsbLatChanged(map_lon); +} + +void Adsb::requestData() { + + //qDebug() << "Adsb::requestData()"; + auto show_adsb = settings.value("show_adsb", false).toBool(); + + adsb_api_sdr = settings.value("adsb_api_sdr").toBool(); + + + if (adsb_api_sdr == true){ + //qDebug() << "timer 1"; + timer->stop(); + timer->start(1000); + + if (groundAddress.isEmpty()) { + // TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("No ADSB Ground Address", 4); + return; + } + + adsb_url= "http://"+groundAddress+":8080/data/aircraft.json"; + } + else { + //qDebug() << "timer 10"; + timer->stop(); + timer->start(10000); + setAdsbLat(m_map_lat); + setAdsbLon(m_map_lon); + // TODO REFORM THE API REQUEST + adsb_url= "test"; + //adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; + } + + if(show_adsb==false){ + emit removeAllMarkers(); + return; + } + //qDebug() << "URL REQUEST:" << adsb_url; + QNetworkRequest request; + QUrl api_request= adsb_url; + request.setUrl(api_request); + request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); + //qDebug() << "url=" << api_request; + QNetworkReply *reply = m_manager->get(request); +} + +void Adsb::processReply(QNetworkReply *reply){ + + QString callsign; + int contact; + double lat; + double lon; + int alt; + int track; + int velocity; + double vertical; + qreal distance; + + if (reply->error()) { + qDebug() << "ADSB request error!"; + qDebug() << reply->errorString(); + // TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB Reply Error", 4); + reply->deleteLater(); + return; + } + + QByteArray data = reply->readAll(); + + QJsonParseError errorPtr; + QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); + + if (doc.isNull()) { + qDebug() << "Parse failed"; + // TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB Parse Error", 4); + } + + if(doc.isNull()){ + qDebug()<<"Failed to create JSON doc."; + reply->deleteLater(); + return; + } + if(!doc.isObject()){ + qDebug()<<"JSON is not an object."; + reply->deleteLater(); + return; + } + + QJsonObject jsonObject = doc.object(); + + if(jsonObject.isEmpty()){ + qDebug()<<"JSON object is empty."; + reply->deleteLater(); + return; + } + + // --------------- PARSE FOR SDR --------------------------------- + + if (adsb_api_sdr == true){ + + QJsonArray array = jsonObject["aircraft"].toArray(); + + //QJsonArray array = doc.array(); + + if(array.isEmpty()){ + qDebug()<<"JSON array is empty."; + } + + //qDebug() << "MYARRAY COUNT=" << array.count(); + + int current_row=0; + int last_row=array.count(); + + emit removeAllMarkers(); + + if (last_row==0){ + //no markers to add.. + reply->deleteLater(); + return; + } + + + foreach (const QJsonValue & val, array){ + + callsign=val.toObject().value("flight").toString(); + if (callsign.length() == 0) { + callsign = "N/A"; + } + + //sdr defaults to imperial and something wacky for speed from dump1090 + + contact=val.toObject().value("seen_pos").toInt(); + lat=val.toObject().value("lat").toDouble(); + lon=val.toObject().value("lon").toDouble(); + alt=val.toObject().value("altitude").toInt(); + alt=alt*0.3048; + velocity=val.toObject().value("speed").toDouble(); + velocity=round(velocity*.51418); + track=val.toObject().value("track").toDouble(); + vertical=val.toObject().value("vert_rate").toDouble(); + vertical=round(vertical*0.3048); + + + + + //calculate distance from center of map so we can sort in marker model + distance = calculateKmDistance(m_map_lat, m_api_lon, lat, lon); + emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); + current_row=current_row+1; + + if (lat!=0 || lon!=0) { + /*dont evaluate marker if position msg is missing + above we are adding markers with 0 lat lon cuz we dont know the last row count at loop start + those "0 position" aircraft are being eliminated in mapcomponent + */ + evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); + } + /* + qDebug() << "callsign=" << callsign; + qDebug() << "last_contact=" << contact; + qDebug() << "lat=" << lat; + qDebug() << "lon=" << lon; + qDebug() << "alt=" << alt; + qDebug() << "velocity=" << velocity; + qDebug() << "track=" << track; + qDebug() << "vertical=" << vertical; + qDebug() << "distance=" << distance; + + qDebug() << "----------------------------------------------------------"; +*/ + + + + } + + } + // --------------- PARSE FOR API --------------------------------- + else { + + QJsonValue value = jsonObject.value("states"); + QJsonArray array = value.toArray(); + + //qDebug() << "MYARRAY COUNT=" << array.count(); + + int current_row=0; + int last_row=array.count(); + + QString callsign; + int contact; + double lat; + double lon; + int alt; + int track; + int velocity; + double vertical; + qreal distance; + + emit removeAllMarkers(); + + if (last_row==0){ + //no markers to add.. either the api is not happy (too zoomed out) or no traffic to report + reply->deleteLater(); + return; + } + + foreach (const QJsonValue & v, array){ + QJsonArray innerarray = v.toArray(); + + callsign=innerarray[1].toString(); + if (callsign.length() == 0) { + callsign = "N/A"; + } + contact=innerarray[4].toInt(); + lat=innerarray[6].toDouble(); + lon=innerarray[5].toDouble(); + alt=innerarray[7].toDouble(); + velocity=innerarray[9].toDouble(); + track=innerarray[10].toDouble(); + vertical=innerarray[11].toDouble(); + + //calculate distance from center of map so we can sort in marker model + + distance = calculateKmDistance(m_map_lat, m_map_lon, lat, lon); + emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); + + evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); + + current_row=current_row+1; + + /* + qDebug() << "callsign=" << innerarray[1].toString(); + qDebug() << "last_contact=" << innerarray[4].toInt(); + qDebug() << "lat=" << innerarray[6].toDouble(); + qDebug() << "lon=" << innerarray[5].toDouble(); + qDebug() << "alt=" << innerarray[7].toDouble(); + qDebug() << "velocity=" << innerarray[9].toDouble(); + qDebug() << "track=" << innerarray[10].toDouble(); + qDebug() << "vertical=" << innerarray[11].toDouble(); + qDebug() << "distance=" << distance; + qDebug() << "----------------------------------------------------------"; +*/ + } + + } + //emit doneAddingMarkers(); + reply->deleteLater(); +} + +void Adsb::evaluateTraffic(QString traffic_callsign, + int traffic_contact, + double traffic_lat, + double traffic_lon, + double traffic_alt, + double traffic_velocity, + double traffic_track, + double traffic_vertical, + double traffic_distance) { + + /* + * Centralise traffic threat detection here. Once threat is detected it should be + * labled and then sent over to the adsb widget + * + * need to calculate azimuth and bearing of any threats so that it can be shared + * and depicted in the adsb widget + */ + + // TODO this is not defined ???????? + int drone_alt = m_msl_alt; + + if (traffic_alt - drone_alt < 300 && traffic_distance < 2) { + // TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("Aircraft Traffic", 3); + } else if (traffic_alt - drone_alt < 500 && traffic_distance < 5) { + // TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("Aircraft Traffic", 4); + } +} + +int Adsb::calculateKmDistance(double lat_1, double lon_1, + double lat_2, double lon_2) { + + double latDistance = qDegreesToRadians(lat_1 - lat_2); + double lngDistance = qDegreesToRadians(lon_1 - lon_2); + + double a = qSin(latDistance / 2) * qSin(latDistance / 2) + + qCos(qDegreesToRadians(m_map_lat)) * qCos(qDegreesToRadians(lat_2)) + * qSin(lngDistance / 2) * qSin(lngDistance / 2); + + double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); + int distance=radius_earth_km * c; + return distance; +} diff --git a/app/adsb/adsb.h b/app/adsb/adsb.h new file mode 100644 index 000000000..d35ff2e33 --- /dev/null +++ b/app/adsb/adsb.h @@ -0,0 +1,83 @@ +#ifndef ADSB_H +#define ADSB_H + +#include +#include + +//#include "constants.h" + +#include +#include +#include + +#include +#include +#include +#include + +//#include "util.h" +#include "common/openhd-util.hpp" + +#include "../telemetry/models/markermodel.h" + + +class Adsb: public QObject { + Q_OBJECT + +public: + explicit Adsb(QObject *parent = nullptr); + static Adsb* instance(); + + Q_PROPERTY(double adsb_lat MEMBER m_adsb_lat WRITE setAdsbLat NOTIFY adsbLatChanged); + void setAdsbLat(double map_lat); + Q_PROPERTY(double adsb_lon MEMBER m_adsb_lon WRITE setAdsbLon NOTIFY adsbLonChanged); + void setAdsbLon(double map_lon); + + +signals: + void addMarker(int current_row, int total_rows, const Traffic &traffic); + void doneAddingMarkers(); + void removeAllMarkers(); + void adsbLatChanged(double map_lat); + void adsbLonChanged(double map_lon); + +public slots: + void onStarted(); + //from old api where you eneded a bound rectangle + //void mapBoundsChanged(double map_lat, double map_lon); + + Q_INVOKABLE void setGroundIP(QString address); + +private slots: + void processReply(QNetworkReply *reply); + void requestData(); + int calculateKmDistance(double center_lat, double center_lon, + double marker_lat, double marker_lon); + void evaluateTraffic(QString callsign,int contact,double lat,double lon,double alt,double velocity, + double track,double vertical,double distance); + +private: + int m_msl_alt; + double m_api_lat; + double m_api_lon; + double m_adsb_lat; + double m_adsb_lon; + QNetworkAccessManager * m_manager; + //QString upperl_lat; + //QString upperl_lon; + //QString lowerr_lat; + //QString lowerr_lon; + double m_map_lat; + double m_map_lon; + QSettings settings; + double radius_earth_km = 6371; + QString adsb_url; + int timer_interval = 1000; //get reset later if api or sdr selected + QTimer *timer; + bool adsb_api_sdr; + QString groundAddress; +}; + + + +#endif diff --git a/app/adsb/adsbvehicle.cpp b/app/adsb/adsbvehicle.cpp new file mode 100644 index 000000000..3d2b2eec9 --- /dev/null +++ b/app/adsb/adsbvehicle.cpp @@ -0,0 +1,98 @@ +/**************************************************************************** + * + * This file has been ported from QGroundControl project + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "adsbvehicle.h" + +#include +#include + +ADSBVehicle::ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent) + : QObject (parent) + , _icaoAddress (vehicleInfo.icaoAddress) + , _altitude (qQNaN()) + , _heading (qQNaN()) + , _alert (false) +{ + update(vehicleInfo); +} + +void ADSBVehicle::update(const VehicleInfo_t& vehicleInfo) +{ + if (_icaoAddress != vehicleInfo.icaoAddress) { + qDebug() << "ICAO address mismatch expected:actual" << _icaoAddress << vehicleInfo.icaoAddress; + return; + } + if (vehicleInfo.availableFlags & CallsignAvailable) { + if (vehicleInfo.callsign != _callsign) { + _callsign = vehicleInfo.callsign; + emit callsignChanged(); + } + } + if (vehicleInfo.availableFlags & LocationAvailable) { + if (_vehicle_lat != vehicleInfo.vehicle_lat || _vehicle_lon != vehicleInfo.vehicle_lon) { + _vehicle_lat = vehicleInfo.vehicle_lat; + _vehicle_lon = vehicleInfo.vehicle_lon; + emit vehicleLatChanged(); + emit vehicleLonChanged(); + } + } + if (vehicleInfo.availableFlags & AltitudeAvailable) { + if (!(qIsNaN(vehicleInfo.altitude) && qIsNaN(_altitude)) && !qFuzzyCompare(vehicleInfo.altitude, _altitude)) { + _altitude = vehicleInfo.altitude; + emit altitudeChanged(); + } + } + if (vehicleInfo.availableFlags & HeadingAvailable) { + if (!(qIsNaN(vehicleInfo.heading) && qIsNaN(_heading)) && !qFuzzyCompare(vehicleInfo.heading, _heading)) { + _heading = vehicleInfo.heading; + emit headingChanged(); + } + } + if (vehicleInfo.availableFlags & AlertAvailable) { + if (vehicleInfo.alert != _alert) { + _alert = vehicleInfo.alert; + emit alertChanged(); + } + } + if (vehicleInfo.availableFlags & VelocityAvailable) { + if (vehicleInfo.velocity != _velocity) { + _velocity = vehicleInfo.velocity; + emit velocityChanged(); + } + } + if (vehicleInfo.availableFlags & VerticalVelAvailable) { + if (vehicleInfo.verticalVel != _verticalVel) { + _verticalVel = vehicleInfo.verticalVel; + emit verticalVelChanged(); + } + } + if (vehicleInfo.availableFlags & LastContactAvailable) { + if (vehicleInfo.lastContact != _lastContact) { + _lastContact = vehicleInfo.lastContact; + emit lastContactChanged(); + } + } + if (vehicleInfo.availableFlags & DistanceAvailable) { + if (vehicleInfo.distance != _distance) { + _distance = vehicleInfo.distance; + emit distanceChanged(); + } + } + _lastUpdateTimer.restart(); +} + +bool ADSBVehicle::expired() +{ + return _lastUpdateTimer.hasExpired(expirationTimeoutMs); +} + +bool ADSBVehicle::tooFar() +{ + return _too_far; +} diff --git a/app/adsb/adsbvehicle.h b/app/adsb/adsbvehicle.h new file mode 100644 index 000000000..6e806d7d0 --- /dev/null +++ b/app/adsb/adsbvehicle.h @@ -0,0 +1,114 @@ +/**************************************************************************** + * + * This file has been ported from QGroundControl project + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include +//#include +#include + +class ADSBVehicle : public QObject +{ + Q_OBJECT + +public: + enum { + CallsignAvailable = 1 << 1, + LocationAvailable = 1 << 2, + AltitudeAvailable = 1 << 3, + HeadingAvailable = 1 << 4, + AlertAvailable = 1 << 5, + VelocityAvailable = 1 << 6, + VerticalVelAvailable = 1 << 7, + LastContactAvailable = 1 << 8, + DistanceAvailable = 1 << 8 + }; + + typedef struct { + uint32_t icaoAddress; // Required + QString callsign; + double vehicle_lat; + double vehicle_lon; + double altitude; + double velocity; + double heading; + int alert; + uint32_t availableFlags; + int lastContact; + double verticalVel; + double distance; + } VehicleInfo_t; + + ADSBVehicle(const VehicleInfo_t& vehicleInfo, QObject* parent); + + Q_PROPERTY(int icaoAddress READ icaoAddress CONSTANT) + Q_PROPERTY(QString callsign READ callsign NOTIFY callsignChanged) + Q_PROPERTY(double vehicle_lat READ vehicle_lat NOTIFY vehicleLatChanged) + Q_PROPERTY(double vehicle_lon READ vehicle_lon NOTIFY vehicleLonChanged) + Q_PROPERTY(double altitude READ altitude NOTIFY altitudeChanged) // NaN for not available + Q_PROPERTY(double velocity READ velocity NOTIFY velocityChanged) // NaN for not available + Q_PROPERTY(double heading READ heading NOTIFY headingChanged) // NaN for not available + Q_PROPERTY(int alert READ alert NOTIFY alertChanged) // Collision path + Q_PROPERTY(int lastContact READ lastContact NOTIFY lastContactChanged) + Q_PROPERTY(double verticalVel READ verticalVel NOTIFY verticalVelChanged) + Q_PROPERTY(double distance READ distance NOTIFY distanceChanged) + + int icaoAddress (void) const { return static_cast(_icaoAddress); } + QString callsign (void) const { return _callsign; } + double vehicle_lat (void) const { return _vehicle_lat; } + double vehicle_lon (void) const { return _vehicle_lon; } + double altitude (void) const { return _altitude; } + double velocity (void) const { return _velocity; } + double heading (void) const { return _heading; } + int alert (void) const { return _alert; } + int lastContact (void) const { return _lastContact; } + double verticalVel (void) const { return _verticalVel; } + double distance (void) const { return _distance; } + + void update(const VehicleInfo_t& vehicleInfo); + + /// check if the vehicle is expired and should be removed + bool expired(); + + bool tooFar(); + +signals: + void vehicleLatChanged (); + void vehicleLonChanged (); + void callsignChanged (); + void altitudeChanged (); + void velocityChanged (); + void headingChanged (); + void alertChanged (); + void lastContactChanged (); + void verticalVelChanged (); + void distanceChanged (); + +private: + // This is the time in ms our vehicle will expire and thus removed from map + static constexpr qint64 expirationTimeoutMs = 25000; + + uint32_t _icaoAddress; + QString _callsign; + double _vehicle_lat; + double _vehicle_lon; + double _altitude; + double _velocity; + double _heading; + int _alert; + int _lastContact; + double _verticalVel; + double _distance; + + QElapsedTimer _lastUpdateTimer; + + bool _too_far = false; +}; + +Q_DECLARE_METATYPE(ADSBVehicle::VehicleInfo_t) diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp new file mode 100644 index 000000000..07eb42c51 --- /dev/null +++ b/app/adsb/adsbvehiclemanager.cpp @@ -0,0 +1,647 @@ +/**************************************************************************** + * + * (c) 2009-2020 QGROUNDCONTROL PROJECT + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "adsbvehiclemanager.h" +//#include "localmessage.h" +//#include "logger.h" +//#include "openhd.h" +//#include "mavlinktelemetry.h" for traffic from FC +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static ADSBVehicleManager* _instance = nullptr; + +ADSBVehicleManager* ADSBVehicleManager::instance() +{ + if ( _instance == nullptr ) { + _instance = new ADSBVehicleManager(); + } + return _instance; +} + +ADSBVehicleManager::ADSBVehicleManager(QObject *parent) : QObject(parent) +{ +} + +ADSBVehicleManager::~ADSBVehicleManager() +{ + // manually stop the threads + _internetLink->quit(); + _internetLink->wait(); + + _sdrLink->quit(); + _sdrLink->wait(); +} + +void ADSBVehicleManager::onStarted() +{ + qDebug() << "<<<<<<<<<<<<=0; i--) { + ADSBVehicle* adsbVehicle = _adsbVehicles.value(i); + if (adsbVehicle->expired()) { + // qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); + _adsbVehicles.removeAt(i); + _adsbICAOMap.remove(adsbVehicle->icaoAddress()); + adsbVehicle->deleteLater(); + } + } + // if more than 20 seconds with with no updates, set frontend indicator red + // if more than 60 seconds with no updates deactivate frontend indicator + if (_last_update_timer.elapsed() > 60000) { + _status = 0; + emit statusChanged(); + + } else if (_last_update_timer.elapsed() > 20000) { + _status = 1; + emit statusChanged(); + } +} + +//currently not used.. was for testing but could have future purpose to turn off display +void ADSBVehicleManager::adsbClearModel(){ + //qDebug() << "_adsbVehicles.clearAndDeleteContents"; + _adsbVehicles.clearAndDeleteContents(); +} + +void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo) +{ + uint32_t icaoAddress = vehicleInfo.icaoAddress; + + //no point in continuing because no location. This is somewhat redundant with parser + //possible situation where we start to not get location.. and gets stale then removed + if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) { + + //decide if its new or needs update + if (_adsbICAOMap.contains(icaoAddress)) { + _adsbICAOMap[icaoAddress]->update(vehicleInfo); + } + else { + + ADSBVehicle* adsbVehicle = new ADSBVehicle(vehicleInfo, this); + _adsbICAOMap[icaoAddress] = adsbVehicle; + _adsbVehicles.append(adsbVehicle); + } + + // Show warnings if adsb reported traffic is too close + _evaluateTraffic(vehicleInfo.altitude, vehicleInfo.distance); + + _last_update_timer.restart(); + _status = 2; + emit statusChanged(); + } +} + +void ADSBVehicleManager::_evaluateTraffic(double traffic_alt, int traffic_distance) +{ + /* + * Centralise traffic threat detection here. Once threat is detected it should be + * labled and then sent over to the adsb widget + * + * need to calculate azimuth and bearing of any threats so that it can be shared + * and depicted in the adsb widget + */ + //TODO refactor calls to mavlink + int drone_alt = 0; + + if (traffic_alt - drone_alt < 300 && traffic_distance < 2) { + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("Aircraft Traffic", 3); + + } else if (traffic_alt - drone_alt < 500 && traffic_distance < 5) { + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("Aircraft Traffic", 4); + } +} + +ADSBapi::ADSBapi() + : QThread() +{ + moveToThread(this); + start(); +} + +ADSBapi::~ADSBapi(void) +{ + quit(); + wait(); +} + +void ADSBapi::run(void) +{ + init(); + exec(); +} + +void ADSBapi::init(void) { + qDebug() << "------------------Adsbapi::init()"; + + QNetworkAccessManager * manager = new QNetworkAccessManager(this); + + m_manager = manager; + + connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))) ; + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &ADSBapi::requestData); + + // How frequently data is requested + timer->start(timer_interval); + mapBoundsChanged(40.48205, -3.35996); // this shouldn't be necesary +} + +/* this from when the old api needed a bounding rectangle + * keep in case api changes again + */ +void ADSBapi::mapBoundsChanged(double map_lat, double map_lon) { + m_api_lat=map_lat; + m_api_lon=map_lon; + qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); +/* + QGeoCoordinate qgeo_upper_left; + QGeoCoordinate qgeo_lower_right; + + qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); + qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); + + upperl_lat= QString::number(qgeo_upper_left.latitude()); + upperl_lon= QString::number(qgeo_upper_left.longitude()); + lowerr_lat= QString::number(qgeo_lower_right.latitude()); + lowerr_lon= QString::number(qgeo_lower_right.longitude()); +*/ +} + + +void ADSBInternet::requestData(void) { + _adsb_api_openskynetwork = _settings.value("adsb_api_openskynetwork").toBool(); + _show_adsb_internet = _settings.value("show_adsb").toBool(); + + // If openskynetwork is disabled by settings don't make the request and return + if (!_adsb_api_openskynetwork || !_show_adsb_internet) { + return; + } + + //adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; + //TODO REFCTOR URL FOR NEW API + adsb_url="<<<<<<<<<<>>>>>>>>>>>>>>>"; + QNetworkRequest request; + QUrl api_request = adsb_url; + request.setUrl(api_request); + request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); + + qDebug() << "url=" << api_request; + m_manager->get(request); +} + +void ADSBInternet::processReply(QNetworkReply *reply) { + + if (!_adsb_api_openskynetwork || !_show_adsb_internet) { + return; + } + + max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; + unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); + + //qDebug() << "MAX adsb distance=" << max_distance; + + if (reply->error()) { + qDebug() << "ADSB OpenSky request error!"; + qDebug() << reply->errorString(); + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB OpenSky Reply Error", 4); + reply->deleteLater(); + return; + } + + QJsonParseError errorPtr; + QByteArray data = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); + + if (doc.isNull()) { + qDebug() << "ADSB Opensky network response: Parse failed"; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB OpenSky Parse Error", 4); + reply->deleteLater(); + return; + } + + if(!doc.isObject()){ + qDebug()<<"JSON is not an object."; + reply->deleteLater(); + return; + } + + QJsonObject jsonObject = doc.object(); + + if(jsonObject.isEmpty()){ + qDebug()<<"ADSB Openskynetwork response: JSON object is empty."; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB OpenSky empty object", 4); + reply->deleteLater(); + return; + } + + QJsonValue value = jsonObject.value("states"); + QJsonArray array = value.toArray(); + + foreach (const QJsonValue & v, array){ + + ADSBVehicle::VehicleInfo_t adsbInfo; + bool icaoOk; + + QJsonArray innerarray = v.toArray(); + QString icaoAux = innerarray[0].toString(); + adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); + + // Skip this element if icao number is not ok + if (!icaoOk) { + continue; + } + + // location comes in lat lon format, but we need it as QGeoCoordinate + if(innerarray[6].isNull() || innerarray[5].isNull()){ //skip if no lat lon + continue; + } + double lat = innerarray[6].toDouble(); + double lon = innerarray[5].toDouble(); + //QGeoCoordinate location(lat, lon); + //adsbInfo.location = location; + adsbInfo.vehicle_lat = lat; + adsbInfo.vehicle_lon = lon; + adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; + + //evaluate distance for INTERNET adsb traffic... this is redundant with sdr + double lat_1 = m_api_lat; + double lon_1 = m_api_lon; + + double latDistance = qDegreesToRadians(lat_1 - lat); + double lngDistance = qDegreesToRadians(lon_1 - lon); + + double a = qSin(latDistance / 2) * qSin(latDistance / 2) + + qCos(qDegreesToRadians(lat_1)) * qCos(qDegreesToRadians(lat)) + * qSin(lngDistance / 2) * qSin(lngDistance / 2); + + double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); + double distance = 6371 * c; + + adsbInfo.distance = distance; + //qDebug() << "adsb internet distance=" << distance; + adsbInfo.availableFlags |= ADSBVehicle::DistanceAvailable; + + // If aircraft beyond max distance than skip this one + if(distance>max_distance){ + //qDebug() << "Beyond max SKIPPING"; + continue; + } + + // rest of fields + + // callsign + adsbInfo.callsign = innerarray[1].toString(); + if (adsbInfo.callsign.length() == 0) { + adsbInfo.callsign = "N/A"; + } + adsbInfo.availableFlags |= ADSBVehicle::CallsignAvailable; + + //altitude + if(innerarray[7].isDouble()){ + adsbInfo.altitude = innerarray[7].toDouble(); + //per setting eliminate all unknown alt + if (adsbInfo.altitude<5 && unknown_zero_alt==false){ + //skip this traffic + continue; + } + } + else { + //per setting eliminate all unknown alt + if (unknown_zero_alt==false){ + //skip this traffic + continue; + } + else { + adsbInfo.altitude=99999.9; + } + } + adsbInfo.availableFlags |= ADSBVehicle::AltitudeAvailable; + + //velocity + if(innerarray[9].isDouble()){ + adsbInfo.velocity = innerarray[9].toDouble() * 3.6; // m/s to km/h + } + else { + adsbInfo.velocity=99999.9; + } + adsbInfo.availableFlags |= ADSBVehicle::VelocityAvailable; + + //heading + if(innerarray[10].isDouble()){ + adsbInfo.heading = innerarray[10].toDouble(); + } + else { + adsbInfo.heading=0.0; + } + adsbInfo.availableFlags |= ADSBVehicle::HeadingAvailable; + + //last contact + if(innerarray[4].isNull()){ + adsbInfo.lastContact=0; + } + else { + adsbInfo.lastContact = innerarray[4].toInt(); + } + adsbInfo.availableFlags |= ADSBVehicle::LastContactAvailable; + + //vertical velocity + if(innerarray[11].isDouble()){ + adsbInfo.verticalVel = innerarray[11].toDouble(); + } + else { + adsbInfo.verticalVel=0.0; + } + adsbInfo.availableFlags |= ADSBVehicle::VerticalVelAvailable; + + // this is received on adsbvehicleupdate slot + emit adsbVehicleUpdate(adsbInfo); + } + reply->deleteLater(); +} + +ADSBSdr::ADSBSdr() + : ADSBapi() +{ +// we need to manage this properly +#if defined(__rasp_pi__)|| defined(__jetson__) + _groundAddress = "127.0.0.1"; +#endif + + timer_interval = 2000; +} + +void ADSBSdr::requestData(void) { + //TODO REFACTOR MSG + //Logger::instance()->logData("request data", 1); + _adsb_api_sdr = _settings.value("adsb_api_sdr").toBool(); + _show_adsb_sdr = _settings.value("show_adsb").toBool(); + + // If sdr is disabled by settings don't make the request and return + if (!_adsb_api_sdr || !_show_adsb_sdr) { + return; + } + + adsb_url= "http://"+_groundAddress+":8080/data/aircraft.json"; + + QNetworkRequest request; + QUrl api_request = adsb_url; + request.setUrl(api_request); + request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); + + // qDebug() << "url=" << api_request; + m_manager->get(request); +} + +void ADSBSdr::processReply(QNetworkReply *reply) { + //Logger::instance()->logData("process reply", 1); + if (!_adsb_api_sdr || !_show_adsb_sdr) { + return; + } + + max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; + unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); + + //qDebug() << "MAX adsb distance=" << max_distance; + + if (reply->error()) { + qDebug() << "ADSB SDR request error!"; + qDebug() << reply->errorString(); + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB SDR Reply Error", 4); + reply->deleteLater(); + return; + } + + QJsonParseError errorPtr; + QByteArray data = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); + + if (doc.isNull()) { + qDebug() << "ADSB SDR response: Parse failed"; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB SDR Parse Error", 4); + reply->deleteLater(); + return; + } + + if(!doc.isObject()){ + qDebug()<<"JSON is not an object."; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB SDR Json not an object", 4); + reply->deleteLater(); + return; + } + + QJsonObject jsonObject = doc.object(); + + if(jsonObject.isEmpty()){ + qDebug()<<"ADSB Openskynetwork response: JSON object is empty."; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB SDR Json empty", 4); + reply->deleteLater(); + return; + } + + QJsonArray array = jsonObject["aircraft"].toArray(); + + if(array.isEmpty()){ + qDebug()<<"JSON array is empty."; + //TODO REFACTOR MSG + //LocalMessage::instance()->showMessage("ADSB SDR Json array empty", 4); + reply->deleteLater(); + return; + } + + foreach (const QJsonValue & val, array){ + //Logger::instance()->logData("For Each Loop... /n", 1); + ADSBVehicle::VehicleInfo_t adsbInfo; + bool icaoOk; + + // TODO + // According to dump1090-mutability, this value can start + // by "~" in case it isn't a valid ICAO. How this will + // behave then? + QString icaoAux = val.toObject().value("hex").toString(); + //Logger::instance()->logData("icaoAux:"+icaoAux, 1); + adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); + + // Only continue if icao number is ok + if (icaoOk) { + //Logger::instance()->logData("icao ok!", 1); + + // location comes in lat lon format, but we need it as QGeoCoordinate + + if(val.toObject().value("lat").isNull() || val.toObject().value("lon").isNull()){ //skip if no lat lon + continue; + } + + double lat = val.toObject().value("lat").toDouble(); + double lon = val.toObject().value("lon").toDouble(); + + //QGeoCoordinate location(lat, lon); + //adsbInfo.location = location; + adsbInfo.vehicle_lat = lat; + adsbInfo.vehicle_lon = lon; + adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; + + //evaluate distance for SDR adsb traffic... this is redundant with internet + double lat_1 = m_api_lat; + double lon_1 = m_api_lon; + + double latDistance = qDegreesToRadians(lat_1 - lat); + double lngDistance = qDegreesToRadians(lon_1 - lon); + + double a = qSin(latDistance / 2) * qSin(latDistance / 2) + + qCos(qDegreesToRadians(lat_1)) * qCos(qDegreesToRadians(lat)) + * qSin(lngDistance / 2) * qSin(lngDistance / 2); + + double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); + double distance = 6371 * c; + + adsbInfo.distance = distance; + adsbInfo.availableFlags |= ADSBVehicle::DistanceAvailable; + + // If aircraft beyond max distance than skip this one + if(distance>max_distance){ + //qDebug() << "Beyond max SKIPPING"; + continue; + } + + // callsign + adsbInfo.callsign = val.toObject().value("flight").toString(); + + if (adsbInfo.callsign.length() == 0) { + adsbInfo.callsign = "N/A"; + } else { + adsbInfo.availableFlags |= ADSBVehicle::CallsignAvailable; + } + + //altitude + if(val.toObject().value("altitude").isNull()){ + //per setting eliminate unknown alt traffic + if (unknown_zero_alt==false){ + //skip this traffic + continue; + } else { + adsbInfo.altitude=99999.9; + } + } + else { + adsbInfo.altitude = val.toObject().value("altitude").toInt() * 0.3048;//feet to meters + //per setting eliminate all unknown alt + if (adsbInfo.altitude<5 && unknown_zero_alt==false){ + //skip this traffic + continue; + } + } + adsbInfo.availableFlags |= ADSBVehicle::AltitudeAvailable; + + //velocity + if(val.toObject().value("speed").isNull()){ + adsbInfo.velocity=99999.9; + } + else { + adsbInfo.velocity = round(val.toObject().value("speed").toDouble() * 1.852); // knots to km/h + } + adsbInfo.availableFlags |= ADSBVehicle::VelocityAvailable; + + //heading + if(val.toObject().value("track").isNull()){ + adsbInfo.heading=0.0; + } + else { + adsbInfo.heading = val.toObject().value("track").toDouble(); + } + adsbInfo.availableFlags |= ADSBVehicle::HeadingAvailable; + + //last contact + if(val.toObject().value("seen_pos").isNull()){ + adsbInfo.lastContact=0; + } + else { + adsbInfo.lastContact = val.toObject().value("seen_pos").toInt(); + } + adsbInfo.availableFlags |= ADSBVehicle::LastContactAvailable; + + //vertical velocity + if(val.toObject().value("vert_rate").isNull()){ + adsbInfo.verticalVel=0.0; + } + else { + adsbInfo.verticalVel = round(val.toObject().value("vert_rate").toDouble() * 0.00508); //feet/min to m/s + } + adsbInfo.availableFlags |= ADSBVehicle::VerticalVelAvailable; + + + // this is received on adsbvehicleupdate slot + emit adsbVehicleUpdate(adsbInfo); + } + else { + //Logger::instance()->logData("icao REJECTED! /n", 1); + qDebug()<<"ICAO number NOT OK!"; + } + } + reply->deleteLater(); +} diff --git a/app/adsb/adsbvehiclemanager.h b/app/adsb/adsbvehiclemanager.h new file mode 100644 index 000000000..d1c48e11a --- /dev/null +++ b/app/adsb/adsbvehiclemanager.h @@ -0,0 +1,167 @@ +#pragma once + +#include "qmlobjectlistmodel.h" +#include "adsbvehicle.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +// This is a base clase for inheriting the links for +// SDR and Opensky network apis +class ADSBapi : public QThread +{ + Q_OBJECT + +public: + ADSBapi(); + ~ADSBapi(); + +signals: + void adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehicleInfo); + + void adsbClearModelRequest(); + +protected: + void run(void) final; + +public slots: + void mapBoundsChanged(double map_lat, double map_lon); + +protected slots: + virtual void processReply(QNetworkReply *reply) = 0; + virtual void requestData() = 0; + +protected: + void init(); + + // network + QNetworkAccessManager * m_manager; + QString adsb_url; + + // boundingbox parameters + //QString upperl_lat; + //QString upperl_lon; + //QString lowerr_lat; + //QString lowerr_lon; + + // timer for requests + int timer_interval; + QTimer *timer; + + QSettings _settings; + + double m_api_lat; //private but duplicated across classes + double m_api_lon; //private but duplicated across classes + + qreal max_distance; + bool unknown_zero_alt; +}; + +// This class gets the info from Openskynetwork api +class ADSBInternet: public ADSBapi { + Q_OBJECT + +public: + ADSBInternet() { timer_interval = 10000; } + ~ADSBInternet() {} + +private slots: + void processReply(QNetworkReply *reply) override; + void requestData() override; + +private: + bool _adsb_api_openskynetwork; + bool _show_adsb_internet; //wired to show widget setting. somewhat redundant +}; + +// This class gets the info from SDR +class ADSBSdr: public ADSBapi { + Q_OBJECT + +public: + ADSBSdr(); + ~ADSBSdr() {} + + void setGroundIP(QString address) { _groundAddress = address; } + +private slots: + void processReply(QNetworkReply *reply) override; + void requestData() override; + +private: + QString _groundAddress = ""; + bool _adsb_api_sdr; + bool _show_adsb_sdr; //wired to show widget setting. somewhat redundant + +}; + +class ADSBVehicleManager : public QObject { + Q_OBJECT + +public: + ADSBVehicleManager(QObject* parent = nullptr); + ~ADSBVehicleManager(); + static ADSBVehicleManager* instance(); + + Q_PROPERTY(QmlObjectListModel* adsbVehicles READ adsbVehicles CONSTANT) + Q_PROPERTY(double apiLat READ apiLat MEMBER _api_lat NOTIFY mapLatChanged) + Q_PROPERTY(double apiLon READ apiLon MEMBER _api_lon NOTIFY mapLonChanged) + + // frontend indicator. 0 inactive, 1 red, 2 green + Q_PROPERTY(uint status READ status NOTIFY statusChanged) + + QmlObjectListModel* adsbVehicles(void) { return &_adsbVehicles; } + double apiLat(void) { return _api_lat; } + double apiLon(void) { return _api_lon; } + uint status() { return _status; } + + // called from qml when the map has moved + Q_INVOKABLE void newMapLat(double map_lat); + Q_INVOKABLE void newMapLon(double map_lon); + + Q_INVOKABLE void setGroundIP(QString address) { _sdrLink->setGroundIP(address); } + +signals: + // sent to ADSBapi to make requests based into this + void mapLatChanged(double map_lat); + void mapLonChanged(double map_lon); + + // sent to adsbwidgetform.ui to update the status indicator + void statusChanged(void); + +public slots: + void adsbVehicleUpdate (const ADSBVehicle::VehicleInfo_t vehicleInfo); + void onStarted(); + void adsbClearModel(); + +private slots: + void _cleanupStaleVehicles(void); + +private: + void _evaluateTraffic(double traffic_alt, int traffic_distance); + + QmlObjectListModel _adsbVehicles; + QMap _adsbICAOMap; + QTimer _adsbVehicleCleanupTimer; + ADSBInternet* _internetLink = nullptr; + ADSBSdr* _sdrLink = nullptr; + double _api_lat; + double _api_lon; + QElapsedTimer _last_update_timer; + uint _status = 0; + + qreal distance = 0; + QSettings _settings; +}; diff --git a/app/adsb/qmlobjectlistmodel.cpp b/app/adsb/qmlobjectlistmodel.cpp new file mode 100644 index 000000000..8d9d4721e --- /dev/null +++ b/app/adsb/qmlobjectlistmodel.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** + * + * This file has been ported from QGroundControl project + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#include "qmlobjectlistmodel.h" + +#include +#include + +const int QmlObjectListModel::ObjectRole = Qt::UserRole; +const int QmlObjectListModel::TextRole = Qt::UserRole + 1; + +QmlObjectListModel::QmlObjectListModel(QObject* parent) + : QAbstractListModel (parent) + , _dirty (false) + , _skipDirtyFirstItem (false) + , _externalBeginResetModel (false) +{ + +} + +QmlObjectListModel::~QmlObjectListModel() +{ + +} + +QObject* QmlObjectListModel::get(int index) +{ + if (index < 0 || index >= _objectList.count()) { + return nullptr; + } + return _objectList[index]; +} + +int QmlObjectListModel::rowCount(const QModelIndex& parent) const +{ + Q_UNUSED(parent); + + return _objectList.count(); +} + +QVariant QmlObjectListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (index.row() < 0 || index.row() >= _objectList.count()) { + return QVariant(); + } + + if (role == ObjectRole) { + return QVariant::fromValue(_objectList[index.row()]); + } else if (role == TextRole) { + return QVariant::fromValue(_objectList[index.row()]->objectName()); + } else { + return QVariant(); + } +} + +QHash QmlObjectListModel::roleNames(void) const +{ + QHash hash; + + hash[ObjectRole] = "object"; + hash[TextRole] = "text"; + + return hash; +} + +bool QmlObjectListModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (index.isValid() && role == ObjectRole) { + _objectList.replace(index.row(), value.value()); + emit dataChanged(index, index); + return true; + } + + return false; +} + +bool QmlObjectListModel::insertRows(int position, int rows, const QModelIndex& parent) +{ + Q_UNUSED(parent); + + if (position < 0 || position > _objectList.count() + 1) { + qWarning() << "Invalid position position:count" << position << _objectList.count(); + } + + beginInsertRows(QModelIndex(), position, position + rows - 1); + endInsertRows(); + + emit countChanged(count()); + + return true; +} + +bool QmlObjectListModel::removeRows(int position, int rows, const QModelIndex& parent) +{ + Q_UNUSED(parent); + + if (position < 0 || position >= _objectList.count()) { + qWarning() << "Invalid position position:count" << position << _objectList.count(); + } else if (position + rows > _objectList.count()) { + qWarning() << "Invalid rows position:rows:count" << position << rows << _objectList.count(); + } + + beginRemoveRows(QModelIndex(), position, position + rows - 1); + for (int row=0; row= _objectList.count()) { + return nullptr; + } + return _objectList[index]; +} + +const QObject* QmlObjectListModel::operator[](int index) const +{ + if (index < 0 || index >= _objectList.count()) { + return nullptr; + } + return _objectList[index]; +} + +void QmlObjectListModel::clear() +{ + if (!_externalBeginResetModel) { + beginResetModel(); + } + _objectList.clear(); + if (!_externalBeginResetModel) { + endResetModel(); + emit countChanged(count()); + } +} + +QObject* QmlObjectListModel::removeAt(int i) +{ + QObject* removedObject = _objectList[i]; + if(removedObject) { + // Look for a dirtyChanged signal on the object + if (_objectList[i]->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { + if (!_skipDirtyFirstItem || i != 0) { + QObject::disconnect(_objectList[i], SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); + } + } + } + removeRows(i, 1); + setDirty(true); + return removedObject; +} + +void QmlObjectListModel::insert(int i, QObject* object) +{ + if (i < 0 || i > _objectList.count()) { + qWarning() << "Invalid index index:count" << i << _objectList.count(); + } + if(object) { + QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); + // Look for a dirtyChanged signal on the object + if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { + if (!_skipDirtyFirstItem || i != 0) { + QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); + } + } + } + _objectList.insert(i, object); + insertRows(i, 1); + setDirty(true); +} + +void QmlObjectListModel::insert(int i, QList objects) +{ + if (i < 0 || i > _objectList.count()) { + qWarning() << "Invalid index index:count" << i << _objectList.count(); + } + + int j = i; + for (QObject* object: objects) { + QQmlEngine::setObjectOwnership(object, QQmlEngine::CppOwnership); + + // Look for a dirtyChanged signal on the object + if (object->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("dirtyChanged(bool)")) != -1) { + if (!_skipDirtyFirstItem || j != 0) { + QObject::connect(object, SIGNAL(dirtyChanged(bool)), this, SLOT(_childDirtyChanged(bool))); + } + } + j++; + + _objectList.insert(j, object); + } + + insertRows(i, objects.count()); + + setDirty(true); +} + +void QmlObjectListModel::append(QObject* object) +{ + insert(_objectList.count(), object); +} + +void QmlObjectListModel::append(QList objects) +{ + insert(_objectList.count(), objects); +} + +QObjectList QmlObjectListModel::swapObjectList(const QObjectList& newlist) +{ + QObjectList oldlist(_objectList); + if (!_externalBeginResetModel) { + beginResetModel(); + } + _objectList = newlist; + if (!_externalBeginResetModel) { + endResetModel(); + emit countChanged(count()); + } + return oldlist; +} + +int QmlObjectListModel::count() const +{ + return rowCount(); +} + +void QmlObjectListModel::setDirty(bool dirty) +{ + if (_dirty != dirty) { + _dirty = dirty; + if (!dirty) { + // Need to clear dirty from all children + for(QObject* object: _objectList) { + if (object->property("dirty").isValid()) { + object->setProperty("dirty", false); + } + } + } + emit dirtyChanged(_dirty); + } +} + +void QmlObjectListModel::_childDirtyChanged(bool dirty) +{ + _dirty |= dirty; + // We want to emit dirtyChanged even if the actual value of _dirty didn't change. It can be a useful + // signal to know when a child has changed dirty state + emit dirtyChanged(_dirty); +} + +void QmlObjectListModel::deleteListAndContents() +{ + for (int i=0; i<_objectList.count(); i++) { + _objectList[i]->deleteLater(); + } + deleteLater(); +} + +void QmlObjectListModel::clearAndDeleteContents() +{ + beginResetModel(); + for (int i=0; i<_objectList.count(); i++) { + _objectList[i]->deleteLater(); + } + clear(); + endResetModel(); +} + +void QmlObjectListModel::beginReset() +{ + if (_externalBeginResetModel) { + qWarning() << "QmlObjectListModel::beginReset already set"; + } + _externalBeginResetModel = true; + beginResetModel(); +} + +void QmlObjectListModel::endReset() +{ + if (!_externalBeginResetModel) { + qWarning() << "QmlObjectListModel::endReset begin not set"; + } + _externalBeginResetModel = false; + endResetModel(); +} diff --git a/app/adsb/qmlobjectlistmodel.h b/app/adsb/qmlobjectlistmodel.h new file mode 100644 index 000000000..50c24cad6 --- /dev/null +++ b/app/adsb/qmlobjectlistmodel.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * + * This file has been ported from QGroundControl project + * + * QGroundControl is licensed according to the terms in the file + * COPYING.md in the root of the source code directory. + * + ****************************************************************************/ + +#pragma once + +#include + +class QmlObjectListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + QmlObjectListModel(QObject* parent = nullptr); + ~QmlObjectListModel() override; + + Q_PROPERTY(int count READ count NOTIFY countChanged) + + /// Returns true if any of the items in the list are dirty. Requires each object to have + /// a dirty property and dirtyChanged signal. + Q_PROPERTY(bool dirty READ dirty WRITE setDirty NOTIFY dirtyChanged) + + Q_INVOKABLE QObject* get(int index); + + // Property accessors + + int count () const; + bool dirty () const { return _dirty; } + + void setDirty (bool dirty); + void append (QObject* object); + void append (QList objects); + QObjectList swapObjectList (const QObjectList& newlist); + void clear (); + QObject* removeAt (int i); + QObject* removeOne (QObject* object) { return removeAt(indexOf(object)); } + void insert (int i, QObject* object); + void insert (int i, QList objects); + bool contains (QObject* object) { return _objectList.indexOf(object) != -1; } + int indexOf (QObject* object) { return _objectList.indexOf(object); } + + QObject* operator[] (int i); + const QObject* operator[] (int i) const; + template T value (int index) { return qobject_cast(_objectList[index]); } + QList* objectList () { return &_objectList; } + + /// Calls deleteLater on all items and this itself. + void deleteListAndContents (); + + /// Clears the list and calls deleteLater on each entry + void clearAndDeleteContents (); + + void beginReset (); + void endReset (); + +signals: + void countChanged (int count); + void dirtyChanged (bool dirtyChanged); + +private slots: + void _childDirtyChanged (bool dirty); + +private: + // Overrides from QAbstractListModel + int rowCount (const QModelIndex & parent = QModelIndex()) const override; + QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const override; + bool insertRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; + bool removeRows (int position, int rows, const QModelIndex &index = QModelIndex()) override; + bool setData (const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + QHash roleNames(void) const override; + +private: + QList _objectList; + + bool _dirty; + bool _skipDirtyFirstItem; + bool _externalBeginResetModel; + + static const int ObjectRole; + static const int TextRole; +}; diff --git a/app/main.cpp b/app/main.cpp index af8261646..3c33ea62f 100755 --- a/app/main.cpp +++ b/app/main.cpp @@ -24,6 +24,7 @@ const QVector permissions({"android.permission.INTERNET", #include "telemetry/models/wificard.h" #include "telemetry/MavlinkTelemetry.h" #include "telemetry/models/rcchannelsmodel.h" +#include "telemetry/models/markermodel.h" #include "telemetry/settings/mavlinksettingsmodel.h" #include "telemetry/settings/wblinksettingshelper.h" #include "telemetry/settings/frequencyhelper.h" @@ -34,6 +35,11 @@ const QVector permissions({"android.permission.INTERNET", #include "osd/horizonladder.h" #include "osd/flightpathvector.h" #include "osd/aoagauge.h" +#include "adsb/adsb.h" +#include "adsb/adsbvehicle.h" +#include "adsb/adsbvehiclemanager.h" +#include "adsb/qmlobjectlistmodel.h" + // Video - annyoing ifdef crap is needed for all the different platforms / configurations #include "decodingstatistcs.h" @@ -293,6 +299,10 @@ int main(int argc, char *argv[]) { qmlRegisterType("OpenHD", 1, 0, "FlightPathVector"); qmlRegisterType("OpenHD", 1, 0, "AoaGauge"); + + qmlRegisterUncreatableType("OpenHD", 1, 0, "QmlObjectListModel", "Reference only"); + + QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("_qopenhd", &QOpenHD::instance()); QOpenHD::instance().setEngine(&engine); @@ -338,6 +348,11 @@ int main(int argc, char *argv[]) { engine.rootContext()->setContextProperty("_wifi_card_gnd2", &WiFiCard::instance_gnd(2)); engine.rootContext()->setContextProperty("_wifi_card_gnd3", &WiFiCard::instance_gnd(3)); engine.rootContext()->setContextProperty("_wifi_card_air", &WiFiCard::instance_air()); + //engine.rootContext()->setContextProperty("_markermodel", &MarkerModel::instance()); + auto adsbVehicleManager = ADSBVehicleManager::instance(); + engine.rootContext()->setContextProperty("AdsbVehicleManager", adsbVehicleManager); + //QObject::connect(openHDSettings, &OpenHDSettings::groundStationIPUpdated, adsbVehicleManager, &ADSBVehicleManager::setGroundIP, Qt::QueuedConnection); + adsbVehicleManager->onStarted(); // And then the main part engine.rootContext()->setContextProperty("_mavlinkTelemetry", &MavlinkTelemetry::instance()); diff --git a/app/telemetry/models/markermodel.cpp b/app/telemetry/models/markermodel.cpp new file mode 100644 index 000000000..a71666bdb --- /dev/null +++ b/app/telemetry/models/markermodel.cpp @@ -0,0 +1,215 @@ +#include +// #include +#include +#include + +#include "markermodel.h" + + +Traffic::Traffic(const QString &callsign, const int &contact, const double &lat, const double &lon, + const double &alt, const double &velocity, const double &track, const double &vertical, + const int &distance) + : m_callsign(callsign), m_contact(contact), m_lat(lat), m_lon(lon), m_alt(alt), + m_velocity(velocity), m_track(track), m_vertical(vertical), m_distance(distance) +{ +} + +QString Traffic::callsign() const{ + return m_callsign; +} +int Traffic::contact() const{ + return m_contact; +} +double Traffic::lat() const{ + return m_lat; +} +double Traffic::lon() const{ + return m_lon; +} +double Traffic::alt() const{ + return m_alt; +} +double Traffic::velocity() const{ + return m_velocity; +} +double Traffic::track() const{ + return m_track; +} +double Traffic::vertical() const{ + return m_vertical; +} +int Traffic::distance() const{ + return m_distance; +} + + +static MarkerModel* _instance = nullptr; + +MarkerModel* MarkerModel::instance() { + if (_instance == nullptr) { + _instance = new MarkerModel(); + } + return _instance; +} + +MarkerModel::MarkerModel(QObject *parent): QAbstractListModel(parent){ + qDebug() << "MarkerModel::MarkerModel()"; +} + +void MarkerModel::initMarkerModel() { + qDebug() << "MarkerModel::initMarkerModel()"; + //auto openSky = OpenSky::instance(); +} + +void MarkerModel::addMarker(int current_row, int total_rows, const Traffic &traffic){ + + if (current_row == 0){ + //LIMIT HOW MANY OF THE NEAREST MARKERS APPEAR + //m_row_limit = settings.value("adsb_marker_limit").toInt(); + + beginInsertRows(QModelIndex(), 0, total_rows-1); + m_traffic.insert(0, traffic); + } + else { + m_traffic.insert(current_row, traffic); + } + + if (current_row == total_rows-1){ + //the last entry has been made + endInsertRows(); + emit dataChanged(this->index(0),this->index(rowCount())); + } + /* old distance sort limiting the rows to nearest X results + int rowcount=rowCount(); + + if (rowcount>0){ + for (int i = 0; i < rowcount; ++i) { + Traffic compare_traffic=m_traffic.at(i); + if (traffic.distance() < compare_traffic.distance()){ + if(i<=m_row_limit){ + m_traffic.insert(i, traffic); + } + break; + } + if (i == rowcount-1){ + if(i+1<=m_row_limit){ + m_traffic.insert(i+1, traffic); + } + break; + } + } + } + else { + //first entry + m_traffic.insert(0, traffic); + } + */ +} + +void MarkerModel::doneAddingMarkers(){ + /* + //NO LONGER CALLED + + //qDebug() << "onDoneAddingMarkers rowcount=" << rowCount(); + + endInsertRows(); + + // this signal is probably redundant but doesnt seem to hurt. It is ussed by the ADSB widget + emit dataChanged(this->index(0),this->index(rowCount())); + + //get the last displayed row and distance of that object (it is most distant) + Traffic distant_traffic=m_traffic.at(rowCount()-1); + set_adsb_radius(distant_traffic.distance()*1000); + */ +} + +void MarkerModel::set_adsb_radius(int adsb_radius){ + //drawing a box now + m_adsb_radius=adsb_radius; + //qDebug() << "adsbradius=" << m_adsb_radius; + emit adsb_radius_changed(m_adsb_radius); +} + +void MarkerModel::removeAllMarkers(){ + + //qDebug() << "removeallmarker"; + //remove all rows before adding new + + beginResetModel(); + //qDeleteAll(m_traffic.begin(), m_traffic.end()); + //qDeleteAll(m_traffic); + m_traffic.clear(); + endResetModel(); + + emit dataChanged(this->index(0),this->index(rowCount())); + + /* + int removerowcount=rowCount(); + + if (removerowcount>0){ + //qDebug() << "begin rowcount= " <index(0),this->index(rowCount())); + } + */ +} + + +Traffic MarkerModel::getMarker(int index)const { + return m_traffic.at(index); +} + +int MarkerModel::rowCount(const QModelIndex &parent) const{ + if (parent.isValid()) + return 0; + return m_traffic.count(); +} + +QVariant MarkerModel::data(const QModelIndex &index, int role) const{ + if (index.row() < 0 || index.row() >= m_traffic.count()) + return QVariant(); + + const Traffic &traffic = m_traffic[index.row()]; + + if(role == Callsign) + return traffic.callsign(); + else if (role == Contact) + return traffic.contact(); + else if (role == Lat) + return traffic.lat(); + else if (role == Lon) + return traffic.lon(); + else if (role == Alt) + return traffic.alt(); + else if (role == Velocity) + return traffic.velocity(); + else if (role == Track) + return traffic.track(); + else if (role == Vertical) + return traffic.vertical(); + else if (role == Distance) + return traffic.distance(); + return QVariant(); +} + +QHash MarkerModel::roleNames() const{ + QHash roles; + roles[Callsign] = "callsign"; + roles[Contact] = "contact"; + roles[Lat] = "lat"; + roles[Lon] = "lon"; + roles[Alt] = "alt"; + roles[Velocity] = "velocity"; + roles[Track] = "track"; + roles[Vertical] = "vertical"; + roles[Distance] = "distance"; + return roles; +} diff --git a/app/telemetry/models/markermodel.h b/app/telemetry/models/markermodel.h new file mode 100644 index 000000000..0d780c06b --- /dev/null +++ b/app/telemetry/models/markermodel.h @@ -0,0 +1,89 @@ +#ifndef MARKERMODEL_H +#define MARKERMODEL_H +#include +#include + +#include +//#include + +class Traffic +{ +public: + Traffic(const QString &callsign, const int &contact, const double &lat, const double &lon, + const double &alt, const double &velocity, const double &track, const double &vertical, + const int &distance); + + QString callsign() const; + int contact() const; + double lat() const; + double lon() const; + double alt() const; + double velocity() const; + double track() const; + double vertical() const; + int distance() const; + +private: + QString m_callsign= "---"; + int m_contact= 0; + double m_lat= 0.0; + double m_lon= 0.0; + double m_alt= 99999; + double m_velocity= 0; + double m_track= 0.0; + double m_vertical= 0.0; + int m_distance= 0; +}; + + + +class MarkerModel : public QAbstractListModel { + Q_OBJECT + + +public: + explicit MarkerModel(QObject *parent = nullptr); + + static MarkerModel* instance(); + + enum MarkerRoles { + Callsign = Qt::UserRole + 1, + Contact, + Lat, + Lon, + Alt, + Velocity, + Track, + Vertical, + Distance + }; + + Q_INVOKABLE Traffic getMarker(int index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + QHash roleNames() const override; + + Q_PROPERTY(int adsb_radius MEMBER m_adsb_radius WRITE set_adsb_radius NOTIFY adsb_radius_changed) + void set_adsb_radius(int adsb_radius); + +signals: + void adsb_radius_changed(int adsb_radius); + +public slots: + void initMarkerModel(); + void addMarker(int current_row , int total_rows, const Traffic &traffic); + void doneAddingMarkers(); + void removeAllMarkers(); + //int getAdsbRadius(); + +private: + QList m_traffic; + int m_row_limit; + QSettings settings; + int m_adsb_radius; +}; + +#endif // MARKERMODEL_H diff --git a/app/telemetry/telemetry.pri b/app/telemetry/telemetry.pri index 468f00337..0497e89bc 100644 --- a/app/telemetry/telemetry.pri +++ b/app/telemetry/telemetry.pri @@ -11,6 +11,7 @@ SOURCES += \ $$PWD/connection/tcp_connection.cpp \ $$PWD/connection/udp_connection.cpp \ $$PWD/models/fcmapmodel.cpp \ + $$PWD/models/markermodel.cpp \ $$PWD/settings/documentedparam.cpp \ $$PWD/settings/frequencyhelper.cpp \ $$PWD/settings/pollutionhelper.cpp \ @@ -35,6 +36,7 @@ HEADERS += \ $$PWD/action/fcmsgintervalhandler.h \ $$PWD/action/ohdaction.h \ $$PWD/connection/tcp_connection.h \ + $$PWD/models/markermodel.h \ $$PWD/settings/documentedparam.h \ $$PWD/action/impl/xparam.h \ $$PWD/settings/frequencyhelper.h \ From 8b27720c8e4892dd2599ba8dadf2b247de63b2c1 Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sun, 17 Dec 2023 10:37:18 -0500 Subject: [PATCH 02/12] Working basic ADSB API for testing --- app/adsb/adsbvehiclemanager.cpp | 93 +++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp index 07eb42c51..e49aa3147 100644 --- a/app/adsb/adsbvehiclemanager.cpp +++ b/app/adsb/adsbvehiclemanager.cpp @@ -126,7 +126,7 @@ void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehi //no point in continuing because no location. This is somewhat redundant with parser //possible situation where we start to not get location.. and gets stale then removed if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) { - +qDebug() << "ADD/Update ADSB Vehicle"; //decide if its new or needs update if (_adsbICAOMap.contains(icaoAddress)) { _adsbICAOMap[icaoAddress]->update(vehicleInfo); @@ -209,6 +209,7 @@ void ADSBapi::init(void) { * keep in case api changes again */ void ADSBapi::mapBoundsChanged(double map_lat, double map_lon) { + qDebug() << "------------------Adsbapi::mapboundschanged()"; m_api_lat=map_lat; m_api_lon=map_lon; qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); @@ -228,17 +229,19 @@ void ADSBapi::mapBoundsChanged(double map_lat, double map_lon) { void ADSBInternet::requestData(void) { + qDebug() << "------------------AdsbInternet::requestdata()"; _adsb_api_openskynetwork = _settings.value("adsb_api_openskynetwork").toBool(); _show_adsb_internet = _settings.value("show_adsb").toBool(); // If openskynetwork is disabled by settings don't make the request and return if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - return; + //TODO manage settings + // return; } //adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; //TODO REFCTOR URL FOR NEW API - adsb_url="<<<<<<<<<<>>>>>>>>>>>>>>>"; + adsb_url="https://api.airplanes.live/v2/point/40.48205/-3.35996/100"; QNetworkRequest request; QUrl api_request = adsb_url; request.setUrl(api_request); @@ -251,13 +254,14 @@ void ADSBInternet::requestData(void) { void ADSBInternet::processReply(QNetworkReply *reply) { if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - return; + //TODO manage user settings + // return; } max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); - //qDebug() << "MAX adsb distance=" << max_distance; + qDebug() << "MAX adsb distance=" << max_distance; if (reply->error()) { qDebug() << "ADSB OpenSky request error!"; @@ -296,30 +300,41 @@ void ADSBInternet::processReply(QNetworkReply *reply) { return; } - QJsonValue value = jsonObject.value("states"); - QJsonArray array = value.toArray(); - foreach (const QJsonValue & v, array){ + + QJsonArray acArray = doc.object()["ac"].toArray(); + + qDebug()<<"count:" << acArray.count(); + + // Iterate through the "ac" array + for (const QJsonValue &aircraftValue : acArray) { + QJsonObject aircraft = aircraftValue.toObject(); + + qDebug() << "Type:" << aircraft["type"].toString(); + qDebug() << "Flight:" << aircraft["flight"].toString(); + // Add more fields as needed + qDebug() << ""; ADSBVehicle::VehicleInfo_t adsbInfo; - bool icaoOk; - QJsonArray innerarray = v.toArray(); - QString icaoAux = innerarray[0].toString(); + //Aircraft Hex + bool icaoOk; + QString icaoAux = aircraft["hex"].toString(); adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); - + qDebug() << "Hex:" << aircraft["hex"].toString(); // Skip this element if icao number is not ok if (!icaoOk) { + qDebug()<<"skipping - icao not ok"; continue; } - // location comes in lat lon format, but we need it as QGeoCoordinate - if(innerarray[6].isNull() || innerarray[5].isNull()){ //skip if no lat lon + //Aircraft lat/lon + if(aircraft["lat"].isNull() || aircraft["lon"].isNull()){ //skip if no lat lon continue; } - double lat = innerarray[6].toDouble(); - double lon = innerarray[5].toDouble(); - //QGeoCoordinate location(lat, lon); + double lat = aircraft["lat"].toDouble(); + double lon = aircraft["lon"].toDouble(); + qDebug()<<"lat/lon" << lat << " / " << lon; //adsbInfo.location = location; adsbInfo.vehicle_lat = lat; adsbInfo.vehicle_lon = lon; @@ -340,37 +355,37 @@ void ADSBInternet::processReply(QNetworkReply *reply) { double distance = 6371 * c; adsbInfo.distance = distance; - //qDebug() << "adsb internet distance=" << distance; + qDebug() << "adsb internet distance=" << distance; adsbInfo.availableFlags |= ADSBVehicle::DistanceAvailable; // If aircraft beyond max distance than skip this one if(distance>max_distance){ - //qDebug() << "Beyond max SKIPPING"; - continue; + qDebug() << "Beyond max SKIPPING"; + //TODO commented for testing + //continue; } - // rest of fields - - // callsign - adsbInfo.callsign = innerarray[1].toString(); + //Aircraft callsign + adsbInfo.callsign = aircraft["flight"].toString(); if (adsbInfo.callsign.length() == 0) { adsbInfo.callsign = "N/A"; } adsbInfo.availableFlags |= ADSBVehicle::CallsignAvailable; - //altitude - if(innerarray[7].isDouble()){ - adsbInfo.altitude = innerarray[7].toDouble(); + //Aircraft altitude + if(aircraft["alt_baro"].toDouble()){ + adsbInfo.altitude = aircraft["alt_baro"].toDouble(); + qDebug()<<"alt:" << aircraft["alt_baro"].toDouble(); //per setting eliminate all unknown alt if (adsbInfo.altitude<5 && unknown_zero_alt==false){ - //skip this traffic + qDebug() << "ADSB Skipping aircraft for alt error"; continue; } } else { //per setting eliminate all unknown alt if (unknown_zero_alt==false){ - //skip this traffic + qDebug() << "ADSB Skipping aircraft for alt error"; continue; } else { @@ -379,18 +394,18 @@ void ADSBInternet::processReply(QNetworkReply *reply) { } adsbInfo.availableFlags |= ADSBVehicle::AltitudeAvailable; - //velocity - if(innerarray[9].isDouble()){ - adsbInfo.velocity = innerarray[9].toDouble() * 3.6; // m/s to km/h + //Aircraft velocity + if(aircraft["gs"].toDouble()){ + adsbInfo.velocity = aircraft["gs"].toDouble() * 3.6; // m/s to km/h } else { adsbInfo.velocity=99999.9; } adsbInfo.availableFlags |= ADSBVehicle::VelocityAvailable; - //heading - if(innerarray[10].isDouble()){ - adsbInfo.heading = innerarray[10].toDouble(); + //Aircraft heading + if(aircraft["track"].toDouble()){ + adsbInfo.heading = aircraft["track"].toDouble(); } else { adsbInfo.heading=0.0; @@ -398,17 +413,17 @@ void ADSBInternet::processReply(QNetworkReply *reply) { adsbInfo.availableFlags |= ADSBVehicle::HeadingAvailable; //last contact - if(innerarray[4].isNull()){ + if(aircraft["seen"].isNull()){ adsbInfo.lastContact=0; } else { - adsbInfo.lastContact = innerarray[4].toInt(); + adsbInfo.lastContact = aircraft["seen"].toInt(); } adsbInfo.availableFlags |= ADSBVehicle::LastContactAvailable; //vertical velocity - if(innerarray[11].isDouble()){ - adsbInfo.verticalVel = innerarray[11].toDouble(); + if(aircraft["baro_rate"].isDouble()){ + adsbInfo.verticalVel = aircraft["baro_rate"].toDouble(); } else { adsbInfo.verticalVel=0.0; From d824327f25536495b251ccba7d923e985845a996 Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sun, 17 Dec 2023 22:30:05 -0500 Subject: [PATCH 03/12] ADSB map display with targets drawn in c++ canvas --- QOpenHD.pro | 2 + app/adsb/adsbvehicle.cpp | 10 +- app/adsb/adsbvehicle.h | 20 +- app/adsb/adsbvehiclemanager.cpp | 22 +- app/adsb/drawingcanvas.cpp | 290 +++++++++++++++++++++++ app/adsb/drawingcanvas.h | 151 ++++++++++++ app/main.cpp | 2 + app/telemetry/models/fcmavlinksystem.cpp | 2 +- qml/ui/widgets/map/MapComponent.qml | 146 ++++++++++++ 9 files changed, 616 insertions(+), 29 deletions(-) create mode 100644 app/adsb/drawingcanvas.cpp create mode 100644 app/adsb/drawingcanvas.h diff --git a/QOpenHD.pro b/QOpenHD.pro index 6ad929c31..835334878 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -104,6 +104,7 @@ SOURCES += \ app/adsb/adsb.cpp \ app/adsb/adsbvehicle.cpp \ app/adsb/adsbvehiclemanager.cpp \ + app/adsb/drawingcanvas.cpp \ app/adsb/qmlobjectlistmodel.cpp \ app/logging/hudlogmessagesmodel.cpp \ app/logging/logmessagesmodel.cpp \ @@ -118,6 +119,7 @@ HEADERS += \ app/adsb/adsb.h \ app/adsb/adsbvehicle.h \ app/adsb/adsbvehiclemanager.h \ + app/adsb/drawingcanvas.h \ app/adsb/qmlobjectlistmodel.h \ app/common/util_fs.h \ app/common/StringHelper.hpp \ diff --git a/app/adsb/adsbvehicle.cpp b/app/adsb/adsbvehicle.cpp index 3d2b2eec9..7b001d342 100644 --- a/app/adsb/adsbvehicle.cpp +++ b/app/adsb/adsbvehicle.cpp @@ -35,11 +35,11 @@ void ADSBVehicle::update(const VehicleInfo_t& vehicleInfo) } } if (vehicleInfo.availableFlags & LocationAvailable) { - if (_vehicle_lat != vehicleInfo.vehicle_lat || _vehicle_lon != vehicleInfo.vehicle_lon) { - _vehicle_lat = vehicleInfo.vehicle_lat; - _vehicle_lon = vehicleInfo.vehicle_lon; - emit vehicleLatChanged(); - emit vehicleLonChanged(); + if (_lat != vehicleInfo.lat || _lon != vehicleInfo.lon) { + _lat = vehicleInfo.lat; + _lon = vehicleInfo.lon; + emit latChanged(); + emit lonChanged(); } } if (vehicleInfo.availableFlags & AltitudeAvailable) { diff --git a/app/adsb/adsbvehicle.h b/app/adsb/adsbvehicle.h index 6e806d7d0..7369b488b 100644 --- a/app/adsb/adsbvehicle.h +++ b/app/adsb/adsbvehicle.h @@ -33,8 +33,8 @@ class ADSBVehicle : public QObject typedef struct { uint32_t icaoAddress; // Required QString callsign; - double vehicle_lat; - double vehicle_lon; + double lat; + double lon; double altitude; double velocity; double heading; @@ -49,8 +49,8 @@ class ADSBVehicle : public QObject Q_PROPERTY(int icaoAddress READ icaoAddress CONSTANT) Q_PROPERTY(QString callsign READ callsign NOTIFY callsignChanged) - Q_PROPERTY(double vehicle_lat READ vehicle_lat NOTIFY vehicleLatChanged) - Q_PROPERTY(double vehicle_lon READ vehicle_lon NOTIFY vehicleLonChanged) + Q_PROPERTY(double lat READ lat NOTIFY latChanged) + Q_PROPERTY(double lon READ lon NOTIFY lonChanged) Q_PROPERTY(double altitude READ altitude NOTIFY altitudeChanged) // NaN for not available Q_PROPERTY(double velocity READ velocity NOTIFY velocityChanged) // NaN for not available Q_PROPERTY(double heading READ heading NOTIFY headingChanged) // NaN for not available @@ -61,8 +61,8 @@ class ADSBVehicle : public QObject int icaoAddress (void) const { return static_cast(_icaoAddress); } QString callsign (void) const { return _callsign; } - double vehicle_lat (void) const { return _vehicle_lat; } - double vehicle_lon (void) const { return _vehicle_lon; } + double lat (void) const { return _lat; } + double lon (void) const { return _lon; } double altitude (void) const { return _altitude; } double velocity (void) const { return _velocity; } double heading (void) const { return _heading; } @@ -79,8 +79,8 @@ class ADSBVehicle : public QObject bool tooFar(); signals: - void vehicleLatChanged (); - void vehicleLonChanged (); + void latChanged (); + void lonChanged (); void callsignChanged (); void altitudeChanged (); void velocityChanged (); @@ -96,8 +96,8 @@ class ADSBVehicle : public QObject uint32_t _icaoAddress; QString _callsign; - double _vehicle_lat; - double _vehicle_lon; + double _lat; + double _lon; double _altitude; double _velocity; double _heading; diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp index e49aa3147..41de231b2 100644 --- a/app/adsb/adsbvehiclemanager.cpp +++ b/app/adsb/adsbvehiclemanager.cpp @@ -95,7 +95,7 @@ void ADSBVehicleManager::_cleanupStaleVehicles() for (int i=_adsbVehicles.count()-1; i>=0; i--) { ADSBVehicle* adsbVehicle = _adsbVehicles.value(i); if (adsbVehicle->expired()) { - // qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); + qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); _adsbVehicles.removeAt(i); _adsbICAOMap.remove(adsbVehicle->icaoAddress()); adsbVehicle->deleteLater(); @@ -115,7 +115,7 @@ void ADSBVehicleManager::_cleanupStaleVehicles() //currently not used.. was for testing but could have future purpose to turn off display void ADSBVehicleManager::adsbClearModel(){ - //qDebug() << "_adsbVehicles.clearAndDeleteContents"; + qDebug() << "_adsbVehicles.clearAndDeleteContents"; _adsbVehicles.clearAndDeleteContents(); } @@ -126,7 +126,7 @@ void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehi //no point in continuing because no location. This is somewhat redundant with parser //possible situation where we start to not get location.. and gets stale then removed if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) { -qDebug() << "ADD/Update ADSB Vehicle"; +//qDebug() << "ADD/Update ADSB Vehicle"; //decide if its new or needs update if (_adsbICAOMap.contains(icaoAddress)) { _adsbICAOMap[icaoAddress]->update(vehicleInfo); @@ -261,7 +261,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); - qDebug() << "MAX adsb distance=" << max_distance; + //qDebug() << "MAX adsb distance=" << max_distance; if (reply->error()) { qDebug() << "ADSB OpenSky request error!"; @@ -310,11 +310,6 @@ void ADSBInternet::processReply(QNetworkReply *reply) { for (const QJsonValue &aircraftValue : acArray) { QJsonObject aircraft = aircraftValue.toObject(); - qDebug() << "Type:" << aircraft["type"].toString(); - qDebug() << "Flight:" << aircraft["flight"].toString(); - // Add more fields as needed - qDebug() << ""; - ADSBVehicle::VehicleInfo_t adsbInfo; //Aircraft Hex @@ -336,9 +331,10 @@ void ADSBInternet::processReply(QNetworkReply *reply) { double lon = aircraft["lon"].toDouble(); qDebug()<<"lat/lon" << lat << " / " << lon; //adsbInfo.location = location; - adsbInfo.vehicle_lat = lat; - adsbInfo.vehicle_lon = lon; + adsbInfo.lat = lat; + adsbInfo.lon = lon; adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; + qDebug()<<"avail flag:"<< adsbInfo.availableFlags; //evaluate distance for INTERNET adsb traffic... this is redundant with sdr double lat_1 = m_api_lat; @@ -557,8 +553,8 @@ void ADSBSdr::processReply(QNetworkReply *reply) { //QGeoCoordinate location(lat, lon); //adsbInfo.location = location; - adsbInfo.vehicle_lat = lat; - adsbInfo.vehicle_lon = lon; + adsbInfo.lat = lat; + adsbInfo.lon = lon; adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; //evaluate distance for SDR adsb traffic... this is redundant with internet diff --git a/app/adsb/drawingcanvas.cpp b/app/adsb/drawingcanvas.cpp new file mode 100644 index 000000000..a6fc6e240 --- /dev/null +++ b/app/adsb/drawingcanvas.cpp @@ -0,0 +1,290 @@ +#include +#include +#include +#include + +//#include "openhd.h" + +#include "../adsb/drawingcanvas.h" +#include "qpainterpath.h" + + +DrawingCanvas::DrawingCanvas(QQuickItem *parent): QQuickPaintedItem(parent) { + //qDebug() << "DrawingCanvas::DrawingCanvas()"; + setRenderTarget(RenderTarget::FramebufferObject); + + //set font to pixels size early + m_font.setPixelSize(14); + m_fontNormal.setPixelSize(35); +//TODO fix settings reference +// QSettings settings; +} + +void DrawingCanvas::paint(QPainter* painter) { + painter->save(); + + // qDebug() << "DrawingCanvas::paint"; + + if (m_draw_request=="adsb"){ //statis for now, but here for future build out + auto pos_x= 130;//the middle + auto pos_y= 130; + + painter->setPen(m_color); + setOpacity(1.0); + + painter->translate(pos_x,pos_y); + + painter->rotate(-90);//glyph is oriented +90 + + + // bool orientation_setting = settings.value("map_orientation").toBool(); + //TODO fix settings reference + bool orientation_setting=true; + if (orientation_setting == true){ //orienting map to drone + m_orientation = m_heading - m_drone_heading; + + if (m_orientation < 0) m_orientation += 360; + if (m_orientation >= 360) m_orientation -=360; + painter->rotate(m_orientation); + } + else{ //orienting map to north + m_orientation=0; + painter->rotate(m_heading); + } + + //draw speed tail + painter->setOpacity(0.5); + painter->fillRect(QRectF(0, -8, -m_speed/12, 4), "white"); + painter->setPen("grey"); + painter->drawRect(QRectF(0, -8, -m_speed/12, 4)); + + //add icon glyph of airplane + /* //for glow effect + painter->setOpacity(1.0); + painter->setPen("grey"); + painter->setFont(m_fontBig); + painter->drawText(0, 0, "\ue3d0"); + */ + + painter->setOpacity(1.0); + painter->setPen("black"); + painter->setFont(m_fontNormal); + + painter->drawText(0, 0, "\uf072"); + + //draw data block + + painter->translate(+50,-60); //+up -down, -left +right + + //de-rotate whatever was done above and the adjustment for the glyph + if (m_orientation!=0){ + painter->rotate(-m_orientation+90); + } + else { + painter->rotate(-m_heading+90); + } + + painter->translate(-33,-24); //preposition the text block + + painter->setOpacity(0.5); + QPainterPath path; + path.addRoundedRect(QRectF(0, 0, 80, 50), 10, 10); + QPen pen(Qt::white, 2); + painter->setPen(pen); + painter->fillPath(path, Qt::black); + painter->drawPath(path); + + painter->setOpacity(1.0); + painter->setPen("white"); + painter->setFont(m_font); + + painter->drawText(5, 15, m_name); + painter->drawText(10, 30, m_speed_text); + painter->drawText(10, 45, m_alt_text); + + painter->restore(); + } +} + + + +QColor DrawingCanvas::color() const { + return m_color; +} + +QColor DrawingCanvas::glow() const { + return m_glow; +} + +void DrawingCanvas::setColor(QColor color) { + m_color = color; + emit colorChanged(m_color); + update(); +} + +void DrawingCanvas::setGlow(QColor glow) { + m_glow = glow; + emit glowChanged(m_glow); + update(); +} + +void DrawingCanvas::setFpvInvertPitch(bool fpvInvertPitch) { + m_fpvInvertPitch = fpvInvertPitch; + emit fpvInvertPitchChanged(m_fpvInvertPitch); + update(); +} + +void DrawingCanvas::setFpvInvertRoll(bool fpvInvertRoll) { + m_fpvInvertRoll = fpvInvertRoll; + emit fpvInvertRollChanged(m_fpvInvertRoll); + update(); +} + +void DrawingCanvas::setHeading(int heading) { + m_heading = heading; + emit headingChanged(m_heading); + update(); +} + +void DrawingCanvas::setDroneHeading(int drone_heading) { + m_drone_heading = drone_heading; + emit droneHeadingChanged(m_drone_heading); + update(); +} + +void DrawingCanvas::setAlt(int alt) { + m_alt = alt; + + if(alt>9999){ + m_alt_text="---"; + } + else{ + //TODO fix settings reference + //imperial = settings.value("enable_imperial").toBool(); + imperial=false; + if (imperial == false){ + m_alt_text = (QString::number(round(alt)))+" M"; + } + else{ + m_alt_text = (QString::number(round((alt) * 3.28084)))+" Ft"; + } + } + + emit altChanged(m_alt); + emit altTextChanged(m_alt_text); + update(); +} + +void DrawingCanvas::setAltText(QString alt_text) { + m_alt_text = alt_text; + emit altTextChanged(m_alt_text); + update(); +} + +void DrawingCanvas::setDroneAlt(int drone_alt) { + m_drone_alt = drone_alt; + emit droneAltChanged(m_drone_alt); + update(); +} + +void DrawingCanvas::setSpeed(int speed) { + if(speed>9999){ + m_speed=0; + m_speed_text="---"; + } + else{ + //TODO fix settings reference + //imperial = settings.value("enable_imperial").toBool(); + imperial=false; + if (imperial == false){ + m_speed =round(speed* 3.6); + m_speed_text = (QString::number(round(speed* 3.6)))+" Kph"; + } + else{ + m_speed = round(speed* 2.23694); + m_speed_text = (QString::number(round((speed* 2.23694))))+" Mph"; + } + } + + emit speedTextChanged(m_speed_text); + emit speedChanged(m_speed); + update(); +} + +void DrawingCanvas::setSpeedText(QString speed_text) { + m_speed_text = speed_text; + emit speedTextChanged(m_speed_text); + update(); +} + +void DrawingCanvas::setVertSpd(int vert_spd) { + m_vert_spd = vert_spd; + emit vertSpdChanged(m_vert_spd); + update(); +} + +void DrawingCanvas::setRoll(int roll) { + m_roll = roll; + emit rollChanged(m_roll); + update(); +} + +void DrawingCanvas::setPitch(int pitch) { + m_pitch = pitch; + emit pitchChanged(m_pitch); + update(); +} + +void DrawingCanvas::setLateral(int lateral) { + m_lateral = lateral; + emit lateralChanged(m_lateral); + update(); +} + +void DrawingCanvas::setVertical(int vertical) { + m_vertical = vertical; + emit verticalChanged(m_vertical); + update(); +} + +void DrawingCanvas::setHorizonSpacing(int horizonSpacing) { + m_horizonSpacing = horizonSpacing; + emit horizonSpacingChanged(m_horizonSpacing); + update(); +} + +void DrawingCanvas::setHorizonWidth(double horizonWidth) { + m_horizonWidth = horizonWidth; + emit horizonWidthChanged(m_horizonWidth); + update(); +} + +void DrawingCanvas::setSize(double size) { + m_size = size; + emit sizeChanged(m_size); + update(); +} + +void DrawingCanvas::setName(QString name) { + m_name = name; + emit nameChanged(m_name); + update(); +} + +void DrawingCanvas::setVerticalLimit(double verticalLimit) { + m_verticalLimit = verticalLimit; + emit verticalLimitChanged(m_verticalLimit); + update(); +} + +void DrawingCanvas::setLateralLimit(double lateralLimit) { + m_lateralLimit = lateralLimit; + emit lateralLimitChanged(m_lateralLimit); + update(); +} + +void DrawingCanvas::setFontFamily(QString fontFamily) { + m_fontFamily = fontFamily; + emit fontFamilyChanged(m_fontFamily); + update(); +} diff --git a/app/adsb/drawingcanvas.h b/app/adsb/drawingcanvas.h new file mode 100644 index 000000000..b674cd211 --- /dev/null +++ b/app/adsb/drawingcanvas.h @@ -0,0 +1,151 @@ +#include +#include +#include + +//#include "openhd.h" + +class DrawingCanvas : public QQuickPaintedItem { + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QColor glow READ glow WRITE setGlow NOTIFY glowChanged) + Q_PROPERTY(bool fpvInvertPitch MEMBER m_fpvInvertPitch WRITE setFpvInvertPitch NOTIFY fpvInvertPitchChanged) + Q_PROPERTY(bool fpvInvertRoll MEMBER m_fpvInvertRoll WRITE setFpvInvertRoll NOTIFY fpvInvertRollChanged) + + Q_PROPERTY(int alt MEMBER m_alt WRITE setAlt NOTIFY altChanged) + Q_PROPERTY(QString alt_text MEMBER m_alt_text WRITE setAltText NOTIFY altTextChanged) + Q_PROPERTY(int drone_alt MEMBER m_drone_alt WRITE setDroneAlt NOTIFY droneAltChanged) + Q_PROPERTY(int speed MEMBER m_speed WRITE setSpeed NOTIFY speedChanged) + Q_PROPERTY(QString speed_text MEMBER m_speed_text WRITE setSpeedText NOTIFY speedTextChanged) + Q_PROPERTY(int vert_spd MEMBER m_vert_spd WRITE setVertSpd NOTIFY vertSpdChanged) + Q_PROPERTY(int heading MEMBER m_heading WRITE setHeading NOTIFY headingChanged) + Q_PROPERTY(int drone_heading MEMBER m_drone_heading WRITE setDroneHeading NOTIFY droneHeadingChanged) + Q_PROPERTY(int roll MEMBER m_roll WRITE setRoll NOTIFY rollChanged) + Q_PROPERTY(int pitch MEMBER m_pitch WRITE setPitch NOTIFY pitchChanged) + + Q_PROPERTY(int lateral MEMBER m_lateral WRITE setLateral NOTIFY lateralChanged) + Q_PROPERTY(int vertical MEMBER m_vertical WRITE setVertical NOTIFY verticalChanged) + + Q_PROPERTY(int horizonSpacing MEMBER m_horizonSpacing WRITE setHorizonSpacing NOTIFY horizonSpacingChanged) + Q_PROPERTY(double horizonWidth MEMBER m_horizonWidth WRITE setHorizonWidth NOTIFY horizonWidthChanged) + Q_PROPERTY(double size MEMBER m_size WRITE setSize NOTIFY sizeChanged) + + Q_PROPERTY(QString name MEMBER m_name WRITE setName NOTIFY nameChanged) + + Q_PROPERTY(double verticalLimit MEMBER m_verticalLimit WRITE setVerticalLimit NOTIFY verticalLimitChanged) + Q_PROPERTY(double lateralLimit MEMBER m_lateralLimit WRITE setLateralLimit NOTIFY lateralLimitChanged) + + Q_PROPERTY(QString fontFamily MEMBER m_fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) + +public: + explicit DrawingCanvas(QQuickItem* parent = nullptr); + + void paint(QPainter* painter) override; + + QColor color() const; + QColor glow() const; + +public slots: + void setColor(QColor color); + void setGlow(QColor glow); + void setFpvInvertPitch(bool fpvInvertPitch); + void setFpvInvertRoll(bool fpvInvertRoll); + + void setAlt(int alt); + void setAltText(QString alt_text); + void setDroneAlt(int drone_alt); + void setSpeed(int speed); + void setSpeedText(QString speed_text); + void setVertSpd(int vert_spd); + void setHeading(int heading); + void setDroneHeading(int drone_heading); + void setRoll(int roll); + void setPitch(int pitch); + + void setLateral(int lateral); + void setVertical(int vertical); + + void setHorizonSpacing(int horizonSpacing); + void setHorizonWidth(double horizonWidth); + void setSize(double size); + + void setName(QString name); + + void setVerticalLimit(double verticalLimit); + void setLateralLimit(double lateralLimit); + + void setFontFamily(QString fontFamily); + +signals: + void colorChanged(QColor color); + void glowChanged(QColor glow); + void fpvInvertPitchChanged(bool fpvInvertPitch); + void fpvInvertRollChanged(bool fpvInvertRoll); + + void altChanged(int alt); + void altTextChanged(QString alt_text); + void droneAltChanged(int drone_alt); + void speedChanged(int speed); + void speedTextChanged(QString speed_text); + void vertSpdChanged(int vert_spd); + void headingChanged(int heading); + void droneHeadingChanged(int drone_heading); + void rollChanged(int roll); + void pitchChanged(int pitch); + + void lateralChanged(int lateral); + void verticalChanged(int vertical); + + void horizonSpacingChanged(int horizonSpacing); + void horizonWidthChanged(double horizonWidth); + void sizeChanged(double size); + + void nameChanged(QString name); + + void verticalLimitChanged(double verticalLimit); + void lateralLimitChanged(double lateralLimit); + + void fontFamilyChanged(QString fontFamily); + +private: + QColor m_color; + QColor m_glow; + bool m_fpvInvertPitch; + bool m_fpvInvertRoll; + + int m_heading; + int m_drone_heading; + int m_alt; + QString m_alt_text; + int m_drone_alt; + int m_speed; + QString m_speed_text; + int m_vert_spd; + int m_roll; + int m_pitch; + + int m_lateral; + int m_vertical; + + int m_horizonSpacing; + double m_horizonWidth; + double m_size; + + QString m_name; + + double m_verticalLimit; + double m_lateralLimit; +//TODO fix setting reference + //QSettings settings; + bool imperial; + + int m_orientation=0; + + QString m_fontFamily; + + QString m_draw_request="adsb"; //for future build out of more draw requests + + QFont m_font = QFont("Font Awesome 5 Free", 10, QFont::Bold, false); + QFont m_fontNormal = QFont("osdicons", 25 , QFont::PreferAntialias, true); + //QFont m_fontBig = QFont("osdicons", 25*1.1, QFont::PreferAntialias, true); + +}; diff --git a/app/main.cpp b/app/main.cpp index 3c33ea62f..a5e0f9760 100755 --- a/app/main.cpp +++ b/app/main.cpp @@ -39,6 +39,7 @@ const QVector permissions({"android.permission.INTERNET", #include "adsb/adsbvehicle.h" #include "adsb/adsbvehiclemanager.h" #include "adsb/qmlobjectlistmodel.h" +#include "adsb/drawingcanvas.h" // Video - annyoing ifdef crap is needed for all the different platforms / configurations @@ -301,6 +302,7 @@ int main(int argc, char *argv[]) { qmlRegisterUncreatableType("OpenHD", 1, 0, "QmlObjectListModel", "Reference only"); + qmlRegisterType("OpenHD", 1, 0, "DrawingCanvas"); QQmlApplicationEngine engine; diff --git a/app/telemetry/models/fcmavlinksystem.cpp b/app/telemetry/models/fcmavlinksystem.cpp index 7a2aaaddb..c2a25a701 100644 --- a/app/telemetry/models/fcmavlinksystem.cpp +++ b/app/telemetry/models/fcmavlinksystem.cpp @@ -211,7 +211,7 @@ bool FCMavlinkSystem::process_message(const mavlink_message_t &msg) else { added_distance_m=distance_between(m_last_lat,m_last_lon,lat,lon); } - qDebug() << "added_distance_m" << added_distance_m; + //qDebug() << "added_distance_m" << added_distance_m; if (added_distance_m > 2.0){ total_dist = total_dist + added_distance_m; diff --git a/qml/ui/widgets/map/MapComponent.qml b/qml/ui/widgets/map/MapComponent.qml index 2ccf1fccc..3f1a07e9f 100644 --- a/qml/ui/widgets/map/MapComponent.qml +++ b/qml/ui/widgets/map/MapComponent.qml @@ -150,6 +150,152 @@ Map { } + //>>>>>>>>>>>>>>>>>>> ADSB <<<<<<<<<<<<<<<<<<< + /* this graphically depicts the area in which adsb traffic is being sourced + *TODO fix.... will be a circle per the api + MapRectangle { + id: adsbSquare + topLeft : EnableADSB ? AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 315, 0.0) : QtPositioning.coordinate(0, 0) + bottomRight: EnableADSB ? AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 135, 0.0) : QtPositioning.coordinate(0, 0) + enabled: EnableADSB + visible: settings.adsb_api_openskynetwork + color: "white" + border.color: "red" + border.width: 5 + smooth: true + opacity: .3 + } + */ + + MapItemView { + id: markerMapView + model: AdsbVehicleManager.adsbVehicles + delegate: markerComponentDelegate + //TODO refactor setting + //visible: EnableADSB + visible: true + + Component { + id: markerComponentDelegate + + MapItemGroup { + id: delegateGroup + + MapQuickItem { + id: marker + + anchorPoint.x: 0 + anchorPoint.y: 0 + width: 260 + height: 260 + + + sourceItem: + + DrawingCanvas { + id: icon + anchors.centerIn: parent + + width: 260 + height: 260 + + color: settings.color_shape + glow: settings.color_glow + + name: object.callsign + + drone_heading: _fcMavlinkSystem.hdg; //need this to adjust orientation + + drone_alt: _fcMavlinkSystem.altitude_msl_m; + + heading: object.heading; + + speed: object.velocity + + alt: { + console.log("map lat / lon:" + object.vehicle_lat + " " + object.vehicle_lon); + + return object.altitude; + +} + +// { +/* check if traffic is a threat.. this should not be done here. Left as REF + if (object.altitude - OpenHD.alt_msl < 300 && model.distance < 2){ + //console.log("TRAFFIC WARNING"); + + //image.source="/airplanemarkerwarn.png"; + background.border.color = "red"; + background.border.width = 5; + background.opacity = 0.5; + } else if (object.altitude - OpenHD.alt_msl < 500 && model.distance < 5){ + //console.log("TRAFFIC ALERT"); + + //image.source="/airplanemarkeralert.png"; + background.border.color = "yellow"; + background.border.width = 5; + background.opacity = 0.5; + } +*/ + +/* *discovered issues when the object is referenced multiple times + *last attempt at putting altitude into a var still resulted in "nulls" + + var _adsb_alt; + + _adsb_alt=object.altitude; + + if ( _adsb_alt> 9999) { + //console.log("qml: model alt or vertical undefined") + return "---"; + } else { + if(object.verticalVel > .2){ //climbing + if (settings.enable_imperial === false){ + return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue696" + } + else{ + return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue696" + } + } + else if (object.verticalVel < -.2){//descending + if (settings.enable_imperial === false){ + return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue697" + } + else{ + return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue697" + } + } + else { + if (settings.enable_imperial === false){//level + return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\u2501" + } + else{ + return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\u2501" + } + } + } + } + */ + } + //position everything + + coordinate : QtPositioning.coordinate( object.lat , object.lon ); + // coordinate: marker.coordinate; + + // coordinate { + // latitude: object.lat + // longitude: object.lon + // console.log("object lat/lon:"+object.lat+ " "+object.lon); + // } + + } + Component.onCompleted: map.addMapItemGroup(this); + } + } + } + //>>>>>>>>>>>>>>>>>>> end ADSB <<<<<<<<<<<<<<< + + //get coordinates on click... for future use MouseArea { anchors.fill: parent From c2806ef8c340981dc301af427eb9a53ac2dcf1cb Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Wed, 3 Jan 2024 20:22:57 -0500 Subject: [PATCH 04/12] ADSB internet api responds to map center --- QOpenHD.pro | 2 - app/adsb/adsb.cpp | 416 ---------------------------- app/adsb/adsb.h | 83 ------ app/adsb/adsbvehiclemanager.cpp | 80 +++--- app/adsb/adsbvehiclemanager.h | 12 +- app/main.cpp | 9 +- qml/ui/widgets/map/MapComponent.qml | 2 + 7 files changed, 48 insertions(+), 556 deletions(-) delete mode 100644 app/adsb/adsb.cpp delete mode 100644 app/adsb/adsb.h diff --git a/QOpenHD.pro b/QOpenHD.pro index 835334878..6ace3a166 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -101,7 +101,6 @@ LinuxBuild { # All Generic files / files that literally have 0!! dependencies other than qt SOURCES += \ - app/adsb/adsb.cpp \ app/adsb/adsbvehicle.cpp \ app/adsb/adsbvehiclemanager.cpp \ app/adsb/drawingcanvas.cpp \ @@ -116,7 +115,6 @@ SOURCES += \ app/main.cpp \ HEADERS += \ - app/adsb/adsb.h \ app/adsb/adsbvehicle.h \ app/adsb/adsbvehiclemanager.h \ app/adsb/drawingcanvas.h \ diff --git a/app/adsb/adsb.cpp b/app/adsb/adsb.cpp deleted file mode 100644 index 90a1c684d..000000000 --- a/app/adsb/adsb.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include "adsb.h" -#include -#include -#include - -#ifndef __windows__ -#include -#endif - -#include -#include -#include - -#include - -#include -#include - - -#include -#include -#include -#include - -//#include -#include - - -#include -//#include -#include "../telemetry/models/markermodel.h" -#include -//#include "openhd.h" -//#include "localmessage.h" - -static Adsb* _instance = nullptr; - -Adsb* Adsb::instance() { - if (_instance == nullptr) { - _instance = new Adsb(); - } - return _instance; -} - -Adsb::Adsb(QObject *parent): QObject(parent) { - qDebug() << "Adsb::Adsb()"; -} - -void Adsb::onStarted() { - qDebug() << "------------------Adsb::onStarted()"; - -#if defined(__rasp_pi__) - groundAddress = "127.0.0.1"; -#endif - - auto markerModel = MarkerModel::instance(); - connect(this, &Adsb::addMarker, markerModel, &MarkerModel::addMarker); - connect(this, &Adsb::doneAddingMarkers, markerModel, &MarkerModel::doneAddingMarkers); - connect(this, &Adsb::removeAllMarkers, markerModel, &MarkerModel::removeAllMarkers); - - QNetworkAccessManager * manager = new QNetworkAccessManager(this); - - m_manager = manager; - - connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))) ; - - timer = new QTimer(this); - connect(timer, &QTimer::timeout, this, &Adsb::requestData); - // How frequently data is requested - timer->start(timer_interval); -} - -void Adsb::setGroundIP(QString address) { - groundAddress = address; -} - -/* -void Adsb::mapBoundsChanged(double map_lat, double map_lon) { - //center_lat= center_coord.latitude(); - //center_lon= center_coord.longitude(); - - // api_request_center_lat=setLatitude(center_lat); - //api_request_center_lon=setLongitude(center_lon); - - - need to limit the map bounds to a distance and - when map orients to drone it breaks api as it expects a box oriented north. - So defining our own bound box for the api... - - - auto adsb_distance_limit = settings.value("adsb_distance_limit").toInt(); - - - QGeoCoordinate qgeo_upper_left; - QGeoCoordinate qgeo_lower_right; - - qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); - qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); - - upperl_lat= QString::number(qgeo_upper_left.latitude()); - upperl_lon= QString::number(qgeo_upper_left.longitude()); - lowerr_lat= QString::number(qgeo_lower_right.latitude()); - lowerr_lon= QString::number(qgeo_lower_right.longitude()); - - - qDebug() << "Adsb::lower right=" << lowerr_lat << " " << lowerr_lon; - qDebug() << "Adsb::upper left=" << upperl_lat << " " << upperl_lon; - qDebug() << "Adsb::Center=" << center_lat << " " << center_lon; - - -} -*/ -void Adsb::setAdsbLat(double map_lat){ - m_adsb_lat=map_lat; - //qDebug() << "adsb_api_coord=" << m_adsb_api_coord; - emit adsbLatChanged(map_lat); -} - -void Adsb::setAdsbLon(double map_lon){ - m_adsb_lon=map_lon; - //qDebug() << "adsb_api_coord=" << m_adsb_api_coord; - emit adsbLatChanged(map_lon); -} - -void Adsb::requestData() { - - //qDebug() << "Adsb::requestData()"; - auto show_adsb = settings.value("show_adsb", false).toBool(); - - adsb_api_sdr = settings.value("adsb_api_sdr").toBool(); - - - if (adsb_api_sdr == true){ - //qDebug() << "timer 1"; - timer->stop(); - timer->start(1000); - - if (groundAddress.isEmpty()) { - // TODO REFACTOR MSG - //LocalMessage::instance()->showMessage("No ADSB Ground Address", 4); - return; - } - - adsb_url= "http://"+groundAddress+":8080/data/aircraft.json"; - } - else { - //qDebug() << "timer 10"; - timer->stop(); - timer->start(10000); - setAdsbLat(m_map_lat); - setAdsbLon(m_map_lon); - // TODO REFORM THE API REQUEST - adsb_url= "test"; - //adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; - } - - if(show_adsb==false){ - emit removeAllMarkers(); - return; - } - //qDebug() << "URL REQUEST:" << adsb_url; - QNetworkRequest request; - QUrl api_request= adsb_url; - request.setUrl(api_request); - request.setRawHeader("User-Agent", "MyOwnBrowser 1.0"); - //qDebug() << "url=" << api_request; - QNetworkReply *reply = m_manager->get(request); -} - -void Adsb::processReply(QNetworkReply *reply){ - - QString callsign; - int contact; - double lat; - double lon; - int alt; - int track; - int velocity; - double vertical; - qreal distance; - - if (reply->error()) { - qDebug() << "ADSB request error!"; - qDebug() << reply->errorString(); - // TODO REFACTOR MSG - //LocalMessage::instance()->showMessage("ADSB Reply Error", 4); - reply->deleteLater(); - return; - } - - QByteArray data = reply->readAll(); - - QJsonParseError errorPtr; - QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); - - if (doc.isNull()) { - qDebug() << "Parse failed"; - // TODO REFACTOR MSG - //LocalMessage::instance()->showMessage("ADSB Parse Error", 4); - } - - if(doc.isNull()){ - qDebug()<<"Failed to create JSON doc."; - reply->deleteLater(); - return; - } - if(!doc.isObject()){ - qDebug()<<"JSON is not an object."; - reply->deleteLater(); - return; - } - - QJsonObject jsonObject = doc.object(); - - if(jsonObject.isEmpty()){ - qDebug()<<"JSON object is empty."; - reply->deleteLater(); - return; - } - - // --------------- PARSE FOR SDR --------------------------------- - - if (adsb_api_sdr == true){ - - QJsonArray array = jsonObject["aircraft"].toArray(); - - //QJsonArray array = doc.array(); - - if(array.isEmpty()){ - qDebug()<<"JSON array is empty."; - } - - //qDebug() << "MYARRAY COUNT=" << array.count(); - - int current_row=0; - int last_row=array.count(); - - emit removeAllMarkers(); - - if (last_row==0){ - //no markers to add.. - reply->deleteLater(); - return; - } - - - foreach (const QJsonValue & val, array){ - - callsign=val.toObject().value("flight").toString(); - if (callsign.length() == 0) { - callsign = "N/A"; - } - - //sdr defaults to imperial and something wacky for speed from dump1090 - - contact=val.toObject().value("seen_pos").toInt(); - lat=val.toObject().value("lat").toDouble(); - lon=val.toObject().value("lon").toDouble(); - alt=val.toObject().value("altitude").toInt(); - alt=alt*0.3048; - velocity=val.toObject().value("speed").toDouble(); - velocity=round(velocity*.51418); - track=val.toObject().value("track").toDouble(); - vertical=val.toObject().value("vert_rate").toDouble(); - vertical=round(vertical*0.3048); - - - - - //calculate distance from center of map so we can sort in marker model - distance = calculateKmDistance(m_map_lat, m_api_lon, lat, lon); - emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); - current_row=current_row+1; - - if (lat!=0 || lon!=0) { - /*dont evaluate marker if position msg is missing - above we are adding markers with 0 lat lon cuz we dont know the last row count at loop start - those "0 position" aircraft are being eliminated in mapcomponent - */ - evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); - } - /* - qDebug() << "callsign=" << callsign; - qDebug() << "last_contact=" << contact; - qDebug() << "lat=" << lat; - qDebug() << "lon=" << lon; - qDebug() << "alt=" << alt; - qDebug() << "velocity=" << velocity; - qDebug() << "track=" << track; - qDebug() << "vertical=" << vertical; - qDebug() << "distance=" << distance; - - qDebug() << "----------------------------------------------------------"; -*/ - - - - } - - } - // --------------- PARSE FOR API --------------------------------- - else { - - QJsonValue value = jsonObject.value("states"); - QJsonArray array = value.toArray(); - - //qDebug() << "MYARRAY COUNT=" << array.count(); - - int current_row=0; - int last_row=array.count(); - - QString callsign; - int contact; - double lat; - double lon; - int alt; - int track; - int velocity; - double vertical; - qreal distance; - - emit removeAllMarkers(); - - if (last_row==0){ - //no markers to add.. either the api is not happy (too zoomed out) or no traffic to report - reply->deleteLater(); - return; - } - - foreach (const QJsonValue & v, array){ - QJsonArray innerarray = v.toArray(); - - callsign=innerarray[1].toString(); - if (callsign.length() == 0) { - callsign = "N/A"; - } - contact=innerarray[4].toInt(); - lat=innerarray[6].toDouble(); - lon=innerarray[5].toDouble(); - alt=innerarray[7].toDouble(); - velocity=innerarray[9].toDouble(); - track=innerarray[10].toDouble(); - vertical=innerarray[11].toDouble(); - - //calculate distance from center of map so we can sort in marker model - - distance = calculateKmDistance(m_map_lat, m_map_lon, lat, lon); - emit addMarker(current_row, last_row, Traffic(callsign,contact,lat,lon,alt,velocity,track,vertical,distance)); - - evaluateTraffic(callsign, contact, lat, lon, alt, velocity, track, vertical, distance); - - current_row=current_row+1; - - /* - qDebug() << "callsign=" << innerarray[1].toString(); - qDebug() << "last_contact=" << innerarray[4].toInt(); - qDebug() << "lat=" << innerarray[6].toDouble(); - qDebug() << "lon=" << innerarray[5].toDouble(); - qDebug() << "alt=" << innerarray[7].toDouble(); - qDebug() << "velocity=" << innerarray[9].toDouble(); - qDebug() << "track=" << innerarray[10].toDouble(); - qDebug() << "vertical=" << innerarray[11].toDouble(); - qDebug() << "distance=" << distance; - qDebug() << "----------------------------------------------------------"; -*/ - } - - } - //emit doneAddingMarkers(); - reply->deleteLater(); -} - -void Adsb::evaluateTraffic(QString traffic_callsign, - int traffic_contact, - double traffic_lat, - double traffic_lon, - double traffic_alt, - double traffic_velocity, - double traffic_track, - double traffic_vertical, - double traffic_distance) { - - /* - * Centralise traffic threat detection here. Once threat is detected it should be - * labled and then sent over to the adsb widget - * - * need to calculate azimuth and bearing of any threats so that it can be shared - * and depicted in the adsb widget - */ - - // TODO this is not defined ???????? - int drone_alt = m_msl_alt; - - if (traffic_alt - drone_alt < 300 && traffic_distance < 2) { - // TODO REFACTOR MSG - //LocalMessage::instance()->showMessage("Aircraft Traffic", 3); - } else if (traffic_alt - drone_alt < 500 && traffic_distance < 5) { - // TODO REFACTOR MSG - //LocalMessage::instance()->showMessage("Aircraft Traffic", 4); - } -} - -int Adsb::calculateKmDistance(double lat_1, double lon_1, - double lat_2, double lon_2) { - - double latDistance = qDegreesToRadians(lat_1 - lat_2); - double lngDistance = qDegreesToRadians(lon_1 - lon_2); - - double a = qSin(latDistance / 2) * qSin(latDistance / 2) - + qCos(qDegreesToRadians(m_map_lat)) * qCos(qDegreesToRadians(lat_2)) - * qSin(lngDistance / 2) * qSin(lngDistance / 2); - - double c = 2 * qAtan2(qSqrt(a), qSqrt(1 - a)); - int distance=radius_earth_km * c; - return distance; -} diff --git a/app/adsb/adsb.h b/app/adsb/adsb.h deleted file mode 100644 index d35ff2e33..000000000 --- a/app/adsb/adsb.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef ADSB_H -#define ADSB_H - -#include -#include - -//#include "constants.h" - -#include -#include -#include - -#include -#include -#include -#include - -//#include "util.h" -#include "common/openhd-util.hpp" - -#include "../telemetry/models/markermodel.h" - - -class Adsb: public QObject { - Q_OBJECT - -public: - explicit Adsb(QObject *parent = nullptr); - static Adsb* instance(); - - Q_PROPERTY(double adsb_lat MEMBER m_adsb_lat WRITE setAdsbLat NOTIFY adsbLatChanged); - void setAdsbLat(double map_lat); - Q_PROPERTY(double adsb_lon MEMBER m_adsb_lon WRITE setAdsbLon NOTIFY adsbLonChanged); - void setAdsbLon(double map_lon); - - -signals: - void addMarker(int current_row, int total_rows, const Traffic &traffic); - void doneAddingMarkers(); - void removeAllMarkers(); - void adsbLatChanged(double map_lat); - void adsbLonChanged(double map_lon); - -public slots: - void onStarted(); - //from old api where you eneded a bound rectangle - //void mapBoundsChanged(double map_lat, double map_lon); - - Q_INVOKABLE void setGroundIP(QString address); - -private slots: - void processReply(QNetworkReply *reply); - void requestData(); - int calculateKmDistance(double center_lat, double center_lon, - double marker_lat, double marker_lon); - void evaluateTraffic(QString callsign,int contact,double lat,double lon,double alt,double velocity, - double track,double vertical,double distance); - -private: - int m_msl_alt; - double m_api_lat; - double m_api_lon; - double m_adsb_lat; - double m_adsb_lon; - QNetworkAccessManager * m_manager; - //QString upperl_lat; - //QString upperl_lon; - //QString lowerr_lat; - //QString lowerr_lon; - double m_map_lat; - double m_map_lon; - QSettings settings; - double radius_earth_km = 6371; - QString adsb_url; - int timer_interval = 1000; //get reset later if api or sdr selected - QTimer *timer; - bool adsb_api_sdr; - QString groundAddress; -}; - - - -#endif diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp index 41de231b2..9460f3fee 100644 --- a/app/adsb/adsbvehiclemanager.cpp +++ b/app/adsb/adsbvehiclemanager.cpp @@ -54,10 +54,10 @@ ADSBVehicleManager::~ADSBVehicleManager() void ADSBVehicleManager::onStarted() { - qDebug() << "<<<<<<<<<<<<=0; i--) { ADSBVehicle* adsbVehicle = _adsbVehicles.value(i); if (adsbVehicle->expired()) { - qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); + qDebug() << "Expired" << QStringLiteral("%1").arg(adsbVehicle->icaoAddress(), 0, 16); _adsbVehicles.removeAt(i); _adsbICAOMap.remove(adsbVehicle->icaoAddress()); adsbVehicle->deleteLater(); @@ -126,7 +127,7 @@ void ADSBVehicleManager::adsbVehicleUpdate(const ADSBVehicle::VehicleInfo_t vehi //no point in continuing because no location. This is somewhat redundant with parser //possible situation where we start to not get location.. and gets stale then removed if (vehicleInfo.availableFlags & ADSBVehicle::LocationAvailable) { -//qDebug() << "ADD/Update ADSB Vehicle"; + //qDebug() << "ADD/Update ADSB Vehicle"; //decide if its new or needs update if (_adsbICAOMap.contains(icaoAddress)) { _adsbICAOMap[icaoAddress]->update(vehicleInfo); @@ -189,8 +190,6 @@ void ADSBapi::run(void) } void ADSBapi::init(void) { - qDebug() << "------------------Adsbapi::init()"; - QNetworkAccessManager * manager = new QNetworkAccessManager(this); m_manager = manager; @@ -202,46 +201,35 @@ void ADSBapi::init(void) { // How frequently data is requested timer->start(timer_interval); - mapBoundsChanged(40.48205, -3.35996); // this shouldn't be necesary + + //init a lat lon here jsut in case map doesnt send anything in time + mapLatChanged(40.4820); + mapLonChanged(-3.3599); } -/* this from when the old api needed a bounding rectangle - * keep in case api changes again - */ -void ADSBapi::mapBoundsChanged(double map_lat, double map_lon) { - qDebug() << "------------------Adsbapi::mapboundschanged()"; +// This is the slot for the singal emitted from adsbvehicle class that map center changed +void ADSBapi::mapLatChanged(double map_lat) { m_api_lat=map_lat; + lat_string=QString::number(m_api_lat); + qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); +} +void ADSBapi::mapLonChanged(double map_lon) { m_api_lon=map_lon; + lon_string=QString::number(m_api_lon); qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); -/* - QGeoCoordinate qgeo_upper_left; - QGeoCoordinate qgeo_lower_right; - - qgeo_upper_left = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 315, 0.0); - qgeo_lower_right = center_coord.atDistanceAndAzimuth(adsb_distance_limit, 135, 0.0); - - upperl_lat= QString::number(qgeo_upper_left.latitude()); - upperl_lon= QString::number(qgeo_upper_left.longitude()); - lowerr_lat= QString::number(qgeo_lower_right.latitude()); - lowerr_lon= QString::number(qgeo_lower_right.longitude()); -*/ } void ADSBInternet::requestData(void) { - qDebug() << "------------------AdsbInternet::requestdata()"; _adsb_api_openskynetwork = _settings.value("adsb_api_openskynetwork").toBool(); _show_adsb_internet = _settings.value("show_adsb").toBool(); // If openskynetwork is disabled by settings don't make the request and return if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - //TODO manage settings - // return; + //TODO manage settings + // return; } - - //adsb_url= "https://opensky-network.org/api/states/all?lamin="+lowerr_lat+"&lomin="+upperl_lon+"&lamax="+upperl_lat+"&lomax="+lowerr_lon; - //TODO REFCTOR URL FOR NEW API - adsb_url="https://api.airplanes.live/v2/point/40.48205/-3.35996/100"; + adsb_url="https://api.airplanes.live/v2/point/"+ lat_string +"/"+ lon_string + "/50"; QNetworkRequest request; QUrl api_request = adsb_url; request.setUrl(api_request); @@ -254,8 +242,8 @@ void ADSBInternet::requestData(void) { void ADSBInternet::processReply(QNetworkReply *reply) { if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - //TODO manage user settings - // return; + //TODO manage user settings + // return; } max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; @@ -304,7 +292,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { QJsonArray acArray = doc.object()["ac"].toArray(); - qDebug()<<"count:" << acArray.count(); + qDebug()<<"adsb aircraft count:" << acArray.count(); // Iterate through the "ac" array for (const QJsonValue &aircraftValue : acArray) { @@ -316,7 +304,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { bool icaoOk; QString icaoAux = aircraft["hex"].toString(); adsbInfo.icaoAddress = icaoAux.toUInt(&icaoOk, 16); - qDebug() << "Hex:" << aircraft["hex"].toString(); + //qDebug() << "Hex:" << aircraft["hex"].toString(); // Skip this element if icao number is not ok if (!icaoOk) { qDebug()<<"skipping - icao not ok"; @@ -329,12 +317,12 @@ void ADSBInternet::processReply(QNetworkReply *reply) { } double lat = aircraft["lat"].toDouble(); double lon = aircraft["lon"].toDouble(); - qDebug()<<"lat/lon" << lat << " / " << lon; + //qDebug()<<"lat/lon" << lat << " / " << lon; //adsbInfo.location = location; adsbInfo.lat = lat; adsbInfo.lon = lon; adsbInfo.availableFlags |= ADSBVehicle::LocationAvailable; - qDebug()<<"avail flag:"<< adsbInfo.availableFlags; + //qDebug()<<"avail flag:"<< adsbInfo.availableFlags; //evaluate distance for INTERNET adsb traffic... this is redundant with sdr double lat_1 = m_api_lat; @@ -356,7 +344,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { // If aircraft beyond max distance than skip this one if(distance>max_distance){ - qDebug() << "Beyond max SKIPPING"; + qDebug() << "Aircraft Beyond max distance... SKIPPING aircraft"; //TODO commented for testing //continue; } @@ -371,7 +359,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { //Aircraft altitude if(aircraft["alt_baro"].toDouble()){ adsbInfo.altitude = aircraft["alt_baro"].toDouble(); - qDebug()<<"alt:" << aircraft["alt_baro"].toDouble(); + //qDebug()<<"alt:" << aircraft["alt_baro"].toDouble(); //per setting eliminate all unknown alt if (adsbInfo.altitude<5 && unknown_zero_alt==false){ qDebug() << "ADSB Skipping aircraft for alt error"; diff --git a/app/adsb/adsbvehiclemanager.h b/app/adsb/adsbvehiclemanager.h index d1c48e11a..46fc93a05 100644 --- a/app/adsb/adsbvehiclemanager.h +++ b/app/adsb/adsbvehiclemanager.h @@ -37,7 +37,8 @@ class ADSBapi : public QThread void run(void) final; public slots: - void mapBoundsChanged(double map_lat, double map_lon); + void mapLatChanged(double map_lat); + void mapLonChanged(double map_lon); protected slots: virtual void processReply(QNetworkReply *reply) = 0; @@ -50,11 +51,10 @@ protected slots: QNetworkAccessManager * m_manager; QString adsb_url; - // boundingbox parameters - //QString upperl_lat; - //QString upperl_lon; - //QString lowerr_lat; - //QString lowerr_lon; + + QString lat_string; + QString lon_string; + // timer for requests int timer_interval; diff --git a/app/main.cpp b/app/main.cpp index a5e0f9760..4eac4887f 100755 --- a/app/main.cpp +++ b/app/main.cpp @@ -35,7 +35,6 @@ const QVector permissions({"android.permission.INTERNET", #include "osd/horizonladder.h" #include "osd/flightpathvector.h" #include "osd/aoagauge.h" -#include "adsb/adsb.h" #include "adsb/adsbvehicle.h" #include "adsb/adsbvehiclemanager.h" #include "adsb/qmlobjectlistmodel.h" @@ -72,6 +71,11 @@ const QVector permissions({"android.permission.INTERNET", #include "util/WorkaroundMessageBox.h" #include "util/restartqopenhdmessagebox.h" +#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 +RESOLVEFUNC(SSL_get1_peer_certificate); +RESOLVEFUNC(EVP_PKEY_get_base_id); +#endif // OPENSSL_VERSION_MAJOR >= 3 + // Load all the fonts we use ?! static void load_fonts(){ @@ -350,11 +354,10 @@ int main(int argc, char *argv[]) { engine.rootContext()->setContextProperty("_wifi_card_gnd2", &WiFiCard::instance_gnd(2)); engine.rootContext()->setContextProperty("_wifi_card_gnd3", &WiFiCard::instance_gnd(3)); engine.rootContext()->setContextProperty("_wifi_card_air", &WiFiCard::instance_air()); - //engine.rootContext()->setContextProperty("_markermodel", &MarkerModel::instance()); auto adsbVehicleManager = ADSBVehicleManager::instance(); engine.rootContext()->setContextProperty("AdsbVehicleManager", adsbVehicleManager); - //QObject::connect(openHDSettings, &OpenHDSettings::groundStationIPUpdated, adsbVehicleManager, &ADSBVehicleManager::setGroundIP, Qt::QueuedConnection); adsbVehicleManager->onStarted(); + // And then the main part engine.rootContext()->setContextProperty("_mavlinkTelemetry", &MavlinkTelemetry::instance()); diff --git a/qml/ui/widgets/map/MapComponent.qml b/qml/ui/widgets/map/MapComponent.qml index 3f1a07e9f..32ad10b21 100644 --- a/qml/ui/widgets/map/MapComponent.qml +++ b/qml/ui/widgets/map/MapComponent.qml @@ -64,6 +64,8 @@ Map { function findMapBounds(){ var center_coord = map.toCoordinate(Qt.point(map.width/2,map.height/2)) //console.log("Map component: center",center_coord.latitude, center_coord.longitude); + AdsbVehicleManager.newMapLat(center_coord.latitude); + AdsbVehicleManager.newMapLon(center_coord.longitude); } PositionSource { From f8ef9c227620b829138579990be7feb02ec213d9 Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sat, 6 Jan 2024 19:42:31 -0500 Subject: [PATCH 05/12] ADSB control settings --- app/adsb/adsbvehiclemanager.cpp | 42 ++++++++----------- app/adsb/adsbvehiclemanager.h | 13 +++--- .../AppWidgetSettingsView.qml | 28 +++++++++++++ qml/ui/elements/AppSettings.qml | 6 +++ qml/ui/widgets/map/MapComponent.qml | 30 ++++++++----- 5 files changed, 76 insertions(+), 43 deletions(-) diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp index 9460f3fee..2d7c093f2 100644 --- a/app/adsb/adsbvehiclemanager.cpp +++ b/app/adsb/adsbvehiclemanager.cpp @@ -211,25 +211,25 @@ void ADSBapi::init(void) { void ADSBapi::mapLatChanged(double map_lat) { m_api_lat=map_lat; lat_string=QString::number(m_api_lat); - qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); } void ADSBapi::mapLonChanged(double map_lon) { m_api_lon=map_lon; lon_string=QString::number(m_api_lon); - qreal adsb_distance_limit = _settings.value("adsb_distance_limit").toInt(); } void ADSBInternet::requestData(void) { - _adsb_api_openskynetwork = _settings.value("adsb_api_openskynetwork").toBool(); - _show_adsb_internet = _settings.value("show_adsb").toBool(); + _adsb_enable = _settings.value("adsb_enable").toBool(); + _adsb_show_internet_data = _settings.value("adsb_show_internet_data").toBool(); + max_distance = _settings.value("adsb_radius").toInt(); - // If openskynetwork is disabled by settings don't make the request and return - if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - //TODO manage settings - // return; + QString distance_string = QString::number(max_distance/1852); // convert meters to NM for api + + // If adsb or adsb_internet is disabled by settings don't make the internet request and return + if (!_adsb_enable || !_adsb_show_internet_data) { + return; } - adsb_url="https://api.airplanes.live/v2/point/"+ lat_string +"/"+ lon_string + "/50"; + adsb_url="https://api.airplanes.live/v2/point/"+ lat_string +"/"+ lon_string + "/" + distance_string; QNetworkRequest request; QUrl api_request = adsb_url; request.setUrl(api_request); @@ -241,15 +241,10 @@ void ADSBInternet::requestData(void) { void ADSBInternet::processReply(QNetworkReply *reply) { - if (!_adsb_api_openskynetwork || !_show_adsb_internet) { - //TODO manage user settings - // return; - } - - max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; + max_distance=(_settings.value("adsb_radius").toInt()); unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); - //qDebug() << "MAX adsb distance=" << max_distance; + qDebug() << "MAX adsb distance=" << max_distance; if (reply->error()) { qDebug() << "ADSB OpenSky request error!"; @@ -265,7 +260,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { QJsonDocument doc = QJsonDocument::fromJson(data, &errorPtr); if (doc.isNull()) { - qDebug() << "ADSB Opensky network response: Parse failed"; + qDebug() << "ADSB internet network response: Parse failed"; //TODO REFACTOR MSG //LocalMessage::instance()->showMessage("ADSB OpenSky Parse Error", 4); reply->deleteLater(); @@ -281,7 +276,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { QJsonObject jsonObject = doc.object(); if(jsonObject.isEmpty()){ - qDebug()<<"ADSB Openskynetwork response: JSON object is empty."; + qDebug()<<"ADSB internet json response: JSON object is empty."; //TODO REFACTOR MSG //LocalMessage::instance()->showMessage("ADSB OpenSky empty object", 4); reply->deleteLater(); @@ -434,11 +429,10 @@ ADSBSdr::ADSBSdr() void ADSBSdr::requestData(void) { //TODO REFACTOR MSG //Logger::instance()->logData("request data", 1); - _adsb_api_sdr = _settings.value("adsb_api_sdr").toBool(); - _show_adsb_sdr = _settings.value("show_adsb").toBool(); + _adsb_show_sdr_data = _settings.value("adsb_show_sdr_data").toBool(); - // If sdr is disabled by settings don't make the request and return - if (!_adsb_api_sdr || !_show_adsb_sdr) { + // If adsb or sdr adsb is disabled by settings don't make the request and return + if (!_adsb_enable || !_adsb_show_sdr_data) { return; } @@ -454,10 +448,8 @@ void ADSBSdr::requestData(void) { } void ADSBSdr::processReply(QNetworkReply *reply) { + //TODO //Logger::instance()->logData("process reply", 1); - if (!_adsb_api_sdr || !_show_adsb_sdr) { - return; - } max_distance=(_settings.value("adsb_distance_limit").toInt())/1000; unknown_zero_alt=_settings.value("adsb_show_unknown_or_zero_alt").toBool(); diff --git a/app/adsb/adsbvehiclemanager.h b/app/adsb/adsbvehiclemanager.h index 46fc93a05..f01a8165a 100644 --- a/app/adsb/adsbvehiclemanager.h +++ b/app/adsb/adsbvehiclemanager.h @@ -47,6 +47,8 @@ protected slots: protected: void init(); + bool _adsb_enable; + // network QNetworkAccessManager * m_manager; QString adsb_url; @@ -82,8 +84,7 @@ private slots: void requestData() override; private: - bool _adsb_api_openskynetwork; - bool _show_adsb_internet; //wired to show widget setting. somewhat redundant + bool _adsb_show_internet_data; }; // This class gets the info from SDR @@ -102,9 +103,7 @@ private slots: private: QString _groundAddress = ""; - bool _adsb_api_sdr; - bool _show_adsb_sdr; //wired to show widget setting. somewhat redundant - + bool _adsb_show_sdr_data; }; class ADSBVehicleManager : public QObject { @@ -157,8 +156,8 @@ private slots: QTimer _adsbVehicleCleanupTimer; ADSBInternet* _internetLink = nullptr; ADSBSdr* _sdrLink = nullptr; - double _api_lat; - double _api_lon; + double _api_lat; + double _api_lon; QElapsedTimer _last_update_timer; uint _status = 0; diff --git a/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml b/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml index 310299701..26f1d637d 100755 --- a/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml +++ b/qml/ui/configpopup/qopenhd_settings/AppWidgetSettingsView.qml @@ -1159,6 +1159,34 @@ ScrollView { height: rowHeight color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + Text { + text: qsTr("Show ADSB") + font.weight: Font.Bold + font.pixelSize: 13 + anchors.leftMargin: 8 + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + width: 224 + height: elementHeight + anchors.left: parent.left + } + + NewSwitch { + width: 32 + height: elementHeight + anchors.rightMargin: Qt.inputMethod.visible ? 96 : 36 + + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + checked: settings.adsb_enable + onCheckedChanged: settings.adsb_enable = checked + } + } + Rectangle { + width: parent.width + height: rowHeight + color: (Positioner.index % 2 == 0) ? "#8cbfd7f3" : "#00000000" + Text { text: qsTr("Show log messages on-screen") font.weight: Font.Bold diff --git a/qml/ui/elements/AppSettings.qml b/qml/ui/elements/AppSettings.qml index fef9304fc..98aa2648f 100644 --- a/qml/ui/elements/AppSettings.qml +++ b/qml/ui/elements/AppSettings.qml @@ -293,6 +293,12 @@ Settings { property bool map_drone_track: true property bool map_show_mission_waypoints: true + property bool adsb_enable: true + property double adsb_radius: 50000 //using meters for now- api is in NM + property bool adsb_show_internet_data: true + property bool adsb_show_sdr_data: true + property bool adsb_show_unknown_or_zero_alt: false //alot of traffic has no alt and is not useful + property int map_zoom: 18 property double map_size: 1 property bool show_throttle: true diff --git a/qml/ui/widgets/map/MapComponent.qml b/qml/ui/widgets/map/MapComponent.qml index 32ad10b21..6e748ddfe 100644 --- a/qml/ui/widgets/map/MapComponent.qml +++ b/qml/ui/widgets/map/MapComponent.qml @@ -25,6 +25,7 @@ Map { property double userLon: 0.0 property double center_coord_lat: 0.0 property double center_coord_lon: 0.0 + property var center_coord property int track_limit: 200; //max number of drone track points before it starts averaging // We start with a minimum distance of 3m, each time we perform a track reduction, the minimum distance is increased @@ -62,7 +63,7 @@ Map { } function findMapBounds(){ - var center_coord = map.toCoordinate(Qt.point(map.width/2,map.height/2)) + center_coord = map.toCoordinate(Qt.point(map.width/2,map.height/2)) //console.log("Map component: center",center_coord.latitude, center_coord.longitude); AdsbVehicleManager.newMapLat(center_coord.latitude); AdsbVehicleManager.newMapLon(center_coord.longitude); @@ -151,23 +152,30 @@ Map { } } + // This graphically depicts the area in which adsb traffic is being sourced - //>>>>>>>>>>>>>>>>>>> ADSB <<<<<<<<<<<<<<<<<<< - /* this graphically depicts the area in which adsb traffic is being sourced - *TODO fix.... will be a circle per the api - MapRectangle { - id: adsbSquare - topLeft : EnableADSB ? AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 315, 0.0) : QtPositioning.coordinate(0, 0) - bottomRight: EnableADSB ? AdsbVehicleManager.apiMapCenter.atDistanceAndAzimuth(settings.adsb_distance_limit, 135, 0.0) : QtPositioning.coordinate(0, 0) - enabled: EnableADSB - visible: settings.adsb_api_openskynetwork + MapCircle { + id: adsbCircle + visible: { + //TODO add control that also responds to no interent setting + if (!settings.adsb_enable){ + return false; + } + else{ + return true; + } + } + center { + latitude: center_coord.latitude + longitude: center_coord.longitude + } + radius: settings.adsb_radius //in meters color: "white" border.color: "red" border.width: 5 smooth: true opacity: .3 } - */ MapItemView { id: markerMapView From 12ce193a97f544ca24f9caf19f1033df57c0e976 Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sun, 7 Jan 2024 13:09:33 -0500 Subject: [PATCH 06/12] Adsb controls in map settings popup --- qml/ui/widgets/map/MapWidgetForm.ui.qml | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/qml/ui/widgets/map/MapWidgetForm.ui.qml b/qml/ui/widgets/map/MapWidgetForm.ui.qml index beabfae41..d15ebca14 100644 --- a/qml/ui/widgets/map/MapWidgetForm.ui.qml +++ b/qml/ui/widgets/map/MapWidgetForm.ui.qml @@ -232,6 +232,99 @@ BaseWidget { onCheckedChanged: settings.map_show_mission_waypoints = checked } } + Item { + width: 230 + height: 32 + Text { + text: qsTr("Enable ADSB") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + NewSwitch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.adsb_enable + onCheckedChanged: settings.adsb_enable = checked + } + } + Item { + width: 230 + height: 32 + visible: settings.adsb_enable + Text { + text: qsTr("ADSB Internet Source") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + NewSwitch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.adsb_show_internet_data + onCheckedChanged: settings.adsb_show_internet_data = checked + } + } + Item { + width: 230 + height: 32 + //TODO turn on once sdr is fixed + visible: false + Text { + text: qsTr("ADSB SDR Source") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + NewSwitch { + width: 32 + height: parent.height + anchors.rightMargin: 6 + anchors.right: parent.right + checked: settings.adsb_show_sdr_data + onCheckedChanged: settings.adsb_show_sdr_data = checked + } + } + Item { + width: 230 + height: 32 + visible: settings.adsb_enable + Text { + id: adsb_radiusTitle + text: qsTr("ADSB Radius") + color: "white" + height: parent.height + font.bold: true + font.pixelSize: detailPanelFontPixels + anchors.left: parent.left + verticalAlignment: Text.AlignVCenter + } + NewSlider { + orientation: Qt.Horizontal + from: 5000 + value: settings.adsb_radius + to: 50000 + stepSize: 1000 + height: parent.height + anchors.rightMargin: 0 + anchors.right: parent.right + width: parent.width - 96 + onValueChanged: settings.adsb_radius = value + } + } } } //--- widgetInner only used to have something to shake --- From b896605100a2d611ae383d6b7ef89dbc10d0b20f Mon Sep 17 00:00:00 2001 From: pilotnbr1 Date: Sun, 7 Jan 2024 17:40:54 -0500 Subject: [PATCH 07/12] UDP port bind logic to try multiple ports --- app/telemetry/connection/udp_connection.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/app/telemetry/connection/udp_connection.cpp b/app/telemetry/connection/udp_connection.cpp index d134f0566..817ba202c 100644 --- a/app/telemetry/connection/udp_connection.cpp +++ b/app/telemetry/connection/udp_connection.cpp @@ -23,6 +23,7 @@ #ifdef __windows__ #endif +int _local_port; UDPConnection::UDPConnection(const std::string local_ip,const int local_port,MAV_MSG_CB cb) :m_local_ip(local_ip),m_local_port(local_port),m_cb(cb) @@ -38,6 +39,7 @@ UDPConnection::~UDPConnection() void UDPConnection::start() { + _local_port=m_local_port; m_keep_receiving=true; m_receive_thread=std::make_unique(&UDPConnection::loop_receive,this); } @@ -117,8 +119,8 @@ void UDPConnection::process_mavlink_message(mavlink_message_t message) void UDPConnection::loop_receive() { - while(m_keep_receiving){ - qDebug()<<"UDP start receiving on "<(&addr), sizeof(addr)) != 0) { qDebug()<<"Cannot bind port "< Date: Sun, 7 Jan 2024 17:41:10 -0500 Subject: [PATCH 08/12] more adsb stuff --- app/adsb/adsbvehiclemanager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/adsb/adsbvehiclemanager.cpp b/app/adsb/adsbvehiclemanager.cpp index 2d7c093f2..dd0ddb461 100644 --- a/app/adsb/adsbvehiclemanager.cpp +++ b/app/adsb/adsbvehiclemanager.cpp @@ -354,7 +354,7 @@ void ADSBInternet::processReply(QNetworkReply *reply) { //Aircraft altitude if(aircraft["alt_baro"].toDouble()){ adsbInfo.altitude = aircraft["alt_baro"].toDouble(); - //qDebug()<<"alt:" << aircraft["alt_baro"].toDouble(); + qDebug()<<"alt:" << aircraft["alt_baro"].toDouble(); //per setting eliminate all unknown alt if (adsbInfo.altitude<5 && unknown_zero_alt==false){ qDebug() << "ADSB Skipping aircraft for alt error"; From ac6b427c68bfc738920fd612869ce14aacc75ab4 Mon Sep 17 00:00:00 2001 From: pilotbnr1 Date: Wed, 24 Jan 2024 15:55:59 -0500 Subject: [PATCH 09/12] ADSB Refactor.. again.. back to qml to draw markers --- qml/qml.qrc | 3 + qml/ui/widgets/map/ADSBcautionMarker.png | Bin 0 -> 15503 bytes qml/ui/widgets/map/ADSBmarker.png | Bin 0 -> 11822 bytes qml/ui/widgets/map/ADSBwarnMarker.png | Bin 0 -> 17574 bytes qml/ui/widgets/map/MapComponent.qml | 309 ++++++++++++++++------- 5 files changed, 223 insertions(+), 89 deletions(-) create mode 100644 qml/ui/widgets/map/ADSBcautionMarker.png create mode 100644 qml/ui/widgets/map/ADSBmarker.png create mode 100644 qml/ui/widgets/map/ADSBwarnMarker.png diff --git a/qml/qml.qrc b/qml/qml.qrc index 0cc509afa..e93bbb63c 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -270,5 +270,8 @@ ui/elements/ButtonIconConnect.qml ui/elements/SmallHeaderInfoRow.qml ui/elements/SettingsCategory.qml + ui/widgets/map/ADSBcautionMarker.png + ui/widgets/map/ADSBmarker.png + ui/widgets/map/ADSBwarnMarker.png diff --git a/qml/ui/widgets/map/ADSBcautionMarker.png b/qml/ui/widgets/map/ADSBcautionMarker.png new file mode 100644 index 0000000000000000000000000000000000000000..560b8fb1f78a71fc4716fc94bb60524843de329a GIT binary patch literal 15503 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 R*DnA7002ovPDHLkV1k8yJuv_P literal 0 HcmV?d00001 diff --git a/qml/ui/widgets/map/ADSBmarker.png b/qml/ui/widgets/map/ADSBmarker.png new file mode 100644 index 0000000000000000000000000000000000000000..e20ca5e6862e1c2f58b29b59126ac16fdbfed2dd GIT binary patch literal 11822 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 N002ovPDHLkV1jTINZ$Ye literal 0 HcmV?d00001 diff --git a/qml/ui/widgets/map/MapComponent.qml b/qml/ui/widgets/map/MapComponent.qml index 6e748ddfe..9b1c831a5 100644 --- a/qml/ui/widgets/map/MapComponent.qml +++ b/qml/ui/widgets/map/MapComponent.qml @@ -177,132 +177,263 @@ Map { opacity: .3 } + //>>>>>>>>>>>>>>>>> Begin ADSB <<<<<<<<<<<<<<<<< MapItemView { - id: markerMapView - model: AdsbVehicleManager.adsbVehicles - delegate: markerComponentDelegate - //TODO refactor setting - //visible: EnableADSB - visible: true + id: markerMapView + model: AdsbVehicleManager.adsbVehicles + delegate: markerComponentDelegate + visible: settings.adsb_enable - Component { - id: markerComponentDelegate + Component { + id: markerComponentDelegate - MapItemGroup { - id: delegateGroup + MapItemGroup { + id: delegateGroup - MapQuickItem { - id: marker + MapQuickItem { + id: marker + property alias lastMouseX: markerMouseArea.lastX + property alias lastMouseY: markerMouseArea.lastY - anchorPoint.x: 0 - anchorPoint.y: 0 - width: 260 - height: 260 + anchorPoint.x: image.width/2 + anchorPoint.y: image.height/2 + width: image.width + height: image.height - sourceItem: + sourceItem: - DrawingCanvas { - id: icon - anchors.centerIn: parent - - width: 260 - height: 260 + Image { - color: settings.color_shape - glow: settings.color_glow + id: image + source: "ADSBmarker.png" - name: object.callsign + Rectangle{// has to be here prior to rotation call + id: speedtail - drone_heading: _fcMavlinkSystem.hdg; //need this to adjust orientation + x: image.width*.4 + y: image.height*.8 - drone_alt: _fcMavlinkSystem.altitude_msl_m; + width: image.width*.2 + height: { + if (object.velocity === undefined) { + console.log("qml: object velocity undefined") + return 0; + } + else { + return object.velocity / 10; + } + } + opacity: .5 + color: "white" + border.color: "grey" + border.width: 1 + } - heading: object.heading; + rotation: { + if (object.heading === undefined) { + console.log("qml: model heading undefined") + return 0; + } - speed: object.velocity - alt: { - console.log("map lat / lon:" + object.vehicle_lat + " " + object.vehicle_lon); - return object.altitude; + if (settings.map_orientation === true){ + var orientation = object.heading-_fcMavlinkSystem.hdg; + if (orientation < 0) orientation += 360; + if (orientation >= 360) orientation -=360; + return orientation; + } + else { + //console.log("TRACK=", object.heading); + return object.heading; + } + } -} + //UNUSED MOUSE AREA.. for future functionality + opacity: markerMouseArea.pressed ? 0.6 : 1.0 + MouseArea { + id: markerMouseArea + property int pressX : -1 + property int pressY : -1 + property int jitterThreshold : 10 + property int lastX: -1 + property int lastY: -1 + anchors.fill: parent + hoverEnabled : false + drag.target: marker + preventStealing: true + + onPressed : { + map.pressX = mouse.x + map.pressY = mouse.y + map.currentMarker = -1 + for (var i = 0; i< map.markers.length; i++){ + if (marker == map.markers[i]){ + map.currentMarker = i + break + } + } + } -// { -/* check if traffic is a threat.. this should not be done here. Left as REF - if (object.altitude - OpenHD.alt_msl < 300 && model.distance < 2){ - //console.log("TRAFFIC WARNING"); - - //image.source="/airplanemarkerwarn.png"; - background.border.color = "red"; - background.border.width = 5; - background.opacity = 0.5; - } else if (object.altitude - OpenHD.alt_msl < 500 && model.distance < 5){ - //console.log("TRAFFIC ALERT"); - - //image.source="/airplanemarkeralert.png"; - background.border.color = "yellow"; - background.border.width = 5; - background.opacity = 0.5; + onPressAndHold:{ + if (Math.abs(map.pressX - mouse.x ) < map.jitterThreshold + && Math.abs(map.pressY - mouse.y ) < map.jitterThreshold) { + var p = map.fromCoordinate(marker.coordinate) + lastX = p.x + lastY = p.y + map.showMarkerMenu(marker.coordinate) + } } -*/ + } -/* *discovered issues when the object is referenced multiple times - *last attempt at putting altitude into a var still resulted in "nulls" + Rectangle{ //holder to "derotate" info block + id: holder - var _adsb_alt; + x: image.width+5 + y: image.height/2 + rotation: { + if (object.heading === undefined) { + console.log("qml: model velocity undefined") + return 0; + } - _adsb_alt=object.altitude; + if (settings.map_orientation === true){ + var orientation = object.heading - _fcMavlinkSystem.hdg; - if ( _adsb_alt> 9999) { - //console.log("qml: model alt or vertical undefined") - return "---"; - } else { - if(object.verticalVel > .2){ //climbing - if (settings.enable_imperial === false){ - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue696" + if (orientation < 0) orientation += 360; + if (orientation >= 360) orientation -=360; + return -orientation; } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue696" + else { + return -object.heading; } } - else if (object.verticalVel < -.2){//descending - if (settings.enable_imperial === false){ - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\ue697" - } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\ue697" + width: image.width + height: image.height + color: "transparent" + + Rectangle{ + id: background + + width: image.width*1.25 + height: image.height + color: "black" + opacity: .2 + border.width: 2 + border.color: "white" + radius: 8 + } + + Text{ + id: callsign + anchors.top: holder.top + topPadding: 2 + leftPadding: 10 + width: image.width + color: "white" + //font.bold: true + font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter + text: { + if (object.callsign === undefined) { + console.log("qml: model callsign undefined") + return "---" + } + else { + return object.callsign + //console.log("Map Callsign=",object.callsign); + } } } - else { - if (settings.enable_imperial === false){//level - return Math.floor(_adsb_alt - OpenHD.alt_msl) + "m " + "\u2501" + + Text{ + id: alt + anchors.top: callsign.bottom + topPadding: 2 + leftPadding: 10 + width: image.width + color: "white" + font.bold: true + font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter + text: { + // check if traffic is a threat and change marker image if needed + if (object.altitude - _fcMavlinkSystem.altitude_msl_m < 300 && object.distance < 2){ + //console.log("TRAFFIC WARNING"); + image.source="ADSBwarnMarker.png"; + background.border.color = "red"; + background.border.width = 5; + background.opacity = 0.5; + } else if (object.altitude - _fcMavlinkSystem.altitude_msl_m < 500 && object.distance < 5){ + //console.log("TRAFFIC ALERT"); + image.source="ADSBcautionMarker.png"; + background.border.color = "yellow"; + background.border.width = 5; + background.opacity = 0.5; + } + + //NOW do altitude Text block + if (object.altitude === undefined || object.verticalVel === undefined) { + //console.log("qml: model alt or vertical undefined") + return "---"; + } else { + if(object.verticalVel > .2){ //climbing + if (settings.enable_imperial === false){ + return Math.floor(object.altitude - _fcMavlinkSystem.altitude_msl_m) + "m " + "\ue696" + } + else{ + return Math.floor((object.altitude - _fcMavlinkSystem.altitude_msl_m) * 3.28084) + "Ft " + "\ue696" + } + } + else if (object.verticalVel < -.2){//descending + if (settings.enable_imperial === false){ + return Math.floor(object.altitude - _fcMavlinkSystem.altitude_msl_m) + "m " + "\ue697" + } + else{ + return Math.floor((object.altitude - _fcMavlinkSystem.altitude_msl_m) * 3.28084) + "Ft " + "\ue697" + } + } + else { + if (settings.enable_imperial === false){//level + return Math.floor(object.altitude - _fcMavlinkSystem.altitude_msl_m) + "m " + "\u2501" + } + else{ + return Math.floor((object.altitude - _fcMavlinkSystem.altitude_msl_m) * 3.28084) + "Ft " + "\u2501" + } + } + } } - else{ - return Math.floor((_adsb_alt - OpenHD.alt_msl) * 3.28084) + "Ft " + "\u2501" + } + Text{ + id: velocity + anchors.top: alt.bottom + topPadding: 2 + leftPadding: 10 + width: image.width + color: "white" + //font.bold: true + font.pixelSize: 11 + horizontalAlignment: Text.AlignHCenter + text: { + if (object.velocity === undefined) { + return "---"; + } + else { + return settings.enable_imperial ? Math.floor(object.velocity * 2.23694) + " mph" + : Math.floor(object.velocity * 3.6) + " kph"; + } } } } } - */ + //position everything + coordinate: QtPositioning.coordinate( object.lat , object.lon ); } - //position everything - - coordinate : QtPositioning.coordinate( object.lat , object.lon ); - // coordinate: marker.coordinate; - - // coordinate { - // latitude: object.lat - // longitude: object.lon - // console.log("object lat/lon:"+object.lat+ " "+object.lon); - // } - + //Component.onCompleted: map.addMapItemGroup(this); } - Component.onCompleted: map.addMapItemGroup(this); } } - } //>>>>>>>>>>>>>>>>>>> end ADSB <<<<<<<<<<<<<<< From 87454fde34e4acb8ca10fe9ab14afa9ef8174d74 Mon Sep 17 00:00:00 2001 From: pilotbnr1 Date: Wed, 24 Jan 2024 15:57:37 -0500 Subject: [PATCH 10/12] ADSB remove c++ drawing of markers --- QOpenHD.pro | 2 - app/adsb/drawingcanvas.cpp | 290 ------------------------------------- app/adsb/drawingcanvas.h | 151 ------------------- 3 files changed, 443 deletions(-) delete mode 100644 app/adsb/drawingcanvas.cpp delete mode 100644 app/adsb/drawingcanvas.h diff --git a/QOpenHD.pro b/QOpenHD.pro index 6ace3a166..2f55d4a44 100755 --- a/QOpenHD.pro +++ b/QOpenHD.pro @@ -103,7 +103,6 @@ LinuxBuild { SOURCES += \ app/adsb/adsbvehicle.cpp \ app/adsb/adsbvehiclemanager.cpp \ - app/adsb/drawingcanvas.cpp \ app/adsb/qmlobjectlistmodel.cpp \ app/logging/hudlogmessagesmodel.cpp \ app/logging/logmessagesmodel.cpp \ @@ -117,7 +116,6 @@ SOURCES += \ HEADERS += \ app/adsb/adsbvehicle.h \ app/adsb/adsbvehiclemanager.h \ - app/adsb/drawingcanvas.h \ app/adsb/qmlobjectlistmodel.h \ app/common/util_fs.h \ app/common/StringHelper.hpp \ diff --git a/app/adsb/drawingcanvas.cpp b/app/adsb/drawingcanvas.cpp deleted file mode 100644 index a6fc6e240..000000000 --- a/app/adsb/drawingcanvas.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#include -#include -#include -#include - -//#include "openhd.h" - -#include "../adsb/drawingcanvas.h" -#include "qpainterpath.h" - - -DrawingCanvas::DrawingCanvas(QQuickItem *parent): QQuickPaintedItem(parent) { - //qDebug() << "DrawingCanvas::DrawingCanvas()"; - setRenderTarget(RenderTarget::FramebufferObject); - - //set font to pixels size early - m_font.setPixelSize(14); - m_fontNormal.setPixelSize(35); -//TODO fix settings reference -// QSettings settings; -} - -void DrawingCanvas::paint(QPainter* painter) { - painter->save(); - - // qDebug() << "DrawingCanvas::paint"; - - if (m_draw_request=="adsb"){ //statis for now, but here for future build out - auto pos_x= 130;//the middle - auto pos_y= 130; - - painter->setPen(m_color); - setOpacity(1.0); - - painter->translate(pos_x,pos_y); - - painter->rotate(-90);//glyph is oriented +90 - - - // bool orientation_setting = settings.value("map_orientation").toBool(); - //TODO fix settings reference - bool orientation_setting=true; - if (orientation_setting == true){ //orienting map to drone - m_orientation = m_heading - m_drone_heading; - - if (m_orientation < 0) m_orientation += 360; - if (m_orientation >= 360) m_orientation -=360; - painter->rotate(m_orientation); - } - else{ //orienting map to north - m_orientation=0; - painter->rotate(m_heading); - } - - //draw speed tail - painter->setOpacity(0.5); - painter->fillRect(QRectF(0, -8, -m_speed/12, 4), "white"); - painter->setPen("grey"); - painter->drawRect(QRectF(0, -8, -m_speed/12, 4)); - - //add icon glyph of airplane - /* //for glow effect - painter->setOpacity(1.0); - painter->setPen("grey"); - painter->setFont(m_fontBig); - painter->drawText(0, 0, "\ue3d0"); - */ - - painter->setOpacity(1.0); - painter->setPen("black"); - painter->setFont(m_fontNormal); - - painter->drawText(0, 0, "\uf072"); - - //draw data block - - painter->translate(+50,-60); //+up -down, -left +right - - //de-rotate whatever was done above and the adjustment for the glyph - if (m_orientation!=0){ - painter->rotate(-m_orientation+90); - } - else { - painter->rotate(-m_heading+90); - } - - painter->translate(-33,-24); //preposition the text block - - painter->setOpacity(0.5); - QPainterPath path; - path.addRoundedRect(QRectF(0, 0, 80, 50), 10, 10); - QPen pen(Qt::white, 2); - painter->setPen(pen); - painter->fillPath(path, Qt::black); - painter->drawPath(path); - - painter->setOpacity(1.0); - painter->setPen("white"); - painter->setFont(m_font); - - painter->drawText(5, 15, m_name); - painter->drawText(10, 30, m_speed_text); - painter->drawText(10, 45, m_alt_text); - - painter->restore(); - } -} - - - -QColor DrawingCanvas::color() const { - return m_color; -} - -QColor DrawingCanvas::glow() const { - return m_glow; -} - -void DrawingCanvas::setColor(QColor color) { - m_color = color; - emit colorChanged(m_color); - update(); -} - -void DrawingCanvas::setGlow(QColor glow) { - m_glow = glow; - emit glowChanged(m_glow); - update(); -} - -void DrawingCanvas::setFpvInvertPitch(bool fpvInvertPitch) { - m_fpvInvertPitch = fpvInvertPitch; - emit fpvInvertPitchChanged(m_fpvInvertPitch); - update(); -} - -void DrawingCanvas::setFpvInvertRoll(bool fpvInvertRoll) { - m_fpvInvertRoll = fpvInvertRoll; - emit fpvInvertRollChanged(m_fpvInvertRoll); - update(); -} - -void DrawingCanvas::setHeading(int heading) { - m_heading = heading; - emit headingChanged(m_heading); - update(); -} - -void DrawingCanvas::setDroneHeading(int drone_heading) { - m_drone_heading = drone_heading; - emit droneHeadingChanged(m_drone_heading); - update(); -} - -void DrawingCanvas::setAlt(int alt) { - m_alt = alt; - - if(alt>9999){ - m_alt_text="---"; - } - else{ - //TODO fix settings reference - //imperial = settings.value("enable_imperial").toBool(); - imperial=false; - if (imperial == false){ - m_alt_text = (QString::number(round(alt)))+" M"; - } - else{ - m_alt_text = (QString::number(round((alt) * 3.28084)))+" Ft"; - } - } - - emit altChanged(m_alt); - emit altTextChanged(m_alt_text); - update(); -} - -void DrawingCanvas::setAltText(QString alt_text) { - m_alt_text = alt_text; - emit altTextChanged(m_alt_text); - update(); -} - -void DrawingCanvas::setDroneAlt(int drone_alt) { - m_drone_alt = drone_alt; - emit droneAltChanged(m_drone_alt); - update(); -} - -void DrawingCanvas::setSpeed(int speed) { - if(speed>9999){ - m_speed=0; - m_speed_text="---"; - } - else{ - //TODO fix settings reference - //imperial = settings.value("enable_imperial").toBool(); - imperial=false; - if (imperial == false){ - m_speed =round(speed* 3.6); - m_speed_text = (QString::number(round(speed* 3.6)))+" Kph"; - } - else{ - m_speed = round(speed* 2.23694); - m_speed_text = (QString::number(round((speed* 2.23694))))+" Mph"; - } - } - - emit speedTextChanged(m_speed_text); - emit speedChanged(m_speed); - update(); -} - -void DrawingCanvas::setSpeedText(QString speed_text) { - m_speed_text = speed_text; - emit speedTextChanged(m_speed_text); - update(); -} - -void DrawingCanvas::setVertSpd(int vert_spd) { - m_vert_spd = vert_spd; - emit vertSpdChanged(m_vert_spd); - update(); -} - -void DrawingCanvas::setRoll(int roll) { - m_roll = roll; - emit rollChanged(m_roll); - update(); -} - -void DrawingCanvas::setPitch(int pitch) { - m_pitch = pitch; - emit pitchChanged(m_pitch); - update(); -} - -void DrawingCanvas::setLateral(int lateral) { - m_lateral = lateral; - emit lateralChanged(m_lateral); - update(); -} - -void DrawingCanvas::setVertical(int vertical) { - m_vertical = vertical; - emit verticalChanged(m_vertical); - update(); -} - -void DrawingCanvas::setHorizonSpacing(int horizonSpacing) { - m_horizonSpacing = horizonSpacing; - emit horizonSpacingChanged(m_horizonSpacing); - update(); -} - -void DrawingCanvas::setHorizonWidth(double horizonWidth) { - m_horizonWidth = horizonWidth; - emit horizonWidthChanged(m_horizonWidth); - update(); -} - -void DrawingCanvas::setSize(double size) { - m_size = size; - emit sizeChanged(m_size); - update(); -} - -void DrawingCanvas::setName(QString name) { - m_name = name; - emit nameChanged(m_name); - update(); -} - -void DrawingCanvas::setVerticalLimit(double verticalLimit) { - m_verticalLimit = verticalLimit; - emit verticalLimitChanged(m_verticalLimit); - update(); -} - -void DrawingCanvas::setLateralLimit(double lateralLimit) { - m_lateralLimit = lateralLimit; - emit lateralLimitChanged(m_lateralLimit); - update(); -} - -void DrawingCanvas::setFontFamily(QString fontFamily) { - m_fontFamily = fontFamily; - emit fontFamilyChanged(m_fontFamily); - update(); -} diff --git a/app/adsb/drawingcanvas.h b/app/adsb/drawingcanvas.h deleted file mode 100644 index b674cd211..000000000 --- a/app/adsb/drawingcanvas.h +++ /dev/null @@ -1,151 +0,0 @@ -#include -#include -#include - -//#include "openhd.h" - -class DrawingCanvas : public QQuickPaintedItem { - Q_OBJECT - Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) - Q_PROPERTY(QColor glow READ glow WRITE setGlow NOTIFY glowChanged) - Q_PROPERTY(bool fpvInvertPitch MEMBER m_fpvInvertPitch WRITE setFpvInvertPitch NOTIFY fpvInvertPitchChanged) - Q_PROPERTY(bool fpvInvertRoll MEMBER m_fpvInvertRoll WRITE setFpvInvertRoll NOTIFY fpvInvertRollChanged) - - Q_PROPERTY(int alt MEMBER m_alt WRITE setAlt NOTIFY altChanged) - Q_PROPERTY(QString alt_text MEMBER m_alt_text WRITE setAltText NOTIFY altTextChanged) - Q_PROPERTY(int drone_alt MEMBER m_drone_alt WRITE setDroneAlt NOTIFY droneAltChanged) - Q_PROPERTY(int speed MEMBER m_speed WRITE setSpeed NOTIFY speedChanged) - Q_PROPERTY(QString speed_text MEMBER m_speed_text WRITE setSpeedText NOTIFY speedTextChanged) - Q_PROPERTY(int vert_spd MEMBER m_vert_spd WRITE setVertSpd NOTIFY vertSpdChanged) - Q_PROPERTY(int heading MEMBER m_heading WRITE setHeading NOTIFY headingChanged) - Q_PROPERTY(int drone_heading MEMBER m_drone_heading WRITE setDroneHeading NOTIFY droneHeadingChanged) - Q_PROPERTY(int roll MEMBER m_roll WRITE setRoll NOTIFY rollChanged) - Q_PROPERTY(int pitch MEMBER m_pitch WRITE setPitch NOTIFY pitchChanged) - - Q_PROPERTY(int lateral MEMBER m_lateral WRITE setLateral NOTIFY lateralChanged) - Q_PROPERTY(int vertical MEMBER m_vertical WRITE setVertical NOTIFY verticalChanged) - - Q_PROPERTY(int horizonSpacing MEMBER m_horizonSpacing WRITE setHorizonSpacing NOTIFY horizonSpacingChanged) - Q_PROPERTY(double horizonWidth MEMBER m_horizonWidth WRITE setHorizonWidth NOTIFY horizonWidthChanged) - Q_PROPERTY(double size MEMBER m_size WRITE setSize NOTIFY sizeChanged) - - Q_PROPERTY(QString name MEMBER m_name WRITE setName NOTIFY nameChanged) - - Q_PROPERTY(double verticalLimit MEMBER m_verticalLimit WRITE setVerticalLimit NOTIFY verticalLimitChanged) - Q_PROPERTY(double lateralLimit MEMBER m_lateralLimit WRITE setLateralLimit NOTIFY lateralLimitChanged) - - Q_PROPERTY(QString fontFamily MEMBER m_fontFamily WRITE setFontFamily NOTIFY fontFamilyChanged) - -public: - explicit DrawingCanvas(QQuickItem* parent = nullptr); - - void paint(QPainter* painter) override; - - QColor color() const; - QColor glow() const; - -public slots: - void setColor(QColor color); - void setGlow(QColor glow); - void setFpvInvertPitch(bool fpvInvertPitch); - void setFpvInvertRoll(bool fpvInvertRoll); - - void setAlt(int alt); - void setAltText(QString alt_text); - void setDroneAlt(int drone_alt); - void setSpeed(int speed); - void setSpeedText(QString speed_text); - void setVertSpd(int vert_spd); - void setHeading(int heading); - void setDroneHeading(int drone_heading); - void setRoll(int roll); - void setPitch(int pitch); - - void setLateral(int lateral); - void setVertical(int vertical); - - void setHorizonSpacing(int horizonSpacing); - void setHorizonWidth(double horizonWidth); - void setSize(double size); - - void setName(QString name); - - void setVerticalLimit(double verticalLimit); - void setLateralLimit(double lateralLimit); - - void setFontFamily(QString fontFamily); - -signals: - void colorChanged(QColor color); - void glowChanged(QColor glow); - void fpvInvertPitchChanged(bool fpvInvertPitch); - void fpvInvertRollChanged(bool fpvInvertRoll); - - void altChanged(int alt); - void altTextChanged(QString alt_text); - void droneAltChanged(int drone_alt); - void speedChanged(int speed); - void speedTextChanged(QString speed_text); - void vertSpdChanged(int vert_spd); - void headingChanged(int heading); - void droneHeadingChanged(int drone_heading); - void rollChanged(int roll); - void pitchChanged(int pitch); - - void lateralChanged(int lateral); - void verticalChanged(int vertical); - - void horizonSpacingChanged(int horizonSpacing); - void horizonWidthChanged(double horizonWidth); - void sizeChanged(double size); - - void nameChanged(QString name); - - void verticalLimitChanged(double verticalLimit); - void lateralLimitChanged(double lateralLimit); - - void fontFamilyChanged(QString fontFamily); - -private: - QColor m_color; - QColor m_glow; - bool m_fpvInvertPitch; - bool m_fpvInvertRoll; - - int m_heading; - int m_drone_heading; - int m_alt; - QString m_alt_text; - int m_drone_alt; - int m_speed; - QString m_speed_text; - int m_vert_spd; - int m_roll; - int m_pitch; - - int m_lateral; - int m_vertical; - - int m_horizonSpacing; - double m_horizonWidth; - double m_size; - - QString m_name; - - double m_verticalLimit; - double m_lateralLimit; -//TODO fix setting reference - //QSettings settings; - bool imperial; - - int m_orientation=0; - - QString m_fontFamily; - - QString m_draw_request="adsb"; //for future build out of more draw requests - - QFont m_font = QFont("Font Awesome 5 Free", 10, QFont::Bold, false); - QFont m_fontNormal = QFont("osdicons", 25 , QFont::PreferAntialias, true); - //QFont m_fontBig = QFont("osdicons", 25*1.1, QFont::PreferAntialias, true); - -}; From bcf9cda779a69a2b6a5dcc19ecaa45a34f1e740b Mon Sep 17 00:00:00 2001 From: consti10 Date: Sun, 18 Feb 2024 15:00:36 +0100 Subject: [PATCH 11/12] fix unneeded newline, util stuff --- app/telemetry/models/openhd_core/camera.hpp | 3 +-- tools/usefull_commands.md | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/telemetry/models/openhd_core/camera.hpp b/app/telemetry/models/openhd_core/camera.hpp index b3efb440b..fd0603c21 100644 --- a/app/telemetry/models/openhd_core/camera.hpp +++ b/app/telemetry/models/openhd_core/camera.hpp @@ -371,8 +371,7 @@ static std::string get_verbose_string_of_resolution( resolution_framerate.height_px == 1440) { ss << "2K 16:9"; } else { - ss << resolution_framerate.width_px << "x" << resolution_framerate.height_px - << "\n"; + ss << resolution_framerate.width_px << "x" << resolution_framerate.height_px; } ss << "\n" << resolution_framerate.fps << "fps"; return ss.str(); diff --git a/tools/usefull_commands.md b/tools/usefull_commands.md index 80191da3d..ab61af36d 100644 --- a/tools/usefull_commands.md +++ b/tools/usefull_commands.md @@ -6,6 +6,10 @@ KNOWN BUG: Switching to or from MJPEG requires a restart of QOpenHD (bug of avco H264 gst-launch-1.0 videotestsrc ! video/x-raw, format=I420,width=640,height=480,framerate=30/1 ! x264enc bitrate=5000 speed-preset=ultrafast tune=zerolatency key-int-max=30 sliced-threads=0 ! queue ! h264parse config-interval=-1 ! rtph264pay mtu=1024 ! udpsink host=127.0.0.1 port=5600 +# funky h264 +gst-launch-1.0 filesrc location=/home/consti10/Desktop/intra_test/out/wing_1920x1080p60.mkv ! decodebin ! videorate max-rate=60 ! queue ! videoscale ! video/x-raw, format=I420,width=1920, height=1080, framerate=60/1 ! x264enc name=swencoder bitrate=8000 speed-preset=ultrafast tune=zerolatency key-int-max=5 sliced-threads=false threads=2 intra-refresh=false qp_min=2 qp_step=10 ! queue ! h264parse config-interval=-1 ! rtph264pay mtu=1024 ! udpsink host=192.168.178.63 port=5600 + + H265 gst-launch-1.0 videotestsrc ! video/x-raw, format=I420,width=640,height=480,framerate=30/1 ! x265enc bitrate=5000 speed-preset=ultrafast tune=zerolatency key-int-max=30 ! queue ! h265parse config-interval=-1 ! rtph265pay mtu=1024 ! udpsink host=127.0.0.1 port=5600 From 0b6dda84a9f7a8d1d4846e190a5956a0863e35e7 Mon Sep 17 00:00:00 2001 From: consti10 Date: Sun, 18 Feb 2024 15:17:43 +0100 Subject: [PATCH 12/12] finalize merge @Luke --- app/main.cpp | 3 - qml/qml.qrc | 3 + qml/ui/elements/NewSlider.qml | 126 +++++++++++++++++ qml/ui/elements/NewSpinBox.qml | 175 ++++++++++++++++++++++++ qml/ui/elements/NewSwitch.qml | 126 +++++++++++++++++ qml/ui/widgets/map/MapWidgetForm.ui.qml | 1 + 6 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 qml/ui/elements/NewSlider.qml create mode 100644 qml/ui/elements/NewSpinBox.qml create mode 100644 qml/ui/elements/NewSwitch.qml diff --git a/app/main.cpp b/app/main.cpp index 4eac4887f..d911f7c8d 100755 --- a/app/main.cpp +++ b/app/main.cpp @@ -38,7 +38,6 @@ const QVector permissions({"android.permission.INTERNET", #include "adsb/adsbvehicle.h" #include "adsb/adsbvehiclemanager.h" #include "adsb/qmlobjectlistmodel.h" -#include "adsb/drawingcanvas.h" // Video - annyoing ifdef crap is needed for all the different platforms / configurations @@ -306,8 +305,6 @@ int main(int argc, char *argv[]) { qmlRegisterUncreatableType("OpenHD", 1, 0, "QmlObjectListModel", "Reference only"); - qmlRegisterType("OpenHD", 1, 0, "DrawingCanvas"); - QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("_qopenhd", &QOpenHD::instance()); diff --git a/qml/qml.qrc b/qml/qml.qrc index f62072358..e759ccf17 100644 --- a/qml/qml.qrc +++ b/qml/qml.qrc @@ -301,5 +301,8 @@ ui/widgets/map/ADSBcautionMarker.png ui/widgets/map/ADSBmarker.png ui/widgets/map/ADSBwarnMarker.png + ui/elements/NewSwitch.qml + ui/elements/NewSpinBox.qml + ui/elements/NewSlider.qml diff --git a/qml/ui/elements/NewSlider.qml b/qml/ui/elements/NewSlider.qml new file mode 100644 index 000000000..703886c5c --- /dev/null +++ b/qml/ui/elements/NewSlider.qml @@ -0,0 +1,126 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Qt.labs.settings 1.0 + + +Slider { + id: control + + //NEW feature of this slider is possibility to go vertical + property bool sliderVertical: false + + rotation: sliderVertical ? 90 : 0 + + property bool selected: false + + NumberAnimation { + id: animateHighlight + target: highlight + properties: "width" + from: 5 + to: 35 + loops: Animation.Infinite + duration: 1000 + easing {type: Easing.OutBack; overshoot: 5} + } + + onActiveFocusChanged: { + if (control.activeFocus == true){ + if (selected == false){ + handle.color="grey" + highlight.color="grey" + } + else{ + handle.color="#33aaff" + highlight.color="#33aaff" + animateHighlight.start() + } + } + else if (control.activeFocus == false){ + //console.log("New slider has lost focus"); + handle.color="#33aaff" + highlight.color="transparent" + animateHighlight.stop() + } + } + + onHoveredChanged: { + if (control.hovered == true){ + handle.color="#33aaff" + highlight.color="#33aaff" + animateHighlight.start() + } + else{ + handle.color="#33aaff" + highlight.color="transparent" + animateHighlight.stop() + } + } + + Keys.onPressed: { + if (event.key === Qt.Key_S){ + if (selected == false){ + selected = true + handle.color="#33aaff" + highlight.color="#33aaff" + animateHighlight.start() + } else { + //console.log("Make Selection"); + selected = false + } + } else if (event.key === Qt.Key_Escape){ + if (selected == true){ + selected = false + handle.color="grey" + highlight.color="grey" + animateHighlight.stop() + } + } else if (event.key === Qt.Key_Minus){ + if (selected == true){ + control.increase() + } + } else if (event.key === Qt.Key_Equal){ + if (selected == true){ + control.decrease() + } + } + + } + + handle: Rectangle { + id:handle + x: control.leftPadding + control.visualPosition * (control.availableWidth - width) + y: control.topPadding + control.availableHeight / 2 - height / 2 + width: 20 + height: 20 + radius: width/2 + color: "#33aaff" + + Rectangle { + id:highlight + anchors.centerIn: handle + z:0 + width: 40 + height: highlight.width + radius: width/2 + opacity: .3 + color: "transparent" + + /* This could be extended later to fix the mouse hover effect... + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: { highlight.color="#33aaff" } + onExited: { highlight.color="transparent" } + + DragHandler { + + target: handle + } + } */ + } + } +} diff --git a/qml/ui/elements/NewSpinBox.qml b/qml/ui/elements/NewSpinBox.qml new file mode 100644 index 000000000..67cf124e9 --- /dev/null +++ b/qml/ui/elements/NewSpinBox.qml @@ -0,0 +1,175 @@ +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.12 +import QtQuick.Controls.Styles 1.4 +import Qt.labs.settings 1.0 + + +SpinBox { + id: control + + property bool selected: false + + NumberAnimation { + id: animatePlus + target: plusHighlight + properties: "width" + from: 20 + to: 40 + loops: Animation.Infinite + duration: 1000 + easing {type: Easing.OutBack; overshoot: 5} + } + + NumberAnimation { + id: animateMinus + target: minusHighlight + properties: "width" + from: 20 + to: 40 + loops: Animation.Infinite + duration: 1000 + easing {type: Easing.OutBack; overshoot: 5} + } + + Keys.onPressed: { + if (event.key === Qt.Key_S){ + selected = true + highlight.color= "#33aaff" + animateMinus.start() + animatePlus.start() + } + else if (event.key === Qt.Key_Escape){ + selected = false + highlight.color= "grey" + } + else if (event.key === Qt.Key_Equal){ + if (selected == true){ + control.increase() + up.pressed + } + } + else if (event.key === Qt.Key_Minus){ + if (selected == true){ + control.decrease() + down.pressed + } + } + } + + onActiveFocusChanged: { + if (control.activeFocus == true){ + //console.log("New spinbox has focus"); + highlight.visible=true + highlight.color= "#33aaff" + + if(control.selected){ + highlight.color= "#33aaff" + } + else + highlight.color= "grey" + } + else if (control.activeFocus == false){ + //console.log("New spinbox has lost focus"); + highlight.visible=false + animateMinus.stop() + animatePlus.stop() + } + } + + contentItem: TextInput { + z: 2 + text: control.textFromValue(control.value, control.locale) + + font: control.font + color: control.activeFocus ? "black" : "grey" + selectionColor: "black" + selectedTextColor: "black" + horizontalAlignment: Qt.AlignHCenter + verticalAlignment: Qt.AlignVCenter + + readOnly: !control.editable + } + + up.indicator: Rectangle { + x: control.mirrored ? 0 : parent.width - width + height: parent.height + implicitWidth: 30 + color: "transparent" + + Rectangle { + id: plusHighlight + anchors.centerIn: parent + width: 30 + height: plusHighlight.width + color: up.pressed ? "grey" : + control.selected ? "#33aaff" : "transparent" + border.color: "transparent" + radius: plusHighlight.width/2 + opacity: .2 + } + + Text { + text: "+" + anchors.centerIn: parent + font.pixelSize: control.font.pixelSize * 2 + color: control.selected ? "black" : "grey" + anchors.fill: parent + fontSizeMode: Text.Fit + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity:1 + } + } + + + down.indicator: Rectangle { + x: control.mirrored ? parent.width - width : 0 + height: parent.height + implicitWidth: 30 + color: "transparent" + + Rectangle { + id: minusHighlight + anchors.centerIn: parent + width: 30 + height: minusHighlight.width + color: down.pressed ? "grey" : + control.selected ? "#33aaff" : "transparent" + border.color: "transparent" + radius: minusHighlight.width/2 + opacity: .2 + } + + Text { + text: "-" + anchors.centerIn: parent + font.pixelSize: control.font.pixelSize * 2 + color: control.selected ? "black" : "grey" + anchors.fill: parent + fontSizeMode: Text.Fit + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + opacity:1 + } + } + + background: Rectangle { + implicitWidth: 100 + border.color: "transparent" + color: "transparent" + radius:12 + } + + Rectangle { + id: highlight //this is the center highlight of the button on text + + z:0 + + anchors.centerIn: parent + width: 60 + height: 35 + radius: 20 + opacity: .3 + } +} diff --git a/qml/ui/elements/NewSwitch.qml b/qml/ui/elements/NewSwitch.qml new file mode 100644 index 000000000..ad503bf3d --- /dev/null +++ b/qml/ui/elements/NewSwitch.qml @@ -0,0 +1,126 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Qt.labs.settings 1.0 + + +SwitchDelegate { + id: control + text: qsTr("SwitchDelegate") + checked: true + clip:false + + NumberAnimation { + id: animateHighlight + target: highlight + properties: "width" + from: 5 + to: 35 + loops: Animation.Infinite + duration: 1000 + easing {type: Easing.OutBack; overshoot: 5} + } + + Keys.onPressed: { + if (event.key === Qt.Key_S && !event.isAutoRepeat) + checked = !checked; + } + + onActiveFocusChanged: { + if (control.activeFocus == true){ + //console.log("New switch has focus"); + control.highlighted=true + animateHighlight.start() + } + else if (control.activeFocus == false){ + //console.log("New switch has lost focus"); + control.highlighted=false + animateHighlight.stop() + } + } + + contentItem: Text { + rightPadding: control.indicator.width + control.spacing + text: control.text + font: control.font + opacity: enabled ? 1.0 : 0.3 + color: control.down ? "#17a81a" : "#21be2b" + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + indicator: Rectangle { + id:pill + implicitWidth: 48 + implicitHeight: 13 + x: control.width - width - control.rightPadding + y: parent.height / 2 - height / 2 + radius: 13 + color: control.checked ? "#7dbcf0" : "#BEBBBA" + border.color: "transparent" + + Rectangle { + id: highlight + + x: control.checked ? parent.width - width : 0 + y: parent.height / 2 - height / 2 + width: 35 + height: 35 + radius: 17 + visible: control.down || control.highlighted + color: control.checked ? "#33aaff" : "grey" + opacity:.3 + } + + Rectangle { + id:knob + x: control.checked ? parent.width - width : 0 + y: parent.height / 2 - height / 2 + width: 22 + height: 22 + radius: 12 + color: control.checked ? "#33aaff" : "#A5A1A0" + border.color: control.checked ? "transparent" : "grey" + opacity: 1 + } + } + + background: Rectangle { + visible:false + } + + MouseArea { + id: mouseArea + + implicitWidth: parent.width + implicitHeight: parent.height + x: control.width - width - control.rightPadding + y: parent.height / 2 - height / 2 + + onClicked: { + + control.clicked(!checked) // emit + + if (control.checked== true) + control.checked=false + else + control.checked=true + } + + hoverEnabled:true + + onEntered: { + control.highlighted=true + if(control.checked) + highlight.color= "#33aaff" + else + highlight.color= "grey" + } + onExited: { + control.highlighted=false + + } + } + +} diff --git a/qml/ui/widgets/map/MapWidgetForm.ui.qml b/qml/ui/widgets/map/MapWidgetForm.ui.qml index feaecb898..2021bd20c 100644 --- a/qml/ui/widgets/map/MapWidgetForm.ui.qml +++ b/qml/ui/widgets/map/MapWidgetForm.ui.qml @@ -10,6 +10,7 @@ import QtQuick.Window 2.12 //import QtLocation 5.15 import "../"; +import "../../elements"; import OpenHD 1.0