mirror of
https://github.com/f4exb/sdrangel.git
synced 2025-06-24 13:05:21 -04:00
Add additional demod stats.
Add analysis of Mode S frames in order to help filter out false positives. Remove Boost. Use Profiler instead. Fix crash if interrrupted before run. Decode full preamble for Mode S frames. Add support for additional Mode S downlink formats. Allow demod stats to be reset. Add timestamps for buffers, to ensure ordering of frames. Add additional data columns (Aircraft Type, Sideview, Track, Interrogator Code, TCAS, ACAS, RA, Max speed, Version, Length, Width, ADS-B/Mode S frame counts, radius, NACp, NACv, GVA, NIC, SIL, Stops). Add PCE (Preamble Chip Errors) settings for Mode S demod. Remove correlate full preamable setting. Send aircraft state to Map feature for display on PFD. Add support for airline route database. Use combined aircraft database from sdrangel.org rather than OSN database. Add stats table, with demod stats and breakdown of frame types received.. Add button to delete all aircraft from table. Add display of interrogator code coverage. Remove airport elevation setting as now calculated dynamically. Add QNH setting. Add coverage map. Add chart of frame rate and aircraft count. Add table/map orientation setting. Add display of aircraft position uncertainty. Add coloured flight paths with several palettes. Add setting to favour airline livery over aircraft type in 3D model matching. Only use 4 engine map icon for aircraft with 4 engines. Add specialised aircraft map icons (Eurofighter, Spitfire, F35, A400M, Apache, Chinook, Glider) Display aircraft route in labels. Better validate local/global aircraft positions. Add support for tc==31, aircraft operational status frames. Add decoding of Mode S df 0, 11, 16 frames. Add decoding of BDS 0,5 0,8, 0,9.
This commit is contained in:
parent
f882747e97
commit
1975c1748c
plugins/channelrx/demodadsb
adsbdemod.cppadsbdemod.hadsbdemodbaseband.cppadsbdemoddisplaydialog.cppadsbdemoddisplaydialog.uiadsbdemodgui.cppadsbdemodgui.hadsbdemodgui.uiadsbdemodnotificationdialog.cppadsbdemodsettings.cppadsbdemodsettings.hadsbdemodsink.cppadsbdemodsink.hadsbdemodsinkworker.cppadsbdemodsinkworker.hadsbdemodstats.h
@ -17,10 +17,6 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define BOOST_CHRONO_HEADER_ONLY
|
||||
#include <boost/chrono/chrono.hpp>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
|
||||
#include <QTime>
|
||||
@ -49,6 +45,7 @@
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgConfigureADSBDemod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgAircraftReport, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ADSBDemod::MsgResetStats, Message)
|
||||
|
||||
const char* const ADSBDemod::m_channelIdURI = "sdrangel.channel.adsbdemod";
|
||||
const char* const ADSBDemod::m_channelId = "ADSBDemod";
|
||||
@ -194,6 +191,13 @@ bool ADSBDemod::handleMessage(const Message& cmd)
|
||||
m_aircraftReport = msg.getReport();
|
||||
return true;
|
||||
}
|
||||
else if (MsgResetStats::match(cmd))
|
||||
{
|
||||
MsgResetStats& msg = (MsgResetStats&) cmd;
|
||||
MsgResetStats* rep = new MsgResetStats(msg);
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
@ -337,9 +341,6 @@ void ADSBDemod::webapiUpdateChannelSettings(
|
||||
if (channelSettingsKeys.contains("samplesPerBit")) {
|
||||
settings.m_samplesPerBit = response.getAdsbDemodSettings()->getSamplesPerBit();
|
||||
}
|
||||
if (channelSettingsKeys.contains("correlateFullPreamble")) {
|
||||
settings.m_correlateFullPreamble = response.getAdsbDemodSettings()->getCorrelateFullPreamble() != 0;
|
||||
}
|
||||
if (channelSettingsKeys.contains("demodModeS")) {
|
||||
settings.m_demodModeS = response.getAdsbDemodSettings()->getDemodModeS() != 0;
|
||||
}
|
||||
@ -458,7 +459,6 @@ void ADSBDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res
|
||||
response.getAdsbDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
|
||||
response.getAdsbDemodSettings()->setCorrelationThreshold(settings.m_correlationThreshold);
|
||||
response.getAdsbDemodSettings()->setSamplesPerBit(settings.m_samplesPerBit);
|
||||
response.getAdsbDemodSettings()->setCorrelateFullPreamble(settings.m_correlateFullPreamble ? 1 : 0);
|
||||
response.getAdsbDemodSettings()->setDemodModeS(settings.m_demodModeS ? 1 : 0);
|
||||
response.getAdsbDemodSettings()->setInterpolatorPhaseSteps(settings.m_interpolatorPhaseSteps);
|
||||
response.getAdsbDemodSettings()->setInterpolatorTapsPerPhase(settings.m_interpolatorTapsPerPhase);
|
||||
@ -587,9 +587,6 @@ void ADSBDemod::webapiReverseSendSettings(const QList<QString>& channelSettingsK
|
||||
if (channelSettingsKeys.contains("samplesPerBit") || force) {
|
||||
swgADSBDemodSettings->setSamplesPerBit(settings.m_samplesPerBit);
|
||||
}
|
||||
if (channelSettingsKeys.contains("correlateFullPreamble") || force) {
|
||||
swgADSBDemodSettings->setCorrelateFullPreamble(settings.m_correlateFullPreamble ? 1 : 0);
|
||||
}
|
||||
if (channelSettingsKeys.contains("demodModeS") || force) {
|
||||
swgADSBDemodSettings->setDemodModeS(settings.m_demodModeS ? 1 : 0);
|
||||
}
|
||||
|
@ -92,6 +92,21 @@ public:
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgResetStats : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
static MsgResetStats* create()
|
||||
{
|
||||
return new MsgResetStats();
|
||||
}
|
||||
|
||||
private:
|
||||
MsgResetStats() :
|
||||
Message()
|
||||
{ }
|
||||
};
|
||||
|
||||
ADSBDemod(DeviceAPI *deviceAPI);
|
||||
virtual ~ADSBDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "dsp/downchannelizer.h"
|
||||
|
||||
#include "adsbdemodbaseband.h"
|
||||
#include "adsbdemod.h"
|
||||
#include "adsb.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ADSBDemodBaseband::MsgConfigureADSBDemodBaseband, Message)
|
||||
@ -132,6 +133,11 @@ bool ADSBDemodBaseband::handleMessage(const Message& cmd)
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (ADSBDemod::MsgResetStats::match(cmd))
|
||||
{
|
||||
m_sink.resetStats();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
|
@ -50,7 +50,6 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWid
|
||||
ui->airportSize->setCurrentIndex((int)settings->m_airportMinimumSize);
|
||||
ui->heliports->setChecked(settings->m_displayHeliports);
|
||||
ui->units->setCurrentIndex((int)settings->m_siUnits);
|
||||
ui->displayStats->setChecked(settings->m_displayDemodStats);
|
||||
ui->autoResizeTableColumns->setChecked(settings->m_autoResizeTableColumns);
|
||||
ui->aviationstackAPIKey->setText(settings->m_aviationstackAPIKey);
|
||||
ui->checkWXAPIKey->setText(settings->m_checkWXAPIKey);
|
||||
@ -71,8 +70,12 @@ ADSBDemodDisplayDialog::ADSBDemodDisplayDialog(ADSBDemodSettings *settings, QWid
|
||||
ui->atcCallsigns->setChecked(settings->m_atcCallsigns);
|
||||
ui->photos->setChecked(settings->m_displayPhotos);
|
||||
ui->verboseModelMatching->setChecked(settings->m_verboseModelMatching);
|
||||
ui->airfieldElevation->setValue(settings->m_airfieldElevation);
|
||||
ui->favourLivery->setChecked(settings->m_favourLivery);
|
||||
ui->transitionAltitude->setValue(settings->m_transitionAlt);
|
||||
for (auto i = ADSBDemodSettings::m_palettes.cbegin(), end = ADSBDemodSettings::m_palettes.cend(); i != end; ++i) {
|
||||
ui->flightPathPalette->addItem(i.key());
|
||||
}
|
||||
ui->flightPathPalette->setCurrentText(settings->m_flightPathPaletteName);
|
||||
}
|
||||
|
||||
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
|
||||
@ -107,16 +110,11 @@ void ADSBDemodDisplayDialog::accept()
|
||||
m_settings->m_displayHeliports = ui->heliports->isChecked();
|
||||
m_settingsKeys.append("displayHeliports");
|
||||
}
|
||||
if (m_settings->m_siUnits != ui->units->currentIndex() == 0 ? false : true)
|
||||
if (m_settings->m_siUnits != (ui->units->currentIndex() == 0 ? false : true))
|
||||
{
|
||||
m_settings->m_siUnits = ui->units->currentIndex() == 0 ? false : true;
|
||||
m_settingsKeys.append("siUnits");
|
||||
}
|
||||
if (m_settings->m_displayDemodStats != ui->displayStats->isChecked())
|
||||
{
|
||||
m_settings->m_displayDemodStats = ui->displayStats->isChecked();
|
||||
m_settingsKeys.append("displayDemodStats");
|
||||
}
|
||||
if (m_settings->m_autoResizeTableColumns != ui->autoResizeTableColumns->isChecked())
|
||||
{
|
||||
m_settings->m_autoResizeTableColumns = ui->autoResizeTableColumns->isChecked();
|
||||
@ -180,10 +178,10 @@ void ADSBDemodDisplayDialog::accept()
|
||||
m_settings->m_verboseModelMatching = ui->verboseModelMatching->isChecked();
|
||||
m_settingsKeys.append("verboseModelMatching");
|
||||
}
|
||||
if (m_settings->m_airfieldElevation != ui->airfieldElevation->value())
|
||||
if (m_settings->m_favourLivery != ui->favourLivery->isChecked())
|
||||
{
|
||||
m_settings->m_airfieldElevation = ui->airfieldElevation->value();
|
||||
m_settingsKeys.append("airfieldElevation");
|
||||
m_settings->m_favourLivery = ui->favourLivery->isChecked();
|
||||
m_settingsKeys.append("favourLivery");
|
||||
}
|
||||
if (m_settings->m_transitionAlt != ui->transitionAltitude->value())
|
||||
{
|
||||
@ -200,6 +198,12 @@ void ADSBDemodDisplayDialog::accept()
|
||||
m_settings->m_tableFontSize = m_fontSize;
|
||||
m_settingsKeys.append("tableFontSize");
|
||||
}
|
||||
if (m_settings->m_flightPathPaletteName != ui->flightPathPalette->currentText())
|
||||
{
|
||||
m_settings->m_flightPathPaletteName = ui->flightPathPalette->currentText();
|
||||
m_settingsKeys.append("flightPathPaletteName");
|
||||
m_settings->applyPalette();
|
||||
}
|
||||
QDialog::accept();
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>417</width>
|
||||
<height>467</height>
|
||||
<height>505</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -124,22 +124,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="displayStatsLabel">
|
||||
<widget class="QLabel" name="verboseModelMatchingLabel">
|
||||
<property name="text">
|
||||
<string>Display demodulator statistics</string>
|
||||
<string>Log 3D model matching information</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="displayStats">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<widget class="QCheckBox" name="verboseModelMatching">
|
||||
<property name="toolTip">
|
||||
<string>Display demodulator statistics</string>
|
||||
<string>Log information about how aircraft are matched to 3D models</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@ -147,16 +141,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="verboseModelMatchingLabel">
|
||||
<widget class="QLabel" name="favourLiveryLabel">
|
||||
<property name="text">
|
||||
<string>Log 3D model matching information</string>
|
||||
<string>Favour airline livery over aircraft type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="verboseModelMatching">
|
||||
<widget class="QCheckBox" name="favourLivery">
|
||||
<property name="toolTip">
|
||||
<string>Log information about how aircraft are matched to 3D models</string>
|
||||
<string>Favour airline livery over aircraft type for 3D models</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
@ -192,36 +186,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="airfieldElevationLabel">
|
||||
<property name="text">
|
||||
<string>Airfield barometric altitude (ft)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="airfieldElevation">
|
||||
<property name="toolTip">
|
||||
<string>Barometric altitude reported by aircraft when on airfield surface</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>-10000</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>30000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>10</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="transitionAltitudeLabel">
|
||||
<property name="text">
|
||||
<string>Transition altitude (ft)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QSpinBox" name="transitionAltitude">
|
||||
<property name="toolTip">
|
||||
<string>Transition altitude in feet</string>
|
||||
@ -232,6 +203,9 @@
|
||||
<property name="maximum">
|
||||
<number>20000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>6000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
@ -598,6 +572,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<widget class="QComboBox" name="flightPathPalette">
|
||||
<property name="toolTip">
|
||||
<string>Colour palette to use for aircraft flight paths</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="flightPathPaletteLabel">
|
||||
<property name="text">
|
||||
<string>Flight path palette</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@ -607,10 +595,10 @@
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@
|
||||
#include <QTextToSpeech>
|
||||
#include <QRandomGenerator>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QtCharts>
|
||||
|
||||
#include "channel/channelgui.h"
|
||||
#include "dsp/dsptypes.h"
|
||||
@ -56,11 +57,17 @@ class WebAPIAdapterInterface;
|
||||
class HttpDownloadManager;
|
||||
class ADSBDemodGUI;
|
||||
class ADSBOSMTemplateServer;
|
||||
class CheckList;
|
||||
class AircraftModel;
|
||||
|
||||
namespace Ui {
|
||||
class ADSBDemodGUI;
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
using namespace QtCharts;
|
||||
#endif
|
||||
|
||||
// Custom widget to allow formatted decimal numbers to be sorted numerically
|
||||
class CustomDoubleTableWidgetItem : public QTableWidgetItem
|
||||
{
|
||||
@ -89,12 +96,16 @@ struct Aircraft {
|
||||
QString m_icaoHex;
|
||||
QString m_callsign; // Flight callsign
|
||||
QString m_flight; // Guess at flight number
|
||||
bool m_globalPosition; // Position has been determined from global decode
|
||||
Real m_latitude; // Latitude in decimal degrees
|
||||
Real m_longitude; // Longitude in decimal degrees
|
||||
int m_altitude; // Altitude in feet
|
||||
float m_radius; // Horizontal containment radius limit (Rc) in metres
|
||||
int m_altitude; // Altitude in feet (will be 0 if on surface)
|
||||
int m_pressureAltitude; // Pressure altitude in feet for Map PFD altimeter (can be negative on surface)
|
||||
bool m_onSurface; // Indicates if on surface or airborne
|
||||
bool m_altitudeGNSS; // Altitude is GNSS HAE (Height above WGS-84 ellipsoid) rather than barometric alitute (relative to 29.92 Hg)
|
||||
float m_heading; // Heading or track in degrees
|
||||
float m_heading; // Heading in degrees magnetic
|
||||
float m_track; // Track in degrees true?
|
||||
int m_verticalRate; // Vertical climb rate in ft/min
|
||||
QString m_emitterCategory; // Aircraft type
|
||||
QString m_status; // Aircraft status
|
||||
@ -102,23 +113,37 @@ struct Aircraft {
|
||||
Real m_range; // Distance from station to aircraft
|
||||
Real m_azimuth; // Azimuth from station to aircraft
|
||||
Real m_elevation; // Elevation from station to aircraft
|
||||
QDateTime m_time; // When last updated
|
||||
QDateTime m_rxTime; // When last frame received (can be long ago if reading from log file)
|
||||
QDateTime m_updateTime; // Last time we updated data for this aircraft (used for determining when to remove an aircraft)
|
||||
|
||||
int m_selAltitude; // Selected altitude in MCP/FCU or FMS in feet
|
||||
int m_selHeading; // Selected heading in MCP/FCU in degrees
|
||||
int m_baro; // Aircraft baro setting in mb (Mode-S)
|
||||
float m_baro; // Aircraft baro setting in mb (Mode-S)
|
||||
float m_roll; // In degrees
|
||||
int m_groundspeed; // In knots
|
||||
float m_turnRate; // In degrees per second
|
||||
int m_trueAirspeed; // In knots
|
||||
int m_indicatedAirspeed; // In knots
|
||||
float m_mach; // Mach number
|
||||
bool m_autopilot;
|
||||
bool m_vnavMode;
|
||||
bool m_altHoldMode;
|
||||
bool m_approachMode;
|
||||
bool m_lnavMode;
|
||||
bool m_tcasOperational; // Appears only to be true if TA/RA, false if TA ONLY
|
||||
|
||||
bool m_bdsCapabilities[16][16]; // BDS capabilities are indicaited by BDS 1.7
|
||||
int m_adsbVersion;
|
||||
bool m_nicSupplementA;
|
||||
bool m_nicSupplementB;
|
||||
bool m_nicSupplementC;
|
||||
|
||||
bool m_positionValid; // Indicates if we have valid data for the above fields
|
||||
bool m_altitudeValid;
|
||||
bool m_pressureAltitudeValid;
|
||||
bool m_onSurfaceValid;
|
||||
bool m_headingValid;
|
||||
bool m_trackValid;
|
||||
bool m_verticalRateValid;
|
||||
bool m_selAltitudeValid;
|
||||
bool m_selHeadingValid;
|
||||
@ -129,16 +154,30 @@ struct Aircraft {
|
||||
bool m_trueAirspeedValid;
|
||||
bool m_indicatedAirspeedValid;
|
||||
bool m_machValid;
|
||||
bool m_autopilotValid;
|
||||
bool m_vnavModeValid;
|
||||
bool m_altHoldModeValid;
|
||||
bool m_approachModeValid;
|
||||
bool m_lnavModeValid;
|
||||
bool m_tcasOperationalValid;
|
||||
|
||||
bool m_bdsCapabilitiesValid;
|
||||
bool m_adsbVersionValid;
|
||||
bool m_nicSupplementAValid;
|
||||
bool m_nicSupplementBValid;
|
||||
bool m_nicSupplementCValid;
|
||||
|
||||
// State for calculating position using two CPR frames
|
||||
bool m_cprValid[2];
|
||||
Real m_cprLat[2];
|
||||
Real m_cprLong[2];
|
||||
double m_cprLat[2];
|
||||
double m_cprLong[2];
|
||||
QDateTime m_cprTime[2];
|
||||
|
||||
int m_adsbFrameCount; // Number of ADS-B frames for this aircraft
|
||||
int m_modesFrameCount; // Number of Mode-S frames for this aircraft
|
||||
int m_nonTransponderFrameCount;
|
||||
int m_tisBFrameCount;
|
||||
int m_adsrFrameCount;
|
||||
float m_minCorrelation;
|
||||
float m_maxCorrelation;
|
||||
float m_correlation;
|
||||
@ -148,8 +187,12 @@ struct Aircraft {
|
||||
bool m_isHighlighted; // Are we highlighting this aircraft in the table and map
|
||||
bool m_showAll;
|
||||
|
||||
QVariantList m_coordinates; // Coordinates we've recorded the aircraft at
|
||||
QList<QVariantList> m_coordinates; // Coordinates we've recorded the aircraft at, split up in to altitude ranges
|
||||
QList<QVariantList> m_recentCoordinates; // Last 20 seconds of coordinates for ATC mode
|
||||
QList<QDateTime> m_coordinateDateTimes;
|
||||
QList<int> m_coordinateColors; // 0-7 index to 8 color palette according to altitude
|
||||
QList<int> m_recentCoordinateColors;
|
||||
int m_lastColor;
|
||||
|
||||
AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database
|
||||
QString m_aircraft3DModel; // 3D model for map based on aircraft type
|
||||
@ -159,6 +202,7 @@ struct Aircraft {
|
||||
ADSBDemodGUI *m_gui;
|
||||
QString m_flagIconURL;
|
||||
QString m_airlineIconURL;
|
||||
QString m_sideviewIconURL;
|
||||
|
||||
// For animation on 3D map
|
||||
float m_runwayAltitude;
|
||||
@ -168,10 +212,14 @@ struct Aircraft {
|
||||
bool m_rotorStarted; // Rotors started on 'Rotorcraft'
|
||||
bool m_engineStarted; // Engines started (typically propellors)
|
||||
QDateTime m_positionDateTime;
|
||||
QDateTime m_orientationDateTime;
|
||||
QDateTime m_orientationDateTime; // FIXME
|
||||
QDateTime m_headingDateTime;
|
||||
QDateTime m_prevHeadingDateTime;
|
||||
int m_prevHeading;
|
||||
QDateTime m_trackDateTime;
|
||||
QDateTime m_altitudeDateTime;
|
||||
QDateTime m_indicatedAirspeedDateTime;
|
||||
QDateTime m_prevTrackDateTime;
|
||||
int m_prevTrack;
|
||||
float m_trackWhenHeadingSet;
|
||||
float m_pitchEst; // Estimated pitch based on vertical rate
|
||||
float m_rollEst; // Estimated roll based on rate of change in heading
|
||||
|
||||
@ -182,30 +230,48 @@ struct Aircraft {
|
||||
QTableWidgetItem *m_callsignItem;
|
||||
QTableWidgetItem* m_atcCallsignItem;
|
||||
QTableWidgetItem *m_modelItem;
|
||||
QTableWidgetItem *m_typeItem;
|
||||
QTableWidgetItem *m_sideviewItem;
|
||||
QTableWidgetItem *m_airlineItem;
|
||||
QTableWidgetItem *m_latitudeItem;
|
||||
QTableWidgetItem *m_longitudeItem;
|
||||
QTableWidgetItem *m_altitudeItem;
|
||||
QTableWidgetItem *m_headingItem;
|
||||
QTableWidgetItem *m_trackItem;
|
||||
QTableWidgetItem *m_verticalRateItem;
|
||||
CustomDoubleTableWidgetItem *m_rangeItem;
|
||||
QTableWidgetItem *m_azElItem;
|
||||
QTableWidgetItem *m_emitterCategoryItem;
|
||||
QTableWidgetItem *m_statusItem;
|
||||
QTableWidgetItem *m_squawkItem;
|
||||
QTableWidgetItem *m_identItem;
|
||||
QTableWidgetItem *m_registrationItem;
|
||||
QTableWidgetItem *m_countryItem;
|
||||
QTableWidgetItem *m_registeredItem;
|
||||
QTableWidgetItem *m_manufacturerNameItem;
|
||||
QTableWidgetItem *m_ownerItem;
|
||||
QTableWidgetItem *m_operatorICAOItem;
|
||||
QTableWidgetItem *m_interogatorCodeItem;
|
||||
QTableWidgetItem *m_timeItem;
|
||||
QTableWidgetItem *m_totalFrameCountItem;
|
||||
QTableWidgetItem *m_adsbFrameCountItem;
|
||||
QTableWidgetItem *m_modesFrameCountItem;
|
||||
QTableWidgetItem *m_nonTransponderItem;
|
||||
QTableWidgetItem *m_tisBFrameCountItem;
|
||||
QTableWidgetItem *m_adsrFrameCountItem;
|
||||
QTableWidgetItem *m_radiusItem;
|
||||
QTableWidgetItem *m_nacpItem;
|
||||
QTableWidgetItem *m_nacvItem;
|
||||
QTableWidgetItem *m_gvaItem;
|
||||
QTableWidgetItem *m_nicItem;
|
||||
QTableWidgetItem *m_nicBaroItem;
|
||||
QTableWidgetItem *m_silItem;
|
||||
QTableWidgetItem *m_correlationItem;
|
||||
QTableWidgetItem *m_rssiItem;
|
||||
QTableWidgetItem *m_flightStatusItem;
|
||||
QTableWidgetItem *m_depItem;
|
||||
QTableWidgetItem *m_arrItem;
|
||||
QTableWidgetItem *m_stopsItem;
|
||||
QTableWidgetItem *m_stdItem;
|
||||
QTableWidgetItem *m_etdItem;
|
||||
QTableWidgetItem *m_atdItem;
|
||||
@ -218,6 +284,13 @@ struct Aircraft {
|
||||
QTableWidgetItem *m_apItem;
|
||||
QTableWidgetItem *m_vModeItem;
|
||||
QTableWidgetItem *m_lModeItem;
|
||||
QTableWidgetItem *m_tcasItem;
|
||||
QTableWidgetItem *m_acasItem;
|
||||
QTableWidgetItem *m_raItem;
|
||||
QTableWidgetItem *m_maxSpeedItem;
|
||||
QTableWidgetItem *m_versionItem;
|
||||
QTableWidgetItem *m_lengthItem;
|
||||
QTableWidgetItem *m_widthItem;
|
||||
QTableWidgetItem *m_rollItem;
|
||||
QTableWidgetItem *m_groundspeedItem;
|
||||
QTableWidgetItem *m_turnRateItem;
|
||||
@ -231,16 +304,18 @@ struct Aircraft {
|
||||
QTableWidgetItem *m_staticPressureItem;
|
||||
QTableWidgetItem *m_staticAirTempItem;
|
||||
QTableWidgetItem *m_humidityItem;
|
||||
QTableWidgetItem *m_tisBItem;
|
||||
|
||||
Aircraft(ADSBDemodGUI *gui) :
|
||||
m_icao(0),
|
||||
m_globalPosition(false),
|
||||
m_latitude(0),
|
||||
m_longitude(0),
|
||||
m_radius(0.0f),
|
||||
m_altitude(0),
|
||||
m_onSurface(false),
|
||||
m_altitudeGNSS(false),
|
||||
m_heading(0),
|
||||
m_track(0),
|
||||
m_verticalRate(0),
|
||||
m_azimuth(0),
|
||||
m_elevation(0),
|
||||
@ -253,9 +328,22 @@ struct Aircraft {
|
||||
m_trueAirspeed(0),
|
||||
m_indicatedAirspeed(0),
|
||||
m_mach(0.0f),
|
||||
m_autopilot(false),
|
||||
m_vnavMode(false),
|
||||
m_altHoldMode(false),
|
||||
m_approachMode(false),
|
||||
m_lnavMode(false),
|
||||
m_adsbVersion(0),
|
||||
m_tcasOperational(false),
|
||||
m_nicSupplementA(false),
|
||||
m_nicSupplementB(false),
|
||||
m_nicSupplementC(false),
|
||||
m_positionValid(false),
|
||||
m_altitudeValid(false),
|
||||
m_pressureAltitudeValid(false),
|
||||
m_onSurfaceValid(false),
|
||||
m_headingValid(false),
|
||||
m_trackValid(false),
|
||||
m_verticalRateValid(false),
|
||||
m_selAltitudeValid(false),
|
||||
m_selHeadingValid(false),
|
||||
@ -266,9 +354,22 @@ struct Aircraft {
|
||||
m_trueAirspeedValid(false),
|
||||
m_indicatedAirspeedValid(false),
|
||||
m_machValid(false),
|
||||
m_autopilotValid(false),
|
||||
m_vnavModeValid(false),
|
||||
m_altHoldModeValid(false),
|
||||
m_approachModeValid(false),
|
||||
m_lnavModeValid(false),
|
||||
m_tcasOperationalValid(false),
|
||||
m_bdsCapabilitiesValid(false),
|
||||
m_adsbVersionValid(false),
|
||||
m_nicSupplementAValid(false),
|
||||
m_nicSupplementBValid(false),
|
||||
m_nicSupplementCValid(false),
|
||||
m_adsbFrameCount(0),
|
||||
m_modesFrameCount(0),
|
||||
m_nonTransponderFrameCount(0),
|
||||
m_tisBFrameCount(0),
|
||||
m_adsrFrameCount(0),
|
||||
m_minCorrelation(INFINITY),
|
||||
m_maxCorrelation(-INFINITY),
|
||||
m_correlation(0.0f),
|
||||
@ -302,9 +403,12 @@ struct Aircraft {
|
||||
m_callsignItem = new QTableWidgetItem();
|
||||
m_atcCallsignItem = new QTableWidgetItem();
|
||||
m_modelItem = new QTableWidgetItem();
|
||||
m_typeItem = new QTableWidgetItem();
|
||||
m_sideviewItem = new QTableWidgetItem();
|
||||
m_airlineItem = new QTableWidgetItem();
|
||||
m_altitudeItem = new QTableWidgetItem();
|
||||
m_headingItem = new QTableWidgetItem();
|
||||
m_trackItem = new QTableWidgetItem();
|
||||
m_verticalRateItem = new QTableWidgetItem();
|
||||
m_rangeItem = new CustomDoubleTableWidgetItem();
|
||||
m_azElItem = new QTableWidgetItem();
|
||||
@ -313,19 +417,34 @@ struct Aircraft {
|
||||
m_emitterCategoryItem = new QTableWidgetItem();
|
||||
m_statusItem = new QTableWidgetItem();
|
||||
m_squawkItem = new QTableWidgetItem();
|
||||
m_identItem = new QTableWidgetItem();
|
||||
m_registrationItem = new QTableWidgetItem();
|
||||
m_countryItem = new QTableWidgetItem();
|
||||
m_registeredItem = new QTableWidgetItem();
|
||||
m_manufacturerNameItem = new QTableWidgetItem();
|
||||
m_ownerItem = new QTableWidgetItem();
|
||||
m_operatorICAOItem = new QTableWidgetItem();
|
||||
m_interogatorCodeItem = new QTableWidgetItem();
|
||||
m_timeItem = new QTableWidgetItem();
|
||||
m_totalFrameCountItem = new QTableWidgetItem();
|
||||
m_adsbFrameCountItem = new QTableWidgetItem();
|
||||
m_modesFrameCountItem = new QTableWidgetItem();
|
||||
m_nonTransponderItem = new QTableWidgetItem();
|
||||
m_tisBFrameCountItem = new QTableWidgetItem();
|
||||
m_adsrFrameCountItem = new QTableWidgetItem();
|
||||
m_radiusItem = new QTableWidgetItem();
|
||||
m_nacpItem = new QTableWidgetItem();
|
||||
m_nacvItem = new QTableWidgetItem();
|
||||
m_gvaItem = new QTableWidgetItem();
|
||||
m_nicItem = new QTableWidgetItem();
|
||||
m_nicBaroItem = new QTableWidgetItem();
|
||||
m_silItem = new QTableWidgetItem();
|
||||
m_correlationItem = new QTableWidgetItem();
|
||||
m_rssiItem = new QTableWidgetItem();
|
||||
m_flightStatusItem = new QTableWidgetItem();
|
||||
m_depItem = new QTableWidgetItem();
|
||||
m_arrItem = new QTableWidgetItem();
|
||||
m_stopsItem = new QTableWidgetItem();
|
||||
m_stdItem = new QTableWidgetItem();
|
||||
m_etdItem = new QTableWidgetItem();
|
||||
m_atdItem = new QTableWidgetItem();
|
||||
@ -338,6 +457,13 @@ struct Aircraft {
|
||||
m_apItem = new QTableWidgetItem();
|
||||
m_vModeItem = new QTableWidgetItem();
|
||||
m_lModeItem = new QTableWidgetItem();
|
||||
m_tcasItem = new QTableWidgetItem();
|
||||
m_acasItem = new QTableWidgetItem();
|
||||
m_raItem = new QTableWidgetItem();
|
||||
m_maxSpeedItem = new QTableWidgetItem();
|
||||
m_versionItem = new QTableWidgetItem();
|
||||
m_lengthItem = new QTableWidgetItem();
|
||||
m_widthItem = new QTableWidgetItem();
|
||||
m_rollItem = new QTableWidgetItem();
|
||||
m_groundspeedItem = new QTableWidgetItem();
|
||||
m_turnRateItem = new QTableWidgetItem();
|
||||
@ -351,16 +477,15 @@ struct Aircraft {
|
||||
m_staticPressureItem = new QTableWidgetItem();
|
||||
m_staticAirTempItem = new QTableWidgetItem();
|
||||
m_humidityItem = new QTableWidgetItem();
|
||||
m_tisBItem = new QTableWidgetItem();
|
||||
}
|
||||
|
||||
QString getImage() const;
|
||||
QString getText(const ADSBDemodSettings *settings, bool all=false) const;
|
||||
// Label to use for aircraft on map
|
||||
QString getLabel(const ADSBDemodSettings *settings) const;
|
||||
QString getLabel(const ADSBDemodSettings *settings, QDateTime& dateTime) const;
|
||||
|
||||
// Name to use when selected as a target (E.g. for use as target name in Rotator Controller)
|
||||
QString targetName()
|
||||
QString targetName() const
|
||||
{
|
||||
if (!m_callsign.isEmpty())
|
||||
return QString("Callsign: %1").arg(m_callsign);
|
||||
@ -368,6 +493,71 @@ struct Aircraft {
|
||||
return QString("ICAO: %1").arg(m_icao, 0, 16);
|
||||
}
|
||||
|
||||
void setOnSurface(const QDateTime& dateTime);
|
||||
void setAltitude(int altitudeFt, bool gnss, const QDateTime& dateTime, const ADSBDemodSettings& settings);
|
||||
void setVerticalRate(int verticalRate, const ADSBDemodSettings& settings);
|
||||
void setGroundspeed(float groundspeed, const ADSBDemodSettings& settings);
|
||||
void setTrueAirspeed(int airspeed, const ADSBDemodSettings& settings);
|
||||
void setIndicatedAirspeed(int airspeed, const QDateTime& dateTime, const ADSBDemodSettings& settings);
|
||||
void setTrack(float track, const QDateTime& dateTime);
|
||||
void setHeading(float heading, const QDateTime& dateTime);
|
||||
void addCoordinate(const QDateTime& dateTime, AircraftModel *model);
|
||||
void clearCoordinates(AircraftModel *model);
|
||||
};
|
||||
|
||||
class AircraftPathModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QAbstractListModel::QAbstractListModel;
|
||||
enum MarkerRoles{
|
||||
coordinatesRole = Qt::UserRole + 1,
|
||||
colorRole = Qt::UserRole + 2,
|
||||
};
|
||||
|
||||
AircraftPathModel(AircraftModel *aircraftModel, Aircraft *aircraft) :
|
||||
m_aircraftModel(aircraftModel),
|
||||
m_aircraft(aircraft),
|
||||
m_paths(0),
|
||||
m_showFullPath(false),
|
||||
m_showATCPath(false)
|
||||
{
|
||||
settingsUpdated();
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
(void) parent;
|
||||
return m_paths;
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void add();
|
||||
void updateLast();
|
||||
void removed();
|
||||
void clear();
|
||||
void settingsUpdated();
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override
|
||||
{
|
||||
(void) index;
|
||||
return Qt::ItemIsEnabled;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const {
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[coordinatesRole] = "coordinates";
|
||||
roles[colorRole] = "color";
|
||||
return roles;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
AircraftModel *m_aircraftModel;
|
||||
Aircraft *m_aircraft;
|
||||
int m_paths; // Should match m_aircraft->m_coordinates.count()
|
||||
bool m_showFullPath;
|
||||
bool m_showATCPath;
|
||||
};
|
||||
|
||||
// Aircraft data model used by QML map item
|
||||
@ -385,12 +575,16 @@ public:
|
||||
aircraftPathRole = Qt::UserRole + 6,
|
||||
showAllRole = Qt::UserRole + 7,
|
||||
highlightedRole = Qt::UserRole + 8,
|
||||
targetRole = Qt::UserRole + 9
|
||||
targetRole = Qt::UserRole + 9,
|
||||
radiusRole = Qt::UserRole + 10,
|
||||
showRadiusRole = Qt::UserRole + 11,
|
||||
aircraftPathModelRole = Qt::UserRole + 12,
|
||||
};
|
||||
|
||||
Q_INVOKABLE void addAircraft(Aircraft *aircraft) {
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_aircrafts.append(aircraft);
|
||||
m_pathModels.append(new AircraftPathModel(this, aircraft));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@ -409,7 +603,8 @@ public:
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
void aircraftUpdated(Aircraft *aircraft) {
|
||||
void aircraftUpdated(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0)
|
||||
{
|
||||
@ -418,30 +613,72 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void allAircraftUpdated() {
|
||||
/*
|
||||
// Not sure why this doesn't work - it should be more efficient
|
||||
// than the following code
|
||||
emit dataChanged(index(0), index(rowCount()));
|
||||
*/
|
||||
for (int i = 0; i < m_aircrafts.count(); i++)
|
||||
void highlightChanged(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0)
|
||||
{
|
||||
QModelIndex idx = index(i);
|
||||
m_pathModels[row]->settingsUpdated();
|
||||
QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
void removeAircraft(Aircraft *aircraft) {
|
||||
void clearCoords(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0) {
|
||||
m_pathModels[row]->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void aircraftCoordsUpdated(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0) {
|
||||
m_pathModels[row]->updateLast();
|
||||
}
|
||||
}
|
||||
|
||||
void aircraftCoordsAdded(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0) {
|
||||
m_pathModels[row]->add();
|
||||
}
|
||||
}
|
||||
|
||||
void aircraftCoordsRemoved(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0) {
|
||||
m_pathModels[row]->removed();
|
||||
}
|
||||
}
|
||||
|
||||
void allAircraftUpdated()
|
||||
{
|
||||
emit dataChanged(index(0), index(rowCount()-1));
|
||||
|
||||
for (int i = 0; i < m_aircrafts.count(); i++) {
|
||||
m_pathModels[i]->settingsUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
void removeAircraft(Aircraft *aircraft)
|
||||
{
|
||||
int row = m_aircrafts.indexOf(aircraft);
|
||||
if (row >= 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_aircrafts.removeAt(row);
|
||||
delete m_pathModels.takeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const {
|
||||
QHash<int, QByteArray> roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[positionRole] = "position";
|
||||
roles[headingRole] = "heading";
|
||||
@ -452,22 +689,12 @@ public:
|
||||
roles[showAllRole] = "showAll";
|
||||
roles[highlightedRole] = "highlighted";
|
||||
roles[targetRole] = "target";
|
||||
roles[radiusRole] = "containmentRadius";
|
||||
roles[aircraftPathModelRole] = "aircraftPathModel";
|
||||
return roles;
|
||||
}
|
||||
|
||||
void setFlightPaths(bool flightPaths)
|
||||
{
|
||||
m_flightPaths = flightPaths;
|
||||
allAircraftUpdated();
|
||||
}
|
||||
|
||||
void setAllFlightPaths(bool allFlightPaths)
|
||||
{
|
||||
m_allFlightPaths = allFlightPaths;
|
||||
allAircraftUpdated();
|
||||
}
|
||||
|
||||
void setSettings(const ADSBDemodSettings *settings)
|
||||
void setSettings(const ADSBDemodSettings *settings)
|
||||
{
|
||||
m_settings = settings;
|
||||
allAircraftUpdated();
|
||||
@ -487,11 +714,11 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
const ADSBDemodSettings *m_settings;
|
||||
|
||||
private:
|
||||
QList<Aircraft *> m_aircrafts;
|
||||
bool m_flightPaths;
|
||||
bool m_allFlightPaths;
|
||||
const ADSBDemodSettings *m_settings;
|
||||
QList<AircraftPathModel *> m_pathModels;
|
||||
};
|
||||
|
||||
// Airport data model used by QML map item
|
||||
@ -667,26 +894,34 @@ public:
|
||||
airspacePolygonRole = Qt::UserRole + 6
|
||||
};
|
||||
|
||||
Q_INVOKABLE void addAirspace(Airspace *airspace) {
|
||||
Q_INVOKABLE void addAirspace(Airspace *airspace)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
||||
m_airspaces.append(airspace);
|
||||
// Convert QPointF to QVariantList of QGeoCoordinates
|
||||
QVariantList polygon;
|
||||
for (const auto p : airspace->m_polygon)
|
||||
{
|
||||
QGeoCoordinate coord(p.y(), p.x(), airspace->topHeightInMetres());
|
||||
polygon.push_back(QVariant::fromValue(coord));
|
||||
}
|
||||
m_polygons.append(polygon);
|
||||
updatePolygon(airspace, -1);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_airspaces.count();
|
||||
}
|
||||
|
||||
void removeAllAirspaces() {
|
||||
void removeAirspace(Airspace *airspace)
|
||||
{
|
||||
int idx = m_airspaces.indexOf(airspace);
|
||||
if (idx >= 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), idx, idx);
|
||||
m_airspaces.removeAt(idx);
|
||||
m_polygons.removeAt(idx);
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void removeAllAirspaces()
|
||||
{
|
||||
if (m_airspaces.count() > 0)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), 0, m_airspaces.count() - 1);
|
||||
@ -696,6 +931,23 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void airspaceUpdated(const Airspace *airspace)
|
||||
{
|
||||
int row = m_airspaces.indexOf(airspace);
|
||||
if (row >= 0)
|
||||
{
|
||||
updatePolygon(airspace, row);
|
||||
|
||||
QModelIndex idx = index(row);
|
||||
emit dataChanged(idx, idx);
|
||||
}
|
||||
}
|
||||
|
||||
bool contains(const Airspace *airspace)
|
||||
{
|
||||
return m_airspaces.contains(airspace);
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
|
||||
@ -706,7 +958,8 @@ public:
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> roleNames() const {
|
||||
QHash<int, QByteArray> roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[nameRole] = "name";
|
||||
roles[detailsRole] = "details";
|
||||
@ -720,6 +973,23 @@ public:
|
||||
private:
|
||||
QList<Airspace *> m_airspaces;
|
||||
QList<QVariantList> m_polygons;
|
||||
|
||||
void updatePolygon(const Airspace *airspace, int row)
|
||||
{
|
||||
// Convert QPointF to QVariantList of QGeoCoordinates
|
||||
QVariantList polygon;
|
||||
for (const auto p : airspace->m_polygon)
|
||||
{
|
||||
QGeoCoordinate coord(p.y(), p.x(), airspace->topHeightInMetres());
|
||||
polygon.push_back(QVariant::fromValue(coord));
|
||||
}
|
||||
if (row == -1) {
|
||||
m_polygons.append(polygon);
|
||||
} else {
|
||||
m_polygons.replace(row, polygon);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// NavAid model used for each NavAid on the map
|
||||
@ -867,6 +1137,23 @@ protected:
|
||||
class ADSBDemodGUI : public ChannelGUI {
|
||||
Q_OBJECT
|
||||
|
||||
struct Interogator {
|
||||
Real m_minLatitude;
|
||||
Real m_maxLatitude;
|
||||
Real m_minLongitude;
|
||||
Real m_maxLongitude;
|
||||
bool m_valid;
|
||||
Airspace m_airspace;
|
||||
|
||||
Interogator() :
|
||||
m_valid(false)
|
||||
{
|
||||
}
|
||||
|
||||
void update(int ic, Aircraft *aircraft, AirspaceModel *airspaceModel, CheckList *checkList, bool display);
|
||||
void calcPoly();
|
||||
};
|
||||
|
||||
public:
|
||||
static ADSBDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
|
||||
virtual void destroy();
|
||||
@ -923,6 +1210,7 @@ private:
|
||||
|
||||
QHash<int, Aircraft *> m_aircraft; // Hashed on ICAO
|
||||
QSharedPointer<const QHash<int, AircraftInformation *>> m_aircraftInfo;
|
||||
QSharedPointer<const QHash<QString, AircraftRouteInformation *>> m_routeInfo; // Hashed on callsign
|
||||
QSharedPointer<const QHash<int, AirportInformation *>> m_airportInfo; // Hashed on id
|
||||
AircraftModel m_aircraftModel;
|
||||
AirportModel m_airportModel;
|
||||
@ -936,12 +1224,85 @@ private:
|
||||
Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report
|
||||
MovingAverageUtil<float, double, 10> m_correlationAvg;
|
||||
MovingAverageUtil<float, double, 10> m_correlationOnesAvg;
|
||||
MovingAverageUtil<float, double, 100> m_qnhAvg;
|
||||
|
||||
Aircraft *m_highlightAircraft; // Aircraft we want to highlight, when selected in table
|
||||
|
||||
float m_currentAirportRange; // Current settings, so we only update if changed
|
||||
ADSBDemodSettings::AirportType m_currentAirportMinimumSize;
|
||||
bool m_currentDisplayHeliports;
|
||||
|
||||
static const int m_maxRangeDeg = 5;
|
||||
QList<float> m_maxRange[2];
|
||||
Airspace m_coverageAirspace[2];
|
||||
|
||||
Interogator m_interogators[ADSB_IC_MAX];
|
||||
|
||||
enum StatsRow {
|
||||
ADSB_FRAMES,
|
||||
MODE_S_FRAMES,
|
||||
TOTAL_FRAMES,
|
||||
ADSB_RATE,
|
||||
MODE_S_RATE,
|
||||
TOTAL_RATE,
|
||||
DATA_RATE,
|
||||
CORRELATOR_MATCHES,
|
||||
PERCENT_VALID,
|
||||
PREAMBLE_FAILS,
|
||||
CRC_FAILS,
|
||||
TYPE_FAILS,
|
||||
INVALID_FAILS,
|
||||
ICAO_FAILS,
|
||||
RANGE_FAILS,
|
||||
ALT_FAILS,
|
||||
AVERAGE_CORRELATION,
|
||||
TC_0,
|
||||
TC_1_4,
|
||||
TC_5_8,
|
||||
TC_9_18,
|
||||
TC_19,
|
||||
TC_20_22,
|
||||
TC_24,
|
||||
TC_28,
|
||||
TC_29,
|
||||
TC_31,
|
||||
TC_RESERVED,
|
||||
DF0,
|
||||
DF4,
|
||||
DF5,
|
||||
DF11,
|
||||
DF16,
|
||||
DF17,
|
||||
DF18,
|
||||
DF19,
|
||||
DF20_21,
|
||||
DF22,
|
||||
DF24,
|
||||
MAX_RANGE,
|
||||
MAX_ALTITUDE,
|
||||
MAX_RATE
|
||||
};
|
||||
|
||||
qint64 m_rangeFails;
|
||||
qint64 m_altFails;
|
||||
QDateTime m_frameRateTime;
|
||||
qint64 m_adsbFrameRateCount;
|
||||
qint64 m_modesFrameRateCount;
|
||||
qint64 m_totalBytes;
|
||||
float m_maxRangeStat;
|
||||
float m_maxAltitudeStat;
|
||||
float m_maxRateState;
|
||||
qint64 m_dfStats[32];
|
||||
qint64 m_tcStats[32];
|
||||
QChart *m_chart;
|
||||
QLineSeries *m_adsbFrameRateSeries;
|
||||
QLineSeries *m_modesFrameRateSeries;
|
||||
QLineSeries *m_aircraftSeries;
|
||||
QDateTimeAxis *m_xAxis;
|
||||
QValueAxis *m_fpsYAxis;
|
||||
QValueAxis *m_aircraftYAxis;
|
||||
QDateTime m_averageTime;
|
||||
|
||||
#ifdef QT_TEXTTOSPEECH_FOUND
|
||||
QTextToSpeech *m_speech;
|
||||
#endif
|
||||
@ -977,6 +1338,7 @@ private:
|
||||
static const QString m_flightStatuses[];
|
||||
static const QString m_hazardSeverity[];
|
||||
static const QString m_fomSources[];
|
||||
static const QString m_nacvStrings[];
|
||||
|
||||
explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
|
||||
virtual ~ADSBDemodGUI();
|
||||
@ -990,8 +1352,6 @@ private:
|
||||
void makeUIConnections();
|
||||
void updateAbsoluteCenterFrequency();
|
||||
|
||||
void updatePosition(Aircraft *aircraft);
|
||||
bool updateLocalPosition(Aircraft *aircraft, double latitude, double longitude, bool surfacePosition);
|
||||
void clearFromMap(const QString& name);
|
||||
void sendToMap(Aircraft *aircraft, QList<SWGSDRangel::SWGMapAnimation *> *animations);
|
||||
Aircraft *getAircraft(int icao, bool &newAircraft);
|
||||
@ -1006,10 +1366,24 @@ private:
|
||||
float correlationOnes,
|
||||
unsigned crc,
|
||||
bool updateModel);
|
||||
void decodeModeS(const QByteArray data, int df, Aircraft *aircraft);
|
||||
|
||||
void decodeID(const QByteArray& data, QString& emitterCategory, QString& callsign);
|
||||
void decodeGroundspeed(const QByteArray& data, float& v, float& h);
|
||||
void decodeAirspeed(const QByteArray& data, bool& tas, int& as, bool& hdgValid, float& hdg);
|
||||
void decodeVerticalRate(const QByteArray& data, int& verticalRate);
|
||||
void updateAircraftPosition(Aircraft *aircraft, double latitude, double longitude, const QDateTime& dateTime);
|
||||
bool validateGlobalPosition(double latitude, double longitude, bool countFailure);
|
||||
bool validateLocalPosition(double latitude, double longitude, bool surfacePosition, bool countFailure);
|
||||
bool decodeGlobalPosition(int f, const double cprLat[2], const double cprLong[2], const QDateTime cprTime[2], double& latitude, double& longitude, bool countFailure);
|
||||
bool decodeLocalPosition(int f, double cprLat, double cprLong, bool onSurface, const QDateTime& dateTime, const Aircraft *aircraft, double& latitude, double& longitude, bool countFailure);
|
||||
void decodeCpr(const QByteArray& data, int& f, double& latCpr, double& lonCpr) const;
|
||||
bool decodeAltitude(const QByteArray& data, int& altFt) const;
|
||||
void decodeModeSAltitude(const QByteArray& data, const QDateTime dateTime, Aircraft *aircraft);
|
||||
void decodeModeS(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft);
|
||||
void decodeCommB(const QByteArray data, const QDateTime dateTime, int df, Aircraft *aircraft, bool &updatedCallsign);
|
||||
QList<SWGSDRangel::SWGMapAnimation *> *animate(QDateTime dateTime, Aircraft *aircraft);
|
||||
SWGSDRangel::SWGMapAnimation *gearAnimation(QDateTime startDateTime, bool up);
|
||||
SWGSDRangel::SWGMapAnimation *gearAngle(QDateTime startDateTime, bool flat);
|
||||
SWGSDRangel::SWGMapAnimation *flapsAnimation(QDateTime startDateTime, float currentFlaps, float flaps);
|
||||
SWGSDRangel::SWGMapAnimation *slatsAnimation(QDateTime startDateTime, bool retract);
|
||||
SWGSDRangel::SWGMapAnimation *rotorAnimation(QDateTime startDateTime, bool stop);
|
||||
@ -1022,18 +1396,18 @@ private:
|
||||
QString subAircraftString(Aircraft *aircraft, const QString &string);
|
||||
void resizeTable();
|
||||
QString getDataDir();
|
||||
void readAirportDB(const QString& filename);
|
||||
void readAirportFrequenciesDB(const QString& filename);
|
||||
void update3DModels();
|
||||
void updateAirports();
|
||||
void updateAirspaces();
|
||||
void updateNavAids();
|
||||
void updateChannelList();
|
||||
void removeAircraft(QHash<int, Aircraft *>::iterator& i, Aircraft *aircraft);
|
||||
QAction *createCheckableItem(QString& text, int idx, bool checked);
|
||||
Aircraft* findAircraftByFlight(const QString& flight);
|
||||
QString dataTimeToShortString(QDateTime dt);
|
||||
void initFlightInformation();
|
||||
void initAviationWeather();
|
||||
void setShowContainmentRadius(bool show);
|
||||
void applyMapSettings();
|
||||
void updatePhotoText(Aircraft *aircraft);
|
||||
void updatePhotoFlightInformation(Aircraft *aircraft);
|
||||
@ -1045,16 +1419,33 @@ private:
|
||||
void applyImportSettings();
|
||||
void sendAircraftReport();
|
||||
void updatePosition(float latitude, float longitude, float altitude);
|
||||
void clearOldHeading(Aircraft *aircraft, const QDateTime& dateTime, float newTrack);
|
||||
void updateQNH(const Aircraft *aircraft, float qnh);
|
||||
void setCallsign(Aircraft *aircraft, const QString& callsign);
|
||||
|
||||
void initCoverageMap();
|
||||
void clearCoverageMap();
|
||||
void updateCoverageMap(float azimuth, float elevation, float distance, float altitude);
|
||||
|
||||
void leaveEvent(QEvent*);
|
||||
void enterEvent(EnterEventType*);
|
||||
|
||||
void updateDFStats(int df);
|
||||
bool updateTCStats(int tc, int row, int low, int high);
|
||||
void resetStats();
|
||||
void plotChart();
|
||||
int countActiveAircraft();
|
||||
void averageSeries(QLineSeries *series, const QDateTime& startTime, const QDateTime& endTime);
|
||||
void legendMarkerClicked();
|
||||
|
||||
private slots:
|
||||
void on_deltaFrequency_changed(qint64 value);
|
||||
void on_rfBW_valueChanged(int value);
|
||||
void on_threshold_valueChanged(int value);
|
||||
void on_chipsThreshold_valueChanged(int value);
|
||||
void on_phaseSteps_valueChanged(int value);
|
||||
void on_tapsPerPhase_valueChanged(int value);
|
||||
void statsTable_customContextMenuRequested(QPoint pos);
|
||||
void adsbData_customContextMenuRequested(QPoint point);
|
||||
void on_adsbData_cellClicked(int row, int column);
|
||||
void on_adsbData_cellDoubleClicked(int row, int column);
|
||||
@ -1063,18 +1454,24 @@ private slots:
|
||||
void columnSelectMenu(QPoint pos);
|
||||
void columnSelectMenuChecked(bool checked = false);
|
||||
void on_spb_currentIndexChanged(int value);
|
||||
void on_correlateFullPreamble_clicked(bool checked);
|
||||
void on_demodModeS_clicked(bool checked);
|
||||
void on_feed_clicked(bool checked);
|
||||
void on_notifications_clicked();
|
||||
void on_flightInfo_clicked();
|
||||
void on_findOnMapFeature_clicked();
|
||||
void on_getOSNDB_clicked();
|
||||
void on_deleteAircraft_clicked();
|
||||
void on_getAircraftDB_clicked();
|
||||
void on_getAirportDB_clicked();
|
||||
void on_getAirspacesDB_clicked();
|
||||
void on_coverage_clicked(bool checked);
|
||||
void on_displayChart_clicked(bool checked);
|
||||
void on_stats_clicked(bool checked);
|
||||
void on_flightPaths_clicked(bool checked);
|
||||
void on_allFlightPaths_clicked(bool checked);
|
||||
void on_atcLabels_clicked(bool checked);
|
||||
void on_displayOrientation_clicked(bool checked);
|
||||
void on_displayRadius_clicked(bool checked);
|
||||
void on_ic_globalCheckStateChanged(int state);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void onMenuDialogCalled(const QPoint& p);
|
||||
void handleInputMessages();
|
||||
@ -1101,6 +1498,12 @@ private slots:
|
||||
void devicePositionChanged(float latitude, float longitude, float altitude);
|
||||
void requestMetar(const QString& icao);
|
||||
void weatherUpdated(const AviationWeather::METAR &metar);
|
||||
void on_manualQNH_clicked(bool checked);
|
||||
void on_qnh_valueChanged(int value);
|
||||
void clearCoverage(const QPoint& p);
|
||||
void clearStats(const QPoint& p);
|
||||
void clearChart(const QPoint& p);
|
||||
void resetChartAxes();
|
||||
|
||||
signals:
|
||||
void homePositionChanged();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@
|
||||
|
||||
// Map main ADS-B table column numbers to combo box indices
|
||||
std::vector<int> ADSBDemodNotificationDialog::m_columnMap = {
|
||||
ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL,
|
||||
ADSB_COL_ICAO, ADSB_COL_CALLSIGN, ADSB_COL_MODEL, ADSB_COL_TYPE,
|
||||
ADSB_COL_ALTITUDE, ADSB_COL_GROUND_SPEED, ADSB_COL_RANGE,
|
||||
ADSB_COL_CATEGORY, ADSB_COL_STATUS, ADSB_COL_SQUAWK,
|
||||
ADSB_COL_REGISTRATION, ADSB_COL_MANUFACTURER, ADSB_COL_OWNER, ADSB_COL_OPERATOR_ICAO
|
||||
@ -117,6 +117,7 @@ void ADSBDemodNotificationDialog::addRow(ADSBDemodSettings::NotificationSettings
|
||||
match->addItem("ICAO ID");
|
||||
match->addItem("Callsign");
|
||||
match->addItem("Aircraft");
|
||||
match->addItem("Type");
|
||||
match->addItem("Alt (ft)");
|
||||
match->addItem("GS (kn)");
|
||||
match->addItem("D (km)");
|
||||
|
@ -36,7 +36,8 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
{
|
||||
m_inputFrequencyOffset = 0;
|
||||
m_rfBandwidth = 2*1300000;
|
||||
m_correlationThreshold = 10.0f; //<! ones/zero powers correlation threshold in dB
|
||||
m_correlationThreshold = 7.0f; //<! ones/zero powers correlation threshold in dB
|
||||
m_chipsThreshold = 0;
|
||||
m_samplesPerBit = 4;
|
||||
m_removeTimeout = 60;
|
||||
m_feedEnabled = false;
|
||||
@ -73,7 +74,6 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
m_tableFontName = "Liberation Sans";
|
||||
m_tableFontSize = 9;
|
||||
m_displayDemodStats = false;
|
||||
m_correlateFullPreamble = true;
|
||||
m_demodModeS = true;
|
||||
m_autoResizeTableColumns = false;
|
||||
m_interpolatorPhaseSteps = 4; // Higher than these two values will struggle to run in real-time
|
||||
@ -98,13 +98,24 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
m_displayNavAids = true;
|
||||
m_displayPhotos = true;
|
||||
m_verboseModelMatching = false;
|
||||
m_airfieldElevation = 0;
|
||||
m_aircraftMinZoom = 11;
|
||||
m_workspaceIndex = 0;
|
||||
m_hidden = false;
|
||||
m_atcLabels = true;
|
||||
m_atcCallsigns = true;
|
||||
m_transitionAlt = 6000; // Depends on airport. 18,000 in USA
|
||||
m_qnh = 1013.25;
|
||||
m_manualQNH = false;
|
||||
m_displayCoverage = false;
|
||||
m_displayChart = false;
|
||||
m_displayOrientation = false;
|
||||
m_displayRadius = false;
|
||||
for (int i = 0; i < ADSB_IC_MAX; i++) {
|
||||
m_displayIC[i] = false;
|
||||
}
|
||||
m_flightPathPaletteName = "Spectral";
|
||||
applyPalette();
|
||||
m_favourLivery = true;
|
||||
}
|
||||
|
||||
QByteArray ADSBDemodSettings::serialize() const
|
||||
@ -141,7 +152,6 @@ QByteArray ADSBDemodSettings::serialize() const
|
||||
s.writeString(25, m_tableFontName);
|
||||
s.writeS32(26, m_tableFontSize);
|
||||
s.writeBool(27, m_displayDemodStats);
|
||||
s.writeBool(28, m_correlateFullPreamble);
|
||||
s.writeBool(29, m_demodModeS);
|
||||
s.writeBool(30, m_autoResizeTableColumns);
|
||||
s.writeS32(31, m_interpolatorPhaseSteps);
|
||||
@ -165,7 +175,6 @@ QByteArray ADSBDemodSettings::serialize() const
|
||||
}
|
||||
|
||||
s.writeBool(44, m_verboseModelMatching);
|
||||
s.writeS32(45, m_airfieldElevation);
|
||||
|
||||
s.writeBool(46, m_exportClientEnabled);
|
||||
s.writeBool(47, m_exportServerEnabled);
|
||||
@ -192,6 +201,17 @@ QByteArray ADSBDemodSettings::serialize() const
|
||||
s.writeS32(67, m_transitionAlt);
|
||||
s.writeString(68, m_amDemod);
|
||||
|
||||
s.writeFloat(69, m_qnh);
|
||||
s.writeBool(70, m_manualQNH);
|
||||
|
||||
s.writeBool(71, m_displayCoverage);
|
||||
s.writeBool(72, m_displayChart);
|
||||
s.writeBool(73, m_displayOrientation);
|
||||
s.writeBool(74, m_displayRadius);
|
||||
s.writeString(75, m_flightPathPaletteName);
|
||||
s.writeS32(76, m_chipsThreshold);
|
||||
s.writeBool(77, m_favourLivery);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++) {
|
||||
s.writeS32(100 + i, m_columnIndexes[i]);
|
||||
}
|
||||
@ -229,7 +249,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
d.readS32(1, &tmp, 0);
|
||||
m_inputFrequencyOffset = tmp;
|
||||
d.readReal(2, &m_rfBandwidth, 2*1300000);
|
||||
d.readReal(3, &m_correlationThreshold, 0.0f);
|
||||
d.readReal(3, &m_correlationThreshold, 7.0f);
|
||||
d.readS32(4, &m_samplesPerBit, 4);
|
||||
d.readS32(5, &m_removeTimeout, 60);
|
||||
d.readBool(6, &m_feedEnabled, false);
|
||||
@ -268,7 +288,6 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
d.readString(25, &m_tableFontName, "Liberation Sans");
|
||||
d.readS32(26, &m_tableFontSize, 9);
|
||||
d.readBool(27, &m_displayDemodStats, false);
|
||||
d.readBool(28, &m_correlateFullPreamble, true);
|
||||
d.readBool(29, &m_demodModeS, true);
|
||||
d.readBool(30, &m_autoResizeTableColumns, false);
|
||||
d.readS32(31, &m_interpolatorPhaseSteps, 4);
|
||||
@ -296,7 +315,6 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
}
|
||||
|
||||
d.readBool(44, &m_verboseModelMatching, false);
|
||||
d.readS32(45, &m_airfieldElevation, 0);
|
||||
|
||||
d.readBool(46, &m_exportClientEnabled, true);
|
||||
d.readBool(47, &m_exportServerEnabled, true);
|
||||
@ -328,6 +346,19 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
d.readS32(67, &m_transitionAlt, 6000);
|
||||
d.readString(68, &m_amDemod);
|
||||
|
||||
d.readFloat(69, &m_qnh, 1013.25);
|
||||
d.readBool(70, &m_manualQNH, false);
|
||||
|
||||
d.readBool(71, &m_displayCoverage, false);
|
||||
d.readBool(72, &m_displayChart, false);
|
||||
d.readBool(73, &m_displayOrientation, false);
|
||||
d.readBool(74, &m_displayRadius, false);
|
||||
d.readString(75, &m_flightPathPaletteName, "Spectral");
|
||||
d.readS32(76, &m_chipsThreshold, 0);
|
||||
d.readBool(77, &m_favourLivery, true);
|
||||
|
||||
applyPalette();
|
||||
|
||||
#ifdef LINUX
|
||||
if (m_mapProvider == "osm") {
|
||||
m_mapProvider = "mapboxgl";
|
||||
@ -413,6 +444,9 @@ void ADSBDemodSettings::applySettings(const QStringList& settingsKeys, const ADS
|
||||
if (settingsKeys.contains("correlationThreshold")) {
|
||||
m_correlationThreshold = settings.m_correlationThreshold;
|
||||
}
|
||||
if (settingsKeys.contains("chipsThreshold")) {
|
||||
m_chipsThreshold = settings.m_chipsThreshold;
|
||||
}
|
||||
if (settingsKeys.contains("samplesPerBit")) {
|
||||
m_samplesPerBit = settings.m_samplesPerBit;
|
||||
}
|
||||
@ -521,9 +555,6 @@ void ADSBDemodSettings::applySettings(const QStringList& settingsKeys, const ADS
|
||||
if (settingsKeys.contains("displayDemodStats")) {
|
||||
m_displayDemodStats = settings.m_displayDemodStats;
|
||||
}
|
||||
if (settingsKeys.contains("correlateFullPreamble")) {
|
||||
m_correlateFullPreamble = settings.m_correlateFullPreamble;
|
||||
}
|
||||
if (settingsKeys.contains("demodModeS")) {
|
||||
m_demodModeS = settings.m_demodModeS;
|
||||
}
|
||||
@ -575,9 +606,6 @@ void ADSBDemodSettings::applySettings(const QStringList& settingsKeys, const ADS
|
||||
if (settingsKeys.contains("verboseModelMatching")) {
|
||||
m_verboseModelMatching = settings.m_verboseModelMatching;
|
||||
}
|
||||
if (settingsKeys.contains("airfieldElevation")) {
|
||||
m_airfieldElevation = settings.m_airfieldElevation;
|
||||
}
|
||||
if (settingsKeys.contains("aircraftMinZoom")) {
|
||||
m_aircraftMinZoom = settings.m_aircraftMinZoom;
|
||||
}
|
||||
@ -590,6 +618,32 @@ void ADSBDemodSettings::applySettings(const QStringList& settingsKeys, const ADS
|
||||
if (settingsKeys.contains("transitionAlt")) {
|
||||
m_transitionAlt = settings.m_transitionAlt;
|
||||
}
|
||||
if (settingsKeys.contains("qnh")) {
|
||||
m_qnh = settings.m_qnh;
|
||||
}
|
||||
if (settingsKeys.contains("manualQNH")) {
|
||||
m_manualQNH = settings.m_manualQNH;
|
||||
}
|
||||
if (settingsKeys.contains("displayCoverage")) {
|
||||
m_displayCoverage = settings.m_displayCoverage;
|
||||
}
|
||||
if (settingsKeys.contains("displayChart")) {
|
||||
m_displayChart = settings.m_displayChart;
|
||||
}
|
||||
if (settingsKeys.contains("displayOrientation")) {
|
||||
m_displayOrientation = settings.m_displayOrientation;
|
||||
}
|
||||
if (settingsKeys.contains("displayRadius")) {
|
||||
m_displayRadius = settings.m_displayRadius;
|
||||
}
|
||||
if (settingsKeys.contains("flightPathPaletteName"))
|
||||
{
|
||||
m_flightPathPaletteName = settings.m_flightPathPaletteName;
|
||||
applyPalette();
|
||||
}
|
||||
if (settingsKeys.contains("favourLivery")) {
|
||||
m_favourLivery = settings.m_favourLivery;
|
||||
}
|
||||
}
|
||||
|
||||
QString ADSBDemodSettings::getDebugString(const QStringList& settingsKeys, bool force) const
|
||||
@ -605,6 +659,9 @@ QString ADSBDemodSettings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("correlationThreshold") || force) {
|
||||
ostr << " m_correlationThreshold: " << m_correlationThreshold;
|
||||
}
|
||||
if (settingsKeys.contains("chipsThreshold") || force) {
|
||||
ostr << " m_chipsThreshold: " << m_chipsThreshold;
|
||||
}
|
||||
if (settingsKeys.contains("samplesPerBit") || force) {
|
||||
ostr << " m_samplesPerBit: " << m_samplesPerBit;
|
||||
}
|
||||
@ -704,9 +761,6 @@ QString ADSBDemodSettings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("displayDemodStats") || force) {
|
||||
ostr << " m_displayDemodStats: " << m_displayDemodStats;
|
||||
}
|
||||
if (settingsKeys.contains("correlateFullPreamble") || force) {
|
||||
ostr << " m_correlateFullPreamble: " << m_correlateFullPreamble;
|
||||
}
|
||||
if (settingsKeys.contains("demodModeS") || force) {
|
||||
ostr << " m_demodModeS: " << m_demodModeS;
|
||||
}
|
||||
@ -755,9 +809,6 @@ QString ADSBDemodSettings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("verboseModelMatching") || force) {
|
||||
ostr << " m_verboseModelMatching: " << m_verboseModelMatching;
|
||||
}
|
||||
if (settingsKeys.contains("airfieldElevation") || force) {
|
||||
ostr << " m_airfieldElevation: " << m_airfieldElevation;
|
||||
}
|
||||
if (settingsKeys.contains("aircraftMinZoom") || force) {
|
||||
ostr << " m_aircraftMinZoom: " << m_aircraftMinZoom;
|
||||
}
|
||||
@ -770,6 +821,114 @@ QString ADSBDemodSettings::getDebugString(const QStringList& settingsKeys, bool
|
||||
if (settingsKeys.contains("transitionAlt") || force) {
|
||||
ostr << " m_transitionAlt: " << m_transitionAlt;
|
||||
}
|
||||
if (settingsKeys.contains("qnh") || force) {
|
||||
ostr << " m_qnh: " << m_qnh;
|
||||
}
|
||||
if (settingsKeys.contains("manualQNH") || force) {
|
||||
ostr << " m_manualQNH: " << m_manualQNH;
|
||||
}
|
||||
if (settingsKeys.contains("displayCoverage") || force) {
|
||||
ostr << " m_displayCoverage: " << m_displayCoverage;
|
||||
}
|
||||
if (settingsKeys.contains("displayChart") || force) {
|
||||
ostr << " m_displayChart: " << m_displayChart;
|
||||
}
|
||||
if (settingsKeys.contains("displayOrientation") || force) {
|
||||
ostr << " m_displayOrientation: " << m_displayOrientation;
|
||||
}
|
||||
if (settingsKeys.contains("displayRadius") || force) {
|
||||
ostr << " m_displayRadius: " << m_displayRadius;
|
||||
}
|
||||
if (settingsKeys.contains("flightPathPaletteName") || force) {
|
||||
ostr << " m_flightPathPaletteName: " << m_flightPathPaletteName.toStdString();
|
||||
}
|
||||
if (settingsKeys.contains("favourLivery") || force) {
|
||||
ostr << " m_favourLivery: " << m_favourLivery;
|
||||
}
|
||||
|
||||
return QString(ostr.str().c_str());
|
||||
}
|
||||
|
||||
void ADSBDemodSettings::applyPalette()
|
||||
{
|
||||
if (m_palettes.contains(m_flightPathPaletteName)) {
|
||||
m_flightPathPalette = m_palettes.value(m_flightPathPaletteName);
|
||||
} else {
|
||||
m_flightPathPalette = m_rainbowPalette;
|
||||
}
|
||||
}
|
||||
|
||||
const QVariant ADSBDemodSettings::m_rainbowPalette[8] = {
|
||||
QVariant(QColor::fromString("#ff0000")),
|
||||
QVariant(QColor::fromString("#ff7f00")),
|
||||
QVariant(QColor::fromString("#ffff00")),
|
||||
QVariant(QColor::fromString("#f7ff00")),
|
||||
QVariant(QColor::fromString("#00ff00")),
|
||||
QVariant(QColor::fromString("#00ff7f")),
|
||||
QVariant(QColor::fromString("#00ffff")),
|
||||
QVariant(QColor::fromString("#007fff")),
|
||||
};
|
||||
|
||||
const QVariant ADSBDemodSettings::m_pastelPalette[8] = {
|
||||
QVariant(QColor::fromString("#ffadad")),
|
||||
QVariant(QColor::fromString("#ffd6a5")),
|
||||
QVariant(QColor::fromString("#fdffb6")),
|
||||
QVariant(QColor::fromString("#caffbf")),
|
||||
QVariant(QColor::fromString("#9bf6ff")),
|
||||
QVariant(QColor::fromString("#a0c4ff")),
|
||||
QVariant(QColor::fromString("#bdb2ff")),
|
||||
QVariant(QColor::fromString("#ffc6ff")),
|
||||
};
|
||||
|
||||
const QVariant ADSBDemodSettings::m_spectralPalette[8] = {
|
||||
QVariant(QColor::fromString("#d53e4f")),
|
||||
QVariant(QColor::fromString("#f46d43")),
|
||||
QVariant(QColor::fromString("#fdae61")),
|
||||
QVariant(QColor::fromString("#fee08b")),
|
||||
QVariant(QColor::fromString("#e6f598")),
|
||||
QVariant(QColor::fromString("#abdda4")),
|
||||
QVariant(QColor::fromString("#66c2a5")),
|
||||
QVariant(QColor::fromString("#3288bd")),
|
||||
};
|
||||
|
||||
const QVariant ADSBDemodSettings::m_bluePalette[8] = {
|
||||
QVariant(QColor::fromString("#deebf7")),
|
||||
QVariant(QColor::fromString("#c6dbef")),
|
||||
QVariant(QColor::fromString("#9ecae1")),
|
||||
QVariant(QColor::fromString("#6baed6")),
|
||||
QVariant(QColor::fromString("#4292c6")),
|
||||
QVariant(QColor::fromString("#2171b5")),
|
||||
QVariant(QColor::fromString("#08519c")),
|
||||
QVariant(QColor::fromString("#08306b")),
|
||||
};
|
||||
|
||||
const QVariant ADSBDemodSettings::m_purplePalette[8] = {
|
||||
QVariant(QColor::fromString("#ccaff2")),
|
||||
QVariant(QColor::fromString("#b699e0")),
|
||||
QVariant(QColor::fromString("#a084cf")),
|
||||
QVariant(QColor::fromString("#8a6fbd")),
|
||||
QVariant(QColor::fromString("#765aac")),
|
||||
QVariant(QColor::fromString("#62459a")),
|
||||
QVariant(QColor::fromString("#4f3089")),
|
||||
QVariant(QColor::fromString("#3e1878")),
|
||||
};
|
||||
|
||||
const QVariant ADSBDemodSettings::m_greyPalette[8] = {
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
QVariant(QColor::fromString("#808080")),
|
||||
};
|
||||
|
||||
const QHash<QString, const QVariant *> ADSBDemodSettings::m_palettes = {
|
||||
{"Rainbow", &ADSBDemodSettings::m_rainbowPalette[0]},
|
||||
{"Pastel", &ADSBDemodSettings::m_pastelPalette[0]},
|
||||
{"Spectral", &ADSBDemodSettings::m_spectralPalette[0]},
|
||||
{"Blues", &ADSBDemodSettings::m_bluePalette[0]},
|
||||
{"Purples", &ADSBDemodSettings::m_purplePalette[0]},
|
||||
{"Grey", &ADSBDemodSettings::m_greyPalette[0]},
|
||||
};
|
@ -29,63 +29,89 @@
|
||||
class Serializable;
|
||||
|
||||
// Number of columns in the table
|
||||
#define ADSBDEMOD_COLUMNS 54
|
||||
#define ADSBDEMOD_COLUMNS 78
|
||||
|
||||
// ADS-B table columns
|
||||
#define ADSB_COL_ICAO 0
|
||||
#define ADSB_COL_CALLSIGN 1
|
||||
#define ADSB_COL_ATC_CALLSIGN 2
|
||||
#define ADSB_COL_MODEL 3
|
||||
#define ADSB_COL_AIRLINE 4
|
||||
#define ADSB_COL_COUNTRY 5
|
||||
#define ADSB_COL_GROUND_SPEED 6
|
||||
#define ADSB_COL_TRUE_AIRSPEED 7
|
||||
#define ADSB_COL_INDICATED_AIRSPEED 8
|
||||
#define ADSB_COL_MACH 9
|
||||
#define ADSB_COL_SEL_ALTITUDE 10
|
||||
#define ADSB_COL_ALTITUDE 11
|
||||
#define ADSB_COL_VERTICALRATE 12
|
||||
#define ADSB_COL_SEL_HEADING 13
|
||||
#define ADSB_COL_HEADING 14
|
||||
#define ADSB_COL_TURNRATE 15
|
||||
#define ADSB_COL_ROLL 16
|
||||
#define ADSB_COL_RANGE 17
|
||||
#define ADSB_COL_AZEL 18
|
||||
#define ADSB_COL_CATEGORY 19
|
||||
#define ADSB_COL_STATUS 20
|
||||
#define ADSB_COL_SQUAWK 21
|
||||
#define ADSB_COL_REGISTRATION 22
|
||||
#define ADSB_COL_REGISTERED 23
|
||||
#define ADSB_COL_MANUFACTURER 24
|
||||
#define ADSB_COL_OWNER 25
|
||||
#define ADSB_COL_OPERATOR_ICAO 26
|
||||
#define ADSB_COL_AP 27
|
||||
#define ADSB_COL_V_MODE 28
|
||||
#define ADSB_COL_L_MODE 29
|
||||
#define ADSB_COL_BARO 30
|
||||
#define ADSB_COL_HEADWIND 31
|
||||
#define ADSB_COL_EST_AIR_TEMP 32
|
||||
#define ADSB_COL_WIND_SPEED 33
|
||||
#define ADSB_COL_WIND_DIR 34
|
||||
#define ADSB_COL_STATIC_PRESSURE 35
|
||||
#define ADSB_COL_STATIC_AIR_TEMP 36
|
||||
#define ADSB_COL_HUMIDITY 37
|
||||
#define ADSB_COL_LATITUDE 38
|
||||
#define ADSB_COL_LONGITUDE 39
|
||||
#define ADSB_COL_TIME 40
|
||||
#define ADSB_COL_FRAMECOUNT 41
|
||||
#define ADSB_COL_TIS_B 42
|
||||
#define ADSB_COL_CORRELATION 43
|
||||
#define ADSB_COL_RSSI 44
|
||||
#define ADSB_COL_FLIGHT_STATUS 45
|
||||
#define ADSB_COL_DEP 46
|
||||
#define ADSB_COL_ARR 47
|
||||
#define ADSB_COL_STD 48
|
||||
#define ADSB_COL_ETD 49
|
||||
#define ADSB_COL_ATD 50
|
||||
#define ADSB_COL_STA 51
|
||||
#define ADSB_COL_ETA 52
|
||||
#define ADSB_COL_ATA 53
|
||||
#define ADSB_COL_TYPE 4
|
||||
#define ADSB_COL_SIDEVIEW 5
|
||||
#define ADSB_COL_AIRLINE 6
|
||||
#define ADSB_COL_COUNTRY 7
|
||||
#define ADSB_COL_GROUND_SPEED 8
|
||||
#define ADSB_COL_TRUE_AIRSPEED 9
|
||||
#define ADSB_COL_INDICATED_AIRSPEED 10
|
||||
#define ADSB_COL_MACH 11
|
||||
#define ADSB_COL_SEL_ALTITUDE 12
|
||||
#define ADSB_COL_ALTITUDE 13
|
||||
#define ADSB_COL_VERTICALRATE 14
|
||||
#define ADSB_COL_SEL_HEADING 15
|
||||
#define ADSB_COL_HEADING 16
|
||||
#define ADSB_COL_TRACK 17
|
||||
#define ADSB_COL_TURNRATE 18
|
||||
#define ADSB_COL_ROLL 19
|
||||
#define ADSB_COL_RANGE 20
|
||||
#define ADSB_COL_AZEL 21
|
||||
#define ADSB_COL_CATEGORY 22
|
||||
#define ADSB_COL_STATUS 23
|
||||
#define ADSB_COL_SQUAWK 24
|
||||
#define ADSB_COL_IDENT 25
|
||||
#define ADSB_COL_REGISTRATION 26
|
||||
#define ADSB_COL_REGISTERED 27
|
||||
#define ADSB_COL_MANUFACTURER 28
|
||||
#define ADSB_COL_OWNER 29
|
||||
#define ADSB_COL_OPERATOR_ICAO 30
|
||||
#define ADSB_COL_AP 31
|
||||
#define ADSB_COL_V_MODE 32
|
||||
#define ADSB_COL_L_MODE 33
|
||||
#define ADSB_COL_TCAS 34
|
||||
#define ADSB_COL_ACAS 35
|
||||
#define ADSB_COL_RA 36
|
||||
#define ADSB_COL_MAX_SPEED 37
|
||||
#define ADSB_COL_VERSION 38
|
||||
#define ADSB_COL_LENGTH 39
|
||||
#define ADSB_COL_WIDTH 40
|
||||
#define ADSB_COL_BARO 41
|
||||
#define ADSB_COL_HEADWIND 42
|
||||
#define ADSB_COL_EST_AIR_TEMP 43
|
||||
#define ADSB_COL_WIND_SPEED 44
|
||||
#define ADSB_COL_WIND_DIR 45
|
||||
#define ADSB_COL_STATIC_PRESSURE 46
|
||||
#define ADSB_COL_STATIC_AIR_TEMP 47
|
||||
#define ADSB_COL_HUMIDITY 48
|
||||
#define ADSB_COL_LATITUDE 49
|
||||
#define ADSB_COL_LONGITUDE 50
|
||||
#define ADSB_COL_IC 51
|
||||
#define ADSB_COL_TIME 52
|
||||
#define ADSB_COL_FRAMECOUNT 53
|
||||
#define ADSB_COL_ADSB_FRAMECOUNT 54
|
||||
#define ADSB_COL_MODES_FRAMECOUNT 55
|
||||
#define ADSB_COL_NON_TRANSPONDER 56
|
||||
#define ADSB_COL_TIS_B_FRAMECOUNT 57
|
||||
#define ADSB_COL_ADSR_FRAMECOUNT 58
|
||||
#define ADSB_COL_RADIUS 59
|
||||
#define ADSB_COL_NACP 60
|
||||
#define ADSB_COL_NACV 61
|
||||
#define ADSB_COL_GVA 62
|
||||
#define ADSB_COL_NIC 63
|
||||
#define ADSB_COL_NIC_BARO 64
|
||||
#define ADSB_COL_SIL 65
|
||||
#define ADSB_COL_CORRELATION 66
|
||||
#define ADSB_COL_RSSI 67
|
||||
#define ADSB_COL_FLIGHT_STATUS 68
|
||||
#define ADSB_COL_DEP 69
|
||||
#define ADSB_COL_ARR 70
|
||||
#define ADSB_COL_STOPS 71
|
||||
#define ADSB_COL_STD 72
|
||||
#define ADSB_COL_ETD 73
|
||||
#define ADSB_COL_ATD 74
|
||||
#define ADSB_COL_STA 75
|
||||
#define ADSB_COL_ETA 76
|
||||
#define ADSB_COL_ATA 77
|
||||
|
||||
#define ADSB_IC_MAX 63
|
||||
|
||||
struct ADSBDemodSettings
|
||||
{
|
||||
@ -104,6 +130,7 @@ struct ADSBDemodSettings
|
||||
int32_t m_inputFrequencyOffset;
|
||||
Real m_rfBandwidth;
|
||||
Real m_correlationThreshold; //!< Correlation power threshold in dB
|
||||
int m_chipsThreshold; //!< How many chips in preamble can be incorrect for Mode S
|
||||
int m_samplesPerBit;
|
||||
int m_removeTimeout; //!< Time in seconds before removing an aircraft, unless a new frame is received
|
||||
|
||||
@ -159,7 +186,6 @@ struct ADSBDemodSettings
|
||||
QString m_tableFontName; //!< Font to use for table
|
||||
int m_tableFontSize;
|
||||
bool m_displayDemodStats;
|
||||
bool m_correlateFullPreamble;
|
||||
bool m_demodModeS; //!< Demodulate all Mode-S frames, not just ADS-B
|
||||
QString m_amDemod; //!< AM Demod to tune to selected ATC frequency
|
||||
bool m_autoResizeTableColumns;
|
||||
@ -186,13 +212,24 @@ struct ADSBDemodSettings
|
||||
bool m_displayPhotos;
|
||||
Serializable *m_rollupState;
|
||||
bool m_verboseModelMatching;
|
||||
int m_airfieldElevation; //!< QFE in ft so aircraft takeoff/land from correct position
|
||||
int m_aircraftMinZoom;
|
||||
|
||||
bool m_atcLabels;
|
||||
bool m_atcCallsigns;
|
||||
int m_transitionAlt;
|
||||
|
||||
float m_qnh;
|
||||
bool m_manualQNH;
|
||||
bool m_displayCoverage;
|
||||
bool m_displayChart;
|
||||
bool m_displayOrientation;
|
||||
bool m_displayRadius;
|
||||
bool m_displayIC[ADSB_IC_MAX];
|
||||
QString m_flightPathPaletteName;
|
||||
bool m_favourLivery; //!< Favour airline livery over aircraft type when exact 3D model isn't available
|
||||
|
||||
const QVariant *m_flightPathPalette;
|
||||
|
||||
ADSBDemodSettings();
|
||||
void resetToDefaults();
|
||||
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
|
||||
@ -203,6 +240,16 @@ struct ADSBDemodSettings
|
||||
void deserializeNotificationSettings(const QByteArray& data, QList<NotificationSettings *>& notificationSettings);
|
||||
void applySettings(const QStringList& settingsKeys, const ADSBDemodSettings& settings);
|
||||
QString getDebugString(const QStringList& settingsKeys, bool force = false) const;
|
||||
void applyPalette();
|
||||
|
||||
static const QVariant m_rainbowPalette[8];
|
||||
static const QVariant m_pastelPalette[8];
|
||||
static const QVariant m_spectralPalette[8];
|
||||
static const QVariant m_bluePalette[8];
|
||||
static const QVariant m_purplePalette[8];
|
||||
static const QVariant m_greyPalette[8];
|
||||
|
||||
static const QHash<QString, const QVariant *> m_palettes;
|
||||
};
|
||||
|
||||
#endif /* PLUGINS_CHANNELRX_DEMODADSB_ADSBDEMODSETTINGS_H_ */
|
||||
|
@ -19,14 +19,16 @@
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
|
||||
#include "util/profiler.h"
|
||||
|
||||
#include "adsbdemodsink.h"
|
||||
#include "adsbdemodsinkworker.h"
|
||||
#include "adsbdemod.h"
|
||||
#include "adsb.h"
|
||||
|
||||
ADSBDemodSink::ADSBDemodSink() :
|
||||
m_channelSampleRate(6000000),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_feedTime(0.0),
|
||||
m_sampleBuffer{nullptr, nullptr, nullptr},
|
||||
m_worker(this),
|
||||
m_writeBuffer(0),
|
||||
@ -53,7 +55,7 @@ ADSBDemodSink::~ADSBDemodSink()
|
||||
void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
// Start timing how long we are in this function
|
||||
m_startPoint = boost::chrono::steady_clock::now();
|
||||
PROFILER_START();
|
||||
|
||||
// Optimise for common case, where no resampling or frequency offset
|
||||
if ((m_interpolatorDistance == 1.0f) && (m_channelFrequencyOffset == 0))
|
||||
@ -113,8 +115,7 @@ void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const Sample
|
||||
}
|
||||
|
||||
// Calculate number of seconds in this function
|
||||
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_startPoint;
|
||||
m_feedTime += sec.count();
|
||||
PROFILER_STOP("ADSB feed");
|
||||
}
|
||||
|
||||
void ADSBDemodSink::processOneSample(Real magsq)
|
||||
@ -127,8 +128,17 @@ void ADSBDemodSink::processOneSample(Real magsq)
|
||||
m_writeIdx++;
|
||||
if (!m_bufferDateTimeValid[m_writeBuffer])
|
||||
{
|
||||
m_bufferFirstSampleDateTime[m_writeBuffer] = QDateTime::currentDateTime();
|
||||
QDateTime dateTime = QDateTime::currentDateTime();
|
||||
if (m_minFirstSampleDateTime.isValid() && (dateTime < m_minFirstSampleDateTime)) {
|
||||
dateTime = m_minFirstSampleDateTime;
|
||||
}
|
||||
m_bufferFirstSampleDateTime[m_writeBuffer] = dateTime;
|
||||
m_bufferDateTimeValid[m_writeBuffer] = true;
|
||||
|
||||
// Make sure timestamps from different buffers are in order, even if we receive samples faster than real time
|
||||
const qint64 samplesPerSecondMSec = ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit / 1000;
|
||||
const qint64 offsetMSec = m_bufferSize / samplesPerSecondMSec;
|
||||
m_minFirstSampleDateTime = dateTime.addMSecs(offsetMSec);
|
||||
}
|
||||
if (m_writeIdx >= m_bufferSize)
|
||||
{
|
||||
@ -138,15 +148,9 @@ void ADSBDemodSink::processOneSample(Real magsq)
|
||||
if (m_writeBuffer >= m_buffers)
|
||||
m_writeBuffer = 0;
|
||||
|
||||
// Don't include time spent waiting for a buffer
|
||||
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_startPoint;
|
||||
m_feedTime += sec.count();
|
||||
|
||||
if (m_worker.isRunning())
|
||||
m_bufferWrite[m_writeBuffer].acquire();
|
||||
|
||||
m_startPoint = boost::chrono::steady_clock::now();
|
||||
|
||||
m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer
|
||||
|
||||
m_bufferDateTimeValid[m_writeBuffer] = false;
|
||||
@ -253,7 +257,6 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, const QStri
|
||||
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
|
||||
<< " m_rfBandwidth: " << settings.m_rfBandwidth
|
||||
<< " m_correlationThreshold: " << settings.m_correlationThreshold
|
||||
<< " m_correlateFullPreamble: " << settings.m_correlateFullPreamble
|
||||
<< " m_demodModeS: " << settings.m_demodModeS
|
||||
<< " m_samplesPerBit: " << settings.m_samplesPerBit
|
||||
<< " force: " << force;
|
||||
@ -285,3 +288,9 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, const QStri
|
||||
m_settings.applySettings(settingsKeys, settings);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodSink::resetStats()
|
||||
{
|
||||
ADSBDemod::MsgResetStats* msg = ADSBDemod::MsgResetStats::create();
|
||||
m_worker.getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
@ -19,9 +19,6 @@
|
||||
#ifndef INCLUDE_ADSBDEMODSINK_H
|
||||
#define INCLUDE_ADSBDEMODSINK_H
|
||||
|
||||
#define BOOST_CHRONO_HEADER_ONLY
|
||||
#include <boost/chrono/chrono.hpp>
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
@ -60,6 +57,7 @@ public:
|
||||
void setMessageQueueToWorker(MessageQueue *messageQueue) { m_messageQueueToWorker = messageQueue; }
|
||||
void startWorker();
|
||||
void stopWorker();
|
||||
void resetStats();
|
||||
|
||||
private:
|
||||
friend ADSBDemodSinkWorker;
|
||||
@ -83,9 +81,6 @@ private:
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
|
||||
boost::chrono::steady_clock::time_point m_startPoint;
|
||||
double m_feedTime; //!< Time spent in feed()
|
||||
|
||||
// Triple buffering for sharing sample data between two threads
|
||||
// Top area of each buffer is not used by writer, as it's used by the reader
|
||||
// for copying the last few samples of the previous buffer, so it can
|
||||
@ -96,6 +91,7 @@ private:
|
||||
QSemaphore m_bufferWrite[3]; //!< Semaphore to control write access to the buffers
|
||||
QSemaphore m_bufferRead[3]; //!< Semaphore to control read access from the buffers
|
||||
QDateTime m_bufferFirstSampleDateTime[3]; //!< Time for first sample in the buffer
|
||||
QDateTime m_minFirstSampleDateTime;
|
||||
bool m_bufferDateTimeValid[3];
|
||||
ADSBDemodSinkWorker m_worker; //!< Worker thread that does the actual demodulation
|
||||
int m_writeBuffer; //!< Which of the 3 buffers we're writing in to
|
||||
|
@ -15,29 +15,292 @@
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#define BOOST_CHRONO_HEADER_ONLY
|
||||
#include <boost/chrono/chrono.hpp>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "util/db.h"
|
||||
#include "util/profiler.h"
|
||||
#include "util/units.h"
|
||||
#include "util/osndb.h"
|
||||
#include "util/popcount.h"
|
||||
|
||||
#include "adsbdemodreport.h"
|
||||
#include "adsbdemodsink.h"
|
||||
#include "adsbdemodsinkworker.h"
|
||||
#include "adsbdemod.h"
|
||||
#include "adsb.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker, Message)
|
||||
|
||||
const Real ADSBDemodSinkWorker::m_correlationScale = 3.0f;
|
||||
|
||||
static int grayToBinary(int gray, int bits)
|
||||
{
|
||||
int binary = 0;
|
||||
for (int i = bits - 1; i >= 0; i--) {
|
||||
binary = binary | ((((1 << (i+1)) & binary) >> 1) ^ ((1 << i) & gray));
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
|
||||
static int gillhamToFeet(int n)
|
||||
{
|
||||
int c1 = (n >> 10) & 1;
|
||||
int a1 = (n >> 9) & 1;
|
||||
int c2 = (n >> 8) & 1;
|
||||
int a2 = (n >> 7) & 1;
|
||||
int c4 = (n >> 6) & 1;
|
||||
int a4 = (n >> 5) & 1;
|
||||
int b1 = (n >> 4) & 1;
|
||||
int b2 = (n >> 3) & 1;
|
||||
int d2 = (n >> 2) & 1;
|
||||
int b4 = (n >> 1) & 1;
|
||||
int d4 = n & 1;
|
||||
|
||||
int n500 = grayToBinary((d2 << 7) | (d4 << 6) | (a1 << 5) | (a2 << 4) | (a4 << 3) | (b1 << 2) | (b2 << 1) | b4, 4);
|
||||
int n100 = grayToBinary((c1 << 2) | (c2 << 1) | c4, 3) - 1;
|
||||
|
||||
if (n100 == 6) {
|
||||
n100 = 4;
|
||||
}
|
||||
if (n500 %2 != 0) {
|
||||
n100 = 4 - n100;
|
||||
}
|
||||
|
||||
return -1200 + n500*500 + n100*100;
|
||||
}
|
||||
|
||||
static int decodeModeSAltitude(const QByteArray& data)
|
||||
{
|
||||
int altitude = 0; // Altitude in feet
|
||||
int altitudeCode = ((data[2] & 0x1f) << 8) | (data[3] & 0xff);
|
||||
|
||||
if (altitudeCode & 0x40) // M bit indicates metres
|
||||
{
|
||||
int altitudeMetres = ((altitudeCode & 0x1f80) >> 1) | (altitudeCode & 0x3f);
|
||||
altitude = Units::metresToFeet(altitudeMetres);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove M and Q bits
|
||||
int altitudeFix = ((altitudeCode & 0x1f80) >> 2) | ((altitudeCode & 0x20) >> 1) | (altitudeCode & 0xf);
|
||||
|
||||
// Convert to feet
|
||||
if (altitudeCode & 0x10) {
|
||||
altitude = altitudeFix * 25 - 1000;
|
||||
} else {
|
||||
altitude = gillhamToFeet(altitudeFix);
|
||||
}
|
||||
}
|
||||
return altitude;
|
||||
}
|
||||
|
||||
void ADSBDemodSinkWorker::handleModeS(unsigned char *data, int bytes, unsigned icao, int df, int firstIndex, unsigned short preamble, Real preambleCorrelation, Real correlationOnes, const QDateTime& dateTime, unsigned crc)
|
||||
{
|
||||
// Ignore downlink formats we can't decode / unlikely
|
||||
if ((df != 19) && (df != 22) && (df < 24))
|
||||
{
|
||||
QList<RXRecord> l;
|
||||
|
||||
if (m_modeSOnlyIcaos.contains(icao))
|
||||
{
|
||||
l = m_modeSOnlyIcaos.value(icao);
|
||||
if (abs(l.last().m_firstIndex - firstIndex) < 4) {
|
||||
return; // Duplicate
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stop hash table from getting too big - clear every 10 seconds or so
|
||||
QDateTime currentDateTime = QDateTime::currentDateTime();
|
||||
if (m_lastClear.secsTo(currentDateTime) >= 10)
|
||||
{
|
||||
//qDebug() << "Clearing ModeS only hash. size=" << m_modeSOnlyIcaos.size();
|
||||
m_modeSOnlyIcaos.clear();
|
||||
m_lastClear = currentDateTime;
|
||||
}
|
||||
}
|
||||
|
||||
RXRecord rx;
|
||||
rx.m_data = QByteArray((char*)data, bytes);
|
||||
rx.m_firstIndex = firstIndex;
|
||||
rx.m_preamble = preamble;
|
||||
rx.m_preambleCorrelation = preambleCorrelation;
|
||||
rx.m_correlationOnes = correlationOnes;
|
||||
rx.m_dateTime = dateTime;
|
||||
rx.m_crc = crc;
|
||||
l.append(rx);
|
||||
m_modeSOnlyIcaos.insert(icao, l);
|
||||
|
||||
// Have we heard from the same address several times in the last 10 seconds?
|
||||
if (l.size() >= 5)
|
||||
{
|
||||
// Check all frames have consistent altitudes and identifiers
|
||||
bool idConsistent = true;
|
||||
bool altitudeConsistent = true;
|
||||
int altitude = -1;
|
||||
int id = -1;
|
||||
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
{
|
||||
int df2 = ((l[i].m_data[0] >> 3) & ADS_B_DF_MASK);
|
||||
Real corr = CalcDb::dbPower(m_correlationScale * l[i].m_preambleCorrelation);
|
||||
|
||||
int curAltitude = -1;
|
||||
int curId = -1;
|
||||
|
||||
if ((df2 == 0) || (df2 == 4) || (df2 == 16) || (df2 == 20))
|
||||
{
|
||||
int curAltitude = decodeModeSAltitude(l[i].m_data);
|
||||
if (altitude == -1)
|
||||
{
|
||||
altitude = curAltitude;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (abs(curAltitude - altitude) > 1000) {
|
||||
altitudeConsistent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((df2 == 5) || (df2 == 21))
|
||||
{
|
||||
int curId = ((data[2] & 0x1f) << 8) | (data[3] & 0xff); // No decode - we just want to know if it changes
|
||||
|
||||
if (id == -1)
|
||||
{
|
||||
id = curId;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (id != curId) {
|
||||
idConsistent = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We could optionally check to see if aircraft ICAO is in db, but the db isn't complete
|
||||
|
||||
if (altitudeConsistent && idConsistent)
|
||||
{
|
||||
// Forward all frames
|
||||
for (int i = 0; i < l.size(); i++) {
|
||||
forwardFrame((const unsigned char *) l[i].m_data.data(), l[i].m_data.size(), l[i].m_preambleCorrelation, l[i].m_correlationOnes, l[i].m_dateTime, l[i].m_crc);
|
||||
}
|
||||
|
||||
m_icaos.insert(icao, l.back().m_dateTime.toMSecsSinceEpoch());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_modeSOnlyIcaos.remove(icao);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a Mode S frame has reserved bits set or reserved field values
|
||||
// Refer: Annex 10 Volume IV
|
||||
bool ADSBDemodSinkWorker::modeSValid(unsigned char *data, unsigned df)
|
||||
{
|
||||
bool invalid = false;
|
||||
|
||||
if (df == 0)
|
||||
{
|
||||
invalid = ((data[0] & 0x1) | (data[1] & 0x18) | (data[2] & 0x60)) != 0;
|
||||
}
|
||||
else if ((df == 4) || (df == 20))
|
||||
{
|
||||
unsigned fs = data[0] & 0x3;
|
||||
unsigned dr = (data[1] >> 3) & 0x1f;
|
||||
|
||||
invalid = (fs == 6) || (fs == 7) || ((dr >= 8) && (dr <= 15));
|
||||
}
|
||||
else if ((df == 5) || (df == 21))
|
||||
{
|
||||
unsigned fs = data[0] & 0x3;
|
||||
unsigned dr = (data[1] >> 3) & 0x1f;
|
||||
|
||||
invalid = (fs == 6) || (fs == 7) || ((dr >= 8) && (dr <= 15)) || ((data[3] & 0x40) != 0);
|
||||
}
|
||||
else if (df == 11)
|
||||
{
|
||||
unsigned ca = data[0] & 0x7;
|
||||
|
||||
invalid = ((ca >= 1) && (ca <= 3));
|
||||
}
|
||||
else if (df == 16)
|
||||
{
|
||||
invalid = ((data[0] & 0x3) | (data[1] & 0x18) | (data[2] & 0x60)) != 0;
|
||||
}
|
||||
|
||||
return invalid;
|
||||
}
|
||||
|
||||
// Check if valid ICAO address - i.e. not a reserved address - see Table 9-1 in Annex X Volume III
|
||||
bool ADSBDemodSinkWorker::icaoValid(unsigned icao)
|
||||
{
|
||||
unsigned msn = (icao >> 20) & 0xf;
|
||||
|
||||
return (icao != 0) && (msn != 0xf) && (msn != 0xb) && (msn != 0xd);
|
||||
}
|
||||
|
||||
// Is it less than a minute since the last received frame for this ICAO
|
||||
bool ADSBDemodSinkWorker::icaoHeardRecently(unsigned icao, const QDateTime &dateTime)
|
||||
{
|
||||
const int timeLimitMSec = 60*1000;
|
||||
|
||||
if (m_icaos.contains(icao))
|
||||
{
|
||||
if ((dateTime.toMSecsSinceEpoch() - m_icaos.value(icao)) < timeLimitMSec) {
|
||||
return true;
|
||||
} else {
|
||||
m_icaos.remove(icao);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ADSBDemodSinkWorker::forwardFrame(const unsigned char *data, int size, float preambleCorrelation, float correlationOnes, const QDateTime& dateTime, unsigned crc)
|
||||
{
|
||||
// Pass to GUI
|
||||
if (m_sink->getMessageQueueToGUI())
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, size),
|
||||
preambleCorrelation,
|
||||
correlationOnes,
|
||||
dateTime,
|
||||
crc);
|
||||
m_sink->getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
// Pass to worker to feed to other servers
|
||||
if (m_sink->getMessageQueueToWorker())
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, size),
|
||||
preambleCorrelation,
|
||||
correlationOnes,
|
||||
dateTime,
|
||||
crc);
|
||||
m_sink->getMessageQueueToWorker()->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void ADSBDemodSinkWorker::run()
|
||||
{
|
||||
int readBuffer = 0;
|
||||
m_lastClear = QDateTime::currentDateTime();
|
||||
|
||||
// Acquire first buffer
|
||||
m_sink->m_bufferRead[readBuffer].acquire();
|
||||
|
||||
if (isInterruptionRequested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Start recording how much time is spent processing in this method
|
||||
boost::chrono::steady_clock::time_point startPoint = boost::chrono::steady_clock::now();
|
||||
PROFILER_START();
|
||||
|
||||
// Check for updated settings
|
||||
handleInputMessages();
|
||||
@ -51,8 +314,6 @@ void ADSBDemodSinkWorker::run()
|
||||
<< " samplesPerFrame: " << samplesPerFrame
|
||||
<< " samplesPerChip: " << samplesPerChip
|
||||
<< " samplesPerBit: " << samplesPerBit
|
||||
<< " correlateFullPreamble: " << m_settings.m_correlateFullPreamble
|
||||
<< " correlationScale: " << m_correlationScale
|
||||
<< " correlationThreshold: " << m_settings.m_correlationThreshold;
|
||||
|
||||
int readIdx = m_sink->m_samplesPerFrame - 1;
|
||||
@ -63,61 +324,33 @@ void ADSBDemodSinkWorker::run()
|
||||
|
||||
// Correlate received signal with expected preamble
|
||||
// chip+ indexes are 0, 2, 7, 9
|
||||
// correlating over first 6 bits gives a reduction in per-sample
|
||||
// processing, but more than doubles the number of false matches
|
||||
Real preambleCorrelationOnes = 0.0;
|
||||
Real preambleCorrelationZeros = 0.0;
|
||||
if (m_settings.m_correlateFullPreamble)
|
||||
for (int i = 0; i < samplesPerChip; i++)
|
||||
{
|
||||
for (int i = 0; i < samplesPerChip; i++)
|
||||
{
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 12*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 13*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 12*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 13*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 14*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 15*samplesPerChip + i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < samplesPerChip; i++)
|
||||
{
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i];
|
||||
preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i];
|
||||
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i];
|
||||
}
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 14*samplesPerChip + i];
|
||||
preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 15*samplesPerChip + i];
|
||||
}
|
||||
|
||||
// Use the ratio of ones power over zeros power, as we don't care how powerful the signal
|
||||
@ -138,6 +371,7 @@ void ADSBDemodSinkWorker::run()
|
||||
int firstIdx = startIdx;
|
||||
|
||||
m_demodStats.m_correlatorMatches++;
|
||||
|
||||
// Skip over preamble
|
||||
startIdx += samplesPerBit*ADS_B_PREAMBLE_BITS;
|
||||
|
||||
@ -147,6 +381,7 @@ void ADSBDemodSinkWorker::run()
|
||||
int currentBit;
|
||||
unsigned char currentByte = 0;
|
||||
int df;
|
||||
int firstIndex = 0;
|
||||
|
||||
for (int bit = 0; bit < ADS_B_ES_BITS; bit++)
|
||||
{
|
||||
@ -165,6 +400,9 @@ void ADSBDemodSinkWorker::run()
|
||||
currentByte |= currentBit << (7-(bit & 0x7));
|
||||
if ((bit & 0x7) == 0x7)
|
||||
{
|
||||
if (byteIdx == 0) {
|
||||
firstIndex = startIdx;
|
||||
}
|
||||
data[byteIdx++] = currentByte;
|
||||
currentByte = 0;
|
||||
// Don't try to demodulate any further, if this isn't an ADS-B frame
|
||||
@ -178,6 +416,10 @@ void ADSBDemodSinkWorker::run()
|
||||
}
|
||||
}
|
||||
|
||||
Real preambleCorrelationScaled = preambleCorrelation * m_correlationScale;
|
||||
Real correlationOnes = preambleCorrelationOnes / samplesPerChip;
|
||||
QDateTime dateTime = rxDateTime(firstIdx, readBuffer);
|
||||
|
||||
// Is ADS-B?
|
||||
df = ((data[0] >> 3) & ADS_B_DF_MASK);
|
||||
if ((df == 17) || (df == 18))
|
||||
@ -188,109 +430,177 @@ void ADSBDemodSinkWorker::run()
|
||||
m_crc.calculate(data, ADS_B_ES_BYTES-3);
|
||||
if (parity == m_crc.get())
|
||||
{
|
||||
// Got a valid frame
|
||||
m_demodStats.m_adsbFrames++;
|
||||
// Get 24-bit ICAO and save in hash of ICAOs that have been seen
|
||||
// Get 24-bit ICAO
|
||||
unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff);
|
||||
m_icaos.insert(icao, true);
|
||||
// Don't try to re-demodulate the same frame
|
||||
// We could possibly allow a partial overlap here
|
||||
readIdx += (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1;
|
||||
// Pass to GUI
|
||||
if (m_sink->getMessageQueueToGUI())
|
||||
|
||||
if (icaoValid(icao))
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, sizeof(data)),
|
||||
preambleCorrelation * m_correlationScale,
|
||||
preambleCorrelationOnes / samplesPerChip,
|
||||
rxDateTime(firstIdx, readBuffer),
|
||||
m_crc.get());
|
||||
m_sink->getMessageQueueToGUI()->push(msg);
|
||||
// Got a valid frame
|
||||
m_demodStats.m_adsbFrames++;
|
||||
// Save in hash of ICAOs that have been seen
|
||||
m_icaos.insert(icao, dateTime.toMSecsSinceEpoch());
|
||||
// Don't try to re-demodulate the same frame
|
||||
// We could possibly allow a partial overlap here
|
||||
readIdx += (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1;
|
||||
forwardFrame(data, sizeof(data), preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get());
|
||||
}
|
||||
// Pass to worker to feed to other servers
|
||||
if (m_sink->getMessageQueueToWorker())
|
||||
else
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, sizeof(data)),
|
||||
preambleCorrelation * m_correlationScale,
|
||||
preambleCorrelationOnes / samplesPerChip,
|
||||
rxDateTime(firstIdx, readBuffer),
|
||||
m_crc.get());
|
||||
m_sink->getMessageQueueToWorker()->push(msg);
|
||||
m_demodStats.m_icaoFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_crcFails++;
|
||||
}
|
||||
}
|
||||
else if (m_settings.m_demodModeS)
|
||||
{
|
||||
int bytes;
|
||||
// Decode premable, as correlation alone results in too many false positives for Mode S frames
|
||||
startIdx = readIdx;
|
||||
unsigned short preamble = 0;
|
||||
QVector<float> preambleChips(16);
|
||||
|
||||
// Determine number of bytes in frame depending on downlink format
|
||||
if ((df == 0) || (df == 4) || (df == 5) || (df == 11)) {
|
||||
bytes = 56/8;
|
||||
} else if ((df == 16) || (df == 20) || (df == 21) || (df >= 24)) {
|
||||
bytes = 112/8;
|
||||
} else {
|
||||
bytes = 0;
|
||||
}
|
||||
if (bytes > 0)
|
||||
for (int bit = 0; bit < ADS_B_PREAMBLE_BITS; bit++)
|
||||
{
|
||||
// Extract received parity
|
||||
int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1];
|
||||
// Calculate CRC on received frame
|
||||
m_crc.init();
|
||||
m_crc.calculate(data, bytes-3);
|
||||
int crc = m_crc.get();
|
||||
// DF4 / DF5 / DF20 / DF21 have ICAO address XORed in to parity.
|
||||
// Extract ICAO from parity and see if it matches an aircraft we've already
|
||||
// received an ADS-B frame from
|
||||
if ((df == 4) || (df == 5) || (df == 20) || (df == 21))
|
||||
preambleChips[bit*2] = 0.0f;
|
||||
preambleChips[bit*2+1] = 0.0f;
|
||||
for (int i = 0; i < samplesPerChip; i++)
|
||||
{
|
||||
unsigned icao = (parity ^ crc) & 0xffffff;
|
||||
if (m_icaos.contains(icao)) {
|
||||
crc ^= icao;
|
||||
}
|
||||
preambleChips[bit*2] += m_sink->m_sampleBuffer[readBuffer][startIdx+i];
|
||||
preambleChips[bit*2+1] += m_sink->m_sampleBuffer[readBuffer][startIdx+samplesPerChip+i];
|
||||
}
|
||||
// For DF11, the last 7 bits may have an address/interogration identifier (II)
|
||||
// XORed in, so we ignore those bits
|
||||
if ((parity == crc) || ((df == 11) && ((parity & 0xffff80) == (crc & 0xffff80))))
|
||||
|
||||
startIdx += samplesPerBit;
|
||||
}
|
||||
startIdx = firstIdx;
|
||||
|
||||
float onesAvg = (preambleChips[0] + preambleChips[2] + preambleChips[7] + preambleChips[9]) / 4.0f;
|
||||
float zerosAvg = (preambleChips[1] + preambleChips[3] + preambleChips[4] + preambleChips[5] + preambleChips[6] + preambleChips[8]
|
||||
+ preambleChips[10] + + preambleChips[11] + preambleChips[12] + preambleChips[13] + preambleChips[14] + preambleChips[15]) / 12.0f;
|
||||
float midPoint = zerosAvg + (onesAvg - zerosAvg) / 2.0f;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
unsigned chip = preambleChips[i] > midPoint;
|
||||
preamble |= chip << (15-i);
|
||||
}
|
||||
|
||||
// qDebug() << "Preamble" << preambleChips << "zerosAvg" << zerosAvg << "onesAvg" << onesAvg << "midPoint" << midPoint << "chips" << Qt::hex << preamble;
|
||||
|
||||
const unsigned short expectedPreamble = 0xa140;
|
||||
int preambleDifferences = popcount(expectedPreamble ^ preamble);
|
||||
|
||||
if (preambleDifferences <= m_settings.m_chipsThreshold)
|
||||
{
|
||||
int bytes;
|
||||
|
||||
// Determine number of bytes in frame depending on downlink format
|
||||
if ((df == 0) || (df == 4) || (df == 5) || (df == 11)) {
|
||||
bytes = 56/8;
|
||||
} else if ((df == 16) || (df == 19) || (df == 20) || (df == 21) || (df == 22) || ((df >= 24) && (df <= 27))) {
|
||||
bytes = 112/8;
|
||||
} else {
|
||||
bytes = 0;
|
||||
}
|
||||
|
||||
if (bytes > 0)
|
||||
{
|
||||
m_demodStats.m_modesFrames++;
|
||||
// Pass to GUI (only pass formats it can decode)
|
||||
if (m_sink->getMessageQueueToGUI() && ((df == 4) || (df == 5) || (df == 20) || (df == 21)))
|
||||
bool invalid = modeSValid(data, df);
|
||||
|
||||
if (!invalid)
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, bytes),
|
||||
preambleCorrelation * m_correlationScale,
|
||||
preambleCorrelationOnes / samplesPerChip,
|
||||
rxDateTime(firstIdx, readBuffer),
|
||||
m_crc.get());
|
||||
m_sink->getMessageQueueToGUI()->push(msg);
|
||||
// Extract received parity
|
||||
int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1];
|
||||
// Calculate CRC on received frame
|
||||
m_crc.init();
|
||||
m_crc.calculate(data, bytes-3);
|
||||
int crc = m_crc.get();
|
||||
bool forward = false;
|
||||
|
||||
// ICAO address XORed in to parity, apart from DF11
|
||||
// Extract ICAO from parity and see if it matches an aircraft we've already
|
||||
// received an ADS-B or Mode S frame from
|
||||
// For DF11, the last 7 bits may have an iterogration code (II/SI)
|
||||
// XORed in, so we ignore those bits. This does sometimes lead to false-positives
|
||||
if (df != 11)
|
||||
{
|
||||
unsigned icao = (parity ^ crc) & 0xffffff;
|
||||
|
||||
if (icaoValid(icao))
|
||||
{
|
||||
if (icaoHeardRecently(icao, dateTime)) {
|
||||
forward = true;
|
||||
} else {
|
||||
handleModeS(data, bytes, icao, df, firstIndex, preamble, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_icaoFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore IC bits
|
||||
parity &= 0xffff80;
|
||||
crc &= 0xffff80;
|
||||
if (parity == crc)
|
||||
{
|
||||
// Get 24-bit ICAO
|
||||
unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff);
|
||||
|
||||
if (icaoValid(icao))
|
||||
{
|
||||
if (icaoHeardRecently(icao, dateTime)) {
|
||||
forward = true;
|
||||
} else {
|
||||
handleModeS(data, bytes, icao, df, firstIndex, preamble, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_icaoFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_crcFails++;
|
||||
}
|
||||
}
|
||||
if (forward)
|
||||
{
|
||||
m_demodStats.m_modesFrames++;
|
||||
// Don't try to re-demodulate the same frame
|
||||
// We could possibly allow a partial overlap here
|
||||
readIdx += ((bytes*8)+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1;
|
||||
forwardFrame(data, bytes, preambleCorrelationScaled, correlationOnes, dateTime, m_crc.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_crcFails++;
|
||||
}
|
||||
}
|
||||
// Pass to worker to feed to other servers
|
||||
if (m_sink->getMessageQueueToWorker())
|
||||
else
|
||||
{
|
||||
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
|
||||
QByteArray((char*)data, bytes),
|
||||
preambleCorrelation * m_correlationScale,
|
||||
preambleCorrelationOnes / samplesPerChip,
|
||||
rxDateTime(firstIdx, readBuffer),
|
||||
m_crc.get());
|
||||
m_sink->getMessageQueueToWorker()->push(msg);
|
||||
m_demodStats.m_invalidFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_crcFails++;
|
||||
m_demodStats.m_typeFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_demodStats.m_typeFails++;
|
||||
{
|
||||
m_demodStats.m_preambleFails++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_demodStats.m_typeFails++;
|
||||
}
|
||||
}
|
||||
|
||||
readIdx++;
|
||||
if (readIdx > m_sink->m_bufferSize - samplesPerFrame)
|
||||
{
|
||||
@ -299,9 +609,7 @@ void ADSBDemodSinkWorker::run()
|
||||
nextBuffer = 0;
|
||||
|
||||
// Update amount of time spent processing (don't include time spend in acquire)
|
||||
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - startPoint;
|
||||
m_demodStats.m_demodTime += sec.count();
|
||||
m_demodStats.m_feedTime = m_sink->m_feedTime;
|
||||
PROFILER_STOP("ADS-B demod");
|
||||
|
||||
// Send stats to GUI
|
||||
if (m_sink->getMessageQueueToGUI())
|
||||
@ -319,7 +627,7 @@ void ADSBDemodSinkWorker::run()
|
||||
handleInputMessages();
|
||||
|
||||
// Resume timing how long we are processing
|
||||
startPoint = boost::chrono::steady_clock::now();
|
||||
PROFILER_RESTART();
|
||||
|
||||
int samplesRemaining = m_sink->m_bufferSize - readIdx;
|
||||
if (samplesRemaining > 0)
|
||||
@ -359,15 +667,6 @@ void ADSBDemodSinkWorker::handleInputMessages()
|
||||
QStringList settingsKeys = cfg->getSettingsKeys();
|
||||
bool force = cfg->getForce();
|
||||
|
||||
if (settingsKeys.contains("correlateFullPreamble") || force)
|
||||
{
|
||||
if (settings.m_correlateFullPreamble) {
|
||||
m_correlationScale = 3.0;
|
||||
} else {
|
||||
m_correlationScale = 2.0;
|
||||
}
|
||||
}
|
||||
|
||||
if ((settingsKeys.contains("correlationThreshold") && (m_settings.m_correlationThreshold != settings.m_correlationThreshold)) || force)
|
||||
{
|
||||
m_correlationThresholdLinear = CalcDb::powerFromdB(settings.m_correlationThreshold);
|
||||
@ -382,6 +681,10 @@ void ADSBDemodSinkWorker::handleInputMessages()
|
||||
}
|
||||
delete message;
|
||||
}
|
||||
else if (ADSBDemod::MsgResetStats::match(*message))
|
||||
{
|
||||
m_demodStats.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,11 +77,29 @@ private:
|
||||
ADSBDemodSink *m_sink;
|
||||
ADSBDemodStats m_demodStats;
|
||||
Real m_correlationThresholdLinear;
|
||||
Real m_correlationScale;
|
||||
static const Real m_correlationScale;
|
||||
crcadsb m_crc; //!< Have as member to avoid recomputing LUT
|
||||
QHash<int, bool> m_icaos; //!< ICAO addresses that have been received
|
||||
QHash<unsigned, qint64> m_icaos; //!< ICAO addresses that have been received and msecsSinceEpoch last heard
|
||||
QDateTime m_lastClear;
|
||||
|
||||
struct RXRecord {
|
||||
QByteArray m_data;
|
||||
int m_firstIndex;
|
||||
unsigned short m_preamble;
|
||||
Real m_preambleCorrelation;
|
||||
Real m_correlationOnes;
|
||||
QDateTime m_dateTime;
|
||||
unsigned m_crc;
|
||||
};
|
||||
|
||||
QHash<int, QList<RXRecord>> m_modeSOnlyIcaos;
|
||||
|
||||
QDateTime rxDateTime(int firstIdx, int readBuffer) const;
|
||||
void handleModeS(unsigned char *data, int bytes, unsigned icao, int df, int firstIndex, unsigned short preamble, Real preambleCorrelation, Real correlationOnes, const QDateTime& dateTime, unsigned crc);
|
||||
void forwardFrame(const unsigned char *data, int size, float preambleCorrelation, float correlationOnes, const QDateTime& dateTime, unsigned crc);
|
||||
bool icaoHeardRecently(unsigned icao, const QDateTime &dateTime);
|
||||
static bool icaoValid(unsigned icao);
|
||||
static bool modeSValid(unsigned char *data, unsigned df);
|
||||
|
||||
};
|
||||
|
||||
|
@ -25,20 +25,27 @@ struct ADSBDemodStats {
|
||||
qint64 m_correlatorMatches; //!< Total number of correlator matches
|
||||
qint64 m_adsbFrames; //!< How many ADS-B frames with correct CRCs
|
||||
qint64 m_modesFrames; //!< How many non-ADS-B Mode-S frames with correct CRCs
|
||||
qint64 m_preambleFails; //!< How many non-ADS-B Mode S frames with errors in preamble
|
||||
qint64 m_crcFails; //!< How many frames we've demoded with incorrect CRCs
|
||||
qint64 m_typeFails; //!< How many frames we've demoded with unknown type (DF) so we can't check CRC
|
||||
double m_demodTime; //!< How long we've spent in run()
|
||||
double m_feedTime; //!< How long we've spent in feed()
|
||||
qint64 m_invalidFails; //!< How many frames we've demoded with reserved bits set
|
||||
qint64 m_icaoFails; //!< How many frames we've demoded with reserved ICAO
|
||||
|
||||
ADSBDemodStats() :
|
||||
m_correlatorMatches(0),
|
||||
m_adsbFrames(0),
|
||||
m_modesFrames(0),
|
||||
m_crcFails(0),
|
||||
m_typeFails(0),
|
||||
m_demodTime(0.0),
|
||||
m_feedTime(0.0)
|
||||
ADSBDemodStats()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
m_correlatorMatches = 0;
|
||||
m_adsbFrames = 0;
|
||||
m_modesFrames = 0;
|
||||
m_preambleFails = 0;
|
||||
m_crcFails = 0;
|
||||
m_typeFails = 0;
|
||||
m_invalidFails = 0;
|
||||
m_icaoFails = 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user