Compare commits

...

12 Commits

Author SHA1 Message Date
Ricardo Guzman aaa44200b5 test query callsign fix 2026-07-06 13:09:19 -04:00
Ricardo Guzman c17ad32650 2026 update in copywrite info 2026-06-29 13:06:58 -04:00
Ricardo Guzman (Richonguzman) 26b5df1bd7 update delete of star in digipeated packet 2026-06-22 22:16:14 -04:00
Ricardo Guzman (Richonguzman) 0c3555bdbe new digimode added 2026-06-18 13:58:10 -04:00
Ricardo Guzman (Richonguzman) cf858ffad2 digi patch 2 2026-06-16 11:29:32 -04:00
Ricardo Guzman (Richonguzman) cbac8b0365 digi patch 1 2026-06-16 10:41:44 -04:00
Ricardo Guzman (Richonguzman) b76aed4d57 cambio digi modes de 2 y 3 a 1 y 2 para agregar patch 2026-06-16 10:25:22 -04:00
Ricardo Guzman (Richonguzman) 96d7be6ffc Map cargado y funcionando 2026-06-12 00:09:02 -04:00
Ricardo Guzman (Richonguzman) 94df6d2e8f map con estaciones 2026-06-11 23:59:24 -04:00
Ricardo Guzman (Richonguzman) 0a222edeb8 primer test solo igate 2026-06-11 22:15:37 -04:00
Ricardo Guzman (Richonguzman) e35d5f6a9e about update 2026-06-11 01:55:45 -04:00
Ricardo Guzman (Richonguzman) 96432b8624 NEW WEB 2026-06-11 01:39:34 -04:00
109 changed files with 1704 additions and 1134 deletions
+1
View File
@@ -51,6 +51,7 @@ ____________________________________________________
<br />
# Timeline (Versions):
- 2026-06-29 New UI (Web) and also Live View over a Map to see Rx stations.
- 2026-06-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
+4 -4
View File
@@ -32,16 +32,16 @@ lib_deps =
adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 7.4.2
bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.10.0
ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.4
display_libs =
adafruit/Adafruit GFX Library @ 1.11.9
adafruit/Adafruit SSD1306 @ 2.5.10
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
usb_flags=
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
+4 -4
View File
@@ -40,7 +40,6 @@
"backupDigiMode": false
},
"lora": {
"cadActive": true,
"rxActive": true,
"rxFreq": 433775000,
"rxSpreadingFactor": 12,
@@ -48,10 +47,11 @@
"rxSignalBandwidth": 125000,
"txActive": false,
"txFreq": 433775000,
"txSpreadingFactor": 12,
"txCodingRate4": 5,
"txSpreadingFactor": 9,
"txCodingRate4": 7,
"txSignalBandwidth": 125000,
"power": 20
"power": 20,
"cadActive": true
},
"display": {
"alwaysOn": true,
+1102 -983
View File
File diff suppressed because one or more lines are too long
+212 -7
View File
@@ -118,7 +118,7 @@ function loadSettings(settings) {
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
document.getElementById("beacon.interval").value = settings.beacon.interval;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
@@ -148,12 +148,26 @@ function loadSettings(settings) {
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
LoRaRxCheckbox.checked = settings.lora.rxActive;
LoRaRxFreq.disabled = !LoRaRxCheckbox.checked;
LoRaRxSpreadingFactor.disabled = !LoRaRxCheckbox.checked;
LoRaRxCodingRate.disabled = !LoRaRxCheckbox.checked;
LoRaRxSignalBandwidth.disabled = !LoRaRxCheckbox.checked;
document.getElementById("lora.txActive").checked = settings.lora.txActive;
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.power").value = settings.lora.power;
document.getElementById("lora.cadActive").checked = settings.lora.cadActive;
LoRaTxCheckbox.checked = settings.lora.txActive;
LoRaTxFreq.disabled = !LoRaTxCheckbox.checked;
LoRaTxSpreadingFactor.disabled = !LoRaTxCheckbox.checked;
LoRaTxCodingRate.disabled = !LoRaTxCheckbox.checked;
LoRaTxSignalBandwidth.disabled = !LoRaTxCheckbox.checked;
LoRaPower.disabled = !LoRaTxCheckbox.checked;
LoRaCadActive.disabled = !LoRaTxCheckbox.checked;
// Display
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
@@ -195,7 +209,7 @@ function loadSettings(settings) {
TelemetryCheckbox.checked = settings.wxsensor.active;
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
// SYSLOG
document.getElementById("syslog.active").checked = settings.syslog.active;
document.getElementById("syslog.server").value = settings.syslog.server;
@@ -205,7 +219,7 @@ function loadSettings(settings) {
SyslogServer.disabled = !SyslogCheckbox.checked;
SyslogPort.disabled = !SyslogCheckbox.checked;
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
// TNC
if (settings.tnc) {
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
@@ -390,6 +404,36 @@ TelemetryCheckbox.addEventListener("change", function () {
TelemetryTempCorrection.disabled = !this.checked;
});
// LoRa Rx Switch
const LoRaRxCheckbox = document.querySelector('input[name="lora.rxActive"]');
const LoRaRxFreq = document.querySelector('[name="lora.rxFreq"]');
const LoRaRxSpreadingFactor = document.querySelector('[name="lora.rxSpreadingFactor"]');
const LoRaRxCodingRate = document.querySelector('[name="lora.rxCodingRate4"]');
const LoRaRxSignalBandwidth = document.querySelector('[name="lora.rxSignalBandwidth"]');
LoRaRxCheckbox.addEventListener("change", function () {
LoRaRxFreq.disabled = !this.checked;
LoRaRxSpreadingFactor.disabled = !this.checked;
LoRaRxCodingRate.disabled = !this.checked;
LoRaRxSignalBandwidth.disabled = !this.checked;
});
// LoRa Tx Switch
const LoRaTxCheckbox = document.querySelector('input[name="lora.txActive"]');
const LoRaTxFreq = document.querySelector('[name="lora.txFreq"]');
const LoRaTxSpreadingFactor = document.querySelector('[name="lora.txSpreadingFactor"]');
const LoRaTxCodingRate = document.querySelector('[name="lora.txCodingRate4"]');
const LoRaTxSignalBandwidth = document.querySelector('[name="lora.txSignalBandwidth"]');
const LoRaPower = document.querySelector('[name="lora.power"]');
const LoRaCadActive = document.querySelector('[name="lora.cadActive"]');
LoRaTxCheckbox.addEventListener("change", function () {
LoRaTxFreq.disabled = !this.checked;
LoRaTxSpreadingFactor.disabled = !this.checked;
LoRaTxCodingRate.disabled = !this.checked;
LoRaTxSignalBandwidth.disabled = !this.checked;
LoRaPower.disabled = !this.checked;
LoRaCadActive.disabled = !this.checked;
});
// Syslog Switches
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
const SyslogServer = document.querySelector('input[name="syslog.server"]');
@@ -559,7 +603,7 @@ function loadReceivedPackets(packets) {
packets.forEach((packet) => {
const element = document.createElement("tr");
element.innerHTML = `
<td>${packet.rxTime}</td>
<td>${packet.packet}</td>
@@ -592,8 +636,169 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchReceivedPackets();
})
})
/* ---------- Stations Map (Leaflet, CDN) ---------- */
let mapInstance = null;
let mapMarkers = null;
let mapTimer = null;
let mapTileErrShown = false;
let iGateLatLng = null;
let mapAutoFitDone = false;
function setMapMessage(text) {
const el = document.getElementById("map");
if (el) {
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--msh-text-dim,#8b949e);text-align:center;padding:20px;">${text}</div>`;
}
}
function loadMapStations(stations) {
if (!mapMarkers) return;
mapMarkers.clearLayers();
let drawn = 0;
(stations || []).forEach((s) => {
if (s.lat === 0 && s.lon === 0) return; // descartar estaciones sin fix
const popup = `<b>${s.callsign}</b>`
+ (s.lastHeard ? `<br>Last: ${s.lastHeard}` : "")
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
+ `<br>Packets: ${s.count}`;
L.circleMarker([s.lat, s.lon], {
radius: 6,
color: "#1d6fe0", // borde
weight: 2,
fillColor: "#3b8cff", // relleno
fillOpacity: 0.9
}).bindPopup(popup).addTo(mapMarkers);
drawn++;
});
if (!mapAutoFitDone && drawn > 0) { // auto-fit la 1ª vez que hay estaciones tras abrir
fitMapToStations();
mapAutoFitDone = true;
}
}
// Encuadra el mapa para que entren todas las estaciones (+ el iGate). 100% navegador.
function fitMapToStations() {
if (!mapInstance) return false;
const pts = [];
if (mapMarkers) {
mapMarkers.eachLayer((layer) => {
if (layer.getLatLng) pts.push(layer.getLatLng());
});
}
if (iGateLatLng) pts.push(iGateLatLng);
if (!pts.length) return false;
mapInstance.fitBounds(L.latLngBounds(pts), { padding: [30, 30], maxZoom: 14 });
return true;
}
function fetchMapStations() {
fetch("/stations.json")
.then((response) => response.json())
.then((stations) => {
loadMapStations(stations);
})
.catch((err) => {
console.error(err);
console.error(`Failed to load stations`);
});
}
window.showMap = function () {
if (typeof L === "undefined") { // sin internet el CDN de Leaflet no cargó
setMapMessage("Map unavailable &mdash; no internet connection.<br>The map needs internet to load (OpenStreetMap).");
return;
}
mapAutoFitDone = false; // re-encuadrar en cada apertura del Map
if (!mapInstance) {
mapInstance = L.map("map");
const tiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; OpenStreetMap"
}).addTo(mapInstance);
tiles.on("tileerror", function () { // tiles no cargan (sin salida a internet)
if (!mapTileErrShown) {
mapTileErrShown = true;
if (window.showToast) showToast("Map tiles could not load — check internet connection.");
}
});
mapMarkers = L.layerGroup().addTo(mapInstance);
const FitControl = L.Control.extend({ // botón "Fit All Stations" (control nativo de Leaflet)
options: { position: "topright" },
onAdd: function () {
const btn = L.DomUtil.create("button", "");
btn.type = "button";
btn.title = "Fit all stations";
btn.textContent = "Fit All Stations";
btn.style.cssText = "background:#fff;color:#333;border:2px solid rgba(0,0,0,.2);border-radius:4px;padding:5px 9px;cursor:pointer;font:600 12px sans-serif;box-shadow:0 1px 4px rgba(0,0,0,.3);";
L.DomEvent.on(btn, "click", (e) => {
L.DomEvent.stop(e);
fitMapToStations();
});
return btn;
}
});
mapInstance.addControl(new FitControl());
fetch("/configuration.json") // centrar y marcar el iGate
.then((response) => response.json())
.then((config) => {
const lat = (config.beacon && config.beacon.latitude) || 0;
const lon = (config.beacon && config.beacon.longitude) || 0;
const callsign = config.callsign || "iGate";
mapInstance.setView([lat, lon], (lat || lon) ? 12 : 2);
if (lat || lon) { // rombo rojo en la posición del iGate
iGateLatLng = L.latLng(lat, lon); // guardar para incluirlo en el auto-fit
const iGateIcon = L.divIcon({
className: "igate-marker",
html: '<div style="width:14px;height:14px;background:#e23b3b;border:2px solid #fff;transform:rotate(45deg);box-shadow:0 0 4px rgba(0,0,0,.6);"></div>',
iconSize: [18, 18],
iconAnchor: [9, 9]
});
L.marker([lat, lon], { icon: iGateIcon })
.bindPopup(`<b>${callsign}</b><br>This station`)
.addTo(mapInstance); // directo al mapa: no se borra en los refrescos
}
})
.catch(() => {
mapInstance.setView([0, 0], 2);
});
}
setTimeout(function () { // el div estuvo oculto: recalcular tamaño
if (mapInstance) mapInstance.invalidateSize();
}, 0);
fetchMapStations();
if (mapTimer) clearInterval(mapTimer);
mapTimer = setInterval(function () { // refrescar solo si la sección está visible
const section = document.getElementById("sec-maps");
if (section && section.classList.contains("active")) {
fetchMapStations();
}
}, 15000);
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+2 -2
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -75,7 +75,6 @@ public:
class LoraModule {
public:
bool cadActive;
bool rxActive;
long rxFreq;
int rxSpreadingFactor;
@@ -87,6 +86,7 @@ public:
int txCodingRate4;
long txSignalBandwidth;
int power;
bool cadActive;
};
class Display {
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+45
View File
@@ -0,0 +1,45 @@
/* 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 MAP_UTILS_H_
#define MAP_UTILS_H_
#include <Arduino.h>
#include <vector>
struct MapStation {
char callsign[10]; // "XX9XXX-NN" + null -> clave para deduplicar
float latitude; // grados decimales (+N / -S)
float longitude; // grados decimales (+E / -W)
char symbol[3]; // tabla + codigo APRS (ej "/>"), "" si no hay
int16_t rssi; // ultimo RSSI
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
uint32_t lastHeardMillis; // millis() -> ordenar / expirar (no se muestra)
};
namespace MAP_Utils {
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr);
String getStationsJson();
}
#endif
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+18
View File
@@ -1,3 +1,21 @@
/* 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/>.
*/
#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+4 -4
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -68,8 +68,8 @@ ___________________________________________________________________*/
#endif
String versionDate = "2026-06-10";
String versionNumber = "3.3.0";
String versionDate = "2026-07-06";
String versionNumber = "4.0.0";
Configuration Config;
WiFiClient aprsIsClient;
WiFiClient mqttClient;
@@ -196,7 +196,7 @@ void loop() {
APRS_IS_Utils::processLoRaPacket(packet); // Send received packet to APRSIS
}
if (Config.loramodule.txActive && (Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
if (Config.loramodule.txActive && (Config.digi.mode == 1 || Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+7 -8
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -97,7 +97,6 @@ bool Configuration::writeFile() {
#endif
data["digi"]["backupDigiMode"] = digi.backupDigiMode;
data["lora"]["cadActive"] = loramodule.cadActive;
data["lora"]["rxActive"] = loramodule.rxActive;
data["lora"]["rxFreq"] = loramodule.rxFreq;
data["lora"]["rxCodingRate4"] = loramodule.rxCodingRate4;
@@ -107,6 +106,7 @@ bool Configuration::writeFile() {
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
data["lora"]["power"] = loramodule.power;
data["lora"]["cadActive"] = loramodule.cadActive;
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
int txSpreadingFactor = loramodule.txSpreadingFactor;
@@ -291,9 +291,7 @@ bool Configuration::readFile() {
#endif
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
if (data["lora"]["cadActive"].isNull() ||
data["lora"]["rxActive"].isNull() ||
if (data["lora"]["rxActive"].isNull() ||
data["lora"]["rxFreq"].isNull() ||
data["lora"]["rxSpreadingFactor"].isNull() ||
data["lora"]["rxCodingRate4"].isNull() ||
@@ -303,8 +301,8 @@ bool Configuration::readFile() {
data["lora"]["txSpreadingFactor"].isNull() ||
data["lora"]["txCodingRate4"].isNull() ||
data["lora"]["txSignalBandwidth"].isNull() ||
data["lora"]["power"].isNull()) needsRewrite = true;
loramodule.cadActive = data["lora"]["cadActive"] | true;
data["lora"]["power"].isNull() ||
data["lora"]["cadActive"].isNull()) needsRewrite = true;
loramodule.rxActive = data["lora"]["rxActive"] | true;
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
@@ -316,6 +314,7 @@ bool Configuration::readFile() {
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
loramodule.power = data["lora"]["power"] | 20;
loramodule.cadActive = data["lora"]["cadActive"] | true;
if (data["display"]["alwaysOn"].isNull() ||
data["display"]["timeout"].isNull() ||
@@ -495,7 +494,6 @@ void Configuration::setDefaultValues() {
digi.ecoMode = 0;
digi.backupDigiMode = false;
loramodule.cadActive = true;
loramodule.rxActive = true;
loramodule.rxFreq = 433775000;
loramodule.rxSpreadingFactor = 12;
@@ -507,6 +505,7 @@ void Configuration::setDefaultValues() {
loramodule.txCodingRate4 = 5;
loramodule.txSignalBandwidth = 125000;
loramodule.power = 20;
loramodule.cadActive = true;
display.alwaysOn = true;
display.timeout = 4;
+48 -10
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -42,8 +42,8 @@ extern bool backupDigiMode;
namespace DIGI_Utils {
String cleanPathAsterisks(String path) {
String terms[] = {",WIDE1*", ",WIDE2*", "*"};
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("*", "");
@@ -51,6 +51,35 @@ namespace DIGI_Utils {
return path;
}
String processMode3Path(const String& path, const String& stationCallsign) {
int start = 0;
bool prevTokensAllStar = true;
int ownTokenEnd = -1;
while (start < path.length()) {
int delim = path.indexOf(',', start);
if (delim == -1) delim = path.length(); // busca todo hasta lograr encontra una coma o el final del string
String token = path.substring(start, delim);
bool tokenIsOwn = (token == stationCallsign) || (token == stationCallsign + "*");
bool tokenStar = token.endsWith("*");
if (tokenIsOwn) {
if (tokenStar) return ""; // already digipeated
if (!prevTokensAllStar) return ""; // earlier tokens must be marked
ownTokenEnd = delim;
break;
}
if (!tokenStar) prevTokensAllStar = false;
start = delim + 1;
}
if (ownTokenEnd == -1) return "";
String tempPacket = cleanPath(path.substring(0, ownTokenEnd));
return tempPacket + "*" + path.substring(ownTokenEnd);
}
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
String suffix = thirdParty ? ":}" : ":";
@@ -60,11 +89,11 @@ namespace DIGI_Utils {
int digiMode = Config.digi.mode;
String tempPath = path;
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 2 || digiMode == 3)) { // WIDE1-1
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 1 || digiMode == 2)) { // WIDE1-1
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
tempPath.replace("WIDE1-1", stationCallsign + "*");
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) { // WIDE2-n Digipeater
tempPath = cleanPathAsterisks(path);
} 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) {
@@ -72,11 +101,14 @@ namespace DIGI_Utils {
} else {
return "";
}
} else if (digiMode == 3) { // Repeat if station callsign is in path (free to repeat).
tempPath = processMode3Path(tempPath, stationCallsign);
if (tempPath == "") return "";
}
packetToRepeat = packet.substring(0, packet.indexOf(",") + 1);
packetToRepeat += tempPath;
} else { // CrossFreq Digipeater
packetToRepeat = cleanPathAsterisks(packet.substring(0, suffixIndex));
packetToRepeat = cleanPath(packet.substring(0, suffixIndex));
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
packetToRepeat += ",";
packetToRepeat += stationCallsign;
@@ -100,14 +132,14 @@ namespace DIGI_Utils {
if (commaIndex > 2) { // "path" found
const String& path = temp.substring(commaIndex + 1);
if (digiMode == 2 || backupDigiMode) {
if (digiMode == 1 || backupDigiMode) {
bool hasWide = path.indexOf("WIDE1-1") != -1;
if (hasWide || crossFreq) {
return buildPacket(path, packet, thirdParty, !hasWide);
}
return "";
}
if (digiMode == 3) {
if (digiMode == 2) {
int wide1Index = path.indexOf("WIDE1-1");
int wide2Index = path.indexOf("WIDE2-");
bool hasWide1 = wide1Index != -1;
@@ -121,10 +153,16 @@ namespace DIGI_Utils {
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);
return "";
}
return "";
}
if (commaIndex == -1 && (digiMode == 2 || backupDigiMode || digiMode == 3) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
return "";
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+8 -3
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -16,6 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <APRSPacketLib.h>
#include <RadioLib.h>
#include "configuration.h"
#include "network_manager.h"
@@ -23,13 +24,12 @@
#include "station_utils.h"
#include "board_pinout.h"
#include "syslog_utils.h"
#include "map_utils.h"
#include "ntp_utils.h"
#include "display.h"
#include "utils.h"
extern Configuration Config;
extern NetworkManager *networkManager;
extern bool packetIsBeacon;
@@ -280,6 +280,11 @@ namespace LoRa_Utils {
receivedPacket.RSSI = rssi;
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 (Config.syslog.active && networkManager->isConnected()) {
+114
View File
@@ -0,0 +1,114 @@
/* 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/>.
*/
#include <ArduinoJson.h>
#include "map_utils.h"
#include "ntp_utils.h"
#define MAX_MAP_STATIONS 50
#define STATION_TTL_MS 3600000UL // 1 hora (en milisegundos)
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) {
String t = NTP_Utils::getFormatedTime();
if (t.length() == 8 && t.charAt(2) == ':') {
t.toCharArray(dst, size);
} else {
dst[0] = '\0';
}
}
// 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
if (callsign.equals(s.callsign)) {
s.latitude = latitude;
s.longitude = longitude;
s.rssi = rssi;
s.snr = snr;
s.count++;
symbol.toCharArray(s.symbol, sizeof(s.symbol));
writeFormatedTime(s.lastHeard, sizeof(s.lastHeard));
s.lastHeardMillis = millis();
return;
}
}
if (mapStations.size() >= MAX_MAP_STATIONS) { // 2) llena -> descartar la mas vieja
size_t oldest = 0;
for (size_t i = 1; i < mapStations.size(); i++) {
if (mapStations[i].lastHeardMillis < mapStations[oldest].lastHeardMillis) {
oldest = i;
}
}
mapStations.erase(mapStations.begin() + oldest);
}
MapStation st = {}; // 3) insertar nueva
callsign.toCharArray(st.callsign, sizeof(st.callsign));
symbol.toCharArray(st.symbol, sizeof(st.symbol));
st.latitude = latitude;
st.longitude = longitude;
st.rssi = rssi;
st.snr = snr;
st.count = 1;
writeFormatedTime(st.lastHeard, sizeof(st.lastHeard));
st.lastHeardMillis = millis();
mapStations.push_back(st);
}
void purgeOldStations() {
uint32_t now = millis();
for (int i = (int)mapStations.size() - 1; i >= 0; i--) {
if (now - mapStations[i].lastHeardMillis > STATION_TTL_MS) {
mapStations.erase(mapStations.begin() + i);
}
}
}
String getStationsJson() {
purgeOldStations();
JsonDocument data;
for (size_t i = 0; i < mapStations.size(); i++) {
data[i]["callsign"] = mapStations[i].callsign;
data[i]["lat"] = mapStations[i].latitude;
data[i]["lon"] = mapStations[i].longitude;
data[i]["symbol"] = mapStations[i].symbol;
data[i]["RSSI"] = mapStations[i].rssi;
data[i]["SNR"] = mapStations[i].snr;
data[i]["count"] = mapStations[i].count;
data[i]["lastHeard"] = mapStations[i].lastHeard;
}
String buffer;
serializeJson(data, buffer);
return buffer;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+18
View File
@@ -1,3 +1,21 @@
/* 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/>.
*/
#include <Arduino.h>
#include "network_manager.h"
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+4 -4
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -82,7 +82,7 @@ namespace QUERY_Utils {
int radioTxActive = Config.loramodule.txActive;
bool onlyRadioActive = radioTxActive && Config.loramodule.rxActive && !Config.aprs_is.active;
if (queryQuestion.startsWith("?EM=OFF")) {
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
if (digiEcoMode == 1 || digiEcoMode == 2) { // Exit Digipeater EcoMode or Digipeater without WiFiAP
answer = (digiEcoMode == 1) ? "DigiEcoMode:OFF" : "Digipeater + WiFiAP enabled";
Config.digi.ecoMode = 0;
@@ -97,7 +97,7 @@ namespace QUERY_Utils {
answer = "Digipeater Mode control not possible";
}
} else if (queryQuestion.startsWith("?EM=ON")) {
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
if (digiEcoMode == 0) { // Start Digipeater EcoMode
answer = "DigiEcoMode:ON";
Config.digi.ecoMode = 1;
@@ -139,7 +139,7 @@ namespace QUERY_Utils {
if (answer == "") return "";
String queryAnswer = Config.callsign;
String queryAnswer = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
queryAnswer += ">APLRG1";
if (queryFromAPRSIS) {
queryAnswer += ",TCPIP,qAC";
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+9 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -20,6 +20,7 @@
#include "configuration.h"
#include "ota_utils.h"
#include "web_utils.h"
#include "map_utils.h"
#include "display.h"
#include "utils.h"
@@ -114,6 +115,10 @@ namespace WEB_Utils {
request->send(200, "application/json", buffer);
}
void handleStations(AsyncWebServerRequest *request) {
request->send(200, "application/json", MAP_Utils::getStationsJson());
}
void handleWriteConfiguration(AsyncWebServerRequest *request) {
Serial.println("Got new Configuration Data from www");
@@ -213,6 +218,8 @@ namespace WEB_Utils {
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
if (Config.loramodule.txActive)
Config.loramodule.cadActive = request->hasParam("lora.cadActive", true);
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
if (!Config.display.alwaysOn) {
@@ -361,6 +368,7 @@ namespace WEB_Utils {
server.on("/", HTTP_GET, handleHome);
server.on("/status", HTTP_GET, handleStatus);
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
server.on("/stations.json", HTTP_GET, handleStations);
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
server.on("/action", HTTP_POST, handleAction);
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+12 -12
View File
@@ -1,17 +1,17 @@
# Copyright (C) 2025 Ricardo Guzman - CA2RXU
#
# 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
# 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/>.
@@ -40,20 +40,20 @@ with open('src/LoRa_APRS_iGate.cpp', encoding='utf-8') as cpp_file:
if start > 0 and end > start:
versionDate = line[start:end]
break
for src in files:
out = src + ".gz"
with open(src, 'rb') as f:
content = f.read()
if src == 'data_embed/index.html':
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
content = content.replace(b'%BUILD_INFO%', build_info)
with open(out, 'wb') as f:
f.write(gzip.compress(content, compresslevel=9))
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
+1 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*

Some files were not shown because too many files have changed in this diff Show More