mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-11-26 17:58:43 -05:00
04aed0b3b3
Add support for replaying of passes in the past, where current time is determined from File Input device. Add latitude and longitude to satellite data table. Update ground track generation to better work with 3D map. Add support for 3D models. Add Cubesat image for 2D map. Send LOS to other plugins, when no device settings are setup. Pass TLEs to other plugins, so they can use a consistent copy for replays.
899 lines
42 KiB
C++
899 lines
42 KiB
C++
///////////////////////////////////////////////////////////////////////////////////
|
|
// 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 <QAbstractSocket>
|
|
#include <QTcpServer>
|
|
#include <QTcpSocket>
|
|
#include <QEventLoop>
|
|
#include <QTimer>
|
|
#include <QDateTime>
|
|
|
|
#include "SWGTargetAzimuthElevation.h"
|
|
#include "SWGMapItem.h"
|
|
|
|
#include "webapi/webapiadapterinterface.h"
|
|
#include "webapi/webapiutils.h"
|
|
|
|
#include "util/units.h"
|
|
#include "device/deviceset.h"
|
|
#include "device/deviceapi.h"
|
|
#include "channel/channelwebapiutils.h"
|
|
#include "feature/featurewebapiutils.h"
|
|
#include "maincore.h"
|
|
|
|
#include "satellitetracker.h"
|
|
#include "satellitetrackerworker.h"
|
|
#include "satellitetrackerreport.h"
|
|
#include "satellitetrackersgp4.h"
|
|
|
|
MESSAGE_CLASS_DEFINITION(SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker, Message)
|
|
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportSat, Message)
|
|
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportAOS, Message)
|
|
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportLOS, Message)
|
|
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportTarget, Message)
|
|
|
|
SatelliteTrackerWorker::SatelliteTrackerWorker(SatelliteTracker* satelliteTracker, WebAPIAdapterInterface *webAPIAdapterInterface) :
|
|
m_satelliteTracker(satelliteTracker),
|
|
m_webAPIAdapterInterface(webAPIAdapterInterface),
|
|
m_msgQueueToFeature(nullptr),
|
|
m_msgQueueToGUI(nullptr),
|
|
m_running(false),
|
|
m_mutex(QMutex::Recursive),
|
|
m_pollTimer(this),
|
|
m_recalculatePasses(true),
|
|
m_flipRotation(false),
|
|
m_extendedAzRotation(false)
|
|
{
|
|
connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update()));
|
|
}
|
|
|
|
SatelliteTrackerWorker::~SatelliteTrackerWorker()
|
|
{
|
|
m_inputMessageQueue.clear();
|
|
}
|
|
|
|
void SatelliteTrackerWorker::reset()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
m_inputMessageQueue.clear();
|
|
}
|
|
|
|
bool SatelliteTrackerWorker::startWork()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
|
connect(thread(), SIGNAL(started()), this, SLOT(started()));
|
|
connect(thread(), SIGNAL(finished()), this, SLOT(finished()));
|
|
m_recalculatePasses = true;
|
|
m_running = true;
|
|
return m_running;
|
|
}
|
|
|
|
// startWork() is called from main thread. Timers need to be started on worker thread
|
|
void SatelliteTrackerWorker::started()
|
|
{
|
|
m_pollTimer.start((int)round(m_settings.m_updatePeriod*1000.0));
|
|
// Resume doppler timers
|
|
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
|
|
while (itr.hasNext())
|
|
{
|
|
itr.next();
|
|
SatWorkerState *satWorkerState = itr.value();
|
|
if (satWorkerState->m_dopplerTimer.interval() > 0)
|
|
satWorkerState->m_dopplerTimer.start();
|
|
}
|
|
disconnect(thread(), SIGNAL(started()), this, SLOT(started()));
|
|
}
|
|
|
|
void SatelliteTrackerWorker::stopWork()
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
|
}
|
|
|
|
void SatelliteTrackerWorker::finished()
|
|
{
|
|
m_pollTimer.stop();
|
|
// Stop doppler timers
|
|
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
|
|
while (itr.hasNext())
|
|
{
|
|
itr.next();
|
|
itr.value()->m_dopplerTimer.stop();
|
|
}
|
|
m_running = false;
|
|
disconnect(thread(), SIGNAL(finished()), this, SLOT(finished()));
|
|
}
|
|
|
|
void SatelliteTrackerWorker::handleInputMessages()
|
|
{
|
|
Message* message;
|
|
|
|
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
|
{
|
|
if (handleMessage(*message)) {
|
|
delete message;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SatelliteTrackerWorker::handleMessage(const Message& message)
|
|
{
|
|
if (MsgConfigureSatelliteTrackerWorker::match(message))
|
|
{
|
|
QMutexLocker mutexLocker(&m_mutex);
|
|
MsgConfigureSatelliteTrackerWorker& cfg = (MsgConfigureSatelliteTrackerWorker&) message;
|
|
|
|
applySettings(cfg.getSettings(), cfg.getForce());
|
|
return true;
|
|
}
|
|
else if (SatelliteTracker::MsgSatData::match(message))
|
|
{
|
|
SatelliteTracker::MsgSatData& satData = (SatelliteTracker::MsgSatData&) message;
|
|
m_satellites = satData.getSatellites();
|
|
m_recalculatePasses = true;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void SatelliteTrackerWorker::applySettings(const SatelliteTrackerSettings& settings, bool force)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::applySettings:"
|
|
<< " m_target: " << settings.m_target
|
|
<< " m_satellites: " << settings.m_satellites
|
|
<< " m_dateTime: " << settings.m_dateTime
|
|
<< " m_utc: " << settings.m_utc
|
|
<< " m_updatePeriod: " << settings.m_updatePeriod
|
|
<< " force: " << force;
|
|
|
|
if ((m_settings.m_target != settings.m_target)
|
|
|| (m_settings.m_latitude != settings.m_latitude)
|
|
|| (m_settings.m_longitude != settings.m_longitude)
|
|
|| (m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel)
|
|
|| (m_settings.m_dateTime != settings.m_dateTime)
|
|
|| (m_settings.m_utc != settings.m_utc)
|
|
|| (m_settings.m_groundTrackPoints != settings.m_groundTrackPoints)
|
|
|| (m_settings.m_minAOSElevation != settings.m_minAOSElevation)
|
|
|| (m_settings.m_minPassElevation != settings.m_minPassElevation)
|
|
|| (m_settings.m_predictionPeriod != settings.m_predictionPeriod)
|
|
|| (m_settings.m_passStartTime != settings.m_passStartTime)
|
|
|| (m_settings.m_passFinishTime != settings.m_passFinishTime)
|
|
|| (!m_settings.m_drawOnMap && settings.m_drawOnMap)
|
|
|| force)
|
|
{
|
|
// Recalculate immediately
|
|
m_recalculatePasses = true;
|
|
QTimer::singleShot(1, this, &SatelliteTrackerWorker::update);
|
|
m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0));
|
|
}
|
|
else if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force)
|
|
{
|
|
m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0));
|
|
}
|
|
|
|
if (!settings.m_drawOnMap && m_settings.m_drawOnMap)
|
|
{
|
|
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
|
|
while (itr.hasNext())
|
|
{
|
|
itr.next();
|
|
removeFromMap(itr.key());
|
|
}
|
|
}
|
|
|
|
// Remove satellites no longer needed
|
|
QMutableHashIterator<QString, SatWorkerState *> itr(m_workerState);
|
|
while (itr.hasNext())
|
|
{
|
|
itr.next();
|
|
if (settings.m_satellites.indexOf(itr.key()) == -1)
|
|
itr.remove();
|
|
}
|
|
|
|
// Add new satellites
|
|
for (int i = 0; i < settings.m_satellites.size(); i++)
|
|
{
|
|
if (!m_workerState.contains(settings.m_satellites[i]))
|
|
{
|
|
SatWorkerState *satWorkerState = new SatWorkerState(settings.m_satellites[i]);
|
|
m_workerState.insert(settings.m_satellites[i], satWorkerState);
|
|
connect(&satWorkerState->m_aosTimer, &QTimer::timeout, [this, satWorkerState]() {
|
|
aos(satWorkerState);
|
|
});
|
|
connect(&satWorkerState->m_losTimer, &QTimer::timeout, [this, satWorkerState]() {
|
|
los(satWorkerState);
|
|
});
|
|
m_recalculatePasses = true;
|
|
}
|
|
}
|
|
|
|
m_settings = settings;
|
|
}
|
|
|
|
void SatelliteTrackerWorker::removeFromMap(QString id)
|
|
{
|
|
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
|
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "mapitems");
|
|
if (mapMessageQueues)
|
|
sendToMap(mapMessageQueues, id, "", "", "", 0.0f, 0.0, 0.0, 0.0, 0.0, nullptr, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
void SatelliteTrackerWorker::sendToMap(QList<MessageQueue*> *mapMessageQueues,
|
|
QString name, QString image, QString model, QString text,
|
|
float labelOffset,
|
|
double lat, double lon, double altitude, double rotation,
|
|
QList<QGeoCoordinate *> *track,
|
|
QList<QDateTime *> *trackDateTime,
|
|
QList<QGeoCoordinate *> *predictedTrack,
|
|
QList<QDateTime *> *predictedTrackDateTime)
|
|
{
|
|
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
|
|
|
|
for (; it != mapMessageQueues->end(); ++it)
|
|
{
|
|
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
|
|
swgMapItem->setName(new QString(name));
|
|
swgMapItem->setLatitude(lat);
|
|
swgMapItem->setLongitude(lon);
|
|
swgMapItem->setAltitude(altitude);
|
|
swgMapItem->setImage(new QString(image));
|
|
swgMapItem->setImageRotation(rotation);
|
|
swgMapItem->setText(new QString(text));
|
|
swgMapItem->setModel(new QString(model));
|
|
swgMapItem->setFixedPosition(false);
|
|
swgMapItem->setOrientation(0);
|
|
swgMapItem->setLabel(new QString(name));
|
|
swgMapItem->setLabelAltitudeOffset(labelOffset);
|
|
if (track != nullptr)
|
|
{
|
|
QList<SWGSDRangel::SWGMapCoordinate *> *mapTrack = new QList<SWGSDRangel::SWGMapCoordinate *>();
|
|
for (int i = 0; i < track->size(); i++)
|
|
{
|
|
SWGSDRangel::SWGMapCoordinate* p = new SWGSDRangel::SWGMapCoordinate();
|
|
QGeoCoordinate *c = track->at(i);
|
|
p->setLatitude(c->latitude());
|
|
p->setLongitude(c->longitude());
|
|
p->setAltitude(c->altitude());
|
|
p->setDateTime(new QString(trackDateTime->at(i)->toString(Qt::ISODate)));
|
|
mapTrack->append(p);
|
|
}
|
|
swgMapItem->setTrack(mapTrack);
|
|
}
|
|
if (predictedTrack != nullptr)
|
|
{
|
|
QList<SWGSDRangel::SWGMapCoordinate *> *mapTrack = new QList<SWGSDRangel::SWGMapCoordinate *>();
|
|
for (int i = 0; i < predictedTrack->size(); i++)
|
|
{
|
|
SWGSDRangel::SWGMapCoordinate* p = new SWGSDRangel::SWGMapCoordinate();
|
|
QGeoCoordinate *c = predictedTrack->at(i);
|
|
p->setLatitude(c->latitude());
|
|
p->setLongitude(c->longitude());
|
|
p->setAltitude(c->altitude());
|
|
p->setDateTime(new QString(predictedTrackDateTime->at(i)->toString(Qt::ISODate)));
|
|
mapTrack->append(p);
|
|
}
|
|
swgMapItem->setPredictedTrack(mapTrack);
|
|
}
|
|
|
|
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_satelliteTracker, swgMapItem);
|
|
(*it)->push(msg);
|
|
}
|
|
}
|
|
|
|
void SatelliteTrackerWorker::update()
|
|
{
|
|
// Get date and time to calculate position at
|
|
QDateTime qdt;
|
|
if (m_settings.m_dateTime == "")
|
|
qdt = m_satelliteTracker->currentDateTimeUtc();
|
|
else if (m_settings.m_utc)
|
|
qdt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
|
|
else
|
|
qdt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs).toUTC();
|
|
|
|
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
|
|
while (itr.hasNext())
|
|
{
|
|
itr.next();
|
|
SatWorkerState *satWorkerState = itr.value();
|
|
QString name = satWorkerState->m_name;
|
|
if (m_satellites.contains(name))
|
|
{
|
|
SatNogsSatellite *sat = m_satellites.value(name);
|
|
if (sat->m_tle != nullptr)
|
|
{
|
|
// Calculate position, AOS/LOS and other details for satellite
|
|
int noOfPasses;
|
|
bool recalcAsPastLOS = (satWorkerState->m_satState.m_passes.size() > 0) && (satWorkerState->m_satState.m_passes[0]->m_los < qdt);
|
|
if (m_recalculatePasses || recalcAsPastLOS)
|
|
noOfPasses = (name == m_settings.m_target) ? 99 : 1;
|
|
else
|
|
noOfPasses = 0;
|
|
getSatelliteState(qdt, sat->m_tle->m_tle0, sat->m_tle->m_tle1, sat->m_tle->m_tle2,
|
|
m_settings.m_latitude, m_settings.m_longitude, m_settings.m_heightAboveSeaLevel/1000.0,
|
|
m_settings.m_predictionPeriod, m_settings.m_minAOSElevation, m_settings.m_minPassElevation,
|
|
m_settings.m_passStartTime, m_settings.m_passFinishTime, m_settings.m_utc,
|
|
noOfPasses, m_settings.m_groundTrackPoints, &satWorkerState->m_satState);
|
|
|
|
// Update AOS/LOS (only set timers if using real time)
|
|
if ((m_settings.m_dateTime == "") && (satWorkerState->m_satState.m_passes.size() > 0))
|
|
{
|
|
// Do we have a new AOS?
|
|
if ((satWorkerState->m_aos != satWorkerState->m_satState.m_passes[0]->m_aos) || (satWorkerState->m_los != satWorkerState->m_satState.m_passes[0]->m_los))
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker: Current time: " << qdt.toString(Qt::ISODateWithMs);
|
|
qDebug() << "SatelliteTrackerWorker: New AOS: " << name << " new: " << satWorkerState->m_satState.m_passes[0]->m_aos << " old: " << satWorkerState->m_aos;
|
|
qDebug() << "SatelliteTrackerWorker: New LOS: " << name << " new: " << satWorkerState->m_satState.m_passes[0]->m_los << " old: " << satWorkerState->m_los;
|
|
satWorkerState->m_aos = satWorkerState->m_satState.m_passes[0]->m_aos;
|
|
satWorkerState->m_los = satWorkerState->m_satState.m_passes[0]->m_los;
|
|
if (satWorkerState->m_aos.isValid())
|
|
{
|
|
if (satWorkerState->m_aos > qdt)
|
|
{
|
|
satWorkerState->m_aosTimer.setInterval(satWorkerState->m_aos.toMSecsSinceEpoch() - qdt.toMSecsSinceEpoch());
|
|
satWorkerState->m_aosTimer.setSingleShot(true);
|
|
satWorkerState->m_aosTimer.start();
|
|
}
|
|
else if (qdt < satWorkerState->m_los)
|
|
aos(satWorkerState);
|
|
|
|
if (satWorkerState->m_los.isValid() && (m_settings.m_target == satWorkerState->m_name))
|
|
calculateRotation(satWorkerState);
|
|
}
|
|
if (satWorkerState->m_los.isValid() && (satWorkerState->m_los > qdt))
|
|
{
|
|
if (satWorkerState->m_losTimer.isActive()) {
|
|
qDebug() << "SatelliteTrackerWorker::update m_losTimer.remainingTime: " << satWorkerState->m_losTimer.remainingTime();
|
|
}
|
|
// We can detect a new AOS for a satellite, a little bit before the LOS has occured
|
|
// Allow for 5s here (1s doesn't appear to be enough in some cases)
|
|
if (satWorkerState->m_losTimer.isActive() && (satWorkerState->m_losTimer.remainingTime() <= 5000))
|
|
{
|
|
satWorkerState->m_losTimer.stop();
|
|
// LOS hasn't been called yet - do so, before we reset timer
|
|
los(satWorkerState);
|
|
}
|
|
qDebug() << "SatelliteTrackerWorker:: Interval to LOS " << (satWorkerState->m_los.toMSecsSinceEpoch() - qdt.toMSecsSinceEpoch());
|
|
satWorkerState->m_losTimer.setInterval(satWorkerState->m_los.toMSecsSinceEpoch() - qdt.toMSecsSinceEpoch());
|
|
satWorkerState->m_losTimer.setSingleShot(true);
|
|
satWorkerState->m_losTimer.start();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
satWorkerState->m_aos = QDateTime();
|
|
satWorkerState->m_los = QDateTime();
|
|
satWorkerState->m_aosTimer.stop();
|
|
satWorkerState->m_losTimer.stop();
|
|
}
|
|
|
|
// Send Az/El of target to Rotator Controllers, if elevation above horizon
|
|
if ((name == m_settings.m_target) && (satWorkerState->m_satState.m_elevation >= 0))
|
|
{
|
|
double azimuth = satWorkerState->m_satState.m_azimuth;
|
|
double elevation = satWorkerState->m_satState.m_elevation;
|
|
if (m_extendedAzRotation)
|
|
{
|
|
if (azimuth < 180.0)
|
|
azimuth += 360.0;
|
|
}
|
|
else if (m_flipRotation)
|
|
{
|
|
azimuth = std::fmod(azimuth + 180.0, 360.0);
|
|
elevation = 180.0 - elevation;
|
|
}
|
|
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
|
QList<MessageQueue*> *rotatorMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "target");
|
|
if (rotatorMessageQueues)
|
|
{
|
|
QList<MessageQueue*>::iterator it = rotatorMessageQueues->begin();
|
|
|
|
for (; it != rotatorMessageQueues->end(); ++it)
|
|
{
|
|
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
|
|
swgTarget->setName(new QString(m_settings.m_target));
|
|
swgTarget->setAzimuth(azimuth);
|
|
swgTarget->setElevation(elevation);
|
|
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_satelliteTracker, swgTarget));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send to Map
|
|
if (m_settings.m_drawOnMap)
|
|
{
|
|
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
|
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "mapitems");
|
|
if (mapMessageQueues)
|
|
{
|
|
const QStringList cubeSats({"AISAT-1", "FOX-1B", "FOX-1C", "FOX-1D", "FOX-1E", "FUNCUBE-1", "NO-84"});
|
|
QString image;
|
|
QString model;
|
|
float labelOffset;
|
|
|
|
if (sat->m_name == "ISS")
|
|
{
|
|
image = "qrc:///satellitetracker/satellitetracker/iss-32.png";
|
|
model = "iss.glb";
|
|
labelOffset = 15.0f;
|
|
}
|
|
else if (cubeSats.contains(sat->m_name))
|
|
{
|
|
image = "qrc:///satellitetracker/satellitetracker/cubesat-32.png";
|
|
model = "cubesat.glb";
|
|
labelOffset = 0.7f;
|
|
}
|
|
else
|
|
{
|
|
image = "qrc:///satellitetracker/satellitetracker/satellite-32.png";
|
|
model = "satellite.glb";
|
|
labelOffset = 2.5f;
|
|
}
|
|
|
|
QString text = QString("Name: %1\nAltitude: %2 km\nRange: %3 km\nRange rate: %4 km/s\nSpeed: %5 km/h\nPeriod: %6 mins")
|
|
.arg(sat->m_name)
|
|
.arg((int)round(satWorkerState->m_satState.m_altitude))
|
|
.arg((int)round(satWorkerState->m_satState.m_range))
|
|
.arg(satWorkerState->m_satState.m_rangeRate, 0, 'f', 1)
|
|
.arg(Units::kmpsToIntegerKPH(satWorkerState->m_satState.m_speed))
|
|
.arg((int)round(satWorkerState->m_satState.m_period));
|
|
if (satWorkerState->m_satState.m_passes.size() > 0)
|
|
{
|
|
if ((qdt >= satWorkerState->m_satState.m_passes[0]->m_aos) && (qdt <= satWorkerState->m_satState.m_passes[0]->m_los))
|
|
text = text.append("\nSatellite is visible");
|
|
else
|
|
text = text.append("\nAOS in: %1 mins").arg((int)round((satWorkerState->m_satState.m_passes[0]->m_aos.toSecsSinceEpoch() - qdt.toSecsSinceEpoch())/60.0));
|
|
QString aosDateTime;
|
|
QString losDateTime;
|
|
if (m_settings.m_utc)
|
|
{
|
|
aosDateTime = satWorkerState->m_satState.m_passes[0]->m_aos.toString(m_settings.m_dateFormat + " hh:mm");
|
|
losDateTime = satWorkerState->m_satState.m_passes[0]->m_los.toString(m_settings.m_dateFormat + " hh:mm");
|
|
}
|
|
else
|
|
{
|
|
aosDateTime = satWorkerState->m_satState.m_passes[0]->m_aos.toLocalTime().toString(m_settings.m_dateFormat + " hh:mm");
|
|
losDateTime = satWorkerState->m_satState.m_passes[0]->m_los.toLocalTime().toString(m_settings.m_dateFormat + " hh:mm");
|
|
}
|
|
text = QString("%1\nAOS: %2\nLOS: %3\nMax El: %4%5")
|
|
.arg(text)
|
|
.arg(aosDateTime)
|
|
.arg(losDateTime)
|
|
.arg((int)round(satWorkerState->m_satState.m_passes[0]->m_maxElevation))
|
|
.arg(QChar(0xb0));
|
|
}
|
|
|
|
sendToMap(mapMessageQueues, sat->m_name, image, model, text, labelOffset,
|
|
satWorkerState->m_satState.m_latitude, satWorkerState->m_satState.m_longitude,
|
|
satWorkerState->m_satState.m_altitude * 1000.0, 0,
|
|
&satWorkerState->m_satState.m_groundTrack, &satWorkerState->m_satState.m_groundTrackDateTime,
|
|
&satWorkerState->m_satState.m_predictedGroundTrack, &satWorkerState->m_satState.m_predictedGroundTrackDateTime);
|
|
}
|
|
}
|
|
|
|
// Send to GUI
|
|
if (getMessageQueueToGUI())
|
|
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportSat::create(new SatelliteState(satWorkerState->m_satState)));
|
|
}
|
|
else
|
|
qDebug() << "SatelliteTrackerWorker::update: No TLE for " << sat->m_name << ". Can't compute position.";
|
|
}
|
|
}
|
|
m_recalculatePasses = false;
|
|
}
|
|
|
|
void SatelliteTrackerWorker::aos(SatWorkerState *satWorkerState)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos " << satWorkerState->m_name;
|
|
|
|
// Indicate AOS to GUI
|
|
if (getMessageQueueToGUI())
|
|
{
|
|
int durationMins = (int)round((satWorkerState->m_los.toSecsSinceEpoch() - satWorkerState->m_aos.toSecsSinceEpoch())/60.0);
|
|
int maxElevation = 0;
|
|
if (satWorkerState->m_satState.m_passes.size() > 0)
|
|
maxElevation = satWorkerState->m_satState.m_passes[0]->m_maxElevation;
|
|
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportAOS::create(satWorkerState->m_name, durationMins, maxElevation));
|
|
}
|
|
|
|
// Update target
|
|
if (m_settings.m_autoTarget && (satWorkerState->m_name != m_settings.m_target))
|
|
{
|
|
// Only switch if higher priority (earlier in list) or other target not in AOS
|
|
SatWorkerState *targetSatWorkerState = m_workerState.value(m_settings.m_target);
|
|
int currentTargetIdx = m_settings.m_satellites.indexOf(m_settings.m_target);
|
|
int newTargetIdx = m_settings.m_satellites.indexOf(satWorkerState->m_name);
|
|
if ((newTargetIdx < currentTargetIdx) || !targetSatWorkerState->hasAOS(m_satelliteTracker->currentDateTimeUtc()))
|
|
{
|
|
// Stop doppler correction for current target
|
|
if (m_workerState.contains(m_settings.m_target))
|
|
m_workerState.value(m_settings.m_target)->m_dopplerTimer.stop();
|
|
|
|
qDebug() << "SatelliteTrackerWorker::aos - autoTarget setting " << satWorkerState->m_name;
|
|
m_settings.m_target = satWorkerState->m_name;
|
|
// Update GUI with new target
|
|
if (getMessageQueueToGUI())
|
|
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportTarget::create(satWorkerState->m_name));
|
|
}
|
|
}
|
|
|
|
// TODO: Detect if different device sets are used and support multiple sats simultaneously
|
|
if (m_settings.m_target == satWorkerState->m_name)
|
|
applyDeviceAOSSettings(satWorkerState->m_name);
|
|
}
|
|
|
|
// Determine if we need to flip rotator or use extended azimuth to avoid 360/0 discontinuity
|
|
void SatelliteTrackerWorker::calculateRotation(SatWorkerState *satWorkerState)
|
|
{
|
|
m_flipRotation = false;
|
|
m_extendedAzRotation = false;
|
|
if (satWorkerState->m_satState.m_passes.size() > 0)
|
|
{
|
|
SatNogsSatellite *sat = m_satellites.value(satWorkerState->m_name);
|
|
bool passes0 = getPassesThrough0Deg(sat->m_tle->m_tle0, sat->m_tle->m_tle1, sat->m_tle->m_tle2,
|
|
m_settings.m_latitude, m_settings.m_longitude, m_settings.m_heightAboveSeaLevel/1000.0,
|
|
satWorkerState->m_satState.m_passes[0]->m_aos, satWorkerState->m_satState.m_passes[0]->m_los);
|
|
if (passes0)
|
|
{
|
|
double aosAz = satWorkerState->m_satState.m_passes[0]->m_aosAzimuth;
|
|
double losAz = satWorkerState->m_satState.m_passes[0]->m_losAzimuth;
|
|
double minAz = std::min(aosAz, losAz);
|
|
if ((m_settings.m_rotatorMaxAzimuth - 360.0) > minAz)
|
|
m_extendedAzRotation = true;
|
|
else if (m_settings.m_rotatorMaxElevation == 180.0)
|
|
m_flipRotation = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SatelliteTrackerWorker::applyDeviceAOSSettings(const QString& name)
|
|
{
|
|
// Execute global program/script
|
|
if (!m_settings.m_aosCommand.isEmpty())
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: executing command: " << m_settings.m_aosCommand;
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
QStringList allArgs = m_settings.m_aosCommand.split(" ", Qt::SkipEmptyParts);
|
|
#else
|
|
QStringList allArgs = m_settings.m_aosCommand.split(" ", QString::SkipEmptyParts);
|
|
#endif
|
|
QString program = allArgs[0];
|
|
allArgs.pop_front();
|
|
QProcess::startDetached(program, allArgs);
|
|
}
|
|
|
|
// Update device set
|
|
if (m_settings.m_deviceSettings.contains(name))
|
|
{
|
|
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(name);
|
|
|
|
MainCore *mainCore = MainCore::instance();
|
|
|
|
// Load presets
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (!devSettings->m_presetGroup.isEmpty())
|
|
{
|
|
const MainSettings& mainSettings = mainCore->getSettings();
|
|
const std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
|
|
|
|
if (devSettings->m_deviceSetIndex < (int)deviceSets.size())
|
|
{
|
|
const DeviceSet *deviceSet = deviceSets[devSettings->m_deviceSetIndex];
|
|
QString presetType;
|
|
if (deviceSet->m_deviceSourceEngine != nullptr) {
|
|
presetType = "R";
|
|
} else if (deviceSet->m_deviceSinkEngine != nullptr) {
|
|
presetType = "T";
|
|
} else if (deviceSet->m_deviceMIMOEngine != nullptr) {
|
|
presetType = "M";
|
|
}
|
|
|
|
const Preset* preset = mainSettings.getPreset(devSettings->m_presetGroup, devSettings->m_presetFrequency, devSettings->m_presetDescription, presetType);
|
|
|
|
if (preset != nullptr)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: Loading preset " << preset->getDescription() << " to device set at " << devSettings->m_deviceSetIndex;
|
|
MainCore::MsgLoadPreset *msg = MainCore::MsgLoadPreset::create(preset, devSettings->m_deviceSetIndex);
|
|
mainCore->getMainMessageQueue()->push(msg);
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "SatelliteTrackerWorker::aos: Unable to get preset: " << devSettings->m_presetGroup << " " << devSettings->m_presetFrequency << " " << devSettings->m_presetDescription;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qWarning() << "SatelliteTrackerWorker::aos: device set at " << devSettings->m_deviceSetIndex << " does not exist";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wait a little bit for presets to load before performing other steps
|
|
QTimer::singleShot(1000, [this, mainCore, name, m_deviceSettingsList]()
|
|
{
|
|
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
|
|
// Override frequency
|
|
if (devSettings->m_frequency != 0)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: setting frequency to: " << devSettings->m_frequency;
|
|
ChannelWebAPIUtils::setCenterFrequency(devSettings->m_deviceSetIndex, devSettings->m_frequency);
|
|
}
|
|
|
|
// Execute per satellite program/script
|
|
if (!devSettings->m_aosCommand.isEmpty())
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: executing command: " << devSettings->m_aosCommand;
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
QStringList allArgs = m_settings.m_aosCommand.split(" ", Qt::SkipEmptyParts);
|
|
#else
|
|
QStringList allArgs = m_settings.m_aosCommand.split(" ", QString::SkipEmptyParts);
|
|
#endif
|
|
QString program = allArgs[0];
|
|
allArgs.pop_front();
|
|
QProcess::startDetached(program, allArgs);
|
|
}
|
|
|
|
}
|
|
|
|
// Start acquisition - Need to use WebAPI, in order for GUI to correctly reflect being started
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (devSettings->m_startOnAOS)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: starting acqusition";
|
|
ChannelWebAPIUtils::run(devSettings->m_deviceSetIndex);
|
|
}
|
|
}
|
|
|
|
// Send AOS message to channels/features
|
|
SatWorkerState *satWorkerState = m_workerState.value(name);
|
|
SatNogsSatellite *sat = m_satellites.value(satWorkerState->m_name);
|
|
// APT needs current time, for current position of satellite, not start of pass which may be in the past
|
|
// if the satellite was already visible when Sat Tracker was started
|
|
ChannelWebAPIUtils::satelliteAOS(name, satWorkerState->m_satState.m_passes[0]->m_northToSouth,
|
|
sat->m_tle->toString(),
|
|
m_satelliteTracker->currentDateTimeUtc());
|
|
FeatureWebAPIUtils::satelliteAOS(name, satWorkerState->m_aos, satWorkerState->m_los);
|
|
|
|
// Start Doppler correction, if needed
|
|
satWorkerState->m_initFrequencyOffset.clear();
|
|
bool requiresDoppler = false;
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (devSettings->m_doppler.size() > 0)
|
|
{
|
|
requiresDoppler = true;
|
|
for (int j = 0; j < devSettings->m_doppler.size(); j++)
|
|
{
|
|
int offset;
|
|
|
|
if (ChannelWebAPIUtils::getFrequencyOffset(devSettings->m_deviceSetIndex, devSettings->m_doppler[j], offset))
|
|
{
|
|
satWorkerState->m_initFrequencyOffset.append(offset);
|
|
qDebug() << "SatelliteTrackerWorker::applyDeviceAOSSettings: Initial frequency offset: " << offset;
|
|
}
|
|
else
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::applyDeviceAOSSettings: Failed to get initial frequency offset";
|
|
satWorkerState->m_initFrequencyOffset.append(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (requiresDoppler)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::applyDeviceAOSSettings: requiresDoppler";
|
|
satWorkerState->m_dopplerTimer.setInterval(m_settings.m_dopplerPeriod * 1000);
|
|
satWorkerState->m_dopplerTimer.start();
|
|
connect(&satWorkerState->m_dopplerTimer, &QTimer::timeout, [this, satWorkerState]() {
|
|
doppler(satWorkerState);
|
|
});
|
|
}
|
|
|
|
// Start file sinks (need a little delay to ensure sample rate message has been handled in filerecord)
|
|
QTimer::singleShot(1000, [this, m_deviceSettingsList]()
|
|
{
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
|
|
if (devSettings->m_startStopFileSink)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::aos: starting file sinks";
|
|
ChannelWebAPIUtils::startStopFileSinks(devSettings->m_deviceSetIndex, true);
|
|
}
|
|
}
|
|
});
|
|
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Send AOS message to channels/features
|
|
SatWorkerState *satWorkerState = m_workerState.value(name);
|
|
SatNogsSatellite *sat = m_satellites.value(satWorkerState->m_name);
|
|
ChannelWebAPIUtils::satelliteAOS(name, satWorkerState->m_satState.m_passes[0]->m_northToSouth,
|
|
sat->m_tle->toString(),
|
|
m_satelliteTracker->currentDateTimeUtc());
|
|
FeatureWebAPIUtils::satelliteAOS(name, satWorkerState->m_aos, satWorkerState->m_los);
|
|
}
|
|
|
|
}
|
|
|
|
void SatelliteTrackerWorker::doppler(SatWorkerState *satWorkerState)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::doppler " << satWorkerState->m_name;
|
|
|
|
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(satWorkerState->m_name);
|
|
if (m_deviceSettingsList != nullptr)
|
|
{
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (devSettings->m_doppler.size() > 0)
|
|
{
|
|
// Get center frequency for this device
|
|
double centerFrequency;
|
|
|
|
if (ChannelWebAPIUtils::getCenterFrequency(devSettings->m_deviceSetIndex, centerFrequency))
|
|
{
|
|
// Calculate frequency delta due to Doppler
|
|
double c = 299792458.0;
|
|
double deltaF = centerFrequency * satWorkerState->m_satState.m_rangeRate * 1000.0 / c;
|
|
|
|
for (int j = 0; j < devSettings->m_doppler.size(); j++)
|
|
{
|
|
// For receive, we subtract, transmit we add
|
|
int offset = satWorkerState->m_initFrequencyOffset[i] - (int)round(deltaF);
|
|
|
|
if (!ChannelWebAPIUtils::setFrequencyOffset(devSettings->m_deviceSetIndex, devSettings->m_doppler[j], offset))
|
|
qDebug() << "SatelliteTrackerWorker::doppler: Failed to set frequency offset";
|
|
}
|
|
}
|
|
else
|
|
qDebug() << "SatelliteTrackerWorker::doppler: couldn't get centre frequency for device at " << devSettings->m_deviceSetIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SatelliteTrackerWorker::los(SatWorkerState *satWorkerState)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::los " << satWorkerState->m_name << " target: " << m_settings.m_target;
|
|
|
|
// Indicate LOS to GUI
|
|
if (getMessageQueueToGUI())
|
|
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportLOS::create(satWorkerState->m_name));
|
|
|
|
// Stop Doppler timer, and set interval to 0, so we don't restart it in start()
|
|
satWorkerState->m_dopplerTimer.stop();
|
|
satWorkerState->m_dopplerTimer.setInterval(0);
|
|
|
|
if (m_settings.m_target == satWorkerState->m_name)
|
|
{
|
|
// Execute program/script
|
|
if (!m_settings.m_losCommand.isEmpty())
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::los: executing command: " << m_settings.m_losCommand;
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
QStringList allArgs = m_settings.m_losCommand.split(" ", Qt::SkipEmptyParts);
|
|
#else
|
|
QStringList allArgs = m_settings.m_losCommand.split(" ", QString::SkipEmptyParts);
|
|
#endif
|
|
QString program = allArgs[0];
|
|
allArgs.pop_front();
|
|
QProcess::startDetached(program, allArgs);
|
|
}
|
|
|
|
// Send LOS message to channels/features
|
|
ChannelWebAPIUtils::satelliteLOS(satWorkerState->m_name);
|
|
FeatureWebAPIUtils::satelliteLOS(satWorkerState->m_name);
|
|
|
|
if (m_settings.m_deviceSettings.contains(satWorkerState->m_name))
|
|
{
|
|
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(satWorkerState->m_name);
|
|
|
|
// Stop file sinks
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (devSettings->m_startStopFileSink)
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::los: stopping file sinks";
|
|
ChannelWebAPIUtils::startStopFileSinks(devSettings->m_deviceSetIndex, false);
|
|
}
|
|
}
|
|
|
|
// Stop acquisition
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
|
|
if (devSettings->m_stopOnLOS) {
|
|
ChannelWebAPIUtils::stop(devSettings->m_deviceSetIndex);
|
|
}
|
|
}
|
|
|
|
// Execute per satellite program/script
|
|
// Do after stopping acquisition, so files are closed by file sink
|
|
for (int i = 0; i < m_deviceSettingsList->size(); i++)
|
|
{
|
|
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
|
|
if (!devSettings->m_losCommand.isEmpty())
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::los: executing command: " << devSettings->m_losCommand;
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
QStringList allArgs = devSettings->m_losCommand.split(" ", Qt::SkipEmptyParts);
|
|
#else
|
|
QStringList allArgs = devSettings->m_losCommand.split(" ", QString::SkipEmptyParts);
|
|
#endif
|
|
QString program = allArgs[0];
|
|
allArgs.pop_front();
|
|
QProcess::startDetached(program, allArgs);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Is another lower-priority satellite with AOS available to switch to?
|
|
if (m_settings.m_autoTarget)
|
|
{
|
|
for (int i = m_settings.m_satellites.indexOf(m_settings.m_target) + 1; i < m_settings.m_satellites.size(); i++)
|
|
{
|
|
if (m_workerState.contains(m_settings.m_satellites[i]))
|
|
{
|
|
SatWorkerState *newSatWorkerState = m_workerState.value(m_settings.m_satellites[i]);
|
|
if (newSatWorkerState->hasAOS(m_satelliteTracker->currentDateTimeUtc()))
|
|
{
|
|
qDebug() << "SatelliteTrackerWorker::los - autoTarget setting " << m_settings.m_satellites[i];
|
|
m_settings.m_target = m_settings.m_satellites[i];
|
|
// Update GUI with new target
|
|
if (getMessageQueueToGUI())
|
|
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportTarget::create(m_settings.m_target));
|
|
// Apply device settings
|
|
applyDeviceAOSSettings(m_settings.m_target);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
m_recalculatePasses = true;
|
|
}
|
|
|
|
bool SatWorkerState::hasAOS(const QDateTime& currentTime)
|
|
{
|
|
return (m_aos <= currentTime) && (m_los > currentTime);
|
|
}
|