mirror of
				https://github.com/f4exb/sdrangel.git
				synced 2025-10-31 04:50:29 -04:00 
			
		
		
		
	
		
			
				
	
	
		
			508 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			508 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| ///////////////////////////////////////////////////////////////////////////////////
 | |
| // Copyright (C) 2021-2022 Jon Beniston, M7RCE <jon@beniston.com>                //
 | |
| // Copyright (C) 2022 Peter Beckman <beckman@angryox.com>                        //
 | |
| //                                                                               //
 | |
| // This program is free software; you can redistribute it and/or modify          //
 | |
| // it under the terms of the GNU General Public License as published by          //
 | |
| // the Free Software Foundation as version 3 of the License, or                  //
 | |
| // (at your option) any later version.                                           //
 | |
| //                                                                               //
 | |
| // This program is distributed in the hope that it will be useful,               //
 | |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of                //
 | |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                  //
 | |
| // GNU General Public License V3 for more details.                               //
 | |
| //                                                                               //
 | |
| // You should have received a copy of the GNU General Public License             //
 | |
| // along with this program. If not, see <http://www.gnu.org/licenses/>.          //
 | |
| ///////////////////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| #ifndef INCLUDE_APRS_H
 | |
| #define INCLUDE_APRS_H
 | |
| 
 | |
| #include <QString>
 | |
| #include <QStringList>
 | |
| #include <QByteArray>
 | |
| #include <QDebug>
 | |
| 
 | |
| #include "export.h"
 | |
| #include "ax25.h"
 | |
| #include "util/units.h"
 | |
| 
 | |
| struct SDRBASE_API APRSPacket {
 | |
|     QString m_from;
 | |
|     QString m_to;
 | |
|     QString m_via;
 | |
|     QByteArray m_data;          // Original binary data
 | |
| 
 | |
|     QDateTime m_dateTime;       // Date/time of reception / decoding
 | |
| 
 | |
|     // Timestamp (where fields are not transmitted, time of decoding is used)
 | |
|     QDateTime m_timestamp;
 | |
|     bool m_utc; // Whether UTC (true) or local time (false)
 | |
|     bool m_hasTimestamp;
 | |
| 
 | |
|     // Position
 | |
|     float m_latitude;
 | |
|     float m_longitude;
 | |
|     bool m_hasPosition;
 | |
| 
 | |
|     float m_altitudeFt;
 | |
|     bool m_hasAltitude;
 | |
| 
 | |
|     // Symbol
 | |
|     char m_symbolTable;
 | |
|     char m_symbolCode;
 | |
|     bool m_hasSymbol;
 | |
|     QString m_symbolImage;      // Image filename for the symbol
 | |
| 
 | |
|     // Course and speed
 | |
|     int m_course;
 | |
|     int m_speed;
 | |
|     bool m_hasCourseAndSpeed;
 | |
| 
 | |
|     // Power, antenna height, gain, directivity
 | |
|     int m_powerWatts;
 | |
|     int m_antennaHeightFt;
 | |
|     int m_antennaGainDB;
 | |
|     QString m_antennaDirectivity; // Omni, or N, NE...
 | |
|     bool m_hasStationDetails;
 | |
| 
 | |
|     // Radio range
 | |
|     int m_radioRangeMiles;
 | |
|     bool m_hasRadioRange;
 | |
| 
 | |
|     // Omni-DF
 | |
|     int m_dfStrength;
 | |
|     int m_dfHeightFt;
 | |
|     int m_dfGainDB;
 | |
|     QString m_dfAntennaDirectivity;
 | |
|     bool m_hasDf;
 | |
| 
 | |
|     QString m_objectName;       // Also used for items
 | |
|     bool m_objectLive;
 | |
|     bool m_objectKilled;
 | |
| 
 | |
|     QString m_comment;
 | |
| 
 | |
|     // Weather reports
 | |
|     int m_windDirection;        // In degrees
 | |
|     bool m_hasWindDirection;
 | |
|     int m_windSpeed;            // In mph
 | |
|     bool m_hasWindSpeed;
 | |
|     int m_gust;                 // Peak wind speed in last 5 minutes in mph
 | |
|     bool m_hasGust;
 | |
|     int m_temp;                 // Fahrenheit, can be negative down to -99
 | |
|     bool m_hasTemp;
 | |
|     int m_rainLastHr;           // Hundreths of an inch
 | |
|     bool m_hasRainLastHr;
 | |
|     int m_rainLast24Hrs;
 | |
|     bool m_hasRainLast24Hrs;
 | |
|     int m_rainSinceMidnight;
 | |
|     bool m_hasRainSinceMidnight;
 | |
|     int m_humidity;             // %
 | |
|     bool m_hasHumidity;
 | |
|     int m_barometricPressure;   // Tenths of millibars / tenths of hPascal
 | |
|     bool m_hasBarometricPressure;
 | |
|     int m_luminosity;           // Watts per m^2
 | |
|     bool m_hasLuminsoity;
 | |
|     int m_snowfallLast24Hrs;    // In inches
 | |
|     bool m_hasSnowfallLast24Hrs;
 | |
|     int m_rawRainCounter;
 | |
|     bool m_hasRawRainCounter;
 | |
|     int m_radiationLevel;
 | |
|     bool m_hasRadiationLevel;
 | |
|     int m_floodLevel;           // Tenths of a foot. Can be negative
 | |
|     bool m_hasFloodLevel;
 | |
|     int m_batteryVolts;         // Tenths of a volt
 | |
|     bool m_hasBatteryVolts;
 | |
|     QString m_weatherUnitType;
 | |
|     bool m_hasWeather;
 | |
| 
 | |
|     int m_stormDirection;
 | |
|     int m_stormSpeed;
 | |
|     QString m_stormType;
 | |
|     int m_stormSustainedWindSpeed;      // knots
 | |
|     int m_stormPeakWindGusts;           // knots
 | |
|     int m_stormCentralPresure;          // millibars/hPascal
 | |
|     int m_stormRadiusHurricanWinds;     // nautical miles
 | |
|     int m_stormRadiusTropicalStormWinds;// nautical miles
 | |
|     int m_stormRadiusWholeGail;         // nautical miles
 | |
|     bool m_hasStormRadiusWholeGail;
 | |
|     bool m_hasStormData;
 | |
| 
 | |
|     // Status messages
 | |
|     QString m_status;
 | |
|     QString m_maidenhead;
 | |
|     int m_beamHeading;
 | |
|     int m_beamPower;
 | |
|     bool m_hasBeam;
 | |
|     bool m_hasStatus;
 | |
| 
 | |
|     // Messages
 | |
|     QString m_addressee;
 | |
|     QString m_message;
 | |
|     QString m_messageNo;
 | |
|     bool m_hasMessage;
 | |
|     QList <QString> m_telemetryNames;
 | |
|     QList <QString> m_telemetryLabels;
 | |
|     double m_telemetryCoefficientsA[5];
 | |
|     double m_telemetryCoefficientsB[5];
 | |
|     double m_telemetryCoefficientsC[5];
 | |
|     int m_hasTelemetryCoefficients;
 | |
|     int m_telemetryBitSense[8];
 | |
|     bool m_hasTelemetryBitSense;
 | |
|     QString m_telemetryProjectName;
 | |
| 
 | |
|     // Telemetry
 | |
|     int m_seqNo;
 | |
|     bool m_hasSeqNo;
 | |
|     int m_a1;
 | |
|     bool m_a1HasValue;
 | |
|     int m_a2;
 | |
|     bool m_a2HasValue;
 | |
|     int m_a3;
 | |
|     bool m_a3HasValue;
 | |
|     int m_a4;
 | |
|     bool m_a4HasValue;
 | |
|     int m_a5;
 | |
|     bool m_a5HasValue;
 | |
|     bool m_b[8];
 | |
|     bool m_bHasValue;
 | |
|     QString m_telemetryComment;
 | |
|     bool m_hasTelemetry;
 | |
| 
 | |
|     bool decode(AX25Packet packet);
 | |
| 
 | |
|     APRSPacket() :
 | |
|         m_hasTimestamp(false),
 | |
|         m_hasPosition(false),
 | |
|         m_hasAltitude(false),
 | |
|         m_hasSymbol(false),
 | |
|         m_hasCourseAndSpeed(false),
 | |
|         m_hasStationDetails(false),
 | |
|         m_hasRadioRange(false),
 | |
|         m_hasDf(false),
 | |
|         m_objectLive(false),
 | |
|         m_objectKilled(false),
 | |
|         m_hasWindDirection(false),
 | |
|         m_hasWindSpeed(false),
 | |
|         m_hasGust(false),
 | |
|         m_hasTemp(false),
 | |
|         m_hasRainLastHr(false),
 | |
|         m_hasRainLast24Hrs(false),
 | |
|         m_hasRainSinceMidnight(false),
 | |
|         m_hasHumidity(false),
 | |
|         m_hasBarometricPressure(false),
 | |
|         m_hasLuminsoity(false),
 | |
|         m_hasSnowfallLast24Hrs(false),
 | |
|         m_hasRawRainCounter(false),
 | |
|         m_hasRadiationLevel(false),
 | |
|         m_hasFloodLevel(false),
 | |
|         m_hasBatteryVolts(false),
 | |
|         m_hasWeather(false),
 | |
|         m_hasStormRadiusWholeGail(false),
 | |
|         m_hasStormData(false),
 | |
|         m_hasBeam(false),
 | |
|         m_hasStatus(false),
 | |
|         m_hasMessage(false),
 | |
|         m_hasTelemetryCoefficients(0),
 | |
|         m_hasTelemetryBitSense(false),
 | |
|         m_hasSeqNo(false),
 | |
|         m_a1HasValue(false),
 | |
|         m_a2HasValue(false),
 | |
|         m_a3HasValue(false),
 | |
|         m_a4HasValue(false),
 | |
|         m_a5HasValue(false),
 | |
|         m_bHasValue(false),
 | |
|         m_hasTelemetry(false)
 | |
|     {
 | |
|     }
 | |
| 
 | |
|     QString date()
 | |
|     {
 | |
|         if (m_hasTimestamp)
 | |
|             return m_timestamp.date().toString("yyyy/MM/dd");
 | |
|         else
 | |
|             return QString("");
 | |
|     }
 | |
| 
 | |
|     QString time()
 | |
|     {
 | |
|         if (m_hasTimestamp)
 | |
|             return m_timestamp.time().toString("hh:mm:ss");
 | |
|         else
 | |
|             return QString("");
 | |
|     }
 | |
| 
 | |
|     QString dateTime()
 | |
|     {
 | |
|         return QString("%1 %2").arg(date()).arg(time());
 | |
|     }
 | |
| 
 | |
|     QString position()
 | |
|     {
 | |
|         return QString("%1,%2").arg(m_latitude).arg(m_longitude);
 | |
|     }
 | |
| 
 | |
|     QByteArray toTNC2(QString igateCallsign)
 | |
|     {
 | |
|         QByteArray data;
 | |
| 
 | |
|         data.append(m_from.toLatin1());
 | |
|         data.append('>');
 | |
|         data.append(m_to.toLatin1());
 | |
|         if (!m_via.isEmpty())
 | |
|         {
 | |
|             data.append(',');
 | |
|             data.append(m_via.toLatin1());
 | |
|         }
 | |
|         data.append(",qAR,");
 | |
|         data.append(igateCallsign.toLatin1());
 | |
|         data.append(':');
 | |
| 
 | |
|         // #2028 - Protect against APRS-IS server command injection, by only sending up to first CR/LF
 | |
|         int idx = m_data.indexOf("\r");
 | |
|         if (idx == -1) {
 | |
|             idx = m_data.indexOf("\n");
 | |
|         }
 | |
|         if (idx >= 0) {
 | |
|             data.append(m_data.left(idx));
 | |
|         } else {
 | |
|             data.append(m_data);
 | |
|         }
 | |
|         data.append('\r');
 | |
|         data.append('\n');
 | |
| 
 | |
|         return data;
 | |
|     }
 | |
| 
 | |
|     // Convert a TNC2 formatted packet (as sent by APRS-IS Igates) to an AX25 byte array
 | |
|     static QByteArray toByteArray(QString tnc2)
 | |
|     {
 | |
|         QByteArray bytes;
 | |
|         QString tmp = "";
 | |
|         QString from;
 | |
|         int state = 0;
 | |
| 
 | |
|         for (int i = 0; i < tnc2.length(); i++)
 | |
|         {
 | |
|             if (state == 0)
 | |
|             {
 | |
|                 // From
 | |
|                 if (tnc2[i] == '>')
 | |
|                 {
 | |
|                     from = tmp;
 | |
|                     tmp = "";
 | |
|                     state = 1;
 | |
|                 }
 | |
|                 else
 | |
|                     tmp.append(tnc2[i]);
 | |
|             }
 | |
|             else if (state == 1)
 | |
|             {
 | |
|                  // To
 | |
|                  if (tnc2[i] == ':')
 | |
|                  {
 | |
|                     bytes.append(AX25Packet::encodeAddress(tmp));
 | |
|                     bytes.append(AX25Packet::encodeAddress(from, 1));
 | |
|                     state = 3;
 | |
|                  }
 | |
|                  else if (tnc2[i] == ',')
 | |
|                  {
 | |
|                     bytes.append(AX25Packet::encodeAddress(tmp));
 | |
|                     bytes.append(AX25Packet::encodeAddress(from));
 | |
|                     tmp = "";
 | |
|                     state = 2;
 | |
|                  }
 | |
|                  else
 | |
|                     tmp.append(tnc2[i]);
 | |
|             }
 | |
|             else if (state == 2)
 | |
|             {
 | |
|                  // Via
 | |
|                  if (tnc2[i] == ':')
 | |
|                  {
 | |
|                     bytes.append(AX25Packet::encodeAddress(tmp, 1));
 | |
|                     state = 3;
 | |
|                  }
 | |
|                  else if (tnc2[i] == ',')
 | |
|                  {
 | |
|                     bytes.append(AX25Packet::encodeAddress(tmp));
 | |
|                     tmp = "";
 | |
|                  }
 | |
|                  else
 | |
|                     tmp.append(tnc2[i]);
 | |
|             }
 | |
|             else if (state == 3)
 | |
|             {
 | |
|                 // UI Type and PID
 | |
|                 bytes.append(3);
 | |
|                 bytes.append(-16); // 0xf0
 | |
|                 // APRS message
 | |
|                 bytes.append(tnc2.mid(i).toLatin1().trimmed());
 | |
|                 // CRC
 | |
|                 bytes.append((char)0);
 | |
|                 bytes.append((char)0);
 | |
|                 break;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return bytes;
 | |
|     }
 | |
| 
 | |
|     QString toText(bool includeFrom=true, bool includePosition=false, char separator='\n',
 | |
|                         bool altitudeMetres=false, int speedUnits=0, bool tempCelsius=false, bool rainfallMM=false)
 | |
|     {
 | |
|         QStringList text;
 | |
| 
 | |
|         if (!m_objectName.isEmpty())
 | |
|         {
 | |
|             text.append(QString("%1 (%2)").arg(m_objectName).arg(m_from));
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|             if (includeFrom)
 | |
|                 text.append(m_from);
 | |
|         }
 | |
| 
 | |
|         if (m_hasTimestamp)
 | |
|         {
 | |
|             QStringList time;
 | |
|             time.append(this->date());
 | |
|             time.append(this->time());
 | |
|             if (m_utc)
 | |
|                 time.append("UTC");
 | |
|             else
 | |
|                 time.append("local");
 | |
|             text.append(time.join(' '));
 | |
|         }
 | |
| 
 | |
|         if (includePosition && m_hasPosition)
 | |
|             text.append(QString("Latitude: %1 Longitude: %2").arg(m_latitude).arg(m_longitude));
 | |
|         if (m_hasAltitude)
 | |
|         {
 | |
|             if (altitudeMetres)
 | |
|                 text.append(QString("Altitude: %1 m").arg((int)std::round(Units::feetToMetres(m_altitudeFt))));
 | |
|             else
 | |
|                 text.append(QString("Altitude: %1 ft").arg(m_altitudeFt));
 | |
|         }
 | |
|         if (m_hasCourseAndSpeed)
 | |
|         {
 | |
|             if (speedUnits == 0)
 | |
|                 text.append(QString("Course: %1%3 Speed: %2 knts").arg(m_course).arg(m_speed).arg(QChar(0xb0)));
 | |
|             else if (speedUnits == 1)
 | |
|                 text.append(QString("Course: %1%3 Speed: %2 mph").arg(m_course).arg(Units::knotsToIntegerMPH(m_speed)).arg(QChar(0xb0)));
 | |
|             else
 | |
|                 text.append(QString("Course: %1%3 Speed: %2 kph").arg(m_course).arg(Units::knotsToIntegerKPH(m_speed)).arg(QChar(0xb0)));
 | |
|         }
 | |
| 
 | |
|         if (m_hasStationDetails)
 | |
|             text.append(QString("TX Power: %1 Watts Antenna Height: %2 m Gain: %3 dB Direction: %4").arg(m_powerWatts).arg(std::round(Units::feetToMetres(m_antennaHeightFt))).arg(m_antennaGainDB).arg(m_antennaDirectivity));
 | |
|         if (m_hasRadioRange)
 | |
|             text.append(QString("Range: %1 km").arg(Units::milesToKilometres(m_radioRangeMiles)));
 | |
|         if (m_hasDf)
 | |
|             text.append(QString("DF Strength: S %1 Height: %2 m Gain: %3 dB Direction: %4").arg(m_dfStrength).arg(std::round(Units::feetToMetres(m_dfHeightFt))).arg(m_dfGainDB).arg(m_dfAntennaDirectivity));
 | |
| 
 | |
|         if (m_hasWeather)
 | |
|         {
 | |
|             QStringList weather, wind, air, rain;
 | |
| 
 | |
|             wind.append(QString("Wind"));
 | |
|             if (m_hasWindDirection)
 | |
|                 wind.append(QString("%1%2").arg(m_windDirection).arg(QChar(0xb0)));
 | |
|             if (m_hasWindSpeed)
 | |
|                 wind.append(QString("%1 mph").arg(m_windSpeed));
 | |
|             if (m_hasGust)
 | |
|                 wind.append(QString("Gusts %1 mph").arg(m_gust));
 | |
|             weather.append(wind.join(' '));
 | |
| 
 | |
|             if (m_hasTemp || m_hasHumidity || m_hasBarometricPressure)
 | |
|             {
 | |
|                 air.append("Air");
 | |
|                 if (m_hasTemp)
 | |
|                 {
 | |
|                     if (tempCelsius)
 | |
|                         air.append(QString("Temperature %1C").arg(Units::fahrenheitToCelsius(m_temp), 0, 'f', 1));
 | |
|                     else
 | |
|                         air.append(QString("Temperature %1F").arg(m_temp));
 | |
|                 }
 | |
|                 if (m_hasHumidity)
 | |
|                     air.append(QString("Humidity %1%").arg(m_humidity));
 | |
|                 if (m_hasBarometricPressure)
 | |
|                     air.append(QString("Pressure %1 mbar").arg(m_barometricPressure/10.0));
 | |
|                 weather.append(air.join(' '));
 | |
|             }
 | |
| 
 | |
|             if (m_hasRainLastHr || m_hasRainLast24Hrs || m_hasRainSinceMidnight)
 | |
|             {
 | |
|                 rain.append("Rain");
 | |
|                 if (m_hasRainLastHr)
 | |
|                 {
 | |
|                     if (rainfallMM)
 | |
|                         rain.append(QString("%1 mm last hour").arg(std::round(Units::inchesToMilimetres(m_rainLastHr/100.0))));
 | |
|                     else
 | |
|                         rain.append(QString("%1 1/100\" last hour").arg(m_rainLastHr));
 | |
|                 }
 | |
|                 if (m_hasRainLast24Hrs)
 | |
|                 {
 | |
|                     if (rainfallMM)
 | |
|                         rain.append(QString("%1 mm last 24 hours").arg(std::round(Units::inchesToMilimetres(m_rainLast24Hrs/100.0))));
 | |
|                     else
 | |
|                         rain.append(QString("%1 1/100\" last 24 hours").arg(m_rainLast24Hrs));
 | |
|                 }
 | |
|                 if (m_hasRainSinceMidnight)
 | |
|                 {
 | |
|                     if (rainfallMM)
 | |
|                         rain.append(QString("%1 mm since midnight").arg(std::round(Units::inchesToMilimetres(m_rainSinceMidnight/100.0))));
 | |
|                     else
 | |
|                         rain.append(QString("%1 1/100\" since midnight").arg(m_rainSinceMidnight));
 | |
|                 }
 | |
|                 weather.append(rain.join(' '));
 | |
|             }
 | |
| 
 | |
|             if (!m_weatherUnitType.isEmpty())
 | |
|                 weather.append(m_weatherUnitType);
 | |
| 
 | |
|             text.append(weather.join(separator));
 | |
|         }
 | |
| 
 | |
|         if (m_hasStormData)
 | |
|         {
 | |
|             QStringList storm;
 | |
| 
 | |
|             storm.append(m_stormType);
 | |
| 
 | |
|             storm.append(QString("Direction: %1%3 Speed: %2").arg(m_stormDirection).arg(m_stormSpeed).arg(QChar(0xb0)));
 | |
|             storm.append(QString("Sustained wind speed: %1 knots Peak wind gusts: %2 knots Central pressure: %3 mbar").arg(m_stormSustainedWindSpeed).arg(m_stormPeakWindGusts).arg(m_stormCentralPresure));
 | |
|             storm.append(QString("Hurrican winds radius: %1 nm Tropical storm winds radius: %2 nm%3").arg(m_stormRadiusHurricanWinds).arg(m_stormRadiusTropicalStormWinds).arg(m_hasStormRadiusWholeGail ? QString("") : QString(" Whole gail radius: %3 nm").arg(m_stormRadiusWholeGail)));
 | |
| 
 | |
|             text.append(storm.join(separator));
 | |
|         }
 | |
| 
 | |
|         if (!m_comment.isEmpty())
 | |
|             text.append(m_comment);
 | |
| 
 | |
|         return text.join(separator);
 | |
|     }
 | |
| 
 | |
| private:
 | |
|     int charToInt(QString &s, int idx);
 | |
|     bool parseTime(QString& info, int& idx);
 | |
|     bool parseTimeMDHM(QString& info, int& idx);
 | |
|     bool isLatLongChar(const QChar c);
 | |
|     bool parsePosition(QString& info, int& idx);
 | |
|     bool parseDataExension(QString& info, int& idx);
 | |
|     bool parseComment(QString& info, int& idx);
 | |
|     bool parseInt(QString& info, int& idx, int chars, int& value, bool& hasValue);
 | |
|     bool parseWeather(QString& info, int& idx, bool positionLess);
 | |
|     bool parseStorm(QString& info, int& idx);
 | |
|     bool parseObject(QString& info, int& idx);
 | |
|     bool parseItem(QString& info, int& idx);
 | |
|     bool parseStatus(QString& info, int& idx);
 | |
|     bool parseMessage(QString& info, int& idx);
 | |
|     bool parseTelemetry(QString& info, int& idx);
 | |
|     bool parseMicE(QString& info, int& idx, QString& dest);
 | |
| };
 | |
| 
 | |
| #endif // INCLUDE_APRS_H
 |