1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-23 01:55:48 -05:00

Add AIS slot map and additional message decoding

This commit is contained in:
Jon Beniston 2023-05-16 10:17:17 +01:00
parent 65b816c8a7
commit 403b62c354
17 changed files with 778 additions and 360 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@ -223,7 +223,8 @@ bool AISDemod::handleMessage(const Message& cmd)
<< ais->getType() << "," << ais->getType() << ","
<< "\"" << ais->toString() << "\"" << "," << "\"" << ais->toString() << "\"" << ","
<< "\"" << ais->toNMEA() << "\"" << "," << "\"" << ais->toNMEA() << "\"" << ","
<< report.getSlot() << "\n"; << report.getSlot() << ","
<< report.getSlots() << "\n";
delete ais; delete ais;
} }
@ -355,7 +356,7 @@ void AISDemod::applySettings(const AISDemodSettings& settings, bool force)
if (newFile) if (newFile)
{ {
// Write header // 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 else

View File

@ -73,22 +73,25 @@ public:
QByteArray getMessage() const { return m_message; } QByteArray getMessage() const { return m_message; }
QDateTime getDateTime() const { return m_dateTime; } QDateTime getDateTime() const { return m_dateTime; }
int getSlot() const { return m_slot; } 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: private:
QByteArray m_message; QByteArray m_message;
QDateTime m_dateTime; QDateTime m_dateTime;
int m_slot; int m_slot;
int m_slots;
MsgMessage(QByteArray message, QDateTime dateTime, int slot) : MsgMessage(QByteArray message, QDateTime dateTime, int slot, int totalSlots) :
Message(), Message(),
m_message(message), m_message(message),
m_dateTime(dateTime), m_dateTime(dateTime),
m_slot(slot) m_slot(slot),
m_slots(totalSlots)
{ {
} }
}; };

View File

@ -28,6 +28,7 @@
#include <QClipboard> #include <QClipboard>
#include <QFileDialog> #include <QFileDialog>
#include <QScrollBar> #include <QScrollBar>
#include <QIcon>
#include "aisdemodgui.h" #include "aisdemodgui.h"
@ -40,6 +41,7 @@
#include "util/ais.h" #include "util/ais.h"
#include "util/csv.h" #include "util/csv.h"
#include "util/db.h" #include "util/db.h"
#include "util/mmsi.h"
#include "gui/basicchannelsettingsdialog.h" #include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h" #include "gui/devicestreamselectiondialog.h"
#include "gui/dialpopup.h" #include "gui/dialpopup.h"
@ -61,10 +63,12 @@ void AISDemodGUI::resizeTable()
// Trailing spaces are for sort arrow // Trailing spaces are for sort arrow
int row = ui->messages->rowCount(); int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1); 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_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, MESSAGE_COL_MMSI, new QTableWidgetItem("123456789")); 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_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_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_NMEA, new QTableWidgetItem("!AIVDM,1,1,,A,AAAAAAAAAAAAAAAAAAAAAAAAAAAA,0*00"));
ui->messages->setItem(row, MESSAGE_COL_HEX, new QTableWidgetItem("04058804002000069a0760728d9e00000040000000")); 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 // 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; AISMessage *ais;
@ -174,7 +440,9 @@ void AISDemodGUI::messageReceived(const QByteArray& message, const QDateTime& da
QTableWidgetItem *dateItem = new QTableWidgetItem(); QTableWidgetItem *dateItem = new QTableWidgetItem();
QTableWidgetItem *timeItem = new QTableWidgetItem(); QTableWidgetItem *timeItem = new QTableWidgetItem();
QTableWidgetItem *mmsiItem = new QTableWidgetItem(); QTableWidgetItem *mmsiItem = new QTableWidgetItem();
QTableWidgetItem *countryItem = new QTableWidgetItem();
QTableWidgetItem *typeItem = new QTableWidgetItem(); QTableWidgetItem *typeItem = new QTableWidgetItem();
QTableWidgetItem *idItem = new QTableWidgetItem();
QTableWidgetItem *dataItem = new QTableWidgetItem(); QTableWidgetItem *dataItem = new QTableWidgetItem();
QTableWidgetItem *nmeaItem = new QTableWidgetItem(); QTableWidgetItem *nmeaItem = new QTableWidgetItem();
QTableWidgetItem *hexItem = 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_DATE, dateItem);
ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem); ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem);
ui->messages->setItem(row, MESSAGE_COL_MMSI, mmsiItem); 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_TYPE, typeItem);
ui->messages->setItem(row, MESSAGE_COL_ID, idItem);
ui->messages->setItem(row, MESSAGE_COL_DATA, dataItem); ui->messages->setItem(row, MESSAGE_COL_DATA, dataItem);
ui->messages->setItem(row, MESSAGE_COL_NMEA, nmeaItem); ui->messages->setItem(row, MESSAGE_COL_NMEA, nmeaItem);
ui->messages->setItem(row, MESSAGE_COL_HEX, hexItem); ui->messages->setItem(row, MESSAGE_COL_HEX, hexItem);
ui->messages->setItem(row, MESSAGE_COL_SLOT, slotItem); ui->messages->setItem(row, MESSAGE_COL_SLOT, slotItem);
dateItem->setText(dateTime.date().toString()); dateItem->setText(dateTime.date().toString());
timeItem->setText(dateTime.time().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()); typeItem->setText(ais->getType());
idItem->setData(Qt::DisplayRole, ais->m_id);
dataItem->setText(ais->toString()); dataItem->setText(ais->toString());
nmeaItem->setText(ais->toNMEA()); nmeaItem->setText(ais->toNMEA());
hexItem->setText(ais->toHex()); hexItem->setText(ais->toHex());
slotItem->setData(Qt::DisplayRole, slot); slotItem->setData(Qt::DisplayRole, slot);
if (!m_loadingData)
{
filterRow(row);
ui->messages->setSortingEnabled(true); ui->messages->setSortingEnabled(true);
if (scrollToBottom) { if (scrollToBottom) {
ui->messages->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; delete ais;
} }
@ -221,7 +516,7 @@ bool AISDemodGUI::handleMessage(const Message& message)
else if (AISDemod::MsgMessage::match(message)) else if (AISDemod::MsgMessage::match(message))
{ {
AISDemod::MsgMessage& report = (AISDemod::MsgMessage&) 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; return true;
} }
else if (DSPSignalNotification::match(message)) else if (DSPSignalNotification::match(message))
@ -330,18 +625,6 @@ void AISDemodGUI::on_udpFormat_currentIndexChanged(int value)
applySettings(); 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) void AISDemodGUI::on_messages_cellDoubleClicked(int row, int column)
{ {
// Get MMSI of message in row double clicked // Get MMSI of message in row double clicked
@ -440,7 +723,9 @@ AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
m_deviceCenterFrequency(0), m_deviceCenterFrequency(0),
m_basebandSampleRate(1), m_basebandSampleRate(1),
m_doApplySettings(true), m_doApplySettings(true),
m_tickCount(0) m_tickCount(0),
m_loadingData(false),
m_slotsUsed(0)
{ {
setAttribute(Qt::WA_DeleteOnClose, true); setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodais/readme.md"; 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 = m_aisDemod->getScopeSink();
m_scopeVis->setGLScope(ui->glScope); m_scopeVis->setGLScope(ui->glScope);
m_scopeVis->setNbStreams(AISDemodSettings::m_scopeStreams);
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer()); ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope); 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 // Scope settings to display the IQ waveforms
ui->scopeGUI->setPreTrigger(1); ui->scopeGUI->setPreTrigger(1);
@ -534,6 +821,16 @@ AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
ui->scopeContainer->setVisible(false); 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(); displaySettings();
makeUIConnections(); makeUIConnections();
applySettings(true); applySettings(true);
@ -612,12 +909,12 @@ void AISDemodGUI::displaySettings()
ui->udpPort->setText(QString::number(m_settings.m_udpPort)); ui->udpPort->setText(QString::number(m_settings.m_udpPort));
ui->udpFormat->setCurrentIndex((int)m_settings.m_udpFormat); 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->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled); 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 // Order and size columns
QHeaderView *header = ui->messages->horizontalHeader(); QHeaderView *header = ui->messages->horizontalHeader();
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++) for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
@ -662,13 +959,22 @@ void AISDemodGUI::tick()
(100.0f + powDbPeak) / 100.0f, (100.0f + powDbPeak) / 100.0f,
nbMagsqSamples); nbMagsqSamples);
if (m_tickCount % 4 == 0) { if (m_tickCount % 4 == 0)
{
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1)); ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
updateSlotMap();
} }
m_tickCount++; 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) void AISDemodGUI::on_logEnable_clicked(bool checked)
{ {
m_settings.m_logEnabled = checked; m_settings.m_logEnabled = checked;
@ -704,6 +1010,8 @@ void AISDemodGUI::on_logOpen_clicked()
QFile file(fileNames[0]); QFile file(fileNames[0]);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{ {
QDateTime startTime = QDateTime::currentDateTime();
m_loadingData = true;
QTextStream in(&file); QTextStream in(&file);
QString error; QString error;
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Date", "Time", "Data", "Slot"}, 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 timeCol = colIndexes.value("Time");
int dataCol = colIndexes.value("Data"); int dataCol = colIndexes.value("Data");
int slotCol = colIndexes.value("Slot"); 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); QMessageBox dialog(this);
dialog.setText("Reading messages"); dialog.setText("Reading messages");
@ -725,7 +1034,7 @@ void AISDemodGUI::on_logOpen_clicked()
QStringList cols; QStringList cols;
QList<ObjectPipe*> aisPipes; QList<ObjectPipe*> aisPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "ais", aisPipes); MainCore::instance()->getMessagePipes().getMessagePipes(m_aisDemod, "ais", aisPipes);
while (!cancelled && CSV::readRow(in, &cols)) while (!cancelled && CSV::readRow(in, &cols))
{ {
@ -736,9 +1045,10 @@ void AISDemodGUI::on_logOpen_clicked()
QDateTime dateTime(date, time); QDateTime dateTime(date, time);
QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1()); QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1());
int slot = cols[slotCol].toInt(); int slot = cols[slotCol].toInt();
int totalSlots = slotsCol == -1 ? 1 : cols[slotsCol].toInt();
// Add to table // Add to table
messageReceived(bytes, dateTime, slot); messageReceived(bytes, dateTime, slot, totalSlots);
// Forward to AIS feature // Forward to AIS feature
for (const auto& pipe : aisPipes) for (const auto& pipe : aisPipes)
@ -764,6 +1074,10 @@ void AISDemodGUI::on_logOpen_clicked()
{ {
QMessageBox::critical(this, "AIS Demod", error); 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 else
{ {
@ -789,8 +1103,7 @@ void AISDemodGUI::makeUIConnections()
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &AISDemodGUI::on_logEnable_clicked); 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->logFilename, &QToolButton::clicked, this, &AISDemodGUI::on_logFilename_clicked);
QObject::connect(ui->logOpen, &QToolButton::clicked, this, &AISDemodGUI::on_logOpen_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->showSlotMap, &ButtonSwitch::clicked, this, &AISDemodGUI::on_showSlotMap_clicked);
QObject::connect(ui->channel2, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &AISDemodGUI::on_channel2_currentIndexChanged);
} }
void AISDemodGUI::updateAbsoluteCenterFrequency() void AISDemodGUI::updateAbsoluteCenterFrequency()

View File

@ -24,6 +24,8 @@
#include <QToolButton> #include <QToolButton>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QMenu> #include <QMenu>
#include <QPainter>
#include <QHash>
#include "channel/channelgui.h" #include "channel/channelgui.h"
#include "dsp/channelmarker.h" #include "dsp/channelmarker.h"
@ -88,20 +90,39 @@ private:
AISDemod* m_aisDemod; AISDemod* m_aisDemod;
uint32_t m_tickCount; uint32_t m_tickCount;
MessageQueue m_inputMessageQueue; MessageQueue m_inputMessageQueue;
bool m_loadingData;
QMenu *messagesMenu; // Column select context menu QMenu *messagesMenu; // Column select context menu
QMenu *copyMenu; 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); explicit AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~AISDemodGUI(); virtual ~AISDemodGUI();
void blockApplySettings(bool block); void blockApplySettings(bool block);
void applySettings(bool force = false); void applySettings(bool force = false);
void displaySettings(); 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); bool handleMessage(const Message& message);
void makeUIConnections(); void makeUIConnections();
void updateAbsoluteCenterFrequency(); 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 leaveEvent(QEvent*);
void enterEvent(EnterEventType*); void enterEvent(EnterEventType*);
@ -113,7 +134,9 @@ private:
MESSAGE_COL_DATE, MESSAGE_COL_DATE,
MESSAGE_COL_TIME, MESSAGE_COL_TIME,
MESSAGE_COL_MMSI, MESSAGE_COL_MMSI,
MESSAGE_COL_COUNTRY,
MESSAGE_COL_TYPE, MESSAGE_COL_TYPE,
MESSAGE_COL_ID,
MESSAGE_COL_DATA, MESSAGE_COL_DATA,
MESSAGE_COL_NMEA, MESSAGE_COL_NMEA,
MESSAGE_COL_HEX, MESSAGE_COL_HEX,
@ -131,12 +154,11 @@ private slots:
void on_udpAddress_editingFinished(); void on_udpAddress_editingFinished();
void on_udpPort_editingFinished(); void on_udpPort_editingFinished();
void on_udpFormat_currentIndexChanged(int value); 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_messages_cellDoubleClicked(int row, int column);
void on_logEnable_clicked(bool checked=false); void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked(); void on_logFilename_clicked();
void on_logOpen_clicked(); void on_logOpen_clicked();
void on_showSlotMap_clicked(bool checked=false);
void filterRow(int row); void filterRow(int row);
void filter(); void filter();
void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex); void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>388</width> <width>388</width>
<height>446</height> <height>985</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -557,6 +557,32 @@
</property> </property>
</spacer> </spacer>
</item> </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> <item>
<widget class="ButtonSwitch" name="logEnable"> <widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize"> <property name="maximumSize">
@ -632,10 +658,10 @@
<widget class="QWidget" name="messageContainer" native="true"> <widget class="QWidget" name="messageContainer" native="true">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>10</x>
<y>210</y> <y>150</y>
<width>391</width> <width>361</width>
<height>171</height> <height>351</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -647,23 +673,137 @@
<property name="windowTitle"> <property name="windowTitle">
<string>Received Messages</string> <string>Received Messages</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayoutTable"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing"> <item>
<number>2</number> <widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property> </property>
<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"> <property name="leftMargin">
<number>3</number> <number>0</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>3</number> <number>0</number>
</property> </property>
<property name="rightMargin"> <property name="rightMargin">
<number>3</number> <number>0</number>
</property> </property>
<property name="bottomMargin"> <property name="bottomMargin">
<number>3</number> <number>0</number>
</property> </property>
<item> <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"> <widget class="QTableWidget" name="messages">
<property name="toolTip"> <property name="toolTip">
<string>Received packets</string> <string>Received packets</string>
@ -675,41 +815,72 @@
<property name="text"> <property name="text">
<string>Date</string> <string>Date</string>
</property> </property>
<property name="toolTip">
<string>Date message was received</string>
</property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Time</string> <string>Time</string>
</property> </property>
<property name="toolTip">
<string>Time message was received</string>
</property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>MMSI</string> <string>MMSI</string>
</property> </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>
<column> <column>
<property name="text"> <property name="text">
<string>Type</string> <string>Type</string>
</property> </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>
<column> <column>
<property name="text"> <property name="text">
<string>Data</string> <string>Data</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Packet data as ASCII</string> <string>Decoded message data</string>
</property> </property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>NMEA</string> <string>NMEA</string>
</property> </property>
<property name="toolTip">
<string>Message data in NMEA format</string>
</property>
</column> </column>
<column> <column>
<property name="text"> <property name="text">
<string>Hex</string> <string>Hex</string>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Packet data as hex</string> <string>Message data as hex</string>
</property> </property>
</column> </column>
<column> <column>
@ -721,6 +892,7 @@
</property> </property>
</column> </column>
</widget> </widget>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -728,7 +900,7 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>20</x> <x>20</x>
<y>400</y> <y>510</y>
<width>716</width> <width>716</width>
<height>341</height> <height>341</height>
</rect> </rect>
@ -758,150 +930,6 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>3</number> <number>3</number>
</property> </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> <item>
<widget class="GLScope" name="glScope" native="true"> <widget class="GLScope" name="glScope" native="true">
<property name="minimumSize"> <property name="minimumSize">

View File

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

View File

@ -27,7 +27,7 @@
class Serializable; class Serializable;
// Number of columns in the tables // Number of columns in the tables
#define AISDEMOD_MESSAGE_COLUMNS 8 #define AISDEMOD_MESSAGE_COLUMNS 10
struct AISDemodSettings struct AISDemodSettings
{ {
@ -44,11 +44,10 @@ struct AISDemodSettings
Binary, Binary,
NMEA NMEA
} m_udpFormat; } m_udpFormat;
int m_scopeCh1;
int m_scopeCh2;
QString m_logFilename; QString m_logFilename;
bool m_logEnabled; bool m_logEnabled;
bool m_showSlotMap;
quint32 m_rgbColor; quint32 m_rgbColor;
QString m_title; QString m_title;
@ -69,6 +68,7 @@ struct AISDemodSettings
int m_messageColumnSizes[AISDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table 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 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(); AISDemodSettings();
void resetToDefaults(); void resetToDefaults();

View File

@ -47,7 +47,9 @@ AISDemodSink::AISDemodSink(AISDemod *aisDemod) :
m_demodBuffer.resize(1<<12); m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0; 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); applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true); applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
@ -59,18 +61,28 @@ AISDemodSink::~AISDemodSink()
delete[] m_train; 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) if (m_scopeSink)
{ {
Real r = std::real(sample) * SDR_RX_SCALEF; m_sampleBuffer[0][m_sampleBufferIndex] = sample;
Real i = std::imag(sample) * SDR_RX_SCALEF; m_sampleBuffer[1][m_sampleBufferIndex] = Complex(m_magsq, 0.0f);
m_sampleBuffer[m_sampleBufferIndex++] = Sample(r, i); 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) if (m_sampleBufferIndex == m_sampleBufferSize)
{ {
std::vector<SampleVector::const_iterator> vbegin; std::vector<ComplexVector::const_iterator> vbegin;
vbegin.push_back(m_sampleBuffer.begin());
for (int i = 0; i < AISDemodSettings::m_scopeStreams; i++) {
vbegin.push_back(m_sampleBuffer[i].begin());
}
m_scopeSink->feed(vbegin, m_sampleBufferSize); m_scopeSink->feed(vbegin, m_sampleBufferSize);
m_sampleBufferIndex = 0; 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 // 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 // But can be used to get an idea of congestion
QDateTime currentTime = QDateTime::currentDateTime(); 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 ms = startDateTime.time().second() * 1000 + startDateTime.time().msec();
int slot = ms / 26.67; // 2250 slots per minute, 26ms per slot float slotTime = 60.0f * 1000.0f / 2250.0f; // 2250 slots per minute, 26.6ms per slot
AISDemod::MsgMessage *msg = AISDemod::MsgMessage::create(rxPacket, currentTime, slot); int slot = ms / slotTime;
int totalSlots = std::ceil(txTimeMs / slotTime);
AISDemod::MsgMessage *msg = AISDemod::MsgMessage::create(rxPacket, currentTime, slot, totalSlots);
getMessageQueueToChannel()->push(msg); getMessageQueueToChannel()->push(msg);
} }
@ -318,74 +333,7 @@ void AISDemodSink::processOneSample(Complex &ci)
} }
// Select signals to feed to scope // Select signals to feed to scope
Complex 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));
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);
// Send demod signal to Demod Analzyer feature // Send demod signal to Demod Analzyer feature
m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits<int16_t>::max(); m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits<int16_t>::max();

View File

@ -128,13 +128,13 @@ private:
QVector<qint16> m_demodBuffer; QVector<qint16> m_demodBuffer;
int m_demodBufferFill; int m_demodBufferFill;
SampleVector m_sampleBuffer; ComplexVector m_sampleBuffer[AISDemodSettings::m_scopeStreams];
static const int m_sampleBufferSize = AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE / 20; static const int m_sampleBufferSize = AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE / 20;
int m_sampleBufferIndex; int m_sampleBufferIndex;
void processOneSample(Complex &ci); void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; } 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 #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. 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> <h3>Received Messages Table</h3>
The received messages table displays information about each AIS message received. Only messages with valid CRCs are displayed. 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. * Date - The date the message was received.
* Time - The time 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/ * 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. * 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. * Data - A textual decode of the message displaying the most interesting fields.
* NMEA - The message in NMEA format. * NMEA - The message in NMEA format.
* Hex - The message in hex 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. 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

@ -31,6 +31,7 @@
#include "gui/dialogpositioner.h" #include "gui/dialogpositioner.h"
#include "mainwindow.h" #include "mainwindow.h"
#include "device/deviceuiset.h" #include "device/deviceuiset.h"
#include "util/mmsi.h"
#include "ui_aisgui.h" #include "ui_aisgui.h"
#include "ais.h" #include "ais.h"
@ -377,6 +378,7 @@ void AISGUI::resizeTable()
int row = ui->vessels->rowCount(); int row = ui->vessels->rowCount();
ui->vessels->setRowCount(row + 1); ui->vessels->setRowCount(row + 1);
ui->vessels->setItem(row, VESSEL_COL_MMSI, new QTableWidgetItem("123456789")); 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_TYPE, new QTableWidgetItem("Base station"));
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, new QTableWidgetItem("90.000000-")); ui->vessels->setItem(row, VESSEL_COL_LATITUDE, new QTableWidgetItem("90.000000-"));
ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, new QTableWidgetItem("180.00000-")); 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) void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
{ {
QTableWidgetItem *mmsiItem; QTableWidgetItem *mmsiItem;
QTableWidgetItem *countryItem;
QTableWidgetItem *typeItem; QTableWidgetItem *typeItem;
QTableWidgetItem *latitudeItem; QTableWidgetItem *latitudeItem;
QTableWidgetItem *longitudeItem; QTableWidgetItem *longitudeItem;
@ -534,6 +537,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
{ {
// Update existing item // Update existing item
mmsiItem = ui->vessels->item(row, VESSEL_COL_MMSI); mmsiItem = ui->vessels->item(row, VESSEL_COL_MMSI);
countryItem = ui->vessels->item(row, VESSEL_COL_COUNTRY);
typeItem = ui->vessels->item(row, VESSEL_COL_TYPE); typeItem = ui->vessels->item(row, VESSEL_COL_TYPE);
latitudeItem = ui->vessels->item(row, VESSEL_COL_LATITUDE); latitudeItem = ui->vessels->item(row, VESSEL_COL_LATITUDE);
longitudeItem = ui->vessels->item(row, VESSEL_COL_LONGITUDE); longitudeItem = ui->vessels->item(row, VESSEL_COL_LONGITUDE);
@ -563,6 +567,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
ui->vessels->setRowCount(row + 1); ui->vessels->setRowCount(row + 1);
mmsiItem = new QTableWidgetItem(); mmsiItem = new QTableWidgetItem();
countryItem = new QTableWidgetItem();
typeItem = new QTableWidgetItem(); typeItem = new QTableWidgetItem();
latitudeItem = new QTableWidgetItem(); latitudeItem = new QTableWidgetItem();
longitudeItem = new QTableWidgetItem(); longitudeItem = new QTableWidgetItem();
@ -580,6 +585,7 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
lastUpdateItem = new QTableWidgetItem(); lastUpdateItem = new QTableWidgetItem();
messagesItem = new QTableWidgetItem(); messagesItem = new QTableWidgetItem();
ui->vessels->setItem(row, VESSEL_COL_MMSI, mmsiItem); 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_TYPE, typeItem);
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, latitudeItem); ui->vessels->setItem(row, VESSEL_COL_LATITUDE, latitudeItem);
ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, longitudeItem); ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, longitudeItem);
@ -605,7 +611,15 @@ void AISGUI::updateVessels(AISMessage *ais, QDateTime dateTime)
previousType = typeItem->text(); previousType = typeItem->text();
previousShipType = shipTypeItem->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); lastUpdateItem->setData(Qt::DisplayRole, dateTime);
messagesItem->setData(Qt::DisplayRole, messagesItem->data(Qt::DisplayRole).toInt() + 1); 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); QAction* mmsiAction = new QAction(QString("View MMSI %1 on vesselfinder.com...").arg(mmsi), tableContextMenu);
connect(mmsiAction, &QAction::triggered, this, [mmsi]()->void { 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); tableContextMenu->addAction(mmsiAction);
if (!imo.isEmpty()) 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 { 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); tableContextMenu->addAction(imoAction);
} }
if (!name.isEmpty()) 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 { 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); tableContextMenu->addAction(nameAction);
} }

View File

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

View File

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

View File

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

View File

@ -336,12 +336,24 @@ QString AISPositionReport::getStatusString(int status)
return statuses[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 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") return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Status: %5")
.arg(m_latitude) .arg(m_latitude)
.arg(m_longitude) .arg(m_longitude)
.arg(m_speedOverGround) .arg(speed)
.arg(m_course) .arg(m_course)
.arg(AISPositionReport::getStatusString(m_status)) .arg(AISPositionReport::getStatusString(m_status))
.arg(QChar(0xb0)); .arg(QChar(0xb0));
@ -444,7 +456,7 @@ AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) :
int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf); int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf);
m_speedOverGroundAvailable = sog != 1023; m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f; m_speedOverGround = sog;
m_positionAccuracy = (ba[7] >> 3) & 0x1; m_positionAccuracy = (ba[7] >> 3) & 0x1;
@ -467,12 +479,14 @@ AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) :
QString AISSARAircraftPositionReport::toString() 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") return QString("Lat: %1%6 Lon: %2%6 Speed: %3 knts Course: %4%6 Alt: %5 m")
.arg(m_latitude) .arg(m_latitude)
.arg(m_longitude) .arg(m_longitude)
.arg(m_speedOverGround) .arg(speed)
.arg(m_course) .arg(m_course)
.arg(m_altitude) .arg(altitude)
.arg(QChar(0xb0)); .arg(QChar(0xb0));
} }
@ -520,6 +534,18 @@ AISInterrogation::AISInterrogation(QByteArray ba) :
AISAssignedModeCommand::AISAssignedModeCommand(QByteArray ba) : AISAssignedModeCommand::AISAssignedModeCommand(QByteArray ba) :
AISMessage(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) : AISGNSSBroadcast::AISGNSSBroadcast(QByteArray ba) :
@ -721,6 +747,25 @@ QString AISStaticDataReport::toString()
AISSingleSlotBinaryMessage::AISSingleSlotBinaryMessage(QByteArray ba) : AISSingleSlotBinaryMessage::AISSingleSlotBinaryMessage(QByteArray ba) :
AISMessage(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) : AISMultipleSlotBinaryMessage::AISMultipleSlotBinaryMessage(QByteArray ba) :

View File

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