mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-07-14 22:01:43 +02:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e31dbbf91f |
@@ -13,12 +13,8 @@ jobs:
|
|||||||
target:
|
target:
|
||||||
- name: ttgo-lora32-v21
|
- name: ttgo-lora32-v21
|
||||||
chip: esp32
|
chip: esp32
|
||||||
- name: ttgo-lora32-v21_GPS
|
|
||||||
chip: esp32
|
|
||||||
- name: ttgo-lora32-v21_915
|
- name: ttgo-lora32-v21_915
|
||||||
chip: esp32
|
chip: esp32
|
||||||
- name: ttgo-lora32-v21_915_GPS
|
|
||||||
chip: esp32
|
|
||||||
- name: ttgo_lora32_t3s3_v1_2
|
- name: ttgo_lora32_t3s3_v1_2
|
||||||
chip: esp32s3
|
chip: esp32s3
|
||||||
- name: heltec-lora32-v2
|
- name: heltec-lora32-v2
|
||||||
@@ -59,8 +55,6 @@ jobs:
|
|||||||
chip: esp32
|
chip: esp32
|
||||||
- name: ttgo-t-beam-v1_SX1268
|
- name: ttgo-t-beam-v1_SX1268
|
||||||
chip: esp32
|
chip: esp32
|
||||||
- name: ttgo-t-beam-v1_SX1262
|
|
||||||
chip: esp32
|
|
||||||
- name: ttgo-t-beam-v1_2_SX1262
|
- name: ttgo-t-beam-v1_2_SX1262
|
||||||
chip: esp32
|
chip: esp32
|
||||||
- name: ttgo_t_deck_plus
|
- name: ttgo_t_deck_plus
|
||||||
@@ -101,8 +95,6 @@ jobs:
|
|||||||
chip: esp32s3
|
chip: esp32s3
|
||||||
- name: QRPLabs_LightGateway_Plus_1_0
|
- name: QRPLabs_LightGateway_Plus_1_0
|
||||||
chip: esp32s3
|
chip: esp32s3
|
||||||
- name: RPC_LORA_DIGIGATE_1W
|
|
||||||
chip: esp32
|
|
||||||
- name: XIAO_ESP32S3_WIO_SX1262
|
- name: XIAO_ESP32S3_WIO_SX1262
|
||||||
chip: esp32s3
|
chip: esp32s3
|
||||||
- name: TROY_LoRa_APRS
|
- name: TROY_LoRa_APRS
|
||||||
@@ -121,7 +113,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build target
|
- name: Build target
|
||||||
run: |
|
run: |
|
||||||
pio run -e ${{ matrix.target.name }}
|
pio run -e ${{ matrix.target.name }}
|
||||||
|
|
||||||
- name: Build FS
|
- name: Build FS
|
||||||
run: |
|
run: |
|
||||||
@@ -136,7 +128,7 @@ jobs:
|
|||||||
cp .pio/build/${{ matrix.target.name }}/partitions.bin installer/firmware/
|
cp .pio/build/${{ matrix.target.name }}/partitions.bin installer/firmware/
|
||||||
cp .pio/build/${{ matrix.target.name }}/spiffs.bin installer/firmware/
|
cp .pio/build/${{ matrix.target.name }}/spiffs.bin installer/firmware/
|
||||||
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin installer/firmware/
|
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin installer/firmware/
|
||||||
|
|
||||||
- name: Merge for web flashing
|
- name: Merge for web flashing
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ matrix.target.chip }}" == "esp32" ]; then
|
if [ "${{ matrix.target.chip }}" == "esp32" ]; then
|
||||||
|
|||||||
@@ -51,9 +51,6 @@ ____________________________________________________
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
# Timeline (Versions):
|
# 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.
|
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
|
||||||
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
|
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
|
||||||
- 2026-02-15 Digipeater code/logic improved.
|
- 2026-02-15 Digipeater code/logic improved.
|
||||||
|
|||||||
+4
-4
@@ -32,16 +32,16 @@ lib_deps =
|
|||||||
adafruit/Adafruit Si7021 Library @ 1.5.3
|
adafruit/Adafruit Si7021 Library @ 1.5.3
|
||||||
arduino-libraries/NTPClient @ 3.2.1
|
arduino-libraries/NTPClient @ 3.2.1
|
||||||
ayushsharma82/ElegantOTA @ 3.1.7
|
ayushsharma82/ElegantOTA @ 3.1.7
|
||||||
bblanchon/ArduinoJson @ 7.4.3
|
bblanchon/ArduinoJson @ 6.21.3
|
||||||
jgromes/RadioLib @ 7.6.0
|
jgromes/RadioLib @ 7.6.0
|
||||||
knolleary/PubSubClient @ 2.8
|
knolleary/PubSubClient @ 2.8
|
||||||
ESP32Async/AsyncTCP @ 3.4.10
|
ESP32Async/AsyncTCP @ 3.4.10
|
||||||
ESP32Async/ESPAsyncWebServer @ 3.11.1
|
ESP32Async/ESPAsyncWebServer @ 3.10.0
|
||||||
mikalhart/TinyGPSPlus @ 1.0.3
|
mikalhart/TinyGPSPlus @ 1.0.3
|
||||||
richonguzman/APRSPacketLib @ 1.0.4
|
richonguzman/APRSPacketLib @ 1.0.4
|
||||||
display_libs =
|
display_libs =
|
||||||
adafruit/Adafruit GFX Library @ 1.12.6
|
adafruit/Adafruit GFX Library @ 1.11.9
|
||||||
adafruit/Adafruit SSD1306 @ 2.5.17
|
adafruit/Adafruit SSD1306 @ 2.5.10
|
||||||
usb_flags=
|
usb_flags=
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
"messagesToRF": false,
|
"messagesToRF": false,
|
||||||
"objectsToRF": false,
|
"objectsToRF": false,
|
||||||
"server": "rotate.aprs2.net",
|
"server": "rotate.aprs2.net",
|
||||||
"passcode": "XYZVW",
|
"passcode": "XYZVW",
|
||||||
"port": 14580,
|
"port": 14580,
|
||||||
"filter": "m/10"
|
"filter": "m/10"
|
||||||
},
|
},
|
||||||
"personalNote": "",
|
"personalNote": "",
|
||||||
"blacklist": "",
|
"blacklist": "",
|
||||||
@@ -47,11 +47,10 @@
|
|||||||
"rxSignalBandwidth": 125000,
|
"rxSignalBandwidth": 125000,
|
||||||
"txActive": false,
|
"txActive": false,
|
||||||
"txFreq": 433775000,
|
"txFreq": 433775000,
|
||||||
"txSpreadingFactor": 9,
|
"txSpreadingFactor": 12,
|
||||||
"txCodingRate4": 7,
|
"txCodingRate4": 5,
|
||||||
"txSignalBandwidth": 125000,
|
"txSignalBandwidth": 125000,
|
||||||
"power": 20,
|
"power": 20
|
||||||
"cadActive": true
|
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"alwaysOn": true,
|
"alwaysOn": true,
|
||||||
|
|||||||
+949
-1090
File diff suppressed because one or more lines are too long
+7
-226
@@ -118,7 +118,7 @@ function loadSettings(settings) {
|
|||||||
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
|
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
|
||||||
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
|
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
|
||||||
document.getElementById("beacon.interval").value = settings.beacon.interval;
|
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.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
|
||||||
|
|
||||||
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
|
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
|
||||||
@@ -148,26 +148,12 @@ function loadSettings(settings) {
|
|||||||
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
|
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
|
||||||
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
|
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
|
||||||
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
|
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.txActive").checked = settings.lora.txActive;
|
||||||
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
|
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
|
||||||
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
|
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
|
||||||
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
|
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.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
|
// Display
|
||||||
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
|
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
|
||||||
@@ -209,7 +195,7 @@ function loadSettings(settings) {
|
|||||||
TelemetryCheckbox.checked = settings.wxsensor.active;
|
TelemetryCheckbox.checked = settings.wxsensor.active;
|
||||||
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
|
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
|
||||||
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
|
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
|
||||||
|
|
||||||
// SYSLOG
|
// SYSLOG
|
||||||
document.getElementById("syslog.active").checked = settings.syslog.active;
|
document.getElementById("syslog.active").checked = settings.syslog.active;
|
||||||
document.getElementById("syslog.server").value = settings.syslog.server;
|
document.getElementById("syslog.server").value = settings.syslog.server;
|
||||||
@@ -219,7 +205,7 @@ function loadSettings(settings) {
|
|||||||
SyslogServer.disabled = !SyslogCheckbox.checked;
|
SyslogServer.disabled = !SyslogCheckbox.checked;
|
||||||
SyslogPort.disabled = !SyslogCheckbox.checked;
|
SyslogPort.disabled = !SyslogCheckbox.checked;
|
||||||
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
|
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
|
||||||
|
|
||||||
// TNC
|
// TNC
|
||||||
if (settings.tnc) {
|
if (settings.tnc) {
|
||||||
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
|
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
|
||||||
@@ -251,10 +237,8 @@ function loadSettings(settings) {
|
|||||||
RebootModeTime.disabled = !RebootModeCheckbox.check;
|
RebootModeTime.disabled = !RebootModeCheckbox.check;
|
||||||
|
|
||||||
// WiFi Auto AP
|
// WiFi Auto AP
|
||||||
document.getElementById("wifi.autoAP.enabled").checked = settings.wifi.autoAP.enabled;
|
|
||||||
document.getElementById("wifi.autoAP.password").value = settings.wifi.autoAP.password;
|
document.getElementById("wifi.autoAP.password").value = settings.wifi.autoAP.password;
|
||||||
document.getElementById("wifi.autoAP.timeout").value = settings.wifi.autoAP.timeout;
|
document.getElementById("wifi.autoAP.timeout").value = settings.wifi.autoAP.timeout;
|
||||||
toggleWiFiAutoAPFields();
|
|
||||||
|
|
||||||
// OTA
|
// OTA
|
||||||
document.getElementById("ota.username").value = settings.ota.username;
|
document.getElementById("ota.username").value = settings.ota.username;
|
||||||
@@ -404,36 +388,6 @@ TelemetryCheckbox.addEventListener("change", function () {
|
|||||||
TelemetryTempCorrection.disabled = !this.checked;
|
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
|
// Syslog Switches
|
||||||
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
|
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
|
||||||
const SyslogServer = document.querySelector('input[name="syslog.server"]');
|
const SyslogServer = document.querySelector('input[name="syslog.server"]');
|
||||||
@@ -478,18 +432,6 @@ WebadminCheckbox.addEventListener("change", function () {
|
|||||||
WebadminPassword.disabled = !this.checked;
|
WebadminPassword.disabled = !this.checked;
|
||||||
});
|
});
|
||||||
|
|
||||||
// WiFi Auto AP Switches
|
|
||||||
const WiFiAutoAPCheckbox = document.querySelector('input[name="wifi.autoAP.enabled"]');
|
|
||||||
WiFiAutoAPCheckbox.addEventListener("change", function () {
|
|
||||||
toggleWiFiAutoAPFields();
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggleWiFiAutoAPFields() {
|
|
||||||
const isEnabled = WiFiAutoAPCheckbox.checked;
|
|
||||||
const autoAPConfig = document.getElementById('wifi-autoap-config');
|
|
||||||
if (autoAPConfig) autoAPConfig.style.display = isEnabled ? 'block' : 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
document.querySelector(".new button").addEventListener("click", function () {
|
document.querySelector(".new button").addEventListener("click", function () {
|
||||||
const networksContainer = document.querySelector(".list-networks");
|
const networksContainer = document.querySelector(".list-networks");
|
||||||
@@ -603,7 +545,7 @@ function loadReceivedPackets(packets) {
|
|||||||
|
|
||||||
packets.forEach((packet) => {
|
packets.forEach((packet) => {
|
||||||
const element = document.createElement("tr");
|
const element = document.createElement("tr");
|
||||||
|
|
||||||
element.innerHTML = `
|
element.innerHTML = `
|
||||||
<td>${packet.rxTime}</td>
|
<td>${packet.rxTime}</td>
|
||||||
<td>${packet.packet}</td>
|
<td>${packet.packet}</td>
|
||||||
@@ -636,169 +578,8 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
|
|||||||
|
|
||||||
document.getElementById('received-packets').classList.remove('d-none');
|
document.getElementById('received-packets').classList.remove('d-none');
|
||||||
document.getElementById('configuration').classList.add('d-none');
|
document.getElementById('configuration').classList.add('d-none');
|
||||||
|
|
||||||
document.querySelector('button[type=submit]').remove();
|
document.querySelector('button[type=submit]').remove();
|
||||||
|
|
||||||
fetchReceivedPackets();
|
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 — 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: "© 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,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -32,7 +32,6 @@ public:
|
|||||||
|
|
||||||
class WiFi_Auto_AP {
|
class WiFi_Auto_AP {
|
||||||
public:
|
public:
|
||||||
bool enabled; // Enable Auto AP
|
|
||||||
String password;
|
String password;
|
||||||
int timeout;
|
int timeout;
|
||||||
};
|
};
|
||||||
@@ -86,7 +85,6 @@ public:
|
|||||||
int txCodingRate4;
|
int txCodingRate4;
|
||||||
long txSignalBandwidth;
|
long txSignalBandwidth;
|
||||||
int power;
|
int power;
|
||||||
bool cadActive;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Display {
|
class Display {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
/* 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,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
/* 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>
|
|
||||||
#include <ETH.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class for managing network connections
|
|
||||||
*/
|
|
||||||
class NetworkManager
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
class WiFiNetwork {
|
|
||||||
public:
|
|
||||||
String ssid;
|
|
||||||
String psk;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool _wifiAPmode = false;
|
|
||||||
bool _wifiSTAmode = false;
|
|
||||||
bool _ethernetMode = false;
|
|
||||||
bool _ethernetConnected = false;
|
|
||||||
unsigned long _apStartup = 0;
|
|
||||||
unsigned long _apTimeout = 0;
|
|
||||||
|
|
||||||
String _hostName = "";
|
|
||||||
std::vector<WiFiNetwork> _wifiNetworks;
|
|
||||||
|
|
||||||
int _findWiFiNetworkIndex(const String& ssid) const;
|
|
||||||
bool _connectWiFi(const WiFiNetwork& network);
|
|
||||||
void _processAPTimeout();
|
|
||||||
void _onNetworkEvent(arduino_event_id_t event, arduino_event_info_t /*info*/);
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Constructor
|
|
||||||
NetworkManager();
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
~NetworkManager();
|
|
||||||
|
|
||||||
// Initialize network module
|
|
||||||
bool setup();
|
|
||||||
void loop();
|
|
||||||
|
|
||||||
void setHostName(const String& hostName);
|
|
||||||
|
|
||||||
// WiFi methods
|
|
||||||
bool setupAP(String apName, String apPsk = "");
|
|
||||||
bool disableAP();
|
|
||||||
void setAPTimeout(unsigned long timeout);
|
|
||||||
void addWiFiNetwork(const String& ssid, const String& psk = "");
|
|
||||||
void clearWiFiNetworks();
|
|
||||||
bool hasWiFiNetworks() const;
|
|
||||||
size_t getWiFiNetworkCount() const;
|
|
||||||
bool connectWiFi();
|
|
||||||
bool connectWiFi(const String& ssid, const String& psk = "");
|
|
||||||
bool disconnectWiFi();
|
|
||||||
String getWiFiSSID() const;
|
|
||||||
String getWiFiAPSSID() const;
|
|
||||||
IPAddress getWiFiIP() const;
|
|
||||||
IPAddress getWiFiAPIP() const;
|
|
||||||
wifi_mode_t getWiFiMode() const;
|
|
||||||
uint8_t* getWiFimacAddress(uint8_t* mac);
|
|
||||||
String getWiFimacAddress(void) const;
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Check if any network is available
|
|
||||||
bool isConnected() const;
|
|
||||||
|
|
||||||
// Check if specific network is connected
|
|
||||||
bool isWiFiConnected() const;
|
|
||||||
bool isEthernetConnected() const;
|
|
||||||
bool isModemConnected() const;
|
|
||||||
|
|
||||||
bool isWifiAPActive() const;
|
|
||||||
};
|
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
namespace NTP_Utils {
|
namespace NTP_Utils {
|
||||||
|
|
||||||
bool setup();
|
void setup();
|
||||||
void update();
|
void update();
|
||||||
String getFormatedTime();
|
String getFormatedTime();
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -27,6 +27,7 @@ namespace WIFI_Utils {
|
|||||||
void checkWiFi();
|
void checkWiFi();
|
||||||
void startAutoAP();
|
void startAutoAP();
|
||||||
void startWiFi();
|
void startWiFi();
|
||||||
|
void checkAutoAPTimeout();
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+2
-2
@@ -20,13 +20,13 @@ platform = espressif32 @ 6.12.0
|
|||||||
board_build.partitions = min_spiffs.csv
|
board_build.partitions = min_spiffs.csv
|
||||||
framework = arduino
|
framework = arduino
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
board_build.embed_files =
|
board_build.embed_files =
|
||||||
data_embed/index.html.gz
|
data_embed/index.html.gz
|
||||||
data_embed/style.css.gz
|
data_embed/style.css.gz
|
||||||
data_embed/script.js.gz
|
data_embed/script.js.gz
|
||||||
data_embed/bootstrap.css.gz
|
data_embed/bootstrap.css.gz
|
||||||
data_embed/bootstrap.js.gz
|
data_embed/bootstrap.js.gz
|
||||||
data_embed/favicon.png.gz
|
data_embed/favicon.png.gz
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:tools/compress.py
|
pre:tools/compress.py
|
||||||
debug_tool = esp-prog
|
debug_tool = esp-prog
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+11
-19
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -41,10 +41,9 @@ ___________________________________________________________________*/
|
|||||||
#include <ElegantOTA.h>
|
#include <ElegantOTA.h>
|
||||||
#include <TinyGPS++.h>
|
#include <TinyGPS++.h>
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFi.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "aprs_is_utils.h"
|
#include "aprs_is_utils.h"
|
||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
#include "battery_utils.h"
|
#include "battery_utils.h"
|
||||||
@@ -68,8 +67,8 @@ ___________________________________________________________________*/
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
String versionDate = "2026-06-29";
|
String versionDate = "2026-03-08";
|
||||||
String versionNumber = "4.0.0";
|
String versionNumber = "3.2.103";
|
||||||
Configuration Config;
|
Configuration Config;
|
||||||
WiFiClient aprsIsClient;
|
WiFiClient aprsIsClient;
|
||||||
WiFiClient mqttClient;
|
WiFiClient mqttClient;
|
||||||
@@ -80,7 +79,9 @@ WiFiClient mqttClient;
|
|||||||
bool gpsInfoToggle = false;
|
bool gpsInfoToggle = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NetworkManager *networkManager;
|
uint8_t myWiFiAPIndex = 0;
|
||||||
|
int myWiFiAPSize = Config.wifiAPs.size();
|
||||||
|
WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
|
||||||
|
|
||||||
bool isUpdatingOTA = false;
|
bool isUpdatingOTA = false;
|
||||||
uint32_t lastBatteryCheck = 0;
|
uint32_t lastBatteryCheck = 0;
|
||||||
@@ -101,12 +102,6 @@ String firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seven
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
Config.setup();
|
Config.setup();
|
||||||
networkManager = new NetworkManager();
|
|
||||||
networkManager->setup();
|
|
||||||
if (Config.wifiAutoAP.enabled) {
|
|
||||||
networkManager->setAPTimeout(Config.wifiAutoAP.timeout * 60 * 1000); // Convert minutes to milliseconds
|
|
||||||
}
|
|
||||||
networkManager->setHostName("iGATE-" + Config.callsign);
|
|
||||||
POWER_Utils::setup();
|
POWER_Utils::setup();
|
||||||
Utils::setupDisplay();
|
Utils::setupDisplay();
|
||||||
LoRa_Utils::setup();
|
LoRa_Utils::setup();
|
||||||
@@ -138,7 +133,7 @@ void loop() {
|
|||||||
Utils::checkSleepByLowBatteryVoltage(1);
|
Utils::checkSleepByLowBatteryVoltage(1);
|
||||||
SLEEP_Utils::startSleeping();
|
SLEEP_Utils::startSleeping();
|
||||||
} else {
|
} else {
|
||||||
networkManager->loop();
|
WIFI_Utils::checkAutoAPTimeout();
|
||||||
|
|
||||||
if (isUpdatingOTA) {
|
if (isUpdatingOTA) {
|
||||||
ElegantOTA.loop();
|
ElegantOTA.loop();
|
||||||
@@ -167,14 +162,11 @@ void loop() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAS_A7670
|
#ifdef HAS_A7670
|
||||||
// TODO: Make this part of Network manager, and use ESP-IDF network stack instead manual AT commands
|
|
||||||
if (Config.aprs_is.active && !modemLoggedToAPRSIS) A7670_Utils::APRS_IS_connect();
|
if (Config.aprs_is.active && !modemLoggedToAPRSIS) A7670_Utils::APRS_IS_connect();
|
||||||
#else
|
#else
|
||||||
WIFI_Utils::checkWiFi();
|
WIFI_Utils::checkWiFi();
|
||||||
if (networkManager->isConnected()) {
|
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) APRS_IS_Utils::connect();
|
||||||
if (Config.aprs_is.active && !aprsIsClient.connected()) APRS_IS_Utils::connect();
|
if (Config.mqtt.active && (WiFi.status() == WL_CONNECTED) && !mqttClient.connected()) MQTT_Utils::connect();
|
||||||
if (Config.mqtt.active && !mqttClient.connected()) MQTT_Utils::connect();
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
NTP_Utils::update();
|
NTP_Utils::update();
|
||||||
@@ -196,7 +188,7 @@ void loop() {
|
|||||||
APRS_IS_Utils::processLoRaPacket(packet); // Send received packet to APRSIS
|
APRS_IS_Utils::processLoRaPacket(packet); // Send received packet to APRSIS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.loramodule.txActive && (Config.digi.mode == 1 || Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
|
if (Config.loramodule.txActive && (Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
|
||||||
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
|
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -17,9 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <APRSPacketLib.h>
|
#include <APRSPacketLib.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFi.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "aprs_is_utils.h"
|
#include "aprs_is_utils.h"
|
||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
#include "board_pinout.h"
|
#include "board_pinout.h"
|
||||||
@@ -33,7 +32,6 @@
|
|||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
|
||||||
extern WiFiClient aprsIsClient;
|
extern WiFiClient aprsIsClient;
|
||||||
extern uint32_t lastScreenOn;
|
extern uint32_t lastScreenOn;
|
||||||
extern String firstLine;
|
extern String firstLine;
|
||||||
@@ -47,6 +45,7 @@ extern bool modemLoggedToAPRSIS;
|
|||||||
extern bool backupDigiMode;
|
extern bool backupDigiMode;
|
||||||
extern String versionNumber;
|
extern String versionNumber;
|
||||||
|
|
||||||
|
uint32_t lastRxTime = millis();
|
||||||
bool passcodeValid = false;
|
bool passcodeValid = false;
|
||||||
uint32_t lastServerCheck = 0;
|
uint32_t lastServerCheck = 0;
|
||||||
|
|
||||||
@@ -94,7 +93,7 @@ namespace APRS_IS_Utils {
|
|||||||
|
|
||||||
void checkStatus() {
|
void checkStatus() {
|
||||||
String wifiState, aprsisState;
|
String wifiState, aprsisState;
|
||||||
if (networkManager->isWiFiConnected()) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
wifiState = "OK";
|
wifiState = "OK";
|
||||||
} else {
|
} else {
|
||||||
if (backupDigiMode || Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
|
if (backupDigiMode || Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
|
||||||
@@ -402,13 +401,14 @@ namespace APRS_IS_Utils {
|
|||||||
String aprsisPacket = aprsIsClient.readStringUntil('\r');
|
String aprsisPacket = aprsIsClient.readStringUntil('\r');
|
||||||
aprsisPacket.trim(); // Serial.println(aprsisPacket);
|
aprsisPacket.trim(); // Serial.println(aprsisPacket);
|
||||||
processAPRSISPacket(aprsisPacket);
|
processAPRSISPacket(aprsisPacket);
|
||||||
|
lastRxTime = millis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void firstConnection() {
|
void firstConnection() {
|
||||||
if (Config.aprs_is.active && networkManager->isConnected() && !aprsIsClient.connected()) {
|
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) {
|
||||||
connect();
|
connect();
|
||||||
while (!passcodeValid) {
|
while (!passcodeValid) {
|
||||||
listenAPRSIS();
|
listenAPRSIS();
|
||||||
@@ -416,4 +416,4 @@ namespace APRS_IS_Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -46,7 +46,7 @@ Adafruit_INA219 ina219;
|
|||||||
#ifdef HAS_ADC_CALIBRATION
|
#ifdef HAS_ADC_CALIBRATION
|
||||||
#include <esp_adc_cal.h>
|
#include <esp_adc_cal.h>
|
||||||
|
|
||||||
#if defined(TTGO_LORA32_V2_1) || defined(TTGO_LORA32_V2_1_GPS) || defined(TTGO_LORA32_V2_1_915) || defined(TTGO_LORA32_V2_1_915_GPS)
|
#if defined(TTGO_LORA32_V2_1) || defined(TTGO_LORA32_V2_1_915)
|
||||||
#define InternalBattery_ADC_Channel ADC1_CHANNEL_7 // t_lora32 pin35
|
#define InternalBattery_ADC_Channel ADC1_CHANNEL_7 // t_lora32 pin35
|
||||||
#define ExternalVoltage_ADC_Channel ADC1_CHANNEL_6 // t_lora32 pin34
|
#define ExternalVoltage_ADC_Channel ADC1_CHANNEL_6 // t_lora32 pin34
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+110
-119
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -26,10 +26,29 @@
|
|||||||
bool shouldSleepStop = true;
|
bool shouldSleepStop = true;
|
||||||
|
|
||||||
|
|
||||||
|
void Configuration::setup() {
|
||||||
|
if (!SPIFFS.begin(false)) {
|
||||||
|
Serial.println("SPIFFS Mount Failed");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Serial.println("SPIFFS Mounted");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool exists = SPIFFS.exists("/igate_conf.json");
|
||||||
|
if (!exists) {
|
||||||
|
setDefaultValues();
|
||||||
|
writeFile();
|
||||||
|
delay(1000);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile();
|
||||||
|
}
|
||||||
|
|
||||||
bool Configuration::writeFile() {
|
bool Configuration::writeFile() {
|
||||||
Serial.println("Saving configuration...");
|
Serial.println("Saving configuration...");
|
||||||
|
|
||||||
JsonDocument data;
|
StaticJsonDocument<3584> data;
|
||||||
File configFile = SPIFFS.open("/igate_conf.json", "w");
|
File configFile = SPIFFS.open("/igate_conf.json", "w");
|
||||||
|
|
||||||
if (!configFile) {
|
if (!configFile) {
|
||||||
@@ -47,7 +66,6 @@ bool Configuration::writeFile() {
|
|||||||
|
|
||||||
data["other"]["startupDelay"] = startupDelay;
|
data["other"]["startupDelay"] = startupDelay;
|
||||||
|
|
||||||
data["wifi"]["autoAP"]["enabled"] = wifiAutoAP.enabled;
|
|
||||||
data["wifi"]["autoAP"]["password"] = wifiAutoAP.password;
|
data["wifi"]["autoAP"]["password"] = wifiAutoAP.password;
|
||||||
data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout;
|
data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout;
|
||||||
|
|
||||||
@@ -80,9 +98,6 @@ bool Configuration::writeFile() {
|
|||||||
data["beacon"]["statusPacket"] = beacon.statusPacket;
|
data["beacon"]["statusPacket"] = beacon.statusPacket;
|
||||||
|
|
||||||
data["beacon"]["gpsActive"] = beacon.gpsActive;
|
data["beacon"]["gpsActive"] = beacon.gpsActive;
|
||||||
#if !defined(HAS_GPS)
|
|
||||||
data["beacon"]["gpsActive"] = false;
|
|
||||||
#endif
|
|
||||||
data["beacon"]["ambiguityLevel"] = beacon.ambiguityLevel;
|
data["beacon"]["ambiguityLevel"] = beacon.ambiguityLevel;
|
||||||
|
|
||||||
data["personalNote"] = personalNote;
|
data["personalNote"] = personalNote;
|
||||||
@@ -106,7 +121,6 @@ bool Configuration::writeFile() {
|
|||||||
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
|
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
|
||||||
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
|
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
|
||||||
data["lora"]["power"] = loramodule.power;
|
data["lora"]["power"] = loramodule.power;
|
||||||
data["lora"]["cadActive"] = loramodule.cadActive;
|
|
||||||
|
|
||||||
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
|
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
|
||||||
int txSpreadingFactor = loramodule.txSpreadingFactor;
|
int txSpreadingFactor = loramodule.txSpreadingFactor;
|
||||||
@@ -200,7 +214,8 @@ bool Configuration::readFile() {
|
|||||||
|
|
||||||
if (configFile) {
|
if (configFile) {
|
||||||
bool needsRewrite = false;
|
bool needsRewrite = false;
|
||||||
JsonDocument data;
|
StaticJsonDocument<3584> data;
|
||||||
|
|
||||||
DeserializationError error = deserializeJson(data, configFile);
|
DeserializationError error = deserializeJson(data, configFile);
|
||||||
if (error) {
|
if (error) {
|
||||||
Serial.println("Failed to read file, using default configuration");
|
Serial.println("Failed to read file, using default configuration");
|
||||||
@@ -215,28 +230,26 @@ bool Configuration::readFile() {
|
|||||||
wifiAPs.push_back(wifiap);
|
wifiAPs.push_back(wifiap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data["other"]["startupDelay"].isNull()) needsRewrite = true;
|
if (!data["other"].containsKey("startupDelay")) needsRewrite = true;
|
||||||
startupDelay = data["other"]["startupDelay"] | 0;
|
startupDelay = data["other"]["startupDelay"] | 0;
|
||||||
|
|
||||||
if (data["wifi"]["autoAP"]["enabled"].isNull() ||
|
if (!data["wifi"]["autoAP"].containsKey("password") ||
|
||||||
data["wifi"]["autoAP"]["password"].isNull() ||
|
!data["wifi"]["autoAP"].containsKey("timeout")) needsRewrite = true;
|
||||||
data["wifi"]["autoAP"]["timeout"].isNull()) needsRewrite = true;
|
|
||||||
wifiAutoAP.enabled = data["wifi"]["autoAP"]["enabled"] | true;
|
|
||||||
wifiAutoAP.password = data["wifi"]["autoAP"]["password"] | "1234567890";
|
wifiAutoAP.password = data["wifi"]["autoAP"]["password"] | "1234567890";
|
||||||
wifiAutoAP.timeout = data["wifi"]["autoAP"]["timeout"] | 10;
|
wifiAutoAP.timeout = data["wifi"]["autoAP"]["timeout"] | 10;
|
||||||
|
|
||||||
if (data["callsign"].isNull()) needsRewrite = true;
|
if (!data.containsKey("callsign")) needsRewrite = true;
|
||||||
callsign = data["callsign"] | "NOCALL-10";
|
callsign = data["callsign"] | "NOCALL-10";
|
||||||
if (data["tacticalCallsign"].isNull()) needsRewrite = true;
|
if (!data.containsKey("tacticalCallsign")) needsRewrite = true;
|
||||||
tacticalCallsign = data["tacticalCallsign"] | "";
|
tacticalCallsign = data["tacticalCallsign"] | "";
|
||||||
|
|
||||||
if (data["aprs_is"]["active"].isNull() ||
|
if (!data["aprs_is"].containsKey("active") ||
|
||||||
data["aprs_is"]["passcode"].isNull() ||
|
!data["aprs_is"].containsKey("passcode") ||
|
||||||
data["aprs_is"]["server"].isNull() ||
|
!data["aprs_is"].containsKey("server") ||
|
||||||
data["aprs_is"]["port"].isNull() ||
|
!data["aprs_is"].containsKey("port") ||
|
||||||
data["aprs_is"]["filter"].isNull() ||
|
!data["aprs_is"].containsKey("filter") ||
|
||||||
data["aprs_is"]["messagesToRF"].isNull() ||
|
!data["aprs_is"].containsKey("messagesToRF") ||
|
||||||
data["aprs_is"]["objectsToRF"].isNull()) needsRewrite = true;
|
!data["aprs_is"].containsKey("objectsToRF")) needsRewrite = true;
|
||||||
aprs_is.active = data["aprs_is"]["active"] | false;
|
aprs_is.active = data["aprs_is"]["active"] | false;
|
||||||
aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV";
|
aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV";
|
||||||
aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net";
|
aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net";
|
||||||
@@ -245,20 +258,20 @@ bool Configuration::readFile() {
|
|||||||
aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false;
|
aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false;
|
||||||
aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false;
|
aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false;
|
||||||
|
|
||||||
if (data["beacon"]["latitude"].isNull() ||
|
if (!data["beacon"].containsKey("latitude") ||
|
||||||
data["beacon"]["longitude"].isNull() ||
|
!data["beacon"].containsKey("longitude") ||
|
||||||
data["beacon"]["comment"].isNull() ||
|
!data["beacon"].containsKey("comment") ||
|
||||||
data["beacon"]["interval"].isNull() ||
|
!data["beacon"].containsKey("interval") ||
|
||||||
data["beacon"]["overlay"].isNull() ||
|
!data["beacon"].containsKey("overlay") ||
|
||||||
data["beacon"]["symbol"].isNull() ||
|
!data["beacon"].containsKey("symbol") ||
|
||||||
data["beacon"]["path"].isNull() ||
|
!data["beacon"].containsKey("path") ||
|
||||||
data["beacon"]["sendViaAPRSIS"].isNull() ||
|
!data["beacon"].containsKey("sendViaAPRSIS") ||
|
||||||
data["beacon"]["sendViaRF"].isNull() ||
|
!data["beacon"].containsKey("sendViaRF") ||
|
||||||
data["beacon"]["beaconFreq"].isNull() ||
|
!data["beacon"].containsKey("beaconFreq") ||
|
||||||
data["beacon"]["statusActive"].isNull() ||
|
!data["beacon"].containsKey("statusActive") ||
|
||||||
data["beacon"]["statusPacket"].isNull() ||
|
!data["beacon"].containsKey("statusPacket") ||
|
||||||
data["beacon"]["gpsActive"].isNull() ||
|
!data["beacon"].containsKey("gpsActive") ||
|
||||||
data["beacon"]["ambiguityLevel"].isNull()) needsRewrite = true;
|
!data["beacon"].containsKey("ambiguityLevel")) needsRewrite = true;
|
||||||
beacon.latitude = data["beacon"]["latitude"] | 0.0;
|
beacon.latitude = data["beacon"]["latitude"] | 0.0;
|
||||||
beacon.longitude = data["beacon"]["longitude"] | 0.0;
|
beacon.longitude = data["beacon"]["longitude"] | 0.0;
|
||||||
beacon.comment = data["beacon"]["comment"] | "LoRa APRS";
|
beacon.comment = data["beacon"]["comment"] | "LoRa APRS";
|
||||||
@@ -274,15 +287,15 @@ bool Configuration::readFile() {
|
|||||||
beacon.gpsActive = data["beacon"]["gpsActive"] | false;
|
beacon.gpsActive = data["beacon"]["gpsActive"] | false;
|
||||||
beacon.ambiguityLevel = data["beacon"]["ambiguityLevel"] | 0;
|
beacon.ambiguityLevel = data["beacon"]["ambiguityLevel"] | 0;
|
||||||
|
|
||||||
if (data["personalNote"].isNull()) needsRewrite = true;
|
if (!data.containsKey("personalNote")) needsRewrite = true;
|
||||||
personalNote = data["personalNote"] | "personal note here";
|
personalNote = data["personalNote"] | "personal note here";
|
||||||
|
|
||||||
if (data["blacklist"].isNull()) needsRewrite = true;
|
if (!data.containsKey("blacklist")) needsRewrite = true;
|
||||||
blacklist = data["blacklist"] | "station callsign";
|
blacklist = data["blacklist"] | "station callsign";
|
||||||
|
|
||||||
if (data["digi"]["mode"].isNull() ||
|
if (!data["digi"].containsKey("mode") ||
|
||||||
data["digi"]["ecoMode"].isNull() ||
|
!data["digi"].containsKey("ecoMode") ||
|
||||||
data["digi"]["backupDigiMode"].isNull()) needsRewrite = true;
|
!data["digi"].containsKey("backupDigiMode")) needsRewrite = true;
|
||||||
digi.mode = data["digi"]["mode"] | 0;
|
digi.mode = data["digi"]["mode"] | 0;
|
||||||
digi.ecoMode = data["digi"]["ecoMode"] | 0;
|
digi.ecoMode = data["digi"]["ecoMode"] | 0;
|
||||||
if (digi.ecoMode == 1) shouldSleepStop = false;
|
if (digi.ecoMode == 1) shouldSleepStop = false;
|
||||||
@@ -291,18 +304,18 @@ bool Configuration::readFile() {
|
|||||||
#endif
|
#endif
|
||||||
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
|
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
|
||||||
|
|
||||||
if (data["lora"]["rxActive"].isNull() ||
|
|
||||||
data["lora"]["rxFreq"].isNull() ||
|
if (!data["lora"].containsKey("rxActive") ||
|
||||||
data["lora"]["rxSpreadingFactor"].isNull() ||
|
!data["lora"].containsKey("rxFreq") ||
|
||||||
data["lora"]["rxCodingRate4"].isNull() ||
|
!data["lora"].containsKey("rxSpreadingFactor") ||
|
||||||
data["lora"]["rxSignalBandwidth"].isNull() ||
|
!data["lora"].containsKey("rxCodingRate4") ||
|
||||||
data["lora"]["txActive"].isNull() ||
|
!data["lora"].containsKey("rxSignalBandwidth") ||
|
||||||
data["lora"]["txFreq"].isNull() ||
|
!data["lora"].containsKey("txActive") ||
|
||||||
data["lora"]["txSpreadingFactor"].isNull() ||
|
!data["lora"].containsKey("txFreq") ||
|
||||||
data["lora"]["txCodingRate4"].isNull() ||
|
!data["lora"].containsKey("txSpreadingFactor") ||
|
||||||
data["lora"]["txSignalBandwidth"].isNull() ||
|
!data["lora"].containsKey("txCodingRate4") ||
|
||||||
data["lora"]["power"].isNull() ||
|
!data["lora"].containsKey("txSignalBandwidth") ||
|
||||||
data["lora"]["cadActive"].isNull()) needsRewrite = true;
|
!data["lora"].containsKey("power")) needsRewrite = true;
|
||||||
loramodule.rxActive = data["lora"]["rxActive"] | true;
|
loramodule.rxActive = data["lora"]["rxActive"] | true;
|
||||||
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
|
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
|
||||||
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
|
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
|
||||||
@@ -314,11 +327,10 @@ bool Configuration::readFile() {
|
|||||||
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
|
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
|
||||||
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
|
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
|
||||||
loramodule.power = data["lora"]["power"] | 20;
|
loramodule.power = data["lora"]["power"] | 20;
|
||||||
loramodule.cadActive = data["lora"]["cadActive"] | true;
|
|
||||||
|
|
||||||
if (data["display"]["alwaysOn"].isNull() ||
|
if (!data["display"].containsKey("alwaysOn") ||
|
||||||
data["display"]["timeout"].isNull() ||
|
!data["display"].containsKey("timeout") ||
|
||||||
data["display"]["turn180"].isNull()) needsRewrite = true;
|
!data["display"].containsKey("turn180")) needsRewrite = true;
|
||||||
#ifdef HAS_EPAPER
|
#ifdef HAS_EPAPER
|
||||||
display.alwaysOn = true;
|
display.alwaysOn = true;
|
||||||
#else
|
#else
|
||||||
@@ -327,17 +339,17 @@ bool Configuration::readFile() {
|
|||||||
display.timeout = data["display"]["timeout"] | 4;
|
display.timeout = data["display"]["timeout"] | 4;
|
||||||
display.turn180 = data["display"]["turn180"] | false;
|
display.turn180 = data["display"]["turn180"] | false;
|
||||||
|
|
||||||
if (data["battery"]["sendInternalVoltage"].isNull() ||
|
if (!data["battery"].containsKey("sendInternalVoltage") ||
|
||||||
data["battery"]["monitorInternalVoltage"].isNull() ||
|
!data["battery"].containsKey("monitorInternalVoltage") ||
|
||||||
data["battery"]["internalSleepVoltage"].isNull() ||
|
!data["battery"].containsKey("internalSleepVoltage") ||
|
||||||
data["battery"]["sendExternalVoltage"].isNull() ||
|
!data["battery"].containsKey("sendExternalVoltage") ||
|
||||||
data["battery"]["monitorExternalVoltage"].isNull() ||
|
!data["battery"].containsKey("monitorExternalVoltage") ||
|
||||||
data["battery"]["externalSleepVoltage"].isNull() ||
|
!data["battery"].containsKey("externalSleepVoltage") ||
|
||||||
data["battery"]["useExternalI2CSensor"].isNull() ||
|
!data["battery"].containsKey("useExternalI2CSensor") ||
|
||||||
data["battery"]["voltageDividerR1"].isNull() ||
|
!data["battery"].containsKey("voltageDividerR1") ||
|
||||||
data["battery"]["voltageDividerR2"].isNull() ||
|
!data["battery"].containsKey("voltageDividerR2") ||
|
||||||
data["battery"]["externalVoltagePin"].isNull() ||
|
!data["battery"].containsKey("externalVoltagePin") ||
|
||||||
data["battery"]["sendVoltageAsTelemetry"].isNull()) needsRewrite = true;
|
!data["battery"].containsKey("sendVoltageAsTelemetry")) needsRewrite = true;
|
||||||
battery.sendInternalVoltage = data["battery"]["sendInternalVoltage"] | false;
|
battery.sendInternalVoltage = data["battery"]["sendInternalVoltage"] | false;
|
||||||
battery.monitorInternalVoltage = data["battery"]["monitorInternalVoltage"] | false;
|
battery.monitorInternalVoltage = data["battery"]["monitorInternalVoltage"] | false;
|
||||||
battery.internalSleepVoltage = data["battery"]["internalSleepVoltage"] | 2.9;
|
battery.internalSleepVoltage = data["battery"]["internalSleepVoltage"] | 2.9;
|
||||||
@@ -350,38 +362,38 @@ bool Configuration::readFile() {
|
|||||||
battery.externalVoltagePin = data["battery"]["externalVoltagePin"] | 34;
|
battery.externalVoltagePin = data["battery"]["externalVoltagePin"] | 34;
|
||||||
battery.sendVoltageAsTelemetry = data["battery"]["sendVoltageAsTelemetry"] | false;
|
battery.sendVoltageAsTelemetry = data["battery"]["sendVoltageAsTelemetry"] | false;
|
||||||
|
|
||||||
if (data["wxsensor"]["active"].isNull() ||
|
if (!data["wxsensor"].containsKey("active") ||
|
||||||
data["wxsensor"]["heightCorrection"].isNull() ||
|
!data["wxsensor"].containsKey("heightCorrection") ||
|
||||||
data["wxsensor"]["temperatureCorrection"].isNull()) needsRewrite = true;
|
!data["wxsensor"].containsKey("temperatureCorrection")) needsRewrite = true;
|
||||||
wxsensor.active = data["wxsensor"]["active"] | false;
|
wxsensor.active = data["wxsensor"]["active"] | false;
|
||||||
wxsensor.heightCorrection = data["wxsensor"]["heightCorrection"] | 0;
|
wxsensor.heightCorrection = data["wxsensor"]["heightCorrection"] | 0;
|
||||||
wxsensor.temperatureCorrection = data["wxsensor"]["temperatureCorrection"] | 0.0;
|
wxsensor.temperatureCorrection = data["wxsensor"]["temperatureCorrection"] | 0.0;
|
||||||
|
|
||||||
if (data["syslog"]["active"].isNull() ||
|
if (!data["syslog"].containsKey("active") ||
|
||||||
data["syslog"]["server"].isNull() ||
|
!data["syslog"].containsKey("server") ||
|
||||||
data["syslog"]["port"].isNull() ||
|
!data["syslog"].containsKey("port") ||
|
||||||
data["syslog"]["logBeaconOverTCPIP"].isNull()) needsRewrite = true;
|
!data["syslog"].containsKey("logBeaconOverTCPIP")) needsRewrite = true;
|
||||||
syslog.active = data["syslog"]["active"] | false;
|
syslog.active = data["syslog"]["active"] | false;
|
||||||
syslog.server = data["syslog"]["server"] | "lora.link9.net";
|
syslog.server = data["syslog"]["server"] | "lora.link9.net";
|
||||||
syslog.port = data["syslog"]["port"] | 1514;
|
syslog.port = data["syslog"]["port"] | 1514;
|
||||||
syslog.logBeaconOverTCPIP = data["syslog"]["logBeaconOverTCPIP"] | false;
|
syslog.logBeaconOverTCPIP = data["syslog"]["logBeaconOverTCPIP"] | false;
|
||||||
|
|
||||||
if (data["tnc"]["enableServer"].isNull() ||
|
if (!data["tnc"].containsKey("enableServer") ||
|
||||||
data["tnc"]["enableSerial"].isNull() ||
|
!data["tnc"].containsKey("enableSerial") ||
|
||||||
data["tnc"]["acceptOwn"].isNull() ||
|
!data["tnc"].containsKey("acceptOwn") ||
|
||||||
data["tnc"]["aprsBridgeActive"].isNull()) needsRewrite = true;
|
!data["tnc"].containsKey("aprsBridgeActive")) needsRewrite = true;
|
||||||
tnc.enableServer = data["tnc"]["enableServer"] | false;
|
tnc.enableServer = data["tnc"]["enableServer"] | false;
|
||||||
tnc.enableSerial = data["tnc"]["enableSerial"] | false;
|
tnc.enableSerial = data["tnc"]["enableSerial"] | false;
|
||||||
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
|
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
|
||||||
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
|
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
|
||||||
|
|
||||||
if (data["mqtt"]["active"].isNull() ||
|
if (!data["mqtt"].containsKey("active") ||
|
||||||
data["mqtt"]["server"].isNull() ||
|
!data["mqtt"].containsKey("server") ||
|
||||||
data["mqtt"]["topic"].isNull() ||
|
!data["mqtt"].containsKey("topic") ||
|
||||||
data["mqtt"]["username"].isNull() ||
|
!data["mqtt"].containsKey("username") ||
|
||||||
data["mqtt"]["password"].isNull() ||
|
!data["mqtt"].containsKey("password") ||
|
||||||
data["mqtt"]["port"].isNull() ||
|
!data["mqtt"].containsKey("port") ||
|
||||||
data["mqtt"]["beaconOverMqtt"].isNull()) needsRewrite = true;
|
!data["mqtt"].containsKey("beaconOverMqtt")) needsRewrite = true;
|
||||||
mqtt.active = data["mqtt"]["active"] | false;
|
mqtt.active = data["mqtt"]["active"] | false;
|
||||||
mqtt.server = data["mqtt"]["server"] | "";
|
mqtt.server = data["mqtt"]["server"] | "";
|
||||||
mqtt.topic = data["mqtt"]["topic"] | "aprs-igate";
|
mqtt.topic = data["mqtt"]["topic"] | "aprs-igate";
|
||||||
@@ -390,34 +402,34 @@ bool Configuration::readFile() {
|
|||||||
mqtt.port = data["mqtt"]["port"] | 1883;
|
mqtt.port = data["mqtt"]["port"] | 1883;
|
||||||
mqtt.beaconOverMqtt = data["mqtt"]["beaconOverMqtt"] | false;
|
mqtt.beaconOverMqtt = data["mqtt"]["beaconOverMqtt"] | false;
|
||||||
|
|
||||||
if (data["ota"]["username"].isNull() ||
|
if (!data["ota"].containsKey("username") ||
|
||||||
data["ota"]["password"].isNull()) needsRewrite = true;
|
!data["ota"].containsKey("password")) needsRewrite = true;
|
||||||
ota.username = data["ota"]["username"] | "";
|
ota.username = data["ota"]["username"] | "";
|
||||||
ota.password = data["ota"]["password"] | "";
|
ota.password = data["ota"]["password"] | "";
|
||||||
|
|
||||||
if (data["webadmin"]["active"].isNull() ||
|
if (!data["webadmin"].containsKey("active") ||
|
||||||
data["webadmin"]["username"].isNull() ||
|
!data["webadmin"].containsKey("username") ||
|
||||||
data["webadmin"]["password"].isNull()) needsRewrite = true;
|
!data["webadmin"].containsKey("password")) needsRewrite = true;
|
||||||
webadmin.active = data["webadmin"]["active"] | false;
|
webadmin.active = data["webadmin"]["active"] | false;
|
||||||
webadmin.username = data["webadmin"]["username"] | "admin";
|
webadmin.username = data["webadmin"]["username"] | "admin";
|
||||||
webadmin.password = data["webadmin"]["password"] | "";
|
webadmin.password = data["webadmin"]["password"] | "";
|
||||||
|
|
||||||
if (data["remoteManagement"]["managers"].isNull() ||
|
if (!data["remoteManagement"].containsKey("managers") ||
|
||||||
data["remoteManagement"]["rfOnly"].isNull()) needsRewrite = true;
|
!data["remoteManagement"].containsKey("rfOnly")) needsRewrite = true;
|
||||||
remoteManagement.managers = data["remoteManagement"]["managers"] | "";
|
remoteManagement.managers = data["remoteManagement"]["managers"] | "";
|
||||||
remoteManagement.rfOnly = data["remoteManagement"]["rfOnly"] | true;
|
remoteManagement.rfOnly = data["remoteManagement"]["rfOnly"] | true;
|
||||||
|
|
||||||
if (data["ntp"]["server"].isNull() ||
|
if (!data["ntp"].containsKey("server") ||
|
||||||
data["ntp"]["gmtCorrection"].isNull()) needsRewrite = true;
|
!data["ntp"].containsKey("gmtCorrection")) needsRewrite = true;
|
||||||
ntp.server = data["ntp"]["server"] | "pool.ntp.org";
|
ntp.server = data["ntp"]["server"] | "pool.ntp.org";
|
||||||
ntp.gmtCorrection = data["ntp"]["gmtCorrection"] | 0.0;
|
ntp.gmtCorrection = data["ntp"]["gmtCorrection"] | 0.0;
|
||||||
|
|
||||||
if (data["other"]["rebootMode"].isNull() ||
|
if (!data["other"].containsKey("rebootMode") ||
|
||||||
data["other"]["rebootModeTime"].isNull()) needsRewrite = true;
|
!data["other"].containsKey("rebootModeTime")) needsRewrite = true;
|
||||||
rebootMode = data["other"]["rebootMode"] | false;
|
rebootMode = data["other"]["rebootMode"] | false;
|
||||||
rebootModeTime = data["other"]["rebootModeTime"] | 6;
|
rebootModeTime = data["other"]["rebootModeTime"] | 6;
|
||||||
|
|
||||||
if (data["other"]["rememberStationTime"].isNull()) needsRewrite = true;
|
if (!data["other"].containsKey("rememberStationTime")) needsRewrite = true;
|
||||||
rememberStationTime = data["other"]["rememberStationTime"] | 30;
|
rememberStationTime = data["other"]["rememberStationTime"] | 30;
|
||||||
|
|
||||||
if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP
|
if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP
|
||||||
@@ -453,7 +465,6 @@ void Configuration::setDefaultValues() {
|
|||||||
|
|
||||||
startupDelay = 0;
|
startupDelay = 0;
|
||||||
|
|
||||||
wifiAutoAP.enabled = true;
|
|
||||||
wifiAutoAP.password = "1234567890";
|
wifiAutoAP.password = "1234567890";
|
||||||
wifiAutoAP.timeout = 10;
|
wifiAutoAP.timeout = 10;
|
||||||
|
|
||||||
@@ -505,7 +516,6 @@ void Configuration::setDefaultValues() {
|
|||||||
loramodule.txCodingRate4 = 5;
|
loramodule.txCodingRate4 = 5;
|
||||||
loramodule.txSignalBandwidth = 125000;
|
loramodule.txSignalBandwidth = 125000;
|
||||||
loramodule.power = 20;
|
loramodule.power = 20;
|
||||||
loramodule.cadActive = true;
|
|
||||||
|
|
||||||
display.alwaysOn = true;
|
display.alwaysOn = true;
|
||||||
display.timeout = 4;
|
display.timeout = 4;
|
||||||
@@ -566,23 +576,4 @@ void Configuration::setDefaultValues() {
|
|||||||
rememberStationTime = 30;
|
rememberStationTime = 30;
|
||||||
|
|
||||||
Serial.println("New Data Created... All is Written!");
|
Serial.println("New Data Created... All is Written!");
|
||||||
}
|
|
||||||
|
|
||||||
void Configuration::setup() {
|
|
||||||
if (!SPIFFS.begin(false)) {
|
|
||||||
Serial.println("SPIFFS Mount Failed");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Serial.println("SPIFFS Mounted");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exists = SPIFFS.exists("/igate_conf.json");
|
|
||||||
if (!exists) {
|
|
||||||
setDefaultValues();
|
|
||||||
writeFile();
|
|
||||||
delay(1000);
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
readFile();
|
|
||||||
}
|
}
|
||||||
+11
-48
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
#include "digi_utils.h"
|
#include "digi_utils.h"
|
||||||
#include "wifi_utils.h"
|
#include "wifi_utils.h"
|
||||||
#include "lora_utils.h"
|
#include "lora_utils.h"
|
||||||
|
#include "gps_utils.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@@ -42,8 +43,8 @@ extern bool backupDigiMode;
|
|||||||
|
|
||||||
namespace DIGI_Utils {
|
namespace DIGI_Utils {
|
||||||
|
|
||||||
String cleanPath(String path) {
|
String cleanPathAsterisks(String path) {
|
||||||
String terms[] = {"WIDE1*,", "WIDE2*,", "*"};
|
String terms[] = {",WIDE1*", ",WIDE2*", "*"};
|
||||||
for (String term : terms) {
|
for (String term : terms) {
|
||||||
int index = path.indexOf(term);
|
int index = path.indexOf(term);
|
||||||
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
|
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
|
||||||
@@ -51,35 +52,6 @@ namespace DIGI_Utils {
|
|||||||
return path;
|
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 buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
|
||||||
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||||
String suffix = thirdParty ? ":}" : ":";
|
String suffix = thirdParty ? ":}" : ":";
|
||||||
@@ -89,11 +61,11 @@ namespace DIGI_Utils {
|
|||||||
int digiMode = Config.digi.mode;
|
int digiMode = Config.digi.mode;
|
||||||
String tempPath = path;
|
String tempPath = path;
|
||||||
|
|
||||||
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 1 || digiMode == 2)) { // WIDE1-1
|
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 2 || digiMode == 3)) { // WIDE1-1
|
||||||
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
|
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
|
||||||
tempPath.replace("WIDE1-1", stationCallsign + "*");
|
tempPath.replace("WIDE1-1", stationCallsign + "*");
|
||||||
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { // WIDE2-n Digipeater
|
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) { // WIDE2-n Digipeater
|
||||||
tempPath = cleanPath(path);
|
tempPath = cleanPathAsterisks(path);
|
||||||
if (tempPath.indexOf("WIDE2-1") != -1) {
|
if (tempPath.indexOf("WIDE2-1") != -1) {
|
||||||
tempPath.replace("WIDE2-1", stationCallsign + "*");
|
tempPath.replace("WIDE2-1", stationCallsign + "*");
|
||||||
} else if (tempPath.indexOf("WIDE2-2") != -1) {
|
} else if (tempPath.indexOf("WIDE2-2") != -1) {
|
||||||
@@ -101,14 +73,11 @@ namespace DIGI_Utils {
|
|||||||
} else {
|
} else {
|
||||||
return "";
|
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 = packet.substring(0, packet.indexOf(",") + 1);
|
||||||
packetToRepeat += tempPath;
|
packetToRepeat += tempPath;
|
||||||
} else { // CrossFreq Digipeater
|
} else { // CrossFreq Digipeater
|
||||||
packetToRepeat = cleanPath(packet.substring(0, suffixIndex));
|
packetToRepeat = cleanPathAsterisks(packet.substring(0, suffixIndex));
|
||||||
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
|
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
|
||||||
packetToRepeat += ",";
|
packetToRepeat += ",";
|
||||||
packetToRepeat += stationCallsign;
|
packetToRepeat += stationCallsign;
|
||||||
@@ -132,14 +101,14 @@ namespace DIGI_Utils {
|
|||||||
|
|
||||||
if (commaIndex > 2) { // "path" found
|
if (commaIndex > 2) { // "path" found
|
||||||
const String& path = temp.substring(commaIndex + 1);
|
const String& path = temp.substring(commaIndex + 1);
|
||||||
if (digiMode == 1 || backupDigiMode) {
|
if (digiMode == 2 || backupDigiMode) {
|
||||||
bool hasWide = path.indexOf("WIDE1-1") != -1;
|
bool hasWide = path.indexOf("WIDE1-1") != -1;
|
||||||
if (hasWide || crossFreq) {
|
if (hasWide || crossFreq) {
|
||||||
return buildPacket(path, packet, thirdParty, !hasWide);
|
return buildPacket(path, packet, thirdParty, !hasWide);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (digiMode == 2) {
|
if (digiMode == 3) {
|
||||||
int wide1Index = path.indexOf("WIDE1-1");
|
int wide1Index = path.indexOf("WIDE1-1");
|
||||||
int wide2Index = path.indexOf("WIDE2-");
|
int wide2Index = path.indexOf("WIDE2-");
|
||||||
bool hasWide1 = wide1Index != -1;
|
bool hasWide1 = wide1Index != -1;
|
||||||
@@ -153,16 +122,10 @@ namespace DIGI_Utils {
|
|||||||
|
|
||||||
return "";
|
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 "";
|
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 == 2 || backupDigiMode || digiMode == 3) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-14
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -65,12 +65,7 @@
|
|||||||
#ifdef HELTEC_WSL_V3_DISPLAY
|
#ifdef HELTEC_WSL_V3_DISPLAY
|
||||||
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST);
|
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST);
|
||||||
#else
|
#else
|
||||||
#if defined RPC_LORA_DIGIGATE_1W
|
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
|
||||||
#define SCREEN_HEIGHT 32
|
|
||||||
#else
|
|
||||||
#define SCREEN_HEIGHT 64
|
|
||||||
#endif
|
|
||||||
Adafruit_SSD1306 display(128, SCREEN_HEIGHT, &Wire, OLED_RST);
|
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
@@ -80,7 +75,6 @@
|
|||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
|
|
||||||
bool displayFound = false;
|
bool displayFound = false;
|
||||||
int maxLines;
|
|
||||||
|
|
||||||
void displaySetup() {
|
void displaySetup() {
|
||||||
#ifdef HAS_DISPLAY
|
#ifdef HAS_DISPLAY
|
||||||
@@ -103,11 +97,6 @@ void displaySetup() {
|
|||||||
sprite.createSprite(160, 80);
|
sprite.createSprite(160, 80);
|
||||||
#endif
|
#endif
|
||||||
#else
|
#else
|
||||||
#if (SCREEN_HEIGHT == 64)
|
|
||||||
maxLines = 6;
|
|
||||||
#elif (SCREEN_HEIGHT == 32)
|
|
||||||
maxLines = 3;
|
|
||||||
#endif
|
|
||||||
#ifdef HAS_EPAPER
|
#ifdef HAS_EPAPER
|
||||||
display.landscape();
|
display.landscape();
|
||||||
display.printCenter("LoRa APRS iGate Initialising...");
|
display.printCenter("LoRa APRS iGate Initialising...");
|
||||||
@@ -308,7 +297,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
|
|||||||
display.setCursor(0, 0);
|
display.setCursor(0, 0);
|
||||||
display.println(header);
|
display.println(header);
|
||||||
display.setTextSize(1);
|
display.setTextSize(1);
|
||||||
for (int i = 0; i < maxLines; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
display.setCursor(0, 16 + (8 * i));
|
display.setCursor(0, 16 + (8 * i));
|
||||||
display.println(*lines[i]);
|
display.println(*lines[i]);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+9
-52
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -16,31 +16,26 @@
|
|||||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <APRSPacketLib.h>
|
|
||||||
#include <RadioLib.h>
|
#include <RadioLib.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "aprs_is_utils.h"
|
#include "aprs_is_utils.h"
|
||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
#include "board_pinout.h"
|
#include "board_pinout.h"
|
||||||
#include "syslog_utils.h"
|
#include "syslog_utils.h"
|
||||||
#include "map_utils.h"
|
|
||||||
#include "ntp_utils.h"
|
#include "ntp_utils.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
extern uint32_t lastRxTime;
|
||||||
extern bool packetIsBeacon;
|
extern bool packetIsBeacon;
|
||||||
|
|
||||||
extern std::vector<ReceivedPacket> receivedPackets;
|
extern std::vector<ReceivedPacket> receivedPackets;
|
||||||
|
|
||||||
bool operationDone = true;
|
bool operationDone = true;
|
||||||
bool transmitFlag = true;
|
bool transmitFlag = true;
|
||||||
|
|
||||||
#define DIFS_SLOTS 2 // Number of secuential CAD slots to consider a free channel to Tx
|
|
||||||
int backoffMax = 4; // Max Backoff value (number of CAD slots to wait before Tx)
|
|
||||||
|
|
||||||
#ifdef HAS_SX1262
|
#ifdef HAS_SX1262
|
||||||
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||||
@@ -171,34 +166,6 @@ namespace LoRa_Utils {
|
|||||||
radio.setBandwidth(signalBandwidth);
|
radio.setBandwidth(signalBandwidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool doCAD() { // CAD (Channel Activity Detection)
|
|
||||||
return radio.scanChannel() != RADIOLIB_CHANNEL_FREE; // false=channel free | true=RADIOLIB_LORA_DETECTED or CAD failed
|
|
||||||
}
|
|
||||||
|
|
||||||
bool doDIFS() {
|
|
||||||
for (uint8_t i = DIFS_SLOTS; i > 0; i--) {
|
|
||||||
if (doCAD()) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void waitForDIFS() {
|
|
||||||
while (!doDIFS()) {
|
|
||||||
Serial.println("CAD/DIFS failed, retry...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void doBEB() {
|
|
||||||
int backoffCounter = random(1, backoffMax + 1);
|
|
||||||
while (backoffCounter > 0) {
|
|
||||||
if (doCAD()) {
|
|
||||||
waitForDIFS(); // busy channel: freeze backoff and restart DIFS
|
|
||||||
} else {
|
|
||||||
backoffCounter--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void sendNewPacket(const String& newPacket) {
|
void sendNewPacket(const String& newPacket) {
|
||||||
if (!Config.loramodule.txActive) return;
|
if (!Config.loramodule.txActive) return;
|
||||||
|
|
||||||
@@ -211,16 +178,10 @@ namespace LoRa_Utils {
|
|||||||
#ifdef INTERNAL_LED_PIN
|
#ifdef INTERNAL_LED_PIN
|
||||||
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
|
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (Config.loramodule.cadActive) {
|
|
||||||
waitForDIFS(); // DIFS (Distributed Inter-Frame Space)
|
|
||||||
doBEB(); // BEB (Binary Exponential Backoff)
|
|
||||||
}
|
|
||||||
|
|
||||||
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
||||||
transmitFlag = true;
|
transmitFlag = true;
|
||||||
if (state == RADIOLIB_ERR_NONE) {
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
if (Config.syslog.active && networkManager->isConnected()) {
|
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
|
||||||
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
|
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
|
||||||
}
|
}
|
||||||
Utils::print("---> LoRa Packet Tx : ");
|
Utils::print("---> LoRa Packet Tx : ");
|
||||||
@@ -280,19 +241,15 @@ namespace LoRa_Utils {
|
|||||||
receivedPacket.RSSI = rssi;
|
receivedPacket.RSSI = rssi;
|
||||||
receivedPacket.SNR = snr;
|
receivedPacket.SNR = snr;
|
||||||
receivedPackets.push_back(receivedPacket);
|
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()) {
|
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
|
||||||
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
|
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
packet = "";
|
packet = "";
|
||||||
}
|
}
|
||||||
|
lastRxTime = millis();
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
||||||
@@ -300,7 +257,7 @@ namespace LoRa_Utils {
|
|||||||
snr = radio.getSNR();
|
snr = radio.getSNR();
|
||||||
freqError = radio.getFrequencyError();
|
freqError = radio.getFrequencyError();
|
||||||
Utils::println(F("CRC error!"));
|
Utils::println(F("CRC error!"));
|
||||||
if (Config.syslog.active && networkManager->isConnected()) {
|
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
|
||||||
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
|
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
|
||||||
}
|
}
|
||||||
packet = "";
|
packet = "";
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClientSecure.h>
|
||||||
#include <PubSubClient.h>
|
#include <PubSubClient.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
|
|||||||
@@ -1,351 +0,0 @@
|
|||||||
/* 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"
|
|
||||||
|
|
||||||
// Constructor
|
|
||||||
NetworkManager::NetworkManager() { }
|
|
||||||
|
|
||||||
// Destructor
|
|
||||||
NetworkManager::~NetworkManager() { }
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
|
|
||||||
int NetworkManager::_findWiFiNetworkIndex(const String& ssid) const {
|
|
||||||
for (size_t i = 0; i < _wifiNetworks.size(); i++) {
|
|
||||||
if (_wifiNetworks[i].ssid == ssid) {
|
|
||||||
return static_cast<int>(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::_connectWiFi(const WiFiNetwork& network) {
|
|
||||||
if (network.ssid.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_wifiSTAmode = true;
|
|
||||||
|
|
||||||
if (!_hostName.isEmpty()) {
|
|
||||||
WiFi.setHostname(_hostName.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
WiFi.mode(_wifiAPmode ? WIFI_AP_STA : WIFI_STA);
|
|
||||||
|
|
||||||
Serial.println("[NM] Attempting to connect to WiFi: " + network.ssid);
|
|
||||||
WiFi.begin(network.ssid.c_str(), network.psk.c_str());
|
|
||||||
|
|
||||||
Serial.print("[NM] Connecting ");
|
|
||||||
|
|
||||||
int attempts = 0;
|
|
||||||
while (!isWiFiConnected() && attempts < 10) {
|
|
||||||
delay(500);
|
|
||||||
#ifdef INTERNAL_LED_PIN
|
|
||||||
digitalWrite(INTERNAL_LED_PIN,HIGH);
|
|
||||||
#endif
|
|
||||||
Serial.print('.');
|
|
||||||
delay(500);
|
|
||||||
#ifdef INTERNAL_LED_PIN
|
|
||||||
digitalWrite(INTERNAL_LED_PIN,LOW);
|
|
||||||
#endif
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
if (isWiFiConnected()) return true;
|
|
||||||
|
|
||||||
Serial.println("[NM] Failed to connect to WiFi after " + String(attempts) + " attempts. SSID: " + network.ssid);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::_processAPTimeout() {
|
|
||||||
if (!_wifiAPmode || _apTimeout == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If any station is connected, reset the timer
|
|
||||||
if (WiFi.softAPgetStationNum() > 0) {
|
|
||||||
_apStartup = millis();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (millis() - _apStartup > _apTimeout) {
|
|
||||||
Serial.println("[NM] AP timeout reached. Disabling AP mode.");
|
|
||||||
disableAP();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::_onNetworkEvent(arduino_event_id_t event, arduino_event_info_t /*info*/) {
|
|
||||||
switch (event) {
|
|
||||||
case ARDUINO_EVENT_ETH_START:
|
|
||||||
Serial.println("[NM] ETH Started");
|
|
||||||
if (!_hostName.isEmpty()) {
|
|
||||||
Serial.println("[NM] ETH Setting Hostname: " + _hostName);
|
|
||||||
ETH.setHostname(_hostName.c_str());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
|
||||||
Serial.println("[NM] ETH Connected");
|
|
||||||
break;
|
|
||||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
|
||||||
Serial.println("[NM] ETH Got IP");
|
|
||||||
_ethernetConnected = true;
|
|
||||||
break;
|
|
||||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
|
||||||
Serial.println("[NM] ETH Disconnected");
|
|
||||||
_ethernetConnected = false;
|
|
||||||
break;
|
|
||||||
case ARDUINO_EVENT_ETH_STOP:
|
|
||||||
Serial.println("[NM] ETH Stopped");
|
|
||||||
_ethernetConnected = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
bool NetworkManager::setup() {
|
|
||||||
Serial.println("[NM] Initializing Networking...");
|
|
||||||
|
|
||||||
WiFi.onEvent(
|
|
||||||
[this](arduino_event_id_t event, arduino_event_info_t info) {
|
|
||||||
_onNetworkEvent(event, info);
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::loop() {
|
|
||||||
if (_wifiAPmode) {
|
|
||||||
_processAPTimeout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::setHostName(const String& hostName) {
|
|
||||||
_hostName = hostName;
|
|
||||||
}
|
|
||||||
|
|
||||||
// WiFi methods
|
|
||||||
|
|
||||||
bool NetworkManager::setupAP(String apName, String apPsk) {
|
|
||||||
_wifiAPmode = true;
|
|
||||||
|
|
||||||
Serial.println("[NM] Starting AP mode: " + apName);
|
|
||||||
|
|
||||||
// Full WiFi reset sequence
|
|
||||||
WiFi.disconnect(true);
|
|
||||||
WiFi.mode(WIFI_OFF);
|
|
||||||
delay(200);
|
|
||||||
|
|
||||||
// Set up AP mode with optimized settings
|
|
||||||
WiFi.mode(WIFI_AP);
|
|
||||||
|
|
||||||
bool apStarted = WiFi.softAP(apName.c_str(), apPsk.c_str());
|
|
||||||
delay(1000); // Give AP time to fully initialize
|
|
||||||
|
|
||||||
if (apStarted) {
|
|
||||||
Serial.println("[NM] AP setup successful");
|
|
||||||
_apStartup = millis();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Serial.println("[NM] AP setup failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress apIP = getWiFiAPIP();
|
|
||||||
Serial.println("[NM] AP IP assigned: " + apIP.toString());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::disableAP() {
|
|
||||||
WiFi.mode(_wifiSTAmode ? WIFI_STA : WIFI_OFF);
|
|
||||||
_wifiAPmode = false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::setAPTimeout(unsigned long timeout) {
|
|
||||||
Serial.println("[NM] Setting AP timeout to " + String(timeout / 1000) + " sec");
|
|
||||||
_apTimeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::addWiFiNetwork(const String& ssid, const String& psk) {
|
|
||||||
if (ssid.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int index = _findWiFiNetworkIndex(ssid);
|
|
||||||
if (index >= 0) {
|
|
||||||
Serial.println("[NM] Updating WiFi network: " + ssid);
|
|
||||||
_wifiNetworks[static_cast<size_t>(index)].psk = psk;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[NM] Adding WiFi network: " + ssid);
|
|
||||||
WiFiNetwork network;
|
|
||||||
network.ssid = ssid;
|
|
||||||
network.psk = psk;
|
|
||||||
_wifiNetworks.push_back(network);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NetworkManager::clearWiFiNetworks() {
|
|
||||||
_wifiNetworks.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::hasWiFiNetworks() const {
|
|
||||||
return !_wifiNetworks.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t NetworkManager::getWiFiNetworkCount() const {
|
|
||||||
return _wifiNetworks.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::connectWiFi() {
|
|
||||||
if (_wifiNetworks.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < _wifiNetworks.size(); i++) {
|
|
||||||
disconnectWiFi();
|
|
||||||
if (_connectWiFi(_wifiNetworks[i])) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::connectWiFi(const String& ssid, const String& psk) {
|
|
||||||
addWiFiNetwork(ssid, psk);
|
|
||||||
return connectWiFi();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::disconnectWiFi() {
|
|
||||||
WiFi.disconnect(true);
|
|
||||||
WiFi.mode(_wifiAPmode ? WIFI_AP : WIFI_OFF);
|
|
||||||
|
|
||||||
_wifiSTAmode = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
String NetworkManager::getWiFiSSID() const {
|
|
||||||
return WiFi.SSID();
|
|
||||||
}
|
|
||||||
|
|
||||||
String NetworkManager::getWiFiAPSSID() const {
|
|
||||||
return WiFi.softAPSSID();
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress NetworkManager::getWiFiIP() const {
|
|
||||||
return WiFi.localIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress NetworkManager::getWiFiAPIP() const {
|
|
||||||
return WiFi.softAPIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
wifi_mode_t NetworkManager::getWiFiMode() const {
|
|
||||||
return WiFi.getMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t* NetworkManager::getWiFimacAddress(uint8_t* mac) {
|
|
||||||
return WiFi.macAddress(mac);
|
|
||||||
}
|
|
||||||
|
|
||||||
String NetworkManager::getWiFimacAddress(void) const {
|
|
||||||
return WiFi.macAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ethernet methods
|
|
||||||
bool NetworkManager::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) {
|
|
||||||
_ethernetMode = true;
|
|
||||||
Serial.println("[NM] Setting up Ethernet...");
|
|
||||||
|
|
||||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
|
|
||||||
// SDK 5.x (Arduino SDK 3.x)
|
|
||||||
#pragma message("Compiling ETH init: SDK 5.x (Arduino core 3.x)")
|
|
||||||
return ETH.begin(type, phy_addr, mdc, mdio, power, clock_mode, use_mac_from_efuse);
|
|
||||||
#else
|
|
||||||
// SDK 4.x (Arduino SDK 2.x)
|
|
||||||
#pragma message("Compiling ETH init: SDK 4.x (Arduino core 2.x)")
|
|
||||||
return ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode, use_mac_from_efuse);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::setEthernetIP(const String& staticIP, const String& gateway, const String& subnet, const String& dns1, const String& dns2) {
|
|
||||||
if (staticIP.isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress ip, gw, sn, d1, d2;
|
|
||||||
if (!ip.fromString(staticIP) || !gw.fromString(gateway) || !sn.fromString(subnet)) {
|
|
||||||
Serial.println("[NM] Invalid static IP configuration");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dns1.isEmpty() && d1.fromString(dns1)) {
|
|
||||||
if (!dns2.isEmpty() && d2.fromString(dns2)) {
|
|
||||||
ETH.config(ip, gw, sn, d1, d2);
|
|
||||||
} else {
|
|
||||||
ETH.config(ip, gw, sn, d1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ETH.config(ip, gw, sn);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[NM] Ethernet static IP: " + staticIP);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
IPAddress NetworkManager::getEthernetIP() const {
|
|
||||||
return ETH.localIP();
|
|
||||||
}
|
|
||||||
|
|
||||||
String NetworkManager::getEthernetMACAddress() const {
|
|
||||||
return ETH.macAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if network is available
|
|
||||||
bool NetworkManager::isConnected() const {
|
|
||||||
return isWiFiConnected() || isEthernetConnected() || isModemConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if WiFi is connected
|
|
||||||
bool NetworkManager::isWiFiConnected() const {
|
|
||||||
return _wifiSTAmode ? WiFi.status() == WL_CONNECTED : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NetworkManager::isWifiAPActive() const {
|
|
||||||
return _wifiAPmode;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Ethernet is connected
|
|
||||||
bool NetworkManager::isEthernetConnected() const {
|
|
||||||
return _ethernetMode && _ethernetConnected && ETH.linkUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Modem is connected
|
|
||||||
bool NetworkManager::isModemConnected() const {
|
|
||||||
// Implement Modem connection check logic here
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
+9
-23
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -18,49 +18,35 @@
|
|||||||
|
|
||||||
#include <NTPClient.h>
|
#include <NTPClient.h>
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "ntp_utils.h"
|
#include "ntp_utils.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
|
||||||
WiFiUDP ntpUDP;
|
WiFiUDP ntpUDP;
|
||||||
NTPClient* timeClient = nullptr;
|
NTPClient* timeClient;
|
||||||
|
|
||||||
|
|
||||||
namespace NTP_Utils {
|
namespace NTP_Utils {
|
||||||
|
|
||||||
bool setup() {
|
void setup() {
|
||||||
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
|
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
|
||||||
int gmt = Config.ntp.gmtCorrection * 3600;
|
int gmt = Config.ntp.gmtCorrection * 3600;
|
||||||
Serial.println("[NTP] Setting up, TZ offset: " + String(gmt) + " Server: " + Config.ntp.server);
|
|
||||||
timeClient = new NTPClient(ntpUDP, Config.ntp.server.c_str(), gmt, 15 * 60 * 1000); // Update interval 15 min
|
timeClient = new NTPClient(ntpUDP, Config.ntp.server.c_str(), gmt, 15 * 60 * 1000); // Update interval 15 min
|
||||||
timeClient->begin();
|
timeClient->begin();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update() {
|
void update() {
|
||||||
if (!networkManager->isConnected() || Config.digi.ecoMode != 0 || Config.callsign == "NOCALL-10") {
|
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") timeClient->update();
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (timeClient == nullptr) {
|
|
||||||
if (!setup()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
timeClient->update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String getFormatedTime() {
|
String getFormatedTime() {
|
||||||
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && timeClient != nullptr) {
|
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0) return timeClient->getFormattedTime();
|
||||||
return timeClient->getFormattedTime();
|
|
||||||
}
|
|
||||||
return "DigiEcoMode Active";
|
return "DigiEcoMode Active";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -74,7 +74,7 @@ namespace OTA_Utils {
|
|||||||
Serial.println(success ? "OTA update finished successfully!" : "There was an error during OTA update!");
|
Serial.println(success ? "OTA update finished successfully!" : "There was an error during OTA update!");
|
||||||
displayShow("", "", statusMessage, "", rebootMessage, "", "", 4000);
|
displayShow("", "", statusMessage, "", rebootMessage, "", "", 4000);
|
||||||
|
|
||||||
if (!success) isUpdatingOTA = false;
|
isUpdatingOTA = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -82,7 +82,7 @@ namespace QUERY_Utils {
|
|||||||
int radioTxActive = Config.loramodule.txActive;
|
int radioTxActive = Config.loramodule.txActive;
|
||||||
bool onlyRadioActive = radioTxActive && Config.loramodule.rxActive && !Config.aprs_is.active;
|
bool onlyRadioActive = radioTxActive && Config.loramodule.rxActive && !Config.aprs_is.active;
|
||||||
if (queryQuestion.startsWith("?EM=OFF")) {
|
if (queryQuestion.startsWith("?EM=OFF")) {
|
||||||
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
||||||
if (digiEcoMode == 1 || digiEcoMode == 2) { // Exit Digipeater EcoMode or Digipeater without WiFiAP
|
if (digiEcoMode == 1 || digiEcoMode == 2) { // Exit Digipeater EcoMode or Digipeater without WiFiAP
|
||||||
answer = (digiEcoMode == 1) ? "DigiEcoMode:OFF" : "Digipeater + WiFiAP enabled";
|
answer = (digiEcoMode == 1) ? "DigiEcoMode:OFF" : "Digipeater + WiFiAP enabled";
|
||||||
Config.digi.ecoMode = 0;
|
Config.digi.ecoMode = 0;
|
||||||
@@ -97,7 +97,7 @@ namespace QUERY_Utils {
|
|||||||
answer = "Digipeater Mode control not possible";
|
answer = "Digipeater Mode control not possible";
|
||||||
}
|
}
|
||||||
} else if (queryQuestion.startsWith("?EM=ON")) {
|
} else if (queryQuestion.startsWith("?EM=ON")) {
|
||||||
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
||||||
if (digiEcoMode == 0) { // Start Digipeater EcoMode
|
if (digiEcoMode == 0) { // Start Digipeater EcoMode
|
||||||
answer = "DigiEcoMode:ON";
|
answer = "DigiEcoMode:ON";
|
||||||
Config.digi.ecoMode = 1;
|
Config.digi.ecoMode = 1;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+10
-2
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -25,11 +25,15 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#define SECS_TO_WAIT 3 // soon to be deleted...
|
||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
|
extern uint32_t lastRxTime;
|
||||||
extern String fourthLine;
|
extern String fourthLine;
|
||||||
extern bool shouldSleepLowVoltage;
|
extern bool shouldSleepLowVoltage;
|
||||||
|
|
||||||
|
uint32_t lastTxTime = millis();
|
||||||
std::vector<LastHeardStation> lastHeardStations;
|
std::vector<LastHeardStation> lastHeardStations;
|
||||||
std::vector<String> blacklist;
|
std::vector<String> blacklist;
|
||||||
std::vector<String> managers;
|
std::vector<String> managers;
|
||||||
@@ -210,11 +214,15 @@ namespace STATION_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void processOutputPacketBuffer() {
|
void processOutputPacketBuffer() {
|
||||||
if (outputPacketBuffer.size() > 0) {
|
int timeToWait = SECS_TO_WAIT * 1000; // 3 segs between packet Tx and also Rx ???
|
||||||
|
uint32_t lastRx = millis() - lastRxTime;
|
||||||
|
uint32_t lastTx = millis() - lastTxTime;
|
||||||
|
if (outputPacketBuffer.size() > 0 && lastTx > timeToWait && lastRx > timeToWait) {
|
||||||
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
|
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
|
||||||
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
|
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
|
||||||
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
|
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
|
||||||
outputPacketBuffer.erase(outputPacketBuffer.begin());
|
outputPacketBuffer.erase(outputPacketBuffer.begin());
|
||||||
|
lastTxTime = millis();
|
||||||
}
|
}
|
||||||
if (shouldSleepLowVoltage) {
|
if (shouldSleepLowVoltage) {
|
||||||
while (outputPacketBuffer.size() > 0) {
|
while (outputPacketBuffer.size() > 0) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -17,14 +17,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "syslog_utils.h"
|
#include "syslog_utils.h"
|
||||||
#include "gps_utils.h"
|
#include "gps_utils.h"
|
||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
|
||||||
extern String versionDate;
|
extern String versionDate;
|
||||||
extern String versionNumber;
|
extern String versionNumber;
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ WiFiUDP udpClient;
|
|||||||
namespace SYSLOG_Utils {
|
namespace SYSLOG_Utils {
|
||||||
|
|
||||||
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
|
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
|
||||||
if (Config.syslog.active && networkManager->isConnected()) {
|
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
|
||||||
String syslogPacket = "<165>1 - ";
|
String syslogPacket = "<165>1 - ";
|
||||||
syslogPacket.concat(Config.callsign);
|
syslogPacket.concat(Config.callsign);
|
||||||
syslogPacket.concat(" CA2RXU_LoRa_iGate_");
|
syslogPacket.concat(" CA2RXU_LoRa_iGate_");
|
||||||
@@ -141,7 +140,7 @@ namespace SYSLOG_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
if (networkManager->isConnected()) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
udpClient.begin(0);
|
udpClient.begin(0);
|
||||||
if (Config.syslog.active) Serial.println("init : Syslog Server ... done! (at " + Config.syslog.server + ")");
|
if (Config.syslog.active) Serial.println("init : Syslog Server ... done! (at " + Config.syslog.server + ")");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+11
-20
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
#include <APRSPacketLib.h>
|
#include <APRSPacketLib.h>
|
||||||
#include <TinyGPS++.h>
|
#include <TinyGPS++.h>
|
||||||
|
#include <WiFi.h>
|
||||||
#include "telemetry_utils.h"
|
#include "telemetry_utils.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
#include "battery_utils.h"
|
#include "battery_utils.h"
|
||||||
#include "aprs_is_utils.h"
|
#include "aprs_is_utils.h"
|
||||||
@@ -37,7 +37,6 @@
|
|||||||
#define DAY_MS (24UL * 60UL * 60UL * 1000UL)
|
#define DAY_MS (24UL * 60UL * 60UL * 1000UL)
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
|
||||||
extern TinyGPSPlus gps;
|
extern TinyGPSPlus gps;
|
||||||
extern String versionDate;
|
extern String versionDate;
|
||||||
extern String firstLine;
|
extern String firstLine;
|
||||||
@@ -53,6 +52,7 @@ extern int rssi;
|
|||||||
extern float snr;
|
extern float snr;
|
||||||
extern int freqError;
|
extern int freqError;
|
||||||
extern String distance;
|
extern String distance;
|
||||||
|
extern bool WiFiConnected;
|
||||||
extern int wxModuleType;
|
extern int wxModuleType;
|
||||||
extern bool backupDigiMode;
|
extern bool backupDigiMode;
|
||||||
extern bool shouldSleepLowVoltage;
|
extern bool shouldSleepLowVoltage;
|
||||||
@@ -75,7 +75,7 @@ String secondaryBeaconPacket;
|
|||||||
namespace Utils {
|
namespace Utils {
|
||||||
|
|
||||||
void processStatus() {
|
void processStatus() {
|
||||||
bool sendOverAPRSIS = Config.beacon.sendViaAPRSIS && Config.aprs_is.active && networkManager->isConnected();
|
bool sendOverAPRSIS = Config.beacon.sendViaAPRSIS && Config.aprs_is.active && WiFi.status() == WL_CONNECTED;
|
||||||
bool sendOverRF = !Config.beacon.sendViaAPRSIS && Config.beacon.sendViaRF;
|
bool sendOverRF = !Config.beacon.sendViaAPRSIS && Config.beacon.sendViaRF;
|
||||||
|
|
||||||
if (!sendOverAPRSIS && !sendOverRF) {
|
if (!sendOverAPRSIS && !sendOverRF) {
|
||||||
@@ -104,14 +104,12 @@ namespace Utils {
|
|||||||
String getLocalIP() {
|
String getLocalIP() {
|
||||||
if (Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
|
if (Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
|
||||||
return "** WiFi AP Killed **";
|
return "** WiFi AP Killed **";
|
||||||
} else if (networkManager->isEthernetConnected()) {
|
} else if (!WiFiConnected) {
|
||||||
return "LAN: " + networkManager->getEthernetIP().toString();
|
return "IP : 192.168.4.1";
|
||||||
} else if (!networkManager->isWiFiConnected() && networkManager->isWifiAPActive()) {
|
|
||||||
return "IP : " + networkManager->getWiFiAPIP().toString();
|
|
||||||
} else if (backupDigiMode) {
|
} else if (backupDigiMode) {
|
||||||
return "- BACKUP DIGI MODE -";
|
return "- BACKUP DIGI MODE -";
|
||||||
} else {
|
} else {
|
||||||
return "IP : " + networkManager->getWiFiIP().toString();
|
return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,18 +151,11 @@ namespace Utils {
|
|||||||
beaconUpdate = true;
|
beaconUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool configLocationIsValid = !(Config.beacon.latitude == 0.0 && Config.beacon.longitude == 0.0);
|
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
if (Config.beacon.gpsActive) { // GPS activated
|
if (Config.beacon.gpsActive && gps.location.lat() == 0.0 && gps.location.lng() == 0.0 && Config.beacon.latitude == 0.0 && Config.beacon.longitude == 0.0) {
|
||||||
if (!gps.location.isValid()) {
|
GPS_Utils::getData();
|
||||||
GPS_Utils::getData(); // refresh GPS
|
beaconUpdate = false;
|
||||||
beaconUpdate = false;
|
|
||||||
}
|
|
||||||
} else { // GPS not active: use saved data in Config
|
|
||||||
if (!configLocationIsValid) beaconUpdate = false;
|
|
||||||
}
|
}
|
||||||
#else // No GPS available: use saved data in Config
|
|
||||||
if (!configLocationIsValid) beaconUpdate = false;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (beaconUpdate) {
|
if (beaconUpdate) {
|
||||||
@@ -490,4 +481,4 @@ namespace Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+3
-11
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -20,7 +20,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "ota_utils.h"
|
#include "ota_utils.h"
|
||||||
#include "web_utils.h"
|
#include "web_utils.h"
|
||||||
#include "map_utils.h"
|
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@@ -99,7 +98,7 @@ namespace WEB_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleReceivedPackets(AsyncWebServerRequest *request) {
|
void handleReceivedPackets(AsyncWebServerRequest *request) {
|
||||||
JsonDocument data;
|
StaticJsonDocument<1536> data;
|
||||||
|
|
||||||
for (int i = 0; i < receivedPackets.size(); i++) {
|
for (int i = 0; i < receivedPackets.size(); i++) {
|
||||||
data[i]["rxTime"] = receivedPackets[i].rxTime;
|
data[i]["rxTime"] = receivedPackets[i].rxTime;
|
||||||
@@ -115,10 +114,6 @@ namespace WEB_Utils {
|
|||||||
request->send(200, "application/json", buffer);
|
request->send(200, "application/json", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleStations(AsyncWebServerRequest *request) {
|
|
||||||
request->send(200, "application/json", MAP_Utils::getStationsJson());
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleWriteConfiguration(AsyncWebServerRequest *request) {
|
void handleWriteConfiguration(AsyncWebServerRequest *request) {
|
||||||
Serial.println("Got new Configuration Data from www");
|
Serial.println("Got new Configuration Data from www");
|
||||||
|
|
||||||
@@ -166,7 +161,7 @@ namespace WEB_Utils {
|
|||||||
|
|
||||||
Config.callsign = getParamStringSafe("callsign", Config.callsign);
|
Config.callsign = getParamStringSafe("callsign", Config.callsign);
|
||||||
Config.tacticalCallsign = getParamStringSafe("tacticalCallsign", Config.tacticalCallsign);
|
Config.tacticalCallsign = getParamStringSafe("tacticalCallsign", Config.tacticalCallsign);
|
||||||
Config.wifiAutoAP.enabled = request->hasParam("wifi.autoAP.enabled", true);
|
|
||||||
Config.wifiAutoAP.password = getParamStringSafe("wifi.autoAP.password", Config.wifiAutoAP.password);
|
Config.wifiAutoAP.password = getParamStringSafe("wifi.autoAP.password", Config.wifiAutoAP.password);
|
||||||
Config.wifiAutoAP.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout);
|
Config.wifiAutoAP.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout);
|
||||||
|
|
||||||
@@ -218,8 +213,6 @@ namespace WEB_Utils {
|
|||||||
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
|
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
|
||||||
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
|
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
|
||||||
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
|
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);
|
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
|
||||||
if (!Config.display.alwaysOn) {
|
if (!Config.display.alwaysOn) {
|
||||||
@@ -368,7 +361,6 @@ namespace WEB_Utils {
|
|||||||
server.on("/", HTTP_GET, handleHome);
|
server.on("/", HTTP_GET, handleHome);
|
||||||
server.on("/status", HTTP_GET, handleStatus);
|
server.on("/status", HTTP_GET, handleStatus);
|
||||||
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
|
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_GET, handleReadConfiguration);
|
||||||
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
|
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
|
||||||
server.on("/action", HTTP_POST, handleAction);
|
server.on("/action", HTTP_POST, handleAction);
|
||||||
|
|||||||
+93
-42
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
|
||||||
#include "board_pinout.h"
|
#include "board_pinout.h"
|
||||||
#include "wifi_utils.h"
|
#include "wifi_utils.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
@@ -26,11 +25,16 @@
|
|||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
|
||||||
|
|
||||||
|
extern uint8_t myWiFiAPIndex;
|
||||||
|
extern int myWiFiAPSize;
|
||||||
|
extern WiFi_AP *currentWiFi;
|
||||||
extern bool backupDigiMode;
|
extern bool backupDigiMode;
|
||||||
extern uint32_t lastServerCheck;
|
extern uint32_t lastServerCheck;
|
||||||
|
|
||||||
|
bool WiFiConnected = false;
|
||||||
|
uint32_t WiFiAutoAPTime = millis();
|
||||||
|
bool WiFiAutoAPStarted = false;
|
||||||
uint8_t wifiCounter = 0;
|
uint8_t wifiCounter = 0;
|
||||||
uint32_t lastBackupDigiTime = millis();
|
uint32_t lastBackupDigiTime = millis();
|
||||||
uint32_t lastWiFiCheck = 0;
|
uint32_t lastWiFiCheck = 0;
|
||||||
@@ -40,35 +44,32 @@ namespace WIFI_Utils {
|
|||||||
|
|
||||||
void checkWiFi() {
|
void checkWiFi() {
|
||||||
if (Config.digi.ecoMode != 0) return;
|
if (Config.digi.ecoMode != 0) return;
|
||||||
|
|
||||||
if (!networkManager->hasWiFiNetworks()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t currentTime = millis();
|
uint32_t currentTime = millis();
|
||||||
|
|
||||||
if (backupDigiMode) {
|
if (backupDigiMode) {
|
||||||
if (!networkManager->isWiFiConnected() && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) {
|
if (WiFi.status() != WL_CONNECTED && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) {
|
||||||
Serial.println("*** Stopping BackUp Digi Mode ***");
|
Serial.println("*** Stopping BackUp Digi Mode ***");
|
||||||
backupDigiMode = false;
|
backupDigiMode = false;
|
||||||
wifiCounter = 0;
|
wifiCounter = 0;
|
||||||
} else if (networkManager->isWiFiConnected()) {
|
} else if (WiFi.status() == WL_CONNECTED) {
|
||||||
Serial.println("*** WiFi Reconnect Success (Stopping Backup Digi Mode) ***");
|
Serial.println("*** WiFi Reconnect Success (Stopping Backup Digi Mode) ***");
|
||||||
backupDigiMode = false;
|
backupDigiMode = false;
|
||||||
wifiCounter = 0;
|
wifiCounter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !networkManager->isWifiAPActive()) {
|
if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !WiFiAutoAPStarted) {
|
||||||
lastWiFiCheck = currentTime;
|
lastWiFiCheck = currentTime;
|
||||||
if (networkManager->isWiFiConnected()) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
if (Config.digi.backupDigiMode && (currentTime - lastServerCheck > 30 * 1000)) {
|
if (Config.digi.backupDigiMode && (currentTime - lastServerCheck > 30 * 1000)) {
|
||||||
Serial.println("*** Server Connection LOST → Backup Digi Mode ***");
|
Serial.println("*** Server Connection LOST → Backup Digi Mode ***");
|
||||||
backupDigiMode = true;
|
backupDigiMode = true;
|
||||||
|
WiFi.disconnect();
|
||||||
lastBackupDigiTime = currentTime;
|
lastBackupDigiTime = currentTime;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Serial.println("Reconnecting to WiFi...");
|
Serial.println("Reconnecting to WiFi...");
|
||||||
|
WiFi.disconnect();
|
||||||
WIFI_Utils::startWiFi();
|
WIFI_Utils::startWiFi();
|
||||||
|
|
||||||
if (Config.digi.backupDigiMode) wifiCounter++;
|
if (Config.digi.backupDigiMode) wifiCounter++;
|
||||||
@@ -82,47 +83,97 @@ namespace WIFI_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startAutoAP() {
|
void startAutoAP() {
|
||||||
displayShow("", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
|
WiFi.mode(WIFI_MODE_NULL);
|
||||||
networkManager->setupAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
|
||||||
|
|
||||||
|
WiFiAutoAPTime = millis();
|
||||||
|
WiFiAutoAPStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void startWiFi() {
|
void startWiFi() {
|
||||||
networkManager->clearWiFiNetworks();
|
bool startAP = false;
|
||||||
for (size_t i = 0; i < Config.wifiAPs.size(); i++) {
|
if (currentWiFi->ssid == "") {
|
||||||
const WiFi_AP& wifiAP = Config.wifiAPs[i];
|
startAP = true;
|
||||||
if (wifiAP.ssid.isEmpty()) continue;
|
} else {
|
||||||
networkManager->addWiFiNetwork(wifiAP.ssid, wifiAP.password);
|
uint8_t wifiCounter = 0;
|
||||||
}
|
String hostName = "iGATE-" + Config.callsign;
|
||||||
|
WiFi.setHostname(hostName.c_str());
|
||||||
if (!networkManager->hasWiFiNetworks()) {
|
WiFi.mode(WIFI_STA);
|
||||||
Serial.println("WiFi SSID not set!");
|
WiFi.disconnect();
|
||||||
if (Config.wifiAutoAP.enabled) {
|
delay(500);
|
||||||
Serial.println("Starting AP fallback...");
|
unsigned long start = millis();
|
||||||
startAutoAP();
|
displayShow("", "Connecting to WiFi:", "", currentWiFi->ssid + " ...", 0);
|
||||||
|
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.print("' ");
|
||||||
|
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||||
|
while (WiFi.status() != WL_CONNECTED && wifiCounter < myWiFiAPSize) {
|
||||||
|
delay(500);
|
||||||
|
#ifdef INTERNAL_LED_PIN
|
||||||
|
digitalWrite(INTERNAL_LED_PIN, HIGH);
|
||||||
|
#endif
|
||||||
|
Serial.print('.');
|
||||||
|
delay(500);
|
||||||
|
#ifdef INTERNAL_LED_PIN
|
||||||
|
digitalWrite(INTERNAL_LED_PIN, LOW);
|
||||||
|
#endif
|
||||||
|
if ((millis() - start) > 10000){
|
||||||
|
delay(1000);
|
||||||
|
if (myWiFiAPIndex >= (myWiFiAPSize - 1)) {
|
||||||
|
myWiFiAPIndex = 0;
|
||||||
|
wifiCounter++;
|
||||||
|
} else {
|
||||||
|
myWiFiAPIndex++;
|
||||||
|
}
|
||||||
|
wifiCounter++;
|
||||||
|
currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
|
||||||
|
start = millis();
|
||||||
|
Serial.print("\nConnecting to WiFi '"); Serial.print(currentWiFi->ssid); Serial.println("' ...");
|
||||||
|
displayShow("", "Connecting to WiFi:", "", currentWiFi->ssid + " ...", 0);
|
||||||
|
WiFi.disconnect();
|
||||||
|
WiFi.begin(currentWiFi->ssid.c_str(), currentWiFi->password.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayShow("", "Connecting to WiFi:", "", " loading ...", 0);
|
|
||||||
networkManager->connectWiFi();
|
|
||||||
|
|
||||||
#ifdef INTERNAL_LED_PIN
|
#ifdef INTERNAL_LED_PIN
|
||||||
digitalWrite(INTERNAL_LED_PIN, LOW);
|
digitalWrite(INTERNAL_LED_PIN, LOW);
|
||||||
#endif
|
#endif
|
||||||
if (networkManager->isWiFiConnected()) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
Serial.print("[WiFi] Connected as ");
|
Serial.print("\nConnected as ");
|
||||||
Serial.print(networkManager->getWiFiIP());
|
Serial.print(WiFi.localIP());
|
||||||
Serial.print(" / MAC Address: ");
|
Serial.print(" / MAC Address: ");
|
||||||
Serial.println(networkManager->getWiFimacAddress());
|
Serial.println(WiFi.macAddress());
|
||||||
displayShow("", " Connected!!", "" , " loading ...", 1000);
|
displayShow("", " Connected!!", "" , " loading ...", 1000);
|
||||||
} else {
|
} else {
|
||||||
Serial.println("[WiFi] Not connected to WiFi!");
|
startAP = true;
|
||||||
if (Config.wifiAutoAP.enabled) {
|
|
||||||
Serial.println("Starting AP fallback...");
|
Serial.println("\nNot connected to WiFi! Starting Auto AP");
|
||||||
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
|
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
|
||||||
startAutoAP();
|
}
|
||||||
|
WiFiConnected = !startAP;
|
||||||
|
if (startAP) {
|
||||||
|
Serial.println("\nNot connected to WiFi! Starting Auto AP");
|
||||||
|
displayShow("", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
|
||||||
|
|
||||||
|
startAutoAP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkAutoAPTimeout() {
|
||||||
|
if (WiFiAutoAPStarted && Config.wifiAutoAP.timeout > 0) {
|
||||||
|
if (WiFi.softAPgetStationNum() > 0) {
|
||||||
|
WiFiAutoAPTime = 0;
|
||||||
} else {
|
} else {
|
||||||
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
|
if (WiFiAutoAPTime == 0) {
|
||||||
|
WiFiAutoAPTime = millis();
|
||||||
|
} else if ((millis() - WiFiAutoAPTime) > Config.wifiAutoAP.timeout * 60 * 1000) {
|
||||||
|
Serial.println("Stopping auto AP");
|
||||||
|
|
||||||
|
WiFiAutoAPStarted = false;
|
||||||
|
WiFi.softAPdisconnect(true);
|
||||||
|
|
||||||
|
Serial.println("Auto AP stopped (timeout)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,4 +183,4 @@ namespace WIFI_Utils {
|
|||||||
btStop();
|
btStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+12
-12
@@ -1,17 +1,17 @@
|
|||||||
# Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
# Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
#
|
#
|
||||||
# This file is part of LoRa APRS iGate.
|
# This file is part of LoRa APRS iGate.
|
||||||
#
|
#
|
||||||
# LoRa APRS iGate is free software: you can redistribute it and/or modify
|
# 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
|
# 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.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# LoRa APRS iGate is distributed in the hope that it will be useful,
|
# LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# 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/>.
|
# 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:
|
if start > 0 and end > start:
|
||||||
versionDate = line[start:end]
|
versionDate = line[start:end]
|
||||||
break
|
break
|
||||||
|
|
||||||
for src in files:
|
for src in files:
|
||||||
out = src + ".gz"
|
out = src + ".gz"
|
||||||
|
|
||||||
|
|
||||||
with open(src, 'rb') as f:
|
with open(src, 'rb') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
if src == 'data_embed/index.html':
|
if src == 'data_embed/index.html':
|
||||||
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
|
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
|
||||||
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
|
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
|
||||||
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
|
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
|
||||||
|
|
||||||
content = content.replace(b'%BUILD_INFO%', build_info)
|
content = content.replace(b'%BUILD_INFO%', build_info)
|
||||||
|
|
||||||
with open(out, 'wb') as f:
|
with open(out, 'wb') as f:
|
||||||
f.write(gzip.compress(content, compresslevel=9))
|
f.write(gzip.compress(content, compresslevel=9))
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
/* 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_SX1268
|
|
||||||
#define HAS_1W_LORA
|
|
||||||
#define HAS_TCXO
|
|
||||||
#define RADIO_SCLK_PIN 18
|
|
||||||
#define RADIO_MISO_PIN 19
|
|
||||||
#define RADIO_MOSI_PIN 23
|
|
||||||
#define RADIO_CS_PIN 5
|
|
||||||
#define RADIO_RST_PIN 27
|
|
||||||
#define RADIO_DIO1_PIN 12
|
|
||||||
#define RADIO_BUSY_PIN 14
|
|
||||||
#define RADIO_RXEN 32
|
|
||||||
#define RADIO_TXEN 25
|
|
||||||
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
|
|
||||||
#define GPIO_WAKEUP_PIN GPIO_SEL_12
|
|
||||||
|
|
||||||
// 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
|
|
||||||
#define BATTERY_PIN 35
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
[env:RPC_LORA_DIGIGATE_1W]
|
|
||||||
board = esp32dev
|
|
||||||
build_flags =
|
|
||||||
${common.build_flags}
|
|
||||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
|
||||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
|
||||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
|
||||||
-D RPC_LORA_DIGIGATE_1W
|
|
||||||
lib_deps =
|
|
||||||
${common.lib_deps}
|
|
||||||
${common.display_libs}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
/* 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_SX1276
|
|
||||||
#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
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
|
|
||||||
// GPS
|
|
||||||
#define HAS_GPS
|
|
||||||
#define GPS_BAUDRATE 9600
|
|
||||||
#define GPS_RX 12
|
|
||||||
#define GPS_TX 34
|
|
||||||
|
|
||||||
// Aditional Config
|
|
||||||
#define INTERNAL_LED_PIN 25 // Green Led
|
|
||||||
#define BATTERY_PIN 35
|
|
||||||
#define HAS_ADC_CALIBRATION
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user