Add APT demodulator

pull/787/head
Jon Beniston 2021-02-26 20:40:23 +00:00
rodzic 5da344b24a
commit bb452a3216
28 zmienionych plików z 3972 dodań i 0 usunięć

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Plik binarny nie jest wyświetlany.

Po

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

Wyświetl plik

@ -18,6 +18,10 @@ add_subdirectory(demodchirpchat)
add_subdirectory(demodvorsc)
add_subdirectory(demodpacket)
if(APT_FOUND)
add_subdirectory(demodapt)
endif()
if(LIBDSDCC_FOUND AND LIBMBE_FOUND)
add_subdirectory(demoddsd)
endif(LIBDSDCC_FOUND AND LIBMBE_FOUND)

Wyświetl plik

@ -0,0 +1,68 @@
project(demodapt)
set(demodapt_SOURCES
aptdemod.cpp
aptdemodsettings.cpp
aptdemodbaseband.cpp
aptdemodsink.cpp
aptdemodplugin.cpp
aptdemodwebapiadapter.cpp
)
set(demodapt_HEADERS
aptdemod.h
aptdemodsettings.h
aptdemodbaseband.h
aptdemodsink.h
aptdemodplugin.h
aptdemodwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${APT_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(demodapt_SOURCES
${demodapt_SOURCES}
aptdemodgui.cpp
aptdemodgui.ui
aptdemodsettingsdialog.cpp
aptdemodsettingsdialog.ui
icons.qrc
)
set(demodapt_HEADERS
${demodapt_HEADERS}
aptdemodgui.h
aptdemodsettingsdialog.h
)
set(TARGET_NAME demodapt)
set(TARGET_LIB "Qt5::Widgets")
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME demodaptsrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${demodapt_SOURCES}
)
if(APT_EXTERNAL)
add_dependencies(${TARGET_NAME} apt)
endif()
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${APT_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

Wyświetl plik

@ -0,0 +1,851 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "aptdemod.h"
#include <QTime>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include <QThread>
#include <stdio.h>
#include <complex.h>
#include "SWGChannelSettings.h"
#include "SWGAPTDemodSettings.h"
#include "SWGChannelReport.h"
#include "SWGChannelActions.h"
#include "SWGMapItem.h"
#include "SWGAPTDemodActions.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(APTDemod::MsgConfigureAPTDemod, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgPixels, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgImage, Message)
MESSAGE_CLASS_DEFINITION(APTDemod::MsgResetDecoder, Message)
const char * const APTDemod::m_channelIdURI = "sdrangel.channel.aptdemod";
const char * const APTDemod::m_channelId = "APTDemod";
APTDemod::APTDemod(DeviceAPI *deviceAPI) :
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
m_deviceAPI(deviceAPI),
m_basebandSampleRate(0)
{
setObjectName(m_channelId);
m_basebandSink = new APTDemodBaseband(this);
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
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*)));
for (int y = 0; y < APT_MAX_HEIGHT; y++)
{
m_image.prow[y] = new float[APT_PROW_WIDTH];
m_tempImage.prow[y] = new float[APT_PROW_WIDTH];
}
resetDecoder();
}
APTDemod::~APTDemod()
{
qDebug("APTDemod::~APTDemod");
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;
for (int y = 0; y < APT_MAX_HEIGHT; y++)
{
delete m_image.prow[y];
delete m_tempImage.prow[y];
}
}
uint32_t APTDemod::getNumberOfDeviceStreams() const
{
return m_deviceAPI->getNbSourceStreams();
}
void APTDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
{
(void) firstOfBurst;
m_basebandSink->feed(begin, end);
}
void APTDemod::start()
{
qDebug("APTDemod::start");
m_basebandSink->reset();
m_basebandSink->startWork();
m_thread.start();
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
m_basebandSink->getInputMessageQueue()->push(dspMsg);
APTDemodBaseband::MsgConfigureAPTDemodBaseband *msg = APTDemodBaseband::MsgConfigureAPTDemodBaseband::create(m_settings, true);
m_basebandSink->getInputMessageQueue()->push(msg);
}
void APTDemod::stop()
{
qDebug("APTDemod::stop");
m_basebandSink->stopWork();
m_thread.quit();
m_thread.wait();
}
bool APTDemod::matchSatellite(const QString satelliteName)
{
return m_settings.m_satelliteTrackerControl
&& ( (satelliteName == m_settings.m_satelliteName)
|| ( (m_settings.m_satelliteName == "All")
&& ( (satelliteName == "NOAA 15")
|| (satelliteName == "NOAA 18")
|| (satelliteName == "NOAA 19"))));
}
bool APTDemod::handleMessage(const Message& cmd)
{
if (MsgConfigureAPTDemod::match(cmd))
{
MsgConfigureAPTDemod& cfg = (MsgConfigureAPTDemod&) cmd;
qDebug() << "APTDemod::handleMessage: MsgConfigureAPTDemod";
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() << "APTDemod::handleMessage: DSPSignalNotification";
m_basebandSink->getInputMessageQueue()->push(rep);
// Forward to GUI if any
if (m_guiMessageQueue)
m_guiMessageQueue->push(new DSPSignalNotification(notif));
return true;
}
else if (APTDemod::MsgPixels::match(cmd))
{
const APTDemod::MsgPixels& pixelsMsg = (APTDemod::MsgPixels&) cmd;
const float *pixels = pixelsMsg.getPixels();
processPixels(pixels);
return true;
}
else if (APTDemod::MsgResetDecoder::match(cmd))
{
resetDecoder();
// Forward to sink
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
return true;
}
else
{
return false;
}
}
void APTDemod::applySettings(const APTDemodSettings& settings, bool force)
{
bool callProcessImage = false;
qDebug() << "APTDemod::applySettings:"
<< " m_cropNoise: " << settings.m_cropNoise
<< " m_denoise: " << settings.m_denoise
<< " m_linearEqualise: " << settings.m_linearEqualise
<< " m_histogramEqualise: " << settings.m_histogramEqualise
<< " m_precipitationOverlay: " << settings.m_precipitationOverlay
<< " m_flip: " << settings.m_flip
<< " m_channels: " << settings.m_channels
<< " m_decodeEnabled: " << settings.m_decodeEnabled
<< " m_autoSave: " << settings.m_autoSave
<< " m_autoSavePath: " << settings.m_autoSavePath
<< " m_autoSaveMinScanLines: " << settings.m_autoSaveMinScanLines
<< " m_streamIndex: " << settings.m_streamIndex
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
reverseAPIKeys.append("inputFrequencyOffset");
}
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
reverseAPIKeys.append("rfBandwidth");
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
reverseAPIKeys.append("fmDeviation");
}
if ((settings.m_denoise != m_settings.m_denoise) || force) {
reverseAPIKeys.append("denoise");
}
if ((settings.m_linearEqualise != m_settings.m_linearEqualise) || force) {
reverseAPIKeys.append("linearEqualise");
}
if ((settings.m_histogramEqualise != m_settings.m_histogramEqualise) || force) {
reverseAPIKeys.append("histogramEqualise");
}
if ((settings.m_precipitationOverlay != m_settings.m_precipitationOverlay) || force) {
reverseAPIKeys.append("precipitationOverlay");
}
if ((settings.m_flip != m_settings.m_flip) || force) {
reverseAPIKeys.append("flip");
}
if ((settings.m_channels != m_settings.m_channels) || force) {
reverseAPIKeys.append("channels");
}
if ((settings.m_decodeEnabled != m_settings.m_decodeEnabled) || force) {
reverseAPIKeys.append("decodeEnabled");
}
if ((settings.m_autoSave != m_settings.m_autoSave) || force) {
reverseAPIKeys.append("autoSave");
}
if ((settings.m_autoSavePath != m_settings.m_autoSavePath) || force) {
reverseAPIKeys.append("autoSavePath");
}
if ((settings.m_autoSaveMinScanLines != m_settings.m_autoSaveMinScanLines) || force) {
reverseAPIKeys.append("autoSaveMinScanLines");
}
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");
}
APTDemodBaseband::MsgConfigureAPTDemodBaseband *msg = APTDemodBaseband::MsgConfigureAPTDemodBaseband::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_cropNoise != m_settings.m_cropNoise) ||
(settings.m_denoise != m_settings.m_denoise) ||
(settings.m_linearEqualise != m_settings.m_linearEqualise) ||
(settings.m_histogramEqualise != m_settings.m_histogramEqualise) ||
(settings.m_precipitationOverlay != m_settings.m_precipitationOverlay) ||
(settings.m_flip != m_settings.m_flip) ||
(settings.m_channels != m_settings.m_channels))
{
// Call after settings have been applied
callProcessImage = true;
}
m_settings = settings;
if (callProcessImage)
sendImageToGUI();
}
QByteArray APTDemod::serialize() const
{
return m_settings.serialize();
}
bool APTDemod::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureAPTDemod *msg = MsgConfigureAPTDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureAPTDemod *msg = MsgConfigureAPTDemod::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
int APTDemod::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAptDemodSettings(new SWGSDRangel::SWGAPTDemodSettings());
response.getAptDemodSettings()->init();
webapiFormatChannelSettings(response, m_settings);
return 200;
}
int APTDemod::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
APTDemodSettings settings = m_settings;
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
MsgConfigureAPTDemod *msg = MsgConfigureAPTDemod::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("APTDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureAPTDemod *msgToGUI = MsgConfigureAPTDemod::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatChannelSettings(response, settings);
return 200;
}
void APTDemod::webapiUpdateChannelSettings(
APTDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response)
{
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
settings.m_inputFrequencyOffset = response.getAptDemodSettings()->getInputFrequencyOffset();
}
if (channelSettingsKeys.contains("fmDeviation")) {
settings.m_fmDeviation = response.getAptDemodSettings()->getFmDeviation();
}
if (channelSettingsKeys.contains("rfBandwidth")) {
settings.m_rfBandwidth = response.getAptDemodSettings()->getRfBandwidth();
}
if (channelSettingsKeys.contains("cropNoise")) {
settings.m_cropNoise = response.getAptDemodSettings()->getCropNoise();
}
if (channelSettingsKeys.contains("denoise")) {
settings.m_denoise = response.getAptDemodSettings()->getDenoise();
}
if (channelSettingsKeys.contains("linearEqualise")) {
settings.m_linearEqualise = response.getAptDemodSettings()->getLinearEqualise();
}
if (channelSettingsKeys.contains("histogramEqualise")) {
settings.m_histogramEqualise = response.getAptDemodSettings()->getHistogramEqualise();
}
if (channelSettingsKeys.contains("precipitationOverlay")) {
settings.m_precipitationOverlay = response.getAptDemodSettings()->getPrecipitationOverlay();
}
if (channelSettingsKeys.contains("flip")) {
settings.m_flip = response.getAptDemodSettings()->getFlip();
}
if (channelSettingsKeys.contains("channels")) {
settings.m_channels = (APTDemodSettings::ChannelSelection)response.getAptDemodSettings()->getChannels();
}
if (channelSettingsKeys.contains("decodeEnabled")) {
settings.m_decodeEnabled = response.getAptDemodSettings()->getDecodeEnabled();
}
if (channelSettingsKeys.contains("autoSave")) {
settings.m_autoSave = response.getAptDemodSettings()->getAutoSave();
}
if (channelSettingsKeys.contains("autoSavePath")) {
settings.m_autoSavePath = *response.getAptDemodSettings()->getAutoSavePath();
}
if (channelSettingsKeys.contains("autoSaveMinScanLines")) {
settings.m_autoSaveMinScanLines = response.getAptDemodSettings()->getAutoSaveMinScanLines();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getAptDemodSettings()->getRgbColor();
}
if (channelSettingsKeys.contains("title")) {
settings.m_title = *response.getAptDemodSettings()->getTitle();
}
if (channelSettingsKeys.contains("streamIndex")) {
settings.m_streamIndex = response.getAptDemodSettings()->getStreamIndex();
}
if (channelSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getAptDemodSettings()->getUseReverseApi() != 0;
}
if (channelSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getAptDemodSettings()->getReverseApiAddress();
}
if (channelSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getAptDemodSettings()->getReverseApiPort();
}
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIDeviceIndex = response.getAptDemodSettings()->getReverseApiDeviceIndex();
}
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIChannelIndex = response.getAptDemodSettings()->getReverseApiChannelIndex();
}
}
void APTDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const APTDemodSettings& settings)
{
response.getAptDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
response.getAptDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
response.getAptDemodSettings()->setFmDeviation(settings.m_fmDeviation);
response.getAptDemodSettings()->setCropNoise(settings.m_cropNoise);
response.getAptDemodSettings()->setCropNoise(settings.m_denoise);
response.getAptDemodSettings()->setLinearEqualise(settings.m_linearEqualise);
response.getAptDemodSettings()->setHistogramEqualise(settings.m_histogramEqualise);
response.getAptDemodSettings()->setPrecipitationOverlay(settings.m_precipitationOverlay);
response.getAptDemodSettings()->setFlip(settings.m_flip);
response.getAptDemodSettings()->setChannels((int)settings.m_channels);
response.getAptDemodSettings()->setDecodeEnabled(settings.m_decodeEnabled);
response.getAptDemodSettings()->setAutoSave(settings.m_autoSave);
response.getAptDemodSettings()->setAutoSavePath(new QString(settings.m_autoSavePath));
response.getAptDemodSettings()->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
response.getAptDemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getAptDemodSettings()->getTitle()) {
*response.getAptDemodSettings()->getTitle() = settings.m_title;
} else {
response.getAptDemodSettings()->setTitle(new QString(settings.m_title));
}
response.getAptDemodSettings()->setStreamIndex(settings.m_streamIndex);
response.getAptDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getAptDemodSettings()->getReverseApiAddress()) {
*response.getAptDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getAptDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getAptDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getAptDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
response.getAptDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
}
void APTDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const APTDemodSettings& 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 APTDemod::webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const APTDemodSettings& settings,
bool force
)
{
swgChannelSettings->setDirection(0); // Single sink (Rx)
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
swgChannelSettings->setChannelType(new QString("APTDemod"));
swgChannelSettings->setAptDemodSettings(new SWGSDRangel::SWGAPTDemodSettings());
SWGSDRangel::SWGAPTDemodSettings *swgAPTDemodSettings = swgChannelSettings->getAptDemodSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
swgAPTDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
}
if (channelSettingsKeys.contains("rfBandwidth") || force) {
swgAPTDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
}
if (channelSettingsKeys.contains("fmDeviation") || force) {
swgAPTDemodSettings->setFmDeviation(settings.m_fmDeviation);
}
if (channelSettingsKeys.contains("cropNoise") || force) {
swgAPTDemodSettings->setCropNoise(settings.m_cropNoise);
}
if (channelSettingsKeys.contains("denoise") || force) {
swgAPTDemodSettings->setDenoise(settings.m_denoise);
}
if (channelSettingsKeys.contains("linearEqualise") || force) {
swgAPTDemodSettings->setLinearEqualise(settings.m_linearEqualise);
}
if (channelSettingsKeys.contains("histogramEqualise") || force) {
swgAPTDemodSettings->setHistogramEqualise(settings.m_histogramEqualise);
}
if (channelSettingsKeys.contains("precipitationOverlay") || force) {
swgAPTDemodSettings->setPrecipitationOverlay(settings.m_precipitationOverlay);
}
if (channelSettingsKeys.contains("flip") || force) {
swgAPTDemodSettings->setFlip(settings.m_flip);
}
if (channelSettingsKeys.contains("channels") || force) {
swgAPTDemodSettings->setChannels((int)settings.m_channels);
}
if (channelSettingsKeys.contains("decodeEnabled") || force) {
swgAPTDemodSettings->setDecodeEnabled(settings.m_decodeEnabled);
}
if (channelSettingsKeys.contains("m_autoSave") || force) {
swgAPTDemodSettings->setAutoSave(settings.m_autoSave);
}
if (channelSettingsKeys.contains("m_autoSavePath") || force) {
swgAPTDemodSettings->setAutoSavePath(new QString(settings.m_autoSavePath));
}
if (channelSettingsKeys.contains("m_autoSaveMinScanLines") || force) {
swgAPTDemodSettings->setAutoSaveMinScanLines(settings.m_autoSaveMinScanLines);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgAPTDemodSettings->setRgbColor(settings.m_rgbColor);
}
if (channelSettingsKeys.contains("title") || force) {
swgAPTDemodSettings->setTitle(new QString(settings.m_title));
}
if (channelSettingsKeys.contains("streamIndex") || force) {
swgAPTDemodSettings->setStreamIndex(settings.m_streamIndex);
}
}
int APTDemod::webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage)
{
SWGSDRangel::SWGAPTDemodActions *swgAPTDemodActions = query.getAptDemodActions();
if (swgAPTDemodActions)
{
if (channelActionsKeys.contains("aos"))
{
qDebug() << "Aos action";
SWGSDRangel::SWGAPTDemodActions_aos* aos = swgAPTDemodActions->getAos();
QString *satelliteName = aos->getSatelliteName();
if (satelliteName != nullptr)
{
qDebug() << "sat " << *satelliteName;
if (matchSatellite(*satelliteName))
{
qDebug() << "Matched sat";
// Reset for new pass
resetDecoder();
m_basebandSink->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
// Save satellite name
m_satelliteName = *satelliteName;
// Enable decoder and set direction of pass
APTDemodSettings settings = m_settings;
settings.m_decodeEnabled = true;
settings.m_flip = !aos->getNorthToSouthPass();
qDebug() << "Sending settings";
m_inputMessageQueue.push(MsgConfigureAPTDemod::create(settings, false));
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgConfigureAPTDemod::create(settings, false));
}
return 202;
}
else
{
errorMessage = "Missing satellite name";
return 400;
}
}
else if (channelActionsKeys.contains("los"))
{
SWGSDRangel::SWGAPTDemodActions_los* los = swgAPTDemodActions->getLos();
QString *satelliteName = los->getSatelliteName();
if (satelliteName != nullptr)
{
if (matchSatellite(*satelliteName))
{
// Save image
if (m_settings.m_autoSave)
saveImageToDisk();
// Disable decoder
APTDemodSettings settings = m_settings;
settings.m_decodeEnabled = false;
m_inputMessageQueue.push(MsgConfigureAPTDemod::create(settings, false));
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgConfigureAPTDemod::create(settings, false));
}
return 202;
}
else
{
errorMessage = "Missing satellite name";
return 400;
}
}
else
{
errorMessage = "Unknown action";
return 400;
}
}
else
{
errorMessage = "Missing APTDemodActions in query";
return 400;
}
}
void APTDemod::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "APTDemod::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("APTDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
void APTDemod::resetDecoder()
{
m_image.nrow = 0;
m_tempImage.nrow = 0;
m_greyImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_Grayscale8);
m_greyImage.fill(0);
m_colourImage = QImage(APT_IMG_WIDTH, APT_MAX_HEIGHT, QImage::Format_RGB888);
m_colourImage.fill(0);
m_satelliteName = "";
}
void APTDemod::processPixels(const float *pixels)
{
memcpy(m_image.prow[m_image.nrow], pixels, sizeof(float) * APT_PROW_WIDTH);
m_image.nrow++;
sendImageToGUI();
}
static void copyImage(apt_image_t *dst, apt_image_t *src)
{
dst->nrow = src->nrow;
dst->zenith = src->zenith;
dst->chA = src->chA;
dst->chB = src->chB;
for (int i = 0; i < src->nrow; i++)
memcpy(dst->prow[i], src->prow[i], sizeof(float) * APT_PROW_WIDTH);
}
static uchar roundAndClip(float p)
{
int q = (int)round(p);
if (q > 255)
q = 255;
else if (q < 0)
q = 0;
return q;
}
QImage APTDemod::extractImage(QImage image)
{
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS)
return image.copy(0, 0, APT_IMG_WIDTH, m_tempImage.nrow);
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A)
return image.copy(APT_CHA_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
else
return image.copy(APT_CHB_OFFSET, 0, APT_CH_WIDTH, m_tempImage.nrow);
}
QImage APTDemod::processImage(QStringList& imageTypes)
{
copyImage(&m_tempImage, &m_image);
// Calibrate channels according to wavelength
if (m_tempImage.nrow >= APT_CALIBRATION_ROWS)
{
m_tempImage.chA = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
m_tempImage.chB = apt_calibrate(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
QStringList channelTypes({
"", // Unknown
"Visible (0.58-0.68 um)",
"Near-IR (0.725-1.0 um)",
"Near-IR (1.58-1.64 um)",
"Mid-infrared (3.55-3.93 um)",
"Thermal-infrared (10.3-11.3 um)",
"Thermal-infrared (11.5-12.5 um)"
});
imageTypes.append(channelTypes[m_tempImage.chA]);
imageTypes.append(channelTypes[m_tempImage.chB]);
}
// Crop noise due to low elevation at top and bottom of image
if (m_settings.m_cropNoise)
m_tempImage.zenith -= apt_cropNoise(&m_tempImage);
// Denoise filter
if (m_settings.m_denoise)
{
apt_denoise(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_denoise(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
// Flip image if satellite pass is North to South
if (m_settings.m_flip)
{
apt_flipImage(&m_tempImage, APT_CH_WIDTH, APT_CHA_OFFSET);
apt_flipImage(&m_tempImage, APT_CH_WIDTH, APT_CHB_OFFSET);
}
// Linear equalise to improve contrast
if (m_settings.m_linearEqualise)
{
apt_linearEnhance(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_linearEnhance(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
// Histogram equalise to improve contrast
if (m_settings.m_histogramEqualise)
{
apt_histogramEqualise(m_tempImage.prow, m_tempImage.nrow, APT_CHA_OFFSET, APT_CH_WIDTH);
apt_histogramEqualise(m_tempImage.prow, m_tempImage.nrow, APT_CHB_OFFSET, APT_CH_WIDTH);
}
if (m_settings.m_precipitationOverlay)
{
// Overlay precipitation
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_colourImage.scanLine(r);
for (int i = 0; i < APT_IMG_WIDTH; i++)
{
float p = m_tempImage.prow[r][i];
if ((i >= APT_CHB_OFFSET) && (i < APT_CHB_OFFSET + APT_CH_WIDTH) && (p >= 198))
{
apt_rgb_t rgb = apt_applyPalette(apt_PrecipPalette, p - 198);
// Negative float values get converted to positive uchars here
l[i*3] = (uchar)rgb.r;
l[i*3+1] = (uchar)rgb.g;
l[i*3+2] = (uchar)rgb.b;
int a = i - APT_CHB_OFFSET + APT_CHA_OFFSET;
l[a*3] = (uchar)rgb.r;
l[a*3+1] = (uchar)rgb.g;
l[a*3+2] = (uchar)rgb.b;
}
else
{
uchar q = roundAndClip(p);
l[i*3] = q;
l[i*3+1] = q;
l[i*3+2] = q;
}
}
}
return extractImage(m_colourImage);
}
else
{
for (int r = 0; r < m_tempImage.nrow; r++)
{
uchar *l = m_greyImage.scanLine(r);
for (int i = 0; i < APT_IMG_WIDTH; i++)
{
float p = m_tempImage.prow[r][i];
l[i] = roundAndClip(p);
}
}
return extractImage(m_greyImage);
}
}
void APTDemod::sendImageToGUI()
{
// Send image to GUI
if (getMessageQueueToGUI())
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
getMessageQueueToGUI()->push(APTDemod::MsgImage::create(image, imageTypes, m_satelliteName));
}
}
void APTDemod::saveImageToDisk()
{
QStringList imageTypes;
QImage image = processImage(imageTypes);
if (image.height() >= m_settings.m_autoSaveMinScanLines)
{
QString filename;
QDateTime datetime = QDateTime::currentDateTime();
filename = QString("apt_%1_%2.png").arg(m_satelliteName).arg(datetime.toString("yyyyMMdd_hhmm"));
if (!m_settings.m_autoSavePath.isEmpty())
{
if (m_settings.m_autoSavePath.endsWith('/'))
filename = m_settings.m_autoSavePath + filename;
else
filename = m_settings.m_autoSavePath + '/' + filename;
}
if (!image.save(filename))
qCritical() << "Failed to save APT image to: " << filename;
}
}

Wyświetl plik

@ -0,0 +1,242 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMOD_H
#define INCLUDE_APTDEMOD_H
#include <vector>
#include <QNetworkRequest>
#include <QThread>
#include <QImage>
#include <apt.h>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "aptdemodbaseband.h"
#include "aptdemodsettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class APTDemod : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureAPTDemod : public Message {
MESSAGE_CLASS_DECLARATION
public:
const APTDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAPTDemod* create(const APTDemodSettings& settings, bool force)
{
return new MsgConfigureAPTDemod(settings, force);
}
private:
APTDemodSettings m_settings;
bool m_force;
MsgConfigureAPTDemod(const APTDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
// One row of pixels from sink
class MsgPixels : public Message {
MESSAGE_CLASS_DECLARATION
public:
const float *getPixels() const { return m_pixels; }
int getZenith() const { return m_zenith; }
static MsgPixels* create(const float *pixels, int zenith)
{
return new MsgPixels(pixels, zenith);
}
private:
float m_pixels[APT_PROW_WIDTH];
int m_zenith;
MsgPixels(const float *pixels, int zenith) :
Message(),
m_zenith(zenith)
{
memcpy(m_pixels, pixels, sizeof(m_pixels));
}
};
// Processed image to be sent to GUI
class MsgImage : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QImage getImage() const { return m_image; }
const QStringList getImageTypes() const { return m_imageTypes; }
const QString getSatelliteName() const { return m_satelliteName; }
static MsgImage* create(const QImage image, const QStringList imageTypes, const QString satelliteName)
{
return new MsgImage(image, imageTypes, satelliteName);
}
private:
QImage m_image;
QStringList m_imageTypes;
QString m_satelliteName;
MsgImage(const QImage image, const QStringList imageTypes, const QString satelliteName) :
Message(),
m_image(image),
m_imageTypes(imageTypes),
m_satelliteName(satelliteName)
{
}
};
// Sent from GUI to reset decoder
class MsgResetDecoder : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgResetDecoder* create()
{
return new MsgResetDecoder();
}
private:
MsgResetDecoder() :
Message()
{
}
};
APTDemod(DeviceAPI *deviceAPI);
virtual ~APTDemod();
virtual void destroy() { delete this; }
using BasebandSampleSink::feed;
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
virtual void start();
virtual void stop();
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) { id = objectName(); }
virtual const QString& getURI() const { return getName(); }
virtual void getTitle(QString& title) { title = m_settings.m_title; }
virtual qint64 getCenterFrequency() const { return 0; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int getNbSinkStreams() const { return 1; }
virtual int getNbSourceStreams() const { return 0; }
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
{
(void) streamIndex;
(void) sinkElseSource;
return 0;
}
virtual int webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage);
virtual int webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage);
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const APTDemodSettings& settings);
static void webapiUpdateChannelSettings(
APTDemodSettings& settings,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response);
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;
APTDemodBaseband* m_basebandSink;
APTDemodSettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
// Image buffers
apt_image_t m_image; // Received image
apt_image_t m_tempImage; // Processed image
QImage m_greyImage;
QImage m_colourImage;
QString m_satelliteName;
void applySettings(const APTDemodSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const APTDemodSettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const APTDemodSettings& settings,
bool force
);
bool matchSatellite(const QString satelliteName);
void resetDecoder();
void processPixels(const float *pixels);
QImage extractImage(QImage image);
QImage processImage(QStringList& imageTypes);
void sendImageToGUI();
void saveImageToDisk();
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_APTDEMOD_H

Wyświetl plik

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

Wyświetl plik

@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMODBASEBAND_H
#define INCLUDE_APTDEMODBASEBAND_H
#include <QObject>
#include <QMutex>
#include "dsp/samplesinkfifo.h"
#include "util/message.h"
#include "util/messagequeue.h"
#include "aptdemodsink.h"
class DownChannelizer;
class APTDemod;
class APTDemodBaseband : public QObject
{
Q_OBJECT
public:
class MsgConfigureAPTDemodBaseband : public Message {
MESSAGE_CLASS_DECLARATION
public:
const APTDemodSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureAPTDemodBaseband* create(const APTDemodSettings& settings, bool force)
{
return new MsgConfigureAPTDemodBaseband(settings, force);
}
private:
APTDemodSettings m_settings;
bool m_force;
MsgConfigureAPTDemodBaseband(const APTDemodSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
APTDemodBaseband(APTDemod *packetDemod);
~APTDemodBaseband();
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);
double getMagSq() const { return m_sink.getMagSq(); }
bool isRunning() const { return m_running; }
private:
SampleSinkFifo m_sampleFifo;
DownChannelizer *m_channelizer;
APTDemodSink m_sink;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
APTDemodSettings m_settings;
bool m_running;
QMutex m_mutex;
bool handleMessage(const Message& cmd);
void calculateOffset(APTDemodSink *sink);
void applySettings(const APTDemodSettings& settings, bool force = false);
private slots:
void handleInputMessages();
void handleData(); //!< Handle data when samples have to be processed
};
#endif // INCLUDE_APTDEMODBASEBAND_H

Wyświetl plik

@ -0,0 +1,479 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <ctype.h>
#include <QDockWidget>
#include <QMainWindow>
#include <QDebug>
#include <QMessageBox>
#include <QAction>
#include <QRegExp>
#include <QFileDialog>
#include "aptdemodgui.h"
#include "util/ax25.h"
#include "device/deviceuiset.h"
#include "dsp/dspengine.h"
#include "dsp/dspcommands.h"
#include "ui_aptdemodgui.h"
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "util/morse.h"
#include "util/units.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "dsp/dspengine.h"
#include "gui/crightclickenabler.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "aptdemod.h"
#include "aptdemodsink.h"
#include "aptdemodsettingsdialog.h"
APTDemodGUI* APTDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
{
APTDemodGUI* gui = new APTDemodGUI(pluginAPI, deviceUISet, rxChannel);
return gui;
}
void APTDemodGUI::destroy()
{
delete this;
}
void APTDemodGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray APTDemodGUI::serialize() const
{
return m_settings.serialize();
}
bool APTDemodGUI::deserialize(const QByteArray& data)
{
if(m_settings.deserialize(data)) {
displaySettings();
applySettings(true);
return true;
} else {
resetToDefaults();
return false;
}
}
bool APTDemodGUI::handleMessage(const Message& message)
{
if (APTDemod::MsgConfigureAPTDemod::match(message))
{
qDebug("APTDemodGUI::handleMessage: APTDemod::MsgConfigureAPTDemod");
const APTDemod::MsgConfigureAPTDemod& cfg = (APTDemod::MsgConfigureAPTDemod&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (APTDemod::MsgImage::match(message))
{
const APTDemod::MsgImage& imageMsg = (APTDemod::MsgImage&) message;
m_image = imageMsg.getImage();
m_pixmap.convertFromImage(m_image);
ui->image->setPixmap(m_pixmap);
QStringList imageTypes = imageMsg.getImageTypes();
if (imageTypes.size() == 0)
{
ui->channelALabel->setText("Channel A");
ui->channelBLabel->setText("Channel B");
}
else
{
if (imageTypes[0].isEmpty())
ui->channelALabel->setText("Channel A");
else
ui->channelALabel->setText(imageTypes[0]);
if (imageTypes[1].isEmpty())
ui->channelBLabel->setText("Channel B");
else
ui->channelBLabel->setText(imageTypes[1]);
}
QString satelliteName = imageMsg.getSatelliteName();
if (!satelliteName.isEmpty())
ui->imageContainer->setWindowTitle("Received image from " + satelliteName);
else
ui->imageContainer->setWindowTitle("Received image");
return true;
}
else if (DSPSignalNotification::match(message))
{
DSPSignalNotification& notif = (DSPSignalNotification&) message;
m_basebandSampleRate = notif.getSampleRate();
return true;
}
return false;
}
void APTDemodGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()) != 0)
{
if (handleMessage(*message))
{
delete message;
}
}
}
void APTDemodGUI::channelMarkerChangedByCursor()
{
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void APTDemodGUI::channelMarkerHighlightedByCursor()
{
setHighlighted(m_channelMarker.getHighlighted());
}
void APTDemodGUI::on_deltaFrequency_changed(qint64 value)
{
m_channelMarker.setCenterFrequency(value);
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
applySettings();
}
void APTDemodGUI::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 APTDemodGUI::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 APTDemodGUI::on_channels_currentIndexChanged(int index)
{
m_settings.m_channels = (APTDemodSettings::ChannelSelection)index;
if (m_settings.m_channels == APTDemodSettings::BOTH_CHANNELS)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(true);
}
else if (m_settings.m_channels == APTDemodSettings::CHANNEL_A)
{
ui->channelALabel->setVisible(true);
ui->channelBLabel->setVisible(false);
}
else
{
ui->channelALabel->setVisible(false);
ui->channelBLabel->setVisible(true);
}
applySettings();
}
void APTDemodGUI::on_cropNoise_clicked(bool checked)
{
m_settings.m_cropNoise = checked;
applySettings();
}
void APTDemodGUI::on_denoise_clicked(bool checked)
{
m_settings.m_denoise = checked;
applySettings();
}
void APTDemodGUI::on_linear_clicked(bool checked)
{
m_settings.m_linearEqualise = checked;
applySettings();
}
void APTDemodGUI::on_histogram_clicked(bool checked)
{
m_settings.m_histogramEqualise = checked;
applySettings();
}
void APTDemodGUI::on_precipitation_clicked(bool checked)
{
m_settings.m_precipitationOverlay = checked;
applySettings();
}
void APTDemodGUI::on_flip_clicked(bool checked)
{
m_settings.m_flip = checked;
if (m_settings.m_flip)
ui->image->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
else
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
applySettings();
}
void APTDemodGUI::on_startStop_clicked(bool checked)
{
m_settings.m_decodeEnabled = checked;
applySettings();
}
void APTDemodGUI::on_resetDecoder_clicked()
{
ui->image->setPixmap(QPixmap());
ui->imageContainer->setWindowTitle("Received image");
// Send message to reset decoder
m_aptDemod->getInputMessageQueue()->push(APTDemod::MsgResetDecoder::create());
}
void APTDemodGUI::on_showSettings_clicked()
{
APTDemodSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
applySettings();
}
// Save image to disk
void APTDemodGUI::on_saveImage_clicked()
{
QFileDialog fileDialog(nullptr, "Select file to save image to", "", "*.png;*.jpg;*.jpeg;*.bmp;*.ppm;*.xbm;*.xpm");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
qDebug() << "APT: Saving image to " << fileNames;
if (!m_image.save(fileNames[0]))
QMessageBox::critical(this, "APT Demodulator", QString("Failed to save image to %1").arg(fileNames[0]));
}
}
}
void APTDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
void APTDemodGUI::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_aptDemod->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();
}
APTDemodGUI::APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
ChannelGUI(parent),
ui(new Ui::APTDemodGUI),
m_pluginAPI(pluginAPI),
m_deviceUISet(deviceUISet),
m_channelMarker(this),
m_doApplySettings(true),
m_tickCount(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
m_aptDemod = reinterpret_cast<APTDemod*>(rxChannel);
m_aptDemod->setMessageQueueToGUI(getInputMessageQueue());
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
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("APT Demodulator");
m_channelMarker.blockSignals(false);
m_channelMarker.setVisible(true); // activate signal on the last setting only
setTitleColor(m_channelMarker.getColor());
m_settings.setChannelMarker(&m_channelMarker);
m_deviceUISet->addChannelMarker(&m_channelMarker);
m_deviceUISet->addRollupWidget(this);
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
displaySettings();
applySettings(true);
}
APTDemodGUI::~APTDemodGUI()
{
delete ui;
}
void APTDemodGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void APTDemodGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
APTDemod::MsgConfigureAPTDemod* message = APTDemod::MsgConfigureAPTDemod::create( m_settings, force);
m_aptDemod->getInputMessageQueue()->push(message);
}
}
void APTDemodGUI::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->startStop->setChecked(m_settings.m_decodeEnabled);
ui->cropNoise->setChecked(m_settings.m_cropNoise);
ui->denoise->setChecked(m_settings.m_denoise);
ui->linear->setChecked(m_settings.m_linearEqualise);
ui->histogram->setChecked(m_settings.m_histogramEqualise);
ui->precipitation->setChecked(m_settings.m_precipitationOverlay);
ui->flip->setChecked(m_settings.m_flip);
if (m_settings.m_flip)
ui->image->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
else
ui->image->setAlignment(Qt::AlignTop | Qt::AlignHCenter);
ui->channels->setCurrentIndex((int)m_settings.m_channels);
displayStreamIndex();
blockApplySettings(false);
}
void APTDemodGUI::displayStreamIndex()
{
if (m_deviceUISet->m_deviceMIMOEngine) {
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
} else {
setStreamIndicator("S"); // single channel indicator
}
}
void APTDemodGUI::leaveEvent(QEvent*)
{
m_channelMarker.setHighlighted(false);
}
void APTDemodGUI::enterEvent(QEvent*)
{
m_channelMarker.setHighlighted(true);
}
void APTDemodGUI::tick()
{
double magsqAvg, magsqPeak;
int nbMagsqSamples;
m_aptDemod->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++;
}

Wyświetl plik

@ -0,0 +1,116 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMODGUI_H
#define INCLUDE_APTDEMODGUI_H
#include <QIcon>
#include <QAbstractListModel>
#include <QModelIndex>
#include <QProgressDialog>
#include <QTableWidgetItem>
#include <QPushButton>
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include <QImage>
#include <QPixmap>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "dsp/movingaverage.h"
#include "util/messagequeue.h"
#include "aptdemodsettings.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class APTDemod;
class APTDemodGUI;
namespace Ui {
class APTDemodGUI;
}
class APTDemodGUI;
class APTDemodGUI : public ChannelGUI {
Q_OBJECT
public:
static APTDemodGUI* 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::APTDemodGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
APTDemodSettings m_settings;
bool m_doApplySettings;
APTDemod* m_aptDemod;
int m_basebandSampleRate;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QImage m_image;
QPixmap m_pixmap;
explicit APTDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~APTDemodGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
bool handleMessage(const Message& message);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_rfBW_valueChanged(int index);
void on_fmDev_valueChanged(int value);
void on_channels_currentIndexChanged(int index);
void on_cropNoise_clicked(bool checked=false);
void on_denoise_clicked(bool checked=false);
void on_linear_clicked(bool checked=false);
void on_histogram_clicked(bool checked=false);
void on_precipitation_clicked(bool checked=false);
void on_flip_clicked(bool checked=false);
void on_startStop_clicked(bool checked=false);
void on_showSettings_clicked();
void on_resetDecoder_clicked();
void on_saveImage_clicked();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
};
#endif // INCLUDE_APTDEMODGUI_H

Wyświetl plik

@ -0,0 +1,723 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>APTDemodGUI</class>
<widget class="RollupWidget" name="APTDemodGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>451</width>
<height>569</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>352</width>
<height>0</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="windowTitle">
<string>APT Demodulator</string>
</property>
<property name="statusTip">
<string>APT Demodulator</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>431</width>
<height>121</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>350</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QHBoxLayout" name="powLayout">
<property name="topMargin">
<number>2</number>
</property>
<item>
<widget class="QLabel" name="deltaFrequencyLabel">
<property name="minimumSize">
<size>
<width>16</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Df</string>
</property>
</widget>
</item>
<item>
<widget class="ValueDialZ" name="deltaFrequency" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="cursor">
<cursorShape>PointingHandCursor</cursorShape>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="toolTip">
<string>Demod shift frequency from center in Hz</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="deltaUnits">
<property name="text">
<string>Hz </string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="channelPowerLayout">
<item>
<widget class="QLabel" name="channelPower">
<property name="toolTip">
<string>Channel power</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelPowerUnits">
<property name="text">
<string> dB</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="levelLayout">
<item>
<widget class="QLabel" name="channelPowerMeterUnits">
<property name="text">
<string>dB</string>
</property>
</widget>
</item>
<item>
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>24</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Mono</family>
<pointsize>8</pointsize>
</font>
</property>
<property name="toolTip">
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="sliderLayout">
<item>
<widget class="QLabel" name="rfBWLabel">
<property name="text">
<string>BW</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="rfBW">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>RF bandwidth</string>
</property>
<property name="minimum">
<number>300</number>
</property>
<property name="maximum">
<number>600</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>400</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="rfBWText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>40.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevLabel">
<property name="text">
<string>Dev</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="fmDev">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Frequency deviation</string>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>250</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>170</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="fmDevText">
<property name="minimumSize">
<size>
<width>30</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>17.0k</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>Start/stop decoding</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="showSettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="resetDecoder">
<property name="toolTip">
<string>Reset decoder (clears current image)</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>
<item>
<widget class="QPushButton" name="saveImage">
<property name="toolTip">
<string>Save image to disk</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/save.png</normaloff>:/save.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="channelsLabel">
<property name="text">
<string>Channels</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="channels">
<property name="minimumSize">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Which channels from the image to display</string>
</property>
<item>
<property name="text">
<string>Both</string>
</property>
</item>
<item>
<property name="text">
<string>A</string>
</property>
</item>
<item>
<property name="text">
<string>B</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="cropNoise">
<property name="toolTip">
<string>Crop noise from top and bottom of image</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/apt/icons/cropnoise.png</normaloff>:/apt/icons/cropnoise.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="denoise">
<property name="toolTip">
<string>Apply denoise filter to the image</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/apt/icons/denoise.png</normaloff>:/apt/icons/denoise.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="linear">
<property name="toolTip">
<string>Apply linear equalisation to the image</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/linear.png</normaloff>:/linear.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="histogram">
<property name="toolTip">
<string>Apply histogram equalisation to the image</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/dsb.png</normaloff>:/dsb.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="precipitation">
<property name="toolTip">
<string>Overlay precipitation</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="icons.qrc">
<normaloff>:/apt/icons/precipitation.png</normaloff>:/apt/icons/precipitation.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="flip">
<property name="toolTip">
<string>Satellite pass direction (flips image)</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>
<normalon>:/arrow_up.png</normalon>:/arrow_down.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="imageContainer" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>150</y>
<width>431</width>
<height>381</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Received Image</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="channelTypeHorizontalLayout">
<item>
<widget class="QLabel" name="channelALabel">
<property name="text">
<string>Channel A</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelBLabel">
<property name="text">
<string>Channel B</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="ScaledImage" name="image">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>300</width>
<height>350</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>LevelMeterSignalDB</class>
<extends>QWidget</extends>
<header>gui/levelmeter.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ValueDialZ</class>
<extends>QWidget</extends>
<header>gui/valuedialz.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ScaledImage</class>
<extends>QLabel</extends>
<header>gui/scaledimage.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>deltaFrequency</tabstop>
<tabstop>rfBW</tabstop>
<tabstop>fmDev</tabstop>
<tabstop>startStop</tabstop>
<tabstop>showSettings</tabstop>
<tabstop>resetDecoder</tabstop>
<tabstop>saveImage</tabstop>
<tabstop>channels</tabstop>
<tabstop>cropNoise</tabstop>
<tabstop>denoise</tabstop>
<tabstop>linear</tabstop>
<tabstop>histogram</tabstop>
<tabstop>precipitation</tabstop>
<tabstop>flip</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections/>
</ui>

Wyświetl plik

@ -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 "aptdemodgui.h"
#endif
#include "aptdemod.h"
#include "aptdemodwebapiadapter.h"
#include "aptdemodplugin.h"
const PluginDescriptor APTDemodPlugin::m_pluginDescriptor = {
APTDemod::m_channelId,
QStringLiteral("APT Demodulator"),
QStringLiteral("6.5.5"),
QStringLiteral("(c) Jon Beniston, M7RCE and Aptdec authors"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
APTDemodPlugin::APTDemodPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(0)
{
}
const PluginDescriptor& APTDemodPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void APTDemodPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerRxChannel(APTDemod::m_channelIdURI, APTDemod::m_channelId, this);
}
void APTDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
{
if (bs || cs)
{
APTDemod *instance = new APTDemod(deviceAPI);
if (bs) {
*bs = instance;
}
if (cs) {
*cs = instance;
}
}
}
#ifdef SERVER_MODE
ChannelGUI* APTDemodPlugin::createRxChannelGUI(
DeviceUISet *deviceUISet,
BasebandSampleSink *rxChannel) const
{
(void) deviceUISet;
(void) rxChannel;
return 0;
}
#else
ChannelGUI* APTDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
{
return APTDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
}
#endif
ChannelWebAPIAdapter* APTDemodPlugin::createChannelWebAPIAdapter() const
{
return new APTDemodWebAPIAdapter();
}

Wyświetl plik

@ -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_APTDEMODPLUGIN_H
#define INCLUDE_APTDEMODPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class DeviceUISet;
class BasebandSampleSink;
class APTDemodPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.channel.aptdemod")
public:
explicit APTDemodPlugin(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_APTDEMODPLUGIN_H

Wyświetl plik

@ -0,0 +1,163 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2015 Edouard Griffiths, F4EXB. //
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include "dsp/dspengine.h"
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "aptdemodsettings.h"
APTDemodSettings::APTDemodSettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void APTDemodSettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_rfBandwidth = 40000.0f;
m_fmDeviation = 17000.0f;
m_cropNoise = false;
m_denoise = true;
m_linearEqualise = false;
m_histogramEqualise = false;
m_precipitationOverlay = false;
m_flip = false;
m_channels = BOTH_CHANNELS;
m_decodeEnabled = true;
m_satelliteTrackerControl = true;
m_satelliteName = "All";
m_autoSave = false;
m_autoSavePath = "";
m_autoSaveMinScanLines = 200;
m_rgbColor = QColor(216, 112, 169).rgb();
m_title = "APT Demodulator";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
}
QByteArray APTDemodSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_streamIndex);
s.writeReal(3, m_rfBandwidth);
s.writeReal(4, m_fmDeviation);
s.writeBool(5, m_cropNoise);
s.writeBool(6, m_denoise);
s.writeBool(7, m_linearEqualise);
s.writeBool(8, m_histogramEqualise);
s.writeBool(9, m_precipitationOverlay);
s.writeBool(10, m_flip);
s.writeS32(11, (int)m_channels);
s.writeBool(12, m_decodeEnabled);
s.writeBool(13, m_satelliteTrackerControl);
s.writeString(14, m_satelliteName);
s.writeBool(15, m_autoSave);
s.writeString(16, m_autoSavePath);
s.writeS32(17, m_autoSaveMinScanLines);
if (m_channelMarker) {
s.writeBlob(20, m_channelMarker->serialize());
}
s.writeU32(21, m_rgbColor);
s.writeString(22, m_title);
s.writeBool(23, m_useReverseAPI);
s.writeString(24, m_reverseAPIAddress);
s.writeU32(25, m_reverseAPIPort);
s.writeU32(26, m_reverseAPIDeviceIndex);
s.writeU32(27, m_reverseAPIChannelIndex);
return s.final();
}
bool APTDemodSettings::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.readS32(2, &m_streamIndex, 0);
d.readReal(3, &m_rfBandwidth, 40000.0f);
d.readReal(4, &m_fmDeviation, 17000.0f);
d.readBool(5, &m_cropNoise, false);
d.readBool(6, &m_denoise, true);
d.readBool(7, &m_linearEqualise, false);
d.readBool(8, &m_histogramEqualise, false);
d.readBool(9, &m_precipitationOverlay, false);
d.readBool(10, &m_flip, false);
d.readS32(11, (int *)&m_channels, (int)BOTH_CHANNELS);
d.readBool(12, &m_decodeEnabled, true);
d.readBool(13, &m_satelliteTrackerControl, true);
d.readString(14, &m_satelliteName, "All");
d.readBool(15, &m_autoSave, false);
d.readString(16, &m_autoSavePath, "");
d.readS32(17, &m_autoSaveMinScanLines, 200);
d.readBlob(20, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readU32(21, &m_rgbColor, QColor(216, 112, 169).rgb());
d.readString(22, &m_title, "APT Demodulator");
d.readBool(23, &m_useReverseAPI, false);
d.readString(24, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(25, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(26, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(27, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}

Wyświetl plik

@ -0,0 +1,64 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMODSETTINGS_H
#define INCLUDE_APTDEMODSETTINGS_H
#include <QByteArray>
#include <QHash>
class Serializable;
struct APTDemodSettings
{
qint32 m_inputFrequencyOffset;
float m_rfBandwidth;
float m_fmDeviation;
bool m_cropNoise;
bool m_denoise;
bool m_linearEqualise;
bool m_histogramEqualise;
bool m_precipitationOverlay;
bool m_flip;
enum ChannelSelection {BOTH_CHANNELS, CHANNEL_A, CHANNEL_B} m_channels;
bool m_decodeEnabled;
bool m_satelliteTrackerControl; //! Whether Sat Tracker can set direction of pass
QString m_satelliteName; //!< All, NOAA 15, NOAA 18 or NOAA 19
bool m_autoSave;
QString m_autoSavePath;
int m_autoSaveMinScanLines;
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
QString m_audioDeviceName;
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;
APTDemodSettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif /* INCLUDE_APTDEMODSETTINGS_H */

Wyświetl plik

@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QFileDialog>
#include "aptdemodsettingsdialog.h"
APTDemodSettingsDialog::APTDemodSettingsDialog(APTDemodSettings *settings, QWidget* parent) :
QDialog(parent),
m_settings(settings),
ui(new Ui::APTDemodSettingsDialog)
{
ui->setupUi(this);
ui->satelliteTrackerControl->setChecked(settings->m_satelliteTrackerControl);
ui->satellite->setCurrentText(settings->m_satelliteName);
ui->autoSave->setChecked(settings->m_autoSave);
ui->autoSavePath->setText(settings->m_autoSavePath);
ui->minScanlines->setValue(settings->m_autoSaveMinScanLines);
}
APTDemodSettingsDialog::~APTDemodSettingsDialog()
{
delete ui;
}
void APTDemodSettingsDialog::accept()
{
m_settings->m_satelliteTrackerControl = ui->satelliteTrackerControl->isChecked();
m_settings->m_satelliteName = ui->satellite->currentText();
m_settings->m_autoSave = ui->autoSave->isChecked();
m_settings->m_autoSavePath = ui->autoSavePath->text();
m_settings->m_autoSaveMinScanLines = ui->minScanlines->value();
QDialog::accept();
}
void APTDemodSettingsDialog::on_autoSavePathBrowse_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this, "Select directory to save images to", "",
QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
ui->autoSavePath->setText(dir);
}

Wyświetl plik

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMODSETTINGSDIALOG_H
#define INCLUDE_APTDEMODSETTINGSDIALOG_H
#include "ui_aptdemodsettingsdialog.h"
#include "aptdemodsettings.h"
class APTDemodSettingsDialog : public QDialog {
Q_OBJECT
public:
explicit APTDemodSettingsDialog(APTDemodSettings *settings, QWidget* parent = 0);
~APTDemodSettingsDialog();
APTDemodSettings *m_settings;
private slots:
void accept();
void on_autoSavePathBrowse_clicked();
private:
Ui::APTDemodSettingsDialog* ui;
};
#endif // INCLUDE_APTDEMODSETTINGSDIALOG_H

Wyświetl plik

@ -0,0 +1,205 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>APTDemodSettingsDialog</class>
<widget class="QDialog" name="APTDemodSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>385</width>
<height>212</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>APT Demodulator Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="5" column="0">
<widget class="QLabel" name="autoSavePathLabel">
<property name="text">
<string>Path to save image</string>
</property>
</widget>
</item>
<item row="5" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="autoSavePath">
<property name="toolTip">
<string>Path to save images to</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="autoSavePathBrowse">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/load.png</normaloff>:/load.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="0">
<widget class="QLabel" name="minScanlinesLabel">
<property name="text">
<string>Minimum scanlines</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="minScanlines">
<property name="toolTip">
<string>Enter the minimum number of scanlines in an image (after cropping) for it to be automatically saved</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>30000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>200</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="satelliteLabel">
<property name="text">
<string>Satellite</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="satellite">
<property name="toolTip">
<string>Select which satellite this channel will be used for</string>
</property>
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>All</string>
</property>
</item>
<item>
<property name="text">
<string>NOAA 15</string>
</property>
</item>
<item>
<property name="text">
<string>NOAA 18</string>
</property>
</item>
<item>
<property name="text">
<string>NOAA 19</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="satelliteTrackerControl">
<property name="toolTip">
<string>Check to enable control by Satellite Tracker feature</string>
</property>
<property name="text">
<string>Enable Satellite Tracker control</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="autoSave">
<property name="toolTip">
<string>Check to automatically save images when acquisition is stopped or LOS</string>
</property>
<property name="text">
<string>Auto save image</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>satelliteTrackerControl</tabstop>
<tabstop>satellite</tabstop>
<tabstop>autoSave</tabstop>
<tabstop>autoSavePath</tabstop>
<tabstop>autoSavePathBrowse</tabstop>
<tabstop>minScanlines</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
<include location="icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>APTDemodSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>APTDemodSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

Wyświetl plik

@ -0,0 +1,205 @@
///////////////////////////////////////////////////////////////////////////////////
// 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/dspengine.h"
#include "util/db.h"
#include "util/stepfunctions.h"
#include "pipes/pipeendpoint.h"
#include "maincore.h"
#include "aptdemod.h"
#include "aptdemodsink.h"
APTDemodSink::APTDemodSink(APTDemod *packetDemod) :
m_aptDemod(packetDemod),
m_channelSampleRate(APTDEMOD_AUDIO_SAMPLE_RATE),
m_channelFrequencyOffset(0),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr),
m_samples(nullptr)
{
m_magsq = 0.0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
m_samplesLength = APTDEMOD_AUDIO_SAMPLE_RATE * APT_MAX_HEIGHT / 2; // APT broadcasts at 2 lines per second
m_samples = new float[m_samplesLength];
resetDecoder();
}
void APTDemodSink::resetDecoder()
{
m_sampleCount = 0;
m_writeIdx = 0;
m_readIdx = 0;
apt_init(APTDEMOD_AUDIO_SAMPLE_RATE);
m_row = 0;
m_zenith = 0;
}
APTDemodSink::~APTDemodSink()
{
delete m_samples;
}
// callback from APT library to get audio samples
static int getsamples(void *context, float *samples, int count)
{
APTDemodSink *sink = (APTDemodSink *)context;
return sink->getSamples(samples, count);
}
int APTDemodSink::getSamples(float *samples, int count)
{
for (int i = 0; i < count; i++)
{
if ((m_sampleCount > 0) && (m_readIdx < m_samplesLength))
{
*samples++ = m_samples[m_readIdx++];
m_sampleCount--;
}
else
return i;
}
return count;
}
void APTDemodSink::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;
}
}
}
// Have we enough samples to decode one line?
// 2 lines per second
if (m_sampleCount >= APTDEMOD_AUDIO_SAMPLE_RATE)
{
float pixels[APT_PROW_WIDTH];
apt_getpixelrow(pixels, m_row, &m_zenith, m_row == 0, getsamples, this);
getMessageQueueToChannel()->push(APTDemod::MsgPixels::create(pixels, m_zenith));
m_row++;
}
}
void APTDemodSink::processOneSample(Complex &ci)
{
Complex ca;
// FM demodulation
double magsqRaw;
Real deviation;
Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
// Add to sample buffer, if there's space and decoding is enabled
if ((m_writeIdx < m_samplesLength) && m_settings.m_decodeEnabled)
{
m_samples[m_writeIdx++] = fmDemod;
m_sampleCount++;
}
// 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++;
}
void APTDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "APTDemodSink::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) APTDEMOD_AUDIO_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void APTDemodSink::applySettings(const APTDemodSettings& settings, bool force)
{
qDebug() << "APTDemodSink::applySettings:"
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fmDeviation: " << settings.m_fmDeviation
<< " m_decodeEnabled: " << settings.m_decodeEnabled
<< " 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) APTDEMOD_AUDIO_SAMPLE_RATE;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
{
m_phaseDiscri.setFMScaling(APTDEMOD_AUDIO_SAMPLE_RATE / (2.0f * settings.m_fmDeviation));
}
m_settings = settings;
}

Wyświetl plik

@ -0,0 +1,126 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_APTDEMODSINK_H
#define INCLUDE_APTDEMODSINK_H
#include "dsp/channelsamplesink.h"
#include "dsp/phasediscri.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/firfilter.h"
#include "util/movingaverage.h"
#include "util/doublebufferfifo.h"
#include "util/messagequeue.h"
#include "aptdemodsettings.h"
#include <apt.h>
#include <vector>
#include <iostream>
#include <fstream>
// FIXME: Use lower sample rate for better SNR?
// Do we want an audio filter? Subcarrier at 2800Hz. Does libaptdec have one?
#define APTDEMOD_AUDIO_SAMPLE_RATE 48000
// Lines are 2 per second -> 4160 words per second
class APTDemod;
class APTDemodSink : public ChannelSampleSink {
public:
APTDemodSink(APTDemod *packetDemod);
~APTDemodSink();
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
void applySettings(const APTDemodSettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
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;
}
int getSamples(float *samples, int count);
void resetDecoder();
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
APTDemod *m_aptDemod;
APTDemodSettings m_settings;
int m_channelSampleRate;
int m_channelFrequencyOffset;
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;
PhaseDiscriminators m_phaseDiscri;
// Audio buffer - should probably use a FIFO
float *m_samples;
int m_sampleCount;
int m_samplesLength;
int m_readIdx;
int m_writeIdx;
int m_row; // Row of image currently being received
int m_zenith; // Row number of Zenith
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
};
#endif // INCLUDE_APTDEMODSINK_H

Wyświetl plik

@ -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 "aptdemod.h"
#include "aptdemodwebapiadapter.h"
APTDemodWebAPIAdapter::APTDemodWebAPIAdapter()
{}
APTDemodWebAPIAdapter::~APTDemodWebAPIAdapter()
{}
int APTDemodWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setAptDemodSettings(new SWGSDRangel::SWGAPTDemodSettings());
response.getAptDemodSettings()->init();
APTDemod::webapiFormatChannelSettings(response, m_settings);
return 200;
}
int APTDemodWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage)
{
(void) force;
(void) errorMessage;
APTDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
return 200;
}

Wyświetl plik

@ -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_APTDEMOD_WEBAPIADAPTER_H
#define INCLUDE_APTDEMOD_WEBAPIADAPTER_H
#include "channel/channelwebapiadapter.h"
#include "aptdemodsettings.h"
/**
* Standalone API adapter only for the settings
*/
class APTDemodWebAPIAdapter : public ChannelWebAPIAdapter {
public:
APTDemodWebAPIAdapter();
virtual ~APTDemodWebAPIAdapter();
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:
APTDemodSettings m_settings;
};
#endif // INCLUDE_APTDEMOD_WEBAPIADAPTER_H

Wyświetl plik

@ -0,0 +1,7 @@
<RCC>
<qresource prefix="/apt">
<file>icons/cropnoise.png</file>
<file>icons/denoise.png</file>
<file>icons/precipitation.png</file>
</qresource>
</RCC>

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 353 B

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 456 B

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 420 B

Wyświetl plik

@ -0,0 +1,108 @@
<h1>APT Demodulator Plugin</h1>
<h2>Introduction</h2>
This plugin can be used to demodulate APT (Automatic Picture Transmission) signals transmitted by NOAA weather satellites. These images are at a 4km/pixel resolution in either the visible, near-IR, mid-IR or thermal-IR bands.
![APT Demodulator plugin GUI](../../../doc/img/APTDemod_plugin.png)
* NOAA 15 transmits on 137.620 MHz.
* NOAA 18 transmits on 137.912 MHz.
* NOAA 19 transmits on 137.100 MHz.
<h2>Interface</h2>
![APT Demodulator plugin GUI](../../../doc/img/APTDemod_plugin_settings.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: RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. APT signals are nominally 34kHz wide, however, this defaults to 40kHz to allow for some Doppler shift.
<h3>5: Frequency deviation</h3>
Adjusts the expected frequency deviation in 0.1 kHz steps from 10 to 25 kHz. The typical value for APT is 17 kHz.
<h3>6: Start/stop decoding</h3>
Starts or stops decoding. A maximum of 3000 scanlines can be decoded, after which, the Reset Decoder (7) button needs to be pressed, to start a new image.
<h3>7: Show settings dialog</h3>
When clicked, shows additional APT Demodulator settings.
![APT Demodulator settings dialog](../../../doc/img/APTDemod_plugin_settingsdialog.png)
This includes:
- Whether the APT demodulator can be controlled by the Satellite Tracker feature. When checked, the image decoder will be enabled and reset on AOS and the satellite pass direction will be used to control image rotation. The decoder will be stopped on LOS.
- Which satellites the APT demodulator will respond to AOS and LOS indications from the Satellite Tracker. This can be used to simulataneously decode images from multiple satellites, by having multiple instances of the APT Demodulator and setting a unique satellite name for each demodulator.
- Whether to automatically save the image on LOS.
- Path to save automatically saved images in.
- The minimum number of scanlines required to be in an image, after noise cropping, for it to be automatically saved.
<h3>8: Reset decoder</h3>
Clears the current image and restarts the decoder. The decoder must be reset between passes of different satellites.
<h3>9: Save image to disk</h3>
Saves the current image to disk. Images can be saved in PNG, JPEG, BMP, PPM, XBM or XPM formats.
<h3>10: Channel selection</h3>
Selects whether:
- both channels are displayed
- only channel A is displayed
- only channel B is displayed
<h3>11: Crop noise</h3>
When checked, noise is cropped from the top and bottom of the image. This is noise that is typically the result of the satellite being at a low elevation.
<h3>12: Apply denoise filter</h3>
When checked, a denoise filter is applied to the received image.
<h3>13: Apply linear equalisation</h3>
When checked, linear equalisation is performed, which can enhance the contrast. The equalisation is performed separately on each channel.
<h3>14: Apply histogram equalisation</h3>
When checked, histogram equalisation is performed, which can enhance the contrast. The equalisation is performed separately on each channel.
<h3>15: Overlay precipitation</h3>
When checked, precipitation is detected from the IR channel and overlayed on both channels using a colour palette.
This option will not work if linear or histogram equalisation has been applied.
<h3>16: Pass direction</h3>
The pass direction check button should be set to match the direction of the satellite pass.
i.e. select down arrow for satellite passing from the North to the South and the up arrow for the satellite passing from the South to the North.
This will ensure the image has the Northern latitudes at the top of the image.
This can be set automatically by the Satellite Tracker feature.
<h2>Attribution</h2>
This plugin uses libapt, part of Aptdec by Thierry Leconte and Xerbo, to perform image decoding and processing: https://github.com/Xerbo/aptdec
Icons are by Freepik from Flaticon https://www.flaticon.com/
Icons are by Hare Krishna from the Noun Project Noun Project: https://thenounproject.com/