Merge pull request #891 from srcejon/ais
Add AIS mod, demod and feature
4
debian/control
vendored
@ -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
After Width: | Height: | Size: 11 KiB |
BIN
doc/img/AISDemod_plugin_messages.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
doc/img/AISMod_plugin.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
doc/img/AIS_plugin.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
doc/img/AIS_plugin_map.png
Normal file
After Width: | Height: | Size: 113 KiB |
@ -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)
|
||||
|
58
plugins/channelrx/demodais/CMakeLists.txt
Normal 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})
|
500
plugins/channelrx/demodais/aisdemod.cpp
Normal 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();
|
||||
}
|
180
plugins/channelrx/demodais/aisdemod.h
Normal 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
|
175
plugins/channelrx/demodais/aisdemodbaseband.cpp
Normal 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());
|
||||
}
|
97
plugins/channelrx/demodais/aisdemodbaseband.h
Normal 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
|
627
plugins/channelrx/demodais/aisdemodgui.cpp
Normal 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++;
|
||||
}
|
134
plugins/channelrx/demodais/aisdemodgui.h
Normal 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
|
898
plugins/channelrx/demodais/aisdemodgui.ui
Normal 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>
|
92
plugins/channelrx/demodais/aisdemodplugin.cpp
Normal 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();
|
||||
}
|
49
plugins/channelrx/demodais/aisdemodplugin.h
Normal 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
|
163
plugins/channelrx/demodais/aisdemodsettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
70
plugins/channelrx/demodais/aisdemodsettings.h
Normal 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 */
|
471
plugins/channelrx/demodais/aisdemodsink.cpp
Normal 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;
|
||||
}
|
142
plugins/channelrx/demodais/aisdemodsink.h
Normal 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
|
52
plugins/channelrx/demodais/aisdemodwebapiadapter.cpp
Normal 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;
|
||||
}
|
50
plugins/channelrx/demodais/aisdemodwebapiadapter.h
Normal 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
|
85
plugins/channelrx/demodais/readme.md
Normal 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.
|
@ -1,5 +1,6 @@
|
||||
project(mod)
|
||||
|
||||
add_subdirectory(modais)
|
||||
add_subdirectory(modam)
|
||||
add_subdirectory(modchirpchat)
|
||||
add_subdirectory(modnfm)
|
||||
|
64
plugins/channeltx/modais/CMakeLists.txt
Normal 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})
|
824
plugins/channeltx/modais/aismod.cpp
Normal 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);
|
||||
}
|
||||
}
|
209
plugins/channeltx/modais/aismod.h
Normal 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_ */
|
201
plugins/channeltx/modais/aismodbaseband.cpp
Normal 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();
|
||||
}
|
99
plugins/channeltx/modais/aismodbaseband.h
Normal 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
|
686
plugins/channeltx/modais/aismodgui.cpp
Normal 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));
|
||||
}
|
119
plugins/channeltx/modais/aismodgui.h
Normal 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_ */
|
1212
plugins/channeltx/modais/aismodgui.ui
Normal file
92
plugins/channeltx/modais/aismodplugin.cpp
Normal 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();
|
||||
}
|
49
plugins/channeltx/modais/aismodplugin.h
Normal 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
|
54
plugins/channeltx/modais/aismodrepeatdialog.cpp
Normal 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();
|
||||
}
|
40
plugins/channeltx/modais/aismodrepeatdialog.h
Normal 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
|
134
plugins/channeltx/modais/aismodrepeatdialog.ui
Normal 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>
|
224
plugins/channeltx/modais/aismodsettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
79
plugins/channeltx/modais/aismodsettings.h
Normal 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 */
|
496
plugins/channeltx/modais/aismodsource.cpp
Normal 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();
|
||||
}
|
147
plugins/channeltx/modais/aismodsource.h
Normal 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
|
54
plugins/channeltx/modais/aismodtxsettingsdialog.cpp
Normal 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();
|
||||
}
|
48
plugins/channeltx/modais/aismodtxsettingsdialog.h
Normal 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
|
211
plugins/channeltx/modais/aismodtxsettingsdialog.ui
Normal 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>
|
53
plugins/channeltx/modais/aismodwebapiadapter.cpp
Normal 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;
|
||||
}
|
50
plugins/channeltx/modais/aismodwebapiadapter.h
Normal 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
|
128
plugins/channeltx/modais/readme.md
Normal 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}}'
|
@ -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)
|
||||
|
55
plugins/feature/ais/CMakeLists.txt
Normal 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
@ -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
@ -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_
|
12
plugins/feature/ais/ais.qrc
Normal 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>
|
604
plugins/feature/ais/aisgui.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
107
plugins/feature/ais/aisgui.h
Normal 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_
|
188
plugins/feature/ais/aisgui.ui
Normal 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>
|
80
plugins/feature/ais/aisplugin.cpp
Normal 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();
|
||||
}
|
49
plugins/feature/ais/aisplugin.h
Normal 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
|
122
plugins/feature/ais/aissettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
54
plugins/feature/ais/aissettings.h
Normal 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_
|
52
plugins/feature/ais/aiswebapiadapter.cpp
Normal 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;
|
||||
}
|
50
plugins/feature/ais/aiswebapiadapter.h
Normal 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
|
BIN
plugins/feature/ais/map/aircraft.png
Normal file
After Width: | Height: | Size: 657 B |
BIN
plugins/feature/ais/map/anchor.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
plugins/feature/ais/map/bouy.png
Normal file
After Width: | Height: | Size: 375 B |
BIN
plugins/feature/ais/map/cargo.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/feature/ais/map/helicopter.png
Normal file
After Width: | Height: | Size: 779 B |
BIN
plugins/feature/ais/map/ship.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
plugins/feature/ais/map/tanker.png
Normal file
After Width: | Height: | Size: 991 B |
BIN
plugins/feature/ais/map/tug.png
Normal file
After Width: | Height: | Size: 856 B |
44
plugins/feature/ais/readme.md
Normal 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.
|
@ -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"),
|
||||
|
@ -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")
|
||||
|
@ -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_
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
@ -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
@ -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
@ -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
|
@ -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);
|
||||
|
@ -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"},
|
||||
|
18
swagger/sdrangel/api/swagger/include/AIS.yaml
Normal 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
|
55
swagger/sdrangel/api/swagger/include/AISDemod.yaml
Normal 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
|
125
swagger/sdrangel/api/swagger/include/AISMod.yaml
Normal 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
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
131
swagger/sdrangel/code/qt5/client/SWGAISDemodReport.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
64
swagger/sdrangel/code/qt5/client/SWGAISDemodReport.h
Normal 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_ */
|
459
swagger/sdrangel/code/qt5/client/SWGAISDemodSettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
149
swagger/sdrangel/code/qt5/client/SWGAISDemodSettings.h
Normal 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_ */
|
110
swagger/sdrangel/code/qt5/client/SWGAISModActions.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
59
swagger/sdrangel/code/qt5/client/SWGAISModActions.h
Normal 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_ */
|
110
swagger/sdrangel/code/qt5/client/SWGAISModActions_tx.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
59
swagger/sdrangel/code/qt5/client/SWGAISModActions_tx.h
Normal 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_ */
|
131
swagger/sdrangel/code/qt5/client/SWGAISModReport.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
64
swagger/sdrangel/code/qt5/client/SWGAISModReport.h
Normal 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_ */
|