File indexing completed on 2024-04-28 03:50:12

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Wes Hardaker <hardaker@users.sourceforge.net>
0004 //
0005 
0006 #include "AprsGatherer.h"
0007 
0008 #include "MarbleDirs.h"
0009 #include "MarbleDebug.h"
0010 
0011 #include <QMutex>
0012 
0013 using namespace Marble;
0014 
0015 AprsGatherer::AprsGatherer( AprsSource *from,
0016                             QMap<QString, AprsObject *> *objects,
0017                             QMutex *mutex,
0018                             QString *filter )
0019     : m_source( from ),
0020       m_socket( nullptr ),
0021       m_filter( filter ),
0022       m_running( true ),
0023       m_dumpOutput( false ),
0024       m_seenFrom( GeoAprsCoordinates::FromNowhere ),
0025       m_sourceName( ),
0026       m_mutex( mutex ),
0027       m_objects( objects )
0028 {
0029     m_sourceName = from->sourceName();
0030     initMicETables();
0031 }
0032 
0033 AprsGatherer::AprsGatherer( QIODevice *from,
0034                             QMap<QString, AprsObject *> *objects,
0035                             QMutex *mutex,
0036                             QString *filter ) 
0037     : m_source( nullptr ),
0038       m_socket( from ),
0039       m_filter( filter ),
0040       m_running( true ),
0041       m_dumpOutput( false ),
0042       m_seenFrom( GeoAprsCoordinates::FromNowhere ),
0043       m_sourceName( "unknown" ),
0044       m_mutex( mutex ),
0045       m_objects( objects )
0046 {
0047     initMicETables();
0048 }
0049 
0050 void 
0051 AprsGatherer::run() 
0052 {
0053     char buf[4096];
0054     qint64 linelength;
0055     // one particular APRS packet sender can add data after the : ( sigh )
0056     QRegExp matcher( "^([0-9A-Z]+-*[0-9A-Z]*)>([^:]*):([!=@\\/])([0-9][0-9][0-9][0-9][0-9][0-9]|)([hz\\/]|)([0-9][0-9])([0-9][0-9]\\.[0-9][0-9])([NS])(.)([0-9][0-9][0-9])([0-9][0-9]\\.[0-9][0-9])([EW])(.)" );
0057 
0058     // mic-e formatted
0059     // 1: src
0060     // 2: dst
0061     // 3: routes
0062     // 4: longitude x 3
0063     // 5: speed and course x3
0064     // 6: symbol and symbol ID
0065     // 7: status text
0066     QRegExp mic_e_matcher( "^([0-9A-Z]+-*[0-9A-Z]*)>([^,:]*),*([^:]*):['`](...)(...)(..)(.*)" );
0067 
0068     // If a source can directly receive a signal (as opposed to
0069     // through a relay like the internet) will return true.  This
0070     // prevents accidentally coloring signals heard over some sources
0071     // as heard directly where it's never possible (such as over the
0072     // internet).
0073     Q_ASSERT(m_source);
0074     if (!m_source) {
0075         return;
0076     }
0077     bool canDoDirect = m_source->canDoDirect();
0078     
0079     while( m_running ) {
0080 
0081         if ( m_socket && !m_socket->isOpen() ) {
0082             // connection closed; attempt to reopen
0083             mDebug() << "aprs: socket closed; attempting to reopen";
0084             delete m_socket;
0085             m_socket = nullptr;
0086         }
0087         
0088             
0089         if ( !m_socket )
0090             m_socket = m_source->openSocket();
0091 
0092         if ( !m_socket ) {
0093             mDebug() << "aprs: failed to open socket from "
0094                      << m_sourceName.toLocal8Bit().data();
0095             sleep( 5 );
0096             continue;
0097         }
0098         
0099         // wait for data to read in
0100         if ( m_socket->bytesAvailable() <= 0 )
0101             // wait no longer than 1s
0102             if ( ! m_socket->waitForReadyRead( 1000 ) )
0103                 continue; // continue to loop again on "not ready"
0104 
0105         // Read the line to parse
0106         linelength = m_socket->readLine( buf, sizeof( buf ) );
0107 
0108         // if we got 0 or less bytes this is probably an odd case; ask
0109         // the source what to do.
0110         m_source->checkReadReturn( linelength, &m_socket, this );
0111         
0112         if ( linelength <= 0 ) {
0113             // don't go into an infinite untimed loop of failed sockets
0114             sleep( 2 );
0115             continue;
0116         }
0117 
0118         if ( m_socket && m_filter != nullptr ) {
0119             QMutexLocker locker( m_mutex );
0120             if ( m_filter->length() > 0 ) {
0121                 m_socket->write( m_filter->toLocal8Bit().data(),
0122                                  m_filter->length() );
0123             }
0124         }
0125 
0126         // Parse the results
0127         QString line( buf );
0128 
0129         // Dump it out if we wanted it dumped
0130         if ( m_dumpOutput )
0131             mDebug() << "aprs: " << m_sourceName.toLocal8Bit().data()
0132                      << ": " << line;
0133 
0134         if ( matcher.indexIn( line ) != -1 ) {
0135             QString callSign  = matcher.cap( 1 );
0136             qreal latitude = matcher.cap( 6 ).toFloat() +
0137                 ( matcher.cap( 7 ).toFloat()/60 );
0138             if (matcher.cap(8) == QLatin1String("S"))
0139                 latitude = - latitude;
0140 
0141             qreal longitude = matcher.cap( 10 ).toFloat() +
0142                 ( matcher.cap( 11 ).toFloat()/60 );
0143             if (matcher.cap(12) == QLatin1String("W"))
0144                 longitude = - longitude;
0145 
0146             addObject( callSign, latitude, longitude, canDoDirect,
0147                        QString( matcher.cap( 2 ) ),
0148                        QChar( matcher.cap( 9 )[0] ),
0149                        QChar( matcher.cap( 13 )[0] ) );
0150         }
0151         else if ( mic_e_matcher.indexIn( line ) != -1 ) {
0152             // MIC-E formatted compressed packet
0153             QString myCall  = mic_e_matcher.cap( 1 ); 
0154             QString dstCall = mic_e_matcher.cap( 2 );
0155 
0156             qreal latitude =
0157                 // hours
0158                 m_dstCallDigits[dstCall[0]] * 10 +
0159                 m_dstCallDigits[dstCall[1]] +
0160 
0161                 // minutes
0162                 ( qreal( m_dstCallDigits[dstCall[2]] * 10 +
0163                          m_dstCallDigits[dstCall[3]] ) +
0164                   qreal( m_dstCallDigits[dstCall[4]] ) / 10.0 +
0165                   qreal( m_dstCallDigits[dstCall[5]] ) / 100 ) / 60.0;
0166 
0167             if ( m_dstCallSouthEast[dstCall[4]] )
0168                 latitude = - latitude;
0169 
0170             qreal longitude =
0171                 calculateLongitude( QString ( mic_e_matcher.cap( 4 ) ),
0172                                     m_dstCallLongitudeOffset[dstCall[4]],
0173                                     m_dstCallSouthEast[dstCall[5]] );
0174 
0175 //           mDebug() << "  MIC-E: " << line.toLocal8Bit().data();
0176 //           mDebug() << "    lat: " << latitude;
0177 //           mDebug() << "    lon: " << longitude;
0178 
0179             addObject( myCall, latitude, longitude, canDoDirect,
0180                        QString( mic_e_matcher.cap( 3 ) ),
0181                        QChar( mic_e_matcher.cap( 6 )[1] ),
0182                        QChar( mic_e_matcher.cap( 6 )[0] ) );
0183         }
0184         else {
0185             mDebug() << "aprs: UNPARSED: " << line;
0186         }
0187 
0188         // If the filter should be changed, send it out the socket
0189         if ( m_filter != nullptr ) {
0190             QMutexLocker locker( m_mutex );
0191             if ( m_filter->length() > 0 ) {
0192                 m_socket->write( m_filter->toLocal8Bit().data(),
0193                                  m_filter->length() );
0194             }
0195         }
0196     }
0197 }
0198 
0199 void
0200 AprsGatherer::shutDown()
0201 {
0202     m_running = false;
0203 }
0204 
0205 void
0206 AprsGatherer::addObject( const QString &callSign,
0207                          qreal latitude, qreal longitude, bool canDoDirect,
0208                          const QString &routePath,
0209                          const QChar &symbolTable,
0210                          const QChar &symbolCode )
0211 {
0212     QMutexLocker locker( m_mutex );
0213 
0214     GeoAprsCoordinates location( longitude, latitude, m_seenFrom );
0215     if ( canDoDirect ) {
0216         if (!routePath.contains(QLatin1Char('*'))) {
0217             location.addSeenFrom( GeoAprsCoordinates::Directly );
0218         }
0219     }
0220 
0221     if ( m_objects->contains( callSign ) ) {
0222         // we already have one for this callSign; just add the new
0223         // history item.
0224         ( *m_objects )[callSign]->setLocation( location );
0225     }
0226     else {
0227         AprsObject *foundObject = new AprsObject( location, callSign );
0228         foundObject->setPixmapId( m_pixmaps[QPair<QChar, QChar>( symbolTable,symbolCode )] );
0229         ( *m_objects )[callSign] = foundObject;
0230         mDebug() << "aprs:  new: " << callSign.toLocal8Bit().data();
0231     }
0232 }
0233 
0234 qreal AprsGatherer::calculateLongitude( const QString &threeBytes, int offset,
0235                                          bool isEast )
0236 {
0237     // otherwise known as "fun with funky encoding"
0238     qreal hours = threeBytes[0].toLatin1() - 28 + offset;
0239     if ( 180 <= hours && hours <= 189 )
0240         hours -= 80;
0241     if ( 190 <= hours && hours <= 199 )
0242         hours -= 190;
0243 
0244     hours +=
0245         ( qreal( (threeBytes[1].toLatin1() - 28 ) % 60 ) + 
0246           ( qreal( threeBytes[2].toLatin1() - 28 ) ) / 100 ) / 60.0;
0247 
0248     if ( ! isEast )
0249         hours = -hours;
0250     return hours;
0251 }
0252 
0253 void
0254 AprsGatherer::setDumpOutput( bool to )
0255 {
0256     m_dumpOutput = to;
0257 }
0258 
0259 bool
0260 AprsGatherer::dumpOutput() const
0261 {
0262     return m_dumpOutput;
0263 }
0264 
0265 void
0266 AprsGatherer::setSeenFrom( GeoAprsCoordinates::SeenFrom to )
0267 {
0268     m_seenFrom = to;
0269 }
0270 
0271 GeoAprsCoordinates::SeenFrom
0272 AprsGatherer::seenFrom()
0273 {
0274     return m_seenFrom;
0275 }
0276 
0277 // gets around parent QThread protected sleep as our objects need a sleep
0278 void
0279 AprsGatherer::sleepFor(int seconds)
0280 {
0281     sleep(seconds);
0282 }
0283 
0284 #include "moc_AprsGatherer.cpp"