1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-22 17:45:48 -05:00

ADS-B Demodulator updates.

Add OpenSky Network aircraft database support, for information about
aircraft model, owner, registration.
Add airline logos and country & military flags for display in the table.
Add OurAirports airport database support, to allow airports and ATC
frequencies to be displayed on the map.
Allow ATC frequency to be tuned by clicking on the map.
Add support for displaying flight paths on the map.
Allow columns in table to be rearranged and hidden.
Allow rows in table to be sorted by clicking on header.
Allow switching units from ft, kn, ft/min to m, kph, m/s
Allow aircraft timeout to be set by the user.
Allow font used for the table to be set by the user.
Add optional display of demodulator statistics.
Support multithreading in demodulator to reduce FIFO overflows.
Add support for demodulating all Mode-S frames and feeding them.
Add support for feeding in Beast hex format.
Allow option of correlating against full preamble or partial preamble.
Supporting highlighting of an aircraft in the table by selecting it on
the map.
Use difference of zeros and ones correlation, rather than absolute
threshold, to better account for varying conditions and make the
threshold easier to set.
Enable anti-aliasing for text on the map.
Improve CRC performance by 5x-10x.
Add HttpDownloadManager class to support downloading of files from the
web to disk.
This commit is contained in:
Jon Beniston 2020-11-06 12:18:55 +00:00
parent c5a36c48a0
commit 72e4e684e2
1245 changed files with 5950 additions and 827 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -1,3 +1,7 @@
if(WIN32)
link_directories(${BOOST_LIBRARYDIR})
endif()
project(adsb)
set(adsb_SOURCES
@ -6,6 +10,7 @@ set(adsb_SOURCES
adsbdemodwebapiadapter.cpp
adsbplugin.cpp
adsbdemodsink.cpp
adsbdemodsinkworker.cpp
adsbdemodbaseband.cpp
adsbdemodreport.cpp
adsbdemodworker.cpp
@ -17,14 +22,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 +40,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)
@ -66,5 +88,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,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <boost/chrono/chrono.hpp>
#include <stdio.h>
#include <complex.h>
@ -288,13 +290,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 +342,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 +410,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

@ -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,170 @@
#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;
stopWorker();
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];
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::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();
qDebug() << "ADSBDemodSink::stopWorker: Worker stopped";
}
m_sampleIdx++;
}
void ADSBDemodSink::init(int samplesPerBit)
{
if (m_sampleBuffer)
delete m_sampleBuffer;
// 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];
m_worker.start();
}
void ADSBDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
@ -278,6 +224,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 +242,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,7 @@
#ifndef INCLUDE_ADSBDEMODSINK_H
#define INCLUDE_ADSBDEMODSINK_H
#include <vector>
#include <boost/chrono/chrono.hpp>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
@ -27,6 +27,8 @@
#include "util/movingaverage.h"
#include "adsbdemodsettings.h"
#include "adsbdemodstats.h"
#include "adsbdemodsinkworker.h"
class ADSBDemodSink : public ChannelSampleSink {
public:
@ -53,13 +55,14 @@ 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; }
private:
friend ADSBDemodSinkWorker;
struct MagSqLevelsStore
{
MagSqLevelsStore() :
@ -78,14 +81,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 +108,17 @@ 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);
void stopWorker();
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,348 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <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;
class ADSBDemodSettings;
class 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.02),
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

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

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