Skip to content

Instantly share code, notes, and snippets.

@sjmurdoch
Created September 3, 2025 21:47
Show Gist options
  • Select an option

  • Save sjmurdoch/72051bd256022ece3f3898d21a96abb6 to your computer and use it in GitHub Desktop.

Select an option

Save sjmurdoch/72051bd256022ece3f3898d21a96abb6 to your computer and use it in GitHub Desktop.
Adding ambiguity to APRS message
diff --git a/include/APRSPacketLib.h b/include/APRSPacketLib.h
index aed7772..9171477 100644
--- a/include/APRSPacketLib.h
+++ b/include/APRSPacketLib.h
@@ -45,10 +45,10 @@ namespace APRSPacketLib {
String generateDigipeatedPacket(const String& packet, const String &callsign, const String& path);
- String encodeGPSIntoBase91(float latitude, float longitude, float course, float speed, const String& symbol, bool sendAltitude = false, int altitude = 0, bool sendStandingUpdate = false);
+ String encodeGPSIntoBase91(float latitude, float longitude, float course, float speed, const String& symbol, bool sendAltitude = false, int altitude = 0, bool sendStandingUpdate = false, int ambiguity_level = 2);
String generateBase91GPSBeaconPacket(const String& callsign, const String& tocall, const String& path, const String& overlay, const String& gpsData);
- String generateMiceGPSBeaconPacket(const String& miceMsgType, const String& callsign, const String& symbol, const String& overlay, const String& path, float latitude, float longitude, float course, float speed, int altitude);
+ String generateMiceGPSBeaconPacket(const String& miceMsgType, const String& callsign, const String& symbol, const String& overlay, const String& path, float latitude, float longitude, float course, float speed, int altitude, int ambiguity_level = 2);
APRSPacket processReceivedPacket(const String& receivedPacket, int rssi, float snr, int freqError);
diff --git a/src/APRSPacketLib.cpp b/src/APRSPacketLib.cpp
index 48daa1c..3ff008b 100644
--- a/src/APRSPacketLib.cpp
+++ b/src/APRSPacketLib.cpp
@@ -28,9 +28,46 @@ _______________________________________________*/
#include <APRSPacketLib.h>
+#include <cmath>
namespace APRSPacketLib {
+ /**
+ * @brief Applies ambiguity to a GPS coordinate by reducing its precision.
+ *
+ * This function implements the standard APRS position ambiguity by truncating
+ * the decimal part of the coordinate's minutes. The new, less-precise
+ * coordinate is then used for encoding.
+ *
+ * @param coordinate The original coordinate in decimal degrees.
+ * @param ambiguity_level An integer from 0 to 4.
+ * - 0: No ambiguity.
+ * - 1: Reduces precision to 0.1 minutes (e.g., 4903.5_N).
+ * - 2: Reduces precision to 1 minute (e.g., 4903.__N).
+ * - 3: Reduces precision to 10 minutes (e.g., 490_.__N).
+ * - 4: Reduces precision to 1 degree (e.g., 49__.__N).
+ * @return The coordinate with reduced precision.
+ */
+ float apply_ambiguity(float coordinate, int ambiguity_level) {
+ if (ambiguity_level <= 0) {
+ return coordinate;
+ }
+
+ float deg = floorf(fabsf(coordinate));
+ float dec_minutes = (fabsf(coordinate) - deg) * 60.0f;
+
+ switch (ambiguity_level) {
+ case 1: dec_minutes = floorf(dec_minutes * 10.0f) / 10.0f; break;
+ case 2: dec_minutes = floorf(dec_minutes); break;
+ case 3: dec_minutes = floorf(dec_minutes / 10.0f) * 10.0f; break;
+ default: dec_minutes = 0.0f; break; // Level 4 and higher
+ }
+
+ float new_coordinate = deg + dec_minutes / 60.0f;
+
+ return copysignf(new_coordinate, coordinate);
+ }
+
String generateBasePacket(const String& callsign, const String& tocall, const String& path) {
String packet = callsign;
packet += ">";
@@ -91,7 +128,14 @@ namespace APRSPacketLib {
return(s);
}
- String encodeGPSIntoBase91(float latitude, float longitude, float course, float speed, const String& symbol, bool sendAltitude, int altitude, bool sendStandingUpdate) {
+ String encodeGPSIntoBase91(float latitude, float longitude, float course, float speed, const String& symbol, bool sendAltitude, int altitude, bool sendStandingUpdate, int ambiguity_level) {
+ // Apply ambiguity to the coordinates if requested.
+ // This truncates the precision before encoding.
+ if (ambiguity_level > 0) {
+ latitude = apply_ambiguity(latitude, ambiguity_level);
+ longitude = apply_ambiguity(longitude, ambiguity_level);
+ }
+
String encodedData;
uint32_t aprs_lat, aprs_lon;
aprs_lat = 900000000 - latitude * 10000000;
@@ -386,7 +430,7 @@ namespace APRSPacketLib {
buf[2] = h28;
}
- void encodeMiceDestinationField(const String& msgType, uint8_t *buf, const gpsLatitudeStruct *lat, const gpsLongitudeStruct *lon) {
+ void encodeMiceDestinationField(const String& msgType, uint8_t *buf, const gpsLatitudeStruct *lat, const gpsLongitudeStruct *lon, int ambiguity_level) {
uint32_t temp;
temp = lat->degrees / 10; // degrees
buf[0] = (temp + 0x30);
@@ -402,6 +446,18 @@ namespace APRSPacketLib {
temp = lat->minuteHundredths/10; // minute hundredths
buf[4] = (temp + 0x30) + ((lon->degrees >= 100 || lon->degrees <= 9) ? 0x20 : 0); // Longitude Offset
buf[5] = (lat->minuteHundredths - temp * 10 + 0x30) + (!lon->east ? 0x20 : 0); // West validation
+
+ // Per APRS spec, ambiguity in Mic-E is shown by replacing characters with spaces.
+ // This is done *after* encoding the (already truncated) coordinate value.
+ if (ambiguity_level >= 1) {
+ buf[5] = ' '; // Obscure 1/100th of a minute
+ }
+ if (ambiguity_level >= 2) {
+ buf[4] = ' '; // Obscure 1/10th of a minute
+ }
+ if (ambiguity_level >= 3) {
+ buf[3] = ' '; // Obscure 1 minute
+ }
}
String doubleToString(double n, int ndec) {
@@ -494,12 +550,18 @@ namespace APRSPacketLib {
return miceLongitudeStruct;
}
- String generateMiceGPSBeaconPacket(const String& miceMsgType, const String& callsign, const String& symbol, const String& overlay, const String& path, float latitude, float longitude, float course, float speed, int altitude) {
+ String generateMiceGPSBeaconPacket(const String& miceMsgType, const String& callsign, const String& symbol, const String& overlay, const String& path, float latitude, float longitude, float course, float speed, int altitude, int ambiguity_level) {
+ // Apply ambiguity to the coordinates if requested. This truncates the precision.
+ if (ambiguity_level > 0) {
+ latitude = apply_ambiguity(latitude, ambiguity_level);
+ longitude = apply_ambiguity(longitude, ambiguity_level);
+ }
+
gpsLatitudeStruct latitudeStruct = gpsDecimalToDegreesMiceLatitude(latitude);
gpsLongitudeStruct longitudeStruct = gpsDecimalToDegreesMiceLongitude(longitude);
uint8_t miceDestinationArray[7];
- encodeMiceDestinationField(miceMsgType, &miceDestinationArray[0], &latitudeStruct, &longitudeStruct);
+ encodeMiceDestinationField(miceMsgType, &miceDestinationArray[0], &latitudeStruct, &longitudeStruct, ambiguity_level);
miceDestinationArray[6] = 0x00; // por repetidor?
String miceDestination = (char*)miceDestinationArray;
diff --git a/src/keyboard_utils.cpp b/src/keyboard_utils.cpp
index bd7b22c..a0ed6ad 100644
--- a/src/keyboard_utils.cpp
+++ b/src/keyboard_utils.cpp
@@ -746,7 +746,7 @@ namespace KEYBOARD_Utils {
} else if (key == 13 && messageText.length() > 0) {
messageText.trim();
if (messageText.length() > 67) messageText = messageText.substring(0, 67);
- String packet = APRSPacketLib::generateBase91GPSBeaconPacket(currentBeacon->callsign, "APLRT1", Config.path, currentBeacon->overlay, APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate));
+ String packet = APRSPacketLib::generateBase91GPSBeaconPacket(currentBeacon->callsign, "APLRT1", Config.path, currentBeacon->overlay, APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, 2));
packet += messageText;
displayShow("<<< TX >>>", "", packet,100);
LoRa_Utils::sendNewPacket(packet);
diff --git a/src/station_utils.cpp b/src/station_utils.cpp
index 07f7b87..10eb694 100644
--- a/src/station_utils.cpp
+++ b/src/station_utils.cpp
@@ -198,9 +198,9 @@ namespace STATION_Utils {
if (gps.speed.kmph() > 200 || gps.altitude.meters() > 9000) path = ""; // avoid plane speed and altitude
String packet;
if (miceActive) {
- packet = APRSPacketLib::generateMiceGPSBeaconPacket(currentBeacon->micE, currentBeacon->callsign, currentBeacon->symbol, currentBeacon->overlay, path, gps.location.lat(), gps.location.lng(), gps.course.deg(), gps.speed.knots(), gps.altitude.meters());
+ packet = APRSPacketLib::generateMiceGPSBeaconPacket(currentBeacon->micE, currentBeacon->callsign, currentBeacon->symbol, currentBeacon->overlay, path, gps.location.lat(), gps.location.lng(), gps.course.deg(), gps.speed.knots(), gps.altitude.meters(), 2);
} else {
- packet = APRSPacketLib::generateBase91GPSBeaconPacket(currentBeacon->callsign, "APLRT1", path, currentBeacon->overlay, APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate));
+ packet = APRSPacketLib::generateBase91GPSBeaconPacket(currentBeacon->callsign, "APLRT1", path, currentBeacon->overlay, APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), gps.course.deg(), gps.speed.knots(), currentBeacon->symbol, Config.sendAltitude, gps.altitude.feet(), sendStandingUpdate, 2));
}
String batteryVoltage = BATTERY_Utils::getBatteryInfoVoltage();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment