1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-02-03 09:44:01 -05:00

Merge pull request #688 from srcejon/adsb_improvements

ADS-B demodulator improvements
This commit is contained in:
Edouard Griffiths 2020-11-07 10:40:48 +01:00 committed by GitHub
commit 35b7f741eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1252 changed files with 6053 additions and 829 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

After

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

View File

@ -6,6 +6,7 @@ set(adsb_SOURCES
adsbdemodwebapiadapter.cpp
adsbplugin.cpp
adsbdemodsink.cpp
adsbdemodsinkworker.cpp
adsbdemodbaseband.cpp
adsbdemodreport.cpp
adsbdemodworker.cpp
@ -17,14 +18,17 @@ set(adsb_HEADERS
adsbdemodwebapiadapter.h
adsbplugin.h
adsbdemodsink.h
absddemodsinkworker.h
adsbdemodbaseband.h
adsbdemodreport.h
adsbdemodworker.h
adsbdemodstats.h
adsb.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${Boost_INCLUDE_DIRS}
)
if(NOT SERVER_MODE)
@ -32,10 +36,24 @@ if(NOT SERVER_MODE)
${adsb_SOURCES}
adsbdemodgui.cpp
adsbdemodgui.ui
adsbdemodfeeddialog.cpp
adsbdemodfeeddialog.ui
adsbdemoddisplaydialog.cpp
adsbdemoddisplaydialog.ui
csv.cpp
airlinelogos.qrc
flags.qrc
map.qrc
icons.qrc
)
set(adsb_HEADERS
${adsb_HEADERS}
adsbdemodgui.h
adsbdemodfeeddialog.h
adsbdemoddisplaydialog.h
ourairports.h
osndb.h
csv.h
)
set(TARGET_NAME demodadsb)
@ -53,7 +71,12 @@ add_library(${TARGET_NAME} SHARED
${adsb_SOURCES}
)
if (NOT WIN32)
link_directories(${Boost_LIBRARY_DIRS})
endif()
target_link_libraries(${TARGET_NAME}
Boost::disable_autolinking
Qt5::Core
${TARGET_LIB}
sdrbase
@ -66,5 +89,5 @@ install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
if(WIN32)
# Run deployqt for QtQuick etc
include(DeployQt)
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/../../../sdrgui/resources)
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/map)
endif()

View File

@ -17,6 +17,9 @@
// 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>
@ -109,6 +112,7 @@ void ADSBDemod::start()
m_worker->reset();
m_worker->startWork();
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread->start();
ADSBDemodWorker::MsgConfigureADSBDemodWorker *msg = ADSBDemodWorker::MsgConfigureADSBDemodWorker::create(m_settings, true);
@ -118,6 +122,7 @@ void ADSBDemod::start()
void ADSBDemod::stop()
{
qDebug() << "ADSBDemod::stop";
m_basebandSink->stopWork();
m_worker->stopWork();
m_thread->exit();
m_thread->wait();
@ -288,13 +293,13 @@ void ADSBDemod::webapiUpdateChannelSettings(
settings.m_removeTimeout = response.getAdsbDemodSettings()->getRemoveTimeout();
}
if (channelSettingsKeys.contains("beastEnabled")) {
settings.m_beastEnabled = response.getAdsbDemodSettings()->getBeastEnabled() != 0;
settings.m_feedEnabled = response.getAdsbDemodSettings()->getBeastEnabled() != 0;
}
if (channelSettingsKeys.contains("beastHost")) {
settings.m_beastHost = *response.getAdsbDemodSettings()->getBeastHost();
settings.m_feedHost = *response.getAdsbDemodSettings()->getBeastHost();
}
if (channelSettingsKeys.contains("beastPort")) {
settings.m_beastPort = response.getAdsbDemodSettings()->getBeastPort();
settings.m_feedPort = response.getAdsbDemodSettings()->getBeastPort();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAdsbDemodSettings()->getRgbColor();
@ -340,9 +345,9 @@ void ADSBDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& res
response.getAdsbDemodSettings()->setCorrelationThreshold(settings.m_correlationThreshold);
response.getAdsbDemodSettings()->setSamplesPerBit(settings.m_samplesPerBit);
response.getAdsbDemodSettings()->setRemoveTimeout(settings.m_removeTimeout);
response.getAdsbDemodSettings()->setBeastEnabled(settings.m_beastEnabled ? 1 : 0);
response.getAdsbDemodSettings()->setBeastHost(new QString(settings.m_beastHost));
response.getAdsbDemodSettings()->setBeastPort(settings.m_beastPort);
response.getAdsbDemodSettings()->setBeastEnabled(settings.m_feedEnabled ? 1 : 0);
response.getAdsbDemodSettings()->setBeastHost(new QString(settings.m_feedHost));
response.getAdsbDemodSettings()->setBeastPort(settings.m_feedPort);
response.getAdsbDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getAdsbDemodSettings()->getTitle()) {
@ -408,13 +413,13 @@ void ADSBDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, c
swgADSBDemodSettings->setRemoveTimeout(settings.m_removeTimeout);
}
if (channelSettingsKeys.contains("beastEnabled") || force) {
swgADSBDemodSettings->setBeastEnabled(settings.m_beastEnabled ? 1 : 0);
swgADSBDemodSettings->setBeastEnabled(settings.m_feedEnabled ? 1 : 0);
}
if (channelSettingsKeys.contains("beastHost") || force) {
swgADSBDemodSettings->setBeastHost(new QString(settings.m_beastHost));
swgADSBDemodSettings->setBeastHost(new QString(settings.m_feedHost));
}
if (channelSettingsKeys.contains("beastPort") || force) {
swgADSBDemodSettings->setBeastPort(settings.m_beastPort);
swgADSBDemodSettings->setBeastPort(settings.m_feedPort);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgADSBDemodSettings->setRgbColor(settings.m_rgbColor);

View File

@ -56,6 +56,16 @@ void ADSBDemodBaseband::reset()
m_sampleFifo.reset();
}
void ADSBDemodBaseband::startWork()
{
m_sink.startWorker();
}
void ADSBDemodBaseband::stopWork()
{
m_sink.stopWorker();
}
void ADSBDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);

View File

@ -60,6 +60,8 @@ public:
ADSBDemodBaseband();
~ADSBDemodBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
int getChannelSampleRate() const;

View File

@ -0,0 +1,66 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QFontDialog>
#include <QDebug>
#include "adsbdemoddisplaydialog.h"
ADSBDemodDisplayDialog::ADSBDemodDisplayDialog
(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats, QWidget* parent) :
QDialog(parent),
ui(new Ui::ADSBDemodDisplayDialog),
m_fontName(fontName),
m_fontSize(fontSize)
{
ui->setupUi(this);
ui->timeout->setValue(removeTimeout);
ui->airportRange->setValue(airportRange);
ui->airportSize->setCurrentIndex((int)airportMinimumSize);
ui->heliports->setChecked(displayHeliports);
ui->units->setCurrentIndex((int)siUnits);
ui->displayStats->setChecked(displayDemodStats);
}
ADSBDemodDisplayDialog::~ADSBDemodDisplayDialog()
{
delete ui;
}
void ADSBDemodDisplayDialog::accept()
{
m_removeTimeout = ui->timeout->value();
m_airportRange = ui->airportRange->value();
m_airportMinimumSize = (ADSBDemodSettings::AirportType)ui->airportSize->currentIndex();
m_displayHeliports = ui->heliports->isChecked();
m_siUnits = ui->units->currentIndex() == 0 ? false : true;
m_displayDemodStats = ui->displayStats->isChecked();
QDialog::accept();
}
void ADSBDemodDisplayDialog::on_font_clicked(bool checked)
{
bool ok;
QFont font = QFontDialog::getFont(&ok, QFont(m_fontName, m_fontSize), this);
if (ok)
{
qDebug() << font;
m_fontName = font.family();
m_fontSize = font.pointSize();
}
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_ADSBDEMODDISPLAYDIALOG_H
#define INCLUDE_ADSBDEMODDISPLAYDIALOG_H
#include "ui_adsbdemoddisplaydialog.h"
#include "adsbdemodsettings.h"
class ADSBDemodDisplayDialog : public QDialog {
Q_OBJECT
public:
explicit ADSBDemodDisplayDialog(int removeTimeout, float airportRange, ADSBDemodSettings::AirportType airportMinimumSize,
bool displayHeliports, bool siUnits, QString fontName, int fontSize, bool displayDemodStats
, QWidget* parent = 0);
~ADSBDemodDisplayDialog();
int m_removeTimeout;
float m_airportRange;
ADSBDemodSettings::AirportType m_airportMinimumSize;
bool m_displayHeliports;
bool m_siUnits;
QString m_fontName;
int m_fontSize;
bool m_displayDemodStats;
private slots:
void accept();
void on_font_clicked(bool checked = false);
private:
Ui::ADSBDemodDisplayDialog* ui;
};
#endif // INCLUDE_ADSBDEMODDISPLAYDIALOG_H

View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ADSBDemodDisplayDialog</class>
<widget class="QDialog" name="ADSBDemodDisplayDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>351</width>
<height>275</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Display Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="unitsLabel">
<property name="text">
<string>Units</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="units">
<property name="toolTip">
<string>The units to use for altitude, speed and climb rate</string>
</property>
<item>
<property name="text">
<string>ft, kn, ft/min</string>
</property>
</item>
<item>
<property name="text">
<string>m, kph, m/s</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="airportSizeLabel">
<property name="text">
<string>Display airports with size</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="airportSize">
<property name="toolTip">
<string>Sets the minimum airport size that will be displayed on the map</string>
</property>
<item>
<property name="text">
<string>Small</string>
</property>
</item>
<item>
<property name="text">
<string>Medium</string>
</property>
</item>
<item>
<property name="text">
<string>Large</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="heliports">
<property name="toolTip">
<string>When checked, heliports are displayed on the map</string>
</property>
<property name="text">
<string>Display heliports</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="airportRangeLabel">
<property name="text">
<string>Airport display range (nm)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="airportRange">
<property name="toolTip">
<string>Displays airports within the specified range in nautical miles from My Position</string>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="timeoutLabel">
<property name="text">
<string>Aircraft timeout (s)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="timeout">
<property name="toolTip">
<string>How long in seconds after not receving any frames will an aircraft be removed from the table and map</string>
</property>
<property name="maximum">
<number>1000000</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="fontLabel">
<property name="text">
<string>Table font</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="font">
<property name="toolTip">
<string>Select a font for the table</string>
</property>
<property name="text">
<string>Select...</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QCheckBox" name="displayStats">
<property name="toolTip">
<string>Display demodulator statistics</string>
</property>
<property name="text">
<string>Display demodulator statistics</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ADSBDemodDisplayDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ADSBDemodDisplayDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QLineEdit>
#include "adsbdemodfeeddialog.h"
#include "adsbdemodsettings.h"
ADSBDemodFeedDialog::ADSBDemodFeedDialog(QString& feedHost, int feedPort, ADSBDemodSettings::FeedFormat feedFormat, QWidget* parent) :
QDialog(parent),
ui(new Ui::ADSBDemodFeedDialog)
{
ui->setupUi(this);
ui->host->lineEdit()->setText(feedHost);
ui->port->setValue(feedPort);
ui->format->setCurrentIndex((int)feedFormat);
}
ADSBDemodFeedDialog::~ADSBDemodFeedDialog()
{
delete ui;
}
void ADSBDemodFeedDialog::accept()
{
m_feedHost = ui->host->currentText();
m_feedPort = ui->port->value();
m_feedFormat = (ADSBDemodSettings::FeedFormat )ui->format->currentIndex();
QDialog::accept();
}
void ADSBDemodFeedDialog::on_host_currentIndexChanged(int value)
{
if (value == 0)
{
ui->host->lineEdit()->setText("feed.adsbexchange.com");
ui->port->setValue(30005);
ui->format->setCurrentIndex(0);
}
else if (value == 1)
{
ui->host->lineEdit()->setText("data.adsbhub.org");
ui->port->setValue(5002);
ui->format->setCurrentIndex(1);
}
}

View File

@ -0,0 +1,43 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_ADSBDEMODFEEDDIALOG_H
#define INCLUDE_ADSBDEMODFEEDDIALOG_H
#include "ui_adsbdemodfeeddialog.h"
#include "adsbdemodsettings.h"
class ADSBDemodFeedDialog : public QDialog {
Q_OBJECT
public:
explicit ADSBDemodFeedDialog(QString& feedHost, int feedPort, ADSBDemodSettings::FeedFormat feedFormat, QWidget* parent = 0);
~ADSBDemodFeedDialog();
QString m_feedHost;
int m_feedPort;
ADSBDemodSettings::FeedFormat m_feedFormat;
private slots:
void accept();
void on_host_currentIndexChanged(int value);
private:
Ui::ADSBDemodFeedDialog* ui;
};
#endif // INCLUDE_ADSBDEMODFEEDDIALOG_H

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ADSBDemodFeedDialog</class>
<widget class="QDialog" name="ADSBDemodFeedDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>351</width>
<height>138</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Feed Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Server hostname</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="portLabel">
<property name="text">
<string>Port number</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="port">
<property name="toolTip">
<string>The TCP port number the server is listening on</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="format">
<property name="toolTip">
<string>Format to feed the data to the server in</string>
</property>
<item>
<property name="text">
<string>Beast binary</string>
</property>
</item>
<item>
<property name="text">
<string>Beast hex</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="formatLabel">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="host">
<property name="toolTip">
<string>Hostname of server to feed ADS-B data to</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>feed.adsbexchange.com</string>
</property>
</item>
<item>
<property name="text">
<string>data.adsbhub.org</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>port</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ADSBDemodFeedDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ADSBDemodFeedDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

File diff suppressed because it is too large Load Diff

View File

@ -20,9 +20,11 @@
#define INCLUDE_ADSBDEMODGUI_H
#include <QTableWidgetItem>
#include <QMenu>
#include <QGeoCoordinate>
#include <QDateTime>
#include <QAbstractListModel>
#include <QProgressDialog>
#include "channel/channelgui.h"
#include "dsp/dsptypes.h"
@ -31,18 +33,46 @@
#include "util/messagequeue.h"
#include "util/azel.h"
#include "util/movingaverage.h"
#include "util/httpdownloadmanager.h"
#include "adsbdemodsettings.h"
#include "ourairportsdb.h"
#include "osndb.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class ADSBDemod;
class WebAPIAdapterInterface;
class HttpDownloadManager;
class ADSBDemodGUI;
namespace Ui {
class ADSBDemodGUI;
}
// Custom widget to allow formatted decimal numbers to be sorted numerically
class CustomDoubleTableWidgetItem : public QTableWidgetItem
{
public:
CustomDoubleTableWidgetItem(const QString text = QString("")) :
QTableWidgetItem(text)
{
}
bool operator <(const QTableWidgetItem& other) const
{
// Treat "" as less than 0
QString thisText = text();
QString otherText = other.text();
if (thisText == "")
return true;
if (otherText == "")
return false;
return thisText.toDouble() < otherText.toDouble();
}
};
// Data about an aircraft extracted from an ADS-B frames
struct Aircraft {
int m_icao; // 24-bit ICAO aircraft address
@ -76,31 +106,49 @@ struct Aircraft {
bool m_cprValid[2];
Real m_cprLat[2];
Real m_cprLong[2];
QDateTime m_cprTime[2];
int m_adsbFrameCount; // Number of ADS-B frames for this aircraft
float m_minCorrelation;
float m_maxCorrelation;
float m_correlation;
bool m_isBeingTracked; // Are we tracking this aircraft
MovingAverageUtil<float, double, 100> m_correlationAvg;
bool m_isTarget; // Are we targetting this aircraft (sending az/el to rotator)
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
AircraftInformation *m_aircraftInfo; // Info about the aircraft from the database
ADSBDemodGUI *m_gui;
// GUI table items for above data
QTableWidgetItem *m_icaoItem;
QTableWidgetItem *m_flightItem;
QTableWidgetItem *m_modelItem;
QTableWidgetItem *m_airlineItem;
QTableWidgetItem *m_latitudeItem;
QTableWidgetItem *m_longitudeItem;
QTableWidgetItem *m_altitudeItem;
QTableWidgetItem *m_speedItem;
QTableWidgetItem *m_headingItem;
QTableWidgetItem *m_verticalRateItem;
CustomDoubleTableWidgetItem *m_rangeItem;
QTableWidgetItem *m_azElItem;
QTableWidgetItem *m_emitterCategoryItem;
QTableWidgetItem *m_statusItem;
QTableWidgetItem *m_rangeItem;
QTableWidgetItem *m_azElItem;
QTableWidgetItem *m_registrationItem;
QTableWidgetItem *m_countryItem;
QTableWidgetItem *m_registeredItem;
QTableWidgetItem *m_manufacturerNameItem;
QTableWidgetItem *m_ownerItem;
QTableWidgetItem *m_operatorICAOItem;
QTableWidgetItem *m_timeItem;
QTableWidgetItem *m_adsbFrameCountItem;
QTableWidgetItem *m_correlationItem;
Aircraft() :
Aircraft(ADSBDemodGUI *gui) :
m_icao(0),
m_latitude(0),
m_longitude(0),
@ -119,7 +167,11 @@ struct Aircraft {
m_minCorrelation(INFINITY),
m_maxCorrelation(-INFINITY),
m_correlation(0.0f),
m_isBeingTracked(false)
m_isTarget(false),
m_isHighlighted(false),
m_showAll(false),
m_aircraftInfo(nullptr),
m_gui(gui)
{
for (int i = 0; i < 2; i++)
{
@ -128,16 +180,24 @@ struct Aircraft {
// These are deleted by QTableWidget
m_icaoItem = new QTableWidgetItem();
m_flightItem = new QTableWidgetItem();
m_latitudeItem = new QTableWidgetItem();
m_longitudeItem = new QTableWidgetItem();
m_modelItem = new QTableWidgetItem();
m_airlineItem = new QTableWidgetItem();
m_altitudeItem = new QTableWidgetItem();
m_speedItem = new QTableWidgetItem();
m_headingItem = new QTableWidgetItem();
m_verticalRateItem = new QTableWidgetItem();
m_rangeItem = new CustomDoubleTableWidgetItem();
m_azElItem = new QTableWidgetItem();
m_latitudeItem = new QTableWidgetItem();
m_longitudeItem = new QTableWidgetItem();
m_emitterCategoryItem = new QTableWidgetItem();
m_statusItem = new QTableWidgetItem();
m_rangeItem = new QTableWidgetItem();
m_azElItem = 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_timeItem = new QTableWidgetItem();
m_adsbFrameCountItem = new QTableWidgetItem();
m_correlationItem = new QTableWidgetItem();
@ -155,7 +215,11 @@ public:
headingRole = Qt::UserRole + 2,
adsbDataRole = Qt::UserRole + 3,
aircraftImageRole = Qt::UserRole + 4,
bubbleColourRole = Qt::UserRole + 5
bubbleColourRole = Qt::UserRole + 5,
aircraftPathRole = Qt::UserRole + 6,
showAllRole = Qt::UserRole + 7,
highlightedRole = Qt::UserRole + 8,
targetRole = Qt::UserRole + 9
};
Q_INVOKABLE void addAircraft(Aircraft *aircraft) {
@ -171,6 +235,12 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
void aircraftUpdated(Aircraft *aircraft) {
int row = m_aircrafts.indexOf(aircraft);
if (row >= 0)
@ -180,6 +250,19 @@ 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++)
{
QModelIndex idx = index(i);
emit dataChanged(idx, idx);
}
}
void removeAircraft(Aircraft *aircraft) {
int row = m_aircrafts.indexOf(aircraft);
if (row >= 0)
@ -197,11 +280,136 @@ public:
roles[adsbDataRole] = "adsbData";
roles[aircraftImageRole] = "aircraftImage";
roles[bubbleColourRole] = "bubbleColour";
roles[aircraftPathRole] = "aircraftPath";
roles[showAllRole] = "showAll";
roles[highlightedRole] = "highlighted";
roles[targetRole] = "target";
return roles;
}
void setFlightPaths(bool flightPaths)
{
m_flightPaths = flightPaths;
allAircraftUpdated();
}
private:
QList<Aircraft *> m_aircrafts;
bool m_flightPaths;
};
// Airport data model used by QML map item
class AirportModel : public QAbstractListModel {
Q_OBJECT
public:
using QAbstractListModel::QAbstractListModel;
enum MarkerRoles {
positionRole = Qt::UserRole + 1,
airportDataRole = Qt::UserRole + 2,
airportDataRowsRole = Qt::UserRole + 3,
airportImageRole = Qt::UserRole + 4,
bubbleColourRole = Qt::UserRole + 5,
showFreqRole = Qt::UserRole + 6,
selectedFreqRole = Qt::UserRole + 7
};
AirportModel(ADSBDemodGUI *gui) :
m_gui(gui)
{
}
Q_INVOKABLE void addAirport(AirportInformation *airport) {
QString text;
int rows;
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_airports.append(airport);
airportFreq(airport, text, rows);
m_airportDataFreq.append(text);
m_airportDataFreqRows.append(rows);
m_showFreq.append(false);
endInsertRows();
}
void removeAirport(AirportInformation *airport) {
int row = m_airports.indexOf(airport);
if (row >= 0)
{
beginRemoveRows(QModelIndex(), row, row);
m_airports.removeAt(row);
m_airportDataFreq.removeAt(row);
m_airportDataFreqRows.removeAt(row);
m_showFreq.removeAt(row);
endRemoveRows();
}
}
void removeAllAirports() {
beginRemoveRows(QModelIndex(), 0, m_airports.count());
m_airports.clear();
m_airportDataFreq.clear();
m_airportDataFreqRows.clear();
m_showFreq.clear();
endRemoveRows();
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override {
Q_UNUSED(parent)
return m_airports.count();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}
void airportFreq(AirportInformation *airport, QString& text, int& rows) {
// Create the text to go in the bubble next to the airport
// Display name and frequencies
QStringList list;
list.append(QString("%1: %2").arg(airport->m_ident).arg(airport->m_name));
rows = 1;
for (int i = 0; i < airport->m_frequencies.size(); i++)
{
AirportInformation::FrequencyInformation *frequencyInfo = airport->m_frequencies[i];
list.append(QString("%1: %2 MHz").arg(frequencyInfo->m_type).arg(frequencyInfo->m_frequency));
rows++;
}
text = list.join("\n");
}
void airportUpdated(AirportInformation *airport) {
int row = m_airports.indexOf(airport);
if (row >= 0)
{
QModelIndex idx = index(row);
emit dataChanged(idx, idx);
}
}
QHash<int, QByteArray> roleNames() const {
QHash<int, QByteArray> roles;
roles[positionRole] = "position";
roles[airportDataRole] = "airportData";
roles[airportDataRowsRole] = "airportDataRows";
roles[airportImageRole] = "airportImage";
roles[bubbleColourRole] = "bubbleColour";
roles[showFreqRole] = "showFreq";
roles[selectedFreqRole] = "selectedFreq";
return roles;
}
private:
ADSBDemodGUI *m_gui;
QList<AirportInformation *> m_airports;
QList<QString> m_airportDataFreq;
QList<int> m_airportDataFreqRows;
QList<bool> m_showFreq;
};
class ADSBDemodGUI : public ChannelGUI {
@ -215,6 +423,10 @@ public:
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void highlightAircraft(Aircraft *aircraft);
void targetAircraft(Aircraft *aircraft);
bool setFrequency(float frequency);
bool useSIUints() { return m_settings.m_siUnits; }
public slots:
void channelMarkerChangedByCursor();
@ -233,13 +445,30 @@ private:
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QHash<int,Aircraft *> m_aircraft; // Hashed on ICAO
QHash<int, Aircraft *> m_aircraft; // Hashed on ICAO
QHash<int, AircraftInformation *> *m_aircraftInfo;
QHash<int, AirportInformation *> *m_airportInfo; // Hashed on id
AircraftModel m_aircraftModel;
AirportModel m_airportModel;
QHash<QString, QIcon *> m_airlineIcons; // Hashed on airline ICAO
QHash<QString, QIcon *> m_flagIcons; // Hashed on country
QHash<QString, QString> *m_prefixMap; // Registration to country (flag name)
QHash<QString, QString> *m_militaryMap; // Operator airforce to military (flag name)
AzEl m_azEl; // Position of station
Aircraft *m_trackAircraft; // Aircraft we want to track in Channel Report
MovingAverageUtil<float, double, 10> m_correlationOnesAvg;
MovingAverageUtil<float, double, 10> m_correlationZerosAvg;
MovingAverageUtil<float, double, 10> m_correlationAvg;
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;
QMenu *menu; // Column select context menu
WebAPIAdapterInterface *m_webAPIAdapterInterface;
HttpDownloadManager m_dlm;
QProgressDialog *m_progressDialog;
explicit ADSBDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~ADSBDemodGUI();
@ -250,8 +479,22 @@ private:
void displayStreamIndex();
bool handleMessage(const Message& message);
void updatePosition(Aircraft *aircraft);
void handleADSB(const QByteArray data, const QDateTime dateTime, float correlationOnes, float correlationZeros);
void handleADSB(const QByteArray data, const QDateTime dateTime, float correlation);
void resizeTable();
QString getDataDir();
QString getAirportDBFilename();
QString getAirportFrequenciesDBFilename();
QString getOSNDBFilename();
QString getFastDBFilename();
void readAirportDB(const QString& filename);
void readAirportFrequenciesDB(const QString& filename);
bool readOSNDB(const QString& filename);
bool readFastDB(const QString& filename);
void updateAirports();
QIcon *getAirlineIcon(const QString &operatorICAO);
QIcon *getFlagIcon(const QString &country);
void updateDeviceSetList();
QAction *createCheckableItem(QString& text, int idx, bool checked);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -260,15 +503,29 @@ private slots:
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int value);
void on_threshold_valueChanged(int value);
void on_adsbData_cellClicked(int row, int column);
void on_adsbData_cellDoubleClicked(int row, int column);
void adsbData_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void adsbData_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void on_spb_currentIndexChanged(int value);
void on_beastEnabled_stateChanged(int state);
void on_host_editingFinished(QString value);
void on_port_valueChanged(int value);
void on_correlateFullPreamble_clicked(bool checked=false);
void on_demodModeS_clicked(bool checked=false);
void on_feed_clicked(bool checked=false);
void on_getOSNDB_clicked(bool checked = false);
void on_getAirportDB_clicked(bool checked = false);
void on_flightPaths_clicked(bool checked = false);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
void updateDownloadProgress(qint64 bytesRead, qint64 totalBytes);
void downloadFinished(const QString& filename, bool success);
void on_devicesRefresh_clicked();
void on_device_currentIndexChanged(int index);
void feedSelect();
void on_displaySettings_clicked(bool checked=false);
signals:
void homePositionChanged();
};

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>350</width>
<height>1019</height>
<height>1046</height>
</rect>
</property>
<property name="sizePolicy">
@ -37,7 +37,7 @@
<x>0</x>
<y>0</y>
<width>340</width>
<height>101</height>
<height>141</height>
</rect>
</property>
<property name="minimumSize">
@ -326,6 +326,39 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="demodModeS">
<property name="toolTip">
<string>Demodulate all Mode-S frames, not just ADS-B</string>
</property>
<property name="text">
<string>S</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="correlateFullPreamble">
<property name="toolTip">
<string>Correlate against full preamble.</string>
</property>
<property name="text">
<string>FP</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="thresholdLabel">
<property name="text">
@ -342,10 +375,10 @@
</size>
</property>
<property name="toolTip">
<string>Correlation threshold in dB. Lower values will increase the number of frames that can be received, but require more processing.</string>
<string>Correlation threshold in dB. Lower values will increase the number of frames that can be received, but require more processing and possibly result in invalid frames.</string>
</property>
<property name="minimum">
<number>-990</number>
<number>-450</number>
</property>
<property name="maximum">
<number>0</number>
@ -354,7 +387,7 @@
<number>1</number>
</property>
<property name="value">
<number>-500</number>
<number>0</number>
</property>
</widget>
</item>
@ -376,58 +409,132 @@
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="beastEnabled">
<widget class="QToolButton" name="getOSNDB">
<property name="toolTip">
<string>Enable feeding of received ADS-B messages in Beast binary format to the specifed server</string>
<string>Download the latest Opensky-Network aircraft database (80MB)</string>
</property>
<property name="text">
<string>Feed</string>
<string>...</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/aircraft.png</normaloff>:/icons/aircraft.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="hostLabel">
<property name="text">
<string>Server</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="host">
<widget class="QToolButton" name="getAirportDB">
<property name="toolTip">
<string>Hostname of the server to feed</string>
<string>Download the latest OurAirports airport databases (10MB)</string>
</property>
<property name="text">
<string>feed.adsbexchange.com</string>
<string>...</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/icons/controltower.png</normaloff>:/icons/controltower.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="portLabel">
<widget class="QToolButton" name="displaySettings">
<property name="toolTip">
<string>Open display settings dialog</string>
</property>
<property name="text">
<string>Port</string>
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="port">
<property name="minimumSize">
<widget class="QToolButton" name="flightPaths">
<property name="toolTip">
<string>Display flight paths</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/logarithmic.png</normaloff>:/logarithmic.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="feed">
<property name="toolTip">
<string>Enable feeding of received ADS-B messages to the specifed server. Right click for settings.</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/txon.png</normaloff>:/txon.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="stats">
<property name="toolTip">
<string>Demodulator statistics</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>60</width>
<height>0</height>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="devicesRefresh">
<property name="toolTip">
<string>Port the server is listening on</string>
<string>Refresh device list</string>
</property>
<property name="minimum">
<number>1024</number>
<property name="text">
<string>...</string>
</property>
<property name="maximum">
<number>65535</number>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
<property name="value">
<number>30005</number>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="device">
<property name="toolTip">
<string>Receive device set to set frequency on when selecting an ATC frequency on the map</string>
</property>
</widget>
</item>
@ -439,7 +546,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>114</y>
<y>140</y>
<width>341</width>
<height>291</height>
</rect>
@ -450,11 +557,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
</font>
</property>
<property name="windowTitle">
<string>ADS-B Data</string>
</property>
@ -476,11 +578,6 @@
</property>
<item>
<widget class="QTableWidget" name="adsbData">
<property name="font">
<font>
<family>Liberation Mono</family>
</font>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
@ -489,7 +586,7 @@
<string>ICAO ID</string>
</property>
<property name="toolTip">
<string extracomment="Double click to search for the aircraft on www.planespotters.net">International Civil Aviation Organization identifier. Links to www.planespotters.net</string>
<string extracomment="Double click to search for the aircraft on www.planespotters.net">Aircraft International Civil Aviation Organization identifier. Links to www.planespotters.net</string>
</property>
</column>
<column>
@ -500,6 +597,70 @@
<string>Commercial flight number. Links to www.flightradar24.com</string>
</property>
</column>
<column>
<property name="text">
<string>Aircraft</string>
</property>
<property name="toolTip">
<string>Aircraft model</string>
</property>
</column>
<column>
<property name="text">
<string>Airline</string>
</property>
<property name="toolTip">
<string>Airline logo</string>
</property>
</column>
<column>
<property name="text">
<string>Alt (ft)</string>
</property>
<property name="toolTip">
<string>Altitude in feet or metres</string>
</property>
</column>
<column>
<property name="text">
<string>Spd (kn)</string>
</property>
<property name="toolTip">
<string>Speed in knots or kilometres per hour</string>
</property>
</column>
<column>
<property name="text">
<string>Hd (°)</string>
</property>
<property name="toolTip">
<string>Aircraft heading in degrees</string>
</property>
</column>
<column>
<property name="text">
<string>VR (ft/m)</string>
</property>
<property name="toolTip">
<string>Vertical climb rate in feet per minute or metres per second</string>
</property>
</column>
<column>
<property name="text">
<string>D (km)</string>
</property>
<property name="toolTip">
<string>Range or distance of aircraft from home location</string>
</property>
</column>
<column>
<property name="text">
<string>Az/El (°)</string>
</property>
<property name="toolTip">
<string>Azimuth and elevation to aircraft from My Position. Double click to set as target.</string>
</property>
</column>
<column>
<property name="text">
<string>Lat (°)</string>
@ -518,39 +679,7 @@
</column>
<column>
<property name="text">
<string>Alt (ft)</string>
</property>
<property name="toolTip">
<string>Altitude in feet</string>
</property>
</column>
<column>
<property name="text">
<string>Sp (kn)</string>
</property>
<property name="toolTip">
<string>Speed in knots</string>
</property>
</column>
<column>
<property name="text">
<string>Hd (°)</string>
</property>
<property name="toolTip">
<string>Aircraft heading in degrees</string>
</property>
</column>
<column>
<property name="text">
<string>Climb</string>
</property>
<property name="toolTip">
<string>Climbing rate in feet per minute</string>
</property>
</column>
<column>
<property name="text">
<string>Category</string>
<string>Cat</string>
</property>
<property name="toolTip">
<string>Aircraft standard category</string>
@ -561,23 +690,55 @@
<string>Status</string>
</property>
<property name="toolTip">
<string>Aircraft standard status</string>
<string>Aircraft emergency status</string>
</property>
</column>
<column>
<property name="text">
<string>D (km)</string>
<string>Reg</string>
</property>
<property name="toolTip">
<string>Range or distance of aircraft to home location</string>
<string>Aircraft registration</string>
</property>
</column>
<column>
<property name="text">
<string>Az/El (°)</string>
<string>Country</string>
</property>
<property name="toolTip">
<string>Aircraft azimuth and elevation from home point in degrees</string>
<string>Country of registration</string>
</property>
</column>
<column>
<property name="text">
<string>Registered</string>
</property>
<property name="toolTip">
<string>Date aircraft was registered</string>
</property>
</column>
<column>
<property name="text">
<string>Manufacturer</string>
</property>
<property name="toolTip">
<string>Aircraft manufacturer</string>
</property>
</column>
<column>
<property name="text">
<string>Owner</string>
</property>
<property name="toolTip">
<string>Owner of the aircraft</string>
</property>
</column>
<column>
<property name="text">
<string>Operator</string>
</property>
<property name="toolTip">
<string>Aircraft operator ICAO code</string>
</property>
</column>
<column>
@ -585,7 +746,7 @@
<string>Updated</string>
</property>
<property name="toolTip">
<string>Last time updated</string>
<string>Time when the last ADS-B message from this aircraft was received.</string>
</property>
</column>
<column>
@ -593,7 +754,7 @@
<string>Frames</string>
</property>
<property name="toolTip">
<string>Number of frames received</string>
<string>Number of ADS-B frames received from this aircraft</string>
</property>
</column>
<column>
@ -601,7 +762,7 @@
<string>Correlation</string>
</property>
<property name="toolTip">
<string>Correlation power min/avg/max in dB</string>
<string>Correlation values for received frames. min/avg/max</string>
</property>
</column>
</widget>
@ -612,7 +773,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>420</y>
<y>450</y>
<width>331</width>
<height>581</height>
</rect>
@ -653,18 +814,18 @@
<property name="minimumSize">
<size>
<width>100</width>
<height>400</height>
<height>500</height>
</size>
</property>
<property name="toolTip">
<string>Aircraft location map</string>
<string>Aircraft map</string>
</property>
<property name="resizeMode">
<enum>QQuickWidget::SizeRootObjectToView</enum>
</property>
<property name="source">
<url>
<string>qrc:/map.qml</string>
<string/>
</url>
</property>
</widget>
@ -702,14 +863,10 @@
<tabstop>rfBW</tabstop>
<tabstop>spb</tabstop>
<tabstop>threshold</tabstop>
<tabstop>beastEnabled</tabstop>
<tabstop>host</tabstop>
<tabstop>port</tabstop>
<tabstop>adsbData</tabstop>
<tabstop>map</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -19,3 +19,4 @@
#include "adsbdemodreport.h"
MESSAGE_CLASS_DEFINITION(ADSBDemodReport::MsgReportADSB, Message)
MESSAGE_CLASS_DEFINITION(ADSBDemodReport::MsgReportDemodStats, Message)

View File

@ -25,6 +25,7 @@
#include "dsp/dsptypes.h"
#include "util/message.h"
#include "adsbdemodstats.h"
class ADSBDemodReport : public QObject
{
@ -36,30 +37,48 @@ public:
public:
QByteArray getData() const { return m_data; }
QDateTime getDateTime() const { return m_dateTime; }
float getPreambleCorrelationOnes() const { return m_premableCorrelationOnes; }
float getPreambleCorrelationZeros() const { return m_premableCorrelationZeros; }
float getPreambleCorrelation() const { return m_preambleCorrelation; }
static MsgReportADSB* create(QByteArray data, float premableCorrelationOnes, float premableCorrelationZeros)
static MsgReportADSB* create(QByteArray data, float preambleCorrelation)
{
return new MsgReportADSB(data, premableCorrelationOnes, premableCorrelationZeros);
return new MsgReportADSB(data, preambleCorrelation);
}
private:
QByteArray m_data;
QDateTime m_dateTime;
float m_premableCorrelationOnes;
float m_premableCorrelationZeros;
float m_preambleCorrelation;
MsgReportADSB(QByteArray data, float premableCorrelationOnes, float premableCorrelationZeros) :
MsgReportADSB(QByteArray data, float preambleCorrelation) :
Message(),
m_data(data),
m_premableCorrelationOnes(premableCorrelationOnes),
m_premableCorrelationZeros(premableCorrelationZeros)
m_preambleCorrelation(preambleCorrelation)
{
m_dateTime = QDateTime::currentDateTime();
}
};
class MsgReportDemodStats : public Message {
MESSAGE_CLASS_DECLARATION
public:
ADSBDemodStats getDemodStats() const { return m_demodStats; }
static MsgReportDemodStats* create(ADSBDemodStats demodStats)
{
return new MsgReportDemodStats(demodStats);
}
private:
ADSBDemodStats m_demodStats;
MsgReportDemodStats(ADSBDemodStats demodStats) :
Message(),
m_demodStats(demodStats)
{
}
};
public:
ADSBDemodReport() {}
~ADSBDemodReport() {}

View File

@ -34,12 +34,13 @@ void ADSBDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 2*1300000;
m_correlationThreshold = -50.0f;
m_correlationThreshold = -20.0f;
m_samplesPerBit = 4;
m_removeTimeout = 60;
m_beastEnabled = false;
m_beastHost = "feed.adsbexchange.com";
m_beastPort = 30005;
m_feedEnabled = false;
m_feedHost = "feed.adsbexchange.com";
m_feedPort = 30005;
m_feedFormat = BeastBinary;
m_rgbColor = QColor(255, 0, 0).rgb();
m_title = "ADS-B Demodulator";
m_streamIndex = 0;
@ -48,6 +49,22 @@ void ADSBDemodSettings::resetToDefaults()
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_airportRange = 100;
m_airportMinimumSize = AirportType::Medium;
m_displayHeliports = false;
m_flightPaths = true;
m_siUnits = false;
m_tableFontName = "Liberation Sans";
m_tableFontSize = 9;
m_displayDemodStats = true;
m_correlateFullPreamble = true;
m_demodModeS = false;
m_deviceIndex = -1;
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray ADSBDemodSettings::serialize() const
@ -58,9 +75,9 @@ QByteArray ADSBDemodSettings::serialize() const
s.writeReal(3, m_correlationThreshold);
s.writeS32(4, m_samplesPerBit);
s.writeS32(5, m_removeTimeout);
s.writeBool(6, m_beastEnabled);
s.writeString(7, m_beastHost);
s.writeU32(8, m_beastPort);
s.writeBool(6, m_feedEnabled);
s.writeString(7, m_feedHost);
s.writeU32(8, m_feedPort);
s.writeU32(9, m_rgbColor);
if (m_channelMarker) {
@ -74,6 +91,24 @@ QByteArray ADSBDemodSettings::serialize() const
s.writeU32(16, m_reverseAPIChannelIndex);
s.writeS32(17, m_streamIndex);
s.writeFloat(18, m_airportRange);
s.writeS32(19, (int)m_airportMinimumSize);
s.writeBool(20, m_displayHeliports);
s.writeBool(21, m_flightPaths);
s.writeS32(22, m_deviceIndex);
s.writeBool(23, m_siUnits);
s.writeS32(24, (int)m_feedFormat);
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);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
s.writeS32(200 + i, m_columnSizes[i]);
return s.final();
}
@ -102,16 +137,16 @@ 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, -50.0f);
d.readReal(3, &m_correlationThreshold, 0.0f);
d.readS32(4, &m_samplesPerBit, 4);
d.readS32(5, &m_removeTimeout, 60);
d.readBool(6, &m_beastEnabled, false);
d.readString(7, &m_beastHost, "feed.adsbexchange.com");
d.readBool(6, &m_feedEnabled, false);
d.readString(7, &m_feedHost, "feed.adsbexchange.com");
d.readU32(8, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_beastPort = utmp;
m_feedPort = utmp;
} else {
m_beastPort = 30005;
m_feedPort = 30005;
}
d.readU32(9, &m_rgbColor, QColor(255, 0, 0).rgb());
@ -132,6 +167,24 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readS32(17, &m_streamIndex, 0);
d.readFloat(18, &m_airportRange, 100);
d.readS32(19, (int *)&m_airportMinimumSize, AirportType::Medium);
d.readBool(20, &m_displayHeliports, false);
d.readBool(21, &m_flightPaths, true);
d.readS32(22, &m_deviceIndex, -1);
d.readBool(23, &m_siUnits, false);
d.readS32(24, (int *) &m_feedFormat, BeastBinary);
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, false);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i);
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
d.readS32(200 + i, &m_columnSizes[i], -1);
return true;
}
else

View File

@ -26,6 +26,9 @@
class Serializable;
// Number of columns in the table
#define ADSBDEMOD_COLUMNS 23
struct ADSBDemodSettings
{
int32_t m_inputFrequencyOffset;
@ -33,9 +36,13 @@ struct ADSBDemodSettings
Real m_correlationThreshold; //!< Correlation power threshold in dB
int m_samplesPerBit;
int m_removeTimeout; //!< Time in seconds before removing an aircraft, unless a new frame is received
bool m_beastEnabled;
QString m_beastHost;
uint16_t m_beastPort;
bool m_feedEnabled;
QString m_feedHost;
uint16_t m_feedPort;
enum FeedFormat {
BeastBinary,
BeastHex
} m_feedFormat;
quint32 m_rgbColor;
QString m_title;
@ -45,9 +52,28 @@ struct ADSBDemodSettings
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_columnIndexes[ADSBDEMOD_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[ADSBDEMOD_COLUMNS]; //!< Size of the coumns in the table
Serializable *m_channelMarker;
float m_airportRange; //!< How far away should we display airports (nm)
enum AirportType {
Small,
Medium,
Large,
Heliport
} m_airportMinimumSize; //!< What's the minimum size airport that should be displayed
bool m_displayHeliports; //!< Whether to display heliports on the map
bool m_flightPaths; //!< Whether to display flight paths
bool m_siUnits; //!< Uses m,kph rather than ft/knts
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
int m_deviceIndex; //!< Device to set to ATC frequencies
ADSBDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }

View File

@ -16,16 +16,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <complex.h>
#include <cmath>
#include <QTime>
#include <QDebug>
#include "util/stepfunctions.h"
#include "util/db.h"
#include "util/crc.h"
#include "audio/audiooutput.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
@ -34,219 +29,189 @@
#include "adsbdemodreport.h"
#include "adsbdemodsink.h"
#include "adsbdemodsinkworker.h"
#include "adsb.h"
ADSBDemodSink::ADSBDemodSink() :
m_channelSampleRate(6000000),
m_channelFrequencyOffset(0),
m_sampleIdx(0),
m_sampleCount(0),
m_skipCount(0),
m_correlationThresholdLinear(0.0),
m_magsq(0.0f),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToGUI(nullptr),
m_sampleBuffer(nullptr)
m_channelSampleRate(6000000),
m_channelFrequencyOffset(0),
m_feedTime(0.0),
m_sampleBuffer{nullptr, nullptr, nullptr},
m_worker(this),
m_writeBuffer(0),
m_writeIdx(0),
m_magsq(0.0f),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToGUI(nullptr)
{
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
for (int i = 0; i < m_buffers; i++)
m_bufferWrite[i].release(1);
m_bufferWrite[m_writeBuffer].acquire();
}
ADSBDemodSink::~ADSBDemodSink()
{
delete m_sampleBuffer;
for (int i = 0; i < m_buffers; i++)
delete m_sampleBuffer[i];
}
void ADSBDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
// Start timing how long we are in this function
m_startPoint = boost::chrono::steady_clock::now();
for (SampleVector::const_iterator it = begin; it != end; ++it)
// Optimise for common case, where no resampling or frequency offset
if ((m_interpolatorDistance == 1.0f) && (m_channelFrequencyOffset == 0))
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
/*
// SampleVector is vector of qint32 or qint16
// Use integer mul to save one FP conversion and it has lower latency
qint64 r = (qint64)it->real();
qint64 i = (qint64)it->imag();
qint64 magsqRaw = r*r + i*i;
Real magsq = (Real)((double)magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED));
processOneSample(magsq);
*/
Complex c(it->real(), it->imag());
Real magsq = complexMagSq(c);
processOneSample(magsq);
}
}
else
{
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
Complex ci;
c *= m_nco.nextIQ();
if (m_interpolatorDistance == 1.0f)
{
processOneSample(c);
}
else if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
if (m_interpolatorDistance == 1.0f)
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
processOneSample(complexMagSq(c));
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
else if (m_interpolatorDistance < 1.0f) // interpolate
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(complexMagSq(ci));
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(complexMagSq(ci));
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
// Calculate number of seconds in this function
boost::chrono::duration<double> sec = boost::chrono::steady_clock::now() - m_startPoint;
m_feedTime += sec.count();
}
void ADSBDemodSink::processOneSample(Complex &ci)
void ADSBDemodSink::processOneSample(Real magsq)
{
Real sample;
double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
m_movingAverage(magsq);
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
sample = magsq;
m_sampleBuffer[m_sampleCount] = sample;
m_sampleCount++;
// Do we have enough data for a frame
if ((m_sampleCount >= m_totalSamples) && (m_skipCount == 0))
m_sampleBuffer[m_writeBuffer][m_writeIdx] = magsq;
m_writeIdx++;
if (m_writeIdx >= m_bufferSize)
{
int startIdx = m_sampleCount - m_totalSamples;
m_bufferRead[m_writeBuffer].release();
// Correlate received signal with expected preamble
// chip+ indexes are 0, 2, 7, 9
// we correlate only over 6 symbols so that the number of zero chips is twice the
// number of one chips - empirically this is enough to get good correlation
Real premableCorrelationOnes = 0.0;
Real preambleCorrelationZeros = 0.0;
m_writeBuffer++;
if (m_writeBuffer >= m_buffers)
m_writeBuffer = 0;
for (int i = 0; i < m_samplesPerChip; i++)
{
premableCorrelationOnes += m_sampleBuffer[startIdx + 0*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 1*m_samplesPerChip + i];
// 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();
premableCorrelationOnes += m_sampleBuffer[startIdx + 2*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 3*m_samplesPerChip + i];
if (m_worker.isRunning())
m_bufferWrite[m_writeBuffer].acquire();
preambleCorrelationZeros += m_sampleBuffer[startIdx + 4*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 5*m_samplesPerChip + i];
m_startPoint = boost::chrono::steady_clock::now();
preambleCorrelationZeros += m_sampleBuffer[startIdx + 6*m_samplesPerChip + i];
premableCorrelationOnes += m_sampleBuffer[startIdx + 7*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 8*m_samplesPerChip + i];
premableCorrelationOnes += m_sampleBuffer[startIdx + 9*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 10*m_samplesPerChip + i];
preambleCorrelationZeros += m_sampleBuffer[startIdx + 11*m_samplesPerChip + i];
}
// If the correlation is exactly 0, it's probably no signal
if ((premableCorrelationOnes > m_correlationThresholdLinear) &&
(preambleCorrelationZeros < 2.0*m_correlationThresholdLinear) &&
(premableCorrelationOnes != 0.0f))
{
// Skip over preamble
startIdx += m_settings.m_samplesPerBit*ADS_B_PREAMBLE_BITS;
// Demodulate waveform to bytes
unsigned char data[ADS_B_ES_BYTES];
int byteIdx = 0;
int currentBit;
unsigned char currentByte = 0;
bool adsbOnly = true;
int df;
for (int bit = 0; bit < ADS_B_ES_BITS; bit++)
{
// PPM (Pulse position modulation) - Each bit spreads to two chips, 1->10, 0->01
// Determine if bit is 1 or 0, by seeing which chip has largest combined energy over the sampling period
Real oneSum = 0.0f;
Real zeroSum = 0.0f;
for (int i = 0; i < m_samplesPerChip; i++)
{
oneSum += m_sampleBuffer[startIdx+i];
zeroSum += m_sampleBuffer[startIdx+m_samplesPerChip+i];
}
currentBit = oneSum > zeroSum;
startIdx += m_settings.m_samplesPerBit;
// Convert bit to bytes - MSB first
currentByte |= currentBit << (7-(bit & 0x7));
if ((bit & 0x7) == 0x7)
{
data[byteIdx++] = currentByte;
currentByte = 0;
// Don't try to demodulate any further, if this isn't an ADS-B frame
// to help reduce processing overhead
if (adsbOnly && (bit == 7))
{
df = ((data[0] >> 3) & ADS_B_DF_MASK);
if ((df != 17) && (df != 18))
break;
}
}
}
// Is ADS-B?
df = ((data[0] >> 3) & ADS_B_DF_MASK);
if ((df == 17) || (df == 18))
{
crcadsb crc;
//int icao = (data[1] << 16) | (data[2] << 8) | data[3]; // ICAO aircraft address
int parity = (data[11] << 16) | (data[12] << 8) | data[13]; // Parity / CRC
crc.calculate(data, ADS_B_ES_BYTES-3);
if (parity == crc.get())
{
// Got a valid frame
// Don't try to re-demodulate the same frame
m_skipCount = (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*m_samplesPerChip;
// Pass to GUI
if (getMessageQueueToGUI())
{
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
premableCorrelationOnes,
preambleCorrelationZeros/2.0);
getMessageQueueToGUI()->push(msg);
}
// Pass to worker
if (getMessageQueueToWorker())
{
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
premableCorrelationOnes,
preambleCorrelationZeros/2.0);
getMessageQueueToWorker()->push(msg);
}
}
}
}
m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer
}
if (m_skipCount > 0)
m_skipCount--;
if (m_sampleCount >= 2*m_totalSamples)
}
void ADSBDemodSink::startWorker()
{
qDebug() << "ADSBDemodSink::startWorker";
if (!m_worker.isRunning())
m_worker.start();
}
void ADSBDemodSink::stopWorker()
{
if (m_worker.isRunning())
{
// Copy second half of buffer to first
memcpy(&m_sampleBuffer[0], &m_sampleBuffer[m_totalSamples], m_totalSamples*sizeof(Real));
m_sampleCount = m_totalSamples;
qDebug() << "ADSBDemodSink::stopWorker: Stopping worker";
m_worker.requestInterruption();
// Worker may be blocked waiting for a buffer
for (int i = 0; i < m_buffers; i++)
{
if (m_bufferRead[i].available() == 0)
m_bufferRead[i].release(1);
}
m_worker.wait();
// If this is called from ADSBDemod, we need to also
// make sure baseband sink thread isnt blocked in processOneSample
for (int i = 0; i < m_buffers; i++)
{
if (m_bufferWrite[i].available() == 0)
m_bufferWrite[i].release(1);
}
qDebug() << "ADSBDemodSink::stopWorker: Worker stopped";
}
m_sampleIdx++;
}
void ADSBDemodSink::init(int samplesPerBit)
{
if (m_sampleBuffer)
delete m_sampleBuffer;
bool restart = m_worker.isRunning();
if (restart)
{
// Stop worker as we're going to delete the buffers
stopWorker();
}
// Reset state of semaphores
for (int i = 0; i < m_buffers; i++)
{
m_bufferWrite[i].acquire(m_bufferWrite[i].available());
m_bufferWrite[i].release(1);
m_bufferRead[i].acquire(m_bufferRead[i].available());
}
m_writeBuffer = 0;
m_bufferWrite[m_writeBuffer].acquire();
m_totalSamples = samplesPerBit*(ADS_B_PREAMBLE_BITS+ADS_B_ES_BITS);
for (int i = 0; i < m_buffers; i++)
{
if (m_sampleBuffer[i])
delete m_sampleBuffer[i];
}
m_samplesPerFrame = samplesPerBit*(ADS_B_PREAMBLE_BITS+ADS_B_ES_BITS);
m_samplesPerChip = samplesPerBit/ADS_B_CHIPS_PER_BIT;
m_writeIdx = m_samplesPerFrame - 1; // Leave space for copying samples from previous buffer
m_sampleBuffer = new Real[2*m_totalSamples];
for (int i = 0; i < m_buffers; i++)
m_sampleBuffer[i] = new Real[m_bufferSize];
if (restart)
startWorker();
}
void ADSBDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
@ -278,6 +243,8 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, bool force)
<< " 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;
@ -294,9 +261,10 @@ void ADSBDemodSink::applySettings(const ADSBDemodSettings& settings, bool force)
init(settings.m_samplesPerBit);
}
if ((settings.m_correlationThreshold != m_settings.m_correlationThreshold) || force) {
m_correlationThresholdLinear = CalcDb::powerFromdB(m_settings.m_correlationThreshold);
}
// Forward to worker
ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker *msg = ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker::create(
settings, force);
m_worker.getInputMessageQueue()->push(msg);
m_settings = settings;
}

View File

@ -19,7 +19,8 @@
#ifndef INCLUDE_ADSBDEMODSINK_H
#define INCLUDE_ADSBDEMODSINK_H
#include <vector>
#define BOOST_CHRONO_HEADER_ONLY
#include <boost/chrono/chrono.hpp>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
@ -27,6 +28,8 @@
#include "util/movingaverage.h"
#include "adsbdemodsettings.h"
#include "adsbdemodstats.h"
#include "adsbdemodsinkworker.h"
class ADSBDemodSink : public ChannelSampleSink {
public:
@ -53,13 +56,16 @@ public:
m_magsqCount = 0;
}
void init(int samplesPerBit);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const ADSBDemodSettings& settings, bool force = false);
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_messageQueueToGUI = messageQueue; }
void setMessageQueueToWorker(MessageQueue *messageQueue) { m_messageQueueToWorker = messageQueue; }
void startWorker();
void stopWorker();
private:
friend ADSBDemodSinkWorker;
struct MagSqLevelsStore
{
MagSqLevelsStore() :
@ -78,14 +84,26 @@ private:
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
int m_sampleIdx;
int m_sampleCount;
int m_skipCount; // Samples to skip, because we've already received a frame
Real *m_sampleBuffer;
int m_totalSamples; // These two values are derived from samplesPerBit
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
// be processed contiguously
const int m_buffers = 3;
const int m_bufferSize = 200000;
Real *m_sampleBuffer[3]; //!< Each buffer is m_bufferSize samples
QSemaphore m_bufferWrite[3]; //!< Sempahore to control write access to the buffers
QSemaphore m_bufferRead[3]; //!< Sempahore to control read access from the buffers
ADSBDemodSinkWorker m_worker; //!< Worker thread that does the actual demodulation
int m_writeBuffer; //!< Which of the 3 buffers we're writing in to
int m_writeIdx; //!< Index to to current write buffer
// These values are derived from samplesPerBit
int m_samplesPerFrame; //!< Including preamble
int m_samplesPerChip;
double m_correlationThresholdLinear; //!< settings m_correlationThreshold is in dB. Linear value is calculated once.
double m_magsq; //!< displayed averaged value
double m_magsqSum;
@ -93,12 +111,16 @@ private:
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MovingAverageUtil<Real, double, 32> m_movingAverage;
MessageQueue *m_messageQueueToGUI;
MessageQueue *m_messageQueueToWorker;
void processOneSample(Complex &ci);
void init(int samplesPerBit);
Real inline complexMagSq(Complex& ci)
{
double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();
return (Real)(magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED));
}
void processOneSample(Real magsq);
MessageQueue *getMessageQueueToGUI() { return m_messageQueueToGUI; }
MessageQueue *getMessageQueueToWorker() { return m_messageQueueToWorker; }
};

View File

@ -0,0 +1,349 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// 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/stepfunctions.h"
#include "util/db.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "adsbdemodreport.h"
#include "adsbdemodsink.h"
#include "adsbdemodsinkworker.h"
#include "adsbdemodsettings.h"
#include "adsb.h"
MESSAGE_CLASS_DEFINITION(ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker, Message)
void ADSBDemodSinkWorker::run()
{
int readBuffer = 0;
// Acquire first buffer
m_sink->m_bufferRead[readBuffer].acquire();
// Start recording how much time is spent processing in this method
boost::chrono::steady_clock::time_point startPoint = boost::chrono::steady_clock::now();
// Check for updated settings
handleInputMessages();
// samplesPerBit is only changed when the thread is stopped
int samplesPerBit = m_settings.m_samplesPerBit;
int samplesPerFrame = samplesPerBit*(ADS_B_PREAMBLE_BITS+ADS_B_ES_BITS);
int samplesPerChip = samplesPerBit/ADS_B_CHIPS_PER_BIT;
qDebug() << "ADSBDemodSinkWorker:: running with"
<< " samplesPerFrame: " << samplesPerFrame
<< " samplesPerChip: " << samplesPerChip
<< " samplesPerBit: " << samplesPerBit
<< " correlateFullPreamble: " << m_settings.m_correlateFullPreamble
<< " correlationZerosScale: " << m_correlationZerosScale
<< " correlationThreshold: " << m_settings.m_correlationThreshold;
int readIdx = m_sink->m_samplesPerFrame - 1;
int cnt = 0;
while (true)
{
int startIdx = readIdx;
// 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++)
{
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 + 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];
}
}
// Use the difference rather than absolute value, as we don't care how powerful the signal
// is, just whether there is a good correlation with the preamble. The absolute value varies
// too much with different radios, AGC settings and and the noise floor is not constant
// (E.g: it's quite possible to receive multiple frames simultaneously, so we don't
// want a maximum threshold for the zeros, as a weaker signal may transmit 1s in
// a stronger signals 0 chip position. Similarly a strong signal in an adjacent
// channel may casue AGC to reduce gain, reducing the ampltiude of an otherwise
// strong signal, as well as the noise floor)
// The scale factors account for different values of samplesPerBit and the different
// number of zeros and ones in the preamble
// If the sum of ones is exactly 0, it's probably no signal
Real preambleCorrelation = (preambleCorrelationOnes * m_correlationZerosScale)
- (preambleCorrelationZeros * m_correlationOnesScale);
if ((preambleCorrelation > m_correlationThresholdLinear) && (preambleCorrelationOnes != 0.0f))
{
m_demodStats.m_correlatorMatches++;
// Skip over preamble
startIdx += samplesPerBit*ADS_B_PREAMBLE_BITS;
// Demodulate waveform to bytes
unsigned char data[ADS_B_ES_BYTES];
int byteIdx = 0;
int currentBit;
unsigned char currentByte = 0;
int df;
for (int bit = 0; bit < ADS_B_ES_BITS; bit++)
{
// PPM (Pulse position modulation) - Each bit spreads to two chips, 1->10, 0->01
// Determine if bit is 1 or 0, by seeing which chip has largest combined energy over the sampling period
Real oneSum = 0.0f;
Real zeroSum = 0.0f;
for (int i = 0; i < samplesPerChip; i++)
{
oneSum += m_sink->m_sampleBuffer[readBuffer][startIdx+i];
zeroSum += m_sink->m_sampleBuffer[readBuffer][startIdx+samplesPerChip+i];
}
currentBit = oneSum > zeroSum;
startIdx += samplesPerBit;
// Convert bit to bytes - MSB first
currentByte |= currentBit << (7-(bit & 0x7));
if ((bit & 0x7) == 0x7)
{
data[byteIdx++] = currentByte;
currentByte = 0;
// Don't try to demodulate any further, if this isn't an ADS-B frame
// to help reduce processing overhead
if (!m_settings.m_demodModeS && (bit == 7))
{
df = ((data[0] >> 3) & ADS_B_DF_MASK);
if ((df != 17) && (df != 18))
break;
}
}
}
// Is ADS-B?
df = ((data[0] >> 3) & ADS_B_DF_MASK);
if ((df == 17) || (df == 18))
{
m_crc.init();
int parity = (data[11] << 16) | (data[12] << 8) | data[13]; // Parity / CRC
m_crc.calculate(data, ADS_B_ES_BYTES-3);
if (parity == m_crc.get())
{
// Got a valid frame
m_demodStats.m_adsbFrames++;
// 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())
{
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
preambleCorrelation);
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, sizeof(data)),
preambleCorrelation);
m_sink->getMessageQueueToWorker()->push(msg);
}
}
else
m_demodStats.m_crcFails++;
}
else if (m_settings.m_demodModeS)
{
int bytes;
m_crc.init();
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)
{
int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1];
m_crc.calculate(data, bytes-3);
int crc = m_crc.get();
// For DF11, the last 7 bits may have an address/interogration indentifier (II)
// XORed in, so we ignore those bits
if ((parity == crc) || ((df == 11) && (parity & 0xffff80) == (crc & 0xffff80)))
{
m_demodStats.m_modesFrames++;
// Pass to worker to feed to other servers
if (m_sink->getMessageQueueToWorker())
{
ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create(
QByteArray((char*)data, sizeof(data)),
preambleCorrelation);
m_sink->getMessageQueueToWorker()->push(msg);
}
}
else
m_demodStats.m_crcFails++;
}
else
m_demodStats.m_typeFails++;
}
else
m_demodStats.m_typeFails++;
}
readIdx++;
if (readIdx > m_sink->m_bufferSize - samplesPerFrame)
{
int nextBuffer = readBuffer+1;
if (nextBuffer >= m_sink->m_buffers)
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;
// Send stats to GUI
if (m_sink->getMessageQueueToGUI())
{
ADSBDemodReport::MsgReportDemodStats *msg = ADSBDemodReport::MsgReportDemodStats::create(m_demodStats);
m_sink->getMessageQueueToGUI()->push(msg);
}
if (!isInterruptionRequested())
{
// Get next buffer
m_sink->m_bufferRead[nextBuffer].acquire();
// Check for updated settings
handleInputMessages();
// Resume timing how long we are processing
startPoint = boost::chrono::steady_clock::now();
int samplesRemaining = m_sink->m_bufferSize - readIdx;
if (samplesRemaining > 0)
{
// Copy remaining samples, to start of next buffer
memcpy(&m_sink->m_sampleBuffer[nextBuffer][samplesPerFrame - 1 - samplesRemaining], &m_sink->m_sampleBuffer[readBuffer][readIdx], samplesRemaining*sizeof(Real));
readIdx = samplesPerFrame - 1 - samplesRemaining;
}
else
{
readIdx = samplesPerFrame - 1;
}
m_sink->m_bufferWrite[readBuffer].release();
readBuffer = nextBuffer;
}
else
{
// Use a break to avoid testing a condition in the main loop
break;
}
}
}
}
void ADSBDemodSinkWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (MsgConfigureADSBDemodSinkWorker::match(*message))
{
MsgConfigureADSBDemodSinkWorker* cfg = (MsgConfigureADSBDemodSinkWorker*)message;
ADSBDemodSettings settings = cfg->getSettings();
bool force = cfg->getForce();
if ((m_settings.m_correlationThreshold != settings.m_correlationThreshold) || force)
{
m_correlationThresholdLinear = CalcDb::powerFromdB(settings.m_correlationThreshold);
qDebug() << "m_correlationThresholdLinear: " << m_correlationThresholdLinear;
}
if (settings.m_correlateFullPreamble)
{
m_correlationOnesScale = 1.0f / settings.m_samplesPerBit;
m_correlationZerosScale = 2.0 * 1.0f / settings.m_samplesPerBit; // As 2x more 0s than 1s
}
else
{
m_correlationOnesScale = 1.0f / settings.m_samplesPerBit;
m_correlationZerosScale = 3.0 * 1.0f / settings.m_samplesPerBit; // As 3x more 0s than 1s
}
m_settings = settings;
delete message;
}
}
}

View File

@ -0,0 +1,82 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_ADSBDEMODSINKWORKER_H
#define INCLUDE_ADSBDEMODSINKWORKER_H
#include <QObject>
#include <QThread>
#include "dsp/dsptypes.h"
#include "util/crc.h"
#include "util/messagequeue.h"
#include "adsbdemodstats.h"
class ADSBDemodSink;
struct ADSBDemodSettings;
struct ADSBDemodStats;
class ADSBDemodSinkWorker : public QThread {
Q_OBJECT
public:
class MsgConfigureADSBDemodSinkWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const ADSBDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureADSBDemodSinkWorker* create(const ADSBDemodSettings& settings, bool force)
{
return new MsgConfigureADSBDemodSinkWorker(settings, force);
}
private:
ADSBDemodSettings m_settings;
bool m_force;
MsgConfigureADSBDemodSinkWorker(const ADSBDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
ADSBDemodSinkWorker(ADSBDemodSink *sink) :
m_sink(sink),
m_demodStats(),
m_correlationThresholdLinear(0.02f),
m_crc()
{
}
void run() override;
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
private:
void handleInputMessages();
MessageQueue m_inputMessageQueue;
ADSBDemodSettings m_settings;
ADSBDemodSink *m_sink;
ADSBDemodStats m_demodStats;
Real m_correlationThresholdLinear;
Real m_correlationZerosScale;
Real m_correlationOnesScale;
crcadsb m_crc; //!< Have as member to avoid recomputing LUT
};
#endif // INCLUDE_ADSBDEMODSINKWORKER_H

View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_ADSBDEMODSTATS_H
#define INCLUDE_ADSBDEMODSTATS_H
#include <QtCore>
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_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()
ADSBDemodStats() :
m_correlatorMatches(0),
m_adsbFrames(0),
m_modesFrames(0),
m_crcFails(0),
m_typeFails(0),
m_demodTime(0.0),
m_feedTime(0.0)
{
}
};
#endif // INCLUDE_ADSBDEMODSTATS_H

View File

@ -17,6 +17,7 @@
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QAbstractSocket>
#include <QTcpServer>
#include <QTcpSocket>
#include <QEventLoop>
@ -33,6 +34,13 @@ ADSBDemodWorker::ADSBDemodWorker() :
{
connect(&m_heartbeatTimer, SIGNAL(timeout()), this, SLOT(heartbeat()));
connect(&m_socket, SIGNAL(readyRead()),this, SLOT(recv()));
connect(&m_socket, SIGNAL(connected()), this, SLOT(connected()));
connect(&m_socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(&m_socket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &ADSBDemodWorker::errorOccurred);
#else
connect(&m_socket, &QAbstractSocket::errorOccurred, this, &ADSBDemodWorker::errorOccurred);
#endif
}
ADSBDemodWorker::~ADSBDemodWorker()
@ -88,7 +96,7 @@ bool ADSBDemodWorker::handleMessage(const Message& message)
else if (ADSBDemodReport::MsgReportADSB::match(message))
{
ADSBDemodReport::MsgReportADSB& report = (ADSBDemodReport::MsgReportADSB&) message;
handleADSB(report.getData(), report.getDateTime(), report.getPreambleCorrelationOnes());
handleADSB(report.getData(), report.getDateTime(), report.getPreambleCorrelation());
return true;
}
else
@ -100,26 +108,42 @@ bool ADSBDemodWorker::handleMessage(const Message& message)
void ADSBDemodWorker::applySettings(const ADSBDemodSettings& settings, bool force)
{
qDebug() << "ADSBDemodWorker::applySettings:"
<< " m_beastEnabled: " << settings.m_beastEnabled
<< " m_beastHost: " << settings.m_beastHost
<< " m_beastPort: " << settings.m_beastPort
<< " m_feedEnabled: " << settings.m_feedEnabled
<< " m_feedHost: " << settings.m_feedHost
<< " m_feedPort: " << settings.m_feedPort
<< " m_feedFormat: " << settings.m_feedFormat
<< " force: " << force;
if ((settings.m_beastEnabled != m_settings.m_beastEnabled)
|| (settings.m_beastHost != m_settings.m_beastHost)
|| (settings.m_beastPort != m_settings.m_beastPort) || force)
if ((settings.m_feedEnabled != m_settings.m_feedEnabled)
|| (settings.m_feedHost != m_settings.m_feedHost)
|| (settings.m_feedPort != m_settings.m_feedPort) || force)
{
// Close any existing connection
if (m_socket.isOpen())
m_socket.close();
// Open connection
if (settings.m_beastEnabled)
m_socket.connectToHost(settings.m_beastHost, settings.m_beastPort);
if (settings.m_feedEnabled)
m_socket.connectToHost(settings.m_feedHost, settings.m_feedPort);
}
m_settings = settings;
}
void ADSBDemodWorker::connected()
{
qDebug() << "ADSBDemodWorker::connected " << m_settings.m_feedHost;
}
void ADSBDemodWorker::disconnected()
{
qDebug() << "ADSBDemodWorker::disconnected";
}
void ADSBDemodWorker::errorOccurred(QAbstractSocket::SocketError socketError)
{
qDebug() << "ADSBDemodWorker::errorOccurred: " << socketError;
}
void ADSBDemodWorker::recv()
{
// Not expecting to receving anything from server
@ -129,11 +153,11 @@ void ADSBDemodWorker::recv()
void ADSBDemodWorker::send(const char *data, int length)
{
if (m_settings.m_beastEnabled)
if (m_settings.m_feedEnabled)
{
// Reopen connection if it was lost
if (!m_socket.isOpen())
m_socket.connectToHost(m_settings.m_beastHost, m_settings.m_beastPort);
m_socket.connectToHost(m_settings.m_feedHost, m_settings.m_feedPort);
// Send data
m_socket.write(data, length);
}
@ -153,40 +177,48 @@ char *ADSBDemodWorker::escape(char *p, char c)
// See: https://wiki.jetvision.de/wiki/Mode-S_Beast:Data_Output_Formats
void ADSBDemodWorker::handleADSB(QByteArray data, const QDateTime dateTime, float correlation)
{
char beastBinary[2+6*2+1*2+14*2];
int length;
char *p = beastBinary;
qint64 timestamp;
unsigned char signalStrength;
if (m_settings.m_feedFormat == ADSBDemodSettings::BeastBinary)
{
char beastBinary[2+6*2+1*2+14*2];
int length;
char *p = beastBinary;
qint64 timestamp;
unsigned char signalStrength;
timestamp = dateTime.toMSecsSinceEpoch();
timestamp = dateTime.toMSecsSinceEpoch();
if (correlation > 255)
signalStrength = 255;
if (correlation < 1)
signalStrength = 1;
else
signalStrength = (unsigned char)correlation;
if (correlation > 255)
signalStrength = 255;
if (correlation < 1)
signalStrength = 1;
else
signalStrength = (unsigned char)correlation;
*p++ = BEAST_ESC;
*p++ = '3'; // Mode-S long
*p++ = BEAST_ESC;
*p++ = '3'; // Mode-S long
p = escape(p, timestamp >> 56); // Big-endian timestamp
p = escape(p, timestamp >> 48);
p = escape(p, timestamp >> 32);
p = escape(p, timestamp >> 24);
p = escape(p, timestamp >> 16);
p = escape(p, timestamp >> 8);
p = escape(p, timestamp);
p = escape(p, timestamp >> 56); // Big-endian timestamp
p = escape(p, timestamp >> 48);
p = escape(p, timestamp >> 32);
p = escape(p, timestamp >> 24);
p = escape(p, timestamp >> 16);
p = escape(p, timestamp >> 8);
p = escape(p, timestamp);
p = escape(p, signalStrength); // Signal strength
p = escape(p, signalStrength); // Signal strength
for (int i = 0; i < data.length(); i++) // ADS-B data
p = escape(p, data[i]);
for (int i = 0; i < data.length(); i++) // ADS-B data
p = escape(p, data[i]);
length = p - beastBinary;
length = p - beastBinary;
send(beastBinary, length);
send(beastBinary, length);
}
else if (m_settings.m_feedFormat == ADSBDemodSettings::BeastHex)
{
QString beastHex = "*" + data.toHex() + ";\n";
send(beastHex.toUtf8(), beastHex.size());
}
}
// Periodically send heartbeat to keep connection alive

View File

@ -80,6 +80,9 @@ private:
private slots:
void handleInputMessages();
void connected();
void disconnected();
void errorOccurred(QAbstractSocket::SocketError socketError);
void recv();
void heartbeat();
};

View File

@ -0,0 +1,758 @@
<RCC>
<qresource prefix="/">
<file>airlinelogos/5AH.bmp</file>
<file>airlinelogos/AAF.bmp</file>
<file>airlinelogos/AAH.bmp</file>
<file>airlinelogos/AAL.bmp</file>
<file>airlinelogos/AAR.bmp</file>
<file>airlinelogos/AAW.bmp</file>
<file>airlinelogos/AAY.bmp</file>
<file>airlinelogos/AAZ.bmp</file>
<file>airlinelogos/ABB.bmp</file>
<file>airlinelogos/ABD.bmp</file>
<file>airlinelogos/ABG.bmp</file>
<file>airlinelogos/ABL.bmp</file>
<file>airlinelogos/ABN.bmp</file>
<file>airlinelogos/ABP.bmp</file>
<file>airlinelogos/ABQ.bmp</file>
<file>airlinelogos/ABR.bmp</file>
<file>airlinelogos/ABS.bmp</file>
<file>airlinelogos/ABV.bmp</file>
<file>airlinelogos/ABW.bmp</file>
<file>airlinelogos/ABX.bmp</file>
<file>airlinelogos/ABY.bmp</file>
<file>airlinelogos/ACA.bmp</file>
<file>airlinelogos/ACG.bmp</file>
<file>airlinelogos/ACI.bmp</file>
<file>airlinelogos/ACV.bmp</file>
<file>airlinelogos/ADN.bmp</file>
<file>airlinelogos/ADY.bmp</file>
<file>airlinelogos/AEA.bmp</file>
<file>airlinelogos/AEE.bmp</file>
<file>airlinelogos/AEG.bmp</file>
<file>airlinelogos/AEH.bmp</file>
<file>airlinelogos/AEI.bmp</file>
<file>airlinelogos/AERORESCUE.bmp</file>
<file>airlinelogos/AFE.bmp</file>
<file>airlinelogos/AFG.bmp</file>
<file>airlinelogos/AFL.bmp</file>
<file>airlinelogos/AFR.bmp</file>
<file>airlinelogos/AFW.bmp</file>
<file>airlinelogos/AGF.bmp</file>
<file>airlinelogos/AGO.bmp</file>
<file>airlinelogos/AGU.bmp</file>
<file>airlinelogos/AHK.bmp</file>
<file>airlinelogos/AHO.bmp</file>
<file>airlinelogos/AHY.bmp</file>
<file>airlinelogos/AIB.bmp</file>
<file>airlinelogos/AIC.bmp</file>
<file>airlinelogos/AIE.bmp</file>
<file>airlinelogos/AIH.bmp</file>
<file>airlinelogos/AIRCOSTA.bmp</file>
<file>airlinelogos/AIZ.bmp</file>
<file>airlinelogos/AJA.bmp</file>
<file>airlinelogos/AJB.bmp</file>
<file>airlinelogos/AJD.bmp</file>
<file>airlinelogos/AJI.bmp</file>
<file>airlinelogos/AJK.bmp</file>
<file>airlinelogos/AJT.bmp</file>
<file>airlinelogos/AKC.bmp</file>
<file>airlinelogos/ALV.bmp</file>
<file>airlinelogos/ALW.bmp</file>
<file>airlinelogos/ALX.bmp</file>
<file>airlinelogos/ALY.bmp</file>
<file>airlinelogos/AMC.bmp</file>
<file>airlinelogos/AMU.bmp</file>
<file>airlinelogos/AMV.bmp</file>
<file>airlinelogos/AMX.bmp</file>
<file>airlinelogos/AMY.bmp</file>
<file>airlinelogos/ANA.bmp</file>
<file>airlinelogos/AND.bmp</file>
<file>airlinelogos/ANE.bmp</file>
<file>airlinelogos/ANG.bmp</file>
<file>airlinelogos/ANK.bmp</file>
<file>airlinelogos/ANO.bmp</file>
<file>airlinelogos/ANQ.bmp</file>
<file>airlinelogos/ANR.bmp</file>
<file>airlinelogos/ANS.bmp</file>
<file>airlinelogos/ANT.bmp</file>
<file>airlinelogos/ANZ.bmp</file>
<file>airlinelogos/AOJ.bmp</file>
<file>airlinelogos/APF.bmp</file>
<file>airlinelogos/APG.bmp</file>
<file>airlinelogos/APJ.bmp</file>
<file>airlinelogos/APK.bmp</file>
<file>airlinelogos/APZ.bmp</file>
<file>airlinelogos/ARA.bmp</file>
<file>airlinelogos/ARE.bmp</file>
<file>airlinelogos/ARG.bmp</file>
<file>airlinelogos/ARK.bmp</file>
<file>airlinelogos/ARN.bmp</file>
<file>airlinelogos/ARR.bmp</file>
<file>airlinelogos/ARU.bmp</file>
<file>airlinelogos/ARZ.bmp</file>
<file>airlinelogos/ASA.bmp</file>
<file>airlinelogos/ASB.bmp</file>
<file>airlinelogos/ASH.bmp</file>
<file>airlinelogos/ASL.bmp</file>
<file>airlinelogos/ASQ.bmp</file>
<file>airlinelogos/ASV.bmp</file>
<file>airlinelogos/ASY.bmp</file>
<file>airlinelogos/ATC.bmp</file>
<file>airlinelogos/ATG.bmp</file>
<file>airlinelogos/ATN.bmp</file>
<file>airlinelogos/ATR.bmp</file>
<file>airlinelogos/ATV.bmp</file>
<file>airlinelogos/ATW.bmp</file>
<file>airlinelogos/ATX.bmp</file>
<file>airlinelogos/AUA.bmp</file>
<file>airlinelogos/AUI.bmp</file>
<file>airlinelogos/AUL.bmp</file>
<file>airlinelogos/AUR.bmp</file>
<file>airlinelogos/AUS5.bmp</file>
<file>airlinelogos/AUT.bmp</file>
<file>airlinelogos/AVA.bmp</file>
<file>airlinelogos/AVJ.bmp</file>
<file>airlinelogos/AVN.bmp</file>
<file>airlinelogos/AVV.bmp</file>
<file>airlinelogos/AVW.bmp</file>
<file>airlinelogos/AWE.bmp</file>
<file>airlinelogos/AWG.bmp</file>
<file>airlinelogos/AWI.bmp</file>
<file>airlinelogos/AWK.bmp</file>
<file>airlinelogos/AWM.bmp</file>
<file>airlinelogos/AWT.bmp</file>
<file>airlinelogos/AXB.bmp</file>
<file>airlinelogos/AXE.bmp</file>
<file>airlinelogos/AXK.bmp</file>
<file>airlinelogos/AXM.bmp</file>
<file>airlinelogos/AXU.bmp</file>
<file>airlinelogos/AYG.bmp</file>
<file>airlinelogos/AYT.bmp</file>
<file>airlinelogos/AZA.bmp</file>
<file>airlinelogos/AZG.bmp</file>
<file>airlinelogos/AZI.bmp</file>
<file>airlinelogos/AZM.bmp</file>
<file>airlinelogos/AZN.bmp</file>
<file>airlinelogos/AZO.bmp</file>
<file>airlinelogos/AZQ.bmp</file>
<file>airlinelogos/AZU.bmp</file>
<file>airlinelogos/AZV.bmp</file>
<file>airlinelogos/AZW.bmp</file>
<file>airlinelogos/BAW.bmp</file>
<file>airlinelogos/BBC.bmp</file>
<file>airlinelogos/BBD.bmp</file>
<file>airlinelogos/BBG.bmp</file>
<file>airlinelogos/BCI.bmp</file>
<file>airlinelogos/BCY.bmp</file>
<file>airlinelogos/BDA.bmp</file>
<file>airlinelogos/BEE.bmp</file>
<file>airlinelogos/BEL.bmp</file>
<file>airlinelogos/BER.bmp</file>
<file>airlinelogos/BGA.bmp</file>
<file>airlinelogos/BGH.bmp</file>
<file>airlinelogos/BGL.bmp</file>
<file>airlinelogos/BGY.bmp</file>
<file>airlinelogos/BHA.bmp</file>
<file>airlinelogos/BHP.bmp</file>
<file>airlinelogos/BIE.bmp</file>
<file>airlinelogos/BLF.bmp</file>
<file>airlinelogos/BLX.bmp</file>
<file>airlinelogos/BMR.bmp</file>
<file>airlinelogos/BOE.bmp</file>
<file>airlinelogos/BON.bmp</file>
<file>airlinelogos/BOS.bmp</file>
<file>airlinelogos/BOT.bmp</file>
<file>airlinelogos/BOV.bmp</file>
<file>airlinelogos/BOX.bmp</file>
<file>airlinelogos/BQB.bmp</file>
<file>airlinelogos/BRINDABELLA.bmp</file>
<file>airlinelogos/BRJ.bmp</file>
<file>airlinelogos/BRQ.bmp</file>
<file>airlinelogos/BRU.bmp</file>
<file>airlinelogos/BRXb.bmp</file>
<file>airlinelogos/BSK.bmp</file>
<file>airlinelogos/BTI.bmp</file>
<file>airlinelogos/BTN.bmp</file>
<file>airlinelogos/BTQ.bmp</file>
<file>airlinelogos/BUC.bmp</file>
<file>airlinelogos/BUR.bmp</file>
<file>airlinelogos/BVR.bmp</file>
<file>airlinelogos/BXA.bmp</file>
<file>airlinelogos/BYR.bmp</file>
<file>airlinelogos/BZH.bmp</file>
<file>airlinelogos/CAD.bmp</file>
<file>airlinelogos/CAI.bmp</file>
<file>airlinelogos/CAO.bmp</file>
<file>airlinelogos/CAV.bmp</file>
<file>airlinelogos/CAY.bmp</file>
<file>airlinelogos/CBJ.bmp</file>
<file>airlinelogos/CCA.bmp</file>
<file>airlinelogos/CCD.bmp</file>
<file>airlinelogos/CCE.bmp</file>
<file>airlinelogos/CCM.bmp</file>
<file>airlinelogos/CDA.bmp</file>
<file>airlinelogos/CDC.bmp</file>
<file>airlinelogos/CDG.bmp</file>
<file>airlinelogos/CEB.bmp</file>
<file>airlinelogos/CEL.bmp</file>
<file>airlinelogos/CES.bmp</file>
<file>airlinelogos/CEY.bmp</file>
<file>airlinelogos/CFE.bmp</file>
<file>airlinelogos/CFG.bmp</file>
<file>airlinelogos/CGF.bmp</file>
<file>airlinelogos/CGH.bmp</file>
<file>airlinelogos/CGN.bmp</file>
<file>airlinelogos/CHB.bmp</file>
<file>airlinelogos/CHH.bmp</file>
<file>airlinelogos/CIM.bmp</file>
<file>airlinelogos/CJA.bmp</file>
<file>airlinelogos/CJC.bmp</file>
<file>airlinelogos/CJT.bmp</file>
<file>airlinelogos/CKK.bmp</file>
<file>airlinelogos/CKS.bmp</file>
<file>airlinelogos/CLG.bmp</file>
<file>airlinelogos/CLX.bmp</file>
<file>airlinelogos/CMM.bmp</file>
<file>airlinelogos/CND.bmp</file>
<file>airlinelogos/CNK.bmp</file>
<file>airlinelogos/CON1.bmp</file>
<file>airlinelogos/COT.bmp</file>
<file>airlinelogos/CPA.bmp</file>
<file>airlinelogos/CPN.bmp</file>
<file>airlinelogos/CPZ.bmp</file>
<file>airlinelogos/CQH.bmp</file>
<file>airlinelogos/CQN.bmp</file>
<file>airlinelogos/CRC.bmp</file>
<file>airlinelogos/CRL.bmp</file>
<file>airlinelogos/CRN.bmp</file>
<file>airlinelogos/CRO.bmp</file>
<file>airlinelogos/CRQ.bmp</file>
<file>airlinelogos/CRUZ.bmp</file>
<file>airlinelogos/CSA.bmp</file>
<file>airlinelogos/CSC.bmp</file>
<file>airlinelogos/CSH.bmp</file>
<file>airlinelogos/CSN.bmp</file>
<file>airlinelogos/CSS.bmp</file>
<file>airlinelogos/CSZ.bmp</file>
<file>airlinelogos/CTM.bmp</file>
<file>airlinelogos/CTN.bmp</file>
<file>airlinelogos/CUA.bmp</file>
<file>airlinelogos/CUB.bmp</file>
<file>airlinelogos/CVA.bmp</file>
<file>airlinelogos/CWC.bmp</file>
<file>airlinelogos/CXA.bmp</file>
<file>airlinelogos/CXB.bmp</file>
<file>airlinelogos/CXH.bmp</file>
<file>airlinelogos/CYL.bmp</file>
<file>airlinelogos/CYP.bmp</file>
<file>airlinelogos/CYZ.bmp</file>
<file>airlinelogos/DAC.bmp</file>
<file>airlinelogos/DAH.bmp</file>
<file>airlinelogos/DAL.bmp</file>
<file>airlinelogos/DAO.bmp</file>
<file>airlinelogos/DAP.bmp</file>
<file>airlinelogos/DCS.bmp</file>
<file>airlinelogos/DER.bmp</file>
<file>airlinelogos/DHL.bmp</file>
<file>airlinelogos/DHX.bmp</file>
<file>airlinelogos/DJT.bmp</file>
<file>airlinelogos/DJU.bmp</file>
<file>airlinelogos/DKH.bmp</file>
<file>airlinelogos/DLA.bmp</file>
<file>airlinelogos/DLH.bmp</file>
<file>airlinelogos/DNV.bmp</file>
<file>airlinelogos/DOB.bmp</file>
<file>airlinelogos/DQI.bmp</file>
<file>airlinelogos/DRK.bmp</file>
<file>airlinelogos/DRU.bmp</file>
<file>airlinelogos/DSM.bmp</file>
<file>airlinelogos/DTA.bmp</file>
<file>airlinelogos/DTH.bmp</file>
<file>airlinelogos/DTR.bmp</file>
<file>airlinelogos/DYA.bmp</file>
<file>airlinelogos/EAA.bmp</file>
<file>airlinelogos/EAL.bmp</file>
<file>airlinelogos/EAQ.bmp</file>
<file>airlinelogos/ECO.bmp</file>
<file>airlinelogos/EGF.bmp</file>
<file>airlinelogos/EKA.bmp</file>
<file>airlinelogos/ELL.bmp</file>
<file>airlinelogos/EMB.bmp</file>
<file>airlinelogos/ENJ.bmp</file>
<file>airlinelogos/ENT.bmp</file>
<file>airlinelogos/EPA.bmp</file>
<file>airlinelogos/ERT.bmp</file>
<file>airlinelogos/ESQ.bmp</file>
<file>airlinelogos/ETD.bmp</file>
<file>airlinelogos/ETH.bmp</file>
<file>airlinelogos/ETS.bmp</file>
<file>airlinelogos/EUG.bmp</file>
<file>airlinelogos/EVA.bmp</file>
<file>airlinelogos/EWG.bmp</file>
<file>airlinelogos/EXS.bmp</file>
<file>airlinelogos/EYT.bmp</file>
<file>airlinelogos/EZA.bmp</file>
<file>airlinelogos/EZD.bmp</file>
<file>airlinelogos/EZE.bmp</file>
<file>airlinelogos/EZY.bmp</file>
<file>airlinelogos/FAB.bmp</file>
<file>airlinelogos/FAG.bmp</file>
<file>airlinelogos/FAH.bmp</file>
<file>airlinelogos/FAT.bmp</file>
<file>airlinelogos/FBD.bmp</file>
<file>airlinelogos/FBR.bmp</file>
<file>airlinelogos/FCM.bmp</file>
<file>airlinelogos/FDB.bmp</file>
<file>airlinelogos/FDX.bmp</file>
<file>airlinelogos/FFT.bmp</file>
<file>airlinelogos/FFV.bmp</file>
<file>airlinelogos/FHY.bmp</file>
<file>airlinelogos/FIN.bmp</file>
<file>airlinelogos/FJA.bmp</file>
<file>airlinelogos/FJI.bmp</file>
<file>airlinelogos/FLE.bmp</file>
<file>airlinelogos/FLI.bmp</file>
<file>airlinelogos/FNA.bmp</file>
<file>airlinelogos/FPK.bmp</file>
<file>airlinelogos/FPO.bmp</file>
<file>airlinelogos/FPY.bmp</file>
<file>airlinelogos/FRF.bmp</file>
<file>airlinelogos/FSK.bmp</file>
<file>airlinelogos/FTZ.bmp</file>
<file>airlinelogos/FWI.bmp</file>
<file>airlinelogos/FZA.bmp</file>
<file>airlinelogos/FZW.bmp</file>
<file>airlinelogos/GAA.bmp</file>
<file>airlinelogos/GAI.bmp</file>
<file>airlinelogos/GBK.bmp</file>
<file>airlinelogos/GBQ.bmp</file>
<file>airlinelogos/GCR.bmp</file>
<file>airlinelogos/GDC.bmp</file>
<file>airlinelogos/GEA.bmp</file>
<file>airlinelogos/GEC.bmp</file>
<file>airlinelogos/GEO.bmp</file>
<file>airlinelogos/GFA.bmp</file>
<file>airlinelogos/GFG.bmp</file>
<file>airlinelogos/GGN.bmp</file>
<file>airlinelogos/GIA.bmp</file>
<file>airlinelogos/GLG.bmp</file>
<file>airlinelogos/GLJ.bmp</file>
<file>airlinelogos/GLO.bmp</file>
<file>airlinelogos/GLR.bmp</file>
<file>airlinelogos/GMI.bmp</file>
<file>airlinelogos/GMQ.bmp</file>
<file>airlinelogos/GOW.bmp</file>
<file>airlinelogos/GRL.bmp</file>
<file>airlinelogos/GTI.bmp</file>
<file>airlinelogos/GTV.bmp</file>
<file>airlinelogos/GUG.bmp</file>
<file>airlinelogos/GUY.bmp</file>
<file>airlinelogos/GWI.bmp</file>
<file>airlinelogos/GXL.bmp</file>
<file>airlinelogos/HAL.bmp</file>
<file>airlinelogos/HAT.bmp</file>
<file>airlinelogos/HBH.bmp</file>
<file>airlinelogos/HCC.bmp</file>
<file>airlinelogos/HCV.bmp</file>
<file>airlinelogos/HLX.bmp</file>
<file>airlinelogos/HOP.bmp</file>
<file>airlinelogos/HRV.bmp</file>
<file>airlinelogos/HUN.bmp</file>
<file>airlinelogos/HVN.bmp</file>
<file>airlinelogos/HVY.bmp</file>
<file>airlinelogos/HXA.bmp</file>
<file>airlinelogos/IAD.bmp</file>
<file>airlinelogos/IAW.bmp</file>
<file>airlinelogos/IBB.bmp</file>
<file>airlinelogos/IBE.bmp</file>
<file>airlinelogos/IBS.bmp</file>
<file>airlinelogos/ICE.bmp</file>
<file>airlinelogos/IFC.bmp</file>
<file>airlinelogos/IGA.bmp</file>
<file>airlinelogos/IGO.bmp</file>
<file>airlinelogos/IJM.bmp</file>
<file>airlinelogos/Inter Island Air.bmp</file>
<file>airlinelogos/IRA.bmp</file>
<file>airlinelogos/IRC.bmp</file>
<file>airlinelogos/IRK.bmp</file>
<file>airlinelogos/IRM.bmp</file>
<file>airlinelogos/ISK.bmp</file>
<file>airlinelogos/ISS.bmp</file>
<file>airlinelogos/ISV.bmp</file>
<file>airlinelogos/IYE.bmp</file>
<file>airlinelogos/JAF.bmp</file>
<file>airlinelogos/JAI.bmp</file>
<file>airlinelogos/JAT.bmp</file>
<file>airlinelogos/JATnew.bmp</file>
<file>airlinelogos/JAV.bmp</file>
<file>airlinelogos/JBU.bmp</file>
<file>airlinelogos/JCC.bmp</file>
<file>airlinelogos/JET.bmp</file>
<file>airlinelogos/JG.bmp</file>
<file>airlinelogos/JLL.bmp</file>
<file>airlinelogos/JNA.bmp</file>
<file>airlinelogos/JOR.bmp</file>
<file>airlinelogos/JOY.bmp</file>
<file>airlinelogos/JSA.bmp</file>
<file>airlinelogos/JST.bmp</file>
<file>airlinelogos/JTF.bmp</file>
<file>airlinelogos/JTG.bmp</file>
<file>airlinelogos/JUS.bmp</file>
<file>airlinelogos/JW.bmp</file>
<file>airlinelogos/JYH.bmp</file>
<file>airlinelogos/JZA.bmp</file>
<file>airlinelogos/JZR.bmp</file>
<file>airlinelogos/KAB.bmp</file>
<file>airlinelogos/KAC.bmp</file>
<file>airlinelogos/KAL.bmp</file>
<file>airlinelogos/KFA.bmp</file>
<file>airlinelogos/KGL.bmp</file>
<file>airlinelogos/KGO.bmp</file>
<file>airlinelogos/KHH.bmp</file>
<file>airlinelogos/KHV.bmp</file>
<file>airlinelogos/KIL.bmp</file>
<file>airlinelogos/KKK.bmp</file>
<file>airlinelogos/KLC.bmp</file>
<file>airlinelogos/KLM.bmp</file>
<file>airlinelogos/KMF.bmp</file>
<file>airlinelogos/KN.bmp</file>
<file>airlinelogos/KNA.bmp</file>
<file>airlinelogos/KNE.bmp</file>
<file>airlinelogos/KOR.bmp</file>
<file>airlinelogos/KQA.bmp</file>
<file>airlinelogos/KRE.bmp</file>
<file>airlinelogos/KRN.bmp</file>
<file>airlinelogos/KRP.bmp</file>
<file>airlinelogos/KZR.bmp</file>
<file>airlinelogos/LAA.bmp</file>
<file>airlinelogos/LAL.bmp</file>
<file>airlinelogos/LAN.bmp</file>
<file>airlinelogos/LAV.bmp</file>
<file>airlinelogos/LBN.bmp</file>
<file>airlinelogos/LBR.bmp</file>
<file>airlinelogos/LBY.bmp</file>
<file>airlinelogos/LC.bmp</file>
<file>airlinelogos/LCO.bmp</file>
<file>airlinelogos/LER.bmp</file>
<file>airlinelogos/LFO.bmp</file>
<file>airlinelogos/LGL.bmp</file>
<file>airlinelogos/LIA.bmp</file>
<file>airlinelogos/LKA.bmp</file>
<file>airlinelogos/LKE.bmp</file>
<file>airlinelogos/LLP.bmp</file>
<file>airlinelogos/LLR.bmp</file>
<file>airlinelogos/LMU.bmp</file>
<file>airlinelogos/LNI.bmp</file>
<file>airlinelogos/LNK.bmp</file>
<file>airlinelogos/LOG.bmp</file>
<file>airlinelogos/LOT.bmp</file>
<file>airlinelogos/LPA.bmp</file>
<file>airlinelogos/LPV.bmp</file>
<file>airlinelogos/LRC.bmp</file>
<file>airlinelogos/LUR.bmp</file>
<file>airlinelogos/LVR.bmp</file>
<file>airlinelogos/LYM.bmp</file>
<file>airlinelogos/LZB.bmp</file>
<file>airlinelogos/MAC.bmp</file>
<file>airlinelogos/MAL.bmp</file>
<file>airlinelogos/MAR.bmp</file>
<file>airlinelogos/MAS.bmp</file>
<file>airlinelogos/MAU.bmp</file>
<file>airlinelogos/MDG.bmp</file>
<file>airlinelogos/MEA.bmp</file>
<file>airlinelogos/MGX.bmp</file>
<file>airlinelogos/MHS.bmp</file>
<file>airlinelogos/MJF.bmp</file>
<file>airlinelogos/MKG.bmp</file>
<file>airlinelogos/MLD.bmp</file>
<file>airlinelogos/MLH.bmp</file>
<file>airlinelogos/MLO.bmp</file>
<file>airlinelogos/MLT.bmp</file>
<file>airlinelogos/MMA.bmp</file>
<file>airlinelogos/MMD.bmp</file>
<file>airlinelogos/MMZ.bmp</file>
<file>airlinelogos/MNB.bmp</file>
<file>airlinelogos/MNO.bmp</file>
<file>airlinelogos/MON.bmp</file>
<file>airlinelogos/MPA.bmp</file>
<file>airlinelogos/MPE.bmp</file>
<file>airlinelogos/MPH.bmp</file>
<file>airlinelogos/MSC.bmp</file>
<file>airlinelogos/MSE.bmp</file>
<file>airlinelogos/MSF.bmp</file>
<file>airlinelogos/MSR.bmp</file>
<file>airlinelogos/MSX.bmp</file>
<file>airlinelogos/MYP.bmp</file>
<file>airlinelogos/MZN.bmp</file>
<file>airlinelogos/NAC.bmp</file>
<file>airlinelogos/NAX.bmp</file>
<file>airlinelogos/NCB.bmp</file>
<file>airlinelogos/NGB.bmp</file>
<file>airlinelogos/NGT.bmp</file>
<file>airlinelogos/NIA.bmp</file>
<file>airlinelogos/NKS.bmp</file>
<file>airlinelogos/NLU.bmp</file>
<file>airlinelogos/NLY.bmp</file>
<file>airlinelogos/NMA.bmp</file>
<file>airlinelogos/NMB.bmp</file>
<file>airlinelogos/NOK.bmp</file>
<file>airlinelogos/NOS.bmp</file>
<file>airlinelogos/NPT.bmp</file>
<file>airlinelogos/NRL.bmp</file>
<file>airlinelogos/NSE.bmp</file>
<file>airlinelogos/NVC.bmp</file>
<file>airlinelogos/NVD.bmp</file>
<file>airlinelogos/NVR.bmp</file>
<file>airlinelogos/NWS.bmp</file>
<file>airlinelogos/OAE.bmp</file>
<file>airlinelogos/OAL.bmp</file>
<file>airlinelogos/OBS.bmp</file>
<file>airlinelogos/OCA.bmp</file>
<file>airlinelogos/OHY.bmp</file>
<file>airlinelogos/OKA.bmp</file>
<file>airlinelogos/OKS.bmp</file>
<file>airlinelogos/OLC.bmp</file>
<file>airlinelogos/OMA.bmp</file>
<file>airlinelogos/ONE.bmp</file>
<file>airlinelogos/ONX.bmp</file>
<file>airlinelogos/OPJ.bmp</file>
<file>airlinelogos/ORB.bmp</file>
<file>airlinelogos/OTC.bmp</file>
<file>airlinelogos/OVA.bmp</file>
<file>airlinelogos/OZW.bmp</file>
<file>airlinelogos/PAG.bmp</file>
<file>airlinelogos/PAL.bmp</file>
<file>airlinelogos/PAM.bmp</file>
<file>airlinelogos/PCP.bmp</file>
<file>airlinelogos/PER.bmp</file>
<file>airlinelogos/PEV.bmp</file>
<file>airlinelogos/PEX.bmp</file>
<file>airlinelogos/PFZ.bmp</file>
<file>airlinelogos/PGA.bmp</file>
<file>airlinelogos/PGT.bmp</file>
<file>airlinelogos/PIA.bmp</file>
<file>airlinelogos/PIC.bmp</file>
<file>airlinelogos/PKZ.bmp</file>
<file>airlinelogos/PLM.bmp</file>
<file>airlinelogos/PLV.bmp</file>
<file>airlinelogos/PLY.bmp</file>
<file>airlinelogos/PMT.bmp</file>
<file>airlinelogos/POE.bmp</file>
<file>airlinelogos/POT.bmp</file>
<file>airlinelogos/PRF.bmp</file>
<file>airlinelogos/PRI.bmp</file>
<file>airlinelogos/PRW.bmp</file>
<file>airlinelogos/PSC.bmp</file>
<file>airlinelogos/PST.bmp</file>
<file>airlinelogos/PTB.bmp</file>
<file>airlinelogos/PTR.bmp</file>
<file>airlinelogos/PVN.bmp</file>
<file>airlinelogos/PWD.bmp</file>
<file>airlinelogos/QAJ.bmp</file>
<file>airlinelogos/QDA.bmp</file>
<file>airlinelogos/QFA.bmp</file>
<file>airlinelogos/QLK.bmp</file>
<file>airlinelogos/QTR.bmp</file>
<file>airlinelogos/RAE.bmp</file>
<file>airlinelogos/RAM.bmp</file>
<file>airlinelogos/RAR.bmp</file>
<file>airlinelogos/RBA.bmp</file>
<file>airlinelogos/RBG.bmp</file>
<file>airlinelogos/REU.bmp</file>
<file>airlinelogos/RGE.bmp</file>
<file>airlinelogos/RJA.bmp</file>
<file>airlinelogos/RJD.bmp</file>
<file>airlinelogos/RKM.bmp</file>
<file>airlinelogos/RLA.bmp</file>
<file>airlinelogos/RLH.bmp</file>
<file>airlinelogos/RLK.bmp</file>
<file>airlinelogos/RLU.bmp</file>
<file>airlinelogos/RLX.bmp</file>
<file>airlinelogos/RNV.bmp</file>
<file>airlinelogos/ROI.bmp</file>
<file>airlinelogos/ROT.bmp</file>
<file>airlinelogos/ROU.bmp</file>
<file>airlinelogos/RPB.bmp</file>
<file>airlinelogos/RSY.bmp</file>
<file>airlinelogos/RUC.bmp</file>
<file>airlinelogos/RWG.bmp</file>
<file>airlinelogos/RWZ.bmp</file>
<file>airlinelogos/RXA.bmp</file>
<file>airlinelogos/RYR.bmp</file>
<file>airlinelogos/RYW.bmp</file>
<file>airlinelogos/RZO.bmp</file>
<file>airlinelogos/SAI.bmp</file>
<file>airlinelogos/SAS.bmp</file>
<file>airlinelogos/SBM.bmp</file>
<file>airlinelogos/SCO.bmp</file>
<file>airlinelogos/SCX.bmp</file>
<file>airlinelogos/SDM.bmp</file>
<file>airlinelogos/SEJ.bmp</file>
<file>airlinelogos/SEY.bmp</file>
<file>airlinelogos/SFF.bmp</file>
<file>airlinelogos/SFW.bmp</file>
<file>airlinelogos/SGA.bmp</file>
<file>airlinelogos/SGG.bmp</file>
<file>airlinelogos/SHU.bmp</file>
<file>airlinelogos/SIA.bmp</file>
<file>airlinelogos/SID.bmp</file>
<file>airlinelogos/SIF.bmp</file>
<file>airlinelogos/SIN.bmp</file>
<file>airlinelogos/SJO.bmp</file>
<file>airlinelogos/SKK.bmp</file>
<file>airlinelogos/SKP.bmp</file>
<file>airlinelogos/SKU.bmp</file>
<file>airlinelogos/SKV.bmp</file>
<file>airlinelogos/SKZ.bmp</file>
<file>airlinelogos/SLI.bmp</file>
<file>airlinelogos/SLK.bmp</file>
<file>airlinelogos/SLM.bmp</file>
<file>airlinelogos/SLX.bmp</file>
<file>airlinelogos/SME.bmp</file>
<file>airlinelogos/SMJ.bmp</file>
<file>airlinelogos/SMR.bmp</file>
<file>airlinelogos/SOR.bmp</file>
<file>airlinelogos/SOV.bmp</file>
<file>airlinelogos/SPR.bmp</file>
<file>airlinelogos/SQC.bmp</file>
<file>airlinelogos/SQS.bmp</file>
<file>airlinelogos/SRR.bmp</file>
<file>airlinelogos/SSQ.bmp</file>
<file>airlinelogos/SSV.bmp</file>
<file>airlinelogos/SUS.bmp</file>
<file>airlinelogos/SVA.bmp</file>
<file>airlinelogos/SVR.bmp</file>
<file>airlinelogos/SWA.bmp</file>
<file>airlinelogos/SWAnew.bmp</file>
<file>airlinelogos/SWG.bmp</file>
<file>airlinelogos/SWM.bmp</file>
<file>airlinelogos/SWR.bmp</file>
<file>airlinelogos/SWT.bmp</file>
<file>airlinelogos/SXS.bmp</file>
<file>airlinelogos/SYL.bmp</file>
<file>airlinelogos/TAI.bmp</file>
<file>airlinelogos/TAK.bmp</file>
<file>airlinelogos/TAM.bmp</file>
<file>airlinelogos/TAO.bmp</file>
<file>airlinelogos/TAP.bmp</file>
<file>airlinelogos/TAR.bmp</file>
<file>airlinelogos/TAY.bmp</file>
<file>airlinelogos/TBA.bmp</file>
<file>airlinelogos/TBN.bmp</file>
<file>airlinelogos/TBZ.bmp</file>
<file>airlinelogos/TCV.bmp</file>
<file>airlinelogos/TCW.bmp</file>
<file>airlinelogos/TCX.bmp</file>
<file>airlinelogos/TDR.bmp</file>
<file>airlinelogos/TFL.bmp</file>
<file>airlinelogos/TGW.bmp</file>
<file>airlinelogos/TGZ.bmp</file>
<file>airlinelogos/THA.bmp</file>
<file>airlinelogos/THE.bmp</file>
<file>airlinelogos/THT.bmp</file>
<file>airlinelogos/THY.bmp</file>
<file>airlinelogos/THYANA.bmp</file>
<file>airlinelogos/TIA.bmp</file>
<file>airlinelogos/TIW.bmp</file>
<file>airlinelogos/TJK.bmp</file>
<file>airlinelogos/TJS.bmp</file>
<file>airlinelogos/TMA.bmp</file>
<file>airlinelogos/TMN.bmp</file>
<file>airlinelogos/TMW.bmp</file>
<file>airlinelogos/TNA.bmp</file>
<file>airlinelogos/TNO.bmp</file>
<file>airlinelogos/TOM.bmp</file>
<file>airlinelogos/TPA.bmp</file>
<file>airlinelogos/TPC.bmp</file>
<file>airlinelogos/TPU.bmp</file>
<file>airlinelogos/TRA.bmp</file>
<file>airlinelogos/TSC.bmp</file>
<file>airlinelogos/TSG.bmp</file>
<file>airlinelogos/TSH.bmp</file>
<file>airlinelogos/TSO.bmp</file>
<file>airlinelogos/TSY.bmp</file>
<file>airlinelogos/TTL.bmp</file>
<file>airlinelogos/TUA.bmp</file>
<file>airlinelogos/TUI.bmp</file>
<file>airlinelogos/TUS.bmp</file>
<file>airlinelogos/TUY.bmp</file>
<file>airlinelogos/TVF.bmp</file>
<file>airlinelogos/TVP.bmp</file>
<file>airlinelogos/TVQ.bmp</file>
<file>airlinelogos/TVS.bmp</file>
<file>airlinelogos/TWI.bmp</file>
<file>airlinelogos/UAE.bmp</file>
<file>airlinelogos/UAL.bmp</file>
<file>airlinelogos/UBD.bmp</file>
<file>airlinelogos/UCA.bmp</file>
<file>airlinelogos/UEA.bmp</file>
<file>airlinelogos/UJX.bmp</file>
<file>airlinelogos/ULG.bmp</file>
<file>airlinelogos/UPS.bmp</file>
<file>airlinelogos/URG.bmp</file>
<file>airlinelogos/URS.bmp</file>
<file>airlinelogos/UTA.bmp</file>
<file>airlinelogos/UTN.bmp</file>
<file>airlinelogos/UTP.bmp</file>
<file>airlinelogos/UTY.bmp</file>
<file>airlinelogos/UZB.bmp</file>
<file>airlinelogos/VAL.bmp</file>
<file>airlinelogos/VAR.bmp</file>
<file>airlinelogos/VAS.bmp</file>
<file>airlinelogos/VAU.bmp</file>
<file>airlinelogos/VAV.bmp</file>
<file>airlinelogos/VBB.bmp</file>
<file>airlinelogos/VBW.bmp</file>
<file>airlinelogos/VCV.bmp</file>
<file>airlinelogos/VDA.bmp</file>
<file>airlinelogos/VEL.bmp</file>
<file>airlinelogos/VFC.bmp</file>
<file>airlinelogos/VIL.bmp</file>
<file>airlinelogos/VIM.bmp</file>
<file>airlinelogos/VIR.bmp</file>
<file>airlinelogos/VIT.bmp</file>
<file>airlinelogos/VIV.bmp</file>
<file>airlinelogos/VJC.bmp</file>
<file>airlinelogos/VJS.bmp</file>
<file>airlinelogos/VKG.bmp</file>
<file>airlinelogos/VLG.bmp</file>
<file>airlinelogos/VLK.bmp</file>
<file>airlinelogos/VLM.bmp</file>
<file>airlinelogos/VMP.bmp</file>
<file>airlinelogos/VNE.bmp</file>
<file>airlinelogos/VNL.bmp</file>
<file>airlinelogos/VOE.bmp</file>
<file>airlinelogos/VOZ.bmp</file>
<file>airlinelogos/VPA.bmp</file>
<file>airlinelogos/VRD.bmp</file>
<file>airlinelogos/VRE.bmp</file>
<file>airlinelogos/VRG.bmp</file>
<file>airlinelogos/VSV.bmp</file>
<file>airlinelogos/VTA.bmp</file>
<file>airlinelogos/VTI.bmp</file>
<file>airlinelogos/VTM.bmp</file>
<file>airlinelogos/VUN.bmp</file>
<file>airlinelogos/VVC.bmp</file>
<file>airlinelogos/WAJ.bmp</file>
<file>airlinelogos/WDA.bmp</file>
<file>airlinelogos/WEN.bmp</file>
<file>airlinelogos/WEW.bmp</file>
<file>airlinelogos/WFR.bmp</file>
<file>airlinelogos/WIF.bmp</file>
<file>airlinelogos/WJA.bmp</file>
<file>airlinelogos/WLC.bmp</file>
<file>airlinelogos/WOW.bmp</file>
<file>airlinelogos/WRC.bmp</file>
<file>airlinelogos/WSG.bmp</file>
<file>airlinelogos/WUK.bmp</file>
<file>airlinelogos/WWW.bmp</file>
<file>airlinelogos/WZZ.bmp</file>
<file>airlinelogos/XAH.bmp</file>
<file>airlinelogos/XAX.bmp</file>
<file>airlinelogos/XLF.bmp</file>
<file>airlinelogos/XLR.bmp</file>
<file>airlinelogos/XME.bmp</file>
<file>airlinelogos/YZR.bmp</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Some files were not shown because too many files have changed in this diff Show More