Compare commits

...

61 Commits

Author SHA1 Message Date
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
Ricardo Guzman (Richonguzman)
db0da96d7f ReadMe update 2026-02-25 10:45:49 -03:00
Ricardo Guzman (Richonguzman)
bf6a0faa90 killing blank spaces 2026-02-24 23:33:58 -03:00
Ricardo Guzman (Richonguzman)
61cf118a3b cleaning Headers 2026-02-24 23:27:36 -03:00
Ricardo Guzman (Richonguzman)
44719083a6 new callsignIsValid update 2026-02-24 21:26:20 -03:00
Ricardo Guzman (Richonguzman)
808d740477 update indexOf kill 2026-02-24 19:29:20 -03:00
Ricardo Guzman (Richonguzman)
23c8257c80 date update 2026-02-24 07:55:53 -03:00
Ricardo Guzman (Richonguzman)
db4582db13 telemetry EUP beacon update 2026-02-24 07:55:32 -03:00
Ricardo Guzman (Richonguzman)
09e06d36e8 checkCallsignList update 2026-02-24 06:50:27 -03:00
Ricardo Guzman (Richonguzman)
e6d44c1b7f replaced string for hash in 25segBuffer 2026-02-23 18:59:27 -03:00
Ricardo Guzman (Richonguzman)
d4fc99466f update digi2000 2026-02-23 18:07:26 -03:00
Ricardo Guzman (Richonguzman)
41f09af7b5 digipeater logic improved 2026-02-23 17:32:52 -03:00
54 changed files with 1256 additions and 712 deletions

View File

@@ -19,6 +19,8 @@ jobs:
chip: esp32s3 chip: esp32s3
- name: heltec-lora32-v2 - name: heltec-lora32-v2
chip: esp32 chip: esp32
- name: heltec-lora32-v2_915
chip: esp32
- name: heltec_wifi_lora_32_V3 - name: heltec_wifi_lora_32_V3
chip: esp32s3 chip: esp32s3
- name: heltec_wifi_lora_32_V3_2 - name: heltec_wifi_lora_32_V3_2
@@ -96,7 +98,9 @@ jobs:
- name: XIAO_ESP32S3_WIO_SX1262 - name: XIAO_ESP32S3_WIO_SX1262
chip: esp32s3 chip: esp32s3
- name: TROY_LoRa_APRS - name: TROY_LoRa_APRS
chip: esp32 chip: esp32
- name: ESP32_9M2IBR_1W_LoRa_GPS
chip: esp32
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -51,6 +51,11 @@ ____________________________________________________
<br /> <br />
# Timeline (Versions): # Timeline (Versions):
- 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.
- 2026-02-05 Improved "digiBackupMode" to check "APRS-IS" server connection.
- 2026-01-07 Tactical Callsign added. - 2026-01-07 Tactical Callsign added.
- 2026-01-05 Heltec V4 support added. - 2026-01-05 Heltec V4 support added.
- 2025-12-22 Heltec Wireless Paper V1.2 and VisionMaster E290 Added. Thanks HA5SZI. - 2025-12-22 Heltec Wireless Paper V1.2 and VisionMaster E290 Added. Thanks HA5SZI.
@@ -85,7 +90,7 @@ ____________________________________________________
- 2024.10.08 New EcoMode for Remote Digipeaters without WiFi/WiFiAP, Screen, Leds (Example: LILYGO LoRa32 uses only 24mA, with WifiAP 150mA). APRS Message/Queries can start/stop this mode too. - 2024.10.08 New EcoMode for Remote Digipeaters without WiFi/WiFiAP, Screen, Leds (Example: LILYGO LoRa32 uses only 24mA, with WifiAP 150mA). APRS Message/Queries can start/stop this mode too.
- 2024.10.06 Cross Frequency Digipeater Rules added. - 2024.10.06 Cross Frequency Digipeater Rules added.
- 2024.09.23 Libraries Update for SDK3 - 2024.09.23 Libraries Update for SDK3
- 2024.09.23 Added Enconded Telemetry for Battery (+ External Voltage) in Station GPS Beacon Packet. - 2024.09.23 Added Enconded Telemetry for Battery (+ External Voltage) in Station GPS Beacon Packet.
- 2024.08.23 Wemos S2 Mini DIY LoRa added. - 2024.08.23 Wemos S2 Mini DIY LoRa added.
- 2024.08.19 HELTEC Wireless Paper working (still missing Epaper code). - 2024.08.19 HELTEC Wireless Paper working (still missing Epaper code).
- 2024.08.13 Web Authentication for WebUI. Thanks Mitja S57PNX. - 2024.08.13 Web Authentication for WebUI. Thanks Mitja S57PNX.

View File

@@ -23,20 +23,20 @@ build_flags =
-D RADIOLIB_EXCLUDE_SSTV=1 -D RADIOLIB_EXCLUDE_SSTV=1
-I variants/${PIOENV} -I variants/${PIOENV}
lib_deps = lib_deps =
adafruit/Adafruit Unified Sensor @ 1.1.14 adafruit/Adafruit Unified Sensor @ 1.1.15
adafruit/Adafruit AHTX0 @ 2.0.5 adafruit/Adafruit AHTX0 @ 2.0.6
adafruit/Adafruit BME280 Library @ 2.2.4 adafruit/Adafruit BME280 Library @ 2.3.0
adafruit/Adafruit BMP280 Library @ 2.6.8 adafruit/Adafruit BMP280 Library @ 3.0.0
adafruit/Adafruit BME680 Library @ 2.0.4 adafruit/Adafruit BME680 Library @ 2.0.6
adafruit/Adafruit INA219 @ 1.2.3 adafruit/Adafruit INA219 @ 1.2.3
adafruit/Adafruit Si7021 Library @ 1.5.3 adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1 arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.7 ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 6.21.3 bblanchon/ArduinoJson @ 7.4.2
jgromes/RadioLib @ 7.1.0 jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8 knolleary/PubSubClient @ 2.8
ESP32Async/AsyncTCP @ 3.4.9 ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.9.3 ESP32Async/ESPAsyncWebServer @ 3.10.0
mikalhart/TinyGPSPlus @ 1.0.3 mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.4 richonguzman/APRSPacketLib @ 1.0.4
display_libs = display_libs =
@@ -44,4 +44,4 @@ display_libs =
adafruit/Adafruit SSD1306 @ 2.5.10 adafruit/Adafruit SSD1306 @ 2.5.10
usb_flags= usb_flags=
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1

View File

@@ -1853,46 +1853,68 @@
</div> </div>
<div class="col-9"> <div class="col-9">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-12">
<label <div class="form-check form-switch">
for="wifi.autoAP.password"
class="form-label"
>Password</label
>
<div class="input-group">
<input <input
type="password" type="checkbox"
name="wifi.autoAP.password" name="wifi.autoAP.enabled"
id="wifi.autoAP.password" id="wifi.autoAP.enabled"
class="form-control" class="form-check-input"
placeholder="1234567890"
required=""
/> />
</div> <label
</div> for="wifi.autoAP.enabled"
<div class="col-6"> class="form-label"
<label >Enable Auto AP</label
for="wifi.autoAP.timeout"
class="form-label"
>WiFiAP timeout (to search again)</label
>
<div class="input-group">
<input
type="number"
name="wifi.autoAP.timeout"
id="wifi.autoAP.timeout"
class="form-control"
placeholder="10"
required=""
step="1"
min="0"
/>
<span class="input-group-text"
>minutes</span
> >
<div class="form-text"> <div class="form-text">
Set to <strong>0</strong> if you don't Create WiFi AP when no network is available
want WiFi AP to stop. </div>
</div>
</div>
</div>
<div id="wifi-autoap-config" style="{{wifi.autoAP.enabled ? '' : 'display:none'}}">
<div class="row mt-3">
<div class="col-6">
<label
for="wifi.autoAP.password"
class="form-label"
>Password</label
>
<div class="input-group">
<input
type="password"
name="wifi.autoAP.password"
id="wifi.autoAP.password"
class="form-control"
placeholder="1234567890"
required=""
/>
</div>
</div>
<div class="col-6">
<label
for="wifi.autoAP.timeout"
class="form-label"
>WiFiAP timeout (to search again)</label
>
<div class="input-group">
<input
type="number"
name="wifi.autoAP.timeout"
id="wifi.autoAP.timeout"
class="form-control"
placeholder="10"
required=""
step="1"
min="0"
/>
<span class="input-group-text"
>minutes</span
>
<div class="form-text">
Set to <strong>0</strong> if you don't
want WiFi AP to stop.
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -237,8 +237,10 @@ function loadSettings(settings) {
RebootModeTime.disabled = !RebootModeCheckbox.check; RebootModeTime.disabled = !RebootModeCheckbox.check;
// WiFi Auto AP // WiFi Auto AP
document.getElementById("wifi.autoAP.enabled").checked = settings.wifi.autoAP.enabled;
document.getElementById("wifi.autoAP.password").value = settings.wifi.autoAP.password; document.getElementById("wifi.autoAP.password").value = settings.wifi.autoAP.password;
document.getElementById("wifi.autoAP.timeout").value = settings.wifi.autoAP.timeout; document.getElementById("wifi.autoAP.timeout").value = settings.wifi.autoAP.timeout;
toggleWiFiAutoAPFields();
// OTA // OTA
document.getElementById("ota.username").value = settings.ota.username; document.getElementById("ota.username").value = settings.ota.username;
@@ -432,6 +434,18 @@ WebadminCheckbox.addEventListener("change", function () {
WebadminPassword.disabled = !this.checked; WebadminPassword.disabled = !this.checked;
}); });
// WiFi Auto AP Switches
const WiFiAutoAPCheckbox = document.querySelector('input[name="wifi.autoAP.enabled"]');
WiFiAutoAPCheckbox.addEventListener("change", function () {
toggleWiFiAutoAPFields();
});
function toggleWiFiAutoAPFields() {
const isEnabled = WiFiAutoAPCheckbox.checked;
const autoAPConfig = document.getElementById('wifi-autoap-config');
if (autoAPConfig) autoAPConfig.style.display = isEnabled ? 'block' : 'none';
}
document.querySelector(".new button").addEventListener("click", function () { document.querySelector(".new button").addEventListener("click", function () {
const networksContainer = document.querySelector(".list-networks"); const networksContainer = document.querySelector(".list-networks");

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -32,6 +32,7 @@ public:
class WiFi_Auto_AP { class WiFi_Auto_AP {
public: public:
bool enabled; // Enable Auto AP
String password; String password;
int timeout; int timeout;
}; };
@@ -44,7 +45,7 @@ public:
int interval; int interval;
String overlay; String overlay;
String symbol; String symbol;
String path; String path;
bool sendViaAPRSIS; bool sendViaAPRSIS;
bool sendViaRF; bool sendViaRF;
int beaconFreq; int beaconFreq;
@@ -78,7 +79,7 @@ public:
long rxFreq; long rxFreq;
int rxSpreadingFactor; int rxSpreadingFactor;
int rxCodingRate4; int rxCodingRate4;
long rxSignalBandwidth; long rxSignalBandwidth;
bool txActive; bool txActive;
long txFreq; long txFreq;
int txSpreadingFactor; int txSpreadingFactor;
@@ -188,20 +189,20 @@ public:
BATTERY battery; BATTERY battery;
WXSENSOR wxsensor; WXSENSOR wxsensor;
SYSLOG syslog; SYSLOG syslog;
TNC tnc; TNC tnc;
OTA ota; OTA ota;
WEBADMIN webadmin; WEBADMIN webadmin;
NTP ntp; NTP ntp;
REMOTE_MANAGEMENT remoteManagement; REMOTE_MANAGEMENT remoteManagement;
MQTT mqtt; MQTT mqtt;
void setup();
void setDefaultValues(); void setDefaultValues();
bool writeFile(); bool writeFile();
Configuration();
private: private:
bool readFile(); bool readFile();
String _filePath; String _filePath;
}; };
#endif #endif

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -21,6 +21,7 @@
#include <Arduino.h> #include <Arduino.h>
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32 #define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
void displaySetup(); void displaySetup();

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -23,7 +23,7 @@
namespace GPS_Utils { namespace GPS_Utils {
String getiGateLoRaBeaconPacket(); String getiGateLoRaBeaconPacket();
char *ax25_base91enc(char *s, uint8_t n, uint32_t v); char *ax25_base91enc(char *s, uint8_t n, uint32_t v);
String encodeGPS(float latitude, float longitude, const String& overlay, const String& symbol); String encodeGPS(float latitude, float longitude, const String& overlay, const String& symbol);

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

82
include/network_manager.h Normal file
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;
};

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -24,7 +24,7 @@
namespace NTP_Utils { namespace NTP_Utils {
void setup(); bool setup();
void update(); void update();
String getFormatedTime(); String getFormatedTime();

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -34,7 +34,7 @@ namespace POWER_Utils {
void vext_ctrl_OFF(); void vext_ctrl_OFF();
#endif #endif
#ifdef ADC_CTRL #ifdef ADC_CTRL_PIN
void adc_ctrl_ON(); void adc_ctrl_ON();
void adc_ctrl_OFF(); void adc_ctrl_OFF();
#endif #endif

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -21,8 +21,9 @@
#include <Arduino.h> #include <Arduino.h>
namespace SLEEP_Utils { namespace SLEEP_Utils {
void setup(); void setup();
void checkWakeUpFlag(); void checkWakeUpFlag();
void startSleeping(); void startSleeping();

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -37,8 +37,7 @@ namespace STATION_Utils {
void deleteNotHeard(); void deleteNotHeard();
void updateLastHeard(const String& station); void updateLastHeard(const String& station);
bool wasHeard(const String& station); bool wasHeard(const String& station);
void clean25SegBuffer(); bool isIn25SegHashBuffer(const String& station, const String& textMessage);
bool check25SegBuffer(const String& station, const String& textMessage);
void processOutputPacketBufferUltraEcoMode(); void processOutputPacketBufferUltraEcoMode();
void processOutputPacketBuffer(); void processOutputPacketBuffer();
void addToOutputPacketBuffer(const String& packet, bool flag = false); void addToOutputPacketBuffer(const String& packet, bool flag = false);

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -27,7 +27,8 @@ namespace TELEMETRY_Utils {
void sendEquationsUnitsParameters(); void sendEquationsUnitsParameters();
String generateEncodedTelemetryBytes(float value, bool counterBytes, byte telemetryType); String generateEncodedTelemetryBytes(float value, bool counterBytes, byte telemetryType);
String generateEncodedTelemetry(); String generateEncodedTelemetry();
void checkEUPInterval();
} }
#endif #endif

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -27,7 +27,6 @@ namespace WIFI_Utils {
void checkWiFi(); void checkWiFi();
void startAutoAP(); void startAutoAP();
void startWiFi(); void startWiFi();
void checkAutoAPTimeout();
void setup(); void setup();
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -20,7 +20,7 @@
#define WX_UTILS_H_ #define WX_UTILS_H_
#include <Adafruit_Sensor.h> #include <Adafruit_Sensor.h>
#include <Adafruit_AHTX0.h> #include <Adafruit_AHTX0.h>
#include <Adafruit_BME280.h> #include <Adafruit_BME280.h>
#include <Adafruit_BMP280.h> #include <Adafruit_BMP280.h>
#include <Adafruit_BME680.h> #include <Adafruit_BME680.h>

View File

@@ -16,7 +16,7 @@ extra_configs =
variants/*/platformio.ini variants/*/platformio.ini
[env] [env]
platform = espressif32 @ 6.7.0 platform = espressif32 @ 6.12.0
board_build.partitions = min_spiffs.csv board_build.partitions = min_spiffs.csv
framework = arduino framework = arduino
monitor_speed = 115200 monitor_speed = 115200

View File

@@ -41,9 +41,10 @@ ___________________________________________________________________*/
#include <ElegantOTA.h> #include <ElegantOTA.h>
#include <TinyGPS++.h> #include <TinyGPS++.h>
#include <Arduino.h> #include <Arduino.h>
#include <WiFi.h> #include <WiFiClient.h>
#include <vector> #include <vector>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h" #include "aprs_is_utils.h"
#include "station_utils.h" #include "station_utils.h"
#include "battery_utils.h" #include "battery_utils.h"
@@ -67,8 +68,8 @@ ___________________________________________________________________*/
#endif #endif
String versionDate = "2026-02-20"; String versionDate = "2026-03-11";
String versionNumber = "3.2"; String versionNumber = "3.2.107";
Configuration Config; Configuration Config;
WiFiClient aprsIsClient; WiFiClient aprsIsClient;
WiFiClient mqttClient; WiFiClient mqttClient;
@@ -79,9 +80,7 @@ WiFiClient mqttClient;
bool gpsInfoToggle = false; bool gpsInfoToggle = false;
#endif #endif
uint8_t myWiFiAPIndex = 0; NetworkManager *networkManager;
int myWiFiAPSize = Config.wifiAPs.size();
WiFi_AP *currentWiFi = &Config.wifiAPs[myWiFiAPIndex];
bool isUpdatingOTA = false; bool isUpdatingOTA = false;
uint32_t lastBatteryCheck = 0; uint32_t lastBatteryCheck = 0;
@@ -101,6 +100,13 @@ String firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seven
void setup() { void setup() {
Serial.begin(115200); 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(); POWER_Utils::setup();
Utils::setupDisplay(); Utils::setupDisplay();
LoRa_Utils::setup(); LoRa_Utils::setup();
@@ -132,7 +138,7 @@ void loop() {
Utils::checkSleepByLowBatteryVoltage(1); Utils::checkSleepByLowBatteryVoltage(1);
SLEEP_Utils::startSleeping(); SLEEP_Utils::startSleeping();
} else { } else {
WIFI_Utils::checkAutoAPTimeout(); networkManager->loop();
if (isUpdatingOTA) { if (isUpdatingOTA) {
ElegantOTA.loop(); ElegantOTA.loop();
@@ -161,11 +167,14 @@ void loop() {
#endif #endif
#ifdef HAS_A7670 #ifdef HAS_A7670
// TODO: Make this part of Network manager, and use ESP-IDF network stack instead manual AT commands
if (Config.aprs_is.active && !modemLoggedToAPRSIS) A7670_Utils::APRS_IS_connect(); if (Config.aprs_is.active && !modemLoggedToAPRSIS) A7670_Utils::APRS_IS_connect();
#else #else
WIFI_Utils::checkWiFi(); WIFI_Utils::checkWiFi();
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) APRS_IS_Utils::connect(); if (networkManager->isConnected()) {
if (Config.mqtt.active && (WiFi.status() == WL_CONNECTED) && !mqttClient.connected()) MQTT_Utils::connect(); if (Config.aprs_is.active && !aprsIsClient.connected()) APRS_IS_Utils::connect();
if (Config.mqtt.active && !mqttClient.connected()) MQTT_Utils::connect();
}
#endif #endif
NTP_Utils::update(); NTP_Utils::update();
@@ -188,7 +197,6 @@ void loop() {
} }
if (Config.loramodule.txActive && (Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled if (Config.loramodule.txActive && (Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
STATION_Utils::clean25SegBuffer();
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
} }
@@ -217,4 +225,4 @@ void loop() {
Utils::checkRebootTime(); Utils::checkRebootTime();
Utils::checkSleepByLowBatteryVoltage(1); Utils::checkSleepByLowBatteryVoltage(1);
} }
} }

View File

@@ -16,9 +16,10 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <APRSPacketLib.h> #include <APRSPacketLib.h>
#include <WiFi.h> #include <WiFiClient.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h" #include "aprs_is_utils.h"
#include "station_utils.h" #include "station_utils.h"
#include "board_pinout.h" #include "board_pinout.h"
@@ -32,6 +33,7 @@
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
extern WiFiClient aprsIsClient; extern WiFiClient aprsIsClient;
extern uint32_t lastScreenOn; extern uint32_t lastScreenOn;
extern String firstLine; extern String firstLine;
@@ -93,7 +95,7 @@ namespace APRS_IS_Utils {
void checkStatus() { void checkStatus() {
String wifiState, aprsisState; String wifiState, aprsisState;
if (WiFi.status() == WL_CONNECTED) { if (networkManager->isWiFiConnected()) {
wifiState = "OK"; wifiState = "OK";
} else { } else {
if (backupDigiMode || Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) { if (backupDigiMode || Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
@@ -135,30 +137,30 @@ namespace APRS_IS_Utils {
} }
String checkForStartingBytes(const String& packet) { String checkForStartingBytes(const String& packet) {
if (packet.indexOf("\x3c\xff\x01") != -1) { int index = packet.indexOf("\x3c\xff\x01");
return packet.substring(0, packet.indexOf("\x3c\xff\x01")); return (index != -1) ? packet.substring(0, index) : packet;
} else {
return packet;
}
} }
String buildPacketToUpload(const String& packet) { String buildPacketToUpload(const String& packet) {
String packetToUpload = packet.substring(3, packet.indexOf(":")); int colonIndex = packet.indexOf(":");
String packetToUpload = packet.substring(3, colonIndex);
if (Config.aprs_is.active && passcodeValid && Config.aprs_is.messagesToRF) { if (Config.aprs_is.active && passcodeValid && Config.aprs_is.messagesToRF) {
packetToUpload += ",qAR,"; packetToUpload += ",qAR,";
} else { } else {
packetToUpload += ",qAO,"; packetToUpload += ",qAO,";
} }
packetToUpload += Config.callsign; packetToUpload += Config.callsign;
packetToUpload += checkForStartingBytes(packet.substring(packet.indexOf(":"))); packetToUpload += checkForStartingBytes(packet.substring(colonIndex));
return packetToUpload; return packetToUpload;
} }
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty) { bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty) {
String receivedMessage; String receivedMessage;
if (packet.indexOf("{") > 0) { // ack? int leftCurlyBraceIndex = packet.indexOf("{");
int colonIndex = packet.indexOf(":");
if (leftCurlyBraceIndex > 0) { // ack?
String ackMessage = "ack"; String ackMessage = "ack";
ackMessage.concat(packet.substring(packet.indexOf("{") + 1)); ackMessage.concat(packet.substring(leftCurlyBraceIndex + 1));
ackMessage.trim(); ackMessage.trim();
//Serial.println(ackMessage); //Serial.println(ackMessage);
@@ -180,9 +182,9 @@ namespace APRS_IS_Utils {
addToBuffer += ":"; addToBuffer += ":";
addToBuffer += ackMessage; addToBuffer += ackMessage;
STATION_Utils::addToOutputPacketBuffer(addToBuffer); STATION_Utils::addToOutputPacketBuffer(addToBuffer);
receivedMessage = packet.substring(packet.indexOf(":") + 1, packet.indexOf("{")); receivedMessage = packet.substring(colonIndex + 1, leftCurlyBraceIndex);
} else { } else {
receivedMessage = packet.substring(packet.indexOf(":") + 1); receivedMessage = packet.substring(colonIndex + 1);
} }
if (receivedMessage.indexOf("?") == 0) { if (receivedMessage.indexOf("?") == 0) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) { if (!Config.display.alwaysOn && Config.display.timeout != 0) {
@@ -207,29 +209,30 @@ namespace APRS_IS_Utils {
if (Sender != Config.callsign && Utils::callsignIsValid(Sender)) { if (Sender != Config.callsign && Utils::callsignIsValid(Sender)) {
STATION_Utils::updateLastHeard(Sender); STATION_Utils::updateLastHeard(Sender);
Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS
const String& AddresseeAndMessage = packet.substring(packet.indexOf("::") + 2); int doubleColonIndex = packet.indexOf("::");
const String& AddresseeAndMessage = packet.substring(doubleColonIndex + 2);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
Addressee.trim(); Addressee.trim();
bool queryMessage = false; bool queryMessage = false;
if (packet.indexOf("::") > 10 && Addressee == Config.callsign) { // its a message for me! if (doubleColonIndex > 10 && Addressee == Config.callsign) { // its a message for me!
queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false); queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false);
} }
if (!queryMessage) { if (queryMessage) return;
const String& aprsPacket = buildPacketToUpload(packet);
if (!Config.display.alwaysOn && Config.display.timeout != 0) { const String& aprsPacket = buildPacketToUpload(packet);
displayToggle(true); if (!Config.display.alwaysOn && Config.display.timeout != 0) {
} displayToggle(true);
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
A7670_Utils::uploadToAPRSIS(aprsPacket);
stationBeacon = false;
#else
upload(aprsPacket);
#endif
Utils::println("---> Uploaded to APRS-IS");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
} }
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
A7670_Utils::uploadToAPRSIS(aprsPacket);
stationBeacon = false;
#else
upload(aprsPacket);
#endif
Utils::println("---> Uploaded to APRS-IS");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
} }
} }
} }
@@ -245,26 +248,30 @@ namespace APRS_IS_Utils {
outputPacket.concat(",TCPIP,"); outputPacket.concat(",TCPIP,");
outputPacket.concat(Config.callsign); outputPacket.concat(Config.callsign);
outputPacket.concat("*"); outputPacket.concat("*");
int colonEqualIndex = packet.indexOf(":=");
int doubleColonIndex = packet.indexOf("::");
int colonInvAccentIndex = packet.indexOf(":`");
switch (packetType) { switch (packetType) {
case 0: // gps case 0: // gps
if (packet.indexOf(":=") > 0) { if (colonEqualIndex > 0) {
outputPacket += packet.substring(packet.indexOf(":=")); outputPacket += packet.substring(colonEqualIndex);
} else { } else {
outputPacket += packet.substring(packet.indexOf(":!")); outputPacket += packet.substring(packet.indexOf(":!"));
} }
break; break;
case 1: // messages case 1: // messages
outputPacket += packet.substring(packet.indexOf("::")); outputPacket += packet.substring(doubleColonIndex);
break; break;
case 2: // status case 2: // status
outputPacket += packet.substring(packet.indexOf(":>")); outputPacket += packet.substring(packet.indexOf(":>"));
break; break;
case 3: // telemetry case 3: // telemetry
outputPacket += packet.substring(packet.indexOf("::")); outputPacket += packet.substring(doubleColonIndex);
break; break;
case 4: // mic-e case 4: // mic-e
if (packet.indexOf(":`") > 0) { if (colonInvAccentIndex > 0) {
outputPacket += packet.substring(packet.indexOf(":`")); outputPacket += packet.substring(colonInvAccentIndex);
} else { } else {
outputPacket += packet.substring(packet.indexOf(":'")); outputPacket += packet.substring(packet.indexOf(":'"));
} }
@@ -316,18 +323,21 @@ namespace APRS_IS_Utils {
if (packet.startsWith("#")) { if (packet.startsWith("#")) {
if (Config.digi.backupDigiMode) lastServerCheck = currentTime; if (Config.digi.backupDigiMode) lastServerCheck = currentTime;
} else { } else {
if (Config.aprs_is.messagesToRF && packet.indexOf("::") > 0) { int doubleColonIndex = packet.indexOf("::");
if (Config.aprs_is.messagesToRF && doubleColonIndex > 0) {
String Sender = packet.substring(0, packet.indexOf(">")); String Sender = packet.substring(0, packet.indexOf(">"));
const String& AddresseeAndMessage = packet.substring(packet.indexOf("::") + 2); const String& AddresseeAndMessage = packet.substring(doubleColonIndex + 2);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); int colonIndex = AddresseeAndMessage.indexOf(":");
String Addressee = AddresseeAndMessage.substring(0, colonIndex);
Addressee.trim(); Addressee.trim();
if (Addressee == Config.callsign) { // its for me! if (Addressee == Config.callsign) { // its for me!
String receivedMessage; String receivedMessage;
if (AddresseeAndMessage.indexOf("{") > 0) { // ack? int curlyBraceIndex = AddresseeAndMessage.indexOf("{");
if (curlyBraceIndex > 0) { // ack?
processAckMessage(Sender, AddresseeAndMessage); processAckMessage(Sender, AddresseeAndMessage);
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":") + 1, AddresseeAndMessage.indexOf("{")); receivedMessage = AddresseeAndMessage.substring(colonIndex + 1, curlyBraceIndex);
} else { } else {
receivedMessage = AddresseeAndMessage.substring(AddresseeAndMessage.indexOf(":") + 1); receivedMessage = AddresseeAndMessage.substring(colonIndex + 1);
} }
if (receivedMessage.indexOf("?") == 0) { if (receivedMessage.indexOf("?") == 0) {
Utils::println("Rx Query (APRS-IS) : " + packet); Utils::println("Rx Query (APRS-IS) : " + packet);
@@ -400,7 +410,7 @@ namespace APRS_IS_Utils {
} }
void firstConnection() { void firstConnection() {
if (Config.aprs_is.active && (WiFi.status() == WL_CONNECTED) && !aprsIsClient.connected()) { if (Config.aprs_is.active && networkManager->isConnected() && !aprsIsClient.connected()) {
connect(); connect();
while (!passcodeValid) { while (!passcodeValid) {
listenAPRSIS(); listenAPRSIS();
@@ -408,4 +418,4 @@ namespace APRS_IS_Utils {
} }
} }
} }

View File

@@ -154,7 +154,7 @@ namespace BATTERY_Utils {
} }
#else #else
#ifdef ADC_CTRL #ifdef ADC_CTRL_PIN
POWER_Utils::adc_ctrl_ON(); POWER_Utils::adc_ctrl_ON();
#endif #endif
@@ -180,7 +180,7 @@ namespace BATTERY_Utils {
delay(3); delay(3);
} }
#ifdef ADC_CTRL #ifdef ADC_CTRL_PIN
POWER_Utils::adc_ctrl_OFF(); POWER_Utils::adc_ctrl_OFF();
#ifdef HELTEC_WP_V1 #ifdef HELTEC_WP_V1

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -29,7 +29,7 @@ bool shouldSleepStop = true;
bool Configuration::writeFile() { bool Configuration::writeFile() {
Serial.println("Saving configuration..."); Serial.println("Saving configuration...");
StaticJsonDocument<3584> data; JsonDocument data;
File configFile = SPIFFS.open("/igate_conf.json", "w"); File configFile = SPIFFS.open("/igate_conf.json", "w");
if (!configFile) { if (!configFile) {
@@ -47,6 +47,7 @@ bool Configuration::writeFile() {
data["other"]["startupDelay"] = startupDelay; data["other"]["startupDelay"] = startupDelay;
data["wifi"]["autoAP"]["enabled"] = wifiAutoAP.enabled;
data["wifi"]["autoAP"]["password"] = wifiAutoAP.password; data["wifi"]["autoAP"]["password"] = wifiAutoAP.password;
data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout; data["wifi"]["autoAP"]["timeout"] = wifiAutoAP.timeout;
@@ -87,6 +88,7 @@ bool Configuration::writeFile() {
data["digi"]["mode"] = digi.mode; data["digi"]["mode"] = digi.mode;
data["digi"]["ecoMode"] = digi.ecoMode; data["digi"]["ecoMode"] = digi.ecoMode;
if (digi.ecoMode == 1) data["aprs_is"]["active"] = false;
#if defined(HAS_A7670) #if defined(HAS_A7670)
if (digi.ecoMode == 1) data["digi"]["ecoMode"] = 2; if (digi.ecoMode == 1) data["digi"]["ecoMode"] = 2;
#endif #endif
@@ -96,12 +98,12 @@ bool Configuration::writeFile() {
data["lora"]["rxFreq"] = loramodule.rxFreq; data["lora"]["rxFreq"] = loramodule.rxFreq;
data["lora"]["rxCodingRate4"] = loramodule.rxCodingRate4; data["lora"]["rxCodingRate4"] = loramodule.rxCodingRate4;
data["lora"]["rxSignalBandwidth"] = loramodule.rxSignalBandwidth; data["lora"]["rxSignalBandwidth"] = loramodule.rxSignalBandwidth;
data["lora"]["txActive"] = loramodule.txActive; data["lora"]["txActive"] = loramodule.txActive;
data["lora"]["txFreq"] = loramodule.txFreq; data["lora"]["txFreq"] = loramodule.txFreq;
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4; data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth; data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
data["lora"]["power"] = loramodule.power; data["lora"]["power"] = loramodule.power;
int rxSpreadingFactor = loramodule.rxSpreadingFactor; int rxSpreadingFactor = loramodule.rxSpreadingFactor;
int txSpreadingFactor = loramodule.txSpreadingFactor; int txSpreadingFactor = loramodule.txSpreadingFactor;
#if defined(HAS_SX1276) || defined(HAS_SX1278) #if defined(HAS_SX1276) || defined(HAS_SX1278)
@@ -194,8 +196,7 @@ bool Configuration::readFile() {
if (configFile) { if (configFile) {
bool needsRewrite = false; bool needsRewrite = false;
StaticJsonDocument<3584> data; JsonDocument data;
DeserializationError error = deserializeJson(data, configFile); DeserializationError error = deserializeJson(data, configFile);
if (error) { if (error) {
Serial.println("Failed to read file, using default configuration"); Serial.println("Failed to read file, using default configuration");
@@ -210,26 +211,28 @@ bool Configuration::readFile() {
wifiAPs.push_back(wifiap); wifiAPs.push_back(wifiap);
} }
if (!data["other"].containsKey("startupDelay")) needsRewrite = true; if (data["other"]["startupDelay"].isNull()) needsRewrite = true;
startupDelay = data["other"]["startupDelay"] | 0; startupDelay = data["other"]["startupDelay"] | 0;
if (!data["wifi"]["autoAP"].containsKey("password") || if (data["wifi"]["autoAP"]["enabled"].isNull() ||
!data["wifi"]["autoAP"].containsKey("timeout")) needsRewrite = true; 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.password = data["wifi"]["autoAP"]["password"] | "1234567890";
wifiAutoAP.timeout = data["wifi"]["autoAP"]["timeout"] | 10; wifiAutoAP.timeout = data["wifi"]["autoAP"]["timeout"] | 10;
if (!data.containsKey("callsign")) needsRewrite = true; if (data["callsign"].isNull()) needsRewrite = true;
callsign = data["callsign"] | "NOCALL-10"; callsign = data["callsign"] | "NOCALL-10";
if (!data.containsKey("tacticalCallsign")) needsRewrite = true; if (data["tacticalCallsign"].isNull()) needsRewrite = true;
tacticalCallsign = data["tacticalCallsign"] | ""; tacticalCallsign = data["tacticalCallsign"] | "";
if (!data["aprs_is"].containsKey("active") || if (data["aprs_is"]["active"].isNull() ||
!data["aprs_is"].containsKey("passcode") || data["aprs_is"]["passcode"].isNull() ||
!data["aprs_is"].containsKey("server") || data["aprs_is"]["server"].isNull() ||
!data["aprs_is"].containsKey("port") || data["aprs_is"]["port"].isNull() ||
!data["aprs_is"].containsKey("filter") || data["aprs_is"]["filter"].isNull() ||
!data["aprs_is"].containsKey("messagesToRF") || data["aprs_is"]["messagesToRF"].isNull() ||
!data["aprs_is"].containsKey("objectsToRF")) needsRewrite = true; data["aprs_is"]["objectsToRF"].isNull()) needsRewrite = true;
aprs_is.active = data["aprs_is"]["active"] | false; aprs_is.active = data["aprs_is"]["active"] | false;
aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV"; aprs_is.passcode = data["aprs_is"]["passcode"] | "XYZWV";
aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net"; aprs_is.server = data["aprs_is"]["server"] | "rotate.aprs2.net";
@@ -238,20 +241,20 @@ bool Configuration::readFile() {
aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false; aprs_is.messagesToRF = data["aprs_is"]["messagesToRF"] | false;
aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false; aprs_is.objectsToRF = data["aprs_is"]["objectsToRF"] | false;
if (!data["beacon"].containsKey("latitude") || if (data["beacon"]["latitude"].isNull() ||
!data["beacon"].containsKey("longitude") || data["beacon"]["longitude"].isNull() ||
!data["beacon"].containsKey("comment") || data["beacon"]["comment"].isNull() ||
!data["beacon"].containsKey("interval") || data["beacon"]["interval"].isNull() ||
!data["beacon"].containsKey("overlay") || data["beacon"]["overlay"].isNull() ||
!data["beacon"].containsKey("symbol") || data["beacon"]["symbol"].isNull() ||
!data["beacon"].containsKey("path") || data["beacon"]["path"].isNull() ||
!data["beacon"].containsKey("sendViaAPRSIS") || data["beacon"]["sendViaAPRSIS"].isNull() ||
!data["beacon"].containsKey("sendViaRF") || data["beacon"]["sendViaRF"].isNull() ||
!data["beacon"].containsKey("beaconFreq") || data["beacon"]["beaconFreq"].isNull() ||
!data["beacon"].containsKey("statusActive") || data["beacon"]["statusActive"].isNull() ||
!data["beacon"].containsKey("statusPacket") || data["beacon"]["statusPacket"].isNull() ||
!data["beacon"].containsKey("gpsActive") || data["beacon"]["gpsActive"].isNull() ||
!data["beacon"].containsKey("ambiguityLevel")) needsRewrite = true; data["beacon"]["ambiguityLevel"].isNull()) needsRewrite = true;
beacon.latitude = data["beacon"]["latitude"] | 0.0; beacon.latitude = data["beacon"]["latitude"] | 0.0;
beacon.longitude = data["beacon"]["longitude"] | 0.0; beacon.longitude = data["beacon"]["longitude"] | 0.0;
beacon.comment = data["beacon"]["comment"] | "LoRa APRS"; beacon.comment = data["beacon"]["comment"] | "LoRa APRS";
@@ -267,15 +270,15 @@ bool Configuration::readFile() {
beacon.gpsActive = data["beacon"]["gpsActive"] | false; beacon.gpsActive = data["beacon"]["gpsActive"] | false;
beacon.ambiguityLevel = data["beacon"]["ambiguityLevel"] | 0; beacon.ambiguityLevel = data["beacon"]["ambiguityLevel"] | 0;
if (!data.containsKey("personalNote")) needsRewrite = true; if (data["personalNote"].isNull()) needsRewrite = true;
personalNote = data["personalNote"] | "personal note here"; personalNote = data["personalNote"] | "personal note here";
if (!data.containsKey("blacklist")) needsRewrite = true; if (data["blacklist"].isNull()) needsRewrite = true;
blacklist = data["blacklist"] | "station callsign"; blacklist = data["blacklist"] | "station callsign";
if (!data["digi"].containsKey("mode") || if (data["digi"]["mode"].isNull() ||
!data["digi"].containsKey("ecoMode") || data["digi"]["ecoMode"].isNull() ||
!data["digi"].containsKey("backupDigiMode")) needsRewrite = true; data["digi"]["backupDigiMode"].isNull()) needsRewrite = true;
digi.mode = data["digi"]["mode"] | 0; digi.mode = data["digi"]["mode"] | 0;
digi.ecoMode = data["digi"]["ecoMode"] | 0; digi.ecoMode = data["digi"]["ecoMode"] | 0;
if (digi.ecoMode == 1) shouldSleepStop = false; if (digi.ecoMode == 1) shouldSleepStop = false;
@@ -285,32 +288,32 @@ bool Configuration::readFile() {
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false; digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
if (!data["lora"].containsKey("rxActive") || if (data["lora"]["rxActive"].isNull() ||
!data["lora"].containsKey("rxFreq") || data["lora"]["rxFreq"].isNull() ||
!data["lora"].containsKey("rxSpreadingFactor") || data["lora"]["rxSpreadingFactor"].isNull() ||
!data["lora"].containsKey("rxCodingRate4") || data["lora"]["rxCodingRate4"].isNull() ||
!data["lora"].containsKey("rxSignalBandwidth") || data["lora"]["rxSignalBandwidth"].isNull() ||
!data["lora"].containsKey("txActive") || data["lora"]["txActive"].isNull() ||
!data["lora"].containsKey("txFreq") || data["lora"]["txFreq"].isNull() ||
!data["lora"].containsKey("txSpreadingFactor") || data["lora"]["txSpreadingFactor"].isNull() ||
!data["lora"].containsKey("txCodingRate4") || data["lora"]["txCodingRate4"].isNull() ||
!data["lora"].containsKey("txSignalBandwidth") || data["lora"]["txSignalBandwidth"].isNull() ||
!data["lora"].containsKey("power")) needsRewrite = true; data["lora"]["power"].isNull()) needsRewrite = true;
loramodule.rxActive = data["lora"]["rxActive"] | true; loramodule.rxActive = data["lora"]["rxActive"] | true;
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000; loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12; loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
loramodule.rxCodingRate4 = data["lora"]["rxCodingRate4"] | 5; loramodule.rxCodingRate4 = data["lora"]["rxCodingRate4"] | 5;
loramodule.rxSignalBandwidth = data["lora"]["rxSignalBandwidth"] | 125000; loramodule.rxSignalBandwidth = data["lora"]["rxSignalBandwidth"] | 125000;
loramodule.txActive = data["lora"]["txActive"] | false; loramodule.txActive = data["lora"]["txActive"] | false;
loramodule.txFreq = data["lora"]["txFreq"] | 433775000; loramodule.txFreq = data["lora"]["txFreq"] | 433775000;
loramodule.txSpreadingFactor = data["lora"]["txSpreadingFactor"] | 12; loramodule.txSpreadingFactor = data["lora"]["txSpreadingFactor"] | 12;
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5; loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000; loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
loramodule.power = data["lora"]["power"] | 20; loramodule.power = data["lora"]["power"] | 20;
if (!data["display"].containsKey("alwaysOn") || if (data["display"]["alwaysOn"].isNull() ||
!data["display"].containsKey("timeout") || data["display"]["timeout"].isNull() ||
!data["display"].containsKey("turn180")) needsRewrite = true; data["display"]["turn180"].isNull()) needsRewrite = true;
#ifdef HAS_EPAPER #ifdef HAS_EPAPER
display.alwaysOn = true; display.alwaysOn = true;
#else #else
@@ -319,17 +322,17 @@ bool Configuration::readFile() {
display.timeout = data["display"]["timeout"] | 4; display.timeout = data["display"]["timeout"] | 4;
display.turn180 = data["display"]["turn180"] | false; display.turn180 = data["display"]["turn180"] | false;
if (!data["battery"].containsKey("sendInternalVoltage") || if (data["battery"]["sendInternalVoltage"].isNull() ||
!data["battery"].containsKey("monitorInternalVoltage") || data["battery"]["monitorInternalVoltage"].isNull() ||
!data["battery"].containsKey("internalSleepVoltage") || data["battery"]["internalSleepVoltage"].isNull() ||
!data["battery"].containsKey("sendExternalVoltage") || data["battery"]["sendExternalVoltage"].isNull() ||
!data["battery"].containsKey("monitorExternalVoltage") || data["battery"]["monitorExternalVoltage"].isNull() ||
!data["battery"].containsKey("externalSleepVoltage") || data["battery"]["externalSleepVoltage"].isNull() ||
!data["battery"].containsKey("useExternalI2CSensor") || data["battery"]["useExternalI2CSensor"].isNull() ||
!data["battery"].containsKey("voltageDividerR1") || data["battery"]["voltageDividerR1"].isNull() ||
!data["battery"].containsKey("voltageDividerR2") || data["battery"]["voltageDividerR2"].isNull() ||
!data["battery"].containsKey("externalVoltagePin") || data["battery"]["externalVoltagePin"].isNull() ||
!data["battery"].containsKey("sendVoltageAsTelemetry")) needsRewrite = true; data["battery"]["sendVoltageAsTelemetry"].isNull()) needsRewrite = true;
battery.sendInternalVoltage = data["battery"]["sendInternalVoltage"] | false; battery.sendInternalVoltage = data["battery"]["sendInternalVoltage"] | false;
battery.monitorInternalVoltage = data["battery"]["monitorInternalVoltage"] | false; battery.monitorInternalVoltage = data["battery"]["monitorInternalVoltage"] | false;
battery.internalSleepVoltage = data["battery"]["internalSleepVoltage"] | 2.9; battery.internalSleepVoltage = data["battery"]["internalSleepVoltage"] | 2.9;
@@ -342,38 +345,38 @@ bool Configuration::readFile() {
battery.externalVoltagePin = data["battery"]["externalVoltagePin"] | 34; battery.externalVoltagePin = data["battery"]["externalVoltagePin"] | 34;
battery.sendVoltageAsTelemetry = data["battery"]["sendVoltageAsTelemetry"] | false; battery.sendVoltageAsTelemetry = data["battery"]["sendVoltageAsTelemetry"] | false;
if (!data["wxsensor"].containsKey("active") || if (data["wxsensor"]["active"].isNull() ||
!data["wxsensor"].containsKey("heightCorrection") || data["wxsensor"]["heightCorrection"].isNull() ||
!data["wxsensor"].containsKey("temperatureCorrection")) needsRewrite = true; data["wxsensor"]["temperatureCorrection"].isNull()) needsRewrite = true;
wxsensor.active = data["wxsensor"]["active"] | false; wxsensor.active = data["wxsensor"]["active"] | false;
wxsensor.heightCorrection = data["wxsensor"]["heightCorrection"] | 0; wxsensor.heightCorrection = data["wxsensor"]["heightCorrection"] | 0;
wxsensor.temperatureCorrection = data["wxsensor"]["temperatureCorrection"] | 0.0; wxsensor.temperatureCorrection = data["wxsensor"]["temperatureCorrection"] | 0.0;
if (!data["syslog"].containsKey("active") || if (data["syslog"]["active"].isNull() ||
!data["syslog"].containsKey("server") || data["syslog"]["server"].isNull() ||
!data["syslog"].containsKey("port") || data["syslog"]["port"].isNull() ||
!data["syslog"].containsKey("logBeaconOverTCPIP")) needsRewrite = true; data["syslog"]["logBeaconOverTCPIP"].isNull()) needsRewrite = true;
syslog.active = data["syslog"]["active"] | false; syslog.active = data["syslog"]["active"] | false;
syslog.server = data["syslog"]["server"] | "lora.link9.net"; syslog.server = data["syslog"]["server"] | "lora.link9.net";
syslog.port = data["syslog"]["port"] | 1514; syslog.port = data["syslog"]["port"] | 1514;
syslog.logBeaconOverTCPIP = data["syslog"]["logBeaconOverTCPIP"] | false; syslog.logBeaconOverTCPIP = data["syslog"]["logBeaconOverTCPIP"] | false;
if (!data["tnc"].containsKey("enableServer") || if (data["tnc"]["enableServer"].isNull() ||
!data["tnc"].containsKey("enableSerial") || data["tnc"]["enableSerial"].isNull() ||
!data["tnc"].containsKey("acceptOwn") || data["tnc"]["acceptOwn"].isNull() ||
!data["tnc"].containsKey("aprsBridgeActive")) needsRewrite = true; data["tnc"]["aprsBridgeActive"].isNull()) needsRewrite = true;
tnc.enableServer = data["tnc"]["enableServer"] | false; tnc.enableServer = data["tnc"]["enableServer"] | false;
tnc.enableSerial = data["tnc"]["enableSerial"] | false; tnc.enableSerial = data["tnc"]["enableSerial"] | false;
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false; tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false; tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
if (!data["mqtt"].containsKey("active") || if (data["mqtt"]["active"].isNull() ||
!data["mqtt"].containsKey("server") || data["mqtt"]["server"].isNull() ||
!data["mqtt"].containsKey("topic") || data["mqtt"]["topic"].isNull() ||
!data["mqtt"].containsKey("username") || data["mqtt"]["username"].isNull() ||
!data["mqtt"].containsKey("password") || data["mqtt"]["password"].isNull() ||
!data["mqtt"].containsKey("port") || data["mqtt"]["port"].isNull() ||
!data["mqtt"].containsKey("beaconOverMqtt")) needsRewrite = true; data["mqtt"]["beaconOverMqtt"].isNull()) needsRewrite = true;
mqtt.active = data["mqtt"]["active"] | false; mqtt.active = data["mqtt"]["active"] | false;
mqtt.server = data["mqtt"]["server"] | ""; mqtt.server = data["mqtt"]["server"] | "";
mqtt.topic = data["mqtt"]["topic"] | "aprs-igate"; mqtt.topic = data["mqtt"]["topic"] | "aprs-igate";
@@ -381,35 +384,35 @@ bool Configuration::readFile() {
mqtt.password = data["mqtt"]["password"] | ""; mqtt.password = data["mqtt"]["password"] | "";
mqtt.port = data["mqtt"]["port"] | 1883; mqtt.port = data["mqtt"]["port"] | 1883;
mqtt.beaconOverMqtt = data["mqtt"]["beaconOverMqtt"] | false; mqtt.beaconOverMqtt = data["mqtt"]["beaconOverMqtt"] | false;
if (!data["ota"].containsKey("username") || if (data["ota"]["username"].isNull() ||
!data["ota"].containsKey("password")) needsRewrite = true; data["ota"]["password"].isNull()) needsRewrite = true;
ota.username = data["ota"]["username"] | ""; ota.username = data["ota"]["username"] | "";
ota.password = data["ota"]["password"] | ""; ota.password = data["ota"]["password"] | "";
if (!data["webadmin"].containsKey("active") || if (data["webadmin"]["active"].isNull() ||
!data["webadmin"].containsKey("username") || data["webadmin"]["username"].isNull() ||
!data["webadmin"].containsKey("password")) needsRewrite = true; data["webadmin"]["password"].isNull()) needsRewrite = true;
webadmin.active = data["webadmin"]["active"] | false; webadmin.active = data["webadmin"]["active"] | false;
webadmin.username = data["webadmin"]["username"] | "admin"; webadmin.username = data["webadmin"]["username"] | "admin";
webadmin.password = data["webadmin"]["password"] | ""; webadmin.password = data["webadmin"]["password"] | "";
if (!data["remoteManagement"].containsKey("managers") || if (data["remoteManagement"]["managers"].isNull() ||
!data["remoteManagement"].containsKey("rfOnly")) needsRewrite = true; data["remoteManagement"]["rfOnly"].isNull()) needsRewrite = true;
remoteManagement.managers = data["remoteManagement"]["managers"] | ""; remoteManagement.managers = data["remoteManagement"]["managers"] | "";
remoteManagement.rfOnly = data["remoteManagement"]["rfOnly"] | true; remoteManagement.rfOnly = data["remoteManagement"]["rfOnly"] | true;
if (!data["ntp"].containsKey("server") || if (data["ntp"]["server"].isNull() ||
!data["ntp"].containsKey("gmtCorrection")) needsRewrite = true; data["ntp"]["gmtCorrection"].isNull()) needsRewrite = true;
ntp.server = data["ntp"]["server"] | "pool.ntp.org"; ntp.server = data["ntp"]["server"] | "pool.ntp.org";
ntp.gmtCorrection = data["ntp"]["gmtCorrection"] | 0.0; ntp.gmtCorrection = data["ntp"]["gmtCorrection"] | 0.0;
if (!data["other"].containsKey("rebootMode") || if (data["other"]["rebootMode"].isNull() ||
!data["other"].containsKey("rebootModeTime")) needsRewrite = true; data["other"]["rebootModeTime"].isNull()) needsRewrite = true;
rebootMode = data["other"]["rebootMode"] | false; rebootMode = data["other"]["rebootMode"] | false;
rebootModeTime = data["other"]["rebootModeTime"] | 6; rebootModeTime = data["other"]["rebootModeTime"] | 6;
if (!data["other"].containsKey("rememberStationTime")) needsRewrite = true; if (data["other"]["rememberStationTime"].isNull()) needsRewrite = true;
rememberStationTime = data["other"]["rememberStationTime"] | 30; rememberStationTime = data["other"]["rememberStationTime"] | 30;
if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP if (wifiAPs.size() == 0) { // If we don't have any WiFi's from config we need to add "empty" SSID for AUTO AP
@@ -426,7 +429,7 @@ bool Configuration::readFile() {
writeFile(); writeFile();
delay(1000); delay(1000);
ESP.restart(); ESP.restart();
} }
Serial.println("Config read successfuly"); Serial.println("Config read successfuly");
return true; return true;
} else { } else {
@@ -434,7 +437,7 @@ bool Configuration::readFile() {
return false; return false;
} }
} }
void Configuration::setDefaultValues() { void Configuration::setDefaultValues() {
WiFi_AP wifiap; WiFi_AP wifiap;
@@ -445,6 +448,7 @@ void Configuration::setDefaultValues() {
startupDelay = 0; startupDelay = 0;
wifiAutoAP.enabled = true;
wifiAutoAP.password = "1234567890"; wifiAutoAP.password = "1234567890";
wifiAutoAP.timeout = 10; wifiAutoAP.timeout = 10;
@@ -470,14 +474,14 @@ void Configuration::setDefaultValues() {
beacon.sendViaAPRSIS = true; beacon.sendViaAPRSIS = true;
beacon.sendViaRF = false; beacon.sendViaRF = false;
beacon.beaconFreq = 1; beacon.beaconFreq = 1;
beacon.statusActive = false; beacon.statusActive = false;
beacon.statusPacket = ""; beacon.statusPacket = "";
beacon.gpsActive = false; beacon.gpsActive = false;
beacon.ambiguityLevel = 0; beacon.ambiguityLevel = 0;
personalNote = ""; personalNote = "";
blacklist = ""; blacklist = "";
@@ -500,7 +504,7 @@ void Configuration::setDefaultValues() {
display.alwaysOn = true; display.alwaysOn = true;
display.timeout = 4; display.timeout = 4;
display.turn180 = false; display.turn180 = false;
battery.sendInternalVoltage = false; battery.sendInternalVoltage = false;
battery.monitorInternalVoltage = false; battery.monitorInternalVoltage = false;
battery.internalSleepVoltage = 2.9; battery.internalSleepVoltage = 2.9;
@@ -558,7 +562,7 @@ void Configuration::setDefaultValues() {
Serial.println("New Data Created... All is Written!"); Serial.println("New Data Created... All is Written!");
} }
Configuration::Configuration() { void Configuration::setup() {
if (!SPIFFS.begin(false)) { if (!SPIFFS.begin(false)) {
Serial.println("SPIFFS Mount Failed"); Serial.println("SPIFFS Mount Failed");
return; return;

View File

@@ -43,58 +43,53 @@ extern bool backupDigiMode;
namespace DIGI_Utils { namespace DIGI_Utils {
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) { String cleanPathAsterisks(String path) {
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign); String terms[] = {",WIDE1*", ",WIDE2*", "*"};
if (!crossFreq) { for (String term : terms) {
String packetToRepeat = packet.substring(0, packet.indexOf(",") + 1); int index = path.indexOf(term);
String tempPath = path; if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
int digiMode = Config.digi.mode; }
return path;
}
if (path.indexOf("WIDE1-1") != -1 && (digiMode == 2 || digiMode == 3)) { String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
String suffix = thirdParty ? ":}" : ":";
int suffixIndex = packet.indexOf(suffix);
String packetToRepeat;
if (!crossFreq) {
int digiMode = Config.digi.mode;
String tempPath = path;
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 2 || digiMode == 3)) { // WIDE1-1
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
tempPath.replace("WIDE1-1", stationCallsign + "*"); tempPath.replace("WIDE1-1", stationCallsign + "*");
} else if (path.indexOf("WIDE2-") != -1 && digiMode == 3) { } else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) { // WIDE2-n Digipeater
int wide1AsteriskIndex = path.indexOf(",WIDE1*"); // less memory than: tempPath.replace(",WIDE1*", ""); tempPath = cleanPathAsterisks(path);
if (wide1AsteriskIndex != -1) { if (tempPath.indexOf("WIDE2-1") != -1) {
tempPath.remove(wide1AsteriskIndex, 7);
}
int asteriskIndex = path.indexOf("*"); // less memory than: tempPath.replace("*", "");
if (asteriskIndex != -1) {
tempPath.remove(asteriskIndex, 1);
}
if (path.indexOf("WIDE2-1") != -1) {
tempPath.replace("WIDE2-1", stationCallsign + "*"); tempPath.replace("WIDE2-1", stationCallsign + "*");
} else if (path.indexOf("WIDE2-2") != -1) { } else if (tempPath.indexOf("WIDE2-2") != -1) {
tempPath.replace("WIDE2-2", stationCallsign + "*,WIDE2-1"); tempPath.replace("WIDE2-2", stationCallsign + "*,WIDE2-1");
} else { } else {
return ""; return "";
} }
} }
packetToRepeat = packet.substring(0, packet.indexOf(",") + 1);
packetToRepeat += tempPath; packetToRepeat += tempPath;
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(packet.indexOf(thirdParty ? ":}" : ":")));
return packetToRepeat;
} else { // CrossFreq Digipeater } else { // CrossFreq Digipeater
String suffix = thirdParty ? ":}" : ":"; packetToRepeat = cleanPathAsterisks(packet.substring(0, suffixIndex));
int suffixIndex = packet.indexOf(suffix); if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
String packetToRepeat = packet.substring(0, suffixIndex);
String terms[] = {",WIDE1*", ",WIDE2*", "*"};
for (String term : terms) {
int index = packetToRepeat.indexOf(term);
if (index != -1) {
packetToRepeat.remove(index, term.length());
}
}
packetToRepeat += ","; packetToRepeat += ",";
packetToRepeat += stationCallsign; packetToRepeat += stationCallsign;
packetToRepeat += "*"; packetToRepeat += "*";
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(suffixIndex));
return packetToRepeat;
} }
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(suffixIndex));
return packetToRepeat;
} }
String generateDigipeatedPacket(const String& packet, bool thirdParty){ String generateDigipeatedPacket(const String& packet, bool thirdParty){
String temp; String temp;
if (thirdParty) { // only header is used if (thirdParty) { // only header is used
const String& header = packet.substring(0, packet.indexOf(":}")); const String& header = packet.substring(0, packet.indexOf(":}"));
temp = header.substring(header.indexOf(">") + 1); temp = header.substring(header.indexOf(">") + 1);
} else { } else {
@@ -104,7 +99,7 @@ namespace DIGI_Utils {
int digiMode = Config.digi.mode; int digiMode = Config.digi.mode;
bool crossFreq = abs(Config.loramodule.txFreq - Config.loramodule.rxFreq) >= 125000; // CrossFreq Digi bool crossFreq = abs(Config.loramodule.txFreq - Config.loramodule.rxFreq) >= 125000; // CrossFreq Digi
if (commaIndex > 2) { // Packet has "path" if (commaIndex > 2) { // "path" found
const String& path = temp.substring(commaIndex + 1); const String& path = temp.substring(commaIndex + 1);
if (digiMode == 2 || backupDigiMode) { if (digiMode == 2 || backupDigiMode) {
bool hasWide = path.indexOf("WIDE1-1") != -1; bool hasWide = path.indexOf("WIDE1-1") != -1;
@@ -154,28 +149,28 @@ namespace DIGI_Utils {
if (Sender == stationCallsign) return; // Avoid listening to self packets if (Sender == stationCallsign) return; // Avoid listening to self packets
if (!thirdPartyPacket && Config.tacticalCallsign == "" && !Utils::callsignIsValid(Sender)) return; // No thirdParty + no tactical y no valid callsign if (!thirdPartyPacket && Config.tacticalCallsign == "" && !Utils::callsignIsValid(Sender)) return; // No thirdParty + no tactical y no valid callsign
if (STATION_Utils::check25SegBuffer(Sender, temp.substring(temp.indexOf(":") + 2))) { if (STATION_Utils::isIn25SegHashBuffer(Sender, temp.substring(temp.indexOf(":") + 2))) return;
STATION_Utils::updateLastHeard(Sender);
Utils::typeOfPacket(temp, 2); // Digi STATION_Utils::updateLastHeard(Sender);
bool queryMessage = false; Utils::typeOfPacket(temp, 2); // Digi
int doubleColonIndex = temp.indexOf("::"); bool queryMessage = false;
if (doubleColonIndex > 10) { // it's a message int doubleColonIndex = temp.indexOf("::");
String AddresseeAndMessage = temp.substring(doubleColonIndex + 2); if (doubleColonIndex > 10) { // it's a message
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":")); String AddresseeAndMessage = temp.substring(doubleColonIndex + 2);
Addressee.trim(); String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
if (Addressee == stationCallsign) { // it's a message for me! Addressee.trim();
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket); if (Addressee == stationCallsign) { // it's a message for me!
} queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
}
if (!queryMessage) {
String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket);
if (loraPacket != "") {
STATION_Utils::addToOutputPacketBuffer(loraPacket);
if (Config.digi.ecoMode != 1) displayToggle(true);
lastScreenOn = millis();
}
} }
} }
if (queryMessage) return; // answer should not be repeated.
String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket);
if (loraPacket != "") {
STATION_Utils::addToOutputPacketBuffer(loraPacket);
if (Config.digi.ecoMode != 1) displayToggle(true);
lastScreenOn = millis();
}
} }
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -26,7 +26,7 @@
#ifdef HAS_TFT #ifdef HAS_TFT
#include <TFT_eSPI.h> #include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI(); TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft); TFT_eSprite sprite = TFT_eSprite(&tft);
#ifdef HELTEC_WIRELESS_TRACKER #ifdef HELTEC_WIRELESS_TRACKER
@@ -57,7 +57,7 @@
String lastEpaperText; String lastEpaperText;
#else #else
#include <Adafruit_GFX.h> #include <Adafruit_GFX.h>
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
#include <Adafruit_SH110X.h> #include <Adafruit_SH110X.h>
Adafruit_SH1106G display(128, 64, &Wire, OLED_RST); Adafruit_SH1106G display(128, 64, &Wire, OLED_RST);
#else #else
@@ -117,7 +117,7 @@ void displaySetup() {
digitalWrite(OLED_RST, HIGH); digitalWrite(OLED_RST, HIGH);
#endif #endif
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
if (display.begin(0x3c, false)) { if (display.begin(0x3c, false)) {
displayFound = true; displayFound = true;
if (Config.display.turn180) display.setRotation(2); if (Config.display.turn180) display.setRotation(2);
@@ -157,7 +157,7 @@ void displayToggle(bool toggle) {
display.printCenter("EPAPER Display Disabled by toggle..."); display.printCenter("EPAPER Display Disabled by toggle...");
display.update(); display.update();
#else #else
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
if (displayFound) display.oled_command(SH110X_DISPLAYON); if (displayFound) display.oled_command(SH110X_DISPLAYON);
#else #else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYON); if (displayFound) display.ssd1306_command(SSD1306_DISPLAYON);
@@ -171,12 +171,12 @@ void displayToggle(bool toggle) {
#ifdef HAS_EPAPER #ifdef HAS_EPAPER
display.update(); display.update();
#else #else
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
if (displayFound) display.oled_command(SH110X_DISPLAYOFF); if (displayFound) display.oled_command(SH110X_DISPLAYOFF);
#else #else
if (displayFound) display.ssd1306_command(SSD1306_DISPLAYOFF); if (displayFound) display.ssd1306_command(SSD1306_DISPLAYOFF);
#endif #endif
#endif #endif
#endif #endif
} }
@@ -222,7 +222,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else #else
if (displayFound) { if (displayFound) {
display.clearDisplay(); display.clearDisplay();
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
display.setTextColor(SH110X_WHITE); display.setTextColor(SH110X_WHITE);
#else #else
display.setTextColor(WHITE); display.setTextColor(WHITE);
@@ -234,7 +234,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 8 + (8 * i)); display.setCursor(0, 8 + (8 * i));
display.println(*lines[i]); display.println(*lines[i]);
} }
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
display.setContrast(1); display.setContrast(1);
#else #else
display.ssd1306_command(SSD1306_SETCONTRAST); display.ssd1306_command(SSD1306_SETCONTRAST);
@@ -288,7 +288,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
#else #else
if (displayFound) { if (displayFound) {
display.clearDisplay(); display.clearDisplay();
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
display.setTextColor(SH110X_WHITE); display.setTextColor(SH110X_WHITE);
#else #else
display.setTextColor(WHITE); display.setTextColor(WHITE);
@@ -301,7 +301,7 @@ void displayShow(const String& header, const String& line1, const String& line2,
display.setCursor(0, 16 + (8 * i)); display.setCursor(0, 16 + (8 * i));
display.println(*lines[i]); display.println(*lines[i]);
} }
#if defined(TTGO_T_Beam_S3_SUPREME_V3) #ifdef HAS_SH1106
display.setContrast(1); display.setContrast(1);
#else #else
display.ssd1306_command(SSD1306_SETCONTRAST); display.ssd1306_command(SSD1306_SETCONTRAST);

View File

@@ -29,29 +29,20 @@ bool validateKISSFrame(const String& kissFormattedFrame) {
} }
String encodeAddressAX25(String tnc2Address) { String encodeAddressAX25(String tnc2Address) {
bool hasBeenDigipited = tnc2Address.indexOf('*') != -1; bool hasBeenDigipited = tnc2Address.indexOf('*') != -1;
int tnc2AddressIndex = tnc2Address.indexOf('-'); if (tnc2Address.indexOf('-') == -1) {
if (tnc2AddressIndex == -1) { if (hasBeenDigipited) tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
if (hasBeenDigipited) {
tnc2Address = tnc2Address.substring(0, tnc2Address.length() - 1);
}
tnc2Address += "-0"; tnc2Address += "-0";
} }
int separatorIndex = tnc2AddressIndex; int separatorIndex = tnc2Address.indexOf('-');;
int ssid = tnc2Address.substring(separatorIndex + 1).toInt(); int ssid = tnc2Address.substring(separatorIndex + 1).toInt();
String kissAddress = ""; String kissAddress = "";
for (int i = 0; i < 6; ++i) { for (int i = 0; i < 6; ++i) {
char addressChar; char addressChar = ' ';
if (tnc2Address.length() > i && i < separatorIndex) { if (tnc2Address.length() > i && i < separatorIndex) addressChar = tnc2Address.charAt(i);
addressChar = tnc2Address.charAt(i);
} else {
addressChar = ' ';
}
kissAddress += (char)(addressChar << 1); kissAddress += (char)(addressChar << 1);
} }
kissAddress += (char)((ssid << 1) | 0b01100000 | (hasBeenDigipited ? HAS_BEEN_DIGIPITED_MASK : 0)); kissAddress += (char)((ssid << 1) | 0b01100000 | (hasBeenDigipited ? HAS_BEEN_DIGIPITED_MASK : 0));
return kissAddress; return kissAddress;
} }

View File

@@ -1,24 +1,24 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <RadioLib.h> #include <RadioLib.h>
#include <WiFi.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "aprs_is_utils.h" #include "aprs_is_utils.h"
#include "station_utils.h" #include "station_utils.h"
#include "board_pinout.h" #include "board_pinout.h"
@@ -29,6 +29,7 @@
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
extern uint32_t lastRxTime; extern uint32_t lastRxTime;
extern bool packetIsBeacon; extern bool packetIsBeacon;
@@ -43,7 +44,7 @@ bool transmitFlag = true;
#ifdef HAS_SX1268 #ifdef HAS_SX1268
#if defined(LIGHTGATEWAY_1_0) || defined(LIGHTGATEWAY_PLUS_1_0) #if defined(LIGHTGATEWAY_1_0) || defined(LIGHTGATEWAY_PLUS_1_0)
SPIClass loraSPI(FSPI); SPIClass loraSPI(FSPI);
SX1268 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN, loraSPI); SX1268 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN, loraSPI);
#else #else
SX1268 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); SX1268 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
#endif #endif
@@ -100,11 +101,11 @@ namespace LoRa_Utils {
while (true); while (true);
} }
#endif*/ #endif*/
radio.setSpreadingFactor(Config.loramodule.rxSpreadingFactor); radio.setSpreadingFactor(Config.loramodule.rxSpreadingFactor);
radio.setCodingRate(Config.loramodule.rxCodingRate4); radio.setCodingRate(Config.loramodule.rxCodingRate4);
float signalBandwidth = Config.loramodule.rxSignalBandwidth / 1000; float signalBandwidth = Config.loramodule.rxSignalBandwidth / 1000;
radio.setBandwidth(signalBandwidth); radio.setBandwidth(signalBandwidth);
radio.setCRC(true); radio.setCRC(true);
#if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // QRP Labs LightGateway has 400M22S (SX1268) #if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // QRP Labs LightGateway has 400M22S (SX1268)
@@ -174,14 +175,14 @@ namespace LoRa_Utils {
changeFreqTx(); changeFreqTx();
} }
} }
#ifdef INTERNAL_LED_PIN #ifdef INTERNAL_LED_PIN
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
#endif #endif
int state = radio.transmit("\x3c\xff\x01" + newPacket); int state = radio.transmit("\x3c\xff\x01" + newPacket);
transmitFlag = true; transmitFlag = true;
if (state == RADIOLIB_ERR_NONE) { if (state == RADIOLIB_ERR_NONE) {
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) { if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
} }
Utils::print("---> LoRa Packet Tx : "); Utils::print("---> LoRa Packet Tx : ");
@@ -243,7 +244,7 @@ namespace LoRa_Utils {
receivedPackets.push_back(receivedPacket); receivedPackets.push_back(receivedPacket);
} }
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) { if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
} }
} else { } else {
@@ -257,7 +258,7 @@ namespace LoRa_Utils {
snr = radio.getSNR(); snr = radio.getSNR();
freqError = radio.getFrequencyError(); freqError = radio.getFrequencyError();
Utils::println(F("CRC error!")); Utils::println(F("CRC error!"));
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) { if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
} }
packet = ""; packet = "";

View File

@@ -1,22 +1,22 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <WiFiClientSecure.h> #include <WiFiClient.h>
#include <PubSubClient.h> #include <PubSubClient.h>
#include "configuration.h" #include "configuration.h"
#include "station_utils.h" #include "station_utils.h"
@@ -95,7 +95,7 @@ namespace MQTT_Utils {
void setup() { void setup() {
if (!Config.mqtt.active) return; if (!Config.mqtt.active) return;
pubSub.setClient(mqttClient); pubSub.setClient(mqttClient);
pubSub.setCallback(receivedFromMqtt); pubSub.setCallback(receivedFromMqtt);
} }
} }

333
src/network_manager.cpp Normal file
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;
}

View File

@@ -1,52 +1,66 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <NTPClient.h> #include <NTPClient.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <WiFi.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "ntp_utils.h" #include "ntp_utils.h"
#include "time.h" #include "time.h"
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
WiFiUDP ntpUDP; WiFiUDP ntpUDP;
NTPClient* timeClient; NTPClient* timeClient = nullptr;
namespace NTP_Utils { namespace NTP_Utils {
void setup() { bool setup() {
if (WiFi.status() == WL_CONNECTED && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") { if (networkManager->isConnected() && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
int gmt = Config.ntp.gmtCorrection * 3600; int gmt = Config.ntp.gmtCorrection * 3600;
Serial.println("[NTP] Setting up, TZ offset: " + String(gmt) + " Server: " + Config.ntp.server);
timeClient = new NTPClient(ntpUDP, Config.ntp.server.c_str(), gmt, 15 * 60 * 1000); // Update interval 15 min timeClient = new NTPClient(ntpUDP, Config.ntp.server.c_str(), gmt, 15 * 60 * 1000); // Update interval 15 min
timeClient->begin(); timeClient->begin();
return true;
} }
return false;
} }
void update() { void update() {
if (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() { 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"; return "DigiEcoMode Active";
} }
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */

View File

@@ -263,8 +263,8 @@ namespace POWER_Utils {
pinMode(Config.battery.externalVoltagePin, INPUT); pinMode(Config.battery.externalVoltagePin, INPUT);
} }
#ifdef VEXT_CTRL #ifdef VEXT_CTRL_PIN
pinMode(VEXT_CTRL,OUTPUT); // GPS + TFT on HELTEC Wireless_Tracker and only for Oled in HELTEC V3 pinMode(VEXT_CTRL_PIN,OUTPUT); // GPS + TFT on HELTEC Wireless_Tracker and only for Oled in HELTEC V3
vext_ctrl_ON(); vext_ctrl_ON();
#endif #endif
@@ -272,8 +272,8 @@ namespace POWER_Utils {
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) activateGPS(); if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) activateGPS();
#endif #endif
#ifdef ADC_CTRL #ifdef ADC_CTRL_PIN
pinMode(ADC_CTRL, OUTPUT); pinMode(ADC_CTRL_PIN, OUTPUT);
adc_ctrl_OFF(); adc_ctrl_OFF();
#endif #endif

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -25,6 +25,8 @@
#include "utils.h" #include "utils.h"
#include <vector> #include <vector>
#define SECS_TO_WAIT 3 // soon to be deleted...
extern Configuration Config; extern Configuration Config;
extern uint32_t lastRxTime; extern uint32_t lastRxTime;
@@ -40,17 +42,16 @@ std::vector<LastHeardStation> lastHeardObjects;
struct OutputPacketBuffer { struct OutputPacketBuffer {
String packet; String packet;
bool isBeacon; bool isBeacon;
OutputPacketBuffer(const String& p, bool b) : packet(p), isBeacon(b) {}
}; };
std::vector<OutputPacketBuffer> outputPacketBuffer; std::vector<OutputPacketBuffer> outputPacketBuffer;
struct Packet25SegBuffer { struct Packet25SegBuffer {
uint32_t receivedTime; uint32_t receivedTime;
String station; uint32_t hash;
String payload;
}; };
std::vector<Packet25SegBuffer> packet25SegBuffer; std::vector<Packet25SegBuffer> packet25SegBuffer;
bool saveNewDigiEcoModeConfig = false; bool saveNewDigiEcoModeConfig = false;
bool packetIsBeacon = false; bool packetIsBeacon = false;
@@ -59,19 +60,18 @@ namespace STATION_Utils {
std::vector<String> loadCallsignList(const String& list) { std::vector<String> loadCallsignList(const String& list) {
std::vector<String> loadedList; std::vector<String> loadedList;
int start = 0;
int listLength = list.length();
String callsigns = list; while (start < listLength) {
callsigns.trim(); while (start < listLength && list[start] == ' ') start++; // avoid blank spaces
if (start >= listLength) break;
while (callsigns.length() > 0) { // != "" int end = start;
int spaceIndex = callsigns.indexOf(" "); while (end < listLength && list[end] != ' ') end++; // find another blank space or reach listLength
if (spaceIndex == -1) { // No more spaces, add the last part
loadedList.push_back(callsigns); loadedList.emplace_back(list.substring(start, end));
break; start = end + 1; // keep on searching if listLength not reached
}
loadedList.push_back(callsigns.substring(0, spaceIndex));
callsigns = callsigns.substring(spaceIndex + 1);
callsigns.trim(); // Trim in case of multiple spaces
} }
return loadedList; return loadedList;
} }
@@ -85,8 +85,9 @@ namespace STATION_Utils {
for (size_t i = 0; i < list.size(); i++) { for (size_t i = 0; i < list.size(); i++) {
int wildcardIndex = list[i].indexOf("*"); int wildcardIndex = list[i].indexOf("*");
if (wildcardIndex >= 0) { if (wildcardIndex >= 0) {
String wildcard = list[i].substring(0, wildcardIndex); if (wildcardIndex >= 2 && callsign.length() >= wildcardIndex && strncmp(callsign.c_str(), list[i].c_str(), wildcardIndex) == 0) {
if (callsign.startsWith(wildcard)) return true; return true;
}
} else { } else {
if (list[i] == callsign) return true; if (list[i] == callsign) return true;
} }
@@ -127,36 +128,33 @@ namespace STATION_Utils {
} }
void deleteNotHeard() { void deleteNotHeard() {
std::vector<LastHeardStation> lastHeardStation_temp; uint32_t currentTime = millis();
for (int i = 0; i < lastHeardStations.size(); i++) { uint32_t timeout = Config.rememberStationTime * 60UL * 1000UL;
if (millis() - lastHeardStations[i].lastHeardTime < Config.rememberStationTime * 60 * 1000) {
lastHeardStation_temp.push_back(lastHeardStations[i]); for (int i = lastHeardStations.size() - 1; i >= 0; i--) {
if (currentTime - lastHeardStations[i].lastHeardTime >= timeout) {
lastHeardStations.erase(lastHeardStations.begin() + i);
} }
} }
lastHeardStations.clear();
for (int j = 0; j < lastHeardStation_temp.size(); j++) {
lastHeardStations.push_back(lastHeardStation_temp[j]);
}
lastHeardStation_temp.clear();
} }
void updateLastHeard(const String& station) { void updateLastHeard(const String& station) {
deleteNotHeard(); deleteNotHeard();
bool stationHeard = false; uint32_t currentTime = millis();
for (int i = 0; i < lastHeardStations.size(); i++) { for (size_t i = 0; i < lastHeardStations.size(); i++) {
if (lastHeardStations[i].station == station) { if (lastHeardStations[i].station == station) {
lastHeardStations[i].lastHeardTime = millis(); lastHeardStations[i].lastHeardTime = currentTime;
stationHeard = true; Utils::showActiveStations();
break; return;
} }
} }
if (!stationHeard) lastHeardStations.emplace_back(LastHeardStation{millis(), station}); lastHeardStations.emplace_back(LastHeardStation{currentTime, station});
Utils::showActiveStations(); Utils::showActiveStations();
} }
bool wasHeard(const String& station) { bool wasHeard(const String& station) {
deleteNotHeard(); deleteNotHeard();
for (int i = 0; i < lastHeardStations.size(); i++) { for (size_t i = 0; i < lastHeardStations.size(); i++) {
if (lastHeardStations[i].station == station) { if (lastHeardStations[i].station == station) {
Utils::println(" ---> Listened Station"); Utils::println(" ---> Listened Station");
return true; return true;
@@ -166,18 +164,33 @@ namespace STATION_Utils {
return false; return false;
} }
void clean25SegBuffer() { void clean25SegHashBuffer() {
if (!packet25SegBuffer.empty() && (millis() - packet25SegBuffer[0].receivedTime) > 25 * 1000) packet25SegBuffer.erase(packet25SegBuffer.begin()); uint32_t currentTime = millis();
} for (int i = packet25SegBuffer.size() - 1; i >= 0; i--) {
if ((currentTime - packet25SegBuffer[i].receivedTime) > 25 * 1000) {
bool check25SegBuffer(const String& station, const String& textMessage) { packet25SegBuffer.erase(packet25SegBuffer.begin() + i);
if (!packet25SegBuffer.empty()) {
for (int i = 0; i < packet25SegBuffer.size(); i++) {
if (packet25SegBuffer[i].station == station && packet25SegBuffer[i].payload == textMessage) return false;
} }
} }
packet25SegBuffer.emplace_back(Packet25SegBuffer{millis(), station, textMessage}); }
return true;
uint32_t makeHash(const String& station, const String& payload) { // DJB2 Hash
uint32_t h = 5381;
for (size_t i = 0; i < station.length(); i++)
h = ((h << 5) + h) + station[i];
for (size_t i = 0; i < payload.length(); i++)
h = ((h << 5) + h) + payload[i];
return h;
}
bool isIn25SegHashBuffer(const String& station, const String& textMessage) {
clean25SegHashBuffer();
uint32_t newHash = makeHash(station, textMessage);
uint32_t currentTime = millis();
for (int i = 0; i < packet25SegBuffer.size(); i++) {
if (packet25SegBuffer[i].hash == newHash) return true;
}
packet25SegBuffer.push_back({currentTime, newHash});
return false;
} }
void processOutputPacketBufferUltraEcoMode() { void processOutputPacketBufferUltraEcoMode() {
@@ -201,7 +214,7 @@ namespace STATION_Utils {
} }
void processOutputPacketBuffer() { void processOutputPacketBuffer() {
int timeToWait = 3 * 1000; // 3 segs between packet Tx and also Rx ??? int timeToWait = SECS_TO_WAIT * 1000; // 3 segs between packet Tx and also Rx ???
uint32_t lastRx = millis() - lastRxTime; uint32_t lastRx = millis() - lastRxTime;
uint32_t lastTx = millis() - lastTxTime; uint32_t lastTx = millis() - lastTxTime;
if (outputPacketBuffer.size() > 0 && lastTx > timeToWait && lastRx > timeToWait) { if (outputPacketBuffer.size() > 0 && lastTx > timeToWait && lastRx > timeToWait) {
@@ -229,11 +242,7 @@ namespace STATION_Utils {
} }
void addToOutputPacketBuffer(const String& packet, bool flag) { void addToOutputPacketBuffer(const String& packet, bool flag) {
OutputPacketBuffer entry; outputPacketBuffer.emplace_back(OutputPacketBuffer{packet, flag});
entry.packet = packet;
entry.isBeacon = flag;
outputPacketBuffer.push_back(entry);
} }
} }

View File

@@ -17,13 +17,14 @@
*/ */
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <WiFi.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "syslog_utils.h" #include "syslog_utils.h"
#include "gps_utils.h" #include "gps_utils.h"
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
extern String versionDate; extern String versionDate;
extern String versionNumber; extern String versionNumber;
@@ -33,7 +34,7 @@ WiFiUDP udpClient;
namespace SYSLOG_Utils { namespace SYSLOG_Utils {
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) { void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
if (Config.syslog.active && WiFi.status() == WL_CONNECTED) { if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = "<165>1 - "; String syslogPacket = "<165>1 - ";
syslogPacket.concat(Config.callsign); syslogPacket.concat(Config.callsign);
syslogPacket.concat(" CA2RXU_LoRa_iGate_"); syslogPacket.concat(" CA2RXU_LoRa_iGate_");
@@ -140,7 +141,7 @@ namespace SYSLOG_Utils {
} }
void setup() { void setup() {
if (WiFi.status() == WL_CONNECTED) { if (networkManager->isConnected()) {
udpClient.begin(0); udpClient.begin(0);
if (Config.syslog.active) Serial.println("init : Syslog Server ... done! (at " + Config.syslog.server + ")"); if (Config.syslog.active) Serial.println("init : Syslog Server ... done! (at " + Config.syslog.server + ")");
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -29,10 +29,11 @@
#include "display.h" #include "display.h"
extern Configuration Config; extern Configuration Config;
extern bool sendStartTelemetry;
int telemetryCounter = random(1,999); int telemetryCounter = random(1,999);
uint32_t telemetryEUPTime = 0;
bool sendEUP = false; // Equations Units Parameters
namespace TELEMETRY_Utils { namespace TELEMETRY_Utils {
@@ -89,7 +90,7 @@ namespace TELEMETRY_Utils {
sendBaseTelemetryPacket("EQNS.", getEquationCoefficients()); sendBaseTelemetryPacket("EQNS.", getEquationCoefficients());
sendBaseTelemetryPacket("UNIT.", getUnitLabels()); sendBaseTelemetryPacket("UNIT.", getUnitLabels());
sendBaseTelemetryPacket("PARM.", getParameterNames()); sendBaseTelemetryPacket("PARM.", getParameterNames());
sendStartTelemetry = false; sendEUP = false;
} }
String generateEncodedTelemetryBytes(float value, bool counterBytes, byte telemetryType) { String generateEncodedTelemetryBytes(float value, bool counterBytes, byte telemetryType) {
@@ -106,7 +107,7 @@ namespace TELEMETRY_Utils {
case 3: tempValue = (value * 8); break; // Pressure case 3: tempValue = (value * 8); break; // Pressure
default: tempValue = value; break; default: tempValue = value; break;
} }
} }
int firstByte = tempValue / 91; int firstByte = tempValue / 91;
tempValue -= firstByte * 91; tempValue -= firstByte * 91;
@@ -127,4 +128,11 @@ namespace TELEMETRY_Utils {
return telemetry; return telemetry;
} }
void checkEUPInterval() {
if (telemetryEUPTime == 0 || millis() - telemetryEUPTime > 24UL * 60UL * 60UL * 1000UL) {
sendEUP = true;
telemetryEUPTime = millis();
}
}
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -45,7 +45,7 @@ String inputSerialBuffer = "";
namespace TNC_Utils { namespace TNC_Utils {
void setup() { void setup() {
if (Config.tnc.enableServer && Config.digi.ecoMode == 0) { if (Config.tnc.enableServer && Config.digi.ecoMode == 0) {
tncServer.stop(); tncServer.stop();

View File

@@ -18,9 +18,9 @@
#include <APRSPacketLib.h> #include <APRSPacketLib.h>
#include <TinyGPS++.h> #include <TinyGPS++.h>
#include <WiFi.h>
#include "telemetry_utils.h" #include "telemetry_utils.h"
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "station_utils.h" #include "station_utils.h"
#include "battery_utils.h" #include "battery_utils.h"
#include "aprs_is_utils.h" #include "aprs_is_utils.h"
@@ -37,6 +37,7 @@
#define DAY_MS (24UL * 60UL * 60UL * 1000UL) #define DAY_MS (24UL * 60UL * 60UL * 1000UL)
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
extern TinyGPSPlus gps; extern TinyGPSPlus gps;
extern String versionDate; extern String versionDate;
extern String firstLine; extern String firstLine;
@@ -52,17 +53,16 @@ extern int rssi;
extern float snr; extern float snr;
extern int freqError; extern int freqError;
extern String distance; extern String distance;
extern bool WiFiConnected;
extern int wxModuleType; extern int wxModuleType;
extern bool backupDigiMode; extern bool backupDigiMode;
extern bool shouldSleepLowVoltage; extern bool shouldSleepLowVoltage;
extern bool transmitFlag; extern bool transmitFlag;
extern bool passcodeValid; extern bool passcodeValid;
extern bool sendEUP; // Equations Units Parameters
extern std::vector<LastHeardStation> lastHeardStations; extern std::vector<LastHeardStation> lastHeardStations;
bool statusAfterBoot = true; bool statusUpdate = true;
bool sendStartTelemetry = true;
bool beaconUpdate = false; bool beaconUpdate = false;
uint32_t lastBeaconTx = 0; uint32_t lastBeaconTx = 0;
uint32_t lastScreenOn = millis(); uint32_t lastScreenOn = millis();
@@ -75,37 +75,43 @@ String secondaryBeaconPacket;
namespace Utils { namespace Utils {
void processStatus() { 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) { if (!sendOverAPRSIS && !sendOverRF) {
delay(1000); statusUpdate = false;
status.concat(",qAC:>"); return;
status.concat(Config.beacon.statusPacket);
APRS_IS_Utils::upload(status);
SYSLOG_Utils::log(2, status, 0, 0.0, 0); // APRSIS TX
} }
if (statusAfterBoot && !Config.beacon.sendViaAPRSIS && Config.beacon.sendViaRF) {
status.concat(":>"); String statusPacket = APRSPacketLib::generateBasePacket(Config.callsign, "APLRG1", Config.beacon.path);
status.concat(Config.beacon.statusPacket); statusPacket += sendOverAPRSIS ? ",qAC:>" : ":>";
STATION_Utils::addToOutputPacketBuffer(status, true); // treated also as beacon on Tx Freq 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(); lastStatusTx = millis();
} }
void checkStatusInterval() { void checkStatusInterval() {
if (lastStatusTx == 0 || millis() - lastStatusTx > DAY_MS) statusAfterBoot = true; if (lastStatusTx == 0 || millis() - lastStatusTx > DAY_MS) statusUpdate = true;
} }
String getLocalIP() { String getLocalIP() {
if (Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) { if (Config.digi.ecoMode == 1 || Config.digi.ecoMode == 2) {
return "** WiFi AP Killed **"; return "** WiFi AP Killed **";
} else if (!WiFiConnected) { } else if (networkManager->isEthernetConnected()) {
return "IP : 192.168.4.1"; return "LAN: " + networkManager->getEthernetIP().toString();
} else if (!networkManager->isWiFiConnected() && networkManager->isWifiAPActive()) {
return "IP : " + networkManager->getWiFiAPIP().toString();
} else if (backupDigiMode) { } else if (backupDigiMode) {
return "- BACKUP DIGI MODE -"; return "- BACKUP DIGI MODE -";
} else { } else {
return "IP : " + String(WiFi.localIP()[0]) + "." + String(WiFi.localIP()[1]) + "." + String(WiFi.localIP()[2]) + "." + String(WiFi.localIP()[3]); return "IP : " + networkManager->getWiFiIP().toString();
} }
} }
@@ -157,7 +163,8 @@ namespace Utils {
if (beaconUpdate) { if (beaconUpdate) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true); if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
if (sendStartTelemetry && TELEMETRY_Utils::checkEUPInterval();
if (sendEUP &&
Config.battery.sendVoltageAsTelemetry && Config.battery.sendVoltageAsTelemetry &&
!Config.wxsensor.active && !Config.wxsensor.active &&
(Config.battery.sendInternalVoltage || Config.battery.sendExternalVoltage) && (Config.battery.sendInternalVoltage || Config.battery.sendExternalVoltage) &&
@@ -290,7 +297,7 @@ namespace Utils {
} }
checkStatusInterval(); checkStatusInterval();
if (statusAfterBoot && Config.beacon.statusActive && !Config.beacon.statusPacket.isEmpty()) processStatus(); if (statusUpdate && Config.beacon.statusActive && !Config.beacon.statusPacket.isEmpty()) processStatus();
} }
void checkDisplayInterval() { void checkDisplayInterval() {
@@ -403,9 +410,9 @@ namespace Utils {
if (mode == 1) { // low voltage detected after a while if (mode == 1) { // low voltage detected after a while
displayToggle(false); displayToggle(false);
} }
#ifdef VEXT_CTRL #ifdef VEXT_CTRL_PIN
#ifndef HELTEC_WSL_V3 #ifndef HELTEC_WSL_V3
digitalWrite(VEXT_CTRL, LOW); digitalWrite(VEXT_CTRL_PIN, LOW);
#endif #endif
#endif #endif
LoRa_Utils::sleepRadio(); LoRa_Utils::sleepRadio();
@@ -417,44 +424,53 @@ namespace Utils {
bool callsignIsValid(const String& callsign) { bool callsignIsValid(const String& callsign) {
if (callsign == "WLNK-1") return true; if (callsign == "WLNK-1") return true;
int totalCallsignLength = callsign.length();
if (totalCallsignLength < 4) return false;
String cleanCallsign; int hyphenIndex = callsign.indexOf("-");
int hypenCallsignIndex = callsign.indexOf("-"); int baseCallsignLength = (hyphenIndex > 0) ? hyphenIndex : totalCallsignLength;
if (hypenCallsignIndex > 0) { // SSID Validation
cleanCallsign = callsign.substring(0, hypenCallsignIndex); if (hyphenIndex > 0) { // SSID Validation
String ssid = callsign.substring(hypenCallsignIndex + 1); if (hyphenIndex < 4) return false; // base Callsign must have at least 4 characters
if (ssid.indexOf("-") != -1 || ssid.length() > 2) return false; int ssidStart = hyphenIndex + 1;
if (ssid.length() == 2 && ssid[0] == '0') return false; int ssidLength = totalCallsignLength - ssidStart;
for (int i = 0; i < ssid.length(); i++) { if (ssidLength == 0 || ssidLength > 2) return false;
if (!isAlphaNumeric(ssid[i])) return false; if (callsign.indexOf('-', ssidStart) != -1) return false; // avoid another "-" in ssid
if (ssidLength == 2 && callsign[ssidStart] == '0') return false; // ssid can't start with "0"
for (int i = ssidStart; i < totalCallsignLength; i++) {
if (!isDigit(callsign[i])) return false;
} }
}
if (baseCallsignLength < 4 || baseCallsignLength > 6) return false;
bool padded = false; // Callsigns with 4 characters like A0AA are padded into 5 characters for further analisis : A0AA --> _A0AA
if (baseCallsignLength == 4 && isAlpha(callsign[0]) && isDigit(callsign[1]) && isAlpha(callsign[2]) && isAlpha(callsign[3])) padded = true;
char c0, c1, c2, c3;
if (padded) {
c0 = ' ';
c1 = callsign[0];
c2 = callsign[1];
c3 = callsign[2];
} else { } else {
cleanCallsign = callsign; c0 = callsign[0];
c1 = callsign[1];
c2 = callsign[2];
c3 = callsign[3];
}
if (!isDigit(c2) || !isAlpha(c3)) { // __0A__ must be validated
if (c0 != 'R' && !isDigit(c1) && !isAlpha(c2)) return false; // to accepto R0A___
} }
if (cleanCallsign.length() < 4 || cleanCallsign.length() > 6) return false; bool isValid =
((isAlphaNumeric(c0) || c0 == ' ') && isAlpha(c1)) || // AA0A (+A+A) + _A0AA (+A) + 0A0A (+A+A)
(isAlpha(c0) && isDigit(c1)) || // A00A (+A+A)
(c0 == 'R' && baseCallsignLength == 6 && isDigit(c1) && isAlpha(c2) && isAlpha(c3) && isAlpha(callsign[4])); // R0AA (+A+A)
if (!isValid) return false; // also 00__ avoided
if (cleanCallsign.length() < 6 && isAlpha(cleanCallsign[0]) && isDigit(cleanCallsign[1]) && isAlpha(cleanCallsign[2]) && isAlpha(cleanCallsign[3]) ) { if (baseCallsignLength > 4 ) { // to validate ____AA
cleanCallsign = " " + cleanCallsign; // A0AA --> _A0AA for (int i = 4; i < baseCallsignLength; i++) {
} if (!isAlpha(callsign[i])) return false;
if (!isDigit(cleanCallsign[2]) || !isAlpha(cleanCallsign[3])) { // __0A__ must be validated
if (cleanCallsign[0] != 'R' && !isDigit(cleanCallsign[1]) && !isAlpha(cleanCallsign[2])) return false; // to accepto R0A___
}
bool isValid = false;
if ((isAlphaNumeric(cleanCallsign[0]) || cleanCallsign[0] == ' ') && isAlpha(cleanCallsign[1])) {
isValid = true; // AA0A (+A+A) + _A0AA (+A) + 0A0A (+A+A)
} else if (isAlpha(cleanCallsign[0]) && isDigit(cleanCallsign[1])) {
isValid = true; // A00A (+A+A)
} else if (cleanCallsign[0] == 'R' && cleanCallsign.length() == 6 && isDigit(cleanCallsign[1]) && isAlpha(cleanCallsign[2]) && isAlpha(cleanCallsign[3]) && isAlpha(cleanCallsign[4])) {
isValid = true; // R0AA (+A+A)
}
if (!isValid) return false; // also 00__ avoided
if (cleanCallsign.length() > 4) { // to validate ____AA
for (int i = 5; i <= cleanCallsign.length(); i++) {
if (!isAlpha(cleanCallsign[i - 1])) return false;
} }
} }
return true; return true;
@@ -467,4 +483,4 @@ namespace Utils {
} }
} }
} }

View File

@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
@@ -88,7 +88,7 @@ namespace WEB_Utils {
return request->requestAuthentication(); return request->requestAuthentication();
File file = SPIFFS.open("/igate_conf.json"); File file = SPIFFS.open("/igate_conf.json");
String fileContent; String fileContent;
while(file.available()){ while(file.available()){
fileContent += String((char)file.read()); fileContent += String((char)file.read());
@@ -161,7 +161,7 @@ namespace WEB_Utils {
Config.callsign = getParamStringSafe("callsign", Config.callsign); Config.callsign = getParamStringSafe("callsign", Config.callsign);
Config.tacticalCallsign = getParamStringSafe("tacticalCallsign", Config.tacticalCallsign); Config.tacticalCallsign = getParamStringSafe("tacticalCallsign", Config.tacticalCallsign);
Config.wifiAutoAP.enabled = request->hasParam("wifi.autoAP.enabled", true);
Config.wifiAutoAP.password = getParamStringSafe("wifi.autoAP.password", Config.wifiAutoAP.password); Config.wifiAutoAP.password = getParamStringSafe("wifi.autoAP.password", Config.wifiAutoAP.password);
Config.wifiAutoAP.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout); Config.wifiAutoAP.timeout = getParamIntSafe("wifi.autoAP.timeout", Config.wifiAutoAP.timeout);
@@ -299,7 +299,7 @@ namespace WEB_Utils {
AsyncWebServerResponse *response = request->beginResponse(302, "text/html", ""); AsyncWebServerResponse *response = request->beginResponse(302, "text/html", "");
response->addHeader("Location", "/?success=1"); response->addHeader("Location", "/?success=1");
request->send(response); request->send(response);
displayToggle(false); displayToggle(false);
delay(500); delay(500);
ESP.restart(); ESP.restart();
@@ -309,7 +309,7 @@ namespace WEB_Utils {
errorPage += "<h1>Configuration Error:</h1>"; errorPage += "<h1>Configuration Error:</h1>";
errorPage += "<p>Couldn't save new configuration. Please try again.</p>"; errorPage += "<p>Couldn't save new configuration. Please try again.</p>";
errorPage += "<a href='/'>Back</a></body></html>"; errorPage += "<a href='/'>Back</a></body></html>";
AsyncWebServerResponse *response = request->beginResponse(500, "text/html", errorPage); AsyncWebServerResponse *response = request->beginResponse(500, "text/html", errorPage);
request->send(response); request->send(response);
} }

View File

@@ -1,23 +1,24 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU /* Copyright (C) 2025 Ricardo Guzman - CA2RXU
* *
* This file is part of LoRa APRS iGate. * This file is part of LoRa APRS iGate.
* *
* LoRa APRS iGate is free software: you can redistribute it and/or modify * LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* LoRa APRS iGate is distributed in the hope that it will be useful, * LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <WiFi.h> #include <WiFi.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h"
#include "board_pinout.h" #include "board_pinout.h"
#include "wifi_utils.h" #include "wifi_utils.h"
#include "display.h" #include "display.h"
@@ -25,16 +26,11 @@
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager;
extern uint8_t myWiFiAPIndex;
extern int myWiFiAPSize;
extern WiFi_AP *currentWiFi;
extern bool backupDigiMode; extern bool backupDigiMode;
extern uint32_t lastServerCheck; extern uint32_t lastServerCheck;
bool WiFiConnected = false;
uint32_t WiFiAutoAPTime = millis();
bool WiFiAutoAPStarted = false;
uint8_t wifiCounter = 0; uint8_t wifiCounter = 0;
uint32_t lastBackupDigiTime = millis(); uint32_t lastBackupDigiTime = millis();
uint32_t lastWiFiCheck = 0; uint32_t lastWiFiCheck = 0;
@@ -44,32 +40,35 @@ namespace WIFI_Utils {
void checkWiFi() { void checkWiFi() {
if (Config.digi.ecoMode != 0) return; if (Config.digi.ecoMode != 0) return;
if (!networkManager->hasWiFiNetworks()) {
return;
}
uint32_t currentTime = millis(); uint32_t currentTime = millis();
if (backupDigiMode) { if (backupDigiMode) {
if (WiFi.status() != WL_CONNECTED && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) { if (!networkManager->isWiFiConnected() && ((currentTime - lastBackupDigiTime) >= 15 * 60 * 1000)) {
Serial.println("*** Stopping BackUp Digi Mode ***"); Serial.println("*** Stopping BackUp Digi Mode ***");
backupDigiMode = false; backupDigiMode = false;
wifiCounter = 0; wifiCounter = 0;
} else if (WiFi.status() == WL_CONNECTED) { } else if (networkManager->isWiFiConnected()) {
Serial.println("*** WiFi Reconnect Success (Stopping Backup Digi Mode) ***"); Serial.println("*** WiFi Reconnect Success (Stopping Backup Digi Mode) ***");
backupDigiMode = false; backupDigiMode = false;
wifiCounter = 0; wifiCounter = 0;
} }
} }
if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !WiFiAutoAPStarted) { if (!backupDigiMode && ((currentTime - lastWiFiCheck) >= 30 * 1000) && !networkManager->isWifiAPActive()) {
lastWiFiCheck = currentTime; lastWiFiCheck = currentTime;
if (WiFi.status() == WL_CONNECTED) { if (networkManager->isWiFiConnected()) {
if (Config.digi.backupDigiMode && (currentTime - lastServerCheck > 30 * 1000)) { if (Config.digi.backupDigiMode && (currentTime - lastServerCheck > 30 * 1000)) {
Serial.println("*** Server Connection LOST → Backup Digi Mode ***"); Serial.println("*** Server Connection LOST → Backup Digi Mode ***");
backupDigiMode = true; backupDigiMode = true;
WiFi.disconnect();
lastBackupDigiTime = currentTime; lastBackupDigiTime = currentTime;
} }
} else { } else {
Serial.println("Reconnecting to WiFi..."); Serial.println("Reconnecting to WiFi...");
WiFi.disconnect();
WIFI_Utils::startWiFi(); WIFI_Utils::startWiFi();
if (Config.digi.backupDigiMode) wifiCounter++; if (Config.digi.backupDigiMode) wifiCounter++;
@@ -83,97 +82,47 @@ namespace WIFI_Utils {
} }
void startAutoAP() { void startAutoAP() {
WiFi.mode(WIFI_MODE_NULL); displayShow("", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
networkManager->setupAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
WiFi.mode(WIFI_AP);
WiFi.softAP(Config.callsign + "-AP", Config.wifiAutoAP.password);
WiFiAutoAPTime = millis();
WiFiAutoAPStarted = true;
} }
void startWiFi() { void startWiFi() {
bool startAP = false; networkManager->clearWiFiNetworks();
if (currentWiFi->ssid == "") { for (size_t i = 0; i < Config.wifiAPs.size(); i++) {
startAP = true; const WiFi_AP& wifiAP = Config.wifiAPs[i];
} else { if (wifiAP.ssid.isEmpty()) continue;
uint8_t wifiCounter = 0; networkManager->addWiFiNetwork(wifiAP.ssid, wifiAP.password);
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());
}
}
} }
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 #ifdef INTERNAL_LED_PIN
digitalWrite(INTERNAL_LED_PIN, LOW); digitalWrite(INTERNAL_LED_PIN, LOW);
#endif #endif
if (WiFi.status() == WL_CONNECTED) { if (networkManager->isWiFiConnected()) {
Serial.print("\nConnected as "); Serial.print("[WiFi] Connected as ");
Serial.print(WiFi.localIP()); Serial.print(networkManager->getWiFiIP());
Serial.print(" / MAC Address: "); Serial.print(" / MAC Address: ");
Serial.println(WiFi.macAddress()); Serial.println(networkManager->getWiFimacAddress());
displayShow("", " Connected!!", "" , " loading ...", 1000); displayShow("", " Connected!!", "" , " loading ...", 1000);
} else { } else {
startAP = true; Serial.println("[WiFi] Not connected to WiFi!");
if (Config.wifiAutoAP.enabled) {
Serial.println("\nNot connected to WiFi! Starting Auto AP"); Serial.println("Starting AP fallback...");
displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000); displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
} startAutoAP();
WiFiConnected = !startAP;
if (startAP) {
Serial.println("\nNot connected to WiFi! Starting Auto AP");
displayShow("", " Starting Auto AP", " Please connect to it " , " loading ...", 1000);
startAutoAP();
}
}
void checkAutoAPTimeout() {
if (WiFiAutoAPStarted && Config.wifiAutoAP.timeout > 0) {
if (WiFi.softAPgetStationNum() > 0) {
WiFiAutoAPTime = 0;
} else { } else {
if (WiFiAutoAPTime == 0) { displayShow("", " WiFi Not Connected!", "" , " loading ...", 1000);
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)");
}
} }
} }
} }
@@ -183,4 +132,4 @@ namespace WIFI_Utils {
btStop(); btStop();
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -49,7 +49,7 @@
#define INTERNAL_LED_PIN 25 #define INTERNAL_LED_PIN 25
#define BATTERY_PIN 37 #define BATTERY_PIN 37
#define ADC_CTRL 21 #define ADC_CTRL_PIN 21
#define ADC_CTRL_ON_STATE LOW #define ADC_CTRL_ON_STATE LOW
#endif #endif

View File

@@ -35,6 +35,7 @@
// Display // Display
#define HAS_DISPLAY #define HAS_DISPLAY
#define HAS_SH1106
#undef OLED_SDA #undef OLED_SDA
#undef OLED_SCL #undef OLED_SCL