Add AIS slot map and additional message decoding

pull/1693/head
Jon Beniston 2023-05-16 10:17:17 +01:00
rodzic 65b816c8a7
commit 403b62c354
17 zmienionych plików z 778 dodań i 360 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 65 KiB

Wyświetl plik

@ -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

Wyświetl plik

@ -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)
{
}
};

Wyświetl plik

@ -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()

Wyświetl plik

@ -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);

Wyświetl plik

@ -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">

Wyświetl plik

@ -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);

Wyświetl plik

@ -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();

Wyświetl plik

@ -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();

Wyświetl plik

@ -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

Wyświetl plik

@ -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.

Wyświetl plik

@ -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);
}

Wyświetl plik

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

Wyświetl plik

@ -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">

Wyświetl plik

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

Wyświetl plik

@ -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) :

Wyświetl plik

@ -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 {