///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2020 Jon Beniston, M7RCE //
// //
// 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 . //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_APRS_H
#define INCLUDE_APRS_H
#include
#include
#include
#include
#include "export.h"
#include "ax25.h"
#include "util/units.h"
struct SDRBASE_API APRSPacket {
QString m_from;
QString m_to;
QString m_via;
QString m_data; // Original ASCII 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 m_telemetryNames;
QList 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);
}
QString toTNC2(QString igateCallsign)
{
return m_from + ">" + m_to + (m_via.isEmpty() ? "" : ("," + m_via)) + ",qAR," + igateCallsign + ":" + m_data + "\r\n";
}
// 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')
{
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)
text.append(QString("Altitude: %1 ft").arg(m_altitudeFt));
if (m_hasCourseAndSpeed)
text.append(QString("Course: %1%3 Speed: %2 knts").arg(m_course).arg(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)
air.append(QString("Temperature %1C").arg(Units::fahrenheitToCelsius(m_temp), 0, 'f', 1));
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)
rain.append(QString("%1 mm last hour").arg(std::round(Units::inchesToMilimetres(m_rainLastHr/100.0))));
if (m_hasRainLast24Hrs)
rain.append(QString("%1 mm last 24 hours").arg(std::round(Units::inchesToMilimetres(m_rainLast24Hrs/100.0))));
if (m_hasRainSinceMidnight)
rain.append(QString("%1 mm since midnight").arg(std::round(Units::inchesToMilimetres(m_rainSinceMidnight/100.0))));
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(QCharRef 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);
};
#endif // INCLUDE_APRS_H