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"