Compare commits

..

13 Commits

Author SHA1 Message Date
Ricardo Guzman (Richonguzman)
76fe27a0ab APRSSSR into APRSSR fix 2026-01-16 09:41:20 -03:00
Ricardo Guzman (Richonguzman)
da80391921 change Freq with bandWidth fix 2026-01-13 01:50:12 -03:00
Ricardo Guzman (Richonguzman)
d638093dbf Tactical 6 update 2026-01-11 11:46:23 -03:00
Ricardo Guzman (Richonguzman)
66a5f03c12 de update for tactical 2026-01-09 00:02:29 -03:00
Ricardo Guzman (Richonguzman)
ce8cf3a2fe minimal interval updated 2026-01-08 14:08:07 -03:00
Ricardo Guzman (Richonguzman)
4b45b90c88 minor path updates 2026-01-08 12:21:19 -03:00
Ricardo Guzman (Richonguzman)
dbe980a081 tactical callsign ready for testing 2026-01-07 23:18:14 -03:00
Ricardo Guzman (Richonguzman)
bb3d59a20d digi tactical changes 2026-01-07 17:25:45 -03:00
Ricardo Guzman (Richonguzman)
fe590b41b0 tactical callsign displayShow 2026-01-07 14:30:32 -03:00
Ricardo Guzman (Richonguzman)
3f76005949 few delays killed 2026-01-07 12:19:51 -03:00
Ricardo Guzman (Richonguzman)
6d95231b9c web mods for tactical 2026-01-07 11:53:10 -03:00
Ricardo Guzman (Richonguzman)
81692010cf tactical validation 2026-01-07 11:44:00 -03:00
Ricardo Guzman (Richonguzman)
50b738d04b Spreading Factor update 2026-01-07 10:37:11 -03:00
14 changed files with 127 additions and 55 deletions

View File

@@ -51,6 +51,8 @@ ____________________________________________________
<br />
# Timeline (Versions):
- 2026-01-07 Tactical Callsign added.
- 2026-01-05 Heltec V4 support added.
- 2025-12-22 Heltec Wireless Paper V1.2 and VisionMaster E290 Added. Thanks HA5SZI.
- 2025-12-18 TCXO and packet decoding updates.
- 2025-12-01 APRSPacketLib updates, AHT20 sensor added, INA219 support added.

View File

@@ -119,7 +119,7 @@
</div>
<div class="col-lg-9 col-sm-12">
<div class="row">
<div class="col-12">
<div class="col-6">
<label for="callsign" class="form-label"
>Callsign - SSID</label
>
@@ -133,6 +133,19 @@
oninput="this.value = this.value.toUpperCase();"
/>
</div>
<div class="col-6">
<label for="tacticalCallsign" class="form-label"
>Tactical Callsign</label
>
<input
type="text"
name="tacticalCallsign"
id="tacticalCallsign"
class="form-control"
placeholder=""
oninput="this.value = this.value.toUpperCase();"
/>
</div>
<div class="col-12 mt-3">
<label
for="beacon.comment"
@@ -158,7 +171,7 @@
name="beacon.path"
id="beacon.path"
class="form-control"
placeholder="We prefer WIDE1-1"
placeholder="WIDE1-1"
/>
</div>
<div class="col-8 mt-3">
@@ -591,11 +604,11 @@
type="number"
name="beacon.interval"
id="beacon.interval"
placeholder="15"
class="form-control"
required=""
value="15"
min="10"
step="1"
min="15"
required
/>
<span class="input-group-text"
>minutes
@@ -853,7 +866,9 @@
id="lora.rxSpreadingFactor"
required=""
>
<option value="7">SF7 - Lowest battery usage</option>
<option value="5">SF5 - Lowest battery usage</option>
<option value="6">SF6</option>
<option value="7">SF7</option>
<option value="8">SF8</option>
<option value="9">SF9</option>
<option value="10">SF10</option>
@@ -873,7 +888,9 @@
id="lora.txSpreadingFactor"
required=""
>
<option value="7">SF7 - Lowest battery usage</option>
<option value="5">SF5 - Lowest battery usage</option>
<option value="6">SF6</option>
<option value="7">SF7</option>
<option value="8">SF8</option>
<option value="9">SF9</option>
<option value="10">SF10</option>

View File

@@ -54,6 +54,7 @@ function loadSettings(settings) {
currentSettings = settings;
// General
document.getElementById("callsign").value = settings.callsign;
document.getElementById("tacticalCallsign").value = settings.tacticalCallsign;
document.getElementById("beacon.comment").value = settings.beacon.comment;
document.getElementById("beacon.path").value = settings.beacon.path;
document.getElementById("beacon.symbol").value = settings.beacon.symbol;

View File

@@ -170,6 +170,7 @@ public:
class Configuration {
public:
String callsign;
String tacticalCallsign;
int rememberStationTime;
bool backupDigiMode;
bool rebootMode;

View File

@@ -67,8 +67,8 @@ ___________________________________________________________________*/
#endif
String versionDate = "2025-12-29";
String versionNumber = "3.1.7";
String versionDate = "2026-01-11";
String versionNumber = "3.1.7.1";
Configuration Config;
WiFiClient aprsIsClient;
WiFiClient mqttClient;

View File

@@ -274,6 +274,29 @@ namespace APRS_IS_Utils {
return outputPacket;
}
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;
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(ackPacket);
#else
upload(ackPacket);
#endif
}
void processAPRSISPacket(const String& packet) {
if (!passcodeValid && packet.indexOf(Config.callsign) != -1) {
if (packet.indexOf("unverified") != -1 ) {
@@ -294,38 +317,19 @@ namespace APRS_IS_Utils {
if (Addressee == Config.callsign) { // its for me!
String receivedMessage;
if (AddresseeAndMessage.indexOf("{") > 0) { // ack?
String ackMessage = "ack";
ackMessage += AddresseeAndMessage.substring(AddresseeAndMessage.indexOf("{") + 1);
ackMessage.trim();
delay(4000);
for (int i = Sender.length(); i < 9; i++) {
Sender += ' ';
}
String ackPacket = Config.callsign;
ackPacket += ">APLRG1,TCPIP,qAC::";
ackPacket += Sender;
ackPacket += ":";
ackPacket += ackMessage;
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(ackPacket);
#else
upload(ackPacket);
#endif
processAckMessage(Sender, AddresseeAndMessage);
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":") + 1, AddresseeAndMessage.indexOf("{"));
} else {
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":") + 1);
}
if (receivedMessage.indexOf("?") == 0) {
Utils::println("Rx Query (APRS-IS) : " + packet);
Sender.trim();
String queryAnswer = QUERY_Utils::process(receivedMessage, Sender, true, false);
//Serial.println("---> QUERY Answer : " + queryAnswer.substring(0,queryAnswer.indexOf("\n")));
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
lastScreenOn = millis();
delay(500);
#ifdef HAS_A7670
A7670_Utils::uploadToAPRSIS(queryAnswer);
#else

View File

@@ -51,8 +51,9 @@ bool Configuration::writeFile() {
data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout;
callsign.trim();
callsign.toUpperCase();
data["callsign"] = callsign;
tacticalCallsign.trim();
data["tacticalCallsign"] = tacticalCallsign;
data["aprs_is"]["active"] = aprs_is.active;
data["aprs_is"]["passcode"] = aprs_is.passcode;
@@ -92,15 +93,31 @@ bool Configuration::writeFile() {
data["lora"]["rxActive"] = loramodule.rxActive;
data["lora"]["rxFreq"] = loramodule.rxFreq;
data["lora"]["rxSpreadingFactor"] = loramodule.rxSpreadingFactor;
data["lora"]["rxCodingRate4"] = loramodule.rxCodingRate4;
data["lora"]["rxSignalBandwidth"] = loramodule.rxSignalBandwidth;
data["lora"]["txActive"] = loramodule.txActive;
data["lora"]["txFreq"] = loramodule.txFreq;
data["lora"]["txSpreadingFactor"] = loramodule.txSpreadingFactor;
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
data["lora"]["power"] = loramodule.power;
data["lora"]["power"] = loramodule.power;
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
int txSpreadingFactor = loramodule.txSpreadingFactor;
#if defined(HAS_SX1276) || defined(HAS_SX1278)
const int minSF = 6, maxSF = 12;
#endif
#if defined(HAS_SX1262) || defined(HAS_SX1268)
const int minSF = 5, maxSF = 12;
#endif
#if defined(HAS_LLCC68)
const int minSF = 5, maxSF = 11;
#endif
rxSpreadingFactor = (rxSpreadingFactor < minSF) ? minSF : (rxSpreadingFactor > maxSF) ? maxSF : rxSpreadingFactor;
txSpreadingFactor = (txSpreadingFactor < minSF) ? minSF : (txSpreadingFactor > maxSF) ? maxSF : txSpreadingFactor;
data["lora"]["rxSpreadingFactor"] = rxSpreadingFactor;
data["lora"]["txSpreadingFactor"] = txSpreadingFactor;
data["display"]["alwaysOn"] = display.alwaysOn;
data["display"]["timeout"] = display.timeout;
@@ -204,6 +221,8 @@ bool Configuration::readFile() {
if (!data.containsKey("callsign")) needsRewrite = true;
callsign = data["callsign"] | "NOCALL-10";
if (!data.containsKey("tacticalCallsign")) needsRewrite = true;
tacticalCallsign = data["tacticalCallsign"] | "";
if (!data["aprs_is"].containsKey("active") ||
!data["aprs_is"].containsKey("passcode") ||
@@ -431,6 +450,7 @@ void Configuration::setDefaultValues() {
wifiAutoAP.timeout = 10;
callsign = "N0CALL-10";
tacticalCallsign = "";
aprs_is.active = false;
aprs_is.passcode = "XYZVW";

View File

@@ -49,7 +49,7 @@ namespace DIGI_Utils {
String tempPath = path;
if (path.indexOf("WIDE1-1") != -1 && (Config.digi.mode == 2 || Config.digi.mode == 3)) {
tempPath.replace("WIDE1-1", Config.callsign + "*");
tempPath.replace("WIDE1-1", (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign) + "*");
} else if (path.indexOf("WIDE2-") != -1 && Config.digi.mode == 3) {
if (path.indexOf(",WIDE1*") != -1) {
tempPath.remove(path.indexOf(",WIDE1*"), 7);
@@ -58,9 +58,9 @@ namespace DIGI_Utils {
tempPath.remove(path.indexOf("*"), 1);
}
if (path.indexOf("WIDE2-1") != -1) {
tempPath.replace("WIDE2-1", Config.callsign + "*");
tempPath.replace("WIDE2-1", (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign) + "*");
} else if (path.indexOf("WIDE2-2") != -1) {
tempPath.replace("WIDE2-2", Config.callsign + "*,WIDE2-1");
tempPath.replace("WIDE2-2", (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign) + "*,WIDE2-1");
} else {
return "";
}
@@ -84,7 +84,7 @@ namespace DIGI_Utils {
}
}
packetToRepeat += ",";
packetToRepeat += Config.callsign;
packetToRepeat += (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
packetToRepeat += "*";
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(packet.indexOf(suffix)));
return packetToRepeat;
@@ -147,8 +147,8 @@ namespace DIGI_Utils {
temp = packet.substring(3);
Sender = packet.substring(3, packet.indexOf(">"));
}
if (Sender != Config.callsign) { // Avoid listening to own packets
if (!thirdPartyPacket && !Utils::checkValidCallsign(Sender)) {
if (Sender != (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign)) { // Avoid listening to own packets
if (!thirdPartyPacket && Config.tacticalCallsign == "" && !Utils::checkValidCallsign(Sender)) {
return;
}
if (STATION_Utils::check25SegBuffer(Sender, temp.substring(temp.indexOf(":") + 2))) {
@@ -159,7 +159,7 @@ namespace DIGI_Utils {
String AddresseeAndMessage = temp.substring(temp.indexOf("::") + 2);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
Addressee.trim();
if (Addressee == Config.callsign) { // it's a message for me!
if (Addressee == (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign)) { // it's a message for me!
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
}
}

View File

@@ -34,6 +34,7 @@
extern Configuration Config;
extern HardwareSerial gpsSerial;
extern TinyGPSPlus gps;
extern bool callsignIsValid;
String distance, iGateBeaconPacket, iGateLoRaBeaconPacket;
@@ -44,18 +45,29 @@ namespace GPS_Utils {
}
void generateBeacons() {
if (Config.callsign.indexOf("NOCALL-10") != 0 && !Utils::checkValidCallsign(Config.callsign)) {
displayShow("***** ERROR ******", "CALLSIGN = NOT VALID!", "", "Only Rx Mode Active", 3000);
Config.loramodule.txActive = false;
Config.aprs_is.messagesToRF = false;
Config.aprs_is.objectsToRF = false;
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);
if (Config.callsign.indexOf("NOCALL-10") != 0) {
if (!callsignIsValid) {
displayShow("***** ERROR ******", "CALLSIGN = NOT VALID!", "", "Only Rx Mode Active", 3000);
Config.loramodule.txActive = false;
Config.aprs_is.messagesToRF = false;
Config.aprs_is.objectsToRF = false;
Config.beacon.sendViaRF = false;
Config.digi.mode = 0;
Config.backupDigiMode = false;
} else if (callsignIsValid && Config.tacticalCallsign != "") {
beaconPacket = APRSPacketLib::generateBasePacket(Config.tacticalCallsign, "APLRG1", Config.beacon.path);
Config.aprs_is.active = false;
Config.beacon.sendViaAPRSIS = false;
Config.backupDigiMode = false;
}
} else {
Config.beacon.sendViaAPRSIS = false;
Config.beacon.sendViaRF = false;
Config.digi.mode = 0;
Config.backupDigiMode = false;
}
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);
iGateBeaconPacket = beaconPacket;
iGateBeaconPacket += ",qAC:=";
iGateBeaconPacket += Config.beacon.overlay;

View File

@@ -105,7 +105,7 @@ namespace LoRa_Utils {
radio.setSpreadingFactor(Config.loramodule.rxSpreadingFactor);
radio.setCodingRate(Config.loramodule.rxCodingRate4);
float signalBandwidth = Config.loramodule.rxSignalBandwidth/1000;
float signalBandwidth = Config.loramodule.rxSignalBandwidth / 1000;
radio.setBandwidth(signalBandwidth);
radio.setCRC(true);
@@ -151,20 +151,20 @@ namespace LoRa_Utils {
}
void changeFreqTx() {
delay(300);
float freq = (float)Config.loramodule.txFreq / 1000000;
radio.setFrequency(freq);
radio.setSpreadingFactor(Config.loramodule.txSpreadingFactor);
radio.setCodingRate(Config.loramodule.txCodingRate4);
float signalBandwidth = Config.loramodule.txSignalBandwidth / 1000;
radio.setBandwidth(Config.loramodule.txSignalBandwidth);
}
void changeFreqRx() {
delay(300);
float freq = (float)Config.loramodule.rxFreq / 1000000;
radio.setFrequency(freq);
radio.setSpreadingFactor(Config.loramodule.rxSpreadingFactor);
radio.setCodingRate(Config.loramodule.rxCodingRate4);
float signalBandwidth = Config.loramodule.rxSignalBandwidth / 1000;
radio.setBandwidth(Config.loramodule.rxSignalBandwidth);
}

View File

@@ -20,6 +20,7 @@
#include "battery_utils.h"
#include "board_pinout.h"
#include "power_utils.h"
#include "utils.h"
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
#ifdef TTGO_T_Beam_S3_SUPREME_V3
@@ -43,6 +44,7 @@
#endif
extern Configuration Config;
extern bool callsignIsValid;
namespace POWER_Utils {
@@ -326,6 +328,7 @@ namespace POWER_Utils {
delay(1000);
BATTERY_Utils::setup();
BATTERY_Utils::startupBatteryHealth();
callsignIsValid = Utils::checkValidCallsign(Config.callsign);
setCpuFrequencyMhz(80);
}

View File

@@ -41,7 +41,7 @@ namespace QUERY_Utils {
String queryQuestion = query;
queryQuestion.toUpperCase();
if (queryQuestion == "?APRS?" || queryQuestion == "H" || queryQuestion == "HELP" || queryQuestion=="?") {
answer.concat("?APRSV ?APRSP ?APRSL ?APRSSSR ?EM=? ?TX=? "); // ?APRSH ?WHERE callsign
answer.concat("?APRSV ?APRSP ?APRSL ?APRSSR ?EM=? ?TX=? "); // ?APRSH ?WHERE callsign
} else if (queryQuestion == "?APRSV") {
answer.concat("CA2RXU_LoRa_iGate v");
answer.concat(versionNumber);

View File

@@ -65,6 +65,7 @@ bool sendStartTelemetry = true;
bool beaconUpdate = false;
uint32_t lastBeaconTx = 0;
uint32_t lastScreenOn = millis();
bool callsignIsValid = false;
String beaconPacket;
String secondaryBeaconPacket;
@@ -120,7 +121,11 @@ namespace Utils {
#ifdef INTERNAL_LED_PIN
digitalWrite(INTERNAL_LED_PIN,LOW);
#endif
firstLine = Config.callsign;
if (Config.tacticalCallsign != "") {
firstLine = Config.tacticalCallsign;
} else {
firstLine = Config.callsign;
}
seventhLine = " listening...";
}
@@ -187,6 +192,12 @@ namespace Utils {
}
beaconPacket += Config.beacon.comment;
secondaryBeaconPacket += Config.beacon.comment;
if (callsignIsValid && Config.tacticalCallsign != "") {
beaconPacket += " de ";
beaconPacket += Config.callsign;
secondaryBeaconPacket += " de ";
secondaryBeaconPacket += Config.callsign;
}
#if defined(BATTERY_PIN) || defined(HAS_AXP192) || defined(HAS_AXP2101)
if (Config.battery.sendInternalVoltage || Config.battery.monitorInternalVoltage) {

View File

@@ -160,6 +160,7 @@ namespace WEB_Utils {
Config.startupDelay = getParamIntSafe("startupDelay", Config.startupDelay);
Config.callsign = getParamStringSafe("callsign", Config.callsign);
Config.tacticalCallsign = getParamStringSafe("tacticalCallsign", Config.tacticalCallsign);
Config.wifiAutoAP.password = getParamStringSafe("wifi.autoAP.password", Config.wifiAutoAP.password);
Config.wifiAutoAP.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout);