sdrangel/plugins/feature/satellitetracker/satellitetracker.cpp

1145 lines
46 KiB
C++

///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "SWGDeviceState.h"
#include "SWGSatelliteTrackerSettings.h"
#include "SWGSatelliteDeviceSettings.h"
#include "util/httpdownloadmanager.h"
#include "settings/serializable.h"
#include "channel/channelwebapiutils.h"
#include "feature/featurewebapiutils.h"
#include "satellitetrackerworker.h"
#include "satellitetrackerreport.h"
#include "satellitetracker.h"
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgConfigureSatelliteTracker, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgUpdateSatData, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgSatData, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgError, Message)
const char* const SatelliteTracker::m_featureIdURI = "sdrangel.feature.satellitetracker";
const char* const SatelliteTracker::m_featureId = "SatelliteTracker";
SatelliteTracker::SatelliteTracker(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface),
m_thread(nullptr),
m_worker(nullptr),
m_updatingSatData(false),
m_tleIndex(0),
m_firstUpdateSatData(true)
{
qDebug("SatelliteTracker::SatelliteTracker: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_state = StIdle;
m_errorMessage = "SatelliteTracker error";
m_networkManager = new QNetworkAccessManager();
QObject::connect(
m_networkManager,
&QNetworkAccessManager::finished,
this,
&SatelliteTracker::networkManagerFinished
);
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &SatelliteTracker::downloadFinished);
if (!readSatData())
updateSatData();
}
SatelliteTracker::~SatelliteTracker()
{
stop();
qDeleteAll(m_satState);
}
void SatelliteTracker::start()
{
qDebug("SatelliteTracker::start");
if (m_settings.m_replayEnabled) {
m_startedDateTime = QDateTime::currentDateTimeUtc();
}
if (m_settings.m_sendTimeToMap) {
FeatureWebAPIUtils::mapSetDateTime(currentDateTime());
}
m_thread = new QThread();
m_worker = new SatelliteTrackerWorker(this, m_webAPIAdapterInterface);
m_worker->moveToThread(m_thread);
QObject::connect(m_thread, &QThread::started, m_worker, &SatelliteTrackerWorker::startWork);
QObject::connect(m_thread, &QThread::finished, m_worker, &QObject::deleteLater);
QObject::connect(m_thread, &QThread::finished, m_thread, &QThread::deleteLater);
m_worker->setMessageQueueToFeature(getInputMessageQueue());
m_worker->setMessageQueueToGUI(getMessageQueueToGUI());
m_thread->start();
m_state = StRunning;
m_worker->getInputMessageQueue()->push(SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker::create(m_settings, QList<QString>(), true));
m_worker->getInputMessageQueue()->push(MsgSatData::create(m_satellites));
}
void SatelliteTracker::stop()
{
qDebug("SatelliteTracker::stop");
m_state = StIdle;
if (m_thread)
{
m_thread->quit();
m_thread->wait();
m_thread = nullptr;
m_worker = nullptr;
}
}
bool SatelliteTracker::handleMessage(const Message& cmd)
{
if (MsgConfigureSatelliteTracker::match(cmd))
{
MsgConfigureSatelliteTracker& cfg = (MsgConfigureSatelliteTracker&) cmd;
qDebug() << "SatelliteTracker::handleMessage: MsgConfigureSatelliteTracker";
applySettings(cfg.getSettings(), cfg.getSettingsKeys(), cfg.getForce());
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
qDebug() << "SatelliteTracker::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
if (cfg.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else if (MsgUpdateSatData::match(cmd))
{
// When the GUI first opens, it will make an initial request to update the sats
// In the first instance, just return the data we've read
if (m_firstUpdateSatData && (m_satellites.size() > 0))
{
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgSatData::create(m_satellites));
m_firstUpdateSatData = false;
}
else
updateSatData();
return true;
}
else if (SatelliteTrackerReport::MsgReportSat::match(cmd))
{
// Save latest satellite state for Web report
SatelliteTrackerReport::MsgReportSat& satReport = (SatelliteTrackerReport::MsgReportSat&) cmd;
SatelliteState *satState = satReport.getSatelliteState();
if (m_satState.contains(satState->m_name))
{
delete m_satState.value(satState->m_name);
m_satState.remove(satState->m_name);
}
if (m_settings.m_satellites.contains(satState->m_name)) {
m_satState.insert(satState->m_name, satState);
}
return true;
}
else
{
return false;
}
}
QByteArray SatelliteTracker::serialize() const
{
return m_settings.serialize();
}
bool SatelliteTracker::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(m_settings, QList<QString>(), true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(m_settings, QList<QString>(), true);
m_inputMessageQueue.push(msg);
return false;
}
}
void SatelliteTracker::applySettings(const SatelliteTrackerSettings& settings, const QList<QString>& settingsKeys, bool force)
{
bool tlesChanged = false;
qDebug() << "SatelliteTracker::applySettings:" << settings.getDebugString(settingsKeys, force) << " force: " << force;
if (settingsKeys.contains("tles") || force) {
tlesChanged = true;
}
SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker *msg = SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker::create(
settings, settingsKeys, force
);
if (m_worker) {
m_worker->getInputMessageQueue()->push(msg);
}
if (settings.m_useReverseAPI)
{
bool fullUpdate = (settingsKeys.contains("useReverseAPI") && settings.m_useReverseAPI) ||
settingsKeys.contains("reverseAPIAddress") ||
settingsKeys.contains("reverseAPIPort") ||
settingsKeys.contains("reverseAPIFeatureSetIndex") ||
settingsKeys.contains("m_reverseAPIFeatureIndex");
webapiReverseSendSettings(settingsKeys, settings, fullUpdate || force);
}
if (force) {
m_settings = settings;
} else {
m_settings.applySettings(settingsKeys, settings);
}
if (tlesChanged)
{
// Do we already have the TLE files, or do we need to download them?
bool existing = true;
for (int i = 0; i < m_settings.m_tles.size(); i++)
{
QFile tlesFile(tleURLToFilename(m_settings.m_tles[i]));
if (!tlesFile.exists())
{
existing = false;
break;
}
}
if (existing) {
readSatData();
} else {
updateSatData();
}
}
// Remove unneeded satellite state
QMutableHashIterator<QString, SatelliteState *> itr(m_satState);
while (itr.hasNext())
{
itr.next();
SatelliteState *satState = itr.value();
if (!m_settings.m_satellites.contains(satState->m_name))
{
delete satState;
itr.remove();
}
}
}
int SatelliteTracker::webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
getFeatureStateStr(*response.getState());
MsgStartStop *msg = MsgStartStop::create(run);
getInputMessageQueue()->push(msg);
return 202;
}
int SatelliteTracker::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSatelliteTrackerSettings(new SWGSDRangel::SWGSatelliteTrackerSettings());
response.getSatelliteTrackerSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int SatelliteTracker::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
SatelliteTrackerSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(settings, featureSettingsKeys, force);
m_inputMessageQueue.push(msg);
qDebug("SatelliteTracker::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureSatelliteTracker *msgToGUI = MsgConfigureSatelliteTracker::create(settings, featureSettingsKeys, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
int SatelliteTracker::webapiReportGet(
SWGSDRangel::SWGFeatureReport& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSatelliteTrackerReport(new SWGSDRangel::SWGSatelliteTrackerReport());
response.getSatelliteTrackerReport()->init();
webapiFormatFeatureReport(response);
return 200;
}
int SatelliteTracker::webapiActionsPost(
const QStringList& featureActionsKeys,
SWGSDRangel::SWGFeatureActions& query,
QString& errorMessage)
{
SWGSDRangel::SWGSatelliteTrackerActions *swgSatelliteTrackerActions = query.getSatelliteTrackerActions();
if (swgSatelliteTrackerActions)
{
if (featureActionsKeys.contains("run"))
{
bool featureRun = swgSatelliteTrackerActions->getRun() != 0;
MsgStartStop *msg = MsgStartStop::create(featureRun);
getInputMessageQueue()->push(msg);
return 202;
}
else
{
errorMessage = "Unknown action";
return 400;
}
}
else
{
errorMessage = "Missing SWGSatelliteTrackerActions in query";
return 400;
}
}
static QList<QString *> *convertStringListToPtrs(QStringList listIn)
{
QList<QString *> *listOut = new QList<QString *>();
for (int i = 0; i < listIn.size(); i++)
listOut->append(new QString(listIn[i]));
return listOut;
}
static QStringList convertPtrsToStringList(QList<QString *> *listIn)
{
QStringList listOut;
for (int i = 0; i < listIn->size(); i++)
listOut.append(*listIn->at(i));
return listOut;
}
// Convert struct SatelliteDeviceSettings to Swagger
QList<SWGSDRangel::SWGSatelliteDeviceSettingsList*>* SatelliteTracker::getSWGSatelliteDeviceSettingsList(const SatelliteTrackerSettings& settings)
{
QList <SWGSDRangel::SWGSatelliteDeviceSettingsList*>* deviceSettingsList = new QList<SWGSDRangel::SWGSatelliteDeviceSettingsList*>();
QHashIterator<QString, QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *> i(settings.m_deviceSettings);
while (i.hasNext())
{
i.next();
QList<SatelliteTrackerSettings::SatelliteDeviceSettings*>* l = i.value();
if (l->size() > 0)
{
SWGSDRangel::SWGSatelliteDeviceSettingsList* dsl = new SWGSDRangel::SWGSatelliteDeviceSettingsList();
dsl->setSatellite(new QString(i.key()));
QList<SWGSDRangel::SWGSatelliteDeviceSettings*>* ds = new QList<SWGSDRangel::SWGSatelliteDeviceSettings*>();
for (int j = 0; j < l->size(); j++)
{
SWGSDRangel::SWGSatelliteDeviceSettings* deviceSettings = new SWGSDRangel::SWGSatelliteDeviceSettings();
deviceSettings->setDeviceSetIndex(l->at(j)->m_deviceSetIndex);
deviceSettings->setPresetGroup(new QString(l->at(j)->m_presetGroup));
deviceSettings->setPresetDescription(new QString(l->at(j)->m_presetDescription));
deviceSettings->setPresetFrequency(l->at(j)->m_presetFrequency);
deviceSettings->setDoppler(new QList<QString*>());
for (int k = 0; k < l->at(j)->m_doppler.size(); k++) {
deviceSettings->getDoppler()->append(new QString(QString::number(l->at(j)->m_doppler[k])));
}
deviceSettings->setStartOnAos((int)l->at(j)->m_startOnAOS ? 1 : 0);
deviceSettings->setStopOnLos((int)l->at(j)->m_stopOnLOS ? 1 : 0);
deviceSettings->setStartStopFileSinks((int)l->at(j)->m_startStopFileSink ? 1 : 0);
deviceSettings->setFrequency((int)l->at(j)->m_frequency);
deviceSettings->setAosCommand(new QString(l->at(j)->m_aosCommand));
deviceSettings->setLosCommand(new QString(l->at(j)->m_losCommand));
ds->append(deviceSettings);
}
dsl->setDeviceSettings(ds);
deviceSettingsList->append(dsl);
}
}
return deviceSettingsList;
}
// Dereference a potentionally null string
static QString getString(QString *sp)
{
QString s;
if (sp != nullptr) {
s = *sp;
}
return s;
}
// Convert Swagger device settings to struct SatelliteDeviceSettings
QHash<QString, QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *> SatelliteTracker::getSatelliteDeviceSettings(QList<SWGSDRangel::SWGSatelliteDeviceSettingsList*>* list)
{
QHash<QString, QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *> hash;
for (int i = 0; i < list->size(); i++)
{
SWGSDRangel::SWGSatelliteDeviceSettingsList* satList = list->at(i);
if (satList->getSatellite())
{
QString satellite = *satList->getSatellite();
QList<SWGSDRangel::SWGSatelliteDeviceSettings*>* swgDeviceSettingsList = satList->getDeviceSettings();
if (swgDeviceSettingsList)
{
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *deviceSettingsList = new QList<SatelliteTrackerSettings::SatelliteDeviceSettings *>();
for (int j = 0; j < swgDeviceSettingsList->size(); j++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *deviceSettings = new SatelliteTrackerSettings::SatelliteDeviceSettings();
deviceSettings->m_deviceSetIndex = swgDeviceSettingsList->at(j)->getDeviceSetIndex();
deviceSettings->m_presetGroup = getString(swgDeviceSettingsList->at(j)->getPresetGroup());
deviceSettings->m_presetFrequency = swgDeviceSettingsList->at(j)->getPresetFrequency();
deviceSettings->m_presetDescription = getString(swgDeviceSettingsList->at(j)->getPresetDescription());
deviceSettings->m_doppler.clear();
if (swgDeviceSettingsList->at(j)->getDoppler())
{
for (auto dopplerStr : *swgDeviceSettingsList->at(j)->getDoppler()) {
deviceSettings->m_doppler.append(dopplerStr->toInt());
}
}
deviceSettings->m_startOnAOS = swgDeviceSettingsList->at(j)->getStartOnAos();
deviceSettings->m_stopOnLOS = swgDeviceSettingsList->at(j)->getStopOnLos();
deviceSettings->m_startStopFileSink = swgDeviceSettingsList->at(j)->getStartStopFileSinks();
deviceSettings->m_frequency = swgDeviceSettingsList->at(j)->getFrequency();
deviceSettings->m_aosCommand = getString(swgDeviceSettingsList->at(j)->getAosCommand());
deviceSettings->m_losCommand = getString(swgDeviceSettingsList->at(j)->getLosCommand());
deviceSettingsList->append(deviceSettings);
}
hash.insert(satellite, deviceSettingsList);
}
else
{
qDebug() << "SatelliteTracker::getSatelliteDeviceSettings: No device settings for satellite " << satellite;
}
}
else
{
qDebug() << "SatelliteTracker::getSatelliteDeviceSettings: No satellite name in device settings";
}
}
return hash;
}
void SatelliteTracker::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const SatelliteTrackerSettings& settings)
{
response.getSatelliteTrackerSettings()->setLatitude(settings.m_latitude);
response.getSatelliteTrackerSettings()->setLongitude(settings.m_longitude);
response.getSatelliteTrackerSettings()->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
response.getSatelliteTrackerSettings()->setTarget(new QString(settings.m_target));
response.getSatelliteTrackerSettings()->setSatellites(convertStringListToPtrs(settings.m_satellites));
response.getSatelliteTrackerSettings()->setTles(convertStringListToPtrs(settings.m_tles));
response.getSatelliteTrackerSettings()->setDateTime(new QString(settings.m_dateTime));
response.getSatelliteTrackerSettings()->setMinAosElevation(settings.m_minAOSElevation);
response.getSatelliteTrackerSettings()->setMinPassElevation(settings.m_minPassElevation);
response.getSatelliteTrackerSettings()->setRotatorMaxAzimuth(settings.m_rotatorMaxAzimuth);
response.getSatelliteTrackerSettings()->setRotatorMaxElevation(settings.m_rotatorMaxElevation);
response.getSatelliteTrackerSettings()->setAzElUnits((int)settings.m_azElUnits);
response.getSatelliteTrackerSettings()->setGroundTrackPoints(settings.m_groundTrackPoints);
response.getSatelliteTrackerSettings()->setDateFormat(new QString(settings.m_dateFormat));
response.getSatelliteTrackerSettings()->setUtc(settings.m_utc ? 1 : 0);
response.getSatelliteTrackerSettings()->setUpdatePeriod(settings.m_updatePeriod);
response.getSatelliteTrackerSettings()->setDopplerPeriod(settings.m_dopplerPeriod);
response.getSatelliteTrackerSettings()->setDefaultFrequency(settings.m_defaultFrequency);
response.getSatelliteTrackerSettings()->setDrawOnMap(settings.m_drawOnMap ? 1 : 0);
response.getSatelliteTrackerSettings()->setAutoTarget(settings.m_autoTarget ? 1 : 0);
response.getSatelliteTrackerSettings()->setAosSpeech(new QString(settings.m_aosSpeech));
response.getSatelliteTrackerSettings()->setLosSpeech(new QString(settings.m_losSpeech));
response.getSatelliteTrackerSettings()->setAosCommand(new QString(settings.m_aosCommand));
response.getSatelliteTrackerSettings()->setLosCommand(new QString(settings.m_losCommand));
response.getSatelliteTrackerSettings()->setPredictionPeriod(settings.m_predictionPeriod);
response.getSatelliteTrackerSettings()->setPassStartTime(new QString(settings.m_passStartTime.toString()));
response.getSatelliteTrackerSettings()->setPassFinishTime(new QString(settings.m_passFinishTime.toString()));
response.getSatelliteTrackerSettings()->setDeviceSettings(getSWGSatelliteDeviceSettingsList(settings));
response.getSatelliteTrackerSettings()->setAzimuthOffset(settings.m_azimuthOffset);
response.getSatelliteTrackerSettings()->setElevationOffset(settings.m_elevationOffset);
if (response.getSatelliteTrackerSettings()->getTitle()) {
*response.getSatelliteTrackerSettings()->getTitle() = settings.m_title;
} else {
response.getSatelliteTrackerSettings()->setTitle(new QString(settings.m_title));
}
response.getSatelliteTrackerSettings()->setRgbColor(settings.m_rgbColor);
response.getSatelliteTrackerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getSatelliteTrackerSettings()->getReverseApiAddress()) {
*response.getSatelliteTrackerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getSatelliteTrackerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getSatelliteTrackerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getSatelliteTrackerSettings()->setReverseApiFeatureSetIndex(settings.m_reverseAPIFeatureSetIndex);
response.getSatelliteTrackerSettings()->setReverseApiFeatureIndex(settings.m_reverseAPIFeatureIndex);
if (settings.m_rollupState)
{
if (response.getSatelliteTrackerSettings()->getRollupState())
{
settings.m_rollupState->formatTo(response.getSatelliteTrackerSettings()->getRollupState());
}
else
{
SWGSDRangel::SWGRollupState *swgRollupState = new SWGSDRangel::SWGRollupState();
settings.m_rollupState->formatTo(swgRollupState);
response.getSatelliteTrackerSettings()->setRollupState(swgRollupState);
}
}
}
void SatelliteTracker::webapiUpdateFeatureSettings(
SatelliteTrackerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("latitude")) {
settings.m_latitude = response.getSatelliteTrackerSettings()->getLatitude();
}
if (featureSettingsKeys.contains("longitude")) {
settings.m_longitude = response.getSatelliteTrackerSettings()->getLongitude();
}
if (featureSettingsKeys.contains("heightAboveSeaLevel")) {
settings.m_heightAboveSeaLevel = response.getSatelliteTrackerSettings()->getHeightAboveSeaLevel();
}
if (featureSettingsKeys.contains("target")) {
settings.m_target = *response.getSatelliteTrackerSettings()->getTarget();
}
if (featureSettingsKeys.contains("satellites")) {
settings.m_satellites = convertPtrsToStringList(response.getSatelliteTrackerSettings()->getSatellites());
}
if (featureSettingsKeys.contains("tles")) {
settings.m_tles = convertPtrsToStringList(response.getSatelliteTrackerSettings()->getTles());
}
if (featureSettingsKeys.contains("dateTime")) {
settings.m_dateTime = *response.getSatelliteTrackerSettings()->getDateTime();
}
if (featureSettingsKeys.contains("minAOSElevation")) {
settings.m_minAOSElevation = response.getSatelliteTrackerSettings()->getMinAosElevation();
}
if (featureSettingsKeys.contains("minPassElevation")) {
settings.m_minPassElevation = response.getSatelliteTrackerSettings()->getMinPassElevation();
}
if (featureSettingsKeys.contains("rotatorMaxAzimuth")) {
settings.m_rotatorMaxAzimuth = response.getSatelliteTrackerSettings()->getRotatorMaxAzimuth();
}
if (featureSettingsKeys.contains("rotatorMaxElevation")) {
settings.m_rotatorMaxElevation = response.getSatelliteTrackerSettings()->getRotatorMaxElevation();
}
if (featureSettingsKeys.contains("azElUnits")) {
settings.m_azElUnits = (SatelliteTrackerSettings::AzElUnits)response.getSatelliteTrackerSettings()->getAzElUnits();
}
if (featureSettingsKeys.contains("groundTrackPoints")) {
settings.m_groundTrackPoints = response.getSatelliteTrackerSettings()->getGroundTrackPoints();
}
if (featureSettingsKeys.contains("dateFormat")) {
settings.m_dateFormat = *response.getSatelliteTrackerSettings()->getDateFormat();
}
if (featureSettingsKeys.contains("utc")) {
settings.m_utc = response.getSatelliteTrackerSettings()->getUtc() != 0;
}
if (featureSettingsKeys.contains("updatePeriod")) {
settings.m_updatePeriod = response.getSatelliteTrackerSettings()->getUpdatePeriod();
}
if (featureSettingsKeys.contains("dopplerPeriod")) {
settings.m_dopplerPeriod = response.getSatelliteTrackerSettings()->getDopplerPeriod();
}
if (featureSettingsKeys.contains("defaultFrequency")) {
settings.m_defaultFrequency = response.getSatelliteTrackerSettings()->getDefaultFrequency();
}
if (featureSettingsKeys.contains("drawOnMap")) {
settings.m_drawOnMap = response.getSatelliteTrackerSettings()->getDrawOnMap() != 0;
}
if (featureSettingsKeys.contains("autoTarget")) {
settings.m_autoTarget = response.getSatelliteTrackerSettings()->getAutoTarget() != 0;
}
if (featureSettingsKeys.contains("aosSpeech")) {
settings.m_aosSpeech = *response.getSatelliteTrackerSettings()->getAosSpeech();
}
if (featureSettingsKeys.contains("losSpeech")) {
settings.m_losSpeech = *response.getSatelliteTrackerSettings()->getLosSpeech();
}
if (featureSettingsKeys.contains("aosCommand")) {
settings.m_aosCommand = *response.getSatelliteTrackerSettings()->getAosCommand();
}
if (featureSettingsKeys.contains("losCommand")) {
settings.m_losCommand = *response.getSatelliteTrackerSettings()->getLosCommand();
}
if (featureSettingsKeys.contains("predictionPeriod")) {
settings.m_predictionPeriod = response.getSatelliteTrackerSettings()->getPredictionPeriod();
}
if (featureSettingsKeys.contains("passStartTime")) {
settings.m_passStartTime = QTime::fromString(*response.getSatelliteTrackerSettings()->getPassStartTime());
}
if (featureSettingsKeys.contains("passFinishTime")) {
settings.m_passFinishTime = QTime::fromString(*response.getSatelliteTrackerSettings()->getPassFinishTime());
}
if (featureSettingsKeys.contains("deviceSettings")) {
settings.m_deviceSettings = getSatelliteDeviceSettings(response.getSatelliteTrackerSettings()->getDeviceSettings());
}
if (featureSettingsKeys.contains("azimuthOffset")) {
settings.m_azimuthOffset = response.getSatelliteTrackerSettings()->getAzimuthOffset();
}
if (featureSettingsKeys.contains("elevationOffset")) {
settings.m_elevationOffset = response.getSatelliteTrackerSettings()->getElevationOffset();
}
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getSatelliteTrackerSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getSatelliteTrackerSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getSatelliteTrackerSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getSatelliteTrackerSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getSatelliteTrackerSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIFeatureSetIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getSatelliteTrackerSettings()->getReverseApiFeatureSetIndex();
}
if (featureSettingsKeys.contains("reverseAPIFeatureIndex")) {
settings.m_reverseAPIFeatureIndex = response.getSatelliteTrackerSettings()->getReverseApiFeatureIndex();
}
if (settings.m_rollupState && featureSettingsKeys.contains("rollupState")) {
settings.m_rollupState->updateFrom(featureSettingsKeys, response.getSatelliteTrackerSettings()->getRollupState());
}
}
void SatelliteTracker::webapiReverseSendSettings(const QList<QString>& featureSettingsKeys, const SatelliteTrackerSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("SatelliteTracker"));
swgFeatureSettings->setSatelliteTrackerSettings(new SWGSDRangel::SWGSatelliteTrackerSettings());
SWGSDRangel::SWGSatelliteTrackerSettings *swgSatelliteTrackerSettings = swgFeatureSettings->getSatelliteTrackerSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("latitude") || force) {
swgSatelliteTrackerSettings->setLatitude(settings.m_latitude);
}
if (featureSettingsKeys.contains("longitude") || force) {
swgSatelliteTrackerSettings->setLongitude(settings.m_longitude);
}
if (featureSettingsKeys.contains("heightAboveSeaLevel") || force) {
swgSatelliteTrackerSettings->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
}
if (featureSettingsKeys.contains("target") || force) {
swgSatelliteTrackerSettings->setTarget(new QString(settings.m_target));
}
if (featureSettingsKeys.contains("satellites") || force) {
swgSatelliteTrackerSettings->setSatellites(convertStringListToPtrs(settings.m_satellites));
}
if (featureSettingsKeys.contains("tles") || force) {
swgSatelliteTrackerSettings->setTles(convertStringListToPtrs(settings.m_satellites));
}
if (featureSettingsKeys.contains("dateTime") || force) {
swgSatelliteTrackerSettings->setDateTime(new QString(settings.m_dateTime));
}
if (featureSettingsKeys.contains("minAOSElevation") || force) {
swgSatelliteTrackerSettings->setMinAosElevation(settings.m_minAOSElevation);
}
if (featureSettingsKeys.contains("minPassElevation") || force) {
swgSatelliteTrackerSettings->setMinPassElevation(settings.m_minPassElevation);
}
if (featureSettingsKeys.contains("azElUnits") || force) {
swgSatelliteTrackerSettings->setAzElUnits((int)settings.m_azElUnits);
}
if (featureSettingsKeys.contains("groundTrackPoints") || force) {
swgSatelliteTrackerSettings->setGroundTrackPoints(settings.m_groundTrackPoints);
}
if (featureSettingsKeys.contains("dateFormat") || force) {
swgSatelliteTrackerSettings->setDateFormat(new QString(settings.m_dateFormat));
}
if (featureSettingsKeys.contains("utc") || force) {
swgSatelliteTrackerSettings->setUtc(settings.m_utc);
}
if (featureSettingsKeys.contains("updatePeriod") || force) {
swgSatelliteTrackerSettings->setUpdatePeriod(settings.m_updatePeriod);
}
if (featureSettingsKeys.contains("dopplerPeriod") || force) {
swgSatelliteTrackerSettings->setDopplerPeriod(settings.m_dopplerPeriod);
}
if (featureSettingsKeys.contains("defaultFrequency") || force) {
swgSatelliteTrackerSettings->setDefaultFrequency(settings.m_defaultFrequency);
}
if (featureSettingsKeys.contains("drawOnMap") || force) {
swgSatelliteTrackerSettings->setDrawOnMap(settings.m_drawOnMap);
}
if (featureSettingsKeys.contains("aosSpeech") || force) {
swgSatelliteTrackerSettings->setAosSpeech(new QString(settings.m_aosSpeech));
}
if (featureSettingsKeys.contains("losSpeech") || force) {
swgSatelliteTrackerSettings->setLosSpeech(new QString(settings.m_losSpeech));
}
if (featureSettingsKeys.contains("aosCommand") || force) {
swgSatelliteTrackerSettings->setAosCommand(new QString(settings.m_aosCommand));
}
if (featureSettingsKeys.contains("losCommand") || force) {
swgSatelliteTrackerSettings->setLosCommand(new QString(settings.m_losCommand));
}
if (featureSettingsKeys.contains("predictionPeriod") || force) {
swgSatelliteTrackerSettings->setPredictionPeriod(settings.m_predictionPeriod);
}
if (featureSettingsKeys.contains("passStartTime") || force) {
swgSatelliteTrackerSettings->setPassStartTime(new QString(settings.m_passStartTime.toString()));
}
if (featureSettingsKeys.contains("passFinishTime") || force) {
swgSatelliteTrackerSettings->setPassFinishTime(new QString(settings.m_passFinishTime.toString()));
}
if (featureSettingsKeys.contains("deviceSettings") || force) {
swgSatelliteTrackerSettings->setDeviceSettings(getSWGSatelliteDeviceSettingsList(settings));
}
if (featureSettingsKeys.contains("azimuthOffset") || force) {
swgSatelliteTrackerSettings->setAzimuthOffset(settings.m_azimuthOffset);
}
if (featureSettingsKeys.contains("elevationOffset") || force) {
swgSatelliteTrackerSettings->setElevationOffset(settings.m_elevationOffset);
}
if (featureSettingsKeys.contains("title") || force) {
swgSatelliteTrackerSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgSatelliteTrackerSettings->setRgbColor(settings.m_rgbColor);
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIFeatureSetIndex)
.arg(settings.m_reverseAPIFeatureIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgFeatureSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgFeatureSettings;
}
void SatelliteTracker::webapiFormatFeatureReport(SWGSDRangel::SWGFeatureReport& response)
{
response.getSatelliteTrackerReport()->setRunningState(getState());
QList<SWGSDRangel::SWGSatelliteState *> *list = response.getSatelliteTrackerReport()->getSatelliteState();
QHashIterator<QString, SatelliteState *> itr(m_satState);
while (itr.hasNext())
{
itr.next();
SatelliteState *satState = itr.value();
SWGSDRangel::SWGSatelliteState *swgSatState = new SWGSDRangel::SWGSatelliteState();
swgSatState->setName(new QString(satState->m_name));
swgSatState->setLatitude(satState->m_latitude);
swgSatState->setLongitude(satState->m_longitude);
swgSatState->setAltitude(satState->m_altitude);
swgSatState->setAzimuth(satState->m_azimuth);
swgSatState->setElevation(satState->m_elevation);
swgSatState->setRange(satState->m_range);
swgSatState->setRangeRate(satState->m_rangeRate);
swgSatState->setSpeed(satState->m_speed);
swgSatState->setPeriod(satState->m_period);
swgSatState->setElevation(satState->m_elevation);
QList<SWGSDRangel::SWGSatellitePass *> *passesList = new QList<SWGSDRangel::SWGSatellitePass*>();
for (auto const &pass : satState->m_passes)
{
SWGSDRangel::SWGSatellitePass *swgPass = new SWGSDRangel::SWGSatellitePass();
swgPass->setAos(new QString(pass.m_aos.toString(Qt::ISODateWithMs)));
swgPass->setLos(new QString(pass.m_los.toString(Qt::ISODateWithMs)));
swgPass->setMaxElevation(pass.m_maxElevation);
passesList->append(swgPass);
}
swgSatState->setPasses(passesList);
list->append(swgSatState);
if (satState->m_name == m_settings.m_target)
{
response.getSatelliteTrackerReport()->setTargetAzimuth(satState->m_azimuth);
response.getSatelliteTrackerReport()->setTargetElevation(satState->m_elevation);
}
}
}
void SatelliteTracker::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "SatelliteTracker::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("SatelliteTracker::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
QString SatelliteTracker::satNogsSatellitesFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_satellites.json";
}
QString SatelliteTracker::satNogsTransmittersFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_transmitters.json";
}
QString SatelliteTracker::satNogsTLEFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_tle.json";
}
QString SatelliteTracker::tleURLToFilename(const QString& string)
{
if (string == "https://db.satnogs.org/api/tle/")
return satNogsTLEFilename();
// Celestrak now uses the same filename with different queries, so we need to include the query
// in the file name, so the filenames don't clash. E.g:
// https://celestrak.org/NORAD/elements/gp.php?GROUP=gps-ops&FORMAT=tle
// https://celestrak.org/NORAD/elements/gp.php?GROUP=active&FORMAT=tle
QUrl url(string);
QString fileName = HttpDownloadManager::downloadDir() + "/tle_" + url.fileName();
if (url.hasQuery())
{
QString query = url.query().replace('%', '_').replace('&', '_').replace('=', '_');
fileName = fileName + query;
}
return fileName;
}
void SatelliteTracker::downloadFinished(const QString& filename, bool success, const QString& url, const QString& error)
{
if (success)
{
if (filename == satNogsSatellitesFilename())
{
m_dlm.download(QUrl("https://db.satnogs.org/api/transmitters/"), satNogsTransmittersFilename());
}
else if (filename == satNogsTransmittersFilename())
{
m_tleIndex = 0;
if (m_settings.m_tles.size() > 0)
m_dlm.download(QUrl(m_settings.m_tles[0]), tleURLToFilename(m_settings.m_tles[0]));
else
qWarning() << "Satellite Tracker: No TLEs";
}
else if ((m_tleIndex < m_settings.m_tles.size()) && (filename == tleURLToFilename(m_settings.m_tles[m_tleIndex])))
{
m_tleIndex++;
if (m_tleIndex < m_settings.m_tles.size())
m_dlm.download(QUrl(m_settings.m_tles[m_tleIndex]), tleURLToFilename(m_settings.m_tles[m_tleIndex]));
else
{
readSatData();
m_updatingSatData = false;
}
}
else
qDebug() << "SatelliteTracker::downloadFinished: Unexpected filename: " << filename;
}
else
{
m_updatingSatData = false;
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgError::create(QString("Failed to download: %1\n\n%2").arg(url).arg(error)));
}
}
bool SatelliteTracker::readSatData()
{
QFile satsFile(satNogsSatellitesFilename());
if (satsFile.open(QIODevice::ReadOnly))
{
if (parseSatellites(satsFile.readAll()))
{
QFile transmittersFile(satNogsTransmittersFilename());
if (transmittersFile.open(QIODevice::ReadOnly))
{
if (parseTransmitters(transmittersFile.readAll()))
{
for (int i = 0; i < m_settings.m_tles.size(); i++)
{
QFile tlesFile(tleURLToFilename(m_settings.m_tles[i]));
if (tlesFile.open(QIODevice::ReadOnly))
{
bool ok;
if (tlesFile.fileName() == satNogsTLEFilename())
{
ok = parseSatNogsTLEs(tlesFile.readAll());
}
else
ok = parseTxtTLEs(tlesFile.readAll());
if (!ok)
{
qDebug() << "SatelliteTracker::readSatData - failed to parse: " << tlesFile.fileName();
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgError::create(QString("Failed to parse: %1").arg(tlesFile.fileName())));
}
}
else
qDebug() << "SatelliteTracker::readSatData - failed to open: " << tlesFile.fileName();
}
qDebug() << "SatelliteTracker::readSatData - read " << m_satellites.size() << " satellites";
// Send to GUI
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgSatData::create(m_satellites));
// Send to worker
if (m_worker)
m_worker->getInputMessageQueue()->push(MsgSatData::create(m_satellites));
return true;
}
}
}
}
qDebug() << "SatelliteTracker::readSatData - Failed to read satellites";
return false;
}
bool SatelliteTracker::parseSatellites(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
m_satellites = SatNogsSatellite::createHash(jsonResponse.array());
m_satellitesId.clear();
// Create second table, hashed on ID
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
m_satellitesId.insert(sat->m_noradCatId, sat);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseTransmitters(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
QList<SatNogsTransmitter *> transmitters = SatNogsTransmitter::createList(jsonResponse.array());
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTransmitters(transmitters);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseSatNogsTLEs(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
QList<SatNogsTLE *> tles = SatNogsTLE::createList(jsonResponse.array());
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTLE(tles);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseTxtTLEs(const QByteArray& txt)
{
QList<SatNogsTLE *> tles = SatNogsTLE::createList(txt);
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTLE(tles);
}
// Create satellites, that we have TLEs for, but no existing entry
for (int i = 0; i < tles.size(); i++)
{
if (!m_satellitesId.contains(tles[i]->m_noradCatId))
{
SatNogsSatellite *sat = new SatNogsSatellite(tles[i]);
m_satellites.insert(sat->m_name, sat);
m_satellitesId.insert(sat->m_noradCatId, sat);
}
}
return true;
}
void SatelliteTracker::updateSatData()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_updatingSatData == false)
{
m_updatingSatData = true;
qDebug() << "SatelliteTracker::updateSatData: requesting satellites";
m_dlm.download(QUrl("https://db.satnogs.org/api/satellites/"), satNogsSatellitesFilename());
}
else
qDebug() << "SatelliteTracker::updateSatData: update in progress";
}
/// Redirect requests for current time via these methods, for replays
QDateTime SatelliteTracker::currentDateTimeUtc()
{
if (m_settings.m_dateTimeSelect == SatelliteTrackerSettings::FROM_FILE)
{
QString dateTimeStr;
int deviceIdx = 0;
if (m_settings.m_fileInputDevice.size() >= 2) {
deviceIdx = m_settings.m_fileInputDevice.mid(1).toInt();
}
if (ChannelWebAPIUtils::getDeviceReportValue(deviceIdx, "absoluteTime", dateTimeStr))
{
return QDateTime::fromString(dateTimeStr, Qt::ISODateWithMs);
}
else
{
return QDateTime::currentDateTimeUtc();
}
}
else if (m_settings.m_dateTimeSelect == SatelliteTrackerSettings::CUSTOM)
{
return QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
}
else if (m_settings.m_dateTimeSelect == SatelliteTrackerSettings::FROM_MAP)
{
QString dateTimeStr;
int featureSet = 0;
int featureIdx = 0;
if (m_settings.m_mapFeature.size() >= 4)
{
QStringList numbers = m_settings.m_mapFeature.mid(1).split(":");
if (numbers.size() == 2)
{
featureSet = numbers[0].toInt();
featureIdx = numbers[1].toInt();
}
}
if (ChannelWebAPIUtils::getFeatureReportValue(featureSet, featureIdx, "dateTime", dateTimeStr))
{
return QDateTime::fromString(dateTimeStr, Qt::ISODateWithMs);
}
else
{
return QDateTime::currentDateTimeUtc();
}
}
else if (m_settings.m_replayEnabled)
{
QDateTime now = QDateTime::currentDateTimeUtc();
return m_settings.m_replayStartDateTime.addSecs(m_startedDateTime.secsTo(now));
}
else
{
return QDateTime::currentDateTimeUtc();
}
}
QDateTime SatelliteTracker::currentDateTime()
{
if (m_settings.m_dateTimeSelect == SatelliteTrackerSettings::NOW) {
return QDateTime::currentDateTime();
} else {
return currentDateTimeUtc().toLocalTime();
}
}