mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-10-31 02:51:58 +01:00
Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3efa333df4 | ||
|
|
aab05be49f | ||
|
|
f2f0d1725e | ||
|
|
ef7d38e940 | ||
|
|
1591d9f7fe | ||
|
|
1747922036 | ||
|
|
37afd9a659 | ||
|
|
f9439704f7 | ||
|
|
9314bf10ba | ||
|
|
a045599df1 | ||
|
|
9930603e99 | ||
|
|
9a8bbc2a99 | ||
|
|
c0d67acffd | ||
|
|
075ed0e6b4 | ||
|
|
8ded0e96df | ||
|
|
d13227c519 | ||
|
|
9556eabd80 | ||
|
|
d75c88fc91 | ||
|
|
1257e5d9e0 | ||
|
|
bfeb0f13eb | ||
|
|
9bddbdf20a |
No files matched your search
@@ -71,6 +71,8 @@ jobs:
|
||||
chip: esp32s3
|
||||
- name: ttgo_t_beam_s3_SUPREME_v3
|
||||
chip: esp32s3
|
||||
- name: ttgo-t-beam-1W
|
||||
chip: esp32s3
|
||||
- name: ESP32_DIY_LoRa_A7670_SX1278
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_LoRa_A7670_915_SX1276
|
||||
|
||||
@@ -37,7 +37,7 @@ ____________________________________________________
|
||||
|
||||
- TTGO Lilygo LoRa32 LoRa32 V2.1 (V1.6 is the same) and T3S3 V1.2.
|
||||
|
||||
- TTGO T-Beam V1.0 , V1.1, V1.2 (also variations with SX1262 and SX1268 LoRa Modules) and Supreme V3.
|
||||
- TTGO T-Beam V1.0 , V1.1, V1.2 (also variations with SX1262 and SX1268 LoRa Modules), Supreme V3 and T-Beam 1W.
|
||||
|
||||
- T-Deck Plus (and also regular T-Deck with/without GPS).
|
||||
|
||||
@@ -64,6 +64,8 @@ ____________________________________________________
|
||||
<br />
|
||||
|
||||
# Timeline (Versions):
|
||||
- 2026-10-14 T-Beam 1W board added (with fan control and Tx over-temperature protection).
|
||||
- 2026-10-05 Fix for stored XSS in received packets, Wifi warning with bad password, date on Rx packets, mDNS kiss-tnc for phone apps, APRSPacketLib V1.0.10 update.
|
||||
- 2026-09-30 GPS baud rate auto-detection, CAD timeout (packet dropped after 10s of busy channel)
|
||||
- 2026-09-23 APRSPacketLib V1.0.7 update and many code cleaning with this.
|
||||
- 2026-09-17 Real APRS Symbols added to map and TNC2 output as a posibility in TNC mode.
|
||||
|
||||
+4
-4
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"active": false,
|
||||
"messagesToRF": false,
|
||||
"objectsToRF": false,
|
||||
"is2Metadata": false,
|
||||
"server": "rotate.aprs2.net",
|
||||
"passcode": "XYZVW",
|
||||
"port": 14580,
|
||||
@@ -47,8 +48,8 @@
|
||||
"rxSignalBandwidth": 125000,
|
||||
"txActive": false,
|
||||
"txFreq": 433775000,
|
||||
"txSpreadingFactor": 9,
|
||||
"txCodingRate4": 7,
|
||||
"txSpreadingFactor": 12,
|
||||
"txCodingRate4": 5,
|
||||
"txSignalBandwidth": 125000,
|
||||
"power": 20,
|
||||
"cadActive": true
|
||||
|
||||
+28
-12
@@ -937,6 +937,22 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="aprs_is.is2Metadata"
|
||||
id="aprs_is.is2Metadata"
|
||||
class="form-check-input"
|
||||
/>
|
||||
<label
|
||||
for="aprs_is.is2Metadata"
|
||||
class="form-label"
|
||||
>Send IS2 Metadata
|
||||
(RSSI/SNR) to APRS-IS</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<label
|
||||
for="aprs_is.server"
|
||||
@@ -1313,10 +1329,10 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
<option value="6">SF6</option>
|
||||
<option value="7">SF7</option>
|
||||
<option value="8">SF8</option>
|
||||
<option value="9" selected>SF9</option>
|
||||
<option value="9">SF9</option>
|
||||
<option value="10">SF10</option>
|
||||
<option value="11">SF11</option>
|
||||
<option value="12">SF12 - Highest sensitivity</option>
|
||||
<option value="12" selected>SF12 - Highest sensitivity</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 mt-3">
|
||||
@@ -1335,10 +1351,10 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
<option value="6">SF6</option>
|
||||
<option value="7">SF7</option>
|
||||
<option value="8">SF8</option>
|
||||
<option value="9" selected>SF9</option>
|
||||
<option value="9">SF9</option>
|
||||
<option value="10">SF10</option>
|
||||
<option value="11">SF11</option>
|
||||
<option value="12">SF12 - Highest sensitivity</option>
|
||||
<option value="12" selected>SF12 - Highest sensitivity</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 mt-3">
|
||||
@@ -1353,9 +1369,9 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
id="lora.rxCodingRate4"
|
||||
required=""
|
||||
>
|
||||
<option value="5">5</option>
|
||||
<option value="5" selected>5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7" selected>7</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1371,9 +1387,9 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
id="lora.txCodingRate4"
|
||||
required=""
|
||||
>
|
||||
<option value="5">5</option>
|
||||
<option value="5" selected>5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7" selected>7</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1421,7 +1437,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
|
||||
@@ -1429,8 +1445,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=""
|
||||
@@ -2467,7 +2483,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>
|
||||
@@ -103,6 +103,7 @@ function loadSettings(settings) {
|
||||
document.getElementById("aprs_is.active").checked = settings.aprs_is.active;
|
||||
document.getElementById("aprs_is.messagesToRF").checked = settings.aprs_is.messagesToRF;
|
||||
document.getElementById("aprs_is.objectsToRF").checked = settings.aprs_is.objectsToRF;
|
||||
document.getElementById("aprs_is.is2Metadata").checked = settings.aprs_is.is2Metadata;
|
||||
document.getElementById("aprs_is.server").value = settings.aprs_is.server;
|
||||
document.getElementById("aprs_is.port").value = settings.aprs_is.port;
|
||||
document.getElementById("aprs_is.filter").value = settings.aprs_is.filter;
|
||||
@@ -110,6 +111,7 @@ function loadSettings(settings) {
|
||||
APRSISCheckbox.checked = settings.aprs_is.active;
|
||||
APRSISGateMessages.disabled = !APRSISCheckbox.checked;
|
||||
APRSISGateObjects.disabled = !APRSISCheckbox.checked;
|
||||
APRSISMetadata.disabled = !APRSISCheckbox.checked;
|
||||
APRSISServer.disabled = !APRSISCheckbox.checked;
|
||||
APRSISPort.disabled = !APRSISCheckbox.checked;
|
||||
APRSISPasscode.disabled = !APRSISCheckbox.checked;
|
||||
@@ -350,6 +352,7 @@ StatusCheckbox.addEventListener("change", function() {
|
||||
const APRSISCheckbox = document.querySelector('input[name="aprs_is.active"]');
|
||||
const APRSISGateMessages = document.querySelector('input[name="aprs_is.messagesToRF"]');
|
||||
const APRSISGateObjects = document.querySelector('input[name="aprs_is.objectsToRF"]');
|
||||
const APRSISMetadata = document.querySelector('input[name="aprs_is.is2Metadata"]');
|
||||
const APRSISServer = document.querySelector('input[name="aprs_is.server"]');
|
||||
const APRSISPort = document.querySelector('input[name="aprs_is.port"]');
|
||||
const APRSISPasscode = document.querySelector('input[name="aprs_is.passcode"]');
|
||||
@@ -357,6 +360,7 @@ const APRSISFilter = document.querySelector('input[name="aprs_is.fi
|
||||
APRSISCheckbox.addEventListener("change", function() {
|
||||
APRSISGateMessages.disabled = !this.checked;
|
||||
APRSISGateObjects.disabled = !this.checked;
|
||||
APRSISMetadata.disabled = !this.checked;
|
||||
APRSISServer.disabled = !this.checked;
|
||||
APRSISPort.disabled = !this.checked;
|
||||
APRSISPasscode.disabled = !this.checked;
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
String filter;
|
||||
bool messagesToRF;
|
||||
bool objectsToRF;
|
||||
bool is2Metadata;
|
||||
};
|
||||
|
||||
class DIGI {
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
|
||||
namespace GPS_Utils {
|
||||
|
||||
String getiGateLoRaBeaconPacket();
|
||||
void generateBeacons();
|
||||
String buildDistanceAndComment(float latitude, float longitude, const String& comment);
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
namespace LoRa_Utils {
|
||||
|
||||
int validPower(int requested);
|
||||
void setup();
|
||||
void sendNewPacket(const String& newPacket);
|
||||
String receivePacketFromSleep();
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||
*
|
||||
* This file is part of LoRa APRS iGate.
|
||||
*
|
||||
* LoRa APRS iGate 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LoRa APRS iGate 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 for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Fan / thermal control adapted from KJ7NYE's LoRa_FieldOps_APRS_Tracker (GPLv3)
|
||||
|
||||
#ifndef THERMAL_UTILS_H_
|
||||
#define THERMAL_UTILS_H_
|
||||
|
||||
#include "board_pinout.h"
|
||||
|
||||
#ifdef FAN_CTRL_PIN
|
||||
|
||||
namespace THERMAL_Utils {
|
||||
|
||||
void setup();
|
||||
void monitor();
|
||||
void onTxStart();
|
||||
void onTxEnd();
|
||||
void turnOffFan();
|
||||
bool isTxBlocked();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+12
-2
@@ -52,6 +52,7 @@ ___________________________________________________________________*/
|
||||
#include "syslog_utils.h"
|
||||
#include "power_utils.h"
|
||||
#include "sleep_utils.h"
|
||||
#include "thermal_utils.h"
|
||||
#include "mqtt_utils.h"
|
||||
#include "lora_utils.h"
|
||||
#include "wifi_utils.h"
|
||||
@@ -68,8 +69,8 @@ ___________________________________________________________________*/
|
||||
#endif
|
||||
|
||||
|
||||
String versionDate = "2026-10-02";
|
||||
String versionNumber = "4.0.2";
|
||||
String versionDate = "2026-10-29";
|
||||
String versionNumber = "4.0.5";
|
||||
Configuration Config;
|
||||
WiFiClient aprsIsClient;
|
||||
WiFiClient mqttClient;
|
||||
@@ -108,6 +109,9 @@ void setup() {
|
||||
}
|
||||
networkManager->setHostName("iGATE-" + Config.callsign);
|
||||
POWER_Utils::setup();
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::setup();
|
||||
#endif
|
||||
Utils::setupDisplay();
|
||||
LoRa_Utils::setup();
|
||||
Utils::validateFreqs();
|
||||
@@ -135,6 +139,9 @@ void loop() {
|
||||
SLEEP_Utils::checkWakeUpFlag();
|
||||
Utils::checkBeaconInterval();
|
||||
STATION_Utils::processOutputPacketBufferUltraEcoMode();
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::monitor();
|
||||
#endif
|
||||
Utils::checkSleepByLowBatteryVoltage(1);
|
||||
SLEEP_Utils::startSleeping();
|
||||
} else {
|
||||
@@ -222,6 +229,9 @@ void loop() {
|
||||
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
#endif
|
||||
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::monitor();
|
||||
#endif
|
||||
Utils::checkRebootTime();
|
||||
Utils::checkSleepByLowBatteryVoltage(1);
|
||||
}
|
||||
|
||||
+40
-54
@@ -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");
|
||||
}
|
||||
|
||||
@@ -151,42 +152,36 @@ namespace APRS_IS_Utils {
|
||||
return packetToUpload;
|
||||
}
|
||||
|
||||
String buildIS2Meta() {
|
||||
static String fixedTail; // rx_modem + rx_bitrate + rx_frequency: RX config doesn't change at runtime
|
||||
if (fixedTail == "") {
|
||||
// LoRa bit rate = SF * (BW / 2^SF) * 4/CR (CR = denominator: 5..8)
|
||||
uint32_t bitrate = (uint32_t)(Config.loramodule.rxSpreadingFactor
|
||||
* ((float)Config.loramodule.rxSignalBandwidth / (1 << Config.loramodule.rxSpreadingFactor))
|
||||
* (4.0 / Config.loramodule.rxCodingRate4));
|
||||
fixedTail = " rx_modem=2 rx_bitrate=" + String(bitrate) + " rx_frequency=" + String(Config.loramodule.rxFreq);
|
||||
}
|
||||
return "#is2-meta rx_rssi_dbm=" + String(lastAprsPacket.rssi) + " rx_snr_db=" + String(lastAprsPacket.snr, 1) + fixedTail;
|
||||
}
|
||||
|
||||
bool processReceivedLoRaMessage(const String& sender, const String& packet) {
|
||||
String receivedMessage;
|
||||
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;
|
||||
@@ -219,7 +214,11 @@ namespace APRS_IS_Utils {
|
||||
A7670_Utils::uploadToAPRSIS(aprsPacketToUpload);
|
||||
stationBeacon = false;
|
||||
#else
|
||||
upload(aprsPacketToUpload);
|
||||
if (Config.aprs_is.is2Metadata && aprsPacketToUpload != "") {
|
||||
upload(buildIS2Meta() + "\r\n" + aprsPacketToUpload); // IS2PLAIN: meta line right before the packet, same write
|
||||
} else {
|
||||
upload(aprsPacketToUpload);
|
||||
}
|
||||
#endif
|
||||
Utils::println("(Uploaded to APRS-IS)");
|
||||
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
@@ -274,21 +273,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 +301,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 +316,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 +324,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));
|
||||
|
||||
@@ -63,6 +63,7 @@ bool Configuration::writeFile() {
|
||||
data["aprs_is"]["filter"] = aprs_is.filter;
|
||||
data["aprs_is"]["messagesToRF"] = aprs_is.messagesToRF;
|
||||
data["aprs_is"]["objectsToRF"] = aprs_is.objectsToRF;
|
||||
data["aprs_is"]["is2Metadata"] = aprs_is.is2Metadata;
|
||||
|
||||
data["beacon"]["comment"] = beacon.comment;
|
||||
data["beacon"]["rfCommentEveryXBeacons"] = beacon.rfCommentEveryXBeacons;
|
||||
@@ -238,7 +239,8 @@ bool Configuration::readFile() {
|
||||
data["aprs_is"]["port"].isNull() ||
|
||||
data["aprs_is"]["filter"].isNull() ||
|
||||
data["aprs_is"]["messagesToRF"].isNull() ||
|
||||
data["aprs_is"]["objectsToRF"].isNull()) needsRewrite = true;
|
||||
data["aprs_is"]["objectsToRF"].isNull() ||
|
||||
data["aprs_is"]["is2Metadata"].isNull()) needsRewrite = true;
|
||||
aprs_is.active = data["aprs_is"]["active"] | false;
|
||||
aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV";
|
||||
aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net";
|
||||
@@ -246,6 +248,7 @@ bool Configuration::readFile() {
|
||||
aprs_is.filter = data["aprs_is"]["filter"] | "m/10";
|
||||
aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false;
|
||||
aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false;
|
||||
aprs_is.is2Metadata = data["aprs_is"]["is2Metadata"] | false;
|
||||
|
||||
if (data["beacon"]["latitude"].isNull() ||
|
||||
data["beacon"]["longitude"].isNull() ||
|
||||
@@ -473,6 +476,7 @@ void Configuration::setDefaultValues() {
|
||||
aprs_is.filter = "m/10";
|
||||
aprs_is.messagesToRF = false;
|
||||
aprs_is.objectsToRF = false;
|
||||
aprs_is.is2Metadata = false;
|
||||
|
||||
beacon.comment = "LoRa APRS";
|
||||
beacon.rfCommentEveryXBeacons = 1;
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
+55
-22
@@ -28,6 +28,7 @@
|
||||
#include "ntp_utils.h"
|
||||
#include "display.h"
|
||||
#include "utils.h"
|
||||
#include "thermal_utils.h"
|
||||
|
||||
|
||||
extern Configuration Config;
|
||||
@@ -69,6 +70,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,10 +98,21 @@ 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() {
|
||||
#ifdef RADIO_VCC_PIN // LoRa module power switch (QRP Labs LightGateway, T-Beam 1W)
|
||||
pinMode(RADIO_VCC_PIN, OUTPUT);
|
||||
digitalWrite(RADIO_VCC_PIN, HIGH);
|
||||
#endif
|
||||
#if defined (LIGHTGATEWAY_1_0) || defined(LIGHTGATEWAY_PLUS_1_0)
|
||||
pinMode(RADIO_VCC_PIN,OUTPUT);
|
||||
digitalWrite(RADIO_VCC_PIN,HIGH);
|
||||
loraSPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN, RADIO_CS_PIN);
|
||||
#else
|
||||
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
|
||||
@@ -103,6 +127,8 @@ namespace LoRa_Utils {
|
||||
#endif
|
||||
#if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // before begin() so RF switch is driven from start (Ebyte E22/E32 1W, QRP Labs LightGateway)
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
|
||||
#elif defined(RADIO_RXEN) // T-Beam 1W: RXEN = LNA only, DIO2 drives the PA
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIOLIB_NC);
|
||||
#endif
|
||||
int state = radio.begin(freq);
|
||||
if (state != RADIOLIB_ERR_NONE) {
|
||||
@@ -131,21 +157,17 @@ namespace LoRa_Utils {
|
||||
radio.setBandwidth(signalBandwidth);
|
||||
radio.setCRC(true);
|
||||
|
||||
/*#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIOLIB_NC);
|
||||
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
|
||||
|
||||
@@ -156,16 +178,15 @@ namespace LoRa_Utils {
|
||||
#if defined(HAS_TCXO) && !defined(HAS_1W_LORA)
|
||||
radio.setDio2AsRfSwitch();
|
||||
#endif
|
||||
#if defined(TTGO_T_BEAM_1W)
|
||||
radio.setDio2AsRfSwitch(true); // DIO2 drives the 1W PA
|
||||
radio.setPaRampTime(RADIOLIB_SX126X_PA_RAMP_800U); // PA needs >800us to settle (default 200us)
|
||||
#endif
|
||||
#ifdef HAS_TCXO
|
||||
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() {
|
||||
@@ -222,6 +243,12 @@ namespace LoRa_Utils {
|
||||
|
||||
void sendNewPacket(const String& newPacket) {
|
||||
if (!Config.loramodule.txActive) return;
|
||||
#ifdef FAN_CTRL_PIN
|
||||
if (THERMAL_Utils::isTxBlocked()) {
|
||||
Utils::println("Thermal: Tx blocked (over-temperature), packet dropped: " + newPacket);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (Config.loramodule.txFreq != Config.loramodule.rxFreq) {
|
||||
if (!packetIsBeacon || (packetIsBeacon && Config.beacon.beaconFreq == 1)) {
|
||||
@@ -245,7 +272,13 @@ namespace LoRa_Utils {
|
||||
}
|
||||
|
||||
if (!cadDropped) {
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::onTxStart();
|
||||
#endif
|
||||
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::onTxEnd();
|
||||
#endif
|
||||
transmitFlag = true;
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
|
||||
+7
-8
@@ -66,7 +66,7 @@ namespace POWER_Utils {
|
||||
}
|
||||
|
||||
void vext_ctrl_OFF() {
|
||||
digitalWrite(VEXT_CTRL_PIN, Config.digi.ecoMode == 1 ? VEXT_CTRL_ON_STATE : !VEXT_CTRL_ON_STATE);
|
||||
digitalWrite(VEXT_CTRL_PIN, !VEXT_CTRL_ON_STATE); // always off, regardless of ecoMode
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -111,9 +111,7 @@ namespace POWER_Utils {
|
||||
PMU.enableALDO3();
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HELTEC_WIRELESS_TRACKER
|
||||
adc_ctrl_ON();
|
||||
#endif
|
||||
// HELTEC_WIRELESS_TRACKER: GPS shares Vext (GPIO3) with the TFT, so it follows the display power (not controlled here)
|
||||
//gpsIsActive = true;
|
||||
}
|
||||
|
||||
@@ -129,9 +127,7 @@ namespace POWER_Utils {
|
||||
PMU.disableALDO3();
|
||||
#endif
|
||||
#endif
|
||||
#ifdef HELTEC_WIRELESS_TRACKER
|
||||
adc_ctrl_OFF();
|
||||
#endif
|
||||
// HELTEC_WIRELESS_TRACKER: GPS shares Vext with the TFT, turned off with it (see activateGPS)
|
||||
//gpsIsActive = false;
|
||||
}
|
||||
|
||||
@@ -199,7 +195,10 @@ namespace POWER_Utils {
|
||||
PMU.setDC1Voltage(3300);
|
||||
PMU.enableDC1();
|
||||
#ifdef TTGO_T_Beam_S3_SUPREME_V3
|
||||
PMU.setALDO1Voltage(3300);
|
||||
PMU.setALDO1Voltage(3300); // BME280, QMC6310, QMI8658, OLED
|
||||
PMU.enableALDO1();
|
||||
PMU.setALDO2Voltage(3300); // sensors/OLED I2C + RTC PCF8563 (must stay on)
|
||||
PMU.enableALDO2();
|
||||
#endif
|
||||
PMU.setButtonBatteryChargeVoltage(3300);
|
||||
PMU.enableButtonBatteryCharge();
|
||||
|
||||
+16
-26
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "sleep_utils.h"
|
||||
#include "digi_utils.h"
|
||||
#include "lora_utils.h"
|
||||
#include "thermal_utils.h"
|
||||
|
||||
|
||||
extern Configuration Config;
|
||||
@@ -75,6 +76,9 @@ namespace SLEEP_Utils {
|
||||
Serial.print("(Sleeping : "); Serial.print(timeToSleep); Serial.println("seconds)");
|
||||
delay(100);
|
||||
LoRa_Utils::wakeRadio();
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::turnOffFan(); // GPIOs keep their level in light sleep
|
||||
#endif
|
||||
esp_light_sleep_start();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||
*
|
||||
* This file is part of LoRa APRS iGate.
|
||||
*
|
||||
* LoRa APRS iGate 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LoRa APRS iGate 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 for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// Fan / thermal control adapted from KJ7NYE's LoRa_FieldOps_APRS_Tracker (GPLv3)
|
||||
// NTC circuit (T-Beam 1W V1.0 schematic): 3V3 -> NCP18XH103F03RB -> TEMP_PIN -> 10K -> GND
|
||||
|
||||
#include "board_pinout.h"
|
||||
|
||||
#ifdef FAN_CTRL_PIN
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <math.h>
|
||||
#include "thermal_utils.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define NTC_B 3380.0f // B25/50 (K)
|
||||
#define NTC_R25 10000.0f // NTC resistance at 25C (ohm)
|
||||
#define NTC_T0 298.15f // 25C in Kelvin
|
||||
#define NTC_R_FIXED 10000.0f // pull-down resistor (ohm)
|
||||
#define NTC_VCC_MV 3300.0f
|
||||
#define NTC_MIN_VALID_MV 100 // below this the sample is a failed ADC2 read (100mV = ~-45C)
|
||||
|
||||
#define FAN_ON_TEMP 50.0f // fan on at or above
|
||||
#define FAN_OFF_TEMP 42.0f // fan off below (hysteresis)
|
||||
#define TEMP_WARNING 75.0f
|
||||
#define TEMP_TX_BLOCK 85.0f // Tx blocked at or above
|
||||
#define TEMP_TX_UNBLOCK 75.0f // Tx allowed again below (hysteresis)
|
||||
#define TX_COOLDOWN_MS 30000 // fan kept on after TX
|
||||
#define TEMP_SAMPLE_MS 30000
|
||||
|
||||
static float currentTemperature = 25.0f;
|
||||
static bool fanOn = false;
|
||||
static bool txBlocked = false;
|
||||
static bool txCooldownActive = false;
|
||||
static uint32_t txEndTime = 0;
|
||||
static uint32_t lastTempSample = 0;
|
||||
static bool firstTempSample = true;
|
||||
|
||||
|
||||
namespace THERMAL_Utils {
|
||||
|
||||
float readTemperature() { // TEMP_PIN is on ADC2: reads fail (return 0) while Wi-Fi holds the ADC2 arbiter
|
||||
analogReadMilliVolts(TEMP_PIN); // dummy read to settle the ADC
|
||||
delay(1);
|
||||
uint32_t sum = 0;
|
||||
int validSamples = 0;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
uint32_t sample = analogReadMilliVolts(TEMP_PIN);
|
||||
if (sample > NTC_MIN_VALID_MV) {
|
||||
sum += sample;
|
||||
validSamples++;
|
||||
}
|
||||
delay(3);
|
||||
}
|
||||
if (validSamples == 0) return NAN;
|
||||
float mv = (float)sum / validSamples;
|
||||
if (mv >= NTC_VCC_MV) return NAN;
|
||||
float ntcResistance = NTC_R_FIXED * (NTC_VCC_MV - mv) / mv;
|
||||
float kelvin = 1.0f / (1.0f / NTC_T0 + (1.0f / NTC_B) * logf(ntcResistance / NTC_R25));
|
||||
return kelvin - 273.15f;
|
||||
}
|
||||
|
||||
void setFan(bool on, const String& reason) {
|
||||
if (fanOn == on) return;
|
||||
fanOn = on;
|
||||
digitalWrite(FAN_CTRL_PIN, on ? HIGH : LOW);
|
||||
Utils::println("Thermal: Fan " + String(on ? "ON" : "OFF") + " (" + reason + ", " + String(currentTemperature, 1) + " C)");
|
||||
}
|
||||
|
||||
void updateFan() {
|
||||
if (!fanOn) {
|
||||
if (txCooldownActive) {
|
||||
setFan(true, "TX cooldown");
|
||||
} else if (currentTemperature >= FAN_ON_TEMP) {
|
||||
setFan(true, "temperature");
|
||||
}
|
||||
} else if (!txCooldownActive && currentTemperature < FAN_OFF_TEMP) {
|
||||
setFan(false, "cooled down");
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
pinMode(TEMP_PIN, INPUT);
|
||||
analogSetPinAttenuation(TEMP_PIN, ADC_11db);
|
||||
pinMode(FAN_CTRL_PIN, OUTPUT);
|
||||
digitalWrite(FAN_CTRL_PIN, LOW);
|
||||
fanOn = false;
|
||||
firstTempSample = true; // first monitor() call samples immediately
|
||||
}
|
||||
|
||||
void monitor() {
|
||||
uint32_t now = millis();
|
||||
|
||||
if (txCooldownActive && (now - txEndTime >= TX_COOLDOWN_MS)) {
|
||||
txCooldownActive = false;
|
||||
updateFan();
|
||||
}
|
||||
|
||||
if (!firstTempSample && (now - lastTempSample < TEMP_SAMPLE_MS)) return;
|
||||
firstTempSample = false;
|
||||
lastTempSample = now;
|
||||
|
||||
float temperature = readTemperature();
|
||||
if (!isnan(temperature)) currentTemperature = temperature;
|
||||
|
||||
if (!txBlocked && currentTemperature >= TEMP_TX_BLOCK) {
|
||||
txBlocked = true;
|
||||
Utils::println("Thermal: Over-temperature at " + String(currentTemperature, 1) + " C, Tx blocked");
|
||||
} else if (txBlocked && currentTemperature < TEMP_TX_UNBLOCK) {
|
||||
txBlocked = false;
|
||||
Utils::println("Thermal: Temperature back to " + String(currentTemperature, 1) + " C, Tx allowed");
|
||||
} else if (!txBlocked && currentTemperature >= TEMP_WARNING) {
|
||||
Utils::println("Thermal: High temperature: " + String(currentTemperature, 1) + " C");
|
||||
}
|
||||
updateFan();
|
||||
}
|
||||
|
||||
void onTxStart() {
|
||||
txCooldownActive = false;
|
||||
setFan(true, "TX");
|
||||
}
|
||||
|
||||
void onTxEnd() {
|
||||
txCooldownActive = true;
|
||||
txEndTime = millis();
|
||||
}
|
||||
|
||||
void turnOffFan() { // before sleeping: no Tx while asleep, so the PA doesn't heat up
|
||||
txCooldownActive = false;
|
||||
setFan(false, "sleep");
|
||||
}
|
||||
|
||||
bool isTxBlocked() {
|
||||
return txBlocked;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
+53
-59
@@ -27,6 +27,7 @@
|
||||
#include "board_pinout.h"
|
||||
#include "power_utils.h"
|
||||
#include "syslog_utils.h"
|
||||
#include "thermal_utils.h"
|
||||
#include "A7670_utils.h"
|
||||
#include "lora_utils.h"
|
||||
#include "wifi_utils.h"
|
||||
@@ -48,7 +49,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 +70,6 @@ uint32_t lastBeaconTx = 0;
|
||||
uint32_t lastScreenOn = millis();
|
||||
uint32_t lastStatusTx = 0;
|
||||
bool stationCallsignIsValid = false;
|
||||
String beaconPacket;
|
||||
String secondaryBeaconPacket;
|
||||
uint32_t rfBeaconCounter = 0;
|
||||
|
||||
|
||||
@@ -85,13 +84,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
|
||||
}
|
||||
@@ -188,49 +187,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; // APRS-IS beacon always carries the 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) secondaryBeaconPacket += Config.beacon.comment;
|
||||
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 += " **IntBattWarning:SLEEP**";
|
||||
loraBeaconPacket += " **IntBattWarning:SLEEP**";
|
||||
shouldSleepLowVoltage = true;
|
||||
}
|
||||
|
||||
@@ -243,10 +235,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,8 +248,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 += " **ExtBattWarning:SLEEP**";
|
||||
loraBeaconPacket += " **ExtBattWarning:SLEEP**";
|
||||
shouldSleepLowVoltage = true;
|
||||
}
|
||||
|
||||
@@ -270,10 +262,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,8 +273,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) {
|
||||
@@ -290,18 +282,18 @@ 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++;
|
||||
}
|
||||
|
||||
@@ -332,6 +324,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
|
||||
@@ -342,11 +340,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
|
||||
@@ -392,11 +386,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";
|
||||
@@ -438,15 +428,19 @@ namespace Utils {
|
||||
if (mode == 0) { // at startup
|
||||
delay(3000);
|
||||
}
|
||||
Serial.println("\n\n*** Sleeping Low Battey Voltage ***\n\n");
|
||||
Serial.println("\n\n*** Sleeping Low Battery Voltage ***\n\n");
|
||||
esp_sleep_enable_timer_wakeup(30 * 60 * 1000000); // sleep 30 min
|
||||
if (mode == 1) { // low voltage detected after a while
|
||||
displayToggle(false);
|
||||
}
|
||||
#ifdef VEXT_CTRL_PIN
|
||||
digitalWrite(VEXT_CTRL_PIN, !VEXT_CTRL_ON_STATE); // VEXT off before deep sleep (always, regardless of ecoMode)
|
||||
POWER_Utils::vext_ctrl_OFF(); // VEXT off before deep sleep (always, regardless of ecoMode)
|
||||
#endif
|
||||
#ifdef FAN_CTRL_PIN
|
||||
THERMAL_Utils::turnOffFan();
|
||||
#endif
|
||||
LoRa_Utils::sleepRadio();
|
||||
POWER_Utils::deactivateGPS(); // GPS rail off (AXP boards), otherwise it keeps draining the low battery while sleeping
|
||||
#ifdef RADIO_VCC_PIN
|
||||
digitalWrite(RADIO_VCC_PIN, LOW); // cut LoRa module power (QRP Labs LightGateway) so it draws nothing while sleeping
|
||||
#endif
|
||||
|
||||
+3
-1
@@ -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"
|
||||
@@ -233,6 +234,7 @@ namespace WEB_Utils {
|
||||
if (Config.aprs_is.active) {
|
||||
Config.aprs_is.messagesToRF = request->hasParam("aprs_is.messagesToRF", true);
|
||||
Config.aprs_is.objectsToRF = request->hasParam("aprs_is.objectsToRF", true);
|
||||
Config.aprs_is.is2Metadata = request->hasParam("aprs_is.is2Metadata", true);
|
||||
Config.aprs_is.server = getParamStringSafe("aprs_is.server", Config.aprs_is.server);
|
||||
Config.aprs_is.passcode = getParamStringSafe("aprs_is.passcode", Config.aprs_is.passcode);
|
||||
Config.aprs_is.port = getParamIntSafe("aprs_is.port", Config.aprs_is.port);
|
||||
@@ -277,7 +279,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
@@ -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
|
||||
@@ -0,0 +1,65 @@
|
||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||
*
|
||||
* This file is part of LoRa APRS iGate.
|
||||
*
|
||||
* LoRa APRS iGate 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, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LoRa APRS iGate 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 for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef BOARD_PINOUT_H_
|
||||
#define BOARD_PINOUT_H_
|
||||
|
||||
// LoRa Radio
|
||||
#define HAS_SX1262
|
||||
#define HAS_1W_LORA
|
||||
#define HAS_TCXO
|
||||
#define RADIO_VCC_PIN 40 // (RADIO_LDO_EN_PIN) HIGH = turn on the radio, LOW = turns off the radio
|
||||
#define RADIO_SCLK_PIN 13
|
||||
#define RADIO_MISO_PIN 12
|
||||
#define RADIO_MOSI_PIN 11
|
||||
#define RADIO_CS_PIN 15
|
||||
#define RADIO_RST_PIN 3
|
||||
#define RADIO_DIO1_PIN 1
|
||||
#define RADIO_BUSY_PIN 38
|
||||
#define RADIO_RXEN 21 // (RADIO_CTRL_PIN) HIGH = LNA on (Rx), LOW = LNA off (Tx)
|
||||
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
|
||||
#define GPIO_WAKEUP_PIN GPIO_SEL_1
|
||||
|
||||
// I2C
|
||||
#define USE_WIRE_WITH_OLED_PINS
|
||||
|
||||
// Display
|
||||
#define HAS_DISPLAY
|
||||
#define HAS_SH1106
|
||||
|
||||
#undef OLED_SDA
|
||||
#undef OLED_SCL
|
||||
#undef OLED_RST
|
||||
|
||||
#define OLED_SDA 8
|
||||
#define OLED_SCL 9
|
||||
#define OLED_RST -1 // Reset pin # (or -1 if sharing Arduino reset pin)
|
||||
|
||||
// Aditional Config
|
||||
#define BATTERY_PIN 4
|
||||
#define BATTERY_CELLS 2 // 7.4V 2S battery
|
||||
#define BUTTON_PIN 0
|
||||
#define TEMP_PIN 14 // NTC Temperature sensor
|
||||
#define FAN_CTRL_PIN 41
|
||||
|
||||
// GPS
|
||||
#define HAS_GPS
|
||||
#define GPS_RX 6
|
||||
#define GPS_TX 5
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,21 @@
|
||||
[env:ttgo-t-beam-1W]
|
||||
board = esp32-s3-devkitm-1
|
||||
board_build.mcu = esp32s3
|
||||
board_build.flash_size = 16MB
|
||||
board_build.flash_mode = qio
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_upload.flash_size = 16MB
|
||||
board_upload.maximum_size = 16777216
|
||||
board_build.psram_type = opi
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
${common.usb_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D TTGO_T_BEAM_1W
|
||||
-D BOARD_HAS_PSRAM
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
adafruit/Adafruit SH110X @ 2.1.15
|
||||
@@ -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
|
||||
Reference in new issue
Block a user