diff --git a/common_settings.ini b/common_settings.ini
index 83724cf..49ce4ec 100644
--- a/common_settings.ini
+++ b/common_settings.ini
@@ -37,7 +37,7 @@ lib_deps =
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3
- richonguzman/APRSPacketLib @ 1.0.8
+ richonguzman/APRSPacketLib @ 1.0.9
display_libs =
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
diff --git a/include/query_utils.h b/include/query_utils.h
index 0c89698..e60105b 100644
--- a/include/query_utils.h
+++ b/include/query_utils.h
@@ -24,7 +24,8 @@
namespace QUERY_Utils {
- String process(const String& query, const String& station, bool queryFromAPRSIS, bool thirdParty);
+ bool isQuery(const String& message);
+ String process(const String& query, const String& station, bool queryFromAPRSIS, bool thirdParty);
}
diff --git a/include/utils.h b/include/utils.h
index 44167ea..d8a4a82 100644
--- a/include/utils.h
+++ b/include/utils.h
@@ -40,6 +40,7 @@ namespace Utils {
void checkBeaconInterval();
void checkDisplayInterval();
void validateFreqs();
+ String padForDisplay(const String& text);
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType);
void updateAPRSISPacketDisplayInfo(const String& packet);
void print(const String& text);
diff --git a/src/A7670_utils.cpp b/src/A7670_utils.cpp
index 5070e9c..f246a1d 100644
--- a/src/A7670_utils.cpp
+++ b/src/A7670_utils.cpp
@@ -197,6 +197,7 @@
}
void uploadToAPRSIS(const String& packet) {
+ if (packet == "") return; // never send an empty line to APRS-IS
beaconBytesSent = checkATResponse("AT+CIPSEND=0," + String(packet.length()+1));
delay(2000);
if (beaconBytesSent) {
diff --git a/src/LoRa_APRS_iGate.cpp b/src/LoRa_APRS_iGate.cpp
index eb75361..25e0424 100644
--- a/src/LoRa_APRS_iGate.cpp
+++ b/src/LoRa_APRS_iGate.cpp
@@ -68,7 +68,7 @@ ___________________________________________________________________*/
#endif
-String versionDate = "2026-10-04";
+String versionDate = "2026-10-05";
String versionNumber = "4.0.2";
Configuration Config;
WiFiClient aprsIsClient;
diff --git a/src/aprs_is_utils.cpp b/src/aprs_is_utils.cpp
index 463e21e..e43c654 100644
--- a/src/aprs_is_utils.cpp
+++ b/src/aprs_is_utils.cpp
@@ -60,6 +60,7 @@ uint32_t lastServerCheck = 0;
namespace APRS_IS_Utils {
void upload(const String& line) {
+ if (line == "") return; // never send an empty line to APRS-IS
aprsIsClient.print(line + "\r\n");
}
@@ -156,37 +157,19 @@ namespace APRS_IS_Utils {
int leftCurlyBraceIndex = packet.indexOf("{");
int colonIndex = packet.indexOf(":");
if (leftCurlyBraceIndex > 0) { // ack?
- String ackMessage = "ack";
- ackMessage.concat(packet.substring(leftCurlyBraceIndex + 1));
- ackMessage.trim();
- //Serial.println(ackMessage);
-
- String addToBuffer = Config.callsign;
- addToBuffer += ">APLRG1";
- if (lastAprsPacket.header == "") addToBuffer += ",RFONLY";
- if (Config.beacon.path != "") {
- addToBuffer += ",";
- addToBuffer += Config.beacon.path;
- }
- addToBuffer += "::";
-
- String processedSender = sender;
- for (int i = sender.length(); i < 9; i++) {
- processedSender += ' ';
- }
- addToBuffer += processedSender;
-
- addToBuffer += ":";
- addToBuffer += ackMessage;
- STATION_Utils::addToOutputPacketBuffer(addToBuffer);
+ String ackCallsign = (Config.tacticalCallsign == "") ? Config.callsign : Config.tacticalCallsign; // ack from the callsign the message was addressed to
+ String ackPath = (lastAprsPacket.header == "") ? "RFONLY," + Config.beacon.path : Config.beacon.path;
+ String ackPacket = APRSPacketLib::generateAckPacket(ackCallsign, "APLRG1", ackPath, sender, packet);
+ if (ackPacket != "") STATION_Utils::addToOutputPacketBuffer(ackPacket);
receivedMessage = packet.substring(colonIndex + 1, leftCurlyBraceIndex);
} else {
receivedMessage = packet.substring(colonIndex + 1);
}
- if (receivedMessage.indexOf("?") == 0) {
+ if (QUERY_Utils::isQuery(receivedMessage)) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
- STATION_Utils::addToOutputPacketBuffer(QUERY_Utils::process(receivedMessage, sender, false, lastAprsPacket.header != ""));
+ String queryAnswer = QUERY_Utils::process(receivedMessage, sender, false, lastAprsPacket.header != "");
+ if (queryAnswer != "") STATION_Utils::addToOutputPacketBuffer(queryAnswer); // unknown query: no answer
lastScreenOn = millis();
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + sender, "TYPE --> QUERY", 0);
return true;
@@ -274,21 +257,9 @@ namespace APRS_IS_Utils {
}
void processAckMessage(const String& sender, const String& message) {
- String ackPacket = Config.callsign;
- ackPacket += ">APLRG1,TCPIP*::";
-
- String senderCallsign = sender;
- for (int i = sender.length(); i < 9; i++) {
- senderCallsign += ' ';
- }
- ackPacket += senderCallsign;
- ackPacket += ":";
-
- String ackMessage = "ack";
- ackMessage += message.substring(message.indexOf("{") + 1);
- ackMessage.trim();
- ackPacket += ackMessage;
-
+ String ackPacket = APRSPacketLib::generateAckPacket(Config.callsign, "APLRG1", "", sender, message);
+ if (ackPacket == "") return;
+ ackPacket = APRSPacketLib::generateAPRSISPacket(ackPacket);
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(ackPacket);
#else
@@ -314,7 +285,7 @@ namespace APRS_IS_Utils {
if (Config.digi.backupDigiMode) lastServerCheck = currentTime;
} else {
int doubleColonIndex = packet.indexOf("::");
- if (Config.aprs_is.messagesToRF && doubleColonIndex > 0) {
+ if (doubleColonIndex > 0) { // messages for this station are always answered; messagesToRF only gates forwarding to RF
String Sender = packet.substring(0, packet.indexOf(">"));
const String& AddresseeAndMessage = packet.substring(doubleColonIndex + 2);
int colonIndex = AddresseeAndMessage.indexOf(":");
@@ -329,7 +300,7 @@ namespace APRS_IS_Utils {
} else {
receivedMessage = AddresseeAndMessage.substring(colonIndex + 1);
}
- if (receivedMessage.indexOf("?") == 0) {
+ if (QUERY_Utils::isQuery(receivedMessage)) {
Utils::println("Rx Query (APRS-IS) : " + packet);
String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, true, false);
//Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n")));
@@ -337,24 +308,23 @@ namespace APRS_IS_Utils {
displayToggle(true);
}
lastScreenOn = currentTime;
- #ifdef HAS_A7670
- A7670_Utils::uploadToAPRSIS(queryAnswer);
- #else
- upload(queryAnswer);
- #endif
- SYSLOG_Utils::logAPRSISTx(queryAnswer);
- fifthLine = "APRS-IS ----> APRS-IS";
- sixthLine = Config.callsign;
- for (int j = sixthLine.length();j < 9;j++) {
- sixthLine += " ";
+ if (queryAnswer != "") { // unknown query: no answer
+ #ifdef HAS_A7670
+ A7670_Utils::uploadToAPRSIS(queryAnswer);
+ #else
+ upload(queryAnswer);
+ #endif
+ SYSLOG_Utils::logAPRSISTx(queryAnswer);
}
+ fifthLine = "APRS-IS ----> APRS-IS";
+ sixthLine = Utils::padForDisplay(Config.callsign);
sixthLine += "> ";
sixthLine += Sender;
seventhLine = "QUERY = ";
seventhLine += receivedMessage;
}
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
- } else {
+ } else if (Config.aprs_is.messagesToRF) {
Utils::print("Rx Message (APRS-IS): " + packet);
if (STATION_Utils::wasHeard(Addressee) && packet.indexOf("EQNS.") == -1 && packet.indexOf("UNIT.") == -1 && packet.indexOf("PARM.") == -1) {
STATION_Utils::addToOutputPacketBuffer(buildPacketToTx(packet, 1));
diff --git a/src/query_utils.cpp b/src/query_utils.cpp
index 1383677..1c68240 100644
--- a/src/query_utils.cpp
+++ b/src/query_utils.cpp
@@ -16,6 +16,7 @@
* along with LoRa APRS iGate. If not, see .
*/
+#include
#include "configuration.h"
#include "battery_utils.h"
#include "station_utils.h"
@@ -36,11 +37,17 @@ extern String versionNumber;
namespace QUERY_Utils {
+ bool isQuery(const String& message) { // "?..." queries, plus "HELP" (any case) without the "?"
+ String queryQuestion = message;
+ queryQuestion.toUpperCase();
+ return queryQuestion.startsWith("?") || queryQuestion == "HELP";
+ }
+
String process(const String& query, const String& station, bool queryFromAPRSIS, bool thirdParty) {
String answer;
String queryQuestion = query;
queryQuestion.toUpperCase();
- if (queryQuestion == "?APRS?" || queryQuestion == "H" || queryQuestion == "HELP" || queryQuestion=="?") {
+ if (queryQuestion == "?APRS?" || queryQuestion == "?" || queryQuestion == "HELP") {
answer.concat("?APRSV ?APRSP ?APRSL ?APRSSR ?EM=? ?TX=? "); // ?APRSH ?WHERE callsign
} else if (queryQuestion == "?APRSV") {
answer.concat("CA2RXU_LoRa_iGate v");
@@ -139,32 +146,15 @@ namespace QUERY_Utils {
if (answer == "") return "";
- String queryAnswer = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
- queryAnswer += ">APLRG1";
- if (queryFromAPRSIS) {
- queryAnswer += ",TCPIP*";
- } else {
- if (!thirdParty) queryAnswer += ",RFONLY";
- if (Config.beacon.path != "") {
- queryAnswer += ",";
- queryAnswer += Config.beacon.path;
- }
- }
- queryAnswer += "::";
+ answer += " *";
+ answer += char(random(97, 123));
+ answer += char(random(97, 123));
+ answer += "*";
- String processedStation = station;
- for (int i = station.length(); i < 9; i++) {
- processedStation += ' ';
- }
- queryAnswer += processedStation;
- queryAnswer += ":";
- queryAnswer += answer;
-
- queryAnswer += " *";
- queryAnswer += char(random(97, 123));
- queryAnswer += char(random(97, 123));
- queryAnswer += "*";
- return queryAnswer;
+ String queryCallsign = (Config.tacticalCallsign == "") ? Config.callsign : Config.tacticalCallsign;
+ String queryPath = queryFromAPRSIS ? "" : (thirdParty ? Config.beacon.path : "RFONLY," + Config.beacon.path);
+ String queryAnswer = APRSPacketLib::generateMessagePacket(queryCallsign, "APLRG1", queryPath, station, answer);
+ return queryFromAPRSIS ? APRSPacketLib::generateAPRSISPacket(queryAnswer) : queryAnswer;
}
}
\ No newline at end of file
diff --git a/src/station_utils.cpp b/src/station_utils.cpp
index 15deebf..9090ce0 100644
--- a/src/station_utils.cpp
+++ b/src/station_utils.cpp
@@ -234,6 +234,7 @@ namespace STATION_Utils {
}
void addToOutputPacketBuffer(const String& packet, bool flag) {
+ if (packet == "") return; // never queue an empty packet
outputPacketBuffer.emplace_back(OutputPacketBuffer{packet, flag});
}
diff --git a/src/utils.cpp b/src/utils.cpp
index 4c74142..e729d56 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -83,7 +83,8 @@ namespace Utils {
return;
}
- String statusPacket = APRSPacketLib::generateStatusPacket(Config.callsign, "APLRG1", Config.beacon.path, Config.beacon.statusPacket);
+ String statusCallsign = (Config.tacticalCallsign == "") ? Config.callsign : Config.tacticalCallsign;
+ String statusPacket = APRSPacketLib::generateStatusPacket(statusCallsign, "APLRG1", Config.beacon.path, Config.beacon.statusPacket);
if (sendOverAPRSIS) {
String aprsisStatusPacket = APRSPacketLib::generateAPRSISPacket(statusPacket);
@@ -322,6 +323,12 @@ namespace Utils {
}
}
+ String padForDisplay(const String& text) { // left-aligned, 9 chars wide (callsign column on screen)
+ char buffer[10];
+ snprintf(buffer, sizeof(buffer), "%-9s", text.c_str());
+ return String(buffer);
+ }
+
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType) {
switch (packetType) {
case 0: // LoRa-APRS
@@ -332,11 +339,7 @@ namespace Utils {
break;
}
- String sender = aprsPacket.sender;
- for (int i = sender.length(); i < 9; i++) {
- sender += " ";
- }
- sixthLine = sender;
+ sixthLine = padForDisplay(aprsPacket.sender);
switch (aprsPacket.type) {
case 1: // MESSAGE
@@ -382,11 +385,7 @@ namespace Utils {
void updateAPRSISPacketDisplayInfo(const String& packet) {
fifthLine = "APRS-IS ----> LoRa Tx";
- String sender = packet.substring(0,packet.indexOf(">"));
- for (int i = sender.length(); i < 9; i++) {
- sender += " ";
- }
- sixthLine = sender;
+ sixthLine = padForDisplay(packet.substring(0,packet.indexOf(">")));
if (packet.indexOf("::") > 0) {
sixthLine += "> MESSAGE";