2021-02-26 20:25:48 +00:00
///////////////////////////////////////////////////////////////////////////////////
2023-11-18 13:12:18 +01:00
// Copyright (C) 2021-2023 Jon Beniston, M7RCE <jon@beniston.com> //
// Copyright (C) 2021-2022 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
// Copyright (C) 2022 Jiří Pinkava <jiri.pinkava@rossum.ai> //
// Copyright (C) 2023 Daniele Forsi <iu5hkx@gmail.com> //
2021-02-26 20:25:48 +00:00
// //
// 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 "util/units.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
2023-05-20 07:57:19 +01:00
#include "channel/channelapi.h"
2021-02-26 20:25:48 +00:00
#include "channel/channelwebapiutils.h"
2021-04-07 21:23:02 +01:00
#include "feature/featurewebapiutils.h"
2021-02-26 20:25:48 +00:00
#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 ),
2021-11-25 09:28:59 +00:00
m_pollTimer ( this ),
2021-02-26 20:25:48 +00:00
m_recalculatePasses ( true ),
m_flipRotation ( false ),
m_extendedAzRotation ( false )
{
connect ( & m_pollTimer , SIGNAL ( timeout ()), this , SLOT ( update ()));
}
SatelliteTrackerWorker ::~ SatelliteTrackerWorker ()
{
2022-09-18 10:59:12 +01:00
qDebug () << "SatelliteTrackerWorker::~SatelliteTrackerWorker" ;
2022-09-20 21:14:36 +01:00
stopWork ();
2021-02-26 20:25:48 +00:00
m_inputMessageQueue . clear ();
2023-02-16 14:33:39 +00:00
// Remove satellites from Map
QHashIterator < QString , SatWorkerState *> itr ( m_workerState );
while ( itr . hasNext ())
{
itr . next ();
if ( m_settings . m_drawOnMap ) {
removeFromMap ( itr . key ());
}
}
qDeleteAll ( m_workerState );
2021-02-26 20:25:48 +00:00
}
2022-09-18 16:45:25 +01:00
void SatelliteTrackerWorker :: startWork ()
2021-02-26 20:25:48 +00:00
{
2022-09-18 10:59:12 +01:00
qDebug () << "SatelliteTrackerWorker::startWork" ;
2021-02-26 20:25:48 +00:00
QMutexLocker mutexLocker ( & m_mutex );
connect ( & m_inputMessageQueue , SIGNAL ( messageEnqueued ()), this , SLOT ( handleInputMessages ()));
2021-11-25 09:28:59 +00:00
m_recalculatePasses = true ;
2022-09-18 10:59:12 +01:00
m_pollTimer . start (( int ) round ( m_settings . m_updatePeriod * 1000.0 ));
2021-02-26 20:25:48 +00:00
// 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 ();
}
2022-09-18 10:59:12 +01:00
// Handle any messages already on the queue
handleInputMessages ();
2021-02-26 20:25:48 +00:00
}
void SatelliteTrackerWorker :: stopWork ()
{
2022-09-18 10:59:12 +01:00
qDebug () << "SatelliteTrackerWorker::stopWork" ;
2021-02-26 20:25:48 +00:00
QMutexLocker mutexLocker ( & m_mutex );
disconnect ( & m_inputMessageQueue , SIGNAL ( messageEnqueued ()), this , SLOT ( handleInputMessages ()));
m_pollTimer . stop ();
// Stop doppler timers
QHashIterator < QString , SatWorkerState *> itr ( m_workerState );
while ( itr . hasNext ())
{
itr . next ();
itr . value () -> m_dopplerTimer . stop ();
}
}
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 ;
2022-11-28 21:52:06 +01:00
applySettings ( cfg . getSettings (), cfg . getSettingsKeys (), cfg . getForce ());
2021-02-26 20:25:48 +00:00
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 ;
}
}
2022-11-28 21:52:06 +01:00
void SatelliteTrackerWorker :: applySettings ( const SatelliteTrackerSettings & settings , const QList < QString >& settingsKeys , bool force )
2021-02-26 20:25:48 +00:00
{
2022-11-28 21:52:06 +01:00
qDebug () << "SatelliteTrackerWorker::applySettings:" << settings . getDebugString ( settingsKeys , force ) << " force: " << force ;
if ( settingsKeys . contains ( "target" )
|| settingsKeys . contains ( "latitude" )
|| settingsKeys . contains ( "longitude" )
|| settingsKeys . contains ( "heightAboveSeaLevel" )
|| settingsKeys . contains ( "dateTime" )
|| settingsKeys . contains ( "utc" )
|| settingsKeys . contains ( "groundTrackPoints" )
|| settingsKeys . contains ( "minAOSElevation" )
|| settingsKeys . contains ( "minPassElevation" )
|| settingsKeys . contains ( "predictionPeriod" )
|| settingsKeys . contains ( "passStartTime" )
|| settingsKeys . contains ( "passFinishTime" )
2021-02-26 20:25:48 +00:00
|| ( ! 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 ));
}
2022-11-28 21:52:06 +01:00
else if ( settingsKeys . contains ( "updatePeriod" ) || force )
2021-02-26 20:25:48 +00:00
{
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 )
2023-02-16 14:33:39 +00:00
{
if ( m_settings . m_drawOnMap ) {
removeFromMap ( itr . key ());
}
delete itr . value ();
2021-02-26 20:25:48 +00:00
itr . remove ();
2023-02-16 14:33:39 +00:00
}
2021-02-26 20:25:48 +00:00
}
// 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 ;
}
}
2023-05-20 07:57:19 +01:00
if ( settingsKeys . contains ( "target" ) && ( settings . m_target != m_settings . m_target ))
{
if ( m_workerState . contains ( m_settings . m_target ))
{
SatWorkerState * satWorkerState = m_workerState . value ( m_settings . m_target );
disableDoppler ( satWorkerState );
}
if ( m_workerState . contains ( settings . m_target ))
{
SatWorkerState * satWorkerState = m_workerState . value ( settings . m_target );
if ( satWorkerState -> hasAOS ( m_satelliteTracker -> currentDateTimeUtc ())) {
enableDoppler ( satWorkerState );
}
}
}
2022-11-28 21:52:06 +01:00
if ( force ) {
m_settings = settings ;
} else {
m_settings . applySettings ( settingsKeys , settings );
}
2021-02-26 20:25:48 +00:00
}
void SatelliteTrackerWorker :: removeFromMap ( QString id )
{
2022-03-29 20:12:15 +02:00
QList < ObjectPipe *> mapMessagePipes ;
MainCore :: instance () -> getMessagePipes (). getMessagePipes ( m_satelliteTracker , "mapitems" , mapMessagePipes );
if ( mapMessagePipes . size () > 0 ) {
sendToMap ( mapMessagePipes , id , "" , "" , "" , 0.0f , 0.0 , 0.0 , 0.0 , 0.0 , nullptr , nullptr , nullptr , nullptr );
}
2021-02-26 20:25:48 +00:00
}
2022-03-29 20:12:15 +02:00
void SatelliteTrackerWorker :: sendToMap (
const QList < ObjectPipe *>& mapMessagePipes ,
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
)
2021-02-26 20:25:48 +00:00
{
2022-03-29 20:12:15 +02:00
for ( const auto & pipe : mapMessagePipes )
2021-02-26 20:25:48 +00:00
{
2022-03-29 20:12:15 +02:00
MessageQueue * messageQueue = qobject_cast < MessageQueue *> ( pipe -> m_element );
2021-02-26 20:25:48 +00:00
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 ));
2022-02-04 17:14:12 +00:00
swgMapItem -> setModel ( new QString ( model ));
swgMapItem -> setFixedPosition ( false );
swgMapItem -> setOrientation ( 0 );
swgMapItem -> setLabel ( new QString ( name ));
swgMapItem -> setLabelAltitudeOffset ( labelOffset );
2021-02-26 20:25:48 +00:00
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 ());
2022-02-04 17:14:12 +00:00
p -> setDateTime ( new QString ( trackDateTime -> at ( i ) -> toString ( Qt :: ISODate )));
2021-02-26 20:25:48 +00:00
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 ());
2022-02-04 17:14:12 +00:00
p -> setDateTime ( new QString ( predictedTrackDateTime -> at ( i ) -> toString ( Qt :: ISODate )));
2021-02-26 20:25:48 +00:00
mapTrack -> append ( p );
}
swgMapItem -> setPredictedTrack ( mapTrack );
}
MainCore :: MsgMapItem * msg = MainCore :: MsgMapItem :: create ( m_satelliteTracker , swgMapItem );
2022-03-29 20:12:15 +02:00
messageQueue -> push ( msg );
2021-02-26 20:25:48 +00:00
}
}
void SatelliteTrackerWorker :: update ()
{
// Get date and time to calculate position at
QDateTime qdt ;
if ( m_settings . m_dateTime == "" )
2022-02-04 17:14:12 +00:00
qdt = m_satelliteTracker -> currentDateTimeUtc ();
2021-02-26 20:25:48 +00:00
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 ();
2022-09-26 12:14:02 +01:00
bool timeReversed = m_lastUpdateDateTime > qdt ;
2021-02-26 20:25:48 +00:00
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 ;
2022-09-18 10:59:12 +01:00
bool recalcAsPastLOS = ( satWorkerState -> m_satState . m_passes . size () > 0 ) && ( satWorkerState -> m_satState . m_passes [ 0 ]. m_los < qdt );
2022-09-26 12:14:02 +01:00
if ( m_recalculatePasses || recalcAsPastLOS || timeReversed )
2021-02-26 20:25:48 +00:00
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 );
2022-09-26 12:14:02 +01:00
// Update AOS/LOS
if ( satWorkerState -> m_satState . m_passes . size () > 0 )
2021-02-26 20:25:48 +00:00
{
2022-09-26 12:14:02 +01:00
// Only use timers if using real time
if ( m_settings . m_dateTimeSelect == SatelliteTrackerSettings :: NOW )
2021-02-26 20:25:48 +00:00
{
2022-09-26 12:14:02 +01:00
// Do we have a new pass?
if (( satWorkerState -> m_aos != satWorkerState -> m_satState . m_passes [ 0 ]. m_aos ) || ( satWorkerState -> m_los != satWorkerState -> m_satState . m_passes [ 0 ]. m_los ))
2021-02-26 20:25:48 +00:00
{
2022-09-26 12:14:02 +01:00
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 ;
satWorkerState -> m_hasSignalledAOS = false ;
if ( satWorkerState -> m_aos . isValid ())
2021-02-26 20:25:48 +00:00
{
2022-09-26 12:14:02 +01:00
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 );
2021-02-26 20:25:48 +00:00
}
2022-09-26 12:14:02 +01:00
if ( satWorkerState -> m_los . isValid () && ( satWorkerState -> m_los > qdt ))
2021-02-26 20:25:48 +00:00
{
2022-09-26 12:14:02 +01:00
if ( satWorkerState -> m_losTimer . isActive ()) {
qDebug () << "SatelliteTrackerWorker::update m_losTimer.remainingTime: " << satWorkerState -> m_losTimer . remainingTime ();
}
2024-07-10 22:59:13 +02:00
// We can detect a new AOS for a satellite, a little bit before the LOS has occurred
2022-09-26 12:14:02 +01:00
// 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 ();
2021-02-26 20:25:48 +00:00
}
2022-09-26 12:14:02 +01:00
}
}
else
{
// Do we need to signal LOS?
if ( satWorkerState -> m_hasSignalledAOS && ! satWorkerState -> hasAOS ( qdt ))
{
los ( satWorkerState );
satWorkerState -> m_hasSignalledAOS = false ;
}
// Do we have a new pass?
if (( satWorkerState -> m_aos != satWorkerState -> m_satState . m_passes [ 0 ]. m_aos ) || ( satWorkerState -> m_los != satWorkerState -> m_satState . m_passes [ 0 ]. m_los ))
{
satWorkerState -> m_aos = satWorkerState -> m_satState . m_passes [ 0 ]. m_aos ;
satWorkerState -> m_los = satWorkerState -> m_satState . m_passes [ 0 ]. m_los ;
satWorkerState -> m_hasSignalledAOS = false ;
}
// Check if we need to signal AOS
if ( ! satWorkerState -> m_hasSignalledAOS && satWorkerState -> m_aos . isValid () && satWorkerState -> hasAOS ( qdt )) {
aos ( satWorkerState );
2021-02-26 20:25:48 +00:00
}
}
}
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 ))
{
2023-05-20 07:57:19 +01:00
double azimuth = satWorkerState -> m_satState . m_azimuth + m_settings . m_azimuthOffset ;
double elevation = satWorkerState -> m_satState . m_elevation + m_settings . m_elevationOffset ;
2021-02-26 20:25:48 +00:00
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 ;
}
2022-03-28 20:12:25 +02:00
QList < ObjectPipe *> rotatorPipes ;
MainCore :: instance () -> getMessagePipes (). getMessagePipes ( m_satelliteTracker , "target" , rotatorPipes );
for ( const auto & pipe : rotatorPipes )
{
MessageQueue * messageQueue = qobject_cast < MessageQueue *> ( pipe -> m_element );
SWGSDRangel :: SWGTargetAzimuthElevation * swgTarget = new SWGSDRangel :: SWGTargetAzimuthElevation ();
swgTarget -> setName ( new QString ( m_settings . m_target ));
swgTarget -> setAzimuth ( azimuth );
swgTarget -> setElevation ( elevation );
messageQueue -> push ( MainCore :: MsgTargetAzimuthElevation :: create ( m_satelliteTracker , swgTarget ));
2021-02-26 20:25:48 +00:00
}
}
// Send to Map
if ( m_settings . m_drawOnMap )
{
2022-03-29 20:12:15 +02:00
QList < ObjectPipe *> mapMessagePipes ;
MainCore :: instance () -> getMessagePipes (). getMessagePipes ( m_satelliteTracker , "mapitems" , mapMessagePipes );
if ( mapMessagePipes . size () > 0 )
2021-02-26 20:25:48 +00:00
{
2022-02-04 17:14:12 +00:00
const QStringList cubeSats ({ "AISAT-1" , "FOX-1B" , "FOX-1C" , "FOX-1D" , "FOX-1E" , "FUNCUBE-1" , "NO-84" });
2021-02-26 20:25:48 +00:00
QString image ;
2022-02-04 17:14:12 +00:00
QString model ;
float labelOffset ;
2021-02-26 20:25:48 +00:00
if ( sat -> m_name == "ISS" )
2022-02-04 17:14:12 +00:00
{
2021-02-26 20:25:48 +00:00
image = "qrc:///satellitetracker/satellitetracker/iss-32.png" ;
2022-02-04 17:14:12 +00:00
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 ;
}
2021-02-26 20:25:48 +00:00
else
2022-02-04 17:14:12 +00:00
{
2021-02-26 20:25:48 +00:00
image = "qrc:///satellitetracker/satellitetracker/satellite-32.png" ;
2022-02-04 17:14:12 +00:00
model = "satellite.glb" ;
labelOffset = 2.5f ;
}
2021-02-26 20:25:48 +00:00
QString text = QString ( "Name: %1 \n Altitude: %2 km \n Range: %3 km \n Range rate: %4 km/s \n Speed: %5 km/h \n Period: %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 )
{
2022-09-18 10:59:12 +01:00
if (( qdt >= satWorkerState -> m_satState . m_passes [ 0 ]. m_aos ) && ( qdt <= satWorkerState -> m_satState . m_passes [ 0 ]. m_los ))
2021-02-26 20:25:48 +00:00
text = text . append ( " \n Satellite is visible" );
else
2022-09-18 10:59:12 +01:00
text = text . append ( " \n AOS in: %1 mins" ). arg (( int ) round (( satWorkerState -> m_satState . m_passes [ 0 ]. m_aos . toSecsSinceEpoch () - qdt . toSecsSinceEpoch ()) / 60.0 ));
2021-04-07 21:23:02 +01:00
QString aosDateTime ;
QString losDateTime ;
if ( m_settings . m_utc )
{
2022-09-18 10:59:12 +01:00
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" );
2021-04-07 21:23:02 +01:00
}
else
{
2022-09-18 10:59:12 +01:00
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" );
2021-04-07 21:23:02 +01:00
}
2021-02-26 20:25:48 +00:00
text = QString ( "%1 \n AOS: %2 \n LOS: %3 \n Max El: %4%5" )
. arg ( text )
2021-04-07 21:23:02 +01:00
. arg ( aosDateTime )
. arg ( losDateTime )
2022-09-18 10:59:12 +01:00
. arg (( int ) round ( satWorkerState -> m_satState . m_passes [ 0 ]. m_maxElevation ))
2021-02-26 20:25:48 +00:00
. arg ( QChar ( 0xb0 ));
}
2022-03-29 20:12:15 +02:00
sendToMap (
mapMessagePipes ,
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
);
2021-02-26 20:25:48 +00:00
}
}
// Send to GUI
if ( getMessageQueueToGUI ())
getMessageQueueToGUI () -> push ( SatelliteTrackerReport :: MsgReportSat :: create ( new SatelliteState ( satWorkerState -> m_satState )));
2022-02-06 11:13:09 +00:00
// Sent to Feature for Web report
if ( m_msgQueueToFeature )
m_msgQueueToFeature -> push ( SatelliteTrackerReport :: MsgReportSat :: create ( new SatelliteState ( satWorkerState -> m_satState )));
2021-02-26 20:25:48 +00:00
}
else
qDebug () << "SatelliteTrackerWorker::update: No TLE for " << sat -> m_name << ". Can't compute position." ;
}
}
2022-09-26 12:14:02 +01:00
m_lastUpdateDateTime = qdt ;
2021-02-26 20:25:48 +00:00
m_recalculatePasses = false ;
}
void SatelliteTrackerWorker :: aos ( SatWorkerState * satWorkerState )
{
qDebug () << "SatelliteTrackerWorker::aos " << satWorkerState -> m_name ;
2022-09-26 12:14:02 +01:00
satWorkerState -> m_hasSignalledAOS = true ;
2021-02-26 20:25:48 +00:00
// Indicate AOS to GUI
if ( getMessageQueueToGUI ())
{
2022-09-29 16:50:04 +01:00
QString speech = substituteVariables ( m_settings . m_aosSpeech , satWorkerState -> m_name );
getMessageQueueToGUI () -> push ( SatelliteTrackerReport :: MsgReportAOS :: create ( satWorkerState -> m_name , speech ));
2021-02-26 20:25:48 +00:00
}
// 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 );
2022-02-04 17:14:12 +00:00
if (( newTargetIdx < currentTargetIdx ) || ! targetSatWorkerState -> hasAOS ( m_satelliteTracker -> currentDateTimeUtc ()))
2021-02-26 20:25:48 +00:00
{
// Stop doppler correction for current target
if ( m_workerState . contains ( m_settings . m_target ))
2023-05-20 07:57:19 +01:00
disableDoppler ( m_workerState . value ( m_settings . m_target ));
2021-02-26 20:25:48 +00:00
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 ,
2022-09-18 10:59:12 +01:00
satWorkerState -> m_satState . m_passes [ 0 ]. m_aos , satWorkerState -> m_satState . m_passes [ 0 ]. m_los );
2021-02-26 20:25:48 +00:00
if ( passes0 )
{
2022-09-18 10:59:12 +01:00
double aosAz = satWorkerState -> m_satState . m_passes [ 0 ]. m_aosAzimuth ;
double losAz = satWorkerState -> m_satState . m_passes [ 0 ]. m_losAzimuth ;
2021-02-26 20:25:48 +00:00
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 ;
}
}
}
2022-09-29 16:50:04 +01:00
QString SatelliteTrackerWorker :: substituteVariables ( const QString & textIn , const QString & satelliteName )
2021-02-26 20:25:48 +00:00
{
2022-09-29 16:50:04 +01:00
SatWorkerState * satWorkerState = m_workerState . value ( satelliteName );
if ( ! satWorkerState ) {
return "" ;
}
int durationMins = ( int ) round (( satWorkerState -> m_los . toSecsSinceEpoch () - satWorkerState -> m_aos . toSecsSinceEpoch ()) / 60.0 );
QString text = textIn ;
text = text . replace ( "${name}" , satelliteName );
text = text . replace ( "${duration}" , QString :: number ( durationMins ));
if ( satWorkerState -> m_satState . m_passes . size () > 0 )
{
text = text . replace ( "${aos}" , satWorkerState -> m_satState . m_passes [ 0 ]. m_aos . toString ());
text = text . replace ( "${los}" , satWorkerState -> m_satState . m_passes [ 0 ]. m_los . toString ());
text = text . replace ( "${elevation}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_passes [ 0 ]. m_maxElevation )));
text = text . replace ( "${aosAzimuth}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_passes [ 0 ]. m_aosAzimuth )));
text = text . replace ( "${losAzimuth}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_passes [ 0 ]. m_losAzimuth )));
text = text . replace ( "${northToSouth}" , QString :: number ( satWorkerState -> m_satState . m_passes [ 0 ]. m_northToSouth ));
text = text . replace ( "${latitude}" , QString :: number ( satWorkerState -> m_satState . m_latitude ));
text = text . replace ( "${longitude}" , QString :: number ( satWorkerState -> m_satState . m_longitude ));
text = text . replace ( "${altitude}" , QString :: number ( satWorkerState -> m_satState . m_altitude ));
text = text . replace ( "${azimuth}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_azimuth )));
text = text . replace ( "${elevation}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_elevation )));
text = text . replace ( "${range}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_range )));
text = text . replace ( "${rangeRate}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_rangeRate )));
text = text . replace ( "${speed}" , QString :: number ( std :: round ( satWorkerState -> m_satState . m_speed )));
text = text . replace ( "${period}" , QString :: number ( satWorkerState -> m_satState . m_period ));
}
return text ;
}
void SatelliteTrackerWorker :: executeCommand ( const QString & command , const QString & satelliteName )
{
if ( ! command . isEmpty ())
2021-02-26 20:25:48 +00:00
{
2024-09-17 09:47:45 +01:00
#if QT_CONFIG(process)
2022-09-29 16:50:04 +01:00
// Replace variables
QString cmd = substituteVariables ( command , satelliteName );
QStringList allArgs = QProcess :: splitCommand ( cmd );
qDebug () << "SatelliteTrackerWorker::executeCommand: Executing: " << allArgs ;
2021-12-27 19:16:59 +01:00
QString program = allArgs [ 0 ];
allArgs . pop_front ();
QProcess :: startDetached ( program , allArgs );
2024-09-17 09:47:45 +01:00
#else
qWarning () << "SatelliteTrackerWorker::executeCommand: QProcess not supported. Can't run: " << command ;
#endif
2021-02-26 20:25:48 +00:00
}
2022-09-29 16:50:04 +01:00
}
void SatelliteTrackerWorker :: applyDeviceAOSSettings ( const QString & name )
{
// Execute global program/script
if ( ! m_settings . m_aosCommand . isEmpty ()) {
executeCommand ( m_settings . m_aosCommand , name );
}
2021-02-26 20:25:48 +00:00
// 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 );
2021-10-03 12:12:39 +02:00
if ( ! devSettings -> m_presetGroup . isEmpty ())
2021-02-26 20:25:48 +00:00
{
const MainSettings & mainSettings = mainCore -> getSettings ();
2021-10-03 12:12:39 +02:00
const std :: vector < DeviceSet *>& deviceSets = mainCore -> getDeviceSets ();
2021-02-26 20:25:48 +00:00
2021-10-12 12:17:57 +01:00
if ( devSettings -> m_deviceSetIndex < ( int ) deviceSets . size ())
2021-02-26 20:25:48 +00:00
{
2021-10-03 12:12:39 +02:00
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 )
2021-02-26 20:25:48 +00:00
{
2021-10-03 12:12:39 +02:00
qDebug () << "SatelliteTrackerWorker::aos: Loading preset " << preset -> getDescription () << " to device set at " << devSettings -> m_deviceSetIndex ;
MainCore :: MsgLoadPreset * msg = MainCore :: MsgLoadPreset :: create ( preset , devSettings -> m_deviceSetIndex );
2021-02-26 20:25:48 +00:00
mainCore -> getMainMessageQueue () -> push ( msg );
}
else
2021-10-03 12:12:39 +02:00
{
qWarning () << "SatelliteTrackerWorker::aos: Unable to get preset: " << devSettings -> m_presetGroup << " " << devSettings -> m_presetFrequency << " " << devSettings -> m_presetDescription ;
}
2021-02-26 20:25:48 +00:00
}
else
2021-10-03 12:12:39 +02:00
{
qWarning () << "SatelliteTrackerWorker::aos: device set at " << devSettings -> m_deviceSetIndex << " does not exist" ;
}
2021-02-26 20:25:48 +00:00
}
}
// Wait a little bit for presets to load before performing other steps
2022-09-18 16:45:25 +01:00
QTimer :: singleShot ( 1000 , [ this , name , m_deviceSettingsList ]()
2021-02-26 20:25:48 +00:00
{
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 ;
2021-10-03 12:12:39 +02:00
ChannelWebAPIUtils :: setCenterFrequency ( devSettings -> m_deviceSetIndex , devSettings -> m_frequency );
2021-02-26 20:25:48 +00:00
}
// Execute per satellite program/script
2022-09-29 16:50:04 +01:00
if ( ! devSettings -> m_aosCommand . isEmpty ()) {
executeCommand ( devSettings -> m_aosCommand , name );
2021-02-26 20:25:48 +00:00
}
}
// 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 )
{
2023-07-23 16:44:42 +02:00
qDebug () << "SatelliteTrackerWorker::aos: starting acquisition" ;
2021-10-03 12:12:39 +02:00
ChannelWebAPIUtils :: run ( devSettings -> m_deviceSetIndex );
2021-02-26 20:25:48 +00:00
}
}
2021-04-07 21:23:02 +01:00
// Send AOS message to channels/features
2021-02-26 20:25:48 +00:00
SatWorkerState * satWorkerState = m_workerState . value ( name );
2022-02-04 17:14:12 +00:00
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
2022-09-18 10:59:12 +01:00
ChannelWebAPIUtils :: satelliteAOS ( name , satWorkerState -> m_satState . m_passes [ 0 ]. m_northToSouth ,
2022-02-04 17:14:12 +00:00
sat -> m_tle -> toString (),
m_satelliteTracker -> currentDateTimeUtc ());
2021-04-07 21:23:02 +01:00
FeatureWebAPIUtils :: satelliteAOS ( name , satWorkerState -> m_aos , satWorkerState -> m_los );
2021-02-26 20:25:48 +00:00
// Start Doppler correction, if needed
2023-05-20 07:57:19 +01:00
enableDoppler ( satWorkerState );
2021-03-08 22:10:43 +00:00
// Start file sinks (need a little delay to ensure sample rate message has been handled in filerecord)
2022-09-18 16:45:25 +01:00
QTimer :: singleShot ( 1000 , [ m_deviceSettingsList ]()
2021-03-08 22:10:43 +00:00
{
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" ;
2021-10-03 12:12:39 +02:00
ChannelWebAPIUtils :: startStopFileSinks ( devSettings -> m_deviceSetIndex , true );
2021-03-08 22:10:43 +00:00
}
}
});
2021-02-26 20:25:48 +00:00
});
}
else
{
2021-04-07 21:23:02 +01:00
// Send AOS message to channels/features
2021-02-26 20:25:48 +00:00
SatWorkerState * satWorkerState = m_workerState . value ( name );
2022-02-04 17:14:12 +00:00
SatNogsSatellite * sat = m_satellites . value ( satWorkerState -> m_name );
2022-09-18 10:59:12 +01:00
ChannelWebAPIUtils :: satelliteAOS ( name , satWorkerState -> m_satState . m_passes [ 0 ]. m_northToSouth ,
2022-02-04 17:14:12 +00:00
sat -> m_tle -> toString (),
m_satelliteTracker -> currentDateTimeUtc ());
2021-04-07 21:23:02 +01:00
FeatureWebAPIUtils :: satelliteAOS ( name , satWorkerState -> m_aos , satWorkerState -> m_los );
2021-02-26 20:25:48 +00:00
}
}
2023-05-20 07:57:19 +01:00
void SatelliteTrackerWorker :: enableDoppler ( SatWorkerState * satWorkerState )
{
QList < SatelliteTrackerSettings :: SatelliteDeviceSettings *> * m_deviceSettingsList = m_settings . m_deviceSettings . value ( satWorkerState -> m_name );
2023-05-23 22:27:35 +01:00
if ( m_deviceSettingsList )
2023-05-20 07:57:19 +01:00
{
2023-05-23 22:27:35 +01:00
satWorkerState -> m_doppler . clear ();
bool requiresDoppler = false ;
for ( int i = 0 ; i < m_deviceSettingsList -> size (); i ++ )
2023-05-20 07:57:19 +01:00
{
2023-05-23 22:27:35 +01:00
SatelliteTrackerSettings :: SatelliteDeviceSettings * devSettings = m_deviceSettingsList -> at ( i );
if ( devSettings -> m_doppler . size () > 0 )
{
requiresDoppler = true ;
2025-04-16 14:43:47 +01:00
satWorkerState -> m_doppler . append ( 0 );
2023-05-20 07:57:19 +01:00
}
}
2023-05-23 22:27:35 +01:00
if ( requiresDoppler )
{
qDebug () << "SatelliteTrackerWorker::applyDeviceAOSSettings: Enabling doppler for " << satWorkerState -> m_name ;
satWorkerState -> m_dopplerTimer . setInterval ( m_settings . m_dopplerPeriod * 1000 );
satWorkerState -> m_dopplerTimer . start ();
2025-04-16 14:43:47 +01:00
disconnect ( satWorkerState -> m_connection );
satWorkerState -> m_connection = connect ( & satWorkerState -> m_dopplerTimer , & QTimer :: timeout , [ this , satWorkerState ]() {
2023-05-23 22:27:35 +01:00
doppler ( satWorkerState );
});
}
2023-05-20 07:57:19 +01:00
}
}
void SatelliteTrackerWorker :: disableDoppler ( SatWorkerState * satWorkerState )
{
// 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 );
2025-04-16 14:43:47 +01:00
disconnect ( satWorkerState -> m_connection );
2023-05-20 07:57:19 +01:00
// Remove doppler correction from any channel
2025-04-16 14:43:47 +01:00
if ( satWorkerState -> m_doppler . size () > 0 )
2023-05-20 07:57:19 +01:00
{
2025-04-16 14:43:47 +01:00
QList < SatelliteTrackerSettings :: SatelliteDeviceSettings *> * m_deviceSettingsList = m_settings . m_deviceSettings . value ( satWorkerState -> m_name );
if ( m_deviceSettingsList )
2023-05-20 07:57:19 +01:00
{
2025-04-16 14:43:47 +01:00
int idx = 0 ;
for ( int i = 0 ; i < m_deviceSettingsList -> size (); i ++ )
2023-05-20 07:57:19 +01:00
{
2025-04-16 11:23:38 +01:00
SatelliteTrackerSettings :: SatelliteDeviceSettings * devSettings = m_deviceSettingsList -> at ( i );
if ( devSettings -> m_doppler . size () > 0 )
2023-05-20 07:57:19 +01:00
{
2025-04-16 11:23:38 +01:00
for ( int j = 0 ; j < devSettings -> m_doppler . size (); j ++ )
2023-05-20 07:57:19 +01:00
{
2025-04-16 11:23:38 +01:00
int offset ;
if ( ChannelWebAPIUtils :: getFrequencyOffset ( devSettings -> m_deviceSetIndex , devSettings -> m_doppler [ j ], offset ))
{
// Remove old doppler
2025-04-16 14:43:47 +01:00
std :: vector < DeviceSet *>& deviceSets = MainCore :: instance () -> getDeviceSets ();
ChannelAPI * channel = deviceSets [ devSettings -> m_deviceSetIndex ] -> getChannelAt ( j );
int tx = false ;
if ( channel ) {
tx = channel -> getStreamType () == ChannelAPI :: StreamSingleSource ; // What if MIMO?
}
if ( tx ) {
offset -= satWorkerState -> m_doppler [ idx ];
} else {
offset += satWorkerState -> m_doppler [ idx ];
}
if ( ! ChannelWebAPIUtils :: setFrequencyOffset ( devSettings -> m_deviceSetIndex , devSettings -> m_doppler [ j ], offset ))
2025-04-16 11:23:38 +01:00
qDebug () << "SatelliteTrackerWorker::disableDoppler: Failed to set frequency offset" ;
}
else
qDebug () << "SatelliteTrackerWorker::disableDoppler: Failed to get frequency offset" ;
2023-05-20 07:57:19 +01:00
}
2025-04-16 14:43:47 +01:00
satWorkerState -> m_doppler [ idx ] = 0 ;
idx ++ ;
2023-05-20 07:57:19 +01:00
}
}
}
}
}
2021-02-26 20:25:48 +00:00
void SatelliteTrackerWorker :: doppler ( SatWorkerState * satWorkerState )
{
qDebug () << "SatelliteTrackerWorker::doppler " << satWorkerState -> m_name ;
QList < SatelliteTrackerSettings :: SatelliteDeviceSettings *> * m_deviceSettingsList = m_settings . m_deviceSettings . value ( satWorkerState -> m_name );
2023-05-23 22:27:35 +01:00
if ( m_deviceSettingsList )
2021-02-26 20:25:48 +00:00
{
2025-04-16 14:43:47 +01:00
int idx = 0 ;
2021-02-26 20:25:48 +00:00
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 ;
2021-10-03 12:12:39 +02:00
if ( ChannelWebAPIUtils :: getCenterFrequency ( devSettings -> m_deviceSetIndex , centerFrequency ))
2021-02-26 20:25:48 +00:00
{
// Calculate frequency delta due to Doppler
double c = 299792458.0 ;
double deltaF = centerFrequency * satWorkerState -> m_satState . m_rangeRate * 1000.0 / c ;
2023-05-20 07:57:19 +01:00
int doppler = ( int ) round ( deltaF );
2021-02-26 20:25:48 +00:00
for ( int j = 0 ; j < devSettings -> m_doppler . size (); j ++ )
{
2023-05-20 07:57:19 +01:00
int offset ;
if ( ChannelWebAPIUtils :: getFrequencyOffset ( devSettings -> m_deviceSetIndex , devSettings -> m_doppler [ j ], offset ))
{
// Apply doppler - For receive, we subtract, transmit we add
std :: vector < DeviceSet *>& deviceSets = MainCore :: instance () -> getDeviceSets ();
ChannelAPI * channel = deviceSets [ devSettings -> m_deviceSetIndex ] -> getChannelAt ( j );
int tx = false ;
if ( channel ) {
tx = channel -> getStreamType () == ChannelAPI :: StreamSingleSource ; // What if MIMO?
}
2021-02-26 20:25:48 +00:00
2023-05-20 07:57:19 +01:00
// Remove old doppler and apply new
int initOffset ;
if ( tx )
{
2025-04-16 14:43:47 +01:00
initOffset = offset - satWorkerState -> m_doppler [ idx ];
2023-05-20 07:57:19 +01:00
offset = initOffset + doppler ;
}
else
{
2025-04-16 14:43:47 +01:00
initOffset = offset + satWorkerState -> m_doppler [ idx ];
2023-05-20 07:57:19 +01:00
offset = initOffset - doppler ;
}
if ( ! ChannelWebAPIUtils :: setFrequencyOffset ( devSettings -> m_deviceSetIndex , devSettings -> m_doppler [ j ], offset ))
qDebug () << "SatelliteTrackerWorker::doppler: Failed to set frequency offset" ;
}
else
qDebug () << "SatelliteTrackerWorker::doppler: Failed to get frequency offset" ;
2021-02-26 20:25:48 +00:00
}
2023-05-20 07:57:19 +01:00
2025-04-16 14:43:47 +01:00
satWorkerState -> m_doppler [ idx ] = doppler ;
2021-02-26 20:25:48 +00:00
}
else
2021-10-03 12:12:39 +02:00
qDebug () << "SatelliteTrackerWorker::doppler: couldn't get centre frequency for device at " << devSettings -> m_deviceSetIndex ;
2025-04-16 14:43:47 +01:00
idx ++ ;
2021-02-26 20:25:48 +00:00
}
}
}
}
void SatelliteTrackerWorker :: los ( SatWorkerState * satWorkerState )
{
2022-02-04 17:14:12 +00:00
qDebug () << "SatelliteTrackerWorker::los " << satWorkerState -> m_name << " target: " << m_settings . m_target ;
2021-02-26 20:25:48 +00:00
// Indicate LOS to GUI
if ( getMessageQueueToGUI ())
2022-09-29 16:50:04 +01:00
{
QString speech = substituteVariables ( m_settings . m_losSpeech , satWorkerState -> m_name );
getMessageQueueToGUI () -> push ( SatelliteTrackerReport :: MsgReportLOS :: create ( satWorkerState -> m_name , speech ));
}
2021-02-26 20:25:48 +00:00
2023-05-20 07:57:19 +01:00
disableDoppler ( satWorkerState );
2021-02-26 20:25:48 +00:00
if ( m_settings . m_target == satWorkerState -> m_name )
{
// Execute program/script
2022-09-29 16:50:04 +01:00
if ( ! m_settings . m_losCommand . isEmpty ()) {
executeCommand ( m_settings . m_losCommand , satWorkerState -> m_name );
2021-02-26 20:25:48 +00:00
}
2022-02-04 17:14:12 +00:00
// Send LOS message to channels/features
ChannelWebAPIUtils :: satelliteLOS ( satWorkerState -> m_name );
FeatureWebAPIUtils :: satelliteLOS ( satWorkerState -> m_name );
2021-02-26 20:25:48 +00:00
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" ;
2021-10-03 12:12:39 +02:00
ChannelWebAPIUtils :: startStopFileSinks ( devSettings -> m_deviceSetIndex , false );
2021-02-26 20:25:48 +00:00
}
}
// Stop acquisition
for ( int i = 0 ; i < m_deviceSettingsList -> size (); i ++ )
{
SatelliteTrackerSettings :: SatelliteDeviceSettings * devSettings = m_deviceSettingsList -> at ( i );
2021-10-03 12:12:39 +02:00
if ( devSettings -> m_stopOnLOS ) {
ChannelWebAPIUtils :: stop ( devSettings -> m_deviceSetIndex );
2021-02-26 20:25:48 +00:00
}
}
// 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 );
2022-09-29 16:50:04 +01:00
if ( ! devSettings -> m_losCommand . isEmpty ()) {
executeCommand ( devSettings -> m_losCommand , satWorkerState -> m_name );
2021-02-26 20:25:48 +00:00
}
}
}
}
// 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 ]);
2022-02-04 17:14:12 +00:00
if ( newSatWorkerState -> hasAOS ( m_satelliteTracker -> currentDateTimeUtc ()))
2021-02-26 20:25:48 +00:00
{
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 ;
}
2021-07-28 14:10:23 +01:00
2022-02-04 17:14:12 +00:00
bool SatWorkerState :: hasAOS ( const QDateTime & currentTime )
2021-07-28 14:10:23 +01:00
{
return ( m_aos <= currentTime ) && ( m_los > currentTime );
}