Merge pull request #1011 from srcejon/radio_astronomy

Add Radio Astronomy plugin
This commit is contained in:
Edouard Griffiths 2021-10-12 20:05:27 +02:00 committed by GitHub
commit 01e2ea93e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
151 changed files with 26852 additions and 923 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

View File

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

View File

@ -0,0 +1,67 @@
project(radioastronomy)
set(radioastronomy_SOURCES
radioastronomy.cpp
radioastronomysettings.cpp
radioastronomybaseband.cpp
radioastronomysink.cpp
radioastronomyplugin.cpp
radioastronomywebapiadapter.cpp
radioastronomyworker.cpp
)
set(radioastronomy_HEADERS
radioastronomy.h
radioastronomysettings.h
radioastronomybaseband.h
radioastronomysink.h
radioastronomyplugin.h
radioastronomywebapiadapter.h
radioastronomyworker.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(radioastronomy_SOURCES
${radioastronomy_SOURCES}
radioastronomygui.cpp
radioastronomygui.ui
radioastronomycalibrationdialog.cpp
radioastronomycalibrationdialog.ui
radioastronomysensordialog.cpp
radioastronomysensordialog.ui
icons.qrc
)
set(radioastronomy_HEADERS
${radioastronomy_HEADERS}
radioastronomygui.h
radioastronomycalibrationdialog.h
radioastronomysensordialog.h
)
set(TARGET_NAME radioastronomy)
set(TARGET_LIB "Qt5::Widgets" Qt5::Charts)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME radioastronomysrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${radioastronomy_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/radioastronomy/">
<file>icons/hot.png</file>
<file>icons/cold.png</file>
<file>icons/temperature.png</file>
<file>icons/reverse.png</file>
<file>icons/gaussian.png</file>
<file>icons/peak.png</file>
<file>icons/marker.png</file>
<file>icons/refline.png</file>
<file>icons/legend.png</file>
<file>icons/lab.png</file>
<file>icons/galactictriangle.png</file>
<file>icons/noise.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,437 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMY_H
#define INCLUDE_RADIOASTRONOMY_H
#include <vector>
#include <QNetworkRequest>
#include <QUdpSocket>
#include <QThread>
#include <QDateTime>
#include <QTimer>
#include "dsp/basebandsamplesink.h"
#include "channel/channelapi.h"
#include "util/message.h"
#include "pipes/pipeendpoint.h"
#include "radioastronomybaseband.h"
#include "radioastronomysettings.h"
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
class DeviceAPI;
class RadioAstronomyWorker;
class RadioAstronomy : public BasebandSampleSink, public ChannelAPI {
Q_OBJECT
public:
class MsgConfigureRadioAstronomy : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RadioAstronomySettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRadioAstronomy* create(const RadioAstronomySettings& settings, bool force)
{
return new MsgConfigureRadioAstronomy(settings, force);
}
private:
RadioAstronomySettings m_settings;
bool m_force;
MsgConfigureRadioAstronomy(const RadioAstronomySettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgSensorMeasurement : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getSensor() const { return m_sensor; }
double getValue() const { return m_value; }
QDateTime getDateTime() const { return m_dateTime; }
static MsgSensorMeasurement* create(int sensor, double value)
{
return new MsgSensorMeasurement(sensor, value, QDateTime::currentDateTime());
}
private:
int m_sensor;
double m_value;
QDateTime m_dateTime;
MsgSensorMeasurement(int sensor, double value, QDateTime dateTime) :
Message(),
m_sensor(sensor),
m_value(value),
m_dateTime(dateTime)
{
}
};
class MsgFFTMeasurement : public Message {
MESSAGE_CLASS_DECLARATION
public:
Real *getFFT() const { return m_fft; }
int getSize() const { return m_size; }
QDateTime getDateTime() const { return m_dateTime; }
static MsgFFTMeasurement* create(const Real *fft, int size, QDateTime dateTime)
{
return new MsgFFTMeasurement(fft, size, dateTime);
}
private:
Real *m_fft;
int m_size;
QDateTime m_dateTime;
MsgFFTMeasurement(const Real *fft, int size, QDateTime dateTime) :
Message(),
m_size(size),
m_dateTime(dateTime)
{
// Take a copy of the data
m_fft = new Real[size];
std::copy(fft, fft + size, m_fft);
}
};
class MsgStartMeasurements : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStartMeasurements* create()
{
return new MsgStartMeasurements();
}
private:
MsgStartMeasurements() :
Message()
{
}
};
class MsgStopMeasurements : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStopMeasurements* create()
{
return new MsgStopMeasurements();
}
private:
MsgStopMeasurements() :
Message()
{
}
};
class MsgMeasurementProgress : public Message {
MESSAGE_CLASS_DECLARATION
public:
int getPercentComplete() const { return m_percentComplete; }
static MsgMeasurementProgress* create(int percentComplete)
{
return new MsgMeasurementProgress(percentComplete);
}
private:
int m_percentComplete;
MsgMeasurementProgress(int percentComplete) :
Message(),
m_percentComplete(percentComplete)
{
}
};
class MsgStartCal : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getHot() const { return m_hot; }
static MsgStartCal* create(bool hot)
{
return new MsgStartCal(hot);
}
private:
bool m_hot;
MsgStartCal(bool hot) :
Message(),
m_hot(hot)
{
}
};
class MsgCalComplete : public Message {
MESSAGE_CLASS_DECLARATION
public:
Real *getCal() const { return m_cal; }
int getSize() const { return m_size; }
bool getHot() const { return m_hot; }
QDateTime getDateTime() const { return m_dateTime; }
static MsgCalComplete* create(const Real *cal, int size, QDateTime dateTime, bool hot)
{
return new MsgCalComplete(cal, size, dateTime, hot);
}
private:
Real *m_cal;
int m_size;
QDateTime m_dateTime;
bool m_hot;
MsgCalComplete(const Real *cal, int size, QDateTime dateTime, bool hot) :
Message(),
m_size(size),
m_dateTime(dateTime),
m_hot(hot)
{
// Take a copy of the data
m_cal = new Real[size];
std::copy(cal, cal + size, m_cal);
}
};
class MsgStartSweep : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStartSweep* create()
{
return new MsgStartSweep();
}
private:
MsgStartSweep() :
Message()
{
}
};
class MsgStopSweep : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgStopSweep* create()
{
return new MsgStopSweep();
}
private:
MsgStopSweep() :
Message()
{
}
};
class MsgSweepComplete : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgSweepComplete* create()
{
return new MsgSweepComplete();
}
private:
MsgSweepComplete() :
Message()
{
}
};
class MsgSweepStatus : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getStatus() const { return m_status; }
static MsgSweepStatus* create(const QString& status)
{
return new MsgSweepStatus(status);
}
private:
QString m_status;
MsgSweepStatus(const QString& status) :
Message(),
m_status(status)
{
}
};
RadioAstronomy(DeviceAPI *deviceAPI);
virtual ~RadioAstronomy();
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) override;
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings& response,
QString& errorMessage) override;
virtual int webapiActionsPost(
const QStringList& channelActionsKeys,
SWGSDRangel::SWGChannelActions& query,
QString& errorMessage) override;
static void webapiFormatChannelSettings(
SWGSDRangel::SWGChannelSettings& response,
const RadioAstronomySettings& settings);
static void webapiUpdateChannelSettings(
RadioAstronomySettings& 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);
}
/* void setMessageQueueToGUI(MessageQueue* queue) override {
ChannelAPI::setMessageQueueToGUI(queue);
m_basebandSink->setMessageQueueToGUI(queue);
}*/
uint32_t getNumberOfDeviceStreams() const;
static const char * const m_channelIdURI;
static const char * const m_channelId;
private:
DeviceAPI *m_deviceAPI;
QThread m_thread;
QThread m_workerThread;
RadioAstronomyBaseband* m_basebandSink;
RadioAstronomyWorker *m_worker;
RadioAstronomySettings m_settings;
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
qint64 m_centerFrequency;
QList<AvailablePipeSource> m_availablePipes;
PipeEndPoint *m_selectedPipe;
QTimer m_updatePipesTimer;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
int m_starTrackerFeatureSetIndex;
int m_starTrackerFeatureIndex;
int m_rotatorFeatureSetIndex;
int m_rotatorFeatureIndex;
void (*sweepState)();
float m_sweep1; // Current sweep position
float m_sweep2;
float m_sweep1Stop;
float m_sweep1Start;
bool m_sweeping;
bool m_sweepStop;
QTimer m_sweepTimer;
QMetaObject::Connection m_sweepTimerConnection;
void applySettings(const RadioAstronomySettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const RadioAstronomySettings& settings, bool force);
void webapiFormatChannelSettings(
QList<QString>& channelSettingsKeys,
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
const RadioAstronomySettings& settings,
bool force
);
void callOnStartTime(void (RadioAstronomy::*f)());
void sweepStart();
void startCal(bool hot);
void calComplete(MsgCalComplete* report);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void updatePipes();
void handleChannelMessages();
void startMeasurement();
void sweepStartMeasurement();
void sweep1();
void sweep2();
void waitUntilOnTarget();
void sweepNext();
void sweepComplete();
};
#endif // INCLUDE_RADIOASTRONOMY_H

View File

@ -0,0 +1,197 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radioastronomybaseband.h"
#include "radioastronomy.h"
MESSAGE_CLASS_DEFINITION(RadioAstronomyBaseband::MsgConfigureRadioAstronomyBaseband, Message)
RadioAstronomyBaseband::RadioAstronomyBaseband(RadioAstronomy *aisDemod) :
m_sink(aisDemod),
m_running(false),
m_mutex(QMutex::Recursive)
{
qDebug("RadioAstronomyBaseband::RadioAstronomyBaseband");
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
m_channelizer = new DownChannelizer(&m_sink);
}
RadioAstronomyBaseband::~RadioAstronomyBaseband()
{
m_inputMessageQueue.clear();
delete m_channelizer;
}
void RadioAstronomyBaseband::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
m_sampleFifo.reset();
}
void RadioAstronomyBaseband::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
QObject::connect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&RadioAstronomyBaseband::handleData,
Qt::QueuedConnection
);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
}
void RadioAstronomyBaseband::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
QObject::disconnect(
&m_sampleFifo,
&SampleSinkFifo::dataReady,
this,
&RadioAstronomyBaseband::handleData
);
m_running = false;
}
void RadioAstronomyBaseband::setChannel(ChannelAPI *channel)
{
m_sink.setChannel(channel);
}
void RadioAstronomyBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
m_sampleFifo.write(begin, end);
}
void RadioAstronomyBaseband::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 RadioAstronomyBaseband::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool RadioAstronomyBaseband::handleMessage(const Message& cmd)
{
if (MsgConfigureRadioAstronomyBaseband::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureRadioAstronomyBaseband& cfg = (MsgConfigureRadioAstronomyBaseband&) cmd;
qDebug() << "RadioAstronomyBaseband::handleMessage: MsgConfigureRadioAstronomyBaseband";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (DSPSignalNotification::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
qDebug() << "RadioAstronomyBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
return true;
}
else if (RadioAstronomy::MsgStartMeasurements::match(cmd))
{
m_sink.startMeasurements();
return true;
}
else if (RadioAstronomy::MsgStopMeasurements::match(cmd))
{
m_sink.stopMeasurements();
return true;
}
else if (RadioAstronomy::MsgStartCal::match(cmd))
{
RadioAstronomy::MsgStartCal& cal = (RadioAstronomy::MsgStartCal&)cmd;
m_sink.startCal(cal.getHot());
return true;
}
else
{
return false;
}
}
void RadioAstronomyBaseband::applySettings(const RadioAstronomySettings& settings, bool force)
{
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset)
|| (settings.m_sampleRate != m_settings.m_sampleRate)
|| force)
{
m_channelizer->setChannelization(settings.m_sampleRate, settings.m_inputFrequencyOffset);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}
m_sink.applySettings(settings, force);
m_settings = settings;
}
void RadioAstronomyBaseband::setBasebandSampleRate(int sampleRate)
{
m_channelizer->setBasebandSampleRate(sampleRate);
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
}

View File

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

View File

@ -0,0 +1,48 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radioastronomycalibrationdialog.h"
RadioAstronomyCalibrationDialog::RadioAstronomyCalibrationDialog(RadioAstronomySettings *settings, QWidget* parent) :
QDialog(parent),
m_settings(settings),
ui(new Ui::RadioAstronomyCalibrationDialog)
{
ui->setupUi(this);
ui->gpioEnabled->setChecked(settings->m_gpioEnabled);
ui->gpioPin->setValue(settings->m_gpioPin);
ui->gpioSense->setCurrentIndex(settings->m_gpioSense);
ui->startCalCommand->setText(settings->m_startCalCommand);
ui->stopCalCommand->setText(settings->m_stopCalCommand);
ui->calCommandDelay->setValue(settings->m_calCommandDelay);
}
RadioAstronomyCalibrationDialog::~RadioAstronomyCalibrationDialog()
{
delete ui;
}
void RadioAstronomyCalibrationDialog::accept()
{
m_settings->m_gpioEnabled = ui->gpioEnabled->isChecked();
m_settings->m_gpioPin = ui->gpioPin->value();
m_settings->m_gpioSense = ui->gpioSense->currentIndex();
m_settings->m_startCalCommand = ui->stopCalCommand->text().trimmed();
m_settings->m_stopCalCommand = ui->stopCalCommand->text().trimmed();
m_settings->m_calCommandDelay = ui->calCommandDelay->value();
QDialog::accept();
}

View File

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMYCALIBRATIONDIALOG_H
#define INCLUDE_RADIOASTRONOMYCALIBRATIONDIALOG_H
#include "ui_radioastronomycalibrationdialog.h"
#include "radioastronomysettings.h"
class RadioAstronomyCalibrationDialog : public QDialog {
Q_OBJECT
public:
explicit RadioAstronomyCalibrationDialog(RadioAstronomySettings *settings, QWidget* parent = 0);
~RadioAstronomyCalibrationDialog();
RadioAstronomySettings *m_settings;
private slots:
void accept();
private:
Ui::RadioAstronomyCalibrationDialog* ui;
};
#endif // INCLUDE_RADIOASTRONOMYCALIBRATIONDIALOG_H

View File

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RadioAstronomyCalibrationDialog</class>
<widget class="QDialog" name="RadioAstronomyCalibrationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>514</width>
<height>345</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Calibration Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="gpioGroup">
<property name="title">
<string>SDR Device GPIO</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="1" column="0">
<widget class="QLabel" name="gpioEnabledLabel">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="gpioEnabled">
<property name="toolTip">
<string>Check to enable setting a GPIO pin in SDR to enable calibration</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="gpioPinLabel">
<property name="text">
<string>Pin</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="gpioPin">
<property name="toolTip">
<string>SDR GPIO pin to set to start/stop calibration</string>
</property>
<property name="maximum">
<number>3</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="gpioSenseLabel">
<property name="text">
<string>Sense</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="gpioSense">
<property name="toolTip">
<string>Whether pin should be set to 1 or 0 to enable calibration</string>
</property>
<property name="currentIndex">
<number>1</number>
</property>
<item>
<property name="text">
<string>Calibrate=0</string>
</property>
</item>
<item>
<property name="text">
<string>Calibrate=1</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="commandGroup">
<property name="title">
<string>Commands</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QLabel" name="stopCalCommandLabel">
<property name="text">
<string>Stop calibration</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="startCalCommand">
<property name="toolTip">
<string>Program/script to execute to start calibration</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="startCalCommandLabel">
<property name="text">
<string>Start calibration</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="stopCalCommand">
<property name="toolTip">
<string>Program/script to execute to stop calibration</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="timingGroup">
<property name="title">
<string>Timing</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="calCommandDelayLabel">
<property name="text">
<string>Pre-calibration delay (seconds)</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="calCommandDelay">
<property name="toolTip">
<string>Delay in seconds after command/GPIO before calibration starts</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.000000000000000</double>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</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>gpioEnabled</tabstop>
<tabstop>gpioPin</tabstop>
<tabstop>gpioSense</tabstop>
<tabstop>startCalCommand</tabstop>
<tabstop>stopCalCommand</tabstop>
<tabstop>calCommandDelay</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RadioAstronomyCalibrationDialog</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>RadioAstronomyCalibrationDialog</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>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,673 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMYGUI_H
#define INCLUDE_RADIOASTRONOMYGUI_H
#include <QTableWidgetItem>
#include <QPushButton>
#include <QToolButton>
#include <QHBoxLayout>
#include <QMenu>
#include <QtCharts>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDebug>
#include "channel/channelgui.h"
#include "dsp/channelmarker.h"
#include "util/messagequeue.h"
#include "util/httpdownloadmanager.h"
#include "pipes/pipeendpoint.h"
#include "radioastronomysettings.h"
#include "radioastronomy.h"
class PluginAPI;
class DeviceUISet;
class BasebandSampleSink;
class RadioAstronomy;
class RadioAstronomyGUI;
namespace Ui {
class RadioAstronomyGUI;
}
class RadioAstronomyGUI;
using namespace QtCharts;
class RadioAstronomyGUI : public ChannelGUI {
Q_OBJECT
struct FFTMeasurement {
QDateTime m_dateTime;
qint64 m_centerFrequency;
int m_sampleRate;
int m_integration;
int m_rfBandwidth;
int m_fftSize;
Real* m_fftData;
Real* m_db; // dBFS
Real* m_snr; // SNR (noise is cold cal data)
Real* m_temp; // Temp in Kelvin base on hot/cold cal data
Real m_totalPower; // Total power based on sum of fftData (i.e unknown units)
Real m_totalPowerdBFS; // m_totalPower in dB
Real m_totalPowerdBm; // Total power in dBm
Real m_totalPowerWatts; // Total power in Watts
Real m_tSys; // Total temp in K
Real m_tSys0; // Total unwanted noise (E.g. Trx+Tgal..) in K
Real m_tSource; // Estimated source temp (tSys-tSys0) in K
Real m_flux; // Average spectral flux density of source in W m^-2 Hz^-1
Real m_sigmaT; // Temperature variation
Real m_sigmaS; // Flux variation
Real m_tempMin; // Minimum value in m_temp array
RadioAstronomySettings::SpectrumBaseline m_baseline;
float m_omegaA;
float m_omegaS;
bool m_coordsValid; //!< Whether follow variables are valid
float m_ra;
float m_dec;
float m_azimuth;
float m_elevation;
float m_l;
float m_b;
float m_vBCRS;
float m_vLSR;
float m_solarFlux;
float m_airTemp;
float m_skyTemp;
float m_sensor[RADIOASTRONOMY_SENSORS];
int m_sweepIndex;
FFTMeasurement() :
m_fftSize(0),
m_fftData(nullptr),
m_db(nullptr),
m_snr(nullptr),
m_temp(nullptr),
m_totalPower(0.0f),
m_totalPowerdBFS(0.0f),
m_totalPowerdBm(0.0f),
m_totalPowerWatts(0.0f),
m_tSys(0.0f),
m_tSys0(0.0f),
m_tSource(0.0f),
m_flux(0.0f),
m_sigmaT(0.0f),
m_sigmaS(0.0f),
m_tempMin(0.0f),
m_baseline(RadioAstronomySettings::SBL_TSYS0),
m_coordsValid(false),
m_airTemp(0.0),
m_skyTemp(0.0),
m_sweepIndex(0)
{
}
~FFTMeasurement()
{
delete[] m_fftData;
delete[] m_db;
delete[] m_snr;
delete[] m_temp;
}
};
struct LABData {
float m_l;
float m_b;
QList<Real> m_vlsr;
QList<Real> m_temp;
LABData() :
m_l(0.0f),
m_b(0.0f)
{
}
void read(QFile* file, float l, float b);
void toSeries(QLineSeries *series);
};
struct SensorMeasurement {
QDateTime m_dateTime;
double m_value;
SensorMeasurement(QDateTime dateTime, double value) :
m_dateTime(dateTime),
m_value(value)
{
}
};
class SensorMeasurements {
QLineSeries* m_series;
QValueAxis* m_yAxis;
double m_max;
double m_min;
QList<SensorMeasurement *> m_measurements;
public:
SensorMeasurements() :
m_series(nullptr),
m_yAxis(nullptr)
{
}
void init(const QString& name, bool visible);
void setName(const QString& name);
void clicked(bool checked);
void append(SensorMeasurement *measurement);
void addToSeries(SensorMeasurement *measurement);
void addAllToSeries();
void clear();
void addToChart(QChart* chart, QDateTimeAxis* xAxis);
void setPen(const QPen& pen);
QValueAxis* yAxis() const;
qreal lastValue();
};
public:
static RadioAstronomyGUI* 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::RadioAstronomyGUI* ui;
PluginAPI* m_pluginAPI;
DeviceUISet* m_deviceUISet;
ChannelMarker m_channelMarker;
RadioAstronomySettings m_settings;
bool m_doApplySettings;
QList<PipeEndPoint::AvailablePipeSource> m_availablePipes;
int m_basebandSampleRate;
quint64 m_centerFrequency;
RadioAstronomy* m_radioAstronomy;
uint32_t m_tickCount;
MessageQueue m_inputMessageQueue;
QMenu *powerTableMenu; // Column select context menu
QMenu *copyMenu;
QChart *m_powerChart;
QLineSeries *m_powerSeries;
QDateTimeAxis *m_powerXAxis;
bool m_powerXAxisSameDay;
QValueAxis *m_powerYAxis;
QScatterSeries *m_powerPeakSeries;
QScatterSeries *m_powerMarkerSeries;
QLineSeries *m_powerTsys0Series;
QLineSeries *m_powerGaussianSeries;
double m_powerMin; // For axis autoscale
double m_powerMax;
bool m_powerPeakValid;
qreal m_powerMinX; // For min peak
qreal m_powerMinY;
qreal m_powerMaxX; // For max peak
qreal m_powerMaxY;
QChart *m_2DChart;
QValueAxis *m_2DXAxis;
QValueAxis *m_2DYAxis;
float *m_2DMapIntensity;
float m_2DMapMax;
float m_2DMapMin;
QImage m_2DMap;
int m_sweepIndex;
SensorMeasurements m_airTemps;
SensorMeasurements m_sensors[RADIOASTRONOMY_SENSORS];
QChart *m_calChart;
QValueAxis *m_calXAxis;
QValueAxis *m_calYAxis;
QLineSeries *m_calHotSeries;
QLineSeries *m_calColdSeries;
FFTMeasurement* m_calHot;
FFTMeasurement* m_calCold;
double *m_calG;
QChart *m_fftChart;
QLineSeries *m_fftSeries;
QLineSeries *m_fftHlineSeries;
QScatterSeries *m_fftPeakSeries;
QScatterSeries *m_fftMarkerSeries;
QLineSeries *m_fftGaussianSeries;
QLineSeries *m_fftLABSeries;
QValueAxis *m_fftXAxis;
QValueAxis *m_fftYAxis;
QValueAxis *m_fftDopplerAxis;
QList<FFTMeasurement*> m_fftMeasurements;
// Markers
bool m_powerM1Valid;
bool m_powerM2Valid;
qreal m_powerM1X;
qreal m_powerM1Y;
qreal m_powerM2X;
qreal m_powerM2Y;
bool m_spectrumM1Valid;
bool m_spectrumM2Valid;
qreal m_spectrumM1X;
qreal m_spectrumM1Y;
qreal m_spectrumM2X;
qreal m_spectrumM2Y;
// Target received from Star Tracker
bool m_coordsValid;
float m_ra;
float m_dec;
float m_azimuth;
float m_elevation;
float m_l;
float m_b;
float m_vBCRS;
float m_vLSR;
float m_solarFlux;
float m_skyTemp;
float m_beamWidth;
float m_lLAB; // Galactic coords for current LAB data
float m_bLAB;
QString m_filenameLAB;
bool m_downloadingLAB;
QList<LABData *> m_dataLAB;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
HttpDownloadManager m_dlm;
explicit RadioAstronomyGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~RadioAstronomyGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void displayStreamIndex();
void displaySpectrumLineFrequency();
void displayRunModeSettings();
void updatePipeList();
void updateRotatorList();
bool handleMessage(const Message& message);
double degreesToSteradian(double deg) const;
double hpbwToSteradians(double hpbw) const;
double calcOmegaA() const;
double calcOmegaS() const;
double beamFillingFactor() const;
void updateOmegaA();
void powerMeasurementReceived(FFTMeasurement *fft, bool skipCalcs);
void calCompletetReceived(const RadioAstronomy::MsgCalComplete& measurement);
void calcFFTPower(FFTMeasurement* fft);
void calcFFTTotalPower(FFTMeasurement* fft);
void calcFFTTemperatures(FFTMeasurement* fft);
void calcFFTTotalTemperature(FFTMeasurement* fft);
void calcFFTMinTemperature(FFTMeasurement* fft);
void addFFT(FFTMeasurement *fft, bool skipCalcs=false);
void fftMeasurementReceived(const RadioAstronomy::MsgFFTMeasurement& measurement);
void addToPowerSeries(FFTMeasurement *fft, bool skipCalcs=false);
void plotPowerGaussian();
void plotSpectrum();
void plotCalSpectrum();
void plotCalMeasurements();
void plotFFTMeasurement(int index);
void plotFFTMeasurement();
void plotTempGaussian(double startFreq, double freqStep, int steps);
void plotRefLine(FFTMeasurement *fft);
void plotLAB();
void plotLAB(float l, float b, float beamWidth);
LABData* parseLAB(QFile* file, float l, float b);
int fftSizeToIndex(int size);
double dopplerToVelocity(double centre, double f, FFTMeasurement *fft);
QHash<QString,int> csvHeadersToHash(QStringList cols);
QString csvData(QHash<QString,int> hash, QStringList cols, QString col);
bool hasNeededFFTData(QHash<QString,int> hash);
void saveFFT(QTextStream& out, const FFTMeasurement* fft);
FFTMeasurement* loadFFT(QHash<QString,int> hash, QStringList cols);
void clearData();
void clearCalData();
bool deleteRow(int row);
void deleteRowsComplete(bool deletedCurrent, int next);
void calcCalibrationScaleFactors();
void calibrate();
void recalibrate();
void calcGalacticBackgroundTemp();
void calcAtmosphericTemp();
void calcCalAvgDiff();
void calcCalTrx();
void calcCalTsp();
void calcAverages();
void calcFWHM();
void calcHPBWFromFWHM();
void calcFHWMFromHPBW();
void calcColumnDensity();
qreal calcSeriesFloor(QXYSeries *series, int percent=10);
void calcVrAndDistanceToPeak(double freq, FFTMeasurement *fft, int row);
int calcDistanceToPeak(double vr, float l, float b, double& r, double &d1, double &d2);
void calcDistances();
void clearLoSMarker(const QString& name);
void updateLoSMarker(const QString& name, float l, float b, float d);
bool losMarkerEnabled(const QString& name);
void showLoSMarker(const QString& name);
void showLoSMarker(int row);
void sensorMeasurementReceived(const RadioAstronomy::MsgSensorMeasurement& measurement);
void updateSpectrumMarkerTableVisibility();
void updatePowerMarkerTableVisibility();
void updatePowerChartWidgetsVisibility();
void updateSpectrumChartWidgetsVisibility();
void updateSpectrumSelect();
void updatePowerSelect();
void spectrumAutoscale();
void powerAutoscale();
void powerAutoscaleY(bool adjustAxis);
void calcSpectrumMarkerDelta();
void calcPowerMarkerDelta();
void calcPowerPeakDelta();
QRgb intensityToColor(float intensity);
void set2DAxisTitles();
void update2DSettingsFromSweep();
void create2DImage();
void update2DImage(FFTMeasurement* fft, bool skipCalcs);
void recolour2DImage();
void power2DAutoscale();
void powerColourAutoscale();
void updatePowerColourScaleStep();
void updateSpectrumMinMax(qreal x, qreal y);
RadioAstronomyGUI::FFTMeasurement* currentFFT();
void spectrumUpdateXRange(FFTMeasurement* fft = nullptr);
void spectrumUpdateYRange(FFTMeasurement* fft = nullptr);
void updateDistanceColumns();
void updateBWLimits();
void updateIntegrationTime();
void updateTSys0();
double calcTSys0() const;
double calcTau() const;
double calcTau(const FFTMeasurement* fft) const;
double calcSigmaT(double tSys) const;
double calcSigmaS(double tSys) const;
double calcSigmaT(const FFTMeasurement* fft) const;
double calcSigmaS(const FFTMeasurement* fft) const;
double calcFlux(double Ta, const FFTMeasurement *fft) const;
double calcTSource(FFTMeasurement *fft) const;
void updatePowerColumns(int row, FFTMeasurement* fft);
void calcPowerChartTickCount(int width);
void calcSpectrumChartTickCount(QValueAxis *axis, int width);
int powerYUnitsToIndex(RadioAstronomySettings::PowerYUnits units);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
void resizePowerTable();
void resizePowerMarkerTable();
void resizeSpectrumMarkerTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
enum PowerTableCol {
POWER_COL_DATE,
POWER_COL_TIME,
POWER_COL_POWER,
POWER_COL_POWER_DB,
POWER_COL_POWER_DBM,
POWER_COL_TSYS,
POWER_COL_TSYS0,
POWER_COL_TSOURCE,
POWER_COL_TB,
POWER_COL_TSKY,
POWER_COL_FLUX,
POWER_COL_SIGMA_T,
POWER_COL_SIGMA_S,
POWER_COL_OMEGA_A,
POWER_COL_OMEGA_S,
POWER_COL_RA,
POWER_COL_DEC,
POWER_COL_GAL_LON,
POWER_COL_GAL_LAT,
POWER_COL_AZ,
POWER_COL_EL,
POWER_COL_VBCRS,
POWER_COL_VLSR,
POWER_COL_SOLAR_FLUX,
POWER_COL_AIR_TEMP,
POWER_COL_SENSOR_1,
POWER_COL_SENSOR_2
};
enum PowerMarkerTable {
POWER_MARKER_COL_NAME,
POWER_MARKER_COL_DATE,
POWER_MARKER_COL_TIME,
POWER_MARKER_COL_VALUE,
POWER_MARKER_COL_DELTA_X,
POWER_MARKER_COL_DELTA_Y,
POWER_MARKER_COL_DELTA_TO
};
enum PowerMarkerRow {
POWER_MARKER_ROW_PEAK_MAX,
POWER_MARKER_ROW_PEAK_MIN,
POWER_MARKER_ROW_M1,
POWER_MARKER_ROW_M2,
POWER_MARKER_ROWS
};
enum SpectrumMarkerTable {
SPECTRUM_MARKER_COL_NAME,
SPECTRUM_MARKER_COL_FREQ,
SPECTRUM_MARKER_COL_VALUE,
SPECTRUM_MARKER_COL_DELTA_X,
SPECTRUM_MARKER_COL_DELTA_Y,
SPECTRUM_MARKER_COL_DELTA_TO,
SPECTRUM_MARKER_COL_VR,
SPECTRUM_MARKER_COL_R,
SPECTRUM_MARKER_COL_D,
SPECTRUM_MARKER_COL_PLOT_MAX,
SPECTRUM_MARKER_COL_R_MIN,
SPECTRUM_MARKER_COL_V
};
enum SpecrumMarkerRow {
SPECTRUM_MARKER_ROW_PEAK,
SPECTRUM_MARKER_ROW_M1,
SPECTRUM_MARKER_ROW_M2,
SPECTRUM_MARKER_ROWS
};
protected:
void resizeEvent(QResizeEvent* size);
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_sampleRate_changed(qint64 index);
void on_rfBW_changed(qint64 index);
void on_integration_changed(qint64 value);
void on_fftSize_currentIndexChanged(int index);
void on_fftWindow_currentIndexChanged(int index);
void on_filterFreqs_editingFinished();
void on_starTracker_currentTextChanged(const QString& text);
void on_rotator_currentTextChanged(const QString& text);
void on_showSensors_clicked();
void on_tempRXSelect_currentIndexChanged(int value);
void on_tempRX_valueChanged(double value);
void on_tempCMB_valueChanged(double value);
void on_tempGal_valueChanged(double value);
void on_tempSP_valueChanged(double value);
void on_tempAtm_valueChanged(double value);
void on_tempAir_valueChanged(double value);
void on_zenithOpacity_valueChanged(double value);
void on_elevation_valueChanged(double value);
void on_tempAtmLink_toggled(bool checked);
void on_tempAirLink_toggled(bool checked);
void on_tempGalLink_toggled(bool checked);
void on_elevationLink_toggled(bool checked);
void on_gainVariation_valueChanged(double value);
void on_omegaAUnits_currentIndexChanged(int index);
void on_sourceType_currentIndexChanged(int index);
void on_omegaS_valueChanged(double value);
void on_omegaSUnits_currentIndexChanged(int index);
void on_spectrumChartSelect_currentIndexChanged(int index);
void on_showCalSettings_clicked();
void on_startCalHot_clicked();
void on_startCalCold_clicked();
void on_recalibrate_toggled(bool checked=false);
void on_spectrumShowLegend_toggled(bool checked);
void on_spectrumShowRefLine_toggled(bool checked);
void on_spectrumTemp_toggled(bool checked);
void on_spectrumMarker_toggled(bool checked);
void on_spectrumPeak_toggled(bool checked);
void on_spectrumReverseXAxis_toggled(bool checked);
void on_savePowerData_clicked();
void on_savePowerChartImage_clicked();
void on_saveSpectrumData_clicked();
void on_loadSpectrumData_clicked();
void on_saveSpectrumChartImage_clicked();
void on_saveSpectrumChartImages_clicked();
void on_clearData_clicked();
void on_clearCal_clicked();
void spectrumSeries_clicked(const QPointF &point);
void on_spectrumAutoscale_toggled(bool checked=false);
void on_spectrumAutoscaleX_clicked();
void on_spectrumAutoscaleY_clicked();
void on_spectrumReference_valueChanged(double value);
void on_spectrumRange_valueChanged(double value);
void on_spectrumCenterFreq_valueChanged(double value);
void on_spectrumSpan_valueChanged(double value);
void on_spectrumYUnits_currentIndexChanged(int index);
void on_spectrumBaseline_currentIndexChanged(int index);
void on_spectrumIndex_valueChanged(int value);
void on_spectrumLine_currentIndexChanged(int value);
void on_spectrumLineFrequency_valueChanged(double value);
void on_refFrame_currentIndexChanged(int value);
void on_sunDistanceToGC_valueChanged(double value);
void on_sunOrbitalVelocity_valueChanged(double value);
void spectrumMarkerTableItemChanged(QTableWidgetItem *item);
void on_spectrumGaussianFreq_valueChanged(double value);
void on_spectrumGaussianAmp_valueChanged(double value);
void on_spectrumGaussianFloor_valueChanged(double value);
void on_spectrumGaussianFWHM_valueChanged(double value);
void on_spectrumGaussianTurb_valueChanged(double value);
void on_spectrumTemperature_valueChanged(double t);
void on_spectrumShowLAB_toggled(bool checked=false);
void on_spectrumShowDistance_toggled(bool checked=false);
void on_tCalHotSelect_currentIndexChanged(int index);
void on_tCalHot_valueChanged(double value);
void on_tCalColdSelect_currentIndexChanged(int index);
void on_tCalCold_valueChanged(double value);
void on_powerChartSelect_currentIndexChanged(int index);
void on_powerYUnits_currentIndexChanged(int index);
void on_powerShowMarker_toggled(bool checked);
void on_powerShowTsys0_toggled(bool checked);
void on_powerShowAirTemp_toggled(bool checked);
void on_powerShowSensor1_toggled(bool checked);
void on_powerShowSensor2_toggled(bool checked);
void on_powerShowPeak_toggled(bool checked);
void on_powerShowAvg_toggled(bool checked);
void on_powerShowLegend_toggled(bool checked);
void powerSeries_clicked(const QPointF &point);
void on_powerAutoscale_toggled(bool checked);
void on_powerAutoscaleY_clicked();
void on_powerAutoscaleX_clicked();
void on_powerReference_valueChanged(double value);
void on_powerRange_valueChanged(double value);
void on_powerStartTime_dateTimeChanged(QDateTime value);
void on_powerEndTime_dateTimeChanged(QDateTime value);
void on_powerShowGaussian_clicked(bool checked=false);
void on_powerGaussianCenter_dateTimeChanged(QDateTime dateTime);
void on_powerGaussianAmp_valueChanged(double value);
void on_powerGaussianFloor_valueChanged(double value);
void on_powerGaussianFWHM_valueChanged(double value);
void on_powerGaussianHPBW_valueChanged(double value);
void on_runMode_currentIndexChanged(int index);
void on_sweepType_currentIndexChanged(int index);
void on_startStop_clicked(bool checked=false);
void on_sweepStartAtTime_currentIndexChanged(int index);
void on_sweepStartDateTime_dateTimeChanged(const QDateTime& dateTime);
void on_sweep1Start_valueChanged(double value);
void on_sweep1Stop_valueChanged(double value);
void on_sweep1Step_valueChanged(double value);
void on_sweep1Delay_valueChanged(double value);
void on_sweep2Start_valueChanged(double value);
void on_sweep2Stop_valueChanged(double value);
void on_sweep2Step_valueChanged(double value);
void on_sweep2Delay_valueChanged(double value);
void on_power2DLinkSweep_toggled(bool checked=false);
void on_power2DAutoscale_clicked();
void on_power2DSweepType_currentIndexChanged(int index);
void on_power2DWidth_valueChanged(int value);
void on_power2DHeight_valueChanged(int value);
void on_power2DXMin_valueChanged(double value);
void on_power2DXMax_valueChanged(double value);
void on_power2DYMin_valueChanged(double value);
void on_power2DYMax_valueChanged(double value);
void on_powerColourAutoscale_toggled(bool checked=false);
void on_powerColourScaleMin_valueChanged(double value);
void on_powerColourScaleMax_valueChanged(double value);
void on_powerColourPalette_currentIndexChanged(int index);
void powerTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void powerTable_sectionResized(int logicalIndex, int oldSize, int newSize);
void powerTableColumnSelectMenu(QPoint pos);
void powerTableColumnSelectMenuChecked(bool checked = false);
void on_powerTable_cellDoubleClicked(int row, int column);
void plotPowerChart();
void plotPowerVsTimeChart();
void plot2DChart();
void plotAreaChanged(const QRectF& plotArea);
void customContextMenuRequested(QPoint point);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();
void tick();
void networkManagerFinished(QNetworkReply *reply);
void downloadFinished(const QString& filename, bool success);
};
#endif // INCLUDE_RADIOASTRONOMYGUI_H

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -0,0 +1,68 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "util/visa.h"
#include "radioastronomysensordialog.h"
RadioAstronomySensorDialog::RadioAstronomySensorDialog(RadioAstronomySettings *settings, QWidget* parent) :
QDialog(parent),
m_settings(settings),
ui(new Ui::RadioAstronomySensorDialog)
{
ui->setupUi(this);
ui->sensor1Enabled->setChecked(settings->m_sensorEnabled[0]);
ui->sensor1Name->setText(settings->m_sensorName[0]);
ui->sensor1Device->setText(settings->m_sensorDevice[0]);
ui->sensor1Init->setPlainText(settings->m_sensorInit[0]);
ui->sensor1Measure->setText(settings->m_sensorMeasure[0]);
ui->sensor2Enabled->setChecked(settings->m_sensorEnabled[1]);
ui->sensor2Name->setText(settings->m_sensorName[1]);
ui->sensor2Device->setText(settings->m_sensorDevice[1]);
ui->sensor2Init->setPlainText(settings->m_sensorInit[1]);
ui->sensor2Measure->setText(settings->m_sensorMeasure[1]);
ui->period->setValue(settings->m_sensorMeasurePeriod);
VISA visa;
if (!visa.isAvailable())
{
ui->sensor1Group->setEnabled(false);
ui->sensor2Group->setEnabled(false);
}
}
RadioAstronomySensorDialog::~RadioAstronomySensorDialog()
{
delete ui;
}
void RadioAstronomySensorDialog::accept()
{
m_settings->m_sensorEnabled[0] = ui->sensor1Enabled->isChecked();
m_settings->m_sensorName[0] = ui->sensor1Name->text().trimmed();
m_settings->m_sensorDevice[0] = ui->sensor1Device->text().trimmed();
m_settings->m_sensorInit[0] = ui->sensor1Init->toPlainText();
m_settings->m_sensorMeasure[0] = ui->sensor1Measure->text().trimmed();
m_settings->m_sensorEnabled[1] = ui->sensor2Enabled->isChecked();
m_settings->m_sensorName[1] = ui->sensor2Name->text().trimmed();
m_settings->m_sensorDevice[1] = ui->sensor2Device->text().trimmed();
m_settings->m_sensorInit[1] = ui->sensor2Init->toPlainText();
m_settings->m_sensorMeasure[1] = ui->sensor2Measure->text().trimmed();
m_settings->m_sensorMeasurePeriod = ui->period->value();
QDialog::accept();
}

View File

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMYSENSORDIALOG_H
#define INCLUDE_RADIOASTRONOMYSENSORDIALOG_H
#include "ui_radioastronomysensordialog.h"
#include "radioastronomysettings.h"
class RadioAstronomySensorDialog : public QDialog {
Q_OBJECT
public:
explicit RadioAstronomySensorDialog(RadioAstronomySettings *settings, QWidget* parent = 0);
~RadioAstronomySensorDialog();
RadioAstronomySettings *m_settings;
private slots:
void accept();
private:
Ui::RadioAstronomySensorDialog* ui;
};
#endif // INCLUDE_RADIOASTRONOMYSENSORDIALOG_H

View File

@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RadioAstronomySensorDialog</class>
<widget class="QDialog" name="RadioAstronomySensorDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>514</width>
<height>746</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Sensor Control Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="sensor1Group">
<property name="title">
<string>Sensor 1</string>
</property>
<layout class="QFormLayout" name="formLayout_4">
<item row="3" column="1">
<widget class="QLineEdit" name="sensor1Device"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="sensor1DeviceLabel">
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="sensor1InitLabel">
<property name="text">
<string>Init</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPlainTextEdit" name="sensor1Init"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="sensor1MeasureLabel">
<property name="text">
<string>Measure</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="sensor1Measure"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="sensor1NameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="sensor1Name"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sensor1EnabledLabel">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="sensor1Enabled">
<property name="toolTip">
<string>Check to enable measurements from this device</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="sensor2Group">
<property name="title">
<string>Sensor 2</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="sensor2DeviceLabel">
<property name="text">
<string>Device</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="sensor2InitLabel">
<property name="text">
<string>Init</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QPlainTextEdit" name="sensor2Init"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sensor2EnabledLabel">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="sensor3NameLabel">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="sensor2MeasureLabel">
<property name="text">
<string>Measure</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QLineEdit" name="sensor2Device"/>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="sensor2Name"/>
</item>
<item row="5" column="2">
<widget class="QLineEdit" name="sensor2Measure"/>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="sensor2Enabled">
<property name="toolTip">
<string>Check to enable measurements from this device</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="timingGroup">
<property name="title">
<string>Timing</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="periodLabel">
<property name="text">
<string>Measurement period (seconds)</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="period">
<property name="toolTip">
<string>Delay in seconds between measurements being made</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>0.001000000000000</double>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
</widget>
</item>
</layout>
</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>period</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RadioAstronomySensorDialog</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>RadioAstronomySensorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,491 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radioastronomysettings.h"
const QStringList RadioAstronomySettings::m_pipeTypes = {
QStringLiteral("StarTracker")
};
const QStringList RadioAstronomySettings::m_pipeURIs = {
QStringLiteral("sdrangel.feature.startracker")
};
RadioAstronomySettings::RadioAstronomySettings() :
m_channelMarker(0)
{
resetToDefaults();
}
void RadioAstronomySettings::resetToDefaults()
{
m_inputFrequencyOffset = 0;
m_sampleRate = 1000000;
m_rfBandwidth = 1000000;
m_integration = 4000;
m_fftSize = 256;
m_fftWindow = HAN;
m_filterFreqs = "";
m_starTracker = "";
m_rotator = "None";
m_tempRX = 75.0f;
m_tempCMB = 2.73;
m_tempGal = 2.0;
m_tempSP = 85.0;
m_tempAtm = 2.0;
m_tempAir = 15.0;
m_zenithOpacity = 0.0055;
m_elevation = 90.0;
m_tempGalLink = true;
m_tempAtmLink = true;
m_tempAirLink = true;
m_elevationLink = true;
m_gainVariation = 0.0011f;
m_sourceType = UNKNOWN;
m_omegaS = 0.0;
m_omegaSUnits = DEGREES;
m_omegaAUnits = DEGREES;
m_spectrumPeaks = false;
m_spectrumMarkers = false;
m_spectrumTemp = false;
m_spectrumReverseXAxis = false;
m_spectrumRefLine = false;
m_spectrumLAB = false;
m_spectrumDistance = false;
m_spectrumLegend = false;
m_spectrumReference = 0.0f;
m_spectrumRange = 120.0f;
m_spectrumCenterFreqOffset = 0.0f;
m_spectrumAutoscale = true;
m_spectrumSpan = 1.0f;
m_spectrumYScale = SY_DBFS;
m_spectrumBaseline = SBL_TSYS0;
m_recalibrate = false;
m_tCalHot = 300.0f;
m_tCalCold = 10.0f;
m_line = HI;
m_lineCustomFrequency = 0.0f;
m_refFrame = LSR;
m_powerAutoscale = true;
m_powerReference = 0.0f;
m_powerRange = 100.0f;
m_powerPeaks = false;
m_powerMarkers = false;
m_powerAvg = false;
m_powerLegend = false;
m_powerYData = PY_POWER;
m_powerYUnits = PY_DBFS;
m_powerShowTsys0 = false;
m_powerShowAirTemp = false;
m_powerShowGaussian = false;
m_power2DLinkSweep = true;
m_power2DSweepType = SWP_OFFSET;
m_power2DHeight = 3;
m_power2DWidth = 3;
m_power2DXMin = 0;
m_power2DXMax = 10;
m_power2DYMin = 0;
m_power2DYMax = 10;
m_powerColourAutoscale = true;
m_powerColourScaleMin = 0.0f;
m_powerColourScaleMax = 0.0f;
m_powerColourPalette = "Colour";
m_sensorName[0] = "Temperature";
m_sensorDevice[0] = "";
m_sensorInit[0] = "UNIT:TEMP C";
m_sensorMeasure[0] = "MEAS:TEMP?";
m_sensorEnabled[0] = false;
m_sensorVisible[0] = false;
m_sensorName[1] = "Voltage";
m_sensorDevice[1] = "";
m_sensorInit[1] = "";
m_sensorMeasure[1] = "MEAS:VOLT:DC?";
m_sensorEnabled[1] = false;
m_sensorVisible[1] = false;
m_sensorMeasurePeriod = 1.0f;
m_sunDistanceToGC = 8.1f;
m_sunOrbitalVelocity = 248.0f;
m_runMode = CONTINUOUS;
m_sweepStartAtTime = false;
m_sweepStartDateTime = QDateTime::currentDateTime();
m_sweepType = SWP_OFFSET;
m_sweep1Start = -5.0;
m_sweep1Stop = 5.0;
m_sweep1Step = 5.0;
m_sweep1Delay = 0.0;
m_sweep2Start = -5.0;
m_sweep2Stop = 5.0;
m_sweep2Step = 5.0;
m_sweep2Delay = 0.0;
m_gpioEnabled = false;
m_gpioPin = 0;
m_gpioSense = 1;
m_startCalCommand = "";
m_stopCalCommand = "";
m_calCommandDelay = 1.0f;
m_rgbColor = QColor(102, 0, 0).rgb();
m_title = "Radio Astronomy";
m_streamIndex = 0;
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIDeviceIndex = 0;
m_reverseAPIChannelIndex = 0;
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++)
{
m_powerTableColumnIndexes[i] = i;
m_powerTableColumnSizes[i] = -1; // Autosize
}
}
QByteArray RadioAstronomySettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_inputFrequencyOffset);
s.writeS32(2, m_sampleRate);
s.writeS32(3, m_rfBandwidth);
s.writeS32(4, m_integration);
s.writeS32(5, m_fftSize);
s.writeS32(6, (int)m_fftWindow);
s.writeString(7, m_filterFreqs);
s.writeString(10, m_starTracker);
s.writeString(11, m_rotator);
s.writeFloat(20, m_tempRX);
s.writeFloat(21, m_tempCMB);
s.writeFloat(22, m_tempGal);
s.writeFloat(23, m_tempSP);
s.writeFloat(24, m_tempAtm);
s.writeFloat(25, m_tempAir);
s.writeFloat(26, m_zenithOpacity);
s.writeFloat(27, m_elevation);
s.writeBool(28, m_tempGalLink);
s.writeBool(29, m_tempAtmLink);
s.writeBool(30, m_tempAirLink);
s.writeBool(31, m_elevationLink);
s.writeFloat(40, m_gainVariation);
s.writeS32(41, (int)m_sourceType);
s.writeFloat(42, m_omegaS);
s.writeS32(43, (int)m_omegaSUnits);
s.writeS32(44, (int)m_omegaAUnits);
s.writeBool(50, m_spectrumPeaks);
s.writeBool(51, m_spectrumMarkers);
s.writeBool(52, m_spectrumTemp);
s.writeBool(53, m_spectrumReverseXAxis);
s.writeBool(54, m_spectrumRefLine);
s.writeBool(55, m_spectrumLegend);
s.writeBool(56, m_spectrumDistance);
s.writeBool(57, m_spectrumLAB);
s.writeFloat(60, m_spectrumReference);
s.writeFloat(61, m_spectrumRange);
s.writeFloat(62, m_spectrumSpan);
s.writeFloat(63, m_spectrumCenterFreqOffset);
s.writeBool(64, m_spectrumAutoscale);
s.writeS32(65, (int)m_spectrumYScale);
s.writeS32(66, (int)m_spectrumBaseline);
s.writeBool(70, m_recalibrate);
s.writeFloat(71, m_tCalHot);
s.writeFloat(72, m_tCalCold);
s.writeS32(73, (int)m_line);
s.writeFloat(74, m_lineCustomFrequency);
s.writeS32(75, (int)m_refFrame);
s.writeFloat(76, m_sunDistanceToGC);
s.writeFloat(77, m_sunOrbitalVelocity);
s.writeBool(80, m_powerPeaks);
s.writeBool(81, m_powerMarkers);
s.writeBool(82, m_powerAvg);
s.writeBool(83, m_powerLegend);
s.writeBool(84, m_powerShowTsys0);
s.writeBool(85, m_powerShowAirTemp);
s.writeBool(86, m_powerShowGaussian);
s.writeFloat(87, m_powerReference);
s.writeFloat(88, m_powerRange);
s.writeBool(89, m_powerAutoscale);
s.writeS32(90, (int)m_powerYData);
s.writeS32(91, (int)m_powerYUnits);
s.writeBool(100, m_power2DLinkSweep);
s.writeS32(102, (int)m_power2DSweepType);
s.writeS32(103, m_power2DWidth);
s.writeS32(104, m_power2DHeight);
s.writeFloat(105, m_power2DXMin);
s.writeFloat(106, m_power2DXMax);
s.writeFloat(107, m_power2DYMin);
s.writeFloat(108, m_power2DYMax);
s.writeBool(109, m_powerColourAutoscale);
s.writeFloat(110, m_powerColourScaleMin);
s.writeFloat(111, m_powerColourScaleMax);
s.writeString(112, m_powerColourPalette);
s.writeS32(120, m_runMode);
s.writeBool(121, m_sweepStartAtTime);
s.writeS64(122, m_sweepStartDateTime.toMSecsSinceEpoch());
s.writeS32(123, (int)m_sweepType);
s.writeFloat(124, m_sweep1Start);
s.writeFloat(125, m_sweep1Stop);
s.writeFloat(126, m_sweep1Step);
s.writeFloat(127, m_sweep1Delay);
s.writeFloat(128, m_sweep2Start);
s.writeFloat(129, m_sweep2Stop);
s.writeFloat(130, m_sweep2Step);
s.writeFloat(131, m_sweep2Delay);
s.writeString(140, m_sensorName[0]);
s.writeString(141, m_sensorDevice[0]);
s.writeString(142, m_sensorInit[0]);
s.writeString(143, m_sensorMeasure[0]);
s.writeBool(144, m_sensorEnabled[0]);
s.writeBool(145, m_sensorVisible[0]);
s.writeString(146, m_sensorName[1]);
s.writeString(147, m_sensorDevice[1]);
s.writeString(148, m_sensorInit[1]);
s.writeString(149, m_sensorMeasure[1]);
s.writeBool(150, m_sensorEnabled[1]);
s.writeBool(151, m_sensorVisible[1]);
s.writeFloat(152, m_sensorMeasurePeriod);
s.writeBool(160, m_gpioEnabled);
s.writeS32(161, m_gpioPin);
s.writeS32(162, m_gpioSense);
s.writeString(167, m_startCalCommand);
s.writeString(168, m_stopCalCommand);
s.writeFloat(169, m_calCommandDelay);
s.writeU32(180, m_rgbColor);
s.writeString(181, m_title);
if (m_channelMarker) {
s.writeBlob(182, m_channelMarker->serialize());
}
s.writeS32(183, m_streamIndex);
s.writeBool(184, m_useReverseAPI);
s.writeString(185, m_reverseAPIAddress);
s.writeU32(186, m_reverseAPIPort);
s.writeU32(187, m_reverseAPIDeviceIndex);
s.writeU32(188, m_reverseAPIChannelIndex);
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) {
s.writeS32(400 + i, m_powerTableColumnIndexes[i]);
}
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) {
s.writeS32(500 + i, m_powerTableColumnSizes[i]);
}
return s.final();
}
bool RadioAstronomySettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if(!d.isValid())
{
resetToDefaults();
return false;
}
if(d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
qint64 dttmp;
d.readS32(1, &m_inputFrequencyOffset, 0);
d.readS32(2, &m_sampleRate, 1000000);
d.readS32(3, &m_rfBandwidth, 1000000);
d.readS32(4, &m_integration, 4000);
d.readS32(5, &m_fftSize, 256);
d.readS32(6, (int*)&m_fftWindow, (int)HAN);
d.readString(7, &m_filterFreqs, "");
d.readString(10, &m_starTracker, "");
d.readString(11, &m_rotator, "None");
d.readFloat(20, &m_tempRX, 75.0f);
d.readFloat(21, &m_tempCMB, 2.73f);
d.readFloat(22, &m_tempGal, 2.0f);
d.readFloat(23, &m_tempSP, 85.0f);
d.readFloat(24, &m_tempAtm, 2.0f);
d.readFloat(25, &m_tempAir, 15.0f);
d.readFloat(26, &m_zenithOpacity, 0.0055f);
d.readFloat(27, &m_elevation, 90.0f);
d.readBool(28, &m_tempGalLink, true);
d.readBool(29, &m_tempAtmLink, true);
d.readBool(30, &m_tempAirLink, true);
d.readBool(31, &m_elevationLink, true);
d.readFloat(40, &m_gainVariation, 0.0011f);
d.readS32(41, (int*)&m_sourceType, UNKNOWN);
d.readFloat(42, &m_omegaS, 0.0f);
d.readS32(43, (int*)&m_omegaSUnits, DEGREES);
d.readS32(44, (int*)&m_omegaAUnits, DEGREES);
d.readBool(50, &m_spectrumPeaks, false);
d.readBool(51, &m_spectrumMarkers, false);
d.readBool(52, &m_spectrumTemp, false);
d.readBool(53, &m_spectrumReverseXAxis, false);
d.readBool(54, &m_spectrumRefLine, false);
d.readBool(55, &m_spectrumLegend, false);
d.readBool(56, &m_spectrumDistance, false);
d.readBool(57, &m_spectrumLAB, false);
d.readFloat(60, &m_spectrumReference, 0.0f);
d.readFloat(61, &m_spectrumRange, 120.0f);
d.readFloat(62, &m_spectrumSpan, 1.0f);
d.readFloat(63, &m_spectrumCenterFreqOffset, 0.0f);
d.readBool(64, &m_spectrumAutoscale, false);
d.readS32(65, (int*)&m_spectrumYScale, SY_DBFS);
d.readS32(66, (int*)&m_spectrumBaseline, SBL_TSYS0);
d.readBool(70, &m_recalibrate, false);
d.readFloat(71, &m_tCalHot, 300.0f);
d.readFloat(72, &m_tCalCold, 10.0f);
d.readS32(73, (int*)&m_line, (int)HI);
d.readFloat(74, &m_lineCustomFrequency, 0.0f);
d.readS32(75, (int*)&m_refFrame, LSR);
d.readFloat(76, &m_sunDistanceToGC, 8.1f);
d.readFloat(77, &m_sunOrbitalVelocity, 248.0f);
d.readBool(80, &m_powerPeaks, false);
d.readBool(81, &m_powerMarkers, false);
d.readBool(82, &m_powerAvg, false);
d.readBool(83, &m_powerLegend, false);
d.readBool(84, &m_powerShowTsys0, false);
d.readBool(85, &m_powerShowAirTemp, false);
d.readBool(86, &m_powerShowGaussian, false);
d.readFloat(87, &m_powerReference, 0.0f);
d.readFloat(88, &m_powerRange, 100.0f);
d.readBool(89, &m_powerAutoscale, true);
d.readS32(90, (int*)&m_powerYData, PY_POWER);
d.readS32(91, (int*)&m_powerYUnits, PY_DBFS);
d.readBool(100, &m_power2DLinkSweep, true);
d.readS32(102, (int*)&m_power2DSweepType, SWP_OFFSET);
d.readS32(103, &m_power2DWidth, 3);
d.readS32(104, &m_power2DHeight, 3);
d.readFloat(105, &m_power2DXMin, 0);
d.readFloat(106, &m_power2DXMax, 10);
d.readFloat(107, &m_power2DYMin, 0);
d.readFloat(108, &m_power2DYMax, 10);
d.readBool(109, &m_powerColourAutoscale, true);
d.readFloat(110, &m_powerColourScaleMin, 0.0f);
d.readFloat(111, &m_powerColourScaleMax, 0.0f);
d.readString(112, &m_powerColourPalette, "Colour");
d.readS32(120, (int *)&m_runMode, CONTINUOUS);
d.readBool(121, &m_sweepStartAtTime, false);
d.readS64(122, &dttmp, QDateTime::currentDateTime().toMSecsSinceEpoch());
m_sweepStartDateTime = QDateTime::fromMSecsSinceEpoch(dttmp);
d.readS32(123, (int*)&m_sweepType, SWP_OFFSET);
d.readFloat(124, &m_sweep1Start, -5.0f);
d.readFloat(125, &m_sweep1Stop, 5.0f);
d.readFloat(126, &m_sweep1Step, 5.0f);
d.readFloat(127, &m_sweep1Delay, 0.0f);
d.readFloat(128, &m_sweep2Start, -5.0f);
d.readFloat(129, &m_sweep2Stop, 5.0);
d.readFloat(130, &m_sweep2Step, 5.0f);
d.readFloat(131, &m_sweep2Delay, 0.0f);
d.readString(140, &m_sensorName[0], "");
d.readString(141, &m_sensorDevice[0], "");
d.readString(142, &m_sensorInit[0], "");
d.readString(143, &m_sensorMeasure[0], "");
d.readBool(144, &m_sensorEnabled[0], false);
d.readBool(145, &m_sensorVisible[0], false);
d.readString(146, &m_sensorName[1], "");
d.readString(147, &m_sensorDevice[1], "");
d.readString(148, &m_sensorInit[1], "");
d.readString(149, &m_sensorMeasure[1], "");
d.readBool(150, &m_sensorEnabled[1], false);
d.readBool(151, &m_sensorVisible[1], false);
d.readFloat(152, &m_sensorMeasurePeriod, 1.0f);
d.readBool(160, &m_gpioEnabled, false);
d.readS32(161, &m_gpioPin, 0);
d.readS32(162, &m_gpioSense, 1);
d.readString(167, &m_startCalCommand, "");
d.readString(168, &m_stopCalCommand, "");
d.readFloat(169, &m_calCommandDelay, 1.0f);
d.readU32(180, &m_rgbColor, QColor(102, 0, 0).rgb());
d.readString(181, &m_title, "Radio Astronomy");
d.readBlob(182, &bytetmp);
if (m_channelMarker) {
m_channelMarker->deserialize(bytetmp);
}
d.readS32(183, &m_streamIndex, 0);
d.readBool(184, &m_useReverseAPI, false);
d.readString(185, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(186, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(187, &utmp, 0);
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
d.readU32(188, &utmp, 0);
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) {
d.readS32(400 + i, &m_powerTableColumnIndexes[i], i);
}
for (int i = 0; i < RADIOASTRONOMY_POWERTABLE_COLUMNS; i++) {
d.readS32(500 + i, &m_powerTableColumnSizes[i], -1);
}
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,225 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMYSETTINGS_H
#define INCLUDE_RADIOASTRONOMYSETTINGS_H
#include <QByteArray>
#include <QString>
#include <QDateTime>
#include "dsp/dsptypes.h"
class Serializable;
// Number of columns in the tables
#define RADIOASTRONOMY_POWERTABLE_COLUMNS 27
// Number of sensors
#define RADIOASTRONOMY_SENSORS 2
struct RadioAstronomySettings
{
int m_inputFrequencyOffset;
int m_sampleRate;
int m_rfBandwidth;
int m_integration; //!< Number of samples to integrate
int m_fftSize;
enum FFTWindow {
REC,
HAN
} m_fftWindow; //!< FFT windowing function
QString m_filterFreqs; //!< List of channels (bin indices) to filter in FFT to remove RFI
QString m_starTracker; //!< Name of Star Tracker plugin to link with
QString m_rotator; //!< Name of antenna rotator
float m_tempRX; //!< Receiver noise temperature in K (Front end NF)
float m_tempCMB; //!< Cosmic microwave background temperature in K
float m_tempGal; //!< Galactic background temperature in K
float m_tempSP; //!< Spillover temperature in K
float m_tempAtm; //!< Atmospheric temperature in K
float m_tempAir; //!< Surface air temperature in C
float m_zenithOpacity; //!< Opacity of atmosphere at zenith
float m_elevation; //!< Elevation in degrees, if not using value from Star Tracker
bool m_tempGalLink;
bool m_tempAtmLink;
bool m_tempAirLink;
bool m_elevationLink;
float m_gainVariation; //!< delta G/G
enum SourceType {
UNKNOWN,
COMPACT,
EXTENDED,
SUN,
CAS_A
} m_sourceType; //!< Whether the source it smaller than the beam
float m_omegaS; //!< Source angle
enum AngleUnits {
DEGREES,
STERRADIANS
} m_omegaSUnits;
enum AngleUnits m_omegaAUnits;
bool m_spectrumPeaks;
bool m_spectrumMarkers;
bool m_spectrumTemp;
bool m_spectrumReverseXAxis;
bool m_spectrumRefLine;
bool m_spectrumLAB;
bool m_spectrumDistance;
bool m_spectrumLegend;
float m_spectrumReference; //!< In dB
float m_spectrumRange; //!< In dB
float m_spectrumSpan; //!< In Mhz
float m_spectrumCenterFreqOffset; //!< Offset - rather than absolute - In Mhz
bool m_spectrumAutoscale;
enum SpectrumYScale {
SY_DBFS,
SY_SNR,
SY_DBM,
SY_TSYS,
SY_TSOURCE
} m_spectrumYScale;
enum SpectrumBaseline {
SBL_TSYS0,
SBL_TMIN,
SBL_CAL_COLD
} m_spectrumBaseline;
bool m_recalibrate;
float m_tCalHot; //!< Hot calibration antenna noise temperature in K (Sky + Spillover?)
float m_tCalCold; //!< Cold calibration antenna noise temperature in K
enum Line {
HI,
OH,
DI,
CUSTOM_LINE
} m_line; //!< Spectral line to plot and use as Doppler reference
float m_lineCustomFrequency; //!< Spectral line frequency when m_line==CUSTOM
enum RefFrame {
TOPOCENTRIC,
BCRS,
LSR
} m_refFrame; //!< Reference frame for velocities
float m_sunDistanceToGC; //!< Sun distance to Galactic Center In kpc
float m_sunOrbitalVelocity; //!< In km/s around GC
bool m_powerPeaks;
bool m_powerMarkers;
bool m_powerAvg;
bool m_powerLegend;
bool m_powerShowTsys0; //!< Plot total noise temperature
bool m_powerShowAirTemp;
bool m_powerShowGaussian;
float m_powerReference; //!< In dB
float m_powerRange; //!< In dB
bool m_powerAutoscale;
enum PowerYData {
PY_POWER,
PY_TSYS,
PY_TSOURCE,
PY_FLUX,
PY_2D_MAP
} m_powerYData;
enum PowerYUnits {
PY_DBFS,
PY_DBM,
PY_WATTS,
PY_KELVIN,
PY_SFU,
PY_JANSKY
} m_powerYUnits;
enum SweepType {
SWP_AZEL,
SWP_LB,
SWP_OFFSET
};
bool m_power2DLinkSweep; //<! Update setting below to match sweep parameters
SweepType m_power2DSweepType;
int m_power2DWidth;
int m_power2DHeight;
float m_power2DXMin;
float m_power2DXMax;
float m_power2DYMin;
float m_power2DYMax;
bool m_powerColourAutoscale;
float m_powerColourScaleMin;
float m_powerColourScaleMax;
QString m_powerColourPalette;
enum RunMode {
SINGLE,
CONTINUOUS,
SWEEP
} m_runMode;
bool m_sweepStartAtTime;
QDateTime m_sweepStartDateTime;
SweepType m_sweepType;
float m_sweep1Start;
float m_sweep1Stop;
float m_sweep1Step;
float m_sweep1Delay; //!< Delay after rotation before a measurement
float m_sweep2Start;
float m_sweep2Stop;
float m_sweep2Step;
float m_sweep2Delay; //!< Delay after a measurement before starting the next
QString m_sensorName[RADIOASTRONOMY_SENSORS];
QString m_sensorDevice[RADIOASTRONOMY_SENSORS];
QString m_sensorInit[RADIOASTRONOMY_SENSORS];
QString m_sensorMeasure[RADIOASTRONOMY_SENSORS];
bool m_sensorEnabled[RADIOASTRONOMY_SENSORS];
bool m_sensorVisible[RADIOASTRONOMY_SENSORS];
float m_sensorMeasurePeriod;
bool m_gpioEnabled;
int m_gpioPin;
int m_gpioSense;
QString m_startCalCommand;
QString m_stopCalCommand;
float m_calCommandDelay; //!< Delay in seconds after command before starting cal
quint32 m_rgbColor;
QString m_title;
Serializable *m_channelMarker;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIDeviceIndex;
uint16_t m_reverseAPIChannelIndex;
int m_powerTableColumnIndexes[RADIOASTRONOMY_POWERTABLE_COLUMNS];//!< How the columns are ordered in the table
int m_powerTableColumnSizes[RADIOASTRONOMY_POWERTABLE_COLUMNS]; //!< Size of the columns in the table
RadioAstronomySettings();
void resetToDefaults();
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static const QStringList m_pipeTypes;
static const QStringList m_pipeURIs;
};
#endif /* INCLUDE_RADIOASTRONOMYSETTINGS_H */

View File

@ -0,0 +1,304 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "radioastronomy.h"
#include "radioastronomysink.h"
RadioAstronomySink::RadioAstronomySink(RadioAstronomy *aisDemod) :
m_radioAstronomy(aisDemod),
m_channelSampleRate(1000000),
m_channelFrequencyOffset(0),
m_fftSequence(-1),
m_fft(nullptr),
m_fftCounter(0),
m_fftSum(nullptr),
m_fftTemp(nullptr),
m_fftSumCount(0),
m_enabled(false),
m_cal(false),
m_magsqSum(0.0f),
m_magsqPeak(0.0f),
m_magsqCount(0),
m_messageQueueToChannel(nullptr)
{
m_magsq = 0.0;
applySettings(m_settings, true);
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
}
RadioAstronomySink::~RadioAstronomySink()
{
delete[] m_fftSum;
delete[] m_fftTemp;
}
void RadioAstronomySink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
{
Complex ci;
for (SampleVector::const_iterator it = begin; it != end; ++it)
{
Complex c(it->real(), it->imag());
c *= m_nco.nextIQ();
if (m_interpolatorDistance < 1.0f) // interpolate
{
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
else // decimate
{
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
{
processOneSample(ci);
m_interpolatorDistanceRemain += m_interpolatorDistance;
}
}
}
}
void RadioAstronomySink::processOneSample(Complex &ci)
{
// Calculate power
double magsqRaw = ci.real()*ci.real() + ci.imag()*ci.imag();
double magsq = (magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED));
// Calculate average and peak levels for level meter
m_movingAverage(magsq);
m_magsq = m_movingAverage.asDouble();
m_magsqSum += magsq;
if (magsq > m_magsqPeak)
{
m_magsqPeak = magsq;
}
m_magsqCount++;
if (m_enabled || m_cal)
{
// Add to FFT input buffer
m_fft->in()[m_fftCounter] = Complex(ci.real() / SDR_RX_SCALEF, ci.imag() / SDR_RX_SCALEF);
m_fftCounter++;
if (m_fftCounter >= m_settings.m_fftSize)
{
// Calculate FFT
m_fftWindow.apply(m_fft->in());
m_fft->transform();
m_fftCounter = 0;
// Calculate power and accumulate
for (int i = 0; i < m_settings.m_fftSize; i++)
{
Complex s = m_fft->out()[i];
Real v = s.real() * s.real() + s.imag() * s.imag();
Real enbw = 1.0f;
/*if (m_settings.m_fftWindow == RadioAstronomySettings::HAN && m_settings.m_fftCorrection == RadioAstronomySettings::POWER) {
enbw = 1.5; // FIXME: Small dependence on fftSize in Matlab
}*/
m_fftSum[i] += v / (enbw * m_settings.m_fftSize * m_settings.m_fftSize); // Why FFT size here and not Fs?
}
m_fftSumCount++;
if (m_fftSumCount >= m_settings.m_integration)
{
// Average
for (int i = 0; i < m_settings.m_fftSize; i++) {
m_fftSum[i] /= m_fftSumCount;
}
// Put negative frequencies first
std::copy(m_fftSum + m_settings.m_fftSize/2, m_fftSum + m_settings.m_fftSize, m_fftTemp);
std::copy(m_fftSum, m_fftSum + m_settings.m_fftSize/2, m_fftTemp + m_settings.m_fftSize/2);
// Filter freqs with RFI
if (m_filterBins.size() > 0)
{
// Find minimum value to use as replacement
// Should possibly use an average of the n lowest values or something
float minVal = std::numeric_limits<float>::max();
for (int i = 0; i < m_settings.m_fftSize; i++) {
minVal = std::min(minVal, m_fftTemp[i]);
}
for (int i = 0; i < m_filterBins.size(); i++)
{
int bin = m_filterBins[i];
if (bin < m_settings.m_fftSize) {
m_fftTemp[bin] = minVal;
}
}
}
getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(100));
if (m_cal)
{
// Indicate calibration complete
if (getMessageQueueToChannel())
{
RadioAstronomy::MsgCalComplete *msg = RadioAstronomy::MsgCalComplete::create(m_fftTemp, m_settings.m_fftSize, QDateTime::currentDateTime(), m_hot);
getMessageQueueToChannel()->push(msg);
}
// Cal complete
m_cal = false;
}
else
{
// Send averaged FFT to channel
if (getMessageQueueToChannel())
{
RadioAstronomy::MsgFFTMeasurement *msg = RadioAstronomy::MsgFFTMeasurement::create(m_fftTemp, m_settings.m_fftSize, QDateTime::currentDateTime());
getMessageQueueToChannel()->push(msg);
}
m_enabled = (m_settings.m_runMode == RadioAstronomySettings::CONTINUOUS);
if (m_enabled) {
getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0));
}
}
m_fftSumCount = 0;
std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f);
}
else
{
// Don't send more than ~4 updates per second
int fftsPerSecond = m_settings.m_sampleRate / m_settings.m_fftSize;
if ((m_fftSumCount % (fftsPerSecond/4)) == 0) {
getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(100 * m_fftSumCount / m_settings.m_integration));
}
}
}
}
}
void RadioAstronomySink::startMeasurements()
{
getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0));
m_enabled = true;
m_fftSumCount = 0;
std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f);
}
void RadioAstronomySink::stopMeasurements()
{
m_enabled = false;
}
void RadioAstronomySink::startCal(bool hot)
{
getMessageQueueToChannel()->push(RadioAstronomy::MsgMeasurementProgress::create(0));
m_cal = true;
m_hot = hot;
m_fftSumCount = 0;
std::fill(m_fftSum, m_fftSum + m_settings.m_fftSize, 0.0f);
}
void RadioAstronomySink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
{
qDebug() << "RadioAstronomySink::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.0f);
m_interpolatorDistance = (Real) channelSampleRate / (Real) m_settings.m_sampleRate;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
m_channelSampleRate = channelSampleRate;
m_channelFrequencyOffset = channelFrequencyOffset;
}
void RadioAstronomySink::applySettings(const RadioAstronomySettings& settings, bool force)
{
qDebug() << "RadioAstronomySink::applySettings:"
<< " m_sampleRate: " << settings.m_sampleRate
<< " m_rfBandwidth: " << settings.m_rfBandwidth
<< " m_fftSize: " << settings.m_fftSize
<< " m_fftWindow: " << settings.m_fftWindow
<< " m_filterFreqs: " << settings.m_filterFreqs
<< " force: " << force;
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth)
|| (settings.m_sampleRate != m_settings.m_sampleRate)
|| force)
{
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.0f); // 2.0 rather than 2.2 as in other plugins, to reduce rolloff at edge of band
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) settings.m_sampleRate;
m_interpolatorDistanceRemain = m_interpolatorDistance;
}
if ((settings.m_fftSize != m_settings.m_fftSize) || force)
{
FFTFactory *fftFactory = DSPEngine::instance()->getFFTFactory();
if (m_fftSequence >= 0) {
fftFactory->releaseEngine(m_settings.m_fftSize, false, m_fftSequence);
}
m_fftSequence = fftFactory->getEngine(settings.m_fftSize, false, &m_fft);
m_fftCounter = 0;
delete[] m_fftSum;
delete[] m_fftTemp;
m_fftSum = new Real[settings.m_fftSize]();
m_fftTemp = new Real[settings.m_fftSize]();
m_fftSumCount = 0;
}
if ((settings.m_fftSize != m_settings.m_fftSize)
|| (settings.m_fftWindow != m_settings.m_fftWindow)
|| force)
{
if (settings.m_fftWindow == RadioAstronomySettings::HAN) {
m_fftWindow.create(FFTWindow::Hanning, settings.m_fftSize);
} else {
m_fftWindow.create(FFTWindow::Rectangle, settings.m_fftSize);
}
}
if ((settings.m_filterFreqs != m_settings.m_filterFreqs) || force)
{
m_filterBins.clear();
QStringList filterFreqs = settings.m_filterFreqs.split(" ");
for (int i = 0; i < filterFreqs.size(); i++)
{
bool ok;
int bin = filterFreqs[i].toInt(&ok);
if (ok) {
m_filterBins.append(bin);
}
}
}
m_settings = settings;
}

View File

@ -0,0 +1,123 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_RADIOASTRONOMYSINK_H
#define INCLUDE_RADIOASTRONOMYSINK_H
#include "dsp/channelsamplesink.h"
#include "dsp/nco.h"
#include "dsp/interpolator.h"
#include "dsp/fftfactory.h"
#include "dsp/fftengine.h"
#include "dsp/fftwindow.h"
#include "util/movingaverage.h"
#include "util/messagequeue.h"
#include "radioastronomysettings.h"
class ChannelAPI;
class RadioAstronomy;
class RadioAstronomySink : public ChannelSampleSink {
public:
RadioAstronomySink(RadioAstronomy *aisDemod);
~RadioAstronomySink();
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 RadioAstronomySettings& settings, bool force = false);
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
void setChannel(ChannelAPI *channel) { m_channel = channel; }
void startMeasurements();
void stopMeasurements();
void startCal(bool hot);
void clearCal();
double getMagSq() const { return m_magsq; }
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
{
if (m_magsqCount > 0)
{
m_magsq = m_magsqSum / m_magsqCount;
m_magSqLevelStore.m_magsq = m_magsq;
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
}
avg = m_magSqLevelStore.m_magsq;
peak = m_magSqLevelStore.m_magsqPeak;
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
m_magsqSum = 0.0f;
m_magsqPeak = 0.0f;
m_magsqCount = 0;
}
private:
struct MagSqLevelsStore
{
MagSqLevelsStore() :
m_magsq(1e-12),
m_magsqPeak(1e-12)
{}
double m_magsq;
double m_magsqPeak;
};
RadioAstronomy *m_radioAstronomy;
RadioAstronomySettings m_settings;
ChannelAPI *m_channel;
int m_channelSampleRate;
int m_channelFrequencyOffset;
int m_fftSequence;
FFTEngine *m_fft;
FFTWindow m_fftWindow;
int m_fftCounter;
QList<int> m_filterBins;
Real *m_fftSum;
Real *m_fftTemp;
int m_fftSumCount;
bool m_enabled;
bool m_cal;
bool m_hot;
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;
void processOneSample(Complex &ci);
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
};
#endif // INCLUDE_RADIOASTRONOMYSINK_H

View File

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

View File

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

View File

@ -0,0 +1,160 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QDebug>
#include "radioastronomy.h"
#include "radioastronomyworker.h"
MESSAGE_CLASS_DEFINITION(RadioAstronomyWorker::MsgConfigureRadioAstronomyWorker, Message)
RadioAstronomyWorker::RadioAstronomyWorker(RadioAstronomy* radioAstronomy) :
m_radioAstronomy(radioAstronomy),
m_msgQueueToChannel(nullptr),
m_msgQueueToGUI(nullptr),
m_running(false),
m_mutex(QMutex::Recursive),
m_sensorTimer(this)
{
connect(&m_sensorTimer, SIGNAL(timeout()), this, SLOT(measureSensors()));
m_sensorTimer.start((int)round(m_settings.m_sensorMeasurePeriod*1000.0));
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++) {
m_session[i] = VI_NULL;
}
}
RadioAstronomyWorker::~RadioAstronomyWorker()
{
m_inputMessageQueue.clear();
m_visa.closeDefault();
}
void RadioAstronomyWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
bool RadioAstronomyWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = true;
return m_running;
}
void RadioAstronomyWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_running = false;
}
void RadioAstronomyWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool RadioAstronomyWorker::handleMessage(const Message& cmd)
{
if (MsgConfigureRadioAstronomyWorker::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureRadioAstronomyWorker& cfg = (MsgConfigureRadioAstronomyWorker&) cmd;
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else
{
return false;
}
}
void RadioAstronomyWorker::applySettings(const RadioAstronomySettings& settings, bool force)
{
qDebug() << "RadioAstronomyWorker::applySettings:"
<< " m_sensorEnabled[0]: " << settings.m_sensorEnabled[0]
<< " m_sensorDevice[0]: " << settings.m_sensorDevice[0]
<< " m_sensorInit[0]: " << settings.m_sensorInit[0]
<< " m_sensorMeasure[0]: " << settings.m_sensorMeasure[0]
<< " force: " << force;
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++)
{
if ( (settings.m_sensorEnabled[i] != m_settings.m_sensorEnabled[i])
|| (settings.m_sensorEnabled[i] && (settings.m_sensorDevice[i] != m_settings.m_sensorDevice[i]))
|| force)
{
if (!settings.m_sensorEnabled[i] && (m_session[i] != VI_NULL))
{
m_visa.close(m_session[i]);
m_session[i] = VI_NULL;
}
if (settings.m_sensorEnabled[i] && !settings.m_sensorDevice[i].trimmed().isEmpty())
{
m_visa.openDefault();
m_session[i] = m_visa.open(settings.m_sensorDevice[i]);
}
}
if ( (settings.m_sensorEnabled[i] && !m_settings.m_sensorEnabled[i])
|| (settings.m_sensorEnabled[i] && (settings.m_sensorInit[i] != m_settings.m_sensorInit[i]))
|| force)
{
if (m_session[i]) {
m_visa.processCommands(m_session[i], settings.m_sensorInit[i]);
}
}
}
if ((settings.m_sensorMeasurePeriod != m_settings.m_sensorMeasurePeriod) || force) {
m_sensorTimer.start((int)round(settings.m_sensorMeasurePeriod * 1000.0));
}
m_settings = settings;
}
void RadioAstronomyWorker::measureSensors()
{
for (int i = 0; i < RADIOASTRONOMY_SENSORS; i++)
{
if (m_settings.m_sensorEnabled[i] && m_session[i])
{
QStringList results = m_visa.processCommands(m_session[i], m_settings.m_sensorMeasure[i]);
if (results.size() >= 1)
{
double value = results[0].toDouble();
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(RadioAstronomy::MsgSensorMeasurement::create(i, value));
}
}
else
{
qDebug() << "RadioAstronomyWorker::measureSensors: No result for command " << m_settings.m_sensorMeasure[i];
}
}
}
}

View File

@ -0,0 +1,93 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_RADIOASTRONOMYWORKER_H
#define INCLUDE_RADIOASTRONOMYWORKER_H
#include <QObject>
#include <QTimer>
#include "util/message.h"
#include "util/messagequeue.h"
#include "util/visa.h"
#include "radioastronomysettings.h"
class RadioAstronomy;
class RadioAstronomyWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureRadioAstronomyWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const RadioAstronomySettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureRadioAstronomyWorker* create(const RadioAstronomySettings& settings, bool force)
{
return new MsgConfigureRadioAstronomyWorker(settings, force);
}
private:
RadioAstronomySettings m_settings;
bool m_force;
MsgConfigureRadioAstronomyWorker(const RadioAstronomySettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
RadioAstronomyWorker(RadioAstronomy* radioAstronomy);
~RadioAstronomyWorker();
void reset();
bool startWork();
void stopWork();
bool isRunning() const { return m_running; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_msgQueueToChannel = messageQueue; }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; }
private:
RadioAstronomy* m_radioAstronomy;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_msgQueueToChannel;
MessageQueue *m_msgQueueToGUI;
RadioAstronomySettings m_settings;
bool m_running;
QMutex m_mutex;
VISA m_visa;
ViSession m_session[RADIOASTRONOMY_SENSORS];
QTimer m_sensorTimer;
bool handleMessage(const Message& cmd);
void applySettings(const RadioAstronomySettings& settings, bool force = false);
MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; }
private slots:
void handleInputMessages();
void measureSensors();
};
#endif // INCLUDE_RADIOASTRONOMYWORKER_H

View File

@ -0,0 +1,864 @@
<h1>Radio Astronomy plugin</h1>
<h2>Introduction</h2>
The Radio Astronomy plugin provides a number of tools to help make radio astronomy measurements. It supports:
- A spectrometer for displaying time averaged spectra.
- A radiometer for displaying time averaged continuum measurements (total power).
- Calibration to enable measurements to be displayed as noise temperatures (K), power (dBm/Watts) and spectral flux density (Jy).
- Utilities are included for estimation and calculation of noise temperature components (Tsys, Trx, Tgal, Tatm, Tsky, Tsp) and sensitivity (sigma Tsys and sigma Sv).
- Spectra can be displayed against frequency and velocity (with a configurable reference spectral line), with the velocity adjusted to topocentric, Solar System barycentric or the Local Standard of Rest (LSR) reference frames.
- Calculation and plotting of radial and Galactocentric distance to HI clouds, based on spectral peaks.
- Position of HI clouds can be sent to Star Tracker plugin for visualisation on the Galactic line-of-sight image and created in to an animation mapping out the Milky Way's spiral arms.
- A Gaussian fitting tool in the spectrometer for HI cloud kinetic temperature and column density estimation.
- A Gaussian fitting tool in the radiometer to enable antenna HPBW measurement from Solar drift-scans.
- Ability to record and plot real-time surface air temperature and other sensor measurements (component voltages / temperatures) alongside radiometer measurements.
- Ability to export charts to animated .png files and static image files.
- Reference spectra from the LAB (Leiden/Argentine/Bonn) Galactic HI survey can be automatically downloaded and plotted for comparison against user measurements.
- 2D sweeps can be made and plotted in different coordinate systems (Az/El, Galactic, offsets around a target and drift scans).
- All spectra are held in memory and can be scrolled through.
- Data can be saved and loaded from .csv files.
- Hardware for calibration (E.g. RF switches) can be automatically controlled.
![Radio Astronomy plugin GUI](../../../doc/img/RadioAstronomy_plugin.png)
Several of the features in this plugin are tailored towards measurements of neutral hydrogen (HI) that is dispersed throughout the Milky Way's interstellar medium (ISM).
The HI in the ISM is a particularly interesting astronomical radiation source for SDR users, as the ground-state hyperfine transition of HI has a rest frequency of 1420.405MHz,
which is both quite powerful (due to the vast amount of HI spread throughout the Milky Way) and at a frequency that is easy to detect with relatively small dishes and low-cost LNAs and SDRs.
The HI spectrum can be used to determine some of the Milky Way's spiral structure and calculate rotation curves for the inner Milky Way, which suggest the presence of dark matter.
In radio astronomy it is common to use noise temperatures rather than power, via the relation:
T=P/(k*B)
Where:
- T is the noise temperature in Kelvin
- P is power in Watts
- k is Boltzmann's constant
- B is bandwidth in Hertz
Similarly, for low frequencies (where the Rayleigh-Jeans approximation is valid), brightness temperatures are used rather than intensity:
Tb=e*Iv*c^2/(2k*v^2)
Where:
- Tb is the brightness temperature in Kelvin
- e is the emissivity of the source
- Iv is the intensity of the source (power per unit solid angle at the frequency v)
- c is the speed of light
- v is the frequency in Hertz
This can be convenient, as if a large astronomical source completely fills the antenna beam with a uniform brightness temperature,
there will be an equal increase in the noise temperature measured by the receiver.
For thermal sources of radiation, the brightness temperature can also correspond directly to the physical temperature of the source.
This isn't true for non-thermal sources, however, such as synchrotron radiation.
In this plugin, the following notation is used for the different temperature sources and combinations:
- Trx - Receiver noise temperature. This is the combined noise temperature due to the LNA, feed line and SDR.
- Tcmb - Cosmic Microwave Background temperature (2.73K).
- Tgal - Galactic background temperature. An estimate of the frequency dependent background that is assumed to be the same in all directions.
- Tsky - Combined CMB, Galactic background and foreground as calculated in Star Tracker plugin using all-sky survey data.
- Tatm - Atmospheric emission, dependent upon frequency, opacity (which is dependent on temperature, pressure and water vapour) and elevation.
- Tsp - Spillover temperature due to thermal ground noise and other thermal noise sources such as trees and buildings around the antenna.
- Tair - Surface air temperature (In C, unlike all other temperatures which are in K).
- Tsource - Contribution from astronomical source (What we are typically trying to measure).
- Tsys0 - Total of all unwanted noise (Trx+Tcmb+Tgal+Tatm+Tsp).
- Tsys - System noise temperature. Sum of all received noise (Tsys0+Tsource).
- Tb - Brightness temperature of the source.
- Ta - Antenna temperature (which is typically Tcmb+Tgal+Tatm+Tsp+Tsource).
Care should be taken when comparing to definitions in the literature, as these vary significantly. In particular, Tsys can be defined to include or
exclude the astronomical source contribution and Ta can be just the source or all antenna noise.
For most astronomical observations, Tsource<<Tsys and so we need to integrate many measurements, such that Tsource is typically five times larger
than the random variations in the total noise (sigma_Tsys). The length of time and bandwidth required for this can be calculated from the
[practical radiometer equation](https://www.cv.nrao.edu/~sransom/web/Ch3.html#E158):
sigma_Tsys = Tsys * sqrt(1/(B*tau) + (deltaG/G)^2)
Where:
- sigma_Tsys = Standard deviation / RMS of system noise temperature.
- Tsys is system noise temperature.
- B is bandwidth in Hertz.
- tau is the integration time in seconds.
- deltaG/G is the receiver gain variation.
<h2>1: Settings</h2>
![Settings GUI](../../../doc/img/RadioAstronomy_Settings.png)
<h3>1.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>1.2: SR - Sample Rate</h3>
Sample rate in millions of samples per second. This determines the maximum bandwidth for the radiometer and spectrometer and thus the maximum Doppler shift range.
Typically this should be set to match SDRangel's baseband sample rate.
<h3>1.3: BW - RF Bandwidth</h3>
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth. This can be used to eliminate RFI.
<h3>1.4: Channel power</h3>
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
<h3>1.5: Integration Count</h3>
Specifies the number of FFTs that are summed in each average. Higher integration counts increase measurement time, but also increase sensitivity (up to the limit imposed by receiver gain variation).
<h3>1.6: Channels</h3>
Specifies the number of channels (FFT bins). A larger number means an increased resolution in the spectrometer,
however, the sensitivity per bin is decreased for a fixed measurement time.
<h3>1.7: Window function</h3>
A windowing function to be applied before the FFT, to compensate for spectral leakage. This can be either:
- Rec: Rectangular or no windowing function. Use for broadband / continuum sources.
- Han: Hanning window. Use for narrowband / spectral line sources for better frequency resolution.
<h3>1.8: Filter</h3>
Specifies a list of FFT bins that will have their values replaced with the minimum of other FFTs bins. This can be used to filter inband RFI.
<h3>1.9: Integration Time</h3>
Displays in seconds or minutes how long it will take to make a single measurement. This is dependent on the sample rate (2), Integration Count (5) and number of channels (6).
<h3>1.10: Star Tracker</h3>
Specifies the Star Tracker feature that determines the observation target.
A corresponding Star Tracker feature is required to calculate and display numerous values within the Radio Astronomy plugin and also for performing sweeps.
<h3>1.11: Rotator</h3>
Specifies the rotator controller feature that is controlling the antenna used for measurements by the Radio Astronomy plugin.
This setting is used when sweeps are performed, to determine when rotation is complete and the antenna is pointing at the target.
If no rotator is used (and only drift scans are performed), this can be set to None.
<h3>1.12: Sensors</h3>
Opens the Sensors dialog.
The Sensors dialog allows two measurements such as voltage and temperature to be recorded and plotted along with radiometer data.
The sensor measurements are made using the VISA (Virtual Instruments Software Architecture) API, which is implemented in many
benchtop multimeters, oscilloscopes and spectrum analyzers.
Sensor measurements can be used to monitor temperatures and voltages that might have an impact on measurements. For example, the gain
on the LNA will be dependent upon both, and gain variations limit the benefit of increased integration counts on sensitivity.
![Sensors dialog](../../../doc/img/RadioAstronomy_SensorSettings.png)
<h3>1.13: Trx/NF</h3>
Sets the receiver noise temperature in Kelvin or noise figure (NF) in dB. This is the noise temperature / figure for the combination of the LNA, feed line and SDR (at a specific gain setting).
The value set here can be measured using SDRangel's [Noise Figure plugin](https://github.com/f4exb/sdrangel/blob/master/plugins/channelrx/noisefigure/readme.md)
or estimated from datasheet values for the individual components using the [Friis formula](https://en.wikipedia.org/wiki/Friis_formulas_for_noise).
<h3>1.14: Tcmb</h3>
Sets the contribution to noise temperature from the Cosmic Microwave Background (CMB). This is 2.73K when an antenna is pointed at the Sky, but may be set to 0K if measurements are made
where there is no CMB contribution (E.g. if the feedhorn is covered with absorbing foam or a 50Ohm terminator or other noise source is used for calibration).
<h3>1.15: Tgal</h3>
Sets the contribution to the noise temperature from the Galactic background. This is frequency dependent and varies with direction. It does not include
the Galactic foreground (i.e. the increased noise temperature when looking in the Galactic plane).
If the link button to the right is unchecked, a value can be entered manually.
If the link button is checked, Tgal is calculated using:
Tgal = 25.2 * (f/f0)^-2.75
Where:
- 25.2 is the 50th percentile of the all-sky distribution temperature.
- f is the center frequency.
- f0 is 408MHz.
Tgal is used in calibration to estimate Tsp.
<h3>1.16: Tsp</h3>
Sets spillover noise temperature. This is unwanted noise due to thermal ground radiation or other thermal radiation sources such as buildings and trees that can be picked
up via an antenna's side and back lobes.
It can be very dependent on azimuth and elevation in urban environments.
An estimate for Tsp can be made via the hot/cold calibration process.
<h3>1.17: Tatm</h3>
Contribution to noise temperature due to atmospheric emission. Atmospheric emission is dependent upon frequency, opacity (which is dependent on air temperature, pressure and water vapour) and antenna elevation.
If the link button to the right is unchecked, a value can be entered manually.
If the link button is checked, Tatm is calculated using:
Tatm = Tair * (1 - exp(-tau_z*sec(el)))
Where:
- Tair is the surface air temperature from (18).
- tau_z is the zenith opacity from (19).
- el is the elevation of the antenna from (20)
<h3>1.18: Tair</h3>
Tair specifies the surface air temperature at the antenna location in degrees Celsius.
If the link button to the right is unchecked, a value can be entered manually.
If the link button is checked, Tair is set to the air temperature value received from the Star Tracker plugin, which itself is periodically downloaded from openweathermap.org for the antenna's location.
<h3>1.19: tau_z - Zenith Opacity</h3>
tau_z specifies the Zenith opacity. This value determines atmospheric absorption and emission. It is dependent upon air temperature, pressure and water vapour.
The default value of 0.0055 roughly corresponds to clear air as per ITU-R P.372-14 figure 5 at 1.4GHz.
<h3>1.20: El - Antenna Elevation</h3>
This specifies the antenna elevation in degrees. It is used for calculating atmospheric emission and absorption.
If the link button to the right is unchecked, a value can be entered manually.
If the link button is checked, El is automatically set to the elevation received from the Star Tracker plugin.
<h3>1.21: Tsys0</h3>
This displays the value of Tsys0, the system noise temperature without an astronomical source, which is calculated as:
Tsys0=Trx+Tcmb+Tgal+Tsp+Tatm.
<h3>1.22: sigma Tsys0</h3>
This displays the value of sigma Tsys0, which is the standard deviation / RMS of Tsys0, and gives an indication of the sensitivity. It is calculated as:
sigma_Tsys0 = Tsys0 * sqrt(1/(B*tau) + (deltaG/G)^2)
<h3>1.24: Baseline</h3>
Specifies the baseline used for calculating Tsource from Tsys. This can be:
- Tsys0 - Tsource = Tsys-Tsys0.
- Tmin - Tsource = Tsys-Tmin - where Tmin is the minimum in-band temperature.
- Tcold - Tsource = Tsys-Tcold - where Tcold is the cold calibration spectrum. This can be used for on/off source observations.
<h3>1.25: delta G / G - Gain Variation</h3>
delta G / G specifies the gain variation of the LNA / receiver. Gain variation places a limit on the sensitvity improvement available by increased integration counts.
This value is only used for the estimation of sigma_Tsys0 and sigma_Ssys0, it does not affect any measurements.
<h3>1.26: HPBW / Omega A</h3>
This displays the antenna half-power (-3dB) beamwidth (HPBW) in degrees or beam solid angle in steradians, as set in the Star Tracker plugins set by (10).
<h3>1.27: Omega S Type</h3>
This sets the type of astronomical source, with respect to its and the antenna beam's angular size:
- Unknown - Used for when the source is unknown. Brightness temperature (Tb) will not be able to be calculated.
- Compact - The source is smaller than the antenna beam. The source angle can be entered in (28).
- Extended - The source is larger than the antenna beam.
- Sun - The source is the Sun and the source angle is set to 0.53 degrees.
- CasA - The source is Cassiopeia A and the source is set to 0.08333 degrees.
<h3>1.28: Omega S</h3>
Enter the angle subtended by the astronomical source. This can be a diameter entered in degrees or solid angle in steradians.
Values for many astronomical sources are available in the [SIMBAD Astronomical Database](http://simbad.u-strasbg.fr/simbad/).
<h3>1.29: Omega S Units</h3>
Select whether Omega S is calculated from a diameter in degrees or solid angle in steradians.
<h3>2: Run Control</h3>
![Run Control GUI](../../../doc/img/RadioAstronomy_RunControl.png)
<h3>2.1: Start / Stop</h3>
Starts or stops measurements.
The Radio Astronomy plugin has this button in addition to SDRangel's device acquisition Start/stop button, so that the
SDR continues to run when measurements are not being taken. This can help to reduce small gain & frequency variations that may occur in a SDR
when it is turned on from cold.
<h3>2.2: Clear Measurements</h3>
Clears all measurements from memory. Calibration data is kept.
<h3>2.3: Run Mode</h3>
The run mode field determines the number of measurements that are made then the Start button is pressed.
- Single: A single measurement is made.
- Continuous: Measurements are continuously made until the Stop button is pressed.
- Sweep: Measurements are made at each of the coordinates specified by the sweep fields.
<h3>2.4: Start Time</h3>
The start time field determines when the measurements start after the Start button is pressed:
- Now: Measurements start immediately.
- At: Measurements start at the date and time specified in the date/time editor immediately below.
Delaying the start of the measurements can be used to ensure that the target has risen to its maximum elevation.
<h3>2.5: Sweep Parameters</h3>
The Sweep Type field determines the coordinates that are swept:
- Az/El - Coordinates are swept in azimuth then elevation.
- l/b - Coordinates are swept in Galactic longitude then latitude.
- Offset - Sweep values are azimuth and elevation offsets from the center of the target set in Star Tracker.
Here are some examples:
Sweep Type: Az/El
Az Start: 100
Az Stop: 120
Az Step: 10
El Start: 80
El Stop: 85
El Step: 5
Will measure at (Az,El): 100,80 110,80, 120,80 100,85 110,85, 120,85
Sweep Type: Az/El
Az Start: 15
Az Stop: 345
Az Step: -15
El Start: 85
El Stop: 85
El Step: 0
Will measure at (Az,El): 15,85 0,85, 345,85
Sweep Type: l/b
l Start: -60
l Stop: 60
l Step: 20
b Start: 0
b Stop: 0
b Step: 0
Will measure at (l,b): 300,0 320,0 340,0 0,0 20,0 40,0, 60,0
Sweep Type: Offset
Az Start: -5
Az Stop: 5
Az Step: 5
El Start: -5
El Stop: 5
El Step: 5
Target in Star Tracker: Sun
Will measure at (Az,El): Sun-5,Sun-5 Sun,Sun-5 Sun+5,Sun-5 Sun-5,Sun Sun,Sun Sun+5,Sun Sun-5,Sun+5 Sun,Sun+5 Sun+5,Sun+5
The Settle field specifies a delay in seconds after antenna rotation has completed, before the measurement starts.
The Delay field specifies a delay in seconds after a measurement has completed, before the antenna is rotated for the next measurement.
<h3>2.6: Status</h3>
The measurement status bar shows how complete a measurement is in percent.
<h2>3: Spectrometer</h2>
![Spectrometer GUI](../../../doc/img/RadioAstronomy_Spectrometer.png)
<h3>3.1: Spectrum Selection</h3>
This combo box selects between the display of measurement spectra and calibration spectra.
<h3>3.2: Y Axis Units</h3>
Selects the units for the Y-axis:
- dBFS displays received power in dB relative to fullscale.
- SNR displays the signal to noise ratio (where the noise is the cold calibration spectrum).
- dBm displays the received power in dBm (requires hot calibration).
- TSys K displays the system noise temperature in Kelvin (requires hot calibration).
- TSource K displays the astronomical source temperature in Kelvin (requires hot calibration).
<h3>3.3: Display Legend</h3>
Displays a legend at the side of the chart with the colour and name of each series.
<h3>3.4: Display LAB Reference Spectrum</h3>
When checked, a reference spectrum from the Leiden/Argentine/Bonn (LAB) Galactic HI survey corresponding to the current Galactic coordinates
and antenna HPBW will be downloaded and displayed along side the measured spectrum.
This allows a comparison between your HI measurements and that from a professional survey.
![LAB Reference Spectrum](../../../doc/img/RadioAstronomy_LAB.png)
In order to reduce bandwidth to the server supplying this data, it is recommended to use this option sparingly.
If the series does not appear on Windows (and you see "SSL handshake failed" in the log file), you may need to open https://www.astro.uni-bonn.de/hisurvey/euhou/index.php in your Web browser first, so that the certificate for the website is downloaded.
<h3>3.5: Calculate and plot distance to HI gas clouds</h3>
When checked, the marker table will have six additional columns that display estimates of the distance to a HI cloud corresponding to the marker and the tangent point
along the line of sight.
- Vr is radial velocity of the cloud in km/s, relative to the selected reference frame.
- R is the distance from the cloud to the Galactic centre in kiloparsecs (kpc).
- d is the line-of-sight distance to the cloud in kpc. In some instances there can be two possible solutions.
- Plot max determines whether the smaller or larger solution to d is sent to the Star Tracker plugin for display.
- Rmin is the minimum distance to the Galactic centre in kiloparsecs (kpc) along the line of sight (i.e. at the tangent point).
- Vmax is the orbital velocity at the tangent point.
Vmax can be plotted against Rmin to determine the rotation curve of the Milky Way.
d can be plotted against Galactic longitude in Star Tracker to map out the Milky Way's spiral arms.
The spectrometer GUI will also display two additional fields, R0 and V0,
which allow you to enter the distance from the Sun to the Galactic centre and the Sun's orbital velocity
around the Galactic centre, which are used in the above calculations.
![Distance to HI Cloud](../../../doc/img/RadioAstronomy_DistanceToHICloud.png)
<h3>3.6: Display Reference Spectral Line</h3>
When checked, a horizontal axis showing Doppler shift in km/s is added to the top of the spectrometer chart and a vertical reference spectral line is plotted at 0km/s.
The rest frequency of the spectral line can be set via the reference spectral line field or manually entered.
The relationship between the frequency and velocity axes is determined by the selected reference frame.
![Reference Spectral Line](../../../doc/img/RadioAstronomy_RefLine.png)
<h3>3.7: Display Gaussian Fitting Tools</h3>
When checked, the Gaussian fitting tools are displayed. These allow a Gaussian to be fitted to a spectral peak for kinetic temperature and column density estimation.
![Gaussian Fit](../../../doc/img/RadioAstronomy_SpectrumGaussian.png)
<h3>3.8: Display Markers</h3>
When checked, the marker table is displayed and the user may place two markers (M1 and M2) on the chart for accurate display of the corresponding values.
<h3>3.9: Display Peaks</h3>
When checked, the peak table is displayed and the peak Max marker is displayed at the maximum value in the spectrum.
<h3>3.10: Reverse X axis</h3>
When checked, the X axis is reversed. This allows switching between an axis that increases with frequency (which is most common in engineering) or increases with velocity (which is most common in radio astronomy).
<h3>3.11: Save Charts to an Animation File</h3>
Click to export all of the spectral measurements to a animated .png file.
<h3>3.12: Save Chart to an Image File</h3>
Click to save the current chart to an image file.
<h3>3.13: Load Data from a .csv File</h3>
Click to restore data that had been saved to a .csv file. All existing data will be cleared.
<h3>3.14: Save Data from a .csv File</h3>
Click to save all data to a .csv file.
<h3>3.15: Autoscale</h3>
When checked, continuously automatically scales both X and Y axis so all data is visible. When unchecked, the axis scales can be set manually.
<h3>3.16: Autoscale X</h3>
When clicked, automatically scales the X axis so all data is visible.
<h3>3.17: Autoscale Y</h3>
When clicked, automatically scales the Y axis so all data is visible.
<h3>3.18: Ref</h3>
Sets the reference level (maximum value) of the Y axis.
<h3>3.19: Range</h3>
Sets the range of the Y axis.
<h3>3.20: CF</h3>
Sets the centre frequency of the X axis.
<h3>3.21: Span</h3>
Sets the span (range) of the X axis.
<h3>3.22: Sel</h3>
Selects what is selected when clicking on the chart:
- M1 sets position of marker 1
- M2 sets position of marker 2
- Gaussian sets peak of Gaussian
<h3>3.23: Date & Time</h3>
Allows the user to scroll through and select the recorded spectra, showing the date and time they were measured at.
<h3>3.24: Line</h3>
Specifies the rest frequency of the reference spectral line:
- HI neutral hydrogen at 1420.405760MHz.
- OH hydroxyl at 1612.231040Mhz.
- DI neutral deuterium at 327.384MHz.
- Custom allows a user-defined frequency in MHz to be entered.
<h3>3.25: Reference Frame</h3>
Determines the reference frame used for calculating velocities from frequency.
- Topo is a topocentric reference frame (i.e. relative to the observation location).
- BCRS is the barycentric celestial reference system (i.e. relative to the Solar System's barycenter (centre of mass)).
- LSR is the local standard of rest (i.e. relative to the local standard of rest, which accounts for the Sun's movements relative to other nearby stars).
Professional astronomers tend to plot spectra using the LSR, so any observed Doppler shift can assumed to be due to the source moving.
<h3>3.26: R0</h3>
Specifies the distance of the Sun from the Galactic centre in kpc.
<h3>2.27: V0</h3>
Specifies the orbital velocity of the Sun around the Galactic centre in km/s.
<h3>3.28: f0</h3>
Specifies the frequency of the centre of the Gaussian in MHz.
<h3>3.29: a</h3>
Specifies the amplitude of the Gaussian. Units correspond to the Y axis units.
<h3>3.30: f</h3>
Specifies the floor (minimum value of the Gaussian). Units correspond to the Y axis units.
<h3>3.31: Delta f FHWM</h3>
Specifies the full-width at half maximum of the Gaussian in Hertz.
<h3>3.32: Tk</h3>
An estimate of kinetic temperature in Kelvin of a HI cloud whose spectral profile matches the Gaussian.
Note that it's not possible to determine how much spectral broadening is due to kinetic temperature and
how much is due to turbulent velocity, as from a single measurement, there is no way
to distinguish between the two.
<h3>3.33: Vt</h3>
An estimate of the turbulent velocity within a HI cloud whose spectral profile matches the Gaussian.
<h3>3.24: NH</h3>
Estimated column density of an optically thin HI cloud whose spectral profile matches the Gaussian,
measured in HI atoms per square centimetre.
<h3>3.25: Marker Table</h3>
The marker table displays corresponding values for markers that are placed on the chart.
<h2>4: Radiometer</h2>
![Radiometer GUI](../../../doc/img/RadioAstronomy_Radiometer.png)
<h3>4.1: Chart Selection</h3>
This field selects between the display of power, temperature and flux in one or two dimensions.
<h3>4.2: Y Axis Units</h3>
Selects the units for the Y-axis:
- dBFS displays received power in dB relative to fullscale.
- dBm displays the received power in dBm (requires hot calibration).
- dBm displays the received power in Watts (requires hot calibration).
- K displays the temperature in Kelvin (requires hot calibration).
- SFU displays the flux in Solar Flux units (requires hot calibration).
- Jy displays the flux in Jansky (requires hot calibration).
<h3>4.3: Display Legend</h3>
Displays a legend at the side of the chart with the colour and name of each series.
<h3>4.4: Plot Sensor 2</h3>
Plot the data recorded for sensor 2 on the chart.
<h3>4.5: Plot Sensor 1</h3>
Plot the data recorded for sensor 1 on the chart.
<h3>4.6: Plot Air Temperature</h3>
Plot the surface air temperature data received from Star Tracker on the chart.
<h3>4.7: Plot Tsys0</h3>
Plot Tsys0 on the chart.
<h3>4.8: Display Statistics</h3>
Displays statistics calculated across all measurements (not just those visible on the chart), including the mean, RMS and standard deviation.
<h3>4.9: Display Gaussian Fitting Tools</h3>
When checked, the Gaussian fitting tools are displayed. These allow a Gaussian to be fitted to the data, allowing measurement of the HPBW of the antenna.
<h3>4.10: Display Markers</h3>
When checked, the marker table is displayed and the user may place two markers (M1 and M2) on the chart for accurate display of the corresponding values from the measurement series.
<h3>4.11: Display Peaks</h3>
When checked, the marker table is displayed and the peak Max and Min markers are displayed at the maximum and minimum values on the measurement series.
<h3>4.12: Save Chart to an Image File</h3>
Click to save the current chart to an image file.
<h3>4.14: Save Data to a .csv File</h3>
Click to save data from the Radiometer Data table to a .csv file.
<h3>4.15: Autoscale</h3>
When checked, continuously automatically scales both X and Y axis so all data is visible. When unchecked, the axis scales can be set manually.
<h3>4.16: Autoscale X</h3>
When clicked, automatically scales the X axis so all data is visible.
<h3>4.17: Autoscale Y</h3>
When clicked, automatically scales the Y axis so all data is visible.
<h3>4.18: Ref</h3>
Sets the reference level (maximum value) of the Y axis.
<h3>4.19: Range</h3>
Sets the range of the Y axis.
<h3>4.20: Start</h3>
Sets the start time of the X axis.
<h3>4.21: End</h3>
Sets the end time of the X axis.
<h3>4.22: Sel</h3>
Selects what is selected when clicking on the chart:
- Row highlights the corresponding row in the Radiometer Data table to the point clicked.
- M1 sets position of marker 1
- M2 sets position of marker 2
- Gaussian sets peak of Gaussian
<h3>4.23: Center</h3>
Specifies the date and time of the center of the Gaussian.
<h3>4.24: a</h3>
Specifies the amplitude of the Gaussian. Units correspond to the Y axis units.
<h3>4.25: f</h3>
Specifies the floor (minimum value of the Gaussian). Units correspond to the Y axis units.
<h3>4.26: Delta t FHWM</h3>
Specifies the full-width at half maximum of the Gaussian in seconds.
<h3>4.27: HPBW</h3>
An estimate of the HPBW in degrees of an antenna whose main lobe corresponds to the Gaussian profile of a drift scan of the Sun, using a linear scale (E.g. Y axis must not be in not dB).
![Radiometer Gaussian Fit](../../../doc/img/RadioAstronomy_RadiometerGaussian.png)
<h3>4.28: Marker Table</h3>
The marker table displays corresponding values for markers that are placed on the chart.
<h2>5: Radiometer 2D Map</h2>
![Radiometer 2D Map](../../../doc/img/RadioAstronomy_Radiometer2D.png)
<h3>5.1: Link Sweep</h3>
When checked, the parameters for the 2D Map will be automatically updated based on the Run Control sweep parameters.
<h3>5.2: Sweep Type</h3>
Sets the coordinates used for the axes of the map.
<h3>5.3: Width</h3>
Width in pixels of the map. Typically there should be one pixel per measurement.
<h3>5.4: Height</h3>
Height in pixels of the map.
<h3>5.5: X Range</h3>
Specifies the range of the 2D map's horizontal axis. This determines how measurements map to pixels.
<h3>5.6: Y Range</h3>
Specifies the range of the 2D map's vertical axis.
<h3>5.7: Autoscale</h3>
Automatically scales the X and Y axes to fit the Radiometer data in the table.
<h3>5.8: Colour Autoscale</h3>
Automatically scales the colour palette to the range of existing values in the Radiometer data table.
<h3>5.9: Min</h3>
Specifies the value that maps to the first colour in the palette. All values lower than this will be clipped to the first colour.
<h3>5.10: Max</h3>
Specifies the value that maps to the last colour in the palette. All values higher than this will be clipped to the last colour.
<h3>5.11: Palette</h3>
Specifies the palette / gradient used to plot the 2D map. This can either be colour or greyscale. The gradient is applied linearly between the Min and Max values.
<h2>6: Radiometer Data</h2>
The Radiometer Data table shows measurement results and settings at the time of measurement in tabular form.
![Radiometer Data GUI](../../../doc/img/RadioAstronomy_RadiometerData.png)
The columns in the table include:
- Date - Date at the end of the measurement.
- Time - Time at the end of the measurement.
- Power (FFT) - Power relative to fullscale (sum of FFT absolute magnitude).
- Power (dBFS) - Power in dBFS.
- Power (dBm) - Power in dBm.
- Tsys (K) - System noise temperature in Kelvin.
- Tsys0 (K) - System noise temperature (excluding Tsource) in Kelvin.
- Tsource (K) - Source noise temperature in Kelvin.
- Tb (K) - Source brightness temperature in Kelvin.
- Tsky (K) - Sky temperature in Kelvin towards the target from Star Tracker.
- Sv (Jy) - Spectral flux density in Jansky.
- sigmaTsys (K) - Standard deviation of Tsys in Kelvin.
- sigmaSsys (Jy) - Standard deviation of Sv in Jansky.
- omegaA (sr) - Antenna beam solid angle.
- omegaS (sr) - Source solid angle.
- RA - Right ascension of target from Star Tracker.
- Dec - Declination of target from Star Tracker.
- l - Galactic longitude of target from Star Tracker.
- b - Galactic latitude of target from Star Tracker.
- Az - Azimuth of target from Star Tracker.
- El - Elevation of target from Star Tracker.
- Vbcrs - Observer velocity relative to barycentric celestial reference system (BCRS).
- Vlsr - Observer velocity relative to local standard of rest (LSR).
- Solar Flux (jy) - Solar flux from Star Tracker.
- Air Temp (C) - Surface air temperature at observation point in Celsius from Star Tracker.
- Sensor 1 - Data recorded for Sensor 1.
- Sensor 2 - Data recorded for Sensor 2.
Right clicking on the table shows a popup menu that supports:
- Copying the value in the cell to the clipboard
- Deleting the selected rows
- Applying the current values of Tsys0, baseline and omega S to recalculate Tsource, Tb and Sv.
<h2>7: Calibration</h2>
Power measurements in SDRs are typically relative (E.g. dBFS) rather than absolute (E.g. dBm). In order to produce absolute power measurements,
and thus noise temperature measurements, we need to perform a calibration process that calculates a mapping from the relative power value to an absolute value.
Also, there are multiple unwanted noise sources that contribute to the measured power (LNA and receiver noise, for example),
that we wish to subtract from our power measurement, to get a measurement of the power of the radiation received from the astronomical object we are observing.
The first step is to measure the noise of the receiver, Trx. This is the combined noise of the LNA, feed line and SDR, for a particular gain setting.
This can be measured with a calibrated noise source connected to the LNA input using SDRangel's [Noise Figure plugin](https://github.com/f4exb/sdrangel/blob/master/plugins/channelrx/noisefigure/readme.md),
or estimated from datasheet values for the individual components using the [Friis formula](https://en.wikipedia.org/wiki/Friis_formulas_for_noise).
It is also possible to calculate this within the Radio Astronomy plugin by running a hot and cold calibration. The plugin will then use the Y factor method
to estimate Trx, and this will be displayed in the Trx field, below the chart. Whatever method is used, the value should be entered in to the Trx field in the Settings area.
In order to map relative powers to absolute powers (and temperatures), a hot calibration should be run. To run a hot calibration, the noise
temperature of the calibration source is entered in to the Thot field (or power into Phot) and then press the "Start hot calibration" button. (The process
is likewise to run a cold calibration). The main consideration for a user, is what can be used as a calibration source and how is it connected to the antenna/receiver.
There are two ways, with and without an antenna:
For parabolic dishes or horn antennas, an object at a known temperature can be used, so long as it completely covers the feed horn aperture.
The object needs to be as close to an ideal blackbody as possible, with high emissivity at the frequencies of interest, so that the temperature
of the object results in an identical increase in noise temperature in the antenna. If the dish is steerable to point towards the ground, the temperature
of the ground may be used.
It is also possible to calibrate by directly connecting a noise source to the LNA input. This could be as simple as 50Ohm termination resistor,
which should result in a noise temperature corresponding to the physical temperature of the resistor, assuming good impedance matching and very low insertion loss.
One large unknown can be the spillover temperature, Tsp. This is the noise contribution due to ground or building thermal radiation leaking in
to the feed horn from the back or side lobes. Once Trx is known, is possible to estimate Tsp by performing a hot and cold calibration,
where the hot calibration uses an object blocking the feed, but the cold calibration has the feed unblocked pointing to a cold part of the sky.
The temperature of the cold sky can be estimated from an all-sky survey in Star Tracker, and this is displayed under the calibration chart as Tsky.
If Thot is measured with Tsp=0, Tcold is Tsky, and Trx is known, then the plugin can estimate Tsp for the cold measurement. Note that Tsp is typically strongly
dependent on the antenna's elevation and azimuth, as this changes the amount of ground thermal radiation that gets in to the antenna.
![Calibration Settings dialog](../../../doc/img/RadioAstronomy_Calibration.png)
<h3>7.1: Show Calibration Settings Dialog</h3>
When clicked, shows the Calibration Settings dialog.
The Calibration Settings dialog allows a user to control hardware used for calibration. It supports two methods: GPIO pins in a SDR can be toggled during calibration and/or
commands/scripts can be run before and after calibration. The pre-calibration delay setting specifies a delay in seconds between the GPIO being toggled or start command
being executed, before the calibration routine in the plugin starts.
An example of its use would be to electronically switch in a 50Ohm resistor to the LNA input when calibration is run, using one of the SDR's GPIO pins to control the RF switch.
![Calibration Settings dialog](../../../doc/img/RadioAstronomy_CalibrationSettings.png)
<h3>7.2: Clear Calibration Data</h3>
Clears all calibration data.
<h3>7.3: Start Hot Calibration</h3>
Starts a measurement that will be used as the hot calibration data.
<h3>7.4: Start Cold Calibration</h3>
Starts a measurement that will be used as the cold calibration data.
<h3>7.5: Recalibrate All Measurements</h3>
When checked, results of a new calibration will be applied to all existing measurements. When unchecked, the calibration will only apply to new measurements.
<h2>API</h2>
Full details of the API can be found in the Swagger documentation. Here is a quick example of how to start a measurement from the command line:
curl -X POST "http://127.0.0.1:8091/sdrangel/deviceset/0/channel/0/actions" -d '{"channelType": "RadioAstronomy", "direction": 0, "RadioAstronomyActions": { "start": {"sampleRate": 2000000} }}'
Or to set the sample rate:
curl -X PATCH "http://127.0.0.1:8091/sdrangel/deviceset/0/channel/0/settings" -d '{"channelType": "RadioAstronomy", "direction": 0, "RadioAstronomySettings": {"sampleRate": 2000000}}'
<h2>Attribution</h2>
Many equations are from Essential Radio Astronomy by James Condon and Scott Ransom: https://www.cv.nrao.edu/~sransom/web/xxx.html
The Leiden/Argentine/Bonn (LAB) Survey of Galactic HI: https://arxiv.org/abs/astro-ph/0504140 and EU-HOU project: https://www.astro.uni-bonn.de/hisurvey/euhou/index.php
Thermometer icons are by Freepik from https://www.flaticon.com/
Reverse icon by Creaticca Creative Agency from https://www.flaticon.com/

View File

@ -16,6 +16,8 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
@ -181,9 +183,9 @@ bool GS232Controller::getOnTarget() const
{
float targetAziumth, targetElevation;
m_settings.calcTargetAzEl(targetAziumth, targetElevation);
float readTolerance = m_settings.m_tolerance + 0.5f;
bool onTarget = (abs(m_currentAzimuth - targetAziumth) < readTolerance)
&& (abs(m_currentElevation - targetElevation) < readTolerance);
float readTolerance = m_settings.m_tolerance + 0.0f;
bool onTarget = (std::fabs(m_currentAzimuth - targetAziumth) < readTolerance)
&& (std::fabs(m_currentElevation - targetElevation) < readTolerance);
return onTarget;
}

View File

@ -221,8 +221,11 @@ void GS232ControllerGUI::updatePipeList()
if (currentText.isEmpty())
{
if (m_availablePipes.size() > 0)
ui->sources->setCurrentIndex(0);
// Source feature may be loaded after this, so may not have existed when
// displaySettings was called
if (m_availablePipes.size() > 0) {
ui->sources->setCurrentIndex(ui->sources->findText(m_settings.m_source));
}
}
else
{

View File

@ -549,7 +549,7 @@ void SatelliteTrackerWorker::applyDeviceAOSSettings(const QString& name)
const MainSettings& mainSettings = mainCore->getSettings();
const std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
if (devSettings->m_deviceSetIndex < deviceSets.size())
if (devSettings->m_deviceSetIndex < (int)deviceSets.size())
{
const DeviceSet *deviceSet = deviceSets[devSettings->m_deviceSetIndex];
QString presetType;

View File

@ -6,6 +6,9 @@ set(startracker_SOURCES
startrackerplugin.cpp
startrackerworker.cpp
startrackerwebapiadapter.cpp
startracker150mhzfits.qrc
startracker408mhzfits.qrc
startracker1420mhzfits.qrc
)
set(startracker_HEADERS

View File

@ -38,11 +38,13 @@ Pressing this button displays a settings dialog, that allows you to set:
* The epoch used when entering RA and Dec. This can be either J2000 (which is used for most catalogues) or JNOW which is the current date and time.
* The units used for the display of the calculated azimuth and elevation. This can be either degrees, minutes and seconds or decimal degrees.
* Whether to correct for atmospheric refraction. You can choose either no correction, the Saemundsson algorithm, typically used for optical astronomy or the more accurate Positional Astronomy Library calculation, which can be used for >250MHz radio frequencies or light. Note that there is only a very minor difference between the two.
* Air pressure in millibars for use in refraction correction.
* Air temperature in degrees Celsius for use in refraction correction.
* Relative humidity in % for use in refraction correction.
* Height above sea level in metres for use in refraction correction.
* Temperature lapse rate in Kelvin per kilometre for use in refraction correction.
* API key for openweathermap.org which is used to download real-time weather (Air temperature, pressure and humidity) for the specified latitude (6) and longitude (7).
* How often to download weather (in minutes).
* Air pressure in millibars. This value can be automatically updated from OpenWeatherMap.
* Air temperature in degrees Celsius. This value can be automatically updated from OpenWeatherMap.
* Relative humidity in %. This value can be automatically updated from OpenWeatherMap.
* Height above sea level in metres of the observation point (anntenna location).
* Temperature lapse rate in Kelvin per kilometre.
* What data to display for the Solar flux measurement. Data can be selected from 2800 from DRAO or a number of different frequencies from Learmonth. Also, the Learmonth data can be linearly interpolated to the observation frequency set in the main window.
* The units to display the solar flux in, either Solar Flux Units, Jansky or Wm^-2Hz-1. 1 sfu equals 10,000 Jansky or 10^-22 Wm^-2Hz-1.
* The update period in seconds, which controls how frequently azimuth and elevation are re-calculated.
@ -78,7 +80,7 @@ Select a target object to track from the list.
To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom RA/Dec.
To allow Stellarium to set the RA and Dec, select Custom RA/Dec, and ensure the Stellarium Server option is checked in the Star Tracker Settings dialog.
| Target | Type | Details | Flux density (Jy) |
| Target | Type | Details | Flux density (Jy) or Temperature (K) |
|------------------|-------------------|------------------------------------------------|---------------------------------------------
| Sun | Star | Targets our Sun | 10k-10M (50MHz), 500k-10M (1.4GHz) |
| Moon | Moon | Targets our Moon | 2 (50MHz), 1000 (1.4GHz) |
@ -91,12 +93,17 @@ To allow Stellarium to set the RA and Dec, select Custom RA/Dec, and ensure the
| Virgo A (M87) | Galaxy | | 2635 (50MHz), 1209 (150MHz), 212 (1.4GHz) |
| Custom RA/Dec | | Manually enter RA and Dec | |
| Custom Az/El | | Manually enter azimuth and elevation | |
| Custom l/b | | Manually enter Galactic longitude and latitude | |
| S7 | HI | IAU secondary calibration region (l=132,b=-1) | Tb=100 peak |
| S8 | HI | IAU primary calibration region (l=207,b=-15) | Tb=72 peak |
| S9 | HI | IAU secondary calibration region (l=356,b=-4) | Tb=85 peak |
References:
* ATNF Pulsar Catalogue - https://www.atnf.csiro.au/research/pulsar/psrcat/
* Cassiopeia A, Cygnus A, Taurus A, and Virgo A at ultra-low radio frequencies - https://research.chalmers.se/publication/516438/file/516438_Fulltext.pdf
* Repeating Jansky - https://www.gb.nrao.edu/~fghigo/JanskyAntenna/RepeatingJansky_memo10.pdf
* Studies of four regions for use as standards in 21CM observations - http://articles.adsabs.harvard.edu/pdf/1973A%26AS....8..505W
<h3>12: Frequency</h3>
@ -110,33 +117,45 @@ Enter the half power (-3dB) beamwidth of your antenna. This value is used for sk
When target is set to Custom RA/Dec, you can specify the right ascension in hours of the target object. This can be specified as a decimal (E.g. 12.23, from 0 to 24) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog.
When target is set to Custom Az/El, this will display the corresponding right ascension.
When target is set to Custom Az/El or Custom l/b, this will display the corresponding right ascension.
<h3>15: Declination</h3>
When target is set to Custom RA/Dec, you can specify the declination in degrees of the target object. This can be specified as a decimal (E.g. 34.6, from -90.0 to 90.0) or in degrees, minutes and seconds (E.g. 34d12m5.6s, 34d12'5.6" 34 12 5.6). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog.
When target is set to Custom Az/El, this will display the corresponding declination.
When target is set to Custom Az/El or Custom l/b, this will display the corresponding declination.
<h3>16: Azimuth</h3>
When target is set to Custom Az/El, you specify the azimuth in degrees of the target object. The corresponding RA/Dec will be calculated and displayed.
When target is set to Custom Az/El, you specify the azimuth in degrees of the target object. The corresponding RA/Dec and l/b will be calculated and displayed.
For all other target settings, this displays the calculated azimuth (angle in degrees, clockwise from North) to the object.
<h3>17: Elevation</h3>
When target is set to Custom Az/El, you specify the elevation in degrees of the target object. The corresponding RA/Dec will be calculated and displayed.
When target is set to Custom Az/El, you specify the elevation in degrees of the target object. The corresponding RA/Dec and l/b will be calculated and displayed.
For all other target settings, this displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object.
<h3>18: b - Galactic latitude</h3>
<h3>18: Az Offset</h3>
Displays the calculated galactic latitude (angle in degrees, positive to the North of the galactic plane) to the object.
An offset in degrees that is added to the computed target azimuth.
<h3>19: l - Galactic longitude</h3>
<h3>19: El Offset</h3>
Displays the calculated galactic longitude (angle in degrees, Eastward from the galactic centre) to the object.
An offset in degrees that is added to the computed target elevation.
<h3>20: l - Galactic Longitude</h3>
When the target is set to Custom l/b, you specify the galactic longitude (angle in degrees, Eastward from the galactic centre) of the target object.
For all other target settings, this sisplays the calculated galactic longitude to the object.
<h3>21: b - Galactic Latitude</h3>
When the target is set to Custom l/b, you specify the galactic lattitude (angle in degrees) of the target object.
For all other target settings, displays the calculated galactic latitude to the object.
<h2>Plots</h2>
@ -169,6 +188,8 @@ This temperature is therefore valid for a beamwidth of less than 1 degree.
The Star Tracker plugin can also estimate a sky temperature based on the user entered observation frequency and beamwidth.
To see this figure, which will be typically lower than the above, select one of the last two temperature maps from the right hand combo box.
The position of the Sun and Moon can also be drawn on the chart. Note that the sky temperature estimate does not take these in to account.
<h3>Drift scan path</h3>
When the target (11) is set to Custom Az/El and the Sky temperature plot is displayed, a curve showing the drift scan path over a 24 hour period will be displayed.
@ -193,6 +214,18 @@ Two images of the Milky Way are availble: a purely graphical image and one annot
The image can be zoomed in to or out from using the mouse wheel or the buttons. Hold CTRL to pan with the mouse wheel.
<h4>Spiral Arm Animations</h4>
When used with the Radio Astronomy plugin, markers corresponding to the position of HI clouds calculated from a marker on the spectrogram, can be plotted to display the estimated position of the cloud.
An animated PNG file can then be created from multiple plots to show how the markers follow the positions of Milky Way's spiral arms as galactic longitude is varied.
This process requires a marker to be placed on a peak in the spectrogram and then the ![add to animation](../../../doc/img/StarTracker_add_to_animation.png) button to be pressed to add the current plot to the animation.
The process then repeats, by selecting the next measurement at a different longitude in the spectrogram and marking the appropriate peak, and then adding it to the animation.
When all frames have been added, the animation can be saved to a PNG file by pressing ![save animation](../../../doc/img/StarTracker_save_animation.png).
To start a new animation, press ![clear animation](../../../doc/img/StarTracker_clear_animation.png).
![StarTracker spiral arm](../../../doc/img/StarTracker_spiral_arm.png)
<h2>Map</h2>
The Star Tracker feature can send the overhead position of the Sun, Moon and target Star to the Map. These can be enabled individually in the settings dialog. The Moon should be displayed with an approximate phase. Stars (or galaxies) are displayed as an image of a pulsar.
@ -241,7 +274,7 @@ Solar radio flux measurements at 245, 410, 610, 1415, 2695, 4995, 8800 and 15400
Milky Way image from NASA/JPL-Caltech: https://photojournal.jpl.nasa.gov/catalog/PIA10748
Icons are by Adnen Kadri and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
Icons are by Adnen Kadri, iconsphere and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
Icons are by Freepik from Flaticon https://www.flaticon.com/

View File

@ -25,14 +25,20 @@
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "SWGDeviceState.h"
#include "SWGStarTrackerDisplaySettings.h"
#include "dsp/dspengine.h"
#include "util/weather.h"
#include "util/units.h"
#include "maincore.h"
#include "startrackerreport.h"
#include "startrackerworker.h"
#include "startracker.h"
MESSAGE_CLASS_DEFINITION(StarTracker::MsgConfigureStarTracker, Message)
MESSAGE_CLASS_DEFINITION(StarTracker::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(StarTracker::MsgSetSolarFlux, Message)
const char* const StarTracker::m_featureIdURI = "sdrangel.feature.startracker";
const char* const StarTracker::m_featureId = "StarTracker";
@ -45,8 +51,17 @@ StarTracker::StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface) :
m_worker = new StarTrackerWorker(this, webAPIAdapterInterface);
m_state = StIdle;
m_errorMessage = "StarTracker error";
connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes()));
m_updatePipesTimer.start(1000);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
m_weather = nullptr;
m_solarFlux = 0.0f;
// Unfortunately, can't seem to access resources in static global constructor
m_temps.append(new FITS(":/startracker/startracker/150mhz_ra_dec.fits"));
m_temps.append(new FITS(":/startracker/startracker/408mhz_ra_dec.fits"));
m_temps.append(new FITS(":/startracker/startracker/1420mhz_ra_dec.fits"));
m_spectralIndex = new FITS(":/startracker/startracker/408mhz_ra_dec_spectral_index.fits");
}
StarTracker::~StarTracker()
@ -58,6 +73,13 @@ StarTracker::~StarTracker()
}
delete m_worker;
if (m_weather)
{
disconnect(m_weather, &Weather::weatherUpdated, this, &StarTracker::weatherUpdated);
delete m_weather;
}
qDeleteAll(m_temps);
delete m_spectralIndex;
}
void StarTracker::start()
@ -71,8 +93,8 @@ void StarTracker::start()
m_state = ok ? StRunning : StError;
m_thread.start();
StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::create(m_settings, true);
m_worker->getInputMessageQueue()->push(msg);
m_worker->getInputMessageQueue()->push(StarTrackerWorker::MsgConfigureStarTrackerWorker::create(m_settings, true));
m_worker->getInputMessageQueue()->push(MsgSetSolarFlux::create(m_solarFlux));
}
void StarTracker::stop()
@ -107,12 +129,52 @@ bool StarTracker::handleMessage(const Message& cmd)
return true;
}
else if (MsgSetSolarFlux::match(cmd))
{
MsgSetSolarFlux& msg = (MsgSetSolarFlux&) cmd;
m_solarFlux = msg.getFlux();
m_worker->getInputMessageQueue()->push(new MsgSetSolarFlux(msg));
return true;
}
else if (MainCore::MsgStarTrackerDisplaySettings::match(cmd))
{
MainCore::MsgStarTrackerDisplaySettings& settings = (MainCore::MsgStarTrackerDisplaySettings&) cmd;
if (m_guiMessageQueue) {
m_guiMessageQueue->push(new MainCore::MsgStarTrackerDisplaySettings(settings));
}
return true;
}
else if (MainCore::MsgStarTrackerDisplayLoSSettings::match(cmd))
{
MainCore::MsgStarTrackerDisplayLoSSettings& settings = (MainCore::MsgStarTrackerDisplayLoSSettings&) cmd;
if (m_guiMessageQueue) {
m_guiMessageQueue->push(new MainCore::MsgStarTrackerDisplayLoSSettings(settings));
}
return true;
}
else
{
return false;
}
}
void StarTracker::updatePipes()
{
QList<AvailablePipeSource> availablePipes = updateAvailablePipeSources("startracker.display", StarTrackerSettings::m_pipeTypes, StarTrackerSettings::m_pipeURIs, this);
if (availablePipes != m_availablePipes)
{
m_availablePipes = availablePipes;
if (getMessageQueueToGUI())
{
MsgReportPipes *msgToGUI = MsgReportPipes::create();
QList<AvailablePipeSource>& msgAvailablePipes = msgToGUI->getAvailablePipes();
msgAvailablePipes.append(availablePipes);
getMessageQueueToGUI()->push(msgToGUI);
}
}
}
QByteArray StarTracker::serialize() const
{
return m_settings.serialize();
@ -143,6 +205,10 @@ void StarTracker::applySettings(const StarTrackerSettings& settings, bool force)
<< " m_dec: " << settings.m_dec
<< " m_az: " << settings.m_az
<< " m_el: " << settings.m_el
<< " m_l: " << settings.m_l
<< " m_b: " << settings.m_b
<< " m_azOffset: " << settings.m_azOffset
<< " m_elOffset: " << settings.m_elOffset
<< " m_latitude: " << settings.m_latitude
<< " m_longitude: " << settings.m_longitude
<< " m_serverPort: " << settings.m_serverPort
@ -215,6 +281,52 @@ void StarTracker::applySettings(const StarTrackerSettings& settings, bool force)
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
if ((m_settings.m_az != settings.m_az) || force) {
reverseAPIKeys.append("azimuth");
}
if ((m_settings.m_el != settings.m_el) || force) {
reverseAPIKeys.append("elevation");
}
if ((m_settings.m_l != settings.m_l) || force) {
reverseAPIKeys.append("l");
}
if ((m_settings.m_b != settings.m_b) || force) {
reverseAPIKeys.append("b");
}
if ((m_settings.m_azOffset != settings.m_azOffset) || force) {
reverseAPIKeys.append("azimuthOffset");
}
if ((m_settings.m_elOffset != settings.m_elOffset) || force) {
reverseAPIKeys.append("elevationOffset");
}
if ((m_settings.m_owmAPIKey != settings.m_owmAPIKey) || force)
{
if (m_weather)
{
disconnect(m_weather, &Weather::weatherUpdated, this, &StarTracker::weatherUpdated);
delete m_weather;
m_weather = nullptr;
}
if (!settings.m_owmAPIKey.isEmpty())
{
m_weather = Weather::create(settings.m_owmAPIKey);
if (m_weather) {
connect(m_weather, &Weather::weatherUpdated, this, &StarTracker::weatherUpdated);
}
}
}
if ( (m_settings.m_owmAPIKey != settings.m_owmAPIKey)
|| (m_settings.m_latitude != settings.m_latitude)
|| (m_settings.m_longitude != settings.m_longitude)
|| (m_settings.m_weatherUpdatePeriod != settings.m_weatherUpdatePeriod)
|| force)
{
if (m_weather) {
m_weather->getWeatherPeriodically(m_settings.m_latitude, m_settings.m_longitude, settings.m_weatherUpdatePeriod);
}
}
StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::create(
settings, force
@ -320,6 +432,13 @@ void StarTracker::webapiFormatFeatureSettings(
response.getStarTrackerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getStarTrackerSettings()->setReverseApiFeatureSetIndex(settings.m_reverseAPIFeatureSetIndex);
response.getStarTrackerSettings()->setReverseApiFeatureIndex(settings.m_reverseAPIFeatureIndex);
response.getStarTrackerSettings()->setAzimuth(settings.m_az);
response.getStarTrackerSettings()->setElevation(settings.m_el);
response.getStarTrackerSettings()->setL(settings.m_l);
response.getStarTrackerSettings()->setB(settings.m_b);
response.getStarTrackerSettings()->setAzimuthOffset(settings.m_azOffset);
response.getStarTrackerSettings()->setElevationOffset(settings.m_elOffset);
}
void StarTracker::webapiUpdateFeatureSettings(
@ -396,6 +515,24 @@ void StarTracker::webapiUpdateFeatureSettings(
if (featureSettingsKeys.contains("reverseAPIFeatureIndex")) {
settings.m_reverseAPIFeatureIndex = response.getStarTrackerSettings()->getReverseApiFeatureIndex();
}
if (featureSettingsKeys.contains("azimuth")) {
settings.m_az = response.getStarTrackerSettings()->getAzimuth();
}
if (featureSettingsKeys.contains("elevation")) {
settings.m_el = response.getStarTrackerSettings()->getElevation();
}
if (featureSettingsKeys.contains("l")) {
settings.m_l = response.getStarTrackerSettings()->getL();
}
if (featureSettingsKeys.contains("b")) {
settings.m_b = response.getStarTrackerSettings()->getB();
}
if (featureSettingsKeys.contains("azimuthOffset")) {
settings.m_azOffset = response.getStarTrackerSettings()->getAzimuthOffset();
}
if (featureSettingsKeys.contains("elevationOffset")) {
settings.m_elOffset = response.getStarTrackerSettings()->getElevationOffset();
}
}
void StarTracker::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const StarTrackerSettings& settings, bool force)
@ -463,6 +600,24 @@ void StarTracker::webapiReverseSendSettings(QList<QString>& featureSettingsKeys,
if (featureSettingsKeys.contains("rgbColor") || force) {
swgStarTrackerSettings->setRgbColor(settings.m_rgbColor);
}
if (featureSettingsKeys.contains("azimuth") || force) {
swgStarTrackerSettings->setAzimuth(settings.m_az);
}
if (featureSettingsKeys.contains("elevation") || force) {
swgStarTrackerSettings->setElevation(settings.m_el);
}
if (featureSettingsKeys.contains("l") || force) {
swgStarTrackerSettings->setL(settings.m_l);
}
if (featureSettingsKeys.contains("b") || force) {
swgStarTrackerSettings->setB(settings.m_b);
}
if (featureSettingsKeys.contains("azimuthOffset") || force) {
swgStarTrackerSettings->setAzimuthOffset(settings.m_azOffset);
}
if (featureSettingsKeys.contains("elevationOffset") || force) {
swgStarTrackerSettings->setElevationOffset(settings.m_elOffset);
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
.arg(settings.m_reverseAPIAddress)
@ -504,3 +659,172 @@ void StarTracker::networkManagerFinished(QNetworkReply *reply)
reply->deleteLater();
}
void StarTracker::weatherUpdated(float temperature, float pressure, float humidity)
{
if (!std::isnan(temperature)) {
m_settings.m_temperature = temperature;
}
if (!std::isnan(pressure)) {
m_settings.m_pressure = pressure;
}
if (!std::isnan(humidity)) {
m_settings.m_humidity = humidity;
}
m_worker->getInputMessageQueue()->push(StarTrackerWorker::MsgConfigureStarTrackerWorker::create(m_settings, false));
if (m_guiMessageQueue) {
m_guiMessageQueue->push(MsgConfigureStarTracker::create(m_settings, false));
}
}
double StarTracker::applyBeam(const FITS *fits, double beamwidth, double ra, double dec, int& imgX, int& imgY) const
{
const double halfBeamwidth = beamwidth/2.0;
// Use cos^p(x) for approximation of radiation pattern
// (Essentially the same as Gaussian of exp(-4*ln(theta^2/beamwidth^2))
// (See a2 in https://arxiv.org/pdf/1812.10084.pdf for Elliptical equivalent))
// We have gain of 0dB (1) at 0 degrees, and -3dB (~0.5) at half-beamwidth degrees
// Find exponent that correponds to -3dB at that angle
double minus3dBLinear = pow(10.0, -3.0/10.0);
double p = log(minus3dBLinear)/log(cos(Units::degreesToRadians(halfBeamwidth)));
// Create an matrix with gain as a function of angle
double degreesPerPixelH = abs(fits->degreesPerPixelH());
double degreesPerPixelV = abs(fits->degreesPerPixelV());
int numberOfCoeffsH = ceil(beamwidth/degreesPerPixelH);
int numberOfCoeffsV = ceil(beamwidth/degreesPerPixelV);
if ((numberOfCoeffsH & 1) == 0) {
numberOfCoeffsH++;
}
if ((numberOfCoeffsV & 1) == 0) {
numberOfCoeffsV++;
}
double *beam = new double[numberOfCoeffsH*numberOfCoeffsV];
double sum = 0.0;
int y0 = numberOfCoeffsV/2;
int x0 = numberOfCoeffsH/2;
int nonZeroCount = 0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
double xp = (x - x0) * degreesPerPixelH;
double yp = (y - y0) * degreesPerPixelV;
double r = sqrt(xp*xp+yp*yp);
if (r < halfBeamwidth)
{
beam[y*numberOfCoeffsH+x] = pow(cos(Units::degreesToRadians(r)), p);
sum += beam[y*numberOfCoeffsH+x];
nonZeroCount++;
}
else
{
beam[y*numberOfCoeffsH+x] = 0.0;
}
}
}
// Get centre pixel coordinates
double centreX;
if (ra <= 12.0) {
centreX = (12.0 - ra) / 24.0;
} else {
centreX = (24 - ra + 12) / 24.0;
}
double centreY = (90.0-dec) / 180.0;
imgX = centreX * fits->width();
imgY = centreY * fits->height();
// Apply weighting to temperature data
double weightedSum = 0.0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
weightedSum += beam[y*numberOfCoeffsH+x] * fits->scaledWrappedValue(imgX + (x-x0), imgY + (y-y0));
}
}
// From: https://www.cv.nrao.edu/~sransom/web/Ch3.html
// The antenna temperature equals the source brightness temperature multiplied by the fraction of the beam solid angle filled by the source
// So we scale the sum by the total number of non-zero pixels (i.e. beam area)
// If we compare to some maps with different beamwidths here: https://www.cv.nrao.edu/~demerson/radiosky/sky_jun96.pdf
// The values we've computed are a bit higher..
double temp = weightedSum/nonZeroCount;
delete[] beam;
return temp;
}
bool StarTracker::calcSkyTemperature(double frequency, double beamwidth, double ra, double dec, double& temp) const
{
const FITS *fits;
int imgX, imgY;
if ((frequency >= 1.4e9) && (frequency <= 1.45e9))
{
// Adjust temperature from 1420MHz FITS file, just using beamwidth
fits = getTempFITS(2);
if (fits && fits->valid())
{
temp = applyBeam(fits, beamwidth, ra, dec, imgX, imgY);
return true;
}
else
{
qDebug() << "StarTracker::calcSkyTemperature: 1420MHz FITS temperature file not valid";
return false;
}
}
else
{
// Adjust temperature from 408MHz FITS file, taking in to account
// observation frequency and beamwidth
fits = getTempFITS(1);
if (fits && fits->valid())
{
double temp408 = applyBeam(fits, beamwidth, ra, dec, imgX, imgY);
// Scale according to frequency - CMB contribution constant
// Power law at low frequencies, with slight variation in spectral index
// See:
// Global Sky Model: https://ascl.net/1011.010
// An improved Model of Diffuse Galactic Radio Emission: https://arxiv.org/pdf/1605.04920.pdf
// A high-resolution self-consistent whole sky foreground model: https://arxiv.org/abs/1812.10084
// (De-striping:) Full sky study of diffuse Galactic emission at decimeter wavelength https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
// Data here: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
// LFmap: https://www.faculty.ece.vt.edu/swe/lwa/memo/lwa0111.pdf
double iso408 = 50 * pow(150e6/408e6, 2.75); // Extra-galactic isotropic in reference map at 408MHz
double isoT = 50 * pow(150e6/frequency, 2.75); // Extra-galactic isotropic at target frequency
double cmbT = 2.725; // Cosmic microwave backgroud;
double spectralIndex;
const FITS *spectralIndexFITS = getSpectralIndexFITS();
if (spectralIndexFITS && spectralIndexFITS->valid())
{
// See https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
spectralIndex = spectralIndexFITS->scaledValue(imgX, imgY);
}
else
{
// See https://arxiv.org/abs/1812.10084 fig 2
if (frequency < 200e6) {
spectralIndex = 2.55;
} else if (frequency < 20e9) {
spectralIndex = 2.695;
} else {
spectralIndex = 3.1;
}
}
double galactic480 = temp408 - cmbT - iso408;
double galacticT = galactic480 * pow(408e6/frequency, spectralIndex); // Scale galactic contribution by frequency
temp = galacticT + cmbT + isoT; // Final temperature
return true;
}
else
{
qDebug() << "StarTracker::calcSkyTemperature: 408MHz FITS temperature file not valid";
return false;
}
}
}

View File

@ -21,9 +21,11 @@
#include <QThread>
#include <QNetworkRequest>
#include <QTimer>
#include "feature/feature.h"
#include "util/message.h"
#include "util/fits.h"
#include "startrackersettings.h"
@ -31,6 +33,7 @@ class WebAPIAdapterInterface;
class StarTrackerWorker;
class QNetworkAccessManager;
class QNetworkReply;
class Weather;
namespace SWGSDRangel {
class SWGDeviceState;
@ -81,6 +84,25 @@ public:
{ }
};
class MsgSetSolarFlux : public Message {
MESSAGE_CLASS_DECLARATION
public:
float getFlux() const { return m_flux; }
static MsgSetSolarFlux* create(float flux) {
return new MsgSetSolarFlux(flux);
}
protected:
float m_flux;
MsgSetSolarFlux(float flux) :
Message(),
m_flux(flux)
{ }
};
StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~StarTracker();
virtual void destroy() { delete this; }
@ -94,17 +116,17 @@ public:
virtual int webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
QString& errorMessage) override;
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
QString& errorMessage) override;
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
QString& errorMessage) override;
static void webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
@ -115,6 +137,10 @@ public:
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
const FITS *getTempFITS(int index) const { return m_temps[index]; }
const FITS *getSpectralIndexFITS() const { return m_spectralIndex; }
bool calcSkyTemperature(double frequency, double beamwidth, double ra, double dec, double& temp) const;
static const char* const m_featureIdURI;
static const char* const m_featureId;
@ -125,14 +151,24 @@ private:
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
QList<AvailablePipeSource> m_availablePipes;
QTimer m_updatePipesTimer;
Weather *m_weather;
float m_solarFlux;
QList<FITS*> m_temps;
FITS *m_spectralIndex;
void start();
void stop();
void applySettings(const StarTrackerSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const StarTrackerSettings& settings, bool force);
double applyBeam(const FITS *fits, double beamwidth, double ra, double dec, int& imgX, int& imgY) const;
private slots:
void updatePipes();
void networkManagerFinished(QNetworkReply *reply);
void weatherUpdated(float temperature, float pressure, float humidity);
};
#endif // INCLUDE_FEATURE_STARTRACKER_H_

View File

@ -14,5 +14,6 @@
<file>startracker/pulsar-32.png</file>
<file>startracker/sun-40.png</file>
<file>startracker/sun-button-24.png</file>
<file>startracker/moon-button-24.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 B

View File

@ -2,6 +2,5 @@
<qresource prefix="/startracker/">
<file>startracker/1420mhz_ra_dec.png</file>
<file>startracker/1420mhz_galactic.png</file>
<file>startracker/1420mhz_ra_dec.fits</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/startracker/">
<file>startracker/1420mhz_ra_dec.fits</file>
</qresource>
</RCC>

View File

@ -2,6 +2,5 @@
<qresource prefix="/startracker/">
<file>startracker/150mhz_ra_dec.png</file>
<file>startracker/150mhz_galactic.png</file>
<file>startracker/150mhz_ra_dec.fits</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/startracker/">
<file>startracker/150mhz_ra_dec.fits</file>
</qresource>
</RCC>

View File

@ -2,7 +2,6 @@
<qresource prefix="/startracker/">
<file>startracker/408mhz_ra_dec.png</file>
<file>startracker/408mhz_galactic.png</file>
<file>startracker/408mhz_ra_dec.fits</file>
<file>startracker/408mhz_ra_dec_spectral_index.fits</file>
</qresource>
</RCC>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/startracker/">
<file>startracker/408mhz_ra_dec.fits</file>
</qresource>
</RCC>

View File

@ -30,6 +30,9 @@
#include <QtCharts/QDateTimeAxis>
#include <QtCharts/QValueAxis>
#include "SWGStarTrackerDisplaySettings.h"
#include "SWGStarTrackerDisplayLoSSettings.h"
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "gui/basicfeaturesettingsdialog.h"
@ -39,6 +42,8 @@
#include "device/deviceuiset.h"
#include "util/units.h"
#include "util/astronomy.h"
#include "util/interpolation.h"
#include "util/png.h"
#include "ui_startrackergui.h"
#include "startracker.h"
@ -46,18 +51,6 @@
#include "startrackerreport.h"
#include "startrackersettingsdialog.h"
// Linear extrapolation
static double extrapolate(double x0, double y0, double x1, double y1, double x)
{
return y0 + ((x-x0)/(x1-x0)) * (y1-y0);
}
// Linear interpolation
static double interpolate(double x0, double y0, double x1, double y1, double x)
{
return (y0*(x1-x) + y1*(x-x0)) / (x1-x0);
}
StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
StarTrackerGUI* gui = new StarTrackerGUI(pluginAPI, featureUISet, feature);
@ -66,6 +59,7 @@ StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featu
void StarTrackerGUI::destroy()
{
qDeleteAll(m_lineOfSightMarkers);
delete this;
}
@ -121,13 +115,94 @@ bool StarTrackerGUI::handleMessage(const Message& message)
else if (StarTrackerReport::MsgReportRADec::match(message))
{
StarTrackerReport::MsgReportRADec& raDec = (StarTrackerReport::MsgReportRADec&) message;
m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDec.getRA());
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec());
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
QString target = raDec.getTarget();
if (target == "target")
{
m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDec.getRA());
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec());
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
}
else if (target == "sun")
{
m_sunRA = raDec.getRA();
m_sunDec = raDec.getDec();
}
else if (target == "moon")
{
m_moonRA = raDec.getRA();
m_moonDec = raDec.getDec();
}
raDecChanged();
return true;
}
else if (StarTrackerReport::MsgReportGalactic::match(message))
{
StarTrackerReport::MsgReportGalactic& galactic = (StarTrackerReport::MsgReportGalactic&) message;
blockApplySettings(true);
ui->galacticLongitude->setValue(galactic.getL());
ui->galacticLatitude->setValue(galactic.getB());
blockApplySettings(false);
return true;
}
else if (MainCore::MsgStarTrackerDisplaySettings::match(message))
{
if (m_settings.m_link)
{
MainCore::MsgStarTrackerDisplaySettings& settings = (MainCore::MsgStarTrackerDisplaySettings&) message;
SWGSDRangel::SWGStarTrackerDisplaySettings *swgSettings = settings.getSWGStarTrackerDisplaySettings();
ui->dateTimeSelect->setCurrentText("Custom");
QDateTime dt = QDateTime::fromString(*swgSettings->getDateTime(), Qt::ISODateWithMs);
ui->dateTime->setDateTime(dt);
ui->target->setCurrentText("Custom Az/El");
ui->azimuth->setValue(swgSettings->getAzimuth());
ui->elevation->setValue(swgSettings->getElevation());
}
return true;
}
else if (MainCore::MsgStarTrackerDisplayLoSSettings::match(message))
{
MainCore::MsgStarTrackerDisplayLoSSettings& settings = (MainCore::MsgStarTrackerDisplayLoSSettings&) message;
SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgSettings = settings.getSWGStarTrackerDisplayLoSSettings();
bool found = false;
for (int i = 0; i < m_lineOfSightMarkers.size(); i++)
{
if (m_lineOfSightMarkers[i]->m_name == swgSettings->getName())
{
if (swgSettings->getD() == 0.0)
{
// Delete
ui->image->scene()->removeItem(m_lineOfSightMarkers[i]->m_text);
delete m_lineOfSightMarkers[i]->m_text;
delete m_lineOfSightMarkers[i];
m_lineOfSightMarkers.removeAt(i);
}
else
{
// Update
m_lineOfSightMarkers[i]->m_l = swgSettings->getL();
m_lineOfSightMarkers[i]->m_b = swgSettings->getB();
m_lineOfSightMarkers[i]->m_d = swgSettings->getD();
plotGalacticMarker(m_lineOfSightMarkers[i]);
}
found = true;
break;
}
}
if (!found && (swgSettings->getD() != 0.0))
{
// Create new
LoSMarker* marker = new LoSMarker();
marker->m_name = *swgSettings->getName();
marker->m_l = swgSettings->getL();
marker->m_b = swgSettings->getB();
marker->m_d = swgSettings->getD();
marker->m_text = ui->image->scene()->addText(marker->m_name);
m_lineOfSightMarkers.append(marker);
plotGalacticMarker(marker);
}
return true;
}
return false;
}
@ -159,6 +234,7 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
m_lastFeatureState(0),
m_azElLineChart(nullptr),
m_azElPolarChart(nullptr),
m_solarFluxChart(nullptr),
m_networkManager(nullptr),
m_solarFlux(0.0),
m_solarFluxesValid(false),
@ -168,12 +244,12 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
QImage(":/startracker/startracker/408mhz_galactic.png"),
QImage(":/startracker/startracker/1420mhz_ra_dec.png"),
QImage(":/startracker/startracker/1420mhz_galactic.png")},
m_temps{FITS(":/startracker/startracker/150mhz_ra_dec.fits"),
FITS(":/startracker/startracker/408mhz_ra_dec.fits"),
FITS(":/startracker/startracker/1420mhz_ra_dec.fits")},
m_spectralIndex(":/startracker/startracker/408mhz_ra_dec_spectral_index.fits"),
m_milkyWayImages{QPixmap(":/startracker/startracker/milkyway.png"),
QPixmap(":/startracker/startracker/milkywayannotated.png")}
QPixmap(":/startracker/startracker/milkywayannotated.png")},
m_sunRA(0.0),
m_sunDec(0.0),
m_moonRA(0.0),
m_moonDec(0.0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
@ -197,8 +273,6 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
ui->elevation->setRange(-90.0, 90.0);
ui->galacticLongitude->setRange(0, 360.0);
ui->galacticLatitude->setRange(-90.0, 90.0);
ui->galacticLatitude->setButtonSymbols(QAbstractSpinBox::NoButtons);
ui->galacticLongitude->setButtonSymbols(QAbstractSpinBox::NoButtons);
ui->galacticLatitude->setText("");
ui->galacticLongitude->setText("");
@ -211,16 +285,6 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
m_chart.layout()->setContentsMargins(0, 0, 0, 0);
m_chart.setMargins(QMargins(1, 1, 1, 1));
m_solarFluxChart.setTitle("");
m_solarFluxChart.legend()->hide();
m_solarFluxChart.addAxis(&m_chartSolarFluxXAxis, Qt::AlignBottom);
m_solarFluxChart.addAxis(&m_chartSolarFluxYAxis, Qt::AlignLeft);
m_solarFluxChart.layout()->setContentsMargins(0, 0, 0, 0);
m_solarFluxChart.setMargins(QMargins(1, 1, 1, 1));
m_chartSolarFluxXAxis.setTitleText(QString("Frequency (MHz)"));
m_chartSolarFluxXAxis.setMinorTickCount(-1);
m_chartSolarFluxYAxis.setTitleText(QString("Solar flux density (%1)").arg(solarFluxUnit()));
// Create axes that are static
m_skyTempGalacticLXAxis.setTitleText(QString("Galactic longitude (%1)").arg(QChar(0xb0)));
@ -260,6 +324,8 @@ StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet,
// Populate subchart menu
on_chartSelect_currentIndexChanged(0);
connect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
// Use My Position from preferences, if none set
if ((m_settings.m_latitude == 0.0) && (m_settings.m_longitude == 0.0))
on_useMyPosition_clicked();
@ -313,8 +379,13 @@ void StarTrackerGUI::displaySettings()
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
ui->darkTheme->setChecked(m_settings.m_chartsDarkTheme);
m_solarFluxChart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
if (m_solarFluxChart) {
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
}
m_chart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
ui->drawSun->setChecked(m_settings.m_drawSunOnSkyTempChart);
ui->drawMoon->setChecked(m_settings.m_drawMoonOnSkyTempChart);
ui->link->setChecked(m_settings.m_link);
ui->latitude->setValue(m_settings.m_latitude);
ui->longitude->setValue(m_settings.m_longitude);
ui->target->setCurrentIndex(ui->target->findText(m_settings.m_target));
@ -322,17 +393,27 @@ void StarTrackerGUI::displaySettings()
ui->elevation->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLatitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->galacticLongitude->setUnits((DMSSpinBox::DisplayUnits)m_settings.m_azElUnits);
ui->azimuthOffset->setValue(m_settings.m_azOffset);
ui->elevationOffset->setValue(m_settings.m_elOffset);
if (m_settings.m_target == "Custom RA/Dec")
{
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
updateGalacticCoords();
}
else if (m_settings.m_target == "Custom Az/El")
{
ui->azimuth->setValue(m_settings.m_az);
ui->elevation->setValue(m_settings.m_el);
}
else if ( (m_settings.m_target == "Custom l/b")
|| (m_settings.m_target == "S7")
|| (m_settings.m_target == "S8")
|| (m_settings.m_target == "S9")
)
{
ui->galacticLatitude->setValue(m_settings.m_b);
ui->galacticLongitude->setValue(m_settings.m_l);
}
if (m_settings.m_dateTime == "")
{
ui->dateTimeSelect->setCurrentIndex(0);
@ -417,21 +498,10 @@ void StarTrackerGUI::on_longitude_valueChanged(double value)
plotChart();
}
void StarTrackerGUI::updateGalacticCoords()
{
float ra = Astronomy::raToDecimal(m_settings.m_ra);
float dec = Astronomy::decToDecimal(m_settings.m_dec);
double l, b;
Astronomy::equatorialToGalactic(ra, dec, l, b);
ui->galacticLatitude->setValue(b);
ui->galacticLongitude->setValue(l);
}
void StarTrackerGUI::on_rightAscension_editingFinished()
{
m_settings.m_ra = ui->rightAscension->text();
applySettings();
updateGalacticCoords();
plotChart();
}
@ -439,7 +509,6 @@ void StarTrackerGUI::on_declination_editingFinished()
{
m_settings.m_dec = ui->declination->text();
applySettings();
updateGalacticCoords();
plotChart();
}
@ -457,6 +526,34 @@ void StarTrackerGUI::on_elevation_valueChanged(double value)
plotChart();
}
void StarTrackerGUI::on_azimuthOffset_valueChanged(double value)
{
m_settings.m_azOffset = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_elevationOffset_valueChanged(double value)
{
m_settings.m_elOffset = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_galacticLatitude_valueChanged(double value)
{
m_settings.m_b = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_galacticLongitude_valueChanged(double value)
{
m_settings.m_l = value;
applySettings();
plotChart();
}
void StarTrackerGUI::updateForTarget()
{
if (m_settings.m_target == "Sun")
@ -478,6 +575,21 @@ void StarTrackerGUI::updateForTarget()
ui->rightAscension->setReadOnly(false);
ui->declination->setReadOnly(false);
}
else if (m_settings.m_target == "S7")
{
ui->galacticLatitude->setValue(-1.0);
ui->galacticLongitude->setValue(132.0);
}
else if (m_settings.m_target == "S8")
{
ui->galacticLatitude->setValue(-15.0);
ui->galacticLongitude->setValue(207.0);
}
else if (m_settings.m_target == "S9")
{
ui->galacticLatitude->setValue(-4.0);
ui->galacticLongitude->setValue(356.0);
}
else
{
ui->rightAscension->setReadOnly(true);
@ -607,6 +719,12 @@ void StarTrackerGUI::applySettings(bool force)
}
}
void StarTrackerGUI::on_link_clicked(bool checked)
{
m_settings.m_link = checked;
applySettings();
}
void StarTrackerGUI::on_useMyPosition_clicked(bool checked)
{
(void) checked;
@ -691,7 +809,6 @@ void StarTrackerGUI::plotChart()
void StarTrackerGUI::raDecChanged()
{
updateGalacticCoords();
if (ui->chartSelect->currentIndex() == 2) {
plotSkyTemperatureChart();
} else if (ui->chartSelect->currentIndex() == 3) {
@ -724,13 +841,28 @@ void StarTrackerGUI::plotSolarFluxChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_solarFluxChart;
m_solarFluxChart = new QChart();
m_solarFluxChart.removeAllSeries();
if (m_solarFluxesValid)
{
m_solarFluxChart->setTitle("");
m_solarFluxChart->legend()->hide();
m_solarFluxChart->layout()->setContentsMargins(0, 0, 0, 0);
m_solarFluxChart->setMargins(QMargins(1, 1, 1, 1));
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
double maxValue = -std::numeric_limits<double>::infinity();
double minValue = std::numeric_limits<double>::infinity();
QLineSeries *series = new QLineSeries();
@ -744,30 +876,50 @@ void StarTrackerGUI::plotSolarFluxChart()
series->setPointLabelsVisible(true);
series->setPointLabelsFormat("@yPoint");
series->setPointLabelsClipping(false);
m_solarFluxChart.addSeries(series);
series->attachAxis(&m_chartSolarFluxXAxis);
series->attachAxis(&m_chartSolarFluxYAxis);
m_solarFluxChart->addSeries(series);
QLogValueAxis *chartSolarFluxXAxis = new QLogValueAxis();
QValueAxis *chartSolarFluxYAxis = new QValueAxis();
chartSolarFluxXAxis->setTitleText(QString("Frequency (MHz)"));
chartSolarFluxXAxis->setMinorTickCount(-1);
chartSolarFluxYAxis->setTitleText(QString("Solar flux density (%1)").arg(solarFluxUnit()));
chartSolarFluxYAxis->setMinorTickCount(-1);
if (m_settings.m_solarFluxUnits == StarTrackerSettings::SFU)
{
m_chartSolarFluxYAxis.setLabelFormat("%d");
m_chartSolarFluxYAxis.setRange(0.0, ((((int)maxValue)+99)/100)*100);
chartSolarFluxYAxis->setLabelFormat("%d");
chartSolarFluxYAxis->setRange(0.0, ((((int)maxValue)+99)/100)*100);
}
else if (m_settings.m_solarFluxUnits == StarTrackerSettings::JANSKY)
{
m_chartSolarFluxYAxis.setLabelFormat("%.2g");
m_chartSolarFluxYAxis.setRange(0, ((((int)maxValue)+999999)/100000)*100000);
chartSolarFluxYAxis->setLabelFormat("%.2g");
chartSolarFluxYAxis->setRange(0, ((((int)maxValue)+999999)/100000)*100000);
}
else
{
m_chartSolarFluxYAxis.setLabelFormat("%.2g");
m_chartSolarFluxYAxis.setRange(minValue, maxValue);
chartSolarFluxYAxis->setLabelFormat("%.2g");
// Bug in QtCharts for values < ~1e-12 https://bugreports.qt.io/browse/QTBUG-95304
// Set range to 0-1 here, then real range after axis have been attached
chartSolarFluxYAxis->setRange(0.0, 1.0);
}
m_solarFluxChart->addAxis(chartSolarFluxXAxis, Qt::AlignBottom);
m_solarFluxChart->addAxis(chartSolarFluxYAxis, Qt::AlignLeft);
series->attachAxis(chartSolarFluxXAxis);
series->attachAxis(chartSolarFluxYAxis);
if (m_settings.m_solarFluxUnits == StarTrackerSettings::WATTS_M_HZ) {
chartSolarFluxYAxis->setRange(minValue, maxValue);
}
}
else
m_solarFluxChart.setTitle("Press download Solar flux density data to view");
ui->chart->setChart(&m_solarFluxChart);
// m_chart.setPlotAreaBackgroundVisible(false);
// disconnect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
m_solarFluxChart->setTitle("Press download Solar flux density data to view");
ui->chart->setChart(m_solarFluxChart);
delete oldChart;
}
QList<QLineSeries*> StarTrackerGUI::createDriftScan(bool galactic)
@ -913,9 +1065,14 @@ void StarTrackerGUI::plotGalacticLineOfSight()
// Draw top-down image of Milky Way
ui->chart->setVisible(false);
ui->image->setVisible(true);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(false);
ui->zoomIn->setVisible(true);
ui->zoomOut->setVisible(true);
ui->addAnimationFrame->setVisible(true);
ui->clearAnimation->setVisible(true);
ui->saveAnimation->setVisible(true);
// Select which Milky Way image to show
int imageIdx = ui->chartSubSelect->currentIndex();
@ -946,6 +1103,15 @@ void StarTrackerGUI::plotGalacticLineOfSight()
m_lineOfSight->setLine(sun.x(), sun.y(), point.x(), -point.y());
}
void StarTrackerGUI::plotGalacticMarker(LoSMarker* marker)
{
QPointF sun(511, 708); // Location of Sun on Milky Way image
double pixelsPerKPC = 564.0/22.995; // 75,000ly = 23kpc
QTransform rotation = QTransform().translate(sun.x(), -sun.y()).rotate(marker->m_l).translate(-sun.x(), sun.y()); // Flip Y
QPointF point = rotation.map(QPointF(511, -sun.y() + pixelsPerKPC*marker->m_d));
marker->m_text->setPos(point.x(), -point.y());
}
void StarTrackerGUI::on_zoomIn_clicked()
{
m_zoom->gentleZoom(1.25);
@ -956,13 +1122,58 @@ void StarTrackerGUI::on_zoomOut_clicked()
m_zoom->gentleZoom(0.75);
}
void StarTrackerGUI::on_addAnimationFrame_clicked()
{
QImage image(ui->image->size(), QImage::Format_ARGB32);
image.fill(Qt::black);
QPainter painter(&image);
ui->image->render(&painter);
m_animationImages.append(image);
ui->saveAnimation->setEnabled(true);
ui->clearAnimation->setEnabled(true);
}
void StarTrackerGUI::on_clearAnimation_clicked()
{
m_animationImages.clear();
ui->saveAnimation->setEnabled(false);
ui->clearAnimation->setEnabled(false);
}
void StarTrackerGUI::on_saveAnimation_clicked()
{
// Get filename of animation file
QFileDialog fileDialog(nullptr, "Select file to save animation to", "", "*.png");
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
if (fileDialog.exec())
{
QStringList fileNames = fileDialog.selectedFiles();
if (fileNames.size() > 0)
{
APNG apng(m_animationImages.size());
for (int i = 0; i < m_animationImages.size(); i++) {
apng.addImage(m_animationImages[i]);
}
if (!apng.save(fileNames[0])) {
QMessageBox::critical(this, "Star Tracker", QString("Failed to write to file %1").arg(fileNames[0]));
}
}
}
}
void StarTrackerGUI::plotSkyTemperatureChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(true);
ui->drawMoon->setVisible(true);
ui->darkTheme->setVisible(false);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
bool galactic = (ui->chartSubSelect->currentIndex() & 1) == 1;
@ -998,133 +1209,22 @@ void StarTrackerGUI::plotSkyTemperatureChart()
int idx = ui->chartSubSelect->currentIndex();
if ((idx == 6) || (idx == 7))
{
// Adjust temperature from 408MHz FITS file, taking in to account
// observation frequency and beamwidth
FITS *fits = &m_temps[1];
if (fits->valid())
double temp;
if (m_starTracker->calcSkyTemperature(m_settings.m_frequency, m_settings.m_beamwidth, ra, dec, temp))
{
const double beamwidth = m_settings.m_beamwidth;
const double halfBeamwidth = beamwidth/2.0;
// Use cos^p(x) for approximation of radiation pattern
// (Essentially the same as Gaussian of exp(-4*ln(theta^2/beamwidth^2))
// (See a2 in https://arxiv.org/pdf/1812.10084.pdf for Elliptical equivalent))
// We have gain of 0dB (1) at 0 degrees, and -3dB (~0.5) at half-beamwidth degrees
// Find exponent that correponds to -3dB at that angle
double minus3dBLinear = pow(10.0, -3.0/10.0);
double p = log(minus3dBLinear)/log(cos(Units::degreesToRadians(halfBeamwidth)));
// Create an matrix with gain as a function of angle
double degreesPerPixelH = abs(fits->degreesPerPixelH());
double degreesPerPixelV = abs(fits->degreesPerPixelV());
int numberOfCoeffsH = ceil(beamwidth/degreesPerPixelH);
int numberOfCoeffsV = ceil(beamwidth/degreesPerPixelV);
if ((numberOfCoeffsH & 1) == 0) {
numberOfCoeffsH++;
}
if ((numberOfCoeffsV & 1) == 0) {
numberOfCoeffsV++;
}
double *beam = new double[numberOfCoeffsH*numberOfCoeffsV];
double sum = 0.0;
int y0 = numberOfCoeffsV/2;
int x0 = numberOfCoeffsH/2;
int nonZeroCount = 0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
double xp = (x - x0) * degreesPerPixelH;
double yp = (y - y0) * degreesPerPixelV;
double r = sqrt(xp*xp+yp*yp);
if (r < halfBeamwidth)
{
beam[y*numberOfCoeffsH+x] = pow(cos(Units::degreesToRadians(r)), p);
sum += beam[y*numberOfCoeffsH+x];
nonZeroCount++;
}
else
{
beam[y*numberOfCoeffsH+x] = 0.0;
}
}
}
// Get centre pixel coordinates
double centreX;
if (ra <= 12.0) {
centreX = (12.0 - ra) / 24.0;
} else {
centreX = (24 - ra + 12) / 24.0;
}
double centreY = (90.0-dec) / 180.0;
int imgX = centreX * fits->width();
int imgY = centreY * fits->height();
// Apply weighting to temperature data
double weightedSum = 0.0;
for (int y = 0; y < numberOfCoeffsV; y++)
{
for (int x = 0; x < numberOfCoeffsH; x++)
{
weightedSum += beam[y*numberOfCoeffsH+x] * fits->scaledWrappedValue(imgX + (x-x0), imgY + (y-y0));
}
}
// From: https://www.cv.nrao.edu/~sransom/web/Ch3.html
// The antenna temperature equals the source brightness temperature multiplied by the fraction of the beam solid angle filled by the source
// So we scale the sum by the total number of non-zero pixels (i.e. beam area)
// If we compare to some maps with different beamwidths here: https://www.cv.nrao.edu/~demerson/radiosky/sky_jun96.pdf
// The values we've computed are a bit higher..
double temp408 = weightedSum/nonZeroCount;
// Scale according to frequency - CMB contribution constant
// Power law at low frequencies, with slight variation in spectral index
// See:
// Global Sky Model: https://ascl.net/1011.010
// An improved Model of Diffuse Galactic Radio Emission: https://arxiv.org/pdf/1605.04920.pdf
// A high-resolution self-consistent whole sky foreground model: https://arxiv.org/abs/1812.10084
// (De-striping:) Full sky study of diffuse Galactic emission at decimeter wavelength https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
// Data here: http://cdsarc.u-strasbg.fr/viz-bin/cat/J/A+A/410/847
// LFmap: https://www.faculty.ece.vt.edu/swe/lwa/memo/lwa0111.pdf
double iso408 = 50 * pow(150e6/408e6, 2.75); // Extra-galactic isotropic in reference map at 408MHz
double isoT = 50 * pow(150e6/m_settings.m_frequency, 2.75); // Extra-galactic isotropic at target frequency
double cmbT = 2.725; // Cosmic microwave backgroud;
double spectralIndex;
if (m_spectralIndex.valid())
{
// See https://www.aanda.org/articles/aa/pdf/2003/42/aah4363.pdf
spectralIndex = m_spectralIndex.scaledValue(imgX, imgY);
}
else
{
// See https://arxiv.org/abs/1812.10084 fig 2
if (m_settings.m_frequency < 200e6) {
spectralIndex = 2.55;
} else if (m_settings.m_frequency < 20e9) {
spectralIndex = 2.695;
} else {
spectralIndex = 3.1;
}
}
double galactic480 = temp408 - cmbT - iso408;
double galacticT = galactic480 * pow(408e6/m_settings.m_frequency, spectralIndex); // Scale galactic contribution by frequency
double temp = galacticT + cmbT + isoT; // Final temperature
series->setPointLabelsVisible(true);
series->setPointLabelsColor(Qt::red);
series->setPointLabelsFormat(QString("%1 K").arg(std::round(temp)));
// Scale marker size by beamwidth
markerSize = std::max((int)round(beamWidth * degPerPixel), 5);
delete[] beam;
}
else
qDebug() << "StarTrackerGUI::plotSkyTemperatureChart: FITS temperature file not valid";
}
else
{
// Read temperature from selected FITS file at target RA/Dec
QImage *img = &m_images[idx];
FITS *fits = &m_temps[idx/2];
const FITS *fits = m_starTracker->getTempFITS(idx/2);
double x;
if (ra <= 12.0) {
x = (12.0 - ra) / 24.0;
@ -1147,6 +1247,10 @@ void StarTrackerGUI::plotSkyTemperatureChart()
series->setPointLabelsColor(Qt::red);
series->setPointLabelsFormat(QString("%1 K").arg(std::round(temp)));
}
else
{
qDebug() << "FITS not valid";
}
// Temperature from just one pixel, but need to make marker visbile
markerSize = 5;
@ -1155,19 +1259,72 @@ void StarTrackerGUI::plotSkyTemperatureChart()
series->setColor(getSeriesColor(0));
m_chart.setTitle("");
// We want scatter to be on top of line, but same color even when no drift line
// We want scatter (for the beam) to be on top of line, but same color even when other series
for (int i = 0; i < lineSeries.length(); i++) {
m_chart.addSeries(lineSeries[i]);
}
// Draw Sun on chart if requested
QScatterSeries *sunSeries = nullptr;
if (m_settings.m_drawSunOnSkyTempChart)
{
sunSeries = new QScatterSeries();
mapRaDec(m_sunRA, m_sunDec, galactic, x, y);
sunSeries->append(x, y);
sunSeries->setMarkerSize((int)std::max(std::round(0.53 * degPerPixel), 5.0));
sunSeries->setColor(Qt::yellow);
sunSeries->setBorderColor(Qt::yellow);
if (m_settings.m_target != "Sun") // Avoid labels on top of each other
{
sunSeries->setPointLabelsVisible(true);
sunSeries->setPointLabelsColor(Qt::red);
sunSeries->setPointLabelsFormat("Sun");
}
m_chart.addSeries(sunSeries);
}
// Draw moon on chart if requested
QScatterSeries *moonSeries = nullptr;
if (m_settings.m_drawMoonOnSkyTempChart)
{
moonSeries = new QScatterSeries();
mapRaDec(m_moonRA, m_moonDec, galactic, x, y);
moonSeries->append(x, y);
moonSeries->setMarkerSize((int)std::max(std::round(0.53 * degPerPixel), 5.0));
moonSeries->setColor(qRgb(150, 150, 150));
moonSeries->setBorderColor(qRgb(150, 150, 150));
if (m_settings.m_target != "Moon") // Avoid labels on top of each other
{
moonSeries->setPointLabelsVisible(true);
moonSeries->setPointLabelsColor(Qt::red);
moonSeries->setPointLabelsFormat("Moon");
}
m_chart.addSeries(moonSeries);
}
m_chart.addSeries(series);
if (galactic)
{
m_chart.addAxis(&m_skyTempGalacticLXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempGalacticLXAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempGalacticLXAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempGalacticLXAxis);
}
m_skyTempYAxis.setTitleText(QString("Galactic latitude (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempYAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempYAxis);
}
for (int i = 0; i < lineSeries.length(); i++)
{
@ -1179,10 +1336,22 @@ void StarTrackerGUI::plotSkyTemperatureChart()
{
m_chart.addAxis(&m_skyTempRAXAxis, Qt::AlignBottom);
series->attachAxis(&m_skyTempRAXAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempRAXAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempRAXAxis);
}
m_skyTempYAxis.setTitleText(QString("Declination (%1)").arg(QChar(0xb0)));
m_chart.addAxis(&m_skyTempYAxis, Qt::AlignLeft);
series->attachAxis(&m_skyTempYAxis);
if (sunSeries) {
sunSeries->attachAxis(&m_skyTempYAxis);
}
if (moonSeries) {
moonSeries->attachAxis(&m_skyTempYAxis);
}
for (int i = 0; i < lineSeries.length(); i++)
{
@ -1192,7 +1361,6 @@ void StarTrackerGUI::plotSkyTemperatureChart()
}
ui->chart->setChart(&m_chart);
plotAreaChanged(m_chart.plotArea());
connect(&m_chart, SIGNAL(plotAreaChanged(QRectF)), this, SLOT(plotAreaChanged(QRectF)));
}
void StarTrackerGUI::plotAreaChanged(const QRectF &plotArea)
@ -1237,9 +1405,14 @@ void StarTrackerGUI::plotElevationLineChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_azElLineChart;
@ -1360,9 +1533,14 @@ void StarTrackerGUI::plotElevationPolarChart()
{
ui->chart->setVisible(true);
ui->image->setVisible(false);
ui->drawSun->setVisible(false);
ui->drawMoon->setVisible(false);
ui->darkTheme->setVisible(true);
ui->zoomIn->setVisible(false);
ui->zoomOut->setVisible(false);
ui->addAnimationFrame->setVisible(false);
ui->clearAnimation->setVisible(false);
ui->saveAnimation->setVisible(false);
QChart *oldChart = m_azElPolarChart;
@ -1484,7 +1662,7 @@ void StarTrackerGUI::plotElevationPolarChart()
qreal el = polarSeries->at(i).y();
if ((prevAz > 270.0) && (az <= 90.0))
{
double elMid = interpolate(prevAz, prevEl, az+360.0, el, 360.0);
double elMid = Interpolation::interpolate(prevAz, prevEl, az+360.0, el, 360.0);
s->append(360.0, elMid);
series.append(new QLineSeries());
s = series.last();
@ -1494,7 +1672,7 @@ void StarTrackerGUI::plotElevationPolarChart()
}
else if ((prevAz <= 90.0) && (az > 270.0))
{
double elMid = interpolate(prevAz, prevEl, az-360.0, el, 0.0);
double elMid = Interpolation::interpolate(prevAz, prevEl, az-360.0, el, 0.0);
s->append(0.0, elMid);
series.append(new QLineSeries());
s = series.last();
@ -1638,14 +1816,59 @@ QString StarTrackerGUI::solarFluxUnit()
return "";
}
// Calculate solar flux at given frequency in SFU
double StarTrackerGUI::calcSolarFlux(double freqMhz)
{
if (m_solarFluxesValid)
{
double solarFlux;
const int fluxes = sizeof(m_solarFluxFrequencies)/sizeof(*m_solarFluxFrequencies);
int i;
for (i = 0; i < fluxes; i++)
{
if (freqMhz < m_solarFluxFrequencies[i])
break;
}
if (i == 0)
{
solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[0], (double)m_solarFluxes[0],
(double)m_solarFluxFrequencies[1], (double)m_solarFluxes[1],
freqMhz
);
}
else if (i == fluxes)
{
solarFlux = Interpolation::extrapolate((double)m_solarFluxFrequencies[fluxes-2], (double)m_solarFluxes[fluxes-2],
(double)m_solarFluxFrequencies[fluxes-1], (double)m_solarFluxes[fluxes-1],
freqMhz
);
}
else
{
solarFlux = Interpolation::interpolate((double)m_solarFluxFrequencies[i-1], (double)m_solarFluxes[i-1],
(double)m_solarFluxFrequencies[i], (double)m_solarFluxes[i],
freqMhz
);
}
return solarFlux;
}
else
{
return 0.0;
}
}
void StarTrackerGUI::displaySolarFlux()
{
if (((m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800) && (m_solarFlux == 0.0))
|| ((m_settings.m_solarFluxData != StarTrackerSettings::DRAO_2800) && !m_solarFluxesValid))
{
ui->solarFlux->setText("");
}
else
{
double solarFlux;
double freqMhz = m_settings.m_frequency/1000000.0;
if (m_settings.m_solarFluxData == StarTrackerSettings::DRAO_2800)
{
solarFlux = m_solarFlux;
@ -1653,35 +1876,7 @@ void StarTrackerGUI::displaySolarFlux()
}
else if (m_settings.m_solarFluxData == StarTrackerSettings::TARGET_FREQ)
{
double freqMhz = m_settings.m_frequency/1000000.0;
const int fluxes = sizeof(m_solarFluxFrequencies)/sizeof(*m_solarFluxFrequencies);
int i;
for (i = 0; i < fluxes; i++)
{
if (freqMhz < m_solarFluxFrequencies[i])
break;
}
if (i == 0)
{
solarFlux = extrapolate(m_solarFluxFrequencies[0], m_solarFluxes[0],
m_solarFluxFrequencies[1], m_solarFluxes[1],
freqMhz
);
}
else if (i == fluxes)
{
solarFlux = extrapolate(m_solarFluxFrequencies[fluxes-2], m_solarFluxes[fluxes-2],
m_solarFluxFrequencies[fluxes-1], m_solarFluxes[fluxes-1],
freqMhz
);
}
else
{
solarFlux = interpolate(m_solarFluxFrequencies[i-1], m_solarFluxes[i-1],
m_solarFluxFrequencies[i], m_solarFluxes[i],
freqMhz
);
}
solarFlux = calcSolarFlux(freqMhz);
ui->solarFlux->setToolTip(QString("Solar flux density interpolated to %1 MHz").arg(freqMhz));
}
else
@ -1692,6 +1887,9 @@ void StarTrackerGUI::displaySolarFlux()
}
ui->solarFlux->setText(QString("%1 %2").arg(convertSolarFluxUnits(solarFlux)).arg(solarFluxUnit()));
ui->solarFlux->setCursorPosition(0);
// Calculate value at target frequency in Jy for Radio Astronomy plugin
m_starTracker->getInputMessageQueue()->push(StarTracker::MsgSetSolarFlux::create(Units::solarFluxUnitsToJansky(calcSolarFlux(freqMhz))));
}
}
@ -1707,7 +1905,8 @@ bool StarTrackerGUI::readSolarFlux()
QString string(bytes);
// HHMMSS 245 410 610 1415 2695 4995 8800 15400 Mhz
// 000000 000019 000027 000037 000056 000073 000116 000202 000514 sfu
QRegExp re("([0-9]{2})([0-9]{2})([0-9]{2}) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)");
// Occasionally, file will contain ////// in a column, presumably to indicate no data
QRegExp re("([0-9]{2})([0-9]{2})([0-9]{2}) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+) ([0-9\\/]+)");
if (re.indexIn(string) != -1)
{
for (int i = 0; i < 8; i++)
@ -1717,6 +1916,8 @@ bool StarTrackerGUI::readSolarFlux()
plotChart();
return true;
}
else
qDebug() << "StarTrackerGUI::readSolarFlux: No match for " << string;
}
}
else
@ -1764,7 +1965,7 @@ void StarTrackerGUI::updateSolarFlux(bool all)
{
QDate today = QDateTime::currentDateTimeUtc().date();
QString solarFluxFile = getSolarFluxFilename();
if (m_dlm.confirmDownload(solarFluxFile, nullptr, 1))
if (m_dlm.confirmDownload(solarFluxFile, nullptr, 0))
{
QString urlString = QString("https://www.sws.bom.gov.au/Category/World Data Centre/Data Display and Download/Solar Radio/station/learmonth/SRD/%1/L%2.SRD")
.arg(today.year()).arg(today.toString("yyMMdd"));
@ -1791,12 +1992,28 @@ void StarTrackerGUI::on_downloadSolarFlux_clicked()
void StarTrackerGUI::on_darkTheme_clicked(bool checked)
{
m_settings.m_chartsDarkTheme = checked;
m_solarFluxChart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
if (m_solarFluxChart) {
m_solarFluxChart->setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
}
m_chart.setTheme(m_settings.m_chartsDarkTheme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
plotChart();
applySettings();
}
void StarTrackerGUI::on_drawSun_clicked(bool checked)
{
m_settings.m_drawSunOnSkyTempChart = checked;
plotChart();
applySettings();
}
void StarTrackerGUI::on_drawMoon_clicked(bool checked)
{
m_settings.m_drawMoonOnSkyTempChart = checked;
plotChart();
applySettings();
}
void StarTrackerGUI::downloadFinished(const QString& filename, bool success)
{
(void) filename;

View File

@ -47,6 +47,15 @@ using namespace QtCharts;
class StarTrackerGUI : public FeatureGUI {
Q_OBJECT
struct LoSMarker {
QString m_name;
float m_l;
float m_b;
float m_d;
QGraphicsTextItem* m_text;
};
public:
static StarTrackerGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
@ -80,9 +89,7 @@ private:
QCategoryAxis m_skyTempRAXAxis;
QValueAxis m_skyTempYAxis;
QChart m_solarFluxChart;
QLogValueAxis m_chartSolarFluxXAxis;
QValueAxis m_chartSolarFluxYAxis;
QChart *m_solarFluxChart;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
@ -96,14 +103,22 @@ private:
// Sky temperature
QList<QImage> m_images;
QList<FITS> m_temps;
FITS m_spectralIndex;
// Galactic line of sight
QList<QPixmap> m_milkyWayImages;
GraphicsViewZoom* m_zoom;
QList<QGraphicsPixmapItem *> m_milkyWayItems;
QGraphicsLineItem* m_lineOfSight;
QList<LoSMarker *> m_lineOfSightMarkers;
// Images that are part of the animation
QList<QImage> m_animationImages;
// Sun and Moon position for drawing on Sky Temperature chart
double m_sunRA;
double m_sunDec;
double m_moonRA;
double m_moonDec;
explicit StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~StarTrackerGUI();
@ -123,17 +138,18 @@ private:
void plotSolarFluxChart();
void plotGalacticLineOfSight();
void createGalacticLineOfSightScene();
void plotGalacticMarker(LoSMarker* marker);
void plotChart();
void removeAllAxes();
double convertSolarFluxUnits(double sfu);
QString solarFluxUnit();
double calcSolarFlux(double freqMhz);
void displaySolarFlux();
QString getSolarFluxFilename();
bool readSolarFlux();
void raDecChanged();
void updateChartSubSelect();
void updateSolarFlux(bool all);
void updateGalacticCoords();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -143,6 +159,7 @@ private slots:
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_link_clicked(bool checked=false);
void on_useMyPosition_clicked(bool checked=false);
void on_latitude_valueChanged(double value);
void on_longitude_valueChanged(double value);
@ -150,6 +167,10 @@ private slots:
void on_declination_editingFinished();
void on_azimuth_valueChanged(double value);
void on_elevation_valueChanged(double value);
void on_azimuthOffset_valueChanged(double value);
void on_elevationOffset_valueChanged(double value);
void on_galacticLatitude_valueChanged(double value);
void on_galacticLongitude_valueChanged(double value);
void on_frequency_valueChanged(int value);
void on_beamwidth_valueChanged(double value);
void on_target_currentTextChanged(const QString &text);
@ -166,6 +187,11 @@ private slots:
void on_darkTheme_clicked(bool checked);
void on_zoomIn_clicked();
void on_zoomOut_clicked();
void on_addAnimationFrame_clicked();
void on_clearAnimation_clicked();
void on_saveAnimation_clicked();
void on_drawSun_clicked(bool checked);
void on_drawMoon_clicked(bool checked);
void networkManagerFinished(QNetworkReply *reply);
void downloadFinished(const QString& filename, bool success);
};

View File

@ -43,7 +43,7 @@
<x>10</x>
<y>10</y>
<width>321</width>
<height>201</height>
<height>251</height>
</rect>
</property>
<property name="windowTitle">
@ -67,22 +67,90 @@
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="7" column="1">
<widget class="QLineEdit" name="rightAscension">
<property name="toolTip">
<string>Right Ascension of the target object.
This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2)</string>
<item row="10" column="0">
<widget class="QLabel" name="galacticLatitudeLabel">
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>23h59m59.59s</string>
<string>l</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="rightAscensionLabel">
<item row="8" column="1">
<widget class="DMSSpinBox" name="azimuth" native="true">
<property name="toolTip">
<string>Azimuth in degrees to the target from the observation point</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>RA</string>
<string>Time</string>
</property>
</widget>
</item>
<item row="9" column="3">
<widget class="QDoubleSpinBox" name="elevationOffset">
<property name="toolTip">
<string>Offset in degrees to add to calculated target elevation</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lst">
<property name="toolTip">
<string>Local sidereal time for selected date, time and longitude</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="elevationLabel">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="declinationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QDoubleSpinBox" name="beamwidth">
<property name="toolTip">
<string>Antenna half power (-3dB) beamwidth (degrees)</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>0.100000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>25.000000000000000</double>
</property>
</widget>
</item>
@ -93,39 +161,6 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="beamwidthLabel">
<property name="text">
<string>Beamwidth</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="latitude">
<property name="toolTip">
<string>Latitude in decimal degrees (North positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>-90.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<layout class="QHBoxLayout" name="targetLayout">
<item>
@ -197,6 +232,26 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
<string>Custom Az/El</string>
</property>
</item>
<item>
<property name="text">
<string>Custom l/b</string>
</property>
</item>
<item>
<property name="text">
<string>S7</string>
</property>
</item>
<item>
<property name="text">
<string>S8</string>
</property>
</item>
<item>
<property name="text">
<string>S9</string>
</property>
</item>
</widget>
</item>
<item>
@ -214,52 +269,10 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</item>
</layout>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lst">
<property name="toolTip">
<string>Local sidereal time for selected date, time and longitude</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="WrappingDateTimeEdit" name="dateTime">
<property name="toolTip">
<string>Date and time to use when calculating target's position</string>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy HH:mm:ss</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="3">
<widget class="DMSSpinBox" name="elevation" native="true">
<property name="toolTip">
<string>Elevation in degrees to the target from the observation point</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="longitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="value">
<double>-180.000000000000000</double>
<item row="4" column="2">
<widget class="QLabel" name="solarFluxLabel">
<property name="text">
<string>Solar Flux</string>
</property>
</widget>
</item>
@ -270,19 +283,6 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="frequency">
<property name="toolTip">
<string>Observation frequency (MHz)</string>
</property>
<property name="minimum">
<number>50</number>
</property>
<property name="maximum">
<number>100000</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lstLabel">
<property name="text">
@ -290,6 +290,30 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="azimuthtLabel">
<property name="text">
<string>Azimuth</string>
</property>
</widget>
</item>
<item row="8" column="3">
<widget class="DMSSpinBox" name="elevation" native="true">
<property name="toolTip">
<string>Elevation in degrees to the target from the observation point</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLineEdit" name="solarFlux">
<property name="toolTip">
<string>Solar flux density</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="targetLabel">
<property name="text">
@ -297,10 +321,15 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="azimuthtLabel">
<item row="7" column="1">
<widget class="QLineEdit" name="rightAscension">
<property name="toolTip">
<string>Right Ascension of the target object.
This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2)</string>
</property>
<property name="text">
<string>Azimuth</string>
<string>23h59m59.59s</string>
</property>
</widget>
</item>
@ -334,6 +363,20 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="link">
<property name="toolTip">
<string>Link display to Radio Astronomy channel</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/link.png</normaloff>:/link.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="viewOnMap">
<property name="toolTip">
@ -392,6 +435,133 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="rightAscensionLabel">
<property name="text">
<string>RA</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QLineEdit" name="declination">
<property name="toolTip">
<string>Declination of the target object
This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2&quot; 34 12 10.2)</string>
</property>
<property name="text">
<string>-90d59'59.59&quot;</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QDoubleSpinBox" name="azimuthOffset">
<property name="toolTip">
<string>Offset in degrees to added to calculated target azimuth</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLabel" name="beamwidthLabel">
<property name="text">
<string>Beamwidth</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="latitude">
<property name="toolTip">
<string>Latitude in decimal degrees (North positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>-90.000000000000000</double>
</property>
</widget>
</item>
<item row="10" column="3">
<widget class="DMSSpinBox" name="galacticLatitude" native="true">
<property name="toolTip">
<string>Galactic latitude in degrees</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="frequency">
<property name="toolTip">
<string>Observation frequency (MHz)</string>
</property>
<property name="minimum">
<number>50</number>
</property>
<property name="maximum">
<number>100000</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="azimuithOffsetLabel">
<property name="text">
<string>Az Offset</string>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="WrappingDateTimeEdit" name="dateTime">
<property name="toolTip">
<string>Date and time to use when calculating target's position</string>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy HH:mm:ss</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="longitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of observation point / antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="value">
<double>-180.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="dateTimeSelect">
<property name="toolTip">
@ -409,84 +579,22 @@ This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds
</item>
</widget>
</item>
<item row="4" column="3">
<widget class="QLineEdit" name="solarFlux">
<item row="9" column="2">
<widget class="QLabel" name="elevationOffsetLabel">
<property name="text">
<string>El Offset</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="DMSSpinBox" name="galacticLongitude" native="true">
<property name="toolTip">
<string>Solar flux density</string>
</property>
<property name="readOnly">
<bool>true</bool>
<string>Galactic longitude in degrees (positive Eastward from galactic center)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="DMSSpinBox" name="azimuth" native="true">
<property name="toolTip">
<string>Azimuth in degrees to the target from the observation point</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QDoubleSpinBox" name="beamwidth">
<property name="toolTip">
<string>Antenna half power (-3dB) beamwidth (degrees)</string>
</property>
<property name="decimals">
<number>0</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="value">
<double>25.000000000000000</double>
</property>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="declinationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QLineEdit" name="declination">
<property name="toolTip">
<string>Declination of the target object
This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2&quot; 34 12 10.2)</string>
</property>
<property name="text">
<string>-90d59'59.59&quot;</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QLabel" name="solarFluxLabel">
<property name="text">
<string>Solar Flux</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item row="8" column="2">
<widget class="QLabel" name="elevationLabel">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="galacticLatitudeLabel">
<item row="10" column="2">
<widget class="QLabel" name="galacticLongitudeLabel">
<property name="font">
<font>
<family>Liberation Sans</family>
@ -499,34 +607,6 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
</property>
</widget>
</item>
<item row="9" column="2">
<widget class="QLabel" name="galacticLongitudeLabel">
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>l</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="DMSSpinBox" name="galacticLatitude" native="true">
<property name="toolTip">
<string>Galactic latitude in degrees</string>
</property>
</widget>
</item>
<item row="9" column="3">
<widget class="DMSSpinBox" name="galacticLongitude" native="true">
<property name="toolTip">
<string>Galactic longitude in degrees (positive Eastward from galactic center)</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -535,7 +615,7 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<property name="geometry">
<rect>
<x>10</x>
<y>250</y>
<y>280</y>
<width>318</width>
<height>268</height>
</rect>
@ -603,6 +683,91 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<item>
<widget class="QComboBox" name="chartSubSelect"/>
</item>
<item>
<widget class="QToolButton" name="drawMoon">
<property name="toolTip">
<string>Draw Moon</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="startracker.qrc">
<normaloff>:/startracker/startracker/moon-button-24.png</normaloff>:/startracker/startracker/moon-button-24.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="drawSun">
<property name="toolTip">
<string>Draw Sun</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="startracker.qrc">
<normaloff>:/startracker/startracker/sun-button-24.png</normaloff>:/startracker/startracker/sun-button-24.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addAnimationFrame">
<property name="toolTip">
<string>Add current image to animation</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/plusw.png</normaloff>:/plusw.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="saveAnimation">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Save animation to a .png file</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/camera.png</normaloff>:/camera.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="clearAnimation">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Clear current animation</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="QToolButton" name="darkTheme">
<property name="toolTip">
@ -726,6 +891,8 @@ This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and secon
<tabstop>beamwidth</tabstop>
<tabstop>rightAscension</tabstop>
<tabstop>declination</tabstop>
<tabstop>azimuthOffset</tabstop>
<tabstop>elevationOffset</tabstop>
<tabstop>chartSelect</tabstop>
<tabstop>chartSubSelect</tabstop>
<tabstop>darkTheme</tabstop>

View File

@ -30,7 +30,7 @@
const PluginDescriptor StarTrackerPlugin::m_pluginDescriptor = {
StarTracker::m_featureId,
QStringLiteral("Star Tracker"),
QStringLiteral("6.15.0"),
QStringLiteral("6.16.7"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,

View File

@ -59,20 +59,47 @@ public:
public:
double getRA() const { return m_ra; }
double getDec() const { return m_dec; }
QString getTarget() const { return m_target; }
static MsgReportRADec* create(double ra, double dec)
static MsgReportRADec* create(double ra, double dec, const QString& target)
{
return new MsgReportRADec(ra, dec);
return new MsgReportRADec(ra, dec, target);
}
private:
double m_ra;
double m_dec;
QString m_target; // "target", "sun" or "moon"
MsgReportRADec(double ra, double dec) :
MsgReportRADec(double ra, double dec, const QString& target) :
Message(),
m_ra(ra),
m_dec(dec)
m_dec(dec),
m_target(target)
{
}
};
class MsgReportGalactic : public Message {
MESSAGE_CLASS_DECLARATION
public:
double getL() const { return m_l; }
double getB() const { return m_b; }
static MsgReportGalactic* create(double l, double b)
{
return new MsgReportGalactic(l, b);
}
private:
double m_l;
double m_b;
MsgReportGalactic(double l, double b) :
Message(),
m_l(l),
m_b(b)
{
}
};

View File

@ -23,6 +23,14 @@
#include "startrackersettings.h"
const QStringList StarTrackerSettings::m_pipeTypes = {
QStringLiteral("RadioAstronomy")
};
const QStringList StarTrackerSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.radioastronomy")
};
StarTrackerSettings::StarTrackerSettings()
{
resetToDefaults();
@ -64,6 +72,15 @@ void StarTrackerSettings::resetToDefaults()
m_reverseAPIFeatureIndex = 0;
m_az = 0.0;
m_el = 0.0;
m_l = 0.0;
m_b = 0.0;
m_azOffset = 0.0;
m_elOffset = 0.0;
m_link = false;
m_owmAPIKey = "";
m_weatherUpdatePeriod = 60;
m_drawSunOnSkyTempChart = true;
m_drawMoonOnSkyTempChart = true;
}
QByteArray StarTrackerSettings::serialize() const
@ -104,6 +121,15 @@ QByteArray StarTrackerSettings::serialize() const
s.writeBool(32, m_chartsDarkTheme);
s.writeDouble(33, m_az);
s.writeDouble(34, m_el);
s.writeDouble(35, m_l);
s.writeDouble(36, m_b);
s.writeBool(37, m_link);
s.writeString(38, m_owmAPIKey);
s.writeS32(39, m_weatherUpdatePeriod);
s.writeDouble(40, m_azOffset);
s.writeDouble(41, m_elOffset);
s.writeBool(42, m_drawSunOnSkyTempChart);
s.writeBool(43, m_drawMoonOnSkyTempChart);
return s.final();
}
@ -175,6 +201,18 @@ bool StarTrackerSettings::deserialize(const QByteArray& data)
d.readDouble(33, &m_az, 0.0);
d.readDouble(34, &m_el, 0.0);
d.readDouble(35, &m_l, 0.0);
d.readDouble(36, &m_b, 0.0);
d.readBool(37, &m_link, false);
d.readString(38, &m_owmAPIKey, "");
d.readS32(39, &m_weatherUpdatePeriod, 60);
d.readDouble(40, &m_azOffset, 0.0);
d.readDouble(41, &m_elOffset, 0.0);
d.readBool(42, &m_drawSunOnSkyTempChart, true);
d.readBool(43, &m_drawMoonOnSkyTempChart, true);
return true;
}

View File

@ -41,7 +41,7 @@ struct StarTrackerSettings
double m_heightAboveSeaLevel; // In metres
double m_temperatureLapseRate; // In K/km
double m_frequency; // Observation frequency in Hz
double m_beamwidth; // Beamwidth in degrees
double m_beamwidth; // Halfpower beamwidth in degrees
uint16_t m_serverPort;
bool m_enableServer; // Enable Stellarium server
enum AzElUnits {DMS, DM, D, Decimal} m_azElUnits; // This needs to match DMSSpinBox::DisplayUnits
@ -60,13 +60,25 @@ struct StarTrackerSettings
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
double m_az; // Azimuth for Custom Az/El
double m_el; // Elevation for Custom Az/El
double m_az; // Azimuth for Custom Az/El
double m_el; // Elevation for Custom Az/El
double m_l; // Galactic longitude for Custom l/b
double m_b; // Galactic lattiude for Custom l/b
bool m_link; // Link settings to Radio Astronomy plugin
QString m_owmAPIKey; // API key for openweathermap.org
int m_weatherUpdatePeriod; // Time in minutes between weather updates
double m_azOffset;
double m_elOffset;
bool m_drawSunOnSkyTempChart;
bool m_drawMoonOnSkyTempChart;
StarTrackerSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
static const QStringList m_pipeTypes;
static const QStringList m_pipeURIs;
};
#endif // INCLUDE_FEATURE_STARTRACKERSETTINGS_H_

View File

@ -31,6 +31,8 @@ StarTrackerSettingsDialog::StarTrackerSettingsDialog(StarTrackerSettings *settin
ui->serverPort->setValue(settings->m_serverPort);
ui->enableServer->setChecked(settings->m_enableServer);
ui->refraction->setCurrentIndex(ui->refraction->findText(settings->m_refraction));
ui->owmAPIKey->setText(settings->m_owmAPIKey);
ui->weatherUpdatePeriod->setValue(settings->m_weatherUpdatePeriod);
ui->pressure->setValue(settings->m_pressure);
ui->temperature->setValue(settings->m_temperature);
ui->humidity->setValue(settings->m_humidity);
@ -40,7 +42,6 @@ StarTrackerSettingsDialog::StarTrackerSettingsDialog(StarTrackerSettings *settin
ui->solarFluxUnits->setCurrentIndex((int)settings->m_solarFluxUnits);
ui->drawSunOnMap->setChecked(settings->m_drawSunOnMap);
ui->drawMoonOnMap->setChecked(settings->m_drawMoonOnMap);
ui->drawStarOnMap->setChecked(settings->m_drawStarOnMap);
}
StarTrackerSettingsDialog::~StarTrackerSettingsDialog()
@ -56,6 +57,8 @@ void StarTrackerSettingsDialog::accept()
m_settings->m_serverPort = (uint16_t)ui->serverPort->value();
m_settings->m_enableServer = ui->enableServer->isChecked();
m_settings->m_refraction = ui->refraction->currentText();
m_settings->m_owmAPIKey = ui->owmAPIKey->text();
m_settings->m_weatherUpdatePeriod = ui->weatherUpdatePeriod->value();
m_settings->m_pressure = ui->pressure->value();
m_settings->m_temperature = ui->temperature->value();
m_settings->m_humidity = ui->humidity->value();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>496</width>
<height>491</height>
<width>569</width>
<height>535</height>
</rect>
</property>
<property name="font">
@ -23,215 +23,6 @@
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Enter the time in seconds between each calculation of the target's position</string>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="temperature">
<property name="toolTip">
<string>Air temperature in degrees Celsius, for use in atmospheric refraction correction</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="azElUnitsLabel">
<property name="text">
<string>Azimuth and elevation units</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QCheckBox" name="drawStarOnMap">
<property name="text">
<string>Draw target star on map</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="pressure">
<property name="toolTip">
<string>Air pressure in millibars, for use in atmospheric refraction correction</string>
</property>
<property name="maximum">
<double>2000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1010.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Height above sea level (m)</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QCheckBox" name="drawMoonOnMap">
<property name="text">
<string>Draw Moon on map</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QComboBox" name="solarFluxUnits">
<property name="toolTip">
<string>Units to use for the display of the Solar flux density</string>
</property>
<item>
<property name="text">
<string>Solar flux units (sfu)</string>
</property>
</item>
<item>
<property name="text">
<string>Jansky (Jy)</string>
</property>
</item>
<item>
<property name="text">
<string>Watts per square metre per hertz (W m^-2 Hz-1)</string>
</property>
</item>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Update period (s)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="height">
<property name="toolTip">
<string>Height of observation/antenna location above sea level in metres</string>
</property>
<property name="minimum">
<number>-1000</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="humidity">
<property name="toolTip">
<string>Relative humidity in %</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>80</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="solarFluxUnitsLabel">
<property name="text">
<string>Solar flux density units</string>
</property>
</widget>
</item>
<item row="17" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="14" column="0">
<widget class="QCheckBox" name="drawSunOnMap">
<property name="text">
<string>Draw Sun on map</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="toolTip">
<string>Stellarium telescope server IP port number</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Stellarium server port</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="epochLabel">
<property name="text">
<string>Epoch for RA &amp; Dec</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="temperatureLapseRateLabel">
<property name="toolTip">
<string>Temperature lapse rate (K/m)</string>
</property>
<property name="text">
<string>Temperature lapse rate (K/km)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pressureLabel">
<property name="text">
<string>Air pressure (mb)</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QCheckBox" name="enableServer">
<property name="toolTip">
<string>Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium</string>
</property>
<property name="text">
<string>Stellarium server</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="epoch">
<property name="toolTip">
@ -249,31 +40,140 @@
</item>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="temperatureLapseRate">
<property name="decimals">
<number>3</number>
<item row="15" column="0">
<widget class="QCheckBox" name="enableServer">
<property name="toolTip">
<string>Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium</string>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>6.490000000000000</double>
<property name="text">
<string>Stellarium server</string>
</property>
</widget>
</item>
<item row="4" column="0">
<item row="5" column="0">
<widget class="QLabel" name="pressureLabel">
<property name="text">
<string>Air pressure (mb)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="humidity">
<property name="toolTip">
<string>Relative humidity in %</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>80</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="temperatureLapseRateLabel">
<property name="toolTip">
<string>Temperature lapse rate (K/m)</string>
</property>
<property name="text">
<string>Temperature lapse rate (K/km)</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QCheckBox" name="drawMoonOnMap">
<property name="text">
<string>Draw Moon on map</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QCheckBox" name="drawSunOnMap">
<property name="text">
<string>Draw Sun on map</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="temperatureLabel">
<property name="text">
<string>Air temperature (C)</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="humidityLabel">
<property name="text">
<string>Humidity (%)</string>
<item row="5" column="1">
<widget class="QDoubleSpinBox" name="pressure">
<property name="toolTip">
<string>Air pressure in millibars, for use in atmospheric refraction correction</string>
</property>
<property name="maximum">
<double>2000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1010.000000000000000</double>
</property>
</widget>
</item>
<item row="20" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="18" column="0">
<widget class="QCheckBox" name="drawStarOnMap">
<property name="text">
<string>Draw target star on map</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="weatherUpdatePeriodLabel">
<property name="text">
<string>Weather update period (min)</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="solarFluxDataLabel">
<property name="text">
<string>Solar flux density data</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="refraction">
<property name="toolTip">
<string>Atmospheric refraction correction</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Saemundsson</string>
</property>
</item>
<item>
<property name="text">
<string>Positional Astronomy Library</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
@ -306,6 +206,42 @@
</item>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="temperature">
<property name="toolTip">
<string>Air temperature in degrees Celsius, for use in atmospheric refraction correction</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="solarFluxUnitsLabel">
<property name="text">
<string>Solar flux density units</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QDoubleSpinBox" name="temperatureLapseRate">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>6.490000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="refractionLabel">
<property name="text">
@ -313,39 +249,134 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="refraction">
<item row="11" column="1">
<widget class="QComboBox" name="solarFluxUnits">
<property name="toolTip">
<string>Atmospheric refraction correction</string>
</property>
<property name="currentIndex">
<number>0</number>
<string>Units to use for the display of the Solar flux density</string>
</property>
<item>
<property name="text">
<string>None</string>
<string>Solar flux units (sfu)</string>
</property>
</item>
<item>
<property name="text">
<string>Saemundsson</string>
<string>Jansky (Jy)</string>
</property>
</item>
<item>
<property name="text">
<string>Positional Astronomy Library</string>
<string>Watts per square metre per hertz (W m^-2 Hz-1)</string>
</property>
</item>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="solarFluxDataLabel">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Solar flux density data</string>
<string>Height above sea level (m)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="owmAPIKey">
<property name="toolTip">
<string>API key from openweathermap.org to download real-time weather</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Stellarium server port</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="epochLabel">
<property name="text">
<string>Epoch for RA &amp; Dec</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="azElUnitsLabel">
<property name="text">
<string>Azimuth and elevation units</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="owmAPIKeyLabel">
<property name="text">
<string>OpenWeatherMap API Key</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="toolTip">
<string>Stellarium telescope server IP port number</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Enter the time in seconds between each calculation of the target's position</string>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Update period (s)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="weatherUpdatePeriod">
<property name="toolTip">
<string>Enter the time in minutes between each weather update</string>
</property>
<property name="maximum">
<number>100000</number>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="humidityLabel">
<property name="text">
<string>Humidity (%)</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="height">
<property name="toolTip">
<string>Height of observation/antenna location above sea level in metres</string>
</property>
<property name="minimum">
<number>-1000</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QComboBox" name="solarFluxData">
<property name="toolTip">
<string>Select frequency at which to display Solar flux density data for</string>
@ -421,11 +452,14 @@
<tabstop>epoch</tabstop>
<tabstop>azElUnits</tabstop>
<tabstop>refraction</tabstop>
<tabstop>owmAPIKey</tabstop>
<tabstop>weatherUpdatePeriod</tabstop>
<tabstop>pressure</tabstop>
<tabstop>temperature</tabstop>
<tabstop>humidity</tabstop>
<tabstop>height</tabstop>
<tabstop>temperatureLapseRate</tabstop>
<tabstop>solarFluxData</tabstop>
<tabstop>solarFluxUnits</tabstop>
<tabstop>updatePeriod</tabstop>
<tabstop>serverPort</tabstop>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/startracker/">
<file>startracker/408mhz_ra_dec_spectral_index.fits</file>
</qresource>
</RCC>

View File

@ -28,6 +28,7 @@
#include "SWGTargetAzimuthElevation.h"
#include "SWGMapItem.h"
#include "SWGStarTrackerTarget.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
@ -42,6 +43,7 @@
MESSAGE_CLASS_DEFINITION(StarTrackerWorker::MsgConfigureStarTrackerWorker, Message)
MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportAzAl, Message)
MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportRADec, Message)
MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportGalactic, Message)
StarTrackerWorker::StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInterface *webAPIAdapterInterface) :
m_starTracker(starTracker),
@ -51,7 +53,8 @@ StarTrackerWorker::StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInte
m_running(false),
m_mutex(QMutex::Recursive),
m_tcpServer(nullptr),
m_clientConnection(nullptr)
m_clientConnection(nullptr),
m_solarFlux(0.0f)
{
connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update()));
}
@ -107,6 +110,12 @@ bool StarTrackerWorker::handleMessage(const Message& cmd)
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (StarTracker::MsgSetSolarFlux::match(cmd))
{
StarTracker::MsgSetSolarFlux& msg = (StarTracker::MsgSetSolarFlux&) cmd;
m_solarFlux = msg.getFlux();
return true;
}
else
{
return false;
@ -125,15 +134,27 @@ void StarTrackerWorker::applySettings(const StarTrackerSettings& settings, bool
<< " m_updatePeriod: " << settings.m_updatePeriod
<< " force: " << force;
if ((m_settings.m_target != settings.m_target)
if ( (m_settings.m_ra != settings.m_ra)
|| (m_settings.m_dec != settings.m_dec)
|| (m_settings.m_latitude != settings.m_latitude)
|| (m_settings.m_longitude != settings.m_longitude)
|| (m_settings.m_target != settings.m_target)
|| (m_settings.m_dateTime != settings.m_dateTime)
|| (m_settings.m_refraction != settings.m_refraction)
|| (m_settings.m_pressure != settings.m_pressure)
|| (m_settings.m_temperature != settings.m_temperature)
|| (m_settings.m_ra != settings.m_ra)
|| (m_settings.m_dec != settings.m_dec) || force)
|| (m_settings.m_humidity != settings.m_humidity)
|| (m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel)
|| (m_settings.m_temperatureLapseRate != settings.m_temperatureLapseRate)
|| (m_settings.m_frequency != settings.m_frequency)
|| (m_settings.m_beamwidth != settings.m_beamwidth)
|| (m_settings.m_az != settings.m_az)
|| (m_settings.m_el != settings.m_el)
|| (m_settings.m_l != settings.m_l)
|| (m_settings.m_b != settings.m_b)
|| (m_settings.m_azOffset != settings.m_azOffset)
|| (m_settings.m_elOffset != settings.m_elOffset)
|| force)
{
// Recalculate immediately
QTimer::singleShot(1, this, &StarTrackerWorker::update);
@ -283,8 +304,9 @@ void StarTrackerWorker::readStellariumCommand()
qDebug() << "StarTrackerWorker: New target from Stellarum: " << m_settings.m_ra << " " << m_settings.m_dec;
// Forward to GUI for display
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(raDeg, decDeg));
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(raDeg, decDeg, "target"));
}
}
else
{
@ -345,7 +367,7 @@ void StarTrackerWorker::writeStellariumTarget(double ra, double dec)
}
void StarTrackerWorker::updateRaDec(RADec rd, QDateTime dt)
void StarTrackerWorker::updateRaDec(RADec rd, QDateTime dt, bool lbTarget)
{
RADec rdJ2000;
double jd;
@ -356,14 +378,15 @@ void StarTrackerWorker::updateRaDec(RADec rd, QDateTime dt)
// Send to Stellarium
writeStellariumTarget(rdJ2000.ra, rdJ2000.dec);
// Send to GUI
if (m_settings.m_target == "Sun" || m_settings.m_target == "Moon")
if (m_settings.m_target == "Sun" || m_settings.m_target == "Moon" || (m_settings.m_target == "Custom Az/El") || lbTarget)
{
if (getMessageQueueToGUI())
{
if (m_settings.m_jnow)
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec));
else
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rdJ2000.ra, rdJ2000.dec));
if (m_settings.m_jnow) {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec, "target"));
} else {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rdJ2000.ra, rdJ2000.dec, "target"));
}
}
}
}
@ -372,8 +395,7 @@ void StarTrackerWorker::removeFromMap(QString id)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems");
if (mapMessageQueues)
{
if (mapMessageQueues) {
sendToMap(mapMessageQueues, id, "", "", 0.0, 0.0);
}
}
@ -438,30 +460,41 @@ void StarTrackerWorker::update()
{
AzAlt aa, sunAA, moonAA;
RADec rd, sunRD, moonRD;
double l, b;
bool lbTarget = false;
QDateTime dt;
// Get date and time to calculate position at
if (m_settings.m_dateTime == "")
if (m_settings.m_dateTime == "") {
dt = QDateTime::currentDateTime();
else
} else {
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
}
// Calculate position
if ((m_settings.m_target == "Sun") || (m_settings.m_drawSunOnMap))
if ((m_settings.m_target == "Sun") || m_settings.m_drawSunOnMap || m_settings.m_drawSunOnSkyTempChart)
{
Astronomy::sunPosition(sunAA, sunRD, m_settings.m_latitude, m_settings.m_longitude, dt);
if ((m_settings.m_target == "Moon") || (m_settings.m_drawMoonOnMap))
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(sunRD.ra, sunRD.dec, "sun"));
}
if ((m_settings.m_target == "Moon") || m_settings.m_drawMoonOnMap || m_settings.m_drawMoonOnSkyTempChart)
{
Astronomy::moonPosition(moonAA, moonRD, m_settings.m_latitude, m_settings.m_longitude, dt);
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(moonRD.ra, moonRD.dec, "moon"));
}
if (m_settings.m_target == "Sun")
{
rd = sunRD;
aa = sunAA;
rd = sunRD;
aa = sunAA;
Astronomy::equatorialToGalactic(rd.ra, rd.dec, l, b);
}
else if (m_settings.m_target == "Moon")
{
rd = moonRD;
aa = moonAA;
rd = moonRD;
aa = moonAA;
Astronomy::equatorialToGalactic(rd.ra, rd.dec, l, b);
}
else if (m_settings.m_target == "Custom Az/El")
{
@ -469,6 +502,26 @@ void StarTrackerWorker::update()
aa.alt = m_settings.m_el;
aa.az = m_settings.m_az;
rd = Astronomy::azAltToRaDec(aa, m_settings.m_latitude, m_settings.m_longitude, dt);
// Precess RA/DEC from Jnow to J2000
RADec rd2000 = Astronomy::precess(rd, Astronomy::julianDate(dt), Astronomy::jd_j2000());
// Convert to l/b
Astronomy::equatorialToGalactic(rd2000.ra, rd2000.dec, l, b);
if (!m_settings.m_jnow) {
rd = rd2000;
}
}
else if ( (m_settings.m_target == "Custom l/b")
|| (m_settings.m_target == "S7")
|| (m_settings.m_target == "S8")
|| (m_settings.m_target == "S9")
)
{
// Convert l/b to RA/Dec, then Alt/Az
l = m_settings.m_l;
b = m_settings.m_b;
Astronomy::galacticToEquatorial(l, b, rd.ra, rd.dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
lbTarget = true;
}
else
{
@ -476,8 +529,9 @@ void StarTrackerWorker::update()
rd.ra = Astronomy::raToDecimal(m_settings.m_ra);
rd.dec = Astronomy::decToDecimal(m_settings.m_dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
Astronomy::equatorialToGalactic(rd.ra, rd.dec, l, b);
}
updateRaDec(rd, dt);
updateRaDec(rd, dt, lbTarget);
// Adjust for refraction
if (m_settings.m_refraction == "Positional Astronomy Library")
@ -485,48 +539,99 @@ void StarTrackerWorker::update()
aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate);
if (aa.alt > 90.0)
if (aa.alt > 90.0) {
aa.alt = 90.0f;
}
}
else if (m_settings.m_refraction == "Saemundsson")
{
aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature);
if (aa.alt > 90.0)
if (aa.alt > 90.0) {
aa.alt = 90.0f;
}
}
// Add user-adjustment
aa.alt += m_settings.m_elOffset;
aa.az += m_settings.m_azOffset;
// Send to GUI
if (getMessageQueueToGUI())
{
if (m_settings.m_target == "Custom Az/El") {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec));
} else {
// MsgReportRADec sent in updateRaDec()
if (m_settings.m_target != "Custom Az/El") {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportAzAl::create(aa.az, aa.alt));
}
if (!lbTarget) {
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportGalactic::create(l, b));
}
}
// Send Az/El to Rotator Controllers
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "target");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
QList<MessageQueue*>* messageQueues;
for (; it != mapMessageQueues->end(); ++it)
// Send Az/El to Rotator Controllers
// Unless we're receiving settings to display from a Radio Astronomy plugins
if (!m_settings.m_link)
{
messageQueues = messagePipes.getMessageQueues(m_starTracker, "target");
if (messageQueues)
{
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
swgTarget->setName(new QString(m_settings.m_target));
swgTarget->setAzimuth(aa.az);
swgTarget->setElevation(aa.alt);
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_starTracker, swgTarget));
QList<MessageQueue*>::iterator it = messageQueues->begin();
for (; it != messageQueues->end(); ++it)
{
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
swgTarget->setName(new QString(m_settings.m_target));
swgTarget->setAzimuth(aa.az);
swgTarget->setElevation(aa.alt);
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_starTracker, swgTarget));
}
}
}
// Send Az/El, RA/Dec and Galactic b/l to Radio Astronomy plugins
// Unless we're receiving settings to display from a Radio Astronomy plugins
if (!m_settings.m_link)
{
messageQueues = messagePipes.getMessageQueues(m_starTracker, "startracker.target");
if (messageQueues)
{
QList<MessageQueue*>::iterator it = messageQueues->begin();
for (; it != messageQueues->end(); ++it)
{
SWGSDRangel::SWGStarTrackerTarget *swgTarget = new SWGSDRangel::SWGStarTrackerTarget();
swgTarget->setName(new QString(m_settings.m_target));
swgTarget->setAzimuth(aa.az);
swgTarget->setElevation(aa.alt);
swgTarget->setRa(rd.ra);
swgTarget->setDec(rd.dec);
swgTarget->setB(b);
swgTarget->setL(l);
swgTarget->setSolarFlux(m_solarFlux);
swgTarget->setAirTemperature(m_settings.m_temperature);
double temp;
m_starTracker->calcSkyTemperature(m_settings.m_frequency, m_settings.m_beamwidth, rd.ra, rd.dec, temp);
swgTarget->setSkyTemperature(temp);
swgTarget->setHpbw(m_settings.m_beamwidth);
// Calculate velocities
double vRot = Astronomy::earthRotationVelocity(rd, m_settings.m_latitude, m_settings.m_longitude, dt);
swgTarget->setEarthRotationVelocity(vRot);
double vOrbit = Astronomy::earthOrbitVelocityBCRS(rd,dt);
swgTarget->setEarthOrbitVelocityBcrs(vOrbit);
double vLSRK = Astronomy::sunVelocityLSRK(rd);
swgTarget->setSunVelocityLsr(vLSRK);
(*it)->push(MainCore::MsgStarTrackerTarget::create(m_starTracker, swgTarget));
}
}
}
// Send to Map
if (m_settings.m_drawSunOnMap || m_settings.m_drawMoonOnMap || m_settings.m_drawStarOnMap)
{
mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems");
if (mapMessageQueues)
messageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems");
if (messageQueues)
{
// Different between GMST(Lst at Greenwich) and RA
double lst = Astronomy::localSiderealTime(dt, 0.0);
@ -537,7 +642,7 @@ void StarTrackerWorker::update()
{
sunLongitude = Astronomy::lstAndRAToLongitude(lst, sunRD.ra);
sunLatitude = sunRD.dec;
sendToMap(mapMessageQueues, "Sun", "qrc:///startracker/startracker/sun-40.png", "Sun", sunLatitude, sunLongitude);
sendToMap(messageQueues, "Sun", "qrc:///startracker/startracker/sun-40.png", "Sun", sunLatitude, sunLongitude);
}
if (m_settings.m_drawMoonOnMap)
{
@ -545,7 +650,7 @@ void StarTrackerWorker::update()
double moonLatitude = moonRD.dec;
double moonRotation;
QString phase = moonPhase(sunLongitude, moonLongitude, m_settings.m_latitude, moonRotation);
sendToMap(mapMessageQueues, "Moon", QString("qrc:///startracker/startracker/moon-%1-32").arg(phase), "Moon",
sendToMap(messageQueues, "Moon", QString("qrc:///startracker/startracker/moon-%1-32").arg(phase), "Moon",
moonLatitude, moonLongitude, moonRotation);
}
if ((m_settings.m_drawStarOnMap) && (m_settings.m_target != "Sun") && (m_settings.m_target != "Moon"))
@ -553,7 +658,7 @@ void StarTrackerWorker::update()
double starLongitude = Astronomy::lstAndRAToLongitude(lst, rd.ra);
double starLatitude = rd.dec;
QString text = m_settings.m_target.startsWith("Custom") ? "Star" : m_settings.m_target;
sendToMap(mapMessageQueues, "Star", "qrc:///startracker/startracker/pulsar-32.png", text, starLatitude, starLongitude);
sendToMap(messageQueues, "Star", "qrc:///startracker/startracker/pulsar-32.png", text, starLatitude, starLongitude);
}
}
}

View File

@ -85,12 +85,13 @@ private:
QTimer m_pollTimer;
QTcpServer *m_tcpServer;
QTcpSocket *m_clientConnection;
float m_solarFlux;
bool handleMessage(const Message& cmd);
void applySettings(const StarTrackerSettings& settings, bool force = false);
void restartServer(bool enabled, uint32_t port);
MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; }
void updateRaDec(RADec rd, QDateTime dt);
void updateRaDec(RADec rd, QDateTime dt, bool lbTarget);
void writeStellariumTarget(double ra, double dec);
void removeFromMap(QString id);
void sendToMap(QList<MessageQueue*> *mapMessageQueues, QString id, QString image, QString text, double lat, double lon, double rotation=0.0);

View File

@ -198,6 +198,7 @@ set(sdrbase_SOURCES
util/message.cpp
util/messagequeue.cpp
util/morse.cpp
util/png.cpp
util/prettyprint.cpp
util/rtpsink.cpp
util/syncmessenger.cpp
@ -209,6 +210,7 @@ set(sdrbase_SOURCES
util/units.cpp
util/timeutil.cpp
util/visa.cpp
util/weather.cpp
plugin/plugininterface.cpp
plugin/pluginapi.cpp
@ -406,6 +408,7 @@ set(sdrbase_HEADERS
util/messagequeue.h
util/morse.h
util/movingaverage.h
util/png.h
util/prettyprint.h
util/rtpsink.h
util/syncmessenger.h
@ -417,6 +420,7 @@ set(sdrbase_HEADERS
util/units.h
util/timeutil.h
util/visa.h
util/weather.h
webapi/webapiadapter.h
webapi/webapiadapterbase.h

View File

@ -130,6 +130,15 @@ public:
virtual int getNbSourceStreams() const = 0;
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const = 0;
void handlePipeMessageQueue(MessageQueue* messageQueue)
{
Message* message;
while ((message = messageQueue->pop()) != nullptr) {
m_channelMessageQueue.push(message);
}
}
protected:
MessageQueue *m_guiMessageQueue; //!< Input message queue to the GUI
MessageQueue m_channelMessageQueue; //!< Input message queue for inter plugin communication

View File

@ -27,6 +27,8 @@
#include "SWGDeviceSet.h"
#include "SWGChannelActions.h"
#include "SWGFileSinkActions.h"
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "maincore.h"
#include "device/deviceset.h"
@ -37,14 +39,13 @@
#include "dsp/devicesamplemimo.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
#include "feature/featureset.h"
#include "feature/feature.h"
// Get device center frequency
bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz)
bool ChannelWebAPIUtils::getDeviceSettings(unsigned int deviceIndex, SWGSDRangel::SWGDeviceSettings &deviceSettingsResponse, DeviceSet *&deviceSet)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
// Get current device settings
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
@ -74,114 +75,129 @@ bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &fr
}
else
{
qDebug() << "ChannelWebAPIUtils::getCenterFrequency - not a sample source device " << deviceIndex;
qDebug() << "ChannelWebAPIUtils::getDeviceSettings - not a sample source device " << deviceIndex;
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::getCenterFrequency - no device " << deviceIndex;
qDebug() << "ChannelWebAPIUtils::getDeviceSettings - no device " << deviceIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getCenterFrequency: get device frequency error %d: %s",
qWarning("ChannelWebAPIUtils::getDeviceSettings: get device settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
return WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz);
return true;
}
bool ChannelWebAPIUtils::getFeatureSettings(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureSettings &featureSettingsResponse, Feature *&feature)
{
QString errorResponse;
int httpRC;
FeatureSet *featureSet;
// Get current feature settings
std::vector<FeatureSet*> featureSets = MainCore::instance()->getFeatureeSets();
if (featureSetIndex < featureSets.size())
{
featureSet = featureSets[featureSetIndex];
if (featureIndex < (unsigned int)featureSet->getNumberOfFeatures())
{
feature = featureSet->getFeatureAt(featureIndex);
httpRC = feature->webapiSettingsGet(featureSettingsResponse, errorResponse);
}
else
{
qDebug() << "ChannelWebAPIUtils::getFeatureSettings: no feature " << featureSetIndex << ":" << featureIndex;
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::getFeatureSettings: no feature set " << featureSetIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getFeatureSettings: get feature settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
// Get device center frequency
bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
DeviceSet *deviceSet;
if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet))
{
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
return WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz);
}
else
{
return false;
}
}
// Set device center frequency
bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double frequencyInHz)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
// Get current device settings
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
if (deviceIndex < deviceSets.size())
if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet))
{
deviceSet = deviceSets[deviceIndex];
if (deviceSet->m_deviceSourceEngine)
// Patch centerFrequency
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
double freq;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", freq))
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(0);
WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz);
QStringList deviceSettingsKeys;
deviceSettingsKeys.append("centerFrequency");
deviceSettingsResponse.init();
deviceSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceSinkEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(1);
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
httpRC = sink->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceMIMOEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(2);
DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO();
httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, errorResponse);
httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage());
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::setCenterFrequency: set device frequency %f OK", frequencyInHz);
return true;
}
else
{
qWarning("ChannelWebAPIUtils::setCenterFrequency: set device frequency error %d: %s",
httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::setCenterFrequency: not a sample source device " << deviceIndex;
qWarning("ChannelWebAPIUtils::setCenterFrequency: no centerFrequency key in device settings");
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::setCenterFrequency: no device " << deviceIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::setCenterFrequency: get device frequency error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
// Patch centerFrequency
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
double freq;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", freq))
{
WebAPIUtils::setSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz);
QStringList deviceSettingsKeys;
deviceSettingsKeys.append("centerFrequency");
deviceSettingsResponse.init();
deviceSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage());
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::setCenterFrequency: set device frequency %f OK", frequencyInHz);
}
else
{
qWarning("ChannelWebAPIUtils::setCenterFrequency: set device frequency error %d: %s",
httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qWarning("ChannelWebAPIUtils::setCenterFrequency: no centerFrequency key in device settings");
return false;
}
return true;
}
// Start acquisition
@ -462,3 +478,213 @@ bool ChannelWebAPIUtils::satelliteLOS(const QString name)
}
return true;
}
bool ChannelWebAPIUtils::getDeviceSetting(unsigned int deviceIndex, const QString &setting, int &value)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
DeviceSet *deviceSet;
if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet))
{
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
return WebAPIUtils::getSubObjectInt(*jsonObj, setting, value);
}
else
{
return false;
}
}
bool ChannelWebAPIUtils::patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
if (getDeviceSettings(deviceIndex, deviceSettingsResponse, deviceSet))
{
// Patch centerFrequency
QJsonObject *jsonObj = deviceSettingsResponse.asJsonObject();
int oldValue;
if (WebAPIUtils::getSubObjectInt(*jsonObj, setting, oldValue))
{
WebAPIUtils::setSubObjectInt(*jsonObj, setting, value);
QStringList deviceSettingsKeys;
deviceSettingsKeys.append(setting);
deviceSettingsResponse.init();
deviceSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiSettingsPutPatch(false, deviceSettingsKeys, deviceSettingsResponse, *errorResponse2.getMessage());
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::patchDeviceSetting: set device setting %s OK", qPrintable(setting));
return true;
}
else
{
qWarning("ChannelWebAPIUtils::patchDeviceSetting: set device setting error %d: %s",
httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qWarning("ChannelWebAPIUtils::patchDeviceSetting: no key %s in device settings", qPrintable(setting));
return false;
}
}
else
{
return false;
}
}
// Set feature setting
bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value)
{
SWGSDRangel::SWGFeatureSettings featureSettingsResponse;
int httpRC;
Feature *feature;
if (getFeatureSettings(featureSetIndex, featureIndex, featureSettingsResponse, feature))
{
// Patch settings
QJsonObject *jsonObj = featureSettingsResponse.asJsonObject();
QString oldValue;
if (WebAPIUtils::getSubObjectString(*jsonObj, setting, oldValue))
{
WebAPIUtils::setSubObjectString(*jsonObj, setting, value);
QStringList featureSettingsKeys;
featureSettingsKeys.append(setting);
featureSettingsResponse.init();
featureSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
httpRC = feature->webapiSettingsPutPatch(false, featureSettingsKeys, featureSettingsResponse, *errorResponse2.getMessage());
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %s OK", qPrintable(setting), qPrintable(value));
return true;
}
else
{
qWarning("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %s error %d: %s",
qPrintable(setting), qPrintable(value), httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qWarning("ChannelWebAPIUtils::patchFeatureSetting: no key %s in feature settings", qPrintable(setting));
return false;
}
}
else
{
return false;
}
}
bool ChannelWebAPIUtils::patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value)
{
SWGSDRangel::SWGFeatureSettings featureSettingsResponse;
QString errorResponse;
int httpRC;
Feature *feature;
if (getFeatureSettings(featureSetIndex, featureIndex, featureSettingsResponse, feature))
{
// Patch settings
QJsonObject *jsonObj = featureSettingsResponse.asJsonObject();
double oldValue;
if (WebAPIUtils::getSubObjectDouble(*jsonObj, setting, oldValue))
{
WebAPIUtils::setSubObjectDouble(*jsonObj, setting, value);
QStringList featureSettingsKeys;
featureSettingsKeys.append(setting);
featureSettingsResponse.init();
featureSettingsResponse.fromJsonObject(*jsonObj);
SWGSDRangel::SWGErrorResponse errorResponse2;
httpRC = feature->webapiSettingsPutPatch(false, featureSettingsKeys, featureSettingsResponse, *errorResponse2.getMessage());
if (httpRC/100 == 2)
{
qDebug("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %f OK", qPrintable(setting), value);
return true;
}
else
{
qWarning("ChannelWebAPIUtils::patchFeatureSetting: set feature setting %s to %f error %d: %s",
qPrintable(setting), value, httpRC, qPrintable(*errorResponse2.getMessage()));
return false;
}
}
else
{
qWarning("ChannelWebAPIUtils::patchFeatureSetting: no key %s in feature settings", qPrintable(setting));
return false;
}
}
else
{
return false;
}
}
bool ChannelWebAPIUtils::getFeatureReportValue(unsigned int featureSetIndex, unsigned int featureIndex, const QString &key, int &value)
{
SWGSDRangel::SWGFeatureReport featureReport;
QString errorResponse;
int httpRC;
FeatureSet *featureSet;
Feature *feature;
// Get feature report
std::vector<FeatureSet*> featureSets = MainCore::instance()->getFeatureeSets();
if (featureSetIndex < featureSets.size())
{
featureSet = featureSets[featureSetIndex];
if (featureIndex < (unsigned int)featureSet->getNumberOfFeatures())
{
feature = featureSet->getFeatureAt(featureIndex);
httpRC = feature->webapiReportGet(featureReport, errorResponse);
}
else
{
qDebug() << "ChannelWebAPIUtils::getFeatureReportValue: no feature " << featureSetIndex << ":" << featureIndex;
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::getFeatureReportValue: no feature set " << featureSetIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getFeatureReportValue: get feature report error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
// Get value of requested key
QJsonObject *jsonObj = featureReport.asJsonObject();
if (WebAPIUtils::getSubObjectInt(*jsonObj, key, value))
{
// Done
return true;
}
else
{
qWarning("ChannelWebAPIUtils::getFeatureReportValue: no key %s in feature report", qPrintable(key));
return false;
}
}

View File

@ -20,8 +20,14 @@
#include <QString>
#include "SWGDeviceSettings.h"
#include "SWGFeatureSettings.h"
#include "export.h"
class DeviceSet;
class Feature;
class SDRBASE_API ChannelWebAPIUtils
{
public:
@ -34,6 +40,14 @@ public:
static bool startStopFileSinks(unsigned int deviceIndex, bool start);
static bool satelliteAOS(const QString name, bool northToSouthPass);
static bool satelliteLOS(const QString name);
static bool getDeviceSetting(unsigned int deviceIndex, const QString &setting, int &value);
static bool patchDeviceSetting(unsigned int deviceIndex, const QString &setting, int value);
static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, const QString &value);
static bool patchFeatureSetting(unsigned int featureSetIndex, unsigned int featureIndex, const QString &setting, double value);
static bool getFeatureReportValue(unsigned int featureSetIndex, unsigned int featureIndex, const QString &key, int &value);
protected:
static bool getDeviceSettings(unsigned int deviceIndex, SWGSDRangel::SWGDeviceSettings &deviceSettingsResponse, DeviceSet *&deviceSet);
static bool getFeatureSettings(unsigned int featureSetIndex, unsigned int featureIndex, SWGSDRangel::SWGFeatureSettings &featureSettingsResponse, Feature *&feature);
};
#endif // SDRBASE_CHANNEL_CHANNELWEBAPIUTILS_H_

View File

@ -54,6 +54,9 @@ MESSAGE_CLASS_DEFINITION(MainCore::MsgChannelDemodQuery, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgMapItem, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgPacket, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgTargetAzimuthElevation, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgStarTrackerTarget, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgStarTrackerDisplaySettings, Message)
MESSAGE_CLASS_DEFINITION(MainCore::MsgStarTrackerDisplayLoSSettings, Message)
MainCore::MainCore()
{

View File

@ -47,6 +47,9 @@ namespace SWGSDRangel
class SWGChannelSettings;
class SWGMapItem;
class SWGTargetAzimuthElevation;
class SWGStarTrackerTarget;
class SWGStarTrackerDisplaySettings;
class SWGStarTrackerDisplayLoSSettings;
}
class SDRBASE_API MainCore
@ -622,6 +625,77 @@ public:
{ }
};
// Messages between Star Tracker and Radio Astronomy plugins
class SDRBASE_API MsgStarTrackerTarget : public Message {
MESSAGE_CLASS_DECLARATION
public:
const PipeEndPoint *getPipeSource() const { return m_pipeSource; }
SWGSDRangel::SWGStarTrackerTarget *getSWGStarTrackerTarget() const { return m_swgStarTrackerTarget; }
static MsgStarTrackerTarget* create(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerTarget *swgStarTrackerTarget)
{
return new MsgStarTrackerTarget(pipeSource, swgStarTrackerTarget);
}
private:
const PipeEndPoint *m_pipeSource;
SWGSDRangel::SWGStarTrackerTarget *m_swgStarTrackerTarget;
MsgStarTrackerTarget(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerTarget *swgStarTrackerTarget) :
Message(),
m_pipeSource(pipeSource),
m_swgStarTrackerTarget(swgStarTrackerTarget)
{ }
};
class SDRBASE_API MsgStarTrackerDisplaySettings : public Message {
MESSAGE_CLASS_DECLARATION
public:
const PipeEndPoint *getPipeSource() const { return m_pipeSource; }
SWGSDRangel::SWGStarTrackerDisplaySettings *getSWGStarTrackerDisplaySettings() const { return m_swgStarTrackerDisplaySettings; }
static MsgStarTrackerDisplaySettings* create(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerDisplaySettings *swgStarTrackerDisplaySettings)
{
return new MsgStarTrackerDisplaySettings(pipeSource, swgStarTrackerDisplaySettings);
}
private:
const PipeEndPoint *m_pipeSource;
SWGSDRangel::SWGStarTrackerDisplaySettings *m_swgStarTrackerDisplaySettings;
MsgStarTrackerDisplaySettings(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerDisplaySettings *swgStarTrackerDisplaySettings) :
Message(),
m_pipeSource(pipeSource),
m_swgStarTrackerDisplaySettings(swgStarTrackerDisplaySettings)
{ }
};
class SDRBASE_API MsgStarTrackerDisplayLoSSettings : public Message {
MESSAGE_CLASS_DECLARATION
public:
const PipeEndPoint *getPipeSource() const { return m_pipeSource; }
SWGSDRangel::SWGStarTrackerDisplayLoSSettings *getSWGStarTrackerDisplayLoSSettings() const { return m_swgStarTrackerDisplayLoSSettings; }
static MsgStarTrackerDisplayLoSSettings* create(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgStarTrackerDisplayLoSSettings)
{
return new MsgStarTrackerDisplayLoSSettings(pipeSource, swgStarTrackerDisplayLoSSettings);
}
private:
const PipeEndPoint *m_pipeSource;
SWGSDRangel::SWGStarTrackerDisplayLoSSettings *m_swgStarTrackerDisplayLoSSettings;
MsgStarTrackerDisplayLoSSettings(const PipeEndPoint *pipeSource, SWGSDRangel::SWGStarTrackerDisplayLoSSettings *swgStarTrackerDisplayLoSSettings) :
Message(),
m_pipeSource(pipeSource),
m_swgStarTrackerDisplayLoSSettings(swgStarTrackerDisplayLoSSettings)
{ }
};
MainCore();

View File

@ -42,14 +42,14 @@ MessagePipes::~MessagePipes()
}
}
MessageQueue *MessagePipes::registerChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type)
MessageQueue *MessagePipes::registerChannelToFeature(const PipeEndPoint *source, PipeEndPoint *dest, const QString& type)
{
return m_registrations.registerProducerToConsumer(source, feature, type);
return m_registrations.registerProducerToConsumer(source, dest, type);
}
MessageQueue *MessagePipes::unregisterChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type)
MessageQueue *MessagePipes::unregisterChannelToFeature(const PipeEndPoint *source, PipeEndPoint *dest, const QString& type)
{
MessageQueue *messageQueue = m_registrations.unregisterProducerToConsumer(source, feature, type);
MessageQueue *messageQueue = m_registrations.unregisterProducerToConsumer(source, dest, type);
m_gcWorker->addMessageQueueToDelete(messageQueue);
return messageQueue;
}

View File

@ -30,7 +30,6 @@
#include "elementpipesregistrations.h"
class PipeEndPoint;
class Feature;
class MessagePipesGCWorker;
class MessageQueue;
@ -43,12 +42,13 @@ public:
MessagePipes& operator=(const MessagePipes&) = delete;
~MessagePipes();
MessageQueue *registerChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type);
MessageQueue *unregisterChannelToFeature(const PipeEndPoint *source, Feature *feature, const QString& type);
// FIXME: Names of these functions should probably change, as we now support channel or feature at either end
MessageQueue *registerChannelToFeature(const PipeEndPoint *source, PipeEndPoint *dest, const QString& type);
MessageQueue *unregisterChannelToFeature(const PipeEndPoint *source, PipeEndPoint *dest, const QString& type);
QList<MessageQueue*>* getMessageQueues(const PipeEndPoint *source, const QString& type);
private:
ElementPipesRegistrations<PipeEndPoint, Feature, MessageQueue> m_registrations;
ElementPipesRegistrations<PipeEndPoint, PipeEndPoint, MessageQueue> m_registrations;
QThread m_gcThread; //!< Garbage collector thread
MessagePipesGCWorker *m_gcWorker; //!< Garbage collector

View File

@ -27,7 +27,6 @@
#include "elementpipescommon.h"
class PipeEndPoint;
class Feature;
class MessageQueue;
class SDRBASE_API MessagePipesCommon

View File

@ -24,26 +24,33 @@
bool MessagePipesGCWorker::MessagePipesGC::existsProducer(const PipeEndPoint *pipeEndPoint)
{
// Not overly sure about casting to both types here, but currently safeish as the
// existing functions only use the pointer address - and I presume these
// may be pointers to deleted objects anyway?
return MainCore::instance()->existsChannel((const ChannelAPI *)pipeEndPoint)
|| MainCore::instance()->existsFeature((const Feature *)pipeEndPoint);
}
bool MessagePipesGCWorker::MessagePipesGC::existsConsumer(const Feature *feature)
bool MessagePipesGCWorker::MessagePipesGC::existsConsumer(const PipeEndPoint *pipeEndPoint)
{
return MainCore::instance()->existsFeature(feature);
return MainCore::instance()->existsChannel((const ChannelAPI *)pipeEndPoint)
|| MainCore::instance()->existsFeature((const Feature *)pipeEndPoint);
}
void MessagePipesGCWorker::MessagePipesGC::sendMessageToConsumer(
const MessageQueue *messageQueue,
MessagePipesCommon::ChannelRegistrationKey channelKey,
Feature *feature)
PipeEndPoint *pipeEndPoint)
{
MessagePipesCommon::MsgReportChannelDeleted *msg = MessagePipesCommon::MsgReportChannelDeleted::create(
messageQueue, channelKey);
feature->getInputMessageQueue()->push(msg);
if (MainCore::instance()->existsFeature((const Feature *)pipeEndPoint)) // Use RTTI instead?
{
Feature *feature = (Feature *)pipeEndPoint;
feature->getInputMessageQueue()->push(msg);
}
else
{
ChannelAPI *channel = (ChannelAPI *)pipeEndPoint;
channel->getChannelMessageQueue()->push(msg);
}
}
MessagePipesGCWorker::MessagePipesGCWorker() :

View File

@ -38,10 +38,10 @@ public:
void setC2FRegistrations(
QMutex *c2fMutex,
QMap<MessagePipesCommon::ChannelRegistrationKey, QList<MessageQueue*>> *c2fQueues,
QMap<MessagePipesCommon::ChannelRegistrationKey, QList<Feature*>> *c2fFeatures
QMap<MessagePipesCommon::ChannelRegistrationKey, QList<PipeEndPoint*>> *c2fPipeEndPoints
)
{
m_messagePipesGC.setRegistrations(c2fMutex, c2fQueues, c2fFeatures);
m_messagePipesGC.setRegistrations(c2fMutex, c2fQueues, c2fPipeEndPoints);
}
void startWork();
@ -50,12 +50,12 @@ public:
bool isRunning() const { return m_running; }
private:
class MessagePipesGC : public ElementPipesGC<PipeEndPoint, Feature, MessageQueue>
class MessagePipesGC : public ElementPipesGC<PipeEndPoint, PipeEndPoint, MessageQueue>
{
private:
virtual bool existsProducer(const PipeEndPoint *pipeEndPoint);
virtual bool existsConsumer(const Feature *feature);
virtual void sendMessageToConsumer(const MessageQueue *messageQueue, MessagePipesCommon::ChannelRegistrationKey key, Feature *feature);
virtual bool existsConsumer(const PipeEndPoint *pipeEndPoint);
virtual void sendMessageToConsumer(const MessageQueue *messageQueue, MessagePipesCommon::ChannelRegistrationKey key, PipeEndPoint *pipeEndPoint);
};
MessagePipesGC m_messagePipesGC;

View File

@ -31,13 +31,14 @@
MESSAGE_CLASS_DEFINITION(PipeEndPoint::MsgReportPipes, Message)
QList<PipeEndPoint::AvailablePipeSource> PipeEndPoint::updateAvailablePipeSources(QString pipeName, QStringList pipeTypes, QStringList pipeURIs, Feature *destinationFeature)
QList<PipeEndPoint::AvailablePipeSource> PipeEndPoint::updateAvailablePipeSources(QString pipeName, QStringList pipeTypes, QStringList pipeURIs, PipeEndPoint *destination)
{
MainCore *mainCore = MainCore::instance();
MessagePipes& messagePipes = mainCore->getMessagePipes();
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
QHash<PipeEndPoint *, AvailablePipeSource> availablePipes;
// Source is a channel
int deviceIndex = 0;
for (std::vector<DeviceSet*>::const_iterator it = deviceSets.begin(); it != deviceSets.end(); ++it, deviceIndex++)
{
@ -55,14 +56,30 @@ QList<PipeEndPoint::AvailablePipeSource> PipeEndPoint::updateAvailablePipeSource
{
if (!availablePipes.contains(channel))
{
MessageQueue *messageQueue = messagePipes.registerChannelToFeature(channel, destinationFeature, pipeName);
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
destinationFeature,
[=](){ destinationFeature->handlePipeMessageQueue(messageQueue); },
Qt::QueuedConnection
);
MessageQueue *messageQueue = messagePipes.registerChannelToFeature(channel, destination, pipeName);
if (MainCore::instance()->existsFeature((const Feature *)destination))
{
// Destination is feature
Feature *featureDest = (Feature *)destination;
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
featureDest,
[=](){ featureDest->handlePipeMessageQueue(messageQueue); },
Qt::QueuedConnection
);
}
else
{
// Destination is a channel
// Can't use Qt::QueuedConnection because ChannelAPI isn't a QObject
ChannelAPI *channelDest = (ChannelAPI *)destination;
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
[=](){ channelDest->handlePipeMessageQueue(messageQueue); }
);
}
}
AvailablePipeSource availablePipe =
@ -79,6 +96,7 @@ QList<PipeEndPoint::AvailablePipeSource> PipeEndPoint::updateAvailablePipeSource
}
}
// Source is a feature
std::vector<FeatureSet*>& featureSets = mainCore->getFeatureeSets();
int featureIndex = 0;
for (std::vector<FeatureSet*>::const_iterator it = featureSets.begin(); it != featureSets.end(); ++it, featureIndex++)
@ -92,14 +110,30 @@ QList<PipeEndPoint::AvailablePipeSource> PipeEndPoint::updateAvailablePipeSource
{
if (!availablePipes.contains(feature))
{
MessageQueue *messageQueue = messagePipes.registerChannelToFeature(feature, destinationFeature, pipeName);
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
destinationFeature,
[=](){ destinationFeature->handlePipeMessageQueue(messageQueue); },
Qt::QueuedConnection
);
MessageQueue *messageQueue = messagePipes.registerChannelToFeature(feature, destination, pipeName);
if (MainCore::instance()->existsFeature((const Feature *)destination))
{
// Destination is feature
Feature *featureDest = (Feature *)destination;
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
featureDest,
[=](){ featureDest->handlePipeMessageQueue(messageQueue); },
Qt::QueuedConnection
);
}
else
{
// Destination is a channel
// Can't use Qt::QueuedConnection because ChannelAPI isn't a QObject
ChannelAPI *channelDest = (ChannelAPI *)destination;
QObject::connect(
messageQueue,
&MessageQueue::messageEnqueued,
[=](){ channelDest->handlePipeMessageQueue(messageQueue); }
);
}
}
AvailablePipeSource availablePipe =
@ -135,13 +169,17 @@ PipeEndPoint *PipeEndPoint::getPipeEndPoint(const QString name, const QList<Avai
QString id = re.capturedTexts()[4];
QListIterator<AvailablePipeSource> itr(availablePipeSources);
while (itr.hasNext()) {
while (itr.hasNext())
{
AvailablePipeSource p = itr.next();
if ((p.m_setIndex == setIndex) && (p.m_index == index) && (id == p.m_id))
if ((p.m_setIndex == setIndex) && (p.m_index == index) && (id == p.m_id)) {
return p.m_source;
}
}
}
else
{
qDebug() << "PipeEndPoint::getPipeEndPoint: " << name << " is malformed";
}
return nullptr;
}

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