Compare commits

..
Author SHA1 Message Date
Ricardo Guzman a472c307d3 cad timeout y oled update 2026-09-29 18:46:41 -03:00
Ricardo Guzman fc256de09c fix to crossfreq 2026-09-28 15:24:17 -03:00
Ricardo Guzman 9b5fa49e31 mejora digi moricef 2026-09-28 15:03:59 -03:00
Ricardo Guzman 102169c0aa gitignore claude update 2026-09-27 12:10:31 -03:00
Ricardo Guzman 295d1fa2ef picture update 2026-09-23 22:45:48 -03:00
Ricardo Guzman 033657836f QRP Labs Gateway update 2026-09-23 19:14:24 -03:00
Ricardo Guzman f739b90adc update Screen Display Eco fix 2026-09-23 15:29:51 -03:00
Ricardo Guzman 5e2341299a ESP32C3_SuperMini_DIY_LoRa_SX1278 added 2026-09-23 14:03:30 -03:00
Ricardo Guzman 8ba8981a32 NTP updated 2026-09-23 13:50:05 -03:00
Ricardo Guzman e52d4566ba WiFi loop protection 2026-09-23 12:39:07 -03:00
Ricardo Guzman c80eb9bd84 update of names 2026-09-23 12:08:57 -03:00
Ricardo Guzman 6b187da9f4 E32 400M30S 2026-09-23 11:28:17 -03:00
Ricardo Guzman ab27c11532 updated build again 2026-09-23 11:06:47 -03:00
Ricardo Guzman 43bfff5033 sin update build 2026-09-23 11:04:06 -03:00
Ricardo Guzman 8fa10a7813 RAK3312 and 3112 added 2026-09-23 11:02:34 -03:00
Ricardo Guzman 316c746936 WebUI fixes 2026-09-23 05:12:51 -03:00
Ricardo Guzman 3618558b21 update and clean code 2026-09-22 21:17:33 -03:00
Ricardo Guzman e1eda2e64a updated aprspacket lib y otros refactoring 2026-09-22 19:59:24 -03:00
Ricardo Guzman 759cddd0e5 date update 2026-09-22 14:28:29 -03:00
Ricardo Guzman 8186ff4fbd updated digi cleanPath and process 2026-09-22 14:27:48 -03:00
Ricardo Guzman 3720fe9881 added fix for mic encoded syslog 2026-09-16 17:12:27 -03:00
Ricardo Guzman 5d04c56fd2 tnc2 added 2026-09-15 11:52:09 -03:00
82 changed files with 925 additions and 1563 deletions

No files matched your search

+25 -17
View File
@@ -18,7 +18,7 @@ jobs:
- name: ttgo-lora32-v21_915
chip: esp32
- name: ttgo-lora32-v21_915_GPS
chip: esp32
chip: esp32
- name: ttgo_lora32_t3s3_v1_2
chip: esp32s3
- name: heltec-lora32-v2
@@ -37,17 +37,19 @@ jobs:
chip: esp32s3
- name: heltec_wireless_bridge
chip: esp32
- name: ESP32_DIY_LoRa
- name: ESP32_DIY_LoRa_SX1278
chip: esp32
- name: ESP32_DIY_LoRa_915
- name: ESP32_DIY_LoRa_915_SX1276
chip: esp32
- name: ESP32_DIY_1W_LoRa
- name: ESP32_DIY_1W_LoRa_E22_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_915
- name: ESP32_DIY_1W_LoRa_915_E22_900M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_LLCC68
- name: ESP32_DIY_1W_LoRa_E220_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2
- name: ESP32_DIY_1W_LoRa_E32_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S
chip: esp32
- name: ttgo-t-beam-v1_2
chip: esp32
@@ -69,9 +71,9 @@ jobs:
chip: esp32s3
- name: ttgo_t_beam_s3_SUPREME_v3
chip: esp32s3
- name: ESP32_DIY_LoRa_A7670
- name: ESP32_DIY_LoRa_A7670_SX1278
chip: esp32
- name: ESP32_DIY_LoRa_A7670_915
- name: ESP32_DIY_LoRa_A7670_915_SX1276
chip: esp32
- name: heltec_wireless_tracker
chip: esp32s3
@@ -85,29 +87,35 @@ jobs:
chip: esp32s3
- name: OE5HWN_MeshCom
chip: esp32
- name: WEMOS-LOLIN32-OLED-DIY
- name: WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278
chip: esp32
- name: WEMOS-D1-R32-RA02
- name: WEMOS_D1_R32_DIY_LoRa_SX1278
chip: esp32
- name: WEMOS_S2_MINI_DIY_LoRa
- name: WEMOS_S2_MINI_DIY_LoRa_SX1278
chip: esp32s2
- name: esp32c3_DIY_1W_LoRa
- name: ESP32C3_DIY_1W_LoRa_E22_400M30S
chip: esp32c3
- name: esp32c3_DIY_1W_LoRa_915
- name: ESP32C3_DIY_1W_LoRa_915_E22_900M30S
chip: esp32c3
- name: ESP32_C3_OctopusLab_LoRa
- name: ESP32C3_OctopusLab_LoRa
chip: esp32c3
- name: ESP32C3_SuperMini_DIY_LoRa_SX1278
chip: esp32c3
- name: QRPLabs_LightGateway_1_0
chip: esp32s3
- name: QRPLabs_LightGateway_Plus_1_0
chip: esp32s3
- name: RPC_LORA_DIGIGATE_1W
chip: esp32
chip: esp32
- name: XIAO_ESP32S3_WIO_SX1262
chip: esp32s3
- name: RAK3312
chip: esp32s3
- name: RAK3112
chip: esp32s3
- name: TROY_LoRa_APRS
chip: esp32
- name: ESP32_9M2IBR_1W_LoRa_GPS
- name: ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S
chip: esp32
steps:
- uses: actions/checkout@v3
+2 -1
View File
@@ -7,4 +7,5 @@
/data_embed/*.gz
installer/firmware
installer/*.bin
**/__pycache__/
**/__pycache__/
.claude/
+7 -3
View File
@@ -20,7 +20,7 @@ ____________________________________________________
| New Menu | Map |
| --- | --- |
| ![new menu](images/1.png) | ![map](images/2.png) |
| ![new menu](images/1.png) | ![map](images/2.jpg) |
____________________________________________________
@@ -43,16 +43,18 @@ ____________________________________________________
- HELTEC V2, V3, V3.2, V4, T114, Wireless Stick, Wireless Stick Lite V3/V3.2, HT-CT62, Wireless Tracker, Wireless Paper.
- RAK Wireless 4631 + 19007(or 19003)
- RAK Wireless 4631 + 19007(or 19003), RAK3312 and RAK3112.
- Faketec V3 (NRF52840 + Heltec HTRA62(SX1262))
- QRP Labs LightGateway 1.0 and Plus 1.0.
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) 1W LoRa Module for a DIY Versions.
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) or Ebyte E32 400M30S 1W LoRa Module for a DIY Versions.
- ESP32C3 + Ebyte 400M30S(or 900M30S) 1W LoRa Module for another DIY version.
- ESP32C3 SuperMini + SX1278 (RA-01/RA-02) LoRa Module DIY version.
- ESP32 + 4G/LTE A7670 Modem + SX1278 DIY Version.
- Wemos Lolin32 Oled + SX1278 DIY Version.
@@ -62,6 +64,8 @@ ____________________________________________________
<br />
# Timeline (Versions):
- 2026-09-23 APRSPacketLib V1.0.7 update and many code cleaning with this.
- 2026-09-17 Real APRS Symbols added to map and TNC2 output as a posibility in TNC mode.
- 2026-08-16 New UI (Web) and also Live View over a Map to see Rx stations.
- 2026-08-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
+1 -2
View File
@@ -30,7 +30,6 @@ lib_deps =
adafruit/Adafruit BME680 Library @ 2.0.6
adafruit/Adafruit INA219 @ 1.2.3
adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0
@@ -38,7 +37,7 @@ lib_deps =
ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.4
richonguzman/APRSPacketLib @ 1.0.7
display_libs =
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
+30 -518
View File
@@ -95,7 +95,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
fill-rule="evenodd"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"
/>
</svg></span><span class="label">General</span></button>
</svg></span><span class="label">Station</span></button>
<button class="side-link" type="button" data-target="sec-beaconing"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471z"/></svg></span><span class="label">Beaconing</span></button>
<button class="side-link" type="button" data-target="sec-connectivity"><span class="side-ico"><svg
xmlns="http://www.w3.org/2000/svg"
@@ -186,8 +186,6 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<button class="side-link" type="button" data-target="sec-maps"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M15.817.113A.5.5 0 0 1 16 .5v14a.5.5 0 0 1-.402.49l-5 1a.5.5 0 0 1-.196 0L5.5 15.01l-4.902.98A.5.5 0 0 1 0 15.5v-14a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0L10.5.99l4.902-.98a.5.5 0 0 1 .415.103M10 1.91l-4-.8v12.98l4 .8zm1 12.98 4-.8V1.11l-4 .8zm-6-.8V1.11l-4 .8v12.98z"/></svg></span><span class="label">Map</span></button>
<button class="side-link" type="button" id="rpToggle"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg></span><span class="label">Received Packets</span></button>
<a href="/received-packets" id="rpDummy" style="display:none" aria-hidden="true"></a>
<button class="side-link" type="button" id="ssToggle"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2"/></svg></span><span class="label">Station</span></button>
<a href="/station-status" id="ssDummy" style="display:none" aria-hidden="true"></a>
<a class="side-link side-ota" href="/update"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 2a5.53 5.53 0 0 0-3.594 1.342c-.766.66-1.321 1.52-1.464 2.383C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13h8.906C14.502 13 16 11.57 16 9.773c0-1.636-1.242-2.969-2.834-3.194C12.923 3.999 10.69 2 8 2m2.354 5.146a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708l2-2a.5.5 0 0 1 .708 0z"/></svg></span><span class="label">Update OTA</span></a>
</aside>
<main class="app-main">
@@ -214,503 +212,6 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
</div>
<hr>
</div>
<div class="container d-none" id="station-status">
<div class="row my-5 d-flex align-items-top">
<div class="col-12">
<h3>Current station status</h3>
<div id="stationStatus.uptime">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-heart-pulse-fill"
viewBox="0 0 16 16"
>
<path
d="M1.475 9C2.702 10.84 4.779 12.871 8 15c3.221-2.129 5.298-4.16 6.525-6H12a.5.5 0 0 1-.464-.314l-1.457-3.642-1.598 5.593a.5.5 0 0 1-.945.049L5.889 6.568l-1.473 2.21A.5.5 0 0 1 4 9z"
/>
<path
d="M.88 8C-2.427 1.68 4.41-2 7.823 1.143q.09.083.176.171a3 3 0 0 1 .176-.17C11.59-2 18.426 1.68 15.12 8h-2.783l-1.874-4.686a.5.5 0 0 0-.945.049L7.921 8.956 6.464 5.314a.5.5 0 0 0-.88-.091L3.732 8z"
/>
</svg>
Up-time
</h5>
<small>Current device up-time</small>
</div>
<div class="col-lg-9 col-sm-12">
<div class="row mt-2">
<div class="col-6 mt-1">
<label for="stationStatus.elapsedTime" class="form-label"
>Elapsed time</label
>
<div class="input-group">
<input
type="text"
name="stationStatus.elapsedTime"
id="stationStatus.elapsedTime"
placeholder="000d 00h 00m 00s"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.voltage">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-battery-fill"
viewBox="0 0 16 16">
<path d="M2 4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2zm1 2h8v4H3V6z"/>
<path d="M15 6.5a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1V6.5z"/>
<path d="M4 7h6v2H4V7z"/>
</svg>
Battery
</h5>
<small>Current Battery status</small>
</div>
<div class="col-lg-9 col-sm-12">
<div class="row mt-2">
<div class="col-6 mt-1">
<label
for="stationStatus.voltageInternal"
class="form-label">
Internal Voltage
</label>
<div class="input-group">
<input
type="text"
name="stationStatus.voltageInternal"
id="stationStatus.voltageInternal"
placeholder="0.00"
class="form-control"
readonly
/>
<span class="input-group-text"
>Volts
</span>
</div>
</div>
<div class="col-6 mt-1">
<label for="stationStatus.voltageExternal" class="form-label"
>External voltage</label
>
<div class="input-group">
<input
type="text"
name="stationStatus.voltageExternal"
id="stationStatus.voltageExternal"
placeholder="0.00"
class="form-control"
readonly
/>
<span class="input-group-text"
>Volts</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.connectivity">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-router-fill"
viewBox="0 0 16 16"
>
<path
d="M5.525 3.025a3.5 3.5 0 0 1 4.95 0 .5.5 0 1 0 .707-.707 4.5 4.5 0 0 0-6.364 0 .5.5 0 0 0 .707.707"
/>
<path
d="M6.94 4.44a1.5 1.5 0 0 1 2.12 0 .5.5 0 0 0 .708-.708 2.5 2.5 0 0 0-3.536 0 .5.5 0 0 0 .707.707Z"
/>
<path
d="M2.974 2.342a.5.5 0 1 0-.948.316L3.806 8H1.5A1.5 1.5 0 0 0 0 9.5v2A1.5 1.5 0 0 0 1.5 13H2a.5.5 0 0 0 .5.5h2A.5.5 0 0 0 5 13h6a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5h.5a1.5 1.5 0 0 0 1.5-1.5v-2A1.5 1.5 0 0 0 14.5 8h-2.306l1.78-5.342a.5.5 0 1 0-.948-.316L11.14 8H4.86zM2.5 11a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1m4.5-.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m2.5.5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1m1.5-.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m2 0a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0"
/>
<path
d="M8.5 5.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0"
/>
</svg>
Connectivity
</h5>
<small>Current device connectivity and service status</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusWifi" class="form-label"
>WiFi status</label
>
<div class="input-group">
<input
name="stationStatus.statusWifi"
id="stationStatus.statusWifi"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.statusIS" class="form-label"
>IS status</label
>
<div class="input-group">
<input
name="stationStatus.statusIS"
id="stationStatus.statusIS"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusNTP" class="form-label"
>NTP status</label
>
<div class="input-group">
<input
name="stationStatus.statusNTP"
id="stationStatus.statusNTP"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.statusSyslog" class="form-label"
>Syslog status</label
>
<div class="input-group">
<input
name="stationStatus.statusSyslog"
id="stationStatus.statusSyslog"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusMQTT" class="form-label"
>MQTT status</label
>
<div class="input-group">
<input
name="stationStatus.statusMQTT"
id="stationStatus.statusMQTT"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.traffic">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-broadcast"
viewBox="0 0 16 16"
>
<path
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707m2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708m5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708m2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM10 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0"
/>
</svg>
Traffic
</h5>
<small>Current packet traffic status. Resets every 24 hours.</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsRFSendSuccess" class="form-label"
>RF send success</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFSendSuccess"
id="stationStatus.packetsRFSendSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsRFSendFail" class="form-label"
>RF send failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFSendFail"
id="stationStatus.packetsRFSendFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsRFReceiveSuccess" class="form-label"
>RF receive success</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFReceiveSuccess"
id="stationStatus.packetsRFReceiveSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsRFReceiveFail" class="form-label"
>RF receive failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFReceiveFail"
id="stationStatus.packetsRFReceiveFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsISSendSuccess" class="form-label"
>IS send success</label
>
<div class="input-group">
<input
name="stationStatus.packetsISSendSuccess"
id="stationStatus.packetsISSendSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsISSendFail" class="form-label"
>IS send failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsISSendFail"
id="stationStatus.packetsISSendFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsISReceiveSuccess" class="form-label"
>IS receive success</label
>
<div class="input-group">
<input
name="stationStatus.packetsISReceiveSuccess"
id="stationStatus.packetsISReceiveSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsISReceiveFail" class="form-label"
>IS receive failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsISReceiveFail"
id="stationStatus.packetsISReceiveFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsBlacklisted" class="form-label"
>Blacklisted packets</label
>
<div class="input-group">
<input
name="stationStatus.packetsBlacklisted"
id="stationStatus.packetsBlacklisted"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.login">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2M2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
Login Events
</h5>
<small>Current login statistics</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.loginsInvalid" class="form-label"
>Invalid logins</label
>
<div class="input-group">
<input
name="stationStatus.loginsInvalid"
id="stationStatus.loginsInvalid"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.weather">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-broadcast-pin"
viewBox="0 0 16 16"
>
<path
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707m2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708m5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708m2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM6 8a2 2 0 1 1 2.5 1.937V15.5a.5.5 0 0 1-1 0V9.937A2 2 0 0 1 6 8"
/>
</svg>
Weather
</h5>
<small>Current Weather status</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.weatherTemperature" class="form-label"
>Temperature</label
>
<div class="input-group">
<input
name="stationStatus.weatherTemperature"
id="stationStatus.weatherTemperature"
class="form-control"
readonly
/>
<span class="input-group-text"
>C°</span
>
</div>
</div>
<div class="col-6">
<label for="stationStatus.weatherHumidity" class="form-label"
>Humidity</label
>
<div class="input-group">
<input
name="stationStatus.weatherHumidity"
id="stationStatus.weatherHumidity"
class="form-control"
readonly
/>
<span class="input-group-text"
>percent</span
>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.weatherBarometricPressure" class="form-label"
>Barometric Pressure</label
>
<div class="input-group">
<input
name="stationStatus.weatherBarometricPressure"
id="stationStatus.weatherBarometricPressure"
class="form-control"
readonly
/>
<span class="input-group-text"
>Mb</span
>
</div>
</div>
<div class="col-6">
<label for="stationStatus.weatherGas" class="form-label"
>Gas</label
>
<div class="input-group">
<input
name="stationStatus.weatherGas"
id="stationStatus.weatherGas"
class="form-control"
readonly
/>
<span class="input-group-text"
>Kohms</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<span>Status updates automatically every 15 seconds.</span><br>
</div>
</div>
<hr>
</div>
<div id="configuration">
<section class="panel-section" id="sec-maps">
<div class="row my-5">
@@ -2019,7 +1520,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<label
for="tnc.enableSerial"
class="form-label"
>Enable Serial KISS</label
>Enable Serial</label
>
</div>
</div>
@@ -2051,7 +1552,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<label
for="tnc.acceptOwn"
class="form-label"
>Accept own frames via KISS</label
>Accept own frames</label
>
</div>
</div>
@@ -2071,6 +1572,23 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label
for="tnc.kissProtocol"
class="form-label"
>Protocol</label
>
<select
class="form-select form-select"
name="tnc.kissProtocol"
id="tnc.kissProtocol"
>
<option value="KISS" selected>KISS</option>
<option value="TNC2">TNC2</option>
</select>
</div>
</div>
</div>
</div>
@@ -2322,11 +1840,15 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
width="20"
height="20"
fill="currentColor"
class="bi bi-battery-fill"
viewBox="0 0 16 16">
<path d="M2 4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2zm1 2h8v4H3V6z"/>
<path d="M15 6.5a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1V6.5z"/>
<path d="M4 7h6v2H4V7z"/>
class="bi bi-heart-pulse-fill"
viewBox="0 0 16 16"
>
<path
d="M1.475 9C2.702 10.84 4.779 12.871 8 15c3.221-2.129 5.298-4.16 6.525-6H12a.5.5 0 0 1-.464-.314l-1.457-3.642-1.598 5.593a.5.5 0 0 1-.945.049L5.889 6.568l-1.473 2.21A.5.5 0 0 1 4 9z"
/>
<path
d="M.88 8C-2.427 1.68 4.41-2 7.823 1.143q.09.083.176.171a3 3 0 0 1 .176-.17C11.59-2 18.426 1.68 15.12 8h-2.783l-1.874-4.686a.5.5 0 0 0-.945.049L7.921 8.956 6.464 5.314a.5.5 0 0 0-.88-.091L3.732 8z"
/>
</svg>
Battery
</h5>
@@ -2895,7 +2417,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
function paint(name){var r=document.documentElement;if(name==="night"){r.setAttribute("data-bs-theme","dark");r.setAttribute("data-variant","night");}else{r.setAttribute("data-bs-theme",name);r.removeAttribute("data-variant");}if(btn)btn.innerHTML=ICONS[name]||MOON;}
paint(logical());
if(btn)btn.addEventListener("click",function(){var c=logical();var n=ORDER[(ORDER.indexOf(c)+1)%ORDER.length];try{localStorage.setItem("igateTheme",n);}catch(e){}paint(n);});
function showConfig(){document.getElementById("configuration").classList.remove("d-none");var rp=document.getElementById("received-packets");if(rp)rp.classList.add("d-none");var ss=document.getElementById("station-status");if(ss)ss.classList.add("d-none");}
function showConfig(){document.getElementById("configuration").classList.remove("d-none");var rp=document.getElementById("received-packets");if(rp)rp.classList.add("d-none");}
document.querySelectorAll(".side-link[data-target]").forEach(function(b){
b.addEventListener("click",function(){
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
@@ -2910,19 +2432,9 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
rpBtn.classList.add("active");
document.getElementById("configuration").classList.add("d-none");
document.getElementById("station-status").classList.add("d-none");
var rp=document.getElementById("received-packets");if(rp)rp.classList.remove("d-none");
if(window.fetchReceivedPackets)window.fetchReceivedPackets();
});
var ssBtn=document.getElementById("ssToggle");
if(ssBtn)ssBtn.addEventListener("click",function(){
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
ssBtn.classList.add("active");
document.getElementById("configuration").classList.add("d-none");
document.getElementById("received-packets").classList.add("d-none");
var ss=document.getElementById("station-status");if(ss)ss.classList.remove("d-none");
if(window.fetchStationStatus)window.fetchStationStatus();
});
var bi=document.getElementById("buildInfo");
if(bi){var h=bi.innerHTML; if(/Version date/i.test(h)){h=h.replace(/(<br\s*\/?>)[\s\S]*?(?=Version date)/i,"$1");bi.innerHTML="Board / Environment = "+h;}}
if(window.loadReceivedPackets){var _o=window.loadReceivedPackets;window.loadReceivedPackets=function(p){if(p&&p.slice){p=p.slice().reverse();}return _o(p);};}
+72 -84
View File
@@ -96,7 +96,7 @@ function loadSettings(settings) {
networksContainer.appendChild(networkElement);
networkCount++;
});
document.getElementById("startupDelay").value = settings.startupDelay;
document.getElementById("startupDelay").value = settings.other.startupDelay;
// APRS-IS
document.getElementById("aprs_is.active").checked = settings.aprs_is.active;
@@ -226,6 +226,7 @@ function loadSettings(settings) {
document.getElementById("tnc.enableSerial").checked = settings.tnc.enableSerial;
document.getElementById("tnc.acceptOwn").checked = settings.tnc.acceptOwn;
document.getElementById("tnc.aprsBridgeActive").checked = settings.tnc.aprsBridgeActive;
document.getElementById("tnc.kissProtocol").value = settings.tnc.kissProtocol ? "KISS" : "TNC2";
}
// MQTT
@@ -276,6 +277,8 @@ function loadSettings(settings) {
document.getElementById("ntp.server").value = settings.ntp.server;
document.getElementById("ntp.gmtCorrection").value = settings.ntp.gmtCorrection;
clampOutOfRangeInputs();
updateImage();
}
@@ -566,7 +569,6 @@ function checkConnection() {
}, 3000);
fetchSettings();
fetchStationStatus();
})
.catch((err) => {
setTimeout(checkConnection, 0);
@@ -636,93 +638,12 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none');
document.getElementById('station-status').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchReceivedPackets();
})
function loadStationStatus(stationStatus) {
if (stationStatus) {
document.getElementById("stationStatus.voltageInternal").value = stationStatus.stationStatus.voltageInternal;
document.getElementById("stationStatus.voltageExternal").value = stationStatus.stationStatus.voltageExternal;
document.getElementById("stationStatus.voltage").style.display = "block";
document.getElementById("stationStatus.elapsedTime").value = stationStatus.stationStatus.elapsedTime;
document.getElementById("stationStatus.uptime").style.display = "block";
document.getElementById("stationStatus.statusWifi").value = stationStatus.stationStatus.statusWifi;
document.getElementById("stationStatus.statusIS").value = stationStatus.stationStatus.statusIS;
document.getElementById("stationStatus.statusNTP").value = stationStatus.stationStatus.statusNTP;
document.getElementById("stationStatus.statusSyslog").value = stationStatus.stationStatus.statusSyslog;
document.getElementById("stationStatus.statusMQTT").value = stationStatus.stationStatus.statusMQTT;
document.getElementById("stationStatus.connectivity").style.display = "block";
if (stationStatus.stationStatus.webAdminActive) {
document.getElementById("stationStatus.loginsInvalid").value = stationStatus.stationStatus.loginsInvalid;
document.getElementById("stationStatus.login").style.display = "block";
} else {
document.getElementById("stationStatus.login").style.display = "none";
}
document.getElementById("stationStatus.packetsRFSendSuccess").value = stationStatus.stationStatus.packetsRFSendSuccess;
document.getElementById("stationStatus.packetsRFReceiveSuccess").value = stationStatus.stationStatus.packetsRFReceiveSuccess;
document.getElementById("stationStatus.packetsISSendSuccess").value = stationStatus.stationStatus.packetsISSendSuccess;
document.getElementById("stationStatus.packetsISReceiveSuccess").value = stationStatus.stationStatus.packetsISReceiveSuccess;
document.getElementById("stationStatus.packetsRFSendFail").value = stationStatus.stationStatus.packetsRFSendFail;
document.getElementById("stationStatus.packetsRFReceiveFail").value = stationStatus.stationStatus.packetsRFReceiveFail;
document.getElementById("stationStatus.packetsISSendFail").value = stationStatus.stationStatus.packetsISSendFail;
document.getElementById("stationStatus.packetsISReceiveFail").value = stationStatus.stationStatus.packetsISReceiveFail;
document.getElementById("stationStatus.packetsBlacklisted").value = stationStatus.stationStatus.packetsBlacklisted;
document.getElementById("stationStatus.traffic").style.display = "block";
if (stationStatus.stationStatus.weatherModulePresent) {
document.getElementById("stationStatus.weatherTemperature").value = stationStatus.stationStatus.weatherTemperature;
document.getElementById("stationStatus.weatherHumidity").value = stationStatus.stationStatus.weatherHumidity;
document.getElementById("stationStatus.weatherBarometricPressure").value = stationStatus.stationStatus.weatherBarometricPressure;
document.getElementById("stationStatus.weatherGas").value = stationStatus.stationStatus.weatherGas;
document.getElementById("stationStatus.weather").style.display = "block";
} else {
document.getElementById("stationStatus.weather").style.display = "none";
}
} else {
document.getElementById("stationStatus.voltage").style.display = "none";
document.getElementById("stationStatus.weather").style.display = "none";
document.getElementById("stationStatus.uptime").style.display = "none";
document.getElementById("stationStatus.connectivity").style.display = "none";
document.getElementById("stationStatus.traffic").style.display = "none";
document.getElementById("stationStatus.login").style.display = "none";
}
setTimeout(fetchStationStatus, 15000);
}
fetchStationStatus();
function fetchStationStatus() {
fetch("/station-status.json")
.then((response) => response.json())
.then((deviceTelemetry) => {
loadStationStatus(deviceTelemetry);
})
.catch((err) => {
console.error(err);
console.error(`Failed to load station status`);
});
}
document.querySelector('a[href="/station-status"]').addEventListener('click', function (e) {
e.preventDefault();
document.getElementById('station-status').classList.remove('d-none');
document.getElementById('received-packets').classList.add('d-none');
document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchDeviceTelemetry();
})
/* ---------- Stations Map (Leaflet, CDN) ---------- */
@@ -1183,4 +1104,71 @@ window.showMap = function () {
fetchMapStations();
}
}, 15000);
}
}
/* ------------------------------------------------- keeping the form submittable
* A control that fails HTML5 validation blocks the whole form, and when it sits in
* a panel that is not the visible one the browser cannot show its bubble either:
* pressing Save then does nothing at all -- no message, no request, no clue. Two
* guards against that: pull stored values back inside the allowed range on load,
* and, if validation still fails, open the section holding the offending control
* and show what is wrong.
*/
function clampOutOfRangeInputs() {
const fixed = [];
document.querySelectorAll("#configuration input[type=number]").forEach((el) => {
if (el.disabled || el.value === "") return;
const value = parseFloat(el.value);
if (isNaN(value)) return;
const min = el.min !== "" ? parseFloat(el.min) : null;
const max = el.max !== "" ? parseFloat(el.max) : null;
let clamped = value;
if (max !== null && value > max) clamped = max;
if (min !== null && value < min) clamped = min;
if (clamped !== value) {
el.value = clamped;
fixed.push((el.name || el.id) + ": " + value + " &rarr; " + clamped);
}
});
if (fixed.length) {
showToast("Stored values outside the allowed range, adjusted:<br>" + fixed.join("<br>")
+ "<br><small>Save to keep them.</small>");
}
}
let invalidFieldReported = false;
form.addEventListener("invalid", function (event) {
const el = event.target;
if (!invalidFieldReported) { // only the first one: the rest would pile up toasts
invalidFieldReported = true;
setTimeout(() => { invalidFieldReported = false; }, 500);
const section = el.closest(".panel-section");
if (section) {
const link = document.querySelector('.side-link[data-target="' + section.id + '"]');
if (link) {
link.click(); // reuses the sidebar switcher (also shows the config view)
} else {
document.querySelectorAll(".panel-section").forEach((s) => s.classList.remove("active"));
section.classList.add("active");
document.getElementById("configuration").classList.remove("d-none");
}
}
showToast("<b>" + (el.name || el.id) + "</b>: " + (el.validationMessage || "invalid value"));
setTimeout(() => {
el.focus();
el.scrollIntoView({ block: "center", behavior: "smooth" });
}, 0);
}
}, true);
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

+2 -17
View File
@@ -27,11 +27,10 @@ namespace APRS_IS_Utils {
void upload(const String& line);
void connect();
void checkStatus();
String checkForStartingBytes(const String& packet);
void updateWiFiAPRSISDisplayInfo();
String buildPacketToUpload(const String& packet);
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty);
bool processReceivedLoRaMessage(const String& sender, const String& packet);
void processLoRaPacket(const String& packet);
String buildPacketToTx(const String& aprsisPacket, uint8_t packetType);
@@ -40,20 +39,6 @@ namespace APRS_IS_Utils {
void firstConnection();
String getAPRSISState();
String getWifiState();
void incrementPacketSendSuccess();
unsigned int getPacketSendSuccess();
void incrementPacketSendFail();
unsigned int getPacketSendFail();
void incrementPacketReceiveSuccess();
unsigned int getPacketReceiveSuccess();
void incrementPacketReceiveFail();
unsigned int getPacketReceiveFail();
bool isBeyondWindow(unsigned long time);
void resetPacketCounters(unsigned long time);
}
#endif
+1
View File
@@ -132,6 +132,7 @@ public:
bool enableSerial;
bool acceptOwn;
bool aprsBridgeActive;
bool kissProtocol; // true = KISS, false = TNC2 -- applies to both Serial and TCP server
};
class OTA {
+2 -3
View File
@@ -24,10 +24,9 @@
namespace DIGI_Utils {
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq);
String generateDigipeatedPacket(const String& packet, bool thirdParty);
String buildPacket(const String& path, const String& packet, bool crossFreq);
String generateDigipeatedPacket(const String& packet);
void processLoRaPacket(const String& packet);
void checkEcoMode();
}
+1 -6
View File
@@ -25,13 +25,8 @@
namespace GPS_Utils {
String getiGateLoRaBeaconPacket();
char *ax25_base91enc(char *s, uint8_t n, uint32_t v);
String encodeGPS(float latitude, float longitude, const String& overlay, const String& symbol);
void generateBeaconFirstPart();
void generateBeacons();
String decodeEncodedGPS(const String& packet);
String getReceivedGPS(const String& packet);
String getDistanceAndComment(const String& packet);
String buildDistanceAndComment(float latitude, float longitude, const String& comment);
void setup();
void getData();
-13
View File
@@ -33,19 +33,6 @@ namespace LoRa_Utils {
void wakeRadio();
void sleepRadio();
void incrementPacketSendSuccess();
unsigned int getPacketSendSuccess();
void incrementPacketSendFail();
unsigned int getPacketSendFail();
void incrementPacketReceiveSuccess();
unsigned int getPacketReceiveSuccess();
void incrementPacketReceiveFail();
unsigned int getPacketReceiveFail();
void incrementPacketBlacklisted();
unsigned int getPacketBlacklisted();
bool isBeyondWindow(unsigned long time);
void resetPacketCounters(unsigned long time);
}
#endif
-1
View File
@@ -28,7 +28,6 @@ namespace MQTT_Utils {
void connect();
void loop();
void setup();
String getMQTTStatus();
}
+3 -1
View File
@@ -84,7 +84,6 @@ public:
// Ethernet methods
bool ethernetConnect(eth_phy_type_t type, uint8_t phy_addr, uint8_t mdc, uint8_t mdio, int power, eth_clock_mode_t clock_mode, bool use_mac_from_efuse = false);
bool setEthernetIP(const String& staticIP, const String& gateway, const String& subnet, const String& dns1, const String& dns2);
bool ethernetDisconnect();
IPAddress getEthernetIP() const;
String getEthernetMACAddress() const;
@@ -97,4 +96,7 @@ public:
bool isModemConnected() const;
bool isWifiAPActive() const;
// Check if any network interface was started (lwip TCP/IP stack is initialized)
bool hasActiveInterface() const;
};
-2
View File
@@ -27,8 +27,6 @@ namespace NTP_Utils {
bool setup();
void update();
String getFormatedTime();
unsigned long getTimeMillis();
String getNTPStatus();
}
+4 -1
View File
@@ -29,7 +29,7 @@
namespace POWER_Utils {
#ifdef VEXT_CTRL
#ifdef VEXT_CTRL_PIN
void vext_ctrl_ON();
void vext_ctrl_OFF();
#endif
@@ -46,6 +46,9 @@ namespace POWER_Utils {
void activateLoRa();
void deactivateLoRa();
bool begin(TwoWire &port);
#ifdef SOLAR_CHARGE_PIN
void checkSolarCharge();
#endif
void setup();
}
-2
View File
@@ -41,8 +41,6 @@ namespace STATION_Utils {
void processOutputPacketBufferUltraEcoMode();
void processOutputPacketBuffer();
void addToOutputPacketBuffer(const String& packet, bool flag = false);
void markBootTime();
unsigned long getBootTime();
}
+5 -2
View File
@@ -19,14 +19,17 @@
#ifndef SYSLOG_H_
#define SYSLOG_H_
#include <APRSPacketLib.h>
#include <Arduino.h>
namespace SYSLOG_Utils {
void log(const uint8_t type ,const String& packet, const int rssi, const float snr, const int freqError);
void logLoRaRx(APRSPacket& aprsPacket, const String& packet, const int rssi, const float snr, const int freqError);
void logAPRSISTx(const String& packet);
void logLoRaTx(const String& packet);
void logCRCError(const String& packet, const int rssi, const float snr, const int freqError);
void setup();
String getSyslogStatus();
}
+3 -2
View File
@@ -19,9 +19,9 @@
#ifndef UTILS_H_
#define UTILS_H_
#include <APRSPacketLib.h>
#include <Arduino.h>
class ReceivedPacket {
public:
String rxTime;
@@ -39,7 +39,8 @@ namespace Utils {
void checkBeaconInterval();
void checkDisplayInterval();
void validateFreqs();
void typeOfPacket(const String& packet, const uint8_t packetType);
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType);
void updateAPRSISPacketDisplayInfo(const String& packet);
void print(const String& text);
void println(const String& text);
void checkRebootMode();
-2
View File
@@ -38,8 +38,6 @@ namespace WEB_Utils {
void setup();
unsigned long getLoginFailCount();
}
#endif
+2 -5
View File
@@ -36,11 +36,8 @@ namespace WX_Utils {
String generateHumString(const float sensorHum);
String generatePresString(const float sensorPres);
String readDataSensor();
float getHumidity();
float getTemperature();
float getBarometricPressure();
float getGas();
boolean isWeatherModulePresent();
float readBoardTemperature();
}
#endif
+2 -4
View File
@@ -63,13 +63,12 @@ ___________________________________________________________________*/
#include "wx_utils.h"
#include "display.h"
#include "utils.h"
#include "telemetry_utils.h"
#ifdef HAS_A7670
#include "A7670_utils.h"
#endif
String versionDate = "2026-09-08";
String versionDate = "2026-09-29";
String versionNumber = "4.0.1";
Configuration Config;
WiFiClient aprsIsClient;
@@ -129,7 +128,6 @@ void setup() {
Utils::checkRebootMode();
APRS_IS_Utils::firstConnection();
SLEEP_Utils::checkSerial();
STATION_Utils::markBootTime(); // should be last
}
void loop() {
@@ -186,7 +184,7 @@ void loop() {
Utils::checkDisplayInterval();
Utils::checkBeaconInterval();
APRS_IS_Utils::checkStatus(); // Need that to update display, maybe split this and send APRSIS status to display func?
APRS_IS_Utils::updateWiFiAPRSISDisplayInfo(); // WiFi / APRS-IS status line (wakes display only on status change)
String packet = "";
if (Config.loramodule.rxActive) {
+39 -147
View File
@@ -33,6 +33,7 @@
extern Configuration Config;
extern APRSPacket lastAprsPacket;
extern NetworkManager *networkManager;
extern WiFiClient aprsIsClient;
extern uint32_t lastScreenOn;
@@ -50,6 +51,7 @@ extern String versionNumber;
bool passcodeValid = false;
uint32_t lastServerCheck = 0;
#ifdef HAS_A7670
extern bool stationBeacon;
#endif
@@ -57,14 +59,7 @@ uint32_t lastServerCheck = 0;
namespace APRS_IS_Utils {
unsigned int packetSendSuccess = 0;
unsigned int packetSendFail = 0;
unsigned int packetReceiveSuccess = 0;
unsigned int packetReceiveFail = 0;
unsigned long packetCountStartTime = 0;
void upload(const String& line) {
incrementPacketSendSuccess();
aprsIsClient.print(line + "\r\n");
}
@@ -98,31 +93,10 @@ namespace APRS_IS_Utils {
}
}
void checkStatus() {
String wifiState = getWifiState();
String aprsisState = getAPRSISState();
if (!networkManager->isWiFiConnected()) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
lastScreenOn = millis();
}
if (Config.aprs_is.active) {
if(aprsisState == "--" && !Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
lastScreenOn = millis();
}
}
secondLine = "WiFi: ";
secondLine += wifiState;
secondLine += " APRS-IS: ";
secondLine += aprsisState;
}
String getWifiState() {
String wifiState;
void updateWiFiAPRSISDisplayInfo() {
static String lastWifiState = "";
static String lastAprsisState = "";
String wifiState, aprsisState;
if (networkManager->isWiFiConnected()) {
wifiState = "OK";
} else {
@@ -133,12 +107,6 @@ namespace APRS_IS_Utils {
}
}
return wifiState;
}
String getAPRSISState() {
String aprsisState;
if (!Config.aprs_is.active) {
aprsisState = "OFF";
} else {
@@ -156,12 +124,18 @@ namespace APRS_IS_Utils {
}
#endif
}
return aprsisState;
}
String checkForStartingBytes(const String& packet) {
int index = packet.indexOf("\x3c\xff\x01");
return (index != -1) ? packet.substring(0, index) : packet;
if (wifiState != lastWifiState || aprsisState != lastAprsisState) { // wake display only on status change (not every loop) so display timeout works
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
lastScreenOn = millis();
lastWifiState = wifiState;
lastAprsisState = aprsisState;
}
secondLine = "WiFi: ";
secondLine += wifiState;
secondLine += " APRS-IS: ";
secondLine += aprsisState;
}
String buildPacketToUpload(const String& packet) {
@@ -173,11 +147,11 @@ namespace APRS_IS_Utils {
packetToUpload += ",qAO,";
}
packetToUpload += Config.callsign;
packetToUpload += checkForStartingBytes(packet.substring(colonIndex));
packetToUpload += APRSPacketLib::checkForStartingBytes(packet.substring(colonIndex));
return packetToUpload;
}
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty) {
bool processReceivedLoRaMessage(const String& sender, const String& packet) {
String receivedMessage;
int leftCurlyBraceIndex = packet.indexOf("{");
int colonIndex = packet.indexOf(":");
@@ -189,7 +163,7 @@ namespace APRS_IS_Utils {
String addToBuffer = Config.callsign;
addToBuffer += ">APLRG1";
if (!thirdParty) addToBuffer += ",RFONLY";
if (lastAprsPacket.header == "") addToBuffer += ",RFONLY";
if (Config.beacon.path != "") {
addToBuffer += ",";
addToBuffer += Config.beacon.path;
@@ -210,52 +184,44 @@ namespace APRS_IS_Utils {
receivedMessage = packet.substring(colonIndex + 1);
}
if (receivedMessage.indexOf("?") == 0) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
STATION_Utils::addToOutputPacketBuffer(QUERY_Utils::process(receivedMessage, sender, false, thirdParty));
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
STATION_Utils::addToOutputPacketBuffer(QUERY_Utils::process(receivedMessage, sender, false, lastAprsPacket.header != ""));
lastScreenOn = millis();
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, "Callsign = " + sender, "TYPE --> QUERY", 0);
return true;
}
else {
} else {
return false;
}
}
void processLoRaPacket(const String& packet) {
if (passcodeValid && (aprsIsClient.connected() || modemLoggedToAPRSIS)) {
if (packet.indexOf("NOGATE") == -1 && packet.indexOf("RFONLY") == -1) {
int firstColonIndex = packet.indexOf(":");
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] != '}' && packet.indexOf("TCPIP") == -1) {
const String& Sender = packet.substring(3, packet.indexOf(">"));
if (Sender != Config.callsign && Utils::callsignIsValid(Sender)) {
STATION_Utils::updateLastHeard(Sender);
Utils::typeOfPacket(packet.substring(3), 0); // LoRa-APRS
int doubleColonIndex = packet.indexOf("::");
const String& AddresseeAndMessage = packet.substring(doubleColonIndex + 2);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
Addressee.trim();
if (lastAprsPacket.path.indexOf("NOGATE") == -1 && lastAprsPacket.path.indexOf("RFONLY") == -1) {
if (lastAprsPacket.header == "" && lastAprsPacket.path.indexOf("TCPIP") == -1) {
if (lastAprsPacket.sender != Config.callsign && Utils::callsignIsValid(lastAprsPacket.sender)) {
STATION_Utils::updateLastHeard(lastAprsPacket.sender);
Utils::updateLoRaPacketDisplayInfo(lastAprsPacket, 0); // LoRa-APRS
bool queryMessage = false;
if (doubleColonIndex > 10 && Addressee == Config.callsign) { // its a message for me!
queryMessage = processReceivedLoRaMessage(Sender, checkForStartingBytes(AddresseeAndMessage), false);
if (lastAprsPacket.type == 1 && lastAprsPacket.addressee == Config.callsign) { // its a message for me!
String AddresseeAndMessage = lastAprsPacket.addressee + ":" + lastAprsPacket.payload;
queryMessage = processReceivedLoRaMessage(lastAprsPacket.sender, APRSPacketLib::checkForStartingBytes(AddresseeAndMessage));
}
if (queryMessage) return;
const String& aprsPacket = buildPacketToUpload(packet);
const String& aprsPacketToUpload = buildPacketToUpload(packet);
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(aprsPacket);
A7670_Utils::uploadToAPRSIS(aprsPacketToUpload);
stationBeacon = false;
#else
upload(aprsPacket);
upload(aprsPacketToUpload);
#endif
Utils::println("---> Uploaded to APRS-IS");
Utils::println("(Uploaded to APRS-IS)");
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
}
}
@@ -324,7 +290,6 @@ namespace APRS_IS_Utils {
ackPacket += ackMessage;
#ifdef HAS_A7670
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(ackPacket);
#else
upload(ackPacket);
@@ -348,7 +313,6 @@ namespace APRS_IS_Utils {
if (packet.startsWith("#")) {
if (Config.digi.backupDigiMode) lastServerCheck = currentTime;
} else {
incrementPacketReceiveSuccess();
int doubleColonIndex = packet.indexOf("::");
if (Config.aprs_is.messagesToRF && doubleColonIndex > 0) {
String Sender = packet.substring(0, packet.indexOf(">"));
@@ -374,12 +338,11 @@ namespace APRS_IS_Utils {
}
lastScreenOn = currentTime;
#ifdef HAS_A7670
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(queryAnswer);
#else
upload(queryAnswer);
#endif
SYSLOG_Utils::log(2, queryAnswer, 0, 0.0, 0); // APRSIS TX
SYSLOG_Utils::logAPRSISTx(queryAnswer);
fifthLine = "APRS-IS ----> APRS-IS";
sixthLine = Config.callsign;
for (int j = sixthLine.length();j < 9;j++) {
@@ -397,7 +360,7 @@ namespace APRS_IS_Utils {
STATION_Utils::addToOutputPacketBuffer(buildPacketToTx(packet, 1));
displayToggle(true);
lastScreenOn = currentTime;
Utils::typeOfPacket(packet, 1); // APRS-LoRa
Utils::updateAPRSISPacketDisplayInfo(packet); // APRS-LoRa
displayShow(firstLine, secondLine, thirdLine, fourthLine, fifthLine, sixthLine, seventhLine, 0);
}
}
@@ -407,7 +370,7 @@ namespace APRS_IS_Utils {
STATION_Utils::addToOutputPacketBuffer(buildPacketToTx(packet, 5));
displayToggle(true);
lastScreenOn = currentTime;
Utils::typeOfPacket(packet, 1); // APRS-LoRa
Utils::updateAPRSISPacketDisplayInfo(packet); // APRS-LoRa
Serial.println();
} else {
Serial.println(" ---> Rejected (Time): No Tx");
@@ -444,75 +407,4 @@ namespace APRS_IS_Utils {
}
}
bool isBeyondWindow(unsigned long time) {
unsigned long window = 24*60*60*1000;
if (time > packetCountStartTime + window) {
return true;
}
return false;
}
void resetPacketCounters(unsigned long time) {
packetCountStartTime = time;
packetSendSuccess = 0;
packetSendFail = 0;
packetReceiveSuccess = 0;
packetReceiveFail = 0;
}
void incrementPacketSendSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
resetPacketCounters(now);
packetSendSuccess = 1;
} else {
packetSendSuccess++;
}
}
unsigned int getPacketSendSuccess() {
return packetSendSuccess;
}
void incrementPacketSendFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetSendFail = 1;
} else {
packetSendFail++;
}
}
unsigned int getPacketSendFail() {
return packetSendFail;
}
void incrementPacketReceiveSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveSuccess = 1;
} else {
packetReceiveSuccess++;
}
}
unsigned int getPacketReceiveSuccess() {
return packetReceiveSuccess;
}
void incrementPacketReceiveFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveFail = 1;
} else {
packetReceiveFail++;
}
}
unsigned int getPacketReceiveFail() {
return packetReceiveFail;
}
}
+5 -1
View File
@@ -160,7 +160,7 @@ namespace BATTERY_Utils {
int sampleSum = 0;
for (int i = 0; i < adcReadings; i++) {
#if defined(ESP32_DIY_LoRa) || defined(ESP32_DIY_LoRa_915) || defined(ESP32_DIY_1W_LoRa) || defined(ESP32_DIY_1W_LoRa_915)
#if defined(ESP32_DIY_LoRa_SX1278) || defined(ESP32_DIY_LoRa_915_SX1276) || defined(ESP32_DIY_1W_LoRa_E22_400M30S) || defined(ESP32_DIY_1W_LoRa_915_E22_900M30S) || defined(ESP32_DIY_1W_LoRa_E32_400M30S) || defined(ESP32C3_SuperMini_DIY_LoRa_SX1278)
sampleSum = 0;
#else
#ifdef HAS_ADC_CALIBRATION
@@ -203,6 +203,10 @@ namespace BATTERY_Utils {
#ifdef LIGHTGATEWAY_PLUS_1_0
double inputDivider = (1.0 / (560.0 + 100.0)) * 100.0; // The voltage divider is a 560k + 100k resistor in series, 100k on the low side.
return (((sampleSum/adcReadings) * adcReadingTransformation) / inputDivider) + 0.41;
#elif defined(RAK3112)
return (sampleSum/adcReadings) * adcReadingTransformation * (3 * 1.73 * 1.187 / 3.3); // multiplier from MeshCore RAK3112Board.h
#elif defined(RAK3312)
return (sampleSum/adcReadings) * adcReadingTransformation * (3 * 1.73 * 1.187 / 3.3) * (1.667 / 2.11); // RAK3112 value scaled by Meshtastic RAK3312/RAK3112 ratio
#else
return (2 * (sampleSum/adcReadings) * adcReadingTransformation) + voltageDividerCorrection; // raw voltage without mapping
#endif
+5 -1
View File
@@ -157,6 +157,7 @@ bool Configuration::writeFile() {
data["tnc"]["enableSerial"] = tnc.enableSerial;
data["tnc"]["acceptOwn"] = tnc.acceptOwn;
data["tnc"]["aprsBridgeActive"] = tnc.aprsBridgeActive;
data["tnc"]["kissProtocol"] = tnc.kissProtocol;
data["mqtt"]["active"] = mqtt.active;
data["mqtt"]["server"] = mqtt.server;
@@ -369,11 +370,13 @@ bool Configuration::readFile() {
if (data["tnc"]["enableServer"].isNull() ||
data["tnc"]["enableSerial"].isNull() ||
data["tnc"]["acceptOwn"].isNull() ||
data["tnc"]["aprsBridgeActive"].isNull()) needsRewrite = true;
data["tnc"]["aprsBridgeActive"].isNull() ||
data["tnc"]["kissProtocol"].isNull()) needsRewrite = true;
tnc.enableServer = data["tnc"]["enableServer"] | false;
tnc.enableSerial = data["tnc"]["enableSerial"] | false;
tnc.acceptOwn = data["tnc"]["acceptOwn"] | false;
tnc.aprsBridgeActive = data["tnc"]["aprsBridgeActive"] | false;
tnc.kissProtocol = data["tnc"]["kissProtocol"] | true;
if (data["mqtt"]["active"].isNull() ||
data["mqtt"]["server"].isNull() ||
@@ -538,6 +541,7 @@ void Configuration::setDefaultValues() {
tnc.enableSerial = false;
tnc.acceptOwn = false;
tnc.aprsBridgeActive = false;
tnc.kissProtocol = true;
mqtt.active = false;
mqtt.server = "";
+62 -50
View File
@@ -16,6 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <APRSPacketLib.h>
#include <WiFi.h>
#include "configuration.h"
#include "station_utils.h"
@@ -29,6 +30,7 @@
extern Configuration Config;
extern uint32_t lastScreenOn;
extern APRSPacket lastAprsPacket;
extern String iGateBeaconPacket;
extern String firstLine;
extern String secondLine;
@@ -42,13 +44,32 @@ extern bool backupDigiMode;
namespace DIGI_Utils {
String cleanPath(String path) {
String terms[] = {"WIDE1*,", "WIDE2*,", "*"};
for (String term : terms) {
int index = path.indexOf(term);
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
String cleanPath(const String& path) {
String result;
unsigned int start = 0;
while (true) {
int delim = path.indexOf(',', start);
int end = (delim == -1) ? path.length() : delim;
String token = path.substring(start, end);
if (token != "WIDE1*" && token != "WIDE2*") {
if (result.length() > 0) result += ",";
result += token;
}
if (delim == -1) break;
start = delim + 1;
}
return path;
return result;
}
static int pathTokenIndex(const String& path, const String& token) {
unsigned int start = 0;
while (start < path.length()) {
int end = path.indexOf(",", start);
if (end == -1) end = path.length();
if (path.substring(start, end) == token) return start;
start = end + 1;
}
return -1;
}
String processMode3Path(const String& path, const String& stationCallsign) {
@@ -80,26 +101,28 @@ namespace DIGI_Utils {
return tempPacket + "*" + path.substring(ownTokenEnd);
}
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
String buildPacket(const String& path, const String& packet, bool crossFreq) {
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
String suffix = thirdParty ? ":}" : ":";
String suffix = (lastAprsPacket.header != "") ? ":}" : ":";
int suffixIndex = packet.indexOf(suffix);
String packetToRepeat;
if (!crossFreq) {
int digiMode = Config.digi.mode;
String tempPath = path;
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 1 || digiMode == 2)) { // WIDE1-1
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 1 || digiMode == 2 || backupDigiMode)) { // WIDE1-1
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
tempPath.replace("WIDE1-1", stationCallsign + "*");
if (pathTokenIndex(tempPath, "WIDE1-1") != 0) return ""; // WIDE1-1 must be the first hop
tempPath = stationCallsign + "*" + tempPath.substring(7);
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { // WIDE2-n Digipeater
tempPath = cleanPath(path);
if (tempPath.indexOf("WIDE2-1") != -1) {
tempPath.replace("WIDE2-1", stationCallsign + "*");
} else if (tempPath.indexOf("WIDE2-2") != -1) {
tempPath.replace("WIDE2-2", stationCallsign + "*,WIDE2-1");
int idx = pathTokenIndex(tempPath, "WIDE2-1");
if (idx != -1) {
tempPath = tempPath.substring(0, idx) + stationCallsign + "*" + tempPath.substring(idx + 7);
} else {
return "";
idx = pathTokenIndex(tempPath, "WIDE2-2");
if (idx == -1) return "";
tempPath = tempPath.substring(0, idx) + stationCallsign + "*,WIDE2-1" + tempPath.substring(idx + 7);
}
} else if (digiMode == 3) { // Repeat if station callsign is in path (free to repeat).
tempPath = processMode3Path(tempPath, stationCallsign);
@@ -108,19 +131,21 @@ namespace DIGI_Utils {
packetToRepeat = packet.substring(0, packet.indexOf(",") + 1);
packetToRepeat += tempPath;
} else { // CrossFreq Digipeater
String cleanedPath = cleanPath(path);
if (pathTokenIndex(cleanedPath, stationCallsign) != -1 ||
pathTokenIndex(cleanedPath, stationCallsign + "*") != -1) return ""; // stationCallsign shouldn't be in path (exact token, path only)
packetToRepeat = cleanPath(packet.substring(0, suffixIndex));
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
packetToRepeat += ",";
packetToRepeat += stationCallsign;
packetToRepeat += "*";
}
packetToRepeat += APRS_IS_Utils::checkForStartingBytes(packet.substring(suffixIndex));
packetToRepeat += APRSPacketLib::checkForStartingBytes(packet.substring(suffixIndex));
return packetToRepeat;
}
String generateDigipeatedPacket(const String& packet, bool thirdParty){
String generateDigipeatedPacket(const String& packet){
String temp;
if (thirdParty) { // only header is used
if (lastAprsPacket.header != "") { // thirdparty : only header is used
const String& header = packet.substring(0, packet.indexOf(":}"));
temp = header.substring(header.indexOf(">") + 1);
} else {
@@ -135,7 +160,7 @@ namespace DIGI_Utils {
if (digiMode == 1 || backupDigiMode) {
bool hasWide = path.indexOf("WIDE1-1") != -1;
if (hasWide || crossFreq) {
return buildPacket(path, packet, thirdParty, !hasWide);
return buildPacket(path, packet, !hasWide);
}
return "";
}
@@ -147,62 +172,49 @@ namespace DIGI_Utils {
if (hasWide1 && hasWide2 && wide2Index < wide1Index) return ""; // check that WIDE1 before WIDE2
if (hasWide1 || hasWide2) return buildPacket(path, packet, thirdParty, false); // regular APRS with WIDEn-N
if (hasWide1 || hasWide2) return buildPacket(path, packet, false); // regular APRS with WIDEn-N
if (crossFreq) return buildPacket(path, packet, thirdParty, true); // CrossFreq (without WIDE)
if (crossFreq) return buildPacket(path, packet, true); // CrossFreq (without WIDE)
return "";
}
if (digiMode == 3) {
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
bool containsOwnCall = path.indexOf(stationCallsign) != -1;
if (containsOwnCall) return buildPacket(path, packet, thirdParty, false);
if (containsOwnCall) return buildPacket(path, packet, false);
return "";
}
return "";
}
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, true); // no "path" but is CrossFreq Digi
return "";
}
void processLoRaPacket(const String& packet) {
if (packet.indexOf("NOGATE") >= 0) return;
if (lastAprsPacket.path.indexOf("NOGATE") >= 0) return;
bool thirdPartyPacket = false;
String temp, Sender;
int firstColonIndex = packet.indexOf(":");
if (firstColonIndex > 5 && firstColonIndex < (packet.length() - 1) && packet[firstColonIndex + 1] == '}' && packet.indexOf("TCPIP") > 0) { // 3rd Party
thirdPartyPacket = true;
temp = packet.substring(packet.indexOf(":}") + 2);
Sender = temp.substring(0, temp.indexOf(">"));
} else {
temp = packet.substring(3);
Sender = packet.substring(3, packet.indexOf(">"));
}
String temp = (lastAprsPacket.header != "") ? packet.substring(packet.indexOf(":}") + 2) : packet.substring(3);
String stationCallsign = Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign;
if (Sender == stationCallsign) return; // Avoid listening to self packets
if (!thirdPartyPacket && Config.tacticalCallsign == "" && !Utils::callsignIsValid(Sender)) return; // No thirdParty + no tactical y no valid callsign
if (lastAprsPacket.sender == stationCallsign) return; // Avoid listening to self packets
if (lastAprsPacket.header == "" && Config.tacticalCallsign == "" && !Utils::callsignIsValid(lastAprsPacket.sender)) return; // No thirdParty + no tactical y no valid callsign
if (STATION_Utils::isIn25SegHashBuffer(Sender, temp.substring(temp.indexOf(":") + 2))) return;
if (STATION_Utils::isIn25SegHashBuffer(lastAprsPacket.sender, temp.substring(temp.indexOf(":") + 2))) return;
STATION_Utils::updateLastHeard(Sender);
Utils::typeOfPacket(temp, 2); // Digi
bool queryMessage = false;
int doubleColonIndex = temp.indexOf("::");
if (doubleColonIndex > 10) { // it's a message
String AddresseeAndMessage = temp.substring(doubleColonIndex + 2);
String Addressee = AddresseeAndMessage.substring(0, AddresseeAndMessage.indexOf(":"));
Addressee.trim();
if (Addressee == stationCallsign) { // it's a message for me!
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(Sender, AddresseeAndMessage, thirdPartyPacket);
STATION_Utils::updateLastHeard(lastAprsPacket.sender);
Utils::updateLoRaPacketDisplayInfo(lastAprsPacket, 1); // Digi
bool queryMessage = false;
if (lastAprsPacket.type == 1) { // MESSAGE
if (lastAprsPacket.addressee == stationCallsign) { // it's a message for me!
String AddresseeAndMessage = lastAprsPacket.addressee + ":" + lastAprsPacket.payload;
queryMessage = APRS_IS_Utils::processReceivedLoRaMessage(lastAprsPacket.sender, APRSPacketLib::checkForStartingBytes(AddresseeAndMessage));
}
}
if (queryMessage) return; // answer should not be repeated.
String loraPacket = generateDigipeatedPacket(packet.substring(3), thirdPartyPacket);
String loraPacket = generateDigipeatedPacket(packet.substring(3));
if (loraPacket != "") {
STATION_Utils::addToOutputPacketBuffer(loraPacket);
if (Config.digi.ecoMode != 1) displayToggle(true);
-94
View File
@@ -101,100 +101,6 @@ namespace GPS_Utils {
}
String decodeEncodedGPS(const String& packet) {
int indexOfExclamation = packet.indexOf(":!");
int indexOfEqual = packet.indexOf(":=");
const uint8_t OFFSET = 3; // Offset for encoded data in the packet
String infoGPS;
if (indexOfExclamation > 10) {
infoGPS = packet.substring(indexOfExclamation + OFFSET);
} else if (indexOfEqual > 10) {
infoGPS = packet.substring(indexOfEqual + OFFSET);
}
float decodedLatitude = APRSPacketLib::decodeBase91EncodedLatitude(infoGPS.substring(0,4));
float decodedLongitude = APRSPacketLib::decodeBase91EncodedLongitude(infoGPS.substring(4,8));
return buildDistanceAndComment(decodedLatitude, decodedLongitude, infoGPS.substring(12));
}
String getReceivedGPS(const String& packet) {
int indexOfExclamation = packet.indexOf(":!");
int indexOfEqual = packet.indexOf(":=");
int indexOfAt = packet.indexOf(":@");
String infoGPS;
if (indexOfExclamation > 10) {
infoGPS = packet.substring(indexOfExclamation + 2);
} else if (indexOfEqual > 10) {
infoGPS = packet.substring(indexOfEqual + 2);
} else if (indexOfAt > 10) {
infoGPS = packet.substring(indexOfAt + 9); // 9 = 2+7 (when 7 is timestamp characters)
}
String Latitude = infoGPS.substring(0,8); // First 8 characters are Latitude
int latitudeColonIndex = Latitude.indexOf(".");
float convertedLatitude = Latitude.substring(0,2).toFloat(); // First 2 digits (Degrees)
convertedLatitude += Latitude.substring(2,4).toFloat() / 60; // Next 2 digits (Minutes)
convertedLatitude += Latitude.substring(latitudeColonIndex + 1, latitudeColonIndex + 3).toFloat() / (60*100);
if (Latitude.endsWith("S")) convertedLatitude = -convertedLatitude; // Handle Southern Hemisphere
String Longitude = infoGPS.substring(9,18); // Next 9 characters are Longitude
int longitudeColonIndex = Longitude.indexOf(".");
float convertedLongitude = Longitude.substring(0,3).toFloat(); // First 3 digits (Degrees)
convertedLongitude += Longitude.substring(3,5).toFloat() / 60; // Next 2 digits (Minutes)
convertedLongitude += Longitude.substring(longitudeColonIndex + 1, longitudeColonIndex + 3).toFloat() / (60*100);
if (Longitude.endsWith("W")) convertedLongitude = -convertedLongitude; // Handle Western Hemisphere
return buildDistanceAndComment(convertedLatitude, convertedLongitude, infoGPS.substring(19));
}
String getDistanceAndComment(const String& packet) {
int indexOfAt = packet.indexOf(":@");
if (indexOfAt > 10) return getReceivedGPS(packet);
const uint8_t nonEncondedLatitudeOffset = 9; // "N" / "S"
const uint8_t nonEncondedLongitudeOffset = 19; // "E" / "W"
const uint8_t encodedByteOffset = 14;
int indexOfExclamation = packet.indexOf(":!");
int indexOfEqual = packet.indexOf(":=");
int baseIndex = - 1;
if (indexOfExclamation > 10) {
baseIndex = indexOfExclamation;
} else if (indexOfEqual > 10) {
baseIndex = indexOfEqual;
}
if (baseIndex == -1) return " _ / _ / _ ";
int latitudeIndex = baseIndex + nonEncondedLatitudeOffset;
int longitudeIndex = baseIndex + nonEncondedLongitudeOffset;
int encodedByteIndex = baseIndex + encodedByteOffset;
int packetLength = packet.length();
if (latitudeIndex < packetLength && longitudeIndex < packetLength) {
char latChar = packet[latitudeIndex];
char lngChar = packet[longitudeIndex];
if ((latChar == 'N' || latChar == 'S') && (lngChar == 'E' || lngChar == 'W')) return getReceivedGPS(packet);
}
if (encodedByteIndex < packetLength) {
char byteChar = packet[encodedByteIndex];
if (byteChar == 'G' || byteChar == 'Q' || byteChar == '[' || byteChar == 'H' || byteChar == 'X' || byteChar == '3') return decodeEncodedGPS(packet);
}
return " _ / _ / _ ";
}
/*String getDistanceAndCommentFromMicE(const String& packet) {
int indexOfMicE1 = packet.indexOf(":`");
int indexOfMicE2 = packet.indexOf(":\'");
if (indexOfMicE1 > 10 || indexOfMicE2 > 10) {
return decodeEncodedGPS(packet);
}
return " _ / _ / _ ";
}*/
void setup() {
#ifdef HAS_GPS
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) {
+46 -124
View File
@@ -20,7 +20,6 @@
#include <RadioLib.h>
#include "configuration.h"
#include "network_manager.h"
#include "lora_utils.h"
#include "aprs_is_utils.h"
#include "station_utils.h"
#include "board_pinout.h"
@@ -42,6 +41,8 @@ bool transmitFlag = true;
#define DIFS_SLOTS 2 // Number of secuential CAD slots to consider a free channel to Tx
int backoffMax = 4; // Max Backoff value (number of CAD slots to wait before Tx)
#define CAD_MAX_WAIT_MS 10000 // Max total time waiting for a free channel, then the packet is dropped
unsigned long cadStartTime = 0;
#ifdef HAS_SX1262
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
@@ -66,16 +67,11 @@ int backoffMax = 4; // Max Backoff value (number of CAD slots to wai
int rssi, freqError;
float snr;
APRSPacket lastAprsPacket;
namespace LoRa_Utils {
unsigned int packetSendSuccess = 0;
unsigned int packetSendFail = 0;
unsigned int packetReceiveSuccess = 0;
unsigned int packetReceiveFail = 0;
unsigned int packetBlacklisted = 0;
unsigned long packetCountStartTime = 0;
static String sanitizeForWeb(const String& s) { // replaces non-printable ASCII with '.' for WebUI display
String out = s;
for (int i = 0; i < (int)out.length(); i++) {
@@ -97,10 +93,17 @@ namespace LoRa_Utils {
#else
SPI.begin(RADIO_SCLK_PIN, RADIO_MISO_PIN, RADIO_MOSI_PIN);
#endif
#ifdef RADIO_ANT_SW_PIN // RAK3312 antenna switch needs power
pinMode(RADIO_ANT_SW_PIN, OUTPUT);
digitalWrite(RADIO_ANT_SW_PIN, RADIO_ANT_SW_ON_STATE);
#endif
float freq = (float)Config.loramodule.rxFreq / 1000000;
#if defined(RADIO_HAS_XTAL)
radio.XTAL = true;
#endif
#if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // before begin() so RF switch is driven from start (Ebyte E22/E32 1W, QRP Labs LightGateway)
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
#endif
int state = radio.begin(freq);
if (state != RADIOLIB_ERR_NONE) {
Utils::println("Starting LoRa failed! State: " + String(state));
@@ -128,10 +131,6 @@ namespace LoRa_Utils {
radio.setBandwidth(signalBandwidth);
radio.setCRC(true);
#if (defined(RADIO_RXEN) && defined(RADIO_TXEN)) // QRP Labs LightGateway has 400M22S (SX1268)
radio.setRfSwitchPins(RADIO_RXEN, RADIO_TXEN);
#endif
/*#ifdef SX126X_DIO2_AS_RF_SWITCH
radio.setRfSwitchPins(RADIO_RXEN, RADIOLIB_NC);
radio.setDio2AsRfSwitch(true);
@@ -198,8 +197,13 @@ namespace LoRa_Utils {
return true;
}
bool cadTimedOut() {
return millis() - cadStartTime > CAD_MAX_WAIT_MS;
}
void waitForDIFS() {
while (!doDIFS()) {
if (cadTimedOut()) return;
Serial.println("CAD/DIFS failed, retry...");
}
}
@@ -207,6 +211,7 @@ namespace LoRa_Utils {
void doBEB() {
int backoffCounter = random(1, backoffMax + 1);
while (backoffCounter > 0) {
if (cadTimedOut()) return;
if (doCAD()) {
waitForDIFS(); // busy channel: freeze backoff and restart DIFS
} else {
@@ -228,24 +233,30 @@ namespace LoRa_Utils {
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
#endif
bool cadDropped = false;
if (Config.loramodule.cadActive) {
cadStartTime = millis();
waitForDIFS(); // DIFS (Distributed Inter-Frame Space)
doBEB(); // BEB (Binary Exponential Backoff)
if (cadTimedOut()) {
Utils::println("CAD timeout, packet dropped: " + newPacket);
cadDropped = true;
}
}
int state = radio.transmit("\x3c\xff\x01" + newPacket);
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE) {
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(3, newPacket, 0, 0.0, 0); // TX
if (!cadDropped) {
int state = radio.transmit("\x3c\xff\x01" + newPacket);
transmitFlag = true;
if (state == RADIOLIB_ERR_NONE) {
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::logLoRaTx(newPacket);
}
Utils::print("---> LoRa Packet Tx : ");
Utils::println(newPacket);
} else {
Utils::print(F("failed, code "));
Utils::println(String(state));
}
Utils::print("---> LoRa Packet Tx : ");
Utils::println(newPacket);
incrementPacketSendSuccess();
} else {
Utils::print(F("failed, code "));
Utils::println(String(state));
incrementPacketSendFail();
}
#ifdef INTERNAL_LED_PIN
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, LOW); // disabled in Ultra Eco Mode
@@ -255,6 +266,7 @@ namespace LoRa_Utils {
changeFreqRx();
}
}
if (cadDropped) radio.startReceive(); // no Tx end IRQ will restart Rx
}
String receivePacketFromSleep() {
@@ -281,14 +293,13 @@ namespace LoRa_Utils {
if (packet != "") {
String sender = packet.substring(3, packet.indexOf(">"));
bool isBlacklisted = STATION_Utils::isBlacklisted(sender);
if (packet.substring(0,3) == "\x3c\xff\x01" && !isBlacklisted) { // avoid processing BlackListed stations
if (packet.substring(0,3) == "\x3c\xff\x01" && !STATION_Utils::isBlacklisted(sender)) { // avoid processing BlackListed stations
rssi = radio.getRSSI();
snr = radio.getSNR();
freqError = radio.getFrequencyError();
Utils::println("<--- LoRa Packet Rx : " + packet.substring(3));
Utils::println("(RSSI:" + String(rssi) + " / SNR:" + String(snr) + " / FreqErr:" + String(freqError) + ")");
Utils::println("<--- LoRa Packet Rx (RSSI:" + String(rssi) + " | SNR:" + String(snr) + " | FreqErr:" + String(freqError) + ") : " + packet.substring(3));
lastAprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
if (Config.digi.ecoMode == 0) {
if (receivedPackets.size() >= 10) {
receivedPackets.erase(receivedPackets.begin());
@@ -300,20 +311,15 @@ namespace LoRa_Utils {
receivedPacket.SNR = snr;
receivedPackets.push_back(receivedPacket);
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
incrementPacketReceiveSuccess();
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (los que traen posición)
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.path, aprsPacket.rssi, aprsPacket.snr);
if (lastAprsPacket.type == 0 || lastAprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (only ones with position)
MAP_Utils::upsert(lastAprsPacket.sender, lastAprsPacket.latitude, lastAprsPacket.longitude, lastAprsPacket.path, lastAprsPacket.overlay + lastAprsPacket.symbol, lastAprsPacket.rssi, lastAprsPacket.snr);
}
}
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
SYSLOG_Utils::logLoRaRx(lastAprsPacket, packet, rssi, snr, freqError); // RX
}
} else {
if (isBlacklisted) {
incrementPacketBlacklisted();
}
packet = "";
}
return packet;
@@ -324,15 +330,13 @@ namespace LoRa_Utils {
freqError = radio.getFrequencyError();
Utils::println(F("CRC error!"));
if (Config.syslog.active && networkManager->isConnected()) {
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
SYSLOG_Utils::logCRCError(packet, rssi, snr, freqError);
}
packet = "";
incrementPacketReceiveFail();
} else {
Utils::print(F("failed, code "));
Utils::println(String(state));
packet = "";
incrementPacketReceiveFail();
}
}
}
@@ -345,91 +349,9 @@ namespace LoRa_Utils {
void sleepRadio() {
radio.sleep();
#ifdef RADIO_ANT_SW_PIN
digitalWrite(RADIO_ANT_SW_PIN, !RADIO_ANT_SW_ON_STATE);
#endif
}
bool isBeyondWindow(unsigned long time) {
unsigned long window = 24*60*60*1000;
if (time > packetCountStartTime + window) {
return true;
}
return false;
}
void resetPacketCounters(unsigned long time) {
packetCountStartTime = time;
packetSendSuccess = 0;
packetSendFail = 0;
packetReceiveSuccess = 0;
packetReceiveFail = 0;
}
void incrementPacketSendSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
resetPacketCounters(now);
packetSendSuccess = 1;
} else {
packetSendSuccess++;
}
}
unsigned int getPacketSendSuccess() {
return packetSendSuccess;
}
void incrementPacketSendFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetSendFail = 1;
} else {
packetSendFail++;
}
}
unsigned int getPacketSendFail() {
return packetSendFail;
}
void incrementPacketReceiveSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveSuccess = 1;
} else {
packetReceiveSuccess++;
}
}
unsigned int getPacketReceiveSuccess() {
return packetReceiveSuccess;
}
void incrementPacketReceiveFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveFail = 1;
} else {
packetReceiveFail++;
}
}
unsigned int getPacketReceiveFail() {
return packetReceiveFail;
}
void incrementPacketBlacklisted() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetBlacklisted = 1;
} else {
packetBlacklisted++;
}
}
unsigned int getPacketBlacklisted() {
return packetBlacklisted;
}
}
-9
View File
@@ -98,13 +98,4 @@ namespace MQTT_Utils {
pubSub.setCallback(receivedFromMqtt);
}
String getMQTTStatus() {
if (!Config.mqtt.active) {
return "Inactive";
}
if (pubSub.connected()) {
return "Connected";
}
return "Not connected";
}
}
+4
View File
@@ -339,6 +339,10 @@ bool NetworkManager::isWifiAPActive() const {
return _wifiAPmode;
}
bool NetworkManager::hasActiveInterface() const {
return _wifiSTAmode || _wifiAPmode || _ethernetMode;
}
// Check if Ethernet is connected
bool NetworkManager::isEthernetConnected() const {
return _ethernetMode && _ethernetConnected && ETH.linkUp();
+21 -33
View File
@@ -16,8 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <esp_sntp.h>
#include "configuration.h"
#include "network_manager.h"
#include "ntp_utils.h"
@@ -26,18 +25,19 @@
extern Configuration Config;
extern NetworkManager *networkManager;
WiFiUDP ntpUDP;
NTPClient* timeClient = nullptr;
bool ntpStarted = false;
bool ntpSynced = false;
namespace NTP_Utils {
bool setup() {
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && Config.callsign != "NOCALL-10") {
int gmt = Config.ntp.gmtCorrection * 3600;
long gmt = Config.ntp.gmtCorrection * 3600;
Serial.println("[NTP] Setting up, TZ offset: " + String(gmt) + " Server: " + Config.ntp.server);
timeClient = new NTPClient(ntpUDP, Config.ntp.server.c_str(), gmt, 15 * 60 * 1000); // Update interval 15 min
timeClient->begin();
sntp_set_sync_interval(3 * 60 * 60 * 1000); // Update interval 3 hours (ESP32 internal clock drift is negligible in that time)
configTime(gmt, 0, Config.ntp.server.c_str()); // SNTP runs in background (DNS + retries) and never blocks the loop
ntpStarted = true;
return true;
}
return false;
@@ -47,39 +47,27 @@ namespace NTP_Utils {
if (!networkManager->isConnected() || Config.digi.ecoMode != 0 || Config.callsign == "NOCALL-10") {
return;
}
if (timeClient == nullptr) {
if (!ntpStarted) {
if (!setup()) {
return;
}
}
timeClient->update();
if (!ntpSynced) {
struct tm timeinfo;
if (getLocalTime(&timeinfo, 0)) {
ntpSynced = true;
Serial.println("[NTP] Time synced: " + getFormatedTime());
}
}
}
String getFormatedTime() {
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && timeClient != nullptr) {
return timeClient->getFormattedTime();
}
return "DigiEcoMode Active";
if (Config.digi.ecoMode != 0) return "DigiEcoMode Active";
struct tm timeinfo;
if (!ntpStarted || !getLocalTime(&timeinfo, 0)) return ""; // 0 ms: don't wait (default timeout would block 5 s)
char formatedTime[9];
strftime(formatedTime, sizeof(formatedTime), "%H:%M:%S", &timeinfo);
return String(formatedTime);
}
unsigned long getTimeMillis() {
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && timeClient != nullptr) {
return timeClient->getEpochTime()*1000;
}
return millis();
}
String getNTPStatus() {
if ((networkManager == nullptr) || (timeClient == nullptr)) {
return "Not connected";
}
if (networkManager->isConnected() && Config.digi.ecoMode != 0) {
return "DigiEcoMode Active";
}
if (timeClient->isTimeSet()) {
return "OK";
}
return "Error";
}
}
+24
View File
@@ -20,6 +20,7 @@
#include "battery_utils.h"
#include "board_pinout.h"
#include "power_utils.h"
#include "wx_utils.h"
#include "utils.h"
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
@@ -210,6 +211,23 @@ namespace POWER_Utils {
#endif
}
#ifdef SOLAR_CHARGE_PIN
void checkSolarCharge() { // called on each beacon: disable solar charging when too cold for Li-Ion
static bool chargingEnabled = true;
float boardTemp = WX_Utils::readBoardTemperature();
if (isnan(boardTemp)) {
chargingEnabled = true; // sensor failure: keep charging (previous behaviour)
} else if (chargingEnabled && boardTemp < SOLAR_CHARGE_DISABLE_TEMP) {
chargingEnabled = false;
Serial.println("[SOLAR] Charging disabled, board temp: " + String(boardTemp, 1) + "C");
} else if (!chargingEnabled && boardTemp > SOLAR_CHARGE_ENABLE_TEMP) {
chargingEnabled = true;
Serial.println("[SOLAR] Charging enabled, board temp: " + String(boardTemp, 1) + "C");
}
digitalWrite(SOLAR_CHARGE_PIN, chargingEnabled ? SOLAR_CHARGE_ENABLED_STATE : !SOLAR_CHARGE_ENABLED_STATE);
}
#endif
void setup() {
#ifdef HAS_AXP192
Wire.begin(SDA, SCL);
@@ -254,6 +272,12 @@ namespace POWER_Utils {
pinMode(BATTERY_PIN, INPUT);
#endif
#ifdef SOLAR_CHARGE_PIN
pinMode(SOLAR_CHARGE_PIN, OUTPUT);
digitalWrite(SOLAR_CHARGE_PIN, SOLAR_CHARGE_ENABLED_STATE); // solar charging enabled by default
gpio_hold_dis((gpio_num_t)SOLAR_CHARGE_PIN); // release hold kept during deep sleep (if any)
#endif
#ifdef INTERNAL_LED_PIN
pinMode(INTERNAL_LED_PIN, OUTPUT);
digitalWrite(INTERNAL_LED_PIN, LOW);
-9
View File
@@ -50,7 +50,6 @@ std::vector<Packet25SegBuffer> packet25SegBuffer;
bool saveNewDigiEcoModeConfig = false;
bool packetIsBeacon = false;
uint32_t bootTimeMillis = 0;
namespace STATION_Utils {
@@ -238,12 +237,4 @@ namespace STATION_Utils {
outputPacketBuffer.emplace_back(OutputPacketBuffer{packet, flag});
}
void markBootTime() {
bootTimeMillis = millis();
}
unsigned long getBootTime() {
return bootTimeMillis;
}
}
+132 -107
View File
@@ -16,6 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <APRSPacketLib.h>
#include <WiFiUdp.h>
#include "configuration.h"
#include "network_manager.h"
@@ -30,117 +31,150 @@ extern String versionNumber;
WiFiUDP udpClient;
namespace {
String createSyslogStart() {
String syslogStartPacket = "<165>1 - ";
syslogStartPacket.concat(Config.callsign);
syslogStartPacket.concat(" CA2RXU_LoRa_iGate_");
syslogStartPacket.concat(versionNumber);
syslogStartPacket.concat(" - - - "); //RFC5424 The Syslog Protocol
return syslogStartPacket;
}
void sendSyslogPacket(const String& syslogPacket) {
udpClient.beginPacket(Config.syslog.server.c_str(), Config.syslog.port);
udpClient.write((const uint8_t*)syslogPacket.c_str(), syslogPacket.length());
udpClient.endPacket();
}
String formatSignalData(int rssi, float snr, int freqError) {
char signalData[35];
snprintf(signalData, sizeof(signalData), " / %ddBm / %.2fdB / %dHz", rssi, snr, freqError);
return String(signalData);
}
}
namespace SYSLOG_Utils {
void log(const uint8_t type, const String& packet, const int rssi, const float snr, const int freqError) {
void logLoRaRx(APRSPacket& aprsPacket, const String& packet, const int rssi, const float snr, const int freqError) {
if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = "<165>1 - ";
syslogPacket.concat(Config.callsign);
syslogPacket.concat(" CA2RXU_LoRa_iGate_");
syslogPacket.concat(versionNumber);
syslogPacket.concat(" - - - "); //RFC5424 The Syslog Protocol
String syslogPacket = createSyslogStart();
syslogPacket.concat("RX / ");
char signalData[35];
snprintf(signalData, sizeof(signalData), " / %ddBm / %.2fdB / %dHz", rssi, snr, freqError);
int colonIndex = packet.indexOf(":");
char nextChar = packet[colonIndex + 1];
int greaterThanIndex = packet.indexOf(">");
int telemetryPacketIndex = packet.indexOf(":T#");
String sender = packet.substring(3, greaterThanIndex);
switch (type) {
case 0: // CRC
syslogPacket.concat("CRC / CRC-ERROR / ");
syslogPacket.concat(packet);
syslogPacket.concat(signalData);
switch (aprsPacket.type) {
case 1: // MESSAGE
syslogPacket.concat("MESSAGE / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(aprsPacket.addressee);
syslogPacket.concat(":");
syslogPacket.concat(aprsPacket.payload);
break;
case 1: // RX
syslogPacket.concat("RX / ");
if (nextChar == ':') {
syslogPacket.concat("MESSAGE / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.substring(colonIndex + 2));
} else if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
syslogPacket.concat("GPS / ");
syslogPacket.concat(sender);
syslogPacket.concat(" / ");
if (packet.indexOf("WIDE1-1") > 10) {
syslogPacket.concat(packet.substring(greaterThanIndex + 1, packet.indexOf(",")));
syslogPacket.concat(" / WIDE1-1");
} else {
syslogPacket.concat(packet.substring(greaterThanIndex + 1, colonIndex));
syslogPacket.concat(" / -");
case 0: // GPS
syslogPacket.concat("GPS / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" / ");
if (aprsPacket.path.indexOf("WIDE1-1") != -1) {
syslogPacket.concat(aprsPacket.tocall);
syslogPacket.concat(" / WIDE1-1");
} else {
syslogPacket.concat(aprsPacket.tocall);
if (aprsPacket.path != "") {
syslogPacket.concat(",");
syslogPacket.concat(aprsPacket.path);
}
} else if (nextChar == '>') {
syslogPacket.concat("STATUS / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.substring(colonIndex + 2));
} else if (nextChar == '`' || nextChar == '\'') { // both Mic-E DTIs are valid
syslogPacket.concat("MIC-E / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(String(nextChar)); // restore the actual indicator received
syslogPacket.concat(packet.substring(colonIndex + 2));
// append distance for Mic-E, same as the GPS path
//syslogPacket.concat(" / ");
//syslogPacket.concat(GPS_Utils::getDistanceAndComment(packet));
} else if (nextChar == ';') {
syslogPacket.concat("OBJECT / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.substring(colonIndex + 2));
} else if (telemetryPacketIndex >= 10 && packet.indexOf(":=/") == -1) {
syslogPacket.concat("TELEMETRY / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.substring(telemetryPacketIndex + 3));
} else {
syslogPacket.concat(packet);
}
syslogPacket.concat(signalData);
if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
syslogPacket.concat(" / ");
syslogPacket.concat(GPS_Utils::getDistanceAndComment(packet));
syslogPacket.concat(" / -");
}
break;
case 2: // APRSIS TX
syslogPacket.concat("APRSIS TX / ");
if (nextChar == '>') {
syslogPacket.concat("StartUp_Status / ");
syslogPacket.concat(packet.substring(colonIndex + 2));
} else if (nextChar == ':') {
syslogPacket.concat("QUERY / ");
syslogPacket.concat(packet);
} else {
syslogPacket.concat("BEACON / ");
syslogPacket.concat(packet);
}
case 2: // STATUS
syslogPacket.concat("STATUS / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(aprsPacket.payload);
break;
case 3: // TX
syslogPacket.concat("TX / ");
if (packet.indexOf("RFONLY") > 10) {
syslogPacket.concat("RFONLY / ");
syslogPacket.concat(packet);
} else if (nextChar == ':') {
syslogPacket.concat("MESSAGE / ");
syslogPacket.concat(sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.substring(colonIndex + 2));
} else {
syslogPacket.concat(packet);
}
case 4: // MIC-E
syslogPacket.concat("MIC-E / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(packet.indexOf(":`") != -1 ? "`" : "'"); // restore the actual indicator received
break;
default:
syslogPacket = "<165>1 - ERROR LoRa - - - ERROR / Error in Syslog Packet"; //RFC5424 The Syslog Protocol
case 5: // OBJECT
syslogPacket.concat("OBJECT / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(aprsPacket.payload);
break;
case 3: // TELEMETRY
syslogPacket.concat("TELEMETRY / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(aprsPacket.payload);
break;
default: // type == 6, unrecognized -- payload is the full, uncut packet
syslogPacket.concat(aprsPacket.payload);
break;
}
udpClient.beginPacket(Config.syslog.server.c_str(), Config.syslog.port);
udpClient.write((const uint8_t*)syslogPacket.c_str(), syslogPacket.length());
udpClient.endPacket();
syslogPacket.concat(formatSignalData(rssi, snr, freqError));
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // GPS or Mic-E -- both already carry
syslogPacket.concat(" / "); // clean lat/lon + comment in aprsPacket
syslogPacket.concat(GPS_Utils::buildDistanceAndComment(aprsPacket.latitude, aprsPacket.longitude, aprsPacket.payload));
}
sendSyslogPacket(syslogPacket);
}
}
void logAPRSISTx(const String& packet) {
if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = createSyslogStart();
syslogPacket.concat("APRSIS TX / ");
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet, 0, 0, 0);
if (aprsPacket.type == 2) { // STATUS
syslogPacket.concat("StartUp_Status / ");
syslogPacket.concat(aprsPacket.payload);
} else if (aprsPacket.type == 1) { // MESSAGE
syslogPacket.concat("QUERY / ");
syslogPacket.concat(packet);
} else {
syslogPacket.concat("BEACON / ");
syslogPacket.concat(packet);
}
sendSyslogPacket(syslogPacket);
}
}
void logLoRaTx(const String& packet) {
if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = createSyslogStart();
syslogPacket.concat("TX / ");
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet, 0, 0, 0);
if (aprsPacket.path.indexOf("RFONLY") != -1) {
syslogPacket.concat("RFONLY / ");
syslogPacket.concat(packet);
} else if (aprsPacket.type == 1) {
syslogPacket.concat("MESSAGE / ");
syslogPacket.concat(aprsPacket.sender);
syslogPacket.concat(" ---> ");
syslogPacket.concat(aprsPacket.addressee);
syslogPacket.concat(":");
syslogPacket.concat(aprsPacket.payload);
} else {
syslogPacket.concat(packet);
}
sendSyslogPacket(syslogPacket);
}
}
void logCRCError(const String& packet, const int rssi, const float snr, const int freqError) {
if (Config.syslog.active && networkManager->isConnected()) {
String syslogPacket = createSyslogStart();
syslogPacket.concat("CRC / CRC-ERROR / ");
syslogPacket.concat(packet);
syslogPacket.concat(formatSignalData(rssi, snr, freqError));
sendSyslogPacket(syslogPacket);
}
}
@@ -151,13 +185,4 @@ namespace SYSLOG_Utils {
}
}
String getSyslogStatus() {
if (Config.syslog.active) {
if (networkManager->isConnected()) {
return "Active";
}
return "Not connected";
}
return "Inactive";
}
}
-92
View File
@@ -1,92 +0,0 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include "time_utils.h"
#include <iostream>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
namespace TIME_Utils {
String getFormattedTime(unsigned long timeMillis) {
// Split into total seconds and remaining milliseconds
std::time_t seconds = timeMillis / 1000;
// Convert seconds to UTC calendar structure safely
std::tm time_info;
gmtime_r(&seconds, &time_info); // POSIX safe variant
// Format the base date/time structure
char buffer[30];
std::strftime(buffer, sizeof(buffer), "%H:%M:%S", &time_info);
// Append fractional milliseconds and UTC anchor 'Z'
std::ostringstream oss;
oss << buffer;
return String(oss.str().c_str());
}
String getFormattedDateTime(unsigned long timeMillis) {
if (!isValid(timeMillis)) {
return "Not set";
}
// Split into total seconds and remaining milliseconds
std::time_t seconds = timeMillis / 1000;
// Convert seconds to UTC calendar structure safely
std::tm time_info;
gmtime_r(&seconds, &time_info); // POSIX safe variant
// Format the base date/time structure
char buffer[30];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &time_info);
// Append fractional milliseconds and UTC anchor 'Z'
std::ostringstream oss;
oss << buffer;
return String(String(oss.str().c_str())+" "+String(timeMillis));
}
boolean isValid(unsigned long timeMillis) {
if (timeMillis < 1767225600) { // 2026-01-01T00:00:00
return false;
}
return true;
}
String generateDuration(unsigned long startMillis, unsigned long endMillis) {
unsigned long diff = endMillis - startMillis;
diff /= 1000; // convert to seconds
unsigned long seconds = diff - ((diff/60) * 60);
diff -= seconds;
diff /= 60; // convert to minutes
unsigned long minutes = diff - ((diff/60) * 60);
diff -= minutes;
diff /= 60; // convert to hours
unsigned long hours = diff - ((diff/24) * 24);
diff -= hours;
diff /= 24; // convert to days
unsigned long days = diff;
return String(days) + " d " + String(hours) + " h " + String(minutes) + " m " + String(seconds) + " s" ;
}
}
+54 -18
View File
@@ -18,6 +18,7 @@
#include <WiFi.h>
#include "ESPmDNS.h"
#include "network_manager.h"
#include "configuration.h"
#include "station_utils.h"
#include "kiss_protocol.h"
@@ -28,6 +29,7 @@
extern Configuration Config;
extern NetworkManager *networkManager;
extern WiFiClient aprsIsClient;
extern bool passcodeValid;
@@ -47,7 +49,7 @@ String inputSerialBuffer = "";
namespace TNC_Utils {
void setup() {
if (Config.tnc.enableServer && Config.digi.ecoMode == 0) {
if (Config.tnc.enableServer && Config.digi.ecoMode == 0 && networkManager->hasActiveInterface()) { // no WiFi STA/AP or Ethernet started = no TCP/IP stack
tncServer.stop();
tncServer.begin();
String host = "igate-" + Config.callsign;
@@ -78,7 +80,23 @@ namespace TNC_Utils {
}
}
void handleInputData(char character, int bufferIndex) {
void processInputFrame(const String& frame, int bufferIndex, const String& protocolLabel) {
if (bufferIndex != -1) {
Utils::print("<--- Got from TNC (" + protocolLabel + ") : ");
Utils::println(frame);
}
String sender = frame.substring(0,frame.indexOf(">"));
if (Config.tnc.acceptOwn || sender != Config.callsign) {
if (Config.loramodule.txActive) STATION_Utils::addToOutputPacketBuffer(frame);
if (Config.tnc.aprsBridgeActive && Config.aprs_is.active && passcodeValid && aprsIsClient.connected()) APRS_IS_Utils::upload(frame);
} else {
Utils::println("Ignored own frame from " + protocolLabel);
}
}
void handleInputDataKISS(char character, int bufferIndex) {
String* data = (bufferIndex == -1) ? &inputSerialBuffer : &inputServerBuffer[bufferIndex];
if (data->length() == 0 && character != (char)FEND) return;
@@ -89,19 +107,7 @@ namespace TNC_Utils {
const String& frame = decodeKISS(*data, isDataFrame);
if (isDataFrame) {
if (bufferIndex != -1) {
Utils::print("<--- Got from TNC : ");
Utils::println(frame);
}
String sender = frame.substring(0,frame.indexOf(">"));
if (Config.tnc.acceptOwn || sender != Config.callsign) {
if (Config.loramodule.txActive) STATION_Utils::addToOutputPacketBuffer(frame);
if (Config.tnc.aprsBridgeActive && Config.aprs_is.active && passcodeValid && aprsIsClient.connected()) APRS_IS_Utils::upload(frame);
} else {
Utils::println("Ignored own frame from KISS");
}
processInputFrame(frame, bufferIndex, "KISS");
}
data->clear();
}
@@ -111,6 +117,36 @@ namespace TNC_Utils {
}
}
void handleInputDataTNC2(char character, int bufferIndex) {
String* data = (bufferIndex == -1) ? &inputSerialBuffer : &inputServerBuffer[bufferIndex];
if (character == '\r') return;
if (character == '\n') {
String frame = *data;
frame.trim();
data->clear();
if (frame.length() > 0 && frame.indexOf(':') != -1 && frame.indexOf('>') != -1) {
processInputFrame(frame, bufferIndex, "TNC2");
}
return;
}
data->concat(character);
if (data->length() > 255) {
data->clear();
}
}
void handleInputData(char character, int bufferIndex) {
if (Config.tnc.kissProtocol) {
handleInputDataKISS(character, bufferIndex);
} else {
handleInputDataTNC2(character, bufferIndex);
}
}
void readFromClients() {
for (int i = 0; i < MAX_CLIENTS; i++) {
auto client = clients[i];
@@ -138,13 +174,13 @@ namespace TNC_Utils {
void sendToClients(const String& packet, bool stripBytes) {
String cleanPacket = stripBytes ? packet.substring(3): packet;
const String kissEncoded = encodeKISS(cleanPacket);
const String encoded = Config.tnc.kissProtocol ? encodeKISS(cleanPacket) : (cleanPacket + "\r\n");
for (int i = 0; i < MAX_CLIENTS; i++) {
auto client = clients[i];
if (client != nullptr) {
if (client->connected()) {
client->print(kissEncoded);
client->print(encoded);
client->flush();
} else {
delete client;
@@ -158,7 +194,7 @@ namespace TNC_Utils {
void sendToSerial(const String& packet, bool stripBytes) {
String cleanPacket = stripBytes ? packet.substring(3): packet;
Serial.print(encodeKISS(cleanPacket));
Serial.print(Config.tnc.kissProtocol ? encodeKISS(cleanPacket) : (cleanPacket + "\r\n"));
Serial.flush();
}
+68 -40
View File
@@ -25,6 +25,7 @@
#include "battery_utils.h"
#include "aprs_is_utils.h"
#include "board_pinout.h"
#include "power_utils.h"
#include "syslog_utils.h"
#include "A7670_utils.h"
#include "lora_utils.h"
@@ -89,7 +90,7 @@ namespace Utils {
if (sendOverAPRSIS) {
APRS_IS_Utils::upload(statusPacket);
SYSLOG_Utils::log(2, statusPacket, 0, 0.0, 0); // APRSIS TX
SYSLOG_Utils::logAPRSISTx(statusPacket);
} else {
STATION_Utils::addToOutputPacketBuffer(statusPacket, true); // treated also as beacon on Tx Freq
}
@@ -168,6 +169,9 @@ namespace Utils {
#endif
if (beaconUpdate) {
#ifdef SOLAR_CHARGE_PIN
POWER_Utils::checkSolarCharge(); // solar boards: enable/disable charging by board temperature
#endif
if (!Config.display.alwaysOn && Config.display.timeout != 0) displayToggle(true);
TELEMETRY_Utils::checkEUPInterval();
@@ -288,7 +292,7 @@ namespace Utils {
#else
APRS_IS_Utils::upload(beaconPacket);
#endif
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::log(1, "tcp" + beaconPacket, 0, 0.0, 0); // APRSIS TX
if (Config.syslog.logBeaconOverTCPIP) SYSLOG_Utils::logAPRSISTx(beaconPacket);
}
if (Config.beacon.sendViaRF || backupDigiMode) {
@@ -325,21 +329,65 @@ namespace Utils {
}
}
void typeOfPacket(const String& packet, const uint8_t packetType) {
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType) {
switch (packetType) {
case 0: // LoRa-APRS
fifthLine = "LoRa Rx ----> APRS-IS";
break;
case 1: // APRS-LoRa
fifthLine = "APRS-IS ----> LoRa Tx";
break;
case 2: // Digipeater
case 1: // Digipeater
fifthLine = "LoRa Rx ----> LoRa Tx";
break;
}
int firstColonIndex = packet.indexOf(":");
char nextChar = packet[firstColonIndex + 1];
String sender = aprsPacket.sender;
for (int i = sender.length(); i < 9; i++) {
sender += " ";
}
sixthLine = sender;
switch (aprsPacket.type) {
case 1: // MESSAGE
sixthLine += "> MESSAGE";
break;
case 2: // STATUS
sixthLine += "> NEW STATUS";
break;
case 0: // GPS
sixthLine += "> GPS BEACON";
if (!Config.syslog.active) GPS_Utils::buildDistanceAndComment(aprsPacket.latitude, aprsPacket.longitude, aprsPacket.payload);
seventhLine = "RSSI:";
seventhLine += String(rssi);
seventhLine += "dBm";
seventhLine += (rssi <= -100) ? " " : " ";
if (distance.indexOf(".") == 1) seventhLine += " ";
seventhLine += "D:";
seventhLine += distance;
seventhLine += "km";
break;
case 3: // TELEMETRY
sixthLine += "> TELEMETRY";
break;
case 4: // MIC-E
sixthLine += "> MIC-E";
break;
case 5: // OBJECT
sixthLine += "> OBJECT";
break;
default: // unrecognized
sixthLine += "> ?????????";
break;
}
if (aprsPacket.type != 0) { // Common assignment for non-GPS cases
seventhLine = "RSSI:";
seventhLine += String(rssi);
seventhLine += "dBm SNR: ";
seventhLine += String(snr);
seventhLine += "dBm";
}
}
void updateAPRSISPacketDisplayInfo(const String& packet) {
fifthLine = "APRS-IS ----> LoRa Tx";
String sender = packet.substring(0,packet.indexOf(">"));
for (int i = sender.length(); i < 9; i++) {
@@ -347,37 +395,12 @@ namespace Utils {
}
sixthLine = sender;
if (nextChar == ':') {
if (packet.indexOf("::") > 0) {
sixthLine += "> MESSAGE";
} else if (nextChar == '>') {
sixthLine += "> NEW STATUS";
} else if (nextChar == '!' || nextChar == '=' || nextChar == '@') {
sixthLine += "> GPS BEACON";
if (!Config.syslog.active) GPS_Utils::getDistanceAndComment(packet); // to be checked!!!
seventhLine = "RSSI:";
seventhLine += String(rssi);
seventhLine += "dBm";
seventhLine += (rssi <= -100) ? " " : " ";
if (distance.indexOf(".") == 1) seventhLine += " ";
seventhLine += "D:";
seventhLine += distance;
seventhLine += "km";
} else if (nextChar == '`' || nextChar == '\'') {
sixthLine += "> MIC-E";
} else if (nextChar == ';') {
sixthLine += "> OBJECT";
} else if (packet.indexOf(":T#") >= 10 && packet.indexOf(":=/") == -1) {
sixthLine += "> TELEMETRY";
} else {
sixthLine += "> ??????????";
}
if (nextChar != '!' && nextChar != '=' && nextChar != '@') { // Common assignment for non-GPS cases
seventhLine = "RSSI:";
seventhLine += String(rssi);
seventhLine += "dBm SNR: ";
seventhLine += String(snr);
seventhLine += "dBm";
sixthLine += "> OBJECT";
}
seventhLine = "";
}
void print(const String& text) {
@@ -418,11 +441,16 @@ namespace Utils {
displayToggle(false);
}
#ifdef VEXT_CTRL_PIN
#ifndef HELTEC_WSL_V3
digitalWrite(VEXT_CTRL_PIN, LOW);
#endif
digitalWrite(VEXT_CTRL_PIN, !VEXT_CTRL_ON_STATE); // VEXT off before deep sleep (always, regardless of ecoMode)
#endif
LoRa_Utils::sleepRadio();
#ifdef RADIO_VCC_PIN
digitalWrite(RADIO_VCC_PIN, LOW); // cut LoRa module power (QRP Labs LightGateway) so it draws nothing while sleeping
#endif
#ifdef SOLAR_CHARGE_PIN
gpio_hold_en((gpio_num_t)SOLAR_CHARGE_PIN); // keep charge on/off decided by temperature while sleeping
gpio_deep_sleep_hold_en();
#endif
transmitFlag = true;
delay(100);
esp_deep_sleep_start();
+7 -86
View File
@@ -17,24 +17,17 @@
*/
#include <ArduinoJson.h>
#include "network_manager.h"
#include "configuration.h"
#include "ota_utils.h"
#include "web_utils.h"
#include "map_utils.h"
#include "display.h"
#include "utils.h"
#include "battery_utils.h"
#include "wx_utils.h"
#include "station_utils.h"
#include "ntp_utils.h"
#include "time_utils.h"
#include "aprs_is_utils.h"
#include "syslog_utils.h"
#include "mqtt_utils.h"
#include "lora_utils.h"
extern Configuration Config;
extern NetworkManager *networkManager;
extern uint32_t lastBeaconTx;
extern std::vector<ReceivedPacket> receivedPackets;
@@ -87,7 +80,6 @@ extern const size_t aprs_symbols_24_2_data_len = aprs_symbols_24_2_data_end - ap
namespace WEB_Utils {
AsyncWebServer server(80);
unsigned int loginFailCount = 0;
void handleNotFound(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", "Not found");
@@ -100,10 +92,9 @@ namespace WEB_Utils {
}
void handleHome(AsyncWebServerRequest *request) {
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str())) {
loginFailCount++;
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str()))
return request->requestAuthentication();
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", (const uint8_t*)web_index_html, web_index_html_len);
response->addHeader("Content-Encoding", "gzip");
request->send(response);
@@ -152,10 +143,8 @@ namespace WEB_Utils {
}
void handleReadConfiguration(AsyncWebServerRequest *request) {
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str())) {
loginFailCount++;
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str()))
return request->requestAuthentication();
}
File file = SPIFFS.open("/igate_conf.json");
@@ -184,74 +173,6 @@ namespace WEB_Utils {
request->send(200, "application/json", buffer);
}
void handleStationStatus(AsyncWebServerRequest *request) {
JsonDocument data;
// voltage
char voltageInfo[10]; // Enough to hold "xx.xx\0"
snprintf(voltageInfo, sizeof(voltageInfo), "%.2f", BATTERY_Utils::checkInternalVoltage());
data["stationStatus"]["voltageInternal"] = voltageInfo;
snprintf(voltageInfo, sizeof(voltageInfo), "%.2f", BATTERY_Utils::checkExternalVoltage());
data["stationStatus"]["voltageExternal"] = voltageInfo;
// weather
boolean weatherModulePresent = WX_Utils::isWeatherModulePresent();
data["stationStatus"]["weatherModulePresent"] = weatherModulePresent;
if (weatherModulePresent) {
data["stationStatus"]["weatherTemperature"] = WX_Utils::getTemperature();
data["stationStatus"]["weatherHumidity"] = WX_Utils::getHumidity();
data["stationStatus"]["weatherBarometricPressure"] = WX_Utils::getBarometricPressure();
data["stationStatus"]["weatherGas"] = WX_Utils::getGas();
} else {
data["stationStatus"]["weatherTemperature"] = "Unknown";
data["stationStatus"]["weatherHumidity"] = "Unknown";
data["stationStatus"]["weatherBarometricPressure"] = "Unknown";
data["stationStatus"]["weatherGas"] = "Unknown";
}
// elapsed time
unsigned long now = millis();
unsigned long bootTime = STATION_Utils::getBootTime();
data["stationStatus"]["elapsedTime"] = TIME_Utils::generateDuration(bootTime, now);
// logins
data["stationStatus"]["loginsInvalid"] = getLoginFailCount();
data["stationStatus"]["webAdminActive"] = Config.webadmin.active;
// packets
data["stationStatus"]["packetsRFSendSuccess"] = LoRa_Utils::getPacketSendSuccess();
data["stationStatus"]["packetsRFReceiveSuccess"] = LoRa_Utils::getPacketReceiveSuccess();
data["stationStatus"]["packetsISSendSuccess"] = APRS_IS_Utils::getPacketSendSuccess();
data["stationStatus"]["packetsISReceiveSuccess"] = APRS_IS_Utils::getPacketReceiveSuccess();
data["stationStatus"]["packetsRFSendFail"] = LoRa_Utils::getPacketSendFail();
data["stationStatus"]["packetsRFReceiveFail"] = LoRa_Utils::getPacketReceiveFail();
data["stationStatus"]["packetsISSendFail"] = APRS_IS_Utils::getPacketSendFail();
data["stationStatus"]["packetsISReceiveFail"] = APRS_IS_Utils::getPacketReceiveFail();
data["stationStatus"]["packetsBlacklisted"] = LoRa_Utils::getPacketBlacklisted();
// connectivity / service status
data["stationStatus"]["statusWifi"] = APRS_IS_Utils::getWifiState();
data["stationStatus"]["statusIS"] = APRS_IS_Utils::getAPRSISState();
data["stationStatus"]["statusNTP"] = NTP_Utils::getNTPStatus();
data["stationStatus"]["statusSyslog"] = SYSLOG_Utils::getSyslogStatus();
data["stationStatus"]["statusMQTT"] = MQTT_Utils::getMQTTStatus();
// misc
data["stationStatus"]["currentTimeRaw"] = now;
data["stationStatus"]["bootTimeRaw"] = bootTime;
data["stationStatus"]["elapsedTimeRaw"] = now - bootTime;
String buffer;
serializeJson(data, buffer);
request->send(200, "application/json", buffer);
}
unsigned long getLoginFailCount() {
return loginFailCount;
}
void handleStations(AsyncWebServerRequest *request) {
request->send(200, "application/json", MAP_Utils::getStationsJson());
}
@@ -403,6 +324,7 @@ namespace WEB_Utils {
Config.tnc.enableSerial = request->hasParam("tnc.enableSerial", true);
Config.tnc.acceptOwn = request->hasParam("tnc.acceptOwn", true);
Config.tnc.aprsBridgeActive = request->hasParam("tnc.aprsBridgeActive", true);
Config.tnc.kissProtocol = getParamStringSafe("tnc.kissProtocol", Config.tnc.kissProtocol ? "KISS" : "TNC2") != "TNC2";
Config.mqtt.active = request->hasParam("mqtt.active", true);
if (Config.mqtt.active) {
@@ -515,11 +437,10 @@ namespace WEB_Utils {
}
void setup() {
if (Config.digi.ecoMode == 0) {
if (Config.digi.ecoMode == 0 && networkManager->hasActiveInterface()) { // no WiFi STA/AP or Ethernet started = no TCP/IP stack
server.on("/", HTTP_GET, handleHome);
server.on("/status", HTTP_GET, handleStatus);
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
server.on("/station-status.json", HTTP_GET, handleStationStatus);
server.on("/stations.json", HTTP_GET, handleStations);
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
+3
View File
@@ -99,6 +99,9 @@ namespace WIFI_Utils {
if (Config.wifiAutoAP.enabled) {
Serial.println("Starting AP fallback...");
startAutoAP();
} else {
Serial.println("AutoAP disabled: no network, WebUI/TNC server not started (reconfigure over USB)");
displayShow("", " No WiFi / No AP", " WebUI disabled", " loading ...", 2000);
}
return;
}
+27 -24
View File
@@ -17,11 +17,11 @@
*/
#include <TinyGPS++.h>
#ifdef LIGHTGATEWAY_PLUS_1_0
#include "Adafruit_SHTC3.h"
#endif
#include "configuration.h"
#include "board_pinout.h"
#ifdef HAS_SHTC3
#include "Adafruit_SHTC3.h"
#endif
#include "wx_utils.h"
#include "display.h"
@@ -39,7 +39,6 @@ int wxModuleType = 0;
uint8_t wxModuleAddress = 0x00;
float newHum, newTemp, newPress, newGas;
bool weatherModulePresent = false;
Adafruit_BME280 bme280;
@@ -52,8 +51,9 @@ Adafruit_BMP280 bmp280;
Adafruit_BME680 bme680;
Adafruit_Si7021 si7021 = Adafruit_Si7021();
#endif
#ifdef LIGHTGATEWAY_PLUS_1_0
#ifdef HAS_SHTC3
Adafruit_SHTC3 shtc3 = Adafruit_SHTC3();
bool boardTempSensorFound = false; // onboard SHTC3 (solar charge protection), independent of WX telemetry
#endif
@@ -67,7 +67,7 @@ namespace WX_Utils {
err = SENSOR_I2C_BUS.endTransmission();
#else
Wire.beginTransmission(addr);
#ifdef LIGHTGATEWAY_PLUS_1_0
#ifdef HAS_SHTC3
Wire.write(0x35);
Wire.write(0x17);
#endif
@@ -91,6 +91,15 @@ namespace WX_Utils {
}
void setup() {
#ifdef HAS_SHTC3
Wire.beginTransmission(0x70); // SHTC3 wakeup (0x3517): it sleeps between reads and ignores its address while asleep (e.g. after deep sleep)
Wire.write(0x35);
Wire.write(0x17);
Wire.endTransmission();
delay(1);
boardTempSensorFound = shtc3.begin();
Serial.println(boardTempSensorFound ? "init : SHTC3 (onboard) ... done!" : "SHTC3 (onboard) not found!");
#endif
if (Config.wxsensor.active) {
getWxModuleAddres();
if (wxModuleAddress != 0x00) {
@@ -134,7 +143,7 @@ namespace WX_Utils {
wxModuleFound = true;
}
}
#ifdef LIGHTGATEWAY_PLUS_1_0
#ifdef HAS_SHTC3
else if (wxModuleAddress == 0x70) {
if (shtc3.begin()) {
Serial.println("SHTC3 sensor found");
@@ -176,7 +185,6 @@ namespace WX_Utils {
break;
}
}
weatherModulePresent = wxModuleFound;
}
}
}
@@ -240,6 +248,16 @@ namespace WX_Utils {
#endif
}
float readBoardTemperature() { // onboard SHTC3 (no WX correction applied), NAN if not available
#ifdef HAS_SHTC3
if (boardTempSensorFound) {
sensors_event_t humidity, temp;
if (shtc3.getEvent(&humidity, &temp)) return temp.temperature;
}
#endif
return NAN;
}
String readDataSensor() {
switch (wxModuleType) {
case 1: // BME280
@@ -273,7 +291,7 @@ namespace WX_Utils {
break;
case 5: // SHTC3
{
#ifdef LIGHTGATEWAY_PLUS_1_0
#ifdef HAS_SHTC3
sensors_event_t humidity, temp;
shtc3.getEvent(&humidity, &temp);
newTemp = temp.temperature;
@@ -336,19 +354,4 @@ namespace WX_Utils {
}
}
float getHumidity() {
return newHum;
}
float getTemperature() {
return newTemp;
}
float getBarometricPressure() {
return newPress;
}
float getGas() {
return newGas;
}
boolean isWeatherModulePresent() {
return weatherModulePresent;
}
}
@@ -1,4 +1,4 @@
[env:esp32c3_DIY_1W_LoRa_915]
[env:ESP32C3_DIY_1W_LoRa_915_E22_900M30S]
board = esp32-c3-devkitm-1
board_build.mcu = esp32c3
build_flags =
@@ -7,7 +7,7 @@ build_flags =
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32C3_DIY_1W_LoRa_915
-D ESP32C3_DIY_1W_LoRa_915_E22_900M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,4 +1,4 @@
[env:ESP32_C3_OctopusLab_LoRa]
[env:ESP32C3_DIY_1W_LoRa_E22_400M30S]
board = esp32-c3-devkitm-1
board_build.mcu = esp32c3
build_flags =
@@ -7,7 +7,7 @@ build_flags =
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_C3_OctopusLab_LoRa
-D ESP32C3_DIY_1W_LoRa_E22_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,4 +1,4 @@
[env:esp32c3_DIY_1W_LoRa]
[env:ESP32C3_OctopusLab_LoRa]
board = esp32-c3-devkitm-1
board_build.mcu = esp32c3
build_flags =
@@ -7,7 +7,7 @@ build_flags =
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32C3_DIY_1W_LoRa
-D ESP32C3_OctopusLab_LoRa
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -0,0 +1,46 @@
/* 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_
#define HAS_SX1278
#define RADIO_SCLK_PIN 4
#define RADIO_MISO_PIN 5
#define RADIO_MOSI_PIN 6
#define RADIO_CS_PIN 7
#define RADIO_RST_PIN 3
#define RADIO_BUSY_PIN 2
#define RADIO_WAKEUP_PIN RADIO_BUSY_PIN
#define GPIO_WAKEUP_PIN GPIO_NUM_2
// Display
#define HAS_DISPLAY
#undef OLED_SDA
#undef OLED_SCL
#undef OLED_RST
#define OLED_SDA 8
#define OLED_SCL 9
#define OLED_RST 10
// I2C
#define USE_WIRE_WITH_OLED_PINS
#endif
@@ -0,0 +1,13 @@
[env:ESP32C3_SuperMini_DIY_LoRa_SX1278]
board = esp32-c3-devkitm-1
board_build.mcu = esp32c3
build_flags =
${common.build_flags}
${common.usb_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32C3_SuperMini_DIY_LoRa_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_9M2IBR_1W_LoRa_GPS]
[env:ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_9M2IBR_1W_LoRa_GPS
-D ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_1W_LoRa]
[env:ESP32_DIY_1W_LoRa_915_E22_900M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_1W_LoRa
-D ESP32_DIY_1W_LoRa_915_E22_900M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_1W_LoRa_LLCC68]
[env:ESP32_DIY_1W_LoRa_E220_400M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_1W_LoRa_LLCC68
-D ESP32_DIY_1W_LoRa_E220_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_1W_LoRa_Mesh_V1_2]
[env:ESP32_DIY_1W_LoRa_E22_400M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_1W_LoRa_Mesh_V1_2
-D ESP32_DIY_1W_LoRa_E22_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -0,0 +1,52 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_SX1278
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 19
#define RADIO_MOSI_PIN 27
#define RADIO_CS_PIN 18
#define RADIO_RST_PIN 14
#define RADIO_BUSY_PIN 26
#define RADIO_WAKEUP_PIN RADIO_BUSY_PIN
#define GPIO_WAKEUP_PIN GPIO_SEL_26
#define RADIO_RXEN 32 // Ebyte E32 400M30S (SX1278 + PA) antenna switch
#define RADIO_TXEN 25
// I2C
#define USE_WIRE_WITH_OLED_PINS
// Display
#define HAS_DISPLAY
#undef OLED_SDA
#undef OLED_SCL
#undef OLED_RST
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST -1 // Reset pin # (or -1 if sharing Arduino reset pin)
// Aditional Config
#define INTERNAL_LED_PIN 2
#endif
@@ -0,0 +1,11 @@
[env:ESP32_DIY_1W_LoRa_E32_400M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_1W_LoRa_E32_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_1W_LoRa_915]
[env:ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_1W_LoRa_915
-D ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_LoRa]
[env:ESP32_DIY_LoRa_915_SX1276]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_LoRa
-D ESP32_DIY_LoRa_915_SX1276
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_LoRa_A7670_915]
[env:ESP32_DIY_LoRa_A7670_915_SX1276]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_LoRa_A7670_915
-D ESP32_DIY_LoRa_A7670_915_SX1276
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_LoRa_A7670]
[env:ESP32_DIY_LoRa_A7670_SX1278]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_LoRa_A7670
-D ESP32_DIY_LoRa_A7670_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:ESP32_DIY_LoRa_915]
[env:ESP32_DIY_LoRa_SX1278]
board = esp32dev
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D ESP32_DIY_LoRa_915
-D ESP32_DIY_LoRa_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -54,5 +54,12 @@
#define INTERNAL_LED_PIN 16
#define BATTERY_PIN 1
#define BUTTON_PIN 0
#define SOLAR_CHARGE_PIN 48 // HIGH = solar charging disabled / LOW = enabled
#define SOLAR_CHARGE_ENABLED_STATE LOW
#define SOLAR_CHARGE_DISABLE_TEMP -4.0 // °C: below this, stop charging (Li-Ion must not charge below 0°C)
#define SOLAR_CHARGE_ENABLE_TEMP -3.0 // °C: above this, charge again (1°C hysteresis)
// Onboard Sensors
#define HAS_SHTC3 // temperature/humidity (I2C 0x70), used for solar charge protection
#endif
@@ -1,4 +1,4 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
@@ -16,17 +16,24 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TIME_UTILS_H_
#define TIME_UTILS_H_
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
#include <Arduino.h>
// LoRa Radio
#define HAS_SX1262
#define HAS_TCXO
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 3
#define RADIO_MOSI_PIN 6
#define RADIO_CS_PIN 7
#define RADIO_RST_PIN 8
#define RADIO_DIO1_PIN 47
#define RADIO_BUSY_PIN 48
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
#define GPIO_WAKEUP_PIN GPIO_SEL_47
namespace TIME_Utils {
// Aditional Config
#define INTERNAL_LED_PIN 46
#define BATTERY_PIN 1
String getFormattedTime(unsigned long timeMillis);
String getFormattedDateTime(unsigned long timeMillis);
String generateDuration(unsigned long startMillis, unsigned long endMillis);
boolean isValid(unsigned long timeMillis);
}
#endif
#endif
+15
View File
@@ -0,0 +1,15 @@
[env:RAK3112]
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
board_build.arduino.memory_type = qio_opi
board_upload.flash_size = 16MB
build_flags =
${common.build_flags}
${common.usb_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D BOARD_HAS_PSRAM
-D RAK3112
lib_deps =
${common.lib_deps}
+52
View File
@@ -0,0 +1,52 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BOARD_PINOUT_H_
#define BOARD_PINOUT_H_
// LoRa Radio
#define HAS_SX1262
#define HAS_TCXO
#define RADIO_SCLK_PIN 5
#define RADIO_MISO_PIN 3
#define RADIO_MOSI_PIN 6
#define RADIO_CS_PIN 7
#define RADIO_RST_PIN 8
#define RADIO_DIO1_PIN 47
#define RADIO_BUSY_PIN 48
#define RADIO_WAKEUP_PIN RADIO_DIO1_PIN
#define GPIO_WAKEUP_PIN GPIO_SEL_47
#define RADIO_ANT_SW_PIN 4 // ANT_SW - antenna switch power enable
#define RADIO_ANT_SW_ON_STATE HIGH
// Base Board Power
#define VEXT_CTRL_PIN 14 // 3V3_EN - powers GPS on Base Board
#define VEXT_CTRL_ON_STATE HIGH
// GPS
#define HAS_GPS
#define GPS_BAUDRATE 9600
#define GPS_RX 43
#define GPS_TX 44
// Aditional Config
#define INTERNAL_LED_PIN 46
#define BATTERY_PIN 1
#endif
+15
View File
@@ -0,0 +1,15 @@
[env:RAK3312]
board = esp32-s3-devkitc-1
board_build.mcu = esp32s3
board_build.arduino.memory_type = qio_opi
board_upload.flash_size = 16MB
build_flags =
${common.build_flags}
${common.usb_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX127X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D BOARD_HAS_PSRAM
-D RAK3312
lib_deps =
${common.lib_deps}
@@ -1,11 +1,11 @@
[env:WEMOS-D1-R32-RA02]
[env:WEMOS_D1_R32_DIY_LoRa_SX1278]
board = wemos_d1_uno32
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D WEMOS_D1_R32_RA02
-D WEMOS_D1_R32_DIY_LoRa_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:WEMOS-LOLIN32-OLED-DIY]
[env:WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278]
board = lolin32
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D WEMOS_LOLIN32_OLED_DIY_LoRa
-D WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
@@ -1,11 +1,11 @@
[env:WEMOS_S2_MINI_DIY_LoRa]
[env:WEMOS_S2_MINI_DIY_LoRa_SX1278]
board = lolin_s2_mini
build_flags =
${common.build_flags}
-D RADIOLIB_EXCLUDE_LR11X0=1
-D RADIOLIB_EXCLUDE_SX126X=1
-D RADIOLIB_EXCLUDE_SX128X=1
-D WEMOS_S2_MINI_DIY_LoRa
-D WEMOS_S2_MINI_DIY_LoRa_SX1278
lib_deps =
${common.lib_deps}
${common.display_libs}
+3
View File
@@ -14,6 +14,9 @@ board_build.embed_files =
data_embed/leaflet.css.gz
data_embed/leaflet.js.gz
data_embed/favicon.png.gz
data_embed/aprs-symbols-24-0.png.gz
data_embed/aprs-symbols-24-1.png.gz
data_embed/aprs-symbols-24-2.png.gz
extra_scripts =
pre:tools/compress.py
debug_tool = esp-prog
+3
View File
@@ -14,6 +14,9 @@ board_build.embed_files =
data_embed/leaflet.css.gz
data_embed/leaflet.js.gz
data_embed/favicon.png.gz
data_embed/aprs-symbols-24-0.png.gz
data_embed/aprs-symbols-24-1.png.gz
data_embed/aprs-symbols-24-2.png.gz
extra_scripts =
pre:tools/compress.py
debug_tool = esp-prog