mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-10-31 02:51:58 +01:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e52d4566ba | ||
|
|
c80eb9bd84 | ||
|
|
6b187da9f4 | ||
|
|
ab27c11532 | ||
|
|
43bfff5033 | ||
|
|
8fa10a7813 | ||
|
|
316c746936 | ||
|
|
3618558b21 | ||
|
|
e1eda2e64a | ||
|
|
759cddd0e5 | ||
|
|
8186ff4fbd | ||
|
|
3720fe9881 | ||
|
|
5d04c56fd2 | ||
|
|
ee65f953d6 | ||
|
|
c952ea0159 | ||
|
|
21a42ef157 | ||
|
|
53ee07a7c1 | ||
|
|
12a0f5bed1 | ||
|
|
27f34c5f67 | ||
|
|
08bfb013ce | ||
|
|
d573c66fd7 | ||
|
|
e0dc2f81ee | ||
|
|
6e4dc3c301 | ||
|
|
d2e42c8571 |
No files matched your search
+23
-17
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: ttgo-lora32-v21_915
|
||||
chip: esp32
|
||||
- name: ttgo-lora32-v21_915_GPS
|
||||
chip: esp32
|
||||
chip: esp32
|
||||
- name: ttgo_lora32_t3s3_v1_2
|
||||
chip: esp32s3
|
||||
- name: heltec-lora32-v2
|
||||
@@ -37,17 +37,19 @@ jobs:
|
||||
chip: esp32s3
|
||||
- name: heltec_wireless_bridge
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_LoRa
|
||||
- name: ESP32_DIY_LoRa_SX1278
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_LoRa_915
|
||||
- name: ESP32_DIY_LoRa_915_SX1276
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_1W_LoRa
|
||||
- name: ESP32_DIY_1W_LoRa_E22_400M30S
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_1W_LoRa_915
|
||||
- name: ESP32_DIY_1W_LoRa_915_E22_900M30S
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_1W_LoRa_LLCC68
|
||||
- name: ESP32_DIY_1W_LoRa_E220_400M30S
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2
|
||||
- name: ESP32_DIY_1W_LoRa_E32_400M30S
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S
|
||||
chip: esp32
|
||||
- name: ttgo-t-beam-v1_2
|
||||
chip: esp32
|
||||
@@ -69,9 +71,9 @@ jobs:
|
||||
chip: esp32s3
|
||||
- name: ttgo_t_beam_s3_SUPREME_v3
|
||||
chip: esp32s3
|
||||
- name: ESP32_DIY_LoRa_A7670
|
||||
- name: ESP32_DIY_LoRa_A7670_SX1278
|
||||
chip: esp32
|
||||
- name: ESP32_DIY_LoRa_A7670_915
|
||||
- name: ESP32_DIY_LoRa_A7670_915_SX1276
|
||||
chip: esp32
|
||||
- name: heltec_wireless_tracker
|
||||
chip: esp32s3
|
||||
@@ -85,29 +87,33 @@ jobs:
|
||||
chip: esp32s3
|
||||
- name: OE5HWN_MeshCom
|
||||
chip: esp32
|
||||
- name: WEMOS-LOLIN32-OLED-DIY
|
||||
- name: WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278
|
||||
chip: esp32
|
||||
- name: WEMOS-D1-R32-RA02
|
||||
- name: WEMOS_D1_R32_DIY_LoRa_SX1278
|
||||
chip: esp32
|
||||
- name: WEMOS_S2_MINI_DIY_LoRa
|
||||
- name: WEMOS_S2_MINI_DIY_LoRa_SX1278
|
||||
chip: esp32s2
|
||||
- name: esp32c3_DIY_1W_LoRa
|
||||
- name: ESP32C3_DIY_1W_LoRa_E22_400M30S
|
||||
chip: esp32c3
|
||||
- name: esp32c3_DIY_1W_LoRa_915
|
||||
- name: ESP32C3_DIY_1W_LoRa_915_E22_900M30S
|
||||
chip: esp32c3
|
||||
- name: ESP32_C3_OctopusLab_LoRa
|
||||
- name: ESP32C3_OctopusLab_LoRa
|
||||
chip: esp32c3
|
||||
- name: QRPLabs_LightGateway_1_0
|
||||
chip: esp32s3
|
||||
- name: QRPLabs_LightGateway_Plus_1_0
|
||||
chip: esp32s3
|
||||
- name: RPC_LORA_DIGIGATE_1W
|
||||
chip: esp32
|
||||
chip: esp32
|
||||
- name: XIAO_ESP32S3_WIO_SX1262
|
||||
chip: esp32s3
|
||||
- name: RAK3312
|
||||
chip: esp32s3
|
||||
- name: RAK3112
|
||||
chip: esp32s3
|
||||
- name: TROY_LoRa_APRS
|
||||
chip: esp32
|
||||
- name: ESP32_9M2IBR_1W_LoRa_GPS
|
||||
- name: ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S
|
||||
chip: esp32
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
Vendored
+2
-1
@@ -5,6 +5,7 @@
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
"pioarduino.pioarduino-ide"
|
||||
]
|
||||
}
|
||||
@@ -16,12 +16,6 @@ ____________________________________________________
|
||||
|
||||
____________________________________________________
|
||||
|
||||
## You can support this project to continue to grow:
|
||||
|
||||
[<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/github-sponsors.png">](https://github.com/sponsors/richonguzman) [<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/paypalme.png">](http://paypal.me/richonguzman)
|
||||
|
||||
____________________________________________________
|
||||
|
||||
## New Web/UI
|
||||
|
||||
| New Menu | Map |
|
||||
@@ -30,26 +24,32 @@ ____________________________________________________
|
||||
|
||||
____________________________________________________
|
||||
|
||||
## You can support this project to continue to grow:
|
||||
|
||||
[<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/github-sponsors.png">](https://github.com/sponsors/richonguzman) [<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/paypalme.png">](http://paypal.me/richonguzman)
|
||||
|
||||
____________________________________________________
|
||||
|
||||
|
||||
## SUPPORTED BOARDS (<a href="https://github.com/richonguzman/LoRa_APRS_iGate/wiki/Supported-Boards-and-Buying-Links" target="_blank">Buying links</a>).
|
||||
|
||||
(NOTE: all boards with 433-868-915 MHz versions)
|
||||
|
||||
- TTGO Lilygo LoRa32 T3S3 V1.2 and LoRa32 V2.1 (V1.6 is the same).
|
||||
- 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.
|
||||
|
||||
- T-Deck Plus (and also regular T-Deck with/without GPS).
|
||||
|
||||
- HELTEC V2, V3, V3.2, T114, Wireless Stick, Wireless Stick Lite V3/V3.2, HT-CT62, Wireless Tracker, Wireless Paper.
|
||||
- HELTEC V2, V3, V3.2, V4, T114, Wireless Stick, Wireless Stick Lite V3/V3.2, HT-CT62, Wireless Tracker, Wireless Paper.
|
||||
|
||||
- RAK Wireless 4631 + 19007(or 19003)
|
||||
- RAK Wireless 4631 + 19007(or 19003), RAK3312 and RAK3112.
|
||||
|
||||
- Faketec V3 (NRF52840 + Heltec HTRA62(SX1262))
|
||||
|
||||
- QRP Labs LightGateway 1.0 and Plus 1.0.
|
||||
|
||||
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) 1W LoRa Module for a DIY Versions.
|
||||
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) or Ebyte E32 400M30S 1W LoRa Module for a DIY Versions.
|
||||
|
||||
- ESP32C3 + Ebyte 400M30S(or 900M30S) 1W LoRa Module for another DIY version.
|
||||
|
||||
@@ -57,9 +57,13 @@ ____________________________________________________
|
||||
|
||||
- Wemos Lolin32 Oled + SX1278 DIY Version.
|
||||
|
||||
- and many more...
|
||||
|
||||
<br />
|
||||
|
||||
# Timeline (Versions):
|
||||
- 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.
|
||||
- 2026-08-16 New UI (Web) and also Live View over a Map to see Rx stations.
|
||||
- 2026-08-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
|
||||
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ lib_deps =
|
||||
ESP32Async/AsyncTCP @ 3.4.10
|
||||
ESP32Async/ESPAsyncWebServer @ 3.11.1
|
||||
mikalhart/TinyGPSPlus @ 1.0.3
|
||||
richonguzman/APRSPacketLib @ 1.0.4
|
||||
richonguzman/APRSPacketLib @ 1.0.7
|
||||
display_libs =
|
||||
adafruit/Adafruit GFX Library @ 1.12.6
|
||||
adafruit/Adafruit SSD1306 @ 2.5.17
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
+19
-2
@@ -1520,7 +1520,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
<label
|
||||
for="tnc.enableSerial"
|
||||
class="form-label"
|
||||
>Enable Serial KISS</label
|
||||
>Enable Serial</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1552,7 +1552,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
<label
|
||||
for="tnc.acceptOwn"
|
||||
class="form-label"
|
||||
>Accept own frames via KISS</label
|
||||
>Accept own frames</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1572,6 +1572,23 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col-6">
|
||||
<label
|
||||
for="tnc.kissProtocol"
|
||||
class="form-label"
|
||||
>Protocol</label
|
||||
>
|
||||
<select
|
||||
class="form-select form-select"
|
||||
name="tnc.kissProtocol"
|
||||
id="tnc.kissProtocol"
|
||||
>
|
||||
<option value="KISS" selected>KISS</option>
|
||||
<option value="TNC2">TNC2</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+381
-11
@@ -96,7 +96,7 @@ function loadSettings(settings) {
|
||||
networksContainer.appendChild(networkElement);
|
||||
networkCount++;
|
||||
});
|
||||
document.getElementById("startupDelay").value = settings.startupDelay;
|
||||
document.getElementById("startupDelay").value = settings.other.startupDelay;
|
||||
|
||||
// APRS-IS
|
||||
document.getElementById("aprs_is.active").checked = settings.aprs_is.active;
|
||||
@@ -226,6 +226,7 @@ function loadSettings(settings) {
|
||||
document.getElementById("tnc.enableSerial").checked = settings.tnc.enableSerial;
|
||||
document.getElementById("tnc.acceptOwn").checked = settings.tnc.acceptOwn;
|
||||
document.getElementById("tnc.aprsBridgeActive").checked = settings.tnc.aprsBridgeActive;
|
||||
document.getElementById("tnc.kissProtocol").value = settings.tnc.kissProtocol ? "KISS" : "TNC2";
|
||||
}
|
||||
|
||||
// MQTT
|
||||
@@ -276,6 +277,8 @@ function loadSettings(settings) {
|
||||
document.getElementById("ntp.server").value = settings.ntp.server;
|
||||
document.getElementById("ntp.gmtCorrection").value = settings.ntp.gmtCorrection;
|
||||
|
||||
clampOutOfRangeInputs();
|
||||
|
||||
updateImage();
|
||||
}
|
||||
|
||||
@@ -490,7 +493,6 @@ function toggleWiFiAutoAPFields() {
|
||||
if (autoAPConfig) autoAPConfig.style.display = isEnabled ? 'block' : 'none';
|
||||
}
|
||||
|
||||
|
||||
document.querySelector(".new button").addEventListener("click", function () {
|
||||
const networksContainer = document.querySelector(".list-networks");
|
||||
|
||||
@@ -659,10 +661,274 @@ function setMapMessage(text) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* APRS symbol renderer for Stations Map
|
||||
*
|
||||
* s.symbol contains APRS table + symbol code, for example:
|
||||
* "/>" Primary table
|
||||
* "\\>" Alternate table
|
||||
* "L&" Alternate table with overlay
|
||||
*
|
||||
* Sprite layout:
|
||||
* aprs-symbols-24-0.png = Primary table
|
||||
* aprs-symbols-24-1.png = Alternate table
|
||||
* aprs-symbols-24-2.png = Overlay characters
|
||||
*/
|
||||
|
||||
function getAprsSymbolIcon(symbol, callsign = "") {
|
||||
|
||||
if (!symbol || String(symbol).length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = String(symbol);
|
||||
|
||||
let table = value.charAt(0);
|
||||
let code = value.charAt(1);
|
||||
let overlay = null;
|
||||
|
||||
/*
|
||||
* Standard APRS:
|
||||
* "/" = Primary
|
||||
* "\\" = Alternate
|
||||
*
|
||||
* A-Z / 0-9 / a-j in table position are overlays
|
||||
* for the Alternate table.
|
||||
*/
|
||||
if (
|
||||
table !== "/" &&
|
||||
table !== "\\"
|
||||
) {
|
||||
overlay = table;
|
||||
table = "\\";
|
||||
}
|
||||
|
||||
const ascii = code.charCodeAt(0);
|
||||
|
||||
/*
|
||||
* APRS printable symbol codes are ASCII 33..126.
|
||||
*/
|
||||
if (
|
||||
ascii < 33 ||
|
||||
ascii > 126
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = ascii - 33;
|
||||
|
||||
/*
|
||||
* Sprite sheet contains 16 columns x 6 rows.
|
||||
* Each cell occupies 24x24 CSS px; the
|
||||
* "background-size" below tells the browser to scale the sheet back
|
||||
* down to its original 24px-per-cell layout, so all position math
|
||||
* stays in 24px units regardless of source resolution.
|
||||
*/
|
||||
const columns = 16;
|
||||
const sheetSize = "384px 144px"; // 16*24 x 6*24, same for all 3 sheets
|
||||
|
||||
const col = index % columns;
|
||||
const row = Math.floor(index / columns);
|
||||
|
||||
const sprite =
|
||||
table === "/"
|
||||
? "/aprs-symbols-24-0.png"
|
||||
: "/aprs-symbols-24-1.png";
|
||||
|
||||
const x = -(col * 24);
|
||||
const y = -(row * 24);
|
||||
|
||||
let overlayHtml = "";
|
||||
|
||||
if (overlay) {
|
||||
|
||||
let displayOverlay = overlay;
|
||||
|
||||
/*
|
||||
* APRS compressed numeric overlay:
|
||||
* a-j -> 0-9
|
||||
*/
|
||||
if (
|
||||
overlay >= "a" &&
|
||||
overlay <= "j"
|
||||
) {
|
||||
displayOverlay =
|
||||
String(
|
||||
overlay.charCodeAt(0) -
|
||||
"a".charCodeAt(0)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* The overlay sprite sheet does NOT follow the ascii-33 layout used
|
||||
* by the primary/alternate tables above; it has its own grid:
|
||||
* "0" at row 0 col 15, "1".."9" at row 1 col 0-8, "A".."P" at row 2,
|
||||
* "Q".."Z" at row 3 col 0-9. Verified by hand against the PNG.
|
||||
*/
|
||||
let overlayCol = null;
|
||||
let overlayRow = null;
|
||||
|
||||
if (displayOverlay === "0") {
|
||||
overlayCol = 15;
|
||||
overlayRow = 0;
|
||||
} else if (displayOverlay >= "1" && displayOverlay <= "9") {
|
||||
overlayCol = displayOverlay.charCodeAt(0) - "1".charCodeAt(0);
|
||||
overlayRow = 1;
|
||||
} else if (displayOverlay >= "A" && displayOverlay <= "P") {
|
||||
overlayCol = displayOverlay.charCodeAt(0) - "A".charCodeAt(0);
|
||||
overlayRow = 2;
|
||||
} else if (displayOverlay >= "Q" && displayOverlay <= "Z") {
|
||||
overlayCol = displayOverlay.charCodeAt(0) - "Q".charCodeAt(0);
|
||||
overlayRow = 3;
|
||||
}
|
||||
|
||||
if (overlayCol !== null) {
|
||||
const ox = -(overlayCol * 24);
|
||||
const oy = -(overlayRow * 24);
|
||||
|
||||
overlayHtml = `
|
||||
<span style="
|
||||
position:absolute;
|
||||
left:0;
|
||||
top:0;
|
||||
width:24px;
|
||||
height:24px;
|
||||
background-image:url('/aprs-symbols-24-2.png');
|
||||
background-repeat:no-repeat;
|
||||
background-position:${ox}px ${oy}px;
|
||||
background-size:${sheetSize};
|
||||
pointer-events:none;
|
||||
"></span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
return L.divIcon({
|
||||
className: "",
|
||||
html: `
|
||||
<div style="
|
||||
position:relative;
|
||||
width:24px;
|
||||
height:24px;
|
||||
background-image:url('${sprite}');
|
||||
background-repeat:no-repeat;
|
||||
background-position:${x}px ${y}px;
|
||||
background-size:${sheetSize};
|
||||
">
|
||||
${overlayHtml}
|
||||
|
||||
<span style="
|
||||
position:absolute;
|
||||
left:27px;
|
||||
top:6px;
|
||||
font:600 10px Arial,sans-serif;
|
||||
line-height:12px;
|
||||
white-space:nowrap;
|
||||
color:#111;
|
||||
text-shadow:
|
||||
-1px -1px 0 #fff,
|
||||
1px -1px 0 #fff,
|
||||
-1px 1px 0 #fff,
|
||||
1px 1px 0 #fff;
|
||||
pointer-events:none;
|
||||
">${callsign}</span>
|
||||
</div>
|
||||
`,
|
||||
iconSize: [24, 24],
|
||||
iconAnchor: [12, 12],
|
||||
popupAnchor: [0, -12]
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let mapStationsCache = [];
|
||||
let mapRouteLayer = null;
|
||||
|
||||
function getUsedDigipeaters(path) {
|
||||
if (!path) return [];
|
||||
|
||||
return path
|
||||
.split(",")
|
||||
.map((x) => x.trim())
|
||||
.filter((x) => x.endsWith("*"))
|
||||
.map((x) => x.replace(/\*+$/, ""))
|
||||
.filter((x) =>
|
||||
x &&
|
||||
!x.startsWith("WIDE") &&
|
||||
!x.startsWith("TRACE") &&
|
||||
!x.startsWith("TCPIP") &&
|
||||
!x.startsWith("TCPXX") &&
|
||||
!x.startsWith("qA")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function describeMapPath(station) {
|
||||
const digis = getUsedDigipeaters(station.path);
|
||||
|
||||
// heardDirect viene del firmware: true si ALGUNA VEZ se oyo directo,
|
||||
// aunque el ultimo paquete haya llegado repetido.
|
||||
if (station.heardDirect && digis.length) {
|
||||
return "Direct+Via: " + digis.join(" → ");
|
||||
}
|
||||
|
||||
if (digis.length) {
|
||||
return "Via: " + digis.join(" → ");
|
||||
}
|
||||
|
||||
return "Direct";
|
||||
}
|
||||
|
||||
|
||||
function clearMapRoute() {
|
||||
if (mapRouteLayer && mapInstance) {
|
||||
mapInstance.removeLayer(mapRouteLayer);
|
||||
}
|
||||
mapRouteLayer = null;
|
||||
}
|
||||
|
||||
|
||||
function drawStationRoute(station) {
|
||||
if (!mapInstance || !iGateLatLng) return;
|
||||
|
||||
clearMapRoute();
|
||||
|
||||
const points = [
|
||||
L.latLng(station.lat, station.lon)
|
||||
];
|
||||
|
||||
const digis = getUsedDigipeaters(station.path);
|
||||
|
||||
digis.forEach((digiCall) => {
|
||||
const digi = mapStationsCache.find((s) =>
|
||||
String(s.callsign || "").toUpperCase() === digiCall.toUpperCase()
|
||||
);
|
||||
|
||||
if (digi && !(digi.lat === 0 && digi.lon === 0)) {
|
||||
points.push(L.latLng(digi.lat, digi.lon));
|
||||
}
|
||||
});
|
||||
|
||||
points.push(iGateLatLng);
|
||||
|
||||
const routeColor = digis.length > 0 ? "#2e7d32" : "#1565c0";
|
||||
|
||||
mapRouteLayer = L.polyline(points, {
|
||||
color: routeColor,
|
||||
weight: 3,
|
||||
opacity: 0.9
|
||||
}).addTo(mapInstance);
|
||||
}
|
||||
|
||||
|
||||
function loadMapStations(stations) {
|
||||
if (!mapMarkers) return;
|
||||
|
||||
mapStationsCache = stations || [];
|
||||
|
||||
mapMarkers.clearLayers();
|
||||
clearMapRoute();
|
||||
|
||||
let drawn = 0;
|
||||
(stations || []).forEach((s) => {
|
||||
@@ -671,15 +937,52 @@ function loadMapStations(stations) {
|
||||
const popup = `<b>${s.callsign}</b>`
|
||||
+ (s.lastHeard ? `<br>Last: ${s.lastHeard}` : "")
|
||||
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
|
||||
+ `<br>Packets: ${s.count}`;
|
||||
+ `<br>Packets: ${s.count}`
|
||||
+ `<br><b>${describeMapPath(s)}</b>`
|
||||
+ (s.path ? `<br><small>Path: ${s.path}</small>` : "");
|
||||
|
||||
const aprsIcon =
|
||||
getAprsSymbolIcon(s.symbol, s.callsign);
|
||||
|
||||
if (aprsIcon) {
|
||||
|
||||
const marker = L.marker(
|
||||
[s.lat, s.lon],
|
||||
{
|
||||
icon: aprsIcon
|
||||
}
|
||||
)
|
||||
.bindPopup(popup)
|
||||
.addTo(mapMarkers);
|
||||
|
||||
marker.on("mouseover", function () {
|
||||
drawStationRoute(s);
|
||||
});
|
||||
|
||||
marker.on("mouseout", function () {
|
||||
clearMapRoute();
|
||||
});
|
||||
|
||||
} else { // Fallback: station with missing or invalid APRS symbol
|
||||
const marker = L.circleMarker([s.lat, s.lon], {
|
||||
radius: 6,
|
||||
color: "#1d6fe0",
|
||||
weight: 2,
|
||||
fillColor: "#3b8cff",
|
||||
fillOpacity: 0.9
|
||||
})
|
||||
.bindPopup(popup)
|
||||
.addTo(mapMarkers);
|
||||
|
||||
marker.on("mouseover", function () {
|
||||
drawStationRoute(s);
|
||||
});
|
||||
|
||||
marker.on("mouseout", function () {
|
||||
clearMapRoute();
|
||||
});
|
||||
}
|
||||
|
||||
L.circleMarker([s.lat, s.lon], {
|
||||
radius: 6,
|
||||
color: "#1d6fe0", // borde
|
||||
weight: 2,
|
||||
fillColor: "#3b8cff", // relleno
|
||||
fillOpacity: 0.9
|
||||
}).bindPopup(popup).addTo(mapMarkers);
|
||||
drawn++;
|
||||
});
|
||||
|
||||
@@ -801,4 +1104,71 @@ window.showMap = function () {
|
||||
fetchMapStations();
|
||||
}
|
||||
}, 15000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------- keeping the form submittable
|
||||
* A control that fails HTML5 validation blocks the whole form, and when it sits in
|
||||
* a panel that is not the visible one the browser cannot show its bubble either:
|
||||
* pressing Save then does nothing at all -- no message, no request, no clue. Two
|
||||
* guards against that: pull stored values back inside the allowed range on load,
|
||||
* and, if validation still fails, open the section holding the offending control
|
||||
* and show what is wrong.
|
||||
*/
|
||||
function clampOutOfRangeInputs() {
|
||||
const fixed = [];
|
||||
|
||||
document.querySelectorAll("#configuration input[type=number]").forEach((el) => {
|
||||
if (el.disabled || el.value === "") return;
|
||||
|
||||
const value = parseFloat(el.value);
|
||||
if (isNaN(value)) return;
|
||||
|
||||
const min = el.min !== "" ? parseFloat(el.min) : null;
|
||||
const max = el.max !== "" ? parseFloat(el.max) : null;
|
||||
|
||||
let clamped = value;
|
||||
if (max !== null && value > max) clamped = max;
|
||||
if (min !== null && value < min) clamped = min;
|
||||
|
||||
if (clamped !== value) {
|
||||
el.value = clamped;
|
||||
fixed.push((el.name || el.id) + ": " + value + " → " + clamped);
|
||||
}
|
||||
});
|
||||
|
||||
if (fixed.length) {
|
||||
showToast("Stored values outside the allowed range, adjusted:<br>" + fixed.join("<br>")
|
||||
+ "<br><small>Save to keep them.</small>");
|
||||
}
|
||||
}
|
||||
|
||||
let invalidFieldReported = false;
|
||||
|
||||
form.addEventListener("invalid", function (event) {
|
||||
const el = event.target;
|
||||
|
||||
if (!invalidFieldReported) { // only the first one: the rest would pile up toasts
|
||||
invalidFieldReported = true;
|
||||
setTimeout(() => { invalidFieldReported = false; }, 500);
|
||||
|
||||
const section = el.closest(".panel-section");
|
||||
if (section) {
|
||||
const link = document.querySelector('.side-link[data-target="' + section.id + '"]');
|
||||
if (link) {
|
||||
link.click(); // reuses the sidebar switcher (also shows the config view)
|
||||
} else {
|
||||
document.querySelectorAll(".panel-section").forEach((s) => s.classList.remove("active"));
|
||||
section.classList.add("active");
|
||||
document.getElementById("configuration").classList.remove("d-none");
|
||||
}
|
||||
}
|
||||
|
||||
showToast("<b>" + (el.name || el.id) + "</b>: " + (el.validationMessage || "invalid value"));
|
||||
|
||||
setTimeout(() => {
|
||||
el.focus();
|
||||
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||
}, 0);
|
||||
}
|
||||
}, true);
|
||||
@@ -28,10 +28,9 @@ namespace APRS_IS_Utils {
|
||||
void connect();
|
||||
|
||||
void checkStatus();
|
||||
String checkForStartingBytes(const String& packet);
|
||||
|
||||
String buildPacketToUpload(const String& packet);
|
||||
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty);
|
||||
bool processReceivedLoRaMessage(const String& sender, const String& packet);
|
||||
void processLoRaPacket(const String& packet);
|
||||
|
||||
String buildPacketToTx(const String& aprsisPacket, uint8_t packetType);
|
||||
|
||||
@@ -132,6 +132,7 @@ public:
|
||||
bool enableSerial;
|
||||
bool acceptOwn;
|
||||
bool aprsBridgeActive;
|
||||
bool kissProtocol; // true = KISS, false = TNC2 -- applies to both Serial and TCP server
|
||||
};
|
||||
|
||||
class OTA {
|
||||
|
||||
@@ -24,10 +24,9 @@
|
||||
|
||||
namespace DIGI_Utils {
|
||||
|
||||
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq);
|
||||
String generateDigipeatedPacket(const String& packet, bool thirdParty);
|
||||
String buildPacket(const String& path, const String& packet, bool crossFreq);
|
||||
String generateDigipeatedPacket(const String& packet);
|
||||
void processLoRaPacket(const String& packet);
|
||||
void checkEcoMode();
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-6
@@ -25,13 +25,8 @@
|
||||
namespace GPS_Utils {
|
||||
|
||||
String getiGateLoRaBeaconPacket();
|
||||
char *ax25_base91enc(char *s, uint8_t n, uint32_t v);
|
||||
String encodeGPS(float latitude, float longitude, const String& overlay, const String& symbol);
|
||||
void generateBeaconFirstPart();
|
||||
void generateBeacons();
|
||||
String decodeEncodedGPS(const String& packet);
|
||||
String getReceivedGPS(const String& packet);
|
||||
String getDistanceAndComment(const String& packet);
|
||||
String buildDistanceAndComment(float latitude, float longitude, const String& comment);
|
||||
|
||||
void setup();
|
||||
void getData();
|
||||
|
||||
+3
-1
@@ -32,12 +32,14 @@ struct MapStation {
|
||||
float snr; // ultimo SNR
|
||||
uint16_t count; // nro de paquetes oidos de esa estacion
|
||||
char lastHeard[10]; // "HH:MM:SS" si hay NTP valido, "" si no
|
||||
char path[96]; // last path received
|
||||
uint32_t lastHeardMillis; // millis() -> ordenar / expirar (no se muestra)
|
||||
bool heardDirect; // true si ALGUNA VEZ se oyo sin digipeaters (no se resetea)
|
||||
};
|
||||
|
||||
namespace MAP_Utils {
|
||||
|
||||
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr);
|
||||
void upsert(const String& callsign, float latitude, float longitude, const String& path, const String& symbol, int rssi, float snr);
|
||||
String getStationsJson();
|
||||
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ public:
|
||||
// Ethernet methods
|
||||
bool ethernetConnect(eth_phy_type_t type, uint8_t phy_addr, uint8_t mdc, uint8_t mdio, int power, eth_clock_mode_t clock_mode, bool use_mac_from_efuse = false);
|
||||
bool setEthernetIP(const String& staticIP, const String& gateway, const String& subnet, const String& dns1, const String& dns2);
|
||||
bool ethernetDisconnect();
|
||||
IPAddress getEthernetIP() const;
|
||||
String getEthernetMACAddress() const;
|
||||
|
||||
@@ -97,4 +96,7 @@ public:
|
||||
bool isModemConnected() const;
|
||||
|
||||
bool isWifiAPActive() const;
|
||||
|
||||
// Check if any network interface was started (lwip TCP/IP stack is initialized)
|
||||
bool hasActiveInterface() const;
|
||||
};
|
||||
@@ -19,12 +19,16 @@
|
||||
#ifndef SYSLOG_H_
|
||||
#define SYSLOG_H_
|
||||
|
||||
#include <APRSPacketLib.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
namespace SYSLOG_Utils {
|
||||
|
||||
void log(const uint8_t type ,const String& packet, const int rssi, const float snr, const int freqError);
|
||||
void logLoRaRx(APRSPacket& aprsPacket, const String& packet, const int rssi, const float snr, const int freqError);
|
||||
void logAPRSISTx(const String& packet);
|
||||
void logLoRaTx(const String& packet);
|
||||
void logCRCError(const String& packet, const int rssi, const float snr, const int freqError);
|
||||
void setup();
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -19,9 +19,9 @@
|
||||
#ifndef UTILS_H_
|
||||
#define UTILS_H_
|
||||
|
||||
#include <APRSPacketLib.h>
|
||||
#include <Arduino.h>
|
||||
|
||||
|
||||
class ReceivedPacket {
|
||||
public:
|
||||
String rxTime;
|
||||
@@ -39,7 +39,8 @@ namespace Utils {
|
||||
void checkBeaconInterval();
|
||||
void checkDisplayInterval();
|
||||
void validateFreqs();
|
||||
void typeOfPacket(const String& packet, const uint8_t packetType);
|
||||
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType);
|
||||
void updateAPRSISPacketDisplayInfo(const String& packet);
|
||||
void print(const String& text);
|
||||
void println(const String& text);
|
||||
void checkRebootMode();
|
||||
|
||||
@@ -29,6 +29,9 @@ board_build.embed_files =
|
||||
data_embed/leaflet.css.gz
|
||||
data_embed/leaflet.js.gz
|
||||
data_embed/favicon.png.gz
|
||||
data_embed/aprs-symbols-24-0.png.gz
|
||||
data_embed/aprs-symbols-24-1.png.gz
|
||||
data_embed/aprs-symbols-24-2.png.gz
|
||||
extra_scripts =
|
||||
pre:tools/compress.py
|
||||
debug_tool = esp-prog
|
||||
@@ -68,8 +68,8 @@ ___________________________________________________________________*/
|
||||
#endif
|
||||
|
||||
|
||||
String versionDate = "2026-08-16";
|
||||
String versionNumber = "4.0.0";
|
||||
String versionDate = "2026-09-23";
|
||||
String versionNumber = "4.0.1";
|
||||
Configuration Config;
|
||||
WiFiClient aprsIsClient;
|
||||
WiFiClient mqttClient;
|
||||
|
||||
+23
-34
@@ -33,6 +33,7 @@
|
||||
|
||||
|
||||
extern Configuration Config;
|
||||
extern APRSPacket lastAprsPacket;
|
||||
extern NetworkManager *networkManager;
|
||||
extern WiFiClient aprsIsClient;
|
||||
extern uint32_t lastScreenOn;
|
||||
@@ -135,11 +136,6 @@ namespace APRS_IS_Utils {
|
||||
secondLine += aprsisState;
|
||||
}
|
||||
|
||||
String checkForStartingBytes(const String& packet) {
|
||||
int index = packet.indexOf("\x3c\xff\x01");
|
||||
return (index != -1) ? packet.substring(0, index) : packet;
|
||||
}
|
||||
|
||||
String buildPacketToUpload(const String& packet) {
|
||||
int colonIndex = packet.indexOf(":");
|
||||
String packetToUpload = packet.substring(3, colonIndex);
|
||||
@@ -149,11 +145,11 @@ namespace APRS_IS_Utils {
|
||||
packetToUpload += ",qAO,";
|
||||
}
|
||||
packetToUpload += Config.callsign;
|
||||
packetToUpload += checkForStartingBytes(packet.substring(colonIndex));
|
||||
packetToUpload += APRSPacketLib::checkForStartingBytes(packet.substring(colonIndex));
|
||||
return packetToUpload;
|
||||
}
|
||||
|
||||
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty) {
|
||||
bool processReceivedLoRaMessage(const String& sender, const String& packet) {
|
||||
String receivedMessage;
|
||||
int leftCurlyBraceIndex = packet.indexOf("{");
|
||||
int colonIndex = packet.indexOf(":");
|
||||
@@ -165,7 +161,7 @@ namespace APRS_IS_Utils {
|
||||
|
||||
String addToBuffer = Config.callsign;
|
||||
addToBuffer += ">APLRG1";
|
||||
if (!thirdParty) addToBuffer += ",RFONLY";
|
||||
if (lastAprsPacket.header == "") addToBuffer += ",RFONLY";
|
||||
if (Config.beacon.path != "") {
|
||||
addToBuffer += ",";
|
||||
addToBuffer += Config.beacon.path;
|
||||
@@ -186,51 +182,44 @@ namespace APRS_IS_Utils {
|
||||
receivedMessage = packet.substring(colonIndex + 1);
|
||||
}
|
||||
if (receivedMessage.indexOf("?") == 0) {
|
||||
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
|
||||
displayToggle(true);
|
||||
}
|
||||
STATION_Utils::addToOutputPacketBuffer(QUERY_Utils::process(receivedMessage, sender, false, thirdParty));
|
||||
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
|
||||
|
||||
STATION_Utils::addToOutputPacketBuffer(QUERY_Utils::process(receivedMessage, sender, false, lastAprsPacket.header != ""));
|
||||
lastScreenOn = millis();
|
||||
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + sender, "TYPE --> QUERY", 0);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void processLoRaPacket(const String& packet) {
|
||||
if (passcodeValid && (aprsIsClient.connected() || modemLoggedToAPRSIS)) {
|
||||
if (packet.indexOf("NOGATE") == -1 && packet.indexOf("RFONLY") == -1) {
|
||||
int firstColonIndex = packet.indexOf(":");
|
||||
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] != '}' && packet.indexOf("TCPIP") == -1) {
|
||||
const String& Sender = packet.substring(3, packet.indexOf(">"));
|
||||
if (Sender != Config.callsign && Utils::callsignIsValid(Sender)) {
|
||||
STATION_Utils::updateLastHeard(Sender);
|
||||
Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS
|
||||
int doubleColonIndex = packet.indexOf("::");
|
||||
const String& AddresseeAndMessage = packet.substring(doubleColonIndex + 2);
|
||||
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
if (lastAprsPacket.path.indexOf("NOGATE") == -1 && lastAprsPacket.path.indexOf("RFONLY") == -1) {
|
||||
if (lastAprsPacket.header == "" && lastAprsPacket.path.indexOf("TCPIP") == -1) {
|
||||
if (lastAprsPacket.sender != Config.callsign && Utils::callsignIsValid(lastAprsPacket.sender)) {
|
||||
STATION_Utils::updateLastHeard(lastAprsPacket.sender);
|
||||
Utils::updateLoRaPacketDisplayInfo(lastAprsPacket, 0); // LoRa-APRS
|
||||
bool queryMessage = false;
|
||||
if (doubleColonIndex > 10 && Addressee == Config.callsign) { // its a message for me!
|
||||
queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false);
|
||||
if (lastAprsPacket.type == 1 && lastAprsPacket.addressee == Config.callsign) { // its a message for me!
|
||||
String AddresseeAndMessage = lastAprsPacket.addressee + ":" + lastAprsPacket.payload;
|
||||
queryMessage = processReceivedLoRaMessage(lastAprsPacket.sender, APRSPacketLib::checkForStartingBytes(AddresseeAndMessage));
|
||||
}
|
||||
if (queryMessage) return;
|
||||
|
||||
const String& aprsPacket = buildPacketToUpload(packet);
|
||||
const String& aprsPacketToUpload = buildPacketToUpload(packet);
|
||||
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
|
||||
displayToggle(true);
|
||||
}
|
||||
lastScreenOn = millis();
|
||||
#ifdef HAS_A7670
|
||||
stationBeacon = true;
|
||||
A7670_Utils::uploadToAPRSIS(aprsPacket);
|
||||
A7670_Utils::uploadToAPRSIS(aprsPacketToUpload);
|
||||
stationBeacon = false;
|
||||
#else
|
||||
upload(aprsPacket);
|
||||
upload(aprsPacketToUpload);
|
||||
#endif
|
||||
Utils::println("---> Uploaded to APRS-IS");
|
||||
Utils::println("(Uploaded to APRS-IS)");
|
||||
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
@@ -351,7 +340,7 @@ namespace APRS_IS_Utils {
|
||||
#else
|
||||
upload(queryAnswer);
|
||||
#endif
|
||||
SYSLOG_Utils::log(2, queryAnswer, 0, 0.0, 0); // APRSIS TX
|
||||
SYSLOG_Utils::logAPRSISTx(queryAnswer);
|
||||
fifthLine = "APRS-IS ----> APRS-IS";
|
||||
sixthLine = Config.callsign;
|
||||
for (int j = sixthLine.length();j < 9;j++) {
|
||||
@@ -369,7 +358,7 @@ namespace APRS_IS_Utils {
|
||||
STATION_Utils::addToOutputPacketBuffer(buildPacketToTx(packet, 1));
|
||||
displayToggle(true);
|
||||
lastScreenOn = currentTime;
|
||||
Utils::typeOfPacket(packet, 1); // APRS-LoRa
|
||||
Utils::updateAPRSISPacketDisplayInfo(packet); // APRS-LoRa
|
||||
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
|
||||
}
|
||||
}
|
||||
@@ -379,7 +368,7 @@ namespace APRS_IS_Utils {
|
||||
STATION_Utils::addToOutputPacketBuffer(buildPacketToTx(packet, 5));
|
||||
displayToggle(true);
|
||||
lastScreenOn = currentTime;
|
||||
Utils::typeOfPacket(packet, 1); // APRS-LoRa
|
||||
Utils::updateAPRSISPacketDisplayInfo(packet); // APRS-LoRa
|
||||
Serial.println();
|
||||
} else {
|
||||
Serial.println(" ---> Rejected (Time): No Tx");
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace BATTERY_Utils {
|
||||
|
||||
int sampleSum = 0;
|
||||
for (int i = 0; i < adcReadings; i++) {
|
||||
#if defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_LoRa_915) || defined(ESP32_DIY_1W_LoRa) || defined(ESP32_DIY_1W_LoRa_915)
|
||||
#if defined(ESP32_DIY_LoRa_SX1278) || defined(ESP32_DIY_LoRa_915_SX1276) || defined(ESP32_DIY_1W_LoRa_E22_400M30S) || defined(ESP32_DIY_1W_LoRa_915_E22_900M30S) || defined(ESP32_DIY_1W_LoRa_E32_400M30S)
|
||||
sampleSum = 0;
|
||||
#else
|
||||
#ifdef HAS_ADC_CALIBRATION
|
||||
@@ -203,6 +203,10 @@ namespace BATTERY_Utils {
|
||||
#ifdef LIGHTGATEWAY_PLUS_1_0
|
||||
double inputDivider = (1.0 / (560.0 + 100.0)) * 100.0; // The voltage divider is a 560k + 100k resistor in series, 100k on the low side.
|
||||
return (((sampleSum/adcReadings) * adcReadingTransformation) / inputDivider) + 0.41;
|
||||
#elif defined(RAK3112)
|
||||
return (sampleSum/adcReadings) * adcReadingTransformation * (3 * 1.73 * 1.187 / 3.3); // multiplier from MeshCore RAK3112Board.h
|
||||
#elif defined(RAK3312)
|
||||
return (sampleSum/adcReadings) * adcReadingTransformation * (3 * 1.73 * 1.187 / 3.3) * (1.667 / 2.11); // RAK3112 value scaled by Meshtastic RAK3312/RAK3112 ratio
|
||||
#else
|
||||
return (2 * (sampleSum/adcReadings) * adcReadingTransformation) + voltageDividerCorrection; // raw voltage without mapping
|
||||
#endif
|
||||
|
||||
@@ -157,6 +157,7 @@ bool Configuration::writeFile() {
|
||||
data["tnc"]["enableSerial"] = tnc.enableSerial;
|
||||
data["tnc"]["acceptOwn"] = tnc.acceptOwn;
|
||||
data["tnc"]["aprsBridgeActive"] = tnc.aprsBridgeActive;
|
||||
data["tnc"]["kissProtocol"] = tnc.kissProtocol;
|
||||
|
||||
data["mqtt"]["active"] = mqtt.active;
|
||||
data["mqtt"]["server"] = mqtt.server;
|
||||
@@ -369,11 +370,13 @@ bool Configuration::readFile() {
|
||||
if (data["tnc"]["enableServer"].isNull() ||
|
||||
data["tnc"]["enableSerial"].isNull() ||
|
||||
data["tnc"]["acceptOwn"].isNull() ||
|
||||
data["tnc"]["aprsBridgeActive"].isNull()) needsRewrite = true;
|
||||
data["tnc"]["aprsBridgeActive"].isNull() ||
|
||||
data["tnc"]["kissProtocol"].isNull()) needsRewrite = true;
|
||||
tnc.enableServer = data["tnc"]["enableServer"] | false;
|
||||
tnc.enableSerial = data["tnc"]["enableSerial"] | false;
|
||||
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
|
||||
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
|
||||
tnc.kissProtocol = data["tnc"]["kissProtocol"] | true;
|
||||
|
||||
if (data["mqtt"]["active"].isNull() ||
|
||||
data["mqtt"]["server"].isNull() ||
|
||||
@@ -538,6 +541,7 @@ void Configuration::setDefaultValues() {
|
||||
tnc.enableSerial = false;
|
||||
tnc.acceptOwn = false;
|
||||
tnc.aprsBridgeActive = false;
|
||||
tnc.kissProtocol = true;
|
||||
|
||||
mqtt.active = false;
|
||||
mqtt.server = "";
|
||||
|
||||
+56
-47
@@ -16,6 +16,7 @@
|
||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <APRSPacketLib.h>
|
||||
#include <WiFi.h>
|
||||
#include "configuration.h"
|
||||
#include "station_utils.h"
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
extern Configuration Config;
|
||||
extern uint32_t lastScreenOn;
|
||||
extern APRSPacket lastAprsPacket;
|
||||
extern String iGateBeaconPacket;
|
||||
extern String firstLine;
|
||||
extern String secondLine;
|
||||
@@ -42,13 +44,32 @@ extern bool backupDigiMode;
|
||||
|
||||
namespace DIGI_Utils {
|
||||
|
||||
String cleanPath(String path) {
|
||||
String terms[] = {"WIDE1*,", "WIDE2*,", "*"};
|
||||
for (String term : terms) {
|
||||
int index = path.indexOf(term);
|
||||
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
|
||||
String cleanPath(const String& path) {
|
||||
String result;
|
||||
unsigned int start = 0;
|
||||
while (true) {
|
||||
int delim = path.indexOf(',', start);
|
||||
int end = (delim == -1) ? path.length() : delim;
|
||||
String token = path.substring(start, end);
|
||||
if (token != "WIDE1*" && token != "WIDE2*") {
|
||||
if (result.length() > 0) result += ",";
|
||||
result += token;
|
||||
}
|
||||
if (delim == -1) break;
|
||||
start = delim + 1;
|
||||
}
|
||||
return path;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int pathTokenIndex(const String& path, const String& token) {
|
||||
unsigned int start = 0;
|
||||
while (start < path.length()) {
|
||||
int end = path.indexOf(",", start);
|
||||
if (end == -1) end = path.length();
|
||||
if (path.substring(start, end) == token) return start;
|
||||
start = end + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
String processMode3Path(const String& path, const String& stationCallsign) {
|
||||
@@ -80,9 +101,9 @@ namespace DIGI_Utils {
|
||||
return tempPacket + "*" + path.substring(ownTokenEnd);
|
||||
}
|
||||
|
||||
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
|
||||
String buildPacket(const String& path, const String& packet, bool crossFreq) {
|
||||
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||
String suffix = thirdParty ? ":}" : ":";
|
||||
String suffix = (lastAprsPacket.header != "") ? ":}" : ":";
|
||||
int suffixIndex = packet.indexOf(suffix);
|
||||
String packetToRepeat;
|
||||
if (!crossFreq) {
|
||||
@@ -94,12 +115,13 @@ namespace DIGI_Utils {
|
||||
tempPath.replace("WIDE1-1", stationCallsign + "*");
|
||||
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { // WIDE2-n Digipeater
|
||||
tempPath = cleanPath(path);
|
||||
if (tempPath.indexOf("WIDE2-1") != -1) {
|
||||
tempPath.replace("WIDE2-1", stationCallsign + "*");
|
||||
} else if (tempPath.indexOf("WIDE2-2") != -1) {
|
||||
tempPath.replace("WIDE2-2", stationCallsign + "*,WIDE2-1");
|
||||
int idx = pathTokenIndex(tempPath, "WIDE2-1");
|
||||
if (idx != -1) {
|
||||
tempPath = tempPath.substring(0, idx) + stationCallsign + "*" + tempPath.substring(idx + 7);
|
||||
} else {
|
||||
return "";
|
||||
idx = pathTokenIndex(tempPath, "WIDE2-2");
|
||||
if (idx == -1) return "";
|
||||
tempPath = tempPath.substring(0, idx) + stationCallsign + "*,WIDE2-1" + tempPath.substring(idx + 7);
|
||||
}
|
||||
} else if (digiMode == 3) { // Repeat if station callsign is in path (free to repeat).
|
||||
tempPath = processMode3Path(tempPath, stationCallsign);
|
||||
@@ -114,13 +136,13 @@ namespace DIGI_Utils {
|
||||
packetToRepeat += stationCallsign;
|
||||
packetToRepeat += "*";
|
||||
}
|
||||
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(suffixIndex));
|
||||
packetToRepeat += APRSPacketLib::checkForStartingBytes(packet.substring(suffixIndex));
|
||||
return packetToRepeat;
|
||||
}
|
||||
|
||||
String generateDigipeatedPacket(const String& packet, bool thirdParty){
|
||||
String generateDigipeatedPacket(const String& packet){
|
||||
String temp;
|
||||
if (thirdParty) { // only header is used
|
||||
if (lastAprsPacket.header != "") { // thirdparty : only header is used
|
||||
const String& header = packet.substring(0, packet.indexOf(":}"));
|
||||
temp = header.substring(header.indexOf(">") + 1);
|
||||
} else {
|
||||
@@ -135,7 +157,7 @@ namespace DIGI_Utils {
|
||||
if (digiMode == 1 || backupDigiMode) {
|
||||
bool hasWide = path.indexOf("WIDE1-1") != -1;
|
||||
if (hasWide || crossFreq) {
|
||||
return buildPacket(path, packet, thirdParty, !hasWide);
|
||||
return buildPacket(path, packet, !hasWide);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@@ -147,62 +169,49 @@ namespace DIGI_Utils {
|
||||
|
||||
if (hasWide1 && hasWide2 && wide2Index < wide1Index) return ""; // check that WIDE1 before WIDE2
|
||||
|
||||
if (hasWide1 || hasWide2) return buildPacket(path, packet, thirdParty, false); // regular APRS with WIDEn-N
|
||||
if (hasWide1 || hasWide2) return buildPacket(path, packet, false); // regular APRS with WIDEn-N
|
||||
|
||||
if (crossFreq) return buildPacket(path, packet, thirdParty, true); // CrossFreq (without WIDE)
|
||||
if (crossFreq) return buildPacket(path, packet, true); // CrossFreq (without WIDE)
|
||||
|
||||
return "";
|
||||
}
|
||||
if (digiMode == 3) {
|
||||
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||
bool containsOwnCall = path.indexOf(stationCallsign) != -1;
|
||||
if (containsOwnCall) return buildPacket(path, packet, thirdParty, false);
|
||||
if (containsOwnCall) return buildPacket(path, packet, false);
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
|
||||
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, true); // no "path" but is CrossFreq Digi
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void processLoRaPacket(const String& packet) {
|
||||
if (packet.indexOf("NOGATE") >= 0) return;
|
||||
if (lastAprsPacket.path.indexOf("NOGATE") >= 0) return;
|
||||
|
||||
bool thirdPartyPacket = false;
|
||||
String temp, Sender;
|
||||
int firstColonIndex = packet.indexOf(":");
|
||||
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] == '}' && packet.indexOf("TCPIP") > 0) { // 3rd Party
|
||||
thirdPartyPacket = true;
|
||||
temp = packet.substring(packet.indexOf(":}") + 2);
|
||||
Sender = temp.substring(0, temp.indexOf(">"));
|
||||
} else {
|
||||
temp = packet.substring(3);
|
||||
Sender = packet.substring(3, packet.indexOf(">"));
|
||||
}
|
||||
String temp = (lastAprsPacket.header != "") ? packet.substring(packet.indexOf(":}") + 2) : packet.substring(3);
|
||||
|
||||
String stationCallsign = Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign;
|
||||
if (Sender == stationCallsign) return; // Avoid listening to self packets
|
||||
if (!thirdPartyPacket && Config.tacticalCallsign == "" && !Utils::callsignIsValid(Sender)) return; // No thirdParty + no tactical y no valid callsign
|
||||
if (lastAprsPacket.sender == stationCallsign) return; // Avoid listening to self packets
|
||||
if (lastAprsPacket.header == "" && Config.tacticalCallsign == "" && !Utils::callsignIsValid(lastAprsPacket.sender)) return; // No thirdParty + no tactical y no valid callsign
|
||||
|
||||
if (STATION_Utils::isIn25SegHashBuffer(Sender, temp.substring(temp.indexOf(":") + 2))) return;
|
||||
if (STATION_Utils::isIn25SegHashBuffer(lastAprsPacket.sender, temp.substring(temp.indexOf(":") + 2))) return;
|
||||
|
||||
STATION_Utils::updateLastHeard(Sender);
|
||||
Utils::typeOfPacket(temp, 2); // Digi
|
||||
bool queryMessage = false;
|
||||
int doubleColonIndex = temp.indexOf("::");
|
||||
if (doubleColonIndex > 10) { // it's a message
|
||||
String AddresseeAndMessage = temp.substring(doubleColonIndex + 2);
|
||||
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
|
||||
Addressee.trim();
|
||||
if (Addressee == stationCallsign) { // it's a message for me!
|
||||
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
|
||||
STATION_Utils::updateLastHeard(lastAprsPacket.sender);
|
||||
Utils::updateLoRaPacketDisplayInfo(lastAprsPacket, 1); // Digi
|
||||
bool queryMessage = false;
|
||||
if (lastAprsPacket.type == 1) { // MESSAGE
|
||||
if (lastAprsPacket.addressee == stationCallsign) { // it's a message for me!
|
||||
String AddresseeAndMessage = lastAprsPacket.addressee + ":" + lastAprsPacket.payload;
|
||||
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(lastAprsPacket.sender, APRSPacketLib::checkForStartingBytes(AddresseeAndMessage));
|
||||
}
|
||||
}
|
||||
if (queryMessage) return; // answer should not be repeated.
|
||||
|
||||
String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket);
|
||||
String loraPacket = generateDigipeatedPacket(packet.substring(3));
|
||||
if (loraPacket != "") {
|
||||
STATION_Utils::addToOutputPacketBuffer(loraPacket);
|
||||
if (Config.digi.ecoMode != 1) displayToggle(true);
|
||||
|
||||
+10
-7
@@ -57,19 +57,20 @@
|
||||
String lastEpaperText;
|
||||
#else
|
||||
#include <Adafruit_GFX.h>
|
||||
#if defined RPC_LORA_DIGIGATE_1W
|
||||
#define SCREEN_HEIGHT 32
|
||||
#else
|
||||
#define SCREEN_HEIGHT 64
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SH1106
|
||||
#include <Adafruit_SH110X.h>
|
||||
Adafruit_SH1106G display(128, 64, &Wire, OLED_RST);
|
||||
Adafruit_SH1106G display(128, SCREEN_HEIGHT, &Wire, OLED_RST);
|
||||
#else
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#ifdef HELTEC_WSL_V3_DISPLAY
|
||||
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST);
|
||||
Adafruit_SSD1306 display(128, SCREEN_HEIGHT, &Wire1, OLED_RST);
|
||||
#else
|
||||
#if defined RPC_LORA_DIGIGATE_1W
|
||||
#define SCREEN_HEIGHT 32
|
||||
#else
|
||||
#define SCREEN_HEIGHT 64
|
||||
#endif
|
||||
Adafruit_SSD1306 display(128, SCREEN_HEIGHT, &Wire, OLED_RST);
|
||||
#endif
|
||||
#endif
|
||||
@@ -107,6 +108,8 @@ void displaySetup() {
|
||||
maxLines = 6;
|
||||
#elif (SCREEN_HEIGHT == 32)
|
||||
maxLines = 3;
|
||||
#else
|
||||
maxLines = 6;
|
||||
#endif
|
||||
#ifdef HAS_EPAPER
|
||||
display.landscape();
|
||||
|
||||
@@ -101,100 +101,6 @@ namespace GPS_Utils {
|
||||
|
||||
}
|
||||
|
||||
String decodeEncodedGPS(const String& packet) {
|
||||
int indexOfExclamation = packet.indexOf(":!");
|
||||
int indexOfEqual = packet.indexOf(":=");
|
||||
|
||||
const uint8_t OFFSET = 3; // Offset for encoded data in the packet
|
||||
String infoGPS;
|
||||
if (indexOfExclamation > 10) {
|
||||
infoGPS = packet.substring(indexOfExclamation + OFFSET);
|
||||
} else if (indexOfEqual > 10) {
|
||||
infoGPS = packet.substring(indexOfEqual + OFFSET);
|
||||
}
|
||||
|
||||
float decodedLatitude = APRSPacketLib::decodeBase91EncodedLatitude(infoGPS.substring(0,4));
|
||||
float decodedLongitude = APRSPacketLib::decodeBase91EncodedLongitude(infoGPS.substring(4,8));
|
||||
|
||||
return buildDistanceAndComment(decodedLatitude, decodedLongitude, infoGPS.substring(12));
|
||||
}
|
||||
|
||||
String getReceivedGPS(const String& packet) {
|
||||
int indexOfExclamation = packet.indexOf(":!");
|
||||
int indexOfEqual = packet.indexOf(":=");
|
||||
int indexOfAt = packet.indexOf(":@");
|
||||
|
||||
String infoGPS;
|
||||
if (indexOfExclamation > 10) {
|
||||
infoGPS = packet.substring(indexOfExclamation + 2);
|
||||
} else if (indexOfEqual > 10) {
|
||||
infoGPS = packet.substring(indexOfEqual + 2);
|
||||
} else if (indexOfAt > 10) {
|
||||
infoGPS = packet.substring(indexOfAt + 9); // 9 = 2+7 (when 7 is timestamp characters)
|
||||
}
|
||||
|
||||
String Latitude = infoGPS.substring(0,8); // First 8 characters are Latitude
|
||||
int latitudeColonIndex = Latitude.indexOf(".");
|
||||
float convertedLatitude = Latitude.substring(0,2).toFloat(); // First 2 digits (Degrees)
|
||||
convertedLatitude += Latitude.substring(2,4).toFloat() / 60; // Next 2 digits (Minutes)
|
||||
convertedLatitude += Latitude.substring(latitudeColonIndex + 1, latitudeColonIndex + 3).toFloat() / (60*100);
|
||||
if (Latitude.endsWith("S")) convertedLatitude = -convertedLatitude; // Handle Southern Hemisphere
|
||||
|
||||
String Longitude = infoGPS.substring(9,18); // Next 9 characters are Longitude
|
||||
int longitudeColonIndex = Longitude.indexOf(".");
|
||||
float convertedLongitude = Longitude.substring(0,3).toFloat(); // First 3 digits (Degrees)
|
||||
convertedLongitude += Longitude.substring(3,5).toFloat() / 60; // Next 2 digits (Minutes)
|
||||
convertedLongitude += Longitude.substring(longitudeColonIndex + 1, longitudeColonIndex + 3).toFloat() / (60*100);
|
||||
if (Longitude.endsWith("W")) convertedLongitude = -convertedLongitude; // Handle Western Hemisphere
|
||||
|
||||
return buildDistanceAndComment(convertedLatitude, convertedLongitude, infoGPS.substring(19));
|
||||
}
|
||||
|
||||
String getDistanceAndComment(const String& packet) {
|
||||
int indexOfAt = packet.indexOf(":@");
|
||||
if (indexOfAt > 10) return getReceivedGPS(packet);
|
||||
|
||||
const uint8_t nonEncondedLatitudeOffset = 9; // "N" / "S"
|
||||
const uint8_t nonEncondedLongitudeOffset = 19; // "E" / "W"
|
||||
const uint8_t encodedByteOffset = 14;
|
||||
|
||||
int indexOfExclamation = packet.indexOf(":!");
|
||||
int indexOfEqual = packet.indexOf(":=");
|
||||
int baseIndex = - 1;
|
||||
if (indexOfExclamation > 10) {
|
||||
baseIndex = indexOfExclamation;
|
||||
} else if (indexOfEqual > 10) {
|
||||
baseIndex = indexOfEqual;
|
||||
}
|
||||
if (baseIndex == -1) return " _ / _ / _ ";
|
||||
|
||||
int latitudeIndex = baseIndex + nonEncondedLatitudeOffset;
|
||||
int longitudeIndex = baseIndex + nonEncondedLongitudeOffset;
|
||||
int encodedByteIndex = baseIndex + encodedByteOffset;
|
||||
int packetLength = packet.length();
|
||||
|
||||
if (latitudeIndex < packetLength && longitudeIndex < packetLength) {
|
||||
char latChar = packet[latitudeIndex];
|
||||
char lngChar = packet[longitudeIndex];
|
||||
if ((latChar == 'N' || latChar == 'S') && (lngChar == 'E' || lngChar == 'W')) return getReceivedGPS(packet);
|
||||
}
|
||||
if (encodedByteIndex < packetLength) {
|
||||
char byteChar = packet[encodedByteIndex];
|
||||
if (byteChar == 'G' || byteChar == 'Q' || byteChar == '[' || byteChar == 'H' || byteChar == 'X' || byteChar == '3') return decodeEncodedGPS(packet);
|
||||
}
|
||||
return " _ / _ / _ ";
|
||||
}
|
||||
|
||||
/*String getDistanceAndCommentFromMicE(const String& packet) {
|
||||
int indexOfMicE1 = packet.indexOf(":`");
|
||||
int indexOfMicE2 = packet.indexOf(":\'");
|
||||
|
||||
if (indexOfMicE1 > 10 || indexOfMicE2 > 10) {
|
||||
return decodeEncodedGPS(packet);
|
||||
}
|
||||
return " _ / _ / _ ";
|
||||
}*/
|
||||
|
||||
void setup() {
|
||||
#ifdef HAS_GPS
|
||||
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) {
|
||||
|
||||
+18
-12
@@ -65,6 +65,7 @@ int backoffMax = 4; // Max Backoff value (number of CAD slots to wai
|
||||
|
||||
int rssi, freqError;
|
||||
float snr;
|
||||
APRSPacket lastAprsPacket;
|
||||
|
||||
|
||||
namespace LoRa_Utils {
|
||||
@@ -90,10 +91,17 @@ namespace LoRa_Utils {
|
||||
#else
|
||||
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
|
||||
#endif
|
||||
#ifdef RADIO_ANT_SW_PIN // RAK3312 antenna switch needs power
|
||||
pinMode(RADIO_ANT_SW_PIN, OUTPUT);
|
||||
digitalWrite(RADIO_ANT_SW_PIN, RADIO_ANT_SW_ON_STATE);
|
||||
#endif
|
||||
float freq = (float)Config.loramodule.rxFreq / 1000000;
|
||||
#if defined(RADIO_HAS_XTAL)
|
||||
radio.XTAL = true;
|
||||
#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);
|
||||
#endif
|
||||
int state = radio.begin(freq);
|
||||
if (state != RADIOLIB_ERR_NONE) {
|
||||
Utils::println("Starting LoRa failed! State: " + String(state));
|
||||
@@ -121,10 +129,6 @@ namespace LoRa_Utils {
|
||||
radio.setBandwidth(signalBandwidth);
|
||||
radio.setCRC(true);
|
||||
|
||||
#if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // QRP Labs LightGateway has 400M22S (SX1268)
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
|
||||
#endif
|
||||
|
||||
/*#ifdef SX126X_DIO2_AS_RF_SWITCH
|
||||
radio.setRfSwitchPins(RADIO_RXEN, RADIOLIB_NC);
|
||||
radio.setDio2AsRfSwitch(true);
|
||||
@@ -230,7 +234,7 @@ namespace LoRa_Utils {
|
||||
transmitFlag = true;
|
||||
if (state == RADIOLIB_ERR_NONE) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
|
||||
SYSLOG_Utils::logLoRaTx(newPacket);
|
||||
}
|
||||
Utils::print("---> LoRa Packet Tx : ");
|
||||
Utils::println(newPacket);
|
||||
@@ -276,9 +280,9 @@ namespace LoRa_Utils {
|
||||
rssi = radio.getRSSI();
|
||||
snr = radio.getSNR();
|
||||
freqError = radio.getFrequencyError();
|
||||
Utils::println("<--- LoRa Packet Rx : " + packet.substring(3));
|
||||
Utils::println("(RSSI:" + String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
|
||||
Utils::println("<--- LoRa Packet Rx (RSSI:" + String(rssi) + " | SNR:" + String(snr) + " | FreqErr:" + String(freqError) + ") : " + packet.substring(3));
|
||||
|
||||
lastAprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
|
||||
if (Config.digi.ecoMode == 0) {
|
||||
if (receivedPackets.size() >= 10) {
|
||||
receivedPackets.erase(receivedPackets.begin());
|
||||
@@ -290,14 +294,13 @@ namespace LoRa_Utils {
|
||||
receivedPacket.SNR = snr;
|
||||
receivedPackets.push_back(receivedPacket);
|
||||
|
||||
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
|
||||
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (los que traen posición)
|
||||
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.rssi, aprsPacket.snr);
|
||||
if (lastAprsPacket.type == 0 || lastAprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (only ones with position)
|
||||
MAP_Utils::upsert(lastAprsPacket.sender, lastAprsPacket.latitude, lastAprsPacket.longitude, lastAprsPacket.path, lastAprsPacket.overlay + lastAprsPacket.symbol, lastAprsPacket.rssi, lastAprsPacket.snr);
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
|
||||
SYSLOG_Utils::logLoRaRx(lastAprsPacket, packet, rssi, snr, freqError); // RX
|
||||
}
|
||||
} else {
|
||||
packet = "";
|
||||
@@ -310,7 +313,7 @@ namespace LoRa_Utils {
|
||||
freqError = radio.getFrequencyError();
|
||||
Utils::println(F("CRC error!"));
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
|
||||
SYSLOG_Utils::logCRCError(packet, rssi, snr, freqError);
|
||||
}
|
||||
packet = "";
|
||||
} else {
|
||||
@@ -329,6 +332,9 @@ namespace LoRa_Utils {
|
||||
|
||||
void sleepRadio() {
|
||||
radio.sleep();
|
||||
#ifdef RADIO_ANT_SW_PIN
|
||||
digitalWrite(RADIO_ANT_SW_PIN, !RADIO_ANT_SW_ON_STATE);
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
+11
-9
@@ -29,9 +29,7 @@ std::vector<MapStation> mapStations;
|
||||
|
||||
namespace MAP_Utils {
|
||||
|
||||
// Copia la hora NTP "HH:MM:SS" si es valida; si no hay red (NTP devuelve
|
||||
// un texto de fallback) deja el campo vacio para no guardar hora falsa.
|
||||
void writeFormatedTime(char* dst, size_t size) {
|
||||
void writeFormatedTime(char* dst, size_t size) { // gets NTP time ("HH:MM:SS") and writes it to dst if valid
|
||||
String t = NTP_Utils::getFormatedTime();
|
||||
if (t.length() == 8 && t.charAt(2) == ':') {
|
||||
t.toCharArray(dst, size);
|
||||
@@ -40,10 +38,8 @@ namespace MAP_Utils {
|
||||
}
|
||||
}
|
||||
|
||||
// Inserta una estacion nueva o actualiza la existente (dedup por callsign).
|
||||
// Llamar solo cuando el parser ya decodifico una posicion valida.
|
||||
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr) {
|
||||
for (auto &s : mapStations) { // 1) ya existe -> actualizar
|
||||
void upsert(const String& callsign, float latitude, float longitude, const String& path, const String& symbol, int rssi, float snr) { // Upsert station only with valid position with callsign dedup.
|
||||
for (auto &s : mapStations) { // 1) update
|
||||
if (callsign.equals(s.callsign)) {
|
||||
s.latitude = latitude;
|
||||
s.longitude = longitude;
|
||||
@@ -51,13 +47,15 @@ namespace MAP_Utils {
|
||||
s.snr = snr;
|
||||
s.count++;
|
||||
symbol.toCharArray(s.symbol, sizeof(s.symbol));
|
||||
path.toCharArray(s.path, sizeof(s.path));
|
||||
if (path.indexOf('*') == -1) s.heardDirect = true; // se oyo directo alguna vez; no se resetea si despues llega repetido
|
||||
writeFormatedTime(s.lastHeard, sizeof(s.lastHeard));
|
||||
s.lastHeardMillis = millis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mapStations.size() >= MAX_MAP_STATIONS) { // 2) llena -> descartar la mas vieja
|
||||
if (mapStations.size() >= MAX_MAP_STATIONS) { // 2) full? delete oldest one
|
||||
size_t oldest = 0;
|
||||
for (size_t i = 1; i < mapStations.size(); i++) {
|
||||
if (mapStations[i].lastHeardMillis < mapStations[oldest].lastHeardMillis) {
|
||||
@@ -67,14 +65,16 @@ namespace MAP_Utils {
|
||||
mapStations.erase(mapStations.begin() + oldest);
|
||||
}
|
||||
|
||||
MapStation st = {}; // 3) insertar nueva
|
||||
MapStation st = {}; // 3) insert new
|
||||
callsign.toCharArray(st.callsign, sizeof(st.callsign));
|
||||
symbol.toCharArray(st.symbol, sizeof(st.symbol));
|
||||
path.toCharArray(st.path, sizeof(st.path));
|
||||
st.latitude = latitude;
|
||||
st.longitude = longitude;
|
||||
st.rssi = rssi;
|
||||
st.snr = snr;
|
||||
st.count = 1;
|
||||
st.heardDirect = (path.indexOf('*') == -1); // primer paquete: directo si no trae digis usados
|
||||
writeFormatedTime(st.lastHeard, sizeof(st.lastHeard));
|
||||
st.lastHeardMillis = millis();
|
||||
mapStations.push_back(st);
|
||||
@@ -102,6 +102,8 @@ namespace MAP_Utils {
|
||||
data[i]["SNR"] = mapStations[i].snr;
|
||||
data[i]["count"] = mapStations[i].count;
|
||||
data[i]["lastHeard"] = mapStations[i].lastHeard;
|
||||
data[i]["path"] = mapStations[i].path;
|
||||
data[i]["heardDirect"] = mapStations[i].heardDirect;
|
||||
}
|
||||
|
||||
String buffer;
|
||||
|
||||
@@ -339,6 +339,10 @@ bool NetworkManager::isWifiAPActive() const {
|
||||
return _wifiAPmode;
|
||||
}
|
||||
|
||||
bool NetworkManager::hasActiveInterface() const {
|
||||
return _wifiSTAmode || _wifiAPmode || _ethernetMode;
|
||||
}
|
||||
|
||||
// Check if Ethernet is connected
|
||||
bool NetworkManager::isEthernetConnected() const {
|
||||
return _ethernetMode && _ethernetConnected && ETH.linkUp();
|
||||
|
||||
+132
-98
@@ -16,6 +16,7 @@
|
||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <APRSPacketLib.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include "configuration.h"
|
||||
#include "network_manager.h"
|
||||
@@ -30,117 +31,150 @@ extern String versionNumber;
|
||||
|
||||
WiFiUDP udpClient;
|
||||
|
||||
namespace {
|
||||
|
||||
String createSyslogStart() {
|
||||
String syslogStartPacket = "<165>1 - ";
|
||||
syslogStartPacket.concat(Config.callsign);
|
||||
syslogStartPacket.concat(" CA2RXU_LoRa_iGate_");
|
||||
syslogStartPacket.concat(versionNumber);
|
||||
syslogStartPacket.concat(" - - - "); //RFC5424 The Syslog Protocol
|
||||
return syslogStartPacket;
|
||||
}
|
||||
|
||||
void sendSyslogPacket(const String& syslogPacket) {
|
||||
udpClient.beginPacket(Config.syslog.server.c_str(), Config.syslog.port);
|
||||
udpClient.write((const uint8_t*)syslogPacket.c_str(), syslogPacket.length());
|
||||
udpClient.endPacket();
|
||||
}
|
||||
|
||||
String formatSignalData(int rssi, float snr, int freqError) {
|
||||
char signalData[35];
|
||||
snprintf(signalData, sizeof(signalData), " / %ddBm / %.2fdB / %dHz", rssi, snr, freqError);
|
||||
return String(signalData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace SYSLOG_Utils {
|
||||
|
||||
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
|
||||
void logLoRaRx(APRSPacket& aprsPacket, const String& packet, const int rssi, const float snr, const int freqError) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
String syslogPacket = "<165>1 - ";
|
||||
syslogPacket.concat(Config.callsign);
|
||||
syslogPacket.concat(" CA2RXU_LoRa_iGate_");
|
||||
syslogPacket.concat(versionNumber);
|
||||
syslogPacket.concat(" - - - "); //RFC5424 The Syslog Protocol
|
||||
String syslogPacket = createSyslogStart();
|
||||
syslogPacket.concat("RX / ");
|
||||
|
||||
char signalData[35];
|
||||
snprintf(signalData, sizeof(signalData), " / %ddBm / %.2fdB / %dHz", rssi, snr, freqError);
|
||||
|
||||
int colonIndex = packet.indexOf(":");
|
||||
char nextChar = packet[colonIndex + 1];
|
||||
int greaterThanIndex = packet.indexOf(">");
|
||||
int telemetryPacketIndex = packet.indexOf(":T#");
|
||||
String sender = packet.substring(3, greaterThanIndex);
|
||||
|
||||
switch (type) {
|
||||
case 0: // CRC
|
||||
syslogPacket.concat("CRC / CRC-ERROR / ");
|
||||
syslogPacket.concat(packet);
|
||||
syslogPacket.concat(signalData);
|
||||
switch (aprsPacket.type) {
|
||||
case 1: // MESSAGE
|
||||
syslogPacket.concat("MESSAGE / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(aprsPacket.addressee);
|
||||
syslogPacket.concat(":");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
break;
|
||||
case 1: // RX
|
||||
syslogPacket.concat("RX / ");
|
||||
if (nextChar == ':') {
|
||||
syslogPacket.concat("MESSAGE / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
} else if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
|
||||
syslogPacket.concat("GPS / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" / ");
|
||||
if (packet.indexOf("WIDE1-1") > 10) {
|
||||
syslogPacket.concat(packet.substring(greaterThanIndex + 1, packet.indexOf(",")));
|
||||
syslogPacket.concat(" / WIDE1-1");
|
||||
} else {
|
||||
syslogPacket.concat(packet.substring(greaterThanIndex + 1, colonIndex));
|
||||
syslogPacket.concat(" / -");
|
||||
case 0: // GPS
|
||||
syslogPacket.concat("GPS / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" / ");
|
||||
if (aprsPacket.path.indexOf("WIDE1-1") != -1) {
|
||||
syslogPacket.concat(aprsPacket.tocall);
|
||||
syslogPacket.concat(" / WIDE1-1");
|
||||
} else {
|
||||
syslogPacket.concat(aprsPacket.tocall);
|
||||
if (aprsPacket.path != "") {
|
||||
syslogPacket.concat(",");
|
||||
syslogPacket.concat(aprsPacket.path);
|
||||
}
|
||||
} else if (nextChar == '>') {
|
||||
syslogPacket.concat("STATUS / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
} else if (nextChar == '`' || nextChar == '\'') { // both Mic-E DTIs are valid
|
||||
syslogPacket.concat("MIC-E / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(String(nextChar)); // restore the actual indicator received
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
// append distance for Mic-E, same as the GPS path
|
||||
//syslogPacket.concat(" / ");
|
||||
//syslogPacket.concat(GPS_Utils::getDistanceAndComment(packet));
|
||||
} else if (nextChar == ';') {
|
||||
syslogPacket.concat("OBJECT / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
} else if (telemetryPacketIndex >= 10 && packet.indexOf(":=/") == -1) {
|
||||
syslogPacket.concat("TELEMETRY / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.substring(telemetryPacketIndex + 3));
|
||||
} else {
|
||||
syslogPacket.concat(packet);
|
||||
}
|
||||
syslogPacket.concat(signalData);
|
||||
if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
|
||||
syslogPacket.concat(" / ");
|
||||
syslogPacket.concat(GPS_Utils::getDistanceAndComment(packet));
|
||||
syslogPacket.concat(" / -");
|
||||
}
|
||||
break;
|
||||
case 2: // APRSIS TX
|
||||
syslogPacket.concat("APRSIS TX / ");
|
||||
if (nextChar == '>') {
|
||||
syslogPacket.concat("StartUp_Status / ");
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
} else if (nextChar == ':') {
|
||||
syslogPacket.concat("QUERY / ");
|
||||
syslogPacket.concat(packet);
|
||||
} else {
|
||||
syslogPacket.concat("BEACON / ");
|
||||
syslogPacket.concat(packet);
|
||||
}
|
||||
case 2: // STATUS
|
||||
syslogPacket.concat("STATUS / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
break;
|
||||
case 3: // TX
|
||||
syslogPacket.concat("TX / ");
|
||||
if (packet.indexOf("RFONLY") > 10) {
|
||||
syslogPacket.concat("RFONLY / ");
|
||||
syslogPacket.concat(packet);
|
||||
} else if (nextChar == ':') {
|
||||
syslogPacket.concat("MESSAGE / ");
|
||||
syslogPacket.concat(sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||
} else {
|
||||
syslogPacket.concat(packet);
|
||||
}
|
||||
case 4: // MIC-E
|
||||
syslogPacket.concat("MIC-E / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(packet.indexOf(":`") != -1 ? "`" : "'"); // restore the actual indicator received
|
||||
break;
|
||||
default:
|
||||
syslogPacket = "<165>1 - ERROR LoRa - - - ERROR / Error in Syslog Packet"; //RFC5424 The Syslog Protocol
|
||||
case 5: // OBJECT
|
||||
syslogPacket.concat("OBJECT / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
break;
|
||||
case 3: // TELEMETRY
|
||||
syslogPacket.concat("TELEMETRY / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
break;
|
||||
default: // type == 6, unrecognized -- payload is the full, uncut packet
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
break;
|
||||
}
|
||||
udpClient.beginPacket(Config.syslog.server.c_str(), Config.syslog.port);
|
||||
udpClient.write((const uint8_t*)syslogPacket.c_str(), syslogPacket.length());
|
||||
udpClient.endPacket();
|
||||
syslogPacket.concat(formatSignalData(rssi, snr, freqError));
|
||||
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // GPS or Mic-E -- both already carry
|
||||
syslogPacket.concat(" / "); // clean lat/lon + comment in aprsPacket
|
||||
syslogPacket.concat(GPS_Utils::buildDistanceAndComment(aprsPacket.latitude, aprsPacket.longitude, aprsPacket.payload));
|
||||
}
|
||||
sendSyslogPacket(syslogPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void logAPRSISTx(const String& packet) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
String syslogPacket = createSyslogStart();
|
||||
syslogPacket.concat("APRSIS TX / ");
|
||||
|
||||
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet, 0, 0, 0);
|
||||
if (aprsPacket.type == 2) { // STATUS
|
||||
syslogPacket.concat("StartUp_Status / ");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
} else if (aprsPacket.type == 1) { // MESSAGE
|
||||
syslogPacket.concat("QUERY / ");
|
||||
syslogPacket.concat(packet);
|
||||
} else {
|
||||
syslogPacket.concat("BEACON / ");
|
||||
syslogPacket.concat(packet);
|
||||
}
|
||||
sendSyslogPacket(syslogPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void logLoRaTx(const String& packet) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
String syslogPacket = createSyslogStart();
|
||||
syslogPacket.concat("TX / ");
|
||||
|
||||
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet, 0, 0, 0);
|
||||
if (aprsPacket.path.indexOf("RFONLY") != -1) {
|
||||
syslogPacket.concat("RFONLY / ");
|
||||
syslogPacket.concat(packet);
|
||||
} else if (aprsPacket.type == 1) {
|
||||
syslogPacket.concat("MESSAGE / ");
|
||||
syslogPacket.concat(aprsPacket.sender);
|
||||
syslogPacket.concat(" ---> ");
|
||||
syslogPacket.concat(aprsPacket.addressee);
|
||||
syslogPacket.concat(":");
|
||||
syslogPacket.concat(aprsPacket.payload);
|
||||
} else {
|
||||
syslogPacket.concat(packet);
|
||||
}
|
||||
sendSyslogPacket(syslogPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void logCRCError(const String& packet, const int rssi, const float snr, const int freqError) {
|
||||
if (Config.syslog.active && networkManager->isConnected()) {
|
||||
String syslogPacket = createSyslogStart();
|
||||
syslogPacket.concat("CRC / CRC-ERROR / ");
|
||||
syslogPacket.concat(packet);
|
||||
syslogPacket.concat(formatSignalData(rssi, snr, freqError));
|
||||
sendSyslogPacket(syslogPacket);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+54
-18
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <WiFi.h>
|
||||
#include "ESPmDNS.h"
|
||||
#include "network_manager.h"
|
||||
#include "configuration.h"
|
||||
#include "station_utils.h"
|
||||
#include "kiss_protocol.h"
|
||||
@@ -28,6 +29,7 @@
|
||||
|
||||
|
||||
extern Configuration Config;
|
||||
extern NetworkManager *networkManager;
|
||||
extern WiFiClient aprsIsClient;
|
||||
extern bool passcodeValid;
|
||||
|
||||
@@ -47,7 +49,7 @@ String inputSerialBuffer = "";
|
||||
namespace TNC_Utils {
|
||||
|
||||
void setup() {
|
||||
if (Config.tnc.enableServer && Config.digi.ecoMode == 0) {
|
||||
if (Config.tnc.enableServer && Config.digi.ecoMode == 0 && networkManager->hasActiveInterface()) { // no WiFi STA/AP or Ethernet started = no TCP/IP stack
|
||||
tncServer.stop();
|
||||
tncServer.begin();
|
||||
String host = "igate-" + Config.callsign;
|
||||
@@ -78,7 +80,23 @@ namespace TNC_Utils {
|
||||
}
|
||||
}
|
||||
|
||||
void handleInputData(char character, int bufferIndex) {
|
||||
void processInputFrame(const String& frame, int bufferIndex, const String& protocolLabel) {
|
||||
if (bufferIndex != -1) {
|
||||
Utils::print("<--- Got from TNC (" + protocolLabel + ") : ");
|
||||
Utils::println(frame);
|
||||
}
|
||||
|
||||
String sender = frame.substring(0,frame.indexOf(">"));
|
||||
|
||||
if (Config.tnc.acceptOwn || sender != Config.callsign) {
|
||||
if (Config.loramodule.txActive) STATION_Utils::addToOutputPacketBuffer(frame);
|
||||
if (Config.tnc.aprsBridgeActive && Config.aprs_is.active && passcodeValid && aprsIsClient.connected()) APRS_IS_Utils::upload(frame);
|
||||
} else {
|
||||
Utils::println("Ignored own frame from " + protocolLabel);
|
||||
}
|
||||
}
|
||||
|
||||
void handleInputDataKISS(char character, int bufferIndex) {
|
||||
String* data = (bufferIndex == -1) ? &inputSerialBuffer : &inputServerBuffer[bufferIndex];
|
||||
if (data->length() == 0 && character != (char)FEND) return;
|
||||
|
||||
@@ -89,19 +107,7 @@ namespace TNC_Utils {
|
||||
const String& frame = decodeKISS(*data, isDataFrame);
|
||||
|
||||
if (isDataFrame) {
|
||||
if (bufferIndex != -1) {
|
||||
Utils::print("<--- Got from TNC : ");
|
||||
Utils::println(frame);
|
||||
}
|
||||
|
||||
String sender = frame.substring(0,frame.indexOf(">"));
|
||||
|
||||
if (Config.tnc.acceptOwn || sender != Config.callsign) {
|
||||
if (Config.loramodule.txActive) STATION_Utils::addToOutputPacketBuffer(frame);
|
||||
if (Config.tnc.aprsBridgeActive && Config.aprs_is.active && passcodeValid && aprsIsClient.connected()) APRS_IS_Utils::upload(frame);
|
||||
} else {
|
||||
Utils::println("Ignored own frame from KISS");
|
||||
}
|
||||
processInputFrame(frame, bufferIndex, "KISS");
|
||||
}
|
||||
data->clear();
|
||||
}
|
||||
@@ -111,6 +117,36 @@ namespace TNC_Utils {
|
||||
}
|
||||
}
|
||||
|
||||
void handleInputDataTNC2(char character, int bufferIndex) {
|
||||
String* data = (bufferIndex == -1) ? &inputSerialBuffer : &inputServerBuffer[bufferIndex];
|
||||
|
||||
if (character == '\r') return;
|
||||
|
||||
if (character == '\n') {
|
||||
String frame = *data;
|
||||
frame.trim();
|
||||
data->clear();
|
||||
|
||||
if (frame.length() > 0 && frame.indexOf(':') != -1 && frame.indexOf('>') != -1) {
|
||||
processInputFrame(frame, bufferIndex, "TNC2");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
data->concat(character);
|
||||
if (data->length() > 255) {
|
||||
data->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void handleInputData(char character, int bufferIndex) {
|
||||
if (Config.tnc.kissProtocol) {
|
||||
handleInputDataKISS(character, bufferIndex);
|
||||
} else {
|
||||
handleInputDataTNC2(character, bufferIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void readFromClients() {
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
auto client = clients[i];
|
||||
@@ -138,13 +174,13 @@ namespace TNC_Utils {
|
||||
void sendToClients(const String& packet, bool stripBytes) {
|
||||
String cleanPacket = stripBytes ? packet.substring(3): packet;
|
||||
|
||||
const String kissEncoded = encodeKISS(cleanPacket);
|
||||
const String encoded = Config.tnc.kissProtocol ? encodeKISS(cleanPacket) : (cleanPacket + "\r\n");
|
||||
|
||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||
auto client = clients[i];
|
||||
if (client != nullptr) {
|
||||
if (client->connected()) {
|
||||
client->print(kissEncoded);
|
||||
client->print(encoded);
|
||||
client->flush();
|
||||
} else {
|
||||
delete client;
|
||||
@@ -158,7 +194,7 @@ namespace TNC_Utils {
|
||||
|
||||
void sendToSerial(const String& packet, bool stripBytes) {
|
||||
String cleanPacket = stripBytes ? packet.substring(3): packet;
|
||||
Serial.print(encodeKISS(cleanPacket));
|
||||
Serial.print(Config.tnc.kissProtocol ? encodeKISS(cleanPacket) : (cleanPacket + "\r\n"));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
|
||||
+56
-37
@@ -89,7 +89,7 @@ namespace Utils {
|
||||
|
||||
if (sendOverAPRSIS) {
|
||||
APRS_IS_Utils::upload(statusPacket);
|
||||
SYSLOG_Utils::log(2, statusPacket, 0, 0.0, 0); // APRSIS TX
|
||||
SYSLOG_Utils::logAPRSISTx(statusPacket);
|
||||
} else {
|
||||
STATION_Utils::addToOutputPacketBuffer(statusPacket, true); // treated also as beacon on Tx Freq
|
||||
}
|
||||
@@ -288,7 +288,7 @@ namespace Utils {
|
||||
#else
|
||||
APRS_IS_Utils::upload(beaconPacket);
|
||||
#endif
|
||||
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::log(1, "tcp" + beaconPacket, 0, 0.0, 0); // APRSIS TX
|
||||
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::logAPRSISTx(beaconPacket);
|
||||
}
|
||||
|
||||
if (Config.beacon.sendViaRF || backupDigiMode) {
|
||||
@@ -325,21 +325,65 @@ namespace Utils {
|
||||
}
|
||||
}
|
||||
|
||||
void typeOfPacket(const String& packet, const uint8_t packetType) {
|
||||
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType) {
|
||||
switch (packetType) {
|
||||
case 0: // LoRa-APRS
|
||||
fifthLine = "LoRa Rx ----> APRS-IS";
|
||||
break;
|
||||
case 1: // APRS-LoRa
|
||||
fifthLine = "APRS-IS ----> LoRa Tx";
|
||||
break;
|
||||
case 2: // Digipeater
|
||||
case 1: // Digipeater
|
||||
fifthLine = "LoRa Rx ----> LoRa Tx";
|
||||
break;
|
||||
}
|
||||
|
||||
int firstColonIndex = packet.indexOf(":");
|
||||
char nextChar = packet[firstColonIndex + 1];
|
||||
String sender = aprsPacket.sender;
|
||||
for (int i = sender.length(); i < 9; i++) {
|
||||
sender += " ";
|
||||
}
|
||||
sixthLine = sender;
|
||||
|
||||
switch (aprsPacket.type) {
|
||||
case 1: // MESSAGE
|
||||
sixthLine += "> MESSAGE";
|
||||
break;
|
||||
case 2: // STATUS
|
||||
sixthLine += "> NEW STATUS";
|
||||
break;
|
||||
case 0: // GPS
|
||||
sixthLine += "> GPS BEACON";
|
||||
if (!Config.syslog.active) GPS_Utils::buildDistanceAndComment(aprsPacket.latitude, aprsPacket.longitude, aprsPacket.payload);
|
||||
seventhLine = "RSSI:";
|
||||
seventhLine += String(rssi);
|
||||
seventhLine += "dBm";
|
||||
seventhLine += (rssi <= -100) ? " " : " ";
|
||||
if (distance.indexOf(".") == 1) seventhLine += " ";
|
||||
seventhLine += "D:";
|
||||
seventhLine += distance;
|
||||
seventhLine += "km";
|
||||
break;
|
||||
case 3: // TELEMETRY
|
||||
sixthLine += "> TELEMETRY";
|
||||
break;
|
||||
case 4: // MIC-E
|
||||
sixthLine += "> MIC-E";
|
||||
break;
|
||||
case 5: // OBJECT
|
||||
sixthLine += "> OBJECT";
|
||||
break;
|
||||
default: // unrecognized
|
||||
sixthLine += "> ?????????";
|
||||
break;
|
||||
}
|
||||
if (aprsPacket.type != 0) { // Common assignment for non-GPS cases
|
||||
seventhLine = "RSSI:";
|
||||
seventhLine += String(rssi);
|
||||
seventhLine += "dBm SNR: ";
|
||||
seventhLine += String(snr);
|
||||
seventhLine += "dBm";
|
||||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
@@ -347,37 +391,12 @@ namespace Utils {
|
||||
}
|
||||
sixthLine = sender;
|
||||
|
||||
if (nextChar == ':') {
|
||||
if (packet.indexOf("::") > 0) {
|
||||
sixthLine += "> MESSAGE";
|
||||
} else if (nextChar == '>') {
|
||||
sixthLine += "> NEW STATUS";
|
||||
} else if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
|
||||
sixthLine += "> GPS BEACON";
|
||||
if (!Config.syslog.active) GPS_Utils::getDistanceAndComment(packet); // to be checked!!!
|
||||
seventhLine = "RSSI:";
|
||||
seventhLine += String(rssi);
|
||||
seventhLine += "dBm";
|
||||
seventhLine += (rssi <= -100) ? " " : " ";
|
||||
if (distance.indexOf(".") == 1) seventhLine += " ";
|
||||
seventhLine += "D:";
|
||||
seventhLine += distance;
|
||||
seventhLine += "km";
|
||||
} else if (nextChar == '`' || nextChar == '\'') {
|
||||
sixthLine += "> MIC-E";
|
||||
} else if (nextChar == ';') {
|
||||
sixthLine += "> OBJECT";
|
||||
} else if (packet.indexOf(":T#") >= 10 && packet.indexOf(":=/") == -1) {
|
||||
sixthLine += "> TELEMETRY";
|
||||
} else {
|
||||
sixthLine += "> ??????????";
|
||||
}
|
||||
if (nextChar != '!' && nextChar != '=' && nextChar != '@') { // Common assignment for non-GPS cases
|
||||
seventhLine = "RSSI:";
|
||||
seventhLine += String(rssi);
|
||||
seventhLine += "dBm SNR: ";
|
||||
seventhLine += String(snr);
|
||||
seventhLine += "dBm";
|
||||
sixthLine += "> OBJECT";
|
||||
}
|
||||
seventhLine = "";
|
||||
}
|
||||
|
||||
void print(const String& text) {
|
||||
|
||||
+55
-1
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include "network_manager.h"
|
||||
#include "configuration.h"
|
||||
#include "ota_utils.h"
|
||||
#include "web_utils.h"
|
||||
@@ -26,6 +27,7 @@
|
||||
|
||||
|
||||
extern Configuration Config;
|
||||
extern NetworkManager *networkManager;
|
||||
extern uint32_t lastBeaconTx;
|
||||
extern std::vector<ReceivedPacket> receivedPackets;
|
||||
|
||||
@@ -62,6 +64,18 @@ extern const unsigned char favicon_data[] asm("_binary_data_embed_favicon_png_gz
|
||||
extern const unsigned char favicon_data_end[] asm("_binary_data_embed_favicon_png_gz_end");
|
||||
extern const size_t favicon_data_len = favicon_data_end - favicon_data;
|
||||
|
||||
extern const unsigned char aprs_symbols_24_0_data[] asm("_binary_data_embed_aprs_symbols_24_0_png_gz_start");
|
||||
extern const unsigned char aprs_symbols_24_0_data_end[] asm("_binary_data_embed_aprs_symbols_24_0_png_gz_end");
|
||||
extern const size_t aprs_symbols_24_0_data_len = aprs_symbols_24_0_data_end - aprs_symbols_24_0_data;
|
||||
|
||||
extern const unsigned char aprs_symbols_24_1_data[] asm("_binary_data_embed_aprs_symbols_24_1_png_gz_start");
|
||||
extern const unsigned char aprs_symbols_24_1_data_end[] asm("_binary_data_embed_aprs_symbols_24_1_png_gz_end");
|
||||
extern const size_t aprs_symbols_24_1_data_len = aprs_symbols_24_1_data_end - aprs_symbols_24_1_data;
|
||||
|
||||
extern const unsigned char aprs_symbols_24_2_data[] asm("_binary_data_embed_aprs_symbols_24_2_png_gz_start");
|
||||
extern const unsigned char aprs_symbols_24_2_data_end[] asm("_binary_data_embed_aprs_symbols_24_2_png_gz_end");
|
||||
extern const size_t aprs_symbols_24_2_data_len = aprs_symbols_24_2_data_end - aprs_symbols_24_2_data;
|
||||
|
||||
|
||||
namespace WEB_Utils {
|
||||
|
||||
@@ -92,6 +106,42 @@ namespace WEB_Utils {
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleAprsSymbols24Primary(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse(
|
||||
200,
|
||||
"image/png",
|
||||
(const uint8_t*)aprs_symbols_24_0_data,
|
||||
aprs_symbols_24_0_data_len
|
||||
);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleAprsSymbols24Alternate(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse(
|
||||
200,
|
||||
"image/png",
|
||||
(const uint8_t*)aprs_symbols_24_1_data,
|
||||
aprs_symbols_24_1_data_len
|
||||
);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleAprsSymbols24Overlay(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse(
|
||||
200,
|
||||
"image/png",
|
||||
(const uint8_t*)aprs_symbols_24_2_data,
|
||||
aprs_symbols_24_2_data_len
|
||||
);
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
response->addHeader("Cache-Control", "max-age=3600");
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void handleReadConfiguration(AsyncWebServerRequest *request) {
|
||||
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str()))
|
||||
return request->requestAuthentication();
|
||||
@@ -274,6 +324,7 @@ namespace WEB_Utils {
|
||||
Config.tnc.enableSerial = request->hasParam("tnc.enableSerial", true);
|
||||
Config.tnc.acceptOwn = request->hasParam("tnc.acceptOwn", true);
|
||||
Config.tnc.aprsBridgeActive = request->hasParam("tnc.aprsBridgeActive", true);
|
||||
Config.tnc.kissProtocol = getParamStringSafe("tnc.kissProtocol", Config.tnc.kissProtocol ? "KISS" : "TNC2") != "TNC2";
|
||||
|
||||
Config.mqtt.active = request->hasParam("mqtt.active", true);
|
||||
if (Config.mqtt.active) {
|
||||
@@ -386,7 +437,7 @@ namespace WEB_Utils {
|
||||
}
|
||||
|
||||
void setup() {
|
||||
if (Config.digi.ecoMode == 0) {
|
||||
if (Config.digi.ecoMode == 0 && networkManager->hasActiveInterface()) { // no WiFi STA/AP or Ethernet started = no TCP/IP stack
|
||||
server.on("/", HTTP_GET, handleHome);
|
||||
server.on("/status", HTTP_GET, handleStatus);
|
||||
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
|
||||
@@ -401,6 +452,9 @@ namespace WEB_Utils {
|
||||
server.on("/leaflet.css", HTTP_GET, handleLeafletStyle);
|
||||
server.on("/leaflet.js", HTTP_GET, handleLeafletScript);
|
||||
server.on("/favicon.png", HTTP_GET, handleFavicon);
|
||||
server.on("/aprs-symbols-24-0.png", HTTP_GET, handleAprsSymbols24Primary);
|
||||
server.on("/aprs-symbols-24-1.png", HTTP_GET, handleAprsSymbols24Alternate);
|
||||
server.on("/aprs-symbols-24-2.png", HTTP_GET, handleAprsSymbols24Overlay);
|
||||
|
||||
OTA_Utils::setup(&server); // Include OTA Updater for WebServer
|
||||
|
||||
|
||||
@@ -99,6 +99,9 @@ namespace WIFI_Utils {
|
||||
if (Config.wifiAutoAP.enabled) {
|
||||
Serial.println("Starting AP fallback...");
|
||||
startAutoAP();
|
||||
} else {
|
||||
Serial.println("AutoAP disabled: no network, WebUI/TNC server not started (reconfigure over USB)");
|
||||
displayShow("", " No WiFi / No AP", " WebUI disabled", " loading ...", 2000);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ files = [
|
||||
'data_embed/leaflet.js',
|
||||
'data_embed/leaflet.css',
|
||||
'data_embed/favicon.png',
|
||||
'data_embed/aprs-symbols-24-0.png',
|
||||
'data_embed/aprs-symbols-24-1.png',
|
||||
'data_embed/aprs-symbols-24-2.png',
|
||||
]
|
||||
|
||||
string_to_find_str = "String"
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -1,4 +1,4 @@
|
||||
[env:esp32c3_DIY_1W_LoRa_915]
|
||||
[env:ESP32C3_DIY_1W_LoRa_915_E22_900M30S]
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.mcu = esp32c3
|
||||
build_flags =
|
||||
@@ -7,7 +7,7 @@ build_flags =
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32C3_DIY_1W_LoRa_915
|
||||
-D ESP32C3_DIY_1W_LoRa_915_E22_900M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,4 +1,4 @@
|
||||
[env:ESP32_C3_OctopusLab_LoRa]
|
||||
[env:ESP32C3_DIY_1W_LoRa_E22_400M30S]
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.mcu = esp32c3
|
||||
build_flags =
|
||||
@@ -7,7 +7,7 @@ build_flags =
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_C3_OctopusLab_LoRa
|
||||
-D ESP32C3_DIY_1W_LoRa_E22_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,4 +1,4 @@
|
||||
[env:esp32c3_DIY_1W_LoRa]
|
||||
[env:ESP32C3_OctopusLab_LoRa]
|
||||
board = esp32-c3-devkitm-1
|
||||
board_build.mcu = esp32c3
|
||||
build_flags =
|
||||
@@ -7,7 +7,7 @@ build_flags =
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32C3_DIY_1W_LoRa
|
||||
-D ESP32C3_OctopusLab_LoRa
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_9M2IBR_1W_LoRa_GPS]
|
||||
[env:ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_9M2IBR_1W_LoRa_GPS
|
||||
-D ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_1W_LoRa]
|
||||
[env:ESP32_DIY_1W_LoRa_915_E22_900M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_1W_LoRa
|
||||
-D ESP32_DIY_1W_LoRa_915_E22_900M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_1W_LoRa_LLCC68]
|
||||
[env:ESP32_DIY_1W_LoRa_E220_400M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_1W_LoRa_LLCC68
|
||||
-D ESP32_DIY_1W_LoRa_E220_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_1W_LoRa_Mesh_V1_2]
|
||||
[env:ESP32_DIY_1W_LoRa_E22_400M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_1W_LoRa_Mesh_V1_2
|
||||
-D ESP32_DIY_1W_LoRa_E22_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* 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_SX1278
|
||||
#define RADIO_SCLK_PIN 5
|
||||
#define RADIO_MISO_PIN 19
|
||||
#define RADIO_MOSI_PIN 27
|
||||
#define RADIO_CS_PIN 18
|
||||
#define RADIO_RST_PIN 14
|
||||
#define RADIO_BUSY_PIN 26
|
||||
#define RADIO_WAKEUP_PIN RADIO_BUSY_PIN
|
||||
#define GPIO_WAKEUP_PIN GPIO_SEL_26
|
||||
#define RADIO_RXEN 32 // Ebyte E32 400M30S (SX1278 + PA) antenna switch
|
||||
#define RADIO_TXEN 25
|
||||
|
||||
// I2C
|
||||
#define USE_WIRE_WITH_OLED_PINS
|
||||
|
||||
// Display
|
||||
#define HAS_DISPLAY
|
||||
|
||||
#undef OLED_SDA
|
||||
#undef OLED_SCL
|
||||
#undef OLED_RST
|
||||
|
||||
#define OLED_SDA 21
|
||||
#define OLED_SCL 22
|
||||
#define OLED_RST -1 // Reset pin # (or -1 if sharing Arduino reset pin)
|
||||
|
||||
// Aditional Config
|
||||
#define INTERNAL_LED_PIN 2
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
[env:ESP32_DIY_1W_LoRa_E32_400M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_1W_LoRa_E32_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_1W_LoRa_915]
|
||||
[env:ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_1W_LoRa_915
|
||||
-D ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_LoRa_915]
|
||||
[env:ESP32_DIY_LoRa_915_SX1276]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_LoRa_915
|
||||
-D ESP32_DIY_LoRa_915_SX1276
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_LoRa_A7670]
|
||||
[env:ESP32_DIY_LoRa_A7670_915_SX1276]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_LoRa_A7670
|
||||
-D ESP32_DIY_LoRa_A7670_915_SX1276
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_LoRa_A7670_915]
|
||||
[env:ESP32_DIY_LoRa_A7670_SX1278]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_LoRa_A7670_915
|
||||
-D ESP32_DIY_LoRa_A7670_SX1278
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:ESP32_DIY_LoRa]
|
||||
[env:ESP32_DIY_LoRa_SX1278]
|
||||
board = esp32dev
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D ESP32_DIY_LoRa
|
||||
-D ESP32_DIY_LoRa_SX1278
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Copyright (C) 2025 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_TCXO
|
||||
#define RADIO_SCLK_PIN 5
|
||||
#define RADIO_MISO_PIN 3
|
||||
#define RADIO_MOSI_PIN 6
|
||||
#define RADIO_CS_PIN 7
|
||||
#define RADIO_RST_PIN 8
|
||||
#define RADIO_DIO1_PIN 47
|
||||
#define RADIO_BUSY_PIN 48
|
||||
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
|
||||
#define GPIO_WAKEUP_PIN GPIO_SEL_47
|
||||
|
||||
// Aditional Config
|
||||
#define INTERNAL_LED_PIN 46
|
||||
#define BATTERY_PIN 1
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
[env:RAK3112]
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.mcu = esp32s3
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_upload.flash_size = 16MB
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
${common.usb_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D BOARD_HAS_PSRAM
|
||||
-D RAK3112
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
@@ -0,0 +1,52 @@
|
||||
/* Copyright (C) 2025 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_TCXO
|
||||
#define RADIO_SCLK_PIN 5
|
||||
#define RADIO_MISO_PIN 3
|
||||
#define RADIO_MOSI_PIN 6
|
||||
#define RADIO_CS_PIN 7
|
||||
#define RADIO_RST_PIN 8
|
||||
#define RADIO_DIO1_PIN 47
|
||||
#define RADIO_BUSY_PIN 48
|
||||
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
|
||||
#define GPIO_WAKEUP_PIN GPIO_SEL_47
|
||||
|
||||
#define RADIO_ANT_SW_PIN 4 // ANT_SW - antenna switch power enable
|
||||
#define RADIO_ANT_SW_ON_STATE HIGH
|
||||
|
||||
// Base Board Power
|
||||
#define VEXT_CTRL_PIN 14 // 3V3_EN - powers GPS on Base Board
|
||||
#define VEXT_CTRL_ON_STATE HIGH
|
||||
|
||||
// GPS
|
||||
#define HAS_GPS
|
||||
#define GPS_BAUDRATE 9600
|
||||
#define GPS_RX 43
|
||||
#define GPS_TX 44
|
||||
|
||||
// Aditional Config
|
||||
#define INTERNAL_LED_PIN 46
|
||||
#define BATTERY_PIN 1
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
[env:RAK3312]
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.mcu = esp32s3
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_upload.flash_size = 16MB
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
${common.usb_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D BOARD_HAS_PSRAM
|
||||
-D RAK3312
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:WEMOS-D1-R32-RA02]
|
||||
[env:WEMOS_D1_R32_DIY_LoRa_SX1278]
|
||||
board = wemos_d1_uno32
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D WEMOS_D1_R32_RA02
|
||||
-D WEMOS_D1_R32_DIY_LoRa_SX1278
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:WEMOS-LOLIN32-OLED-DIY]
|
||||
[env:WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278]
|
||||
board = lolin32
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D WEMOS_LOLIN32_OLED_DIY_LoRa
|
||||
-D WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
File renamed without changes.
+2
-2
@@ -1,11 +1,11 @@
|
||||
[env:WEMOS_S2_MINI_DIY_LoRa]
|
||||
[env:WEMOS_S2_MINI_DIY_LoRa_SX1278]
|
||||
board = lolin_s2_mini
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-D RADIOLIB_EXCLUDE_SX126X=1
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D WEMOS_S2_MINI_DIY_LoRa
|
||||
-D WEMOS_S2_MINI_DIY_LoRa_SX1278
|
||||
lib_deps =
|
||||
${common.lib_deps}
|
||||
${common.display_libs}
|
||||
@@ -5,12 +5,14 @@ platform = espressif32 @ 6.3.1
|
||||
board_build.partitions = min_spiffs.csv
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.mcu = esp32s3
|
||||
board_build.embed_files =
|
||||
board_build.embed_files =
|
||||
data_embed/index.html.gz
|
||||
data_embed/style.css.gz
|
||||
data_embed/script.js.gz
|
||||
data_embed/bootstrap.css.gz
|
||||
data_embed/bootstrap.js.gz
|
||||
data_embed/leaflet.css.gz
|
||||
data_embed/leaflet.js.gz
|
||||
data_embed/favicon.png.gz
|
||||
extra_scripts =
|
||||
pre:tools/compress.py
|
||||
|
||||
@@ -5,12 +5,14 @@ platform = espressif32 @ 6.3.1
|
||||
board_build.partitions = min_spiffs.csv
|
||||
board = esp32-s3-devkitc-1
|
||||
board_build.mcu = esp32s3
|
||||
board_build.embed_files =
|
||||
board_build.embed_files =
|
||||
data_embed/index.html.gz
|
||||
data_embed/style.css.gz
|
||||
data_embed/script.js.gz
|
||||
data_embed/bootstrap.css.gz
|
||||
data_embed/bootstrap.js.gz
|
||||
data_embed/leaflet.css.gz
|
||||
data_embed/leaflet.js.gz
|
||||
data_embed/favicon.png.gz
|
||||
extra_scripts =
|
||||
pre:tools/compress.py
|
||||
|
||||
Reference in new issue
Block a user