Compare commits

...
24 changed files with 221 additions and 211 deletions

No files matched your search

+4 -4
View File
@@ -34,10 +34,10 @@ lib_deps =
bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.7
ESP32Async/AsyncTCP @ 3.5.0
ESP32Async/ESPAsyncWebServer @ 3.12.1
mikalhart/TinyGPSPlus @ 1.1.0
richonguzman/APRSPacketLib @ 1.0.10
display_libs =
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
+36 -6
View File
@@ -62,6 +62,12 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
#received-packets .table{--bs-table-color:var(--msh-text);--bs-table-border-color:var(--msh-border);color:var(--msh-text);}
#received-packets th{white-space:nowrap;}
#received-packets td:nth-child(1),#received-packets td:nth-child(2),#received-packets td:nth-child(4),#received-packets td:nth-child(5){white-space:nowrap;}
#received-packets th:nth-child(1){width:calc(10ch + 1rem);min-width:calc(10ch + 1rem);}
#received-packets th:nth-child(2){width:calc(8ch + 1rem);min-width:calc(8ch + 1rem);}
#received-packets th:nth-child(4){width:calc(4ch + 1rem);min-width:calc(4ch + 1rem);}
#received-packets th:nth-child(5){width:calc(6ch + 1rem);min-width:calc(6ch + 1rem);}
#received-packets td:nth-child(1),#received-packets td:nth-child(2),#received-packets td:nth-child(4),#received-packets td:nth-child(5){font-variant-numeric:tabular-nums;}
#received-packets td:nth-child(3){overflow-wrap:anywhere;}
@media(max-width:768px){.app-sidebar{width:60px;padding:12px 6px;}.side-link .label,.nav-group-label{display:none;}.app-main{padding:14px;}}
</style>
</head>
@@ -198,7 +204,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<tr>
<th scope="col">Date</th>
<th scope="col">Time</th>
<th scope="col">Frame</th>
<th scope="col">Packet</th>
<th scope="col">RSSI</th>
<th scope="col">SNR</th>
</tr>
@@ -285,7 +291,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
oninput="this.value = this.value.toUpperCase();"
/>
</div>
<div class="col-12 mt-3">
<div class="col-12 col-md-9 mt-3">
<label
for="beacon.comment"
class="form-label"
@@ -299,6 +305,30 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
placeholder=""
/>
</div>
<div class="col-12 col-md-3 mt-3">
<label
for="beacon.rfCommentEveryXBeacons"
class="form-label"
>RF Comment every</label
>
<div class="input-group">
<input
type="number"
name="beacon.rfCommentEveryXBeacons"
id="beacon.rfCommentEveryXBeacons"
class="form-control"
value="1"
min="1"
max="100"
step="1"
required
/>
<span class="input-group-text"
>beacons
</span>
</div>
<small>(1 = every beacon, RF only)</small>
</div>
<div class="col-12 mt-3">
<label
for="beacon.path"
@@ -1391,7 +1421,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<label
for="lora.power"
class="form-label">
Power
Power <small>(SX127X Max 20dBm / SX126X Max 22dBm)</small>
</label>
<div class="input-group">
<input
@@ -1399,8 +1429,8 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
name="lora.power"
id="lora.power"
placeholder="20"
min="0"
max="20"
min="2"
max="22"
step="1"
class="form-control"
required=""
@@ -2437,7 +2467,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
if(window.fetchReceivedPackets)window.fetchReceivedPackets();
});
var bi=document.getElementById("buildInfo");
if(bi){var h=bi.innerHTML; if(/Version date/i.test(h)){h=h.replace(/(<br\s*\/?>)[\s\S]*?(?=Version date)/i,"$1");bi.innerHTML="Board / Environment = "+h;}}
if(bi){var h=bi.innerHTML; if(/Version date/i.test(h)){h=h.replace(/(<br\s*\/?>)[\s\S]*?(?=Version: )/i,"$1");bi.innerHTML="Board / Environment = "+h;}}
if(window.loadReceivedPackets){var _o=window.loadReceivedPackets;window.loadReceivedPackets=function(p){if(p&&p.slice){p=p.slice().reverse();}return _o(p);};}
})();
</script>
+21 -13
View File
@@ -56,6 +56,7 @@ function loadSettings(settings) {
document.getElementById("callsign").value = settings.callsign;
document.getElementById("tacticalCallsign").value = settings.tacticalCallsign;
document.getElementById("beacon.comment").value = settings.beacon.comment;
document.getElementById("beacon.rfCommentEveryXBeacons").value = settings.beacon.rfCommentEveryXBeacons;
document.getElementById("beacon.path").value = settings.beacon.path;
document.getElementById("beacon.symbol").value = settings.beacon.symbol;
document.getElementById("beacon.overlay").value = settings.beacon.overlay;
@@ -606,13 +607,11 @@ function loadReceivedPackets(packets) {
packets.forEach((packet) => {
const element = document.createElement("tr");
element.innerHTML = `
<td>${packet.rxDate}</td>
<td>${packet.rxTime}</td>
<td>${packet.packet}</td>
<td>${packet.RSSI}</td>
<td>${packet.SNR}</td>
`;
['rxDate', 'rxTime', 'packet', 'RSSI', 'SNR'].forEach((field) => {
const cell = document.createElement("td");
cell.textContent = String(packet[field]);
element.appendChild(cell);
});
container.appendChild(element);
})
@@ -655,6 +654,15 @@ let mapTileErrShown = false;
let iGateLatLng = null;
let mapAutoFitDone = false;
function escapeHtml(value) { // shows received data as text inside HTML strings (map popups / icons)
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function setMapMessage(text) {
const el = document.getElementById("map");
if (el) {
@@ -833,7 +841,7 @@ function getAprsSymbolIcon(symbol, callsign = "") {
-1px 1px 0 #fff,
1px 1px 0 #fff;
pointer-events:none;
">${callsign}</span>
">${escapeHtml(callsign)}</span>
</div>
`,
iconSize: [24, 24],
@@ -935,12 +943,12 @@ function loadMapStations(stations) {
(stations || []).forEach((s) => {
if (s.lat === 0 && s.lon === 0) return; // descartar estaciones sin fix
const popup = `<b>${s.callsign}</b>`
+ (s.lastHeard ? `<br>Last: ${s.lastHeard}` : "")
const popup = `<b>${escapeHtml(s.callsign)}</b>`
+ (s.lastHeard ? `<br>Last: ${escapeHtml(s.lastHeard)}` : "")
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
+ `<br>Packets: ${s.count}`
+ `<br><b>${describeMapPath(s)}</b>`
+ (s.path ? `<br><small>Path: ${s.path}</small>` : "");
+ `<br><b>${escapeHtml(describeMapPath(s))}</b>`
+ (s.path ? `<br><small>Path: ${escapeHtml(s.path)}</small>` : "");
const aprsIcon =
getAprsSymbolIcon(s.symbol, s.callsign);
@@ -1172,4 +1180,4 @@ form.addEventListener("invalid", function (event) {
el.scrollIntoView({ block: "center", behavior: "smooth" });
}, 0);
}
}, true);
}, true);
+1
View File
@@ -42,6 +42,7 @@ public:
double latitude;
double longitude;
String comment;
int rfCommentEveryXBeacons;
int interval;
String overlay;
String symbol;
-1
View File
@@ -24,7 +24,6 @@
namespace GPS_Utils {
String getiGateLoRaBeaconPacket();
void generateBeacons();
String buildDistanceAndComment(float latitude, float longitude, const String& comment);
+1
View File
@@ -24,6 +24,7 @@
namespace LoRa_Utils {
int validPower(int requested);
void setup();
void sendNewPacket(const String& newPacket);
String receivePacketFromSleep();
+2 -1
View File
@@ -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);
}
+1
View File
@@ -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);
+1
View File
@@ -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) {
+2 -2
View File
@@ -68,8 +68,8 @@ ___________________________________________________________________*/
#endif
String versionDate = "2026-10-01";
String versionNumber = "4.0.2";
String versionDate = "2026-10-05";
String versionNumber = "4.0.3";
Configuration Config;
WiFiClient aprsIsClient;
WiFiClient mqttClient;
+23 -53
View File
@@ -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,qAC::";
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));
+4
View File
@@ -65,6 +65,7 @@ bool Configuration::writeFile() {
data["aprs_is"]["objectsToRF"] = aprs_is.objectsToRF;
data["beacon"]["comment"] = beacon.comment;
data["beacon"]["rfCommentEveryXBeacons"] = beacon.rfCommentEveryXBeacons;
data["beacon"]["interval"] = beacon.interval;
data["beacon"]["latitude"] = beacon.latitude;
data["beacon"]["longitude"] = beacon.longitude;
@@ -249,6 +250,7 @@ bool Configuration::readFile() {
if (data["beacon"]["latitude"].isNull() ||
data["beacon"]["longitude"].isNull() ||
data["beacon"]["comment"].isNull() ||
data["beacon"]["rfCommentEveryXBeacons"].isNull() ||
data["beacon"]["interval"].isNull() ||
data["beacon"]["overlay"].isNull() ||
data["beacon"]["symbol"].isNull() ||
@@ -263,6 +265,7 @@ bool Configuration::readFile() {
beacon.latitude = data["beacon"]["latitude"] | 0.0;
beacon.longitude = data["beacon"]["longitude"] | 0.0;
beacon.comment = data["beacon"]["comment"] | "LoRa APRS";
beacon.rfCommentEveryXBeacons = data["beacon"]["rfCommentEveryXBeacons"] | 1;
beacon.interval = data["beacon"]["interval"] | 15;
beacon.overlay = data["beacon"]["overlay"] | "L";
beacon.symbol = data["beacon"]["symbol"] | "a";
@@ -472,6 +475,7 @@ void Configuration::setDefaultValues() {
aprs_is.objectsToRF = false;
beacon.comment = "LoRa APRS";
beacon.rfCommentEveryXBeacons = 1;
beacon.latitude = 0.0;
beacon.longitude = 0.0;
beacon.interval = 15;
-1
View File
@@ -31,7 +31,6 @@
extern Configuration Config;
extern uint32_t lastScreenOn;
extern APRSPacket lastAprsPacket;
extern String iGateBeaconPacket;
extern String firstLine;
extern String secondLine;
extern String thirdLine;
+6 -16
View File
@@ -40,7 +40,7 @@ extern Configuration Config;
extern HardwareSerial gpsSerial;
extern TinyGPSPlus gps;
extern bool stationCallsignIsValid;
String distance, iGateBeaconPacket, iGateLoRaBeaconPacket;
String distance, iGateAPRSISBeaconPacket, iGateLoRaBeaconPacket;
namespace GPS_Utils {
@@ -115,13 +115,8 @@ namespace GPS_Utils {
}
#endif
String getiGateLoRaBeaconPacket() {
return iGateLoRaBeaconPacket;
}
void generateBeacons() {
String beaconPacket = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
String encodedGPS = APRSPacketLib::encodeGPSIntoBase91(Config.beacon.latitude, Config.beacon.longitude, 0, 0, Config.beacon.symbol, false, 0, true, Config.beacon.ambiguityLevel);
String beaconCallsign = Config.callsign;
if (Config.callsign.indexOf("NOCALL-10") != 0) {
if (!stationCallsignIsValid) {
@@ -133,7 +128,7 @@ namespace GPS_Utils {
Config.digi.mode = 0;
Config.digi.backupDigiMode = false;
} else if (stationCallsignIsValid && Config.tacticalCallsign != "") {
beaconPacket = APRSPacketLib::generateBasePacket(Config.tacticalCallsign, "APLRG1", Config.beacon.path);
beaconCallsign = Config.tacticalCallsign;
Config.aprs_is.active = false;
Config.beacon.sendViaAPRSIS = false;
Config.digi.backupDigiMode = false;
@@ -143,15 +138,10 @@ namespace GPS_Utils {
Config.beacon.sendViaRF = false;
}
iGateBeaconPacket = beaconPacket;
iGateBeaconPacket += ",qAC:=";
iGateBeaconPacket += Config.beacon.overlay;
iGateBeaconPacket += encodedGPS;
String encodedGPS = APRSPacketLib::encodeGPSIntoBase91(Config.beacon.latitude, Config.beacon.longitude, 0, 0, Config.beacon.symbol, false, 0, true, Config.beacon.ambiguityLevel);
iGateLoRaBeaconPacket = beaconPacket;
iGateLoRaBeaconPacket += ":=";
iGateLoRaBeaconPacket += Config.beacon.overlay;
iGateLoRaBeaconPacket += encodedGPS;
iGateLoRaBeaconPacket = APRSPacketLib::generateBase91GPSBeaconPacket(beaconCallsign, "APLRG1", Config.beacon.path, Config.beacon.overlay, encodedGPS);
iGateAPRSISBeaconPacket = APRSPacketLib::generateAPRSISPacket(iGateLoRaBeaconPacket);
}
double calculateDistanceTo(double latitude, double longitude) {
+32 -15
View File
@@ -69,6 +69,18 @@ int rssi, freqError;
float snr;
APRSPacket lastAprsPacket;
#if defined(HAS_SX1278) || defined(HAS_SX1276)
#define CHIP_MIN_POWER 2 // PA_BOOST (below 2 RadioLib switches to RFO, not wired on most modules)
#define CHIP_MAX_POWER 20
#else // SX1262 / SX1268 / LLCC68
#define CHIP_MIN_POWER -9
#define CHIP_MAX_POWER 22
#endif
#ifndef RADIO_MAX_POWER // optional per board in board_pinout.h (e.g. 1W PA modules)
#define RADIO_MAX_POWER CHIP_MAX_POWER
#endif
namespace LoRa_Utils {
@@ -85,6 +97,15 @@ namespace LoRa_Utils {
operationDone = true;
}
int validPower(int requested) {
const int maxPower = (RADIO_MAX_POWER < CHIP_MAX_POWER) ? RADIO_MAX_POWER : CHIP_MAX_POWER;
int power = constrain(requested, CHIP_MIN_POWER, maxPower);
#if defined(HAS_SX1278) || defined(HAS_SX1276)
if (power > 17 && power < 20) power = 17; // SX127x PA_BOOST: only 2-17 or 20
#endif
return power;
}
void setup() {
#if defined (LIGHTGATEWAY_1_0) || defined(LIGHTGATEWAY_PLUS_1_0)
pinMode(RADIO_VCC_PIN,OUTPUT);
@@ -136,16 +157,17 @@ namespace LoRa_Utils {
radio.setDio2AsRfSwitch(true);
#endif*/
#ifdef HAS_1W_LORA // Ebyte E22 400M30S (SX1268) / 900M30S (SX1262) / Ebyte E220 400M30S (LLCC68)
state = radio.setOutputPower(Config.loramodule.power); // max value 20dB for 1W modules as they have Low Noise Amp
radio.setCurrentLimit(140); // to be validated (100 , 120, 140)?
#endif
int power = validPower(Config.loramodule.power);
if (power != Config.loramodule.power) {
Utils::println("LoRa power adjusted: " + String(Config.loramodule.power) + " -> " + String(power));
}
state = radio.setOutputPower(power);
if (state != RADIOLIB_ERR_NONE) {
Utils::println("LoRa setOutputPower failed! State: " + String(state)); // log and keep going
}
#if defined(HAS_SX1278) || defined(HAS_SX1276)
state = radio.setOutputPower(Config.loramodule.power); // max value 20dB for 400M30S as it has Low Noise Amp
radio.setCurrentLimit(100); // to be validated (80 , 100)?
#endif
#if (defined(HAS_SX1268) || defined(HAS_SX1262)) && !defined(HAS_1W_LORA)
state = radio.setOutputPower(Config.loramodule.power + 2); // values available: 10, 17, 22 --> if 20 in tracker_conf.json it will be updated to 22.
radio.setCurrentLimit(120); // OCP ceiling for SX127x: ~120mA needed at +20dBm (not a fixed consumption)
#else // SX1262 / SX1268 / LLCC68 (also 1W Ebyte E22 / E220 modules)
radio.setCurrentLimit(140);
#endif
@@ -160,12 +182,7 @@ namespace LoRa_Utils {
radio.setTCXO(1.8);
#endif
if (state == RADIOLIB_ERR_NONE) {
Utils::println("init : LoRa Module ... done!");
} else {
Utils::println("Starting LoRa failed! State: " + String(state));
while (true);
}
Utils::println("init : LoRa Module ... done!");
}
void changeFreqTx() {
+16 -26
View File
@@ -16,6 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <APRSPacketLib.h>
#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,qAC";
} 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;
}
}
+1
View File
@@ -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});
}
+2 -2
View File
@@ -71,8 +71,9 @@ namespace TELEMETRY_Utils {
void sendBaseTelemetryPacket(const String& prefix, const std::vector<String>& values) {
String packet = prefix + joinWithCommas(values);
String currentCallsign = (Config.tacticalCallsign != "") ? Config.tacticalCallsign : Config.callsign;
String baseRFTelemetryPacket = APRSPacketLib::generateMessagePacket(currentCallsign, "APLRG1", Config.beacon.path, currentCallsign, packet);
if (Config.beacon.sendViaAPRSIS) {
String baseAPRSISTelemetryPacket = APRSPacketLib::generateMessagePacket(currentCallsign, "APLRG1", "TCPIP,qAC", currentCallsign, packet);
String baseAPRSISTelemetryPacket = APRSPacketLib::generateAPRSISPacket(baseRFTelemetryPacket);
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(baseAPRSISTelemetryPacket);
#else
@@ -80,7 +81,6 @@ namespace TELEMETRY_Utils {
#endif
delay(300);
} else if (Config.beacon.sendViaRF) {
String baseRFTelemetryPacket = APRSPacketLib::generateMessagePacket(currentCallsign, "APLRG1", Config.beacon.path, currentCallsign, packet);
LoRa_Utils::sendNewPacket(baseRFTelemetryPacket);
delay(3000);
}
+49 -57
View File
@@ -48,7 +48,7 @@ extern String fourthLine;
extern String fifthLine;
extern String sixthLine;
extern String seventhLine;
extern String iGateBeaconPacket;
extern String iGateAPRSISBeaconPacket;
extern String iGateLoRaBeaconPacket;
extern int rssi;
extern float snr;
@@ -69,8 +69,7 @@ uint32_t lastBeaconTx = 0;
uint32_t lastScreenOn = millis();
uint32_t lastStatusTx = 0;
bool stationCallsignIsValid = false;
String beaconPacket;
String secondaryBeaconPacket;
uint32_t rfBeaconCounter = 0;
namespace Utils {
@@ -84,13 +83,13 @@ namespace Utils {
return;
}
String statusPacket = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
statusPacket += sendOverAPRSIS ? ",qAC:>" : ":>";
statusPacket += 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) {
APRS_IS_Utils::upload(statusPacket);
SYSLOG_Utils::logAPRSISTx(statusPacket);
String aprsisStatusPacket = APRSPacketLib::generateAPRSISPacket(statusPacket);
APRS_IS_Utils::upload(aprsisStatusPacket);
SYSLOG_Utils::logAPRSISTx(aprsisStatusPacket);
} else {
STATION_Utils::addToOutputPacketBuffer(statusPacket, true); // treated also as beacon on Tx Freq
}
@@ -187,48 +186,42 @@ namespace Utils {
showActiveStations();
beaconPacket = iGateBeaconPacket;
secondaryBeaconPacket = iGateLoRaBeaconPacket;
String aprsisBeaconPacket = iGateAPRSISBeaconPacket;
String loraBeaconPacket = iGateLoRaBeaconPacket;
#ifdef HAS_GPS
if (Config.beacon.gpsActive && Config.digi.ecoMode == 0) {
GPS_Utils::getData();
if (gps.location.isUpdated() && gps.location.lat() != 0.0 && gps.location.lng() != 0.0) {
String basePacket = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
String encodedGPS = APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), 0, 0, Config.beacon.symbol, false, 0, true, Config.beacon.ambiguityLevel);
String beaconCallsign = (stationCallsignIsValid && Config.tacticalCallsign != "") ? Config.tacticalCallsign : Config.callsign;
String encodedGPS = APRSPacketLib::encodeGPSIntoBase91(gps.location.lat(),gps.location.lng(), 0, 0, Config.beacon.symbol, false, 0, true, Config.beacon.ambiguityLevel);
beaconPacket = basePacket;
beaconPacket += ",qAC:!";
beaconPacket += Config.beacon.overlay;
beaconPacket += encodedGPS;
secondaryBeaconPacket = basePacket;
secondaryBeaconPacket += ":=";
secondaryBeaconPacket += Config.beacon.overlay;
secondaryBeaconPacket += encodedGPS;
loraBeaconPacket = APRSPacketLib::generateBase91GPSBeaconPacket(beaconCallsign, "APLRG1", Config.beacon.path, Config.beacon.overlay, encodedGPS);
aprsisBeaconPacket = APRSPacketLib::generateAPRSISPacket(loraBeaconPacket);
}
}
#endif
if (Config.wxsensor.active) {
String sensorData = (wxModuleType == 0) ? ".../...g...t..." : WX_Utils::readDataSensor();
beaconPacket += sensorData;
secondaryBeaconPacket += sensorData;
aprsisBeaconPacket += sensorData;
loraBeaconPacket += sensorData;
}
beaconPacket += Config.beacon.comment;
secondaryBeaconPacket += Config.beacon.comment;
aprsisBeaconPacket += Config.beacon.comment; // APRS-IS beacon always carries the comment
int commentEveryX = max(1, Config.beacon.rfCommentEveryXBeacons); // safety: 0 or negative behaves as "every beacon"
if (rfBeaconCounter % commentEveryX == 0) loraBeaconPacket += Config.beacon.comment;
if (stationCallsignIsValid && Config.tacticalCallsign != "") {
beaconPacket += " de ";
beaconPacket += Config.callsign;
secondaryBeaconPacket += " de ";
secondaryBeaconPacket += Config.callsign;
aprsisBeaconPacket += " de ";
aprsisBeaconPacket += Config.callsign;
loraBeaconPacket += " de ";
loraBeaconPacket += Config.callsign;
}
#if defined(BATTERY_PIN) || defined(HAS_AXP192) || defined(HAS_AXP2101)
if (Config.battery.sendInternalVoltage || Config.battery.monitorInternalVoltage) {
float internalVoltage = BATTERY_Utils::checkInternalVoltage();
if (Config.battery.monitorInternalVoltage && internalVoltage < Config.battery.internalSleepVoltage) {
beaconPacket += " **IntBatWarning:SLEEP**";
secondaryBeaconPacket += " **IntBatWarning:SLEEP**";
aprsisBeaconPacket += " **IntBatWarning:SLEEP**";
loraBeaconPacket += " **IntBatWarning:SLEEP**";
shouldSleepLowVoltage = true;
}
@@ -241,10 +234,10 @@ namespace Utils {
sixthLine = sixthLineBuffer;
if (!Config.battery.sendVoltageAsTelemetry) {
beaconPacket += " Batt=";
beaconPacket += internalVoltageInfo;
secondaryBeaconPacket += " Batt=";
secondaryBeaconPacket += internalVoltageInfo;
aprsisBeaconPacket += " Batt=";
aprsisBeaconPacket += internalVoltageInfo;
loraBeaconPacket += " Batt=";
loraBeaconPacket += internalVoltageInfo;
}
}
}
@@ -254,8 +247,8 @@ namespace Utils {
if (Config.battery.sendExternalVoltage || Config.battery.monitorExternalVoltage) {
float externalVoltage = BATTERY_Utils::checkExternalVoltage();
if (Config.battery.monitorExternalVoltage && externalVoltage < Config.battery.externalSleepVoltage) {
beaconPacket += " **ExtBatWarning:SLEEP**";
secondaryBeaconPacket += " **ExtBatWarning:SLEEP**";
aprsisBeaconPacket += " **ExtBatWarning:SLEEP**";
loraBeaconPacket += " **ExtBatWarning:SLEEP**";
shouldSleepLowVoltage = true;
}
@@ -268,10 +261,10 @@ namespace Utils {
sixthLine = sixthLineBuffer;
if (!Config.battery.sendVoltageAsTelemetry) {
beaconPacket += " Ext=";
beaconPacket += externalVoltageInfo;
secondaryBeaconPacket += " Ext=";
secondaryBeaconPacket += externalVoltageInfo;
aprsisBeaconPacket += " Ext=";
aprsisBeaconPacket += externalVoltageInfo;
loraBeaconPacket += " Ext=";
loraBeaconPacket += externalVoltageInfo;
}
}
}
@@ -279,8 +272,8 @@ namespace Utils {
if (Config.battery.sendVoltageAsTelemetry && !Config.wxsensor.active && (Config.battery.sendInternalVoltage || Config.battery.sendExternalVoltage)){
String encodedTelemetry = TELEMETRY_Utils::generateEncodedTelemetry();
beaconPacket += encodedTelemetry;
secondaryBeaconPacket += encodedTelemetry;
aprsisBeaconPacket += encodedTelemetry;
loraBeaconPacket += encodedTelemetry;
}
if (Config.beacon.sendViaAPRSIS && Config.aprs_is.active && passcodeValid && !backupDigiMode) {
@@ -288,18 +281,19 @@ namespace Utils {
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, "SENDING IGATE BEACON", 0);
seventhLine = " listening...";
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(beaconPacket);
A7670_Utils::uploadToAPRSIS(aprsisBeaconPacket);
#else
APRS_IS_Utils::upload(beaconPacket);
APRS_IS_Utils::upload(aprsisBeaconPacket);
#endif
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::logAPRSISTx(beaconPacket);
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::logAPRSISTx(aprsisBeaconPacket);
}
if (Config.beacon.sendViaRF || backupDigiMode) {
Utils::println("-- Sending Beacon to RF --");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, "SENDING DIGI BEACON", 0);
seventhLine = " listening...";
STATION_Utils::addToOutputPacketBuffer(secondaryBeaconPacket, true);
STATION_Utils::addToOutputPacketBuffer(loraBeaconPacket, true);
rfBeaconCounter++;
}
lastBeaconTx = millis();
@@ -329,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
@@ -339,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
@@ -389,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";
+3 -1
View File
@@ -19,6 +19,7 @@
#include <ArduinoJson.h>
#include "network_manager.h"
#include "configuration.h"
#include "lora_utils.h"
#include "ota_utils.h"
#include "web_utils.h"
#include "map_utils.h"
@@ -246,6 +247,7 @@ namespace WEB_Utils {
Config.beacon.latitude = getParamDoubleSafe("beacon.latitude", Config.beacon.latitude);
Config.beacon.longitude = getParamDoubleSafe("beacon.longitude", Config.beacon.longitude);
Config.beacon.comment = getParamStringSafe("beacon.comment", Config.beacon.comment);
Config.beacon.rfCommentEveryXBeacons = getParamIntSafe("beacon.rfCommentEveryXBeacons", Config.beacon.rfCommentEveryXBeacons);
Config.beacon.overlay = getParamStringSafe("beacon.overlay", Config.beacon.overlay);
Config.beacon.symbol = getParamStringSafe("beacon.symbol", Config.beacon.symbol);
Config.beacon.path = getParamStringSafe("beacon.path", Config.beacon.path);
@@ -276,7 +278,7 @@ namespace WEB_Utils {
Config.loramodule.txSpreadingFactor = getParamIntSafe("lora.txSpreadingFactor", Config.loramodule.txSpreadingFactor);
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
Config.loramodule.power = LoRa_Utils::validPower(getParamIntSafe("lora.power", Config.loramodule.power)); // saved as the power the radio chip really uses
if (Config.loramodule.txActive)
Config.loramodule.cadActive = request->hasParam("lora.cadActive", true);
+13 -10
View File
@@ -35,16 +35,19 @@ files = [
]
string_to_find_str = "String"
string_to_find_ver = "versionDate"
with open('src/LoRa_APRS_iGate.cpp', encoding='utf-8') as cpp_file:
for line in cpp_file:
if string_to_find_str in line and string_to_find_ver in line:
start = line.find('"') + 1
end = line.find('"', start)
if start > 0 and end > start:
versionDate = line[start:end]
break
def find_string_value(variable_name):
with open('src/LoRa_APRS_iGate.cpp', encoding='utf-8') as cpp_file:
for line in cpp_file:
if string_to_find_str in line and variable_name in line:
start = line.find('"') + 1
end = line.find('"', start)
if start > 0 and end > start:
return line[start:end]
return ""
versionDate = find_string_value("versionDate")
versionNumber = find_string_value("versionNumber")
for src in files:
out = src + ".gz"
@@ -54,7 +57,7 @@ for src in files:
content = f.read()
if src == 'data_embed/index.html':
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version: " + versionNumber + "<br>" + "Version date: " + versionDate
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
@@ -9,4 +9,4 @@ build_flags =
lib_deps =
${common.lib_deps}
${common.display_libs}
adafruit/Adafruit SH110X @ 2.1.10
adafruit/Adafruit SH110X @ 2.1.15
@@ -11,4 +11,4 @@ build_flags =
lib_deps =
${common.lib_deps}
${common.display_libs}
adafruit/Adafruit SHTC3 Library @ 1.0.1
adafruit/Adafruit SHTC3 Library @ 1.0.2
@@ -12,4 +12,4 @@ lib_deps =
${common.lib_deps}
${common.display_libs}
lewisxhe/XPowersLib @ 0.2.4
adafruit/Adafruit SH110X @ 2.1.10
adafruit/Adafruit SH110X @ 2.1.15