Add RS41 Radiosonde (Weather Ballon) demodulator and feature

This commit is contained in:
Jon Beniston 2022-03-01 17:17:56 +00:00
parent 7ab11615f6
commit 874ca5e85e
72 changed files with 10938 additions and 1 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

View File

@ -21,6 +21,7 @@ add_subdirectory(demodais)
add_subdirectory(demodpager)
add_subdirectory(radioclock)
add_subdirectory(radioastronomy)
add_subdirectory(demodradiosonde)
if(DAB_FOUND AND ZLIB_FOUND AND FAAD_FOUND)
add_subdirectory(demoddab)

View File

@ -0,0 +1,68 @@
project(demodradiosonde)
set(demodradiosonde_SOURCES
radiosondedemod.cpp
radiosondedemodsettings.cpp
radiosondedemodbaseband.cpp
radiosondedemodsink.cpp
radiosondedemodplugin.cpp
radiosondedemodwebapiadapter.cpp
)
set(demodradiosonde_HEADERS
radiosondedemod.h
radiosondedemodsettings.h
radiosondedemodbaseband.h
radiosondedemodsink.h
radiosondedemodplugin.h
radiosondedemodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(demodradiosonde_SOURCES
${demodradiosonde_SOURCES}
radiosondedemodgui.cpp
radiosondedemodgui.ui
)
set(demodradiosonde_HEADERS
${demodradiosonde_HEADERS}
radiosondedemodgui.h
)
set(TARGET_NAME demodradiosonde)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demodradiosondesrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demodradiosonde_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

View File

@ -0,0 +1,715 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radiosondedemod.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/db.h"
#include "maincore.h"
MESSAGE_CLASS_DEFINITION(RadiosondeDemod::MsgConfigureRadiosondeDemod, Message)
MESSAGE_CLASS_DEFINITION(RadiosondeDemod::MsgMessage, Message)
const char * const RadiosondeDemod::m_channelIdURI = "sdrangel.channel.radiosondedemod";
const char * const RadiosondeDemod::m_channelId = "RadiosondeDemod";
RadiosondeDemod::RadiosondeDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new RadiosondeDemodBaseband(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*)));
connect(&m_channelMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleChannelMessages()));
}
RadiosondeDemod::~RadiosondeDemod()
{
qDebug("RadiosondeDemod::~RadiosondeDemod");
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 RadiosondeDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void RadiosondeDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void RadiosondeDemod::start()
{
qDebug("RadiosondeDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
RadiosondeDemodBaseband::MsgConfigureRadiosondeDemodBaseband *msg = RadiosondeDemodBaseband::MsgConfigureRadiosondeDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void RadiosondeDemod::stop()
{
qDebug("RadiosondeDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool RadiosondeDemod::handleMessage(const Message& cmd)
{
if (MsgConfigureRadiosondeDemod::match(cmd))
{
MsgConfigureRadiosondeDemod& cfg = (MsgConfigureRadiosondeDemod&) cmd;
qDebug() << "RadiosondeDemod::handleMessage: MsgConfigureRadiosondeDemod";
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() << "RadiosondeDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
return true;
}
else if (MsgMessage::match(cmd))
{
MsgMessage& report = (MsgMessage&)cmd;
// Decode the message
RS41Frame *frame = RS41Frame::decode(report.getMessage());
RS41Subframe *subframe = nullptr;
if (m_subframes.contains(frame->m_serial))
{
subframe = m_subframes.value(frame->m_serial);
}
else
{
subframe = new RS41Subframe();
m_subframes.insert(frame->m_serial, subframe);
}
subframe->update(frame);
// Forward to GUI
if (getMessageQueueToGUI())
{
MsgMessage *msg = new MsgMessage(report);
getMessageQueueToGUI()->push(msg);
}
MessagePipesLegacy& messagePipes = MainCore::instance()->getMessagePipes();
// Forward to Radiosonde feature
QList<MessageQueue*> *radiosondeMessageQueues = messagePipes.getMessageQueues(this, "radiosonde");
if (radiosondeMessageQueues)
{
QList<MessageQueue*>::iterator it = radiosondeMessageQueues->begin();
for (; it != radiosondeMessageQueues->end(); ++it)
{
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(this, report.getMessage(), report.getDateTime());
(*it)->push(msg);
}
}
// Forward via UDP
if (m_settings.m_udpEnabled)
{
m_udpSocket.writeDatagram(report.getMessage().data(), report.getMessage().size(),
QHostAddress(m_settings.m_udpAddress), m_settings.m_udpPort);
}
// Write to log file
if (m_logFile.isOpen())
{
m_logStream << report.getDateTime().date().toString() << ","
<< report.getDateTime().time().toString() << ","
<< report.getMessage().toHex() << ",";
if (frame->m_statusValid)
{
m_logStream << frame->m_serial << ","
<< frame->m_frameNumber << ",";
}
else
{
m_logStream << ",,";
}
if (frame->m_posValid)
{
m_logStream << frame->m_latitude << ","
<< frame->m_longitude << ",";
}
else
{
m_logStream << ",,";
}
if (frame->m_measValid)
{
m_logStream << frame->getPressureString(subframe) << ","
<< frame->getTemperatureString(subframe) << ","
<< frame->getHumidityString(subframe) << ",";
}
else
{
m_logStream << ",,,";
}
m_logStream << "\n";
}
delete frame;
return true;
}
else if (MainCore::MsgChannelDemodQuery::match(cmd))
{
qDebug() << "RadiosondeDemod::handleMessage: MsgChannelDemodQuery";
sendSampleRateToDemodAnalyzer();
return true;
}
else
{
return false;
}
}
ScopeVis *RadiosondeDemod::getScopeSink()
{
return m_basebandSink->getScopeSink();
}
void RadiosondeDemod::setCenterFrequency(qint64 frequency)
{
RadiosondeDemodSettings settings = m_settings;
settings.m_inputFrequencyOffset = frequency;
applySettings(settings, false);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureRadiosondeDemod *msgToGUI = MsgConfigureRadiosondeDemod::create(settings, false);
m_guiMessageQueue->push(msgToGUI);
}
}
void RadiosondeDemod::applySettings(const RadiosondeDemodSettings& settings, bool force)
{
qDebug() << "RadiosondeDemod::applySettings:"
<< " m_logEnabled: " << settings.m_logEnabled
<< " m_logFilename: " << settings.m_logFilename
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_baud != m_settings.m_baud) || force) {
reverseAPIKeys.append("baud");
}
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_logFilename != m_settings.m_logFilename) || force) {
reverseAPIKeys.append("logFilename");
}
if ((settings.m_logEnabled != m_settings.m_logEnabled) || force) {
reverseAPIKeys.append("logEnabled");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
{
m_deviceAPI->removeChannelSinkAPI(this);
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
m_deviceAPI->addChannelSinkAPI(this);
}
reverseAPIKeys.append("streamIndex");
}
RadiosondeDemodBaseband::MsgConfigureRadiosondeDemodBaseband *msg = RadiosondeDemodBaseband::MsgConfigureRadiosondeDemodBaseband::create(settings, force);
m_basebandSink->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
if ((settings.m_logEnabled != m_settings.m_logEnabled)
|| (settings.m_logFilename != m_settings.m_logFilename)
|| force)
{
if (m_logFile.isOpen())
{
m_logStream.flush();
m_logFile.close();
}
if (settings.m_logEnabled && !settings.m_logFilename.isEmpty())
{
m_logFile.setFileName(settings.m_logFilename);
if (m_logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text))
{
qDebug() << "RadiosondeDemod::applySettings - Logging to: " << settings.m_logFilename;
bool newFile = m_logFile.size() == 0;
m_logStream.setDevice(&m_logFile);
if (newFile)
{
// Write header
m_logStream << "Date,Time,Data,Serial,Frame,Lat,Lon,P (hPa),T (C), U (%)\n";
}
}
else
{
qDebug() << "RadiosondeDemod::applySettings - Unable to open log file: " << settings.m_logFilename;
}
}
}
m_settings = settings;
}
QByteArray RadiosondeDemod::serialize() const
{
return m_settings.serialize();
}
bool RadiosondeDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureRadiosondeDemod *msg = MsgConfigureRadiosondeDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureRadiosondeDemod *msg = MsgConfigureRadiosondeDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void RadiosondeDemod::sendSampleRateToDemodAnalyzer()
{
QList<MessageQueue*> *messageQueues = MainCore::instance()->getMessagePipes().getMessageQueues(this, "reportdemod");
if (messageQueues)
{
QList<MessageQueue*>::iterator it = messageQueues->begin();
for (; it != messageQueues->end(); ++it)
{
MainCore::MsgChannelDemodReport *msg = MainCore::MsgChannelDemodReport::create(
this,
RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE
);
(*it)->push(msg);
}
}
}
int RadiosondeDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setRadiosondeDemodSettings(new SWGSDRangel::SWGRadiosondeDemodSettings());
response.getRadiosondeDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int RadiosondeDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
RadiosondeDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureRadiosondeDemod *msg = MsgConfigureRadiosondeDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("RadiosondeDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureRadiosondeDemod *msgToGUI = MsgConfigureRadiosondeDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void RadiosondeDemod::webapiUpdateChannelSettings(
RadiosondeDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("baud")) {
settings.m_baud = response.getRadiosondeDemodSettings()->getBaud();
}
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getRadiosondeDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getRadiosondeDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getRadiosondeDemodSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("correlationThreshold")) {
settings.m_correlationThreshold = response.getRadiosondeDemodSettings()->getCorrelationThreshold();
}
if (channelSettingsKeys.contains("udpEnabled")) {
settings.m_udpEnabled = response.getRadiosondeDemodSettings()->getUdpEnabled();
}
if (channelSettingsKeys.contains("udpAddress")) {
settings.m_udpAddress = *response.getRadiosondeDemodSettings()->getUdpAddress();
}
if (channelSettingsKeys.contains("udpPort")) {
settings.m_udpPort = response.getRadiosondeDemodSettings()->getUdpPort();
}
if (channelSettingsKeys.contains("logFilename")) {
settings.m_logFilename = *response.getRadiosondeDemodSettings()->getLogFilename();
}
if (channelSettingsKeys.contains("logEnabled")) {
settings.m_logEnabled = response.getRadiosondeDemodSettings()->getLogEnabled();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getRadiosondeDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getRadiosondeDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getRadiosondeDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getRadiosondeDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getRadiosondeDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getRadiosondeDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getRadiosondeDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getRadiosondeDemodSettings()->getReverseApiChannelIndex();
}
if (settings.m_scopeGUI && channelSettingsKeys.contains("scopeConfig")) {
settings.m_scopeGUI->updateFrom(channelSettingsKeys, response.getRadiosondeDemodSettings()->getScopeConfig());
}
if (settings.m_channelMarker && channelSettingsKeys.contains("channelMarker")) {
settings.m_channelMarker->updateFrom(channelSettingsKeys, response.getRadiosondeDemodSettings()->getChannelMarker());
}
if (settings.m_rollupState && channelSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(channelSettingsKeys, response.getRadiosondeDemodSettings()->getRollupState());
}
}
void RadiosondeDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const RadiosondeDemodSettings& settings)
{
response.getRadiosondeDemodSettings()->setBaud(settings.m_baud);
response.getRadiosondeDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getRadiosondeDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getRadiosondeDemodSettings()->setFmDeviation(settings.m_fmDeviation);
response.getRadiosondeDemodSettings()->setCorrelationThreshold(settings.m_correlationThreshold);
response.getRadiosondeDemodSettings()->setUdpEnabled(settings.m_udpEnabled);
response.getRadiosondeDemodSettings()->setUdpAddress(new QString(settings.m_udpAddress));
response.getRadiosondeDemodSettings()->setUdpPort(settings.m_udpPort);
response.getRadiosondeDemodSettings()->setLogFilename(new QString(settings.m_logFilename));
response.getRadiosondeDemodSettings()->setLogEnabled(settings.m_logEnabled);
response.getRadiosondeDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getRadiosondeDemodSettings()->getTitle()) {
*response.getRadiosondeDemodSettings()->getTitle() = settings.m_title;
} else {
response.getRadiosondeDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getRadiosondeDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getRadiosondeDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getRadiosondeDemodSettings()->getReverseApiAddress()) {
*response.getRadiosondeDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getRadiosondeDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getRadiosondeDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getRadiosondeDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getRadiosondeDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
if (settings.m_scopeGUI)
{
if (response.getRadiosondeDemodSettings()->getScopeConfig())
{
settings.m_scopeGUI->formatTo(response.getRadiosondeDemodSettings()->getScopeConfig());
}
else
{
SWGSDRangel::SWGGLScope *swgGLScope = new SWGSDRangel::SWGGLScope();
settings.m_scopeGUI->formatTo(swgGLScope);
response.getRadiosondeDemodSettings()->setScopeConfig(swgGLScope);
}
}
if (settings.m_channelMarker)
{
if (response.getRadiosondeDemodSettings()->getChannelMarker())
{
settings.m_channelMarker->formatTo(response.getRadiosondeDemodSettings()->getChannelMarker());
}
else
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
response.getRadiosondeDemodSettings()->setChannelMarker(swgChannelMarker);
}
}
if (settings.m_rollupState)
{
if (response.getRadiosondeDemodSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getRadiosondeDemodSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getRadiosondeDemodSettings()->setRollupState(swgRollupState);
}
}
}
void RadiosondeDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const RadiosondeDemodSettings& 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 RadiosondeDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const RadiosondeDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("RadiosondeDemod"));
swgChannelSettings->setRadiosondeDemodSettings(new SWGSDRangel::SWGRadiosondeDemodSettings());
SWGSDRangel::SWGRadiosondeDemodSettings *swgRadiosondeDemodSettings = swgChannelSettings->getRadiosondeDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("baud") || force) {
swgRadiosondeDemodSettings->setBaud(settings.m_baud);
}
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgRadiosondeDemodSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgRadiosondeDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgRadiosondeDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("correlationThreshold") || force) {
swgRadiosondeDemodSettings->setCorrelationThreshold(settings.m_correlationThreshold);
}
if (channelSettingsKeys.contains("udpEnabled") || force) {
swgRadiosondeDemodSettings->setUdpEnabled(settings.m_udpEnabled);
}
if (channelSettingsKeys.contains("udpAddress") || force) {
swgRadiosondeDemodSettings->setUdpAddress(new QString(settings.m_udpAddress));
}
if (channelSettingsKeys.contains("udpPort") || force) {
swgRadiosondeDemodSettings->setUdpPort(settings.m_udpPort);
}
if (channelSettingsKeys.contains("logFilename") || force) {
swgRadiosondeDemodSettings->setLogFilename(new QString(settings.m_logFilename));
}
if (channelSettingsKeys.contains("logEnabled") || force) {
swgRadiosondeDemodSettings->setLogEnabled(settings.m_logEnabled);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgRadiosondeDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgRadiosondeDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgRadiosondeDemodSettings->setStreamIndex(settings.m_streamIndex);
}
if (settings.m_scopeGUI && (channelSettingsKeys.contains("scopeConfig") || force))
{
SWGSDRangel::SWGGLScope *swgGLScope = new SWGSDRangel::SWGGLScope();
settings.m_scopeGUI->formatTo(swgGLScope);
swgRadiosondeDemodSettings->setScopeConfig(swgGLScope);
}
if (settings.m_channelMarker && (channelSettingsKeys.contains("channelMarker") || force))
{
SWGSDRangel::SWGChannelMarker *swgChannelMarker = new SWGSDRangel::SWGChannelMarker();
settings.m_channelMarker->formatTo(swgChannelMarker);
swgRadiosondeDemodSettings->setChannelMarker(swgChannelMarker);
}
}
void RadiosondeDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "RadiosondeDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("RadiosondeDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
void RadiosondeDemod::handleChannelMessages()
{
Message* message;
while ((message = m_channelMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}

View File

@ -0,0 +1,195 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEDEMOD_H
#define INCLUDE_RADIOSONDEDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QDateTime>
#include <QFile>
#include <QTextStream>
#include <QVector3D>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "util/radiosonde.h"
#include "radiosondedemodbaseband.h"
#include "radiosondedemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class ScopeVis;
class RadiosondeDemod : public BasebandSampleSink, public ChannelAPI {
public:
class MsgConfigureRadiosondeDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RadiosondeDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRadiosondeDemod* create(const RadiosondeDemodSettings& settings, bool force)
{
return new MsgConfigureRadiosondeDemod(settings, force);
}
private:
RadiosondeDemodSettings m_settings;
bool m_force;
MsgConfigureRadiosondeDemod(const RadiosondeDemodSettings& 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; }
int getErrorsCorrected() const { return m_errorsCorrected; }
int getThreshold() const { return m_threshold; }
static MsgMessage* create(QByteArray message, int errorsCorrected, int threshold)
{
return new MsgMessage(message, QDateTime::currentDateTime(), errorsCorrected, threshold);
}
private:
QByteArray m_message;
QDateTime m_dateTime;
int m_errorsCorrected;
int m_threshold;
MsgMessage(QByteArray message, QDateTime dateTime, int errorsCorrected, int threshold) :
Message(),
m_message(message),
m_dateTime(dateTime),
m_errorsCorrected(errorsCorrected),
m_threshold(threshold)
{
}
};
RadiosondeDemod(DeviceAPI *deviceAPI);
virtual ~RadiosondeDemod();
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 void pushMessage(Message *msg) { m_inputMessageQueue.push(msg); }
virtual QString getSinkName() { return objectName(); }
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 m_settings.m_inputFrequencyOffset; }
virtual void setCenterFrequency(qint64 frequency);
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const RadiosondeDemodSettings& settings);
static void webapiUpdateChannelSettings(
RadiosondeDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
ScopeVis *getScopeSink();
double getMagSq() const { return m_basebandSink->getMagSq(); }
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
}
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
RadiosondeDemodBaseband* m_basebandSink;
RadiosondeDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QUdpSocket m_udpSocket;
QFile m_logFile;
QTextStream m_logStream;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QHash<QString, RS41Subframe *> m_subframes; // Hash of serial to subframes
virtual bool handleMessage(const Message& cmd);
void applySettings(const RadiosondeDemodSettings& settings, bool force = false);
void sendSampleRateToDemodAnalyzer();
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const RadiosondeDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const RadiosondeDemodSettings& settings,
bool force
);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void handleChannelMessages();
};
#endif // INCLUDE_RADIOSONDEDEMOD_H

View File

@ -0,0 +1,176 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radiosondedemodbaseband.h"
MESSAGE_CLASS_DEFINITION(RadiosondeDemodBaseband::MsgConfigureRadiosondeDemodBaseband, Message)
RadiosondeDemodBaseband::RadiosondeDemodBaseband(RadiosondeDemod *radiosondeDemod) :
m_sink(radiosondeDemod),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("RadiosondeDemodBaseband::RadiosondeDemodBaseband");
m_sink.setScopeSink(&m_scopeSink);
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
RadiosondeDemodBaseband::~RadiosondeDemodBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void RadiosondeDemodBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void RadiosondeDemodBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&RadiosondeDemodBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void RadiosondeDemodBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&RadiosondeDemodBaseband::handleData
);
m_running = false;
}
void RadiosondeDemodBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void RadiosondeDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void RadiosondeDemodBaseband::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 RadiosondeDemodBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool RadiosondeDemodBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureRadiosondeDemodBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureRadiosondeDemodBaseband& cfg = (MsgConfigureRadiosondeDemodBaseband&) cmd;
qDebug() << "RadiosondeDemodBaseband::handleMessage: MsgConfigureRadiosondeDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "RadiosondeDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else
{
return false;
}
}
void RadiosondeDemodBaseband::applySettings(const RadiosondeDemodSettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
{
m_channelizer->setChannelization(RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
void RadiosondeDemodBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

View File

@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEDEMODBASEBAND_H
#define INCLUDE_RADIOSONDEDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "dsp/scopevis.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "radiosondedemodsink.h"
class DownChannelizer;
class ChannelAPI;
class RadiosondeDemod;
class ScopeVis;
class RadiosondeDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureRadiosondeDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RadiosondeDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRadiosondeDemodBaseband* create(const RadiosondeDemodSettings& settings, bool force)
{
return new MsgConfigureRadiosondeDemodBaseband(settings, force);
}
private:
RadiosondeDemodSettings m_settings;
bool m_force;
MsgConfigureRadiosondeDemodBaseband(const RadiosondeDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
RadiosondeDemodBaseband(RadiosondeDemod *radiosondeDemod);
~RadiosondeDemodBaseband();
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);
ScopeVis *getScopeSink() { return &m_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;
RadiosondeDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
RadiosondeDemodSettings m_settings;
ScopeVis m_scopeSink;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void calculateOffset(RadiosondeDemodSink *sink);
void applySettings(const RadiosondeDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_RADIOSONDEDEMODBASEBAND_H

View File

@ -0,0 +1,898 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QDebug>
#include <QDesktopServices>
#include <QMessageBox>
#include <QAction>
#include <QRegExp>
#include <QClipboard>
#include <QFileDialog>
#include <QScrollBar>
#include "radiosondedemodgui.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_radiosondedemodgui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/csv.h"
#include "util/db.h"
#include "util/units.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "gui/datetimedelegate.h"
#include "gui/decimaldelegate.h"
#include "gui/timedelegate.h"
#include "dsp/dspengine.h"
#include "dsp/glscopesettings.h"
#include "gui/crightclickenabler.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "feature/featurewebapiutils.h"
#include "radiosondedemod.h"
#include "radiosondedemodsink.h"
void RadiosondeDemodGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
// Trailing chars are for sort arrow
int row = ui->frames->rowCount();
ui->frames->setRowCount(row + 1);
ui->frames->setItem(row, FRAME_COL_DATE, new QTableWidgetItem("2015/04/15-"));
ui->frames->setItem(row, FRAME_COL_TIME, new QTableWidgetItem("10:17:00"));
ui->frames->setItem(row, FRAME_COL_SERIAL, new QTableWidgetItem("S1234567"));
ui->frames->setItem(row, FRAME_COL_FRAME_NUMBER, new QTableWidgetItem("10000"));
ui->frames->setItem(row, FRAME_COL_FLIGHT_PHASE, new QTableWidgetItem("Descent"));
ui->frames->setItem(row, FRAME_COL_LATITUDE, new QTableWidgetItem("-90.00000"));
ui->frames->setItem(row, FRAME_COL_LONGITUDE, new QTableWidgetItem("-180.00000"));
ui->frames->setItem(row, FRAME_COL_ALTITUDE, new QTableWidgetItem("20000.0"));
ui->frames->setItem(row, FRAME_COL_SPEED, new QTableWidgetItem("50.0"));
ui->frames->setItem(row, FRAME_COL_VERTICAL_RATE, new QTableWidgetItem("50.0"));
ui->frames->setItem(row, FRAME_COL_HEADING, new QTableWidgetItem("359.0"));
ui->frames->setItem(row, FRAME_COL_PRESSURE, new QTableWidgetItem("100.0"));
ui->frames->setItem(row, FRAME_COL_TEMP, new QTableWidgetItem("-50.1U"));
ui->frames->setItem(row, FRAME_COL_HUMIDITY, new QTableWidgetItem("100.0"));
ui->frames->setItem(row, FRAME_COL_BATTERY_VOLTAGE, new QTableWidgetItem("2.7"));
ui->frames->setItem(row, FRAME_COL_BATTERY_STATUS, new QTableWidgetItem("Low"));
ui->frames->setItem(row, FRAME_COL_PCB_TEMP, new QTableWidgetItem("21"));
ui->frames->setItem(row, FRAME_COL_HUMIDITY_PWM, new QTableWidgetItem("1000"));
ui->frames->setItem(row, FRAME_COL_TX_POWER, new QTableWidgetItem("7"));
ui->frames->setItem(row, FRAME_COL_MAX_SUBFRAME_NO, new QTableWidgetItem("50"));
ui->frames->setItem(row, FRAME_COL_SUBFRAME_NO, new QTableWidgetItem("50"));
ui->frames->setItem(row, FRAME_COL_SUBFRAME, new QTableWidgetItem("00112233445566778899aabbccddeeff----"));
ui->frames->setItem(row, FRAME_COL_GPS_TIME, new QTableWidgetItem("2015/04/15 10:17:00"));
ui->frames->setItem(row, FRAME_COL_GPS_SATS, new QTableWidgetItem("12"));
ui->frames->setItem(row, FRAME_COL_ECC, new QTableWidgetItem("12"));
ui->frames->setItem(row, FRAME_COL_CORR, new QTableWidgetItem("-500"));
ui->frames->resizeColumnsToContents();
ui->frames->removeRow(row);
}
// Columns in table reordered
void RadiosondeDemodGUI::frames_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_frameColumnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void RadiosondeDemodGUI::frames_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_frameColumnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void RadiosondeDemodGUI::framesColumnSelectMenu(QPoint pos)
{
framesMenu->popup(ui->frames->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void RadiosondeDemodGUI::framesColumnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->frames->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *RadiosondeDemodGUI::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;
}
RadiosondeDemodGUI* RadiosondeDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
RadiosondeDemodGUI* gui = new RadiosondeDemodGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void RadiosondeDemodGUI::destroy()
{
delete this;
}
void RadiosondeDemodGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray RadiosondeDemodGUI::serialize() const
{
return m_settings.serialize();
}
bool RadiosondeDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
// Add row to table
void RadiosondeDemodGUI::frameReceived(const QByteArray& frame, const QDateTime& dateTime, int errorsCorrected, int threshold)
{
RS41Frame *radiosonde;
// Decode the frame
radiosonde = RS41Frame::decode(frame);
// Is scroll bar at bottom
QScrollBar *sb = ui->frames->verticalScrollBar();
bool scrollToBottom = sb->value() == sb->maximum();
// Add to frames table
ui->frames->setSortingEnabled(false);
int row = ui->frames->rowCount();
ui->frames->setRowCount(row + 1);
QTableWidgetItem *dateItem = new QTableWidgetItem();
QTableWidgetItem *timeItem = new QTableWidgetItem();
QTableWidgetItem *serialItem = new QTableWidgetItem();
QTableWidgetItem *frameNumberItem = new QTableWidgetItem();
QTableWidgetItem *flightPhaseItem = new QTableWidgetItem();
QTableWidgetItem *latitudeItem = new QTableWidgetItem();
QTableWidgetItem *longitudeItem = new QTableWidgetItem();
QTableWidgetItem *altitudeItem = new QTableWidgetItem();
QTableWidgetItem *speedItem = new QTableWidgetItem();
QTableWidgetItem *verticalRateItem = new QTableWidgetItem();
QTableWidgetItem *headingItem = new QTableWidgetItem();
QTableWidgetItem *pressureItem = new QTableWidgetItem();
QTableWidgetItem *tempItem = new QTableWidgetItem();
QTableWidgetItem *humidityItem = new QTableWidgetItem();
QTableWidgetItem *batteryVoltageItem = new QTableWidgetItem();
QTableWidgetItem *batteryStatusItem = new QTableWidgetItem();
QTableWidgetItem *pcbTempItem = new QTableWidgetItem();
QTableWidgetItem *humidityPWMItem = new QTableWidgetItem();
QTableWidgetItem *txPowerItem = new QTableWidgetItem();
QTableWidgetItem *maxSubframeNoItem = new QTableWidgetItem();
QTableWidgetItem *subframeNoItem = new QTableWidgetItem();
QTableWidgetItem *subframeItem = new QTableWidgetItem();
QTableWidgetItem *gpsTimeItem = new QTableWidgetItem();
QTableWidgetItem *gpsSatsItem = new QTableWidgetItem();
QTableWidgetItem *eccItem = new QTableWidgetItem();
QTableWidgetItem *thItem = new QTableWidgetItem();
ui->frames->setItem(row, FRAME_COL_DATE, dateItem);
ui->frames->setItem(row, FRAME_COL_TIME, timeItem);
ui->frames->setItem(row, FRAME_COL_SERIAL, serialItem);
ui->frames->setItem(row, FRAME_COL_FRAME_NUMBER, frameNumberItem);
ui->frames->setItem(row, FRAME_COL_FLIGHT_PHASE, flightPhaseItem);
ui->frames->setItem(row, FRAME_COL_LATITUDE, latitudeItem);
ui->frames->setItem(row, FRAME_COL_LONGITUDE, longitudeItem);
ui->frames->setItem(row, FRAME_COL_ALTITUDE, altitudeItem);
ui->frames->setItem(row, FRAME_COL_SPEED, speedItem);
ui->frames->setItem(row, FRAME_COL_VERTICAL_RATE, verticalRateItem);
ui->frames->setItem(row, FRAME_COL_HEADING, headingItem);
ui->frames->setItem(row, FRAME_COL_PRESSURE, pressureItem);
ui->frames->setItem(row, FRAME_COL_TEMP, tempItem);
ui->frames->setItem(row, FRAME_COL_HUMIDITY, humidityItem);
ui->frames->setItem(row, FRAME_COL_BATTERY_VOLTAGE, batteryVoltageItem);
ui->frames->setItem(row, FRAME_COL_BATTERY_STATUS, batteryStatusItem);
ui->frames->setItem(row, FRAME_COL_PCB_TEMP, pcbTempItem);
ui->frames->setItem(row, FRAME_COL_HUMIDITY_PWM, humidityPWMItem);
ui->frames->setItem(row, FRAME_COL_TX_POWER, txPowerItem);
ui->frames->setItem(row, FRAME_COL_MAX_SUBFRAME_NO, maxSubframeNoItem);
ui->frames->setItem(row, FRAME_COL_SUBFRAME_NO, subframeNoItem);
ui->frames->setItem(row, FRAME_COL_SUBFRAME, subframeItem);
ui->frames->setItem(row, FRAME_COL_GPS_TIME, gpsTimeItem);
ui->frames->setItem(row, FRAME_COL_GPS_SATS, gpsSatsItem);
ui->frames->setItem(row, FRAME_COL_ECC, eccItem);
ui->frames->setItem(row, FRAME_COL_CORR, thItem);
dateItem->setData(Qt::DisplayRole, dateTime.date());
timeItem->setData(Qt::DisplayRole, dateTime.time());
RS41Subframe *subframe = nullptr;
frameNumberItem->setData(Qt::DisplayRole, radiosonde->m_frameNumber);
if (radiosonde->m_statusValid)
{
serialItem->setText(radiosonde->m_serial);
flightPhaseItem->setText(radiosonde->m_flightPhase);
batteryVoltageItem->setData(Qt::DisplayRole, radiosonde->m_batteryVoltage);
batteryStatusItem->setText(radiosonde->m_batteryStatus);
pcbTempItem->setData(Qt::DisplayRole, radiosonde->m_pcbTemperature);
humidityPWMItem->setData(Qt::DisplayRole, (int)round(radiosonde->m_humiditySensorHeating / 1000.0 * 100.0));
txPowerItem->setData(Qt::DisplayRole, (int)round(radiosonde->m_transmitPower / 7.0 * 100.0));
maxSubframeNoItem->setData(Qt::DisplayRole, radiosonde->m_maxSubframeNumber);
subframeNoItem->setData(Qt::DisplayRole, radiosonde->m_subframeNumber);
subframeItem->setText(radiosonde->m_subframe.toHex());
if (m_subframes.contains(radiosonde->m_serial))
{
subframe = m_subframes.value(radiosonde->m_serial);
}
else
{
subframe = new RS41Subframe();
m_subframes.insert(radiosonde->m_serial, subframe);
}
subframe->update(radiosonde);
}
if (radiosonde->m_posValid)
{
latitudeItem->setData(Qt::DisplayRole, radiosonde->m_latitude);
longitudeItem->setData(Qt::DisplayRole, radiosonde->m_longitude);
altitudeItem->setData(Qt::DisplayRole, radiosonde->m_height);
speedItem->setData(Qt::DisplayRole, Units::kmpsToKPH(radiosonde->m_speed/1000.0));
verticalRateItem->setData(Qt::DisplayRole, radiosonde->m_verticalRate);
headingItem->setData(Qt::DisplayRole, radiosonde->m_heading);
gpsSatsItem->setData(Qt::DisplayRole, radiosonde->m_satellitesUsed);
}
if (radiosonde->m_gpsInfoValid)
{
gpsTimeItem->setData(Qt::DisplayRole, radiosonde->m_gpsDateTime);
}
if (radiosonde->m_measValid && subframe)
{
pressureItem->setData(Qt::DisplayRole, radiosonde->getPressureString(subframe));
tempItem->setData(Qt::DisplayRole, radiosonde->getTemperatureString(subframe));
humidityItem->setData(Qt::DisplayRole, radiosonde->getHumidityString(subframe));
}
eccItem->setData(Qt::DisplayRole, errorsCorrected);
thItem->setData(Qt::DisplayRole, threshold);
ui->frames->setSortingEnabled(true);
if (scrollToBottom) {
ui->frames->scrollToBottom();
}
filterRow(row);
delete radiosonde;
}
bool RadiosondeDemodGUI::handleMessage(const Message& frame)
{
if (RadiosondeDemod::MsgConfigureRadiosondeDemod::match(frame))
{
qDebug("RadiosondeDemodGUI::handleMessage: RadiosondeDemod::MsgConfigureRadiosondeDemod");
const RadiosondeDemod::MsgConfigureRadiosondeDemod& cfg = (RadiosondeDemod::MsgConfigureRadiosondeDemod&) frame;
m_settings = cfg.getSettings();
blockApplySettings(true);
ui->scopeGUI->updateSettings();
m_channelMarker.updateSettings(static_cast<const ChannelMarker*>(m_settings.m_channelMarker));
displaySettings();
blockApplySettings(false);
return true;
}
else if (RadiosondeDemod::MsgMessage::match(frame))
{
RadiosondeDemod::MsgMessage& report = (RadiosondeDemod::MsgMessage&) frame;
frameReceived(report.getMessage(), report.getDateTime(), report.getErrorsCorrected(), report.getThreshold());
return true;
}
return false;
}
void RadiosondeDemodGUI::handleInputMessages()
{
Message* frame;
while ((frame = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*frame))
{
delete frame;
}
}
}
void RadiosondeDemodGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void RadiosondeDemodGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void RadiosondeDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void RadiosondeDemodGUI::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 RadiosondeDemodGUI::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 RadiosondeDemodGUI::on_threshold_valueChanged(int value)
{
ui->thresholdText->setText(QString("%1").arg(value));
m_settings.m_correlationThreshold = value;
applySettings();
}
void RadiosondeDemodGUI::on_filterSerial_editingFinished()
{
m_settings.m_filterSerial = ui->filterSerial->text();
filter();
applySettings();
}
void RadiosondeDemodGUI::on_clearTable_clicked()
{
ui->frames->setRowCount(0);
}
void RadiosondeDemodGUI::on_udpEnabled_clicked(bool checked)
{
m_settings.m_udpEnabled = checked;
applySettings();
}
void RadiosondeDemodGUI::on_udpAddress_editingFinished()
{
m_settings.m_udpAddress = ui->udpAddress->text();
applySettings();
}
void RadiosondeDemodGUI::on_udpPort_editingFinished()
{
m_settings.m_udpPort = ui->udpPort->text().toInt();
applySettings();
}
void RadiosondeDemodGUI::on_channel1_currentIndexChanged(int index)
{
m_settings.m_scopeCh1 = index;
applySettings();
}
void RadiosondeDemodGUI::on_channel2_currentIndexChanged(int index)
{
m_settings.m_scopeCh2 = index;
applySettings();
}
void RadiosondeDemodGUI::on_frames_cellDoubleClicked(int row, int column)
{
// Get serial in row double clicked
QString serial = ui->frames->item(row, FRAME_COL_SERIAL)->text();
if (column == FRAME_COL_SERIAL)
{
// Search for Serial on sondehub
QDesktopServices::openUrl(QUrl(QString("https://sondehub.org/?f=%1#!mt=Mapnik&f=%1&q=%1").arg(serial)));
}
else if ((column == FRAME_COL_LATITUDE) || (column == FRAME_COL_LONGITUDE))
{
// Find serial on Map
FeatureWebAPIUtils::mapFind(serial);
}
}
void RadiosondeDemodGUI::filterRow(int row)
{
bool hidden = false;
if (m_settings.m_filterSerial != "")
{
QRegExp re(m_settings.m_filterSerial);
QTableWidgetItem *fromItem = ui->frames->item(row, FRAME_COL_SERIAL);
if (!re.exactMatch(fromItem->text()))
hidden = true;
}
ui->frames->setRowHidden(row, hidden);
}
void RadiosondeDemodGUI::filter()
{
for (int i = 0; i < ui->frames->rowCount(); i++)
{
filterRow(i);
}
}
void RadiosondeDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
if (widget == ui->scopeContainer)
{
if (rollDown)
{
// Make wide enough for scope controls
setMinimumWidth(716);
}
else
{
setMinimumWidth(352);
}
}
saveState(m_rollupState);
applySettings();
}
void RadiosondeDemodGUI::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_radiosondeDemod->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();
}
RadiosondeDemodGUI::RadiosondeDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::RadiosondeDemodGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_doApplySettings(true),
m_tickCount(0)
{
ui->setupUi(this);
m_helpURL = "plugins/channelrx/demodradiosonde/readme.md";
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_radiosondeDemod = reinterpret_cast<RadiosondeDemod*>(rxChannel);
m_radiosondeDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
m_scopeVis = m_radiosondeDemod->getScopeSink();
m_scopeVis->setGLScope(ui->glScope);
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);
GLScopeSettings::TraceData traceDataI, traceDataQ;
traceDataI.m_projectionType = Projector::ProjectionReal;
traceDataI.m_amp = 1.0; // for -1 to +1
traceDataI.m_ofs = 0.0; // vertical offset
traceDataQ.m_projectionType = Projector::ProjectionImag;
traceDataQ.m_amp = 1.0;
traceDataQ.m_ofs = 0.0;
ui->scopeGUI->changeTrace(0, traceDataI);
ui->scopeGUI->addTrace(traceDataQ);
ui->scopeGUI->setDisplayMode(GLScopeSettings::DisplayXYV);
ui->scopeGUI->focusOnTrace(0); // re-focus to take changes into account in the GUI
GLScopeSettings::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("Radiosonde 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_settings.setScopeGUI(ui->scopeGUI);
m_settings.setRollupState(&m_rollupState);
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->frames->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->frames->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
framesMenu = new QMenu(ui->frames);
for (int i = 0; i < ui->frames->horizontalHeader()->count(); i++)
{
QString text = ui->frames->horizontalHeaderItem(i)->text();
framesMenu->addAction(createCheckableItem(text, i, true, SLOT(framesColumnSelectMenuChecked())));
}
ui->frames->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->frames->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(framesColumnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->frames->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(frames_sectionMoved(int, int, int)));
connect(ui->frames->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(frames_sectionResized(int, int, int)));
ui->frames->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->frames, SIGNAL(customContextMenuRequested(QPoint)), SLOT(customContextMenuRequested(QPoint)));
ui->frames->setItemDelegateForColumn(FRAME_COL_DATE, new DateTimeDelegate("yyyy/MM/dd"));
ui->frames->setItemDelegateForColumn(FRAME_COL_TIME, new TimeDelegate());
ui->frames->setItemDelegateForColumn(FRAME_COL_LATITUDE, new DecimalDelegate(5));
ui->frames->setItemDelegateForColumn(FRAME_COL_LONGITUDE, new DecimalDelegate(5));
ui->frames->setItemDelegateForColumn(FRAME_COL_ALTITUDE, new DecimalDelegate(1));
ui->frames->setItemDelegateForColumn(FRAME_COL_SPEED, new DecimalDelegate(1));
ui->frames->setItemDelegateForColumn(FRAME_COL_VERTICAL_RATE, new DecimalDelegate(1));
ui->frames->setItemDelegateForColumn(FRAME_COL_HEADING, new DecimalDelegate(1));
ui->frames->setItemDelegateForColumn(FRAME_COL_GPS_TIME, new DateTimeDelegate("yyyy/MM/dd hh:mm:ss"));
ui->scopeContainer->setVisible(false);
displaySettings();
applySettings(true);
}
void RadiosondeDemodGUI::customContextMenuRequested(QPoint pos)
{
QTableWidgetItem *item = ui->frames->itemAt(pos);
if (item)
{
int row = item->row();
QString serial = ui->frames->item(row, FRAME_COL_SERIAL)->text();
QMenu* tableContextMenu = new QMenu(ui->frames);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
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);
// View radiosonde on various websites
QAction* mmsiRadiosondeHubAction = new QAction(QString("View %1 on sondehub.net...").arg(serial), tableContextMenu);
connect(mmsiRadiosondeHubAction, &QAction::triggered, this, [serial]()->void {
QDesktopServices::openUrl(QUrl(QString("https://sondehub.org/?f=%1#!mt=Mapnik&f=%1&q=%1").arg(serial)));
});
tableContextMenu->addAction(mmsiRadiosondeHubAction);
// Find on Map
tableContextMenu->addSeparator();
QAction* findMapFeatureAction = new QAction(QString("Find %1 on map").arg(serial), tableContextMenu);
connect(findMapFeatureAction, &QAction::triggered, this, [serial]()->void {
FeatureWebAPIUtils::mapFind(serial);
});
tableContextMenu->addAction(findMapFeatureAction);
tableContextMenu->popup(ui->frames->viewport()->mapToGlobal(pos));
}
}
RadiosondeDemodGUI::~RadiosondeDemodGUI()
{
delete ui;
qDeleteAll(m_subframes);
}
void RadiosondeDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void RadiosondeDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
RadiosondeDemod::MsgConfigureRadiosondeDemod* frame = RadiosondeDemod::MsgConfigureRadiosondeDemod::create( m_settings, force);
m_radiosondeDemod->getInputMessageQueue()->push(frame);
}
}
void RadiosondeDemodGUI::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->filterSerial->setText(m_settings.m_filterSerial);
ui->udpEnabled->setChecked(m_settings.m_udpEnabled);
ui->udpAddress->setText(m_settings.m_udpAddress);
ui->udpPort->setText(QString::number(m_settings.m_udpPort));
ui->channel1->setCurrentIndex(m_settings.m_scopeCh1);
ui->channel2->setCurrentIndex(m_settings.m_scopeCh2);
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled);
// Order and size columns
QHeaderView *header = ui->frames->horizontalHeader();
for (int i = 0; i < RADIOSONDEDEMOD_FRAME_COLUMNS; i++)
{
bool hidden = m_settings.m_frameColumnSizes[i] == 0;
header->setSectionHidden(i, hidden);
framesMenu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_frameColumnSizes[i] > 0)
ui->frames->setColumnWidth(i, m_settings.m_frameColumnSizes[i]);
header->moveSection(header->visualIndex(i), m_settings.m_frameColumnIndexes[i]);
}
filter();
restoreState(m_rollupState);
blockApplySettings(false);
}
void RadiosondeDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void RadiosondeDemodGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void RadiosondeDemodGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void RadiosondeDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_radiosondeDemod->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++;
}
void RadiosondeDemodGUI::on_logEnable_clicked(bool checked)
{
m_settings.m_logEnabled = checked;
applySettings();
}
void RadiosondeDemodGUI::on_logFilename_clicked()
{
// Get filename to save to
QFileDialog fileDialog(nullptr, "Select file to log received frames to", "", "*.csv");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
m_settings.m_logFilename = fileNames[0];
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
applySettings();
}
}
}
// Read .csv log and process as received frames
void RadiosondeDemodGUI::on_logOpen_clicked()
{
QFileDialog fileDialog(nullptr, "Select .csv log file to read", "", "*.csv");
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
QFile file(fileNames[0]);
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
{
QTextStream in(&file);
QString error;
QHash<QString, int> colIndexes = CSV::readHeader(in, {"Date", "Time", "Data"}, error);
if (error.isEmpty())
{
int dateCol = colIndexes.value("Date");
int timeCol = colIndexes.value("Time");
int dataCol = colIndexes.value("Data");
int maxCol = std::max({dateCol, timeCol, dataCol});
QMessageBox dialog(this);
dialog.setText("Reading frames");
dialog.addButton(QMessageBox::Cancel);
dialog.show();
QApplication::processEvents();
int count = 0;
bool cancelled = false;
QStringList cols;
MessagePipesLegacy& framePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *radiosondeMessageQueues = framePipes.getMessageQueues(m_radiosondeDemod, "radiosonde");
while (!cancelled && CSV::readRow(in, &cols))
{
if (cols.size() > maxCol)
{
QDate date = QDate::fromString(cols[dateCol]);
QTime time = QTime::fromString(cols[timeCol]);
QDateTime dateTime(date, time);
QByteArray bytes = QByteArray::fromHex(cols[dataCol].toLatin1());
// Add to table
frameReceived(bytes, dateTime, 0, 0);
// Forward to Radiosonde feature
if (radiosondeMessageQueues)
{
QList<MessageQueue*>::iterator it = radiosondeMessageQueues->begin();
for (; it != radiosondeMessageQueues->end(); ++it)
{
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(m_radiosondeDemod, bytes, dateTime);
(*it)->push(msg);
}
}
if (count % 100 == 0)
{
QApplication::processEvents();
if (dialog.clickedButton()) {
cancelled = true;
}
}
count++;
}
}
dialog.close();
}
else
{
QMessageBox::critical(this, "Radiosonde Demod", error);
}
}
else
{
QMessageBox::critical(this, "Radiosonde Demod", QString("Failed to open file %1").arg(fileNames[0]));
}
}
}
}

View File

@ -0,0 +1,155 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEDEMODGUI_H
#define INCLUDE_RADIOSONDEDEMODGUI_H
#include <QTableWidgetItem>
#include <QMenu>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/messagequeue.h"
#include "settings/rollupstate.h"
#include "radiosondedemodsettings.h"
#include "radiosondedemod.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class ScopeVis;
class ScopeVisXY;
class RadiosondeDemod;
class RadiosondeDemodGUI;
class RS41Frame;
namespace Ui {
class RadiosondeDemodGUI;
}
class RadiosondeDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static RadiosondeDemodGUI* 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::RadiosondeDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RollupState m_rollupState;
RadiosondeDemodSettings m_settings;
bool m_doApplySettings;
ScopeVis* m_scopeVis;
RadiosondeDemod* m_radiosondeDemod;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QMenu *framesMenu; // Column select context menu
QMenu *copyMenu;
QHash<QString, RS41Subframe *> m_subframes; // Hash of serial to subframes
explicit RadiosondeDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~RadiosondeDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
void frameReceived(const QByteArray& frame, const QDateTime& dateTime, int errorsCorrected, int threshold);
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 {
FRAME_COL_DATE,
FRAME_COL_TIME,
FRAME_COL_SERIAL,
FRAME_COL_FRAME_NUMBER,
FRAME_COL_FLIGHT_PHASE,
FRAME_COL_LATITUDE,
FRAME_COL_LONGITUDE,
FRAME_COL_ALTITUDE,
FRAME_COL_SPEED,
FRAME_COL_VERTICAL_RATE,
FRAME_COL_HEADING,
FRAME_COL_PRESSURE,
FRAME_COL_TEMP,
FRAME_COL_HUMIDITY,
FRAME_COL_BATTERY_VOLTAGE,
FRAME_COL_BATTERY_STATUS,
FRAME_COL_PCB_TEMP,
FRAME_COL_HUMIDITY_PWM,
FRAME_COL_TX_POWER,
FRAME_COL_MAX_SUBFRAME_NO,
FRAME_COL_SUBFRAME_NO,
FRAME_COL_SUBFRAME,
FRAME_COL_GPS_TIME,
FRAME_COL_GPS_SATS,
FRAME_COL_ECC,
FRAME_COL_CORR
};
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_filterSerial_editingFinished();
void on_clearTable_clicked();
void on_udpEnabled_clicked(bool checked);
void on_udpAddress_editingFinished();
void on_udpPort_editingFinished();
void on_channel1_currentIndexChanged(int index);
void on_channel2_currentIndexChanged(int index);
void on_frames_cellDoubleClicked(int row, int column);
void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked();
void on_logOpen_clicked();
void filterRow(int row);
void filter();
void frames_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void frames_sectionResized(int logicalIndex, int oldSize, int newSize);
void framesColumnSelectMenu(QPoint pos);
void framesColumnSelectMenuChecked(bool checked = false);
void customContextMenuRequested(QPoint point);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_RADIOSONDEDEMODGUI_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,92 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 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 "radiosondedemodgui.h"
#endif
#include "radiosondedemod.h"
#include "radiosondedemodwebapiadapter.h"
#include "radiosondedemodplugin.h"
const PluginDescriptor RadiosondeDemodPlugin::m_pluginDescriptor = {
RadiosondeDemod::m_channelId,
QStringLiteral("Radiosonde Demodulator"),
QStringLiteral("6.20.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
RadiosondeDemodPlugin::RadiosondeDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& RadiosondeDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void RadiosondeDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(RadiosondeDemod::m_channelIdURI, RadiosondeDemod::m_channelId, this);
}
void RadiosondeDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
RadiosondeDemod *instance = new RadiosondeDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* RadiosondeDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* RadiosondeDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return RadiosondeDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* RadiosondeDemodPlugin::createChannelWebAPIAdapter() const
{
return new RadiosondeDemodWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_RADIOSONDEDEMODPLUGIN_H
#define INCLUDE_RADIOSONDEDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class RadiosondeDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.radiosondedemod")
public:
explicit RadiosondeDemodPlugin(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_RADIOSONDEDEMODPLUGIN_H

View File

@ -0,0 +1,200 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radiosondedemodsettings.h"
RadiosondeDemodSettings::RadiosondeDemodSettings() :
m_channelMarker(nullptr),
m_scopeGUI(nullptr),
m_rollupState(nullptr)
{
resetToDefaults();
}
void RadiosondeDemodSettings::resetToDefaults()
{
m_baud = 4800; // Fixed for RS41 - may change for others
m_inputFrequencyOffset = 0;
m_rfBandwidth = 9600.0f;
m_fmDeviation = 2400.0f;
m_correlationThreshold = 450;
m_filterSerial = "";
m_udpEnabled = false;
m_udpAddress = "127.0.0.1";
m_udpPort = 9999;
m_scopeCh1 = 5;
m_scopeCh2 = 6;
m_logFilename = "radiosonde_log.csv";
m_logEnabled = false;
m_rgbColor = QColor(102, 0, 102).rgb();
m_title = "Radiosonde 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 < RADIOSONDEDEMOD_FRAME_COLUMNS; i++)
{
m_frameColumnIndexes[i] = i;
m_frameColumnSizes[i] = -1; // Autosize
}
}
QByteArray RadiosondeDemodSettings::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_filterSerial);
s.writeBool(6, m_udpEnabled);
s.writeString(7, m_udpAddress);
s.writeU32(8, m_udpPort);
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);
s.writeBlob(21, m_scopeGUI->serialize());
s.writeString(22, m_logFilename);
s.writeBool(23, m_logEnabled);
s.writeS32(24, m_baud);
if (m_rollupState) {
s.writeBlob(25, m_rollupState->serialize());
}
for (int i = 0; i < RADIOSONDEDEMOD_FRAME_COLUMNS; i++)
s.writeS32(100 + i, m_frameColumnIndexes[i]);
for (int i = 0; i < RADIOSONDEDEMOD_FRAME_COLUMNS; i++)
s.writeS32(200 + i, m_frameColumnSizes[i]);
return s.final();
}
bool RadiosondeDemodSettings::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, 450);
d.readString(5, &m_filterSerial, "");
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(10, &m_scopeCh1, 0);
d.readS32(11, &m_scopeCh2, 0);
d.readU32(12, &m_rgbColor, QColor(102, 0, 102).rgb());
d.readString(13, &m_title, "Radiosonde Demodulator");
if (m_channelMarker)
{
d.readBlob(14, &bytetmp);
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;
if (m_scopeGUI)
{
d.readBlob(21, &bytetmp);
m_scopeGUI->deserialize(bytetmp);
}
d.readString(22, &m_logFilename, "radiosonde_log.csv");
d.readBool(23, &m_logEnabled, false);
d.readS32(24, &m_baud, 9600);
if (m_rollupState)
{
d.readBlob(25, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
for (int i = 0; i < RADIOSONDEDEMOD_FRAME_COLUMNS; i++) {
d.readS32(100 + i, &m_frameColumnIndexes[i], i);
}
for (int i = 0; i < RADIOSONDEDEMOD_FRAME_COLUMNS; i++) {
d.readS32(200 + i, &m_frameColumnSizes[i], -1);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,75 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEDEMODSETTINGS_H
#define INCLUDE_RADIOSONDEDEMODSETTINGS_H
#include <QByteArray>
#include <QString>
#include "dsp/dsptypes.h"
class Serializable;
// Number of columns in the tables
#define RADIOSONDEDEMOD_FRAME_COLUMNS 26
struct RadiosondeDemodSettings
{
qint32 m_baud;
qint32 m_inputFrequencyOffset;
Real m_rfBandwidth;
Real m_fmDeviation;
Real m_correlationThreshold;
QString m_filterSerial;
bool m_udpEnabled;
QString m_udpAddress;
uint16_t m_udpPort;
int m_scopeCh1;
int m_scopeCh2;
QString m_logFilename;
bool m_logEnabled;
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;
Serializable *m_scopeGUI;
Serializable *m_rollupState;
int m_frameColumnIndexes[RADIOSONDEDEMOD_FRAME_COLUMNS];//!< How the columns are ordered in the table
int m_frameColumnSizes[RADIOSONDEDEMOD_FRAME_COLUMNS]; //!< Size of the columns in the table
static const int RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE = 57600; //!< 12x 4800 baud rate (use even multiple so Gausian filter has odd number of taps)
RadiosondeDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
void setScopeGUI(Serializable *scopeGUI) { m_scopeGUI = scopeGUI; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_RADIOSONDEDEMODSETTINGS_H */

View File

@ -0,0 +1,576 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "dsp/scopevis.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "util/reedsolomon.h"
#include "maincore.h"
#include "radiosondedemod.h"
#include "radiosondedemodsink.h"
const uint8_t RadiosondeDemodSink::m_descramble[64] = {
0x96, 0x83, 0x3E, 0x51, 0xB1, 0x49, 0x08, 0x98,
0x32, 0x05, 0x59, 0x0E, 0xF9, 0x44, 0xC6, 0x26,
0x21, 0x60, 0xC2, 0xEA, 0x79, 0x5D, 0x6D, 0xA1,
0x54, 0x69, 0x47, 0x0C, 0xDC, 0xE8, 0x5C, 0xF1,
0xF7, 0x76, 0x82, 0x7F, 0x07, 0x99, 0xA2, 0x2C,
0x93, 0x7C, 0x30, 0x63, 0xF5, 0x10, 0x2E, 0x61,
0xD0, 0xBC, 0xB4, 0xB6, 0x06, 0xAA, 0xF4, 0x23,
0x78, 0x6E, 0x3B, 0xAE, 0xBF, 0x7B, 0x4C, 0xC1
};
RadiosondeDemodSink::RadiosondeDemodSink(RadiosondeDemod *radiosondeDemod) :
m_scopeSink(nullptr),
m_radiosondeDemod(radiosondeDemod),
m_channelSampleRate(RadiosondeDemodSettings::RADIOSONDEDEMOD_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_sampleBufferIndex(0)
{
m_magsq = 0.0;
m_demodBuffer.resize(1<<12);
m_demodBufferFill = 0;
m_sampleBuffer.resize(m_sampleBufferSize);
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
RadiosondeDemodSink::~RadiosondeDemodSink()
{
delete[] m_rxBuf;
delete[] m_train;
}
void RadiosondeDemodSink::sampleToScope(Complex sample)
{
if (m_scopeSink)
{
Real r = std::real(sample) * SDR_RX_SCALEF;
Real i = std::imag(sample) * SDR_RX_SCALEF;
m_sampleBuffer[m_sampleBufferIndex++] = Sample(r, i);
if (m_sampleBufferIndex == m_sampleBufferSize)
{
std::vector<SampleVector::const_iterator> vbegin;
vbegin.push_back(m_sampleBuffer.begin());
m_scopeSink->feed(vbegin, m_sampleBufferSize);
m_sampleBufferIndex = 0;
}
}
}
void RadiosondeDemodSink::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 RadiosondeDemodSink::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
// What frequency offset is RS41 specified too?
// 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;
bool gotSOP = false;
if ((m_rxBufCnt >= m_rxBufLength))
{
// Correlate with training sequence
corr = correlate(m_rxBufIdx);
// 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)
{
// Try to see if starting at a later sample improves correlation
int maxCorrOffset = 0;
Real maxCorr;
Real initCorr = fabs(corr);
do
{
maxCorr = fabs(corr);
maxCorrOffset++;
corr = correlate(m_rxBufIdx + maxCorrOffset);
}
while (fabs(corr) > maxCorr);
maxCorrOffset--;
// Calculate mean of preamble as DC offset (as it should be 0 on an ideal signal)
Real trainingSum = 0.0f;
for (int i = 0; i < m_correlationLength; i++)
{
int j = (m_rxBufIdx + maxCorrOffset + i) % m_rxBufLength;
trainingSum += m_rxBuf[j];
}
dcOffset = trainingSum/m_correlationLength;
// Start demod after (most of) preamble
int x = (m_rxBufIdx + maxCorrOffset + m_correlationLength*3/4 + 0) % m_rxBufLength;
// Attempt to demodulate
uint64_t bits = 0;
int bitCount = 0;
int onesCount = 0;
int byteCount = 0;
int symbolPrev = 0;
QList<int> sampleIdxs;
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;
sampleIdxs.append((x + sampleOffset + i) % m_rxBufLength);
}
int symbol = sampleSum >= 0.0f ? 1 : 0;
// Move to next symbol
x = (x + m_samplesPerSymbol) % m_rxBufLength;
// Symbols map directly to bits
int bit = symbol;
// Store in shift reg (little endian)
bits |= ((uint64_t)bit) << bitCount;
bitCount++;
if (gotSOP)
{
if (bitCount == 8)
{
// Got a complete byte
m_bytes[byteCount] = bits;
byteCount++;
bits = 0;
bitCount = 0;
if (byteCount >= RADIOSONDE_LENGTH_STD)
{
// Get expected length of frame
uint8_t frameType = m_bytes[RADIOSONDE_OFFSET_FRAME_TYPE] ^ m_descramble[RADIOSONDE_OFFSET_FRAME_TYPE];
int length = RS41Frame::getFrameLength(frameType);
// Have we received a complete frame?
if (byteCount == length)
{
int firstError;
bool ok = processFrame(length, corr, sampleIdx, &firstError);
scopeCRCValid = ok;
scopeCRCInvalid = !ok;
break;
}
}
}
}
else if (bits == 0xf812962211cab610ULL) // Scrambled header
{
// Start of packet
gotSOP = true;
bits = 0;
bitCount = 0;
m_bytes[0] = 0x10;
m_bytes[1] = 0xb6;
m_bytes[2] = 0xca;
m_bytes[3] = 0x11;
m_bytes[4] = 0x22;
m_bytes[5] = 0x96;
m_bytes[6] = 0x12;
m_bytes[7] = 0xf8;
byteCount = 8;
}
else
{
if (bitCount == 64)
{
bits >>= 1;
bitCount--;
}
if (sampleIdx >= 16 * 8 * m_samplesPerSymbol)
{
// Too many bits without receving header
break;
}
}
}
// printf("\n");
}
}
// 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(gotSOP);
break;
case 9:
scopeSample.real(dcOffset);
break;
case 10:
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(gotSOP);
break;
case 9:
scopeSample.imag(dcOffset);
break;
case 10:
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<ObjectPipe*> dataPipes;
MainCore::instance()->getDataPipes().getDataPipes(m_channel, "demod", dataPipes);
if (dataPipes.size() > 0)
{
QList<ObjectPipe*>::iterator it = dataPipes.begin();
for (; it != dataPipes.end(); ++it)
{
DataFifo *fifo = qobject_cast<DataFifo*>((*it)->m_element);
if (fifo) {
fifo->write((quint8*) &m_demodBuffer[0], m_demodBuffer.size() * sizeof(qint16), DataFifo::DataTypeI16);
}
}
}
m_demodBufferFill = 0;
}
}
// Correlate received signal with training sequence
// Note that DC offset doesn't matter for this
Real RadiosondeDemodSink::correlate(int idx) const
{
Real corr = 0.0f;
for (int i = 0; i < m_correlationLength; i++)
{
int j = (idx + i) % m_rxBufLength;
corr += m_train[i] * m_rxBuf[j];
}
return corr;
}
bool RadiosondeDemodSink::processFrame(int length, float corr, int sampleIdx, int *firstError)
{
// Descramble
for (int i = 0; i < length; i++) {
m_bytes[i] = m_bytes[i] ^ m_descramble[i & 0x3f];
}
// Reed-Solomon Error Correction
int errorsCorrected = reedSolomonErrorCorrection();
if (errorsCorrected >= 0)
{
// Check per-block CRCs are correct
if (checkCRCs(length))
{
if (getMessageQueueToChannel())
{
QByteArray rxPacket((char *)m_bytes, length);
RadiosondeDemod::MsgMessage *msg = RadiosondeDemod::MsgMessage::create(rxPacket, errorsCorrected, corr);
getMessageQueueToChannel()->push(msg);
}
// Skip over received packet, so we don't try to re-demodulate it
m_rxBufCnt -= sampleIdx;
return true;
}
}
return false;
}
// Reed Solomon error correction
// Returns number of errors corrected, or -1 if there are uncorrectable errors
int RadiosondeDemodSink::reedSolomonErrorCorrection()
{
ReedSolomon::RS<RADIOSONDE_RS_N,RADIOSONDE_RS_K> rs;
int errorsCorrected = 0;
for (int i = 0; (i < RADIOSONDE_RS_INTERLEAVE) && (errorsCorrected >= 0); i++)
{
// Deinterleave and reverse order
uint8_t rsData[RADIOSONDE_RS_N];
memset(rsData, 0, RADIOSONDE_RS_PAD);
for (int j = 0; j < RADIOSONDE_RS_DATA; j++) {
rsData[RADIOSONDE_RS_K-1-j] = m_bytes[RADIOSONDE_OFFSET_FRAME_TYPE+j*RADIOSONDE_RS_INTERLEAVE+i];
}
for (int j = 0; j < RADIOSONDE_RS_2T; j++) {
rsData[RADIOSONDE_RS_N-1-j] = m_bytes[RADIOSONDE_OFFSET_RS+i*RADIOSONDE_RS_2T+j];
}
// Detect and correct errors
int errors = rs.decode(&rsData[0], RADIOSONDE_RS_K); // FIXME: Indicate 0 padding?
if (errors >= 0) {
errorsCorrected += errors;
} else {
// Uncorrectable errors
return -1;
}
// Restore corrected data
for (int j = 0; j < RADIOSONDE_RS_DATA; j++) {
m_bytes[RADIOSONDE_OFFSET_FRAME_TYPE+j*RADIOSONDE_RS_INTERLEAVE+i] = rsData[RADIOSONDE_RS_K-1-j];
}
}
return errorsCorrected;
}
// Check per-block CRCs
// We could pass partial frames that have some correct CRCs, but for now, whole frame has to be correct
bool RadiosondeDemodSink::checkCRCs(int length)
{
for (int i = RADIOSONDE_OFFSET_BLOCK_0; i < length; )
{
uint8_t blockID = m_bytes[i+0];
uint8_t blockLength = m_bytes[i+1];
uint16_t rxCrc = m_bytes[i+2+blockLength] | (m_bytes[i+2+blockLength+1] << 8);
// CRC doesn't include ID/len - so these can be wrong
m_crc.init();
m_crc.calculate(&m_bytes[i+2], blockLength);
uint16_t calcCrc = m_crc.get();
if (calcCrc != rxCrc) {
return false;
}
i += blockLength+4;
}
return true;
}
void RadiosondeDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "RadiosondeDemodSink::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) RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
m_samplesPerSymbol = RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE / m_settings.m_baud;
qDebug() << "RadiosondeDemodSink::applyChannelSettings: m_samplesPerSymbol: " << m_samplesPerSymbol;
}
void RadiosondeDemodSink::applySettings(const RadiosondeDemodSettings& settings, bool force)
{
qDebug() << "RadiosondeDemodSink::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) RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
m_lowpass.create(301, RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f);
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
m_phaseDiscri.setFMScaling(RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE / (2.0f * settings.m_fmDeviation));
}
if ((settings.m_baud != m_settings.m_baud) || force)
{
m_samplesPerSymbol = RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE / settings.m_baud;
qDebug() << "RadiosondeDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol << " baud " << settings.m_baud;
// What value to use for BT? RFIC is Si4032 - its datasheet only appears to support 0.5
m_pulseShape.create(0.5, 3, m_samplesPerSymbol);
// Recieve buffer, long enough for one max length message
delete[] m_rxBuf;
m_rxBufLength = RADIOSONDEDEMOD_MAX_BYTES*8*m_samplesPerSymbol;
m_rxBuf = new Real[m_rxBufLength];
m_rxBufIdx = 0;
m_rxBufCnt = 0;
// Create training sequence for correlation
delete[] m_train;
const int correlateBits = 200; // Preamble is 320bits - leave some for AGC (and clock recovery eventually)
m_correlationLength = correlateBits*m_samplesPerSymbol; // Don't want to use header, as we want to calculate DC offset
m_train = new Real[m_correlationLength]();
// 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 < correlateBits; i++)
{
for (int j = 0; j < m_samplesPerSymbol; j++) {
m_train[i*m_samplesPerSymbol+j] = -m_pulseShape.filter((i&1) * 2.0f - 1.0f);
}
}
}
m_settings = settings;
}

View File

@ -0,0 +1,143 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEDEMODSINK_H
#define INCLUDE_RADIOSONDEDEMODSINK_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 "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "util/messagequeue.h"
#include "util/crc.h"
#include "radiosondedemodsettings.h"
// Length of preamble (40 bytes) and frame (std 320 bytes - extended 518)
#define RADIOSONDEDEMOD_MAX_BYTES (40+518)
class ChannelAPI;
class RadiosondeDemod;
class ScopeVis;
class RadiosondeDemodSink : public ChannelSampleSink {
public:
RadiosondeDemodSink(RadiosondeDemod *radiosondeDemod);
~RadiosondeDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void setScopeSink(ScopeVis* scopeSink) { m_scopeSink = scopeSink; }
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const RadiosondeDemodSettings& 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;
};
ScopeVis* m_scopeSink; // Scope GUI to display baseband waveform
RadiosondeDemod *m_radiosondeDemod;
RadiosondeDemodSettings 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;
unsigned char m_bytes[RADIOSONDEDEMOD_MAX_BYTES];
crc16ccitt m_crc;
QVector<qint16> m_demodBuffer;
int m_demodBufferFill;
SampleVector m_sampleBuffer;
static const int m_sampleBufferSize = RadiosondeDemodSettings::RADIOSONDEDEMOD_CHANNEL_SAMPLE_RATE / 20;
int m_sampleBufferIndex;
static const uint8_t m_descramble[64];
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
void processOneSample(Complex &ci);
Real correlate(int idx) const;
bool processFrame(int length, float corr, int sampleIdx, int *firstError);
int reedSolomonErrorCorrection();
bool checkCRCs(int length);
void sampleToScope(Complex sample);
};
#endif // INCLUDE_RADIOSONDEDEMODSINK_H

View File

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

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_RADIOSONDEDEMOD_WEBAPIADAPTER_H
#define INCLUDE_RADIOSONDEDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "radiosondedemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class RadiosondeDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
RadiosondeDemodWebAPIAdapter();
virtual ~RadiosondeDemodWebAPIAdapter();
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:
RadiosondeDemodSettings m_settings;
};
#endif // INCLUDE_RADIOSONDEDEMOD_WEBAPIADAPTER_H

View File

@ -0,0 +1,106 @@
<h1>Radiosonde demodulator plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate RS41 radiosonde weather ballon signals. Radiosondes typically transmit on 400-406MHz and are in the sky around the world for around 1 hour around 00:00 UTC.
RS41 radiosondes transmit data frames every second, containing position, velocity and PTU (Pressure, Temperature and Humidity) readings. The radios use GFSK modulation, with ±2.4kHz deviation at 4,800 baud. Reed Solomon encoding is used for ECC (Error Checking and Correction).
The Radiosonde demodulator can forward received data to the [Radiosone feature](../../feature/radiosonde/readme.md), which can plot charts showing how altitude and PTU vary over time, and also plot the position of the radiosonde on the 2D and 3D maps.
<h2>Interface</h2>
![Radiosonde Demodulator plugin GUI](../../../doc/img/RadiosondeDemod_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. For RS41 radiosondes, this can be 9.6kHz.
<h3>5: Dev - Frequency deviation</h3>
Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 5 kHz. Typical value to RS41 is 2.4kHz.
<h3>6: TH - Correlation Threshold</h3>
The correlation threshold between the received signal and the preamble (training sequence). A lower value may be able to demodulate weaker signals, but increases processor usage. The default value is 450.
<h3>7: UDP</h3>
When checked, received receives are forwarded to the specified UDP address (12) and port (13).
<h3>8: UDP address</h3>
IP address of the host to forward received frames to via UDP.
<h3>9: UDP port</h3>
UDP port number to forward received frames to.
<h3>10: Find</h3>
Entering a regular expression in the Find field displays only frames where the radiosonde serial number matches the given regular expression.
<h3>11: Start/stop Logging Frames to .csv File</h3>
When checked, writes all received frames to a .csv file.
<h3>14: .csv Log Filename</h3>
Click to specify the name of the .csv file which received frames are logged to.
<h3>15: Read Data from .csv File</h3>
Click to specify a previously written radiosonde .csv log file, which is read and used to update the table.
<h3>11: Clear Data from table</h3>
Pressing this button clears all data from the table.
<h3>Received Data Table</h3>
The received frames table displays information about each radiosonde frame received.
![Radiosonde Demodulator plugin table](../../../doc/img/RadiosondeDemod_plugin_table.png)
* Date - The date the frame was received.
* Time - The time the frame was received.
* Serial - The serial number of the radiosonde. Double clicking on this column will search for the radiosone on https://sondehub.org/
* Frame - Frame number
* Phase - Flight phase: On ground, Ascent and Descent.
* Lat (°) - Latitude in degrees, North positive. Double clicking on this column will search for the radiosonde on the Map.
* Lon (°) - Longitude in degrees, East positive. Double clicking on this column will search for the radiosonde on the Map.
* Alt (m) - Altitude in metres.
* Spd (km/h) - Speed over ground in kilometres per hour.
* VR (m/s) - Vertical climb rate in metres per second.
* Hdg (°) - Heading in degrees.
* P (hPA) - Air pressure in hectopascals. Not all RS41s include a pressure sensor. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* T (°C) - Air temperature in degrees Celsius. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* U (%) - Relative humidity in percent. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* Bat (V) - Battery voltage in Volts.
* Bat - Battery status: OK or low.
* PCB (°C) - Temperature of PCB.
* PWM (%) - Humidity sensor heater PWM (Pulse Width Modulation) setting, in percent.
* TX (%) - Transmit power in percent.
* Max SF - Maximum subframe number.
* SF No. - Subframe number of subframe data in this frame.
* Subframe - Subframe data.
* GPS Time - GPS date and time on board radiosonde. GPS time is offset 18 seconds from UTC.
* GPS Sats - Number of GPS satellites used in position estimate.
* ECC - Number of symbol errors corrected by Reed Solomon ECC.
* Corr - Premable correlation value calculated for the frame. This can be used to choose a value for TH (6).
Right clicking on the table header allows you to select which columns to show. The columns can be reorderd by left clicking and dragging the column header. Right clicking on an item in the table allows you to copy the value to the clipboard.

View File

@ -31,4 +31,7 @@ else()
endif()
endif()
add_subdirectory(startracker)
if (Qt5Charts_FOUND)
add_subdirectory(radiosonde)
add_subdirectory(startracker)
endif()

View File

@ -0,0 +1,60 @@
project(radiosonde)
set(radiosonde_SOURCES
radiosonde.cpp
radiosondesettings.cpp
radiosondeplugin.cpp
radiosondewebapiadapter.cpp
)
set(radiosonde_HEADERS
radiosonde.h
radiosondesettings.h
radiosondeplugin.h
radiosondewebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(radiosonde_SOURCES
${radiosonde_SOURCES}
radiosondegui.cpp
radiosondegui.ui
radiosonde.qrc
)
set(radiosonde_HEADERS
${radiosonde_HEADERS}
radiosondegui.h
)
set(TARGET_NAME featureradiosonde)
set(TARGET_LIB Qt5::Widgets Qt5::Charts)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featureradiosondesrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${radiosonde_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
# Install debug symbols
if (WIN32)
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
endif()

Binary file not shown.

After

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -0,0 +1,327 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "settings/serializable.h"
#include "maincore.h"
#include "radiosonde.h"
MESSAGE_CLASS_DEFINITION(Radiosonde::MsgConfigureRadiosonde, Message)
const char* const Radiosonde::m_featureIdURI = "sdrangel.feature.radiosonde";
const char* const Radiosonde::m_featureId = "Radiosonde";
Radiosonde::Radiosonde(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface)
{
qDebug("Radiosonde::Radiosonde: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "Radiosonde 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*)));
}
Radiosonde::~Radiosonde()
{
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
delete m_networkManager;
}
void Radiosonde::start()
{
qDebug("Radiosonde::start");
m_state = StRunning;
}
void Radiosonde::stop()
{
qDebug("Radiosonde::stop");
m_state = StIdle;
}
bool Radiosonde::handleMessage(const Message& cmd)
{
if (MsgConfigureRadiosonde::match(cmd))
{
MsgConfigureRadiosonde& cfg = (MsgConfigureRadiosonde&) cmd;
qDebug() << "Radiosonde::handleMessage: MsgConfigureRadiosonde";
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 Radiosonde::updatePipes()
{
QList<AvailablePipeSource> availablePipes = updateAvailablePipeSources("radiosonde", RadiosondeSettings::m_pipeTypes, RadiosondeSettings::m_pipeURIs, this);
if (availablePipes != m_availablePipes) {
m_availablePipes = availablePipes;
}
}
QByteArray Radiosonde::serialize() const
{
return m_settings.serialize();
}
bool Radiosonde::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureRadiosonde *msg = MsgConfigureRadiosonde::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureRadiosonde *msg = MsgConfigureRadiosonde::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void Radiosonde::applySettings(const RadiosondeSettings& settings, bool force)
{
qDebug() << "Radiosonde::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 Radiosonde::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setRadiosondeSettings(new SWGSDRangel::SWGRadiosondeSettings());
response.getRadiosondeSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int Radiosonde::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
RadiosondeSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureRadiosonde *msg = MsgConfigureRadiosonde::create(settings, force);
m_inputMessageQueue.push(msg);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureRadiosonde *msgToGUI = MsgConfigureRadiosonde::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
void Radiosonde::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const RadiosondeSettings& settings)
{
if (response.getRadiosondeSettings()->getTitle()) {
*response.getRadiosondeSettings()->getTitle() = settings.m_title;
} else {
response.getRadiosondeSettings()->setTitle(new QString(settings.m_title));
}
response.getRadiosondeSettings()->setRgbColor(settings.m_rgbColor);
response.getRadiosondeSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getRadiosondeSettings()->getReverseApiAddress()) {
*response.getRadiosondeSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getRadiosondeSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getRadiosondeSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getRadiosondeSettings()->setReverseApiFeatureSetIndex(settings.m_reverseAPIFeatureSetIndex);
response.getRadiosondeSettings()->setReverseApiFeatureIndex(settings.m_reverseAPIFeatureIndex);
if (settings.m_rollupState)
{
if (response.getRadiosondeSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getRadiosondeSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getRadiosondeSettings()->setRollupState(swgRollupState);
}
}
}
void Radiosonde::webapiUpdateFeatureSettings(
RadiosondeSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getRadiosondeSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getRadiosondeSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getRadiosondeSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getRadiosondeSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getRadiosondeSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIFeatureSetIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getRadiosondeSettings()->getReverseApiFeatureSetIndex();
}
if (featureSettingsKeys.contains("reverseAPIFeatureIndex")) {
settings.m_reverseAPIFeatureIndex = response.getRadiosondeSettings()->getReverseApiFeatureIndex();
}
if (settings.m_rollupState && featureSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(featureSettingsKeys, response.getRadiosondeSettings()->getRollupState());
}
}
void Radiosonde::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const RadiosondeSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("Radiosonde"));
swgFeatureSettings->setRadiosondeSettings(new SWGSDRangel::SWGRadiosondeSettings());
SWGSDRangel::SWGRadiosondeSettings *swgRadiosondeSettings = swgFeatureSettings->getRadiosondeSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("title") || force) {
swgRadiosondeSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgRadiosondeSettings->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 Radiosonde::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "Radiosonde::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("Radiosonde::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

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

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/radiosonde/">
<file>map/ballon.png</file>
<file>map/parachute.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,857 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QDesktopServices>
#include <QAction>
#include <QClipboard>
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "gui/datetimedelegate.h"
#include "gui/decimaldelegate.h"
#include "mainwindow.h"
#include "device/deviceuiset.h"
#include "ui_radiosondegui.h"
#include "radiosonde.h"
#include "radiosondegui.h"
#include "SWGMapItem.h"
RadiosondeGUI* RadiosondeGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
RadiosondeGUI* gui = new RadiosondeGUI(pluginAPI, featureUISet, feature);
return gui;
}
void RadiosondeGUI::destroy()
{
delete this;
}
void RadiosondeGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray RadiosondeGUI::serialize() const
{
return m_settings.serialize();
}
bool RadiosondeGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
bool RadiosondeGUI::handleMessage(const Message& message)
{
if (Radiosonde::MsgConfigureRadiosonde::match(message))
{
qDebug("RadiosondeGUI::handleMessage: Radiosonde::MsgConfigureRadiosonde");
const Radiosonde::MsgConfigureRadiosonde& cfg = (Radiosonde::MsgConfigureRadiosonde&) 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
RS41Frame *frame = RS41Frame::decode(report.getPacket());
// Update table
updateRadiosondes(frame, report.getDateTime());
}
return false;
}
void RadiosondeGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void RadiosondeGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
saveState(m_rollupState);
applySettings();
}
RadiosondeGUI::RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::RadiosondeGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_lastFeatureState(0)
{
ui->setupUi(this);
m_helpURL = "plugins/feature/radiosonde/readme.md";
setAttribute(Qt::WA_DeleteOnClose, true);
setChannelWidget(false);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_radiosonde = reinterpret_cast<Radiosonde*>(feature);
m_radiosonde->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()));
// Intialise chart
ui->chart->setRenderHint(QPainter::Antialiasing);
// Resize the table using dummy data
resizeTable();
// Allow user to reorder columns
ui->radiosondes->horizontalHeader()->setSectionsMovable(true);
// Allow user to sort table by clicking on headers
ui->radiosondes->setSortingEnabled(true);
// Add context menu to allow hiding/showing of columns
radiosondesMenu = new QMenu(ui->radiosondes);
for (int i = 0; i < ui->radiosondes->horizontalHeader()->count(); i++)
{
QString text = ui->radiosondes->horizontalHeaderItem(i)->text();
radiosondesMenu->addAction(createCheckableItem(text, i, true, SLOT(radiosondesColumnSelectMenuChecked())));
}
ui->radiosondes->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->radiosondes->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(radiosondesColumnSelectMenu(QPoint)));
// Get signals when columns change
connect(ui->radiosondes->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(radiosondes_sectionMoved(int, int, int)));
connect(ui->radiosondes->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(radiosondes_sectionResized(int, int, int)));
// Context menu
ui->radiosondes->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->radiosondes, SIGNAL(customContextMenuRequested(QPoint)), SLOT(radiosondes_customContextMenuRequested(QPoint)));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_LATITUDE, new DecimalDelegate(5));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_LONGITUDE, new DecimalDelegate(5));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_ALTITUDE, new DecimalDelegate(1));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_SPEED, new DecimalDelegate(1));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_VERTICAL_RATE, new DecimalDelegate(1));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_HEADING, new DecimalDelegate(1));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_ALT_MAX, new DecimalDelegate(1));
ui->radiosondes->setItemDelegateForColumn(RADIOSONDE_COL_LAST_UPDATE, new DateTimeDelegate());
m_settings.setRollupState(&m_rollupState);
displaySettings();
applySettings(true);
plotChart();
}
RadiosondeGUI::~RadiosondeGUI()
{
qDeleteAll(m_radiosondes);
delete ui;
}
void RadiosondeGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void RadiosondeGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
// Order and size columns
QHeaderView *header = ui->radiosondes->horizontalHeader();
for (int i = 0; i < RADIOSONDES_COLUMNS; i++)
{
bool hidden = m_settings.m_radiosondesColumnSizes[i] == 0;
header->setSectionHidden(i, hidden);
radiosondesMenu->actions().at(i)->setChecked(!hidden);
if (m_settings.m_radiosondesColumnSizes[i] > 0) {
ui->radiosondes->setColumnWidth(i, m_settings.m_radiosondesColumnSizes[i]);
}
header->moveSection(header->visualIndex(i), m_settings.m_radiosondesColumnIndexes[i]);
}
ui->y1->setCurrentIndex((int)m_settings.m_y1);
ui->y2->setCurrentIndex((int)m_settings.m_y2);
restoreState(m_rollupState);
blockApplySettings(false);
arrangeRollups();
}
void RadiosondeGUI::leaveEvent(QEvent*)
{
}
void RadiosondeGUI::enterEvent(QEvent*)
{
}
void RadiosondeGUI::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 RadiosondeGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
Radiosonde::MsgConfigureRadiosonde* message = Radiosonde::MsgConfigureRadiosonde::create(m_settings, force);
m_radiosonde->getInputMessageQueue()->push(message);
}
}
void RadiosondeGUI::resizeTable()
{
// Fill table with a row of dummy data that will size the columns nicely
int row = ui->radiosondes->rowCount();
ui->radiosondes->setRowCount(row + 1);
ui->radiosondes->setItem(row, RADIOSONDE_COL_SERIAL, new QTableWidgetItem("123456789"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_TYPE, new QTableWidgetItem("RS41-SGP"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_LATITUDE, new QTableWidgetItem("90.000000-"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_LONGITUDE, new QTableWidgetItem("180.00000-"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_ALTITUDE, new QTableWidgetItem("10000"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_SPEED, new QTableWidgetItem("120"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_VERTICAL_RATE, new QTableWidgetItem("120"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_HEADING, new QTableWidgetItem("360"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_STATUS, new QTableWidgetItem("Ascent"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_PRESSURE, new QTableWidgetItem("1234"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_TEMPERATURE, new QTableWidgetItem("-50.0"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_HUMIDITY, new QTableWidgetItem("100.0"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_ALT_MAX, new QTableWidgetItem("10000"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_FREQUENCY, new QTableWidgetItem("400.000"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_BURSTKILL_STATUS, new QTableWidgetItem("0"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_BURSTKILL_TIMER, new QTableWidgetItem("00:00"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_LAST_UPDATE, new QTableWidgetItem("2022/12/12 12:00:00"));
ui->radiosondes->setItem(row, RADIOSONDE_COL_MESSAGES, new QTableWidgetItem("1000"));
ui->radiosondes->resizeColumnsToContents();
ui->radiosondes->removeRow(row);
}
// Columns in table reordered
void RadiosondeGUI::radiosondes_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
{
(void) oldVisualIndex;
m_settings.m_radiosondesColumnIndexes[logicalIndex] = newVisualIndex;
}
// Column in table resized (when hidden size is 0)
void RadiosondeGUI::radiosondes_sectionResized(int logicalIndex, int oldSize, int newSize)
{
(void) oldSize;
m_settings.m_radiosondesColumnSizes[logicalIndex] = newSize;
}
// Right click in table header - show column select menu
void RadiosondeGUI::radiosondesColumnSelectMenu(QPoint pos)
{
radiosondesMenu->popup(ui->radiosondes->horizontalHeader()->viewport()->mapToGlobal(pos));
}
// Hide/show column when menu selected
void RadiosondeGUI::radiosondesColumnSelectMenuChecked(bool checked)
{
(void) checked;
QAction* action = qobject_cast<QAction*>(sender());
if (action != nullptr)
{
int idx = action->data().toInt(nullptr);
ui->radiosondes->setColumnHidden(idx, !action->isChecked());
}
}
// Create column select menu item
QAction *RadiosondeGUI::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;
}
// Send to Map feature
void RadiosondeGUI::sendToMap(const QString &name, const QString &label,
const QString &image, const QString &text,
const QString &model, float labelOffset,
float latitude, float longitude, float altitude, QDateTime positionDateTime,
float heading
)
{
MessagePipesLegacy& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_radiosonde, "mapitems");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setLatitude(latitude);
swgMapItem->setLongitude(longitude);
swgMapItem->setAltitude(altitude);
swgMapItem->setAltitudeReference(0); // ABSOLUTE
if (positionDateTime.isValid()) {
swgMapItem->setPositionDateTime(new QString(positionDateTime.toString(Qt::ISODateWithMs)));
}
swgMapItem->setImageRotation(heading);
swgMapItem->setText(new QString(text));
if (image.isEmpty()) {
swgMapItem->setImage(new QString(""));
} else {
swgMapItem->setImage(new QString(QString("qrc:///radiosonde/map/%1").arg(image)));
}
swgMapItem->setModel(new QString(model));
swgMapItem->setModelAltitudeOffset(0.0f);
swgMapItem->setLabel(new QString(label));
swgMapItem->setLabelAltitudeOffset(labelOffset);
swgMapItem->setFixedPosition(false);
swgMapItem->setOrientation(1);
swgMapItem->setHeading(heading);
swgMapItem->setPitch(0.0);
swgMapItem->setRoll(0.0);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_radiosonde, swgMapItem);
(*it)->push(msg);
}
}
}
// Update table with received message
void RadiosondeGUI::updateRadiosondes(RS41Frame *message, QDateTime dateTime)
{
QTableWidgetItem *serialItem;
QTableWidgetItem *typeItem;
QTableWidgetItem *latitudeItem;
QTableWidgetItem *longitudeItem;
QTableWidgetItem *alitutudeItem;
QTableWidgetItem *speedItem;
QTableWidgetItem *verticalRateItem;
QTableWidgetItem *headingItem;
QTableWidgetItem *statusItem;
QTableWidgetItem *pressureItem;
QTableWidgetItem *temperatureItem;
QTableWidgetItem *humidityItem;
QTableWidgetItem *altMaxItem;
QTableWidgetItem *frequencyItem;
QTableWidgetItem *burstKillStatusItem;
QTableWidgetItem *burstKillTimerItem;
QTableWidgetItem *lastUpdateItem;
QTableWidgetItem *messagesItem;
if (!message->m_statusValid)
{
// Nothing to display if no serial number
return;
}
RadiosondeData *radiosonde;
// See if radiosonde is already in table
QString messageSerial = message->m_serial;
bool found = false;
for (int row = 0; row < ui->radiosondes->rowCount(); row++)
{
QString itemSerial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
if (messageSerial == itemSerial)
{
// Update existing item
serialItem = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL);
typeItem = ui->radiosondes->item(row, RADIOSONDE_COL_TYPE);
latitudeItem = ui->radiosondes->item(row, RADIOSONDE_COL_LATITUDE);
longitudeItem = ui->radiosondes->item(row, RADIOSONDE_COL_LONGITUDE);
alitutudeItem = ui->radiosondes->item(row, RADIOSONDE_COL_ALTITUDE);
speedItem = ui->radiosondes->item(row, RADIOSONDE_COL_SPEED);
verticalRateItem = ui->radiosondes->item(row, RADIOSONDE_COL_VERTICAL_RATE);
headingItem = ui->radiosondes->item(row, RADIOSONDE_COL_HEADING);
statusItem = ui->radiosondes->item(row, RADIOSONDE_COL_STATUS);
pressureItem = ui->radiosondes->item(row, RADIOSONDE_COL_PRESSURE);
temperatureItem = ui->radiosondes->item(row, RADIOSONDE_COL_TEMPERATURE);
humidityItem = ui->radiosondes->item(row, RADIOSONDE_COL_HUMIDITY);
altMaxItem = ui->radiosondes->item(row, RADIOSONDE_COL_ALT_MAX);
frequencyItem = ui->radiosondes->item(row, RADIOSONDE_COL_FREQUENCY);
burstKillStatusItem = ui->radiosondes->item(row, RADIOSONDE_COL_BURSTKILL_STATUS);
burstKillTimerItem = ui->radiosondes->item(row, RADIOSONDE_COL_BURSTKILL_TIMER);
lastUpdateItem = ui->radiosondes->item(row, RADIOSONDE_COL_LAST_UPDATE);
messagesItem = ui->radiosondes->item(row, RADIOSONDE_COL_MESSAGES);
radiosonde = m_radiosondes.value(messageSerial);
found = true;
break;
}
}
if (!found)
{
// Add new radiosonde
ui->radiosondes->setSortingEnabled(false);
int row = ui->radiosondes->rowCount();
ui->radiosondes->setRowCount(row + 1);
serialItem = new QTableWidgetItem();
typeItem = new QTableWidgetItem();
latitudeItem = new QTableWidgetItem();
longitudeItem = new QTableWidgetItem();
alitutudeItem = new QTableWidgetItem();
speedItem = new QTableWidgetItem();
verticalRateItem = new QTableWidgetItem();
headingItem = new QTableWidgetItem();
statusItem = new QTableWidgetItem();
temperatureItem = new QTableWidgetItem();
humidityItem = new QTableWidgetItem();
pressureItem = new QTableWidgetItem();
altMaxItem = new QTableWidgetItem();
frequencyItem = new QTableWidgetItem();
burstKillStatusItem = new QTableWidgetItem();
burstKillTimerItem = new QTableWidgetItem();
lastUpdateItem = new QTableWidgetItem();
messagesItem = new QTableWidgetItem();
ui->radiosondes->setItem(row, RADIOSONDE_COL_SERIAL, serialItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_TYPE, typeItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_LATITUDE, latitudeItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_LONGITUDE, longitudeItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_ALTITUDE, alitutudeItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_SPEED, speedItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_VERTICAL_RATE, verticalRateItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_HEADING, headingItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_STATUS, statusItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_PRESSURE, pressureItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_TEMPERATURE, temperatureItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_HUMIDITY, humidityItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_ALT_MAX, altMaxItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_FREQUENCY, frequencyItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_BURSTKILL_STATUS, burstKillStatusItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_BURSTKILL_TIMER, burstKillTimerItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_LAST_UPDATE, lastUpdateItem);
ui->radiosondes->setItem(row, RADIOSONDE_COL_MESSAGES, messagesItem);
messagesItem->setData(Qt::DisplayRole, 0);
radiosonde = new RadiosondeData();
m_radiosondes.insert(messageSerial, radiosonde);
}
serialItem->setText(message->m_serial);
lastUpdateItem->setData(Qt::DisplayRole, dateTime);
messagesItem->setData(Qt::DisplayRole, messagesItem->data(Qt::DisplayRole).toInt() + 1);
if (message->m_posValid)
{
latitudeItem->setData(Qt::DisplayRole, message->m_latitude);
longitudeItem->setData(Qt::DisplayRole, message->m_longitude);
alitutudeItem->setData(Qt::DisplayRole, message->m_height);
float altMax = altMaxItem->data(Qt::DisplayRole).toFloat();
if (message->m_height > altMax) {
altMaxItem->setData(Qt::DisplayRole, message->m_height);
}
speedItem->setData(Qt::DisplayRole, Units::kmpsToKPH(message->m_speed/1000.0));
verticalRateItem->setData(Qt::DisplayRole, message->m_verticalRate);
headingItem->setData(Qt::DisplayRole, message->m_heading);
}
statusItem->setText(message->m_flightPhase);
radiosonde->m_subframe.update(message);
if (message->m_measValid)
{
pressureItem->setData(Qt::DisplayRole, message->getPressureString(&radiosonde->m_subframe));
temperatureItem->setData(Qt::DisplayRole, message->getTemperatureString(&radiosonde->m_subframe));
humidityItem->setData(Qt::DisplayRole, message->getHumidityString(&radiosonde->m_subframe));
}
if (message->m_measValid && message->m_posValid) {
radiosonde->addMessage(dateTime, message);
}
typeItem->setText(radiosonde->m_subframe.getType());
frequencyItem->setText(radiosonde->m_subframe.getFrequencyMHz());
burstKillStatusItem->setText(radiosonde->m_subframe.getBurstKillStatus());
burstKillTimerItem->setText(radiosonde->m_subframe.getBurstKillTimer());
ui->radiosondes->setSortingEnabled(true);
if (message->m_posValid)
{
// Text to display in info box
QStringList text;
QString type = typeItem->text();
QVariant altitudeV = alitutudeItem->data(Qt::DisplayRole);
QVariant speedV = speedItem->data(Qt::DisplayRole);
QVariant verticalRateV = verticalRateItem->data(Qt::DisplayRole);
QVariant headingV = headingItem->data(Qt::DisplayRole);
QString pressure = pressureItem->text();
QString temperature = temperatureItem->text();
QString humidity = humidityItem->text();
QString status = statusItem->text();
QString frequency = frequencyItem->text();
text.append(QString("Serial: %1").arg(serialItem->text()));
if (!type.isEmpty()) {
text.append(QString("Type: %1").arg(type));
}
if (!altitudeV.isNull()) {
text.append(QString("Altitude: %1m").arg(altitudeV.toDouble(), 0, 'f', 1));
}
if (!speedV.isNull()) {
text.append(QString("Speed: %1km/h").arg(speedV.toDouble(), 0, 'f', 1));
}
if (!verticalRateV.isNull()) {
text.append(QString("Vertical rate: %1m/s").arg(verticalRateV.toDouble(), 0, 'f', 1));
}
if (!headingV.isNull()) {
text.append(QString("Heading: %1%2").arg(headingV.toDouble(), 0, 'f', 1).arg(QChar(0xb0)));
}
if (!status.isEmpty()) {
text.append(QString("Status: %1").arg(status));
}
if (!pressure.isEmpty()) {
text.append(QString("Pressure: %1hPa").arg(pressure));
}
if (!temperature.isEmpty()) {
text.append(QString("Temperature: %1C").arg(temperature));
}
if (!humidity.isEmpty()) {
text.append(QString("Humidity: %1%").arg(humidity));
}
if (!frequency.isEmpty()) {
text.append(QString("Frequency: %1MHz").arg(frequency));
}
QString image = message->m_flightPhase == "Descent" ? "parachute.png" : "ballon.png";
QString model = message->m_flightPhase == "Descent" ? "radiosondeparachute.glb" : "radiosondeballon.glb";
// Send to map feature
sendToMap(serialItem->text(), serialItem->text(),
image, text.join("<br>"),
model, 0.0,
message->m_latitude, message->m_longitude, message->m_height, dateTime,
0.0f);
}
// If this is the first row in the table, select it, so that the chart is plotted
QList<QTableWidgetItem *> selectedItems = ui->radiosondes->selectedItems();
if (selectedItems.size() == 0)
{
QTableWidgetSelectionRange r(0, 0, 0, RADIOSONDES_COLUMNS);
ui->radiosondes->setRangeSelected(r, true);
}
plotChart();
}
void RadiosondeGUI::on_radiosondes_itemSelectionChanged()
{
plotChart();
}
void RadiosondeGUI::on_radiosondes_cellDoubleClicked(int row, int column)
{
if (column == RADIOSONDE_COL_SERIAL)
{
// Get serial of Radiosonde in row double clicked
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
// Search for MMSI on www.radiosondefinder.com
QDesktopServices::openUrl(QUrl(QString("https://sondehub.org/?f=%1#!mt=Mapnik&f=%1&q=%1").arg(serial)));
}
else if ((column == RADIOSONDE_COL_LATITUDE) || (column == RADIOSONDE_COL_LONGITUDE))
{
// Get serial of Radiosonde in row double clicked
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
// Find serial on Map
FeatureWebAPIUtils::mapFind(serial);
}
}
// Table cells context menu
void RadiosondeGUI::radiosondes_customContextMenuRequested(QPoint pos)
{
QTableWidgetItem *item = ui->radiosondes->itemAt(pos);
if (item)
{
int row = item->row();
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
QVariant latitudeV = ui->radiosondes->item(row, RADIOSONDE_COL_LATITUDE)->data(Qt::DisplayRole);
QVariant longitudeV = ui->radiosondes->item(row, RADIOSONDE_COL_LONGITUDE)->data(Qt::DisplayRole);
QMenu* tableContextMenu = new QMenu(ui->radiosondes);
connect(tableContextMenu, &QMenu::aboutToHide, tableContextMenu, &QMenu::deleteLater);
// Copy current cell
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->addSeparator();
// View radiosonde on various websites
QAction* mmsiRadiosondeHubAction = new QAction(QString("View %1 on sondehub.net...").arg(serial), tableContextMenu);
connect(mmsiRadiosondeHubAction, &QAction::triggered, this, [serial]()->void {
QDesktopServices::openUrl(QUrl(QString("https://sondehub.org/?f=%1#!mt=Mapnik&f=%1&q=%1").arg(serial)));
});
tableContextMenu->addAction(mmsiRadiosondeHubAction);
// Find on Map
tableContextMenu->addSeparator();
QAction* findMapFeatureAction = new QAction(QString("Find %1 on map").arg(serial), tableContextMenu);
connect(findMapFeatureAction, &QAction::triggered, this, [serial]()->void {
FeatureWebAPIUtils::mapFind(serial);
});
tableContextMenu->addAction(findMapFeatureAction);
tableContextMenu->popup(ui->radiosondes->viewport()->mapToGlobal(pos));
}
}
void RadiosondeGUI::on_y1_currentIndexChanged(int index)
{
m_settings.m_y1 = (RadiosondeSettings::ChartData)index;
applySettings();
plotChart();
}
void RadiosondeGUI::on_y2_currentIndexChanged(int index)
{
m_settings.m_y2 = (RadiosondeSettings::ChartData)index;
applySettings();
plotChart();
}
float RadiosondeGUI::getData(RadiosondeSettings::ChartData dataType, RadiosondeData *radiosonde, RS41Frame *message)
{
float data;
switch (dataType)
{
case RadiosondeSettings::ALTITUDE:
data = message->m_height;
break;
case RadiosondeSettings::TEMPERATURE:
data = message->getTemperatureFloat(&radiosonde->m_subframe);
break;
case RadiosondeSettings::HUMIDITY:
data = message->getHumidityFloat(&radiosonde->m_subframe);
break;
case RadiosondeSettings::PRESSURE:
data = 0.0f;
break;
case RadiosondeSettings::SPEED:
data = Units::kmpsToKPH(message->m_speed/1000.0);
break;
case RadiosondeSettings::VERTICAL_RATE:
data = message->m_verticalRate;
break;
case RadiosondeSettings::HEADING:
data = message->m_speed;
break;
case RadiosondeSettings::BATTERY_VOLTAGE:
data = message->m_batteryVoltage;
break;
default:
data = 0.0f;
break;
}
return data;
}
static QString getAxisTitle(RadiosondeSettings::ChartData dataType)
{
switch (dataType)
{
case RadiosondeSettings::ALTITUDE:
return "Altitude (m)";
break;
case RadiosondeSettings::TEMPERATURE:
return QString("Temperature (%1C)").arg(QChar(0xb0));
break;
case RadiosondeSettings::HUMIDITY:
return "Relative humidty (%)";
break;
case RadiosondeSettings::PRESSURE:
return "Pressure (hPa)";
break;
case RadiosondeSettings::SPEED:
return "Speed (km/h)";
break;
case RadiosondeSettings::VERTICAL_RATE:
return "Vertical rate (m/s)";
break;
case RadiosondeSettings::HEADING:
return QString("Heading (%1)").arg(QChar(0xb0));
break;
case RadiosondeSettings::BATTERY_VOLTAGE:
return "Battery Voltage (V)";
break;
default:
return "";
}
}
void RadiosondeGUI::plotChart()
{
QChart *oldChart = ui->chart->chart();
QChart *m_chart;
m_chart = new QChart();
m_chart->layout()->setContentsMargins(0, 0, 0, 0);
m_chart->setMargins(QMargins(1, 1, 1, 1));
m_chart->setTheme(QChart::ChartThemeDark);
// Get selected radiosonde
QList<QTableWidgetItem *> selectedItems = ui->radiosondes->selectedItems();
if (selectedItems.size() >= 1)
{
int row = selectedItems[0]->row();
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
RadiosondeData *radiosonde = m_radiosondes.value(serial);
if (radiosonde)
{
// Plot selected data
QDateTimeAxis *m_chartXAxis;
QValueAxis *m_chartY1Axis;
QValueAxis *m_chartY2Axis;
m_chartXAxis = new QDateTimeAxis();
if (m_settings.m_y1 != RadiosondeSettings::NONE) {
m_chartY1Axis = new QValueAxis();
}
if (m_settings.m_y2 != RadiosondeSettings::NONE) {
m_chartY2Axis = new QValueAxis();
}
m_chart->legend()->hide();
m_chart->addAxis(m_chartXAxis, Qt::AlignBottom);
QLineSeries *y1Series = new QLineSeries();
QLineSeries *y2Series = new QLineSeries();
int idx = 0;
for (auto message : radiosonde->m_messages)
{
float y1, y2;
if (m_settings.m_y1 != RadiosondeSettings::NONE)
{
y1 = getData(m_settings.m_y1, radiosonde, message);
y1Series->append(radiosonde->m_messagesDateTime[idx].toMSecsSinceEpoch(), y1);
}
if (m_settings.m_y2 != RadiosondeSettings::NONE)
{
y2 = getData(m_settings.m_y2, radiosonde, message);
y2Series->append(radiosonde->m_messagesDateTime[idx].toMSecsSinceEpoch(), y2);
}
idx++;
}
if (m_settings.m_y1 != RadiosondeSettings::NONE)
{
m_chart->addSeries(y1Series);
m_chart->addAxis(m_chartY1Axis, Qt::AlignLeft);
y1Series->attachAxis(m_chartXAxis);
y1Series->attachAxis(m_chartY1Axis);
m_chartY1Axis->setTitleText(getAxisTitle(m_settings.m_y1));
}
if (m_settings.m_y2 != RadiosondeSettings::NONE)
{
m_chart->addSeries(y2Series);
m_chart->addAxis(m_chartY2Axis, Qt::AlignRight);
y2Series->attachAxis(m_chartXAxis);
y2Series->attachAxis(m_chartY2Axis);
m_chartY2Axis->setTitleText(getAxisTitle(m_settings.m_y2));
}
}
}
ui->chart->setChart(m_chart);
delete oldChart;
}
void RadiosondeGUI::on_deleteAll_clicked()
{
for (int row = ui->radiosondes->rowCount() - 1; row >= 0; row--)
{
QString serial = ui->radiosondes->item(row, RADIOSONDE_COL_SERIAL)->text();
// Remove from map
sendToMap(serial, "",
"", "",
"", 0.0f,
0.0f, 0.0f, 0.0f, QDateTime(),
0.0f);
// Remove from table
ui->radiosondes->removeRow(row);
// Remove from hash
m_radiosondes.remove(serial);
}
}

View File

@ -0,0 +1,155 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDEGUI_H_
#define INCLUDE_FEATURE_RADIOSONDEGUI_H_
#include <QTimer>
#include <QMenu>
#include <QDateTime>
#include <QHash>
#include <QtCharts>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "util/radiosonde.h"
#include "pipes/pipeendpoint.h"
#include "settings/rollupstate.h"
#include "radiosondesettings.h"
class PluginAPI;
class FeatureUISet;
class Radiosonde;
namespace Ui {
class RadiosondeGUI;
}
using namespace QtCharts;
class RadiosondeGUI : public FeatureGUI {
Q_OBJECT
// Holds information not in the table
struct RadiosondeData {
QList<QDateTime> m_messagesDateTime;
QList<RS41Frame *> m_messages;
RS41Subframe m_subframe;
~RadiosondeData()
{
qDeleteAll(m_messages);
}
void addMessage(QDateTime dateTime, RS41Frame *message)
{
m_messagesDateTime.append(dateTime);
m_messages.append(message);
}
};
public:
static RadiosondeGUI* 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::RadiosondeGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
RadiosondeSettings m_settings;
RollupState m_rollupState;
bool m_doApplySettings;
Radiosonde* m_radiosonde;
MessageQueue m_inputMessageQueue;
int m_lastFeatureState;
QHash<QString, RadiosondeData *> m_radiosondes; // Hash of serial to radiosondes
QMenu *radiosondesMenu; // Column select context menu
explicit RadiosondeGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~RadiosondeGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void sendToMap(const QString &name, const QString &label,
const QString &image, const QString &text,
const QString &model, float labelOffset,
float latitude, float longitude, float altitude, QDateTime positionDateTime,
float heading
);
void updateRadiosondes(RS41Frame *radiosonde, QDateTime dateTime);
void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
void plotChart();
float getData(RadiosondeSettings::ChartData dataType, RadiosondeData *radiosonde, RS41Frame *message);
enum RadiosondeCol {
RADIOSONDE_COL_SERIAL,
RADIOSONDE_COL_TYPE,
RADIOSONDE_COL_LATITUDE,
RADIOSONDE_COL_LONGITUDE,
RADIOSONDE_COL_ALTITUDE,
RADIOSONDE_COL_SPEED,
RADIOSONDE_COL_VERTICAL_RATE,
RADIOSONDE_COL_HEADING,
RADIOSONDE_COL_STATUS,
RADIOSONDE_COL_PRESSURE,
RADIOSONDE_COL_TEMPERATURE,
RADIOSONDE_COL_HUMIDITY,
RADIOSONDE_COL_ALT_MAX,
RADIOSONDE_COL_FREQUENCY,
RADIOSONDE_COL_BURSTKILL_STATUS,
RADIOSONDE_COL_BURSTKILL_TIMER,
RADIOSONDE_COL_LAST_UPDATE,
RADIOSONDE_COL_MESSAGES
};
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_radiosondes_itemSelectionChanged();
void on_radiosondes_cellDoubleClicked(int row, int column);
void radiosondes_customContextMenuRequested(QPoint pos);
void radiosondes_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void radiosondes_sectionResized(int logicalIndex, int oldSize, int newSize);
void radiosondesColumnSelectMenu(QPoint pos);
void radiosondesColumnSelectMenuChecked(bool checked = false);
void on_y1_currentIndexChanged(int index);
void on_y2_currentIndexChanged(int index);
void on_deleteAll_clicked();
};
#endif // INCLUDE_FEATURE_RADIOSONDEGUI_H_

View File

@ -0,0 +1,444 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RadiosondeGUI</class>
<widget class="RollupWidget" name="RadiosondeGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>484</width>
<height>732</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>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Radiosonde</string>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<widget class="QWidget" name="tableContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>336</width>
<height>508</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Radiosondes</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="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTableWidget" name="radiosondes">
<property name="toolTip">
<string>Radiosondes</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Serial</string>
</property>
<property name="toolTip">
<string>Serial number of the Radiosonde</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
<property name="toolTip">
<string>Type of Radiosonde</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>Alt (m)</string>
</property>
<property name="toolTip">
<string>Altitude in metres</string>
</property>
</column>
<column>
<property name="text">
<string>Spd (km/h)</string>
</property>
<property name="toolTip">
<string>Speed in kilometers per second</string>
</property>
</column>
<column>
<property name="text">
<string>VR (m/s)</string>
</property>
<property name="toolTip">
<string>Vertical climb rate in metres per second</string>
</property>
</column>
<column>
<property name="text">
<string>Hd (°)</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>P (hPa)</string>
</property>
<property name="toolTip">
<string>Pressure in hectpascals</string>
</property>
</column>
<column>
<property name="text">
<string>T (C)</string>
</property>
<property name="toolTip">
<string>Temperature in degrees Celsius</string>
</property>
</column>
<column>
<property name="text">
<string>U (%)</string>
</property>
<property name="toolTip">
<string>Relative humidity in percent</string>
</property>
</column>
<column>
<property name="text">
<string>Alt Max (m)</string>
</property>
<property name="toolTip">
<string>Maximum altitude seen in metres</string>
</property>
</column>
<column>
<property name="text">
<string>Freq (MHz)</string>
</property>
<property name="toolTip">
<string>Transmission frequency in MHz</string>
</property>
</column>
<column>
<property name="text">
<string>BurstKill Status</string>
</property>
</column>
<column>
<property name="text">
<string>BurstKill Timer</string>
</property>
<property name="toolTip">
<string>Time last position was received</string>
</property>
</column>
<column>
<property name="text">
<string>Updated</string>
</property>
<property name="toolTip">
<string>Time last message was received</string>
</property>
</column>
<column>
<property name="text">
<string>Messages</string>
</property>
<property name="toolTip">
<string>Number of messages received</string>
</property>
</column>
</widget>
<widget class="QWidget" name="chartContainer" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="chartButtonsLayout">
<item>
<widget class="QLabel" name="y1Label">
<property name="text">
<string>Y1</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="y1">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select data to plot on left Y axis</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Altitude</string>
</property>
</item>
<item>
<property name="text">
<string>Temperature</string>
</property>
</item>
<item>
<property name="text">
<string>Humidity</string>
</property>
</item>
<item>
<property name="text">
<string>Pressure</string>
</property>
</item>
<item>
<property name="text">
<string>Speed</string>
</property>
</item>
<item>
<property name="text">
<string>Vertical rate</string>
</property>
</item>
<item>
<property name="text">
<string>Heading</string>
</property>
</item>
<item>
<property name="text">
<string>Battery Voltage</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="y2Label">
<property name="text">
<string>Y2</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="y2">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select data to plot on right Y axis</string>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Altitude</string>
</property>
</item>
<item>
<property name="text">
<string>Temperature</string>
</property>
</item>
<item>
<property name="text">
<string>Humidity</string>
</property>
</item>
<item>
<property name="text">
<string>Pressure</string>
</property>
</item>
<item>
<property name="text">
<string>Speed</string>
</property>
</item>
<item>
<property name="text">
<string>Vertical rate</string>
</property>
</item>
<item>
<property name="text">
<string>Heading</string>
</property>
</item>
<item>
<property name="text">
<string>Battery Voltage</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteAll">
<property name="toolTip">
<string>Delete all data</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>
<item>
<widget class="QChartView" name="chart">
<property name="minimumSize">
<size>
<width>300</width>
<height>100</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QChartView</class>
<extends>QGraphicsView</extends>
<header>QtCharts</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,79 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 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 "radiosondegui.h"
#endif
#include "radiosonde.h"
#include "radiosondeplugin.h"
#include "radiosondewebapiadapter.h"
const PluginDescriptor RadiosondePlugin::m_pluginDescriptor = {
Radiosonde::m_featureId,
QStringLiteral("Radiosonde"),
QStringLiteral("6.20.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
RadiosondePlugin::RadiosondePlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& RadiosondePlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void RadiosondePlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(Radiosonde::m_featureIdURI, Radiosonde::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* RadiosondePlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* RadiosondePlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return RadiosondeGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* RadiosondePlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new Radiosonde(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* RadiosondePlugin::createFeatureWebAPIAdapter() const
{
return new RadiosondeWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_RADIOSONDEPLUGIN_H
#define INCLUDE_FEATURE_RADIOSONDEPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class RadiosondePlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.radiosonde")
public:
explicit RadiosondePlugin(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_RADIOSONDEPLUGIN_H

View File

@ -0,0 +1,149 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 "radiosondesettings.h"
const QStringList RadiosondeSettings::m_pipeTypes = {
QStringLiteral("RadiosondeDemod")
};
const QStringList RadiosondeSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.radiosondedemod")
};
RadiosondeSettings::RadiosondeSettings() :
m_rollupState(nullptr)
{
resetToDefaults();
}
void RadiosondeSettings::resetToDefaults()
{
m_title = "Radiosonde";
m_rgbColor = QColor(102, 0, 102).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
m_y1 = ALTITUDE;
m_y2 = TEMPERATURE;
for (int i = 0; i < RADIOSONDES_COLUMNS; i++)
{
m_radiosondesColumnIndexes[i] = i;
m_radiosondesColumnSizes[i] = -1; // Autosize
}
}
QByteArray RadiosondeSettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_title);
s.writeU32(2, m_rgbColor);
s.writeBool(3, m_useReverseAPI);
s.writeString(4, m_reverseAPIAddress);
s.writeU32(5, m_reverseAPIPort);
s.writeU32(6, m_reverseAPIFeatureSetIndex);
s.writeU32(7, m_reverseAPIFeatureIndex);
if (m_rollupState) {
s.writeBlob(8, m_rollupState->serialize());
}
s.writeS32(10, (int)m_y1);
s.writeS32(11, (int)m_y2);
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
s.writeS32(300 + i, m_radiosondesColumnIndexes[i]);
}
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
s.writeS32(400 + i, m_radiosondesColumnSizes[i]);
}
return s.final();
}
bool RadiosondeSettings::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(1, &m_title, "Radiosonde");
d.readU32(2, &m_rgbColor, QColor(102, 0, 102).rgb());
d.readBool(3, &m_useReverseAPI, false);
d.readString(4, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(5, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(6, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(7, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
if (m_rollupState)
{
d.readBlob(8, &bytetmp);
m_rollupState->deserialize(bytetmp);
}
d.readS32(10, (int *)&m_y1, (int)ALTITUDE);
d.readS32(11, (int *)&m_y2, (int)TEMPERATURE);
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
d.readS32(300 + i, &m_radiosondesColumnIndexes[i], i);
}
for (int i = 0; i < RADIOSONDES_COLUMNS; i++) {
d.readS32(400 + i, &m_radiosondesColumnSizes[i], -1);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,70 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDESETTINGS_H_
#define INCLUDE_FEATURE_RADIOSONDESETTINGS_H_
#include <QByteArray>
#include <QString>
#include "util/message.h"
class Serializable;
// Number of columns in the table
#define RADIOSONDES_COLUMNS 16
struct RadiosondeSettings
{
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
Serializable *m_rollupState;
enum ChartData {
NONE,
ALTITUDE,
TEMPERATURE,
HUMIDITY,
PRESSURE,
SPEED,
VERTICAL_RATE,
HEADING,
BATTERY_VOLTAGE
};
ChartData m_y1;
ChartData m_y2;
int m_radiosondesColumnIndexes[RADIOSONDES_COLUMNS];
int m_radiosondesColumnSizes[RADIOSONDES_COLUMNS];
RadiosondeSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; }
static const QStringList m_pipeTypes;
static const QStringList m_pipeURIs;
};
#endif // INCLUDE_FEATURE_RADIOSONDESETTINGS_H_

View File

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

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_Radiosonde_WEBAPIADAPTER_H
#define INCLUDE_Radiosonde_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "radiosondesettings.h"
/**
* Standalone API adapter only for the settings
*/
class RadiosondeWebAPIAdapter : public FeatureWebAPIAdapter {
public:
RadiosondeWebAPIAdapter();
virtual ~RadiosondeWebAPIAdapter();
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:
RadiosondeSettings m_settings;
};
#endif // INCLUDE_Radiosonde_WEBAPIADAPTER_H

View File

@ -0,0 +1,55 @@
<h1>Radiosonde Feature Plugin</h1>
<h2>Introduction</h2>
The Radiosonde feature displays a table containing the most recent information received from radiosondes
based on data received via [Radiosonde Demodulators](../../channelrx/demodradiasonde/readme.md).
The chart can plot two data series vs time for the radiosonde selected in the table.
The Radiosonde feature can draw ballons objects on the [Map](../../feature/map/readme.md) feature in 2D and 3D.
<h2>Interface</h2>
![Radiosonde feature plugin GUI](../../../doc/img/Radiosonde_plugin.png)
<h3>Radiosonde Table</h3>
The Radiosonde table displays the current status of each radiosonde, based on the latest received data from all Radiosonde Demodulators.
* Serial - The serial number that uniquely identifiers each radiosonde.
* Type - The type of radiosonde.
* 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.
* Alt (m) - The altitude of the radiosonde in metres.
* Spd (km/h) - Speed over ground in kilometres per hour.
* VR (m/s) - Vertical climb rate in metres per second.
* Hdg (°) - Heading in degrees.
* Status - Flight status of the radiosonde (E.g. On ground, ascent or descent).
* P (hPA) - Air pressure in hectopascals. Not all RS41s include a pressure sensor. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* T (°C) - Air temperature in degrees Celsius. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* U (%) - Relative humidity in percent. A value ending with 'U' indicates a uncalibrated estimate and may be inaccurate.
* Alt Max (m) - The maximum altitude seen for the radiosonde in metres.
* Freq (MHz) - The transmission frequency in megahertz as indicated by the radiosonde.
* BurstKill status - Whether the BurstKill timer is active.
* BurstKill timer - BurstKill timer.
* Updated - Gives the date and time the last message was received.
* Messages - Displays the number of messages received.
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 a table cell allows you to copy the cell contents, or find the radiosonde on the map.
<h3>Map</h3>
The Radiosonde feature can plot ballons (during ascent) and parachutes (during descent) on the [Map](../../feature/map/readme.md).
To use, simply open a Map feature and the Radiosonde plugin will display objects based upon the data it receives from that point.
Selecting a radiosonde 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.
![Radiosonde on map](../../../doc/img/Radiosonde_plugin_map.png)
<h2>Attribution</h2>
Hot-air-balloon icons created by Freepik - https://www.flaticon.com/free-icons/hot-air-balloon
Parachute icons created by Freepik - https://www.flaticon.com/free-icons/parachute

View File

@ -191,6 +191,7 @@ set(sdrbase_SOURCES
util/aprs.cpp
util/astronomy.cpp
util/azel.cpp
util/coordinates.cpp
util/crc.cpp
util/CRC64.cpp
util/csv.cpp
@ -210,6 +211,7 @@ set(sdrbase_SOURCES
util/planespotters.cpp
util/png.cpp
util/prettyprint.cpp
util/radiosonde.cpp
util/rtpsink.cpp
util/syncmessenger.cpp
util/samplesourceserializer.cpp
@ -406,6 +408,7 @@ set(sdrbase_HEADERS
util/aprs.h
util/astronomy.h
util/azel.h
util/coordinates.h
util/CRC64.h
util/csv.h
util/db.h
@ -430,6 +433,7 @@ set(sdrbase_HEADERS
util/planespotters.h
util/png.h
util/prettyprint.h
util/radiosonde.h
util/rtpsink.h
util/syncmessenger.h
util/samplesourceserializer.h

View File

@ -0,0 +1,318 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2011-2020 Cesium Contributors //
// Copyright (C) 2022 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 "coordinates.h"
#include "units.h"
// Scale cartesian position on to surface of ellipsoid
QVector3D Coordinates::scaleToGeodeticSurface(QVector3D cartesian, QVector3D oneOverRadii, QVector3D oneOverRadiiSquared)
{
float centerToleranceSquared = 0.1;
double x2 = cartesian.x() * cartesian.x() * oneOverRadii.x() * oneOverRadii.x();
double y2 = cartesian.y() * cartesian.y() * oneOverRadii.y() * oneOverRadii.y();
double z2 = cartesian.z() * cartesian.z() * oneOverRadii.z() * oneOverRadii.z();
double squaredNorm = x2 + y2 + z2;
double ratio = sqrt(1.0 / squaredNorm);
QVector3D intersection = cartesian * ratio;
if (squaredNorm < centerToleranceSquared) {
return intersection;
}
QVector3D gradient(
intersection.x() * oneOverRadiiSquared.x() * 2.0,
intersection.y() * oneOverRadiiSquared.y() * 2.0,
intersection.z() * oneOverRadiiSquared.z() * 2.0
);
double lambda = ((1.0 - ratio) * cartesian.length()) / (0.5 * gradient.length());
double correction = 0.0;
double func;
double denominator;
double xMultiplier;
double yMultiplier;
double zMultiplier;
double xMultiplier2;
double yMultiplier2;
double zMultiplier2;
double xMultiplier3;
double yMultiplier3;
double zMultiplier3;
do
{
lambda -= correction;
xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.x());
yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.y());
zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquared.z());
xMultiplier2 = xMultiplier * xMultiplier;
yMultiplier2 = yMultiplier * yMultiplier;
zMultiplier2 = zMultiplier * zMultiplier;
xMultiplier3 = xMultiplier2 * xMultiplier;
yMultiplier3 = yMultiplier2 * yMultiplier;
zMultiplier3 = zMultiplier2 * zMultiplier;
func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0;
denominator =
x2 * xMultiplier3 * oneOverRadiiSquared.x() +
y2 * yMultiplier3 * oneOverRadiiSquared.y() +
z2 * zMultiplier3 * oneOverRadiiSquared.z();
double derivative = -2.0 * denominator;
correction = func / derivative;
}
while (abs(func) > 0.000000000001);
QVector3D result(
cartesian.x() * xMultiplier,
cartesian.y() * yMultiplier,
cartesian.z() * zMultiplier
);
return result;
}
// QVector3D.normalized doesn't work with small numbers
QVector3D Coordinates::normalized(QVector3D vec)
{
QVector3D result;
float magnitude = vec.length();
result.setX(vec.x() / magnitude);
result.setY(vec.y() / magnitude);
result.setZ(vec.z() / magnitude);
return result;
}
// Convert ECEF position to geodetic coordinates
void Coordinates::ecefToGeodetic(double x, double y, double z, double &latitude, double &longitude, double &height)
{
QVector3D wgs84OneOverRadix(1.0 / 6378137.0,
1.0 / 6378137.0,
1.0 / 6356752.3142451793);
QVector3D wgs84OneOverRadiiSquared(1.0 / (6378137.0 * 6378137.0),
1.0 / (6378137.0 * 6378137.0),
1.0 / (6356752.3142451793 * 6356752.3142451793));
QVector3D cartesian(x, y, z);
QVector3D p = scaleToGeodeticSurface(cartesian, wgs84OneOverRadix, wgs84OneOverRadiiSquared);
QVector3D n = p * wgs84OneOverRadiiSquared;
n = normalized(n);
QVector3D h = cartesian - p;
longitude = atan2(n.y(), n.x());
latitude = asin(n.z());
longitude = Units::radiansToDegrees(longitude);
latitude = Units::radiansToDegrees(latitude);
double t = QVector3D::dotProduct(h, cartesian);
double sign = t >= 0.0 ? 1.0 : 0.0;
height = sign * h.length();
}
// Convert ECEF velocity to speed and heading
void Coordinates::ecefVelToSpeedHeading(double latitude, double longitude,
double velX, double velY, double velZ,
double &speed, double &verticalRate, double &heading)
{
if ((velX == 0.0) && (velY == 0.0) && (velZ == 0.0))
{
speed = 0.0;
heading = 0.0;
verticalRate = 0.0;
return;
}
double latRad = Units::degreesToRadians(latitude);
double lonRad = Units::degreesToRadians(longitude);
double sinLat = sin(latRad);
double cosLat = cos(latRad);
double sinLon = sin(lonRad);
double cosLon = cos(lonRad);
double velEast = -velX * sinLon + velY * cosLon;
double velNorth = -velX * sinLat * cosLon - velY * sinLat * sinLon + velZ * cosLat;
double velUp = velX * cosLat * cosLon + velY * cosLat * sinLon + velZ * sinLat;
speed = sqrt(velNorth * velNorth + velEast * velEast);
verticalRate = velUp;
double headingRad = atan2(velEast, velNorth);
heading = Units::radiansToDegrees(headingRad);
if (heading < 0.0) {
heading += 360.0;
} else if (heading >= 360.0) {
heading -= 360.0;
}
}
// Convert a position specified in longitude, latitude in degrees and height in metres above WGS84 ellipsoid in to
// Earth Centered Earth Fixed frame cartesian coordinates
// See Cesium.Cartesian3.fromDegrees
QVector3D Coordinates::geodeticToECEF(double longitude, double latitude, double height)
{
return geodeticRadiansToECEF(Units::degreesToRadians(longitude), Units::degreesToRadians(latitude), height);
}
// FIXME: QVector3D is only float!
// See Cesium.Cartesian3.fromRadians
QVector3D Coordinates::geodeticRadiansToECEF(double longitude, double latitude, double height)
{
QVector3D wgs84RadiiSquared(6378137.0 * 6378137.0, 6378137.0 * 6378137.0, 6356752.3142451793 * 6356752.3142451793);
double cosLatitude = cos(latitude);
QVector3D n;
n.setX(cosLatitude * cos(longitude));
n.setY(cosLatitude * sin(longitude));
n.setZ(sin(latitude));
n.normalize();
QVector3D k;
k = wgs84RadiiSquared * n;
double gamma = sqrt(QVector3D::dotProduct(n, k));
k = k / gamma;
n = n * height;
return k + n;
}
// Convert heading, pitch and roll in degrees to a quaternoin
// See: Cesium.Quaternion.fromHeadingPitchRoll
QQuaternion Coordinates::fromHeadingPitchRoll(double heading, double pitch, double roll)
{
QVector3D xAxis(1, 0, 0);
QVector3D yAxis(0, 1, 0);
QVector3D zAxis(0, 0, 1);
QQuaternion rollQ = QQuaternion::fromAxisAndAngle(xAxis, roll);
QQuaternion pitchQ = QQuaternion::fromAxisAndAngle(yAxis, -pitch);
QQuaternion headingQ = QQuaternion::fromAxisAndAngle(zAxis, -heading);
QQuaternion temp = rollQ * pitchQ;
return headingQ * temp;
}
// Calculate a transformation matrix from a East, North, Up frame at the given position to Earth Centered Earth Fixed frame
// See: Cesium.Transforms.eastNorthUpToFixedFrame
QMatrix4x4 Coordinates::eastNorthUpToECEF(QVector3D origin)
{
// TODO: Handle special case at centre of earth and poles
QVector3D up = origin.normalized();
QVector3D east(-origin.y(), origin.x(), 0.0);
east.normalize();
QVector3D north = QVector3D::crossProduct(up, east);
QMatrix4x4 result(
east.x(), north.x(), up.x(), origin.x(),
east.y(), north.y(), up.y(), origin.y(),
east.z(), north.z(), up.z(), origin.z(),
0.0, 0.0, 0.0, 1.0
);
return result;
}
// Convert 3x3 rotation matrix to a quaternoin
// Although there is a method for this in Qt: QQuaternion::fromRotationMatrix, it seems to
// result in different signs, so the following is based on Cesium code
QQuaternion Coordinates::fromRotation(QMatrix3x3 mat)
{
QQuaternion q;
double trace = mat(0, 0) + mat(1, 1) + mat(2, 2);
if (trace > 0.0)
{
double root = sqrt(trace + 1.0);
q.setScalar(0.5 * root);
root = 0.5 / root;
q.setX((mat(2,1) - mat(1,2)) * root);
q.setY((mat(0,2) - mat(2,0)) * root);
q.setZ((mat(1,0) - mat(0,1)) * root);
}
else
{
double next[] = {1, 2, 0};
int i = 0;
if (mat(1,1) > mat(0,0)) {
i = 1;
}
if (mat(2,2) > mat(0,0) && mat(2,2) > mat(1,1)) {
i = 2;
}
int j = next[i];
int k = next[j];
double root = sqrt(mat(i,i) - mat(j,j) - mat(k,k) + 1);
double quat[] = {0.0, 0.0, 0.0};
quat[i] = 0.5 * root;
root = 0.5 / root;
q.setScalar((mat(j,k) - mat(k,j)) * root);
quat[j] = (mat(i,j) + mat(j,i)) * root;
quat[k] = (mat(i,k) + mat(k,i)) * root;
q.setX(-quat[0]);
q.setY(-quat[1]);
q.setZ(-quat[2]);
}
return q;
}
// Calculate orientation quaternion for a model (such as an aircraft) based on position and (HPR) heading, pitch and roll (in degrees)
// While Cesium supports specifying orientation as HPR, CZML doesn't currently. See https://github.com/CesiumGS/cesium/issues/5184
// CZML requires the orientation to be in the Earth Centered Earth Fixed (geocentric) reference frame (https://en.wikipedia.org/wiki/Local_tangent_plane_coordinates)
// The orientation therefore depends not only on HPR but also on position
//
// glTF uses a right-handed axis convention; that is, the cross product of right and forward yields up. glTF defines +Y as up, +Z as forward, and -X as right.
// Cesium.Quaternion.fromHeadingPitchRoll Heading is the rotation about the negative z axis. Pitch is the rotation about the negative y axis. Roll is the rotation about the positive x axis.
QQuaternion Coordinates::orientation(double longitude, double latitude, double altitude, double heading, double pitch, double roll)
{
// Forward direction for gltf models in Cesium seems to be Eastward, rather than Northward, so we adjust heading by -90 degrees
heading = -90 + heading;
// Convert position to Earth Centered Earth Fixed (ECEF) frame
QVector3D positionECEF = geodeticToECEF(longitude, latitude, altitude);
// Calculate matrix to transform from East, North, Up (ENU) frame to ECEF frame
QMatrix4x4 enuToECEFTransform = eastNorthUpToECEF(positionECEF);
// Calculate rotation based on HPR in ENU frame
QQuaternion hprENU = fromHeadingPitchRoll(heading, pitch, roll);
// Transform rotation from ENU to ECEF
QMatrix3x3 hprENU3 = hprENU.toRotationMatrix();
QMatrix4x4 hprENU4(hprENU3);
QMatrix4x4 transform = enuToECEFTransform * hprENU4;
// Convert from 4x4 matrix to 3x3 matrix then to a quaternion
QQuaternion oq = fromRotation(transform.toGenericMatrix<3,3>());
return oq;
}

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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_COORDINATES_H
#define INCLUDE_COORDINATES_H
#include "export.h"
#include <QDateTime>
#include <QVector3D>
#include <QMatrix3x3>
#include <QMatrix4x4>
#include <QQuaternion>
// Functions for transformations between geodetic and ECEF coordinates
class SDRBASE_API Coordinates {
public:
static QVector3D geodeticToECEF(double longitude, double latitude, double height=0.0);
static QVector3D geodeticRadiansToECEF(double longitude, double latitude, double height=0.0);
static QMatrix4x4 eastNorthUpToECEF(QVector3D origin);
static void ecefToGeodetic(double x, double y, double z, double &latitude, double &longitude, double &height);
static void ecefVelToSpeedHeading(double latitude, double longitude,
double velX, double velY, double velZ,
double &speed, double &verticalRate, double &heading);
static QQuaternion orientation(double longitude, double latitude, double altitude, double heading, double pitch, double roll);
protected:
static QVector3D scaleToGeodeticSurface(QVector3D cartesian, QVector3D oneOverRadii, QVector3D oneOverRadiiSquared);
static QVector3D normalized(QVector3D vec);
static QQuaternion fromHeadingPitchRoll(double heading, double pitch, double roll);
static QQuaternion fromRotation(QMatrix3x3 mat);
};
#endif // INCLUDE_COORDINATES_H

710
sdrbase/util/radiosonde.cpp Normal file
View File

@ -0,0 +1,710 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// Based on code and docs by einergehtnochrein, rs1729 and bazjo //
// //
// 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 <QDateTime>
#include <QVector3D>
#include "util/radiosonde.h"
#include "util/coordinates.h"
RS41Frame::RS41Frame(const QByteArray ba) :
m_statusValid(false),
m_batteryVoltage(0.0),
m_pcbTemperature(0),
m_humiditySensorHeating(0),
m_transmitPower(0),
m_maxSubframeNumber(0),
m_subframeNumber(0),
m_measValid(false),
m_gpsInfoValid(false),
m_posValid(false),
m_latitude(0.0),
m_longitude(0.0),
m_height(0.0),
m_bytes(ba),
m_temperatureCalibrated(false),
m_pressureCalibrated(false),
m_humidityTemperatureCalibrated(false),
m_humidityCalibrated(false)
{
int length = getFrameLength(ba[RS41_OFFSET_FRAME_TYPE]);
for (int i = RS41_OFFSET_BLOCK_0; i < length; )
{
uint8_t blockID = ba[i+0];
uint8_t blockLength = ba[i+1];
switch (blockID)
{
case RS41_ID_STATUS:
decodeStatus(ba.mid(i+2, blockLength));
break;
case RS41_ID_MEAS:
decodeMeas(ba.mid(i+2, blockLength));
break;
case RS41_ID_GPSINFO:
decodeGPSInfo(ba.mid(i+2, blockLength));
break;
case RS41_ID_GPSRAW:
break;
case RS41_ID_GPSPOS:
decodeGPSPos(ba.mid(i+2, blockLength));
break;
case RS41_ID_EMPTY:
break;
}
i += 2 + blockLength + 2; // ID, length, data, CRC
}
}
QString RS41Frame::toHex()
{
return m_bytes.toHex();
}
uint16_t RS41Frame::getUInt16(const QByteArray ba, int offset) const
{
return (ba[offset] & 0xff)
| ((ba[offset+1] & 0xff) << 8);
}
uint32_t RS41Frame::getUInt24(const QByteArray ba, int offset) const
{
return (ba[offset] & 0xff)
| ((ba[offset+1] & 0xff) << 8)
| ((ba[offset+2] & 0xff) << 16);
}
uint32_t RS41Frame::getUInt32(const QByteArray ba, int offset) const
{
return (ba[offset] & 0xff)
| ((ba[offset+1] & 0xff) << 8)
| ((ba[offset+2] & 0xff) << 16)
| ((ba[offset+3] & 0xff) << 24);
}
void RS41Frame::decodeStatus(const QByteArray ba)
{
m_statusValid = true;
m_frameNumber = getUInt16(ba, 0);
m_serial = QString(ba.mid(0x2, 8));
m_batteryVoltage = (ba[0xa] & 0xff) / 10.0;
QStringList phases = {"Ground", "Ascent", "0x2", "Descent"};
int phase = ba[0xd] & 0x3;
m_flightPhase = phases[phase];
m_batteryStatus = (ba[0xe] & 0x10) == 0 ? "OK" : "Low";
m_pcbTemperature = (ba[0x10] & 0xff);
m_humiditySensorHeating = getUInt16(ba, 0x13);
m_transmitPower = ba[0x15] & 0xff;
m_maxSubframeNumber = ba[0x16] & 0xff;
m_subframeNumber = ba[0x17] & 0xff;
m_subframe = ba.mid(0x18, 16);
}
void RS41Frame::decodeMeas(const QByteArray ba)
{
m_measValid = true;
m_tempMain = getUInt24(ba, 0x0);
m_tempRef1 = getUInt24(ba, 0x3);
m_tempRef2 = getUInt24(ba, 0x6);
m_humidityMain = getUInt24(ba, 0x9);
m_humidityRef1 = getUInt24(ba, 0xc);
m_humidityRef2 = getUInt24(ba, 0xf);
m_humidityTempMain = getUInt24(ba, 0x12);
m_humidityTempRef1 = getUInt24(ba, 0x15);
m_humidityTempRef2 = getUInt24(ba, 0x18);
m_pressureMain = getUInt24(ba, 0x1b);
m_pressureRef1 = getUInt24(ba, 0x1e);
m_pressureRef2 = getUInt24(ba, 0x21);
m_pressureTemp = getUInt16(ba, 0x26) / 100.0f;
}
void RS41Frame::decodeGPSInfo(const QByteArray ba)
{
m_gpsInfoValid = true;
uint16_t gpsWeek = getUInt16(ba, 0x0);
uint32_t gpsTimeOfWeek = getUInt32(ba, 0x2); // Milliseconds
QDateTime epoch(QDate(1980, 1, 6), QTime(0, 0, 0), Qt::OffsetFromUTC, 18); // GPS doesn't count leap seconds
m_gpsDateTime = epoch.addDays(gpsWeek*7).addMSecs(gpsTimeOfWeek);
}
void RS41Frame::decodeGPSPos(const QByteArray ba)
{
m_satellitesUsed = ba[0x12] & 0xff;
if (m_satellitesUsed > 0)
{
m_posValid = true;
int32_t ecefX = (int32_t)getUInt32(ba, 0x0);
int32_t ecefY = (int32_t)getUInt32(ba, 0x4);
int32_t ecefZ = (int32_t)getUInt32(ba, 0x8);
// Convert cm to m
// Convert to latitude, longitude and altitude
Coordinates::ecefToGeodetic(ecefX / 100.0, ecefY / 100.0, ecefZ / 100.0, m_latitude, m_longitude, m_height);
int32_t velX = (int16_t)getUInt16(ba, 0xc);
int32_t velY = (int16_t)getUInt16(ba, 0xe);
int32_t velZ = (int16_t)getUInt16(ba, 0x10);
// Convert cm/s to m/s
// Calculate speed / heading
Coordinates::ecefVelToSpeedHeading(m_latitude, m_longitude, velX / 100.0, velY / 100.0, velZ / 100.0, m_speed, m_verticalRate, m_heading);
}
}
// Find the water vapor saturation pressure for a given temperature.
float waterVapourSaturationPressure(float tCelsius)
{
// Convert to Kelvin
float T = tCelsius + 273.15f;
// Correction
T = - 0.4931358f
+ (1.0f + 4.6094296e-3f) * T
- 1.3746454e-5f * T * T
+ 1.2743214e-8f * T * T * T;
// Hyland and Wexler equation
float p = expf(-5800.2206f / T
+ 1.3914993f
+ 6.5459673f * logf(T)
- 4.8640239e-2f * T
+ 4.1764768e-5f * T * T
- 1.4452093e-8f * T * T * T);
// Scale result to hPa
return p / 100.0f;
}
float calcT(int f, int f1, int f2, float r1, float r2, float *poly, float *cal)
{
/*float g = (float)(f2-f1) / (r2-r1); // gain
float Rb = (f1*r2-f2*r1) / (float)(f2-f1); // offset
float Rc = f/g - Rb;
float R = Rc * cal[0];
float T = (poly[0] + poly[1]*R + poly[2]*R*R + cal[1])*(1.0 + cal[2]);
return T;
*/
// Convert integer measurement to scale factor
float s = (f-f1)/(float)(f2-f1);
// Calculate resistance (scale between two reference resistors)
float rUncal = r1 + (r2 - r1) * s;
float r = rUncal * cal[0];
// Convert resistance to temperature
float tUncal = poly[0] + poly[1]*r + poly[2]*r*r;
// Correct temperature (5th order polynomial)
float tCal = 0.0f;
for (int i = 6; i > 0; i--)
{
tCal *= tUncal;
tCal += cal[i];
}
tCal += tUncal;
return tCal;
}
float calcU(int cInt, int cMin, int cMax, float c1, float c2, float T, float HT, float *capCal, float *matrixCal)
{
//qDebug() << "cInt " << cInt << " cMin " << cMin << " cMax " << cMax << " c1 " << c1 << " c2 " << c2 << " T " << T << " HT " << HT << " capCal[0] " << capCal[0] << " capCal[1] " << capCal[1];
/*
float a0 = 7.5f;
float a1 = 350.0f / capCal[0];
float fh = (cInt-cMin) / (float)(cMax-cMin);
float rh = 100.0f * (a1*fh - a0);
float T0 = 0.0f;
float T1 = -25.0f;
rh += T0 - T/5.5;
if (T < T1) {
rh *= 1.0 + (T1-T)/90.0;
}
if (rh < 0.0) {
rh = 0.0;
}
if (rh > 100.0) {
rh = 100.0;
}
if (T < -273.0) {
rh = -1.0;
}
qDebug() << "RH old method: " << rh; */
// Convert integer measurement to scale factor
float s = (cInt - cMin) / (float)(cMax-cMin);
// Calculate capacitance (scale between two reference caps)
float cUncal = c1 + (c2 - c1) * s;
float cCal = (cUncal / capCal[0] - 1.0f) * capCal[1];
float uUncal = 0.0f;
float t = (HT - 20.0f) / 180.0f;
float f1 = 1.0f;
for (int i = 0; i < 7; i++)
{
float f2 = 1.0;
for (int j = 0; j < 6; j++)
{
uUncal += f1 * f2 * matrixCal[i*6+j];
f2 *= t;
}
f1 *= cCal;
}
// Adjust for difference in outside air temperature and the humidty sensor temperature
float uCal = uUncal * waterVapourSaturationPressure(T) / waterVapourSaturationPressure(HT);
// Ensure within range of 0..100%
uCal = std::min(100.0f, uCal);
uCal = std::max(0.0f, uCal);
return uCal;
}
float calcP(int f, int f1, int f2, float pressureTemp, float *cal)
{
// Convert integer measurement to scale factor
float s = (f-f1) / (float)(f2-f1);
float t = pressureTemp;
float t2 = t * t;
float t3 = t2 * t;
float poly[6];
poly[0] = cal[0] + cal[7] * t + cal[11] * t2 + cal[15] * t3;
poly[1] = cal[1] + cal[8] * t + cal[12] * t2 + cal[16] * t3;
poly[2] = cal[2] + cal[9] * t + cal[13] * t2 + cal[17] * t3;
poly[3] = cal[3] + cal[10] * t + cal[14] * t2;
poly[4] = cal[4];
poly[5] = cal[5];
float p = cal[6] / s;
float p2 = p * p;
float p3 = p2 * p;
float p4 = p3 * p;
float p5 = p4 * p;
float pCal = poly[0] + poly[1] * p + poly[2] * p2 + poly[3] * p3 + poly[4] * p4 + poly[5] * p5;
return pCal;
}
float RS41Frame::getPressureFloat(const RS41Subframe *subframe)
{
if (!m_pressureCalibrated) {
calcPressure(subframe);
}
return m_pressure;
}
QString RS41Frame::getPressureString(const RS41Subframe *subframe)
{
if (!m_pressureCalibrated) {
calcPressure(subframe);
}
return m_pressureString;
}
float RS41Frame::getTemperatureFloat(const RS41Subframe *subframe)
{
if (!m_temperatureCalibrated) {
calcTemperature(subframe);
}
return m_temperature;
}
QString RS41Frame::getTemperatureString(const RS41Subframe *subframe)
{
if (!m_temperatureCalibrated) {
calcTemperature(subframe);
}
return m_temperatureString;
}
void RS41Frame::calcPressure(const RS41Subframe *subframe)
{
float cal[18];
if (m_pressureMain == 0)
{
m_pressure = 0.0f;
m_pressureString = "";
return;
}
m_pressureCalibrated = subframe->getPressureCal(cal);
m_pressure = calcP(m_pressureMain, m_pressureRef1, m_pressureRef2, m_pressureTemp, cal);
// RS41 pressure resolution of 0.01hPa
m_pressureString = QString::number(m_pressure, 'f', 2);
if (!m_pressureCalibrated) {
m_pressureString = m_pressureString + "U"; // U for uncalibrated
}
}
void RS41Frame::calcTemperature(const RS41Subframe *subframe)
{
float r1, r2;
float poly[3];
float cal[7];
if (m_tempMain == 0)
{
m_temperature = 0.0f;
m_temperatureString = "";
return;
}
m_temperatureCalibrated = subframe->getTempCal(r1, r2, poly, cal);
m_temperature = calcT(m_tempMain, m_tempRef1, m_tempRef2,
r1, r2,
poly, cal);
// RS41 temperature resolution of 0.01C
m_temperatureString = QString::number(m_temperature, 'f', 2);
if (!m_temperatureCalibrated) {
m_temperatureString = m_temperatureString + "U"; // U for uncalibrated
}
}
float RS41Frame::getHumidityTemperatureFloat(const RS41Subframe *subframe)
{
if (!m_humidityTemperatureCalibrated) {
calcHumidityTemperature(subframe);
}
return m_humidityTemperature;
}
void RS41Frame::calcHumidityTemperature(const RS41Subframe *subframe)
{
float r1, r2;
float poly[3];
float cal[7];
if (m_humidityTempMain == 0)
{
m_humidityTemperature = 0.0f;
return;
}
m_humidityTemperatureCalibrated = subframe->getHumidityTempCal(r1, r2, poly, cal);
m_humidityTemperature = calcT(m_humidityTempMain, m_humidityTempRef1, m_humidityTempRef2,
r1, r2,
poly, cal);
}
float RS41Frame::getHumidityFloat(const RS41Subframe *subframe)
{
if (!m_humidityCalibrated) {
calcHumidity(subframe);
}
return m_humidity;
}
QString RS41Frame::getHumidityString(const RS41Subframe *subframe)
{
if (!m_humidityCalibrated) {
calcHumidity(subframe);
}
return m_humidityString;
}
void RS41Frame::calcHumidity(const RS41Subframe *subframe)
{
float c1, c2;
float capCal[2];
float calMatrix[7*6];
if (m_humidityMain == 0)
{
m_humidity = 0.0f;
m_humidityString = "";
return;
}
float temperature = getTemperatureFloat(subframe);
float humidityTemperature = getHumidityTemperatureFloat(subframe);
bool humidityCalibrated = subframe->getHumidityCal(c1, c2, capCal, calMatrix);
m_humidityCalibrated = m_temperatureCalibrated && m_humidityTemperatureCalibrated && humidityCalibrated;
m_humidity = calcU(m_humidityMain, m_humidityRef1, m_humidityRef2,
c1, c2,
temperature, humidityTemperature,
capCal, calMatrix);
// RS41 humidity resolution of 0.1%
m_humidityString = QString::number(m_humidity, 'f', 1);
if (!m_humidityCalibrated) {
m_humidityString = m_humidityString + "U"; // U for uncalibrated
}
}
RS41Frame* RS41Frame::decode(const QByteArray ba)
{
return new RS41Frame(ba);
}
int RS41Frame::getFrameLength(int frameType)
{
return frameType == RS41_FRAME_STD ? RS41_LENGTH_STD : RS41_LENGTH_EXT;
}
RS41Subframe::RS41Subframe() :
m_subframe(51*16, (char)0)
{
for (int i = 0; i < 51; i++) {
m_subframeValid[i] = false;
}
}
// Update subframe with subframe data from received message
void RS41Subframe::update(RS41Frame *message)
{
m_subframeValid[message->m_subframeNumber] = true;
int offset = message->m_subframeNumber * 16;
for (int i = 0; i < 16; i++) {
m_subframe[offset+i] = message->m_subframe[i];
}
}
// Indicate if we have all the required temperature calibration data
bool RS41Subframe::hasTempCal() const
{
return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[5] && m_subframeValid[6] && m_subframeValid[7];
}
// Get temperature calibration data
// r1, r2 - Temperature reference resistances (Ohms)
// poly - Resistance to temperature 2nd order polynomial
bool RS41Subframe::getTempCal(float &r1, float &r2, float *poly, float *cal) const
{
if (hasTempCal())
{
r1 = getFloat(0x3d);
r2 = getFloat(0x41);
for (int i = 0; i < 3; i++) {
poly[i] = getFloat(0x4d + i * 4);
}
for (int i = 0; i < 7; i++) {
cal[i] = getFloat(0x59 + i * 4);
}
return true;
}
else
{
// Use default values
r1 = 750.0f;
r2 = 1100.0f;
poly[0] = -243.9108f;
poly[1] = 0.187654f;
poly[2] = 8.2e-06f;
cal[0] = 1.279928f;
for (int i = 1; i < 7; i++) {
cal[i] = 0.0f;
}
return false;
}
}
// Indicate if we have all the required humidty calibration data
bool RS41Subframe::hasHumidityCal() const
{
return m_subframeValid[4] && m_subframeValid[7]
&& m_subframeValid[8] && m_subframeValid[9] && m_subframeValid[0xa] && m_subframeValid[0xb]
&& m_subframeValid[0xc] && m_subframeValid[0xd] && m_subframeValid[0xe] && m_subframeValid[0xf]
&& m_subframeValid[0x10] && m_subframeValid[0x11] && m_subframeValid[0x12];
}
// Get humidty calibration data
bool RS41Subframe::getHumidityCal(float &c1, float &c2, float *capCal, float *calMatrix) const
{
if (hasHumidityCal())
{
c1 = getFloat(0x45);
c2 = getFloat(0x49);
for (int i = 0; i < 2; i++) {
capCal[i] = getFloat(0x75 + i * 4);
}
for (int i = 0; i < 7*6; i++) {
calMatrix[i] = getFloat(0x7d + i * 4);
}
return true;
}
else
{
// Use default values
c1 = 0.0f;
c2 = 47.0f;
capCal[0] = 45.9068f;
capCal[1] = 4.92924f;
static const float calMatrixDefault[7*6] = {
-0.002586f, -2.24367f, 9.92294f, -3.61913f, 54.3554f, -93.3012f,
51.7056f, 38.8709f, 209.437f, -378.437f, 9.17326f, 19.5301f,
150.257f, -150.907f, -280.315f, 182.293f, 3247.39f, 4083.65f,
-233.568f, 345.375f, 200.217f, -388.246f, -3617.66f, 0.0f,
225.841f, -233.051f, 0.0f, 0.0f, 0.0f, 0.0f,
-93.0635f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f
};
std::copy(calMatrixDefault, calMatrixDefault + 7*6, calMatrix);
return false;
}
}
// Indicate if we have all the required humidty temperature sensor calibration data
bool RS41Subframe::hasHumidityTempCal() const
{
return m_subframeValid[3] && m_subframeValid[4] && m_subframeValid[0x12] && m_subframeValid[0x13] && m_subframeValid[0x14];
}
// Get humidty temperature sensor calibration data
bool RS41Subframe::getHumidityTempCal(float &r1, float &r2, float *poly, float *cal) const
{
if (hasHumidityTempCal())
{
r1 = getFloat(0x3d);
r2 = getFloat(0x41);
for (int i = 0; i < 3; i++) {
poly[i] = getFloat(0x125 + i * 4);
}
for (int i = 0; i < 7; i++) {
cal[i] = getFloat(0x131 + i * 4);
}
return true;
}
else
{
// Use default values
r1 = 750.0f;
r2 = 1100.0f;
poly[0] = -243.9108f;
poly[1] = 0.187654f;
poly[2] = 8.2e-06f;
cal[0] = 1.279928f;
for (int i = 1; i < 7; i++) {
cal[i] = 0.0f;
}
return false;
}
}
// Indicate if we have all the required pressure calibration data
bool RS41Subframe::hasPressureCal() const
{
return m_subframeValid[0x25] && m_subframeValid[0x26] && m_subframeValid[0x27]
&& m_subframeValid[0x28] && m_subframeValid[0x29] && m_subframeValid[0x2a];
}
// Get pressure calibration data
bool RS41Subframe::getPressureCal(float *cal) const
{
if (hasPressureCal())
{
for (int i = 0; i < 18; i++) {
cal[i] = getFloat(0x25e + i * 4);
}
return true;
}
else
{
// Use default values - TODO: Need to obtain from inflight device
for (int i = 0; i < 18; i++) {
cal[i] = 0.0f;
}
return false;
}
}
// Get type of RS41. E.g. "RS41-SGP"
QString RS41Subframe::getType() const
{
if (m_subframeValid[0x21] & m_subframeValid[0x22])
{
return QString(m_subframe.mid(0x218, 10)).trimmed();
}
else
{
return "RS41";
}
}
// Get transmission frequency in MHz
QString RS41Subframe::getFrequencyMHz() const
{
if (m_subframeValid[0])
{
uint8_t lower = m_subframe[2] & 0xff;
uint8_t upper = m_subframe[3] & 0xff;
float freq = 400.0 + (upper + (lower / 255.0)) * 0.04;
return QString::number(freq, 'f', 3);
}
else
{
return "";
}
}
QString RS41Subframe::getBurstKillStatus() const
{
if (m_subframeValid[2])
{
uint8_t status = m_subframe[0x2b];
return status == 0 ? "Inactive" : "Active";
}
else
{
return "";
}
}
// Seconds until power-off once active
QString RS41Subframe::getBurstKillTimer() const
{
if (m_subframeValid[0x31])
{
uint16_t secs = getUInt16(0x316);
QTime t(0, 0, 0);
t = t.addSecs(secs);
return t.toString("hh:mm:ss");
}
else
{
return "";
}
}
uint16_t RS41Subframe::getUInt16(int offset) const
{
return (m_subframe[offset] & 0xff) | ((m_subframe[offset+1] & 0xff) << 8);
}
float RS41Subframe::getFloat(int offset) const
{
float f;
// Assumes host is little endian with 32-bit float
memcpy(&f, m_subframe.data() + offset, 4);
return f;
}

180
sdrbase/util/radiosonde.h Normal file
View File

@ -0,0 +1,180 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOSONDE_H
#define INCLUDE_RADIOSONDE_H
#include <cstdint>
#include <algorithm>
#include <iterator>
#include <QtCore>
#include <QDateTime>
#include "util/units.h"
#include "export.h"
#define RS41_LENGTH_STD 320
#define RS41_LENGTH_EXT 518
#define RS41_OFFSET_RS 0x08
#define RS41_OFFSET_FRAME_TYPE 0x38
#define RS41_OFFSET_BLOCK_0 0x39
#define RS41_FRAME_STD 0x0f
#define RS41_FRAME_EXT 0xf0
#define RS41_ID_STATUS 0x79
#define RS41_ID_MEAS 0x7a
#define RS41_ID_GPSINFO 0x7c
#define RS41_ID_GPSRAW 0x7d
#define RS41_ID_GPSPOS 0x7b
#define RS41_ID_EMPTY 0x76
#define RS41_RS_N 255
#define RS41_RS_K 231
#define RS41_RS_2T (RS41_RS_N-RS41_RS_K)
#define RS41_RS_INTERLEAVE 2
#define RS41_RS_DATA (264/RS41_RS_INTERLEAVE)
#define RS41_RS_PAD (RS41_RS_K-RS41_RS_DATA)
class RS41Subframe;
// Frame of data transmitted by RS41 radiosonde
class SDRBASE_API RS41Frame {
public:
// Status
bool m_statusValid;
uint16_t m_frameNumber; // Increments every frame
QString m_serial; // Serial number
float m_batteryVoltage; // In volts
QString m_flightPhase; // On ground, ascent, descent
QString m_batteryStatus; // OK or Low
uint8_t m_pcbTemperature; // In degrees C
uint16_t m_humiditySensorHeating; // 0..1000
uint8_t m_transmitPower; // 0..7
uint8_t m_maxSubframeNumber;
uint8_t m_subframeNumber;
QByteArray m_subframe; // 16 bytes of subframe
// Meas
bool m_measValid;
uint32_t m_tempMain;
uint32_t m_tempRef1;
uint32_t m_tempRef2;
uint32_t m_humidityMain;
uint32_t m_humidityRef1;
uint32_t m_humidityRef2;
uint32_t m_humidityTempMain;
uint32_t m_humidityTempRef1;
uint32_t m_humidityTempRef2;
uint32_t m_pressureMain;
uint32_t m_pressureRef1;
uint32_t m_pressureRef2;
float m_pressureTemp; // Pressure sensor module temperature - In degrees C
// GPSInfo
bool m_gpsInfoValid;
QDateTime m_gpsDateTime;
// GPSPos
bool m_posValid;
double m_latitude; // In degrees
double m_longitude; // In degrees
double m_height; // In metres
double m_speed; // In m/s
double m_heading; // In degreees
double m_verticalRate; // In m/s
int m_satellitesUsed;
RS41Frame(const QByteArray ba);
~RS41Frame() {}
QString toHex();
void decodeStatus(const QByteArray ba);
void decodeMeas(const QByteArray ba);
void decodeGPSInfo(const QByteArray ba);
void decodeGPSPos(const QByteArray ba);
float getPressureFloat(const RS41Subframe *subframe);
QString getPressureString(const RS41Subframe *subframe);
float getTemperatureFloat(const RS41Subframe *subframe);
QString getTemperatureString(const RS41Subframe *subframe);
float getHumidityTemperatureFloat(const RS41Subframe *subframe);
float getHumidityFloat(const RS41Subframe *subframe);
QString getHumidityString(const RS41Subframe *subframe);
static RS41Frame* decode(const QByteArray ba);
static int getFrameLength(int frameType);
protected:
uint16_t getUInt16(const QByteArray ba, int offset) const;
uint32_t getUInt24(const QByteArray ba, int offset) const;
uint32_t getUInt32(const QByteArray ba, int offset) const;
void calcPressure(const RS41Subframe *subframe);
void calcTemperature(const RS41Subframe *subframe);
void calcHumidityTemperature(const RS41Subframe *subframe);
void calcHumidity(const RS41Subframe *subframe);
QByteArray m_bytes;
float m_pressure;
QString m_pressureString;
bool m_pressureCalibrated;
float m_temperature;
QString m_temperatureString;
bool m_temperatureCalibrated;
float m_humidityTemperature;
bool m_humidityTemperatureCalibrated;
float m_humidity;
QString m_humidityString;
bool m_humidityCalibrated;
};
// RS41 subframe holding calibration data collected from multiple RS51Frames
class SDRBASE_API RS41Subframe {
public:
RS41Subframe();
void update(RS41Frame *message);
bool hasTempCal() const;
bool getTempCal(float &r1, float &r2, float *poly, float *cal) const;
bool hasHumidityCal() const;
bool getHumidityCal(float &c1, float &c2, float *capCal, float *calMatrix) const;
bool hasHumidityTempCal() const;
bool getHumidityTempCal(float &r1, float &r2, float *poly, float *cal) const;
bool hasPressureCal() const;
bool getPressureCal(float *cal) const;
QString getType() const;
QString getFrequencyMHz() const;
QString getBurstKillStatus() const;
QString getBurstKillTimer() const;
protected:
bool m_subframeValid[51];
QByteArray m_subframe;
uint16_t getUInt16(int offset) const;
float getFloat(int offset) const;
};
#endif // INCLUDE_RADIOSONDE_H

650
sdrbase/util/reedsolomon.h Normal file
View File

@ -0,0 +1,650 @@
/*
* Reed-Solomon -- Reed-Solomon encoder / decoder library
*
* Copyright (c) 2014 Hard Consulting Corporation.
* Copyright (c) 2006 Phil Karn, KA9Q
*
* It may be used under the terms of the GNU Lesser General Public License (LGPL).
*
* Simplified version of https://github.com/pjkundert/ezpwd-reed-solomon which
* seems to be the fastest open-source decoder.
*
*/
#ifndef REEDSOLOMON_H
#define REEDSOLOMON_H
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <vector>
// Preprocessor defines available:
//
// EZPWD_NO_MOD_TAB -- define to force no "modnn" Galois modulo table acceleration
//
//#define EZPWD_NO_MOD_TAB
namespace ReedSolomon {
//
// reed_solomon_base - Reed-Solomon codec generic base class
//
class reed_solomon_base {
public:
virtual size_t datum() const = 0; // a data element's bits
virtual size_t symbol() const = 0; // a symbol's bits
virtual int size() const = 0; // R-S block size (maximum total symbols)
virtual int nroots() const = 0; // R-S roots (parity symbols)
virtual int load() const = 0; // R-S net payload (data symbols)
virtual ~reed_solomon_base() {}
reed_solomon_base() {}
//
// {en,de}code -- Compute/Correct errors/erasures in a Reed-Solomon encoded container
//
/// For decode, optionally specify some known erasure positions (up to nroots()). If
/// non-empty 'erasures' is provided, it contains the positions of each erasure. If a
/// non-zero pointer to a 'position' vector is provided, its capacity will be increased to
/// be capable of storing up to 'nroots()' ints; the actual deduced error locations will be
/// returned.
///
/// RETURN VALUE
///
/// Return -1 on error. The encode returns the number of parity symbols produced;
/// decode returns the number of symbols corrected. Both errors and erasures are included,
/// so long as they are actually different than the deduced value. In other words, if a
/// symbol is marked as an erasure but it actually turns out to be correct, it's index will
/// NOT be included in the returned count, nor the modified erasure vector!
///
virtual int encode(const uint8_t *data, int len, uint8_t *parity) const = 0;
virtual int decode1(uint8_t *data, int len, uint8_t *parity,
const std::vector<int> &erasure = std::vector<int>(), std::vector<int> *position = 0) const = 0;
int decode(uint8_t *data,
int len,
int pad = 0, // ignore 'pad' symbols at start of array
const std::vector<int> &erasure = std::vector<int>(),
std::vector<int> *position = 0) const
{
return decode1((uint8_t*)(data + pad), len, (uint8_t*)(data + len), erasure, position);
}
};
//
// gfpoly - default field polynomial generator functor.
//
template <int PLY>
struct gfpoly {
int operator()(int sr) const
{
if (sr == 0) {
sr = 1;
} else {
sr <<= 1;
if (sr & (1 << 8))
sr ^= PLY;
sr &= ((1 << 8) - 1);
}
return sr;
}
};
//
// class reed_solomon_tabs -- R-S tables common to all RS(NN,*) with same SYM, PRM and PLY
//
template <int PRM, class PLY>
class reed_solomon_tabs : public reed_solomon_base {
public:
typedef uint8_t symbol_t;
static const size_t DATUM = 8; // bits
static const size_t SYMBOL = 8; // bits / symbol
static const int MM = 8;
static const int SIZE = (1 << 8) - 1; // maximum symbols in field
static const int NN = SIZE;
static const int A0 = SIZE;
static const int MODS // modulo table: 1/2 the symbol size squared, up to 4k
#if defined(EZPWD_NO_MOD_TAB)
= 0;
#else
= 8 > 8 ? (1 << 12) : (1 << 8 << 8 / 2);
#endif
static int iprim; // initialized to -1, below
protected:
static std::array<uint8_t, NN + 1> alpha_to;
static std::array<uint8_t, NN + 1> index_of;
static std::array<uint8_t, MODS> mod_of;
virtual ~reed_solomon_tabs() {}
reed_solomon_tabs() : reed_solomon_base()
{
// Do init if not already done. We check one value which is initialized to -1; this is
// safe, 'cause the value will not be set 'til the initializing thread has completely
// initialized the structure. Worst case scenario: multiple threads will initialize
// identically. No mutex necessary.
if (iprim >= 0)
return;
// Generate Galois field lookup tables
index_of[0] = A0; // log(zero) = -inf
alpha_to[A0] = 0; // alpha**-inf = 0
PLY poly;
int sr = poly(0);
for (int i = 0; i < NN; i++) {
index_of[sr] = i;
alpha_to[i] = sr;
sr = poly(sr);
}
// If it's not primitive, raise exception or abort
if (sr != alpha_to[0]) {
abort();
}
// Generate modulo table for some commonly used (non-trivial) values
for (int x = NN; x < NN + MODS; ++x)
mod_of[x - NN] = _modnn(x);
// Find prim-th root of 1, index form, used in decoding.
int iptmp = 1;
while (iptmp % PRM != 0)
iptmp += NN;
iprim = iptmp / PRM;
}
//
// modnn -- modulo replacement for galois field arithmetics, optionally w/ table acceleration
//
// @x: the value to reduce (will never be -'ve)
//
// where
// MM = number of bits per symbol
// NN = (2^MM) - 1
//
// Simple arithmetic modulo would return a wrong result for values >= 3 * NN
//
uint8_t _modnn(int x) const
{
while (x >= NN) {
x -= NN;
x = (x >> MM) + (x & NN);
}
return x;
}
uint8_t modnn(int x) const
{
while (x >= NN + MODS) {
x -= NN;
x = (x >> MM) + (x & NN);
}
if (MODS && x >= NN)
x = mod_of[x - NN];
return x;
}
};
//
// class reed_solomon - Reed-Solomon codec
//
// @TYP: A symbol datum; {en,de}code operates on arrays of these
// @DATUM: Bits per datum (a TYP())
// @SYM{BOL}, MM: Bits per symbol
// @NN: Symbols per block (== (1<<MM)-1)
// @alpha_to: log lookup table
// @index_of: Antilog lookup table
// @genpoly: Generator polynomial
// @NROOTS: Number of generator roots = number of parity symbols
// @FCR: First consecutive root, index form
// @PRM: Primitive element, index form
// @iprim: prim-th root of 1, index form
// @PLY: The primitive generator polynominal functor
//
// All reed_solomon<T, ...> instances with the same template type parameters share a common
// (static) set of alpha_to, index_of and genpoly tables. The first instance to be constructed
// initializes the tables.
//
// Each specialized type of reed_solomon implements a specific encode/decode method
// appropriate to its datum 'TYP'. When accessed via a generic reed_solomon_base pointer, only
// access via "safe" (size specifying) containers or iterators is available.
//
template <int RTS, int FCR, int PRM, class PLY>
class reed_solomon : public reed_solomon_tabs<PRM, PLY> {
public:
typedef reed_solomon_tabs<PRM, PLY> tabs_t;
using tabs_t::A0;
using tabs_t::DATUM;
using tabs_t::MM;
using tabs_t::NN;
using tabs_t::SIZE;
using tabs_t::SYMBOL;
using tabs_t::iprim;
using tabs_t::alpha_to;
using tabs_t::index_of;
using tabs_t::modnn;
static const int NROOTS = RTS;
static const int LOAD = SIZE - NROOTS; // maximum non-parity symbol payload
protected:
static std::array<uint8_t, NROOTS + 1> genpoly;
public:
virtual size_t datum() const { return DATUM; }
virtual size_t symbol() const { return SYMBOL; }
virtual int size() const { return SIZE; }
virtual int nroots() const { return NROOTS; }
virtual int load() const { return LOAD; }
using reed_solomon_base::decode;
virtual int decode1(uint8_t *data, int len, uint8_t *parity,
const std::vector<int> &erasure = std::vector<int>(), std::vector<int> *position = 0) const
{
return decode_mask(data, len, parity, erasure, position);
}
//
// decode_mask -- mask INP data into valid SYMBOL data
//
/// Incoming data may be in a variety of sizes, and may contain information beyond the
/// R-S symbol capacity. For example, we might use a 6-bit R-S symbol to correct the lower
/// 6 bits of an 8-bit data character. This would allow us to correct common substitution
/// errors (such as '2' for '3', 'R' for 'T', 'n' for 'm').
///
int decode_mask(uint8_t *data, int len,
uint8_t *parity = 0, // either 0, or pointer to all parity symbols
const std::vector<int> &erasure = std::vector<int>(), std::vector<int> *position = 0) const
{
if (!parity) {
len -= NROOTS;
parity = data + len;
}
std::array<uint8_t, SIZE> tmp;
uint8_t msk = static_cast<uint8_t>(~0UL << SYMBOL);
int corrects;
if (!erasure.size() && !position) {
// No erasures, and error position info not wanted.
corrects = decode(data, len, parity);
} else {
// Either erasure location info specified, or resultant error position info wanted;
// Prepare pos (a temporary, if no position vector provided), and copy any provided
// erasure positions. After number of corrections is known, resize the position
// vector. Thus, we use any supplied erasure info, and optionally return any
// correction position info separately.
std::vector<int> _pos;
std::vector<int> &pos = position ? *position : _pos;
pos.resize(std::max(size_t(NROOTS), erasure.size()));
std::copy(erasure.begin(), erasure.end(), pos.begin());
corrects = decode(data, len, parity, &pos.front(), erasure.size());
if (corrects > int(pos.size())) {
return -1;
}
pos.resize(std::max(0, corrects));
}
return corrects;
}
virtual ~reed_solomon()
{
}
reed_solomon() : reed_solomon_tabs<PRM, PLY>()
{
// We check one element of the array; this is safe, 'cause the value will not be
// initialized 'til the initializing thread has completely initialized the array. Worst
// case scenario: multiple threads will initialize identically. No mutex necessary.
if (genpoly[0])
return;
std::array<uint8_t, NROOTS + 1> tmppoly; // uninitialized
// Form RS code generator polynomial from its roots. Only lower-index entries are
// consulted, when computing subsequent entries; only index 0 needs initialization.
tmppoly[0] = 1;
for (int i = 0, root = FCR * PRM; i < NROOTS; i++, root += PRM) {
tmppoly[i + 1] = 1;
// Multiply tmppoly[] by @**(root + x)
for (int j = i; j > 0; j--) {
if (tmppoly[j] != 0)
tmppoly[j] = tmppoly[j - 1] ^ alpha_to[modnn(index_of[tmppoly[j]] + root)];
else
tmppoly[j] = tmppoly[j - 1];
}
// tmppoly[0] can never be zero
tmppoly[0] = alpha_to[modnn(index_of[tmppoly[0]] + root)];
}
// convert NROOTS entries of tmppoly[] to genpoly[] in index form for quicker encoding,
// in reverse order so genpoly[0] is last element initialized.
for (int i = NROOTS; i >= 0; --i)
genpoly[i] = index_of[tmppoly[i]];
}
virtual int encode(const uint8_t *data, int len, uint8_t *parity) // at least nroots
const
{
// Check length parameter for validity
int pad = NN - NROOTS - len;
for (int i = 0; i < NROOTS; i++) parity[i] = 0;
for (int i = 0; i < len; i++) {
uint8_t feedback = index_of[data[i] ^ parity[0]];
if (feedback != A0) {
for (int j = 1; j < NROOTS; j++)
parity[j] ^= alpha_to[modnn(feedback + genpoly[NROOTS - j])];
}
std::rotate(parity, parity + 1, parity + NROOTS);
if (feedback != A0)
parity[NROOTS - 1] = alpha_to[modnn(feedback + genpoly[0])];
else
parity[NROOTS - 1] = 0;
}
return NROOTS;
}
int decode(uint8_t *data, int len,
uint8_t *parity, // Requires: at least NROOTS
int *eras_pos = 0, // Capacity: at least NROOTS
int no_eras = 0, // Maximum: at most NROOTS
uint8_t *corr = 0) // Capacity: at least NROOTS
const
{
typedef std::array<uint8_t, NROOTS> typ_nroots;
typedef std::array<uint8_t, NROOTS + 1> typ_nroots_1;
typedef std::array<int, NROOTS> int_nroots;
typ_nroots_1 lambda{{0}};
typ_nroots syn;
typ_nroots_1 b;
typ_nroots_1 t;
typ_nroots_1 omega;
int_nroots root;
typ_nroots_1 reg;
int_nroots loc;
int count = 0;
// Check length parameter and erasures for validity
int pad = NN - NROOTS - len;
if (no_eras) {
if (no_eras > NROOTS) {
return -1;
}
for (int i = 0; i < no_eras; ++i) {
if (eras_pos[i] < 0 || eras_pos[i] >= len + NROOTS) {
return -1;
}
}
}
// form the syndromes; i.e., evaluate data(x) at roots of g(x)
for (int i = 0; i < NROOTS; i++)
syn[i] = data[0];
for (int j = 1; j < len; j++) {
for (int i = 0; i < NROOTS; i++) {
if (syn[i] == 0) {
syn[i] = data[j];
} else {
syn[i] = data[j] ^ alpha_to[modnn(index_of[syn[i]] + (FCR + i) * PRM)];
}
}
}
for (int j = 0; j < NROOTS; j++) {
for (int i = 0; i < NROOTS; i++) {
if (syn[i] == 0) {
syn[i] = parity[j];
} else {
syn[i] = parity[j] ^ alpha_to[modnn(index_of[syn[i]] + (FCR + i) * PRM)];
}
}
}
// Convert syndromes to index form, checking for nonzero condition
uint8_t syn_error = 0;
for (int i = 0; i < NROOTS; i++) {
syn_error |= syn[i];
syn[i] = index_of[syn[i]];
}
int deg_lambda = 0;
int deg_omega = 0;
int r = no_eras;
int el = no_eras;
if (!syn_error) {
// if syndrome is zero, data[] is a codeword and there are no errors to correct.
count = 0;
goto finish;
}
lambda[0] = 1;
if (no_eras > 0) {
// Init lambda to be the erasure locator polynomial. Convert erasure positions
// from index into data, to index into Reed-Solomon block.
lambda[1] = alpha_to[modnn(PRM * (NN - 1 - (eras_pos[0] + pad)))];
for (int i = 1; i < no_eras; i++) {
uint8_t u = modnn(PRM * (NN - 1 - (eras_pos[i] + pad)));
for (int j = i + 1; j > 0; j--) {
uint8_t tmp = index_of[lambda[j - 1]];
if (tmp != A0) {
lambda[j] ^= alpha_to[modnn(u + tmp)];
}
}
}
}
for (int i = 0; i < NROOTS + 1; i++)
b[i] = index_of[lambda[i]];
//
// Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
//
while (++r <= NROOTS) { // r is the step number
// Compute discrepancy at the r-th step in poly-form
uint8_t discr_r = 0;
for (int i = 0; i < r; i++) {
if ((lambda[i] != 0) && (syn[r - i - 1] != A0)) {
discr_r ^= alpha_to[modnn(index_of[lambda[i]] + syn[r - i - 1])];
}
}
discr_r = index_of[discr_r]; // Index form
if (discr_r == A0) {
// 2 lines below: B(x) <-- x*B(x)
// Rotate the last element of b[NROOTS+1] to b[0]
std::rotate(b.begin(), b.begin() + NROOTS, b.end());
b[0] = A0;
} else {
// 7 lines below: T(x) <-- lambda(x)-discr_r*x*b(x)
t[0] = lambda[0];
for (int i = 0; i < NROOTS; i++) {
if (b[i] != A0) {
t[i + 1] = lambda[i + 1] ^ alpha_to[modnn(discr_r + b[i])];
} else
t[i + 1] = lambda[i + 1];
}
if (2 * el <= r + no_eras - 1) {
el = r + no_eras - el;
// 2 lines below: B(x) <-- inv(discr_r) * lambda(x)
for (int i = 0; i <= NROOTS; i++) {
b[i] = ((lambda[i] == 0) ? A0 : modnn(index_of[lambda[i]] - discr_r + NN));
}
} else {
// 2 lines below: B(x) <-- x*B(x)
std::rotate(b.begin(), b.begin() + NROOTS, b.end());
b[0] = A0;
}
lambda = t;
}
}
// Convert lambda to index form and compute deg(lambda(x))
for (int i = 0; i < NROOTS + 1; i++) {
lambda[i] = index_of[lambda[i]];
if (lambda[i] != NN)
deg_lambda = i;
}
// Find roots of error+erasure locator polynomial by Chien search
reg = lambda;
count = 0; // Number of roots of lambda(x)
for (int i = 1, k = iprim - 1; i <= NN; i++, k = modnn(k + iprim)) {
uint8_t q = 1; // lambda[0] is always 0
for (int j = deg_lambda; j > 0; j--) {
if (reg[j] != A0) {
reg[j] = modnn(reg[j] + j);
q ^= alpha_to[reg[j]];
}
}
if (q != 0)
continue; // Not a root
// store root (index-form) and error location number
root[count] = i;
loc[count] = k;
// If we've already found max possible roots, abort the search to save time
if (++count == deg_lambda)
break;
}
if (deg_lambda != count) {
// deg(lambda) unequal to number of roots => uncorrectable error detected
count = -1;
goto finish;
}
//
// Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo x**NROOTS). in
// index form. Also find deg(omega).
//
deg_omega = deg_lambda - 1;
for (int i = 0; i <= deg_omega; i++) {
uint8_t tmp = 0;
for (int j = i; j >= 0; j--) {
if ((syn[i - j] != A0) && (lambda[j] != A0))
tmp ^= alpha_to[modnn(syn[i - j] + lambda[j])];
}
omega[i] = index_of[tmp];
}
//
// Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(fcr-1)
// and den = lambda_pr(inv(X(l))) all in poly-form
//
for (int j = count - 1; j >= 0; j--) {
uint8_t num1 = 0;
for (int i = deg_omega; i >= 0; i--) {
if (omega[i] != A0)
num1 ^= alpha_to[modnn(omega[i] + i * root[j])];
}
uint8_t num2 = alpha_to[modnn(root[j] * (FCR - 1) + NN)];
uint8_t den = 0;
// lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i]
for (int i = std::min(deg_lambda, NROOTS - 1) & ~1; i >= 0; i -= 2) {
if (lambda[i + 1] != A0) {
den ^= alpha_to[modnn(lambda[i + 1] + i * root[j])];
}
}
// Apply error to data. Padding ('pad' unused symbols) begin at index 0.
if (num1 != 0) {
if (loc[j] < pad) {
// If the computed error position is in the 'pad' (the unused portion of the
// R-S data capacity), then our solution has failed -- we've computed a
// correction location outside of the data and parity we've been provided!
count = -1;
goto finish;
}
uint8_t cor = alpha_to[modnn(index_of[num1] + index_of[num2] + NN - index_of[den])];
// Store the error correction pattern, if a correction buffer is available
if (corr)
corr[j] = cor;
// If a data/parity buffer is given and the error is inside the message or
// parity data, correct it
if (loc[j] < (NN - NROOTS)) {
if (data) {
data[loc[j] - pad] ^= cor;
}
} else if (loc[j] < NN) {
if (parity)
parity[loc[j] - (NN - NROOTS)] ^= cor;
}
}
}
finish:
if (eras_pos != NULL) {
for (int i = 0; i < count; i++)
eras_pos[i] = loc[i] - pad;
}
return count;
}
};
//
// Define the static reed_solomon...<...> members; allowed in header for template types.
//
// The reed_solomon_tags<...>::iprim < 0 is used to indicate to the first instance that the
// static tables require initialization.
//
template <int PRM, class PLY>
int reed_solomon_tabs<PRM, PLY>::iprim = -1;
template <int PRM, class PLY>
std::array<uint8_t, reed_solomon_tabs<PRM, PLY>::NN + 1> reed_solomon_tabs<PRM, PLY>::alpha_to;
template <int PRM, class PLY>
std::array<uint8_t, reed_solomon_tabs<PRM, PLY>::NN + 1> reed_solomon_tabs<PRM, PLY>::index_of;
template <int PRM, class PLY>
std::array<uint8_t, reed_solomon_tabs<PRM, PLY>::MODS> reed_solomon_tabs<PRM, PLY>::mod_of;
template <int RTS, int FCR, int PRM, class PLY>
std::array<uint8_t, reed_solomon<RTS, FCR, PRM, PLY>::NROOTS + 1> reed_solomon<RTS, FCR, PRM, PLY>::genpoly;
//
// RS( ... ) -- Define a reed-solomon codec
//
// @SYMBOLS: Total number of symbols; must be a power of 2 minus 1, eg 2^8-1 == 255
// @PAYLOAD: The maximum number of non-parity symbols, eg 253 ==> 2 parity symbols
// @POLY: A primitive polynomial appropriate to the SYMBOLS size
// @FCR: The first consecutive root of the Reed-Solomon generator polynomial
// @PRIM: The primitive root of the generator polynomial
//
//
// RS<SYMBOLS, PAYLOAD> -- Standard partial specializations for Reed-Solomon codec type access
//
// Normally, Reed-Solomon codecs are described with terms like RS(255,252). Obtain various
// standard Reed-Solomon codecs using macros of a similar form, eg. RS<255, 252>. Standard PLY,
// FCR and PRM values are provided for various SYMBOL sizes, along with appropriate basic types
// capable of holding all internal Reed-Solomon tabular data.
//
// In order to provide "default initialization" of const RS<...> types, a user-provided
// default constructor must be provided.
//
template <size_t SYMBOLS, size_t PAYLOAD>
struct RS;
template <size_t PAYLOAD>
struct RS<255, PAYLOAD> : public ReedSolomon::reed_solomon<(255) - (PAYLOAD), 0, 1, ReedSolomon::gfpoly<0x11d>>
{
RS()
: ReedSolomon::reed_solomon<(255) - (PAYLOAD), 0, 1, ReedSolomon::gfpoly<0x11d>>()
{
}
};
} // namespace ReedSolomon
#endif // REEDSOLOMON_H

View File

@ -4317,6 +4317,11 @@ bool WebAPIRequestMapper::getChannelSettings(
channelSettings->setRadioClockSettings(new SWGSDRangel::SWGRadioClockSettings());
channelSettings->getRadioClockSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "RadiosondeDemodSettings")
{
channelSettings->setRadiosondeDemodSettings(new SWGSDRangel::SWGRadiosondeDemodSettings());
channelSettings->getRadiosondeDemodSettings()->fromJsonObject(settingsJsonObject);
}
else if (channelSettingsKey == "RemoteSinkSettings")
{
channelSettings->setRemoteSinkSettings(new SWGSDRangel::SWGRemoteSinkSettings());
@ -4840,6 +4845,11 @@ bool WebAPIRequestMapper::getFeatureSettings(
featureSettings->setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings());
featureSettings->getStarTrackerSettings()->fromJsonObject(settingsJsonObject);
}
else if (featureSettingsKey == "RadiosondeSettings")
{
featureSettings->setRadiosondeSettings(new SWGSDRangel::SWGRadiosondeSettings());
featureSettings->getRadiosondeSettings()->fromJsonObject(settingsJsonObject);
}
else if (featureSettingsKey == "RigCtlServerSettings")
{
featureSettings->setRigCtlServerSettings(new SWGSDRangel::SWGRigCtlServerSettings());
@ -5060,6 +5070,7 @@ void WebAPIRequestMapper::resetChannelSettings(SWGSDRangel::SWGChannelSettings&
channelSettings.setPagerDemodSettings(nullptr);
channelSettings.setRadioAstronomySettings(nullptr);
channelSettings.setRadioClockSettings(nullptr);
channelSettings.setRadiosondeDemodSettings(nullptr);
channelSettings.setRemoteSinkSettings(nullptr);
channelSettings.setRemoteSourceSettings(nullptr);
channelSettings.setSsbDemodSettings(nullptr);
@ -5091,6 +5102,7 @@ void WebAPIRequestMapper::resetChannelReport(SWGSDRangel::SWGChannelReport& chan
channelReport.setPacketModReport(nullptr);
channelReport.setRadioAstronomyReport(nullptr);
channelReport.setRadioClockReport(nullptr);
channelReport.setRadiosondeDemodReport(nullptr);
channelReport.setRemoteSourceReport(nullptr);
channelReport.setSsbDemodReport(nullptr);
channelReport.setSsbModReport(nullptr);
@ -5130,6 +5142,7 @@ void WebAPIRequestMapper::resetFeatureSettings(SWGSDRangel::SWGFeatureSettings&
{
featureSettings.cleanup();
featureSettings.setFeatureType(nullptr);
featureSettings.setAisSettings(nullptr);
featureSettings.setAntennaToolsSettings(nullptr);
featureSettings.setAprsSettings(nullptr);
featureSettings.setGs232ControllerSettings(nullptr);
@ -5138,6 +5151,7 @@ void WebAPIRequestMapper::resetFeatureSettings(SWGSDRangel::SWGFeatureSettings&
featureSettings.setSatelliteTrackerSettings(nullptr);
featureSettings.setSimplePttSettings(nullptr);
featureSettings.setStarTrackerSettings(nullptr);
featureSettings.setRadiosondeSettings(nullptr);
featureSettings.setRigCtlServerSettings(nullptr);
}

View File

@ -58,6 +58,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelURIToSettingsKey = {
{"sdrangel.channeltx.modpacket", "PacketModSettings"},
{"sdrangel.channeltx.mod802.15.4", "IEEE_802_15_4_ModSettings"},
{"sdrangel.channel.radioclock", "RadioClockSettings"},
{"sdrangel.channel.radiosondedemod", "RadiosondeDemodSettings"},
{"sdrangel.demod.remotesink", "RemoteSinkSettings"},
{"sdrangel.channeltx.remotesource", "RemoteSourceSettings"},
{"sdrangel.channeltx.modssb", "SSBModSettings"},
@ -159,6 +160,7 @@ const QMap<QString, QString> WebAPIUtils::m_channelTypeToSettingsKey = {
{"LocalSource", "LocalSourceSettings"},
{"RadioAstronomy", "RadioAstronomySettings"},
{"RadioClock", "RadioClockSettings"},
{"RadiosondeDemod", "RadiosondeDemodSettings"},
{"RemoteSink", "RemoteSinkSettings"},
{"RemoteSource", "RemoteSourceSettings"},
{"SSBMod", "SSBModSettings"},
@ -273,6 +275,7 @@ const QMap<QString, QString> WebAPIUtils::m_featureTypeToSettingsKey = {
{"GS232Controller", "GS232ControllerSettings"}, // a.k.a Rotator Controller
{"Map", "MapSettings"},
{"PERTester", "PERTesterSettings"},
{"Radiosonde", "RadiosondeSettings"},
{"RigCtlServer", "RigCtlServerSettings"},
{"SatelliteTracker", "SatelliteTrackerSettings"},
{"SimplePTT", "SimplePTTSettings"},
@ -302,6 +305,7 @@ const QMap<QString, QString> WebAPIUtils::m_featureURIToSettingsKey = {
{"sdrangel.feature.gs232controller", "GS232ControllerSettings"},
{"sdrangel.feature.map", "MapSettings"},
{"sdrangel.feature.pertester", "PERTesterSettings"},
{"sdrangel.feature.radiosonde", "RadiosondeSettings"},
{"sdrangel.feature.rigctlserver", "RigCtlServerSettings"},
{"sdrangel.feature.satellitetracker", "SatelliteTrackerSettings"},
{"sdrangel.feature.simpleptt", "SimplePTTSettings"},

View File

@ -25,6 +25,8 @@ set(sdrgui_SOURCES
gui/crightclickenabler.cpp
gui/customtextedit.cpp
gui/cwkeyergui.cpp
gui/datetimedelegate.cpp
gui/decimaldelegate.cpp
gui/devicestreamselectiondialog.cpp
gui/deviceuserargsdialog.cpp
gui/dmsspinbox.cpp
@ -62,6 +64,7 @@ set(sdrgui_SOURCES
gui/spectrumcalibrationpointsdialog.cpp
gui/spectrummarkersdialog.cpp
gui/tickedslider.cpp
gui/timedelegate.cpp
gui/transverterbutton.cpp
gui/transverterdialog.cpp
gui/tvscreen.cpp
@ -113,6 +116,8 @@ set(sdrgui_HEADERS
gui/crightclickenabler.h
gui/customtextedit.h
gui/cwkeyergui.h
gui/datetimedelegate.h
gui/decimaldelegate.h
gui/devicestreamselectiondialog.h
gui/deviceuserargsdialog.h
gui/dmsspinbox.h
@ -152,6 +157,7 @@ set(sdrgui_HEADERS
gui/spectrumcalibrationpointsdialog.h
gui/spectrummarkersdialog.h
gui/tickedslider.h
gui/timedelegate.h
gui/transverterbutton.h
gui/transverterdialog.h
gui/tvscreen.h

View File

@ -0,0 +1,36 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QDateTime>
#include "datetimedelegate.h"
DateTimeDelegate::DateTimeDelegate(QString format) :
m_format(format)
{
}
QString DateTimeDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
(void) locale;
if (value.toString() == "") {
return "";
} else {
return value.toDateTime().toString(m_format);
}
}

View File

@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 SDRGUI_GUI_DATETIMEDELGATE_H
#define SDRGUI_GUI_DATETIMEDELGATE_H
#include <QStyledItemDelegate>
#include "export.h"
// Delegate for table to display time
class SDRGUI_API DateTimeDelegate : public QStyledItemDelegate {
public:
DateTimeDelegate(QString format = "yyyy/MM/dd hh:mm:ss");
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
private:
QString m_format;
};
#endif // SDRGUI_GUI_DATETIMEDELGATE_H

View File

@ -0,0 +1,35 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "decimaldelegate.h"
DecimalDelegate::DecimalDelegate(int precision) :
m_precision(precision)
{
}
QString DecimalDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
(void) locale;
bool ok;
double d = value.toDouble(&ok);
if (ok) {
return QString::number(d, 'f', m_precision);
} else {
return value.toString();
}
}

View File

@ -0,0 +1,38 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 SDRGUI_GUI_DECIMALDELGATE_H
#define SDRGUI_GUI_DECIMALDELGATE_H
#include <QStyledItemDelegate>
#include "export.h"
// Deligate for table to control precision used to display floating point values - also supports strings
class SDRGUI_API DecimalDelegate : public QStyledItemDelegate {
public:
DecimalDelegate(int precision = 2);
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
private:
int m_precision;
};
#endif // SDRGUI_GUI_DECIMALDELGATE_H

View File

@ -0,0 +1,36 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QTime>
#include "timedelegate.h"
TimeDelegate::TimeDelegate(QString format) :
m_format(format)
{
}
QString TimeDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
(void) locale;
if (value.toString() == "") {
return "";
} else {
return value.toTime().toString(m_format);
}
}

37
sdrgui/gui/timedelegate.h Normal file
View File

@ -0,0 +1,37 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 SDRGUI_GUI_TIMEDELGATE_H
#define SDRGUI_GUI_TIMEDELGATE_H
#include <QStyledItemDelegate>
#include "export.h"
// Delegate for table to display time
class SDRGUI_API TimeDelegate : public QStyledItemDelegate {
public:
TimeDelegate(QString format = "hh:mm:ss");
virtual QString displayText(const QVariant &value, const QLocale &locale) const override;
private:
QString m_format;
};
#endif // SDRGUI_GUI_DECIMALDELGATE_H

View File

@ -59,6 +59,8 @@ ChannelReport:
$ref: "http://swgserver:8081/api/swagger/include/RadioAstronomy.yaml#/RadioAstronomyReport"
RadioClockReport:
$ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockReport"
RadiosondeDemodReport:
$ref: "http://swgserver:8081/api/swagger/include/RadiosondeDemod.yaml#/RadiosondeDemodReport"
RemoteSourceReport:
$ref: "http://swgserver:8081/api/swagger/include/RemoteSource.yaml#/RemoteSourceReport"
PacketDemodReport:

View File

@ -85,6 +85,8 @@ ChannelSettings:
$ref: "http://swgserver:8081/api/swagger/include/RadioAstronomy.yaml#/RadioAstronomySettings"
RadioClockSettings:
$ref: "http://swgserver:8081/api/swagger/include/RadioClock.yaml#/RadioClockSettings"
RadiosondeDemodSettings:
$ref: "http://swgserver:8081/api/swagger/include/RadiosondeDemod.yaml#/RadiosondeDemodSettings"
RemoteSinkSettings:
$ref: "http://swgserver:8081/api/swagger/include/RemoteSink.yaml#/RemoteSinkSettings"
RemoteSourceSettings:

View File

@ -31,6 +31,8 @@ FeatureSettings:
$ref: "http://swgserver:8081/api/swagger/include/Map.yaml#/MapSettings"
PERTesterSettings:
$ref: "http://swgserver:8081/api/swagger/include/PERTester.yaml#/PERTesterSettings"
RadiosondeSettings:
$ref: "http://swgserver:8081/api/swagger/include/Radiosonde.yaml#/RadiosondeSettings"
RigCtlServerSettings:
$ref: "http://swgserver:8081/api/swagger/include/RigCtlServer.yaml#/RigCtlServerSettings"
SatelliteTrackerSettings:

View File

@ -0,0 +1,20 @@
RadiosondeSettings:
description: "Radiosonde 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
reverseAPIFeatureSetIndex:
type: integer
reverseAPIFeatureIndex:
type: integer
rollupState:
$ref: "http://swgserver:8081/api/swagger/include/RollupState.yaml#/RollupState"

View File

@ -0,0 +1,65 @@
RadiosondeDemodSettings:
description: RadiosondeDemod
properties:
baud:
type: integer
description: baud rate (nominal is 4800)
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
logFilename:
type: string
logEnabled:
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
scopeConfig:
$ref: "http://swgserver:8081/api/swagger/include/GLScope.yaml#/GLScope"
channelMarker:
$ref: "http://swgserver:8081/api/swagger/include/ChannelMarker.yaml#/ChannelMarker"
rollupState:
$ref: "http://swgserver:8081/api/swagger/include/RollupState.yaml#/RollupState"
RadiosondeDemodReport:
description: RadiosondeDemod
properties:
channelPowerDB:
description: power received in channel (dB)
type: number
format: float
channelSampleRate:
type: integer

View File

@ -80,6 +80,8 @@ SWGChannelReport::SWGChannelReport() {
m_radio_astronomy_report_isSet = false;
radio_clock_report = nullptr;
m_radio_clock_report_isSet = false;
radiosonde_demod_report = nullptr;
m_radiosonde_demod_report_isSet = false;
remote_source_report = nullptr;
m_remote_source_report_isSet = false;
packet_demod_report = nullptr;
@ -164,6 +166,8 @@ SWGChannelReport::init() {
m_radio_astronomy_report_isSet = false;
radio_clock_report = new SWGRadioClockReport();
m_radio_clock_report_isSet = false;
radiosonde_demod_report = new SWGRadiosondeDemodReport();
m_radiosonde_demod_report_isSet = false;
remote_source_report = new SWGRemoteSourceReport();
m_remote_source_report_isSet = false;
packet_demod_report = new SWGPacketDemodReport();
@ -268,6 +272,9 @@ SWGChannelReport::cleanup() {
if(radio_clock_report != nullptr) {
delete radio_clock_report;
}
if(radiosonde_demod_report != nullptr) {
delete radiosonde_demod_report;
}
if(remote_source_report != nullptr) {
delete remote_source_report;
}
@ -369,6 +376,8 @@ SWGChannelReport::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&radio_clock_report, pJson["RadioClockReport"], "SWGRadioClockReport", "SWGRadioClockReport");
::SWGSDRangel::setValue(&radiosonde_demod_report, pJson["RadiosondeDemodReport"], "SWGRadiosondeDemodReport", "SWGRadiosondeDemodReport");
::SWGSDRangel::setValue(&remote_source_report, pJson["RemoteSourceReport"], "SWGRemoteSourceReport", "SWGRemoteSourceReport");
::SWGSDRangel::setValue(&packet_demod_report, pJson["PacketDemodReport"], "SWGPacketDemodReport", "SWGPacketDemodReport");
@ -487,6 +496,9 @@ SWGChannelReport::asJsonObject() {
if((radio_clock_report != nullptr) && (radio_clock_report->isSet())){
toJsonValue(QString("RadioClockReport"), radio_clock_report, obj, QString("SWGRadioClockReport"));
}
if((radiosonde_demod_report != nullptr) && (radiosonde_demod_report->isSet())){
toJsonValue(QString("RadiosondeDemodReport"), radiosonde_demod_report, obj, QString("SWGRadiosondeDemodReport"));
}
if((remote_source_report != nullptr) && (remote_source_report->isSet())){
toJsonValue(QString("RemoteSourceReport"), remote_source_report, obj, QString("SWGRemoteSourceReport"));
}
@ -787,6 +799,16 @@ SWGChannelReport::setRadioClockReport(SWGRadioClockReport* radio_clock_report) {
this->m_radio_clock_report_isSet = true;
}
SWGRadiosondeDemodReport*
SWGChannelReport::getRadiosondeDemodReport() {
return radiosonde_demod_report;
}
void
SWGChannelReport::setRadiosondeDemodReport(SWGRadiosondeDemodReport* radiosonde_demod_report) {
this->radiosonde_demod_report = radiosonde_demod_report;
this->m_radiosonde_demod_report_isSet = true;
}
SWGRemoteSourceReport*
SWGChannelReport::getRemoteSourceReport() {
return remote_source_report;
@ -990,6 +1012,9 @@ SWGChannelReport::isSet(){
if(radio_clock_report && radio_clock_report->isSet()){
isObjectUpdated = true; break;
}
if(radiosonde_demod_report && radiosonde_demod_report->isSet()){
isObjectUpdated = true; break;
}
if(remote_source_report && remote_source_report->isSet()){
isObjectUpdated = true; break;
}

View File

@ -48,6 +48,7 @@
#include "SWGPagerDemodReport.h"
#include "SWGRadioAstronomyReport.h"
#include "SWGRadioClockReport.h"
#include "SWGRadiosondeDemodReport.h"
#include "SWGRemoteSourceReport.h"
#include "SWGSSBDemodReport.h"
#include "SWGSSBModReport.h"
@ -156,6 +157,9 @@ public:
SWGRadioClockReport* getRadioClockReport();
void setRadioClockReport(SWGRadioClockReport* radio_clock_report);
SWGRadiosondeDemodReport* getRadiosondeDemodReport();
void setRadiosondeDemodReport(SWGRadiosondeDemodReport* radiosonde_demod_report);
SWGRemoteSourceReport* getRemoteSourceReport();
void setRemoteSourceReport(SWGRemoteSourceReport* remote_source_report);
@ -274,6 +278,9 @@ private:
SWGRadioClockReport* radio_clock_report;
bool m_radio_clock_report_isSet;
SWGRadiosondeDemodReport* radiosonde_demod_report;
bool m_radiosonde_demod_report_isSet;
SWGRemoteSourceReport* remote_source_report;
bool m_remote_source_report_isSet;

View File

@ -104,6 +104,8 @@ SWGChannelSettings::SWGChannelSettings() {
m_radio_astronomy_settings_isSet = false;
radio_clock_settings = nullptr;
m_radio_clock_settings_isSet = false;
radiosonde_demod_settings = nullptr;
m_radiosonde_demod_settings_isSet = false;
remote_sink_settings = nullptr;
m_remote_sink_settings_isSet = false;
remote_source_settings = nullptr;
@ -210,6 +212,8 @@ SWGChannelSettings::init() {
m_radio_astronomy_settings_isSet = false;
radio_clock_settings = new SWGRadioClockSettings();
m_radio_clock_settings_isSet = false;
radiosonde_demod_settings = new SWGRadiosondeDemodSettings();
m_radiosonde_demod_settings_isSet = false;
remote_sink_settings = new SWGRemoteSinkSettings();
m_remote_sink_settings_isSet = false;
remote_source_settings = new SWGRemoteSourceSettings();
@ -344,6 +348,9 @@ SWGChannelSettings::cleanup() {
if(radio_clock_settings != nullptr) {
delete radio_clock_settings;
}
if(radiosonde_demod_settings != nullptr) {
delete radiosonde_demod_settings;
}
if(remote_sink_settings != nullptr) {
delete remote_sink_settings;
}
@ -466,6 +473,8 @@ SWGChannelSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&radio_clock_settings, pJson["RadioClockSettings"], "SWGRadioClockSettings", "SWGRadioClockSettings");
::SWGSDRangel::setValue(&radiosonde_demod_settings, pJson["RadiosondeDemodSettings"], "SWGRadiosondeDemodSettings", "SWGRadiosondeDemodSettings");
::SWGSDRangel::setValue(&remote_sink_settings, pJson["RemoteSinkSettings"], "SWGRemoteSinkSettings", "SWGRemoteSinkSettings");
::SWGSDRangel::setValue(&remote_source_settings, pJson["RemoteSourceSettings"], "SWGRemoteSourceSettings", "SWGRemoteSourceSettings");
@ -618,6 +627,9 @@ SWGChannelSettings::asJsonObject() {
if((radio_clock_settings != nullptr) && (radio_clock_settings->isSet())){
toJsonValue(QString("RadioClockSettings"), radio_clock_settings, obj, QString("SWGRadioClockSettings"));
}
if((radiosonde_demod_settings != nullptr) && (radiosonde_demod_settings->isSet())){
toJsonValue(QString("RadiosondeDemodSettings"), radiosonde_demod_settings, obj, QString("SWGRadiosondeDemodSettings"));
}
if((remote_sink_settings != nullptr) && (remote_sink_settings->isSet())){
toJsonValue(QString("RemoteSinkSettings"), remote_sink_settings, obj, QString("SWGRemoteSinkSettings"));
}
@ -1035,6 +1047,16 @@ SWGChannelSettings::setRadioClockSettings(SWGRadioClockSettings* radio_clock_set
this->m_radio_clock_settings_isSet = true;
}
SWGRadiosondeDemodSettings*
SWGChannelSettings::getRadiosondeDemodSettings() {
return radiosonde_demod_settings;
}
void
SWGChannelSettings::setRadiosondeDemodSettings(SWGRadiosondeDemodSettings* radiosonde_demod_settings) {
this->radiosonde_demod_settings = radiosonde_demod_settings;
this->m_radiosonde_demod_settings_isSet = true;
}
SWGRemoteSinkSettings*
SWGChannelSettings::getRemoteSinkSettings() {
return remote_sink_settings;
@ -1264,6 +1286,9 @@ SWGChannelSettings::isSet(){
if(radio_clock_settings && radio_clock_settings->isSet()){
isObjectUpdated = true; break;
}
if(radiosonde_demod_settings && radiosonde_demod_settings->isSet()){
isObjectUpdated = true; break;
}
if(remote_sink_settings && remote_sink_settings->isSet()){
isObjectUpdated = true; break;
}

View File

@ -56,6 +56,7 @@
#include "SWGPagerDemodSettings.h"
#include "SWGRadioAstronomySettings.h"
#include "SWGRadioClockSettings.h"
#include "SWGRadiosondeDemodSettings.h"
#include "SWGRemoteSinkSettings.h"
#include "SWGRemoteSourceSettings.h"
#include "SWGSSBDemodSettings.h"
@ -201,6 +202,9 @@ public:
SWGRadioClockSettings* getRadioClockSettings();
void setRadioClockSettings(SWGRadioClockSettings* radio_clock_settings);
SWGRadiosondeDemodSettings* getRadiosondeDemodSettings();
void setRadiosondeDemodSettings(SWGRadiosondeDemodSettings* radiosonde_demod_settings);
SWGRemoteSinkSettings* getRemoteSinkSettings();
void setRemoteSinkSettings(SWGRemoteSinkSettings* remote_sink_settings);
@ -352,6 +356,9 @@ private:
SWGRadioClockSettings* radio_clock_settings;
bool m_radio_clock_settings_isSet;
SWGRadiosondeDemodSettings* radiosonde_demod_settings;
bool m_radiosonde_demod_settings_isSet;
SWGRemoteSinkSettings* remote_sink_settings;
bool m_remote_sink_settings_isSet;

View File

@ -52,6 +52,8 @@ SWGFeatureSettings::SWGFeatureSettings() {
m_map_settings_isSet = false;
per_tester_settings = nullptr;
m_per_tester_settings_isSet = false;
radiosonde_settings = nullptr;
m_radiosonde_settings_isSet = false;
rig_ctl_server_settings = nullptr;
m_rig_ctl_server_settings_isSet = false;
satellite_tracker_settings = nullptr;
@ -94,6 +96,8 @@ SWGFeatureSettings::init() {
m_map_settings_isSet = false;
per_tester_settings = new SWGPERTesterSettings();
m_per_tester_settings_isSet = false;
radiosonde_settings = new SWGRadiosondeSettings();
m_radiosonde_settings_isSet = false;
rig_ctl_server_settings = new SWGRigCtlServerSettings();
m_rig_ctl_server_settings_isSet = false;
satellite_tracker_settings = new SWGSatelliteTrackerSettings();
@ -140,6 +144,9 @@ SWGFeatureSettings::cleanup() {
if(per_tester_settings != nullptr) {
delete per_tester_settings;
}
if(radiosonde_settings != nullptr) {
delete radiosonde_settings;
}
if(rig_ctl_server_settings != nullptr) {
delete rig_ctl_server_settings;
}
@ -192,6 +199,8 @@ SWGFeatureSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&per_tester_settings, pJson["PERTesterSettings"], "SWGPERTesterSettings", "SWGPERTesterSettings");
::SWGSDRangel::setValue(&radiosonde_settings, pJson["RadiosondeSettings"], "SWGRadiosondeSettings", "SWGRadiosondeSettings");
::SWGSDRangel::setValue(&rig_ctl_server_settings, pJson["RigCtlServerSettings"], "SWGRigCtlServerSettings", "SWGRigCtlServerSettings");
::SWGSDRangel::setValue(&satellite_tracker_settings, pJson["SatelliteTrackerSettings"], "SWGSatelliteTrackerSettings", "SWGSatelliteTrackerSettings");
@ -254,6 +263,9 @@ SWGFeatureSettings::asJsonObject() {
if((per_tester_settings != nullptr) && (per_tester_settings->isSet())){
toJsonValue(QString("PERTesterSettings"), per_tester_settings, obj, QString("SWGPERTesterSettings"));
}
if((radiosonde_settings != nullptr) && (radiosonde_settings->isSet())){
toJsonValue(QString("RadiosondeSettings"), radiosonde_settings, obj, QString("SWGRadiosondeSettings"));
}
if((rig_ctl_server_settings != nullptr) && (rig_ctl_server_settings->isSet())){
toJsonValue(QString("RigCtlServerSettings"), rig_ctl_server_settings, obj, QString("SWGRigCtlServerSettings"));
}
@ -393,6 +405,16 @@ SWGFeatureSettings::setPerTesterSettings(SWGPERTesterSettings* per_tester_settin
this->m_per_tester_settings_isSet = true;
}
SWGRadiosondeSettings*
SWGFeatureSettings::getRadiosondeSettings() {
return radiosonde_settings;
}
void
SWGFeatureSettings::setRadiosondeSettings(SWGRadiosondeSettings* radiosonde_settings) {
this->radiosonde_settings = radiosonde_settings;
this->m_radiosonde_settings_isSet = true;
}
SWGRigCtlServerSettings*
SWGFeatureSettings::getRigCtlServerSettings() {
return rig_ctl_server_settings;
@ -484,6 +506,9 @@ SWGFeatureSettings::isSet(){
if(per_tester_settings && per_tester_settings->isSet()){
isObjectUpdated = true; break;
}
if(radiosonde_settings && radiosonde_settings->isSet()){
isObjectUpdated = true; break;
}
if(rig_ctl_server_settings && rig_ctl_server_settings->isSet()){
isObjectUpdated = true; break;
}

View File

@ -31,6 +31,7 @@
#include "SWGJogdialControllerSettings.h"
#include "SWGMapSettings.h"
#include "SWGPERTesterSettings.h"
#include "SWGRadiosondeSettings.h"
#include "SWGRigCtlServerSettings.h"
#include "SWGSatelliteTrackerSettings.h"
#include "SWGSimplePTTSettings.h"
@ -92,6 +93,9 @@ public:
SWGPERTesterSettings* getPerTesterSettings();
void setPerTesterSettings(SWGPERTesterSettings* per_tester_settings);
SWGRadiosondeSettings* getRadiosondeSettings();
void setRadiosondeSettings(SWGRadiosondeSettings* radiosonde_settings);
SWGRigCtlServerSettings* getRigCtlServerSettings();
void setRigCtlServerSettings(SWGRigCtlServerSettings* rig_ctl_server_settings);
@ -147,6 +151,9 @@ private:
SWGPERTesterSettings* per_tester_settings;
bool m_per_tester_settings_isSet;
SWGRadiosondeSettings* radiosonde_settings;
bool m_radiosonde_settings_isSet;
SWGRigCtlServerSettings* rig_ctl_server_settings;
bool m_rig_ctl_server_settings_isSet;

View File

@ -221,6 +221,9 @@
#include "SWGRadioAstronomySettings.h"
#include "SWGRadioClockReport.h"
#include "SWGRadioClockSettings.h"
#include "SWGRadiosondeDemodReport.h"
#include "SWGRadiosondeDemodSettings.h"
#include "SWGRadiosondeSettings.h"
#include "SWGRange.h"
#include "SWGRangeFloat.h"
#include "SWGRemoteInputReport.h"
@ -1355,6 +1358,21 @@ namespace SWGSDRangel {
obj->init();
return obj;
}
if(QString("SWGRadiosondeDemodReport").compare(type) == 0) {
SWGRadiosondeDemodReport *obj = new SWGRadiosondeDemodReport();
obj->init();
return obj;
}
if(QString("SWGRadiosondeDemodSettings").compare(type) == 0) {
SWGRadiosondeDemodSettings *obj = new SWGRadiosondeDemodSettings();
obj->init();
return obj;
}
if(QString("SWGRadiosondeSettings").compare(type) == 0) {
SWGRadiosondeSettings *obj = new SWGRadiosondeSettings();
obj->init();
return obj;
}
if(QString("SWGRange").compare(type) == 0) {
SWGRange *obj = new SWGRange();
obj->init();

View File

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

View File

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

View File

@ -0,0 +1,582 @@
/**
* 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 "SWGRadiosondeDemodSettings.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGRadiosondeDemodSettings::SWGRadiosondeDemodSettings(QString* json) {
init();
this->fromJson(*json);
}
SWGRadiosondeDemodSettings::SWGRadiosondeDemodSettings() {
baud = 0;
m_baud_isSet = false;
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;
log_filename = nullptr;
m_log_filename_isSet = false;
log_enabled = 0;
m_log_enabled_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
m_title_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = nullptr;
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
scope_config = nullptr;
m_scope_config_isSet = false;
channel_marker = nullptr;
m_channel_marker_isSet = false;
rollup_state = nullptr;
m_rollup_state_isSet = false;
}
SWGRadiosondeDemodSettings::~SWGRadiosondeDemodSettings() {
this->cleanup();
}
void
SWGRadiosondeDemodSettings::init() {
baud = 0;
m_baud_isSet = false;
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;
log_filename = new QString("");
m_log_filename_isSet = false;
log_enabled = 0;
m_log_enabled_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
m_title_isSet = false;
stream_index = 0;
m_stream_index_isSet = false;
use_reverse_api = 0;
m_use_reverse_api_isSet = false;
reverse_api_address = new QString("");
m_reverse_api_address_isSet = false;
reverse_api_port = 0;
m_reverse_api_port_isSet = false;
reverse_api_device_index = 0;
m_reverse_api_device_index_isSet = false;
reverse_api_channel_index = 0;
m_reverse_api_channel_index_isSet = false;
scope_config = new SWGGLScope();
m_scope_config_isSet = false;
channel_marker = new SWGChannelMarker();
m_channel_marker_isSet = false;
rollup_state = new SWGRollupState();
m_rollup_state_isSet = false;
}
void
SWGRadiosondeDemodSettings::cleanup() {
if(udp_address != nullptr) {
delete udp_address;
}
if(log_filename != nullptr) {
delete log_filename;
}
if(title != nullptr) {
delete title;
}
if(reverse_api_address != nullptr) {
delete reverse_api_address;
}
if(scope_config != nullptr) {
delete scope_config;
}
if(channel_marker != nullptr) {
delete channel_marker;
}
if(rollup_state != nullptr) {
delete rollup_state;
}
}
SWGRadiosondeDemodSettings*
SWGRadiosondeDemodSettings::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGRadiosondeDemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&baud, pJson["baud"], "qint32", "");
::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(&log_filename, pJson["logFilename"], "QString", "QString");
::SWGSDRangel::setValue(&log_enabled, pJson["logEnabled"], "qint32", "");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
::SWGSDRangel::setValue(&use_reverse_api, pJson["useReverseAPI"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_address, pJson["reverseAPIAddress"], "QString", "QString");
::SWGSDRangel::setValue(&reverse_api_port, pJson["reverseAPIPort"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_device_index, pJson["reverseAPIDeviceIndex"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_channel_index, pJson["reverseAPIChannelIndex"], "qint32", "");
::SWGSDRangel::setValue(&scope_config, pJson["scopeConfig"], "SWGGLScope", "SWGGLScope");
::SWGSDRangel::setValue(&channel_marker, pJson["channelMarker"], "SWGChannelMarker", "SWGChannelMarker");
::SWGSDRangel::setValue(&rollup_state, pJson["rollupState"], "SWGRollupState", "SWGRollupState");
}
QString
SWGRadiosondeDemodSettings::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGRadiosondeDemodSettings::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(m_baud_isSet){
obj->insert("baud", QJsonValue(baud));
}
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(log_filename != nullptr && *log_filename != QString("")){
toJsonValue(QString("logFilename"), log_filename, obj, QString("QString"));
}
if(m_log_enabled_isSet){
obj->insert("logEnabled", QJsonValue(log_enabled));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString"));
}
if(m_stream_index_isSet){
obj->insert("streamIndex", QJsonValue(stream_index));
}
if(m_use_reverse_api_isSet){
obj->insert("useReverseAPI", QJsonValue(use_reverse_api));
}
if(reverse_api_address != nullptr && *reverse_api_address != QString("")){
toJsonValue(QString("reverseAPIAddress"), reverse_api_address, obj, QString("QString"));
}
if(m_reverse_api_port_isSet){
obj->insert("reverseAPIPort", QJsonValue(reverse_api_port));
}
if(m_reverse_api_device_index_isSet){
obj->insert("reverseAPIDeviceIndex", QJsonValue(reverse_api_device_index));
}
if(m_reverse_api_channel_index_isSet){
obj->insert("reverseAPIChannelIndex", QJsonValue(reverse_api_channel_index));
}
if((scope_config != nullptr) && (scope_config->isSet())){
toJsonValue(QString("scopeConfig"), scope_config, obj, QString("SWGGLScope"));
}
if((channel_marker != nullptr) && (channel_marker->isSet())){
toJsonValue(QString("channelMarker"), channel_marker, obj, QString("SWGChannelMarker"));
}
if((rollup_state != nullptr) && (rollup_state->isSet())){
toJsonValue(QString("rollupState"), rollup_state, obj, QString("SWGRollupState"));
}
return obj;
}
qint32
SWGRadiosondeDemodSettings::getBaud() {
return baud;
}
void
SWGRadiosondeDemodSettings::setBaud(qint32 baud) {
this->baud = baud;
this->m_baud_isSet = true;
}
qint64
SWGRadiosondeDemodSettings::getInputFrequencyOffset() {
return input_frequency_offset;
}
void
SWGRadiosondeDemodSettings::setInputFrequencyOffset(qint64 input_frequency_offset) {
this->input_frequency_offset = input_frequency_offset;
this->m_input_frequency_offset_isSet = true;
}
float
SWGRadiosondeDemodSettings::getRfBandwidth() {
return rf_bandwidth;
}
void
SWGRadiosondeDemodSettings::setRfBandwidth(float rf_bandwidth) {
this->rf_bandwidth = rf_bandwidth;
this->m_rf_bandwidth_isSet = true;
}
float
SWGRadiosondeDemodSettings::getFmDeviation() {
return fm_deviation;
}
void
SWGRadiosondeDemodSettings::setFmDeviation(float fm_deviation) {
this->fm_deviation = fm_deviation;
this->m_fm_deviation_isSet = true;
}
float
SWGRadiosondeDemodSettings::getCorrelationThreshold() {
return correlation_threshold;
}
void
SWGRadiosondeDemodSettings::setCorrelationThreshold(float correlation_threshold) {
this->correlation_threshold = correlation_threshold;
this->m_correlation_threshold_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getUdpEnabled() {
return udp_enabled;
}
void
SWGRadiosondeDemodSettings::setUdpEnabled(qint32 udp_enabled) {
this->udp_enabled = udp_enabled;
this->m_udp_enabled_isSet = true;
}
QString*
SWGRadiosondeDemodSettings::getUdpAddress() {
return udp_address;
}
void
SWGRadiosondeDemodSettings::setUdpAddress(QString* udp_address) {
this->udp_address = udp_address;
this->m_udp_address_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getUdpPort() {
return udp_port;
}
void
SWGRadiosondeDemodSettings::setUdpPort(qint32 udp_port) {
this->udp_port = udp_port;
this->m_udp_port_isSet = true;
}
QString*
SWGRadiosondeDemodSettings::getLogFilename() {
return log_filename;
}
void
SWGRadiosondeDemodSettings::setLogFilename(QString* log_filename) {
this->log_filename = log_filename;
this->m_log_filename_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getLogEnabled() {
return log_enabled;
}
void
SWGRadiosondeDemodSettings::setLogEnabled(qint32 log_enabled) {
this->log_enabled = log_enabled;
this->m_log_enabled_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getRgbColor() {
return rgb_color;
}
void
SWGRadiosondeDemodSettings::setRgbColor(qint32 rgb_color) {
this->rgb_color = rgb_color;
this->m_rgb_color_isSet = true;
}
QString*
SWGRadiosondeDemodSettings::getTitle() {
return title;
}
void
SWGRadiosondeDemodSettings::setTitle(QString* title) {
this->title = title;
this->m_title_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getStreamIndex() {
return stream_index;
}
void
SWGRadiosondeDemodSettings::setStreamIndex(qint32 stream_index) {
this->stream_index = stream_index;
this->m_stream_index_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getUseReverseApi() {
return use_reverse_api;
}
void
SWGRadiosondeDemodSettings::setUseReverseApi(qint32 use_reverse_api) {
this->use_reverse_api = use_reverse_api;
this->m_use_reverse_api_isSet = true;
}
QString*
SWGRadiosondeDemodSettings::getReverseApiAddress() {
return reverse_api_address;
}
void
SWGRadiosondeDemodSettings::setReverseApiAddress(QString* reverse_api_address) {
this->reverse_api_address = reverse_api_address;
this->m_reverse_api_address_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getReverseApiPort() {
return reverse_api_port;
}
void
SWGRadiosondeDemodSettings::setReverseApiPort(qint32 reverse_api_port) {
this->reverse_api_port = reverse_api_port;
this->m_reverse_api_port_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getReverseApiDeviceIndex() {
return reverse_api_device_index;
}
void
SWGRadiosondeDemodSettings::setReverseApiDeviceIndex(qint32 reverse_api_device_index) {
this->reverse_api_device_index = reverse_api_device_index;
this->m_reverse_api_device_index_isSet = true;
}
qint32
SWGRadiosondeDemodSettings::getReverseApiChannelIndex() {
return reverse_api_channel_index;
}
void
SWGRadiosondeDemodSettings::setReverseApiChannelIndex(qint32 reverse_api_channel_index) {
this->reverse_api_channel_index = reverse_api_channel_index;
this->m_reverse_api_channel_index_isSet = true;
}
SWGGLScope*
SWGRadiosondeDemodSettings::getScopeConfig() {
return scope_config;
}
void
SWGRadiosondeDemodSettings::setScopeConfig(SWGGLScope* scope_config) {
this->scope_config = scope_config;
this->m_scope_config_isSet = true;
}
SWGChannelMarker*
SWGRadiosondeDemodSettings::getChannelMarker() {
return channel_marker;
}
void
SWGRadiosondeDemodSettings::setChannelMarker(SWGChannelMarker* channel_marker) {
this->channel_marker = channel_marker;
this->m_channel_marker_isSet = true;
}
SWGRollupState*
SWGRadiosondeDemodSettings::getRollupState() {
return rollup_state;
}
void
SWGRadiosondeDemodSettings::setRollupState(SWGRollupState* rollup_state) {
this->rollup_state = rollup_state;
this->m_rollup_state_isSet = true;
}
bool
SWGRadiosondeDemodSettings::isSet(){
bool isObjectUpdated = false;
do{
if(m_baud_isSet){
isObjectUpdated = true; break;
}
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(log_filename && *log_filename != QString("")){
isObjectUpdated = true; break;
}
if(m_log_enabled_isSet){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}
if(title && *title != QString("")){
isObjectUpdated = true; break;
}
if(m_stream_index_isSet){
isObjectUpdated = true; break;
}
if(m_use_reverse_api_isSet){
isObjectUpdated = true; break;
}
if(reverse_api_address && *reverse_api_address != QString("")){
isObjectUpdated = true; break;
}
if(m_reverse_api_port_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_device_index_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_channel_index_isSet){
isObjectUpdated = true; break;
}
if(scope_config && scope_config->isSet()){
isObjectUpdated = true; break;
}
if(channel_marker && channel_marker->isSet()){
isObjectUpdated = true; break;
}
if(rollup_state && rollup_state->isSet()){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,182 @@
/**
* SDRangel
* This is the web REST/JSON API of SDRangel SDR software. SDRangel is an Open Source Qt5/OpenGL 3.0+ (4.3+ in Windows) GUI and server Software Defined Radio and signal analyzer in software. It supports Airspy, BladeRF, HackRF, LimeSDR, PlutoSDR, RTL-SDR, SDRplay RSP1 and FunCube --- Limitations and specifcities: * In SDRangel GUI the first Rx device set cannot be deleted. Conversely the server starts with no device sets and its number of device sets can be reduced to zero by as many calls as necessary to /sdrangel/deviceset with DELETE method. * Preset import and export from/to file is a server only feature. * Device set focus is a GUI only feature. * The following channels are not implemented (status 501 is returned): ATV and DATV demodulators, Channel Analyzer NG, LoRa demodulator * The device settings and report structures contains only the sub-structure corresponding to the device type. The DeviceSettings and DeviceReport structures documented here shows all of them but only one will be or should be present at a time * The channel settings and report structures contains only the sub-structure corresponding to the channel type. The ChannelSettings and ChannelReport structures documented here shows all of them but only one will be or should be present at a time ---
*
* OpenAPI spec version: 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.
*/
/*
* SWGRadiosondeDemodSettings.h
*
* RadiosondeDemod
*/
#ifndef SWGRadiosondeDemodSettings_H_
#define SWGRadiosondeDemodSettings_H_
#include <QJsonObject>
#include "SWGChannelMarker.h"
#include "SWGGLScope.h"
#include "SWGRollupState.h"
#include <QString>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGRadiosondeDemodSettings: public SWGObject {
public:
SWGRadiosondeDemodSettings();
SWGRadiosondeDemodSettings(QString* json);
virtual ~SWGRadiosondeDemodSettings();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGRadiosondeDemodSettings* fromJson(QString &jsonString) override;
qint32 getBaud();
void setBaud(qint32 baud);
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);
QString* getLogFilename();
void setLogFilename(QString* log_filename);
qint32 getLogEnabled();
void setLogEnabled(qint32 log_enabled);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
QString* getTitle();
void setTitle(QString* title);
qint32 getStreamIndex();
void setStreamIndex(qint32 stream_index);
qint32 getUseReverseApi();
void setUseReverseApi(qint32 use_reverse_api);
QString* getReverseApiAddress();
void setReverseApiAddress(QString* reverse_api_address);
qint32 getReverseApiPort();
void setReverseApiPort(qint32 reverse_api_port);
qint32 getReverseApiDeviceIndex();
void setReverseApiDeviceIndex(qint32 reverse_api_device_index);
qint32 getReverseApiChannelIndex();
void setReverseApiChannelIndex(qint32 reverse_api_channel_index);
SWGGLScope* getScopeConfig();
void setScopeConfig(SWGGLScope* scope_config);
SWGChannelMarker* getChannelMarker();
void setChannelMarker(SWGChannelMarker* channel_marker);
SWGRollupState* getRollupState();
void setRollupState(SWGRollupState* rollup_state);
virtual bool isSet() override;
private:
qint32 baud;
bool m_baud_isSet;
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;
QString* log_filename;
bool m_log_filename_isSet;
qint32 log_enabled;
bool m_log_enabled_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;
QString* title;
bool m_title_isSet;
qint32 stream_index;
bool m_stream_index_isSet;
qint32 use_reverse_api;
bool m_use_reverse_api_isSet;
QString* reverse_api_address;
bool m_reverse_api_address_isSet;
qint32 reverse_api_port;
bool m_reverse_api_port_isSet;
qint32 reverse_api_device_index;
bool m_reverse_api_device_index_isSet;
qint32 reverse_api_channel_index;
bool m_reverse_api_channel_index_isSet;
SWGGLScope* scope_config;
bool m_scope_config_isSet;
SWGChannelMarker* channel_marker;
bool m_channel_marker_isSet;
SWGRollupState* rollup_state;
bool m_rollup_state_isSet;
};
}
#endif /* SWGRadiosondeDemodSettings_H_ */

View File

@ -0,0 +1,275 @@
/**
* 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 "SWGRadiosondeSettings.h"
#include "SWGHelpers.h"
#include <QJsonDocument>
#include <QJsonArray>
#include <QObject>
#include <QDebug>
namespace SWGSDRangel {
SWGRadiosondeSettings::SWGRadiosondeSettings(QString* json) {
init();
this->fromJson(*json);
}
SWGRadiosondeSettings::SWGRadiosondeSettings() {
title = nullptr;
m_title_isSet = false;
rgb_color = 0;
m_rgb_color_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_feature_set_index = 0;
m_reverse_api_feature_set_index_isSet = false;
reverse_api_feature_index = 0;
m_reverse_api_feature_index_isSet = false;
rollup_state = nullptr;
m_rollup_state_isSet = false;
}
SWGRadiosondeSettings::~SWGRadiosondeSettings() {
this->cleanup();
}
void
SWGRadiosondeSettings::init() {
title = new QString("");
m_title_isSet = false;
rgb_color = 0;
m_rgb_color_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_feature_set_index = 0;
m_reverse_api_feature_set_index_isSet = false;
reverse_api_feature_index = 0;
m_reverse_api_feature_index_isSet = false;
rollup_state = new SWGRollupState();
m_rollup_state_isSet = false;
}
void
SWGRadiosondeSettings::cleanup() {
if(title != nullptr) {
delete title;
}
if(reverse_api_address != nullptr) {
delete reverse_api_address;
}
if(rollup_state != nullptr) {
delete rollup_state;
}
}
SWGRadiosondeSettings*
SWGRadiosondeSettings::fromJson(QString &json) {
QByteArray array (json.toStdString().c_str());
QJsonDocument doc = QJsonDocument::fromJson(array);
QJsonObject jsonObject = doc.object();
this->fromJsonObject(jsonObject);
return this;
}
void
SWGRadiosondeSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "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_feature_set_index, pJson["reverseAPIFeatureSetIndex"], "qint32", "");
::SWGSDRangel::setValue(&reverse_api_feature_index, pJson["reverseAPIFeatureIndex"], "qint32", "");
::SWGSDRangel::setValue(&rollup_state, pJson["rollupState"], "SWGRollupState", "SWGRollupState");
}
QString
SWGRadiosondeSettings::asJson ()
{
QJsonObject* obj = this->asJsonObject();
QJsonDocument doc(*obj);
QByteArray bytes = doc.toJson();
delete obj;
return QString(bytes);
}
QJsonObject*
SWGRadiosondeSettings::asJsonObject() {
QJsonObject* obj = new QJsonObject();
if(title != nullptr && *title != QString("")){
toJsonValue(QString("title"), title, obj, QString("QString"));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
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_feature_set_index_isSet){
obj->insert("reverseAPIFeatureSetIndex", QJsonValue(reverse_api_feature_set_index));
}
if(m_reverse_api_feature_index_isSet){
obj->insert("reverseAPIFeatureIndex", QJsonValue(reverse_api_feature_index));
}
if((rollup_state != nullptr) && (rollup_state->isSet())){
toJsonValue(QString("rollupState"), rollup_state, obj, QString("SWGRollupState"));
}
return obj;
}
QString*
SWGRadiosondeSettings::getTitle() {
return title;
}
void
SWGRadiosondeSettings::setTitle(QString* title) {
this->title = title;
this->m_title_isSet = true;
}
qint32
SWGRadiosondeSettings::getRgbColor() {
return rgb_color;
}
void
SWGRadiosondeSettings::setRgbColor(qint32 rgb_color) {
this->rgb_color = rgb_color;
this->m_rgb_color_isSet = true;
}
qint32
SWGRadiosondeSettings::getUseReverseApi() {
return use_reverse_api;
}
void
SWGRadiosondeSettings::setUseReverseApi(qint32 use_reverse_api) {
this->use_reverse_api = use_reverse_api;
this->m_use_reverse_api_isSet = true;
}
QString*
SWGRadiosondeSettings::getReverseApiAddress() {
return reverse_api_address;
}
void
SWGRadiosondeSettings::setReverseApiAddress(QString* reverse_api_address) {
this->reverse_api_address = reverse_api_address;
this->m_reverse_api_address_isSet = true;
}
qint32
SWGRadiosondeSettings::getReverseApiPort() {
return reverse_api_port;
}
void
SWGRadiosondeSettings::setReverseApiPort(qint32 reverse_api_port) {
this->reverse_api_port = reverse_api_port;
this->m_reverse_api_port_isSet = true;
}
qint32
SWGRadiosondeSettings::getReverseApiFeatureSetIndex() {
return reverse_api_feature_set_index;
}
void
SWGRadiosondeSettings::setReverseApiFeatureSetIndex(qint32 reverse_api_feature_set_index) {
this->reverse_api_feature_set_index = reverse_api_feature_set_index;
this->m_reverse_api_feature_set_index_isSet = true;
}
qint32
SWGRadiosondeSettings::getReverseApiFeatureIndex() {
return reverse_api_feature_index;
}
void
SWGRadiosondeSettings::setReverseApiFeatureIndex(qint32 reverse_api_feature_index) {
this->reverse_api_feature_index = reverse_api_feature_index;
this->m_reverse_api_feature_index_isSet = true;
}
SWGRollupState*
SWGRadiosondeSettings::getRollupState() {
return rollup_state;
}
void
SWGRadiosondeSettings::setRollupState(SWGRollupState* rollup_state) {
this->rollup_state = rollup_state;
this->m_rollup_state_isSet = true;
}
bool
SWGRadiosondeSettings::isSet(){
bool isObjectUpdated = false;
do{
if(title && *title != QString("")){
isObjectUpdated = true; break;
}
if(m_rgb_color_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_feature_set_index_isSet){
isObjectUpdated = true; break;
}
if(m_reverse_api_feature_index_isSet){
isObjectUpdated = true; break;
}
if(rollup_state && rollup_state->isSet()){
isObjectUpdated = true; break;
}
}while(false);
return isObjectUpdated;
}
}

View File

@ -0,0 +1,102 @@
/**
* 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.
*/
/*
* SWGRadiosondeSettings.h
*
* Radiosonde settings
*/
#ifndef SWGRadiosondeSettings_H_
#define SWGRadiosondeSettings_H_
#include <QJsonObject>
#include "SWGRollupState.h"
#include <QString>
#include "SWGObject.h"
#include "export.h"
namespace SWGSDRangel {
class SWG_API SWGRadiosondeSettings: public SWGObject {
public:
SWGRadiosondeSettings();
SWGRadiosondeSettings(QString* json);
virtual ~SWGRadiosondeSettings();
void init();
void cleanup();
virtual QString asJson () override;
virtual QJsonObject* asJsonObject() override;
virtual void fromJsonObject(QJsonObject &json) override;
virtual SWGRadiosondeSettings* fromJson(QString &jsonString) override;
QString* getTitle();
void setTitle(QString* title);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
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 getReverseApiFeatureSetIndex();
void setReverseApiFeatureSetIndex(qint32 reverse_api_feature_set_index);
qint32 getReverseApiFeatureIndex();
void setReverseApiFeatureIndex(qint32 reverse_api_feature_index);
SWGRollupState* getRollupState();
void setRollupState(SWGRollupState* rollup_state);
virtual bool isSet() override;
private:
QString* title;
bool m_title_isSet;
qint32 rgb_color;
bool m_rgb_color_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_feature_set_index;
bool m_reverse_api_feature_set_index_isSet;
qint32 reverse_api_feature_index;
bool m_reverse_api_feature_index_isSet;
SWGRollupState* rollup_state;
bool m_rollup_state_isSet;
};
}
#endif /* SWGRadiosondeSettings_H_ */