Compare commits

..

78 Commits

Author SHA1 Message Date
Ricardo Guzman (Richonguzman) 96d7be6ffc Map cargado y funcionando 2026-06-12 00:09:02 -04:00
Ricardo Guzman (Richonguzman) 94df6d2e8f map con estaciones 2026-06-11 23:59:24 -04:00
Ricardo Guzman (Richonguzman) 0a222edeb8 primer test solo igate 2026-06-11 22:15:37 -04:00
Ricardo Guzman (Richonguzman) e35d5f6a9e about update 2026-06-11 01:55:45 -04:00
Ricardo Guzman (Richonguzman) 96432b8624 NEW WEB 2026-06-11 01:39:34 -04:00
Ricardo Guzman (Richonguzman) 25ea82737a cad test 3 2026-06-10 10:46:07 -04:00
Ricardo Guzman (Richonguzman) df22b9dbc0 readme and date update 2026-06-10 00:27:15 -04:00
Ricardo Guzman (Richonguzman) 9898e123d2 CAD with backoff process 2026-06-10 00:25:27 -04:00
Ricardo Guzman (Richonguzman) a97757c0b2 test CAD 2026-06-09 22:46:41 -04:00
Ricardo Guzman (Richonguzman) 305c5b73f7 mayusculas a RPC 2026-04-21 11:16:02 -04:00
Ricardo Guzman (Richonguzman) dd3688f23b fake name 2026-04-21 11:12:00 -04:00
Ricardo Guzman (Richonguzman) 6f5846c53d test algo dos 2026-04-21 11:05:52 -04:00
Ricardo Guzman (Richonguzman) 5c30e72a88 test algo 2026-04-21 11:05:27 -04:00
Ricardo Guzman (Richonguzman) a024b939f5 Merge pull request #423 from richonguzman/richonguzman-patch-19
Add RPC_LORA_DIGIGATE_1W to build workflow
2026-04-21 10:42:19 -04:00
Ricardo Guzman (Richonguzman) 96c52c042e Add RPC_LORA_DIGIGATE_1W to build workflow 2026-04-21 10:40:33 -04:00
Ricardo Guzman (Richonguzman) 236ae65485 ElegantOta fix 2026-04-21 10:34:20 -04:00
Ricardo Guzman (Richonguzman) 4263d55839 screen height fix 2026-04-20 12:56:03 -04:00
Ricardo Guzman (Richonguzman) ba96bb135e test new screen 2026-04-20 12:37:52 -04:00
Ricardo Guzman (Richonguzman) 944d1fceaf cleaning code again 2026-04-13 15:24:55 -04:00
Ricardo Guzman (Richonguzman) 005abb3593 Merge pull request #412 from richonguzman/richonguzman-patch-18
Add 2 new target configurations for GPS variants
2026-03-25 12:22:03 -03:00
Ricardo Guzman (Richonguzman) fcf77f6d4b Add 2 new target configurations for GPS variants 2026-03-25 12:20:40 -03:00
Ricardo Guzman (Richonguzman) f576ac6c04 readme update 2026-03-25 12:09:43 -03:00
Ricardo Guzman (Richonguzman) f9f8a9b957 test new boards and gps read update 2026-03-25 12:03:00 -03:00
Ricardo Guzman (Richonguzman) 7f8c2af86b killing blank spaces 2026-03-20 16:47:05 -03:00
Ricardo Guzman (Richonguzman) 198245f600 minor blackspace fix 2026-03-10 23:43:50 -03:00
Ricardo Guzman (Richonguzman) 372bb532b8 Merge pull request #410 from richonguzman/richonguzman-patch-17
Add ttgo-t-beam-v1_SX1262 configuration
2026-03-10 23:38:02 -03:00
Ricardo Guzman (Richonguzman) 05f69fe450 Add ttgo-t-beam-v1_SX1262 configuration 2026-03-10 23:34:22 -03:00
Ricardo Guzman (Richonguzman) 0e8337292f added Tbeam AXP192 and SX1262 2026-03-10 23:25:04 -03:00
Ricardo Guzman (Richonguzman) f87a89213f ArduinoJson 7 update 2026-03-10 23:10:04 -03:00
Ricardo Guzman (Richonguzman) 3810821f45 Minor improvements for OTA and others 2026-03-10 22:44:02 -03:00
Ricardo Guzman (Richonguzman) 6a3b1c903d Merge pull request #382 from petrkr/networkmanager
Network manager
2026-03-10 19:40:14 -03:00
Ricardo Guzman (Richonguzman) 11c36a91fb Safe Poing V3.2.2 2026-03-08 10:32:18 -03:00
Ricardo Guzman (Richonguzman) a97ffe709f Merge pull request #407 from richonguzman/backBefore-FIX-OTA
Back before fix ota
2026-03-08 10:21:40 -03:00
Ricardo Guzman (Richonguzman) c80b565730 Merge branch 'main' into backBefore-FIX-OTA 2026-03-08 10:18:44 -03:00
Ricardo Guzman (Richonguzman) 84cbcc30e8 intentando arreglar 2026-03-08 10:12:57 -03:00
Ricardo Guzman (Richonguzman) 11d413cb17 corrigiendo desorden 2026-03-08 10:11:43 -03:00
Ricardo Guzman (Richonguzman) 19d767e3cb corrigiendo desorden 2026-03-08 10:11:10 -03:00
Ricardo Guzman (Richonguzman) 803bde1008 Merge pull request #405 from petrkr/fixconfig
Fix OTA update due bad configuration behaviour
2026-03-08 09:51:40 -03:00
Ricardo Guzman (Richonguzman) 8ab1f5ec24 many more libraries update 2026-03-08 09:21:34 -03:00
Ricardo Guzman (Richonguzman) cfc9474469 RadioLib 7.6.0 update 2026-03-08 09:12:56 -03:00
Ricardo Guzman (Richonguzman) 949807bc14 Merge pull request #249 from petrkr/updates
Updates
2026-03-08 09:00:06 -03:00
Petr Kracik 37e3a2f831 Wifi: use hasWifiNetworks 2026-03-06 04:08:17 +01:00
Petr Kracik 8bca896e31 Utils: Added Ethernet IP 2026-03-06 04:08:17 +01:00
Petr Kracik e241321abc Use AutoAP 2026-03-06 04:08:17 +01:00
Petr Kracik d8b658df30 Prepare AutoAP enable/disable 2026-03-06 04:08:17 +01:00
Petr Kracik 9775c601bb Wifi utils add multiple SSID 2026-03-06 04:08:17 +01:00
Petr Kracik b0d441a519 Use setHostname from network manager 2026-03-06 04:08:16 +01:00
Petr Kracik 413318e94b Use SoftAP from network manager 2026-03-06 04:08:16 +01:00
Petr Kracik ec28dc0978 Removed deprecated WiFiConnected extern 2026-03-06 04:08:16 +01:00
Petr Kracik e5fa6574f0 Syslog uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 8a16be1bea NTP: Fix nullptr if network is connected after setup() 2026-03-06 04:08:16 +01:00
Petr Kracik 891fc906bb NTP uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik eb4087073b Lora uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 10d1fb4ede Utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik f18d16a6fb ARPS Utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 1edb333b1d WiFi utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik ca567e720d NetworkManager: NM log prefix 2026-03-06 04:08:09 +01:00
Petr Kracík 35760b74f7 Fix OTA update due bad configuration behaviour 2026-03-06 03:49:11 +01:00
Petr Kracik 801641f781 Network manager supports basic Ethernet 2026-03-06 00:09:51 +01:00
Petr Kracik 447c2d4937 Network manager support more Wifi networks 2026-03-06 00:08:44 +01:00
Petr Kracik 1668fc1412 Network manager: AutoAP Disable 2026-03-06 00:08:44 +01:00
Petr Kracik 1ff5504956 Network manager: wifi connect use LED 2026-03-05 23:04:14 +01:00
Petr Kracik e0c6608055 Network manager supports setHostname 2026-03-05 23:04:14 +01:00
Petr Kracík d2c9bcb71d NetworkManager: Initial commit 2026-03-05 23:04:14 +01:00
Petr Kracik 630de55feb Main include WiFiClient instead generic WiFi 2026-03-05 23:04:14 +01:00
Petr Kracík b5690a2f37 Updated espressif SDK 2026-03-05 22:06:00 +01:00
Petr Kracík a7ae6c9fd4 Fix include WiFiClient in MQTT client 2026-03-05 22:01:35 +01:00
Ricardo Guzman (Richonguzman) b448e2fc6b ADC_CTRL_PIN fix 2026-03-05 10:06:22 -03:00
Ricardo Guzman (Richonguzman) c33720a5fb VEXT_CTRL_PIN definition missing fix 2026-03-04 12:24:16 -03:00
Ricardo Guzman (Richonguzman) e57fad6666 aprsis.active update in digi.ecoMode == 1 ? 2026-03-04 11:21:26 -03:00
Ricardo Guzman (Richonguzman) 2418291ac9 digiEcoMode forces APRS.active = false 2026-03-03 11:22:41 -03:00
Ricardo Guzman (Richonguzman) 796ba35357 add new ESP32 device 9M2IBR 2026-02-26 12:28:52 -03:00
Ricardo Guzman (Richonguzman) 57873e4181 readme and date update 2026-02-26 11:51:59 -03:00
Ricardo Guzman (Richonguzman) 453222d69f new board added 9M2IBR 2026-02-26 11:08:49 -03:00
Ricardo Guzman (Richonguzman) fbb347fb7f new update to kiss utils 2026-02-26 10:31:45 -03:00
Ricardo Guzman (Richonguzman) 50fd831fdb kiss correction of missing new indexOf 2026-02-26 10:23:40 -03:00
Ricardo Guzman (Richonguzman) 3a2c0304d0 status logic improved 2026-02-25 12:25:40 -03:00
Ricardo Guzman (Richonguzman) b24c2b2527 update build for heltec v2 915 2026-02-25 10:57:07 -03:00
44 changed files with 2619 additions and 1285 deletions
+15 -3
View File
@@ -13,12 +13,18 @@ jobs:
target:
- name: ttgo-lora32-v21
chip: esp32
- name: ttgo-lora32-v21_GPS
chip: esp32
- name: ttgo-lora32-v21_915
chip: esp32
- name: ttgo-lora32-v21_915_GPS
chip: esp32
- name: ttgo_lora32_t3s3_v1_2
chip: esp32s3
- name: heltec-lora32-v2
chip: esp32
- name: heltec-lora32-v2_915
chip: esp32
- name: heltec_wifi_lora_32_V3
chip: esp32s3
- name: heltec_wifi_lora_32_V3_2
@@ -53,6 +59,8 @@ jobs:
chip: esp32
- name: ttgo-t-beam-v1_SX1268
chip: esp32
- name: ttgo-t-beam-v1_SX1262
chip: esp32
- name: ttgo-t-beam-v1_2_SX1262
chip: esp32
- name: ttgo_t_deck_plus
@@ -93,10 +101,14 @@ jobs:
chip: esp32s3
- name: QRPLabs_LightGateway_Plus_1_0
chip: esp32s3
- name: RPC_LORA_DIGIGATE_1W
chip: esp32
- name: XIAO_ESP32S3_WIO_SX1262
chip: esp32s3
- name: TROY_LoRa_APRS
chip: esp32
chip: esp32
- name: ESP32_9M2IBR_1W_LoRa_GPS
chip: esp32
steps:
- uses: actions/checkout@v3
@@ -109,7 +121,7 @@ jobs:
- name: Build target
run: |
pio run -e ${{ matrix.target.name }}
pio run -e ${{ matrix.target.name }}
- name: Build FS
run: |
@@ -124,7 +136,7 @@ jobs:
cp .pio/build/${{ matrix.target.name }}/partitions.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/
- name: Merge for web flashing
run: |
if [ "${{ matrix.target.chip }}" == "esp32" ]; then
+3
View File
@@ -51,6 +51,9 @@ ____________________________________________________
<br />
# Timeline (Versions):
- 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-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
- 2026-02-15 Digipeater code/logic improved.
- 2026-02-08 Heltec V2 915MHz added.
+12 -12
View File
@@ -23,25 +23,25 @@ build_flags =
-D RADIOLIB_EXCLUDE_SSTV=1
-I variants/${PIOENV}
lib_deps =
adafruit/Adafruit Unified Sensor @ 1.1.14
adafruit/Adafruit AHTX0 @ 2.0.5
adafruit/Adafruit BME280 Library @ 2.2.4
adafruit/Adafruit BMP280 Library @ 2.6.8
adafruit/Adafruit BME680 Library @ 2.0.4
adafruit/Adafruit Unified Sensor @ 1.1.15
adafruit/Adafruit AHTX0 @ 2.0.6
adafruit/Adafruit BME280 Library @ 2.3.0
adafruit/Adafruit BMP280 Library @ 3.0.0
adafruit/Adafruit BME680 Library @ 2.0.6
adafruit/Adafruit INA219 @ 1.2.3
adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 6.21.3
jgromes/RadioLib @ 7.1.0
bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8
ESP32Async/AsyncTCP @ 3.4.9
ESP32Async/ESPAsyncWebServer @ 3.9.3
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.4
display_libs =
adafruit/Adafruit GFX Library @ 1.11.9
adafruit/Adafruit SSD1306 @ 2.5.10
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
usb_flags=
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_CDC_ON_BOOT=1
+6 -5
View File
@@ -28,9 +28,9 @@
"messagesToRF": false,
"objectsToRF": false,
"server": "rotate.aprs2.net",
"passcode": "XYZVW",
"passcode": "XYZVW",
"port": 14580,
"filter": "m/10"
"filter": "m/10"
},
"personalNote": "",
"blacklist": "",
@@ -47,10 +47,11 @@
"rxSignalBandwidth": 125000,
"txActive": false,
"txFreq": 433775000,
"txSpreadingFactor": 12,
"txCodingRate4": 5,
"txSpreadingFactor": 9,
"txCodingRate4": 7,
"txSignalBandwidth": 125000,
"power": 20
"power": 20,
"cadActive": true
},
"display": {
"alwaysOn": true,
+1093 -956
View File
File diff suppressed because one or more lines are too long
+226 -7
View File
@@ -118,7 +118,7 @@ function loadSettings(settings) {
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
document.getElementById("beacon.interval").value = settings.beacon.interval;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
@@ -148,12 +148,26 @@ function loadSettings(settings) {
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
LoRaRxCheckbox.checked = settings.lora.rxActive;
LoRaRxFreq.disabled = !LoRaRxCheckbox.checked;
LoRaRxSpreadingFactor.disabled = !LoRaRxCheckbox.checked;
LoRaRxCodingRate.disabled = !LoRaRxCheckbox.checked;
LoRaRxSignalBandwidth.disabled = !LoRaRxCheckbox.checked;
document.getElementById("lora.txActive").checked = settings.lora.txActive;
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.power").value = settings.lora.power;
document.getElementById("lora.cadActive").checked = settings.lora.cadActive;
LoRaTxCheckbox.checked = settings.lora.txActive;
LoRaTxFreq.disabled = !LoRaTxCheckbox.checked;
LoRaTxSpreadingFactor.disabled = !LoRaTxCheckbox.checked;
LoRaTxCodingRate.disabled = !LoRaTxCheckbox.checked;
LoRaTxSignalBandwidth.disabled = !LoRaTxCheckbox.checked;
LoRaPower.disabled = !LoRaTxCheckbox.checked;
LoRaCadActive.disabled = !LoRaTxCheckbox.checked;
// Display
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
@@ -195,7 +209,7 @@ function loadSettings(settings) {
TelemetryCheckbox.checked = settings.wxsensor.active;
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
// SYSLOG
document.getElementById("syslog.active").checked = settings.syslog.active;
document.getElementById("syslog.server").value = settings.syslog.server;
@@ -205,7 +219,7 @@ function loadSettings(settings) {
SyslogServer.disabled = !SyslogCheckbox.checked;
SyslogPort.disabled = !SyslogCheckbox.checked;
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
// TNC
if (settings.tnc) {
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
@@ -237,8 +251,10 @@ function loadSettings(settings) {
RebootModeTime.disabled = !RebootModeCheckbox.check;
// 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.timeout").value = settings.wifi.autoAP.timeout;
toggleWiFiAutoAPFields();
// OTA
document.getElementById("ota.username").value = settings.ota.username;
@@ -388,6 +404,36 @@ TelemetryCheckbox.addEventListener("change", function () {
TelemetryTempCorrection.disabled = !this.checked;
});
// LoRa Rx Switch
const LoRaRxCheckbox = document.querySelector('input[name="lora.rxActive"]');
const LoRaRxFreq = document.querySelector('[name="lora.rxFreq"]');
const LoRaRxSpreadingFactor = document.querySelector('[name="lora.rxSpreadingFactor"]');
const LoRaRxCodingRate = document.querySelector('[name="lora.rxCodingRate4"]');
const LoRaRxSignalBandwidth = document.querySelector('[name="lora.rxSignalBandwidth"]');
LoRaRxCheckbox.addEventListener("change", function () {
LoRaRxFreq.disabled = !this.checked;
LoRaRxSpreadingFactor.disabled = !this.checked;
LoRaRxCodingRate.disabled = !this.checked;
LoRaRxSignalBandwidth.disabled = !this.checked;
});
// LoRa Tx Switch
const LoRaTxCheckbox = document.querySelector('input[name="lora.txActive"]');
const LoRaTxFreq = document.querySelector('[name="lora.txFreq"]');
const LoRaTxSpreadingFactor = document.querySelector('[name="lora.txSpreadingFactor"]');
const LoRaTxCodingRate = document.querySelector('[name="lora.txCodingRate4"]');
const LoRaTxSignalBandwidth = document.querySelector('[name="lora.txSignalBandwidth"]');
const LoRaPower = document.querySelector('[name="lora.power"]');
const LoRaCadActive = document.querySelector('[name="lora.cadActive"]');
LoRaTxCheckbox.addEventListener("change", function () {
LoRaTxFreq.disabled = !this.checked;
LoRaTxSpreadingFactor.disabled = !this.checked;
LoRaTxCodingRate.disabled = !this.checked;
LoRaTxSignalBandwidth.disabled = !this.checked;
LoRaPower.disabled = !this.checked;
LoRaCadActive.disabled = !this.checked;
});
// Syslog Switches
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
const SyslogServer = document.querySelector('input[name="syslog.server"]');
@@ -432,6 +478,18 @@ WebadminCheckbox.addEventListener("change", function () {
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 () {
const networksContainer = document.querySelector(".list-networks");
@@ -545,7 +603,7 @@ function loadReceivedPackets(packets) {
packets.forEach((packet) => {
const element = document.createElement("tr");
element.innerHTML = `
<td>${packet.rxTime}</td>
<td>${packet.packet}</td>
@@ -578,8 +636,169 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchReceivedPackets();
})
})
/* ---------- Stations Map (Leaflet, CDN) ---------- */
let mapInstance = null;
let mapMarkers = null;
let mapTimer = null;
let mapTileErrShown = false;
let iGateLatLng = null;
let mapAutoFitDone = false;
function setMapMessage(text) {
const el = document.getElementById("map");
if (el) {
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--msh-text-dim,#8b949e);text-align:center;padding:20px;">${text}</div>`;
}
}
function loadMapStations(stations) {
if (!mapMarkers) return;
mapMarkers.clearLayers();
let drawn = 0;
(stations || []).forEach((s) => {
if (s.lat === 0 && s.lon === 0) return; // descartar estaciones sin fix
const popup = `<b>${s.callsign}</b>`
+ (s.lastHeard ? `<br>Last: ${s.lastHeard}` : "")
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
+ `<br>Packets: ${s.count}`;
L.circleMarker([s.lat, s.lon], {
radius: 6,
color: "#1d6fe0", // borde
weight: 2,
fillColor: "#3b8cff", // relleno
fillOpacity: 0.9
}).bindPopup(popup).addTo(mapMarkers);
drawn++;
});
if (!mapAutoFitDone && drawn > 0) { // auto-fit la 1ª vez que hay estaciones tras abrir
fitMapToStations();
mapAutoFitDone = true;
}
}
// Encuadra el mapa para que entren todas las estaciones (+ el iGate). 100% navegador.
function fitMapToStations() {
if (!mapInstance) return false;
const pts = [];
if (mapMarkers) {
mapMarkers.eachLayer((layer) => {
if (layer.getLatLng) pts.push(layer.getLatLng());
});
}
if (iGateLatLng) pts.push(iGateLatLng);
if (!pts.length) return false;
mapInstance.fitBounds(L.latLngBounds(pts), { padding: [30, 30], maxZoom: 14 });
return true;
}
function fetchMapStations() {
fetch("/stations.json")
.then((response) => response.json())
.then((stations) => {
loadMapStations(stations);
})
.catch((err) => {
console.error(err);
console.error(`Failed to load stations`);
});
}
window.showMap = function () {
if (typeof L === "undefined") { // sin internet el CDN de Leaflet no cargó
setMapMessage("Map unavailable &mdash; no internet connection.<br>The map needs internet to load (OpenStreetMap).");
return;
}
mapAutoFitDone = false; // re-encuadrar en cada apertura del Map
if (!mapInstance) {
mapInstance = L.map("map");
const tiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; OpenStreetMap"
}).addTo(mapInstance);
tiles.on("tileerror", function () { // tiles no cargan (sin salida a internet)
if (!mapTileErrShown) {
mapTileErrShown = true;
if (window.showToast) showToast("Map tiles could not load — check internet connection.");
}
});
mapMarkers = L.layerGroup().addTo(mapInstance);
const FitControl = L.Control.extend({ // botón "Fit All Stations" (control nativo de Leaflet)
options: { position: "topright" },
onAdd: function () {
const btn = L.DomUtil.create("button", "");
btn.type = "button";
btn.title = "Fit all stations";
btn.textContent = "Fit All Stations";
btn.style.cssText = "background:#fff;color:#333;border:2px solid rgba(0,0,0,.2);border-radius:4px;padding:5px 9px;cursor:pointer;font:600 12px sans-serif;box-shadow:0 1px 4px rgba(0,0,0,.3);";
L.DomEvent.on(btn, "click", (e) => {
L.DomEvent.stop(e);
fitMapToStations();
});
return btn;
}
});
mapInstance.addControl(new FitControl());
fetch("/configuration.json") // centrar y marcar el iGate
.then((response) => response.json())
.then((config) => {
const lat = (config.beacon && config.beacon.latitude) || 0;
const lon = (config.beacon && config.beacon.longitude) || 0;
const callsign = config.callsign || "iGate";
mapInstance.setView([lat, lon], (lat || lon) ? 12 : 2);
if (lat || lon) { // rombo rojo en la posición del iGate
iGateLatLng = L.latLng(lat, lon); // guardar para incluirlo en el auto-fit
const iGateIcon = L.divIcon({
className: "igate-marker",
html: '<div style="width:14px;height:14px;background:#e23b3b;border:2px solid #fff;transform:rotate(45deg);box-shadow:0 0 4px rgba(0,0,0,.6);"></div>',
iconSize: [18, 18],
iconAnchor: [9, 9]
});
L.marker([lat, lon], { icon: iGateIcon })
.bindPopup(`<b>${callsign}</b><br>This station`)
.addTo(mapInstance); // directo al mapa: no se borra en los refrescos
}
})
.catch(() => {
mapInstance.setView([0, 0], 2);
});
}
setTimeout(function () { // el div estuvo oculto: recalcular tamaño
if (mapInstance) mapInstance.invalidateSize();
}, 0);
fetchMapStations();
if (mapTimer) clearInterval(mapTimer);
mapTimer = setInterval(function () { // refrescar solo si la sección está visible
const section = document.getElementById("sec-maps");
if (section && section.classList.contains("active")) {
fetchMapStations();
}
}, 15000);
}
+4 -2
View File
@@ -32,6 +32,7 @@ public:
class WiFi_Auto_AP {
public:
bool enabled; // Enable Auto AP
String password;
int timeout;
};
@@ -85,6 +86,7 @@ public:
int txCodingRate4;
long txSignalBandwidth;
int power;
bool cadActive;
};
class Display {
@@ -195,13 +197,13 @@ public:
REMOTE_MANAGEMENT remoteManagement;
MQTT mqtt;
void setup();
void setDefaultValues();
bool writeFile();
Configuration();
private:
bool readFile();
String _filePath;
};
#endif
#endif
+45
View File
@@ -0,0 +1,45 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef 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
+82
View File
@@ -0,0 +1,82 @@
#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;
};
+1 -1
View File
@@ -24,7 +24,7 @@
namespace NTP_Utils {
void setup();
bool setup();
void update();
String getFormatedTime();
+1 -1
View File
@@ -34,7 +34,7 @@ namespace POWER_Utils {
void vext_ctrl_OFF();
#endif
#ifdef ADC_CTRL
#ifdef ADC_CTRL_PIN
void adc_ctrl_ON();
void adc_ctrl_OFF();
#endif
-1
View File
@@ -27,7 +27,6 @@ namespace WIFI_Utils {
void checkWiFi();
void startAutoAP();
void startWiFi();
void checkAutoAPTimeout();
void setup();
}
+3 -3
View File
@@ -16,17 +16,17 @@ extra_configs =
variants/*/platformio.ini
[env]
platform = espressif32 @ 6.7.0
platform = espressif32 @ 6.12.0
board_build.partitions = min_spiffs.csv
framework = arduino
monitor_speed = 115200
board_build.embed_files =
board_build.embed_files =
data_embed/index.html.gz
data_embed/style.css.gz
data_embed/script.js.gz
data_embed/bootstrap.css.gz
data_embed/bootstrap.js.gz
data_embed/favicon.png.gz
extra_scripts =
extra_scripts =
pre:tools/compress.py
debug_tool = esp-prog
+19 -10
View File
@@ -41,9 +41,10 @@ ___________________________________________________________________*/
#include <ElegantOTA.h>
#include <TinyGPS++.h>
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <vector>
#include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h"
#include "station_utils.h"
#include "battery_utils.h"
@@ -67,8 +68,8 @@ ___________________________________________________________________*/
#endif
String versionDate = "2026-02-25";
String versionNumber = "3.2";
String versionDate = "2026-06-12";
String versionNumber = "4.0.0";
Configuration Config;
WiFiClient aprsIsClient;
WiFiClient mqttClient;
@@ -79,9 +80,7 @@ WiFiClient mqttClient;
bool gpsInfoToggle = false;
#endif
uint8_t myWiFiAPIndex = 0;
int myWiFiAPSize = Config.wifiAPs.size();
WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
NetworkManager *networkManager;
bool isUpdatingOTA = false;
uint32_t lastBatteryCheck = 0;
@@ -101,6 +100,13 @@ String firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seven
void setup() {
Serial.begin(115200);
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();
Utils::setupDisplay();
LoRa_Utils::setup();
@@ -132,7 +138,7 @@ void loop() {
Utils::checkSleepByLowBatteryVoltage(1);
SLEEP_Utils::startSleeping();
} else {
WIFI_Utils::checkAutoAPTimeout();
networkManager->loop();
if (isUpdatingOTA) {
ElegantOTA.loop();
@@ -161,11 +167,14 @@ void loop() {
#endif
#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();
#else
WIFI_Utils::checkWiFi();
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) APRS_IS_Utils::connect();
if (Config.mqtt.active && (WiFi.status() == WL_CONNECTED) && !mqttClient.connected()) MQTT_Utils::connect();
if (networkManager->isConnected()) {
if (Config.aprs_is.active && !aprsIsClient.connected()) APRS_IS_Utils::connect();
if (Config.mqtt.active && !mqttClient.connected()) MQTT_Utils::connect();
}
#endif
NTP_Utils::update();
@@ -216,4 +225,4 @@ void loop() {
Utils::checkRebootTime();
Utils::checkSleepByLowBatteryVoltage(1);
}
}
}
+6 -6
View File
@@ -17,8 +17,9 @@
*/
#include <APRSPacketLib.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h"
#include "station_utils.h"
#include "board_pinout.h"
@@ -32,6 +33,7 @@
extern Configuration Config;
extern NetworkManager *networkManager;
extern WiFiClient aprsIsClient;
extern uint32_t lastScreenOn;
extern String firstLine;
@@ -45,7 +47,6 @@ extern bool modemLoggedToAPRSIS;
extern bool backupDigiMode;
extern String versionNumber;
uint32_t lastRxTime = millis();
bool passcodeValid = false;
uint32_t lastServerCheck = 0;
@@ -93,7 +94,7 @@ namespace APRS_IS_Utils {
void checkStatus() {
String wifiState, aprsisState;
if (WiFi.status() == WL_CONNECTED) {
if (networkManager->isWiFiConnected()) {
wifiState = "OK";
} else {
if (backupDigiMode || Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
@@ -401,14 +402,13 @@ namespace APRS_IS_Utils {
String aprsisPacket = aprsIsClient.readStringUntil('\r');
aprsisPacket.trim(); // Serial.println(aprsisPacket);
processAPRSISPacket(aprsisPacket);
lastRxTime = millis();
}
}
#endif
}
void firstConnection() {
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) {
if (Config.aprs_is.active && networkManager->isConnected() && !aprsIsClient.connected()) {
connect();
while (!passcodeValid) {
listenAPRSIS();
@@ -416,4 +416,4 @@ namespace APRS_IS_Utils {
}
}
}
}
+3 -3
View File
@@ -46,7 +46,7 @@ Adafruit_INA219 ina219;
#ifdef HAS_ADC_CALIBRATION
#include <esp_adc_cal.h>
#if defined(TTGO_LORA32_V2_1) || defined(TTGO_LORA32_V2_1_915)
#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)
#define InternalBattery_ADC_Channel ADC1_CHANNEL_7 // t_lora32 pin35
#define ExternalVoltage_ADC_Channel ADC1_CHANNEL_6 // t_lora32 pin34
#endif
@@ -154,7 +154,7 @@ namespace BATTERY_Utils {
}
#else
#ifdef ADC_CTRL
#ifdef ADC_CTRL_PIN
POWER_Utils::adc_ctrl_ON();
#endif
@@ -180,7 +180,7 @@ namespace BATTERY_Utils {
delay(3);
}
#ifdef ADC_CTRL
#ifdef ADC_CTRL_PIN
POWER_Utils::adc_ctrl_OFF();
#ifdef HELTEC_WP_V1
+101 -91
View File
@@ -29,7 +29,7 @@ bool shouldSleepStop = true;
bool Configuration::writeFile() {
Serial.println("Saving configuration...");
StaticJsonDocument<3584> data;
JsonDocument data;
File configFile = SPIFFS.open("/igate_conf.json", "w");
if (!configFile) {
@@ -47,6 +47,7 @@ bool Configuration::writeFile() {
data["other"]["startupDelay"] = startupDelay;
data["wifi"]["autoAP"]["enabled"] = wifiAutoAP.enabled;
data["wifi"]["autoAP"]["password"] = wifiAutoAP.password;
data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout;
@@ -79,6 +80,9 @@ bool Configuration::writeFile() {
data["beacon"]["statusPacket"] = beacon.statusPacket;
data["beacon"]["gpsActive"] = beacon.gpsActive;
#if !defined(HAS_GPS)
data["beacon"]["gpsActive"] = false;
#endif
data["beacon"]["ambiguityLevel"] = beacon.ambiguityLevel;
data["personalNote"] = personalNote;
@@ -87,6 +91,7 @@ bool Configuration::writeFile() {
data["digi"]["mode"] = digi.mode;
data["digi"]["ecoMode"] = digi.ecoMode;
if (digi.ecoMode == 1) data["aprs_is"]["active"] = false;
#if defined(HAS_A7670)
if (digi.ecoMode == 1) data["digi"]["ecoMode"] = 2;
#endif
@@ -101,6 +106,7 @@ bool Configuration::writeFile() {
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
data["lora"]["power"] = loramodule.power;
data["lora"]["cadActive"] = loramodule.cadActive;
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
int txSpreadingFactor = loramodule.txSpreadingFactor;
@@ -194,8 +200,7 @@ bool Configuration::readFile() {
if (configFile) {
bool needsRewrite = false;
StaticJsonDocument<3584> data;
JsonDocument data;
DeserializationError error = deserializeJson(data, configFile);
if (error) {
Serial.println("Failed to read file, using default configuration");
@@ -210,26 +215,28 @@ bool Configuration::readFile() {
wifiAPs.push_back(wifiap);
}
if (!data["other"].containsKey("startupDelay")) needsRewrite = true;
if (data["other"]["startupDelay"].isNull()) needsRewrite = true;
startupDelay = data["other"]["startupDelay"] | 0;
if (!data["wifi"]["autoAP"].containsKey("password") ||
!data["wifi"]["autoAP"].containsKey("timeout")) needsRewrite = true;
if (data["wifi"]["autoAP"]["enabled"].isNull() ||
data["wifi"]["autoAP"]["password"].isNull() ||
data["wifi"]["autoAP"]["timeout"].isNull()) needsRewrite = true;
wifiAutoAP.enabled = data["wifi"]["autoAP"]["enabled"] | true;
wifiAutoAP.password = data["wifi"]["autoAP"]["password"] | "1234567890";
wifiAutoAP.timeout = data["wifi"]["autoAP"]["timeout"] | 10;
if (!data.containsKey("callsign")) needsRewrite = true;
if (data["callsign"].isNull()) needsRewrite = true;
callsign = data["callsign"] | "NOCALL-10";
if (!data.containsKey("tacticalCallsign")) needsRewrite = true;
if (data["tacticalCallsign"].isNull()) needsRewrite = true;
tacticalCallsign = data["tacticalCallsign"] | "";
if (!data["aprs_is"].containsKey("active") ||
!data["aprs_is"].containsKey("passcode") ||
!data["aprs_is"].containsKey("server") ||
!data["aprs_is"].containsKey("port") ||
!data["aprs_is"].containsKey("filter") ||
!data["aprs_is"].containsKey("messagesToRF") ||
!data["aprs_is"].containsKey("objectsToRF")) needsRewrite = true;
if (data["aprs_is"]["active"].isNull() ||
data["aprs_is"]["passcode"].isNull() ||
data["aprs_is"]["server"].isNull() ||
data["aprs_is"]["port"].isNull() ||
data["aprs_is"]["filter"].isNull() ||
data["aprs_is"]["messagesToRF"].isNull() ||
data["aprs_is"]["objectsToRF"].isNull()) needsRewrite = true;
aprs_is.active = data["aprs_is"]["active"] | false;
aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV";
aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net";
@@ -238,20 +245,20 @@ bool Configuration::readFile() {
aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false;
aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false;
if (!data["beacon"].containsKey("latitude") ||
!data["beacon"].containsKey("longitude") ||
!data["beacon"].containsKey("comment") ||
!data["beacon"].containsKey("interval") ||
!data["beacon"].containsKey("overlay") ||
!data["beacon"].containsKey("symbol") ||
!data["beacon"].containsKey("path") ||
!data["beacon"].containsKey("sendViaAPRSIS") ||
!data["beacon"].containsKey("sendViaRF") ||
!data["beacon"].containsKey("beaconFreq") ||
!data["beacon"].containsKey("statusActive") ||
!data["beacon"].containsKey("statusPacket") ||
!data["beacon"].containsKey("gpsActive") ||
!data["beacon"].containsKey("ambiguityLevel")) needsRewrite = true;
if (data["beacon"]["latitude"].isNull() ||
data["beacon"]["longitude"].isNull() ||
data["beacon"]["comment"].isNull() ||
data["beacon"]["interval"].isNull() ||
data["beacon"]["overlay"].isNull() ||
data["beacon"]["symbol"].isNull() ||
data["beacon"]["path"].isNull() ||
data["beacon"]["sendViaAPRSIS"].isNull() ||
data["beacon"]["sendViaRF"].isNull() ||
data["beacon"]["beaconFreq"].isNull() ||
data["beacon"]["statusActive"].isNull() ||
data["beacon"]["statusPacket"].isNull() ||
data["beacon"]["gpsActive"].isNull() ||
data["beacon"]["ambiguityLevel"].isNull()) needsRewrite = true;
beacon.latitude = data["beacon"]["latitude"] | 0.0;
beacon.longitude = data["beacon"]["longitude"] | 0.0;
beacon.comment = data["beacon"]["comment"] | "LoRa APRS";
@@ -267,15 +274,15 @@ bool Configuration::readFile() {
beacon.gpsActive = data["beacon"]["gpsActive"] | false;
beacon.ambiguityLevel = data["beacon"]["ambiguityLevel"] | 0;
if (!data.containsKey("personalNote")) needsRewrite = true;
if (data["personalNote"].isNull()) needsRewrite = true;
personalNote = data["personalNote"] | "personal note here";
if (!data.containsKey("blacklist")) needsRewrite = true;
if (data["blacklist"].isNull()) needsRewrite = true;
blacklist = data["blacklist"] | "station callsign";
if (!data["digi"].containsKey("mode") ||
!data["digi"].containsKey("ecoMode") ||
!data["digi"].containsKey("backupDigiMode")) needsRewrite = true;
if (data["digi"]["mode"].isNull() ||
data["digi"]["ecoMode"].isNull() ||
data["digi"]["backupDigiMode"].isNull()) needsRewrite = true;
digi.mode = data["digi"]["mode"] | 0;
digi.ecoMode = data["digi"]["ecoMode"] | 0;
if (digi.ecoMode == 1) shouldSleepStop = false;
@@ -284,18 +291,18 @@ bool Configuration::readFile() {
#endif
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
if (!data["lora"].containsKey("rxActive") ||
!data["lora"].containsKey("rxFreq") ||
!data["lora"].containsKey("rxSpreadingFactor") ||
!data["lora"].containsKey("rxCodingRate4") ||
!data["lora"].containsKey("rxSignalBandwidth") ||
!data["lora"].containsKey("txActive") ||
!data["lora"].containsKey("txFreq") ||
!data["lora"].containsKey("txSpreadingFactor") ||
!data["lora"].containsKey("txCodingRate4") ||
!data["lora"].containsKey("txSignalBandwidth") ||
!data["lora"].containsKey("power")) needsRewrite = true;
if (data["lora"]["rxActive"].isNull() ||
data["lora"]["rxFreq"].isNull() ||
data["lora"]["rxSpreadingFactor"].isNull() ||
data["lora"]["rxCodingRate4"].isNull() ||
data["lora"]["rxSignalBandwidth"].isNull() ||
data["lora"]["txActive"].isNull() ||
data["lora"]["txFreq"].isNull() ||
data["lora"]["txSpreadingFactor"].isNull() ||
data["lora"]["txCodingRate4"].isNull() ||
data["lora"]["txSignalBandwidth"].isNull() ||
data["lora"]["power"].isNull() ||
data["lora"]["cadActive"].isNull()) needsRewrite = true;
loramodule.rxActive = data["lora"]["rxActive"] | true;
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
@@ -307,10 +314,11 @@ bool Configuration::readFile() {
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
loramodule.power = data["lora"]["power"] | 20;
loramodule.cadActive = data["lora"]["cadActive"] | true;
if (!data["display"].containsKey("alwaysOn") ||
!data["display"].containsKey("timeout") ||
!data["display"].containsKey("turn180")) needsRewrite = true;
if (data["display"]["alwaysOn"].isNull() ||
data["display"]["timeout"].isNull() ||
data["display"]["turn180"].isNull()) needsRewrite = true;
#ifdef HAS_EPAPER
display.alwaysOn = true;
#else
@@ -319,17 +327,17 @@ bool Configuration::readFile() {
display.timeout = data["display"]["timeout"] | 4;
display.turn180 = data["display"]["turn180"] | false;
if (!data["battery"].containsKey("sendInternalVoltage") ||
!data["battery"].containsKey("monitorInternalVoltage") ||
!data["battery"].containsKey("internalSleepVoltage") ||
!data["battery"].containsKey("sendExternalVoltage") ||
!data["battery"].containsKey("monitorExternalVoltage") ||
!data["battery"].containsKey("externalSleepVoltage") ||
!data["battery"].containsKey("useExternalI2CSensor") ||
!data["battery"].containsKey("voltageDividerR1") ||
!data["battery"].containsKey("voltageDividerR2") ||
!data["battery"].containsKey("externalVoltagePin") ||
!data["battery"].containsKey("sendVoltageAsTelemetry")) needsRewrite = true;
if (data["battery"]["sendInternalVoltage"].isNull() ||
data["battery"]["monitorInternalVoltage"].isNull() ||
data["battery"]["internalSleepVoltage"].isNull() ||
data["battery"]["sendExternalVoltage"].isNull() ||
data["battery"]["monitorExternalVoltage"].isNull() ||
data["battery"]["externalSleepVoltage"].isNull() ||
data["battery"]["useExternalI2CSensor"].isNull() ||
data["battery"]["voltageDividerR1"].isNull() ||
data["battery"]["voltageDividerR2"].isNull() ||
data["battery"]["externalVoltagePin"].isNull() ||
data["battery"]["sendVoltageAsTelemetry"].isNull()) needsRewrite = true;
battery.sendInternalVoltage = data["battery"]["sendInternalVoltage"] | false;
battery.monitorInternalVoltage = data["battery"]["monitorInternalVoltage"] | false;
battery.internalSleepVoltage = data["battery"]["internalSleepVoltage"] | 2.9;
@@ -342,38 +350,38 @@ bool Configuration::readFile() {
battery.externalVoltagePin = data["battery"]["externalVoltagePin"] | 34;
battery.sendVoltageAsTelemetry = data["battery"]["sendVoltageAsTelemetry"] | false;
if (!data["wxsensor"].containsKey("active") ||
!data["wxsensor"].containsKey("heightCorrection") ||
!data["wxsensor"].containsKey("temperatureCorrection")) needsRewrite = true;
if (data["wxsensor"]["active"].isNull() ||
data["wxsensor"]["heightCorrection"].isNull() ||
data["wxsensor"]["temperatureCorrection"].isNull()) needsRewrite = true;
wxsensor.active = data["wxsensor"]["active"] | false;
wxsensor.heightCorrection = data["wxsensor"]["heightCorrection"] | 0;
wxsensor.temperatureCorrection = data["wxsensor"]["temperatureCorrection"] | 0.0;
if (!data["syslog"].containsKey("active") ||
!data["syslog"].containsKey("server") ||
!data["syslog"].containsKey("port") ||
!data["syslog"].containsKey("logBeaconOverTCPIP")) needsRewrite = true;
if (data["syslog"]["active"].isNull() ||
data["syslog"]["server"].isNull() ||
data["syslog"]["port"].isNull() ||
data["syslog"]["logBeaconOverTCPIP"].isNull()) needsRewrite = true;
syslog.active = data["syslog"]["active"] | false;
syslog.server = data["syslog"]["server"] | "lora.link9.net";
syslog.port = data["syslog"]["port"] | 1514;
syslog.logBeaconOverTCPIP = data["syslog"]["logBeaconOverTCPIP"] | false;
if (!data["tnc"].containsKey("enableServer") ||
!data["tnc"].containsKey("enableSerial") ||
!data["tnc"].containsKey("acceptOwn") ||
!data["tnc"].containsKey("aprsBridgeActive")) needsRewrite = true;
if (data["tnc"]["enableServer"].isNull() ||
data["tnc"]["enableSerial"].isNull() ||
data["tnc"]["acceptOwn"].isNull() ||
data["tnc"]["aprsBridgeActive"].isNull()) needsRewrite = true;
tnc.enableServer = data["tnc"]["enableServer"] | false;
tnc.enableSerial = data["tnc"]["enableSerial"] | false;
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
if (!data["mqtt"].containsKey("active") ||
!data["mqtt"].containsKey("server") ||
!data["mqtt"].containsKey("topic") ||
!data["mqtt"].containsKey("username") ||
!data["mqtt"].containsKey("password") ||
!data["mqtt"].containsKey("port") ||
!data["mqtt"].containsKey("beaconOverMqtt")) needsRewrite = true;
if (data["mqtt"]["active"].isNull() ||
data["mqtt"]["server"].isNull() ||
data["mqtt"]["topic"].isNull() ||
data["mqtt"]["username"].isNull() ||
data["mqtt"]["password"].isNull() ||
data["mqtt"]["port"].isNull() ||
data["mqtt"]["beaconOverMqtt"].isNull()) needsRewrite = true;
mqtt.active = data["mqtt"]["active"] | false;
mqtt.server = data["mqtt"]["server"] | "";
mqtt.topic = data["mqtt"]["topic"] | "aprs-igate";
@@ -382,34 +390,34 @@ bool Configuration::readFile() {
mqtt.port = data["mqtt"]["port"] | 1883;
mqtt.beaconOverMqtt = data["mqtt"]["beaconOverMqtt"] | false;
if (!data["ota"].containsKey("username") ||
!data["ota"].containsKey("password")) needsRewrite = true;
if (data["ota"]["username"].isNull() ||
data["ota"]["password"].isNull()) needsRewrite = true;
ota.username = data["ota"]["username"] | "";
ota.password = data["ota"]["password"] | "";
if (!data["webadmin"].containsKey("active") ||
!data["webadmin"].containsKey("username") ||
!data["webadmin"].containsKey("password")) needsRewrite = true;
if (data["webadmin"]["active"].isNull() ||
data["webadmin"]["username"].isNull() ||
data["webadmin"]["password"].isNull()) needsRewrite = true;
webadmin.active = data["webadmin"]["active"] | false;
webadmin.username = data["webadmin"]["username"] | "admin";
webadmin.password = data["webadmin"]["password"] | "";
if (!data["remoteManagement"].containsKey("managers") ||
!data["remoteManagement"].containsKey("rfOnly")) needsRewrite = true;
if (data["remoteManagement"]["managers"].isNull() ||
data["remoteManagement"]["rfOnly"].isNull()) needsRewrite = true;
remoteManagement.managers = data["remoteManagement"]["managers"] | "";
remoteManagement.rfOnly = data["remoteManagement"]["rfOnly"] | true;
if (!data["ntp"].containsKey("server") ||
!data["ntp"].containsKey("gmtCorrection")) needsRewrite = true;
if (data["ntp"]["server"].isNull() ||
data["ntp"]["gmtCorrection"].isNull()) needsRewrite = true;
ntp.server = data["ntp"]["server"] | "pool.ntp.org";
ntp.gmtCorrection = data["ntp"]["gmtCorrection"] | 0.0;
if (!data["other"].containsKey("rebootMode") ||
!data["other"].containsKey("rebootModeTime")) needsRewrite = true;
if (data["other"]["rebootMode"].isNull() ||
data["other"]["rebootModeTime"].isNull()) needsRewrite = true;
rebootMode = data["other"]["rebootMode"] | false;
rebootModeTime = data["other"]["rebootModeTime"] | 6;
if (!data["other"].containsKey("rememberStationTime")) needsRewrite = true;
if (data["other"]["rememberStationTime"].isNull()) needsRewrite = true;
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
@@ -445,6 +453,7 @@ void Configuration::setDefaultValues() {
startupDelay = 0;
wifiAutoAP.enabled = true;
wifiAutoAP.password = "1234567890";
wifiAutoAP.timeout = 10;
@@ -496,6 +505,7 @@ void Configuration::setDefaultValues() {
loramodule.txCodingRate4 = 5;
loramodule.txSignalBandwidth = 125000;
loramodule.power = 20;
loramodule.cadActive = true;
display.alwaysOn = true;
display.timeout = 4;
@@ -558,7 +568,7 @@ void Configuration::setDefaultValues() {
Serial.println("New Data Created... All is Written!");
}
Configuration::Configuration() {
void Configuration::setup() {
if (!SPIFFS.begin(false)) {
Serial.println("SPIFFS Mount Failed");
return;
-1
View File
@@ -23,7 +23,6 @@
#include "digi_utils.h"
#include "wifi_utils.h"
#include "lora_utils.h"
#include "gps_utils.h"
#include "display.h"
#include "utils.h"
+21 -10
View File
@@ -57,7 +57,7 @@
String lastEpaperText;
#else
#include <Adafruit_GFX.h>
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
#include <Adafruit_SH110X.h>
Adafruit_SH1106G display(128, 64, &Wire, OLED_RST);
#else
@@ -65,7 +65,12 @@
#ifdef HELTEC_WSL_V3_DISPLAY
Adafruit_SSD1306 display(128, 64, &Wire1, OLED_RST);
#else
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
#if defined RPC_LORA_DIGIGATE_1W
#define SCREEN_HEIGHT 32
#else
#define SCREEN_HEIGHT 64
#endif
Adafruit_SSD1306 display(128, SCREEN_HEIGHT, &Wire, OLED_RST);
#endif
#endif
#endif
@@ -75,6 +80,7 @@
extern Configuration Config;
bool displayFound = false;
int maxLines;
void displaySetup() {
#ifdef HAS_DISPLAY
@@ -97,6 +103,11 @@ void displaySetup() {
sprite.createSprite(160, 80);
#endif
#else
#if (SCREEN_HEIGHT == 64)
maxLines = 6;
#elif (SCREEN_HEIGHT == 32)
maxLines = 3;
#endif
#ifdef HAS_EPAPER
display.landscape();
display.printCenter("LoRa APRS iGate Initialising...");
@@ -117,7 +128,7 @@ void displaySetup() {
digitalWrite(OLED_RST, HIGH);
#endif
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
if (display.begin(0x3c, false)) {
displayFound = true;
if (Config.display.turn180) display.setRotation(2);
@@ -157,7 +168,7 @@ void displayToggle(bool toggle) {
display.printCenter("EPAPER Display Disabled by toggle...");
display.update();
#else
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
if (displayFound) display.oled_command(SH110X_DISPLAYON);
#else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYON);
@@ -171,7 +182,7 @@ void displayToggle(bool toggle) {
#ifdef HAS_EPAPER
display.update();
#else
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
if (displayFound) display.oled_command(SH110X_DISPLAYOFF);
#else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYOFF);
@@ -222,7 +233,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else
if (displayFound) {
display.clearDisplay();
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
display.setTextColor(SH110X_WHITE);
#else
display.setTextColor(WHITE);
@@ -234,7 +245,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 8 + (8 * i));
display.println(*lines[i]);
}
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
display.setContrast(1);
#else
display.ssd1306_command(SSD1306_SETCONTRAST);
@@ -288,7 +299,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else
if (displayFound) {
display.clearDisplay();
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
display.setTextColor(SH110X_WHITE);
#else
display.setTextColor(WHITE);
@@ -297,11 +308,11 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 0);
display.println(header);
display.setTextSize(1);
for (int i = 0; i < 6; i++) {
for (int i = 0; i < maxLines; i++) {
display.setCursor(0, 16 + (8 * i));
display.println(*lines[i]);
}
#if defined(TTGO_T_Beam_S3_SUPREME_V3)
#ifdef HAS_SH1106
display.setContrast(1);
#else
display.ssd1306_command(SSD1306_SETCONTRAST);
+7 -16
View File
@@ -29,29 +29,20 @@ bool validateKISSFrame(const String& kissFormattedFrame) {
}
String encodeAddressAX25(String tnc2Address) {
bool hasBeenDigipited = tnc2Address.indexOf('*') != -1;
int tnc2AddressIndex = tnc2Address.indexOf('-');
if (tnc2AddressIndex == -1) {
if (hasBeenDigipited) {
tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
}
bool hasBeenDigipited = tnc2Address.indexOf('*') != -1;
if (tnc2Address.indexOf('-') == -1) {
if (hasBeenDigipited) tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
tnc2Address += "-0";
}
int separatorIndex = tnc2AddressIndex;
int ssid = tnc2Address.substring(separatorIndex + 1).toInt();
int separatorIndex = tnc2Address.indexOf('-');;
int ssid = tnc2Address.substring(separatorIndex + 1).toInt();
String kissAddress = "";
for (int i = 0; i < 6; ++i) {
char addressChar;
if (tnc2Address.length() > i && i < separatorIndex) {
addressChar = tnc2Address.charAt(i);
} else {
addressChar = ' ';
}
char addressChar = ' ';
if (tnc2Address.length() > i && i < separatorIndex) addressChar = tnc2Address.charAt(i);
kissAddress += (char)(addressChar << 1);
}
kissAddress += (char)((ssid << 1) | 0b01100000 | (hasBeenDigipited ? HAS_BEEN_DIGIPITED_MASK : 0));
return kissAddress;
}
+51 -8
View File
@@ -16,26 +16,31 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <APRSPacketLib.h>
#include <RadioLib.h>
#include <WiFi.h>
#include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h"
#include "station_utils.h"
#include "board_pinout.h"
#include "syslog_utils.h"
#include "map_utils.h"
#include "ntp_utils.h"
#include "display.h"
#include "utils.h"
extern Configuration Config;
extern uint32_t lastRxTime;
extern NetworkManager *networkManager;
extern bool packetIsBeacon;
extern std::vector<ReceivedPacket> receivedPackets;
bool operationDone = true;
bool transmitFlag = true;
bool operationDone = 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
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
@@ -166,6 +171,34 @@ namespace LoRa_Utils {
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) {
if (!Config.loramodule.txActive) return;
@@ -178,10 +211,16 @@ namespace LoRa_Utils {
#ifdef INTERNAL_LED_PIN
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
#endif
if (Config.loramodule.cadActive) {
waitForDIFS(); // DIFS (Distributed Inter-Frame Space)
doBEB(); // BEB (Binary Exponential Backoff)
}
int state = radio.transmit("\x3c\xff\x01" + newPacket);
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE) {
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
}
Utils::print("---> LoRa Packet Tx : ");
@@ -241,15 +280,19 @@ namespace LoRa_Utils {
receivedPacket.RSSI = rssi;
receivedPacket.SNR = snr;
receivedPackets.push_back(receivedPacket);
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (los que traen posición)
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.rssi, aprsPacket.snr);
}
}
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
}
} else {
packet = "";
}
lastRxTime = millis();
return packet;
}
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
@@ -257,7 +300,7 @@ namespace LoRa_Utils {
snr = radio.getSNR();
freqError = radio.getFrequencyError();
Utils::println(F("CRC error!"));
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
}
packet = "";
+114
View File
@@ -0,0 +1,114 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <ArduinoJson.h>
#include "map_utils.h"
#include "ntp_utils.h"
#define MAX_MAP_STATIONS 50
#define STATION_TTL_MS 3600000UL // 1 hora (en milisegundos)
std::vector<MapStation> mapStations;
namespace MAP_Utils {
// Copia la hora NTP "HH:MM:SS" si es valida; si no hay red (NTP devuelve
// un texto de fallback) deja el campo vacio para no guardar hora falsa.
void writeFormatedTime(char* dst, size_t size) {
String t = NTP_Utils::getFormatedTime();
if (t.length() == 8 && t.charAt(2) == ':') {
t.toCharArray(dst, size);
} else {
dst[0] = '\0';
}
}
// Inserta una estacion nueva o actualiza la existente (dedup por callsign).
// Llamar solo cuando el parser ya decodifico una posicion valida.
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr) {
for (auto &s : mapStations) { // 1) ya existe -> actualizar
if (callsign.equals(s.callsign)) {
s.latitude = latitude;
s.longitude = longitude;
s.rssi = rssi;
s.snr = snr;
s.count++;
symbol.toCharArray(s.symbol, sizeof(s.symbol));
writeFormatedTime(s.lastHeard, sizeof(s.lastHeard));
s.lastHeardMillis = millis();
return;
}
}
if (mapStations.size() >= MAX_MAP_STATIONS) { // 2) llena -> descartar la mas vieja
size_t oldest = 0;
for (size_t i = 1; i < mapStations.size(); i++) {
if (mapStations[i].lastHeardMillis < mapStations[oldest].lastHeardMillis) {
oldest = i;
}
}
mapStations.erase(mapStations.begin() + oldest);
}
MapStation st = {}; // 3) insertar nueva
callsign.toCharArray(st.callsign, sizeof(st.callsign));
symbol.toCharArray(st.symbol, sizeof(st.symbol));
st.latitude = latitude;
st.longitude = longitude;
st.rssi = rssi;
st.snr = snr;
st.count = 1;
writeFormatedTime(st.lastHeard, sizeof(st.lastHeard));
st.lastHeardMillis = millis();
mapStations.push_back(st);
}
void purgeOldStations() {
uint32_t now = millis();
for (int i = (int)mapStations.size() - 1; i >= 0; i--) {
if (now - mapStations[i].lastHeardMillis > STATION_TTL_MS) {
mapStations.erase(mapStations.begin() + i);
}
}
}
String getStationsJson() {
purgeOldStations();
JsonDocument data;
for (size_t i = 0; i < mapStations.size(); i++) {
data[i]["callsign"] = mapStations[i].callsign;
data[i]["lat"] = mapStations[i].latitude;
data[i]["lon"] = mapStations[i].longitude;
data[i]["symbol"] = mapStations[i].symbol;
data[i]["RSSI"] = mapStations[i].rssi;
data[i]["SNR"] = mapStations[i].snr;
data[i]["count"] = mapStations[i].count;
data[i]["lastHeard"] = mapStations[i].lastHeard;
}
String buffer;
serializeJson(data, buffer);
return buffer;
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <WiFiClientSecure.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include "configuration.h"
#include "station_utils.h"
+333
View File
@@ -0,0 +1,333 @@
#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;
}
+22 -8
View File
@@ -18,35 +18,49 @@
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <WiFi.h>
#include "configuration.h"
#include "network_manager.h"
#include "ntp_utils.h"
#include "time.h"
extern Configuration Config;
extern NetworkManager *networkManager;
WiFiUDP ntpUDP;
NTPClient* timeClient;
NTPClient* timeClient = nullptr;
namespace NTP_Utils {
void setup() {
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
bool setup() {
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
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->begin();
return true;
}
return false;
}
void update() {
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") timeClient->update();
if (!networkManager->isConnected() || Config.digi.ecoMode != 0 || Config.callsign == "NOCALL-10") {
return;
}
if (timeClient == nullptr) {
if (!setup()) {
return;
}
}
timeClient->update();
}
String getFormatedTime() {
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0) return timeClient->getFormattedTime();
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && timeClient != nullptr) {
return timeClient->getFormattedTime();
}
return "DigiEcoMode Active";
}
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ namespace OTA_Utils {
Serial.println(success ? "OTA update finished successfully!" : "There was an error during OTA update!");
displayShow("", "", statusMessage, "", rebootMessage, "", "", 4000);
isUpdatingOTA = false;
if (!success) isUpdatingOTA = false;
}
}
+4 -4
View File
@@ -263,8 +263,8 @@ namespace POWER_Utils {
pinMode(Config.battery.externalVoltagePin, INPUT);
}
#ifdef VEXT_CTRL
pinMode(VEXT_CTRL,OUTPUT); // GPS + TFT on HELTEC Wireless_Tracker and only for Oled in HELTEC V3
#ifdef VEXT_CTRL_PIN
pinMode(VEXT_CTRL_PIN,OUTPUT); // GPS + TFT on HELTEC Wireless_Tracker and only for Oled in HELTEC V3
vext_ctrl_ON();
#endif
@@ -272,8 +272,8 @@ namespace POWER_Utils {
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) activateGPS();
#endif
#ifdef ADC_CTRL
pinMode(ADC_CTRL, OUTPUT);
#ifdef ADC_CTRL_PIN
pinMode(ADC_CTRL_PIN, OUTPUT);
adc_ctrl_OFF();
#endif
+1 -9
View File
@@ -25,15 +25,11 @@
#include "utils.h"
#include <vector>
#define SECS_TO_WAIT 3 // soon to be deleted...
extern Configuration Config;
extern uint32_t lastRxTime;
extern String fourthLine;
extern bool shouldSleepLowVoltage;
uint32_t lastTxTime = millis();
std::vector<LastHeardStation> lastHeardStations;
std::vector<String> blacklist;
std::vector<String> managers;
@@ -214,15 +210,11 @@ namespace STATION_Utils {
}
void processOutputPacketBuffer() {
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.size() > 0) {
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
outputPacketBuffer.erase(outputPacketBuffer.begin());
lastTxTime = millis();
}
if (shouldSleepLowVoltage) {
while (outputPacketBuffer.size() > 0) {
+4 -3
View File
@@ -17,13 +17,14 @@
*/
#include <WiFiUdp.h>
#include <WiFi.h>
#include "configuration.h"
#include "network_manager.h"
#include "syslog_utils.h"
#include "gps_utils.h"
extern Configuration Config;
extern NetworkManager *networkManager;
extern String versionDate;
extern String versionNumber;
@@ -33,7 +34,7 @@ WiFiUDP udpClient;
namespace SYSLOG_Utils {
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) {
if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = "<165>1 - ";
syslogPacket.concat(Config.callsign);
syslogPacket.concat(" CA2RXU_LoRa_iGate_");
@@ -140,7 +141,7 @@ namespace SYSLOG_Utils {
}
void setup() {
if (WiFi.status() == WL_CONNECTED) {
if (networkManager->isConnected()) {
udpClient.begin(0);
if (Config.syslog.active) Serial.println("init : Syslog Server ... done! (at " + Config.syslog.server + ")");
}
+39 -27
View File
@@ -18,9 +18,9 @@
#include <APRSPacketLib.h>
#include <TinyGPS++.h>
#include <WiFi.h>
#include "telemetry_utils.h"
#include "configuration.h"
#include "network_manager.h"
#include "station_utils.h"
#include "battery_utils.h"
#include "aprs_is_utils.h"
@@ -37,6 +37,7 @@
#define DAY_MS (24UL * 60UL * 60UL * 1000UL)
extern Configuration Config;
extern NetworkManager *networkManager;
extern TinyGPSPlus gps;
extern String versionDate;
extern String firstLine;
@@ -52,7 +53,6 @@ extern int rssi;
extern float snr;
extern int freqError;
extern String distance;
extern bool WiFiConnected;
extern int wxModuleType;
extern bool backupDigiMode;
extern bool shouldSleepLowVoltage;
@@ -62,8 +62,7 @@ extern bool sendEUP; // Equations Units Parameters
extern std::vector<LastHeardStation> lastHeardStations;
bool statusAfterBoot = true;
bool statusUpdate = true;
bool beaconUpdate = false;
uint32_t lastBeaconTx = 0;
uint32_t lastScreenOn = millis();
@@ -76,37 +75,43 @@ String secondaryBeaconPacket;
namespace Utils {
void processStatus() {
String status = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
bool sendOverAPRSIS = Config.beacon.sendViaAPRSIS && Config.aprs_is.active && networkManager->isConnected();
bool sendOverRF = !Config.beacon.sendViaAPRSIS && Config.beacon.sendViaRF;
if (WiFi.status() == WL_CONNECTED && Config.aprs_is.active && Config.beacon.sendViaAPRSIS) {
delay(1000);
status.concat(",qAC:>");
status.concat(Config.beacon.statusPacket);
APRS_IS_Utils::upload(status);
SYSLOG_Utils::log(2, status, 0, 0.0, 0); // APRSIS TX
if (!sendOverAPRSIS && !sendOverRF) {
statusUpdate = false;
return;
}
if (statusAfterBoot && !Config.beacon.sendViaAPRSIS && Config.beacon.sendViaRF) {
status.concat(":>");
status.concat(Config.beacon.statusPacket);
STATION_Utils::addToOutputPacketBuffer(status, true); // treated also as beacon on Tx Freq
String statusPacket = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
statusPacket += sendOverAPRSIS ? ",qAC:>" : ":>";
statusPacket += Config.beacon.statusPacket;
if (sendOverAPRSIS) {
APRS_IS_Utils::upload(statusPacket);
SYSLOG_Utils::log(2, statusPacket, 0, 0.0, 0); // APRSIS TX
} else {
STATION_Utils::addToOutputPacketBuffer(statusPacket, true); // treated also as beacon on Tx Freq
}
statusAfterBoot = false;
statusUpdate = false;
lastStatusTx = millis();
}
void checkStatusInterval() {
if (lastStatusTx == 0 || millis() - lastStatusTx > DAY_MS) statusAfterBoot = true;
if (lastStatusTx == 0 || millis() - lastStatusTx > DAY_MS) statusUpdate = true;
}
String getLocalIP() {
if (Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
return "** WiFi AP Killed **";
} else if (!WiFiConnected) {
return "IP : 192.168.4.1";
} else if (networkManager->isEthernetConnected()) {
return "LAN: " + networkManager->getEthernetIP().toString();
} else if (!networkManager->isWiFiConnected() && networkManager->isWifiAPActive()) {
return "IP : " + networkManager->getWiFiAPIP().toString();
} else if (backupDigiMode) {
return "- BACKUP DIGI MODE -";
} else {
return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]);
return "IP : " + networkManager->getWiFiIP().toString();
}
}
@@ -148,11 +153,18 @@ namespace Utils {
beaconUpdate = true;
}
bool configLocationIsValid = !(Config.beacon.latitude == 0.0 && Config.beacon.longitude == 0.0);
#ifdef HAS_GPS
if (Config.beacon.gpsActive && gps.location.lat() == 0.0 && gps.location.lng() == 0.0 && Config.beacon.latitude == 0.0 && Config.beacon.longitude == 0.0) {
GPS_Utils::getData();
beaconUpdate = false;
if (Config.beacon.gpsActive) { // GPS activated
if (!gps.location.isValid()) {
GPS_Utils::getData(); // refresh GPS
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
if (beaconUpdate) {
@@ -292,7 +304,7 @@ namespace Utils {
}
checkStatusInterval();
if (statusAfterBoot && Config.beacon.statusActive && !Config.beacon.statusPacket.isEmpty()) processStatus();
if (statusUpdate && Config.beacon.statusActive && !Config.beacon.statusPacket.isEmpty()) processStatus();
}
void checkDisplayInterval() {
@@ -405,9 +417,9 @@ namespace Utils {
if (mode == 1) { // low voltage detected after a while
displayToggle(false);
}
#ifdef VEXT_CTRL
#ifdef VEXT_CTRL_PIN
#ifndef HELTEC_WSL_V3
digitalWrite(VEXT_CTRL, LOW);
digitalWrite(VEXT_CTRL_PIN, LOW);
#endif
#endif
LoRa_Utils::sleepRadio();
@@ -478,4 +490,4 @@ namespace Utils {
}
}
}
}
+10 -2
View File
@@ -20,6 +20,7 @@
#include "configuration.h"
#include "ota_utils.h"
#include "web_utils.h"
#include "map_utils.h"
#include "display.h"
#include "utils.h"
@@ -98,7 +99,7 @@ namespace WEB_Utils {
}
void handleReceivedPackets(AsyncWebServerRequest *request) {
StaticJsonDocument<1536> data;
JsonDocument data;
for (int i = 0; i < receivedPackets.size(); i++) {
data[i]["rxTime"] = receivedPackets[i].rxTime;
@@ -114,6 +115,10 @@ namespace WEB_Utils {
request->send(200, "application/json", buffer);
}
void handleStations(AsyncWebServerRequest *request) {
request->send(200, "application/json", MAP_Utils::getStationsJson());
}
void handleWriteConfiguration(AsyncWebServerRequest *request) {
Serial.println("Got new Configuration Data from www");
@@ -161,7 +166,7 @@ namespace WEB_Utils {
Config.callsign = getParamStringSafe("callsign", Config.callsign);
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.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout);
@@ -213,6 +218,8 @@ namespace WEB_Utils {
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
if (Config.loramodule.txActive)
Config.loramodule.cadActive = request->hasParam("lora.cadActive", true);
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
if (!Config.display.alwaysOn) {
@@ -361,6 +368,7 @@ namespace WEB_Utils {
server.on("/", HTTP_GET, handleHome);
server.on("/status", HTTP_GET, handleStatus);
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
server.on("/stations.json", HTTP_GET, handleStations);
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
server.on("/action", HTTP_POST, handleAction);
+42 -93
View File
@@ -18,6 +18,7 @@
#include <WiFi.h>
#include "configuration.h"
#include "network_manager.h"
#include "board_pinout.h"
#include "wifi_utils.h"
#include "display.h"
@@ -25,16 +26,11 @@
extern Configuration Config;
extern NetworkManager *networkManager;
extern uint8_t myWiFiAPIndex;
extern int myWiFiAPSize;
extern WiFi_AP *currentWiFi;
extern bool backupDigiMode;
extern uint32_t lastServerCheck;
bool WiFiConnected = false;
uint32_t WiFiAutoAPTime = millis();
bool WiFiAutoAPStarted = false;
uint8_t wifiCounter = 0;
uint32_t lastBackupDigiTime = millis();
uint32_t lastWiFiCheck = 0;
@@ -44,32 +40,35 @@ namespace WIFI_Utils {
void checkWiFi() {
if (Config.digi.ecoMode != 0) return;
if (!networkManager->hasWiFiNetworks()) {
return;
}
uint32_t currentTime = millis();
if (backupDigiMode) {
if (WiFi.status() != WL_CONNECTED && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) {
if (!networkManager->isWiFiConnected() && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) {
Serial.println("*** Stopping BackUp Digi Mode ***");
backupDigiMode = false;
wifiCounter = 0;
} else if (WiFi.status() == WL_CONNECTED) {
} else if (networkManager->isWiFiConnected()) {
Serial.println("*** WiFi Reconnect Success (Stopping Backup Digi Mode) ***");
backupDigiMode = false;
wifiCounter = 0;
}
}
if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !WiFiAutoAPStarted) {
if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !networkManager->isWifiAPActive()) {
lastWiFiCheck = currentTime;
if (WiFi.status() == WL_CONNECTED) {
if (networkManager->isWiFiConnected()) {
if (Config.digi.backupDigiMode && (currentTime - lastServerCheck > 30 * 1000)) {
Serial.println("*** Server Connection LOST → Backup Digi Mode ***");
backupDigiMode = true;
WiFi.disconnect();
lastBackupDigiTime = currentTime;
}
} else {
Serial.println("Reconnecting to WiFi...");
WiFi.disconnect();
WIFI_Utils::startWiFi();
if (Config.digi.backupDigiMode) wifiCounter++;
@@ -83,97 +82,47 @@ namespace WIFI_Utils {
}
void startAutoAP() {
WiFi.mode(WIFI_MODE_NULL);
WiFi.mode(WIFI_AP);
WiFi.softAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
WiFiAutoAPTime = millis();
WiFiAutoAPStarted = true;
displayShow("", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
networkManager->setupAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
}
void startWiFi() {
bool startAP = false;
if (currentWiFi->ssid == "") {
startAP = true;
} else {
uint8_t wifiCounter = 0;
String hostName = "iGATE-" + Config.callsign;
WiFi.setHostname(hostName.c_str());
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(500);
unsigned long start = millis();
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());
}
}
networkManager->clearWiFiNetworks();
for (size_t i = 0; i < Config.wifiAPs.size(); i++) {
const WiFi_AP& wifiAP = Config.wifiAPs[i];
if (wifiAP.ssid.isEmpty()) continue;
networkManager->addWiFiNetwork(wifiAP.ssid, wifiAP.password);
}
if (!networkManager->hasWiFiNetworks()) {
Serial.println("WiFi SSID not set!");
if (Config.wifiAutoAP.enabled) {
Serial.println("Starting AP fallback...");
startAutoAP();
}
return;
}
displayShow("", "Connecting to WiFi:", "", " loading ...", 0);
networkManager->connectWiFi();
#ifdef INTERNAL_LED_PIN
digitalWrite(INTERNAL_LED_PIN, LOW);
#endif
if (WiFi.status() == WL_CONNECTED) {
Serial.print("\nConnected as ");
Serial.print(WiFi.localIP());
if (networkManager->isWiFiConnected()) {
Serial.print("[WiFi] Connected as ");
Serial.print(networkManager->getWiFiIP());
Serial.print(" / MAC Address: ");
Serial.println(WiFi.macAddress());
Serial.println(networkManager->getWiFimacAddress());
displayShow("", " Connected!!", "" , " loading ...", 1000);
} else {
startAP = true;
Serial.println("\nNot connected to WiFi! Starting Auto AP");
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
}
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;
Serial.println("[WiFi] Not connected to WiFi!");
if (Config.wifiAutoAP.enabled) {
Serial.println("Starting AP fallback...");
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
startAutoAP();
} else {
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)");
}
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
}
}
}
@@ -183,4 +132,4 @@ namespace WIFI_Utils {
btStop();
}
}
}
@@ -0,0 +1,63 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_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
#define HAS_SH1106
#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 17
#define GPS_TX 16
// Aditional Config
#define INTERNAL_LED_PIN 2
#define BATTERY_PIN 35
#endif
@@ -0,0 +1,12 @@
[env:ESP32_9M2IBR_1W_LoRa_GPS]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_9M2IBR_1W_LoRa_GPS
lib_deps =
${common.lib_deps}
${common.display_libs}
adafruit/Adafruit SH110X @ 2.1.10
@@ -0,0 +1,56 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_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
@@ -0,0 +1,11 @@
[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 -1
View File
@@ -49,7 +49,7 @@
#define INTERNAL_LED_PIN 25
#define BATTERY_PIN 37
#define ADC_CTRL 21
#define ADC_CTRL_PIN 21
#define ADC_CTRL_ON_STATE LOW
#endif
@@ -0,0 +1,58 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_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
@@ -0,0 +1,11 @@
[env:ttgo-lora32-v21_915_GPS]
board = ttgo-lora32-v21
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D TTGO_LORA32_V2_1_915_GPS
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -0,0 +1,58 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_SX1278
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 19
#define RADIO_MOSI_PIN 27
#define RADIO_CS_PIN 18
#define RADIO_RST_PIN 14
#define RADIO_BUSY_PIN 26
#define RADIO_WAKEUP_PIN RADIO_BUSY_PIN
#define GPIO_WAKEUP_PIN GPIO_SEL_26
// 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
@@ -0,0 +1,11 @@
[env:ttgo-lora32-v21_GPS]
board = ttgo-lora32-v21
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D TTGO_LORA32_V2_1_GPS
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -0,0 +1,55 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_SX1262
#define HAS_TCXO
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 19
#define RADIO_MOSI_PIN 27
#define RADIO_CS_PIN 18
#define RADIO_DIO0_PIN 26
#define RADIO_RST_PIN 23
#define RADIO_DIO1_PIN 33
#define RADIO_BUSY_PIN 32
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
#define GPIO_WAKEUP_PIN GPIO_SEL_33
// 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 HAS_AXP192
// GPS
#define HAS_GPS
#define GPS_RX 12
#define GPS_TX 34
#endif
@@ -0,0 +1,12 @@
[env:ttgo-t-beam-v1_SX1262]
board = ttgo-t-beam
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D TTGO_T_BEAM_V1_0_SX1262
lib_deps =
${common.lib_deps}
${common.display_libs}
lewisxhe/XPowersLib @ 0.2.4
@@ -35,6 +35,7 @@
// Display
#define HAS_DISPLAY
#define HAS_SH1106
#undef OLED_SDA
#undef OLED_SCL