1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-22 16:08:39 -05:00

Merge pull request #891 from srcejon/ais

Add AIS mod, demod and feature
This commit is contained in:
Edouard Griffiths 2021-05-08 16:26:39 +02:00 committed by GitHub
commit 8440e4e63b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 15641 additions and 12 deletions

4
debian/control vendored
View File

@ -61,9 +61,9 @@ Description: SDR/Analyzer/Generator front-end for various hardware
Builds on Linux, Windows and Mac O/S
Reception modes supported:
Analog: AM, ATV, NFM, WFM, SSB, broadcast FM, APT
Digital: D-Star, Yaesu SF, DMR, dPMR, FreeDV, DAB, DVB-S, LoRa, ADS-B, Packet (AX.25/APRS)
Digital: D-Star, Yaesu SF, DMR, dPMR, FreeDV, DAB, DVB-S, LoRa, ADS-B, Packet (AX.25/APRS), AIS
Analyzer: Generic channel
Transmission modes supported:
Analog: AM, ATV, NFM, SSB, WFM
Digital: DVB-S, Packet (AX.25), 802.15.4
Digital: DVB-S, Packet (AX.25), AIS, 802.15.4
Homepage: https://github.com/f4exb/sdrangel

BIN
doc/img/AISDemod_plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
doc/img/AISMod_plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
doc/img/AIS_plugin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
doc/img/AIS_plugin_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

View File

@ -17,6 +17,7 @@ add_subdirectory(freqtracker)
add_subdirectory(demodchirpchat)
add_subdirectory(demodvorsc)
add_subdirectory(demodpacket)
add_subdirectory(demodais)
if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND)
add_subdirectory(demoddab)

View File

@ -0,0 +1,58 @@
project(demodais)
set(demodais_SOURCES
aisdemod.cpp
aisdemodsettings.cpp
aisdemodbaseband.cpp
aisdemodsink.cpp
aisdemodplugin.cpp
aisdemodwebapiadapter.cpp
)
set(demodais_HEADERS
aisdemod.h
aisdemodsettings.h
aisdemodbaseband.h
aisdemodsink.h
aisdemodplugin.h
aisdemodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(demodais_SOURCES
${demodais_SOURCES}
aisdemodgui.cpp
aisdemodgui.ui
)
set(demodais_HEADERS
${demodais_HEADERS}
aisdemodgui.h
)
set(TARGET_NAME demodais)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demodaissrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demodais_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,500 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "aisdemod.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGChannelReport.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/ais.h"
#include "util/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(AISDemod::MsgConfigureAISDemod, Message)
MESSAGE_CLASS_DEFINITION(AISDemod::MsgMessage, Message)
const char * const AISDemod::m_channelIdURI = "sdrangel.channel.aisdemod";
const char * const AISDemod::m_channelId = "AISDemod";
AISDemod::AISDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new AISDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
m_basebandSink->setChannel(this);
m_basebandSink->moveToThread(&m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSink(this);
m_deviceAPI->addChannelSinkAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
AISDemod::~AISDemod()
{
qDebug("AISDemod::~AISDemod");
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this);
if (m_basebandSink->isRunning()) {
stop();
}
delete m_basebandSink;
}
uint32_t AISDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void AISDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void AISDemod::start()
{
qDebug("AISDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
AISDemodBaseband::MsgConfigureAISDemodBaseband *msg = AISDemodBaseband::MsgConfigureAISDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void AISDemod::stop()
{
qDebug("AISDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool AISDemod::handleMessage(const Message& cmd)
{
if (MsgConfigureAISDemod::match(cmd))
{
MsgConfigureAISDemod& cfg = (MsgConfigureAISDemod&) cmd;
qDebug() << "AISDemod::handleMessage: MsgConfigureAISDemod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "AISDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
return true;
}
else if (MsgMessage::match(cmd))
{
// Forward to GUI
MsgMessage& report = (MsgMessage&)cmd;
if (getMessageQueueToGUI())
{
MsgMessage *msg = new MsgMessage(report);
getMessageQueueToGUI()->push(msg);
}
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
// Forward to AIS feature
QList<MessageQueue*> *aisMessageQueues = messagePipes.getMessageQueues(this, "ais");
if (aisMessageQueues)
{
QList<MessageQueue*>::iterator it = aisMessageQueues->begin();
for (; it != aisMessageQueues->end(); ++it)
{
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(this, report.getMessage(), report.getDateTime());
(*it)->push(msg);
}
}
// Forward via UDP
if (m_settings.m_udpEnabled)
{
if (m_settings.m_udpFormat == AISDemodSettings::Binary)
{
m_udpSocket.writeDatagram(report.getMessage().data(), report.getMessage().size(),
QHostAddress(m_settings.m_udpAddress), m_settings.m_udpPort);
}
else
{
QString nmea = AISMessage::toNMEA(report.getMessage().data());
QByteArray bytes = nmea.toLatin1();
m_udpSocket.writeDatagram(bytes.data(), bytes.size(),
QHostAddress(m_settings.m_udpAddress), m_settings.m_udpPort);
}
}
return true;
}
else
{
return false;
}
}
void AISDemod::setScopeSink(BasebandSampleSink* scopeSink)
{
m_basebandSink->setScopeSink(scopeSink);
}
void AISDemod::applySettings(const AISDemodSettings& settings, bool force)
{
qDebug() << "AISDemod::applySettings:"
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
}
if ((settings.m_correlationThreshold != m_settings.m_correlationThreshold) || force) {
reverseAPIKeys.append("correlationThreshold");
}
if ((settings.m_udpEnabled != m_settings.m_udpEnabled) || force) {
reverseAPIKeys.append("udpEnabled");
}
if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) {
reverseAPIKeys.append("udpAddress");
}
if ((settings.m_udpPort != m_settings.m_udpPort) || force) {
reverseAPIKeys.append("udpPort");
}
if ((settings.m_udpFormat != m_settings.m_udpFormat) || force) {
reverseAPIKeys.append("udpFormat");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
AISDemodBaseband::MsgConfigureAISDemodBaseband *msg = AISDemodBaseband::MsgConfigureAISDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
}
QByteArray AISDemod::serialize() const
{
return m_settings.serialize();
}
bool AISDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureAISDemod *msg = MsgConfigureAISDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureAISDemod *msg = MsgConfigureAISDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int AISDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisDemodSettings(new SWGSDRangel::SWGAISDemodSettings());
response.getAisDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int AISDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
AISDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureAISDemod *msg = MsgConfigureAISDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("AISDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAISDemod *msgToGUI = MsgConfigureAISDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void AISDemod::webapiUpdateChannelSettings(
AISDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getAisDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getAisDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getAisDemodSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("correlationThreshold")) {
settings.m_correlationThreshold = response.getAisDemodSettings()->getCorrelationThreshold();
}
if (channelSettingsKeys.contains("udpEnabled")) {
settings.m_udpEnabled = response.getAisDemodSettings()->getUdpEnabled();
}
if (channelSettingsKeys.contains("udpAddress")) {
settings.m_udpAddress = *response.getAisDemodSettings()->getUdpAddress();
}
if (channelSettingsKeys.contains("udpPort")) {
settings.m_udpPort = response.getAisDemodSettings()->getUdpPort();
}
if (channelSettingsKeys.contains("udpFormat")) {
settings.m_udpFormat = (AISDemodSettings::UDPFormat)response.getAisDemodSettings()->getUdpFormat();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAisDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getAisDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getAisDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAisDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAisDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAisDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getAisDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getAisDemodSettings()->getReverseApiChannelIndex();
}
}
void AISDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AISDemodSettings& settings)
{
response.getAisDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getAisDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getAisDemodSettings()->setFmDeviation(settings.m_fmDeviation);
response.getAisDemodSettings()->setCorrelationThreshold(settings.m_correlationThreshold);
response.getAisDemodSettings()->setUdpEnabled(settings.m_udpEnabled);
response.getAisDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress));
response.getAisDemodSettings()->setUdpPort(settings.m_udpPort);
response.getAisDemodSettings()->setUdpFormat((int)settings.m_udpFormat);
response.getAisDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getAisDemodSettings()->getTitle()) {
*response.getAisDemodSettings()->getTitle() = settings.m_title;
} else {
response.getAisDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getAisDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getAisDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAisDemodSettings()->getReverseApiAddress()) {
*response.getAisDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAisDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAisDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getAisDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getAisDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void AISDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AISDemodSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void AISDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const AISDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("AISDemod"));
swgChannelSettings->setAisDemodSettings(new SWGSDRangel::SWGAISDemodSettings());
SWGSDRangel::SWGAISDemodSettings *swgAISDemodSettings = swgChannelSettings->getAisDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgAISDemodSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgAISDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgAISDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("correlationThreshold") || force) {
swgAISDemodSettings->setCorrelationThreshold(settings.m_correlationThreshold);
}
if (channelSettingsKeys.contains("udpEnabled") || force) {
swgAISDemodSettings->setUdpEnabled(settings.m_udpEnabled);
}
if (channelSettingsKeys.contains("udpAddress") || force) {
swgAISDemodSettings->setUdpAddress(new QString(settings.m_udpAddress));
}
if (channelSettingsKeys.contains("udpPort") || force) {
swgAISDemodSettings->setUdpPort(settings.m_udpPort);
}
if (channelSettingsKeys.contains("udpFormat") || force) {
swgAISDemodSettings->setUdpPort((int)settings.m_udpFormat);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAISDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgAISDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgAISDemodSettings->setStreamIndex(settings.m_streamIndex);
}
}
void AISDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AISDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AISDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,180 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015-2018 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMOD_H
#define INCLUDE_AISDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QDateTime>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "aisdemodbaseband.h"
#include "aisdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class AISDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureAISDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AISDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAISDemod* create(const AISDemodSettings& settings, bool force)
{
return new MsgConfigureAISDemod(settings, force);
}
private:
AISDemodSettings m_settings;
bool m_force;
MsgConfigureAISDemod(const AISDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgMessage : public Message {
MESSAGE_CLASS_DECLARATION
public:
QByteArray getMessage() const { return m_message; }
QDateTime getDateTime() const { return m_dateTime; }
static MsgMessage* create(QByteArray message)
{
return new MsgMessage(message, QDateTime::currentDateTime());
}
private:
QByteArray m_message;
QDateTime m_dateTime;
MsgMessage(QByteArray message, QDateTime dateTime) :
Message(),
m_message(message),
m_dateTime(dateTime)
{
}
};
AISDemod(DeviceAPI *deviceAPI);
virtual ~AISDemod();
virtual void destroy() { delete this; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return 0; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const AISDemodSettings& settings);
static void webapiUpdateChannelSettings(
AISDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
void setScopeSink(BasebandSampleSink* scopeSink);
double getMagSq() const { return m_basebandSink->getMagSq(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
/* void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}*/
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
AISDemodBaseband* m_basebandSink;
AISDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QUdpSocket m_udpSocket;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void applySettings(const AISDemodSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AISDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const AISDemodSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_AISDEMOD_H

View File

@ -0,0 +1,175 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "dsp/downchannelizer.h"
#include "aisdemodbaseband.h"
MESSAGE_CLASS_DEFINITION(AISDemodBaseband::MsgConfigureAISDemodBaseband, Message)
AISDemodBaseband::AISDemodBaseband(AISDemod *aisDemod) :
m_sink(aisDemod),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("AISDemodBaseband::AISDemodBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
AISDemodBaseband::~AISDemodBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void AISDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void AISDemodBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&AISDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void AISDemodBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&AISDemodBaseband::handleData
);
m_running = false;
}
void AISDemodBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void AISDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void AISDemodBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
{
SampleVector::iterator part1begin;
SampleVector::iterator part1end;
SampleVector::iterator part2begin;
SampleVector::iterator part2end;
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
// first part of FIFO data
if (part1begin != part1end) {
m_channelizer->feed(part1begin, part1end);
}
// second part of FIFO data (used when block wraps around)
if(part2begin != part2end) {
m_channelizer->feed(part2begin, part2end);
}
m_sampleFifo.readCommit((unsigned int) count);
}
}
void AISDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool AISDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureAISDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureAISDemodBaseband& cfg = (MsgConfigureAISDemodBaseband&) cmd;
qDebug() << "AISDemodBaseband::handleMessage: MsgConfigureAISDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "AISDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else
{
return false;
}
}
void AISDemodBaseband::applySettings(const AISDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(AISDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
void AISDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

View File

@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMODBASEBAND_H
#define INCLUDE_AISDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "aisdemodsink.h"
class DownChannelizer;
class ChannelAPI;
class AISDemod;
class AISDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureAISDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AISDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAISDemodBaseband* create(const AISDemodSettings& settings, bool force)
{
return new MsgConfigureAISDemodBaseband(settings, force);
}
private:
AISDemodSettings m_settings;
bool m_force;
MsgConfigureAISDemodBaseband(const AISDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
AISDemodBaseband(AISDemod *aisDemod);
~AISDemodBaseband();
void reset();
void startWork();
void stopWork();
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_sink.getMagSqLevels(avg, peak, nbSamples);
}
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
void setBasebandSampleRate(int sampleRate);
void setScopeSink(BasebandSampleSink* scopeSink) { m_sink.setScopeSink(scopeSink); }
void setChannel(ChannelAPI *channel);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
AISDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
AISDemodSettings m_settings;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void calculateOffset(AISDemodSink *sink);
void applySettings(const AISDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_AISDEMODBASEBAND_H

View File

@ -0,0 +1,627 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <ctype.h>
#include <QDockWidget>
#include <QMainWindow>
#include <QDebug>
#include <QDesktopServices>
#include <QMessageBox>
#include <QAction>
#include <QRegExp>
#include <QClipboard>
#include "aisdemodgui.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_aisdemodgui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/ais.h"
#include "util/db.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "dsp/dspengine.h"
#include "gui/crightclickenabler.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "feature/featurewebapiutils.h"
#include "aisdemod.h"
#include "aisdemodsink.h"
#include "SWGMapItem.h"
void AISDemodGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
// 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_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, MESSAGE_COL_MMSI, new QTableWidgetItem("123456789"));
ui->messages->setItem(row, MESSAGE_COL_TYPE, new QTableWidgetItem("Position report"));
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"));
ui->messages->resizeColumnsToContents();
ui->messages->removeRow(row);
}
// Columns in table reordered
void AISDemodGUI::messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_messageColumnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void AISDemodGUI::messages_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_messageColumnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void AISDemodGUI::messagesColumnSelectMenu(QPoint pos)
{
messagesMenu->popup(ui->messages->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void AISDemodGUI::messagesColumnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->messages->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *AISDemodGUI::createCheckableItem(QString &text, int idx, bool checked, const char *slot)
{
QAction *action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
connect(action, SIGNAL(triggered()), this, slot);
return action;
}
AISDemodGUI* AISDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
AISDemodGUI* gui = new AISDemodGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void AISDemodGUI::destroy()
{
delete this;
}
void AISDemodGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray AISDemodGUI::serialize() const
{
return m_settings.serialize();
}
bool AISDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
// Add row to table
void AISDemodGUI::messageReceived(const AISDemod::MsgMessage& message)
{
AISMessage *ais;
// Decode the message
ais = AISMessage::decode(message.getMessage());
// Add to messages table
ui->messages->setSortingEnabled(false);
int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1);
QTableWidgetItem *dateItem = new QTableWidgetItem();
QTableWidgetItem *timeItem = new QTableWidgetItem();
QTableWidgetItem *mmsiItem = new QTableWidgetItem();
QTableWidgetItem *typeItem = new QTableWidgetItem();
QTableWidgetItem *dataItem = new QTableWidgetItem();
QTableWidgetItem *nmeaItem = new QTableWidgetItem();
QTableWidgetItem *hexItem = new QTableWidgetItem();
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_TYPE, typeItem);
ui->messages->setItem(row, MESSAGE_COL_DATA, dataItem);
ui->messages->setItem(row, MESSAGE_COL_NMEA, nmeaItem);
ui->messages->setItem(row, MESSAGE_COL_HEX, hexItem);
dateItem->setText(message.getDateTime().date().toString());
timeItem->setText(message.getDateTime().time().toString());
mmsiItem->setText(QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0')));
typeItem->setText(ais->getType());
dataItem->setText(ais->toString());
nmeaItem->setText(ais->toNMEA());
hexItem->setText(ais->toHex());
ui->messages->setSortingEnabled(true);
ui->messages->scrollToItem(dateItem); // Will only scroll if not hidden
filterRow(row);
}
bool AISDemodGUI::handleMessage(const Message& message)
{
if (AISDemod::MsgConfigureAISDemod::match(message))
{
qDebug("AISDemodGUI::handleMessage: AISDemod::MsgConfigureAISDemod");
const AISDemod::MsgConfigureAISDemod& cfg = (AISDemod::MsgConfigureAISDemod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (AISDemod::MsgMessage::match(message))
{
AISDemod::MsgMessage& report = (AISDemod::MsgMessage&) message;
messageReceived(report);
return true;
}
return false;
}
void AISDemodGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void AISDemodGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void AISDemodGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void AISDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void AISDemodGUI::on_rfBW_valueChanged(int value)
{
float bw = value * 100.0f;
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_channelMarker.setBandwidth(bw);
m_settings.m_rfBandwidth = bw;
applySettings();
}
void AISDemodGUI::on_fmDev_valueChanged(int value)
{
ui->fmDevText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_settings.m_fmDeviation = value * 100.0;
applySettings();
}
void AISDemodGUI::on_threshold_valueChanged(int value)
{
ui->thresholdText->setText(QString("%1").arg(value));
m_settings.m_correlationThreshold = value;
applySettings();
}
void AISDemodGUI::on_filterMMSI_editingFinished()
{
m_settings.m_filterMMSI = ui->filterMMSI->text();
filter();
applySettings();
}
void AISDemodGUI::on_clearTable_clicked()
{
ui->messages->setRowCount(0);
}
void AISDemodGUI::on_udpEnabled_clicked(bool checked)
{
m_settings.m_udpEnabled = checked;
applySettings();
}
void AISDemodGUI::on_udpAddress_editingFinished()
{
m_settings.m_udpAddress = ui->udpAddress->text();
applySettings();
}
void AISDemodGUI::on_udpPort_editingFinished()
{
m_settings.m_udpPort = ui->udpPort->text().toInt();
applySettings();
}
void AISDemodGUI::on_udpFormat_currentIndexChanged(int value)
{
m_settings.m_udpFormat = (AISDemodSettings::UDPFormat)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
QString mmsi = ui->messages->item(row, MESSAGE_COL_MMSI)->text();
if (column == MESSAGE_COL_MMSI)
{
// Search for MMSI on www.vesselfinder.com
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(mmsi)));
}
}
void AISDemodGUI::filterRow(int row)
{
bool hidden = false;
if (m_settings.m_filterMMSI != "")
{
QRegExp re(m_settings.m_filterMMSI);
QTableWidgetItem *fromItem = ui->messages->item(row, MESSAGE_COL_MMSI);
if (!re.exactMatch(fromItem->text()))
hidden = true;
}
ui->messages->setRowHidden(row, hidden);
}
void AISDemodGUI::filter()
{
for (int i = 0; i < ui->messages->rowCount(); i++)
{
filterRow(i);
}
}
void AISDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void AISDemodGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine))
{
DeviceStreamSelectionDialog dialog(this);
dialog.setNumberOfStreams(m_aisDemod->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
dialog.move(p);
dialog.exec();
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
displayStreamIndex();
applySettings();
}
resetContextMenuType();
}
AISDemodGUI::AISDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::AISDemodGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_doApplySettings(true),
m_tickCount(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_aisDemod = reinterpret_cast<AISDemod*>(rxChannel);
m_aisDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
m_scopeVis = new ScopeVis(ui->glScope);
m_aisDemod->setScopeSink(m_scopeVis);
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
// Scope settings to display the IQ waveforms
ui->scopeGUI->setPreTrigger(1);
ScopeVis::TraceData traceDataI, traceDataQ;
traceDataI.m_projectionType = Projector::ProjectionReal;
traceDataI.m_amp = 1.0; // for -1 to +1
traceDataI.m_ampIndex = 0;
traceDataI.m_ofs = 0.0; // vertical offset
traceDataI.m_ofsCoarse = 0;
traceDataQ.m_projectionType = Projector::ProjectionImag;
traceDataQ.m_amp = 1.0;
traceDataQ.m_ampIndex = 0;
traceDataQ.m_ofs = 0.0;
traceDataQ.m_ofsCoarse = 0;
ui->scopeGUI->changeTrace(0, traceDataI);
ui->scopeGUI->addTrace(traceDataQ);
ui->scopeGUI->setDisplayMode(GLScopeGUI::DisplayXYV);
ui->scopeGUI->focusOnTrace(0); // re-focus to take changes into account in the GUI
ScopeVis::TriggerData triggerData;
triggerData.m_triggerLevel = 0.1;
triggerData.m_triggerLevelCoarse = 10;
triggerData.m_triggerPositiveEdge = true;
ui->scopeGUI->changeTrigger(0, triggerData);
ui->scopeGUI->focusOnTrigger(0); // re-focus to take changes into account in the GUI
m_scopeVis->setLiveRate(9600*6);
//m_scopeVis->setFreeRun(false); // FIXME: add method rather than call m_scopeVis->configure()
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::yellow);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle("AIS Demodulator");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->messages->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->messages->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
messagesMenu = new QMenu(ui->messages);
for (int i = 0; i < ui->messages->horizontalHeader()->count(); i++)
{
QString text = ui->messages->horizontalHeaderItem(i)->text();
messagesMenu->addAction(createCheckableItem(text, i, true, SLOT(messagesColumnSelectMenuChecked())));
}
ui->messages->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->messages->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(messagesColumnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->messages->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(messages_sectionMoved(int, int, int)));
connect(ui->messages->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(messages_sectionResized(int, int, int)));
ui->messages->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->messages, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint)));
ui->scopeContainer->setVisible(false);
displaySettings();
applySettings(true);
}
void AISDemodGUI::customContextMenuRequested(QPoint pos)
{
QTableWidgetItem *item = ui->messages->itemAt(pos);
if (item)
{
QMenu* tableContextMenu = new QMenu(ui->messages);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
QAction* copyAction = new QAction("Copy", tableContextMenu);
const QString text = item->text();
connect(copyAction, &QAction::triggered, this, [text]()->void {
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(text);
});
tableContextMenu->addAction(copyAction);
tableContextMenu->popup(ui->messages->viewport()->mapToGlobal(pos));
}
}
AISDemodGUI::~AISDemodGUI()
{
delete ui;
}
void AISDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void AISDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AISDemod::MsgConfigureAISDemod* message = AISDemod::MsgConfigureAISDemod::create( m_settings, force);
m_aisDemod->getInputMessageQueue()->push(message);
}
}
void AISDemodGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
ui->thresholdText->setText(QString("%1").arg(m_settings.m_correlationThreshold));
ui->threshold->setValue(m_settings.m_correlationThreshold);
displayStreamIndex();
ui->filterMMSI->setText(m_settings.m_filterMMSI);
ui->udpEnabled->setChecked(m_settings.m_udpEnabled);
ui->udpAddress->setText(m_settings.m_udpAddress);
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);
// Order and size columns
QHeaderView *header = ui->messages->horizontalHeader();
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
{
bool hidden = m_settings.m_messageColumnSizes[i] == 0;
header->setSectionHidden(i, hidden);
messagesMenu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_messageColumnSizes[i] > 0)
ui->messages->setColumnWidth(i, m_settings.m_messageColumnSizes[i]);
header->moveSection(header->visualIndex(i), m_settings.m_messageColumnIndexes[i]);
}
filter();
blockApplySettings(false);
}
void AISDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void AISDemodGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void AISDemodGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void AISDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_aisDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
double powDbAvg = CalcDb::dbPower(magsqAvg);
double powDbPeak = CalcDb::dbPower(magsqPeak);
ui->channelPowerMeter->levelChanged(
(100.0f + powDbAvg) / 100.0f,
(100.0f + powDbPeak) / 100.0f,
nbMagsqSamples);
if (m_tickCount % 4 == 0) {
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
}
m_tickCount++;
}

View File

@ -0,0 +1,134 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMODGUI_H
#define INCLUDE_AISDEMODGUI_H
#include <QTableWidgetItem>
#include <QPushButton>
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/messagequeue.h"
#include "aisdemodsettings.h"
#include "aisdemod.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class ScopeVis;
class ScopeVisXY;
class AISDemod;
class AISDemodGUI;
namespace Ui {
class AISDemodGUI;
}
class AISDemodGUI;
class AISMessage;
class AISDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static AISDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
public slots:
void channelMarkerChangedByCursor();
void channelMarkerHighlightedByCursor();
private:
Ui::AISDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
AISDemodSettings m_settings;
bool m_doApplySettings;
ScopeVis* m_scopeVis;
AISDemod* m_aisDemod;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QMenu *messagesMenu; // Column select context menu
QMenu *copyMenu;
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 displayStreamIndex();
void messageReceived(const AISDemod::MsgMessage& message);
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
enum MessageCol {
MESSAGE_COL_DATE,
MESSAGE_COL_TIME,
MESSAGE_COL_MMSI,
MESSAGE_COL_TYPE,
MESSAGE_COL_DATA,
MESSAGE_COL_NMEA,
MESSAGE_COL_HEX
};
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_threshold_valueChanged(int value);
void on_filterMMSI_editingFinished();
void on_clearTable_clicked();
void on_udpEnabled_clicked(bool checked);
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 filterRow(int row);
void filter();
void messages_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void messages_sectionResized(int logicalIndex, int oldSize, int newSize);
void messagesColumnSelectMenu(QPoint pos);
void messagesColumnSelectMenuChecked(bool checked = false);
void customContextMenuRequested(QPoint point);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_AISDEMODGUI_H

View File

@ -0,0 +1,898 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AISDemodGUI</class>
<widget class="RollupWidget" name="AISDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>404</width>
<height>764</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>AIS Demodulator</string>
</property>
<property name="statusTip">
<string>AIS Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>151</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="powerLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="phySettingsLayout">
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rfBW">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>RF bandwidth</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>400</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>100</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>10.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
<string>Dev</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="fmDev">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Frequency deviation</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>24</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>2.4k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="thresholdLabel">
<property name="text">
<string>TH</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="threshold">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Correlation threshold</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>60</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="pageStep">
<number>5</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="thresholdText">
<property name="text">
<string>60</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="udpLayout">
<item>
<widget class="QCheckBox" name="udpEnabled">
<property name="toolTip">
<string>Send packets via UDP</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>UDP</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpAddress">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP address</string>
</property>
<property name="inputMask">
<string>000.000.000.000</string>
</property>
<property name="text">
<string>127.0.0.1</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="udpSeparator">
<property name="text">
<string>:</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udpPort">
<property name="minimumSize">
<size>
<width>50</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="toolTip">
<string>Destination UDP port</string>
</property>
<property name="inputMask">
<string>00000</string>
</property>
<property name="text">
<string>9998</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="udpFormatLabel">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="udpFormat">
<property name="minimumSize">
<size>
<width>60</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Format used to forward received AIS messages</string>
</property>
<item>
<property name="text">
<string>Binary</string>
</property>
</item>
<item>
<property name="text">
<string>NMEA</string>
</property>
</item>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="filterLayout">
<item>
<widget class="QLabel" name="filterMMSILabel">
<property name="text">
<string>Find</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="filterMMSI">
<property name="toolTip">
<string>Display only messages where the MMSI matches the specified regular expression</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="clearTable">
<property name="toolTip">
<string>Clear messages from table</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="messageContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>210</y>
<width>391</width>
<height>171</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<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>
<item>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Received packets</string>
</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>
</property>
<property name="toolTip">
<string>Packet data as ASCII</string>
</property>
</column>
<column>
<property name="text">
<string>NMEA</string>
</property>
</column>
<column>
<property name="text">
<string>Hex</string>
</property>
<property name="toolTip">
<string>Packet data as hex</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="scopeContainer" native="true">
<property name="geometry">
<rect>
<x>20</x>
<y>400</y>
<width>351</width>
<height>341</height>
</rect>
</property>
<property name="windowTitle">
<string>Waveforms</string>
</property>
<layout class="QVBoxLayout" name="transmittedLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<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">
<size>
<width>200</width>
<height>250</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
</widget>
</item>
<item>
<widget class="GLScopeGUI" name="scopeGUI" native="true"/>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScope</class>
<extends>QWidget</extends>
<header>gui/glscope.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>GLScopeGUI</class>
<extends>QWidget</extends>
<header>gui/glscopegui.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "aisdemodgui.h"
#endif
#include "aisdemod.h"
#include "aisdemodwebapiadapter.h"
#include "aisdemodplugin.h"
const PluginDescriptor AISDemodPlugin::m_pluginDescriptor = {
AISDemod::m_channelId,
QStringLiteral("AIS Demodulator"),
QStringLiteral("6.11.1"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
AISDemodPlugin::AISDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& AISDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AISDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(AISDemod::m_channelIdURI, AISDemod::m_channelId, this);
}
void AISDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
AISDemod *instance = new AISDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* AISDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* AISDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return AISDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* AISDemodPlugin::createChannelWebAPIAdapter() const
{
return new AISDemodWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMODPLUGIN_H
#define INCLUDE_AISDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class AISDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.aisdemod")
public:
explicit AISDemodPlugin(QObject* parent = NULL);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_AISDEMODPLUGIN_H

View File

@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "aisdemodsettings.h"
AISDemodSettings::AISDemodSettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void AISDemodSettings::resetToDefaults()
{
m_baud = 9600; // Fixed
m_inputFrequencyOffset = 0;
m_rfBandwidth = 16000.0f;
m_fmDeviation = 4800.0f;
m_correlationThreshold = 30;
m_filterMMSI = "";
m_udpEnabled = false;
m_udpAddress = "127.0.0.1";
m_udpPort = 9999;
m_udpFormat = Binary;
m_scopeCh1 = 5;
m_scopeCh2 = 6;
m_rgbColor = QColor(102, 0, 0).rgb();
m_title = "AIS Demodulator";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
{
m_messageColumnIndexes[i] = i;
m_messageColumnSizes[i] = -1; // Autosize
}
}
QByteArray AISDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeFloat(2, m_rfBandwidth);
s.writeFloat(3, m_fmDeviation);
s.writeFloat(4, m_correlationThreshold);
s.writeString(5, m_filterMMSI);
s.writeBool(6, m_udpEnabled);
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);
if (m_channelMarker) {
s.writeBlob(14, m_channelMarker->serialize());
}
s.writeS32(15, m_streamIndex);
s.writeBool(16, m_useReverseAPI);
s.writeString(17, m_reverseAPIAddress);
s.writeU32(18, m_reverseAPIPort);
s.writeU32(19, m_reverseAPIDeviceIndex);
s.writeU32(20, m_reverseAPIChannelIndex);
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
s.writeS32(100 + i, m_messageColumnIndexes[i]);
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
s.writeS32(200 + i, m_messageColumnSizes[i]);
return s.final();
}
bool AISDemodSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readFloat(2, &m_rfBandwidth, 16000.0f);
d.readFloat(3, &m_fmDeviation, 4800.0f);
d.readFloat(4, &m_correlationThreshold, 30);
d.readString(5, &m_filterMMSI, "");
d.readBool(6, &m_udpEnabled);
d.readString(7, &m_udpAddress);
d.readU32(8, &utmp);
if ((utmp > 1023) && (utmp < 65535)) {
m_udpPort = utmp;
} else {
m_udpPort = 9999;
}
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");
d.readBlob(14, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readS32(15, &m_streamIndex, 0);
d.readBool(16, &m_useReverseAPI, false);
d.readString(17, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(18, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(19, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(20, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
d.readS32(100 + i, &m_messageColumnIndexes[i], i);
for (int i = 0; i < AISDEMOD_MESSAGE_COLUMNS; i++)
d.readS32(200 + i, &m_messageColumnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,70 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMODSETTINGS_H
#define INCLUDE_AISDEMODSETTINGS_H
#include <QByteArray>
#include <QString>
#include "dsp/dsptypes.h"
class Serializable;
// Number of columns in the tables
#define AISDEMOD_MESSAGE_COLUMNS 7
struct AISDemodSettings
{
qint32 m_baud;
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth;
Real m_fmDeviation;
Real m_correlationThreshold;
QString m_filterMMSI;
bool m_udpEnabled;
QString m_udpAddress;
uint16_t m_udpPort;
enum UDPFormat {
Binary,
NMEA
} m_udpFormat;
int m_scopeCh1;
int m_scopeCh2;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_messageColumnIndexes[AISDEMOD_MESSAGE_COLUMNS];//!< How the columns are ordered in the table
int m_messageColumnSizes[AISDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table
AISDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_AISDEMODSETTINGS_H */

View File

@ -0,0 +1,471 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <complex.h>
#include "dsp/dspengine.h"
#include "dsp/datafifo.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "pipes/pipeendpoint.h"
#include "maincore.h"
#include "aisdemod.h"
#include "aisdemodsink.h"
#define AISDEMOD_MINS_MAXS 5
AISDemodSink::AISDemodSink(AISDemod *aisDemod) :
m_scopeSink(nullptr),
m_aisDemod(aisDemod),
m_channelSampleRate(AISDEMOD_CHANNEL_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_rxBuf(nullptr),
m_train(nullptr)
{
m_magsq = 0.0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
AISDemodSink::~AISDemodSink()
{
delete[] m_rxBuf;
delete[] m_train;
}
void AISDemodSink::sampleToScope(Complex sample)
{
if (m_scopeSink)
{
Real r = std::real(sample) * SDR_RX_SCALEF;
Real i = std::imag(sample) * SDR_RX_SCALEF;
SampleVector m_sampleBuffer;
m_sampleBuffer.push_back(Sample(r, i));
m_scopeSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true);
m_sampleBuffer.clear();
}
}
void AISDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void AISDemodSink::processOneSample(Complex &ci)
{
Complex ca;
// FM demodulation
double magsqRaw;
Real deviation;
Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
// Calculate average and peak levels for level meter
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
// Gaussian filter
Real filt = m_pulseShape.filter(fmDemod);
// An input frequency offset corresponds to a DC offset after FM demodulation
// AIS spec allows up to +-1kHz offset
// We need to remove this, otherwise it may effect the sampling
// To calculate what it is, we sum the training sequence, which should be zero
// Clip, as large noise can result in high correlation
// Don't clip to 1.0 - as there may be some DC offset (1k/4.8k max dev=0.2)
Real filtClipped;
filtClipped = std::fmax(-1.4, std::fmin(1.4, filt));
// Buffer filtered samples. We buffer enough samples for a max length message
// before trying to demod, so false triggering can't make us miss anything
m_rxBuf[m_rxBufIdx] = filtClipped;
m_rxBufIdx = (m_rxBufIdx + 1) % m_rxBufLength;
m_rxBufCnt = std::min(m_rxBufCnt + 1, m_rxBufLength);
Real corr = 0.0f;
bool scopeCRCValid = false;
bool scopeCRCInvalid = false;
Real dcOffset = 0.0f;
bool thresholdMet = false;
if (m_rxBufCnt >= m_rxBufLength)
{
Real trainingSum = 0.0f;
// Correlate with training sequence
// Note that DC offset doesn't matter for this
// Calculate sum to estimate DC offset
for (int i = 0; i < m_correlationLength; i++)
{
int j = (m_rxBufIdx + i) % m_rxBufLength;
corr += m_train[i] * m_rxBuf[j];
trainingSum += m_rxBuf[j];
}
// If we meet threshold, try to demod
// Take abs value, to account for both initial phases
thresholdMet = fabs(corr) >= m_settings.m_correlationThreshold;
if (thresholdMet)
{
// Use mean of preamble as DC offset
dcOffset = trainingSum/m_correlationLength;
// Start demod after (most of) preamble
int x = (m_rxBufIdx + m_correlationLength*3/4 + 4) % m_rxBufLength;
// Attempt to demodulate
bool gotSOP = false;
int bits = 0;
int bitCount = 0;
int onesCount = 0;
int byteCount = 0;
int symbolPrev = 0;
for (int sampleIdx = 0; sampleIdx < m_rxBufLength; sampleIdx += m_samplesPerSymbol)
{
// Sum and slice
// Summing 3 samples seems to give a very small improvement vs just using 1
int sampleCnt = 3;
int sampleOffset = -1;
Real sampleSum = 0.0f;
for (int i = 0; i < sampleCnt; i++) {
sampleSum += m_rxBuf[(x + sampleOffset + i) % m_rxBufLength] - dcOffset;
}
int sample = sampleSum >= 0.0f ? 1 : 0;
// Move to next sample
x = (x + m_samplesPerSymbol) % m_rxBufLength;
// HDLC deframing
// NRZI decoding
int bit;
if (sample != symbolPrev) {
bit = 0;
} else {
bit = 1;
}
symbolPrev = sample;
// Store in shift reg
bits |= bit << bitCount;
bitCount++;
if (bit == 1)
{
onesCount++;
// Shouldn't ever get 7 1s in a row
if ((onesCount == 7) && gotSOP)
{
gotSOP = false;
byteCount = 0;
break;
}
}
else if (bit == 0)
{
if (onesCount == 5)
{
// Remove bit-stuffing (5 1s followed by a 0)
bitCount--;
}
else if (onesCount == 6)
{
// Start/end of packet
if (gotSOP && (bitCount == 8) && (bits == 0x7e) && (byteCount > 0))
{
// End of packet
// Check CRC is valid
m_crc.init();
m_crc.calculate(m_bytes, byteCount - 2);
uint16_t calcCrc = m_crc.get();
uint16_t rxCrc = m_bytes[byteCount-2] | (m_bytes[byteCount-1] << 8);
if (calcCrc == rxCrc)
{
scopeCRCValid = true;
QByteArray rxPacket((char *)m_bytes, byteCount - 2); // Don't include CRC
//qDebug() << "RX: " << rxPacket.toHex();
if (getMessageQueueToChannel())
{
AISDemod::MsgMessage *msg = AISDemod::MsgMessage::create(rxPacket);
getMessageQueueToChannel()->push(msg);
}
// Skip over received packet, so we don't try to re-demodulate it
m_rxBufCnt -= sampleIdx;
}
else
{
//qDebug() << QString("CRC mismatch: %1 %2").arg(calcCrc, 4, 16, QLatin1Char('0')).arg(rxCrc, 4, 16, QLatin1Char('0'));
scopeCRCInvalid = true;
}
break;
}
else if (gotSOP)
{
// Repeated start flag without data or misalignment, something not right
break;
}
else
{
// Start of packet
gotSOP = true;
bits = 0;
bitCount = 0;
byteCount = 0;
}
}
onesCount = 0;
}
if (gotSOP)
{
if (bitCount == 8)
{
// Could also check count according to message ID as that varies
if (byteCount >= AISDEMOD_MAX_BYTES)
{
// Too many bytes
break;
}
else
{
// Got a complete byte
m_bytes[byteCount] = bits;
byteCount++;
}
bits = 0;
bitCount = 0;
}
}
// Abort demod if we haven't found start flag within a couple of bytes of presumed preamble
if (!gotSOP && (sampleIdx >= 16 * m_samplesPerSymbol)) {
break;
}
}
}
}
// 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);
// Send demod signal to Demod Analzyer feature
m_demodBuffer[m_demodBufferFill++] = fmDemod * std::numeric_limits<int16_t>::max();
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
if (dataFifos)
{
QList<DataFifo*>::iterator it = dataFifos->begin();
for (; it != dataFifos->end(); ++it) {
(*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
}
}
m_demodBufferFill = 0;
}
}
void AISDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "AISDemodSink::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset;
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
(m_channelSampleRate != channelSampleRate) || force)
{
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolator.create(16, channelSampleRate, m_settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) channelSampleRate / (Real) AISDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_samplesPerSymbol = AISDEMOD_CHANNEL_SAMPLE_RATE / m_settings.m_baud;
qDebug() << "AISDemodSink::applyChannelSettings: m_samplesPerSymbol: " << m_samplesPerSymbol;
}
void AISDemodSink::applySettings(const AISDemodSettings& settings, bool force)
{
qDebug() << "AISDemodSink::applySettings:"
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) AISDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
m_lowpass.create(301, AISDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f);
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
m_phaseDiscri.setFMScaling(AISDEMOD_CHANNEL_SAMPLE_RATE / (2.0f * settings.m_fmDeviation));
}
if ((settings.m_baud != m_settings.m_baud) || force)
{
m_samplesPerSymbol = AISDEMOD_CHANNEL_SAMPLE_RATE / settings.m_baud;
qDebug() << "ISDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol << " baud " << settings.m_baud;
m_pulseShape.create(0.5, 3, m_samplesPerSymbol);
// Recieve buffer, long enough for one max length message
delete[] m_rxBuf;
m_rxBufLength = AISDEMOD_MAX_BYTES*8*m_samplesPerSymbol;
m_rxBuf = new Real[m_rxBufLength];
m_rxBufIdx = 0;
m_rxBufCnt = 0;
// Create 24-bit training sequence for correlation
delete[] m_train;
m_correlationLength = 24*m_samplesPerSymbol;
m_train = new Real[m_correlationLength]();
const int trainNRZ[24] = {1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1};
// Pulse shape filter takes a few symbols before outputting expected shape
for (int j = 0; j < m_samplesPerSymbol; j++)
m_pulseShape.filter(-1.0f);
for (int j = 0; j < m_samplesPerSymbol; j++)
m_pulseShape.filter(1.0f);
for (int i = 0; i < 24; i++)
{
for (int j = 0; j < m_samplesPerSymbol; j++)
{
m_train[i*m_samplesPerSymbol+j] = m_pulseShape.filter(trainNRZ[i] * 2.0f - 1.0f);
}
}
}
m_settings = settings;
}

View File

@ -0,0 +1,142 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMODSINK_H
#define INCLUDE_AISDEMODSINK_H
#include <QVector>
#include "dsp/channelsamplesink.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "dsp/gaussian.h"
#include "dsp/fftfactory.h"
#include "dsp/fftengine.h"
#include "dsp/fftwindow.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "util/messagequeue.h"
#include "util/crc.h"
#include "aisdemodsettings.h"
#include <vector>
#include <iostream>
#include <fstream>
// 6x 9600 baud rate (use even multiple so Gausian filter has odd number of taps)
#define AISDEMOD_CHANNEL_SAMPLE_RATE 57600
#define AISDEMOD_MAX_BYTES (3+1+126+2+1+1)
class ChannelAPI;
class AISDemod;
class AISDemodSink : public ChannelSampleSink {
public:
AISDemodSink(AISDemod *aisDemod);
~AISDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setScopeSink(BasebandSampleSink* scopeSink) { m_scopeSink = scopeSink; }
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const AISDemodSettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
double getMagSq() const { return m_magsq; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
BasebandSampleSink* m_scopeSink; // Scope GUI to display baseband waveform
AISDemod *m_aisDemod;
AISDemodSettings m_settings;
ChannelAPI *m_channel;
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_samplesPerSymbol; // Number of samples per symbol
NCO m_nco;
Interpolator m_interpolator;
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
double m_magsq;
double m_magsqSum;
double m_magsqPeak;
int m_magsqCount;
MagSqLevelsStore m_magSqLevelStore;
MessageQueue *m_messageQueueToChannel;
MovingAverageUtil<Real, double, 16> m_movingAverage;
Lowpass<Complex> m_lowpass; // RF input filter
PhaseDiscriminators m_phaseDiscri; // FM demodulator
Gaussian<Real> m_pulseShape; // Pulse shaping filter
Real *m_rxBuf; // Receive sample buffer, large enough for one max length messsage
int m_rxBufLength; // Size in elements in m_rxBuf
int m_rxBufIdx; // Index in to circular buffer
int m_rxBufCnt; // Number of valid samples in buffer
Real *m_train; // Training sequence to look for
int m_correlationLength;
int m_symbolPrev;
unsigned char m_bytes[AISDEMOD_MAX_BYTES];
crc16x25 m_crc;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
void sampleToScope(Complex sample);
};
#endif // INCLUDE_AISDEMODSINK_H

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "aisdemod.h"
#include "aisdemodwebapiadapter.h"
AISDemodWebAPIAdapter::AISDemodWebAPIAdapter()
{}
AISDemodWebAPIAdapter::~AISDemodWebAPIAdapter()
{}
int AISDemodWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisDemodSettings(new SWGSDRangel::SWGAISDemodSettings());
response.getAisDemodSettings()->init();
AISDemod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int AISDemodWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
AISDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISDEMOD_WEBAPIADAPTER_H
#define INCLUDE_AISDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "aisdemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class AISDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
AISDemodWebAPIAdapter();
virtual ~AISDemodWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
AISDemodSettings m_settings;
};
#endif // INCLUDE_AISDEMOD_WEBAPIADAPTER_H

View File

@ -0,0 +1,85 @@
<h1>AIS demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate AIS (Automatic Identification System) messages. AIS can be used to track ships and other marine vessels at sea, that are equiped with AIS transponders. It is also used by shore-side infrastructure known as base stations, aids-to-navigation such as buoys and some search and rescue aircraft.
AIS is broadcast globally on 25kHz channels at 161.975MHz and 162.025MHz, with other frequencies being used regionally or for special purposes. This demodulator is single channel, so if you wish to decode multiple channels simulatenously, you will need to add one AIS demodulator per frequency. As most AIS messages are on 161.975MHz and 162.025MHz, you can set the center frequency as 162MHz, with a sample rate of 100k+Sa/s, with one AIS demod with an input offset -25kHz and another at +25kHz.
The AIS demodulators can send received messages to the AIS feature, which displays a table combining the latest data for vessels amalgamated from multiple demodulators and display their position on the Map Feature.
AIS uses GMSK/FM modulation at a baud rate of 9,600, with a modulation index of 0.5. The demodulator works at a sample rate of 57,600Sa/s.
Received AIS messages can be NMEA encoded and forwarded via UDP to 3rd party applications.
The AIS specification is ITU-R M.1371-5: https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1371-5-201402-I!!PDF-E.pdf
<h2>Interface</h2>
![AIS Demodulator plugin GUI](../../../doc/img/AISDemod_plugin.png)
<h3>1: Frequency shift from center frequency of reception</h3>
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>3: Level meter in dB</h3>
- top bar (green): average value
- bottom bar (blue green): instantaneous peak value
- tip vertical bar (bright green): peak hold value
<h3>4: BW - RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. While AIS channels are 25kHz wide, more messages seem to be able to be received if this is around 16kHz.
<h3>5: Dev - Frequency deviation</h3>
Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. Typical values are 4.8 kHz, corresponding to a modulation index of 0.5 at 9,600 baud.
<h3>6: TH - Correlation Threshold</h3>
The correlation threshold between the received signal and the preamble (training sequence). A lower value should be able to demodulate weaker signals, but increases processor usage and may result in invalid messages if too low.
<h3>7: Find</h3>
Entering a regular expression in the Find field displays only messages where the source MMSI matches the given regular expression.
<h3>8: Clear Messages from table</h3>
Pressing this button clears all messages from the table.
<h3>9: UDP</h3>
When checked, received messages are forwarded to the specified UDP address (12) and port (13).
<h3>10: UDP address</h3>
IP address of the host to forward received messages to via UDP.
<h3>11: UDP port</h3>
UDP port number to forward received messages to.
<h3>12: UDP format</h3>
The format the messages are forwared via UDP in. This can be either binary (which is useful for SDRangel's PERTester feature) or NMEA (which is useful for 3rd party applications such as OpenCPN).
<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)
* 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/
* Type - The type of AIS message. E.g. Position report, Base station report or Ship static and voyage related data.
* Data - A textual decode of the message displaying the most interesting fields.
* NMEA - The message in NMEA format.
* Hex - The message in hex format.
Right clicking on the table header allows you to select which columns to show. The columns can be reorderd 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

@ -1,5 +1,6 @@
project(mod)
add_subdirectory(modais)
add_subdirectory(modam)
add_subdirectory(modchirpchat)
add_subdirectory(modnfm)

View File

@ -0,0 +1,64 @@
project(modais)
set(modais_SOURCES
aismod.cpp
aismodbaseband.cpp
aismodsource.cpp
aismodplugin.cpp
aismodsettings.cpp
aismodwebapiadapter.cpp
)
set(modais_HEADERS
aismod.h
aismodbaseband.h
aismodsource.h
aismodplugin.h
aismodsettings.h
aismodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(modais_SOURCES
${modais_SOURCES}
aismodgui.cpp
aismodgui.ui
aismodrepeatdialog.cpp
aismodrepeatdialog.ui
aismodtxsettingsdialog.cpp
aismodtxsettingsdialog.ui
)
set(modais_HEADERS
${modais_HEADERS}
aismodgui.h
aismodrepeatdialog.h
aismodtxsettingsdialog.h
)
set(TARGET_NAME modais)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME modaissrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${modais_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
swagger
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,824 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QTime>
#include <QDebug>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkDatagram>
#include <QNetworkReply>
#include <QBuffer>
#include <QUdpSocket>
#include <QThread>
#include "SWGChannelSettings.h"
#include "SWGChannelReport.h"
#include "SWGChannelActions.h"
#include <stdio.h>
#include <complex.h>
#include <algorithm>
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "device/deviceapi.h"
#include "feature/feature.h"
#include "util/db.h"
#include "maincore.h"
#include "aismodbaseband.h"
#include "aismod.h"
MESSAGE_CLASS_DEFINITION(AISMod::MsgConfigureAISMod, Message)
MESSAGE_CLASS_DEFINITION(AISMod::MsgTXAISMod, Message)
MESSAGE_CLASS_DEFINITION(AISMod::MsgTXPacketBytes, Message)
const char* const AISMod::m_channelIdURI = "sdrangel.channel.modais";
const char* const AISMod::m_channelId = "AISMod";
AISMod::AISMod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSource),
m_deviceAPI(deviceAPI),
m_spectrumVis(SDR_TX_SCALEF),
m_settingsMutex(QMutex::Recursive),
m_udpSocket(nullptr)
{
setObjectName(m_channelId);
m_thread = new QThread(this);
m_basebandSource = new AISModBaseband();
m_basebandSource->setSpectrumSampleSink(&m_spectrumVis);
m_basebandSource->setChannel(this);
m_basebandSource->moveToThread(m_thread);
applySettings(m_settings, true);
m_deviceAPI->addChannelSource(this);
m_deviceAPI->addChannelSourceAPI(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
AISMod::~AISMod()
{
closeUDP();
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this);
delete m_basebandSource;
delete m_thread;
}
void AISMod::start()
{
qDebug("AISMod::start");
m_basebandSource->reset();
m_thread->start();
}
void AISMod::stop()
{
qDebug("AISMod::stop");
m_thread->exit();
m_thread->wait();
}
void AISMod::pull(SampleVector::iterator& begin, unsigned int nbSamples)
{
m_basebandSource->pull(begin, nbSamples);
}
bool AISMod::handleMessage(const Message& cmd)
{
if (MsgConfigureAISMod::match(cmd))
{
MsgConfigureAISMod& cfg = (MsgConfigureAISMod&) cmd;
qDebug() << "AISMod::handleMessage: MsgConfigureAISMod";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgTXAISMod::match(cmd))
{
// Forward a copy to baseband
MsgTXAISMod* rep = new MsgTXAISMod((MsgTXAISMod&)cmd);
qDebug() << "AISMod::handleMessage: MsgTXAISMod";
m_basebandSource->getInputMessageQueue()->push(rep);
return true;
}
else if (DSPSignalNotification::match(cmd))
{
// Forward to the source
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
qDebug() << "AISMod::handleMessage: DSPSignalNotification";
m_basebandSource->getInputMessageQueue()->push(rep);
return true;
}
else
{
return false;
}
}
void AISMod::setScopeSink(BasebandSampleSink* scopeSink)
{
m_basebandSource->setScopeSink(scopeSink);
}
void AISMod::applySettings(const AISModSettings& settings, bool force)
{
qDebug() << "AISMod::applySettings:"
<< " m_inputFrequencyOffset: " << settings.m_inputFrequencyOffset
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fmDeviation: " << settings.m_fmDeviation
<< " m_gain: " << settings.m_gain
<< " m_channelMute: " << settings.m_channelMute
<< " m_repeat: " << settings.m_repeat
<< " m_repeatDelay: " << settings.m_repeatDelay
<< " m_repeatCount: " << settings.m_repeatCount
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIAddress: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
}
if ((settings.m_gain != m_settings.m_gain) || force) {
reverseAPIKeys.append("gain");
}
if ((settings.m_channelMute != m_settings.m_channelMute) || force) {
reverseAPIKeys.append("channelMute");
}
if ((settings.m_repeat != m_settings.m_repeat) || force) {
reverseAPIKeys.append("repeat");
}
if ((settings.m_repeatDelay != m_settings.m_repeatDelay) || force) {
reverseAPIKeys.append("repeatDelay");
}
if ((settings.m_repeatCount != m_settings.m_repeatCount) || force) {
reverseAPIKeys.append("repeatCount");
}
if ((settings.m_rampUpBits != m_settings.m_rampUpBits) || force) {
reverseAPIKeys.append("rampUpBits");
}
if ((settings.m_rampDownBits != m_settings.m_rampDownBits) || force) {
reverseAPIKeys.append("rampDownBits");
}
if ((settings.m_rampRange != m_settings.m_rampRange) || force) {
reverseAPIKeys.append("rampRange");
}
if ((settings.m_rfNoise != m_settings.m_rfNoise) || force) {
reverseAPIKeys.append("rfNoise");
}
if ((settings.m_writeToFile != m_settings.m_writeToFile) || force) {
reverseAPIKeys.append("writeToFile");
}
if ((settings.m_msgId != m_settings.m_msgId) || force) {
reverseAPIKeys.append("msgId");
}
if ((settings.m_mmsi != m_settings.m_mmsi) || force) {
reverseAPIKeys.append("mmsi");
}
if ((settings.m_status != m_settings.m_status) || force) {
reverseAPIKeys.append("status");
}
if ((settings.m_latitude != m_settings.m_latitude) || force) {
reverseAPIKeys.append("latitude");
}
if ((settings.m_longitude != m_settings.m_longitude) || force) {
reverseAPIKeys.append("longitude");
}
if ((settings.m_course != m_settings.m_course) || force) {
reverseAPIKeys.append("course");
}
if ((settings.m_speed != m_settings.m_speed) || force) {
reverseAPIKeys.append("speed");
}
if ((settings.m_heading != m_settings.m_heading) || force) {
reverseAPIKeys.append("heading");
}
if ((settings.m_data != m_settings.m_data) || force) {
reverseAPIKeys.append("data");
}
if ((settings.m_bt != m_settings.m_bt) || force) {
reverseAPIKeys.append("bt");
}
if ((settings.m_symbolSpan != m_settings.m_symbolSpan) || force) {
reverseAPIKeys.append("symbolSpan");
}
if ((settings.m_udpEnabled != m_settings.m_udpEnabled) || force) {
reverseAPIKeys.append("udpEnabled");
}
if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) {
reverseAPIKeys.append("udpAddress");
}
if ((settings.m_udpPort != m_settings.m_udpPort) || force) {
reverseAPIKeys.append("udpPort");
}
if ( (settings.m_udpEnabled != m_settings.m_udpEnabled)
|| (settings.m_udpAddress != m_settings.m_udpAddress)
|| (settings.m_udpPort != m_settings.m_udpPort)
|| force)
{
if (settings.m_udpEnabled)
openUDP(settings);
else
closeUDP();
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSourceAPI(this);
m_deviceAPI->removeChannelSource(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSource(this, settings.m_streamIndex);
m_deviceAPI->addChannelSourceAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
AISModBaseband::MsgConfigureAISModBaseband *msg = AISModBaseband::MsgConfigureAISModBaseband::create(settings, force);
m_basebandSource->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(this, "settings");
if (messageQueues) {
sendChannelSettings(messageQueues, reverseAPIKeys, settings, force);
}
m_settings = settings;
}
QByteArray AISMod::serialize() const
{
return m_settings.serialize();
}
bool AISMod::deserialize(const QByteArray& data)
{
bool success = true;
if (!m_settings.deserialize(data))
{
m_settings.resetToDefaults();
success = false;
}
MsgConfigureAISMod *msg = MsgConfigureAISMod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return success;
}
int AISMod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisModSettings(new SWGSDRangel::SWGAISModSettings());
response.getAisModSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int AISMod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
AISModSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureAISMod *msg = MsgConfigureAISMod::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAISMod *msgToGUI = MsgConfigureAISMod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void AISMod::webapiUpdateChannelSettings(
AISModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getAisModSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getAisModSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getAisModSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("gain")) {
settings.m_gain = response.getAisModSettings()->getGain();
}
if (channelSettingsKeys.contains("channelMute")) {
settings.m_channelMute = response.getAisModSettings()->getChannelMute() != 0;
}
if (channelSettingsKeys.contains("repeat")) {
settings.m_repeat = response.getAisModSettings()->getRepeat() != 0;
}
if (channelSettingsKeys.contains("repeatDelay")) {
settings.m_repeatDelay = response.getAisModSettings()->getRepeatDelay();
}
if (channelSettingsKeys.contains("repeatCount")) {
settings.m_repeatCount = response.getAisModSettings()->getRepeatCount();
}
if (channelSettingsKeys.contains("rampUpBits")) {
settings.m_rampUpBits = response.getAisModSettings()->getRampUpBits();
}
if (channelSettingsKeys.contains("rampDownBits")) {
settings.m_rampDownBits = response.getAisModSettings()->getRampDownBits();
}
if (channelSettingsKeys.contains("rampRange")) {
settings.m_rampRange = response.getAisModSettings()->getRampRange();
}
if (channelSettingsKeys.contains("rfNoise")) {
settings.m_rfNoise = response.getAisModSettings()->getRfNoise() != 0;
}
if (channelSettingsKeys.contains("writeToFile")) {
settings.m_writeToFile = response.getAisModSettings()->getWriteToFile() != 0;
}
if (channelSettingsKeys.contains("mmsi")) {
settings.m_mmsi = *response.getAisModSettings()->getMmsi();
}
if (channelSettingsKeys.contains("status")) {
settings.m_status = response.getAisModSettings()->getStatus();
}
if (channelSettingsKeys.contains("latitude")) {
settings.m_latitude = response.getAisModSettings()->getLatitude();
}
if (channelSettingsKeys.contains("longitude")) {
settings.m_longitude = response.getAisModSettings()->getLongitude();
}
if (channelSettingsKeys.contains("course")) {
settings.m_course = response.getAisModSettings()->getCourse();
}
if (channelSettingsKeys.contains("speed")) {
settings.m_speed = response.getAisModSettings()->getSpeed();
}
if (channelSettingsKeys.contains("heading")) {
settings.m_heading = response.getAisModSettings()->getHeading();
}
if (channelSettingsKeys.contains("data")) {
settings.m_data = *response.getAisModSettings()->getData();
}
if (channelSettingsKeys.contains("bt")) {
settings.m_bt = response.getAisModSettings()->getBt();
}
if (channelSettingsKeys.contains("symbolSpan")) {
settings.m_symbolSpan = response.getAisModSettings()->getSymbolSpan();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAisModSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getAisModSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getAisModSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAisModSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAisModSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAisModSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getAisModSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getAisModSettings()->getReverseApiChannelIndex();
}
if (channelSettingsKeys.contains("udpEnabled")) {
settings.m_udpEnabled = response.getAisModSettings()->getUdpEnabled();
}
if (channelSettingsKeys.contains("udpAddress")) {
settings.m_udpAddress = *response.getAisModSettings()->getUdpAddress();
}
if (channelSettingsKeys.contains("udpPort")) {
settings.m_udpPort = response.getAisModSettings()->getUdpPort();
}
}
int AISMod::webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisModReport(new SWGSDRangel::SWGAISModReport());
response.getAisModReport()->init();
webapiFormatChannelReport(response);
return 200;
}
int AISMod::webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage)
{
SWGSDRangel::SWGAISModActions *swgAISModActions = query.getAisModActions();
if (swgAISModActions)
{
if (channelActionsKeys.contains("tx"))
{
SWGSDRangel::SWGAISModActions_tx* tx = swgAISModActions->getTx();
QString *dataP = tx->getData();
if (dataP)
{
QString data(*dataP);
AISMod::MsgTXAISMod *msg = AISMod::MsgTXAISMod::create(data);
m_basebandSource->getInputMessageQueue()->push(msg);
return 202;
}
else
{
errorMessage = "Message must contain data";
return 400;
}
}
else
{
errorMessage = "Unknown action";
return 400;
}
}
else
{
errorMessage = "Missing AISModActions in query";
return 400;
}
return 400;
}
void AISMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const AISModSettings& settings)
{
response.getAisModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getAisModSettings()->setFmDeviation(settings.m_fmDeviation);
response.getAisModSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getAisModSettings()->setGain(settings.m_gain);
response.getAisModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0);
response.getAisModSettings()->setRepeat(settings.m_repeat ? 1 : 0);
response.getAisModSettings()->setRepeatDelay(settings.m_repeatDelay);
response.getAisModSettings()->setRepeatCount(settings.m_repeatCount);
response.getAisModSettings()->setRampUpBits(settings.m_rampUpBits);
response.getAisModSettings()->setRampDownBits(settings.m_rampDownBits);
response.getAisModSettings()->setRampRange(settings.m_rampRange);
response.getAisModSettings()->setRfNoise(settings.m_rfNoise ? 1 : 0);
response.getAisModSettings()->setWriteToFile(settings.m_writeToFile ? 1 : 0);
if (response.getAisModSettings()->getMmsi()) {
*response.getAisModSettings()->getMmsi() = settings.m_mmsi;
} else {
response.getAisModSettings()->setMmsi(new QString(settings.m_mmsi));
}
response.getAisModSettings()->setStatus(settings.m_status);
response.getAisModSettings()->setLatitude(settings.m_latitude);
response.getAisModSettings()->setLongitude(settings.m_longitude);
response.getAisModSettings()->setCourse(settings.m_course);
response.getAisModSettings()->setSpeed(settings.m_speed);
response.getAisModSettings()->setHeading(settings.m_heading);
if (response.getAisModSettings()->getData()) {
*response.getAisModSettings()->getData() = settings.m_data;
} else {
response.getAisModSettings()->setData(new QString(settings.m_data));
}
response.getAisModSettings()->setBt(settings.m_bt);
response.getAisModSettings()->setSymbolSpan(settings.m_symbolSpan);
response.getAisModSettings()->setRgbColor(settings.m_rgbColor);
if (response.getAisModSettings()->getTitle()) {
*response.getAisModSettings()->getTitle() = settings.m_title;
} else {
response.getAisModSettings()->setTitle(new QString(settings.m_title));
}
response.getAisModSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAisModSettings()->getReverseApiAddress()) {
*response.getAisModSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAisModSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAisModSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getAisModSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getAisModSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
response.getAisModSettings()->setUdpEnabled(settings.m_udpEnabled);
response.getAisModSettings()->setUdpAddress(new QString(settings.m_udpAddress));
response.getAisModSettings()->setUdpPort(settings.m_udpPort);
}
void AISMod::webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response)
{
response.getAisModReport()->setChannelPowerDb(CalcDb::dbPower(getMagSq()));
response.getAisModReport()->setChannelSampleRate(m_basebandSource->getChannelSampleRate());
}
void AISMod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AISModSettings& settings, bool force)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIDeviceIndex)
.arg(settings.m_reverseAPIChannelIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgChannelSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgChannelSettings;
}
void AISMod::sendChannelSettings(
QList<MessageQueue*> *messageQueues,
QList<QString>& channelSettingsKeys,
const AISModSettings& settings,
bool force)
{
QList<MessageQueue*>::iterator it = messageQueues->begin();
for (; it != messageQueues->end(); ++it)
{
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
MainCore::MsgChannelSettings *msg = MainCore::MsgChannelSettings::create(
this,
channelSettingsKeys,
swgChannelSettings,
force
);
(*it)->push(msg);
}
}
void AISMod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const AISModSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(1); // single source (Tx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString(m_channelId));
swgChannelSettings->setAisModSettings(new SWGSDRangel::SWGAISModSettings());
SWGSDRangel::SWGAISModSettings *swgAISModSettings = swgChannelSettings->getAisModSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgAISModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgAISModSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgAISModSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("gain") || force) {
swgAISModSettings->setGain(settings.m_gain);
}
if (channelSettingsKeys.contains("channelMute") || force) {
swgAISModSettings->setChannelMute(settings.m_channelMute ? 1 : 0);
}
if (channelSettingsKeys.contains("repeat") || force) {
swgAISModSettings->setRepeat(settings.m_repeat ? 1 : 0);
}
if (channelSettingsKeys.contains("repeatDelay") || force) {
swgAISModSettings->setRepeatDelay(settings.m_repeatDelay);
}
if (channelSettingsKeys.contains("repeatCount") || force) {
swgAISModSettings->setRepeatCount(settings.m_repeatCount);
}
if (channelSettingsKeys.contains("rampUpBits")) {
swgAISModSettings->setRampUpBits(settings.m_rampUpBits);
}
if (channelSettingsKeys.contains("rampDownBits")) {
swgAISModSettings->setRampDownBits(settings.m_rampDownBits);
}
if (channelSettingsKeys.contains("rampRange")) {
swgAISModSettings->setRampRange(settings.m_rampRange);
}
if (channelSettingsKeys.contains("rfNoise")) {
swgAISModSettings->setRfNoise(settings.m_rfNoise ? 1 : 0);
}
if (channelSettingsKeys.contains("writeToFile")) {
swgAISModSettings->setWriteToFile(settings.m_writeToFile ? 1 : 0);
}
if (channelSettingsKeys.contains("mmsi")) {
swgAISModSettings->setMmsi(new QString(settings.m_mmsi));
}
if (channelSettingsKeys.contains("status")) {
swgAISModSettings->setStatus(settings.m_status);
}
if (channelSettingsKeys.contains("latitude")) {
swgAISModSettings->setLatitude(settings.m_latitude);
}
if (channelSettingsKeys.contains("longitude")) {
swgAISModSettings->setLongitude(settings.m_longitude);
}
if (channelSettingsKeys.contains("course")) {
swgAISModSettings->setCourse(settings.m_course);
}
if (channelSettingsKeys.contains("speed")) {
swgAISModSettings->setSpeed(settings.m_speed);
}
if (channelSettingsKeys.contains("heading")) {
swgAISModSettings->setHeading(settings.m_heading);
}
if (channelSettingsKeys.contains("data")) {
swgAISModSettings->setData(new QString(settings.m_data));
}
if (channelSettingsKeys.contains("bt")) {
swgAISModSettings->setBt(settings.m_bt);
}
if (channelSettingsKeys.contains("symbolSpan")) {
swgAISModSettings->setSymbolSpan(settings.m_symbolSpan);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAISModSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgAISModSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgAISModSettings->setStreamIndex(settings.m_streamIndex);
}
if (channelSettingsKeys.contains("udpEnabled") || force) {
swgAISModSettings->setUdpEnabled(settings.m_udpEnabled);
}
if (channelSettingsKeys.contains("udpAddress") || force) {
swgAISModSettings->setUdpAddress(new QString(settings.m_udpAddress));
}
if (channelSettingsKeys.contains("udpPort") || force) {
swgAISModSettings->setUdpPort(settings.m_udpPort);
}
}
void AISMod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AISMod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AISMod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
double AISMod::getMagSq() const
{
return m_basebandSource->getMagSq();
}
void AISMod::setLevelMeter(QObject *levelMeter)
{
connect(m_basebandSource, SIGNAL(levelChanged(qreal, qreal, int)), levelMeter, SLOT(levelChanged(qreal, qreal, int)));
}
uint32_t AISMod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSinkStreams();
}
void AISMod::openUDP(const AISModSettings& settings)
{
closeUDP();
m_udpSocket = new QUdpSocket();
if (!m_udpSocket->bind(QHostAddress(settings.m_udpAddress), settings.m_udpPort))
qCritical() << "AISMod::openUDP: Failed to bind to port " << settings.m_udpAddress << ":" << settings.m_udpPort << ". Error: " << m_udpSocket->error();
else
qDebug() << "AISMod::openUDP: Listening for messages on " << settings.m_udpAddress << ":" << settings.m_udpPort;
connect(m_udpSocket, &QUdpSocket::readyRead, this, &AISMod::udpRx);
}
void AISMod::closeUDP()
{
if (m_udpSocket != nullptr)
{
disconnect(m_udpSocket, &QUdpSocket::readyRead, this, &AISMod::udpRx);
delete m_udpSocket;
m_udpSocket = nullptr;
}
}
void AISMod::udpRx()
{
while (m_udpSocket->hasPendingDatagrams())
{
QNetworkDatagram datagram = m_udpSocket->receiveDatagram();
MsgTXPacketBytes *msg = MsgTXPacketBytes::create(datagram.data());
m_basebandSource->getInputMessageQueue()->push(msg);
}
}

View File

@ -0,0 +1,209 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016-2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODAIS_AISMOD_H_
#define PLUGINS_CHANNELTX_MODAIS_AISMOD_H_
#include <vector>
#include <QMutex>
#include <QNetworkRequest>
#include "dsp/basebandsamplesource.h"
#include "dsp/spectrumvis.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "aismodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class QUdpSocket;
class DeviceAPI;
class AISModBaseband;
class AISMod : public BasebandSampleSource, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureAISMod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AISModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAISMod* create(const AISModSettings& settings, bool force)
{
return new MsgConfigureAISMod(settings, force);
}
private:
AISModSettings m_settings;
bool m_force;
MsgConfigureAISMod(const AISModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgTXAISMod : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgTXAISMod* create(QString data)
{
return new MsgTXAISMod(data);
}
QString m_data;
private:
MsgTXAISMod(QString data) :
Message(),
m_data(data)
{ }
};
class MsgTXPacketBytes : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgTXPacketBytes* create(QByteArray data)
{
return new MsgTXPacketBytes(data);
}
QByteArray m_data;
private:
MsgTXPacketBytes(QByteArray data) :
Message(),
m_data(data)
{ }
};
//=================================================================
AISMod(DeviceAPI *deviceAPI);
virtual ~AISMod();
virtual void destroy() { delete this; }
virtual void start();
virtual void stop();
virtual void pull(SampleVector::iterator& begin, unsigned int nbSamples);
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return m_settings.m_inputFrequencyOffset; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return m_settings.m_inputFrequencyOffset;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiReportGet(
SWGSDRangel::SWGChannelReport& response,
QString& errorMessage);
virtual int webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const AISModSettings& settings);
static void webapiUpdateChannelSettings(
AISModSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
SpectrumVis *getSpectrumVis() { return &m_spectrumVis; }
void setScopeSink(BasebandSampleSink* scopeSink);
double getMagSq() const;
void setLevelMeter(QObject *levelMeter);
uint32_t getNumberOfDeviceStreams() const;
static const char* const m_channelIdURI;
static const char* const m_channelId;
private:
DeviceAPI* m_deviceAPI;
QThread *m_thread;
AISModBaseband* m_basebandSource;
AISModSettings m_settings;
SpectrumVis m_spectrumVis;
QMutex m_settingsMutex;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QUdpSocket *m_udpSocket;
void applySettings(const AISModSettings& settings, bool force = false);
void webapiFormatChannelReport(SWGSDRangel::SWGChannelReport& response);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const AISModSettings& settings, bool force);
void sendChannelSettings(
QList<MessageQueue*> *messageQueues,
QList<QString>& channelSettingsKeys,
const AISModSettings& settings,
bool force
);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const AISModSettings& settings,
bool force
);
void openUDP(const AISModSettings& settings);
void closeUDP();
private slots:
void networkManagerFinished(QNetworkReply *reply);
void udpRx();
};
#endif /* PLUGINS_CHANNELTX_MODAIS_AISMOD_H_ */

View File

@ -0,0 +1,201 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/upchannelizer.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "aismodbaseband.h"
#include "aismod.h"
MESSAGE_CLASS_DEFINITION(AISModBaseband::MsgConfigureAISModBaseband, Message)
AISModBaseband::AISModBaseband() :
m_mutex(QMutex::Recursive)
{
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(48000));
m_channelizer = new UpChannelizer(&m_source);
qDebug("AISModBaseband::AISModBaseband");
QObject::connect(
&m_sampleFifo,
&SampleSourceFifo::dataRead,
this,
&AISModBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
}
AISModBaseband::~AISModBaseband()
{
delete m_channelizer;
}
void AISModBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_sampleFifo.reset();
}
void AISModBaseband::setChannel(ChannelAPI *channel)
{
m_source.setChannel(channel);
}
void AISModBaseband::pull(const SampleVector::iterator& begin, unsigned int nbSamples)
{
unsigned int part1Begin, part1End, part2Begin, part2End;
m_sampleFifo.read(nbSamples, part1Begin, part1End, part2Begin, part2End);
SampleVector& data = m_sampleFifo.getData();
if (part1Begin != part1End)
{
std::copy(
data.begin() + part1Begin,
data.begin() + part1End,
begin
);
}
unsigned int shift = part1End - part1Begin;
if (part2Begin != part2End)
{
std::copy(
data.begin() + part2Begin,
data.begin() + part2End,
begin + shift
);
}
}
void AISModBaseband::handleData()
{
QMutexLocker mutexLocker(&m_mutex);
SampleVector& data = m_sampleFifo.getData();
unsigned int ipart1begin;
unsigned int ipart1end;
unsigned int ipart2begin;
unsigned int ipart2end;
qreal rmsLevel, peakLevel;
int numSamples;
unsigned int remainder = m_sampleFifo.remainder();
while ((remainder > 0) && (m_inputMessageQueue.size() == 0))
{
m_sampleFifo.write(remainder, ipart1begin, ipart1end, ipart2begin, ipart2end);
if (ipart1begin != ipart1end) { // first part of FIFO data
processFifo(data, ipart1begin, ipart1end);
}
if (ipart2begin != ipart2end) { // second part of FIFO data (used when block wraps around)
processFifo(data, ipart2begin, ipart2end);
}
remainder = m_sampleFifo.remainder();
}
m_source.getLevels(rmsLevel, peakLevel, numSamples);
emit levelChanged(rmsLevel, peakLevel, numSamples);
}
void AISModBaseband::processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd)
{
m_channelizer->prefetch(iEnd - iBegin);
m_channelizer->pull(data.begin() + iBegin, iEnd - iBegin);
}
void AISModBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool AISModBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureAISModBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureAISModBaseband& cfg = (MsgConfigureAISModBaseband&) cmd;
qDebug() << "AISModBaseband::handleMessage: MsgConfigureAISModBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (AISMod::MsgTXAISMod::match(cmd))
{
AISMod::MsgTXAISMod& tx = (AISMod::MsgTXAISMod&) cmd;
m_source.addTXPacket(tx.m_data);
return true;
}
else if (AISMod::MsgTXPacketBytes::match(cmd))
{
AISMod::MsgTXPacketBytes& tx = (AISMod::MsgTXPacketBytes&) cmd;
m_source.addTXPacket(tx.m_data);
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "AISModBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
m_sampleFifo.resize(SampleSourceFifo::getSizePolicy(notif.getSampleRate()));
m_channelizer->setBasebandSampleRate(notif.getSampleRate());
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
return true;
}
else
{
qDebug() << "AISModBaseband - Baseband got unknown message";
return false;
}
}
void AISModBaseband::applySettings(const AISModSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(m_channelizer->getChannelSampleRate(), settings.m_inputFrequencyOffset);
m_source.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_source.applySettings(settings, force);
m_settings = settings;
}
int AISModBaseband::getChannelSampleRate() const
{
return m_channelizer->getChannelSampleRate();
}

View File

@ -0,0 +1,99 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMODBASEBAND_H
#define INCLUDE_AISMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesourcefifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "aismodsource.h"
class UpChannelizer;
class ChannelAPI;
class AISModBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureAISModBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AISModSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAISModBaseband* create(const AISModSettings& settings, bool force)
{
return new MsgConfigureAISModBaseband(settings, force);
}
private:
AISModSettings m_settings;
bool m_force;
MsgConfigureAISModBaseband(const AISModSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
AISModBaseband();
~AISModBaseband();
void reset();
void pull(const SampleVector::iterator& begin, unsigned int nbSamples);
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
double getMagSq() const { return m_source.getMagSq(); }
int getChannelSampleRate() const;
void setSpectrumSampleSink(BasebandSampleSink* sampleSink) { m_source.setSpectrumSink(sampleSink); }
void setScopeSink(BasebandSampleSink* scopeSink) { m_source.setScopeSink(scopeSink); }
void setChannel(ChannelAPI *channel);
signals:
/**
* Level changed
* \param rmsLevel RMS level in range 0.0 - 1.0
* \param peakLevel Peak level in range 0.0 - 1.0
* \param numSamples Number of audio samples analyzed
*/
void levelChanged(qreal rmsLevel, qreal peakLevel, int numSamples);
private:
SampleSourceFifo m_sampleFifo;
UpChannelizer *m_channelizer;
AISModSource m_source;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
AISModSettings m_settings;
QMutex m_mutex;
void processFifo(SampleVector& data, unsigned int iBegin, unsigned int iEnd);
bool handleMessage(const Message& cmd);
void applySettings(const AISModSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_AISMODBASEBAND_H

View File

@ -0,0 +1,686 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDockWidget>
#include <QMainWindow>
#include <QDateTime>
#include <QTime>
#include <QDebug>
#include "dsp/spectrumvis.h"
#include "dsp/scopevis.h"
#include "device/deviceuiset.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "dsp/dspengine.h"
#include "gui/glspectrum.h"
#include "gui/crightclickenabler.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "maincore.h"
#include "ui_aismodgui.h"
#include "aismodgui.h"
#include "aismodrepeatdialog.h"
#include "aismodtxsettingsdialog.h"
#include "aismodsource.h"
AISModGUI* AISModGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx)
{
AISModGUI* gui = new AISModGUI(pluginAPI, deviceUISet, channelTx);
return gui;
}
void AISModGUI::destroy()
{
delete this;
}
void AISModGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray AISModGUI::serialize() const
{
return m_settings.serialize();
}
bool AISModGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
bool AISModGUI::handleMessage(const Message& message)
{
if (AISMod::MsgConfigureAISMod::match(message))
{
const AISMod::MsgConfigureAISMod& cfg = (AISMod::MsgConfigureAISMod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else
{
return false;
}
}
void AISModGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void AISModGUI::handleSourceMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void AISModGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void AISModGUI::on_mode_currentIndexChanged(int value)
{
QString mode = ui->mode->currentText();
// If m_doApplySettings is set, we are here from a call to displaySettings,
// so we only want to display the current settings, not update them
// as though a user had selected a new mode
if (m_doApplySettings)
m_settings.setMode(mode);
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
ui->btText->setText(QString("%1").arg(m_settings.m_bt, 0, 'f', 1));
ui->bt->setValue(m_settings.m_bt * 10);
applySettings();
// Remove custom mode when deselected, as we no longer know how to set it
if (value < 2)
ui->mode->removeItem(2);
}
void AISModGUI::on_rfBW_valueChanged(int value)
{
float bw = value * 100.0f;
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_channelMarker.setBandwidth(bw);
m_settings.m_rfBandwidth = bw;
applySettings();
}
void AISModGUI::on_fmDev_valueChanged(int value)
{
ui->fmDevText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
m_settings.m_fmDeviation = value * 100.0;
applySettings();
}
void AISModGUI::on_bt_valueChanged(int value)
{
ui->btText->setText(QString("%1").arg(value / 10.0, 0, 'f', 1));
m_settings.m_bt = value / 10.0;
applySettings();
}
void AISModGUI::on_gain_valueChanged(int value)
{
ui->gainText->setText(QString("%1dB").arg(value));
m_settings.m_gain = value;
applySettings();
}
void AISModGUI::on_channelMute_toggled(bool checked)
{
m_settings.m_channelMute = checked;
applySettings();
}
void AISModGUI::on_insertPosition_clicked()
{
float latitude = MainCore::instance()->getSettings().getLatitude();
float longitude = MainCore::instance()->getSettings().getLongitude();
ui->latitude->setValue(latitude);
ui->longitude->setValue(longitude);
}
void AISModGUI::on_txButton_clicked()
{
transmit();
}
void AISModGUI::on_message_returnPressed()
{
transmit();
}
void AISModGUI::on_msgId_currentIndexChanged(int index)
{
m_settings.m_msgId = index + 1;
applySettings();
}
void AISModGUI::on_mmsi_editingFinished()
{
m_settings.m_mmsi = ui->mmsi->text();
applySettings();
}
void AISModGUI::on_status_currentIndexChanged(int index)
{
m_settings.m_status = index;
applySettings();
}
void AISModGUI::on_latitude_valueChanged(double value)
{
m_settings.m_latitude = (float)value;
applySettings();
}
void AISModGUI::on_longitude_valueChanged(double value)
{
m_settings.m_longitude = (float)value;
applySettings();
}
void AISModGUI::on_course_valueChanged(double value)
{
m_settings.m_course = (float)value;
applySettings();
}
void AISModGUI::on_speed_valueChanged(double value)
{
m_settings.m_speed = (float)value;
applySettings();
}
void AISModGUI::on_heading_valueChanged(int value)
{
m_settings.m_heading = value;
applySettings();
}
void AISModGUI::on_message_editingFinished()
{
m_settings.m_data = ui->message->text();
applySettings();
}
// Convert decimal degrees to 1/10000 minutes
static int degToMinFracs(float decimal)
{
return std::round(decimal * 60.0f * 10000.0f);
}
// Encode the message specified by the GUI controls in to a hex string and put in message field
void AISModGUI::on_encode_clicked()
{
unsigned char bytes[168/8];
int mmsi;
int latitude;
int longitude;
mmsi = m_settings.m_mmsi.toInt();
latitude = degToMinFracs(m_settings.m_latitude);
longitude = degToMinFracs(m_settings.m_longitude);
if (m_settings.m_msgId == 4)
{
// Base station report
QDateTime currentDateTime = QDateTime::currentDateTimeUtc();
QDate currentDate = currentDateTime.date();
QTime currentTime = currentDateTime.time();
int year = currentDate.year();
int month = currentDate.month();
int day = currentDate.day();
int hour = currentTime.hour();
int minute = currentTime.minute();
int second = currentTime.second();
bytes[0] = (m_settings.m_msgId << 2); // Repeat indicator = 0
bytes[1] = (mmsi >> 22) & 0xff;
bytes[2] = (mmsi >> 14) & 0xff;
bytes[3] = (mmsi >> 6) & 0xff;
bytes[4] = ((mmsi & 0x3f) << 2) | ((year >> 12) & 0x3);
bytes[5] = (year >> 4) & 0xff;
bytes[6] = ((year & 0xf) << 4) | month;
bytes[7] = (day << 3) | ((hour >> 2) & 0x7);
bytes[8] = ((hour & 0x3) << 6) | minute;
bytes[9] = (second << 2) | (0 << 1) | ((longitude >> 27) & 1);
bytes[10] = (longitude >> 19) & 0xff;
bytes[11] = (longitude >> 11) & 0xff;
bytes[12] = (longitude >> 3) & 0xff;
bytes[13] = ((longitude & 0x7) << 5) | ((latitude >> 22) & 0x1f);
bytes[14] = (latitude >> 14) & 0xff;
bytes[15] = (latitude >> 6) & 0xff;
bytes[16] = ((latitude & 0x3f) << 2);
bytes[17] = 0;
bytes[18] = 0;
bytes[19] = 0;
bytes[20] = 0;
}
else
{
// Position report
int status;
int rateOfTurn = 0x80; // Not available as not currently in GUI
int speedOverGround;
int courseOverGround;
int timestamp;
timestamp = QDateTime::currentDateTimeUtc().time().second();
if (m_settings.m_speed >= 102.2)
speedOverGround = 1022;
else
speedOverGround = std::round(m_settings.m_speed * 10.0);
courseOverGround = std::floor(m_settings.m_course * 10.0);
if (m_settings.m_status == 9) // Not defined (last in combo box)
status = 15;
else
status = m_settings.m_status;
bytes[0] = (m_settings.m_msgId << 2); // Repeat indicator = 0
bytes[1] = (mmsi >> 22) & 0xff;
bytes[2] = (mmsi >> 14) & 0xff;
bytes[3] = (mmsi >> 6) & 0xff;
bytes[4] = ((mmsi & 0x3f) << 2) | (status >> 2);
bytes[5] = ((status & 0x3) << 6) | ((rateOfTurn >> 2) & 0x3f);
bytes[6] = ((rateOfTurn & 0x3) << 6) | ((speedOverGround >> 4) & 0x3f);
bytes[7] = ((speedOverGround & 0xf) << 4) | (0 << 3) | ((longitude >> 25) & 0x7); // Position accuracy = 0
bytes[8] = (longitude >> 17) & 0xff;
bytes[9] = (longitude >> 9) & 0xff;
bytes[10] = (longitude >> 1) & 0xff;
bytes[11] = ((longitude & 0x1) << 7) | ((latitude >> 20) & 0x7f);
bytes[12] = (latitude >> 12) & 0xff;
bytes[13] = (latitude >> 4) & 0xff;
bytes[14] = ((latitude & 0xf) << 4) | ((courseOverGround >> 8) & 0xf);
bytes[15] = courseOverGround & 0xff;
bytes[16] = ((m_settings.m_heading >> 1) & 0xff);
bytes[17] = ((m_settings.m_heading & 0x1) << 7) | ((timestamp & 0x3f) << 1);
bytes[18] = 0;
bytes[19] = 0;
bytes[20] = 0;
}
QByteArray ba((const char *)bytes, sizeof(bytes));
ui->message->setText(ba.toHex());
m_settings.m_data = ui->message->text();
applySettings();
}
void AISModGUI::on_repeat_toggled(bool checked)
{
m_settings.m_repeat = checked;
applySettings();
}
void AISModGUI::repeatSelect()
{
AISModRepeatDialog dialog(m_settings.m_repeatDelay, m_settings.m_repeatCount);
if (dialog.exec() == QDialog::Accepted)
{
m_settings.m_repeatDelay = dialog.m_repeatDelay;
m_settings.m_repeatCount = dialog.m_repeatCount;
applySettings();
}
}
void AISModGUI::txSettingsSelect()
{
AISModTXSettingsDialog dialog(m_settings.m_rampUpBits, m_settings.m_rampDownBits,
m_settings.m_rampRange,
m_settings.m_baud,
m_settings.m_symbolSpan,
m_settings.m_rfNoise,
m_settings.m_writeToFile);
if (dialog.exec() == QDialog::Accepted)
{
m_settings.m_rampUpBits = dialog.m_rampUpBits;
m_settings.m_rampDownBits = dialog.m_rampDownBits;
m_settings.m_rampRange = dialog.m_rampRange;
m_settings.m_baud = dialog.m_baud;
m_settings.m_symbolSpan = dialog.m_symbolSpan;
m_settings.m_rfNoise = dialog.m_rfNoise;
m_settings.m_writeToFile = dialog.m_writeToFile;
displaySettings();
applySettings();
}
}
void AISModGUI::on_udpEnabled_clicked(bool checked)
{
m_settings.m_udpEnabled = checked;
applySettings();
}
void AISModGUI::on_udpAddress_editingFinished()
{
m_settings.m_udpAddress = ui->udpAddress->text();
applySettings();
}
void AISModGUI::on_udpPort_editingFinished()
{
m_settings.m_udpPort = ui->udpPort->text().toInt();
applySettings();
}
void AISModGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void AISModGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
dialog.move(p);
dialog.exec();
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
m_settings.m_title = m_channelMarker.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine))
{
DeviceStreamSelectionDialog dialog(this);
dialog.setNumberOfStreams(m_aisMod->getNumberOfDeviceStreams());
dialog.setStreamIndex(m_settings.m_streamIndex);
dialog.move(p);
dialog.exec();
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
m_channelMarker.clearStreamIndexes();
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
displayStreamIndex();
applySettings();
}
resetContextMenuType();
}
AISModGUI::AISModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::AISModGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_doApplySettings(true)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_aisMod = (AISMod*) channelTx;
m_aisMod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick()));
m_scopeVis = new ScopeVis(ui->glScope);
m_aisMod->setScopeSink(m_scopeVis);
ui->glScope->connectTimer(MainCore::instance()->getMasterTimer());
ui->scopeGUI->setBuddies(m_scopeVis->getInputMessageQueue(), m_scopeVis, ui->glScope);
// Scope settings to display the IQ waveforms
ui->scopeGUI->setPreTrigger(1);
ScopeVis::TraceData traceDataI, traceDataQ;
traceDataI.m_projectionType = Projector::ProjectionReal;
traceDataI.m_amp = 1.0; // for -1 to +1
traceDataI.m_ampIndex = 0;
traceDataI.m_ofs = 0.0; // vertical offset
traceDataI.m_ofsCoarse = 0;
traceDataQ.m_projectionType = Projector::ProjectionImag;
traceDataQ.m_amp = 1.0;
traceDataQ.m_ampIndex = 0;
traceDataQ.m_ofs = 0.0;
traceDataQ.m_ofsCoarse = 0;
ui->scopeGUI->changeTrace(0, traceDataI);
ui->scopeGUI->addTrace(traceDataQ);
ui->scopeGUI->setDisplayMode(GLScopeGUI::DisplayPol);
ui->scopeGUI->focusOnTrace(0); // re-focus to take changes into account in the GUI
ScopeVis::TriggerData triggerData;
triggerData.m_triggerLevel = 0.1;
triggerData.m_triggerLevelCoarse = 10;
triggerData.m_triggerPositiveEdge = true;
ui->scopeGUI->changeTrigger(0, triggerData);
ui->scopeGUI->focusOnTrigger(0); // re-focus to take changes into account in the GUI
m_scopeVis->setLiveRate(AISMOD_SAMPLE_RATE);
//m_scopeVis->setFreeRun(false); // FIXME: add method rather than call m_scopeVis->configure()
m_spectrumVis = m_aisMod->getSpectrumVis();
m_spectrumVis->setGLSpectrum(ui->glSpectrum);
// Extra /2 here because SSB?
ui->glSpectrum->setCenterFrequency(0);
ui->glSpectrum->setSampleRate(AISMOD_SAMPLE_RATE);
ui->glSpectrum->setSsbSpectrum(true);
ui->glSpectrum->setDisplayCurrent(true);
ui->glSpectrum->setLsbDisplay(false);
ui->glSpectrum->setDisplayWaterfall(false);
ui->glSpectrum->setDisplayMaxHold(false);
ui->glSpectrum->setDisplayHistogram(false);
CRightClickEnabler *repeatRightClickEnabler = new CRightClickEnabler(ui->repeat);
connect(repeatRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(repeatSelect()));
CRightClickEnabler *txRightClickEnabler = new CRightClickEnabler(ui->txButton);
connect(txRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(txSettingsSelect()));
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
m_channelMarker.blockSignals(true);
m_channelMarker.setColor(Qt::red);
m_channelMarker.setBandwidth(12500);
m_channelMarker.setCenterFrequency(0);
m_channelMarker.setTitle("AIS Modulator");
m_channelMarker.setSourceOrSinkStream(false);
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleSourceMessages()));
m_aisMod->setLevelMeter(ui->volumeMeter);
m_settings.setChannelMarker(&m_channelMarker);
ui->spectrumGUI->setBuddies(m_spectrumVis, ui->glSpectrum);
ui->scopeContainer->setVisible(false);
ui->spectrumContainer->setVisible(false);
displaySettings();
applySettings();
}
AISModGUI::~AISModGUI()
{
delete ui;
}
void AISModGUI::transmit()
{
QString data = ui->message->text();
ui->transmittedText->appendPlainText(data + "\n");
AISMod::MsgTXAISMod *msg = AISMod::MsgTXAISMod::create(data);
m_aisMod->getInputMessageQueue()->push(msg);
}
void AISModGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void AISModGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AISMod::MsgConfigureAISMod *msg = AISMod::MsgConfigureAISMod::create(m_settings, force);
m_aisMod->getInputMessageQueue()->push(msg);
}
}
void AISModGUI::displaySettings()
{
m_channelMarker.blockSignals(true);
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
m_channelMarker.setTitle(m_settings.m_title);
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
m_channelMarker.blockSignals(false);
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_channelMarker.getTitle());
displayStreamIndex();
blockApplySettings(true);
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
if ((m_settings.m_rfBandwidth == 12500.0f) && (m_settings.m_bt == 0.3f))
ui->mode->setCurrentIndex(0);
else if ((m_settings.m_rfBandwidth == 25000.0f) && (m_settings.m_bt == 0.4f))
ui->mode->setCurrentIndex(1);
else
{
ui->mode->removeItem(2);
ui->mode->addItem(m_settings.getMode());
ui->mode->setCurrentIndex(2);
}
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
ui->btText->setText(QString("%1").arg(m_settings.m_bt, 0, 'f', 1));
ui->bt->setValue(m_settings.m_bt * 10);
ui->gainText->setText(QString("%1").arg((double)m_settings.m_gain, 0, 'f', 1));
ui->gain->setValue(m_settings.m_gain);
ui->udpEnabled->setChecked(m_settings.m_udpEnabled);
ui->udpAddress->setText(m_settings.m_udpAddress);
ui->udpPort->setText(QString::number(m_settings.m_udpPort));
ui->channelMute->setChecked(m_settings.m_channelMute);
ui->repeat->setChecked(m_settings.m_repeat);
ui->msgId->setCurrentIndex(m_settings.m_msgId - 1);
ui->mmsi->setText(m_settings.m_mmsi);
ui->status->setCurrentIndex(m_settings.m_status);
ui->latitude->setValue(m_settings.m_latitude);
ui->longitude->setValue(m_settings.m_longitude);
ui->course->setValue(m_settings.m_course);
ui->speed->setValue(m_settings.m_speed);
ui->heading->setValue(m_settings.m_heading);
ui->message->setText(m_settings.m_data);
blockApplySettings(false);
}
void AISModGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void AISModGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void AISModGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void AISModGUI::tick()
{
double powDb = CalcDb::dbPower(m_aisMod->getMagSq());
m_channelPowerDbAvg(powDb);
ui->channelPower->setText(tr("%1 dB").arg(m_channelPowerDbAvg.asDouble(), 0, 'f', 1));
}

View File

@ -0,0 +1,119 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODAIS_AISMODGUI_H_
#define PLUGINS_CHANNELTX_MODAIS_AISMODGUI_H_
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "aismod.h"
#include "aismodsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSource;
class SpectrumVis;
class ScopeVis;
namespace Ui {
class AISModGUI;
}
class AISModGUI : public ChannelGUI {
Q_OBJECT
public:
static AISModGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
public slots:
void channelMarkerChangedByCursor();
private:
Ui::AISModGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
AISModSettings m_settings;
bool m_doApplySettings;
SpectrumVis* m_spectrumVis;
ScopeVis* m_scopeVis;
AISMod* m_aisMod;
MovingAverageUtil<double, double, 2> m_channelPowerDbAvg; // Less than other mods, as messages are short
MessageQueue m_inputMessageQueue;
explicit AISModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSource *channelTx, QWidget* parent = 0);
virtual ~AISModGUI();
void transmit();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void handleSourceMessages();
void on_deltaFrequency_changed(qint64 value);
void on_mode_currentIndexChanged(int value);
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_bt_valueChanged(int value);
void on_gain_valueChanged(int value);
void on_channelMute_toggled(bool checked);
void on_txButton_clicked();
void on_encode_clicked();
void on_msgId_currentIndexChanged(int index);
void on_mmsi_editingFinished();
void on_status_currentIndexChanged(int index);
void on_latitude_valueChanged(double value);
void on_longitude_valueChanged(double value);
void on_insertPosition_clicked();
void on_course_valueChanged(double value);
void on_speed_valueChanged(double value);
void on_heading_valueChanged(int value);
void on_message_editingFinished();
void on_message_returnPressed();
void on_repeat_toggled(bool checked);
void repeatSelect();
void txSettingsSelect();
void on_udpEnabled_clicked(bool checked);
void on_udpAddress_editingFinished();
void on_udpPort_editingFinished();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void tick();
};
#endif /* PLUGINS_CHANNELTX_MODAIS_AISMODGUI_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "aismodgui.h"
#endif
#include "aismod.h"
#include "aismodwebapiadapter.h"
#include "aismodplugin.h"
const PluginDescriptor AISModPlugin::m_pluginDescriptor = {
AISMod::m_channelId,
QStringLiteral("AIS Modulator"),
QStringLiteral("6.11.1"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
AISModPlugin::AISModPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& AISModPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AISModPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerTxChannel(AISMod::m_channelIdURI, AISMod::m_channelId, this);
}
void AISModPlugin::createTxChannel(DeviceAPI *deviceAPI, BasebandSampleSource **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
AISMod *instance = new AISMod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* AISModPlugin::createTxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSource *txChannel) const
{
(void) deviceUISet;
(void) txChannel;
return nullptr;
}
#else
ChannelGUI* AISModPlugin::createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *txChannel) const
{
return AISModGUI::create(m_pluginAPI, deviceUISet, txChannel);
}
#endif
ChannelWebAPIAdapter* AISModPlugin::createChannelWebAPIAdapter() const
{
return new AISModWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMODPLUGIN_H
#define INCLUDE_AISMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSource;
class AISModPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.modais")
public:
explicit AISModPlugin(QObject* parent = 0);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual void createTxChannel(DeviceAPI *deviceAPI, BasebandSampleSource **bs, ChannelAPI **cs) const;
virtual ChannelGUI* createTxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSource *rxChannel) const;
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_AISMODPLUGIN_H

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "aismodrepeatdialog.h"
#include "aismodsettings.h"
#include <QLineEdit>
AISModRepeatDialog::AISModRepeatDialog(float repeatDelay, int repeatCount, QWidget* parent) :
QDialog(parent),
ui(new Ui::AISModRepeatDialog)
{
ui->setupUi(this);
ui->repeatDelay->setValue(repeatDelay);
QLineEdit *edit = ui->repeatCount->lineEdit();
if (edit)
{
if (repeatCount == AISModSettings::infinitePackets) {
edit->setText("Infinite");
} else {
edit->setText(QString("%1").arg(repeatCount));
}
}
}
AISModRepeatDialog::~AISModRepeatDialog()
{
delete ui;
}
void AISModRepeatDialog::accept()
{
m_repeatDelay = ui->repeatDelay->value();
QString text = ui->repeatCount->currentText();
if (!text.compare(QString("Infinite"), Qt::CaseInsensitive)) {
m_repeatCount = AISModSettings::infinitePackets;
} else {
m_repeatCount = text.toUInt();
}
QDialog::accept();
}

View File

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMODREPEATDIALOG_H
#define INCLUDE_AISMODREPEATDIALOG_H
#include "ui_aismodrepeatdialog.h"
class AISModRepeatDialog : public QDialog {
Q_OBJECT
public:
explicit AISModRepeatDialog(float repeatDelay, int repeatCount, QWidget* parent = 0);
~AISModRepeatDialog();
float m_repeatDelay; // Delay in seconds between messages
int m_repeatCount; // Number of messages to transmit (-1 = infinite)
private slots:
void accept();
private:
Ui::AISModRepeatDialog* ui;
};
#endif // INCLUDE_AISMODREPEATDIALOG_H

View File

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AISModRepeatDialog</class>
<widget class="QDialog" name="AISModRepeatDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>351</width>
<height>115</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Packet Repeat Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="repeatDelayLabel">
<property name="text">
<string>Delay between messages (s)</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="repeatCountLabel">
<property name="text">
<string>Messages to transmit</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="repeatCount">
<property name="toolTip">
<string>Number of messages to transmit</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>Infinite</string>
</property>
</item>
<item>
<property name="text">
<string>10</string>
</property>
</item>
<item>
<property name="text">
<string>100</string>
</property>
</item>
<item>
<property name="text">
<string>1000</string>
</property>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="repeatDelay">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>1000000.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>repeatDelay</tabstop>
<tabstop>repeatCount</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AISModRepeatDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AISModRepeatDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,224 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QDebug>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "aismodsettings.h"
AISModSettings::AISModSettings()
{
resetToDefaults();
}
void AISModSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_baud = 9600;
m_rfBandwidth = 25000.0f; // 12.5k for narrow, 25k for wide (narrow is obsolete)
m_fmDeviation = 4800.0f; // To give modulation index of 0.5 for 9600 baud
m_gain = -1.0f; // To avoid overflow, which results in out-of-band RF
m_channelMute = false;
m_repeat = false;
m_repeatDelay = 1.0f;
m_repeatCount = infinitePackets;
m_rampUpBits = 0;
m_rampDownBits = 0;
m_rampRange = 60;
m_rfNoise = false;
m_writeToFile = false;
m_msgId = 1;
m_mmsi = "0000000000";
m_status = 0;
m_latitude = 0.0f;
m_longitude = 0.0f;
m_course = 0.0f;
m_speed = 0.0f;
m_heading = 0;
m_data = "";
m_rgbColor = QColor(102, 0, 0).rgb();
m_title = "AIS Modulator";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
m_bt = 0.4f; // 0.3 for narrow, 0.4 for wide
m_symbolSpan = 3;
m_udpEnabled = false;
m_udpAddress = "127.0.0.1";
m_udpPort = 9998;
}
bool AISModSettings::setMode(QString mode)
{
if (mode.endsWith("Narrow"))
{
m_rfBandwidth = 12500.0f;
m_fmDeviation = m_baud * 0.25;
m_bt = 0.3f;
return true;
}
else if (mode.endsWith("Wide"))
{
m_rfBandwidth = 25000.0f;
m_fmDeviation = m_baud * 0.5;
m_bt = 0.4f;
return true;
}
else
{
return false;
}
}
QString AISModSettings::getMode() const
{
return QString("%1 %2 %3").arg(m_rfBandwidth).arg(m_fmDeviation).arg(m_bt);
}
QByteArray AISModSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_baud);
s.writeReal(3, m_rfBandwidth);
s.writeReal(4, m_fmDeviation);
s.writeReal(5, m_gain);
s.writeBool(6, m_channelMute);
s.writeBool(7, m_repeat);
s.writeReal(8, m_repeatDelay);
s.writeS32(9, m_repeatCount);
s.writeS32(10, m_rampUpBits);
s.writeS32(11, m_rampDownBits);
s.writeS32(12, m_rampRange);
s.writeBool(14, m_rfNoise);
s.writeBool(15, m_writeToFile);
s.writeS32(17, m_msgId);
s.writeString(18, m_mmsi);
s.writeS32(19, m_status);
s.writeFloat(20, m_latitude);
s.writeFloat(21, m_longitude);
s.writeFloat(22, m_course);
s.writeFloat(23, m_speed);
s.writeS32(24, m_heading);
s.writeString(25, m_data);
s.writeReal(26, m_bt);
s.writeS32(27, m_symbolSpan);
s.writeU32(28, m_rgbColor);
s.writeString(29, m_title);
if (m_channelMarker) {
s.writeBlob(30, m_channelMarker->serialize());
}
s.writeS32(31, m_streamIndex);
s.writeBool(32, m_useReverseAPI);
s.writeString(33, m_reverseAPIAddress);
s.writeU32(34, m_reverseAPIPort);
s.writeU32(35, m_reverseAPIDeviceIndex);
s.writeU32(36, m_reverseAPIChannelIndex);
s.writeBool(37, m_udpEnabled);
s.writeString(38, m_udpAddress);
s.writeU32(39, m_udpPort);
return s.final();
}
bool AISModSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
qint32 tmp;
uint32_t utmp;
d.readS32(1, &tmp, 0);
m_inputFrequencyOffset = tmp;
d.readS32(2, &m_baud, 9600);
d.readReal(3, &m_rfBandwidth, 25000.0f);
d.readReal(4, &m_fmDeviation, 4800.0f);
d.readReal(5, &m_gain, -1.0f);
d.readBool(6, &m_channelMute, false);
d.readBool(7, &m_repeat, false);
d.readReal(8, &m_repeatDelay, 1.0f);
d.readS32(9, &m_repeatCount, -1);
d.readS32(10, &m_rampUpBits, 8);
d.readS32(11, &m_rampDownBits, 8);
d.readS32(12, &m_rampRange, 8);
d.readBool(14, &m_rfNoise, false);
d.readBool(15, &m_writeToFile, false);
d.readS32(17, &m_msgId, 1);
d.readString(18, &m_mmsi, "0000000000");
d.readS32(19, &m_status, 0);
d.readFloat(20, &m_latitude, 0.0f);
d.readFloat(21, &m_longitude, 0.0f);
d.readFloat(22, &m_course, 0.0f);
d.readFloat(23, &m_speed, 0.0f);
d.readS32(24, &m_heading, 0);
d.readString(25, &m_data, "");
d.readReal(26, &m_bt, 0.3f);
d.readS32(27, &m_symbolSpan, 3);
d.readU32(28, &m_rgbColor, QColor(102, 0, 0).rgb());
d.readString(29, &m_title, "AIS Modulator");
if (m_channelMarker) {
d.readBlob(30, &bytetmp);
m_channelMarker->deserialize(bytetmp);
}
d.readS32(31, &m_streamIndex, 0);
d.readBool(32, &m_useReverseAPI, false);
d.readString(33, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(34, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(35, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(36, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
d.readBool(37, &m_udpEnabled);
d.readString(38, &m_udpAddress, "127.0.0.1");
d.readU32(39, &utmp);
if ((utmp > 1023) && (utmp < 65535)) {
m_udpPort = utmp;
} else {
m_udpPort = 9998;
}
return true;
}
else
{
qDebug() << "AISModSettings::deserialize: ERROR";
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef PLUGINS_CHANNELTX_MODAIS_AISMODSETTINGS_H
#define PLUGINS_CHANNELTX_MODAIS_AISMODSETTINGS_H
#include <QByteArray>
#include <stdint.h>
#include "dsp/dsptypes.h"
class Serializable;
struct AISModSettings
{
static const int infinitePackets = -1;
qint64 m_inputFrequencyOffset;
int m_baud;
Real m_rfBandwidth;
Real m_fmDeviation;
Real m_gain;
bool m_channelMute;
bool m_repeat;
Real m_repeatDelay;
int m_repeatCount;
int m_rampUpBits;
int m_rampDownBits;
int m_rampRange;
bool m_rfNoise;
bool m_writeToFile;
int m_msgId;
QString m_mmsi;
int m_status;
float m_latitude;
float m_longitude;
float m_course;
float m_speed;
int m_heading;
QString m_data;
float m_bt;
int m_symbolSpan;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
bool m_udpEnabled;
QString m_udpAddress;
uint16_t m_udpPort;
AISModSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
bool setMode(QString mode);
QString getMode() const;
};
#endif /* PLUGINS_CHANNELTX_MODAIS_AISMODSETTINGS_H */

View File

@ -0,0 +1,496 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include "dsp/basebandsamplesink.h"
#include "dsp/datafifo.h"
#include "aismodsource.h"
#include "util/crc.h"
#include "util/messagequeue.h"
#include "maincore.h"
#include "channel/channelapi.h"
AISModSource::AISModSource() :
m_channelSampleRate(AISMOD_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_fmPhase(0.0),
m_spectrumSink(nullptr),
m_scopeSink(nullptr),
m_magsq(0.0),
m_levelCalcCount(0),
m_peakLevel(0.0f),
m_levelSum(0.0f),
m_state(idle),
m_byteIdx(0),
m_bitIdx(0),
m_last5Bits(0),
m_bitCount(0)
{
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
AISModSource::~AISModSource()
{
}
void AISModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
{
std::for_each(
begin,
begin + nbSamples,
[this](Sample& s) {
pullOne(s);
}
);
}
void AISModSource::pullOne(Sample& sample)
{
if (m_settings.m_channelMute)
{
sample.m_real = 0.0f;
sample.m_imag = 0.0f;
return;
}
Complex ci;
if (m_interpolatorDistance > 1.0f)
{
modulateSample();
while (!m_interpolator.decimate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
else
{
if (m_interpolator.interpolate(&m_interpolatorDistanceRemain, m_modSample, &ci))
{
modulateSample();
}
}
m_interpolatorDistanceRemain += m_interpolatorDistance;
ci *= m_carrierNco.nextIQ(); // shift to carrier frequency
// Calculate power
double magsq = ci.real() * ci.real() + ci.imag() * ci.imag();
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
// Convert from float to fixed point
sample.m_real = (FixReal) (ci.real() * SDR_TX_SCALEF);
sample.m_imag = (FixReal) (ci.imag() * SDR_TX_SCALEF);
}
void AISModSource::sampleToSpectrum(Complex sample)
{
if (m_spectrumSink)
{
Real r = std::real(sample) * SDR_TX_SCALEF;
Real i = std::imag(sample) * SDR_TX_SCALEF;
m_sampleBuffer.push_back(Sample(r, i));
m_spectrumSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), false);
m_sampleBuffer.clear();
}
}
void AISModSource::sampleToScope(Complex sample)
{
if (m_scopeSink)
{
Real r = std::real(sample) * SDR_RX_SCALEF;
Real i = std::imag(sample) * SDR_RX_SCALEF;
m_sampleBuffer.push_back(Sample(r, i));
m_scopeSink->feed(m_sampleBuffer.begin(), m_sampleBuffer.end(), true);
m_sampleBuffer.clear();
}
}
void AISModSource::modulateSample()
{
Real mod;
Real linearRampGain;
if ((m_state == idle) || (m_state == wait))
{
m_modSample.real(0.0f);
m_modSample.imag(0.0f);
sampleToSpectrum(m_modSample);
sampleToScope(m_modSample);
Real s = std::abs(m_modSample);
calculateLevel(s);
if (m_state == wait)
{
m_waitCounter--;
if (m_waitCounter == 0) {
initTX();
}
}
}
else
{
if (m_sampleIdx == 0)
{
if (bitsValid())
{
// NRZI encoding - encode 0 as change of freq, 1 no change
if (getBit() == 0) {
m_nrziBit = m_nrziBit == 1 ? 0 : 1;
}
}
// Should we start ramping down power?
if ((m_bitCount < m_settings.m_rampDownBits) || ((m_bitCount == 0) && !m_settings.m_rampDownBits))
{
m_state = ramp_down;
if (m_settings.m_rampDownBits > 0) {
m_powRamp = -m_settings.m_rampRange/(m_settings.m_rampDownBits * (Real)m_samplesPerSymbol);
}
}
}
m_sampleIdx++;
if (m_sampleIdx >= m_samplesPerSymbol) {
m_sampleIdx = 0;
}
// Apply Gaussian pulse shaping filter
mod = m_pulseShape.filter(m_nrziBit ? 1.0f : -1.0f);
// FM
m_fmPhase += m_phaseSensitivity * mod;
// Keep phase in range -pi,pi
if (m_fmPhase > M_PI) {
m_fmPhase -= 2.0f * M_PI;
} else if (m_fmPhase < -M_PI) {
m_fmPhase += 2.0f * M_PI;
}
linearRampGain = powf(10.0f, m_pow/20.0f);
m_modSample.real(m_linearGain * linearRampGain * cos(m_fmPhase));
m_modSample.imag(m_linearGain * linearRampGain * sin(m_fmPhase));
if (m_iqFile.is_open()) {
m_iqFile << mod << "," << m_modSample.real() << "," << m_modSample.imag() << "\n";
}
if (m_settings.m_rfNoise)
{
// Noise to test filter frequency response
m_modSample.real(m_linearGain * ((Real)rand()/((Real)RAND_MAX)-0.5f));
m_modSample.imag(m_linearGain * ((Real)rand()/((Real)RAND_MAX)-0.5f));
}
// Display baseband in spectrum analyser and scope
sampleToSpectrum(m_modSample);
sampleToScope(m_modSample);
// Ramp up/down power at start/end of packet
if ((m_state == ramp_up) || (m_state == ramp_down))
{
m_pow += m_powRamp;
if ((m_state == ramp_up) && (m_pow >= 0.0f))
{
// Finished ramp up, transmit at full gain
m_state = tx;
m_pow = 0.0f;
}
else if ((m_state == ramp_down) && ( (m_settings.m_rampRange == 0)
|| (m_settings.m_rampDownBits == 0)
|| (m_pow <= -(Real)m_settings.m_rampRange)
))
{
m_state = idle;
// Do we need to retransmit the packet?
if (m_settings.m_repeat)
{
if (m_packetRepeatCount > 0)
m_packetRepeatCount--;
if ((m_packetRepeatCount == AISModSettings::infinitePackets) || (m_packetRepeatCount > 0))
{
if (m_settings.m_repeatDelay > 0.0f)
{
// Wait before retransmitting
m_state = wait;
m_waitCounter = m_settings.m_repeatDelay * AISMOD_SAMPLE_RATE;
}
else
{
// Retransmit immediately
initTX();
}
}
}
}
}
Real s = std::abs(m_modSample);
calculateLevel(s);
}
// Send Gaussian filter output to mod analyzer
m_demodBuffer[m_demodBufferFill] = std::real(mod) * std::numeric_limits<int16_t>::max();
++m_demodBufferFill;
if (m_demodBufferFill >= m_demodBuffer.size())
{
QList<DataFifo*> *dataFifos = MainCore::instance()->getDataPipes().getFifos(m_channel, "demod");
if (dataFifos)
{
QList<DataFifo*>::iterator it = dataFifos->begin();
for (; it != dataFifos->end(); ++it) {
(*it)->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16));
}
}
m_demodBufferFill = 0;
}
}
void AISModSource::calculateLevel(Real& sample)
{
if (m_levelCalcCount < m_levelNbSamples)
{
m_peakLevel = std::max(std::fabs(m_peakLevel), sample);
m_levelSum += sample * sample;
m_levelCalcCount++;
}
else
{
m_rmsLevel = sqrt(m_levelSum / m_levelNbSamples);
m_peakLevelOut = m_peakLevel;
m_peakLevel = 0.0f;
m_levelSum = 0.0f;
m_levelCalcCount = 0;
}
}
void AISModSource::applySettings(const AISModSettings& settings, bool force)
{
if ((settings.m_bt != m_settings.m_bt) || (settings.m_symbolSpan != m_settings.m_symbolSpan) || (settings.m_baud != m_settings.m_baud) || force)
{
qDebug() << "AISModSource::applySettings: Recreating pulse shaping filter: "
<< " SampleRate:" << AISMOD_SAMPLE_RATE
<< " bt: " << settings.m_bt
<< " symbolSpan: " << settings.m_symbolSpan
<< " baud:" << settings.m_baud;
m_pulseShape.create(settings.m_bt, settings.m_symbolSpan, AISMOD_SAMPLE_RATE/settings.m_baud);
}
m_settings = settings;
// Precalculate FM sensensity and linear gain to save doing it in the loop
m_samplesPerSymbol = AISMOD_SAMPLE_RATE / m_settings.m_baud;
Real modIndex = m_settings.m_fmDeviation / (Real)m_settings.m_baud;
m_phaseSensitivity = 2.0f * M_PI * modIndex / (Real)m_samplesPerSymbol;
m_linearGain = powf(10.0f, m_settings.m_gain/20.0f);
}
void AISModSource::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "AISModSource::applyChannelSettings:"
<< " channelSampleRate: " << channelSampleRate
<< " channelFrequencyOffset: " << channelFrequencyOffset
<< " rfBandwidth: " << m_settings.m_rfBandwidth;
if ((channelFrequencyOffset != m_channelFrequencyOffset)
|| (channelSampleRate != m_channelSampleRate) || force)
{
m_carrierNco.setFreq(channelFrequencyOffset, channelSampleRate);
}
if ((m_channelSampleRate != channelSampleRate) || force)
{
m_interpolatorDistanceRemain = 0;
m_interpolatorDistance = (Real) AISMOD_SAMPLE_RATE / (Real) channelSampleRate;
m_interpolator.create(48, AISMOD_SAMPLE_RATE, m_settings.m_rfBandwidth / 2.2, 3.0);
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(m_channel, "reportdemod");
if (messageQueues)
{
QList<MessageQueue*>::iterator it = messageQueues->begin();
for (; it != messageQueues->end(); ++it)
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(m_channel, m_channelSampleRate);
(*it)->push(msg);
}
}
}
bool AISModSource::bitsValid()
{
return m_bitCount > 0;
}
int AISModSource::getBit()
{
int bit;
if (m_bitCount > 0)
{
bit = (m_bits[m_byteIdx] >> m_bitIdx) & 1;
m_bitIdx++;
m_bitCount--;
if (m_bitIdx == 8)
{
m_byteIdx++;
m_bitIdx = 0;
}
}
else
bit = 0;
return bit;
}
void AISModSource::addBit(int bit)
{
// Transmit LSB first
m_bits[m_byteIdx] |= bit << m_bitIdx;
m_bitIdx++;
m_bitCount++;
m_bitCountTotal++;
if (m_bitIdx == 8)
{
m_byteIdx++;
m_bits[m_byteIdx] = 0;
m_bitIdx = 0;
}
m_last5Bits = ((m_last5Bits << 1) | bit) & 0x1f;
}
void AISModSource::initTX()
{
m_byteIdx = 0;
m_bitIdx = 0;
m_bitCount = m_bitCountTotal; // Reset to allow retransmission
m_nrziBit = 1;
if (m_settings.m_rampUpBits == 0)
{
m_state = tx;
m_pow = 0.0f;
}
else
{
m_state = ramp_up;
m_pow = -(Real)m_settings.m_rampRange;
m_powRamp = m_settings.m_rampRange/(m_settings.m_rampUpBits * (Real)m_samplesPerSymbol);
}
}
void AISModSource::addTXPacket(const QString& data)
{
QByteArray ba = QByteArray::fromHex(data.toUtf8());
addTXPacket(ba);
}
void AISModSource::addTXPacket(QByteArray data)
{
uint8_t packet[AIS_MAX_BYTES];
uint8_t *crc_start;
uint8_t *packet_end;
uint8_t *p;
crc16x25 crc;
uint16_t crcValue;
int packet_length;
// Create AIS message
p = packet;
// Training
*p++ = AIS_TRAIN;
*p++ = AIS_TRAIN;
*p++ = AIS_TRAIN;
// Flag
*p++ = AIS_FLAG;
crc_start = p;
// Copy packet payload
for (int i = 0; i < data.size(); i++)
*p++ = data[i];
// CRC (do not include flags)
crc.calculate(crc_start, p-crc_start);
crcValue = crc.get();
*p++ = crcValue & 0xff;
*p++ = (crcValue >> 8);
packet_end = p;
// Flag
*p++ = AIS_FLAG;
// Buffer
*p++ = 0;
packet_length = p-&packet[0];
encodePacket(packet, packet_length, crc_start, packet_end);
}
void AISModSource::encodePacket(uint8_t *packet, int packet_length, uint8_t *crc_start, uint8_t *packet_end)
{
// HDLC bit stuffing
m_byteIdx = 0;
m_bitIdx = 0;
m_last5Bits = 0;
m_bitCount = 0;
m_bitCountTotal = 0;
for (int i = 0; i < packet_length; i++)
{
for (int j = 0; j < 8; j++)
{
int tx_bit = (packet[i] >> j) & 1;
// Stuff 0 if last 5 bits are 1s, unless transmitting flag
// Except for special case of when last 5 bits of CRC are 1s
if ( ( (packet[i] != AIS_FLAG)
|| ( (&packet[i] >= crc_start)
&& ( (&packet[i] < packet_end)
|| ((&packet[i] == packet_end) && (j == 0))
)
)
)
&& (m_last5Bits == 0x1f)
)
addBit(0);
addBit(tx_bit);
}
}
//m_samplesPerSymbol = AISMOD_SAMPLE_RATE / m_settings.m_baud;
m_packetRepeatCount = m_settings.m_repeatCount;
initTX();
// Only reset phases at start of new packet TX, not in initTX(), so that
// there isn't a discontinuity in phase when repeatedly transmitting a
// single tone
m_sampleIdx = 0;
m_fmPhase = 0.0;
if (m_settings.m_writeToFile)
m_iqFile.open("aismod.csv", std::ofstream::out);
else if (m_iqFile.is_open())
m_iqFile.close();
}

View File

@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMODSOURCE_H
#define INCLUDE_AISMODSOURCE_H
#include <QMutex>
#include <QDebug>
#include <QVector>
#include <iostream>
#include <fstream>
#include "dsp/channelsamplesource.h"
#include "dsp/nco.h"
#include "dsp/ncof.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "dsp/gaussian.h"
#include "util/movingaverage.h"
#include "aismodsettings.h"
// Train, flag, data, crc, flag and a zero for ramp down. Longest message is 1008/8=126 bytes
#define AIS_MAX_BYTES (3+1+126+2+1+1)
// Add extra space for bit stuffing
#define AIS_MAX_BITS (AIS_MAX_BYTES*2)
#define AIS_TRAIN 0x55
#define AIS_FLAG 0x7e
// Sample rate is multiple of 9600 baud rate (use even multiple so Gausian filter has odd number of taps)
// Is there any benefit to having this higher?
#define AISMOD_SAMPLE_RATE (9600*6)
class BasebandSampleSink;
class ChannelAPI;
class AISModSource : public ChannelSampleSource
{
public:
AISModSource();
virtual ~AISModSource();
virtual void pull(SampleVector::iterator begin, unsigned int nbSamples);
virtual void pullOne(Sample& sample);
virtual void prefetch(unsigned int nbSamples) { (void) nbSamples; }
double getMagSq() const { return m_magsq; }
void getLevels(qreal& rmsLevel, qreal& peakLevel, int& numSamples) const
{
rmsLevel = m_rmsLevel;
peakLevel = m_peakLevelOut;
numSamples = m_levelNbSamples;
}
void setSpectrumSink(BasebandSampleSink *sampleSink) { m_spectrumSink = sampleSink; }
void setScopeSink(BasebandSampleSink* scopeSink) { m_scopeSink = scopeSink; }
void applySettings(const AISModSettings& settings, bool force = false);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void addTXPacket(const QString& data);
void addTXPacket(QByteArray data);
void encodePacket(uint8_t *packet, int packet_length, uint8_t *crc_start, uint8_t *packet_end);
void setChannel(ChannelAPI *channel) { m_channel = channel; }
private:
int m_channelSampleRate;
int m_channelFrequencyOffset;
AISModSettings m_settings;
ChannelAPI *m_channel;
NCO m_carrierNco;
double m_fmPhase; // Double gives cleaner spectrum than Real
double m_phaseSensitivity;
Real m_linearGain;
Complex m_modSample;
int m_nrziBit; // Output of NRZI coder
Gaussian<Real> m_pulseShape; // Pulse shaping filter
BasebandSampleSink* m_spectrumSink; // Spectrum GUI to display baseband waveform
BasebandSampleSink* m_scopeSink; // Scope GUI to display baseband waveform
SampleVector m_sampleBuffer;
Interpolator m_interpolator; // Interpolator to channel sample rate
Real m_interpolatorDistance;
Real m_interpolatorDistanceRemain;
double m_magsq;
MovingAverageUtil<double, double, 16> m_movingAverage;
quint32 m_levelCalcCount;
qreal m_rmsLevel;
qreal m_peakLevelOut;
Real m_peakLevel;
Real m_levelSum;
static const int m_levelNbSamples = 480; // every 10ms assuming 48k Sa/s
int m_sampleIdx; // Sample index in to symbol
int m_samplesPerSymbol; // Number of samples per symbol
Real m_pow; // In dB
Real m_powRamp; // In dB
enum AISModState {
idle, ramp_up, tx, ramp_down, wait
} m_state; // States for sample modulation
int m_packetRepeatCount;
uint64_t m_waitCounter; // Samples to wait before retransmission
uint8_t m_bits[AIS_MAX_BITS]; // HDLC encoded bits to transmit
int m_byteIdx; // Index in to m_bits
int m_bitIdx; // Index in to current byte of m_bits
int m_last5Bits; // Last 5 bits to be HDLC encoded
int m_bitCount; // Count of number of valid bits in m_bits
int m_bitCountTotal;
std::ofstream m_iqFile; // For debug output of baseband waveform
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
bool bitsValid(); // Are there and bits to transmit
int getBit(); // Get bit from m_bits
void addBit(int bit); // Add bit to m_bits, with zero stuffing
void initTX();
void calculateLevel(Real& sample);
void modulateSample();
void sampleToSpectrum(Complex sample);
void sampleToScope(Complex sample);
};
#endif // INCLUDE_AISMODSOURCE_H

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "aismodtxsettingsdialog.h"
AISModTXSettingsDialog::AISModTXSettingsDialog(int rampUpBits, int rampDownBits,
int rampRange,
int baud, int symbolSpan,
bool rfNoise, bool writeToFile,
QWidget* parent) :
QDialog(parent),
ui(new Ui::AISModTXSettingsDialog)
{
ui->setupUi(this);
ui->rampUp->setValue(rampUpBits);
ui->rampDown->setValue(rampDownBits);
ui->rampRange->setValue(rampRange);
ui->baud->setValue(baud);
ui->symbolSpan->setValue(symbolSpan);
ui->rfNoise->setChecked(rfNoise);
ui->writeToFile->setChecked(writeToFile);
}
AISModTXSettingsDialog::~AISModTXSettingsDialog()
{
delete ui;
}
void AISModTXSettingsDialog::accept()
{
m_rampUpBits = ui->rampUp->value();
m_rampDownBits = ui->rampDown->value();
m_rampRange = ui->rampRange->value();
m_baud = ui->baud->value();
m_symbolSpan = ui->symbolSpan->value();
m_rfNoise = ui->rfNoise->isChecked();
m_writeToFile = ui->writeToFile->isChecked();
QDialog::accept();
}

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMODTXSETTINGSDIALOG_H
#define INCLUDE_AISMODTXSETTINGSDIALOG_H
#include "ui_aismodtxsettingsdialog.h"
class AISModTXSettingsDialog : public QDialog {
Q_OBJECT
public:
explicit AISModTXSettingsDialog(int rampUpBits, int rampDownBits, int rampRange,
int baud, int symbolSpan,
bool rfNoise, bool writeToFile,
QWidget* parent = 0);
~AISModTXSettingsDialog();
int m_rampUpBits;
int m_rampDownBits;
int m_rampRange;
int m_baud;
int m_symbolSpan;
bool m_rfNoise;
bool m_writeToFile;
private slots:
void accept();
private:
Ui::AISModTXSettingsDialog* ui;
};
#endif // INCLUDE_AISMODTXSETTINGSDIALOG_H

View File

@ -0,0 +1,211 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AISModTXSettingsDialog</class>
<widget class="QDialog" name="AISModTXSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>351</width>
<height>321</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Packet TX Extra Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="baudGroup">
<property name="title">
<string>Modulation</string>
</property>
<layout class="QFormLayout" name="formLayout_8">
<item row="0" column="0">
<widget class="QLabel" name="baudLabel">
<property name="text">
<string>Baud rate</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="baud">
<property name="toolTip">
<string>Baud rate (symbols per second).</string>
</property>
<property name="maximum">
<number>100000</number>
</property>
<property name="value">
<number>9600</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="symbolSpanLabel">
<property name="text">
<string>Filter symbol span</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="symbolSpan">
<property name="toolTip">
<string>Number of symbols over which filter is applied</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>20</number>
</property>
<property name="value">
<number>3</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="powerRampingGroup">
<property name="title">
<string>Power Ramping</string>
</property>
<layout class="QFormLayout" name="formLayout_6">
<item row="0" column="0">
<widget class="QLabel" name="rampUpLabel">
<property name="text">
<string>Ramp up bits</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="rampUp">
<property name="toolTip">
<string>Number of bits at start of frame during which output power is ramped up.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="rampDownLabel">
<property name="text">
<string>Ramp down bits</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="rampDown">
<property name="toolTip">
<string>Number of bits at end of frame during which output power is ramped down.</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="rampRangeLabel">
<property name="text">
<string>Ramp range (dB)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="rampRange">
<property name="toolTip">
<string>Range in dB over which power is ramped up or down. E.g. a value of 60 causes power to be ramped from -60dB to 0dB.</string>
</property>
<property name="maximum">
<number>120</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="debugGroup">
<property name="title">
<string>Debug</string>
</property>
<layout class="QFormLayout" name="formLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="rfNoise">
<property name="toolTip">
<string>Generate white noise as RF signal.</string>
</property>
<property name="text">
<string>Generate RF noise</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="writeToFile">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Write baseband signal to a CSV file named aismod.csv</string>
</property>
<property name="text">
<string>Write baseband to CSV</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AISModTXSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AISModTXSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGChannelSettings.h"
#include "aismod.h"
#include "aismodwebapiadapter.h"
AISModWebAPIAdapter::AISModWebAPIAdapter()
{}
AISModWebAPIAdapter::~AISModWebAPIAdapter()
{}
int AISModWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisModSettings(new SWGSDRangel::SWGAISModSettings());
response.getAisModSettings()->init();
AISMod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int AISModWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
AISMod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
AISMod::webapiFormatChannelSettings(response, m_settings);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AISMOD_WEBAPIADAPTER_H
#define INCLUDE_AISMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "aismodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class AISModWebAPIAdapter : public ChannelWebAPIAdapter {
public:
AISModWebAPIAdapter();
virtual ~AISModWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
private:
AISModSettings m_settings;
};
#endif // INCLUDE_AISMOD_WEBAPIADAPTER_H

View File

@ -0,0 +1,128 @@
<h1>AIS modulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to transmit AIS (Automatic Identification System) messages using GMSK/FM modulation. AIS is used to track ships and other marine vessels at sea.
You need an AIS license to transmit on the AIS VHF frequencies (161.975MHz and 162.025MHz). This plugin should not therefore be used on those frequencies unless the transmitter and receiver are directly connected via coax. If you have an amateur license, you should be able to transmit AIS within amateur bands.
<h2>Interface</h2>
![AIS Modulator plugin GUI](../../../doc/img/AISMod_plugin.png)
<h3>1: Frequency shift from center frequency of transmission</h3>
Use the wheels to adjust the frequency shift in Hz from the center frequency of transmission. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
<h3>2: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal generated in the pass band.
<h3>3: Channel mute</h3>
Use this button to toggle mute for this channel.
<h3>4: Mode</h3>
Allows setting of RF bandwidth, FM deviation and BT values according to the chosen mode, which can be Narrow (BW=12.5kHz, Dev=2.4k, BT=0.3) or Wide (BW=25kHz, Dev=4.8k, BT=0.4). The latest specification for AIS, ITU-R M.1371-5, only specifies Wide operation.
<h3>5: RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the output signal to limit the RF bandwidth. Typically this should be 25kHz.
<h3>6: FM Deviation</h3>
This specifies the maximum frequency deviation. Typically this should be 4.8kHz, giving a modulation index of 0.5 at 9,600 baud.
<h3>7: BT Bandwidth</h3>
Bandwidth-time product for the Gaussian filter, used for GMSK modulation. This should typically be 0.4.
<h3>8: Gain</h3>
Adjusts the gain in dB from -60 to 0dB. The gain should be set to ensure the level meter remains below 100%.
<h3>9: Level meter in %</h3>
- top bar (beige): average value
- bottom bar (brown): instantaneous peak value
- tip vertical bar (bright red): peak hold value
<h3>10: UDP</h3>
When checked, a UDP port is opened to receive messages from other features or applications that will be transmitted. These messages do not need to contain the CRC, as it is appended automatically.
<h3>11: UDP address</h3>
IP address of the interface open the UDP port on, to receive messages to be transmitted.
<h3>12: UDP port</h3>
UDP port number to receive messages to be transmitted on.
<h3>13: Encode</h3>
When pressed, the message field will be set to a hex encoded string that represents the message determined by the following controls.
<h3>14: Message Type</h3>
Select a message type:
- Scheduled postion report
- Assigned position report
- Special position report
- Base station report
<h3>15: MMSI</h3>
Enter a 9 digit Maritime Mobile Service Identity, which uniquely identifies a vessel.
<h3>16: Status</h3>
For position reports, specify the status of the vessel.
<h3>17: Latitude</h3>
Specifiy the latitude of the vessel or station in decimal degrees, North positive.
<h3>18: Longitude</h3>
Specifiy the longitude of the vessel or station in decimal degrees, East positive.
<h3>19: Insert position</h3>
Sets the latitude and longitude fields to the values specified under Preferences > My position.
<h3>20: Course</h3>
For position reports, specify the vessel's course in degrees. This is the direction in which the vessel is moving.
<h3>21: Speed</h3>
For position reports, specify the vessel's speed in knots.
<h3>22: Heading</h3>
For position reports, specify the vessel's heading. This is the direction the vessel is pointing towards.
<h3>23: Message</h3>
The AIS message send. This should be a hex encoded string.
<h3>24: Repeat</h3>
Check this button to repeatedly transmit a message. Right click to open the dialog to adjust the delay between retransmission and number of times the message should be repeated.
<h3>25: TX</h3>
Transmits the message.
<h2>API</h2>
Full details of the API can be found in the Swagger documentation. Here is a quick example of how to transmit a message from the command line:
curl -X POST "http://127.0.0.1:8091/sdrangel/deviceset/1/channel/0/actions" -d '{"channelType": "AISMod", "direction": 1, "AISModActions": { "tx": { "data": "000000000000000000000000000000000" }}}'
Or to set the FM deviation:
curl -X PATCH "http://127.0.0.1:8091/sdrangel/deviceset/1/channel/0/settings" -d '{"channelType": "AISMod", "direction": 1, "AISModSettings": {"fmDeviation": 4800}}'

View File

@ -12,6 +12,7 @@ if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)
endif()
add_subdirectory(afc)
add_subdirectory(ais)
add_subdirectory(aprs)
add_subdirectory(demodanalyzer)
add_subdirectory(pertester)

View File

@ -0,0 +1,55 @@
project(ais)
set(ais_SOURCES
ais.cpp
aissettings.cpp
aisplugin.cpp
aiswebapiadapter.cpp
)
set(ais_HEADERS
ais.h
aissettings.h
aisplugin.h
aiswebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(ais_SOURCES
${ais_SOURCES}
aisgui.cpp
aisgui.ui
ais.qrc
)
set(ais_HEADERS
${ais_HEADERS}
aisgui.h
)
set(TARGET_NAME featureais)
set(TARGET_LIB Qt5::Widgets)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featureaissrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${ais_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

301
plugins/feature/ais/ais.cpp Normal file
View File

@ -0,0 +1,301 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QTimer>
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "SWGDeviceState.h"
#include "dsp/dspengine.h"
#include "device/deviceset.h"
#include "channel/channelapi.h"
#include "feature/featureset.h"
#include "maincore.h"
#include "ais.h"
MESSAGE_CLASS_DEFINITION(AIS::MsgConfigureAIS, Message)
const char* const AIS::m_featureIdURI = "sdrangel.feature.ais";
const char* const AIS::m_featureId = "AIS";
AIS::AIS(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface)
{
qDebug("AIS::AIS: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "AIS error";
connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes()));
m_updatePipesTimer.start(1000);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
}
AIS::~AIS()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
}
void AIS::start()
{
qDebug("AIS::start");
m_state = StRunning;
}
void AIS::stop()
{
qDebug("AIS::stop");
m_state = StIdle;
}
bool AIS::handleMessage(const Message& cmd)
{
if (MsgConfigureAIS::match(cmd))
{
MsgConfigureAIS& cfg = (MsgConfigureAIS&) cmd;
qDebug() << "AIS::handleMessage: MsgConfigureAIS";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MainCore::MsgPacket::match(cmd))
{
MainCore::MsgPacket& report = (MainCore::MsgPacket&) cmd;
if (getMessageQueueToGUI())
{
MainCore::MsgPacket *copy = new MainCore::MsgPacket(report);
getMessageQueueToGUI()->push(copy);
}
return true;
}
else
{
return false;
}
}
void AIS::updatePipes()
{
QList<AvailablePipeSource> availablePipes = updateAvailablePipeSources("ais", AISSettings::m_pipeTypes, AISSettings::m_pipeURIs, this);
if (availablePipes != m_availablePipes) {
m_availablePipes = availablePipes;
}
}
QByteArray AIS::serialize() const
{
return m_settings.serialize();
}
bool AIS::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureAIS *msg = MsgConfigureAIS::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureAIS *msg = MsgConfigureAIS::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void AIS::applySettings(const AISSettings& settings, bool force)
{
qDebug() << "AIS::applySettings:"
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex
<< " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) ||
(m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
}
int AIS::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisSettings(new SWGSDRangel::SWGAISSettings());
response.getAisSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int AIS::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
AISSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureAIS *msg = MsgConfigureAIS::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAIS *msgToGUI = MsgConfigureAIS::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
void AIS::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const AISSettings& settings)
{
if (response.getAisSettings()->getTitle()) {
*response.getAisSettings()->getTitle() = settings.m_title;
} else {
response.getAisSettings()->setTitle(new QString(settings.m_title));
}
response.getAisSettings()->setRgbColor(settings.m_rgbColor);
response.getAisSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAisSettings()->getReverseApiAddress()) {
*response.getAisSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAisSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAisSettings()->setReverseApiPort(settings.m_reverseAPIPort);
}
void AIS::webapiUpdateFeatureSettings(
AISSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getAisSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAisSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAisSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAisSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAisSettings()->getReverseApiPort();
}
}
void AIS::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const AISSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("AIS"));
swgFeatureSettings->setAisSettings(new SWGSDRangel::SWGAISSettings());
SWGSDRangel::SWGAISSettings *swgAISSettings = swgFeatureSettings->getAisSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("title") || force) {
swgAISSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgAISSettings->setRgbColor(settings.m_rgbColor);
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIFeatureSetIndex)
.arg(settings.m_reverseAPIFeatureIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgFeatureSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgFeatureSettings;
}
void AIS::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "AIS::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("AIS::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

116
plugins/feature/ais/ais.h Normal file
View File

@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_AIS_H_
#define INCLUDE_FEATURE_AIS_H_
#include <QThread>
#include <QNetworkRequest>
#include <QTimer>
#include "feature/feature.h"
#include "util/message.h"
#include "aissettings.h"
class WebAPIAdapterInterface;
class QNetworkAccessManager;
class QNetworkReply;
namespace SWGSDRangel {
class SWGDeviceState;
}
class AIS : public Feature
{
Q_OBJECT
public:
class MsgConfigureAIS : public Message {
MESSAGE_CLASS_DECLARATION
public:
const AISSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAIS* create(const AISSettings& settings, bool force) {
return new MsgConfigureAIS(settings, force);
}
private:
AISSettings m_settings;
bool m_force;
MsgConfigureAIS(const AISSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
AIS(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~AIS();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) const { id = objectName(); }
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
static void webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const AISSettings& settings);
static void webapiUpdateFeatureSettings(
AISSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
static const char* const m_featureIdURI;
static const char* const m_featureId;
private:
AISSettings m_settings;
QList<PipeEndPoint::AvailablePipeSource> m_availablePipes;
QTimer m_updatePipesTimer;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void start();
void stop();
void applySettings(const AISSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const AISSettings& settings, bool force);
private slots:
void updatePipes();
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_FEATURE_AIS_H_

View File

@ -0,0 +1,12 @@
<RCC>
<qresource prefix="/ais/">
<file>map/aircraft.png</file>
<file>map/helicopter.png</file>
<file>map/ship.png</file>
<file>map/tanker.png</file>
<file>map/cargo.png</file>
<file>map/tug.png</file>
<file>map/bouy.png</file>
<file>map/anchor.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,604 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QMessageBox>
#include <QLineEdit>
#include <QDesktopServices>
#include <QAction>
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "mainwindow.h"
#include "device/deviceuiset.h"
#include "ui_aisgui.h"
#include "ais.h"
#include "aisgui.h"
#include "SWGMapItem.h"
AISGUI* AISGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
AISGUI* gui = new AISGUI(pluginAPI, featureUISet, feature);
return gui;
}
void AISGUI::destroy()
{
delete this;
}
void AISGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray AISGUI::serialize() const
{
return m_settings.serialize();
}
bool AISGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool AISGUI::handleMessage(const Message& message)
{
if (AIS::MsgConfigureAIS::match(message))
{
qDebug("AISGUI::handleMessage: AIS::MsgConfigureAIS");
const AIS::MsgConfigureAIS& cfg = (AIS::MsgConfigureAIS&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (MainCore::MsgPacket::match(message))
{
MainCore::MsgPacket& report = (MainCore::MsgPacket&) message;
// Decode the message
AISMessage *ais = AISMessage::decode(report.getPacket());
// Update table
updateVessels(ais);
}
return false;
}
void AISGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void AISGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
AISGUI::AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::AISGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_lastFeatureState(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
setChannelWidget(false);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_ais = reinterpret_cast<AIS*>(feature);
m_ais->setMessageQueueToGUI(&m_inputMessageQueue);
m_featureUISet->addRollupWidget(this);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(1000);
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->vessels->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->vessels->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
vesselsMenu = new QMenu(ui->vessels);
for (int i = 0; i < ui->vessels->horizontalHeader()->count(); i++)
{
QString text = ui->vessels->horizontalHeaderItem(i)->text();
vesselsMenu->addAction(createCheckableItem(text, i, true, SLOT(vesselsColumnSelectMenuChecked())));
}
ui->vessels->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->vessels->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(vesselsColumnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->vessels->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(vessels_sectionMoved(int, int, int)));
connect(ui->vessels->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(vessels_sectionResized(int, int, int)));
displaySettings();
applySettings(true);
}
AISGUI::~AISGUI()
{
delete ui;
}
void AISGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void AISGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
// Order and size columns
QHeaderView *header = ui->vessels->horizontalHeader();
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
{
bool hidden = m_settings.m_vesselColumnSizes[i] == 0;
header->setSectionHidden(i, hidden);
vesselsMenu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_vesselColumnSizes[i] > 0) {
ui->vessels->setColumnWidth(i, m_settings.m_vesselColumnSizes[i]);
}
header->moveSection(header->visualIndex(i), m_settings.m_vesselColumnIndexes[i]);
}
blockApplySettings(false);
arrangeRollups();
}
void AISGUI::leaveEvent(QEvent*)
{
}
void AISGUI::enterEvent(QEvent*)
{
}
void AISGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setColor(m_settings.m_rgbColor);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = dialog.getColor().rgb();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void AISGUI::updateStatus()
{
}
void AISGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
AIS::MsgConfigureAIS* message = AIS::MsgConfigureAIS::create(m_settings, force);
m_ais->getInputMessageQueue()->push(message);
}
}
void AISGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
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_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-"));
ui->vessels->setItem(row, VESSEL_COL_COURSE, new QTableWidgetItem("360.0"));
ui->vessels->setItem(row, VESSEL_COL_SPEED, new QTableWidgetItem("120"));
ui->vessels->setItem(row, VESSEL_COL_HEADING, new QTableWidgetItem("360"));
ui->vessels->setItem(row, VESSEL_COL_STATUS, new QTableWidgetItem("Under way using engine"));
ui->vessels->setItem(row, VESSEL_COL_IMO, new QTableWidgetItem("123456789"));
ui->vessels->setItem(row, VESSEL_COL_NAME, new QTableWidgetItem("12345678901234567890"));
ui->vessels->setItem(row, VESSEL_COL_CALLSIGN, new QTableWidgetItem("1234567"));
ui->vessels->setItem(row, VESSEL_COL_SHIP_TYPE, new QTableWidgetItem("Passenger"));
ui->vessels->setItem(row, VESSEL_COL_DESTINATION, new QTableWidgetItem("12345678901234567890"));
ui->vessels->resizeColumnsToContents();
ui->vessels->removeRow(row);
}
// Columns in table reordered
void AISGUI::vessels_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_vesselColumnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void AISGUI::vessels_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_vesselColumnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void AISGUI::vesselsColumnSelectMenu(QPoint pos)
{
vesselsMenu->popup(ui->vessels->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void AISGUI::vesselsColumnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->vessels->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *AISGUI::createCheckableItem(QString &text, int idx, bool checked, const char *slot)
{
QAction *action = new QAction(text, this);
action->setCheckable(true);
action->setChecked(checked);
action->setData(QVariant(idx));
connect(action, SIGNAL(triggered()), this, slot);
return action;
}
void AISGUI::updateVessels(AISMessage *ais)
{
QTableWidgetItem *mmsiItem;
QTableWidgetItem *typeItem;
QTableWidgetItem *latitudeItem;
QTableWidgetItem *longitudeItem;
QTableWidgetItem *courseItem;
QTableWidgetItem *speedItem;
QTableWidgetItem *headingItem;
QTableWidgetItem *statusItem;
QTableWidgetItem *imoItem;
QTableWidgetItem *nameItem;
QTableWidgetItem *callsignItem;
QTableWidgetItem *shipTypeItem;
QTableWidgetItem *destinationItem;
// See if vessel is already in table
QString messageMMSI = QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0'));
bool found = false;
for (int row = 0; row < ui->vessels->rowCount(); row++)
{
QString itemMMSI = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
if (messageMMSI == itemMMSI)
{
// Update existing item
mmsiItem = ui->vessels->item(row, VESSEL_COL_MMSI);
typeItem = ui->vessels->item(row, VESSEL_COL_TYPE);
latitudeItem = ui->vessels->item(row, VESSEL_COL_LATITUDE);
longitudeItem = ui->vessels->item(row, VESSEL_COL_LONGITUDE);
courseItem = ui->vessels->item(row, VESSEL_COL_COURSE);
speedItem = ui->vessels->item(row, VESSEL_COL_SPEED);
headingItem = ui->vessels->item(row, VESSEL_COL_HEADING);
statusItem = ui->vessels->item(row, VESSEL_COL_STATUS);
imoItem = ui->vessels->item(row, VESSEL_COL_IMO);
nameItem = ui->vessels->item(row, VESSEL_COL_NAME);
callsignItem = ui->vessels->item(row, VESSEL_COL_CALLSIGN);
shipTypeItem = ui->vessels->item(row, VESSEL_COL_SHIP_TYPE);
destinationItem = ui->vessels->item(row, VESSEL_COL_DESTINATION);
found = true;
break;
}
}
if (!found)
{
// Add new vessel
ui->vessels->setSortingEnabled(false);
int row = ui->vessels->rowCount();
ui->vessels->setRowCount(row + 1);
mmsiItem = new QTableWidgetItem();
typeItem = new QTableWidgetItem();
latitudeItem = new QTableWidgetItem();
longitudeItem = new QTableWidgetItem();
courseItem = new QTableWidgetItem();
speedItem = new QTableWidgetItem();
headingItem = new QTableWidgetItem();
statusItem = new QTableWidgetItem();
imoItem = new QTableWidgetItem();
nameItem = new QTableWidgetItem();
callsignItem = new QTableWidgetItem();
shipTypeItem = new QTableWidgetItem();
destinationItem = new QTableWidgetItem();
ui->vessels->setItem(row, VESSEL_COL_MMSI, mmsiItem);
ui->vessels->setItem(row, VESSEL_COL_TYPE, typeItem);
ui->vessels->setItem(row, VESSEL_COL_LATITUDE, latitudeItem);
ui->vessels->setItem(row, VESSEL_COL_LONGITUDE, longitudeItem);
ui->vessels->setItem(row, VESSEL_COL_COURSE, courseItem);
ui->vessels->setItem(row, VESSEL_COL_SPEED, speedItem);
ui->vessels->setItem(row, VESSEL_COL_HEADING, headingItem);
ui->vessels->setItem(row, VESSEL_COL_STATUS, statusItem);
ui->vessels->setItem(row, VESSEL_COL_IMO, imoItem);
ui->vessels->setItem(row, VESSEL_COL_NAME, nameItem);
ui->vessels->setItem(row, VESSEL_COL_CALLSIGN, callsignItem);
ui->vessels->setItem(row, VESSEL_COL_SHIP_TYPE, shipTypeItem);
ui->vessels->setItem(row, VESSEL_COL_DESTINATION, destinationItem);
}
mmsiItem->setText(QString("%1").arg(ais->m_mmsi, 9, 10, QChar('0')));
if ((ais->m_id <= 3) || (ais->m_id == 5) || (ais->m_id == 18) || (ais->m_id == 19)) {
typeItem->setText("Vessel");
} else if (ais->m_id == 4) {
typeItem->setText("Base station");
} else if (ais->m_id == 9) {
typeItem->setText("Aircraft");
} else if (ais->m_id == 21) {
typeItem->setText("Aid-to-nav");
}
if (ais->m_id == 21)
{
AISAidsToNavigationReport *aids = dynamic_cast<AISAidsToNavigationReport*>(ais);
if (aids) {
nameItem->setText(aids->m_name);
}
}
if (ais->m_id == 5)
{
AISShipStaticAndVoyageData *vd = dynamic_cast<AISShipStaticAndVoyageData*>(ais);
if (vd)
{
if (vd->m_imo != 0) {
imoItem->setData(Qt::DisplayRole, vd->m_imo);
}
nameItem->setText(vd->m_name);
callsignItem->setText(vd->m_callsign);
shipTypeItem->setText(AISMessage::typeToString(vd->m_type));
destinationItem->setText(vd->m_destination);
}
}
else
{
if (ais->hasPosition())
{
latitudeItem->setData(Qt::DisplayRole, ais->getLatitude());
longitudeItem->setData(Qt::DisplayRole, ais->getLongitude());
}
if (ais->hasCourse()) {
courseItem->setData(Qt::DisplayRole, ais->getCourse());
}
if (ais->hasSpeed()) {
speedItem->setData(Qt::DisplayRole, ais->getSpeed());
}
if (ais->hasHeading()) {
headingItem->setData(Qt::DisplayRole, ais->getHeading());
}
AISPositionReport *pr = dynamic_cast<AISPositionReport*>(ais);
if (pr) {
statusItem->setText(AISPositionReport::getStatusString(pr->m_status));
}
AISLongRangePositionReport *lrpr = dynamic_cast<AISLongRangePositionReport*>(ais);
if (lrpr) {
statusItem->setText(AISPositionReport::getStatusString(lrpr->m_status));
}
}
if (ais->m_id == 24)
{
AISStaticDataReport *dr = dynamic_cast<AISStaticDataReport*>(ais);
if (dr)
{
if (dr->m_partNumber == 0)
{
nameItem->setText(dr->m_name);
}
else if (dr->m_partNumber == 1)
{
callsignItem->setText(dr->m_callsign);
shipTypeItem->setText(AISMessage::typeToString(dr->m_type));
}
}
}
ui->vessels->setSortingEnabled(true);
QVariant latitudeV = latitudeItem->data(Qt::DisplayRole);
QVariant longitudeV = longitudeItem->data(Qt::DisplayRole);
QString type = typeItem->text();
if (!latitudeV.isNull() && !longitudeV.isNull() && !type.isEmpty())
{
// Send to Map feature
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_ais, "mapitems");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(QString("%1").arg(mmsiItem->text())));
swgMapItem->setLatitude(latitudeV.toFloat());
swgMapItem->setLongitude(longitudeV.toFloat());
swgMapItem->setAltitude(0);
QString image;
if (type == "Aircraft") {
// I presume search and rescue aircraft are more likely to be helicopters
image = "helicopter.png";
} else if (type == "Base station") {
image = "anchor.png";
} else if (type == "Aid-to-nav") {
image = "bouy.png";
} else {
image = "ship.png";
QString shipType = shipTypeItem->text();
if (!shipType.isEmpty())
{
if (shipType == "Tug") {
image = "tug.png";
} else if (shipType == "Cargo") {
image = "cargo.png";
} else if (shipType == "Tanker") {
image = "tanker.png";
}
}
}
swgMapItem->setImage(new QString(QString("qrc:///ais/map/%1").arg(image)));
swgMapItem->setImageMinZoom(11);
QStringList text;
QVariant courseV = courseItem->data(Qt::DisplayRole);
QVariant speedV = speedItem->data(Qt::DisplayRole);
QVariant headingV = headingItem->data(Qt::DisplayRole);
QString name = nameItem->text();
QString callsign = callsignItem->text();
QString destination = destinationItem->text();
QString shipType = shipTypeItem->text();
QString status = statusItem->text();
if (!name.isEmpty()) {
text.append(QString("Name: %1").arg(name));
}
if (!callsign.isEmpty()) {
text.append(QString("Callsign: %1").arg(callsign));
}
if (!destination.isEmpty()) {
text.append(QString("Destination: %1").arg(destination));
}
if (!courseV.isNull()) {
text.append(QString("Course: %1%2").arg(courseV.toFloat()).arg(QChar(0xb0)));
}
if (!speedV.isNull()) {
text.append(QString("Speed: %1 knts").arg(speedV.toFloat()));
}
if (!headingV.isNull())
{
float heading = headingV.toFloat();
text.append(QString("Heading: %1%2").arg(heading).arg(QChar(0xb0)));
swgMapItem->setImageRotation(heading);
}
if (!shipType.isEmpty()) {
text.append(QString("Ship type: %1").arg(shipType));
}
if (!status.isEmpty()) {
text.append(QString("Status: %1").arg(status));
}
swgMapItem->setText(new QString(text.join("\n")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_ais, swgMapItem);
(*it)->push(msg);
}
}
}
}
void AISGUI::on_vessels_cellDoubleClicked(int row, int column)
{
if (column == VESSEL_COL_MMSI)
{
// Get MMSI of vessel in row double clicked
QString mmsi = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
// Search for MMSI on www.vesselfinder.com
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(mmsi)));
}
else if ((column == VESSEL_COL_LATITUDE) || (column == VESSEL_COL_LONGITUDE))
{
// Get MMSI of vessel in row double clicked
QString mmsi = ui->vessels->item(row, VESSEL_COL_MMSI)->text();
// Find MMSI on Map
FeatureWebAPIUtils::mapFind(mmsi);
}
else if (column == VESSEL_COL_IMO)
{
QString imo = ui->vessels->item(row, VESSEL_COL_IMO)->text();
if (!imo.isEmpty() && (imo != "0"))
{
// Search for IMO on www.vesselfinder.com
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(imo)));
}
}
else if (column == VESSEL_COL_NAME)
{
QString name = ui->vessels->item(row, VESSEL_COL_NAME)->text();
if (!name.isEmpty())
{
// Search for name on www.vesselfinder.com
QDesktopServices::openUrl(QUrl(QString("https://www.vesselfinder.com/vessels?name=%1").arg(name)));
}
}
else if (column == VESSEL_COL_DESTINATION)
{
QString destination = ui->vessels->item(row, VESSEL_COL_DESTINATION)->text();
if (!destination.isEmpty())
{
// Find destination on Map
FeatureWebAPIUtils::mapFind(destination);
}
}
}

View File

@ -0,0 +1,107 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_AISGUI_H_
#define INCLUDE_FEATURE_AISGUI_H_
#include <QTimer>
#include <QMenu>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "util/ais.h"
#include "pipes/pipeendpoint.h"
#include "aissettings.h"
class PluginAPI;
class FeatureUISet;
class AIS;
namespace Ui {
class AISGUI;
}
class AISGUI : public FeatureGUI {
Q_OBJECT
public:
static AISGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
private:
Ui::AISGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
AISSettings m_settings;
bool m_doApplySettings;
AIS* m_ais;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
int m_lastFeatureState;
QMenu *vesselsMenu; // Column select context menu
explicit AISGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~AISGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void updateVessels(AISMessage *ais);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
enum VesselCol {
VESSEL_COL_MMSI,
VESSEL_COL_TYPE,
VESSEL_COL_LATITUDE,
VESSEL_COL_LONGITUDE,
VESSEL_COL_COURSE,
VESSEL_COL_SPEED,
VESSEL_COL_HEADING,
VESSEL_COL_STATUS,
VESSEL_COL_IMO,
VESSEL_COL_NAME,
VESSEL_COL_CALLSIGN,
VESSEL_COL_SHIP_TYPE,
VESSEL_COL_DESTINATION
};
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void updateStatus();
void on_vessels_cellDoubleClicked(int row, int column);
void vessels_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void vessels_sectionResized(int logicalIndex, int oldSize, int newSize);
void vesselsColumnSelectMenu(QPoint pos);
void vesselsColumnSelectMenuChecked(bool checked = false);
};
#endif // INCLUDE_FEATURE_AISGUI_H_

View File

@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AISGUI</class>
<widget class="RollupWidget" name="AISGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>484</width>
<height>328</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>2000</width>
<height>500</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>GS-232 Rotator Controller</string>
</property>
<widget class="QWidget" name="tableContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>461</width>
<height>291</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Vessels, Base stations and Aids-to-navigation</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QTableWidget" name="vessels">
<property name="toolTip">
<string>Vessels</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>MMSI</string>
</property>
<property name="toolTip">
<string>Maritime Mobile Service Identity</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Lat</string>
</property>
<property name="toolTip">
<string>Latitude in degrees. East positive</string>
</property>
</column>
<column>
<property name="text">
<string>Lon</string>
</property>
<property name="toolTip">
<string>Longitude in degrees. North positive.</string>
</property>
</column>
<column>
<property name="text">
<string>Course</string>
</property>
<property name="toolTip">
<string>Course in degrees.</string>
</property>
</column>
<column>
<property name="text">
<string>Speed</string>
</property>
<property name="toolTip">
<string>Speed in knots.</string>
</property>
</column>
<column>
<property name="text">
<string>Heading</string>
</property>
<property name="toolTip">
<string>Heading in degrees.</string>
</property>
</column>
<column>
<property name="text">
<string>Status</string>
</property>
</column>
<column>
<property name="text">
<string>IMO</string>
</property>
<property name="toolTip">
<string>International Maritime Organization number</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
<property name="toolTip">
<string>Name of the vessel</string>
</property>
</column>
<column>
<property name="text">
<string>Callsign</string>
</property>
</column>
<column>
<property name="text">
<string>Ship Type</string>
</property>
</column>
<column>
<property name="text">
<string>Destination</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "aisgui.h"
#endif
#include "ais.h"
#include "aisplugin.h"
#include "aiswebapiadapter.h"
const PluginDescriptor AISPlugin::m_pluginDescriptor = {
AIS::m_featureId,
QStringLiteral("AIS"),
QStringLiteral("6.11.1"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
AISPlugin::AISPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& AISPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void AISPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(AIS::m_featureIdURI, AIS::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* AISPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* AISPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return AISGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* AISPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new AIS(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* AISPlugin::createFeatureWebAPIAdapter() const
{
return new AISWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_AISPLUGIN_H
#define INCLUDE_FEATURE_AISPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class AISPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.ais")
public:
explicit AISPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_AISPLUGIN_H

View File

@ -0,0 +1,122 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QDataStream>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "aissettings.h"
const QStringList AISSettings::m_pipeTypes = {
QStringLiteral("AISDemod")
};
const QStringList AISSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.aisdemod")
};
AISSettings::AISSettings()
{
resetToDefaults();
}
void AISSettings::resetToDefaults()
{
m_title = "AIS";
m_rgbColor = QColor(102, 0, 0).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
{
m_vesselColumnIndexes[i] = i;
m_vesselColumnSizes[i] = -1; // Autosize
}
}
QByteArray AISSettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(20, m_title);
s.writeU32(21, m_rgbColor);
s.writeBool(22, m_useReverseAPI);
s.writeString(23, m_reverseAPIAddress);
s.writeU32(24, m_reverseAPIPort);
s.writeU32(25, m_reverseAPIFeatureSetIndex);
s.writeU32(26, m_reverseAPIFeatureIndex);
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
s.writeS32(300 + i, m_vesselColumnIndexes[i]);
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
s.writeS32(400 + i, m_vesselColumnSizes[i]);
return s.final();
}
bool AISSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
QByteArray blob;
d.readString(20, &m_title, "AIS");
d.readU32(21, &m_rgbColor, QColor(102, 0, 0).rgb());
d.readBool(22, &m_useReverseAPI, false);
d.readString(23, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(24, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(25, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(26, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
d.readS32(300 + i, &m_vesselColumnIndexes[i], i);
for (int i = 0; i < AIS_VESSEL_COLUMNS; i++)
d.readS32(400 + i, &m_vesselColumnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,54 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_AISSETTINGS_H_
#define INCLUDE_FEATURE_AISSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "util/message.h"
class Serializable;
// Number of columns in the tables
#define AIS_VESSEL_COLUMNS 13
struct AISSettings
{
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
int m_vesselColumnIndexes[AIS_VESSEL_COLUMNS];
int m_vesselColumnSizes[AIS_VESSEL_COLUMNS];
AISSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static const QStringList m_pipeTypes;
static const QStringList m_pipeURIs;
};
#endif // INCLUDE_FEATURE_AISSETTINGS_H_

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGFeatureSettings.h"
#include "ais.h"
#include "aiswebapiadapter.h"
AISWebAPIAdapter::AISWebAPIAdapter()
{}
AISWebAPIAdapter::~AISWebAPIAdapter()
{}
int AISWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAisSettings(new SWGSDRangel::SWGAISSettings());
response.getAisSettings()->init();
AIS::webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int AISWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
AIS::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AIS_WEBAPIADAPTER_H
#define INCLUDE_AIS_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "aissettings.h"
/**
* Standalone API adapter only for the settings
*/
class AISWebAPIAdapter : public FeatureWebAPIAdapter {
public:
AISWebAPIAdapter();
virtual ~AISWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
private:
AISSettings m_settings;
};
#endif // INCLUDE_AIS_WEBAPIADAPTER_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 991 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

View File

@ -0,0 +1,44 @@
<h1>AIS Plugin</h1>
<h2>Introduction</h2>
The AIS feature displays a table containing the most recent information about vessels, base-stations and aids-to-navigation, based on messages received via AIS Demodulators.
The AIS feature can draw corresponding objects on the Map.
<h2>Interface</h2>
![AIS feature plugin GUI](../../../doc/img/AIS_plugin.png)
<h3>Vessels Table</h3>
The vessels table displays the current status for each vessel, base station or aid-to-navigation, based on the latest received messages, aggregated from all AIS Demodulators.
* MMSI - The Maritime Mobile Service Identity number of the vessel or base station. Double clicking on this column will search for the MMSI on VesselFinder.
* Type - Vessel, Base station or Aid-to-Navigation.
* Lat - Latitude in degrees. East positive. Double clicking on this column will center the map on this object.
* Lon - Longitude in degrees. West positive. Double clicking on this column will center the map on this object.
* Course - Course over ground in degrees.
* Speed - Speed over ground in knots.
* Heading - Heading in degrees (Heading is the direction the vessel is facing, whereas course is the direction it is moving in).
* Status - Status of the vessel (E.g. Underway using engine, At anchor).
* IMO - International Maritime Organization (IMO) number which uniquely identifies a ship. Double clicking on this column will search for the IMO on https://www.vesselfinder.com/
* Name - Name of the vessel. Double clicking on this column will search for the name on https://www.vesselfinder.com/
* Callsign - Callsign of the vessel.
* Ship Type - Type of ship (E.g. Passenger ship, Cargo ship, Tanker) and activity (Fishing, Towing, Sailing).
* Destination - Destination the vessel is travelling to. Double clicking on this column will search for this location on the map on this object.
Right clicking on the table header allows you to select which columns to show. The columns can be reorderd by left clicking and dragging the column header.
<h3>Map</h3>
The AIS feature can plot ships, base stations and aids-to-navigation on the Map. To use, simply open a Map feature and the AIS plugin will display objects based upon the messages it receives from that point.
Selecting an AIS item on the map will display a text bubble containing information from the above table. To centre the map on an item in the table, double click in the Lat or Lon columns.
![AIS map](../../../doc/img/AIS_plugin_map.png)
<h2>Attribution</h2>
Map icons are by Maarten van der Werf, DE Alvida Biersack, ID and jokokerto, MY, from the Noun Project https://thenounproject.com/
Map icons are from http://all-free-download.com.

View File

@ -23,6 +23,8 @@
#include "demodanalyzersettings.h"
const QStringList DemodAnalyzerSettings::m_channelTypes = {
QStringLiteral("AISDemod"),
QStringLiteral("AISMod"),
QStringLiteral("AMDemod"),
QStringLiteral("AMMod"),
QStringLiteral("DABDemod"),
@ -38,6 +40,8 @@ const QStringList DemodAnalyzerSettings::m_channelTypes = {
};
const QStringList DemodAnalyzerSettings::m_channelURIs = {
QStringLiteral("sdrangel.channel.aisdemod"),
QStringLiteral("sdrangel.channel.modais"),
QStringLiteral("sdrangel.channel.amdemod"),
QStringLiteral("sdrangel.channeltx.modam"),
QStringLiteral("sdrangel.channel.dabdemod"),

View File

@ -25,6 +25,7 @@
const QStringList MapSettings::m_pipeTypes = {
QStringLiteral("ADSBDemod"),
QStringLiteral("AIS"),
QStringLiteral("APRS"),
QStringLiteral("StarTracker"),
QStringLiteral("SatelliteTracker")
@ -32,6 +33,7 @@ const QStringList MapSettings::m_pipeTypes = {
const QStringList MapSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.adsbdemod"),
QStringLiteral("sdrangel.feature.ais"),
QStringLiteral("sdrangel.feature.aprs"),
QStringLiteral("sdrangel.feature.startracker"),
QStringLiteral("sdrangel.feature.satellitetracker")

View File

@ -58,11 +58,12 @@ struct MapSettings
// The first few should match the order in m_pipeTypes for MapGUI::getSourceMask to work
static const quint32 SOURCE_ADSB = 0x1;
static const quint32 SOURCE_APRS = 0x2;
static const quint32 SOURCE_STAR_TRACKER = 0x4;
static const quint32 SOURCE_SATELLITE_TRACKER = 0x8;
static const quint32 SOURCE_BEACONS = 0x10;
static const quint32 SOURCE_STATION = 0x20;
static const quint32 SOURCE_AIS = 0x2;
static const quint32 SOURCE_APRS = 0x4;
static const quint32 SOURCE_STAR_TRACKER = 0x8;
static const quint32 SOURCE_SATELLITE_TRACKER = 0x10;
static const quint32 SOURCE_BEACONS = 0x20;
static const quint32 SOURCE_STATION = 0x40;
};
#endif // INCLUDE_FEATURE_MAPSETTINGS_H_

View File

@ -49,6 +49,14 @@
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>AIS</string>
</property>
<property name="checkState">
<enum>Checked</enum>
</property>
</item>
<item>
<property name="text">
<string>APRS</string>

View File

@ -7,11 +7,12 @@ On top of this, it can plot data from other plugins, such as:
* APRS symbols from the APRS Feature,
* Aircraft from the ADS-B Demodulator,
* Ships from the AIS Demodulator,
* Satellites from the Satellite Tracker,
* The Sun, Moon and Stars from the Star Tracker,
* Beacons based on the IARU Region 1 beacon database.
It can also create tracks showing the path aircraft and APRS objects have taken, as well as predicted paths for satellites.
It can also create tracks showing the path aircraft, ships and APRS objects have taken, as well as predicted paths for satellites.
![Map feature](../../../doc/img/Map_plugin_beacons.png)

View File

@ -2,7 +2,7 @@
<h2>Introduction</h2>
The Packet Error Rate (PER) Tester feature can be used to measure the packet error rate over digital, packet based protcols such as AX.25 (Packet mod/demod), LoRa (ChipChat mod/demod) and 802.15.4.
The Packet Error Rate (PER) Tester feature can be used to measure the packet error rate over digital, packet based protcols such as AX.25 (Packet mod/demod), LoRa (ChipChat mod/demod), AIS and 802.15.4.
The PER Tester feature allows you to define the contents of the packets to transmit, which can include a per-packet 32-bit identifier, as well as a user-defined or random payload, how many packets are sent and the interval between them.
@ -42,7 +42,7 @@ Specify the interval in seconds between packet transmissions.
Specify the contents of the packet to transmit and expect to be received. Data should be entered in hexidecimal bytes (E.g: 00 11 22 33 44).
The exact format required will depend on the underlying protocol being used. For AX.25 using the Packet modulator, LoRo using the ChirpChat modulator and 802.15.4, it is not necessary to include the trailing CRC, as this is appended automatically by the SDRangel modulators.
The exact format required will depend on the underlying protocol being used. For AX.25 using the Packet modulator, LoRo using the ChirpChat modulator, AIS and 802.15.4, it is not necessary to include the trailing CRC, as this is appended automatically by the SDRangel modulators.
Aside from hex values, a number of variables can be used:
@ -74,6 +74,7 @@ This can be used in cases where the demodulator outputs a different byte sequenc
* For AX.25 (with Packet mod/demod), set Leading to 0 and Trailing to 2.
* For LoRa (with ChirpChat mod/demod), set Leading to 0 and Trailing to 0.
* For AIS set Leading to 0 and Trailing to 0.
<h3>8: TX UDP port</h3>

View File

@ -176,6 +176,7 @@ set(sdrbase_SOURCES
settings/preset.cpp
settings/mainsettings.cpp
util/ais.cpp
util/ax25.cpp
util/aprs.cpp
util/astronomy.cpp
@ -313,6 +314,7 @@ set(sdrbase_HEADERS
dsp/kissfft.h
dsp/kissengine.h
dsp/firfilter.h
dsp/gaussian.h
dsp/mimochannel.h
dsp/misc.h
dsp/movingaverage.h
@ -372,6 +374,7 @@ set(sdrbase_HEADERS
settings/preset.h
settings/mainsettings.h
util/ais.h
util/ax25.h
util/aprs.h
util/astronomy.h

121
sdrbase/dsp/gaussian.h Normal file
View File

@ -0,0 +1,121 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_GAUSSIAN_H
#define INCLUDE_GAUSSIAN_H
#include <math.h>
#include "dsp/dsptypes.h"
// Standard values for bt
#define GAUSSIAN_BT_BLUETOOTH 0.5
#define GAUSSIAN_BT_GSM 0.3
#define GAUSSIAN_BT_CCSDS 0.25
#define GAUSSIAN_BT_802_15_4 0.5
#define GAUSSIAN_BT_AIS 0.5
// Gaussian low-pass filter for pulse shaping
// https://onlinelibrary.wiley.com/doi/pdf/10.1002/9780470041956.app2
// Unlike raisedcosine.h, this should be feed NRZ values rather than impulse stream, as described here:
// https://www.mathworks.com/matlabcentral/answers/107231-why-does-the-pulse-shape-generated-by-gaussdesign-differ-from-that-used-in-the-comm-gmskmodulator-ob
template <class Type> class Gaussian {
public:
Gaussian() : m_ptr(0) { }
// bt - 3dB bandwidth symbol time product
// symbolSpan - number of symbols over which the filter is spread
// samplesPerSymbol - number of samples per symbol
void create(double bt, int symbolSpan, int samplesPerSymbol)
{
int nTaps = symbolSpan * samplesPerSymbol + 1;
int i;
// check constraints
if(!(nTaps & 1)) {
qDebug("Gaussian filter has to have an odd number of taps");
nTaps++;
}
// make room
m_samples.resize(nTaps);
for(int i = 0; i < nTaps; i++)
m_samples[i] = 0;
m_ptr = 0;
m_taps.resize(nTaps / 2 + 1);
// See eq B.2 - this is alpha over Ts
double alpha_t = std::sqrt(std::log(2.0) / 2.0) / (bt);
double sqrt_pi_alpha_t = std::sqrt(M_PI) / alpha_t;
// calculate filter taps
for(i = 0; i < nTaps / 2 + 1; i++)
{
double t = (i - (nTaps / 2)) / (double)samplesPerSymbol;
// See eq B.5
m_taps[i] = sqrt_pi_alpha_t * std::exp(-std::pow(t * M_PI / alpha_t, 2.0));
}
// normalize
double sum = 0;
for(i = 0; i < (int)m_taps.size() - 1; i++)
sum += m_taps[i] * 2;
sum += m_taps[i];
for(i = 0; i < (int)m_taps.size(); i++)
m_taps[i] /= sum;
}
Type filter(Type sample)
{
Type acc = 0;
unsigned int n_samples = m_samples.size();
unsigned int n_taps = m_taps.size() - 1;
unsigned int a = m_ptr;
unsigned int b = a == n_samples - 1 ? 0 : a + 1;
m_samples[m_ptr] = sample;
for (unsigned int i = 0; i < n_taps; ++i)
{
acc += (m_samples[a] + m_samples[b]) * m_taps[i];
a = (a == 0) ? n_samples - 1 : a - 1;
b = (b == n_samples - 1) ? 0 : b + 1;
}
acc += m_samples[a] * m_taps[n_taps];
m_ptr = (m_ptr == n_samples - 1) ? 0 : m_ptr + 1;
return acc;
}
/*
void printTaps()
{
for (int i = 0; i < m_taps.size(); i++)
printf("%.4f ", m_taps[i]);
printf("\n");
}
*/
private:
std::vector<Real> m_taps;
std::vector<Type> m_samples;
unsigned int m_ptr;
};
#endif // INCLUDE_GAUSSIAN_H

757
sdrbase/util/ais.cpp Normal file
View File

@ -0,0 +1,757 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "ais.h"
AISMessage::AISMessage(const QByteArray ba)
{
// All AIS messages have these 3 fields in common
m_id = (ba[0] & 0xff) >> 2 & 0x3f;
m_repeatIndicator = ba[0] & 0x3;
m_mmsi = ((ba[1] & 0xff) << 22) | ((ba[2] & 0xff) << 14) | ((ba[3] & 0xff) << 6) | ((ba[4] & 0xff) >> 2);
m_bytes = ba;
}
QString AISMessage::toHex()
{
return m_bytes.toHex();
}
// See: https://gpsd.gitlab.io/gpsd/AIVDM.html
QString AISMessage::toNMEA(QByteArray bytes)
{
QStringList nmeaSentences;
// Max payload is ~61 chars -> 366 bits
int sentences = bytes.size() / 45 + 1;
int sentence = 1;
int bits = 8;
int i = 0;
while (i < bytes.size())
{
QStringList nmeaSentence;
QStringList nmea;
QStringList payload;
nmea.append(QString("AIVDM,%1,%2,%3,,").arg(sentences).arg(sentence).arg(sentences > 1 ? "1" : ""));
int maxPayload = 80 - 1 - nmea[0].length() - 5;
// Encode message data in 6-bit ASCII
while ((payload.size() < maxPayload) && (i < bytes.size()))
{
int c = 0;
for (int j = 0; j < 6; j++)
{
c = (c << 1) | ((bytes[i] >> (bits - 1)) & 0x1);
bits--;
if (bits == 0)
{
i++;
bits = 8;
}
}
if (c >= 40) {
c += 56;
} else {
c += 48;
}
payload.append(QChar(c));
}
nmea.append(payload);
nmea.append(QString(",%1").arg((i == bytes.size()) ? (8 - bits) : 0)); // Number of filler bits to ignore
// Calculate checksum
QString nmeaProtected = nmea.join("");
int checksum = AISMessage::nmeaChecksum(nmeaProtected);
// Construct complete sentence with leading ! and trailing checksum
nmeaSentence.append("!");
nmeaSentence.append(nmeaProtected);
nmeaSentence.append(QString("*%1").arg(checksum, 2, 16, QChar('0')));
nmeaSentences.append(nmeaSentence.join(""));
sentence++;
}
return nmeaSentences.join("\n");
}
QString AISMessage::toNMEA()
{
return AISMessage::toNMEA(m_bytes);
}
qint8 AISMessage::nmeaChecksum(QString string)
{
qint8 checksum = 0;
for (int i = 0; i < string.length(); i++)
{
qint8 c = (qint8)string[i].toLatin1();
checksum ^= c;
}
return checksum;
}
// Type as in message 5 and 19
QString AISMessage::typeToString(quint8 type)
{
if (type == 0) {
return "N/A";
} else if ((type >= 100) && (type < 199)) {
return "Preserved for regional use";
} else if (type >= 200) {
return "Preserved for future use";
} else if ((type >= 50) && (type <= 59)) {
const QStringList specialCrafts = {
"Pilot vessel",
"Search and rescue vessel",
"Tug",
"Port tender",
"Anti-pollution vessel",
"Law enforcement vessel",
"Spare (56)",
"Spare (57)",
"Medical transport",
"Ships and aircraft of States not parties to an armed conflict"
};
return specialCrafts[type-50];
} else {
int firstDigit = type / 10;
int secondDigit = type % 10;
const QStringList shipType = {
"0",
"Reserved (1)",
"WIG",
"Vessel",
"HSC",
"5",
"Passenger",
"Cargo",
"Tanker",
"Other"
};
const QStringList activity = {
"Fishing",
"Towing",
"Towing",
"Dredging or underwater operations",
"Diving operations",
"Military operations",
"Sailing",
"Pleasure craft",
"Reserved (8)",
"Reserved (9)"
};
if (firstDigit == 3) {
return shipType[firstDigit] + " - " + activity[secondDigit];
} else {
return shipType[firstDigit];
}
}
}
AISMessage* AISMessage::decode(const QByteArray ba)
{
int id = (ba[0] >> 2) & 0x3f;
if ((id == 1) || (id == 2) || (id == 3)) {
return new AISPositionReport(ba);
} else if ((id == 4) || (id == 11)) {
return new AISBaseStationReport(ba);
} else if (id == 5) {
return new AISShipStaticAndVoyageData(ba);
} else if (id == 6) {
return new AISBinaryMessage(ba);
} else if (id == 7) {
return new AISBinaryAck(ba);
} else if (id == 8) {
return new AISBinaryBroadcast(ba);
} else if (id == 9) {
return new AISSARAircraftPositionReport(ba);
} else if (id == 10) {
return new AISUTCInquiry(ba);
} else if (id == 12) {
return new AISSafetyMessage(ba);
} else if (id == 13) {
return new AISSafetyAck(ba);
} else if (id == 14) {
return new AISSafetyBroadcast(ba);
} else if (id == 15) {
return new AISInterrogation(ba);
} else if (id == 16) {
return new AISAssignedModeCommand(ba);
} else if (id == 17) {
return new AISGNSSBroadcast(ba);
} else if (id == 18) {
return new AISStandardClassBPositionReport(ba);
} else if (id == 19) {
return new AISExtendedClassBPositionReport(ba);
} else if (id == 20) {
return new AISDatalinkManagement(ba);
} else if (id == 21) {
return new AISAidsToNavigationReport(ba);
} else if (id == 22) {
return new AISChannelManagement(ba);
} else if (id == 23) {
return new AISGroupAssignment(ba);
} else if (id == 24) {
return new AISStaticDataReport(ba);
} else if (id == 25) {
return new AISSingleSlotBinaryMessage(ba);
} else if (id == 26) {
return new AISMultipleSlotBinaryMessage(ba);
} else if (id == 27) {
return new AISLongRangePositionReport(ba);
} else {
return new AISUnknownMessageID(ba);
}
}
// Extract 6-bit ASCII string
QString AISMessage::getString(const QByteArray ba, int byteIdx, int bitsLeft, int chars)
{
QString s;
for (int i = 0; i < chars; i++)
{
// Extract 6-bits
int c = 0;
for (int j = 0; j < 6; j++)
{
c = (c << 1) | ((ba[byteIdx] >> (bitsLeft - 1)) & 0x1);
bitsLeft--;
if (bitsLeft == 0)
{
byteIdx++;
bitsLeft = 8;
}
}
// Map from 6-bit to 8-bit ASCII
if (c < 32) {
c |= 0x40;
}
s.append(c);
}
// Remove leading/trailing spaces
s = s.trimmed();
// Remave @s, which indiciate no character
while (s.endsWith("@")) {
s = s.left(s.length() - 1);
}
while (s.startsWith("@")) {
s = s.mid(1);
}
return s;
}
AISPositionReport::AISPositionReport(QByteArray ba) :
AISMessage(ba)
{
m_status = ((ba[4] & 0x3) << 2) | ((ba[5] >> 6) & 0x3);
int rateOfTurn = ((ba[5] << 2) & 0xfc) | ((ba[6] >> 6) & 0x3);
if (rateOfTurn == 127) {
m_rateOfTurn = 720.0f;
} else if (rateOfTurn == -127) {
m_rateOfTurn = -720.0f;
} else {
m_rateOfTurn = (rateOfTurn / 4.733f) * (rateOfTurn / 4.733f);
}
m_rateOfTurnAvailable = rateOfTurn != 0x80;
int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf);
m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f;
m_positionAccuracy = (ba[7] >> 3) & 0x1;
int32_t longitude = ((ba[7] & 0x7) << 25) | ((ba[8] & 0xff) << 17) | ((ba[9] & 0xff) << 9) | ((ba[10] & 0xff) << 1) | ((ba[11] >> 7) & 1);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[11] & 0x7f) << 20) | ((ba[12] & 0xff) << 12) | ((ba[13] & 0xff) << 4) | ((ba[14] >> 4) & 0x4);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
int cog = ((ba[14] & 0xf) << 8) | (ba[15] & 0xff);
m_courseAvailable = cog != 3600;
m_course = cog * 0.1f;
m_heading = ((ba[16] & 0xff) << 1) | ((ba[17] >> 7) & 0x1);
m_headingAvailable = m_heading != 511;
m_timeStamp = (ba[17] >> 1) & 0x3f;
m_specialManoeuvre = ((ba[17] & 0x1) << 1) | ((ba[18] >> 7) & 0x1);
}
QString AISPositionReport::getStatusString(int status)
{
const QStringList statuses = {
"Under way using engine",
"At anchor",
"Not under command",
"Restricted manoeuvrability",
"Constrained by her draught",
"Moored",
"Aground",
"Engaged in fishing",
"Under way sailing",
"Reserved for future amendment of navigational status for ships carrying DG, HS, or MP, or IMO hazard or pollutant category C (HSC)",
"Reserved for future amendment of navigational status for carrying DG, HS or MP, or IMO hazard or pollutant category A (WIG)",
"Reserved for future use",
"Reserved for future use",
"Reserved for future use",
"Reserved for future use",
"Not defined"
};
return statuses[status];
}
QString AISPositionReport::toString()
{
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(m_course)
.arg(AISPositionReport::getStatusString(m_status))
.arg(QChar(0xb0));
}
AISBaseStationReport::AISBaseStationReport(QByteArray ba) :
AISMessage(ba)
{
int year = ((ba[4] & 0x3) << 12) | ((ba[5] & 0xff) << 4) | ((ba[6] >> 4) & 0xf);
int month = ba[6] & 0xf;
int day = ((ba[7] >> 3) & 0x1f);
int hour = ((ba[7] & 0x7) << 2) | ((ba[8] >> 6) & 0x3);
int minute = ba[8] & 0x3f;
int second = (ba[9] >> 2) & 0x3f;
m_utc = QDateTime(QDate(year, month, day), QTime(hour, minute, second), Qt::UTC);
m_positionAccuracy = (ba[9] >> 1) & 0x1;
int32_t longitude = ((ba[9] & 0x1) << 27) | ((ba[10] & 0xff) << 19) | ((ba[11] & 0xff) << 11) | ((ba[12] & 0xff) << 3) | ((ba[13] >> 5) & 0x7);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[13] & 0x1f) << 22) | ((ba[14] & 0xff) << 14) | ((ba[15] & 0xff) << 6) | ((ba[16] >> 2) & 0x3f);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
}
QString AISBaseStationReport::toString()
{
return QString("Lat: %1%3 Lon: %2%3 %4")
.arg(m_latitude)
.arg(m_longitude)
.arg(QChar(0xb0))
.arg(m_utc.toString());
}
AISShipStaticAndVoyageData::AISShipStaticAndVoyageData(QByteArray ba) :
AISMessage(ba)
{
m_version = ba[4] & 0x3;
m_imo = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f);
m_callsign = AISMessage::getString(ba, 8, 2, 7);
m_name = AISMessage::getString(ba, 14, 8, 20);
m_type = ba[29] & 0xff;
m_dimension = ((ba[30] & 0xff) << 22) | ((ba[31] & 0xff) << 14) | ((ba[32] & 0xff) << 6) | ((ba[33] >> 2) & 0x3f);
m_positionFixing = ((ba[33] & 0x3) << 2) | ((ba[34] >> 6) & 0x3);
m_eta = ((ba[34] & 0x3f) << 14) | ((ba[35] & 0xff) << 6) | ((ba[36] >> 2) & 0x3f);
m_draught = ((ba[36] & 0x3) << 6) | ((ba[37] >> 2) & 0x3f);
m_destination = AISMessage::getString(ba, 37, 2, 20);
}
QString AISShipStaticAndVoyageData::toString()
{
return QString("IMO: %1 Callsign: %2 Name: %3 Type: %4 Destination: %5")
.arg(m_imo == 0 ? "N/A" : QString::number(m_imo))
.arg(m_callsign)
.arg(m_name)
.arg(AISMessage::typeToString(m_type))
.arg(m_destination);
}
AISBinaryMessage::AISBinaryMessage(QByteArray ba) :
AISMessage(ba)
{
m_sequenceNumber = ba[4] & 0x3;
m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f);
m_retransmitFlag = (ba[8] >> 1) & 0x1;
}
QString AISBinaryMessage::toString()
{
return QString("Seq No: %1 Destination: %2 Retransmit: %3")
.arg(m_sequenceNumber)
.arg(m_destinationId)
.arg(m_retransmitFlag);
}
AISBinaryAck::AISBinaryAck(QByteArray ba) :
AISMessage(ba)
{
}
AISBinaryBroadcast::AISBinaryBroadcast(QByteArray ba) :
AISMessage(ba)
{
}
AISSARAircraftPositionReport::AISSARAircraftPositionReport(QByteArray ba) :
AISMessage(ba)
{
m_altitude = ((ba[4] & 0x3) << 10) | ((ba[5] & 0xff) << 2) | ((ba[6] >> 6) & 0x3);
m_altitudeAvailable = m_altitude != 4095;
int sog = ((ba[6] & 0x3f) << 4) | ((ba[7] >> 4) & 0xf);
m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f;
m_positionAccuracy = (ba[7] >> 3) & 0x1;
int32_t longitude = ((ba[7] & 0x7) << 25) | ((ba[8] & 0xff) << 17) | ((ba[9] & 0xff) << 9) | ((ba[10] & 0xff) << 1) | ((ba[11] >> 7) & 1);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[11] & 0x7f) << 20) | ((ba[12] & 0xff) << 12) | ((ba[13] & 0xff) << 4) | ((ba[14] >> 4) & 0x4);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
int cog = ((ba[14] & 0xf) << 8) | (ba[15] & 0xff);
m_courseAvailable = cog != 3600;
m_course = cog * 0.1f;
m_timeStamp = (ba[16] >> 2) & 0x3f;
}
QString AISSARAircraftPositionReport::toString()
{
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(m_course)
.arg(m_altitude)
.arg(QChar(0xb0));
}
AISUTCInquiry::AISUTCInquiry(QByteArray ba) :
AISMessage(ba)
{
}
AISSafetyMessage::AISSafetyMessage(QByteArray ba) :
AISMessage(ba)
{
m_sequenceNumber = ba[4] & 0x3;
m_destinationId = ((ba[5] & 0xff) << 22) | ((ba[6] & 0xff) << 14) | ((ba[7] & 0xff) << 6) | ((ba[8] >> 2) & 0x3f);
m_retransmitFlag = (ba[8] >> 1) & 0x1;
m_safetyRelatedText = AISMessage::getString(ba, 9, 0, (ba.size() - 9) * 8 / 6);
}
QString AISSafetyMessage::toString()
{
return QString("To %1: Safety message: %2").arg(m_destinationId).arg(m_safetyRelatedText);
}
AISSafetyAck::AISSafetyAck(QByteArray ba) :
AISMessage(ba)
{
}
AISSafetyBroadcast::AISSafetyBroadcast(QByteArray ba) :
AISMessage(ba)
{
m_safetyRelatedText = AISMessage::getString(ba, 5, 0, (ba.size() - 6) * 8 / 6);
}
QString AISSafetyBroadcast::toString()
{
return QString("Safety message: %1").arg(m_safetyRelatedText);
}
AISInterrogation::AISInterrogation(QByteArray ba) :
AISMessage(ba)
{
}
AISAssignedModeCommand::AISAssignedModeCommand(QByteArray ba) :
AISMessage(ba)
{
}
AISGNSSBroadcast::AISGNSSBroadcast(QByteArray ba) :
AISMessage(ba)
{
}
AISStandardClassBPositionReport::AISStandardClassBPositionReport(QByteArray ba) :
AISMessage(ba)
{
int sog = ((ba[5] & 0x3) << 8) | (ba[6] & 0xff);
m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f;
m_positionAccuracy = (ba[7] >> 7) & 0x1;
int32_t longitude = ((ba[7] & 0x7f) << 21) | ((ba[8] & 0xff) << 13) | ((ba[9] & 0xff) << 5) | ((ba[10] >> 3) & 0x1f);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[10] & 0x3) << 24) | ((ba[11] & 0xff) << 16) | ((ba[12] & 0xff) << 8) | (ba[13] & 0xff);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
int cog = ((ba[14] & 0xff) << 4) | ((ba[15] >> 4) & 0xf);
m_courseAvailable = cog != 3600;
m_course = cog * 0.1f;
m_heading = ((ba[15] & 0xf) << 5) | ((ba[17] >> 3) & 0x1f);
m_headingAvailable = m_heading != 511;
m_timeStamp = ((ba[17] & 0x7) << 3) | ((ba[18] >> 5) & 0x7);
}
QString AISStandardClassBPositionReport::toString()
{
return QString("Lat: %1%5 Lon: %2%5 Speed: %3 knts Course: %4%5")
.arg(m_latitude)
.arg(m_longitude)
.arg(m_speedOverGround)
.arg(m_course)
.arg(QChar(0xb0));
}
AISExtendedClassBPositionReport::AISExtendedClassBPositionReport(QByteArray ba) :
AISMessage(ba)
{
int sog = ((ba[5] & 0x3) << 8) | (ba[6] & 0xff);
m_speedOverGroundAvailable = sog != 1023;
m_speedOverGround = sog * 0.1f;
m_positionAccuracy = (ba[7] >> 7) & 0x1;
int32_t longitude = ((ba[7] & 0x7f) << 21) | ((ba[8] & 0xff) << 13) | ((ba[9] & 0xff) << 5) | ((ba[10] >> 3) & 0x1f);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[10] & 0x3) << 24) | ((ba[11] & 0xff) << 16) | ((ba[12] & 0xff) << 8) | (ba[13] & 0xff);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
int cog = ((ba[14] & 0xff) << 4) | ((ba[15] >> 4) & 0xf);
m_courseAvailable = cog != 3600;
m_course = cog * 0.1f;
m_heading = ((ba[15] & 0xf) << 5) | ((ba[17] >> 3) & 0x1f);
m_headingAvailable = m_heading != 511;
m_timeStamp = ((ba[17] & 0x7) << 3) | ((ba[18] >> 5) & 0x7);
m_name = AISMessage::getString(ba, 18, 1, 20);
}
QString AISExtendedClassBPositionReport::toString()
{
return QString("Lat: %1%5 Lon: %2%5 Speed: %3 knts Course: %4%5 Name: %6")
.arg(m_latitude)
.arg(m_longitude)
.arg(m_speedOverGround)
.arg(m_course)
.arg(QChar(0xb0))
.arg(m_name);
}
AISDatalinkManagement::AISDatalinkManagement(QByteArray ba) :
AISMessage(ba)
{
}
AISAidsToNavigationReport::AISAidsToNavigationReport(QByteArray ba) :
AISMessage(ba)
{
m_type = ((ba[4] & 0x3) << 3) | ((ba[5] >> 5) & 0x7);
m_name = AISMessage::getString(ba, 5, 5, 20);
m_positionAccuracy = (ba[20] >> 4) & 0x1;
int32_t longitude = ((ba[20] & 0xf) << 24) | ((ba[21] & 0xff) << 16) | ((ba[22] & 0xff) << 8) | (ba[23] & 0xff);
longitude = (longitude << 4) >> 4;
m_longitudeAvailable = longitude != 0x6791ac0;
m_longitude = longitude / 60.0f / 10000.0f;
int32_t latitude = ((ba[24] & 0xff) << 19) | ((ba[25] & 0xff) << 11) | ((ba[26] & 0xff) << 3) | ((ba[27] >> 5) & 0x7);
latitude = (latitude << 5) >> 5;
m_latitudeAvailable = latitude != 0x3412140;
m_latitude = latitude / 60.0f / 10000.0f;
}
QString AISAidsToNavigationReport::toString()
{
const QStringList types = {
"N/A",
"Reference point",
"RACON",
"Fixed structure off short",
"Emergency wreck marking buoy",
"Light, without sectors",
"Light, with sectors",
"Leading light front",
"Leading light rear",
"Beacon, Cardinal N",
"Beacon, Cardinal E",
"Beacon, Cardinal S",
"Beacon, Cardianl W",
"Beacon, Port hand",
"Beacon, Starboard hand",
"Beacon, Preferred channel port hand",
"Beacon, Preferred channel starboard hand",
"Beacon, Isolated danger",
"Beacon, Safe water",
"Beacon, Special mark"
"Cardinal mark N",
"Cardinal mark E",
"Cardinal mark S",
"Cardinal mark W",
"Port hand mark",
"Starboard hand mark",
"Preferred channel port hand",
"Preferred channel starboard hand",
"Isolated danger",
"Safe water",
"Special mark",
"Light vessel/LANBY/Rigs"
};
return QString("Lat: %1%5 Lon: %2%5 Name: %3 Type: %4")
.arg(m_latitude)
.arg(m_longitude)
.arg(m_name)
.arg(types[m_type])
.arg(QChar(0xb0));
}
AISChannelManagement::AISChannelManagement(QByteArray ba) :
AISMessage(ba)
{
}
AISGroupAssignment::AISGroupAssignment(QByteArray ba) :
AISMessage(ba)
{
}
AISStaticDataReport::AISStaticDataReport(QByteArray ba) :
AISMessage(ba)
{
m_partNumber = ba[4] & 0x3;
if (m_partNumber == 0)
{
m_name = AISMessage::getString(ba, 5, 0, 20);
}
else if (m_partNumber == 1)
{
m_type = ba[5] & 0xff;
m_vendorId = AISMessage::getString(ba, 6, 0, 7);
m_callsign = AISMessage::getString(ba, 11, 6, 7);
}
}
QString AISStaticDataReport::toString()
{
if (m_partNumber == 0) {
return QString("Name: %1").arg(m_name);
} else if (m_partNumber == 1) {
return QString("Type: %1 Vendor ID: %2 Callsign: %3").arg(typeToString(m_type)).arg(m_vendorId).arg(m_callsign);
} else {
return "";
}
}
AISSingleSlotBinaryMessage::AISSingleSlotBinaryMessage(QByteArray ba) :
AISMessage(ba)
{
}
AISMultipleSlotBinaryMessage::AISMultipleSlotBinaryMessage(QByteArray ba) :
AISMessage(ba)
{
}
AISLongRangePositionReport::AISLongRangePositionReport(QByteArray ba) :
AISMessage(ba)
{
m_positionAccuracy = (ba[4] >> 1) & 0x1;
m_raim = ba[4] & 0x1;
m_status = (ba[5] >> 4) & 0xf;
int32_t longitude = ((ba[5] & 0xf) << 14) | ((ba[6] & 0xff) << 6) | ((ba[7] >> 2) & 0x3f);
longitude = (longitude << 14) >> 14;
m_longitudeAvailable = longitude != 0x1a838;
m_longitude = longitude / 60.0f / 10.0f;
int32_t latitude = ((ba[7] & 0x3) << 15) | ((ba[8] & 0xff) << 7) | ((ba[9] >> 1) & 0x7f);
latitude = (latitude << 15) >> 15;
m_latitudeAvailable = latitude != 0xd548;
m_latitude = latitude / 60.0f / 10.0f;
m_speedOverGround = ((ba[9] & 0x1) << 5) | ((ba[10] >> 3) & 0x1f);
m_speedOverGroundAvailable = m_speedOverGround != 63;
m_course = ((ba[10] & 0x7) << 6) | ((ba[11] >> 2) & 0x3f);
m_courseAvailable = m_course != 512;
}
QString AISLongRangePositionReport::toString()
{
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(m_course)
.arg(AISPositionReport::getStatusString(m_status))
.arg(QChar(0xb0));
}
AISUnknownMessageID::AISUnknownMessageID(QByteArray ba) :
AISMessage(ba)
{
}

385
sdrbase/util/ais.h Normal file
View File

@ -0,0 +1,385 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_AIS_H
#define INCLUDE_AIS_H
#include <QByteArray>
#include <QString>
#include <QStringList>
#include <QDateTime>
#include "export.h"
class SDRBASE_API AISMessage {
public:
int m_id;
int m_repeatIndicator;
int m_mmsi;
AISMessage(const QByteArray ba);
virtual QString getType() = 0;
virtual bool hasPosition() { return false; }
virtual float getLatitude() { return 0.0f; }
virtual float getLongitude() { return 0.0f; }
virtual bool hasCourse() { return false; }
virtual float getCourse() { return 0.0f; }
virtual bool hasSpeed() { return false; }
virtual float getSpeed() { return 0.0f; }
virtual bool hasHeading() { return false; }
virtual float getHeading() { return 0.0f; }
virtual QString toString() { return ""; }
QString toHex();
QString toNMEA();
static AISMessage* decode(const QByteArray ba);
static QString toNMEA(const QByteArray ba);
static QString typeToString(quint8 type);
protected:
static QString getString(QByteArray ba, int byteIdx, int bitsLeft, int chars);
static qint8 nmeaChecksum(QString string);
QByteArray m_bytes;
};
class SDRBASE_API AISPositionReport : public AISMessage {
public:
int m_status;
bool m_rateOfTurnAvailable;
float m_rateOfTurn; // Degrees per minute
bool m_speedOverGroundAvailable;
float m_speedOverGround; // Knots
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
bool m_courseAvailable;
float m_course; // Degrees
bool m_headingAvailable;
int m_heading; // Degrees
int m_timeStamp;
int m_specialManoeuvre;
AISPositionReport(const QByteArray ba);
virtual QString getType() override { return "Position report"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual bool hasCourse() { return m_courseAvailable; }
virtual float getCourse() { return m_course; }
virtual bool hasSpeed() { return m_speedOverGroundAvailable; }
virtual float getSpeed() { return m_speedOverGround; }
virtual bool hasHeading() { return m_headingAvailable; }
virtual float getHeading() { return m_heading; }
virtual QString toString() override;
static QString getStatusString(int status);
};
class SDRBASE_API AISBaseStationReport : public AISMessage {
public:
QDateTime m_utc;
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
AISBaseStationReport(const QByteArray ba);
virtual QString getType() override {
if (m_id == 4)
return "Base station report";
else
return "UTC and data reponse";
}
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual QString toString() override;
};
class SDRBASE_API AISShipStaticAndVoyageData : public AISMessage {
public:
int m_version;
int m_imo;
QString m_callsign;
QString m_name;
quint8 m_type;
int m_dimension;
int m_positionFixing;
int m_eta;
int m_draught;
QString m_destination;
AISShipStaticAndVoyageData(const QByteArray ba);
virtual QString getType() override { return "Ship static and voyage related data"; }
virtual QString toString() override;
};
class SDRBASE_API AISBinaryMessage : public AISMessage {
public:
int m_sequenceNumber;
int m_destinationId;
int m_retransmitFlag;
AISBinaryMessage(const QByteArray ba);
virtual QString getType() override { return "Addressed binary message"; }
virtual QString toString() override;
};
class SDRBASE_API AISBinaryAck : public AISMessage {
public:
AISBinaryAck(const QByteArray ba);
virtual QString getType() override { return "Binary acknowledge"; }
};
class SDRBASE_API AISBinaryBroadcast : public AISMessage {
public:
AISBinaryBroadcast(const QByteArray ba);
virtual QString getType() override { return "Binary broadcast message"; }
};
class SDRBASE_API AISSARAircraftPositionReport : public AISMessage {
public:
bool m_altitudeAvailable;
float m_altitude; // Metres. 4094 = 4094+
bool m_speedOverGroundAvailable;
float m_speedOverGround; // Knots
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
bool m_courseAvailable;
float m_course; // Degrees
bool m_headingAvailable;
int m_heading; // Degrees
int m_timeStamp;
AISSARAircraftPositionReport(const QByteArray ba);
virtual QString getType() override { return "Standard SAR aircraft position report"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual bool hasCourse() { return m_courseAvailable; }
virtual float getCourse() { return m_course; }
virtual bool hasSpeed() { return m_speedOverGroundAvailable; }
virtual float getSpeed() { return m_speedOverGround; }
virtual QString toString() override;
};
class SDRBASE_API AISUTCInquiry : public AISMessage {
public:
AISUTCInquiry(const QByteArray ba);
virtual QString getType() override { return "UTC and date inquiry"; }
};
class SDRBASE_API AISSafetyMessage : public AISMessage {
public:
int m_sequenceNumber;
int m_destinationId;
int m_retransmitFlag;
QString m_safetyRelatedText;
AISSafetyMessage(const QByteArray ba);
virtual QString getType() override { return "Addressed safety related message"; }
virtual QString toString() override;
};
class SDRBASE_API AISSafetyAck : public AISMessage {
public:
AISSafetyAck(const QByteArray ba);
virtual QString getType() override { return "Safety related acknowledge"; }
};
class SDRBASE_API AISSafetyBroadcast : public AISMessage {
public:
QString m_safetyRelatedText;
AISSafetyBroadcast(const QByteArray ba);
virtual QString getType() override { return "Safety related broadcast message"; }
virtual QString toString() override;
};
class SDRBASE_API AISInterrogation : public AISMessage {
public:
AISInterrogation(const QByteArray ba);
virtual QString getType() override { return "Interrogation"; }
};
class SDRBASE_API AISAssignedModeCommand : public AISMessage {
public:
AISAssignedModeCommand(const QByteArray ba);
virtual QString getType() override { return "Assigned mode command"; }
};
class SDRBASE_API AISGNSSBroadcast : public AISMessage {
public:
AISGNSSBroadcast(const QByteArray ba);
virtual QString getType() override { return "GNSS broadcast binary message"; }
};
class SDRBASE_API AISStandardClassBPositionReport : public AISMessage {
public:
bool m_speedOverGroundAvailable;
float m_speedOverGround; // Knots
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
bool m_courseAvailable;
float m_course; // Degrees
bool m_headingAvailable;
int m_heading; // Degrees
int m_timeStamp;
AISStandardClassBPositionReport(const QByteArray ba);
virtual QString getType() override { return "Standard Class B equipment position report"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual bool hasCourse() { return m_courseAvailable; }
virtual float getCourse() { return m_course; }
virtual bool hasSpeed() { return m_speedOverGroundAvailable; }
virtual float getSpeed() { return m_speedOverGround; }
virtual QString toString() override;
};
class SDRBASE_API AISExtendedClassBPositionReport : public AISMessage {
public:
bool m_speedOverGroundAvailable;
float m_speedOverGround; // Knots
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
bool m_courseAvailable;
float m_course; // Degrees
bool m_headingAvailable;
int m_heading; // Degrees
int m_timeStamp;
QString m_name;
AISExtendedClassBPositionReport(const QByteArray ba);
virtual QString getType() override { return "Extended Class B equipment position report"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual bool hasCourse() { return m_courseAvailable; }
virtual float getCourse() { return m_course; }
virtual bool hasSpeed() { return m_speedOverGroundAvailable; }
virtual float getSpeed() { return m_speedOverGround; }
virtual QString toString() override;
};
class SDRBASE_API AISDatalinkManagement : public AISMessage {
public:
AISDatalinkManagement(const QByteArray ba);
virtual QString getType() override { return "Data link management message"; }
};
class SDRBASE_API AISAidsToNavigationReport : public AISMessage {
public:
int m_type;
QString m_name;
int m_positionAccuracy;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
AISAidsToNavigationReport(const QByteArray ba);
virtual QString getType() override { return "Aids-to-navigation report"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual QString toString() override;
};
class SDRBASE_API AISChannelManagement : public AISMessage {
public:
AISChannelManagement(const QByteArray ba);
virtual QString getType() override { return "Channel management"; }
};
class SDRBASE_API AISGroupAssignment : public AISMessage {
public:
AISGroupAssignment(const QByteArray ba);
virtual QString getType() override { return "Group assignment command"; }
};
class SDRBASE_API AISStaticDataReport : public AISMessage {
public:
int m_partNumber;
QString m_name;
quint8 m_type;
QString m_vendorId;
QString m_callsign;
AISStaticDataReport(const QByteArray ba);
virtual QString getType() override { return "Static data report"; }
virtual QString toString() override;
};
class SDRBASE_API AISSingleSlotBinaryMessage : public AISMessage {
public:
AISSingleSlotBinaryMessage(const QByteArray ba);
virtual QString getType() override { return "Single slot binary message"; }
};
class SDRBASE_API AISMultipleSlotBinaryMessage : public AISMessage {
public:
AISMultipleSlotBinaryMessage(const QByteArray ba);
virtual QString getType() override { return "Multiple slot binary message"; }
};
class SDRBASE_API AISLongRangePositionReport : public AISMessage {
public:
int m_positionAccuracy;
int m_raim;
int m_status;
bool m_longitudeAvailable;
float m_longitude; // Degrees, North positive
bool m_latitudeAvailable;
float m_latitude; // Degrees, East positive
bool m_speedOverGroundAvailable;
float m_speedOverGround; // Knots
bool m_courseAvailable;
float m_course; // Degrees
AISLongRangePositionReport(const QByteArray ba);
virtual QString getType() override { return "Position report for long-range applications"; }
virtual bool hasPosition() { return m_latitudeAvailable && m_longitudeAvailable; }
virtual float getLatitude() { return m_latitude; }
virtual float getLongitude() { return m_longitude; }
virtual bool hasCourse() { return m_courseAvailable; }
virtual float getCourse() { return m_course; }
virtual bool hasSpeed() { return m_speedOverGroundAvailable; }
virtual float getSpeed() { return m_speedOverGround; }
QString getStatus();
virtual QString toString() override;
};
class SDRBASE_API AISUnknownMessageID : public AISMessage {
public:
AISUnknownMessageID(const QByteArray ba);
virtual QString getType() override { return QString("Unknown message ID (%1)").arg(m_id); }
};
#endif // INCLUDE_AIS_H

View File

@ -3811,6 +3811,16 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setAdsbDemodSettings(new SWGSDRangel::SWGADSBDemodSettings());
channelSettings->getAdsbDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "AISDemodSettings")
{
channelSettings->setAisDemodSettings(new SWGSDRangel::SWGAISDemodSettings());
channelSettings->getAisDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "AISModSettings")
{
channelSettings->setAisModSettings(new SWGSDRangel::SWGAISModSettings());
channelSettings->getAisModSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "AMDemodSettings")
{
channelSettings->setAmDemodSettings(new SWGSDRangel::SWGAMDemodSettings());
@ -4023,7 +4033,12 @@ bool WebAPIRequestMapper::getChannelActions(
QJsonObject actionsJsonObject = channelActionsJson[channelActionsKey].toObject();
channelActionsKeys = actionsJsonObject.keys();
if (channelActionsKey == "APTDemodActions")
if (channelActionsKey == "AISModActions")
{
channelActions->setAisModActions(new SWGSDRangel::SWGAISModActions());
channelActions->getAisModActions()->fromJsonObject(actionsJsonObject);
}
else if (channelActionsKey == "APTDemodActions")
{
channelActions->setAptDemodActions(new SWGSDRangel::SWGAPTDemodActions());
channelActions->getAptDemodActions()->fromJsonObject(actionsJsonObject);
@ -4405,7 +4420,12 @@ bool WebAPIRequestMapper::getFeatureSettings(
QJsonObject settingsJsonObject = featureSettingsJson[featureSettingsKey].toObject();
featureSettingsKeys = settingsJsonObject.keys();
if (featureSettingsKey == "APRSSettings")
if (featureSettingsKey == "AISSSettings")
{
featureSettings->setAisSettings(new SWGSDRangel::SWGAISSettings());
featureSettings->getAisSettings()->fromJsonObject(settingsJsonObject);
}
else if (featureSettingsKey == "APRSSettings")
{
featureSettings->setAprsSettings(new SWGSDRangel::SWGAPRSSettings());
featureSettings->getAprsSettings()->fromJsonObject(settingsJsonObject);
@ -4594,6 +4614,8 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings&
channelSettings.cleanup();
channelSettings.setChannelType(nullptr);
channelSettings.setAdsbDemodSettings(nullptr);
channelSettings.setAisDemodSettings(nullptr);
channelSettings.setAisModSettings(nullptr);
channelSettings.setAmDemodSettings(nullptr);
channelSettings.setAmModSettings(nullptr);
channelSettings.setAptDemodSettings(nullptr);
@ -4623,6 +4645,8 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan
channelReport.cleanup();
channelReport.setChannelType(nullptr);
channelReport.setAdsbDemodReport(nullptr);
channelReport.setAisDemodReport(nullptr);
channelReport.setAisModReport(nullptr);
channelReport.setAmDemodReport(nullptr);
channelReport.setAmModReport(nullptr);
channelReport.setAtvModReport(nullptr);
@ -4646,6 +4670,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan
void WebAPIRequestMapper::resetChannelActions(SWGSDRangel::SWGChannelActions& channelActions)
{
channelActions.cleanup();
channelActions.setAisModActions(nullptr);
channelActions.setAptDemodActions(nullptr);
channelActions.setChannelType(nullptr);
channelActions.setFileSourceActions(nullptr);

View File

@ -23,6 +23,8 @@
const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channel.adsbdemod", "ADSBDemodSettings"},
{"sdrangel.channel.modais", "AISModSettings"},
{"sdrangel.channel.aisdemod", "AISDemodSettings"},
{"sdrangel.channel.amdemod", "AMDemodSettings"},
{"sdrangel.channel.aptdemod", "APTDemodSettings"},
{"de.maintech.sdrangelove.channel.am", "AMDemodSettings"}, // remap
@ -122,6 +124,8 @@ const QMap<QString, QString> WebAPIUtils::m_deviceIdToSettingsKey = {
const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"ADSBDemod", "ADSBDemodSettings"},
{"AISDemod", "AISDemodSettings"},
{"AISMod", "AISModSettings"},
{"APTDemod", "APTemodSettings"},
{"AMDemod", "AMDemodSettings"},
{"AMMod", "AMModSettings"},
@ -162,6 +166,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
};
const QMap<QString, QString> WebAPIUtils::m_channelTypeToActionsKey = {
{"AISMod", "AISModActions"},
{"APTDemod", "APTDemodActions"},
{"FileSink", "FileSinkActions"},
{"FileSource", "FileSourceActions"},
@ -249,6 +254,7 @@ const QMap<QString, QString> WebAPIUtils::m_mimoDeviceHwIdToActionsKey = {
};
const QMap<QString, QString> WebAPIUtils::m_featureTypeToSettingsKey = {
{"AIS", "AISSettings"},
{"APRS", "APRSSettings"},
{"GS232Controller", "GS232ControllerSettings"},
{"Map", "MapSettings"},
@ -264,6 +270,7 @@ const QMap<QString, QString> WebAPIUtils::m_featureTypeToActionsKey = {
};
const QMap<QString, QString> WebAPIUtils::m_featureURIToSettingsKey = {
{"sdrangel.feature.ais", "AISSSettings"},
{"sdrangel.feature.aprs", "APRSSettings"},
{"sdrangel.feature.gs232controller", "GS232ControllerSettings"},
{"sdrangel.feature.map", "MapSettings"},

View File

@ -0,0 +1,18 @@
AISSettings:
description: "AIS settings"
properties:
title:
type: string
rgbColor:
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer

View File

@ -0,0 +1,55 @@
AISDemodSettings:
description: AISDemod
properties:
inputFrequencyOffset:
type: integer
format: int64
rfBandwidth:
type: number
format: float
fmDeviation:
type: number
format: float
correlationThreshold:
type: number
format: float
udpEnabled:
description: "Whether to forward received messages to specified UDP port"
type: integer
udpAddress:
description: "UDP address to forward received messages to"
type: string
udpPort:
description: "UDP port to forward received messages to"
type: integer
udpFormat:
description: "0 for binary, 1 for NMEA"
type: integer
rgbColor:
type: integer
title:
type: string
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer
AISDemodReport:
description: AISDemod
properties:
channelPowerDB:
description: power received in channel (dB)
type: number
format: float
channelSampleRate:
type: integer

View File

@ -0,0 +1,125 @@
AISModSettings:
description: AISMod
properties:
inputFrequencyOffset:
description: channel center frequency shift from baseband center in Hz
type: integer
format: int64
baud:
type: integer
rfBandwidth:
description: channel RF bandwidth in Hz
type: number
format: float
fmDeviation:
description: frequency deviation in Hz
type: integer
gain:
type: number
format: float
channelMute:
type: integer
repeat:
type: integer
repeatDelay:
type: number
format: float
repeatCount:
type: integer
rampUpBits:
type: integer
rampDownBits:
type: integer
rampRange:
type: integer
lpfTaps:
type: integer
rfNoise:
type: integer
description: >
Boolean
* 0 - off
* 1 - on
writeToFile:
type: integer
description: >
Boolean
* 0 - off
* 1 - on
spectrumRate:
type: integer
msgId:
type: integer
mmsi:
type: string
status:
type: integer
latitude:
type: number
format: float
longitude:
type: number
format: float
course:
type: number
format: float
speed:
type: number
format: float
heading:
type: integer
data:
type: string
bt:
type: number
format: float
symbolSpan:
type: integer
rgbColor:
type: integer
title:
type: string
streamIndex:
description: MIMO channel. Not relevant when connected to SI (single Rx).
type: integer
useReverseAPI:
description: Synchronize with reverse API (1 for yes, 0 for no)
type: integer
reverseAPIAddress:
type: string
reverseAPIPort:
type: integer
reverseAPIDeviceIndex:
type: integer
reverseAPIChannelIndex:
type: integer
udpEnabled:
description: "Whether to receive messages to transmit on specified UDP port"
type: integer
udpAddress:
description: "UDP address to receive messages to transmit via"
type: string
udpPort:
description: "UDP port to receive messages to transmit via"
type: integer
AISModReport:
description: AISMod
properties:
channelPowerDB:
description: power transmitted in channel (dB)
type: number
format: float
channelSampleRate:
type: integer
AISModActions:
description: AISMod
properties:
tx:
type: object
properties:
data:
type: string
description: >
Transmit a message

View File

@ -17,6 +17,8 @@ ChannelActions:
originatorChannelIndex:
description: Optional for reverse API. This is the channel index from where the message comes from.
type: integer
AISModActions:
$ref: "http://swgserver:8081/api/swagger/include/AISMod.yaml#/AISModActions"
APTDemodActions:
$ref: "http://swgserver:8081/api/swagger/include/APTDemod.yaml#/APTDemodActions"
FileSinkActions:

View File

@ -13,6 +13,10 @@ ChannelReport:
type: integer
ADSBDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/ADSBDemod.yaml#/ADSBDemodReport"
AISDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/AISDemod.yaml#/AISDemodReport"
AISModReport:
$ref: "http://swgserver:8081/api/swagger/include/AISMod.yaml#/AISModReport"
AMDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/AMDemod.yaml#/AMDemodReport"
AMModReport:

View File

@ -19,6 +19,10 @@ ChannelSettings:
type: integer
ADSBDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/ADSBDemod.yaml#/ADSBDemodSettings"
AISDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/AISDemod.yaml#/AISDemodSettings"
AISModSettings:
$ref: "http://swgserver:8081/api/swagger/include/AISMod.yaml#/AISModSettings"
AMDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/AMDemod.yaml#/AMDemodSettings"
AMModSettings:

View File

@ -15,6 +15,8 @@ FeatureSettings:
type: integer
AFCSettings:
$ref: "http://swgserver:8081/api/swagger/include/AFC.yaml#/AFCSettings"
AISSettings:
$ref: "http://swgserver:8081/api/swagger/include/AIS.yaml#/AISSettings"
APRSSettings:
$ref: "http://swgserver:8081/api/swagger/include/APRS.yaml#/APRSSettings"
DemodAnalyzerSettings:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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