1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2025-09-03 13:47:50 -04:00

Merge pull request #1693 from srcejon/dsc_demod

DSC demod
This commit is contained in:
Edouard Griffiths 2023-05-18 08:50:06 +02:00 committed by GitHub
commit 9631206fa9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 8914 additions and 438 deletions

View File

@ -84,6 +84,7 @@ option(ENABLE_CHANNELRX_DEMODFT8 "Enable channelrx demodft8 plugin" ON)
option(ENABLE_CHANNELRX_DEMODNAVTEX "Enable channelrx demodnavtex plugin" ON)
option(ENABLE_CHANNELRX_DEMODRTTY "Enable channelrx demodrtty plugin" ON)
option(ENABLE_CHANNELRX_DEMODILS "Enable channelrx demodils plugin" ON)
option(ENABLE_CHANNELRX_DEMODDSC "Enable channelrx demoddsc plugin" ON)
# Channel Tx enablers
option(ENABLE_CHANNELTX "Enable channeltx plugins" ON)

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
doc/img/DSCDemod_plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -125,6 +125,10 @@ if (ENABLE_CHANNELRX_DEMODILS)
add_subdirectory(demodils)
endif()
if (ENABLE_CHANNELRX_DEMODDSC)
add_subdirectory(demoddsc)
endif()
if(NOT SERVER_MODE)
add_subdirectory(heatmap)

View File

@ -951,10 +951,7 @@ Aircraft *ADSBDemodGUI::getAircraft(int icao, bool &newAircraft)
QIcon *icon = nullptr;
if (aircraft->m_aircraftInfo->m_operatorICAO.size() > 0)
{
aircraft->m_airlineIconURL = AircraftInformation::getAirlineIconPath(aircraft->m_aircraftInfo->m_operatorICAO);
if (aircraft->m_airlineIconURL.startsWith(':')) {
aircraft->m_airlineIconURL = "qrc://" + aircraft->m_airlineIconURL.mid(1);
}
aircraft->m_airlineIconURL = AircraftInformation::getFlagIconURL(aircraft->m_aircraftInfo->m_operatorICAO);
icon = AircraftInformation::getAirlineIcon(aircraft->m_aircraftInfo->m_operatorICAO);
if (icon != nullptr)
{

View File

@ -223,7 +223,8 @@ bool AISDemod::handleMessage(const Message& cmd)
<< ais->getType() << ","
<< "\"" << ais->toString() << "\"" << ","
<< "\"" << ais->toNMEA() << "\"" << ","
<< report.getSlot() << "\n";
<< report.getSlot() << ","
<< report.getSlots() << "\n";
delete ais;
}
@ -355,7 +356,7 @@ void AISDemod::applySettings(const AISDemodSettings& settings, bool force)
if (newFile)
{
// Write header
m_logStream << "Date,Time,Data,MMSI,Type,Message,NMEA,Slot\n";
m_logStream << "Date,Time,Data,MMSI,Type,Message,NMEA,Slot,Slots\n";
}
}
else

View File

@ -73,22 +73,25 @@ public:
QByteArray getMessage() const { return m_message; }
QDateTime getDateTime() const { return m_dateTime; }
int getSlot() const { return m_slot; }
int getSlots() const { return m_slots; }
static MsgMessage* create(QByteArray message, QDateTime dateTime, int slot)
static MsgMessage* create(QByteArray message, QDateTime dateTime, int slot, int totalSlots)
{
return new MsgMessage(message, dateTime, slot);
return new MsgMessage(message, dateTime, slot, totalSlots);
}
private:
QByteArray m_message;
QDateTime m_dateTime;
int m_slot;
int m_slots;
MsgMessage(QByteArray message, QDateTime dateTime, int slot) :
MsgMessage(QByteArray message, QDateTime dateTime, int slot, int totalSlots) :
Message(),
m_message(message),
m_dateTime(dateTime),
m_slot(slot)
m_slot(slot),
m_slots(totalSlots)
{
}
};

View File

@ -28,6 +28,7 @@
#include <QClipboard>
#include <QFileDialog>
#include <QScrollBar>
#include <QIcon>
#include "aisdemodgui.h"
@ -40,6 +41,7 @@
#include "util/ais.h"
#include "util/csv.h"
#include "util/db.h"
#include "util/mmsi.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "gui/dialpopup.h"
@ -61,10 +63,12 @@ void AISDemodGUI::resizeTable()
// Trailing spaces are for sort arrow
int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1);
ui->messages->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016-"));
ui->messages->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("Frid Apr 15 2016-"));
ui->messages->setItem(row, MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, MESSAGE_COL_MMSI, new QTableWidgetItem("123456789"));
ui->messages->setItem(row, MESSAGE_COL_COUNTRY, new QTableWidgetItem("flag"));
ui->messages->setItem(row, MESSAGE_COL_TYPE, new QTableWidgetItem("Position report"));
ui->messages->setItem(row, MESSAGE_COL_ID, new QTableWidgetItem("25"));
ui->messages->setItem(row, MESSAGE_COL_DATA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, MESSAGE_COL_NMEA, new QTableWidgetItem("!AIVDM,1,1,,A,AAAAAAAAAAAAAAAAAAAAAAAAAAAA,0*00"));
ui->messages->setItem(row, MESSAGE_COL_HEX, new QTableWidgetItem("04058804002000069a0760728d9e00000040000000"));
@ -154,8 +158,270 @@ bool AISDemodGUI::deserialize(const QByteArray& data)
}
}
// Distint palette generator
// https://mokole.com/palette.html
QList<QRgb> AISDemodGUI::m_colors = {
0xffffff,
0xff0000,
0x00ff00,
0x0000ff,
0x00ffff,
0xff00ff,
0x7fff00,
0x000080,
0xa9a9a9,
0x2f4f4f,
0x556b2f,
0x8b4513,
0x6b8e23,
0x191970,
0x006400,
0x708090,
0x8b0000,
0x3cb371,
0xbc8f8f,
0x663399,
0xb8860b,
0xbdb76b,
0x008b8b,
0x4682b4,
0xd2691e,
0x9acd32,
0xcd5c5c,
0x32cd32,
0x8fbc8f,
0x8b008b,
0xb03060,
0x66cdaa,
0x9932cc,
0x00ced1,
0xff8c00,
0xffd700,
0xc71585,
0x0000cd,
0xdeb887,
0x00ff7f,
0x4169e1,
0xe9967a,
0xdc143c,
0x00bfff,
0xf4a460,
0x9370db,
0xa020f0,
0xff6347,
0xd8bfd8,
0xdb7093,
0xf0e68c,
0xffff54,
0x6495ed,
0xdda0dd,
0x87ceeb,
0xff1493,
0xafeeee,
0xee82ee,
0xfaf0e6,
0x98fb98,
0x7fffd4,
0xff69b4,
0xfffacd,
0xffb6c1,
};
QHash<QString, QRgb> m_categoryColors = {
{"Class A Vessel", 0xff0000},
{"Class B Vessel", 0x0000ff},
{"Coast", 0x00ff00},
{"Physical AtoN", 0xffff00},
{"Virtual AtoN", 0xc0c000},
{"Mobile AtoN", 0xa0a000},
{"AtoN", 0x808000},
{"SAR", 0x00ffff},
{"SAR Aircraft", 0x00c0c0},
{"SAR Helicopter", 0x00a0a0},
{"Group", 0xff00ff},
{"Man overboard", 0xc000c0},
{"EPIRB", 0xa000a0},
{"AMRD", 0x800080},
{"Craft with parent ship", 0x600060}
};
QMutex AISDemodGUI::m_colorMutex;
QHash<QString, bool> AISDemodGUI::m_usedInFrame;
QHash<QString, QColor> AISDemodGUI::m_slotMapColors;
QDateTime AISDemodGUI::m_lastColorUpdate;
QHash<QString, QString> AISDemodGUI::m_category;
QColor AISDemodGUI::getColor(const QString& mmsi)
{
if (true)
{
if (m_category.contains(mmsi))
{
QString category = m_category.value(mmsi);
if (m_categoryColors.contains(category)) {
return QColor(m_categoryColors.value(category));
}
qDebug() << "No color for " << category;
}
else
{
// Use white for no category
return Qt::white;
}
}
else
{
QMutexLocker locker(&m_colorMutex);
QColor color;
if (m_slotMapColors.contains(mmsi))
{
m_usedInFrame.insert(mmsi, true);
color = m_slotMapColors.value(mmsi);
}
else
{
if (m_colors.size() > 0)
{
color = m_colors.takeFirst();
qDebug() << "Taking colour from list " << color << "for" << mmsi << " - remaining " << m_colors.size();
}
else
{
qDebug() << "Out of colors - looking to reuse";
// Look for recently unused color
QMutableHashIterator<QString, bool> it(m_usedInFrame);
color = Qt::black;
while (it.hasNext())
{
it.next();
if (!it.value())
{
color = m_slotMapColors.value(it.key());
if (color != Qt::black)
{
qDebug() << "Reusing " << color << " from " << it.key();
m_slotMapColors.remove(it.key());
m_usedInFrame.remove(it.key());
break;
}
}
}
}
if (color != Qt::black)
{
m_slotMapColors.insert(mmsi, color);
m_usedInFrame.insert(mmsi, true);
}
else
{
qDebug() << "No free colours";
}
}
// Don't actually draw with black, as it's the background colour
if (color == Qt::black) {
return Qt::white;
} else {
return color;
}
}
}
void AISDemodGUI::updateColors()
{
QMutexLocker locker(&m_colorMutex);
QDateTime currentDateTime = QDateTime::currentDateTime();
if (!m_lastColorUpdate.isValid() || (m_lastColorUpdate.time().minute() != currentDateTime.time().minute()))
{
QHashIterator<QString, bool> it(m_usedInFrame);
while (it.hasNext())
{
it.next();
m_usedInFrame.insert(it.key(), false);
}
}
m_lastColorUpdate = currentDateTime;
}
void AISDemodGUI::updateSlotMap()
{
QDateTime currentDateTime = QDateTime::currentDateTime();
if (!m_lastSlotMapUpdate.isValid() || (m_lastSlotMapUpdate.time().minute() != currentDateTime.time().minute()))
{
// Update slot utilisation stats for previous frame
ui->slotsFree->setText(QString::number(2250 - m_slotsUsed));
ui->slotsUsed->setText(QString::number(m_slotsUsed));
ui->slotUtilization->setValue(std::round(m_slotsUsed * 100.0 / 2250.0));
m_slotsUsed = 0;
// Draw empty grid
m_image.fill(Qt::transparent);
//m_image.fill(Qt::);
m_painter.setPen(Qt::black);
for (int x = 0; x < m_image.width(); x += 5) {
m_painter.drawLine(x, 0, x, m_image.height() - 1);
}
for (int y = 0; y < m_image.height(); y += 5) {
m_painter.drawLine(0, y, m_image.width() - 1, y);
}
updateColors();
}
ui->slotMap->setPixmap(m_image);
m_lastSlotMapUpdate = currentDateTime;
}
void AISDemodGUI::updateCategory(const QString& mmsi, const AISMessage *message)
{
QMutexLocker locker(&m_colorMutex);
if (!m_category.contains(mmsi))
{
// Categorise by MMSI
QString category = MMSI::getCategory(mmsi);
if (category != "Ship")
{
m_category.insert(mmsi, category);
return;
}
// Handle Search and Rescue Aircraft Report, where MMSI doesn't indicate SAR
if (message->m_id == 9)
{
m_category.insert(mmsi, "SAR");
return;
}
// If ship, determine Class A or B by message type
// See table 42 in ITU-R M.1371-5
if ( (message->m_id <= 12)
|| ((message->m_id >= 15) && (message->m_id <= 17))
|| ((message->m_id >= 20) && (message->m_id <= 23))
|| (message->m_id >= 25)
)
{
m_category.insert(mmsi, "Class A Vessel");
return;
}
// Only Class B should transmit Part B static data reports
const AISStaticDataReport *staticDataReport = dynamic_cast<const AISStaticDataReport *>(message);
if ( (message->m_id == 18)
|| (message->m_id == 19)
|| (staticDataReport && (staticDataReport->m_partNumber == 1))
)
{
m_category.insert(mmsi, "Class B Vessel");
return;
}
// Other messages (such as safety) could be broadcast from either Class A or B
}
}
// Add row to table
void AISDemodGUI::messageReceived(const QByteArray& message, const QDateTime& dateTime, int slot)
void AISDemodGUI::messageReceived(const QByteArray& message, const QDateTime& dateTime, int slot, int totalSlots)
{
AISMessage *ais;
@ -174,7 +440,9 @@ void AISDemodGUI::messageReceived(const QByteArray& message, const QDateTime& da
QTableWidgetItem *dateItem = new QTableWidgetItem();
QTableWidgetItem *timeItem = new QTableWidgetItem();
QTableWidgetItem *mmsiItem = new QTableWidgetItem();
QTableWidgetItem *countryItem = new QTableWidgetItem();
QTableWidgetItem *typeItem = new QTableWidgetItem();
QTableWidgetItem *idItem = new QTableWidgetItem();
QTableWidgetItem *dataItem = new QTableWidgetItem();
QTableWidgetItem *nmeaItem = new QTableWidgetItem();
QTableWidgetItem *hexItem = new QTableWidgetItem();
@ -182,24 +450,51 @@ void AISDemodGUI::messageReceived(const QByteArray& message, const QDateTime& da
ui->messages->setItem(row, MESSAGE_COL_DATE, dateItem);
ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem);
ui->messages->setItem(row, MESSAGE_COL_MMSI, mmsiItem);
ui->messages->setItem(row, MESSAGE_COL_COUNTRY, countryItem);
ui->messages->setItem(row, MESSAGE_COL_TYPE, typeItem);
ui->messages->setItem(row, MESSAGE_COL_ID, idItem);
ui->messages->setItem(row, MESSAGE_COL_DATA, dataItem);
ui->messages->setItem(row, MESSAGE_COL_NMEA, nmeaItem);
ui->messages->setItem(row, MESSAGE_COL_HEX, hexItem);
ui->messages->setItem(row, MESSAGE_COL_SLOT, slotItem);
dateItem->setText(dateTime.date().toString());
timeItem->setText(dateTime.time().toString());
mmsiItem->setText(QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0')));
QString mmsi = QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0'));
mmsiItem->setText(mmsi);
QIcon *flag = MMSI::getFlagIcon(mmsi);
if (flag)
{
countryItem->setSizeHint(QSize(40, 20));
countryItem->setIcon(*flag);
}
typeItem->setText(ais->getType());
idItem->setData(Qt::DisplayRole, ais->m_id);
dataItem->setText(ais->toString());
nmeaItem->setText(ais->toNMEA());
hexItem->setText(ais->toHex());
slotItem->setData(Qt::DisplayRole, slot);
ui->messages->setSortingEnabled(true);
if (scrollToBottom) {
ui->messages->scrollToBottom();
if (!m_loadingData)
{
filterRow(row);
ui->messages->setSortingEnabled(true);
if (scrollToBottom) {
ui->messages->scrollToBottom();
}
}
filterRow(row);
updateCategory(mmsi, ais);
// Update slot map
updateSlotMap();
QColor color = getColor(mmsi);
m_painter.setPen(color);
for (int i = 0; i < totalSlots; i++)
{
int y = (slot + i) / m_slotMapWidth;
int x = (slot + i) % m_slotMapWidth;
m_painter.fillRect(x * 5 + 1, y * 5 + 1, 4, 4, color);
}
m_slotsUsed += totalSlots;
delete ais;
}
@ -221,7 +516,7 @@ bool AISDemodGUI::handleMessage(const Message& message)
else if (AISDemod::MsgMessage::match(message))
{
AISDemod::MsgMessage& report = (AISDemod::MsgMessage&) message;
messageReceived(report.getMessage(), report.getDateTime(), report.getSlot());
messageReceived(report.getMessage(), report.getDateTime(), report.getSlot(), report.getSlots());
return true;
}
else if (DSPSignalNotification::match(message))
@ -330,18 +625,6 @@ void AISDemodGUI::on_udpFormat_currentIndexChanged(int value)
applySettings();
}
void AISDemodGUI::on_channel1_currentIndexChanged(int index)
{
m_settings.m_scopeCh1 = index;
applySettings();
}
void AISDemodGUI::on_channel2_currentIndexChanged(int index)
{
m_settings.m_scopeCh2 = index;
applySettings();
}
void AISDemodGUI::on_messages_cellDoubleClicked(int row, int column)
{
// Get MMSI of message in row double clicked
@ -440,7 +723,9 @@ AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_deviceCenterFrequency(0),
m_basebandSampleRate(1),
m_doApplySettings(true),
m_tickCount(0)
m_tickCount(0),
m_loadingData(false),
m_slotsUsed(0)
{
setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodais/readme.md";
@ -458,8 +743,10 @@ AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_scopeVis = m_aisDemod->getScopeSink();
m_scopeVis->setGLScope(ui->glScope);
m_scopeVis->setNbStreams(AISDemodSettings::m_scopeStreams);
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
ui->scopeGUI->setStreams(QStringList({"IQ", "MagSq", "FM demod", "Gaussian", "RX buf", "Correlation", "Threshold met", "DC offset", "CRC"}));
// Scope settings to display the IQ waveforms
ui->scopeGUI->setPreTrigger(1);
@ -534,6 +821,16 @@ AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
ui->scopeContainer->setVisible(false);
// Create slot map image
m_image = QPixmap(m_slotMapWidth*5+1, m_slotMapHeight*5+1);
m_image.fill(Qt::transparent);
m_image.fill(Qt::black);
m_painter.begin(&m_image);
m_pen.setColor(Qt::white);
m_painter.setPen(m_pen);
ui->slotMap->setPixmap(m_image);
updateSlotMap();
displaySettings();
makeUIConnections();
applySettings(true);
@ -612,12 +909,12 @@ void AISDemodGUI::displaySettings()
ui->udpPort->setText(QString::number(m_settings.m_udpPort));
ui->udpFormat->setCurrentIndex((int)m_settings.m_udpFormat);
ui->channel1->setCurrentIndex(m_settings.m_scopeCh1);
ui->channel2->setCurrentIndex(m_settings.m_scopeCh2);
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled);
ui->showSlotMap->setChecked(m_settings.m_showSlotMap);
ui->slotMapWidget->setVisible(m_settings.m_showSlotMap);
// Order and size columns
QHeaderView *header = ui->messages->horizontalHeader();
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
@ -662,13 +959,22 @@ void AISDemodGUI::tick()
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
if (m_tickCount % 4 == 0)
{
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
updateSlotMap();
}
m_tickCount++;
}
void AISDemodGUI::on_showSlotMap_clicked(bool checked)
{
ui->slotMapWidget->setVisible(checked);
m_settings.m_showSlotMap = checked;
applySettings();
}
void AISDemodGUI::on_logEnable_clicked(bool checked)
{
m_settings.m_logEnabled = checked;
@ -704,6 +1010,8 @@ void AISDemodGUI::on_logOpen_clicked()
QFile file(fileNames[0]);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QDateTime startTime = QDateTime::currentDateTime();
m_loadingData = true;
QTextStream in(&file);
QString error;
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Date", "Time", "Data", "Slot"}, error);
@ -713,7 +1021,8 @@ void AISDemodGUI::on_logOpen_clicked()
int timeCol = colIndexes.value("Time");
int dataCol = colIndexes.value("Data");
int slotCol = colIndexes.value("Slot");
int maxCol = std::max({dateCol, timeCol, dataCol, slotCol});
int slotsCol = colIndexes.contains("Slots") ? colIndexes.value("Slots") : -1;
int maxCol = std::max({dateCol, timeCol, dataCol, slotCol, slotsCol});
QMessageBox dialog(this);
dialog.setText("Reading messages");
@ -725,7 +1034,7 @@ void AISDemodGUI::on_logOpen_clicked()
QStringList cols;
QList<ObjectPipe*> aisPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "ais", aisPipes);
MainCore::instance()->getMessagePipes().getMessagePipes(m_aisDemod, "ais", aisPipes);
while (!cancelled && CSV::readRow(in, &cols))
{
@ -736,9 +1045,10 @@ void AISDemodGUI::on_logOpen_clicked()
QDateTime dateTime(date, time);
QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1());
int slot = cols[slotCol].toInt();
int totalSlots = slotsCol == -1 ? 1 : cols[slotsCol].toInt();
// Add to table
messageReceived(bytes, dateTime, slot);
messageReceived(bytes, dateTime, slot, totalSlots);
// Forward to AIS feature
for (const auto& pipe : aisPipes)
@ -764,6 +1074,10 @@ void AISDemodGUI::on_logOpen_clicked()
{
QMessageBox::critical(this, "AIS Demod", error);
}
m_loadingData = false;
ui->messages->setSortingEnabled(true);
QDateTime finishTime = QDateTime::currentDateTime();
qDebug() << "Read CSV in " << startTime.secsTo(finishTime);
}
else
{
@ -789,8 +1103,7 @@ void AISDemodGUI::makeUIConnections()
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &AISDemodGUI::on_logEnable_clicked);
QObject::connect(ui->logFilename, &QToolButton::clicked, this, &AISDemodGUI::on_logFilename_clicked);
QObject::connect(ui->logOpen, &QToolButton::clicked, this, &AISDemodGUI::on_logOpen_clicked);
QObject::connect(ui->channel1, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AISDemodGUI::on_channel1_currentIndexChanged);
QObject::connect(ui->channel2, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AISDemodGUI::on_channel2_currentIndexChanged);
QObject::connect(ui->showSlotMap, &ButtonSwitch::clicked, this, &AISDemodGUI::on_showSlotMap_clicked);
}
void AISDemodGUI::updateAbsoluteCenterFrequency()

View File

@ -24,6 +24,8 @@
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include <QPainter>
#include <QHash>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
@ -88,20 +90,39 @@ private:
AISDemod* m_aisDemod;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
bool m_loadingData;
QMenu *messagesMenu; // Column select context menu
QMenu *copyMenu;
QPixmap m_image;
QPainter m_painter;
QPen m_pen;
QDateTime m_lastSlotMapUpdate;
int m_slotsUsed;
static QMutex m_colorMutex;
static QHash<QString, bool> m_usedInFrame; // Indicates if MMSI used in current frame
static QHash<QString, QColor> m_slotMapColors; // MMSI to color
static QHash<QString, QString> m_category; // MMSI to category
static QList<QRgb> m_colors;
static QDateTime m_lastColorUpdate;
static const int m_slotMapWidth = 50; // 2250 slots per minute
static const int m_slotMapHeight = 45;
explicit AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~AISDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void messageReceived(const QByteArray& message, const QDateTime& dateTime, int slot);
void messageReceived(const QByteArray& message, const QDateTime& dateTime, int slot, int slots);
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void updateSlotMap();
static void updateColors();
static QColor getColor(const QString& mmsi);
static void updateCategory(const QString& mmsi, const AISMessage *message);
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
@ -113,7 +134,9 @@ private:
MESSAGE_COL_DATE,
MESSAGE_COL_TIME,
MESSAGE_COL_MMSI,
MESSAGE_COL_COUNTRY,
MESSAGE_COL_TYPE,
MESSAGE_COL_ID,
MESSAGE_COL_DATA,
MESSAGE_COL_NMEA,
MESSAGE_COL_HEX,
@ -131,12 +154,11 @@ private slots:
void on_udpAddress_editingFinished();
void on_udpPort_editingFinished();
void on_udpFormat_currentIndexChanged(int value);
void on_channel1_currentIndexChanged(int index);
void on_channel2_currentIndexChanged(int index);
void on_messages_cellDoubleClicked(int row, int column);
void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked();
void on_logOpen_clicked();
void on_showSlotMap_clicked(bool checked=false);
void filterRow(int row);
void filter();
void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>388</width>
<height>446</height>
<height>985</height>
</rect>
</property>
<property name="sizePolicy">
@ -557,6 +557,32 @@
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="showSlotMap">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Show/hide slot map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/constellation.png</normaloff>:/constellation.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize">
@ -632,10 +658,10 @@
<widget class="QWidget" name="messageContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>210</y>
<width>391</width>
<height>171</height>
<x>10</x>
<y>150</y>
<width>361</width>
<height>351</height>
</rect>
</property>
<property name="sizePolicy">
@ -647,79 +673,225 @@
<property name="windowTitle">
<string>Received Messages</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Received packets</string>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Date</string>
</property>
</column>
<column>
<property name="text">
<string>Time</string>
</property>
</column>
<column>
<property name="text">
<string>MMSI</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Data</string>
<widget class="QWidget" name="slotMapWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="slotMapLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="slotMap">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>64</width>
<height>35</height>
</size>
</property>
<property name="toolTip">
<string>Slot map</string>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="slotStatsLayout">
<item>
<widget class="QLabel" name="slotsUsedLabel">
<property name="text">
<string>Used</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="slotsUsed">
<property name="toolTip">
<string>Number of used slots in previous frame</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="slotsFreeLabel">
<property name="text">
<string>Free</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="slotsFree">
<property name="toolTip">
<string>Number of free slots in previous frame</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="slotUtilizationLabel">
<property name="text">
<string>Utilisation</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="slotUtilization">
<property name="toolTip">
<string>Slot utilisation in % for previous frame</string>
</property>
<property name="value">
<number>24</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Packet data as ASCII</string>
<string>Received packets</string>
</property>
</column>
<column>
<property name="text">
<string>NMEA</string>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
</column>
<column>
<property name="text">
<string>Hex</string>
</property>
<property name="toolTip">
<string>Packet data as hex</string>
</property>
</column>
<column>
<property name="text">
<string>Slot</string>
</property>
<property name="toolTip">
<string>Time slot</string>
</property>
</column>
<column>
<property name="text">
<string>Date</string>
</property>
<property name="toolTip">
<string>Date message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Time</string>
</property>
<property name="toolTip">
<string>Time message was received</string>
</property>
</column>
<column>
<property name="text">
<string>MMSI</string>
</property>
<property name="toolTip">
<string>Maritime Mobile Service Identity</string>
</property>
</column>
<column>
<property name="text">
<string>Country</string>
</property>
<property name="toolTip">
<string>Country with jurisdiction over station/vessel</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>Message type</string>
</property>
</column>
<column>
<property name="text">
<string>Id</string>
</property>
<property name="toolTip">
<string>Message type identifier</string>
</property>
</column>
<column>
<property name="text">
<string>Data</string>
</property>
<property name="toolTip">
<string>Decoded message data</string>
</property>
</column>
<column>
<property name="text">
<string>NMEA</string>
</property>
<property name="toolTip">
<string>Message data in NMEA format</string>
</property>
</column>
<column>
<property name="text">
<string>Hex</string>
</property>
<property name="toolTip">
<string>Message data as hex</string>
</property>
</column>
<column>
<property name="text">
<string>Slot</string>
</property>
<property name="toolTip">
<string>Time slot</string>
</property>
</column>
</widget>
</widget>
</item>
</layout>
@ -728,7 +900,7 @@
<property name="geometry">
<rect>
<x>20</x>
<y>400</y>
<y>510</y>
<width>716</width>
<height>341</height>
</rect>
@ -758,150 +930,6 @@
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="scopelLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Real</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channel1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>I</string>
</property>
</item>
<item>
<property name="text">
<string>Q</string>
</property>
</item>
<item>
<property name="text">
<string>Mag Sq</string>
</property>
</item>
<item>
<property name="text">
<string>FM demod</string>
</property>
</item>
<item>
<property name="text">
<string>Gaussian</string>
</property>
</item>
<item>
<property name="text">
<string>RX buf</string>
</property>
</item>
<item>
<property name="text">
<string>Correlation</string>
</property>
</item>
<item>
<property name="text">
<string>Threshold met</string>
</property>
</item>
<item>
<property name="text">
<string>DC offset</string>
</property>
</item>
<item>
<property name="text">
<string>CRC</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Imag</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channel2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>I</string>
</property>
</item>
<item>
<property name="text">
<string>Q</string>
</property>
</item>
<item>
<property name="text">
<string>Mag Sq</string>
</property>
</item>
<item>
<property name="text">
<string>FM demod</string>
</property>
</item>
<item>
<property name="text">
<string>Gaussian</string>
</property>
</item>
<item>
<property name="text">
<string>RX buf</string>
</property>
</item>
<item>
<property name="text">
<string>Correlation</string>
</property>
</item>
<item>
<property name="text">
<string>Threshold met</string>
</property>
</item>
<item>
<property name="text">
<string>DC offset</string>
</property>
</item>
<item>
<property name="text">
<string>CRC</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="GLScope" name="glScope" native="true">
<property name="minimumSize">

View File

@ -43,10 +43,9 @@ void AISDemodSettings::resetToDefaults()
m_udpAddress = "127.0.0.1";
m_udpPort = 9999;
m_udpFormat = Binary;
m_scopeCh1 = 5;
m_scopeCh2 = 6;
m_logFilename = "ais_log.csv";
m_logEnabled = false;
m_showSlotMap = false;
m_rgbColor = QColor(102, 0, 0).rgb();
m_title = "AIS Demodulator";
m_streamIndex = 0;
@ -78,8 +77,6 @@ QByteArray AISDemodSettings::serialize() const
s.writeString(7, m_udpAddress);
s.writeU32(8, m_udpPort);
s.writeS32(9, (int)m_udpFormat);
s.writeS32(10, m_scopeCh1);
s.writeS32(11, m_scopeCh2);
s.writeU32(12, m_rgbColor);
s.writeString(13, m_title);
@ -105,6 +102,7 @@ QByteArray AISDemodSettings::serialize() const
s.writeS32(26, m_workspaceIndex);
s.writeBlob(27, m_geometryBytes);
s.writeBool(28, m_hidden);
s.writeBool(29, m_showSlotMap);
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
s.writeS32(100 + i, m_messageColumnIndexes[i]);
@ -146,8 +144,6 @@ bool AISDemodSettings::deserialize(const QByteArray& data)
}
d.readS32(9, (int *)&m_udpFormat, (int)Binary);
d.readS32(10, &m_scopeCh1, 0);
d.readS32(11, &m_scopeCh2, 0);
d.readU32(12, &m_rgbColor, QColor(102, 0, 0).rgb());
d.readString(13, &m_title, "AIS Demodulator");
@ -192,6 +188,7 @@ bool AISDemodSettings::deserialize(const QByteArray& data)
d.readS32(26, &m_workspaceIndex, 0);
d.readBlob(27, &m_geometryBytes);
d.readBool(28, &m_hidden, false);
d.readBool(29, &m_showSlotMap, false);
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++) {
d.readS32(100 + i, &m_messageColumnIndexes[i], i);

View File

@ -27,7 +27,7 @@
class Serializable;
// Number of columns in the tables
#define AISDEMOD_MESSAGE_COLUMNS 8
#define AISDEMOD_MESSAGE_COLUMNS 10
struct AISDemodSettings
{
@ -44,11 +44,10 @@ struct AISDemodSettings
Binary,
NMEA
} m_udpFormat;
int m_scopeCh1;
int m_scopeCh2;
QString m_logFilename;
bool m_logEnabled;
bool m_showSlotMap;
quint32 m_rgbColor;
QString m_title;
@ -69,6 +68,7 @@ struct AISDemodSettings
int m_messageColumnSizes[AISDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table
static const int AISDEMOD_CHANNEL_SAMPLE_RATE = 57600; //!< 6x 9600 baud rate (use even multiple so Gausian filter has odd number of taps)
static const int m_scopeStreams = 9;
AISDemodSettings();
void resetToDefaults();

View File

@ -47,7 +47,9 @@ AISDemodSink::AISDemodSink(AISDemod *aisDemod) :
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
m_sampleBuffer.resize(m_sampleBufferSize);
for (int i = 0; i < AISDemodSettings::m_scopeStreams; i++) {
m_sampleBuffer[i].resize(m_sampleBufferSize);
}
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
@ -59,18 +61,28 @@ AISDemodSink::~AISDemodSink()
delete[] m_train;
}
void AISDemodSink::sampleToScope(Complex sample)
void AISDemodSink::sampleToScope(Complex sample, Real magsq, Real fmDemod, Real filt, Real rxBuf, Real corr, Real thresholdMet, Real dcOffset, Real crcValid)
{
if (m_scopeSink)
{
Real r = std::real(sample) * SDR_RX_SCALEF;
Real i = std::imag(sample) * SDR_RX_SCALEF;
m_sampleBuffer[m_sampleBufferIndex++] = Sample(r, i);
m_sampleBuffer[0][m_sampleBufferIndex] = sample;
m_sampleBuffer[1][m_sampleBufferIndex] = Complex(m_magsq, 0.0f);
m_sampleBuffer[2][m_sampleBufferIndex] = Complex(fmDemod, 0.0f);
m_sampleBuffer[3][m_sampleBufferIndex] = Complex(filt, 0.0f);
m_sampleBuffer[4][m_sampleBufferIndex] = Complex(rxBuf, 0.0f);
m_sampleBuffer[5][m_sampleBufferIndex] = Complex(corr, 0.0f);
m_sampleBuffer[6][m_sampleBufferIndex] = Complex(thresholdMet, 0.0f);
m_sampleBuffer[7][m_sampleBufferIndex] = Complex(dcOffset, 0.0f);
m_sampleBuffer[8][m_sampleBufferIndex] = Complex(crcValid, 0.0f);
m_sampleBufferIndex++;
if (m_sampleBufferIndex == m_sampleBufferSize)
{
std::vector<SampleVector::const_iterator> vbegin;
vbegin.push_back(m_sampleBuffer.begin());
std::vector<ComplexVector::const_iterator> vbegin;
for (int i = 0; i < AISDemodSettings::m_scopeStreams; i++) {
vbegin.push_back(m_sampleBuffer[i].begin());
}
m_scopeSink->feed(vbegin, m_sampleBufferSize);
m_sampleBufferIndex = 0;
}
@ -252,10 +264,13 @@ void AISDemodSink::processOneSample(Complex &ci)
// This is unlikely to be accurate in absolute terms, given we don't know latency from SDR or buffering within SDRangel
// But can be used to get an idea of congestion
QDateTime currentTime = QDateTime::currentDateTime();
QDateTime startDateTime = currentTime.addMSecs(-(totalBitCount + 8 + 24 + 8) * (1000.0 / m_settings.m_baud)); // Add ramp up, preamble and start-flag
int txTimeMs = (totalBitCount + 8 + 24 + 8) * (1000.0 / m_settings.m_baud); // Add ramp up, preamble and start-flag
QDateTime startDateTime = currentTime.addMSecs(-txTimeMs);
int ms = startDateTime.time().second() * 1000 + startDateTime.time().msec();
int slot = ms / 26.67; // 2250 slots per minute, 26ms per slot
AISDemod::MsgMessage *msg = AISDemod::MsgMessage::create(rxPacket, currentTime, slot);
float slotTime = 60.0f * 1000.0f / 2250.0f; // 2250 slots per minute, 26.6ms per slot
int slot = ms / slotTime;
int totalSlots = std::ceil(txTimeMs / slotTime);
AISDemod::MsgMessage *msg = AISDemod::MsgMessage::create(rxPacket, currentTime, slot, totalSlots);
getMessageQueueToChannel()->push(msg);
}
@ -318,74 +333,7 @@ void AISDemodSink::processOneSample(Complex &ci)
}
// Select signals to feed to scope
Complex scopeSample;
switch (m_settings.m_scopeCh1)
{
case 0:
scopeSample.real(ci.real() / SDR_RX_SCALEF);
break;
case 1:
scopeSample.real(ci.imag() / SDR_RX_SCALEF);
break;
case 2:
scopeSample.real(magsq);
break;
case 3:
scopeSample.real(fmDemod);
break;
case 4:
scopeSample.real(filt);
break;
case 5:
scopeSample.real(m_rxBuf[m_rxBufIdx]);
break;
case 6:
scopeSample.real(corr / 100.0);
break;
case 7:
scopeSample.real(thresholdMet);
break;
case 8:
scopeSample.real(dcOffset);
break;
case 9:
scopeSample.real(scopeCRCValid ? 1.0 : (scopeCRCInvalid ? -1.0 : 0));
break;
}
switch (m_settings.m_scopeCh2)
{
case 0:
scopeSample.imag(ci.real() / SDR_RX_SCALEF);
break;
case 1:
scopeSample.imag(ci.imag() / SDR_RX_SCALEF);
break;
case 2:
scopeSample.imag(magsq);
break;
case 3:
scopeSample.imag(fmDemod);
break;
case 4:
scopeSample.imag(filt);
break;
case 5:
scopeSample.imag(m_rxBuf[m_rxBufIdx]);
break;
case 6:
scopeSample.imag(corr / 100.0);
break;
case 7:
scopeSample.imag(thresholdMet);
break;
case 8:
scopeSample.imag(dcOffset);
break;
case 9:
scopeSample.imag(scopeCRCValid ? 1.0 : (scopeCRCInvalid ? -1.0 : 0));
break;
}
sampleToScope(scopeSample);
sampleToScope(ci / SDR_RX_SCALEF, magsq, fmDemod, filt, m_rxBuf[m_rxBufIdx], corr / 100.0, thresholdMet, dcOffset, scopeCRCValid ? 1.0 : (scopeCRCInvalid ? -1.0 : 0));
// Send demod signal to Demod Analzyer feature
m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits<int16_t>::max();

View File

@ -128,13 +128,13 @@ private:
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
SampleVector m_sampleBuffer;
ComplexVector m_sampleBuffer[AISDemodSettings::m_scopeStreams];
static const int m_sampleBufferSize = AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE / 20;
int m_sampleBufferIndex;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
void sampleToScope(Complex sample);
void sampleToScope(Complex sample, Real magsq, Real fmDemod, Real filt, Real rxBuf, Real corr, Real thresholdMet, Real dcOffset, Real crcValid);
};
#endif // INCLUDE_AISDEMODSINK_H

View File

@ -82,19 +82,40 @@ Click to specify the name of the .csv file which received AIS messages are logge
Click to specify a previously written AIS .csv log file, which is read and used to update the table.
<h3>Slot Map</h3>
AIS uses TMDA (Time Division Multiple Access), whereby each one minute frame is divided into 2,250 26.6ms slots.
The slot map shows which slots within a frame are used. The slot map is drawn as bitmap of 50x45 pixels.
![AIS Slot Map](../../../doc/img/AISDemod_plugin_slotmap.png)
Slots are by category:
* Red: Class A Mobile
* Blue: Class B Mobile
* Green: Base Station
* Yellow: AtoN (Aid-to-Navigation)
* Cyan: Search and Rescue
* Magenta: Other (Man overboard / EPIRB / AMRD).
Due to SDR to SDRangel latency being unknown, the slot map is likely to have some offset, as slot timing is calculated based on the time messages
are demodulated in SDRangel.
<h3>Received Messages Table</h3>
The received messages table displays information about each AIS message received. Only messages with valid CRCs are displayed.
![AIS Demodulator plugin GUI](../../../doc/img/AISDemod_plugin_messages.png)
![AIS Received Messages Table](../../../doc/img/AISDemod_plugin_messages.png)
* Date - The date the message was received.
* Time - The time the message was received.
* MMSI - The Maritime Mobile Service Identity number of the source of the message. Double clicking on this column will search for the MMSI on https://www.vesselfinder.com/
* Country - The country with jurisdiction over station/vessel.
* Type - The type of AIS message. E.g. Position report, Base station report or Ship static and voyage related data.
* Id - Message type numeric identifier.
* Data - A textual decode of the message displaying the most interesting fields.
* NMEA - The message in NMEA format.
* Hex - The message in hex format.
* Slot - Time slot (0-2249). Due to SDR to SDRangel latency being unknown, this is likely to have some offset.
* Slot - Time slot (0-2249).
Right clicking on the table header allows you to select which columns to show. The columns can be reordered by left clicking and dragging the column header. Right clicking on an item in the table allows you to copy the value to the clipboard.

View File

@ -191,8 +191,8 @@ void DABDemodGUI::addProgramName(const DABDemod::MsgDABProgramName& program)
frequencyItem->setData(Qt::UserRole, 0.0);
}
ensembleItem->setText(ui->ensemble->text());
ui->programs->setSortingEnabled(true);
filterRow(row);
ui->programs->setSortingEnabled(true);
}
// Tune to the selected program

View File

@ -0,0 +1,63 @@
project(demoddsc)
set(demoddsc_SOURCES
dscdemod.cpp
dscdemodsettings.cpp
dscdemodbaseband.cpp
dscdemodsink.cpp
dscdemodplugin.cpp
dscdemodwebapiadapter.cpp
)
set(demoddsc_HEADERS
dscdemod.h
dscdemodsettings.h
dscdemodbaseband.h
dscdemodsink.h
dscdemodplugin.h
dscdemodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(demoddsc_SOURCES
${demoddsc_SOURCES}
dscdemodgui.cpp
dscdemodgui.ui
)
set(demoddsc_HEADERS
${demoddsc_HEADERS}
dscdemodgui.h
)
set(TARGET_NAME demoddsc)
set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demoddscsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demoddsc_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,763 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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 "dscdemod.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <QHostInfo>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGWorkspaceInfo.h"
#include "SWGDSCDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGMapItem.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "settings/serializable.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(DSCDemod::MsgConfigureDSCDemod, Message)
MESSAGE_CLASS_DEFINITION(DSCDemod::MsgMessage, Message)
const char * const DSCDemod::m_channelIdURI = "sdrangel.channel.dscdemod";
const char * const DSCDemod::m_channelId = "DSCDemod";
DSCDemod::DSCDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new DSCDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
m_basebandSink->setChannel(this);
m_basebandSink->moveToThread(&m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&DSCDemod::networkManagerFinished
);
QObject::connect(
this,
&ChannelAPI::indexInDeviceSetChanged,
this,
&DSCDemod::handleIndexInDeviceSetChanged
);
}
DSCDemod::~DSCDemod()
{
qDebug("DSCDemod::~DSCDemod");
QObject::disconnect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&DSCDemod::networkManagerFinished
);
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_basebandSink->isRunning()) {
stop();
}
delete m_basebandSink;
}
void DSCDemod::setDeviceAPI(DeviceAPI *deviceAPI)
{
if (deviceAPI != m_deviceAPI)
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
m_deviceAPI = deviceAPI;
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
}
}
uint32_t DSCDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void DSCDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void DSCDemod::start()
{
qDebug("DSCDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
DSCDemodBaseband::MsgConfigureDSCDemodBaseband *msg = DSCDemodBaseband::MsgConfigureDSCDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void DSCDemod::stop()
{
qDebug("DSCDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool DSCDemod::handleMessage(const Message& cmd)
{
if (MsgConfigureDSCDemod::match(cmd))
{
MsgConfigureDSCDemod& cfg = (MsgConfigureDSCDemod&) cmd;
qDebug() << "DSCDemod::handleMessage: MsgConfigureDSCDemod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "DSCDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (m_guiMessageQueue) {
m_guiMessageQueue->push(new DSPSignalNotification(notif));
}
return true;
}
else if (DSCDemod::MsgMessage::match(cmd))
{
// Forward to GUI
DSCDemod::MsgMessage& report = (DSCDemod::MsgMessage&)cmd;
if (getMessageQueueToGUI())
{
DSCDemod::MsgMessage *msg = new DSCDemod::MsgMessage(report);
getMessageQueueToGUI()->push(msg);
}
// Forward via UDP
if (m_settings.m_udpEnabled)
{
//qDebug() << "Forwarding to " << m_settings.m_udpAddress << ":" << m_settings.m_udpPort;
QByteArray bytes = report.getMessage().m_data;
m_udpSocket.writeDatagram(bytes, bytes.size(),
QHostAddress(m_settings.m_udpAddress), m_settings.m_udpPort);
}
// Forward valid messages to yaddnet.org
if (m_settings.m_feed)
{
const DSCMessage& message = report.getMessage();
if (message.m_valid)
{
QString yaddnet = message.toYaddNetFormat(MainCore::instance()->getSettings().getStationName(), m_centerFrequency + m_settings.m_inputFrequencyOffset);
qDebug() << "Forwarding to yaddnet.org " << yaddnet;
QByteArray bytes = yaddnet.toLocal8Bit();
QHostInfo info = QHostInfo::fromName("www.yaddnet.org");
if (info.addresses().size() > 0)
{
qint64 sent = m_udpSocket.writeDatagram(bytes.data(), bytes.size(), info.addresses()[0], 50666);
if (bytes.size() != sent) {
qDebug() << "Failed to send datagram to www.yaddnet.org. Sent " << sent << " of " << bytes.size() << " Error " << m_udpSocket.error();
}
}
else
{
qDebug() << "Can't get IP address for www.yaddnet.org";
}
}
}
// Write to log file
if (m_logFile.isOpen())
{
const DSCMessage &dscMsg = report.getMessage();
if (dscMsg.m_valid)
{
m_logStream
<< dscMsg.m_dateTime.date().toString() << ","
<< dscMsg.m_dateTime.time().toString() << ","
<< dscMsg.formatSpecifier() << ","
<< dscMsg.m_selfId << ","
<< dscMsg.m_address << ","
<< dscMsg.m_data.toHex() << ","
<< report.getErrors() << ","
<< report.getRSSI()
<< "\n";
}
}
return true;
}
else if (MainCore::MsgChannelDemodQuery::match(cmd))
{
qDebug() << "DSCDemod::handleMessage: MsgChannelDemodQuery";
sendSampleRateToDemodAnalyzer();
return true;
}
else
{
return false;
}
}
ScopeVis *DSCDemod::getScopeSink()
{
return m_basebandSink->getScopeSink();
}
void DSCDemod::setCenterFrequency(qint64 frequency)
{
DSCDemodSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureDSCDemod *msgToGUI = MsgConfigureDSCDemod::create(settings, false);
m_guiMessageQueue->push(msgToGUI);
}
}
void DSCDemod::applySettings(const DSCDemodSettings& settings, bool force)
{
qDebug() << "DSCDemod::applySettings:"
<< " m_logEnabled: " << settings.m_logEnabled
<< " m_logFilename: " << settings.m_logFilename
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_filterInvalid != m_settings.m_filterInvalid) || force) {
reverseAPIKeys.append("filterInvalid");
}
if ((settings.m_filterColumn != m_settings.m_filterColumn) || force) {
reverseAPIKeys.append("filterColumn");
}
if ((settings.m_filter != m_settings.m_filter) || force) {
reverseAPIKeys.append("filter");
}
if ((settings.m_udpEnabled != m_settings.m_udpEnabled) || force) {
reverseAPIKeys.append("udpEnabled");
}
if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) {
reverseAPIKeys.append("udpAddress");
}
if ((settings.m_udpPort != m_settings.m_udpPort) || force) {
reverseAPIKeys.append("udpPort");
}
if ((settings.m_logFilename != m_settings.m_logFilename) || force) {
reverseAPIKeys.append("logFilename");
}
if ((settings.m_logEnabled != m_settings.m_logEnabled) || force) {
reverseAPIKeys.append("logEnabled");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
DSCDemodBaseband::MsgConfigureDSCDemodBaseband *msg = DSCDemodBaseband::MsgConfigureDSCDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
if ((settings.m_logEnabled != m_settings.m_logEnabled)
|| (settings.m_logFilename != m_settings.m_logFilename)
|| force)
{
if (m_logFile.isOpen())
{
m_logStream.flush();
m_logFile.close();
}
if (settings.m_logEnabled && !settings.m_logFilename.isEmpty())
{
m_logFile.setFileName(settings.m_logFilename);
if (m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
{
qDebug() << "DSCDemod::applySettings - Logging to: " << settings.m_logFilename;
bool newFile = m_logFile.size() == 0;
m_logStream.setDevice(&m_logFile);
if (newFile)
{
// Write header
m_logStream << "Date,Time,Format,From,To,Message,Errors,RSSI\n";
}
}
else
{
qDebug() << "DSCDemod::applySettings - Unable to open log file: " << settings.m_logFilename;
}
}
}
m_settings = settings;
}
void DSCDemod::sendSampleRateToDemodAnalyzer()
{
QList<ObjectPipe*> pipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "reportdemod", pipes);
if (pipes.size() > 0)
{
for (const auto& pipe : pipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE
);
messageQueue->push(msg);
}
}
}
QByteArray DSCDemod::serialize() const
{
return m_settings.serialize();
}
bool DSCDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureDSCDemod *msg = MsgConfigureDSCDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureDSCDemod *msg = MsgConfigureDSCDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int DSCDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setDscDemodSettings(new SWGSDRangel::SWGDSCDemodSettings());
response.getDscDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int DSCDemod::webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage)
{
(void) errorMessage;
response.setIndex(m_settings.m_workspaceIndex);
return 200;
}
int DSCDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
DSCDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureDSCDemod *msg = MsgConfigureDSCDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("DSCDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureDSCDemod *msgToGUI = MsgConfigureDSCDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
int DSCDemod::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setDscDemodReport(new SWGSDRangel::SWGDSCDemodReport());
response.getDscDemodReport()->init();
webapiFormatChannelReport(response);
return 200;
}
void DSCDemod::webapiUpdateChannelSettings(
DSCDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getDscDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getDscDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("filterInvalid")) {
settings.m_filterInvalid = response.getDscDemodSettings()->getFilterInvalid();
}
if (channelSettingsKeys.contains("filterColumn")) {
settings.m_filterColumn = response.getDscDemodSettings()->getFilterColumn();
}
if (channelSettingsKeys.contains("filter")) {
settings.m_filter = *response.getDscDemodSettings()->getFilter();
}
if (channelSettingsKeys.contains("udpEnabled")) {
settings.m_udpEnabled = response.getDscDemodSettings()->getUdpEnabled();
}
if (channelSettingsKeys.contains("udpAddress")) {
settings.m_udpAddress = *response.getDscDemodSettings()->getUdpAddress();
}
if (channelSettingsKeys.contains("udpPort")) {
settings.m_udpPort = response.getDscDemodSettings()->getUdpPort();
}
if (channelSettingsKeys.contains("logFilename")) {
settings.m_logFilename = *response.getAdsbDemodSettings()->getLogFilename();
}
if (channelSettingsKeys.contains("logEnabled")) {
settings.m_logEnabled = response.getAdsbDemodSettings()->getLogEnabled();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getDscDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getDscDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getDscDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getDscDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getDscDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getDscDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getDscDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getDscDemodSettings()->getReverseApiChannelIndex();
}
if (settings.m_scopeGUI && channelSettingsKeys.contains("scopeConfig")) {
settings.m_scopeGUI->updateFrom(channelSettingsKeys, response.getDscDemodSettings()->getScopeConfig());
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getDscDemodSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getDscDemodSettings()->getRollupState());
}
}
void DSCDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const DSCDemodSettings& settings)
{
response.getDscDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getDscDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getDscDemodSettings()->setFilterInvalid(settings.m_filterInvalid);
response.getDscDemodSettings()->setFilterColumn(settings.m_filterColumn);
response.getDscDemodSettings()->setFilter(new QString(settings.m_filter));
response.getDscDemodSettings()->setUdpEnabled(settings.m_udpEnabled);
response.getDscDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress));
response.getDscDemodSettings()->setUdpPort(settings.m_udpPort);
response.getDscDemodSettings()->setLogFilename(new QString(settings.m_logFilename));
response.getDscDemodSettings()->setLogEnabled(settings.m_logEnabled);
response.getDscDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getDscDemodSettings()->getTitle()) {
*response.getDscDemodSettings()->getTitle() = settings.m_title;
} else {
response.getDscDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getDscDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getDscDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getDscDemodSettings()->getReverseApiAddress()) {
*response.getDscDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getDscDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getDscDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getDscDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getDscDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_scopeGUI)
{
if (response.getDscDemodSettings()->getScopeConfig())
{
settings.m_scopeGUI->formatTo(response.getDscDemodSettings()->getScopeConfig());
}
else
{
SWGSDRangel::SWGGLScope *swgGLScope = new SWGSDRangel::SWGGLScope();
settings.m_scopeGUI->formatTo(swgGLScope);
response.getDscDemodSettings()->setScopeConfig(swgGLScope);
}
}
if (settings.m_channelMarker)
{
if (response.getDscDemodSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getDscDemodSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getDscDemodSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getDscDemodSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getDscDemodSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getDscDemodSettings()->setRollupState(swgRollupState);
}
}
}
void DSCDemod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
response.getDscDemodReport()->setChannelPowerDb(CalcDb::dbPower(magsqAvg));
response.getDscDemodReport()->setChannelSampleRate(m_basebandSink->getChannelSampleRate());
}
void DSCDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSCDemodSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void DSCDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const DSCDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("DSCDemod"));
swgChannelSettings->setDscDemodSettings(new SWGSDRangel::SWGDSCDemodSettings());
SWGSDRangel::SWGDSCDemodSettings *swgDSCDemodSettings = swgChannelSettings->getDscDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgDSCDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgDSCDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("filterInvalid") || force) {
swgDSCDemodSettings->setFilterInvalid(settings.m_filterInvalid);
}
if (channelSettingsKeys.contains("filterColumn") || force) {
swgDSCDemodSettings->setFilterColumn(settings.m_filterColumn);
}
if (channelSettingsKeys.contains("filter") || force) {
swgDSCDemodSettings->setFilter(new QString(settings.m_filter));
}
if (channelSettingsKeys.contains("udpEnabled") || force) {
swgDSCDemodSettings->setUdpEnabled(settings.m_udpEnabled);
}
if (channelSettingsKeys.contains("udpAddress") || force) {
swgDSCDemodSettings->setUdpAddress(new QString(settings.m_udpAddress));
}
if (channelSettingsKeys.contains("udpPort") || force) {
swgDSCDemodSettings->setUdpPort(settings.m_udpPort);
}
if (channelSettingsKeys.contains("logFilename") || force) {
swgDSCDemodSettings->setLogFilename(new QString(settings.m_logFilename));
}
if (channelSettingsKeys.contains("logEnabled") || force) {
swgDSCDemodSettings->setLogEnabled(settings.m_logEnabled);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgDSCDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgDSCDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgDSCDemodSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_scopeGUI && (channelSettingsKeys.contains("scopeConfig") || force))
{
SWGSDRangel::SWGGLScope *swgGLScope = new SWGSDRangel::SWGGLScope();
settings.m_scopeGUI->formatTo(swgGLScope);
swgDSCDemodSettings->setScopeConfig(swgGLScope);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgDSCDemodSettings->setChannelMarker(swgChannelMarker);
}
if (settings.m_rollupState && (channelSettingsKeys.contains("rollupState") || force))
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
swgDSCDemodSettings->setRollupState(swgRollupState);
}
}
void DSCDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "DSCDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("DSCDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
void DSCDemod::handleIndexInDeviceSetChanged(int index)
{
if (index < 0) {
return;
}
QString fifoLabel = QString("%1 [%2:%3]")
.arg(m_channelId)
.arg(m_deviceAPI->getDeviceSetIndex())
.arg(index);
m_basebandSink->setFifoLabel(fifoLabel);
}

View File

@ -0,0 +1,205 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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_DSCDEMOD_H
#define INCLUDE_DSCDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QFile>
#include <QTextStream>
#include <QRegularExpression>
#include <QDateTime>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "util/navtex.h"
#include "dscdemodbaseband.h"
#include "dscdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class ScopeVis;
class DSCDemod : public BasebandSampleSink, public ChannelAPI {
public:
class MsgConfigureDSCDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DSCDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureDSCDemod* create(const DSCDemodSettings& settings, bool force)
{
return new MsgConfigureDSCDemod(settings, force);
}
private:
DSCDemodSettings m_settings;
bool m_force;
MsgConfigureDSCDemod(const DSCDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgMessage : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DSCMessage& getMessage() const { return m_message; }
int getErrors() const { return m_errors; }
float getRSSI() const { return m_rssi; }
static MsgMessage* create(const DSCMessage& message, int errors, float rssi)
{
return new MsgMessage(message, errors, rssi);
}
private:
DSCMessage m_message;
int m_errors;
float m_rssi;
MsgMessage(const DSCMessage& message, int errors, float rssi) :
m_message(message),
m_errors(errors),
m_rssi(rssi)
{}
};
DSCDemod(DeviceAPI *deviceAPI);
virtual ~DSCDemod();
virtual void destroy() { delete this; }
virtual void setDeviceAPI(DeviceAPI *deviceAPI);
virtual DeviceAPI *getDeviceAPI() { return m_deviceAPI; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSinkName() { return objectName(); }
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual QString getIdentifier() const { return objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiWorkspaceGet(
SWGSDRangel::SWGWorkspaceInfo& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const DSCDemodSettings& settings);
static void webapiUpdateChannelSettings(
DSCDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
ScopeVis *getScopeSink();
double getMagSq() const { return m_basebandSink->getMagSq(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
/* void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}*/
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
DSCDemodBaseband* m_basebandSink;
DSCDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QUdpSocket m_udpSocket;
QFile m_logFile;
QTextStream m_logStream;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
virtual bool handleMessage(const Message& cmd);
void applySettings(const DSCDemodSettings& settings, bool force = false);
void sendSampleRateToDemodAnalyzer();
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const DSCDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const DSCDemodSettings& settings,
bool force
);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleIndexInDeviceSetChanged(int index);
};
#endif // INCLUDE_DSCDEMOD_H

View File

@ -0,0 +1,182 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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 <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "dscdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(DSCDemodBaseband::MsgConfigureDSCDemodBaseband, Message)
DSCDemodBaseband::DSCDemodBaseband(DSCDemod *packetDemod) :
m_sink(packetDemod),
m_running(false)
{
qDebug("DSCDemodBaseband::DSCDemodBaseband");
m_sink.setScopeSink(&m_scopeSink);
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
DSCDemodBaseband::~DSCDemodBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void DSCDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void DSCDemodBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&DSCDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void DSCDemodBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&DSCDemodBaseband::handleData
);
m_running = false;
}
void DSCDemodBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void DSCDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void DSCDemodBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void DSCDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool DSCDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureDSCDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureDSCDemodBaseband& cfg = (MsgConfigureDSCDemodBaseband&) cmd;
qDebug() << "DSCDemodBaseband::handleMessage: MsgConfigureDSCDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "DSCDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
// We can run with very slow sample rate (E.g. 4k), but we don't want FIFO getting too small
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(std::max(notif.getSampleRate(), 48000)));
return true;
}
else
{
return false;
}
}
void DSCDemodBaseband::applySettings(const DSCDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
int DSCDemodBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}
void DSCDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

View File

@ -0,0 +1,103 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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_DSCDEMODBASEBAND_H
#define INCLUDE_DSCDEMODBASEBAND_H
#include <QObject>
#include <QRecursiveMutex>
#include "dsp/samplesinkfifo.h"
#include "dsp/scopevis.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "dscdemodsink.h"
class DownChannelizer;
class ChannelAPI;
class DSCDemod;
class ScopeVis;
class DSCDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureDSCDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const DSCDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureDSCDemodBaseband* create(const DSCDemodSettings& settings, bool force)
{
return new MsgConfigureDSCDemodBaseband(settings, force);
}
private:
DSCDemodSettings m_settings;
bool m_force;
MsgConfigureDSCDemodBaseband(const DSCDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
DSCDemodBaseband(DSCDemod *packetDemod);
~DSCDemodBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_sink.getMagSqLevels(avg, peak, nbSamples);
}
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setBasebandSampleRate(int sampleRate);
int getChannelSampleRate() const;
ScopeVis *getScopeSink() { return &m_scopeSink; }
void setChannel(ChannelAPI *channel);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }
void setFifoLabel(const QString& label) { m_sampleFifo.setLabel(label); }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
DSCDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
DSCDemodSettings m_settings;
ScopeVis m_scopeSink;
bool m_running;
QRecursiveMutex m_mutex;
bool handleMessage(const Message& cmd);
void calculateOffset(DSCDemodSink *sink);
void applySettings(const DSCDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_DSCDEMODBASEBAND_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,171 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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_DSCDEMODGUI_H
#define INCLUDE_DSCDEMODGUI_H
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "util/aprsfi.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "dscdemod.h"
#include "dscdemodsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class ScopeVis;
class DSCDemod;
class DSCDemodGUI;
namespace Ui {
class DSCDemodGUI;
}
class DSCDemodGUI;
class DSCDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static DSCDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
virtual void setWorkspaceIndex(int index) { m_settings.m_workspaceIndex = index; };
virtual int getWorkspaceIndex() const { return m_settings.m_workspaceIndex; };
virtual void setGeometryBytes(const QByteArray& blob) { m_settings.m_geometryBytes = blob; };
virtual QByteArray getGeometryBytes() const { return m_settings.m_geometryBytes; };
virtual QString getTitle() const { return m_settings.m_title; };
virtual QColor getTitleColor() const { return m_settings.m_rgbColor; };
virtual void zetHidden(bool hidden) { m_settings.m_hidden = hidden; }
virtual bool getHidden() const { return m_settings.m_hidden; }
virtual ChannelMarker& getChannelMarker() { return m_channelMarker; }
virtual int getStreamIndex() const { return m_settings.m_streamIndex; }
virtual void setStreamIndex(int streamIndex) { m_settings.m_streamIndex = streamIndex; }
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
Ui::DSCDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
DSCDemodSettings m_settings;
qint64 m_deviceCenterFrequency;
bool m_doApplySettings;
ScopeVis* m_scopeVis;
DSCDemod* m_dscDemod;
int m_basebandSampleRate;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
APRSFi *m_aprsFi;
QMenu *m_menu; // Column select context menu
QStringList m_mapItems;
enum MessageCol {
MESSAGE_COL_RX_DATE,
MESSAGE_COL_RX_TIME,
MESSAGE_COL_FORMAT,
MESSAGE_COL_ADDRESS,
MESSAGE_COL_ADDRESS_COUNTRY,
MESSAGE_COL_ADDRESS_TYPE,
MESSAGE_COL_ADDRESS_NAME,
MESSAGE_COL_CATEGORY,
MESSAGE_COL_SELF_ID,
MESSAGE_COL_SELF_ID_COUNTRY,
MESSAGE_COL_SELF_ID_TYPE,
MESSAGE_COL_SELF_ID_NAME,
MESSAGE_COL_SELF_ID_RANGE,
MESSAGE_COL_TELECOMMAND_1,
MESSAGE_COL_TELECOMMAND_2,
MESSAGE_COL_RX,
MESSAGE_COL_TX,
MESSAGE_COL_POSITION,
MESSAGE_COL_DISTRESS_ID,
MESSAGE_COL_DISTRESS,
MESSAGE_COL_NUMBER,
MESSAGE_COL_TIME,
MESSAGE_COL_COMMS,
MESSAGE_COL_EOS,
MESSAGE_COL_ECC,
MESSAGE_COL_ERRORS,
MESSAGE_COL_VALID,
MESSAGE_COL_RSSI
};
explicit DSCDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~DSCDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void messageReceived(const DSCMessage& message, int errors, float rssi);
bool handleMessage(const Message& message);
void makeUIConnections();
void updateAbsoluteCenterFrequency();
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked);
void createMenuOpenURLAction(QMenu* tableContextMenu, const QString& text, const QString& url, const QString& arg);
void createMenuFindOnMapAction(QMenu* tableContextMenu, const QString& text, const QString& target);
void sendAreaToMapFeature(const QString& name, const QString& address, const QString& text);
void clearAreaFromMapFeature(const QString& name);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_filterInvalid_clicked(bool checked=false);
void on_filterColumn_currentIndexChanged(int index);
void on_filter_editingFinished();
void on_clearTable_clicked();
void on_udpEnabled_clicked(bool checked);
void on_udpAddress_editingFinished();
void on_udpPort_editingFinished();
void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked();
void on_logOpen_clicked();
void on_feed_clicked(bool checked=false);
void on_feed_rightClicked(const QPoint &point);
void filterRow(int row);
void filter();
void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void messages_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
void customContextMenuRequested(QPoint point);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
void aprsFiDataUpdated(const QList<APRSFi::AISData>& data);
};
#endif // INCLUDE_DSCDEMODGUI_H

View File

@ -0,0 +1,982 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DSCDemodGUI</class>
<widget class="RollupContents" name="DSCDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>411</width>
<height>751</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>Packet Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>121</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="powerLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="udpLayout">
<item>
<widget class="QLabel" name="udpLabel">
<property name="text">
<string>UDP</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="udpEnabled">
<property name="toolTip">
<string>Send messages via UDP</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpAddress">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP address</string>
</property>
<property name="inputMask">
<string/>
</property>
<property name="text">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="udpSeparator">
<property name="text">
<string>:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpPort">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP port</string>
</property>
<property name="inputMask">
<string>00000</string>
</property>
<property name="text">
<string>4530</string>
</property>
</widget>
</item>
<item>
<spacer name="udpSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="toolbarLayout">
<item>
<widget class="QLabel" name="filterLabel">
<property name="text">
<string>Filter</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="filterColumn">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Display messages only from the specified station</string>
</property>
<item>
<property name="text">
<string>Date</string>
</property>
</item>
<item>
<property name="text">
<string>Time</string>
</property>
</item>
<item>
<property name="text">
<string>Format</string>
</property>
</item>
<item>
<property name="text">
<string>To</string>
</property>
</item>
<item>
<property name="text">
<string>To Country</string>
</property>
</item>
<item>
<property name="text">
<string>To Type</string>
</property>
</item>
<item>
<property name="text">
<string>To Name</string>
</property>
</item>
<item>
<property name="text">
<string>Category</string>
</property>
</item>
<item>
<property name="text">
<string>From</string>
</property>
</item>
<item>
<property name="text">
<string>From Country</string>
</property>
</item>
<item>
<property name="text">
<string>From Type</string>
</property>
</item>
<item>
<property name="text">
<string>From Name</string>
</property>
</item>
<item>
<property name="text">
<string>Range</string>
</property>
</item>
<item>
<property name="text">
<string>Telecommand 1</string>
</property>
</item>
<item>
<property name="text">
<string>Telecommand 2</string>
</property>
</item>
<item>
<property name="text">
<string>RX</string>
</property>
</item>
<item>
<property name="text">
<string>TX</string>
</property>
</item>
<item>
<property name="text">
<string>Position</string>
</property>
</item>
<item>
<property name="text">
<string>Distress Id</string>
</property>
</item>
<item>
<property name="text">
<string>Distress</string>
</property>
</item>
<item>
<property name="text">
<string>Number</string>
</property>
</item>
<item>
<property name="text">
<string>Time</string>
</property>
</item>
<item>
<property name="text">
<string>Comms</string>
</property>
</item>
<item>
<property name="text">
<string>EOS</string>
</property>
</item>
<item>
<property name="text">
<string>ECC</string>
</property>
</item>
<item>
<property name="text">
<string>Errors</string>
</property>
</item>
<item>
<property name="text">
<string>Valid</string>
</property>
</item>
<item>
<property name="text">
<string>RSSI</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filter">
<property name="toolTip">
<string>Filter regular expression</string>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="filterInvalid">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>When checked, invalid messages are filtered from the table</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/funnel.png</normaloff>:/funnel.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="toolbarSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="feed">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Feed messages to yaddnet.org</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/txon.png</normaloff>:/txon.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Start/stop logging of received messages to .csv file</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/record_off.png</normaloff>:/record_off.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="logFilename">
<property name="toolTip">
<string>Set log .csv filename</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/save.png</normaloff>:/save.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="logOpen">
<property name="toolTip">
<string>Read messages from .csv log file</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="clearTable">
<property name="toolTip">
<string>Clear messages</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>140</y>
<width>381</width>
<height>241</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Received Messages</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Received messages</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Date</string>
</property>
<property name="toolTip">
<string>Local date message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Time</string>
</property>
<property name="toolTip">
<string>Local time message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Format</string>
</property>
<property name="toolTip">
<string>Format specifier</string>
</property>
</column>
<column>
<property name="text">
<string>To</string>
</property>
<property name="toolTip">
<string>Address (MMSI or coordinates) of who the message is to</string>
</property>
</column>
<column>
<property name="text">
<string>Country</string>
</property>
<property name="toolTip">
<string>Country with jurisdiction of the destination of the message</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>MMSI type of the destination of the message</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Name of the station the message is to</string>
</property>
</column>
<column>
<property name="text">
<string>Category</string>
</property>
<property name="toolTip">
<string>Message category</string>
</property>
</column>
<column>
<property name="text">
<string>From</string>
</property>
<property name="toolTip">
<string>MMSI of sender of message</string>
</property>
</column>
<column>
<property name="text">
<string>Country</string>
</property>
<property name="toolTip">
<string>Country with jurisdiction of the sender of the message</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>MMSI type of the sender of the message</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Name of the station the message is from</string>
</property>
</column>
<column>
<property name="text">
<string>Range (km)</string>
</property>
<property name="toolTip">
<string>Distance in kilometers from My Position to ship's position obtained from aprs.fi</string>
</property>
</column>
<column>
<property name="text">
<string>Telecommand 1</string>
</property>
<property name="toolTip">
<string>Telecommand</string>
</property>
</column>
<column>
<property name="text">
<string>Telecommand 2</string>
</property>
<property name="toolTip">
<string>Telecommand</string>
</property>
</column>
<column>
<property name="text">
<string>RX</string>
</property>
<property name="toolTip">
<string>RX frequency (Hz) or channel</string>
</property>
</column>
<column>
<property name="text">
<string>TX</string>
</property>
<property name="toolTip">
<string>TX frequency (Hz) or channel</string>
</property>
</column>
<column>
<property name="text">
<string>Position</string>
</property>
<property name="toolTip">
<string>Position of ship in degrees and minutes</string>
</property>
</column>
<column>
<property name="text">
<string>Distress Id</string>
</property>
<property name="toolTip">
<string>MMSI of ship in distress</string>
</property>
</column>
<column>
<property name="text">
<string>Distress</string>
</property>
<property name="toolTip">
<string>Nature of distress</string>
</property>
</column>
<column>
<property name="text">
<string>Number</string>
</property>
<property name="toolTip">
<string>Telephone number</string>
</property>
</column>
<column>
<property name="text">
<string>Time</string>
</property>
<property name="toolTip">
<string>UTC Time</string>
</property>
</column>
<column>
<property name="text">
<string>Comms</string>
</property>
<property name="toolTip">
<string>Subsequent communications</string>
</property>
</column>
<column>
<property name="text">
<string>EOS</string>
</property>
<property name="toolTip">
<string>End of Signal</string>
</property>
</column>
<column>
<property name="text">
<string>ECC</string>
</property>
<property name="toolTip">
<string>Error checking code</string>
</property>
</column>
<column>
<property name="text">
<string>Errors</string>
</property>
<property name="toolTip">
<string>Number of symbols received with errors (which may have been corrected if ECC OK)</string>
</property>
</column>
<column>
<property name="text">
<string>Valid</string>
</property>
<property name="toolTip">
<string>Whether the message is determined to be valid (contains no detected errors)</string>
</property>
</column>
<column>
<property name="text">
<string>RSSI</string>
</property>
<property name="toolTip">
<string>Received signal strenth indicator (Average power in dBFS)</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="scopeContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>390</y>
<width>716</width>
<height>341</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>714</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Waveforms</string>
</property>
<layout class="QVBoxLayout" name="transmittedLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="GLScope" name="glScope" native="true">
<property name="minimumSize">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLScopeGUI" name="scopeGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>RollupContents</class>
<extends>QWidget</extends>
<header>gui/rollupcontents.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScope</class>
<extends>QWidget</extends>
<header>gui/glscope.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeGUI</class>
<extends>QWidget</extends>
<header>gui/glscopegui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>deltaFrequency</tabstop>
<tabstop>udpEnabled</tabstop>
<tabstop>filterColumn</tabstop>
<tabstop>logEnable</tabstop>
<tabstop>logFilename</tabstop>
<tabstop>logOpen</tabstop>
<tabstop>clearTable</tabstop>
<tabstop>messages</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,93 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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 <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "dscdemodgui.h"
#endif
#include "dscdemod.h"
#include "dscdemodwebapiadapter.h"
#include "dscdemodplugin.h"
const PluginDescriptor DSCDemodPlugin::m_pluginDescriptor = {
DSCDemod::m_channelId,
QStringLiteral("DSC Demodulator"),
QStringLiteral("7.14.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
DSCDemodPlugin::DSCDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& DSCDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void DSCDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(DSCDemod::m_channelIdURI, DSCDemod::m_channelId, this);
}
void DSCDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
DSCDemod *instance = new DSCDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* DSCDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* DSCDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return DSCDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* DSCDemodPlugin::createChannelWebAPIAdapter() const
{
return new DSCDemodWebAPIAdapter();
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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_DSCDEMODPLUGIN_H
#define INCLUDE_DSCDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class DSCDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.dscdemod")
public:
explicit DSCDemodPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_DSCDEMODPLUGIN_H

View File

@ -0,0 +1,206 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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 <QColor>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "dscdemodsettings.h"
DSCDemodSettings::DSCDemodSettings() :
m_channelMarker(nullptr),
m_scopeGUI(nullptr),
m_rollupState(nullptr)
{
resetToDefaults();
}
void DSCDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 450.0f; // OBW for 2FSK = 2 * deviation + data rate. Then add a bit for carrier frequency offset
m_filterInvalid = true;
m_filterColumn = 4;
m_filter = "";
m_udpEnabled = false;
m_udpAddress = "127.0.0.1";
m_udpPort = 9999;
m_logFilename = "dsc_log.csv";
m_logEnabled = false;
m_feed = true;
m_rgbColor = QColor(181, 230, 29).rgb();
m_title = "DSC Demodulator";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_workspaceIndex = 0;
m_hidden = false;
for (int i = 0; i < DSCDEMOD_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray DSCDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_streamIndex);
s.writeBool(3, m_filterInvalid);
s.writeS32(4, m_filterColumn);
s.writeString(5, m_filter);
if (m_channelMarker) {
s.writeBlob(6, m_channelMarker->serialize());
}
s.writeFloat(7, m_rfBandwidth);
s.writeBool(9, m_udpEnabled);
s.writeString(10, m_udpAddress);
s.writeU32(11, m_udpPort);
s.writeString(12, m_logFilename);
s.writeBool(13, m_logEnabled);
s.writeBool(14, m_feed);
s.writeU32(20, m_rgbColor);
s.writeString(21, m_title);
s.writeBool(22, m_useReverseAPI);
s.writeString(23, m_reverseAPIAddress);
s.writeU32(24, m_reverseAPIPort);
s.writeU32(25, m_reverseAPIDeviceIndex);
s.writeU32(26, m_reverseAPIChannelIndex);
if (m_rollupState) {
s.writeBlob(27, m_rollupState->serialize());
}
s.writeS32(28, m_workspaceIndex);
s.writeBlob(29, m_geometryBytes);
s.writeBool(30, m_hidden);
s.writeBlob(31, m_scopeGUI->serialize());
for (int i = 0; i < DSCDEMOD_COLUMNS; i++) {
s.writeS32(100 + i, m_columnIndexes[i]);
}
for (int i = 0; i < DSCDEMOD_COLUMNS; i++) {
s.writeS32(200 + i, m_columnSizes[i]);
}
return s.final();
}
bool DSCDemodSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(2, &m_streamIndex, 0);
d.readBool(3, &m_filterInvalid, true);
d.readS32(4, &m_filterColumn, 5);
d.readString(5, &m_filter, "");
if (m_channelMarker)
{
d.readBlob(6, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readFloat(7, &m_rfBandwidth, 450.0f);
d.readBool(9, &m_udpEnabled);
d.readString(10, &m_udpAddress);
d.readU32(11, &utmp);
if ((utmp > 1023) && (utmp < 65535)) {
m_udpPort = utmp;
} else {
m_udpPort = 9999;
}
d.readString(12, &m_logFilename, "dsc_log.csv");
d.readBool(13, &m_logEnabled, false);
d.readBool(14, &m_feed, true);
d.readU32(20, &m_rgbColor, QColor(181, 230, 29).rgb());
d.readString(21, &m_title, "DSC Demodulator");
d.readBool(22, &m_useReverseAPI, false);
d.readString(23, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(24, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(25, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(26, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(27, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(28, &m_workspaceIndex, 0);
d.readBlob(29, &m_geometryBytes);
d.readBool(30, &m_hidden, false);
if (m_scopeGUI)
{
d.readBlob(31, &bytetmp);
m_scopeGUI->deserialize(bytetmp);
}
for (int i = 0; i < DSCDEMOD_COLUMNS; i++) {
d.readS32(100 + i, &m_columnIndexes[i], i);
}
for (int i = 0; i < DSCDEMOD_COLUMNS; i++) {
d.readS32(200 + i, &m_columnSizes[i], -1);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,76 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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_DSCDEMODSETTINGS_H
#define INCLUDE_DSCDEMODSETTINGS_H
#include <QByteArray>
class Serializable;
// Number of columns in the table
#define DSCDEMOD_COLUMNS 28
struct DSCDemodSettings
{
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth; // Not currently in GUI as probably doesn't need to be adjusted
bool m_filterInvalid;
int m_filterColumn;
QString m_filter;
bool m_udpEnabled;
QString m_udpAddress;
uint16_t m_udpPort;
bool m_feed;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
QString m_logFilename;
bool m_logEnabled;
Serializable *m_scopeGUI;
Serializable *m_rollupState;
int m_workspaceIndex;
QByteArray m_geometryBytes;
bool m_hidden;
int m_columnIndexes[DSCDEMOD_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[DSCDEMOD_COLUMNS]; //!< Size of the columns in the table
static const int DSCDEMOD_CHANNEL_SAMPLE_RATE = 1000; // Must be integer multiple of baud rate (x10)
static const int DSCDEMOD_BAUD_RATE = 100;
static const int DSCDEMOD_FREQUENCY_SHIFT = 170;
static const int m_scopeStreams = 10;
DSCDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_DSCDEMODSETTINGS_H */

View File

@ -0,0 +1,331 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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 <QDebug>
#include <complex.h>
#include "dsp/dspengine.h"
#include "dsp/scopevis.h"
#include "util/db.h"
#include "util/popcount.h"
#include "maincore.h"
#include "dscdemod.h"
#include "dscdemodsink.h"
DSCDemodSink::DSCDemodSink(DSCDemod *packetDemod) :
m_dscDemod(packetDemod),
m_channelSampleRate(DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_exp(nullptr),
m_sampleBufferIndex(0)
{
m_magsq = 0.0;
for (int i = 0; i < DSCDemodSettings::m_scopeStreams; i++) {
m_sampleBuffer[i].resize(m_sampleBufferSize);
}
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
m_lowpassComplex1.create(301, DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE, DSCDemodSettings::DSCDEMOD_BAUD_RATE * 1.1);
m_lowpassComplex2.create(301, DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE, DSCDemodSettings::DSCDEMOD_BAUD_RATE * 1.1);
}
DSCDemodSink::~DSCDemodSink()
{
delete[] m_exp;
}
void DSCDemodSink::sampleToScope(Complex sample, Real abs1Filt, Real abs2Filt, Real unbiasedData, Real biasedData)
{
if (m_scopeSink)
{
m_sampleBuffer[0][m_sampleBufferIndex] = sample;
m_sampleBuffer[1][m_sampleBufferIndex] = Complex(m_magsq, 0.0f);
m_sampleBuffer[2][m_sampleBufferIndex] = Complex(abs1Filt, 0.0f);
m_sampleBuffer[3][m_sampleBufferIndex] = Complex(abs2Filt, 0.0f);
m_sampleBuffer[4][m_sampleBufferIndex] = Complex(unbiasedData, 0.0f);
m_sampleBuffer[5][m_sampleBufferIndex] = Complex(biasedData, 0.0f);
m_sampleBuffer[6][m_sampleBufferIndex] = Complex(m_data, 0.0f);
m_sampleBuffer[7][m_sampleBufferIndex] = Complex(m_clock, 0.0f);
m_sampleBuffer[8][m_sampleBufferIndex] = Complex(m_bit, 0.0f);
m_sampleBuffer[9][m_sampleBufferIndex] = Complex(m_gotSOP, 0.0f);
m_sampleBufferIndex++;
if (m_sampleBufferIndex == m_sampleBufferSize)
{
std::vector<ComplexVector::const_iterator> vbegin;
for (int i = 0; i < DSCDemodSettings::m_scopeStreams; i++) {
vbegin.push_back(m_sampleBuffer[i].begin());
}
m_scopeSink->feed(vbegin, m_sampleBufferSize);
m_sampleBufferIndex = 0;
}
}
}
void DSCDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void DSCDemodSink::processOneSample(Complex &ci)
{
// Calculate average and peak levels for level meter
double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();;
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
// Sum power while data is being received
if (m_gotSOP)
{
m_rssiMagSqSum += magsq;
m_rssiMagSqCount++;
}
ci /= SDR_RX_SCALEF;
// Correlate with expected frequencies
Complex exp = m_exp[m_expIdx];
m_expIdx = (m_expIdx + 1) % m_expLength;
Complex corr1 = ci * exp;
Complex corr2 = ci * std::conj(exp);
// Low pass filter
Real abs1Filt = std::abs(m_lowpassComplex1.filter(corr1));
Real abs2Filt = std::abs(m_lowpassComplex2.filter(corr2));
// Envelope calculation
m_movMax1(abs1Filt);
m_movMax2(abs2Filt);
Real env1 = m_movMax1.getMaximum();
Real env2 = m_movMax2.getMaximum();
// Automatic threshold correction to compensate for frequency selective fading
// http://www.w7ay.net/site/Technical/ATC/index.html
Real bias1 = abs1Filt - 0.5 * env1;
Real bias2 = abs2Filt - 0.5 * env2;
Real unbiasedData = abs1Filt - abs2Filt;
Real biasedData = bias1 - bias2;
// Save current data for edge detection
m_dataPrev = m_data;
// Set data according to stongest correlation
m_data = biasedData > 0;
// Calculate timing error (we expect clockCount to be 0 when data changes), and add a proportion of it
if (m_data && !m_dataPrev) {
m_clockCount -= m_clockCount * 0.25;
}
m_clockCount += 1.0;
if (m_clockCount >= m_samplesPerBit/2.0-1.0)
{
// Sample in middle of symbol
receiveBit(m_data);
m_clock = 1;
// Wrap clock counter
m_clockCount -= m_samplesPerBit;
}
else
{
m_clock = 0;
}
sampleToScope(ci, abs1Filt, abs2Filt, unbiasedData, biasedData);
}
const QList<DSCDemodSink::PhasingPattern> DSCDemodSink::m_phasingPatterns = {
{0b1011111001'1111011001'1011111001, 9}, // 125 111 125
{0b1111011001'1011111001'0111011010, 8}, // 111 125 110
{0b1011111001'0111011010'1011111001, 7}, // 125 110 125
{0b0111011010'1011111001'1011011010, 6}, // 110 125 109
{0b1011111001'1011011010'1011111001, 5}, // 125 109 125
{0b1011011010'1011111001'0011011011, 4}, // 109 125 108
{0b1011111001'0011011011'1011111001, 3}, // 125 108 125
{0b0011011011'1011111001'1101011010, 2}, // 108 125 107
{0b1011111001'1101011010'1011111001, 1}, // 125 107 125
{0b1101011010'1011111001'0101011011, 0}, // 107 125 106
};
void DSCDemodSink::receiveBit(bool bit)
{
m_bit = bit;
// Store in shift reg
m_bits = (m_bits << 1) | m_bit;
m_bitCount++;
if (!m_gotSOP)
{
// Dot pattern - 200 1/0s or 20 1/0s
// Phasing pattern - 6 DX=125 RX=111 110 109 108 107 106 105 104
// Phasing is considered to be achieved when two DXs and one RX, or two RXs and one DX, or three RXs in the appropriate DX or RX positions, respectively, are successfully received.
if (m_bitCount == 10*3)
{
m_bitCount--;
unsigned int pat = m_bits & 0x3fffffff;
for (int i = 0; i < m_phasingPatterns.size(); i++)
{
if (pat == m_phasingPatterns[i].m_pattern)
{
m_dscDecoder.init(m_phasingPatterns[i].m_offset);
m_gotSOP = true;
m_bitCount = 0;
break;
}
}
}
}
else
{
if (m_bitCount == 10)
{
if (m_dscDecoder.decodeBits(m_bits & 0x3ff))
{
QByteArray bytes = m_dscDecoder.getMessage();
DSCMessage message(bytes, QDateTime::currentDateTime());
//qDebug() << "RX Bytes: " << bytes.toHex();
//qDebug() << "DSC Message: " << message.toString();
if (getMessageQueueToChannel())
{
float rssi = CalcDb::dbPower(m_rssiMagSqSum / m_rssiMagSqCount);
DSCDemod::MsgMessage *msg = DSCDemod::MsgMessage::create(message, m_dscDecoder.getErrors(), rssi);
getMessageQueueToChannel()->push(msg);
}
// Reset demod
init();
}
m_bitCount = 0;
}
}
}
void DSCDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "DSCDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) channelSampleRate / (Real) DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void DSCDemodSink::init()
{
m_expIdx = 0;
m_bit = 0;
m_bits = 0;
m_bitCount = 0;
m_gotSOP = false;
m_errorCount = 0;
m_clockCount = -m_samplesPerBit/2.0;
m_clock = 0;
m_int = 0.0;
m_rssiMagSqSum = 0.0;
m_rssiMagSqCount = 0;
m_consecutiveErrors = 0;
m_messageBuffer = "";
}
void DSCDemodSink::applySettings(const DSCDemodSettings& settings, bool force)
{
qDebug() << "DSCDemodSink::applySettings:"
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
if (force)
{
delete[] m_exp;
m_exp = new Complex[m_expLength];
Real f0 = 0.0f;
for (int i = 0; i < m_expLength; i++)
{
m_exp[i] = Complex(cos(f0), sin(f0));
f0 += 2.0f * (Real)M_PI * (DSCDemodSettings::DSCDEMOD_FREQUENCY_SHIFT/2.0f) / DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE;
}
init();
m_movMax1.setSize(m_samplesPerBit * 8);
m_movMax2.setSize(m_samplesPerBit * 8);
}
m_settings = settings;
}

View File

@ -0,0 +1,154 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2023 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_DSCDEMODSINK_H
#define INCLUDE_DSCDEMODSINK_H
#include <QVector>
#include <QMap>
#include <QDateTime>
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "util/movingaverage.h"
#include "util/movingmaximum.h"
#include "util/messagequeue.h"
#include "util/dsc.h"
#include "dscdemodsettings.h"
class ChannelAPI;
class DSCDemod;
class ScopeVis;
class DSCDemodSink : public ChannelSampleSink {
public:
DSCDemodSink(DSCDemod *packetDemod);
~DSCDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setScopeSink(ScopeVis* scopeSink) { m_scopeSink = scopeSink; }
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const DSCDemodSettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
double getMagSq() const { return m_magsq; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
struct PhasingPattern {
unsigned int m_pattern;
unsigned int m_offset;
};
ScopeVis* m_scopeSink; // Scope GUI to display baseband waveform
DSCDemod *m_dscDemod;
DSCDemodSettings m_settings;
ChannelAPI *m_channel;
int m_channelSampleRate;
int m_channelFrequencyOffset;
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MessageQueue *m_messageQueueToChannel;
MovingAverageUtil<Real, double, 16> m_movingAverage;
Lowpass<Complex> m_lowpassComplex1;
Lowpass<Complex> m_lowpassComplex2;
MovingMaximum<Real> m_movMax1;
MovingMaximum<Real> m_movMax2;
static const int m_expLength = 600;
static const int m_samplesPerBit = DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE / DSCDemodSettings::DSCDEMOD_BAUD_RATE;
Complex *m_exp;
int m_expIdx;
int m_bit;
bool m_data;
bool m_dataPrev;
double m_clockCount;
double m_clock;
double m_int;
double m_rssiMagSqSum;
int m_rssiMagSqCount;
unsigned int m_bits;
int m_bitCount;
bool m_gotSOP;
int m_errorCount;
int m_consecutiveErrors;
QString m_messageBuffer;
DSCDecoder m_dscDecoder;
static const QList<PhasingPattern> m_phasingPatterns;
ComplexVector m_sampleBuffer[DSCDemodSettings::m_scopeStreams];
static const int m_sampleBufferSize = DSCDemodSettings::DSCDEMOD_CHANNEL_SAMPLE_RATE / 20;
int m_sampleBufferIndex;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
void sampleToScope(Complex sample, Real abs1Filt, Real abs2Filt, Real unbiasedData, Real biasedData);
void init();
void receiveBit(bool bit);
};
#endif // INCLUDE_DSCDEMODSINK_H

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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 "SWGChannelSettings.h"
#include "dscdemod.h"
#include "dscdemodwebapiadapter.h"
DSCDemodWebAPIAdapter::DSCDemodWebAPIAdapter()
{}
DSCDemodWebAPIAdapter::~DSCDemodWebAPIAdapter()
{}
int DSCDemodWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setDscDemodSettings(new SWGSDRangel::SWGDSCDemodSettings());
response.getDscDemodSettings()->init();
DSCDemod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int DSCDemodWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
DSCDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2023 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_DSCDEMOD_WEBAPIADAPTER_H
#define INCLUDE_DSCDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "dscdemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class DSCDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
DSCDemodWebAPIAdapter();
virtual ~DSCDemodWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
DSCDemodSettings m_settings;
};
#endif // INCLUDE_DSCDEMOD_WEBAPIADAPTER_H

View File

@ -0,0 +1,121 @@
<h1>DSC (Digital Selective Calling) Demodulator Plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate DSC (Digital Selective Calling) transmissions, which are short, pre-defined digital messages transmitted by marine radios.
DSC messages are transmitted using FSK with 170Hz separation at 100 baud, as specified by [ITU-R M.493](https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.493-15-201901-I!!PDF-E.pdf]).
DSC messages can be transmitted on a variety of frequencies, but are most commonly found on: 2,187.5kHz, 8,414.5kHz, 16,804.5kHz and 156.525 MHz (VHF Ch. 70).
<h2>Interface</h2>
The top and bottom bars of the channel window are described [here](../../../sdrgui/channel/readme.md)
![DSC Demodulator plugin GUI](../../../doc/img/DSCDemod_plugin.png)
<h3>1: Frequency shift from center frequency of reception</h3>
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>4: UDP</h3>
When checked, received messages are forwarded to the specified UDP address (5) and port (6).
<h3>5: UDP address</h3>
IP address of the host to forward received messages to via UDP.
<h3>6: UDP port</h3>
UDP port number to forward received messages to.
<h3>7: Filter</h3>
This drop down displays a list of all columns which can be used for filtering (8).
<h3>8: Filter Reg Exp</h3>
Specifes a [regular expression](https://regexr.com/) used to filter data in the table, using data in the column specified by (7).
<h3>9: Filter Invalid</h3>
When checked, invalid messages will be filtered from the table.
<h3>10: Feed to YaDDNet</h3>
When checked, valid messages will be forwarded to [YaDDNet](http://yaddnet.org/).
YaDDNet aggregates DSC messages from different users around the world storing them in a searchable database.
The messages are submitted with Preferences > My Position... > Station name used as the ID.
Right click to open http://yaddnet.org/ in your browser, showing recent messages received from this ID.
<h3>11: Start/stop Logging Messages to .csv File</h3>
When checked, writes all received messages to a .csv file, specified by (12).
<h3>12: .csv Log Filename</h3>
Click to specify the name of the .csv file which received messasges are logged to.
<h3>13: Read Data from .csv File</h3>
Click to specify a previously written .csv log file, which is read and used to update the table.
<h3>14: Received Messages Table</h3>
![DSC Demodulator plugin GUI](../../../doc/img/DSCDemod_plugin_messages.png)
The received messages table displays the contents of the messages that have been received. Most of the fields are decoded directly from the message,
however, a few, such as ship names, are found by querying [aprs.fi](http://aprs.fi) with the MMSI.
* Date - Date the message was received.
* Time - Time the message was received.
* Format - The message format (Selective call, Geographic call, Group call, Distress alert, All ships, Automatic call).
* To - Who the message is to (The address field). This is typically an MMSI, but can also be a geographic area.
* Country - Country with jurisdiction of the destination of the message.
* Type - MMSI type of the destination of the message (Ship / Coast station).
* Name - The name of ship / station the message is for (From aprs.fi).
* Category - The message category (Safety, Routine, Urgency, Distress).
* From - MMSI of sender of message.
* Country - Country with jurisdiction of the sender of the message.
* Type - MMSI type of the sender of the message (Ship / Coast station).
* Name - The name of ship / station sending the message (From aprs.fi).
* Range (km) - The distance in kilometers from My Position (specified under Preferences > My Position) to the position of the sender of the message, as reported by aprs.fi (usually from AIS data).
* Telecommand 1 - First telecommand (Test / J3E (SSB) telephony and so on).
* Telecommand 2 - Second telecommand.
* RX - RX frequency (Hz) or channel.
* TX - TX frequency (Hz) or channel.
* Position - Position of ship in degrees and minutes.
* Distress Id - MMSI of ship in distress.
* Distress - Nature of distress (Sinking, Collision, Man overboard and so on).
* Number - Telephone number.
* Time - UTC Time.
* Comms - Subsequent communications.
* EOS - End of Signal (Req ACK, ACK, EOS).
* ECC - Indicates if calculated ECC (Error Checking Code) matches received ECC.
* Errors - Number of symbols received with errors (which may have been corrected if ECC is OK)
* Valid - Whether the message is determined to be valid (contains no detected errors).
* RSSI - Average channel power in dB, while receiving the message.
Right clicking on the header will open a menu allowing you to select which columns are visible.
To reorder the columns, left click and drag left or right a column header.
Left click on a header to sort the table by the data in that column.
Right clicking on a cell will open a pop-up menu that that allows:
* MMSIs to be looked up on some popular web sites,
* Ships to be located on the [Map](../../feature/map/readme.md) if also being tracked via AIS,
* Tune SSB Demods to the RX frequency or
* Geographical call areas to be drawn on the [Map](../../feature/map/readme.md):
![DSC Demodulator plugin GUI](../../../doc/img/DSCDemod_plugin_geocall.png)

View File

@ -145,7 +145,8 @@ bool NavtexDemodBaseband::handleMessage(const Message& cmd)
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "NavtexDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
// We can run with very slow sample rate (E.g. 4k), but we don't want FIFO getting too small
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(std::max(notif.getSampleRate(), 48000)));
return true;
}

View File

@ -222,12 +222,12 @@ void NavtexDemodGUI::messageReceived(const NavtexMessage& message, int errors, f
rssiItem->setData(Qt::DisplayRole, rssi);
}
messageItem->setText(message.m_message);
filterRow(row);
ui->messages->setSortingEnabled(true);
ui->messages->resizeRowToContents(row);
if (scrollToBottom) {
ui->messages->scrollToBottom();
}
filterRow(row);
}
bool NavtexDemodGUI::handleMessage(const Message& message)

View File

@ -193,11 +193,11 @@ void PacketDemodGUI::packetReceived(QByteArray packet)
pidItem->setText(ax25.m_pid);
dataASCIIItem->setText(ax25.m_dataASCII);
dataHexItem->setText(ax25.m_dataHex);
filterRow(row);
ui->packets->setSortingEnabled(true);
if (scrollToBottom) {
ui->packets->scrollToBottom();
}
filterRow(row);
}
else
qDebug() << "Unsupported AX.25 packet: " << packet;

View File

@ -249,11 +249,11 @@ void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int f
numericItem->setText(numericMessage);
evenPEItem->setText(QString("%1").arg(evenParityErrors));
bchPEItem->setText(QString("%1").arg(bchParityErrors));
filterRow(row);
ui->messages->setSortingEnabled(true);
if (scrollToBottom) {
ui->messages->scrollToBottom();
}
filterRow(row);
}
bool PagerDemodGUI::handleMessage(const Message& message)

View File

@ -298,11 +298,11 @@ void RadiosondeDemodGUI::frameReceived(const QByteArray& frame, const QDateTime&
eccItem->setData(Qt::DisplayRole, errorsCorrected);
thItem->setData(Qt::DisplayRole, threshold);
filterRow(row);
ui->frames->setSortingEnabled(true);
if (scrollToBottom) {
ui->frames->scrollToBottom();
}
filterRow(row);
delete radiosonde;
}

View File

@ -145,7 +145,8 @@ bool RttyDemodBaseband::handleMessage(const Message& cmd)
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "RttyDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
// We can run with very slow sample rate (E.g. 4k), but we don't want FIFO getting too small
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(std::max(notif.getSampleRate(), 48000)));
return true;
}

View File

@ -107,9 +107,9 @@ QByteArray HeatMapSettings::serialize() const
s.writeBlob(30, m_rollupState->serialize());
}
s.writeS32(32, m_workspaceIndex);
s.writeBlob(33, m_geometryBytes);
s.writeBool(34, m_hidden);
s.writeS32(31, m_workspaceIndex);
s.writeBlob(32, m_geometryBytes);
s.writeBool(33, m_hidden);
return s.final();
}

View File

@ -31,6 +31,7 @@
#include "gui/dialogpositioner.h"
#include "mainwindow.h"
#include "device/deviceuiset.h"
#include "util/mmsi.h"
#include "ui_aisgui.h"
#include "ais.h"
@ -377,6 +378,7 @@ void AISGUI::resizeTable()
int row = ui->vessels->rowCount();
ui->vessels->setRowCount(row + 1);
ui->vessels->setItem(row, VESSEL_COL_MMSI, new QTableWidgetItem("123456789"));
ui->vessels->setItem(row, VESSEL_COL_COUNTRY, new QTableWidgetItem("flag"));
ui->vessels->setItem(row, VESSEL_COL_TYPE, new QTableWidgetItem("Base station"));
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, new QTableWidgetItem("90.000000-"));
ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, new QTableWidgetItem("180.00000-"));
@ -503,6 +505,7 @@ void AISGUI::sendToMap(const QString &name, const QString &label,
void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
{
QTableWidgetItem *mmsiItem;
QTableWidgetItem *countryItem;
QTableWidgetItem *typeItem;
QTableWidgetItem *latitudeItem;
QTableWidgetItem *longitudeItem;
@ -534,6 +537,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
{
// Update existing item
mmsiItem = ui->vessels->item(row, VESSEL_COL_MMSI);
countryItem = ui->vessels->item(row, VESSEL_COL_COUNTRY);
typeItem = ui->vessels->item(row, VESSEL_COL_TYPE);
latitudeItem = ui->vessels->item(row, VESSEL_COL_LATITUDE);
longitudeItem = ui->vessels->item(row, VESSEL_COL_LONGITUDE);
@ -563,6 +567,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
ui->vessels->setRowCount(row + 1);
mmsiItem = new QTableWidgetItem();
countryItem = new QTableWidgetItem();
typeItem = new QTableWidgetItem();
latitudeItem = new QTableWidgetItem();
longitudeItem = new QTableWidgetItem();
@ -580,6 +585,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
lastUpdateItem = new QTableWidgetItem();
messagesItem = new QTableWidgetItem();
ui->vessels->setItem(row, VESSEL_COL_MMSI, mmsiItem);
ui->vessels->setItem(row, VESSEL_COL_COUNTRY, countryItem);
ui->vessels->setItem(row, VESSEL_COL_TYPE, typeItem);
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, latitudeItem);
ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, longitudeItem);
@ -605,7 +611,15 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
previousType = typeItem->text();
previousShipType = shipTypeItem->text();
mmsiItem->setText(QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0')));
QString mmsi = QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0'));
mmsiItem->setText(mmsi);
QIcon *flag = MMSI::getFlagIcon(mmsi);
if (flag)
{
countryItem->setSizeHint(QSize(40, 20));
countryItem->setIcon(*flag);
}
lastUpdateItem->setData(Qt::DisplayRole, dateTime);
messagesItem->setData(Qt::DisplayRole, messagesItem->data(Qt::DisplayRole).toInt() + 1);
@ -991,24 +1005,24 @@ void AISGUI::vessels_customContextMenuRequested(QPoint pos)
QAction* mmsiAction = new QAction(QString("View MMSI %1 on vesselfinder.com...").arg(mmsi), tableContextMenu);
connect(mmsiAction, &QAction::triggered, this, [mmsi]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(mmsi)));
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(mmsi)));
});
tableContextMenu->addAction(mmsiAction);
if (!imo.isEmpty())
{
QAction* imoAction = new QAction(QString("View IMO %1 on vesselfinder.net...").arg(imo), tableContextMenu);
QAction* imoAction = new QAction(QString("View IMO %1 on vesselfinder.com...").arg(imo), tableContextMenu);
connect(imoAction, &QAction::triggered, this, [imo]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(imo)));
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(imo)));
});
tableContextMenu->addAction(imoAction);
}
if (!name.isEmpty())
{
QAction* nameAction = new QAction(QString("View %1 on vesselfinder.net...").arg(name), tableContextMenu);
QAction* nameAction = new QAction(QString("View %1 on vesselfinder.com...").arg(name), tableContextMenu);
connect(nameAction, &QAction::triggered, this, [name]()->void {
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.net/vessels?name=%1").arg(name)));
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(name)));
});
tableContextMenu->addAction(nameAction);
}

View File

@ -106,6 +106,7 @@ private:
enum VesselCol {
VESSEL_COL_MMSI,
VESSEL_COL_COUNTRY,
VESSEL_COL_TYPE,
VESSEL_COL_LATITUDE,
VESSEL_COL_LONGITUDE,

View File

@ -89,10 +89,21 @@
<string>Maritime Mobile Service Identity</string>
</property>
</column>
<column>
<property name="text">
<string>Country</string>
</property>
<property name="toolTip">
<string>Country with jurisdiction over station/vessel</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>Message type</string>
</property>
</column>
<column>
<property name="text">

View File

@ -27,7 +27,7 @@
class Serializable;
// Number of columns in the tables
#define AIS_VESSEL_COLUMNS 16
#define AIS_VESSEL_COLUMNS 18
struct AISSettings
{

View File

@ -77,6 +77,7 @@ QJsonObject CZML::update(PolygonMapItem *mapItem)
if ( !mapItem->m_itemSettings->m_enabled
|| !mapItem->m_itemSettings->m_display3DTrack
|| filter(mapItem)
|| mapItem->m_deleted
)
{
// Delete obj completely (including any history)
@ -147,6 +148,15 @@ QJsonObject CZML::update(PolygonMapItem *mapItem)
polygon.insert("extrudedHeight", mapItem->m_extrudedHeight);
}
// We need to have a position, otherwise viewer entity tracking doesn't seem to work
QJsonArray coords {
mapItem->m_longitude, mapItem->m_latitude, mapItem->m_altitude
};
QJsonObject position {
{"cartographicDegrees", coords},
};
obj.insert("position", position);
obj.insert("polygon", polygon);
obj.insert("description", mapItem->m_label);
@ -165,6 +175,7 @@ QJsonObject CZML::update(PolylineMapItem *mapItem)
if ( !mapItem->m_itemSettings->m_enabled
|| !mapItem->m_itemSettings->m_display3DTrack
|| filter(mapItem)
|| mapItem->m_deleted
)
{
// Delete obj completely (including any history)
@ -214,6 +225,15 @@ QJsonObject CZML::update(PolylineMapItem *mapItem)
polyline.insert("altitudeReference", m_heightReferences[mapItem->m_altitudeReference]); // Custom code in map3d.html
}
// We need to have a position, otherwise viewer entity tracking doesn't seem to work
QJsonArray coords {
mapItem->m_longitude, mapItem->m_latitude, mapItem->m_altitude
};
QJsonObject position {
{"cartographicDegrees", coords},
};
obj.insert("position", position);
obj.insert("polyline", polyline);
obj.insert("description", mapItem->m_label);

View File

@ -194,6 +194,7 @@ Item {
Text {
id: polygonText
text: label
textFormat: TextEdit.RichText
}
}
}
@ -226,6 +227,7 @@ Item {
Text {
id: polylineText
text: label
textFormat: TextEdit.RichText
}
}
}

View File

@ -309,6 +309,7 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
addAirspace();
addAirports();
addNavtex();
addVLF();
displaySettings();
applySettings(true);
@ -419,6 +420,53 @@ void MapGUI::addIBPBeacons()
}
}
// https://sidstation.loudet.org/stations-list-en.xhtml
// https://core.ac.uk/download/pdf/224769021.pdf -- Table 1
// GQD/GQZ callsigns: https://groups.io/g/VLF/message/19212?p=%2C%2C%2C20%2C0%2C0%2C0%3A%3Arecentpostdate%2Fsticky%2C%2C19.6%2C20%2C2%2C0%2C38924431
const QList<RadioTimeTransmitter> MapGUI::m_vlfTransmitters = {
// Other signals possibly seen: 13800, 19000
{"VTX2", 17000, 8.387015, 77.752762, -1}, // South Vijayanarayanam, India
{"GQD", 19580, 54.911643, -3.278456, 100}, // Anthorn, UK, Often referred to as GBZ
{"NWC", 19800, -21.816325, 114.16546, 1000}, // Exmouth, Aus
{"ICV", 20270, 40.922946, 9.731881, 50}, // Isola di Tavolara, Italy (Can be distorted on 3D map if terrain used)
{"FTA", 20900, 48.544632, 2.579429, 50}, // Sainte-Assise, France (Satellite imagary obfuscated)
{"NPM", 21400, 21.420166, -158.151140, 600}, // Pearl Harbour, Lualuahei, USA (Not seen?)
{"HWU", 21750, 46.713129, 1.245248, 200}, // Rosnay, France
{"GQZ", 22100, 54.731799, -2.883033, 100}, // Skelton, UK (GVT in paper)
{"DHO38", 23400, 53.078900, 7.615000, 300}, // Rhauderfehn, Germany - Off air 7-8 UTC - Not seen on air!
{"NAA", 24000, 44.644506, -67.284565, 1000}, // Cutler, Maine, USA
{"TFK/NRK", 37500, 63.850365, -22.466773, 100}, // Grindavik, Iceland
{"SRC/SHR", 38000, 57.120328, 16.153083, -1}, // Ruda, Sweden
};
void MapGUI::addVLF()
{
for (int i = 0; i < m_vlfTransmitters.size(); i++)
{
SWGSDRangel::SWGMapItem vlfMapItem;
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
QString name = QString("%1").arg(m_vlfTransmitters[i].m_callsign);
vlfMapItem.setName(new QString(name));
vlfMapItem.setLatitude(m_vlfTransmitters[i].m_latitude);
vlfMapItem.setLongitude(m_vlfTransmitters[i].m_longitude);
vlfMapItem.setAltitude(0.0);
vlfMapItem.setImage(new QString("antenna.png"));
vlfMapItem.setImageRotation(0);
QString text = QString("VLF Transmitter\nCallsign: %1\nFrequency: %2 kHz")
.arg(m_vlfTransmitters[i].m_callsign)
.arg(m_vlfTransmitters[i].m_frequency/1000.0);
vlfMapItem.setText(new QString(text));
vlfMapItem.setModel(new QString("antenna.glb"));
vlfMapItem.setFixedPosition(true);
vlfMapItem.setOrientation(0);
vlfMapItem.setLabel(new QString(name));
vlfMapItem.setLabelAltitudeOffset(4.5);
vlfMapItem.setAltitudeReference(1);
update(m_map, &vlfMapItem, "VLF");
}
}
const QList<RadioTimeTransmitter> MapGUI::m_radioTimeTransmitters = {
{"MSF", 60000, 54.9075f, -3.27333f, 17}, // UK
{"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, // Germany
@ -1575,8 +1623,7 @@ void MapGUI::find(const QString& target)
}
else
{
// FIXME: Support polygon/polyline
ObjectMapItem *mapItem = m_objectMapModel.findMapItem(target);
ObjectMapItem *mapItem = (ObjectMapItem *)m_objectMapModel.findMapItem(target);
if (mapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(mapItem->getCoordinates()));
@ -1584,25 +1631,47 @@ void MapGUI::find(const QString& target)
m_cesium->track(target);
}
m_objectMapModel.moveToFront(m_objectMapModel.findMapItemIndex(target).row());
return;
}
PolylineMapItem *polylineMapItem = (PolylineMapItem *)m_polylineMapModel.findMapItem(target);
if (polylineMapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(polylineMapItem->getCoordinates()));
if (m_cesium) {
m_cesium->track(target);
}
//m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row());
return;
}
PolygonMapItem *polygonMapItem = (PolygonMapItem *)m_polylineMapModel.findMapItem(target);
if (polygonMapItem != nullptr)
{
map->setProperty("center", QVariant::fromValue(polygonMapItem->getCoordinates()));
if (m_cesium) {
m_cesium->track(target);
}
//m_polylineMapModel.moveToFront(m_polylineMapModel.findMapItemIndex(target).row());
return;
}
// Search as an address
QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm");
if (geoSrv != nullptr)
{
QLocale qLocaleC(QLocale::C, QLocale::AnyCountry);
geoSrv->setLocale(qLocaleC);
QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(target);
if (pQGeoCode) {
QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapGUI::geoReply);
} else {
qDebug() << "MapGUI::find: GeoCoding failed";
}
}
else
{
QGeoServiceProvider* geoSrv = new QGeoServiceProvider("osm");
if (geoSrv != nullptr)
{
QLocale qLocaleC(QLocale::C, QLocale::AnyCountry);
geoSrv->setLocale(qLocaleC);
QGeoCodeReply *pQGeoCode = geoSrv->geocodingManager()->geocode(target);
if (pQGeoCode) {
QObject::connect(pQGeoCode, &QGeoCodeReply::finished, this, &MapGUI::geoReply);
} else {
qDebug() << "MapGUI::find: GeoCoding failed";
}
}
else
{
qDebug() << "MapGUI::find: osm not available";
}
qDebug() << "MapGUI::find: osm not available";
}
}
}

View File

@ -161,6 +161,7 @@ public:
void addAirspace();
void addAirports();
void addNavtex();
void addVLF();
void find(const QString& target);
void track3D(const QString& target);
Q_INVOKABLE void supportedMapsChanged();
@ -228,6 +229,7 @@ private:
static QString getDataDir();
static const QList<RadioTimeTransmitter> m_radioTimeTransmitters;
static const QList<RadioTimeTransmitter> m_vlfTransmitters;
private slots:
void init3DMap();

View File

@ -39,6 +39,14 @@ void MapItem::update(SWGSDRangel::SWGMapItem *mapItem)
m_altitude = mapItem->getAltitude();
}
QGeoCoordinate MapItem::getCoordinates()
{
QGeoCoordinate coords;
coords.setLatitude(m_latitude);
coords.setLongitude(m_longitude);
return coords;
}
void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
{
MapItem::update(mapItem);
@ -116,6 +124,7 @@ void PolygonMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
m_colorValid = mapItem->getColorValid();
m_color = mapItem->getColor();
m_altitudeReference = mapItem->getAltitudeReference();
m_deleted = *mapItem->getImage() == "";
qDeleteAll(m_points);
m_points.clear();
@ -151,6 +160,7 @@ void PolylineMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
m_colorValid = mapItem->getColorValid();
m_color = mapItem->getColor();
m_altitudeReference = mapItem->getAltitudeReference();
m_deleted = *mapItem->getImage() == "";
qDeleteAll(m_points);
m_points.clear();
@ -180,14 +190,6 @@ void PolylineMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
m_bounds = QGeoRectangle(QGeoCoordinate(latMax, lonMin), QGeoCoordinate(latMin, lonMax));
}
QGeoCoordinate ObjectMapItem::getCoordinates()
{
QGeoCoordinate coords;
coords.setLatitude(m_latitude);
coords.setLongitude(m_longitude);
return coords;
}
void ObjectMapItem::findFrequency()
{
// Look for a frequency in the text for this object

View File

@ -41,6 +41,7 @@ public:
MapItem(const QObject *sourcePipe, const QString &group, MapSettings::MapItemSettings *itemSettings, SWGSDRangel::SWGMapItem *mapItem);
virtual void update(SWGSDRangel::SWGMapItem *mapItem);
QGeoCoordinate getCoordinates();
protected:
@ -74,7 +75,6 @@ public:
update(mapItem);
}
void update(SWGSDRangel::SWGMapItem *mapItem) override;
QGeoCoordinate getCoordinates();
protected:
void findFrequency();
@ -143,6 +143,7 @@ protected:
bool m_colorValid;
QRgb m_color;
int m_altitudeReference;
bool m_deleted;
};
class PolylineMapItem : public MapItem {
@ -165,6 +166,7 @@ protected:
bool m_colorValid;
QRgb m_color;
int m_altitudeReference;
bool m_deleted;
};
class ImageMapItem : public MapItem {

View File

@ -87,12 +87,11 @@ void MapModel::update(const QObject *sourcePipe, SWGSDRangel::SWGMapItem *swgMap
QString image = *swgMapItem->getImage();
if (image.isEmpty())
{
// Delete the item
// Delete the item from 2D map
remove(item);
// Need to call update, for it to be removed in 3D map
// Item is set to not be available from this point in time
// It will still be available if time is set in the past
// Delete from 3D map
item->update(swgMapItem);
update3D(item);
}
else
{
@ -178,6 +177,36 @@ MapItem *MapModel::findMapItem(const QObject *source, const QString& name)
return nullptr;
}
// FIXME: This should potentially return a list, as we have have multiple items with the same name
// from different sources
MapItem *MapModel::findMapItem(const QString& name)
{
QListIterator<MapItem *> i(m_items);
while (i.hasNext())
{
MapItem *item = i.next();
if (!item->m_name.compare(name, Qt::CaseInsensitive)) {
return item;
}
}
return nullptr;
}
QModelIndex MapModel::findMapItemIndex(const QString& name)
{
int idx = 0;
QListIterator<MapItem *> i(m_items);
while (i.hasNext())
{
MapItem *item = i.next();
if (item->m_name == name) {
return index(idx);
}
idx++;
}
return index(-1);
}
QHash<int, QByteArray> MapModel::roleNames() const
{
QHash<int, QByteArray> roles;
@ -589,36 +618,6 @@ Q_INVOKABLE void ObjectMapModel::moveToBack(int oldRow)
}
}
// FIXME: This should potentially return a list, as we have have multiple items with the same name
// from different sources
ObjectMapItem *ObjectMapModel::findMapItem(const QString& name)
{
QListIterator<MapItem *> i(m_items);
while (i.hasNext())
{
MapItem *item = i.next();
if (!item->m_name.compare(name, Qt::CaseInsensitive)) {
return (ObjectMapItem *)item;
}
}
return nullptr;
}
QModelIndex ObjectMapModel::findMapItemIndex(const QString& name)
{
int idx = 0;
QListIterator<MapItem *> i(m_items);
while (i.hasNext())
{
MapItem *item = i.next();
if (item->m_name == name) {
return index(idx);
}
idx++;
}
return index(-1);
}
QVariant ObjectMapModel::data(const QModelIndex &index, int role) const
{
int row = index.row();

View File

@ -62,6 +62,8 @@ public:
void allUpdated();
MapItem *findMapItem(const QObject *source, const QString& name);
MapItem *findMapItem(const QString& name);
QModelIndex findMapItemIndex(const QString& name);
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@ -235,9 +237,6 @@ public:
Q_INVOKABLE void moveToFront(int oldRow);
Q_INVOKABLE void moveToBack(int oldRow);
ObjectMapItem *findMapItem(const QString& name);
QModelIndex findMapItemIndex(const QString& name);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant& value, int role = Qt::EditRole) override;

View File

@ -31,6 +31,7 @@ const QStringList MapSettings::m_pipeTypes = {
QStringLiteral("AIS"),
QStringLiteral("APRS"),
QStringLiteral("APTDemod"),
QStringLiteral("DSCDemod"),
QStringLiteral("FT8Demod"),
QStringLiteral("HeatMap"),
QStringLiteral("ILSDemod"),
@ -46,6 +47,7 @@ const QStringList MapSettings::m_pipeURIs = {
QStringLiteral("sdrangel.feature.ais"),
QStringLiteral("sdrangel.feature.aprs"),
QStringLiteral("sdrangel.channel.aptdemod"),
QStringLiteral("sdrangel.channel.dscdemod"),
QStringLiteral("sdrangel.channel.ft8demod"),
QStringLiteral("sdrangel.channel.heatmap"),
QStringLiteral("sdrangel.channel.ilsdemod"),
@ -79,6 +81,7 @@ MapSettings::MapSettings() :
MapItemSettings *aprsSettings = new MapItemSettings("APRS", true, QColor(255, 255, 0), true, false, 11);
aprsSettings->m_extrapolate = 0;
m_itemSettings.insert("APRS", aprsSettings);
m_itemSettings.insert("DSCDemod", new MapItemSettings("DSCDemod", true, QColor(181, 230, 29), true, true, 3));
m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3));
m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize));
m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8));
@ -87,6 +90,7 @@ MapSettings::MapSettings() :
m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("FT8Demod", new MapItemSettings("FT8Demod", true, QColor(0, 192, 255), true, true, 8));
m_itemSettings.insert("HeatMap", new MapItemSettings("HeatMap", true, QColor(102, 40, 220), true, true, 11));
m_itemSettings.insert("VLF", new MapItemSettings("VLF", false, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("AM", new MapItemSettings("AM", false, QColor(255, 0, 0), false, true, 10));
MapItemSettings *fmSettings = new MapItemSettings("FM", false, QColor(255, 0, 0), false, true, 12);

View File

@ -15,6 +15,7 @@ On top of this, it can plot data from other plugins, such as:
* RF Heat Maps from the Heap Map channel,
* Radials and estimated position from the VOR localizer feature,
* ILS course line and glide path from the ILS Demodulator.
* DSC geographic call areas.
As well as internet data sources:
@ -25,6 +26,7 @@ As well as internet data sources:
* GRAVES radar,
* Ionosonde station data,
* Navtex transmitters.
* VLF transmitters.
It can also create tracks showing the path aircraft, ships and APRS objects have taken, as well as predicted paths for satellites.

View File

@ -172,6 +172,7 @@ set(sdrbase_SOURCES
util/ais.cpp
util/android.cpp
util/aprsfi.cpp
util/aviationweather.cpp
util/ax25.cpp
util/aprs.cpp
@ -184,6 +185,7 @@ set(sdrbase_SOURCES
util/CRC64.cpp
util/csv.cpp
util/db.cpp
util/dsc.cpp
util/fixedtraits.cpp
util/fits.cpp
util/flightinformation.cpp
@ -196,6 +198,7 @@ set(sdrbase_SOURCES
util/maidenhead.cpp
util/message.cpp
util/messagequeue.cpp
util/mmsi.cpp
util/morse.cpp
util/navtex.cpp
util/openaip.cpp
@ -398,6 +401,7 @@ set(sdrbase_HEADERS
util/ais.h
util/android.h
util/aprsfi.h
util/aviationweather.h
util/ax25.h
util/aprs.h
@ -409,6 +413,7 @@ set(sdrbase_HEADERS
util/CRC64.h
util/csv.h
util/db.h
util/dsc.h
util/doublebuffer.h
util/doublebufferfifo.h
util/doublebuffermultiple.h
@ -426,6 +431,7 @@ set(sdrbase_HEADERS
util/maidenhead.h
util/message.h
util/messagequeue.h
util/mmsi.h
util/morse.h
util/movingaverage.h
util/movingmaximum.h

View File

@ -336,12 +336,24 @@ QString AISPositionReport::getStatusString(int status)
return statuses[status];
}
QString AISPositionReport::getType()
{
if (m_id == 1) {
return "Position report (Scheduled)";
} else if (m_id == 2) {
return "Position report (Assigned)";
} else {
return "Position report (Interrogated)";
}
}
QString AISPositionReport::toString()
{
QString speed = m_speedOverGround == 1022 ? ">102.2" : QString::number(m_speedOverGround);
return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Status: %5")
.arg(m_latitude)
.arg(m_longitude)
.arg(m_speedOverGround)
.arg(speed)
.arg(m_course)
.arg(AISPositionReport::getStatusString(m_status))
.arg(QChar(0xb0));
@ -444,7 +456,7 @@ AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) :
int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf);
m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f;
m_speedOverGround = sog;
m_positionAccuracy = (ba[7] >> 3) & 0x1;
@ -467,12 +479,14 @@ AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) :
QString AISSARAircraftPositionReport::toString()
{
QString altitude = m_altitude == 4094 ? ">4094" : QString::number(m_altitude);
QString speed = m_speedOverGround == 1022 ? ">1022" : QString::number(m_speedOverGround);
return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Alt: %5 m")
.arg(m_latitude)
.arg(m_longitude)
.arg(m_speedOverGround)
.arg(speed)
.arg(m_course)
.arg(m_altitude)
.arg(altitude)
.arg(QChar(0xb0));
}
@ -520,6 +534,18 @@ AISInterrogation::AISInterrogation(QByteArray ba) :
AISAssignedModeCommand::AISAssignedModeCommand(QByteArray ba) :
AISMessage(ba)
{
m_destinationIdA = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f);
m_offsetA = ((ba[8] & 0x3) << 10) | ((ba[9] & 0xff) << 2) | ((ba[10] >> 6) & 0x3);
m_incrementA = ((ba[10] & 0x3f) << 4) | ((ba[11] >> 4) & 0xf);
m_bAvailable = false;
}
QString AISAssignedModeCommand::toString()
{
return QString("Dest A: %1 Offset A: %2 Inc A: %3")
.arg(m_destinationIdA)
.arg(m_offsetA)
.arg(m_incrementA);
}
AISGNSSBroadcast::AISGNSSBroadcast(QByteArray ba) :
@ -721,6 +747,25 @@ QString AISStaticDataReport::toString()
AISSingleSlotBinaryMessage::AISSingleSlotBinaryMessage(QByteArray ba) :
AISMessage(ba)
{
m_destinationIndicator = (ba[4] >> 1) & 1;
m_binaryDataFlag = ba[4] & 1;
if (m_destinationIndicator) {
m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f);
}
m_destinationIdAvailable = m_destinationIndicator;
}
QString AISSingleSlotBinaryMessage::toString()
{
QStringList s;
s.append(QString("Destination: %1").arg(m_destinationIndicator ? "Broadcast" : "Addressed"));
s.append(QString("Flag: %1").arg(m_binaryDataFlag ? "Unstructured" : "Structured"));
if (m_destinationIdAvailable) {
s.append(QString("Destination Id: %1").arg(m_destinationId));
}
return s.join(" ");
}
AISMultipleSlotBinaryMessage::AISMultipleSlotBinaryMessage(QByteArray ba) :

View File

@ -79,7 +79,7 @@ public:
int m_specialManoeuvre;
AISPositionReport(const QByteArray ba);
virtual QString getType() override { return "Position report"; }
virtual QString getType() override;
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
@ -229,8 +229,16 @@ public:
class SDRBASE_API AISAssignedModeCommand : public AISMessage {
public:
int m_destinationIdA;
int m_offsetA;
int m_incrementA;
int m_destinationIdB;
int m_offsetB;
int m_incrementB;
bool m_bAvailable;
AISAssignedModeCommand(const QByteArray ba);
virtual QString getType() override { return "Assigned mode command"; }
virtual QString toString() override;
};
class SDRBASE_API AISGNSSBroadcast : public AISMessage {
@ -345,8 +353,14 @@ public:
class SDRBASE_API AISSingleSlotBinaryMessage : public AISMessage {
public:
bool m_destinationIndicator;
bool m_binaryDataFlag;
int m_destinationId;
bool m_destinationIdAvailable;
AISSingleSlotBinaryMessage(const QByteArray ba);
virtual QString getType() override { return "Single slot binary message"; }
virtual QString toString() override;
};
class SDRBASE_API AISMultipleSlotBinaryMessage : public AISMessage {

187
sdrbase/util/aprsfi.cpp Normal file
View File

@ -0,0 +1,187 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 "aprsfi.h"
#include <QDebug>
#include <QUrl>
#include <QUrlQuery>
#include <QNetworkReply>
#include <QJsonObject>
#include <QJsonDocument>
QMutex APRSFi::m_mutex;
QHash<QString, APRSFi::AISData> APRSFi::m_aisCache;
APRSFi::APRSFi(const QString& apiKey, int cacheValidMins) :
m_apiKey(apiKey),
m_cacheValidMins(cacheValidMins)
{
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, &QNetworkAccessManager::finished, this, &APRSFi::handleReply);
}
APRSFi::~APRSFi()
{
disconnect(m_networkManager, &QNetworkAccessManager::finished, this, &APRSFi::handleReply);
delete m_networkManager;
}
APRSFi* APRSFi::create(const QString& apiKey, int cacheValidMins)
{
return new APRSFi(apiKey, cacheValidMins);
}
void APRSFi::getData(const QStringList& names)
{
QStringList nonCachedNames;
QDateTime currentDateTime = QDateTime::currentDateTime();
QMutexLocker locker(&m_mutex);
for (const auto& name : names)
{
bool cached = false;
QList<AISData> dataList;
if (m_aisCache.contains(name))
{
const AISData& d = m_aisCache[name];
if (d.m_dateTime.secsTo(currentDateTime) < m_cacheValidMins*60)
{
dataList.append(d);
cached = true;
}
}
if (dataList.size() > 0) {
emit dataUpdated(dataList);
}
if (!cached) {
nonCachedNames.append(name);
}
}
if (nonCachedNames.size() > 0)
{
QString nameList = nonCachedNames.join(",");
QUrl url(QString("https://api.aprs.fi/api/get"));
QUrlQuery query;
query.addQueryItem("name", nameList);
query.addQueryItem("what", "loc");
query.addQueryItem("apikey", m_apiKey);
query.addQueryItem("format", "json");
url.setQuery(query);
m_networkManager->get(QNetworkRequest(url));
}
}
void APRSFi::getData(const QString& name)
{
QStringList names;
names.append(name);
getData(names);
}
bool APRSFi::containsNonNull(const QJsonObject& obj, const QString &key) const
{
if (obj.contains(key))
{
QJsonValue val = obj.value(key);
return !val.isNull();
}
return false;
}
void APRSFi::handleReply(QNetworkReply* reply)
{
if (reply)
{
if (!reply->error())
{
QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
if (document.isObject())
{
QJsonObject docObj = document.object();
QDateTime receivedDateTime = QDateTime::currentDateTime();
if (docObj.contains(QStringLiteral("entries")))
{
QJsonArray array = docObj.value(QStringLiteral("entries")).toArray();
QList<AISData> data;
for (auto valRef : array)
{
if (valRef.isObject())
{
QJsonObject obj = valRef.toObject();
AISData measurement;
measurement.m_dateTime = receivedDateTime;
if (obj.contains(QStringLiteral("name"))) {
measurement.m_name = obj.value(QStringLiteral("name")).toString();
}
if (obj.contains(QStringLiteral("mmsi"))) {
measurement.m_mmsi = obj.value(QStringLiteral("mmsi")).toString();
}
if (containsNonNull(obj, QStringLiteral("time"))) {
measurement.m_firstTime = QDateTime::fromString(obj.value(QStringLiteral("time")).toString(), Qt::ISODate);
}
if (containsNonNull(obj, QStringLiteral("lastTime"))) {
measurement.m_lastTime = QDateTime::fromString(obj.value(QStringLiteral("lastTime")).toString(), Qt::ISODate);
}
if (containsNonNull(obj, QStringLiteral("lat"))) {
measurement.m_latitude = obj.value(QStringLiteral("lat")).toDouble();
}
if (containsNonNull(obj, QStringLiteral("lng"))) {
measurement.m_longitude = obj.value(QStringLiteral("lng")).toDouble();
}
data.append(measurement);
if (!measurement.m_mmsi.isEmpty())
{
QMutexLocker locker(&m_mutex);
m_aisCache.insert(measurement.m_mmsi, measurement);
}
}
else
{
qDebug() << "APRSFi::handleReply: Array element is not an object: " << valRef;
}
}
if (data.size() > 0) {
emit dataUpdated(data);
} else {
qDebug() << "APRSFi::handleReply: No data in array: " << document;
}
}
}
else
{
qDebug() << "APRSFi::handleReply: Document is not an object: " << document;
}
}
else
{
qWarning() << "APRSFi::handleReply: error: " << reply->error();
}
reply->deleteLater();
}
else
{
qWarning() << "APRSFi::handleReply: reply is null";
}
}

89
sdrbase/util/aprsfi.h Normal file
View File

@ -0,0 +1,89 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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_APRSFI_H
#define INCLUDE_APRSFI_H
#include <QtCore>
#include <QMutex>
#include "export.h"
class QNetworkAccessManager;
class QNetworkReply;
// aprs.fi API
// Allows querying APRS and AIS data
// Data can be cached to help avoid rate limiting on the server
class SDRBASE_API APRSFi : public QObject
{
Q_OBJECT
protected:
APRSFi(const QString& apiKey, int cacheValidMins);
public:
struct LocationData {
QString m_name;
QDateTime m_firstTime; // First time this position was reported
QDateTime m_lastTime; // Last time this position was reported
float m_latitude;
float m_longitude;
QString m_callsign;
QDateTime m_dateTime; // Data/time this data was received from APRS.fi
LocationData() :
m_latitude(NAN),
m_longitude(NAN)
{
}
};
struct AISData : LocationData {
QString m_mmsi;
QString m_imo;
AISData()
{
}
};
// Keys are free from https://aprs.fi/ - so get your own
static APRSFi* create(const QString& apiKey="184212.WhYgz2jqu3l2O", int cacheValidMins=10);
~APRSFi();
void getData(const QStringList& names);
void getData(const QString& name);
private slots:
void handleReply(QNetworkReply* reply);
signals:
void dataUpdated(const QList<AISData>& data); // Called when new data available.
private:
bool containsNonNull(const QJsonObject& obj, const QString &key) const;
QNetworkAccessManager *m_networkManager;
QString m_apiKey;
int m_cacheValidMins;
static QMutex m_mutex;
static QHash<QString, AISData> m_aisCache;
};
#endif /* INCLUDE_APRSFI_H */

972
sdrbase/util/dsc.cpp Normal file
View File

@ -0,0 +1,972 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 <QDebug>
#include "util/dsc.h"
#include "util/popcount.h"
// "short" strings are meant to be compatible with YaDDNet
QMap<DSCMessage::FormatSpecifier, QString> DSCMessage::m_formatSpecifierStrings = {
{GEOGRAPHIC_CALL, "Geographic call"},
{DISTRESS_ALERT, "Distress alert"},
{GROUP_CALL, "Group call"},
{ALL_SHIPS, "All ships"},
{SELECTIVE_CALL, "Selective call"},
{AUTOMATIC_CALL, "Automatic call"}
};
QMap<DSCMessage::FormatSpecifier, QString> DSCMessage::m_formatSpecifierShortStrings = {
{GEOGRAPHIC_CALL, "AREA"},
{DISTRESS_ALERT, "DIS"},
{GROUP_CALL, "GRP"},
{ALL_SHIPS, "ALL"},
{SELECTIVE_CALL, "SEL"},
{AUTOMATIC_CALL, "AUT"}
};
QMap<DSCMessage::Category, QString> DSCMessage::m_categoryStrings = {
{ROUTINE, "Routine"},
{SAFETY, "Safety"},
{URGENCY, "Urgency"},
{DISTRESS, "Distress"}
};
QMap<DSCMessage::Category, QString> DSCMessage::m_categoryShortStrings = {
{ROUTINE, "RTN"},
{SAFETY, "SAF"},
{URGENCY, "URG"},
{DISTRESS, "DIS"}
};
QMap<DSCMessage::FirstTelecommand, QString> DSCMessage::m_telecommand1Strings = {
{F3E_G3E_ALL_MODES_TP, "F3E (FM speech)/G3E (phase modulated speech) all modes telephony"},
{F3E_G3E_DUPLEX_TP, "F3E (FM speech)/G3E (phase modulated speech) duplex telephony"},
{POLLING, "Polling"},
{UNABLE_TO_COMPLY, "Unable to comply"},
{END_OF_CALL, "End of call"},
{DATA, "Data"},
{J3E_TP, "J3E (SSB) telephony"},
{DISTRESS_ACKNOWLEDGEMENT, "Distress acknowledgement"},
{DISTRESS_ALERT_RELAY, "Distress alert relay"},
{F1B_J2B_TTY_FEC, "F1B (FSK) J2B (FSK via SSB) TTY FEC"},
{F1B_J2B_TTY_AQR, "F1B (FSK) J2B (FSK via SSB) TTY AQR"},
{TEST, "Test"},
{POSITION_UPDATE, "Position update"},
{NO_INFORMATION, "No information"}
};
QMap<DSCMessage::FirstTelecommand, QString> DSCMessage::m_telecommand1ShortStrings = {
{F3E_G3E_ALL_MODES_TP, "F3E/G3E"},
{F3E_G3E_DUPLEX_TP, "F3E/G3E, Duplex TP"},
{POLLING, "POLL"},
{UNABLE_TO_COMPLY, "UNABLE TO COMPLY"},
{END_OF_CALL, "EOC"},
{DATA, "DATA"},
{J3E_TP, "J3E TP"},
{DISTRESS_ACKNOWLEDGEMENT, "DISTRESS ACK"},
{DISTRESS_ALERT_RELAY, "DISTRESS RELAY"},
{F1B_J2B_TTY_FEC, "F1B/J2B TTY-FEC"},
{F1B_J2B_TTY_AQR, "F1B/J2B TTY-ARQ"},
{TEST, "TEST"},
{POSITION_UPDATE, "POSUPD"},
{NO_INFORMATION, "NOINF"}
};
QMap<DSCMessage::SecondTelecommand, QString> DSCMessage::m_telecommand2Strings = {
{NO_REASON, "No reason"},
{CONGESTION, "Congestion at switching centre"},
{BUSY, "Busy"},
{QUEUE, "Queue indication"},
{BARRED, "Station barred"},
{NO_OPERATOR, "No operator available"},
{OPERATOR_UNAVAILABLE, "Operator temporarily unavailable"},
{EQUIPMENT_DISABLED, "Equipment disabled"},
{UNABLE_TO_USE_CHANNEL, "Unable to use proposed channel"},
{UNABLE_TO_USE_MODE, "Unable to use proposed mode"},
{NOT_PARTIES_TO_CONFLICT, "Ships and aircraft of States not parties to an armed conflict"},
{MEDICAL_TRANSPORTS, "Medical transports"},
{PAY_PHONE, "Pay-phone/public call office"},
{FAX, "Facsimile"},
{NO_INFORMATION_2, "No information"}
};
QMap<DSCMessage::SecondTelecommand, QString> DSCMessage::m_telecommand2ShortStrings = {
{NO_REASON, "NO REASON GIVEN"},
{CONGESTION, "CONGESTION AT MARITIME CENTRE"},
{BUSY, "BUSY"},
{QUEUE, "QUEUE INDICATION"},
{BARRED, "STATION BARRED"},
{NO_OPERATOR, "NO OPERATOR AVAILABLE"},
{OPERATOR_UNAVAILABLE, "OPERATOR TEMPORARILY UNAVAILABLE"},
{EQUIPMENT_DISABLED, "EQUIPMENT DISABLED"},
{UNABLE_TO_USE_CHANNEL, "UNABLE TO USE PROPOSED CHANNEL"},
{UNABLE_TO_USE_MODE, "UNABLE TO USE PROPOSED MODE"},
{NOT_PARTIES_TO_CONFLICT, "SHIPS/AIRCRAFT OF STATES NOT PARTIES TO ARMED CONFLICT"},
{MEDICAL_TRANSPORTS, "MEDICAL TRANSPORTS"},
{PAY_PHONE, "PAY-PHONE/PUBLIC CALL OFFICE"},
{FAX, "FAX/DATA ACCORDING ITU-R M1081"},
{NO_INFORMATION_2, "NOINF"}
};
QMap<DSCMessage::DistressNature, QString> DSCMessage::m_distressNatureStrings = {
{FIRE, "Fire, explosion"},
{FLOODING, "Flooding"},
{COLLISION, "Collision"},
{GROUNDING, "Grounding"},
{LISTING, "Listing"},
{SINKING, "Sinking"},
{ADRIFT, "Adrift"},
{UNDESIGNATED, "Undesignated"},
{ABANDONING_SHIP, "Abandoning ship"},
{PIRACY, "Piracy, armed attack"},
{MAN_OVERBOARD, "Man overboard"},
{EPIRB, "EPIRB"}
};
QMap<DSCMessage::EndOfSignal, QString> DSCMessage::m_endOfSignalStrings = {
{REQ, "Req ACK"},
{ACK, "ACK"},
{EOS, "EOS"}
};
QMap<DSCMessage::EndOfSignal, QString> DSCMessage::m_endOfSignalShortStrings = {
{REQ, "REQ"},
{ACK, "ACK"},
{EOS, "EOS"}
};
DSCMessage::DSCMessage(const QByteArray& data, QDateTime dateTime) :
m_dateTime(dateTime),
m_data(data)
{
decode(data);
}
QString DSCMessage::toString(const QString separator) const
{
QStringList s;
s.append(QString("Format specifier: %1").arg(formatSpecifier()));
if (m_hasAddress) {
s.append(QString("Address: %1").arg(m_address));
}
if (m_hasCategory) {
s.append(QString("Category: %1").arg(category()));
}
s.append(QString("Self Id: %1").arg(m_selfId));
if (m_hasTelecommand1) {
s.append(QString("Telecommand 1: %1").arg(telecommand1(m_telecommand1)));
}
if (m_hasTelecommand2) {
s.append(QString("Telecommand 2: %1").arg(telecommand2(m_telecommand2)));
}
if (m_hasDistressId) {
s.append(QString("Distress Id: %1").arg(m_distressId));
}
if (m_hasDistressNature)
{
s.append(QString("Distress nature: %1").arg(distressNature(m_distressNature)));
s.append(QString("Distress coordinates: %1").arg(m_position));
}
else if (m_hasPosition)
{
s.append(QString("Position: %1").arg(m_position));
}
if (m_hasFrequency1) {
s.append(QString("RX Frequency: %1Hz").arg(m_frequency1));
}
if (m_hasChannel1) {
s.append(QString("RX Channel: %1").arg(m_channel1));
}
if (m_hasFrequency2) {
s.append(QString("TX Frequency: %1Hz").arg(m_frequency2));
}
if (m_hasChannel2) {
s.append(QString("TX Channel: %1").arg(m_channel2));
}
if (m_hasNumber) {
s.append(QString("Phone Number: %1").arg(m_number));
}
if (m_hasTime) {
s.append(QString("Time: %1").arg(m_time.toString()));
}
if (m_hasSubsequenceComms) {
s.append(QString("Subsequent comms: %1").arg(telecommand1(m_subsequenceComms)));
}
return s.join(separator);
}
QString DSCMessage::toYaddNetFormat(const QString& id, qint64 frequency) const
{
QStringList s;
// rx_id
s.append(QString("[%1]").arg(id));
// rx_freq
float frequencyKHZ = frequency / 1000.0f;
s.append(QString("%1").arg(frequencyKHZ, 0, 'f', 1));
// fmt
s.append(formatSpecifier(true));
// to
if (m_hasAddress)
{
if (m_formatSpecifier == GEOGRAPHIC_CALL)
{
char ns = m_addressLatitude >= 0 ? 'N' : 'S';
char ew = m_addressLongitude >= 0 ? 'E' : 'W';
int lat = abs(m_addressLatitude);
int lon = abs(m_addressLongitude);
s.append(QString("AREA %2%1%6=>%4%1 %3%1%7=>%5%1")
.arg(QChar(0xb0)) // degree
.arg(lat, 2, 10, QChar('0'))
.arg(lon, 3, 10, QChar('0'))
.arg(m_addressLatAngle, 2, 10, QChar('0'))
.arg(m_addressLonAngle, 2, 10, QChar('0'))
.arg(ns)
.arg(ew));
}
else
{
s.append(m_address);
}
}
else
{
s.append("");
}
// cat
s.append(category(true));
// from
s.append(m_selfId);
// tc1
if (m_hasTelecommand1) {
s.append(telecommand1(m_telecommand1, true));
} else {
s.append("--");
}
// tc2
if (m_hasTelecommand2) {
s.append(telecommand2(m_telecommand2, true));
} else {
s.append("--");
}
// distress fields don't appear to be used!
// freq
if (m_hasFrequency1 && m_hasFrequency2) {
s.append(QString("%1/%2KHz").arg(m_frequency1/1000.0, 7, 'f', 1, QChar('0')).arg(m_frequency2/1000.0, 7, 'f', 1, QChar('0')));
} else if (m_hasFrequency1) {
s.append(QString("%1KHz").arg(m_frequency1/1000.0, 7, 'f', 1, QChar('0')));
} else if (m_hasFrequency2) {
s.append(QString("%1KHz").arg(m_frequency2/1000.0, 7, 'f', 1, QChar('0')));
} else if (m_hasChannel1 && m_hasChannel2) {
s.append(QString("%1/%2").arg(m_channel1).arg(m_channel2));
} else if (m_hasChannel1) {
s.append(QString("%1").arg(m_channel1));
} else if (m_hasChannel2) {
s.append(QString("%1").arg(m_channel2));
} else {
s.append("--");
}
// pos
if (m_hasPosition) {
s.append(m_position); // FIXME: Format??
} else {
s.append("--"); // Sometimes this is " -- ". in YaDD Why?
}
// eos
s.append(endOfSignal(m_eos, true));
// ecc
s.append(QString("ECC %1 %2").arg(m_calculatedECC).arg(m_eccOk ? "OK" : "ERR"));
return s.join(";");
}
QString DSCMessage::formatSpecifier(bool shortString) const
{
if (shortString)
{
if (m_formatSpecifierShortStrings.contains(m_formatSpecifier)) {
return m_formatSpecifierShortStrings[m_formatSpecifier];
} else {
return QString("UNK/ERR").arg(m_formatSpecifier);
}
}
else
{
if (m_formatSpecifierStrings.contains(m_formatSpecifier)) {
return m_formatSpecifierStrings[m_formatSpecifier];
} else {
return QString("Unknown (%1)").arg(m_formatSpecifier);
}
}
}
QString DSCMessage::category(bool shortString) const
{
if (shortString)
{
if (m_categoryShortStrings.contains(m_category)) {
return m_categoryShortStrings[m_category];
} else {
return QString("UNK/ERR").arg(m_category);
}
}
else
{
if (!m_hasCategory) {
return "N/A";
} else if (m_categoryStrings.contains(m_category)) {
return m_categoryStrings[m_category];
} else {
return QString("Unknown (%1)").arg(m_category);
}
}
}
QString DSCMessage::telecommand1(FirstTelecommand telecommand, bool shortString)
{
if (shortString)
{
if (m_telecommand1ShortStrings.contains(telecommand)) {
return m_telecommand1ShortStrings[telecommand];
} else {
return QString("UNK/ERR").arg(telecommand);
}
}
else
{
if (m_telecommand1Strings.contains(telecommand)) {
return m_telecommand1Strings[telecommand];
} else {
return QString("Unknown (%1)").arg(telecommand);
}
}
}
QString DSCMessage::telecommand2(SecondTelecommand telecommand, bool shortString)
{
if (shortString)
{
if (m_telecommand2ShortStrings.contains(telecommand)) {
return m_telecommand2ShortStrings[telecommand];
} else {
return QString("UNK/ERR").arg(telecommand);
}
}
else
{
if (m_telecommand2Strings.contains(telecommand)) {
return m_telecommand2Strings[telecommand];
} else {
return QString("Unknown (%1)").arg(telecommand);
}
}
}
QString DSCMessage::distressNature(DistressNature nature)
{
if (m_distressNatureStrings.contains(nature)) {
return m_distressNatureStrings[nature];
} else {
return QString("Unknown (%1)").arg(nature);
}
}
QString DSCMessage::endOfSignal(EndOfSignal eos, bool shortString)
{
if (shortString)
{
if (m_endOfSignalShortStrings.contains(eos)) {
return m_endOfSignalShortStrings[eos];
} else {
return QString("UNK/ERR").arg(eos);
}
}
else
{
if (m_endOfSignalStrings.contains(eos)) {
return m_endOfSignalStrings[eos];
} else {
return QString("Unknown (%1)").arg(eos);
}
}
}
QString DSCMessage::symbolsToDigits(const QByteArray data, int startIdx, int length)
{
QString s;
for (int i = 0; i < length; i++)
{
QString digits = QString("%1").arg((int)data[startIdx+i], 2, 10, QChar('0'));
s = s.append(digits);
}
return s;
}
QString DSCMessage::formatCoordinates(int latitude, int longitude)
{
QString lat, lon;
if (latitude >= 0) {
lat = QString("%1%2N").arg(latitude).arg(QChar(0xb0));
} else {
lat = QString("%1%2S").arg(-latitude).arg(QChar(0xb0));
}
if (longitude >= 0) {
lon = QString("%1%2E").arg(longitude).arg(QChar(0xb0));
} else {
lon = QString("%1%2W").arg(-longitude).arg(QChar(0xb0));
}
return QString("%1 %2").arg(lat).arg(lon);
}
void DSCMessage::decode(const QByteArray& data)
{
int idx = 0;
// Format specifier
m_formatSpecifier = (FormatSpecifier) data[idx++];
m_formatSpecifierMatch = m_formatSpecifier == data[idx++];
// Address and category
if (m_formatSpecifier != DISTRESS_ALERT)
{
if (m_formatSpecifier != ALL_SHIPS)
{
m_address = symbolsToDigits(data, idx, 5);
idx += 5;
m_hasAddress = true;
if (m_formatSpecifier == SELECTIVE_CALL)
{
m_address = formatAddress(m_address);
}
else if (m_formatSpecifier == GEOGRAPHIC_CALL)
{
// Address definines a geographic rectangle. We have NW coord + 2 angles
QChar azimuthSector = m_address[0];
m_addressLatitude = m_address[1].digitValue() * 10 + m_address[2].digitValue(); // In degrees
m_addressLongitude = m_address[3].digitValue() * 100 + m_address[4].digitValue() * 10 + m_address[5].digitValue(); // In degrees
switch (azimuthSector.toLatin1())
{
case '0': // NE
break;
case '1': // NW
m_addressLongitude = -m_addressLongitude;
break;
case '2': // SE
m_addressLatitude = -m_addressLatitude;
break;
case '3': // SW
m_addressLongitude = -m_addressLongitude;
m_addressLatitude = -m_addressLatitude;
break;
default:
break;
}
m_addressLatAngle = m_address[6].digitValue() * 10 + m_address[7].digitValue();
m_addressLonAngle = m_address[8].digitValue() * 10 + m_address[9].digitValue();
int latitude2 = m_addressLatitude + m_addressLatAngle;
int longitude2 = m_addressLongitude + m_addressLonAngle;
/*m_address = QString("Lat %2%1 Lon %3%1 %4%5%6%1 %4%7%8%1")
.arg(QChar(0xb0)) // degree
.arg(m_addressLatitude)
.arg(m_addressLongitude)
.arg(QChar(0x0394)) // delta
.arg(QChar(0x03C6)) // phi
.arg(m_addressLatAngle)
.arg(QChar(0x03BB)) // lambda
.arg(m_addressLonAngle);*/
m_address = QString("%1 - %2")
.arg(formatCoordinates(m_addressLatitude, m_addressLongitude))
.arg(formatCoordinates(latitude2, longitude2));
}
}
else
{
m_hasAddress = false;
}
m_category = (Category) data[idx++];
m_hasCategory = true;
}
else
{
m_hasAddress = false;
m_hasCategory = true;
}
// Self Id
m_selfId = symbolsToDigits(data, idx, 5);
m_selfId = formatAddress(m_selfId);
idx += 5;
// Telecommands
if (m_formatSpecifier != DISTRESS_ALERT)
{
m_telecommand1 = (FirstTelecommand) data[idx++];
m_hasTelecommand1 = true;
if (m_category != DISTRESS) // Not Distress Alert Ack / Relay
{
m_telecommand2 = (SecondTelecommand) data[idx++];
m_hasTelecommand2 = true;
}
else
{
m_hasTelecommand2 = false;
}
}
else
{
m_hasTelecommand1 = false;
m_hasTelecommand2 = false;
}
// ID of source of distress for relays and acks
if (m_hasCategory && m_category == DISTRESS)
{
m_distressId = symbolsToDigits(data, idx, 5);
m_distressId = formatAddress(m_distressId);
idx += 5;
m_hasDistressId = true;
}
else
{
m_hasDistressId = false;
}
if (m_formatSpecifier == DISTRESS_ALERT)
{
m_distressNature = (DistressNature) data[idx++];
m_position = formatCoordinates(symbolsToDigits(data, idx, 5));
idx += 5;
m_hasDistressNature = true;
m_hasPosition = true;
m_hasFrequency1 = false;
m_hasChannel1 = false;
m_hasFrequency2 = false;
m_hasChannel2 = false;
}
else if (m_hasCategory && (m_category != DISTRESS))
{
m_hasDistressNature = false;
// Frequency or position
if (data[idx] == 55)
{
// Position 6
m_position = formatCoordinates(symbolsToDigits(data, idx, 5));
idx += 5;
m_hasPosition = true;
m_hasFrequency1 = false;
m_hasChannel1 = false;
m_hasFrequency2 = false;
m_hasChannel2 = false;
}
else
{
m_hasPosition = false;
// Frequency
m_frequency1 = 0;
decodeFrequency(data, idx, m_frequency1, m_channel1);
m_hasFrequency1 = m_frequency1 != 0;
m_hasChannel1 = !m_channel1.isEmpty();
if (m_formatSpecifier != AUTOMATIC_CALL)
{
m_frequency2 = 0;
decodeFrequency(data, idx, m_frequency2, m_channel2);
m_hasFrequency2 = m_frequency2 != 0;
m_hasChannel2 = !m_channel2.isEmpty();
}
else
{
m_hasFrequency2 = false;
m_hasChannel2 = false;
}
}
}
else
{
m_hasDistressNature = false;
m_hasPosition = false;
m_hasFrequency1 = false;
m_hasChannel1 = false;
m_hasFrequency2 = false;
m_hasChannel2 = false;
}
if (m_formatSpecifier == AUTOMATIC_CALL)
{
signed char oddEven = data[idx++];
int len = data.size() - idx - 2; // EOS + ECC
m_number = symbolsToDigits(data, idx, len);
idx += len;
if (oddEven == 105) { // Is number an odd number?
m_number = m_number.mid(1); // Drop leading digit (which should be a 0)
}
m_hasNumber = true;
}
else
{
m_hasNumber = false;
}
// Time
if ( (m_formatSpecifier == DISTRESS_ALERT)
|| (m_hasCategory && (m_category == DISTRESS))
//|| (m_formatSpecifier == SELECTIVE_CALL) && (m_category == SAFETY) && (m_telecommand1 == POSITION_UPDATE) && (m_telecommand2 == 126) && (m_frequency == pos4))
)
{
// 8 8 8 8 for no time
QString time = symbolsToDigits(data, idx, 2);
if (time != "8888")
{
m_time = QTime(time.left(2).toInt(), time.right(2).toInt());
m_hasTime = true;
}
else
{
m_hasTime = false;
}
// FIXME: Convert to QTime?
}
else
{
m_hasTime = false;
}
// Subsequent communications
if ((m_formatSpecifier == DISTRESS_ALERT) || (m_hasCategory && (m_category == DISTRESS)))
{
m_subsequenceComms = (FirstTelecommand)data[idx++];
m_hasSubsequenceComms = true;
}
else
{
m_hasSubsequenceComms = false;
}
m_eos = (EndOfSignal) data[idx++];
m_ecc = data[idx++];
checkECC(data);
// Indicate message as being invalid if any unexpected data, too long, or ECC didn't match
if ( m_formatSpecifierStrings.contains(m_formatSpecifier)
&& (!m_hasCategory || (m_hasCategory && m_categoryStrings.contains(m_category)))
&& (!m_hasTelecommand1 || (m_hasTelecommand1 && m_telecommand1Strings.contains(m_telecommand1)))
&& (!m_hasTelecommand2 || (m_hasTelecommand2 && m_telecommand2Strings.contains(m_telecommand2)))
&& (!m_hasDistressNature || (m_hasDistressNature && m_distressNatureStrings.contains(m_distressNature)))
&& m_endOfSignalStrings.contains(m_eos)
&& (!data.contains(-1))
&& (data.size() < DSCDecoder::m_maxBytes)
&& m_eccOk
) {
m_valid = true;
} else {
m_valid = false;
}
}
void DSCMessage::checkECC(const QByteArray& data)
{
m_calculatedECC = 0;
// Only use one format specifier and one EOS
for (int i = 1; i < data.size() - 1; i++) {
m_calculatedECC ^= data[i];
}
m_eccOk = m_calculatedECC == m_ecc;
}
void DSCMessage::decodeFrequency(const QByteArray& data, int& idx, int& frequency, QString& channel)
{
// No frequency information is indicated by 126 repeated 3 times
if ((data[idx] == 126) && (data[idx+1] == 126) && (data[idx+2] == 126))
{
idx += 3;
return;
}
// Extract frequency digits
QString s = symbolsToDigits(data, idx, 3);
idx += 3;
if (s[0] == '4')
{
s = s.append(symbolsToDigits(data, idx, 1));
idx++;
}
if ((s[0] == '0') || (s[0] == '1') || (s[0] == '2'))
{
frequency = s.toInt() * 100;
}
else if (s[0] == '3')
{
channel = "CH" + s.mid(1); // HF/MF
}
else if (s[0] == '4')
{
frequency = s.mid(1).toInt() * 10; // Frequency in multiples of 10Hz
}
else if (s[0] == '9')
{
channel = "CH" + s.mid(2) + "VHF"; // VHF
}
}
QString DSCMessage::formatAddress(const QString &address) const
{
// First 9 digits should be MMSI
// Last digit should always be 0, except for ITU-R M.1080, which allows 10th digit to specify different equipement on same vessel
if (address.right(1) == "0") {
return address.left(9);
} else {
return QString("%1-%2").arg(address.left(9)).arg(address.right(1));
}
}
QString DSCMessage::formatCoordinates(const QString& coords)
{
if (coords == "9999999999")
{
return "Not available";
}
else
{
QChar quadrant = coords[0];
QString latitude = QString("%1%3%2\'")
.arg(coords.mid(1, 2))
.arg(coords.mid(3, 2))
.arg(QChar(0xb0));
QString longitude = QString("%1%3%2\'")
.arg(coords.mid(1, 3))
.arg(coords.mid(4, 2))
.arg(QChar(0xb0));
switch (quadrant.toLatin1())
{
case '0':
latitude = latitude.append('N');
longitude = longitude.append('E');
break;
case '1':
latitude = latitude.append('N');
longitude = longitude.append('W');
break;
case '2':
latitude = latitude.append('S');
longitude = longitude.append('E');
break;
case '3':
latitude = latitude.append('S');
longitude = longitude.append('W');
break;
}
return QString("%1 %2").arg(latitude).arg(longitude);
}
}
// Doesn't include 125 111 125 as these will have be detected already, in DSDDemodSink
const signed char DSCDecoder::m_expectedSymbols[] = {
110,
125, 109,
125, 108,
125, 107,
125, 106
};
int DSCDecoder::m_maxBytes = 40; // Max bytes in any message
void DSCDecoder::init(int offset)
{
if (offset == 0)
{
m_state = FILL_DX;
}
else
{
m_phaseIdx = offset;
m_state = PHASING;
}
m_idx = 0;
m_errors = 0;
m_bytes = QByteArray();
m_eos = false;
}
bool DSCDecoder::decodeSymbol(signed char symbol)
{
bool ret = false;
switch (m_state)
{
case PHASING:
// Check if received phasing signals are as expected
if (symbol != m_expectedSymbols[9-m_phaseIdx]) {
m_errors++;
}
m_phaseIdx--;
if (m_phaseIdx == 0) {
m_state = FILL_DX;
}
break;
case FILL_DX:
// Fill up buffer
m_buf[m_idx++] = symbol;
if (m_idx == BUFFER_SIZE)
{
m_state = RX;
m_idx = 0;
}
else
{
m_state = FILL_RX;
}
break;
case FILL_RX:
if ( ((m_idx == 1) && (symbol != 106))
|| ((m_idx == 2) && (symbol != 105))
)
{
m_errors++;
}
m_state = FILL_DX;
break;
case RX:
{
signed char a = selectSymbol(m_buf[m_idx], symbol);
if (DSCMessage::m_endOfSignalStrings.contains((DSCMessage::EndOfSignal) a)) {
m_state = DX_EOS;
} else {
m_state = DX;
}
if (m_bytes.size() > m_maxBytes)
{
ret = true;
m_state = NO_EOS;
}
}
break;
case DX:
// Save received character in buffer
m_buf[m_idx] = symbol;
m_idx = (m_idx + 1) % BUFFER_SIZE;
m_state = RX;
break;
case DX_EOS:
// Save, EOS symbol
m_buf[m_idx] = symbol;
m_idx = (m_idx + 1) % BUFFER_SIZE;
m_state = RX_EOS;
break;
case RX_EOS:
selectSymbol(m_buf[m_idx], symbol);
m_state = DONE;
ret = true;
break;
case DONE:
case NO_EOS:
break;
}
return ret;
}
// Reverse order of bits in a byte
unsigned char DSCDecoder::reverse(unsigned char b)
{
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
// Convert 10 bits to a symbol
// Returns -1 if error detected
signed char DSCDecoder::bitsToSymbol(unsigned int bits)
{
signed char data = reverse(bits >> 3) >> 1;
int zeros = 7-popcount(data);
int expectedZeros = bits & 0x7;
if (zeros == expectedZeros) {
return data;
} else {
return -1;
}
}
// Decode 10-bits to symbols then remove errors using repeated symbols
bool DSCDecoder::decodeBits(int bits)
{
signed char symbol = bitsToSymbol(bits);
//qDebug() << "Bits2sym: " << Qt::hex << bits << Qt::hex << symbol;
return decodeSymbol(symbol);
}
// Select time diversity symbol without errors
signed char DSCDecoder::selectSymbol(signed char dx, signed char rx)
{
signed char s;
if (dx != -1)
{
s = dx; // First received character has no detectable error
if (dx != rx) {
m_errors++;
}
}
else if (rx != -1)
{
s = rx; // Second received character has no detectable error
m_errors++;
}
else
{
s = '*'; // Both received characters have errors
m_errors += 2;
}
m_bytes.append(s);
return s;
}

234
sdrbase/util/dsc.h Normal file
View File

@ -0,0 +1,234 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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_UTIL_DSC_H
#define INCLUDE_UTIL_DSC_H
#include "export.h"
#include <QByteArray>
#include <QString>
#include <QDateTime>
// Digital Select Calling
// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.493-15-201901-I!!PDF-E.pdf
class SDRBASE_API DSCDecoder {
public:
void init(int offset);
bool decodeBits(int bits);
QByteArray getMessage() const { return m_bytes; }
int getErrors() const { return m_errors; }
static int m_maxBytes;
private:
static const int BUFFER_SIZE = 3;
signed char m_buf[3];
enum State {
PHASING,
FILL_DX,
FILL_RX,
DX,
RX,
DX_EOS,
RX_EOS,
DONE,
NO_EOS
} m_state;
int m_idx;
int m_errors;
int m_phaseIdx;
bool m_eos;
static const signed char m_expectedSymbols[];
QByteArray m_bytes;
bool decodeSymbol(signed char symbol);
static signed char bitsToSymbol(unsigned int bits);
static unsigned char reverse(unsigned char b);
signed char selectSymbol(signed char dx, signed char rx);
};
class SDRBASE_API DSCMessage {
public:
enum FormatSpecifier {
GEOGRAPHIC_CALL = 102,
DISTRESS_ALERT = 112,
GROUP_CALL = 114,
ALL_SHIPS = 116,
SELECTIVE_CALL = 120,
AUTOMATIC_CALL = 123
};
enum Category {
ROUTINE = 100,
SAFETY = 108,
URGENCY = 110,
DISTRESS = 112
};
enum FirstTelecommand {
F3E_G3E_ALL_MODES_TP = 100,
F3E_G3E_DUPLEX_TP = 101,
POLLING = 103,
UNABLE_TO_COMPLY = 104,
END_OF_CALL = 105,
DATA = 106,
J3E_TP = 109,
DISTRESS_ACKNOWLEDGEMENT = 110,
DISTRESS_ALERT_RELAY = 112,
F1B_J2B_TTY_FEC = 113,
F1B_J2B_TTY_AQR = 115,
TEST = 118,
POSITION_UPDATE = 121,
NO_INFORMATION = 126
};
enum SecondTelecommand {
NO_REASON = 100,
CONGESTION = 101,
BUSY = 102,
QUEUE = 103,
BARRED = 104,
NO_OPERATOR = 105,
OPERATOR_UNAVAILABLE = 106,
EQUIPMENT_DISABLED = 107,
UNABLE_TO_USE_CHANNEL = 108,
UNABLE_TO_USE_MODE = 109,
NOT_PARTIES_TO_CONFLICT = 110,
MEDICAL_TRANSPORTS = 111,
PAY_PHONE = 112,
FAX = 113,
NO_INFORMATION_2 = 126
};
enum DistressNature {
FIRE = 100,
FLOODING = 101,
COLLISION = 102,
GROUNDING = 103,
LISTING = 104,
SINKING = 105,
ADRIFT = 106,
UNDESIGNATED = 107,
ABANDONING_SHIP = 108,
PIRACY = 109,
MAN_OVERBOARD = 110,
EPIRB = 112
};
enum EndOfSignal {
REQ = 117,
ACK = 122,
EOS = 127
};
static QMap<FormatSpecifier, QString> m_formatSpecifierStrings;
static QMap<FormatSpecifier, QString> m_formatSpecifierShortStrings;
static QMap<Category, QString> m_categoryStrings;
static QMap<Category, QString> m_categoryShortStrings;
static QMap<FirstTelecommand, QString> m_telecommand1Strings;
static QMap<FirstTelecommand, QString> m_telecommand1ShortStrings;
static QMap<SecondTelecommand, QString> m_telecommand2Strings;
static QMap<SecondTelecommand, QString> m_telecommand2ShortStrings;
static QMap<DistressNature, QString> m_distressNatureStrings;
static QMap<EndOfSignal, QString> m_endOfSignalStrings;
static QMap<EndOfSignal, QString> m_endOfSignalShortStrings;
FormatSpecifier m_formatSpecifier;
bool m_formatSpecifierMatch;
QString m_address;
bool m_hasAddress;
int m_addressLatitude; // For GEOGRAPHIC_CALL
int m_addressLongitude;
int m_addressLatAngle;
int m_addressLonAngle;
Category m_category;
bool m_hasCategory;
QString m_selfId;
FirstTelecommand m_telecommand1;
bool m_hasTelecommand1;
SecondTelecommand m_telecommand2;
bool m_hasTelecommand2;
QString m_distressId;
bool m_hasDistressId;
DistressNature m_distressNature;
bool m_hasDistressNature;
QString m_position;
bool m_hasPosition;
int m_frequency1; // Rx
bool m_hasFrequency1;
QString m_channel1;
bool m_hasChannel1;
int m_frequency2; // Tx
bool m_hasFrequency2;
QString m_channel2;
bool m_hasChannel2;
QString m_number; // Phone number
bool m_hasNumber;
QTime m_time;
bool m_hasTime;
FirstTelecommand m_subsequenceComms;
bool m_hasSubsequenceComms;
EndOfSignal m_eos;
signed char m_ecc; // Error checking code (parity)
signed char m_calculatedECC;
bool m_eccOk;
bool m_valid; // Data is within defined values
QDateTime m_dateTime; // Date/time when received
QByteArray m_data;
DSCMessage(const QByteArray& data, QDateTime dateTime);
QString toString(const QString separator = " ") const;
QString toYaddNetFormat(const QString& id, qint64 frequency) const;
QString formatSpecifier(bool shortString=false) const;
QString category(bool shortString=false) const;
static QString telecommand1(FirstTelecommand telecommand, bool shortString=false);
static QString telecommand2(SecondTelecommand telecommand, bool shortString=false);
static QString distressNature(DistressNature nature);
static QString endOfSignal(EndOfSignal eos, bool shortString=false);
protected:
QString symbolsToDigits(const QByteArray data, int startIdx, int length);
QString formatCoordinates(int latitude, int longitude);
void decode(const QByteArray& data);
void checkECC(const QByteArray& data);
void decodeFrequency(const QByteArray& data, int& idx, int& frequency, QString& channel);
QString formatAddress(const QString &address) const;
QString formatCoordinates(const QString& coords);
};
#endif /* INCLUDE_UTIL_DSC_H */

422
sdrbase/util/mmsi.cpp Normal file
View File

@ -0,0 +1,422 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 <QResource>
#include <QDebug>
#include "util/mmsi.h"
#include "util/osndb.h"
// https://www.itu.int/en/ITU-R/terrestrial/fmd/Pages/mid.aspx
// Names used match up with names used for flags in ADS-B directory
QMap<int, QString> MMSI::m_mid = {
{201, "albania"},
{202, "andorra"},
{203, "austria"},
{204, "portugal"},
{205, "belgium"},
{206, "belarus"},
{207, "bulgaria"},
{208, "vatican_city"},
{209, "cyprus"},
{210, "cyprus"},
{211, "germany"},
{212, "cyprus"},
{213, "georgia"},
{214, "moldova"},
{215, "malta"},
{216, "armenia"},
{218, "germany"},
{219, "denmark"},
{220, "denmark"},
{224, "spain"},
{225, "spain"},
{226, "france"},
{227, "france"},
{228, "france"},
{229, "malta"},
{230, "finland"},
{231, "denmark"},
{232, "united_kingdom"},
{233, "united_kingdom"},
{234, "united_kingdom"},
{235, "united_kingdom"},
{236, "united_kingdom"},
{237, "greece"},
{238, "croatia"},
{239, "greece"},
{240, "greece"},
{241, "greece"},
{242, "morocco"},
{243, "hungary"},
{244, "netherlands"},
{245, "netherlands"},
{246, "netherlands"},
{247, "italy"},
{248, "malta"},
{249, "malta"},
{250, "ireland"},
{251, "iceland"},
{252, "liechtenstein"},
{253, "luxembourg"},
{254, "monaco"},
{255, "portugal"},
{256, "malta"},
{257, "norway"},
{258, "norway"},
{259, "norway"},
{261, "poland"},
{262, "montenegro"},
{263, "portugal"},
{264, "romania"},
{265, "sweden"},
{266, "sweden"},
{267, "slovakia"},
{268, "san_marino"},
{269, "switzerland"},
{270, "czech_republic"},
{271, "turkey"},
{272, "ukraine"},
{273, "russia"},
{274, "macedonia"},
{275, "latvia"},
{276, "estonia"},
{277, "slovenia"},
{279, "serbia"},
{301, "united_kingdom"},
{303, "united_states"},
{304, "antigua_and_barbuda"},
{305, "antigua_and_barbuda"},
{306, "netherlands"},
{307, "netherlands"},
{308, "bahamas"},
{309, "bahamas"},
{310, "bermuda"},
{311, "bahamas"},
{312, "belize"},
{314, "barbados"},
{316, "canada"},
{319, "cayman_isles"},
{321, "costa_rica"},
{323, "cuba"},
{325, "dominica"},
{327, "dominican_republic"},
{329, "france"},
{330, "grenada"},
{331, "denmark"}, // greenland
{332, "guatemala"},
{334, "honduras"},
{336, "haiti"},
{338, "united_states"},
{339, "jamaica"},
{341, "st_kitts_and_nevis"},
{343, "st_lucia"},
{345, "mexico"},
{347, "france"}, // martinique
{348, "united_kingdom"}, // montserrat
{350, "nicaragua"},
{351, "panama"},
{352, "panama"},
{353, "panama"},
{354, "panama"},
{355, "panama"},
{356, "panama"},
{357, "panama"},
{358, "united_states"}, // puerto_rico
{359, "el_salvador"},
{361, "france"},
{362, "trinidad_and_tobago"},
{364, "turks_and_caicos"},
{366, "united_states"},
{367, "united_states"},
{368, "united_states"},
{369, "united_states"},
{370, "panama"},
{371, "panama"},
{372, "panama"},
{373, "panama"},
{374, "panama"},
{375, "st_vincent"},
{376, "st_vincent"},
{377, "st_vincent"},
{378, "virgin_isles"},
{401, "afghanistan"},
{403, "saudi_arabia"},
{405, "bangladesh"},
{408, "bahrain"},
{410, "bhutan"},
{412, "china"},
{413, "china"},
{414, "china"},
{416, "taiwan"},
{417, "sri_lanka"},
{419, "india"},
{422, "iran"},
{423, "azerbaijan"},
{425, "iraq"},
{428, "israel"},
{431, "japan"},
{432, "japan"},
{434, "turkmenistan"},
{436, "kazakhstan"},
{437, "uzbekistan"},
{438, "jordan"},
{440, "korea_south"},
{441, "korea_south"},
{443, "palestine"},
{445, "korea_north"},
{447, "kuwait"},
{450, "lebanon"},
{451, "kyrgyzstan"},
{453, "china"}, // macao
{455, "maldives"},
{457, "mongolia"},
{459, "nepal"},
{461, "oman"},
{463, "pakistan"},
{466, "qatar"},
{468, "syria"},
{470, "united_arab_emirates"},
{471, "united_arab_emirates"},
{472, "tajikistan"},
{473, "yemen"},
{474, "yemen"},
{477, "hong_kong"},
{478, "bosnia"},
{501, "france"},
{503, "australia"},
{506, "myanmar"},
{508, "brunei"},
{510, "micronesia"},
{511, "palau"},
{512, "new_zealand"},
{514, "cambodia"},
{515, "cambodia"},
{516, "australia"},
{518, "cook_islands"},
{520, "fiji"},
{523, "australia"},
{525, "indonesia"},
{529, "kiribati"},
{531, "laos"},
{533, "malaysia"},
{536, "united_states"},
{538, "marshall islands"},
{540, "france"},
{542, "new_zealand"},
{544, "nauru"},
{546, "france"},
{548, "philippines"},
{550, "timorleste"},
{553, "papua_new_guinea"},
{555, "united_kingdom"},
{557, "solomon_islands"},
{559, "united_states"}, // american_samoa
{561, "samoa"},
{563, "singapore"},
{564, "singapore"},
{565, "singapore"},
{566, "singapore"},
{567, "thailand"},
{570, "tonga"},
{572, "tuvalu"},
{574, "vietnam"},
{576, "vanuatu"},
{577, "vanuatu"},
{578, "france"},
{601, "south_africa"},
{603, "angola"},
{605, "algeria"},
{607, "france"},
{608, "united_kingdom"}, // ascension_island
{609, "burundi"},
{610, "benin"},
{611, "botswana"},
{612, "central_african_republic"},
{613, "cameroun"}, // cameroon
{615, "congoroc"},
{616, "comoros"},
{617, "cape_verde"},
{618, "france"},
{619, "ivory_coast"},
{620, "comoros"},
{621, "djibouti"},
{622, "egypt"},
{624, "ethiopia"},
{625, "eritrea"},
{626, "gabon"},
{627, "ghana"},
{629, "gambia"},
{630, "guinea_bissau"},
{631, "equatorial_guinea"},
{632, "guinea"},
{633, "burkina_faso"},
{634, "kenya"},
{635, "france"},
{636, "liberia"},
{637, "liberia"},
{638, "south_sudan"},
{642, "libya"},
{644, "lesotho"},
{645, "mauritius"},
{647, "madagascar"},
{649, "mali"},
{650, "mozambique"},
{654, "mauritania"},
{655, "malawi"},
{656, "niger"},
{657, "nigeria"},
{659, "namibia"},
{660, "france"}, // reunion
{661, "rwanda"},
{662, "sudan"},
{663, "senegal"},
{664, "seychelles"},
{665, "united_kingdom"}, // saint_helena
{666, "somalia"},
{667, "sierra_leone"},
{668, "sao_tome_principe"},
{669, "swaziland"}, // eswatini
{670, "chad"},
{671, "togo"},
{672, "tunisia"},
{674, "tanzania"},
{675, "uganda"},
{676, "congodrc"},
{677, "tanzania"},
{678, "zambia"},
{679, "zimbabwe"},
{701, "argentina"},
{710, "brazil"},
{720, "bolivia"},
{725, "chile"},
{730, "colombia"},
{735, "ecuador"},
{740, "falkland_isles"},
{745, "france"}, // guiana
{750, "guyana"},
{755, "paraguay"},
{760, "peru"},
{765, "suriname"},
{770, "uruguay"},
{775, "venezuela"},
};
QString MMSI::getMID(const QString &mmsi)
{
if (mmsi.startsWith("00") || mmsi.startsWith("99") || mmsi.startsWith("98")) {
return mmsi.mid(2, 3);
} else if (mmsi.startsWith("0") || mmsi.startsWith("8")) {
return mmsi.mid(1, 3);
} else if (mmsi.startsWith("111")) {
return mmsi.mid(3, 3);
} else {
return mmsi.left(3);
}
}
QString MMSI::getCountry(const QString &mmsi)
{
return m_mid[MMSI::getMID(mmsi).toInt()];
}
void MMSI::checkFlags()
{
// Loop through all MIDs and check to see if we have a flag icon
for (auto id : m_mid.keys())
{
QString country = m_mid.value(id);
QString path = QString(":/flags/%1.bmp").arg(country);
QResource res(path);
if (!res.isValid()) {
qDebug() << "MMSI::checkFlags: Resource invalid " << path;
}
}
}
QIcon *MMSI::getFlagIcon(const QString &mmsi)
{
QString country = getCountry(mmsi);
return AircraftInformation::getFlagIcon(country);
}
QString MMSI::getFlagIconURL(const QString &mmsi)
{
QString country = getCountry(mmsi);
return AircraftInformation::getFlagIconURL(country);
}
QString MMSI::getCategory(const QString &mmsi)
{
switch (mmsi[0].toLatin1())
{
case '0':
if (mmsi.startsWith("00")) {
return "Coast";
} else {
return "Group"; // Group of ships
}
case '1':
// Search and rescue
if (mmsi[6] == '1') {
return "SAR Aircraft";
} else if (mmsi[6] == '5') {
return "SAR Helicopter";
} else {
return "SAR";
}
case '8':
return "Handheld";
case '9':
if (mmsi.startsWith("970"))
{
return "SAR";
}
else if (mmsi.startsWith("972"))
{
return "Man overboard";
}
else if (mmsi.startsWith("974"))
{
return "EPIRB"; // Emergency Becaon
}
else if (mmsi.startsWith("979"))
{
return "AMRD"; // Autonomous
}
else if (mmsi.startsWith("98"))
{
return "Craft with parent ship";
}
else if (mmsi.startsWith("99"))
{
if (mmsi[5] == '1') {
return "Physical AtoN";
} else if (mmsi[5] == '6') {
return "Virtual AtoN";
} else if (mmsi[5] == '8') {
return "Mobile AtoN";
} else {
return "AtoN"; // Aid-to-navigation
}
}
break;
default:
return "Ship"; // Vessel better?
}
return "Unknown";
}

47
sdrbase/util/mmsi.h Normal file
View File

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 UTIL_MMSI_H
#define UTIL_MMSI_H
#include <QMap>
#include <QIcon>
#include "export.h"
// Maritime mobile service identities (basically ship identifiers)
// MMSIs defined by ITU-R M.585
// https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.585-9-202205-I!!PDF-E.pdf
class SDRBASE_API MMSI {
public:
static QString getMID(const QString &mmsi);
static QString getCountry(const QString &mmsi);
static QString getCategory(const QString &mmsi);
static QIcon *getFlagIcon(const QString &mmsi);
static QString getFlagIconURL(const QString &mmsi);
private:
static QMap<int, QString> m_mid;
static void checkFlags();
};
#endif /* UTIL_MMSI_H */

View File

@ -120,6 +120,7 @@ class MovingAverageUtilVar
double asDouble() const { return m_total * m_samplesSizeInvD; }
float asFloat() const { return m_total * m_samplesSizeInvF; }
operator T() const { return m_total / m_samples.size(); }
T instantAverage() const { return m_total / (m_num_samples == 0 ? 1 : m_num_samples); }
private:
std::vector<T> m_samples;

View File

@ -527,6 +527,7 @@ QIcon *AircraftInformation::getAirlineIcon(const QString &operatorICAO)
return icon;
}
}
QString AircraftInformation::getFlagIconPath(const QString &country)
{
QString endPath = QString("/flags/%1.bmp").arg(country);
@ -550,6 +551,15 @@ QString AircraftInformation::getFlagIconPath(const QString &country)
return QString();
}
QString AircraftInformation::getFlagIconURL(const QString &country)
{
QString path = getFlagIconPath(country);
if (path.startsWith(':')) {
path = "qrc://" + path.mid(1);
}
return path;
}
QIcon *AircraftInformation::getFlagIcon(const QString &country)
{
if (m_flagIcons.contains(country))

View File

@ -69,6 +69,7 @@ struct SDRBASE_API AircraftInformation {
static QIcon *getAirlineIcon(const QString &operatorICAO);
static QString getFlagIconPath(const QString &country);
static QString getFlagIconURL(const QString &country);
// Try to find an flag logo based on a country
static QIcon *getFlagIcon(const QString &country);

View File

@ -4493,6 +4493,11 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setDoa2Settings(new SWGSDRangel::SWGDOA2Settings());
channelSettings->getDoa2Settings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "DSCDemodSettings")
{
channelSettings->setDscDemodSettings(new SWGSDRangel::SWGDSCDemodSettings());
channelSettings->getDscDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "DSDDemodSettings")
{
channelSettings->setDsdDemodSettings(new SWGSDRangel::SWGDSDDemodSettings());

View File

@ -41,6 +41,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channel.demoddatv", "DATVDemodSettings"},
{"sdrangel.channel.dabdemod", "DABDemodSettings"},
{"sdrangel.channel.doa2", "DOA2Settings"},
{"sdrangel.channel.dscdemod", "DSCDemodSettings"},
{"sdrangel.channel.dsddemod", "DSDDemodSettings"},
{"sdrangel.channel.filesink", "FileSinkSettings"},
{"sdrangel.channeltx.filesource", "FileSourceSettings"},
@ -155,6 +156,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"DATVMod", "DATVModSettings"},
{"DABDemod", "DABDemodSettings"},
{"DOA2", "DOA2Settings"},
{"DSCDemod", "DSCDemodSettings"},
{"DSDDemod", "DSDDemodSettings"},
{"FileSink", "FileSinkSettings"},
{"FileSource", "FileSourceSettings"},

View File

@ -44,6 +44,7 @@ set(sdrgui_SOURCES
gui/fftwisdomdialog.cpp
gui/flowlayout.cpp
gui/framelesswindowresizer.cpp
gui/frequencydelegate.cpp
gui/glscope.cpp
gui/glscopegui.cpp
gui/glshadercolors.cpp
@ -157,6 +158,7 @@ set(sdrgui_HEADERS
gui/fftwisdomdialog.h
gui/flowlayout.h
gui/framelesswindowresizer.h
gui/frequencydelegate.h
gui/glscope.h
gui/glscopegui.h
gui/glshadercolors.h

View File

@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 "frequencydelegate.h"
FrequencyDelegate::FrequencyDelegate(QString units, int precision, bool group) :
m_units(units),
m_precision(precision),
m_group(group)
{
}
QString FrequencyDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
bool ok;
qlonglong v = value.toLongLong(&ok);
if (ok)
{
double d;
if (m_units == "GHz") {
d = v / 1000000000.0;
} else if (m_units == "MHz") {
d = v / 1000000.0;
} else if (m_units == "kHz") {
d = v / 1000.0;
} else {
d = v;
}
QLocale l(locale);
if (m_group) {
l.setNumberOptions(l.numberOptions() & ~QLocale::OmitGroupSeparator);
} else {
l.setNumberOptions(l.numberOptions() | QLocale::OmitGroupSeparator);
}
QString s = l.toString(d, 'f', m_precision);
return QString("%1 %2").arg(s).arg(m_units);
}
else
{
return value.toString();
}
}

View File

@ -0,0 +1,39 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 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 SDRGUI_GUI_FREQUENCYDELGATE_H
#define SDRGUI_GUI_FREQUENCYDELGATE_H
#include <QStyledItemDelegate>
#include "export.h"
// Delegate for table to display frequency
class SDRGUI_API FrequencyDelegate : public QStyledItemDelegate {
public:
FrequencyDelegate(QString units = "kHz", int precision=1, bool group=true);
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
private:
QString m_units;
int m_precision;
bool m_group;
};
#endif // SDRGUI_GUI_FREQUENCYDELGATE_H

View File

@ -35,6 +35,8 @@ ChannelReport:
$ref: "http://swgserver:8081/api/swagger/include/DATVMod.yaml#/DATVModReport"
DOA2Report:
$ref: "http://swgserver:8081/api/swagger/include/DOA2.yaml#/DOA2Report"
DSCDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/DSCDemod.yaml#/DSCDemodReport"
DSDDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodReport"
IEEE_802_15_4_ModReport:

View File

@ -51,6 +51,8 @@ ChannelSettings:
$ref: "http://swgserver:8081/api/swagger/include/DABDemod.yaml#/DABDemodSettings"
DOA2Settings:
$ref: "http://swgserver:8081/api/swagger/include/DOA2.yaml#/DOA2Settings"
DSCDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/DSCDemod.yaml#/DSCDemodSettings"
DSDDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/DSDDemod.yaml#/DSDDemodSettings"
FileSinkSettings:

View File

@ -56,6 +56,8 @@ SWGChannelReport::SWGChannelReport() {
m_datv_mod_report_isSet = false;
doa2_report = nullptr;
m_doa2_report_isSet = false;
dsc_demod_report = nullptr;
m_dsc_demod_report_isSet = false;
dsd_demod_report = nullptr;
m_dsd_demod_report_isSet = false;
ieee_802_15_4_mod_report = nullptr;
@ -156,6 +158,8 @@ SWGChannelReport::init() {
m_datv_mod_report_isSet = false;
doa2_report = new SWGDOA2Report();
m_doa2_report_isSet = false;
dsc_demod_report = new SWGDSCDemodReport();
m_dsc_demod_report_isSet = false;
dsd_demod_report = new SWGDSDDemodReport();
m_dsd_demod_report_isSet = false;
ieee_802_15_4_mod_report = new SWGIEEE_802_15_4_ModReport();
@ -264,6 +268,9 @@ SWGChannelReport::cleanup() {
if(doa2_report != nullptr) {
delete doa2_report;
}
if(dsc_demod_report != nullptr) {
delete dsc_demod_report;
}
if(dsd_demod_report != nullptr) {
delete dsd_demod_report;
}
@ -401,6 +408,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&doa2_report, pJson["DOA2Report"], "SWGDOA2Report", "SWGDOA2Report");
::SWGSDRangel::setValue(&dsc_demod_report, pJson["DSCDemodReport"], "SWGDSCDemodReport", "SWGDSCDemodReport");
::SWGSDRangel::setValue(&dsd_demod_report, pJson["DSDDemodReport"], "SWGDSDDemodReport", "SWGDSDDemodReport");
::SWGSDRangel::setValue(&ieee_802_15_4_mod_report, pJson["IEEE_802_15_4_ModReport"], "SWGIEEE_802_15_4_ModReport", "SWGIEEE_802_15_4_ModReport");
@ -523,6 +532,9 @@ SWGChannelReport::asJsonObject() {
if((doa2_report != nullptr) && (doa2_report->isSet())){
toJsonValue(QString("DOA2Report"), doa2_report, obj, QString("SWGDOA2Report"));
}
if((dsc_demod_report != nullptr) && (dsc_demod_report->isSet())){
toJsonValue(QString("DSCDemodReport"), dsc_demod_report, obj, QString("SWGDSCDemodReport"));
}
if((dsd_demod_report != nullptr) && (dsd_demod_report->isSet())){
toJsonValue(QString("DSDDemodReport"), dsd_demod_report, obj, QString("SWGDSDDemodReport"));
}
@ -763,6 +775,16 @@ SWGChannelReport::setDoa2Report(SWGDOA2Report* doa2_report) {
this->m_doa2_report_isSet = true;
}
SWGDSCDemodReport*
SWGChannelReport::getDscDemodReport() {
return dsc_demod_report;
}
void
SWGChannelReport::setDscDemodReport(SWGDSCDemodReport* dsc_demod_report) {
this->dsc_demod_report = dsc_demod_report;
this->m_dsc_demod_report_isSet = true;
}
SWGDSDDemodReport*
SWGChannelReport::getDsdDemodReport() {
return dsd_demod_report;
@ -1130,6 +1152,9 @@ SWGChannelReport::isSet(){
if(doa2_report && doa2_report->isSet()){
isObjectUpdated = true; break;
}
if(dsc_demod_report && dsc_demod_report->isSet()){
isObjectUpdated = true; break;
}
if(dsd_demod_report && dsd_demod_report->isSet()){
isObjectUpdated = true; break;
}

View File

@ -34,6 +34,7 @@
#include "SWGDATVDemodReport.h"
#include "SWGDATVModReport.h"
#include "SWGDOA2Report.h"
#include "SWGDSCDemodReport.h"
#include "SWGDSDDemodReport.h"
#include "SWGFT8DemodReport.h"
#include "SWGFileSinkReport.h"
@ -128,6 +129,9 @@ public:
SWGDOA2Report* getDoa2Report();
void setDoa2Report(SWGDOA2Report* doa2_report);
SWGDSCDemodReport* getDscDemodReport();
void setDscDemodReport(SWGDSCDemodReport* dsc_demod_report);
SWGDSDDemodReport* getDsdDemodReport();
void setDsdDemodReport(SWGDSDDemodReport* dsd_demod_report);
@ -270,6 +274,9 @@ private:
SWGDOA2Report* doa2_report;
bool m_doa2_report_isSet;
SWGDSCDemodReport* dsc_demod_report;
bool m_dsc_demod_report_isSet;
SWGDSDDemodReport* dsd_demod_report;
bool m_dsd_demod_report_isSet;

View File

@ -70,6 +70,8 @@ SWGChannelSettings::SWGChannelSettings() {
m_dab_demod_settings_isSet = false;
doa2_settings = nullptr;
m_doa2_settings_isSet = false;
dsc_demod_settings = nullptr;
m_dsc_demod_settings_isSet = false;
dsd_demod_settings = nullptr;
m_dsd_demod_settings_isSet = false;
file_sink_settings = nullptr;
@ -194,6 +196,8 @@ SWGChannelSettings::init() {
m_dab_demod_settings_isSet = false;
doa2_settings = new SWGDOA2Settings();
m_doa2_settings_isSet = false;
dsc_demod_settings = new SWGDSCDemodSettings();
m_dsc_demod_settings_isSet = false;
dsd_demod_settings = new SWGDSDDemodSettings();
m_dsd_demod_settings_isSet = false;
file_sink_settings = new SWGFileSinkSettings();
@ -329,6 +333,9 @@ SWGChannelSettings::cleanup() {
if(doa2_settings != nullptr) {
delete doa2_settings;
}
if(dsc_demod_settings != nullptr) {
delete dsc_demod_settings;
}
if(dsd_demod_settings != nullptr) {
delete dsd_demod_settings;
}
@ -495,6 +502,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&doa2_settings, pJson["DOA2Settings"], "SWGDOA2Settings", "SWGDOA2Settings");
::SWGSDRangel::setValue(&dsc_demod_settings, pJson["DSCDemodSettings"], "SWGDSCDemodSettings", "SWGDSCDemodSettings");
::SWGSDRangel::setValue(&dsd_demod_settings, pJson["DSDDemodSettings"], "SWGDSDDemodSettings", "SWGDSDDemodSettings");
::SWGSDRangel::setValue(&file_sink_settings, pJson["FileSinkSettings"], "SWGFileSinkSettings", "SWGFileSinkSettings");
@ -648,6 +657,9 @@ SWGChannelSettings::asJsonObject() {
if((doa2_settings != nullptr) && (doa2_settings->isSet())){
toJsonValue(QString("DOA2Settings"), doa2_settings, obj, QString("SWGDOA2Settings"));
}
if((dsc_demod_settings != nullptr) && (dsc_demod_settings->isSet())){
toJsonValue(QString("DSCDemodSettings"), dsc_demod_settings, obj, QString("SWGDSCDemodSettings"));
}
if((dsd_demod_settings != nullptr) && (dsd_demod_settings->isSet())){
toJsonValue(QString("DSDDemodSettings"), dsd_demod_settings, obj, QString("SWGDSDDemodSettings"));
}
@ -973,6 +985,16 @@ SWGChannelSettings::setDoa2Settings(SWGDOA2Settings* doa2_settings) {
this->m_doa2_settings_isSet = true;
}
SWGDSCDemodSettings*
SWGChannelSettings::getDscDemodSettings() {
return dsc_demod_settings;
}
void
SWGChannelSettings::setDscDemodSettings(SWGDSCDemodSettings* dsc_demod_settings) {
this->dsc_demod_settings = dsc_demod_settings;
this->m_dsc_demod_settings_isSet = true;
}
SWGDSDDemodSettings*
SWGChannelSettings::getDsdDemodSettings() {
return dsd_demod_settings;
@ -1411,6 +1433,9 @@ SWGChannelSettings::isSet(){
if(doa2_settings && doa2_settings->isSet()){
isObjectUpdated = true; break;
}
if(dsc_demod_settings && dsc_demod_settings->isSet()){
isObjectUpdated = true; break;
}
if(dsd_demod_settings && dsd_demod_settings->isSet()){
isObjectUpdated = true; break;
}

View File

@ -39,6 +39,7 @@
#include "SWGDATVDemodSettings.h"
#include "SWGDATVModSettings.h"
#include "SWGDOA2Settings.h"
#include "SWGDSCDemodSettings.h"
#include "SWGDSDDemodSettings.h"
#include "SWGFT8DemodSettings.h"
#include "SWGFileSinkSettings.h"
@ -159,6 +160,9 @@ public:
SWGDOA2Settings* getDoa2Settings();
void setDoa2Settings(SWGDOA2Settings* doa2_settings);
SWGDSCDemodSettings* getDscDemodSettings();
void setDscDemodSettings(SWGDSCDemodSettings* dsc_demod_settings);
SWGDSDDemodSettings* getDsdDemodSettings();
void setDsdDemodSettings(SWGDSDDemodSettings* dsd_demod_settings);
@ -337,6 +341,9 @@ private:
SWGDOA2Settings* doa2_settings;
bool m_doa2_settings_isSet;
SWGDSCDemodSettings* dsc_demod_settings;
bool m_dsc_demod_settings_isSet;
SWGDSDDemodSettings* dsd_demod_settings;
bool m_dsd_demod_settings_isSet;

View File

@ -0,0 +1,131 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
#include "SWGDSCDemodReport.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGDSCDemodReport::SWGDSCDemodReport(QString* json) {
init();
this->fromJson(*json);
}
SWGDSCDemodReport::SWGDSCDemodReport() {
channel_power_db = 0.0f;
m_channel_power_db_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
SWGDSCDemodReport::~SWGDSCDemodReport() {
this->cleanup();
}
void
SWGDSCDemodReport::init() {
channel_power_db = 0.0f;
m_channel_power_db_isSet = false;
channel_sample_rate = 0;
m_channel_sample_rate_isSet = false;
}
void
SWGDSCDemodReport::cleanup() {
}
SWGDSCDemodReport*
SWGDSCDemodReport::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGDSCDemodReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&channel_power_db, pJson["channelPowerDB"], "float", "");
::SWGSDRangel::setValue(&channel_sample_rate, pJson["channelSampleRate"], "qint32", "");
}
QString
SWGDSCDemodReport::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGDSCDemodReport::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(m_channel_power_db_isSet){
obj->insert("channelPowerDB", QJsonValue(channel_power_db));
}
if(m_channel_sample_rate_isSet){
obj->insert("channelSampleRate", QJsonValue(channel_sample_rate));
}
return obj;
}
float
SWGDSCDemodReport::getChannelPowerDb() {
return channel_power_db;
}
void
SWGDSCDemodReport::setChannelPowerDb(float channel_power_db) {
this->channel_power_db = channel_power_db;
this->m_channel_power_db_isSet = true;
}
qint32
SWGDSCDemodReport::getChannelSampleRate() {
return channel_sample_rate;
}
void
SWGDSCDemodReport::setChannelSampleRate(qint32 channel_sample_rate) {
this->channel_sample_rate = channel_sample_rate;
this->m_channel_sample_rate_isSet = true;
}
bool
SWGDSCDemodReport::isSet(){
bool isObjectUpdated = false;
do{
if(m_channel_power_db_isSet){
isObjectUpdated = true; break;
}
if(m_channel_sample_rate_isSet){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,64 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/*
* SWGDSCDemodReport.h
*
* DSCDemod
*/
#ifndef SWGDSCDemodReport_H_
#define SWGDSCDemodReport_H_
#include <QJsonObject>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGDSCDemodReport: public SWGObject {
public:
SWGDSCDemodReport();
SWGDSCDemodReport(QString* json);
virtual ~SWGDSCDemodReport();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGDSCDemodReport* fromJson(QString &jsonString) override;
float getChannelPowerDb();
void setChannelPowerDb(float channel_power_db);
qint32 getChannelSampleRate();
void setChannelSampleRate(qint32 channel_sample_rate);
virtual bool isSet() override;
private:
float channel_power_db;
bool m_channel_power_db_isSet;
qint32 channel_sample_rate;
bool m_channel_sample_rate_isSet;
};
}
#endif /* SWGDSCDemodReport_H_ */

View File

@ -0,0 +1,584 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
#include "SWGDSCDemodSettings.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGDSCDemodSettings::SWGDSCDemodSettings(QString* json) {
init();
this->fromJson(*json);
}
SWGDSCDemodSettings::SWGDSCDemodSettings() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
filter_invalid = 0;
m_filter_invalid_isSet = false;
filter_column = 0;
m_filter_column_isSet = false;
filter = nullptr;
m_filter_isSet = false;
udp_enabled = 0;
m_udp_enabled_isSet = false;
udp_address = nullptr;
m_udp_address_isSet = false;
udp_port = 0;
m_udp_port_isSet = false;
log_filename = nullptr;
m_log_filename_isSet = false;
log_enabled = 0;
m_log_enabled_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
m_title_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = nullptr;
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
scope_config = nullptr;
m_scope_config_isSet = false;
channel_marker = nullptr;
m_channel_marker_isSet = false;
rollup_state = nullptr;
m_rollup_state_isSet = false;
}
SWGDSCDemodSettings::~SWGDSCDemodSettings() {
this->cleanup();
}
void
SWGDSCDemodSettings::init() {
input_frequency_offset = 0L;
m_input_frequency_offset_isSet = false;
rf_bandwidth = 0.0f;
m_rf_bandwidth_isSet = false;
filter_invalid = 0;
m_filter_invalid_isSet = false;
filter_column = 0;
m_filter_column_isSet = false;
filter = new QString("");
m_filter_isSet = false;
udp_enabled = 0;
m_udp_enabled_isSet = false;
udp_address = new QString("");
m_udp_address_isSet = false;
udp_port = 0;
m_udp_port_isSet = false;
log_filename = new QString("");
m_log_filename_isSet = false;
log_enabled = 0;
m_log_enabled_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
m_title_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = new QString("");
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
scope_config = new SWGGLScope();
m_scope_config_isSet = false;
channel_marker = new SWGChannelMarker();
m_channel_marker_isSet = false;
rollup_state = new SWGRollupState();
m_rollup_state_isSet = false;
}
void
SWGDSCDemodSettings::cleanup() {
if(filter != nullptr) {
delete filter;
}
if(udp_address != nullptr) {
delete udp_address;
}
if(log_filename != nullptr) {
delete log_filename;
}
if(title != nullptr) {
delete title;
}
if(reverse_api_address != nullptr) {
delete reverse_api_address;
}
if(scope_config != nullptr) {
delete scope_config;
}
if(channel_marker != nullptr) {
delete channel_marker;
}
if(rollup_state != nullptr) {
delete rollup_state;
}
}
SWGDSCDemodSettings*
SWGDSCDemodSettings::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGDSCDemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&input_frequency_offset, pJson["inputFrequencyOffset"], "qint64", "");
::SWGSDRangel::setValue(&rf_bandwidth, pJson["rfBandwidth"], "float", "");
::SWGSDRangel::setValue(&filter_invalid, pJson["filterInvalid"], "qint32", "");
::SWGSDRangel::setValue(&filter_column, pJson["filterColumn"], "qint32", "");
::SWGSDRangel::setValue(&filter, pJson["filter"], "QString", "QString");
::SWGSDRangel::setValue(&udp_enabled, pJson["udpEnabled"], "qint32", "");
::SWGSDRangel::setValue(&udp_address, pJson["udpAddress"], "QString", "QString");
::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", "");
::SWGSDRangel::setValue(&log_filename, pJson["logFilename"], "QString", "QString");
::SWGSDRangel::setValue(&log_enabled, pJson["logEnabled"], "qint32", "");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString");
::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", "");
::SWGSDRangel::setValue(&scope_config, pJson["scopeConfig"], "SWGGLScope", "SWGGLScope");
::SWGSDRangel::setValue(&channel_marker, pJson["channelMarker"], "SWGChannelMarker", "SWGChannelMarker");
::SWGSDRangel::setValue(&rollup_state, pJson["rollupState"], "SWGRollupState", "SWGRollupState");
}
QString
SWGDSCDemodSettings::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGDSCDemodSettings::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(m_input_frequency_offset_isSet){
obj->insert("inputFrequencyOffset", QJsonValue(input_frequency_offset));
}
if(m_rf_bandwidth_isSet){
obj->insert("rfBandwidth", QJsonValue(rf_bandwidth));
}
if(m_filter_invalid_isSet){
obj->insert("filterInvalid", QJsonValue(filter_invalid));
}
if(m_filter_column_isSet){
obj->insert("filterColumn", QJsonValue(filter_column));
}
if(filter != nullptr && *filter != QString("")){
toJsonValue(QString("filter"), filter, obj, QString("QString"));
}
if(m_udp_enabled_isSet){
obj->insert("udpEnabled", QJsonValue(udp_enabled));
}
if(udp_address != nullptr && *udp_address != QString("")){
toJsonValue(QString("udpAddress"), udp_address, obj, QString("QString"));
}
if(m_udp_port_isSet){
obj->insert("udpPort", QJsonValue(udp_port));
}
if(log_filename != nullptr && *log_filename != QString("")){
toJsonValue(QString("logFilename"), log_filename, obj, QString("QString"));
}
if(m_log_enabled_isSet){
obj->insert("logEnabled", QJsonValue(log_enabled));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString"));
}
if(m_stream_index_isSet){
obj->insert("streamIndex", QJsonValue(stream_index));
}
if(m_use_reverse_api_isSet){
obj->insert("useReverseAPI", QJsonValue(use_reverse_api));
}
if(reverse_api_address != nullptr && *reverse_api_address != QString("")){
toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString"));
}
if(m_reverse_api_port_isSet){
obj->insert("reverseAPIPort", QJsonValue(reverse_api_port));
}
if(m_reverse_api_device_index_isSet){
obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index));
}
if(m_reverse_api_channel_index_isSet){
obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index));
}
if((scope_config != nullptr) && (scope_config->isSet())){
toJsonValue(QString("scopeConfig"), scope_config, obj, QString("SWGGLScope"));
}
if((channel_marker != nullptr) && (channel_marker->isSet())){
toJsonValue(QString("channelMarker"), channel_marker, obj, QString("SWGChannelMarker"));
}
if((rollup_state != nullptr) && (rollup_state->isSet())){
toJsonValue(QString("rollupState"), rollup_state, obj, QString("SWGRollupState"));
}
return obj;
}
qint64
SWGDSCDemodSettings::getInputFrequencyOffset() {
return input_frequency_offset;
}
void
SWGDSCDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) {
this->input_frequency_offset = input_frequency_offset;
this->m_input_frequency_offset_isSet = true;
}
float
SWGDSCDemodSettings::getRfBandwidth() {
return rf_bandwidth;
}
void
SWGDSCDemodSettings::setRfBandwidth(float rf_bandwidth) {
this->rf_bandwidth = rf_bandwidth;
this->m_rf_bandwidth_isSet = true;
}
qint32
SWGDSCDemodSettings::getFilterInvalid() {
return filter_invalid;
}
void
SWGDSCDemodSettings::setFilterInvalid(qint32 filter_invalid) {
this->filter_invalid = filter_invalid;
this->m_filter_invalid_isSet = true;
}
qint32
SWGDSCDemodSettings::getFilterColumn() {
return filter_column;
}
void
SWGDSCDemodSettings::setFilterColumn(qint32 filter_column) {
this->filter_column = filter_column;
this->m_filter_column_isSet = true;
}
QString*
SWGDSCDemodSettings::getFilter() {
return filter;
}
void
SWGDSCDemodSettings::setFilter(QString* filter) {
this->filter = filter;
this->m_filter_isSet = true;
}
qint32
SWGDSCDemodSettings::getUdpEnabled() {
return udp_enabled;
}
void
SWGDSCDemodSettings::setUdpEnabled(qint32 udp_enabled) {
this->udp_enabled = udp_enabled;
this->m_udp_enabled_isSet = true;
}
QString*
SWGDSCDemodSettings::getUdpAddress() {
return udp_address;
}
void
SWGDSCDemodSettings::setUdpAddress(QString* udp_address) {
this->udp_address = udp_address;
this->m_udp_address_isSet = true;
}
qint32
SWGDSCDemodSettings::getUdpPort() {
return udp_port;
}
void
SWGDSCDemodSettings::setUdpPort(qint32 udp_port) {
this->udp_port = udp_port;
this->m_udp_port_isSet = true;
}
QString*
SWGDSCDemodSettings::getLogFilename() {
return log_filename;
}
void
SWGDSCDemodSettings::setLogFilename(QString* log_filename) {
this->log_filename = log_filename;
this->m_log_filename_isSet = true;
}
qint32
SWGDSCDemodSettings::getLogEnabled() {
return log_enabled;
}
void
SWGDSCDemodSettings::setLogEnabled(qint32 log_enabled) {
this->log_enabled = log_enabled;
this->m_log_enabled_isSet = true;
}
qint32
SWGDSCDemodSettings::getRgbColor() {
return rgb_color;
}
void
SWGDSCDemodSettings::setRgbColor(qint32 rgb_color) {
this->rgb_color = rgb_color;
this->m_rgb_color_isSet = true;
}
QString*
SWGDSCDemodSettings::getTitle() {
return title;
}
void
SWGDSCDemodSettings::setTitle(QString* title) {
this->title = title;
this->m_title_isSet = true;
}
qint32
SWGDSCDemodSettings::getStreamIndex() {
return stream_index;
}
void
SWGDSCDemodSettings::setStreamIndex(qint32 stream_index) {
this->stream_index = stream_index;
this->m_stream_index_isSet = true;
}
qint32
SWGDSCDemodSettings::getUseReverseApi() {
return use_reverse_api;
}
void
SWGDSCDemodSettings::setUseReverseApi(qint32 use_reverse_api) {
this->use_reverse_api = use_reverse_api;
this->m_use_reverse_api_isSet = true;
}
QString*
SWGDSCDemodSettings::getReverseApiAddress() {
return reverse_api_address;
}
void
SWGDSCDemodSettings::setReverseApiAddress(QString* reverse_api_address) {
this->reverse_api_address = reverse_api_address;
this->m_reverse_api_address_isSet = true;
}
qint32
SWGDSCDemodSettings::getReverseApiPort() {
return reverse_api_port;
}
void
SWGDSCDemodSettings::setReverseApiPort(qint32 reverse_api_port) {
this->reverse_api_port = reverse_api_port;
this->m_reverse_api_port_isSet = true;
}
qint32
SWGDSCDemodSettings::getReverseApiDeviceIndex() {
return reverse_api_device_index;
}
void
SWGDSCDemodSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) {
this->reverse_api_device_index = reverse_api_device_index;
this->m_reverse_api_device_index_isSet = true;
}
qint32
SWGDSCDemodSettings::getReverseApiChannelIndex() {
return reverse_api_channel_index;
}
void
SWGDSCDemodSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) {
this->reverse_api_channel_index = reverse_api_channel_index;
this->m_reverse_api_channel_index_isSet = true;
}
SWGGLScope*
SWGDSCDemodSettings::getScopeConfig() {
return scope_config;
}
void
SWGDSCDemodSettings::setScopeConfig(SWGGLScope* scope_config) {
this->scope_config = scope_config;
this->m_scope_config_isSet = true;
}
SWGChannelMarker*
SWGDSCDemodSettings::getChannelMarker() {
return channel_marker;
}
void
SWGDSCDemodSettings::setChannelMarker(SWGChannelMarker* channel_marker) {
this->channel_marker = channel_marker;
this->m_channel_marker_isSet = true;
}
SWGRollupState*
SWGDSCDemodSettings::getRollupState() {
return rollup_state;
}
void
SWGDSCDemodSettings::setRollupState(SWGRollupState* rollup_state) {
this->rollup_state = rollup_state;
this->m_rollup_state_isSet = true;
}
bool
SWGDSCDemodSettings::isSet(){
bool isObjectUpdated = false;
do{
if(m_input_frequency_offset_isSet){
isObjectUpdated = true; break;
}
if(m_rf_bandwidth_isSet){
isObjectUpdated = true; break;
}
if(m_filter_invalid_isSet){
isObjectUpdated = true; break;
}
if(m_filter_column_isSet){
isObjectUpdated = true; break;
}
if(filter && *filter != QString("")){
isObjectUpdated = true; break;
}
if(m_udp_enabled_isSet){
isObjectUpdated = true; break;
}
if(udp_address && *udp_address != QString("")){
isObjectUpdated = true; break;
}
if(m_udp_port_isSet){
isObjectUpdated = true; break;
}
if(log_filename && *log_filename != QString("")){
isObjectUpdated = true; break;
}
if(m_log_enabled_isSet){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}
if(title && *title != QString("")){
isObjectUpdated = true; break;
}
if(m_stream_index_isSet){
isObjectUpdated = true; break;
}
if(m_use_reverse_api_isSet){
isObjectUpdated = true; break;
}
if(reverse_api_address && *reverse_api_address != QString("")){
isObjectUpdated = true; break;
}
if(m_reverse_api_port_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_device_index_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_channel_index_isSet){
isObjectUpdated = true; break;
}
if(scope_config && scope_config->isSet()){
isObjectUpdated = true; break;
}
if(channel_marker && channel_marker->isSet()){
isObjectUpdated = true; break;
}
if(rollup_state && rollup_state->isSet()){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,182 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 7.0.0
* Contact: f4exb06@gmail.com
*
* NOTE: This class is auto generated by the swagger code generator program.
* https://github.com/swagger-api/swagger-codegen.git
* Do not edit the class manually.
*/
/*
* SWGDSCDemodSettings.h
*
* DSCDemod
*/
#ifndef SWGDSCDemodSettings_H_
#define SWGDSCDemodSettings_H_
#include <QJsonObject>
#include "SWGChannelMarker.h"
#include "SWGGLScope.h"
#include "SWGRollupState.h"
#include <QString>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGDSCDemodSettings: public SWGObject {
public:
SWGDSCDemodSettings();
SWGDSCDemodSettings(QString* json);
virtual ~SWGDSCDemodSettings();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGDSCDemodSettings* fromJson(QString &jsonString) override;
qint64 getInputFrequencyOffset();
void setInputFrequencyOffset(qint64 input_frequency_offset);
float getRfBandwidth();
void setRfBandwidth(float rf_bandwidth);
qint32 getFilterInvalid();
void setFilterInvalid(qint32 filter_invalid);
qint32 getFilterColumn();
void setFilterColumn(qint32 filter_column);
QString* getFilter();
void setFilter(QString* filter);
qint32 getUdpEnabled();
void setUdpEnabled(qint32 udp_enabled);
QString* getUdpAddress();
void setUdpAddress(QString* udp_address);
qint32 getUdpPort();
void setUdpPort(qint32 udp_port);
QString* getLogFilename();
void setLogFilename(QString* log_filename);
qint32 getLogEnabled();
void setLogEnabled(qint32 log_enabled);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
QString* getTitle();
void setTitle(QString* title);
qint32 getStreamIndex();
void setStreamIndex(qint32 stream_index);
qint32 getUseReverseApi();
void setUseReverseApi(qint32 use_reverse_api);
QString* getReverseApiAddress();
void setReverseApiAddress(QString* reverse_api_address);
qint32 getReverseApiPort();
void setReverseApiPort(qint32 reverse_api_port);
qint32 getReverseApiDeviceIndex();
void setReverseApiDeviceIndex(qint32 reverse_api_device_index);
qint32 getReverseApiChannelIndex();
void setReverseApiChannelIndex(qint32 reverse_api_channel_index);
SWGGLScope* getScopeConfig();
void setScopeConfig(SWGGLScope* scope_config);
SWGChannelMarker* getChannelMarker();
void setChannelMarker(SWGChannelMarker* channel_marker);
SWGRollupState* getRollupState();
void setRollupState(SWGRollupState* rollup_state);
virtual bool isSet() override;
private:
qint64 input_frequency_offset;
bool m_input_frequency_offset_isSet;
float rf_bandwidth;
bool m_rf_bandwidth_isSet;
qint32 filter_invalid;
bool m_filter_invalid_isSet;
qint32 filter_column;
bool m_filter_column_isSet;
QString* filter;
bool m_filter_isSet;
qint32 udp_enabled;
bool m_udp_enabled_isSet;
QString* udp_address;
bool m_udp_address_isSet;
qint32 udp_port;
bool m_udp_port_isSet;
QString* log_filename;
bool m_log_filename_isSet;
qint32 log_enabled;
bool m_log_enabled_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;
QString* title;
bool m_title_isSet;
qint32 stream_index;
bool m_stream_index_isSet;
qint32 use_reverse_api;
bool m_use_reverse_api_isSet;
QString* reverse_api_address;
bool m_reverse_api_address_isSet;
qint32 reverse_api_port;
bool m_reverse_api_port_isSet;
qint32 reverse_api_device_index;
bool m_reverse_api_device_index_isSet;
qint32 reverse_api_channel_index;
bool m_reverse_api_channel_index_isSet;
SWGGLScope* scope_config;
bool m_scope_config_isSet;
SWGChannelMarker* channel_marker;
bool m_channel_marker_isSet;
SWGRollupState* rollup_state;
bool m_rollup_state_isSet;
};
}
#endif /* SWGDSCDemodSettings_H_ */

View File

@ -99,6 +99,8 @@
#include "SWGDATVModSettings.h"
#include "SWGDOA2Report.h"
#include "SWGDOA2Settings.h"
#include "SWGDSCDemodReport.h"
#include "SWGDSCDemodSettings.h"
#include "SWGDSDDemodReport.h"
#include "SWGDSDDemodSettings.h"
#include "SWGDVSerialDevice.h"
@ -780,6 +782,16 @@ namespace SWGSDRangel {
obj->init();
return obj;
}
if(QString("SWGDSCDemodReport").compare(type) == 0) {
SWGDSCDemodReport *obj = new SWGDSCDemodReport();
obj->init();
return obj;
}
if(QString("SWGDSCDemodSettings").compare(type) == 0) {
SWGDSCDemodSettings *obj = new SWGDSCDemodSettings();
obj->init();
return obj;
}
if(QString("SWGDSDDemodReport").compare(type) == 0) {
SWGDSDDemodReport *obj = new SWGDSDDemodReport();
obj->init();