mirror of
https://github.com/richonguzman/LoRa_APRS_iGate.git
synced 2026-08-12 03:43:09 +02:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7c9201830 | |||
| 1a042b7d0e | |||
| aaa44200b5 | |||
| c17ad32650 | |||
| 26b5df1bd7 | |||
| 0c3555bdbe | |||
| cf858ffad2 | |||
| cbac8b0365 | |||
| b76aed4d57 | |||
| 96d7be6ffc | |||
| 94df6d2e8f | |||
| 0a222edeb8 | |||
| e35d5f6a9e | |||
| 96432b8624 | |||
| 25ea82737a | |||
| df22b9dbc0 | |||
| 9898e123d2 | |||
| a97757c0b2 |
@@ -51,6 +51,8 @@ ____________________________________________________
|
|||||||
<br />
|
<br />
|
||||||
|
|
||||||
# Timeline (Versions):
|
# Timeline (Versions):
|
||||||
|
- 2026-06-29 New UI (Web) and also Live View over a Map to see Rx stations.
|
||||||
|
- 2026-06-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
|
||||||
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
|
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
|
||||||
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
|
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
|
||||||
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
|
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
|
||||||
|
|||||||
+4
-4
@@ -32,16 +32,16 @@ lib_deps =
|
|||||||
adafruit/Adafruit Si7021 Library @ 1.5.3
|
adafruit/Adafruit Si7021 Library @ 1.5.3
|
||||||
arduino-libraries/NTPClient @ 3.2.1
|
arduino-libraries/NTPClient @ 3.2.1
|
||||||
ayushsharma82/ElegantOTA @ 3.1.7
|
ayushsharma82/ElegantOTA @ 3.1.7
|
||||||
bblanchon/ArduinoJson @ 7.4.2
|
bblanchon/ArduinoJson @ 7.4.3
|
||||||
jgromes/RadioLib @ 7.6.0
|
jgromes/RadioLib @ 7.6.0
|
||||||
knolleary/PubSubClient @ 2.8
|
knolleary/PubSubClient @ 2.8
|
||||||
ESP32Async/AsyncTCP @ 3.4.10
|
ESP32Async/AsyncTCP @ 3.4.10
|
||||||
ESP32Async/ESPAsyncWebServer @ 3.10.0
|
ESP32Async/ESPAsyncWebServer @ 3.11.1
|
||||||
mikalhart/TinyGPSPlus @ 1.0.3
|
mikalhart/TinyGPSPlus @ 1.0.3
|
||||||
richonguzman/APRSPacketLib @ 1.0.4
|
richonguzman/APRSPacketLib @ 1.0.4
|
||||||
display_libs =
|
display_libs =
|
||||||
adafruit/Adafruit GFX Library @ 1.11.9
|
adafruit/Adafruit GFX Library @ 1.12.6
|
||||||
adafruit/Adafruit SSD1306 @ 2.5.10
|
adafruit/Adafruit SSD1306 @ 2.5.17
|
||||||
usb_flags=
|
usb_flags=
|
||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
@@ -28,9 +28,9 @@
|
|||||||
"messagesToRF": false,
|
"messagesToRF": false,
|
||||||
"objectsToRF": false,
|
"objectsToRF": false,
|
||||||
"server": "rotate.aprs2.net",
|
"server": "rotate.aprs2.net",
|
||||||
"passcode": "XYZVW",
|
"passcode": "XYZVW",
|
||||||
"port": 14580,
|
"port": 14580,
|
||||||
"filter": "m/10"
|
"filter": "m/10"
|
||||||
},
|
},
|
||||||
"personalNote": "",
|
"personalNote": "",
|
||||||
"blacklist": "",
|
"blacklist": "",
|
||||||
@@ -47,10 +47,11 @@
|
|||||||
"rxSignalBandwidth": 125000,
|
"rxSignalBandwidth": 125000,
|
||||||
"txActive": false,
|
"txActive": false,
|
||||||
"txFreq": 433775000,
|
"txFreq": 433775000,
|
||||||
"txSpreadingFactor": 12,
|
"txSpreadingFactor": 9,
|
||||||
"txCodingRate4": 5,
|
"txCodingRate4": 7,
|
||||||
"txSignalBandwidth": 125000,
|
"txSignalBandwidth": 125000,
|
||||||
"power": 20
|
"power": 20,
|
||||||
|
"cadActive": true
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"alwaysOn": true,
|
"alwaysOn": true,
|
||||||
|
|||||||
+1102
-983
File diff suppressed because one or more lines are too long
@@ -0,0 +1,661 @@
|
|||||||
|
/* required styles */
|
||||||
|
|
||||||
|
.leaflet-pane,
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-tile-container,
|
||||||
|
.leaflet-pane > svg,
|
||||||
|
.leaflet-pane > canvas,
|
||||||
|
.leaflet-zoom-box,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-layer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile,
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
/* Prevents IE11 from highlighting tiles in blue */
|
||||||
|
.leaflet-tile::selection {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||||
|
.leaflet-safari .leaflet-tile {
|
||||||
|
image-rendering: -webkit-optimize-contrast;
|
||||||
|
}
|
||||||
|
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||||
|
.leaflet-safari .leaflet-tile-container {
|
||||||
|
width: 1600px;
|
||||||
|
height: 1600px;
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||||
|
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||||
|
.leaflet-container .leaflet-overlay-pane svg {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
}
|
||||||
|
.leaflet-container .leaflet-marker-pane img,
|
||||||
|
.leaflet-container .leaflet-shadow-pane img,
|
||||||
|
.leaflet-container .leaflet-tile-pane img,
|
||||||
|
.leaflet-container img.leaflet-image-layer,
|
||||||
|
.leaflet-container .leaflet-tile {
|
||||||
|
max-width: none !important;
|
||||||
|
max-height: none !important;
|
||||||
|
width: auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container img.leaflet-tile {
|
||||||
|
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||||
|
mix-blend-mode: plus-lighter;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-container.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: pan-x pan-y;
|
||||||
|
touch-action: pan-x pan-y;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag {
|
||||||
|
-ms-touch-action: pinch-zoom;
|
||||||
|
/* Fallback for FF which doesn't support pinch-zoom */
|
||||||
|
touch-action: none;
|
||||||
|
touch-action: pinch-zoom;
|
||||||
|
}
|
||||||
|
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||||
|
-ms-touch-action: none;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
.leaflet-container {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tile {
|
||||||
|
filter: inherit;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.leaflet-tile-loaded {
|
||||||
|
visibility: inherit;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
z-index: 800;
|
||||||
|
}
|
||||||
|
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||||
|
.leaflet-overlay-pane svg {
|
||||||
|
-moz-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-pane { z-index: 400; }
|
||||||
|
|
||||||
|
.leaflet-tile-pane { z-index: 200; }
|
||||||
|
.leaflet-overlay-pane { z-index: 400; }
|
||||||
|
.leaflet-shadow-pane { z-index: 500; }
|
||||||
|
.leaflet-marker-pane { z-index: 600; }
|
||||||
|
.leaflet-tooltip-pane { z-index: 650; }
|
||||||
|
.leaflet-popup-pane { z-index: 700; }
|
||||||
|
|
||||||
|
.leaflet-map-pane canvas { z-index: 100; }
|
||||||
|
.leaflet-map-pane svg { z-index: 200; }
|
||||||
|
|
||||||
|
.leaflet-vml-shape {
|
||||||
|
width: 1px;
|
||||||
|
height: 1px;
|
||||||
|
}
|
||||||
|
.lvml {
|
||||||
|
behavior: url(#default#VML);
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* control positioning */
|
||||||
|
|
||||||
|
.leaflet-control {
|
||||||
|
position: relative;
|
||||||
|
z-index: 800;
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-top,
|
||||||
|
.leaflet-bottom {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-top {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
.leaflet-right {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.leaflet-bottom {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
.leaflet-left {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control {
|
||||||
|
float: left;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
.leaflet-top .leaflet-control {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.leaflet-right .leaflet-control {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* zoom and fade animations */
|
||||||
|
|
||||||
|
.leaflet-fade-anim .leaflet-popup {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transition: opacity 0.2s linear;
|
||||||
|
-moz-transition: opacity 0.2s linear;
|
||||||
|
transition: opacity 0.2s linear;
|
||||||
|
}
|
||||||
|
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-animated {
|
||||||
|
-webkit-transform-origin: 0 0;
|
||||||
|
-ms-transform-origin: 0 0;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
}
|
||||||
|
svg.leaflet-zoom-animated {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||||
|
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||||
|
}
|
||||||
|
.leaflet-zoom-anim .leaflet-tile,
|
||||||
|
.leaflet-pan-anim .leaflet-tile {
|
||||||
|
-webkit-transition: none;
|
||||||
|
-moz-transition: none;
|
||||||
|
transition: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* cursors */
|
||||||
|
|
||||||
|
.leaflet-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.leaflet-grab {
|
||||||
|
cursor: -webkit-grab;
|
||||||
|
cursor: -moz-grab;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
.leaflet-crosshair,
|
||||||
|
.leaflet-crosshair .leaflet-interactive {
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
.leaflet-popup-pane,
|
||||||
|
.leaflet-control {
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
.leaflet-dragging .leaflet-grab,
|
||||||
|
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||||
|
.leaflet-dragging .leaflet-marker-draggable {
|
||||||
|
cursor: move;
|
||||||
|
cursor: -webkit-grabbing;
|
||||||
|
cursor: -moz-grabbing;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* marker & overlays interactivity */
|
||||||
|
.leaflet-marker-icon,
|
||||||
|
.leaflet-marker-shadow,
|
||||||
|
.leaflet-image-layer,
|
||||||
|
.leaflet-pane > svg path,
|
||||||
|
.leaflet-tile-container {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-marker-icon.leaflet-interactive,
|
||||||
|
.leaflet-image-layer.leaflet-interactive,
|
||||||
|
.leaflet-pane > svg path.leaflet-interactive,
|
||||||
|
svg.leaflet-image-layer.leaflet-interactive path {
|
||||||
|
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* visual tweaks */
|
||||||
|
|
||||||
|
.leaflet-container {
|
||||||
|
background: #ddd;
|
||||||
|
outline-offset: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-container a {
|
||||||
|
color: #0078A8;
|
||||||
|
}
|
||||||
|
.leaflet-zoom-box {
|
||||||
|
border: 2px dotted #38f;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general typography */
|
||||||
|
.leaflet-container {
|
||||||
|
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||||
|
font-size: 12px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* general toolbar styles */
|
||||||
|
|
||||||
|
.leaflet-bar {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
line-height: 26px;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
.leaflet-bar a,
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-position: 50% 50%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:hover,
|
||||||
|
.leaflet-bar a:focus {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.leaflet-bar a.leaflet-disabled {
|
||||||
|
cursor: default;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
color: #bbb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-bar a {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:first-child {
|
||||||
|
border-top-left-radius: 2px;
|
||||||
|
border-top-right-radius: 2px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-bar a:last-child {
|
||||||
|
border-bottom-left-radius: 2px;
|
||||||
|
border-bottom-right-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* zoom control */
|
||||||
|
|
||||||
|
.leaflet-control-zoom-in,
|
||||||
|
.leaflet-control-zoom-out {
|
||||||
|
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||||
|
text-indent: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* layers control */
|
||||||
|
|
||||||
|
.leaflet-control-layers {
|
||||||
|
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers.png);
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.leaflet-retina .leaflet-control-layers-toggle {
|
||||||
|
background-image: url(images/layers-2x.png);
|
||||||
|
background-size: 26px 26px;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers-toggle {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers .leaflet-control-layers-list,
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-expanded {
|
||||||
|
padding: 6px 10px 6px 6px;
|
||||||
|
color: #333;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-scrollbar {
|
||||||
|
overflow-y: scroll;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-selector {
|
||||||
|
margin-top: 2px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
|
}
|
||||||
|
.leaflet-control-layers-separator {
|
||||||
|
height: 0;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
margin: 5px -10px 5px -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default icon URLs */
|
||||||
|
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||||
|
background-image: url(images/marker-icon.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* attribution and scale controls */
|
||||||
|
|
||||||
|
.leaflet-container .leaflet-control-attribution {
|
||||||
|
background: #fff;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution,
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
padding: 0 5px;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.leaflet-control-attribution a:hover,
|
||||||
|
.leaflet-control-attribution a:focus {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.leaflet-attribution-flag {
|
||||||
|
display: inline !important;
|
||||||
|
vertical-align: baseline !important;
|
||||||
|
width: 1em;
|
||||||
|
height: 0.6669em;
|
||||||
|
}
|
||||||
|
.leaflet-left .leaflet-control-scale {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-bottom .leaflet-control-scale {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line {
|
||||||
|
border: 2px solid #777;
|
||||||
|
border-top: none;
|
||||||
|
line-height: 1.1;
|
||||||
|
padding: 2px 5px 1px;
|
||||||
|
white-space: nowrap;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
text-shadow: 1px 1px #fff;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child) {
|
||||||
|
border-top: 2px solid #777;
|
||||||
|
border-bottom: none;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||||
|
border-bottom: 2px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-touch .leaflet-control-attribution,
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.leaflet-touch .leaflet-control-layers,
|
||||||
|
.leaflet-touch .leaflet-bar {
|
||||||
|
border: 2px solid rgba(0,0,0,0.2);
|
||||||
|
background-clip: padding-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* popup */
|
||||||
|
|
||||||
|
.leaflet-popup {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper {
|
||||||
|
padding: 1px;
|
||||||
|
text-align: left;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content {
|
||||||
|
margin: 13px 24px 13px 20px;
|
||||||
|
line-height: 1.3;
|
||||||
|
font-size: 13px;
|
||||||
|
font-size: 1.08333em;
|
||||||
|
min-height: 1px;
|
||||||
|
}
|
||||||
|
.leaflet-popup-content p {
|
||||||
|
margin: 17px 0;
|
||||||
|
margin: 1.3em 0;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip-container {
|
||||||
|
width: 40px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -1px;
|
||||||
|
margin-left: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
width: 17px;
|
||||||
|
height: 17px;
|
||||||
|
padding: 1px;
|
||||||
|
|
||||||
|
margin: -10px auto 0;
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
-moz-transform: rotate(45deg);
|
||||||
|
-ms-transform: rotate(45deg);
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
.leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-popup-tip {
|
||||||
|
background: white;
|
||||||
|
color: #333;
|
||||||
|
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
border: none;
|
||||||
|
text-align: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||||
|
color: #757575;
|
||||||
|
text-decoration: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||||
|
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||||
|
color: #585858;
|
||||||
|
}
|
||||||
|
.leaflet-popup-scrolled {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||||
|
-ms-zoom: 1;
|
||||||
|
}
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
width: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||||
|
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-oldie .leaflet-control-zoom,
|
||||||
|
.leaflet-oldie .leaflet-control-layers,
|
||||||
|
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||||
|
.leaflet-oldie .leaflet-popup-tip {
|
||||||
|
border: 1px solid #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* div icon */
|
||||||
|
|
||||||
|
.leaflet-div-icon {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Tooltip */
|
||||||
|
/* Base styles for the element that has a tooltip */
|
||||||
|
.leaflet-tooltip {
|
||||||
|
position: absolute;
|
||||||
|
padding: 6px;
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
color: #222;
|
||||||
|
white-space: nowrap;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
pointer-events: none;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.leaflet-tooltip.leaflet-interactive {
|
||||||
|
cursor: pointer;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before,
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
background: transparent;
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Directions */
|
||||||
|
|
||||||
|
.leaflet-tooltip-bottom {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top {
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before,
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-top:before {
|
||||||
|
bottom: 0;
|
||||||
|
margin-bottom: -12px;
|
||||||
|
border-top-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-bottom:before {
|
||||||
|
top: 0;
|
||||||
|
margin-top: -12px;
|
||||||
|
margin-left: -6px;
|
||||||
|
border-bottom-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left {
|
||||||
|
margin-left: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before,
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -6px;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-left:before {
|
||||||
|
right: 0;
|
||||||
|
margin-right: -12px;
|
||||||
|
border-left-color: #fff;
|
||||||
|
}
|
||||||
|
.leaflet-tooltip-right:before {
|
||||||
|
left: 0;
|
||||||
|
margin-left: -12px;
|
||||||
|
border-right-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Printing */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Prevent printers from removing background-images of controls. */
|
||||||
|
.leaflet-control {
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
+212
-7
@@ -118,7 +118,7 @@ function loadSettings(settings) {
|
|||||||
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
|
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
|
||||||
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
|
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
|
||||||
document.getElementById("beacon.interval").value = settings.beacon.interval;
|
document.getElementById("beacon.interval").value = settings.beacon.interval;
|
||||||
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
|
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
|
||||||
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
|
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
|
||||||
|
|
||||||
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
|
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
|
||||||
@@ -148,12 +148,26 @@ function loadSettings(settings) {
|
|||||||
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
|
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
|
||||||
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
|
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
|
||||||
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
|
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
|
||||||
|
LoRaRxCheckbox.checked = settings.lora.rxActive;
|
||||||
|
LoRaRxFreq.disabled = !LoRaRxCheckbox.checked;
|
||||||
|
LoRaRxSpreadingFactor.disabled = !LoRaRxCheckbox.checked;
|
||||||
|
LoRaRxCodingRate.disabled = !LoRaRxCheckbox.checked;
|
||||||
|
LoRaRxSignalBandwidth.disabled = !LoRaRxCheckbox.checked;
|
||||||
|
|
||||||
document.getElementById("lora.txActive").checked = settings.lora.txActive;
|
document.getElementById("lora.txActive").checked = settings.lora.txActive;
|
||||||
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
|
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
|
||||||
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
|
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
|
||||||
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
|
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
|
||||||
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
|
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
|
||||||
document.getElementById("lora.power").value = settings.lora.power;
|
document.getElementById("lora.power").value = settings.lora.power;
|
||||||
|
document.getElementById("lora.cadActive").checked = settings.lora.cadActive;
|
||||||
|
LoRaTxCheckbox.checked = settings.lora.txActive;
|
||||||
|
LoRaTxFreq.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
LoRaTxSpreadingFactor.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
LoRaTxCodingRate.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
LoRaTxSignalBandwidth.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
LoRaPower.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
LoRaCadActive.disabled = !LoRaTxCheckbox.checked;
|
||||||
|
|
||||||
// Display
|
// Display
|
||||||
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
|
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
|
||||||
@@ -195,7 +209,7 @@ function loadSettings(settings) {
|
|||||||
TelemetryCheckbox.checked = settings.wxsensor.active;
|
TelemetryCheckbox.checked = settings.wxsensor.active;
|
||||||
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
|
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
|
||||||
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
|
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
|
||||||
|
|
||||||
// SYSLOG
|
// SYSLOG
|
||||||
document.getElementById("syslog.active").checked = settings.syslog.active;
|
document.getElementById("syslog.active").checked = settings.syslog.active;
|
||||||
document.getElementById("syslog.server").value = settings.syslog.server;
|
document.getElementById("syslog.server").value = settings.syslog.server;
|
||||||
@@ -205,7 +219,7 @@ function loadSettings(settings) {
|
|||||||
SyslogServer.disabled = !SyslogCheckbox.checked;
|
SyslogServer.disabled = !SyslogCheckbox.checked;
|
||||||
SyslogPort.disabled = !SyslogCheckbox.checked;
|
SyslogPort.disabled = !SyslogCheckbox.checked;
|
||||||
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
|
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
|
||||||
|
|
||||||
// TNC
|
// TNC
|
||||||
if (settings.tnc) {
|
if (settings.tnc) {
|
||||||
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
|
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
|
||||||
@@ -390,6 +404,36 @@ TelemetryCheckbox.addEventListener("change", function () {
|
|||||||
TelemetryTempCorrection.disabled = !this.checked;
|
TelemetryTempCorrection.disabled = !this.checked;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// LoRa Rx Switch
|
||||||
|
const LoRaRxCheckbox = document.querySelector('input[name="lora.rxActive"]');
|
||||||
|
const LoRaRxFreq = document.querySelector('[name="lora.rxFreq"]');
|
||||||
|
const LoRaRxSpreadingFactor = document.querySelector('[name="lora.rxSpreadingFactor"]');
|
||||||
|
const LoRaRxCodingRate = document.querySelector('[name="lora.rxCodingRate4"]');
|
||||||
|
const LoRaRxSignalBandwidth = document.querySelector('[name="lora.rxSignalBandwidth"]');
|
||||||
|
LoRaRxCheckbox.addEventListener("change", function () {
|
||||||
|
LoRaRxFreq.disabled = !this.checked;
|
||||||
|
LoRaRxSpreadingFactor.disabled = !this.checked;
|
||||||
|
LoRaRxCodingRate.disabled = !this.checked;
|
||||||
|
LoRaRxSignalBandwidth.disabled = !this.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
// LoRa Tx Switch
|
||||||
|
const LoRaTxCheckbox = document.querySelector('input[name="lora.txActive"]');
|
||||||
|
const LoRaTxFreq = document.querySelector('[name="lora.txFreq"]');
|
||||||
|
const LoRaTxSpreadingFactor = document.querySelector('[name="lora.txSpreadingFactor"]');
|
||||||
|
const LoRaTxCodingRate = document.querySelector('[name="lora.txCodingRate4"]');
|
||||||
|
const LoRaTxSignalBandwidth = document.querySelector('[name="lora.txSignalBandwidth"]');
|
||||||
|
const LoRaPower = document.querySelector('[name="lora.power"]');
|
||||||
|
const LoRaCadActive = document.querySelector('[name="lora.cadActive"]');
|
||||||
|
LoRaTxCheckbox.addEventListener("change", function () {
|
||||||
|
LoRaTxFreq.disabled = !this.checked;
|
||||||
|
LoRaTxSpreadingFactor.disabled = !this.checked;
|
||||||
|
LoRaTxCodingRate.disabled = !this.checked;
|
||||||
|
LoRaTxSignalBandwidth.disabled = !this.checked;
|
||||||
|
LoRaPower.disabled = !this.checked;
|
||||||
|
LoRaCadActive.disabled = !this.checked;
|
||||||
|
});
|
||||||
|
|
||||||
// Syslog Switches
|
// Syslog Switches
|
||||||
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
|
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
|
||||||
const SyslogServer = document.querySelector('input[name="syslog.server"]');
|
const SyslogServer = document.querySelector('input[name="syslog.server"]');
|
||||||
@@ -559,7 +603,7 @@ function loadReceivedPackets(packets) {
|
|||||||
|
|
||||||
packets.forEach((packet) => {
|
packets.forEach((packet) => {
|
||||||
const element = document.createElement("tr");
|
const element = document.createElement("tr");
|
||||||
|
|
||||||
element.innerHTML = `
|
element.innerHTML = `
|
||||||
<td>${packet.rxTime}</td>
|
<td>${packet.rxTime}</td>
|
||||||
<td>${packet.packet}</td>
|
<td>${packet.packet}</td>
|
||||||
@@ -592,8 +636,169 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
|
|||||||
|
|
||||||
document.getElementById('received-packets').classList.remove('d-none');
|
document.getElementById('received-packets').classList.remove('d-none');
|
||||||
document.getElementById('configuration').classList.add('d-none');
|
document.getElementById('configuration').classList.add('d-none');
|
||||||
|
|
||||||
document.querySelector('button[type=submit]').remove();
|
document.querySelector('button[type=submit]').remove();
|
||||||
|
|
||||||
fetchReceivedPackets();
|
fetchReceivedPackets();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/* ---------- Stations Map (Leaflet, CDN) ---------- */
|
||||||
|
|
||||||
|
let mapInstance = null;
|
||||||
|
let mapMarkers = null;
|
||||||
|
let mapTimer = null;
|
||||||
|
let mapTileErrShown = false;
|
||||||
|
let iGateLatLng = null;
|
||||||
|
let mapAutoFitDone = false;
|
||||||
|
|
||||||
|
function setMapMessage(text) {
|
||||||
|
const el = document.getElementById("map");
|
||||||
|
if (el) {
|
||||||
|
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--msh-text-dim,#8b949e);text-align:center;padding:20px;">${text}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMapStations(stations) {
|
||||||
|
if (!mapMarkers) return;
|
||||||
|
|
||||||
|
mapMarkers.clearLayers();
|
||||||
|
|
||||||
|
let drawn = 0;
|
||||||
|
(stations || []).forEach((s) => {
|
||||||
|
if (s.lat === 0 && s.lon === 0) return; // descartar estaciones sin fix
|
||||||
|
|
||||||
|
const popup = `<b>${s.callsign}</b>`
|
||||||
|
+ (s.lastHeard ? `<br>Last: ${s.lastHeard}` : "")
|
||||||
|
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
|
||||||
|
+ `<br>Packets: ${s.count}`;
|
||||||
|
|
||||||
|
L.circleMarker([s.lat, s.lon], {
|
||||||
|
radius: 6,
|
||||||
|
color: "#1d6fe0", // borde
|
||||||
|
weight: 2,
|
||||||
|
fillColor: "#3b8cff", // relleno
|
||||||
|
fillOpacity: 0.9
|
||||||
|
}).bindPopup(popup).addTo(mapMarkers);
|
||||||
|
drawn++;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!mapAutoFitDone && drawn > 0) { // auto-fit la 1ª vez que hay estaciones tras abrir
|
||||||
|
fitMapToStations();
|
||||||
|
mapAutoFitDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encuadra el mapa para que entren todas las estaciones (+ el iGate). 100% navegador.
|
||||||
|
function fitMapToStations() {
|
||||||
|
if (!mapInstance) return false;
|
||||||
|
|
||||||
|
const pts = [];
|
||||||
|
if (mapMarkers) {
|
||||||
|
mapMarkers.eachLayer((layer) => {
|
||||||
|
if (layer.getLatLng) pts.push(layer.getLatLng());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (iGateLatLng) pts.push(iGateLatLng);
|
||||||
|
|
||||||
|
if (!pts.length) return false;
|
||||||
|
mapInstance.fitBounds(L.latLngBounds(pts), { padding: [30, 30], maxZoom: 14 });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchMapStations() {
|
||||||
|
fetch("/stations.json")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((stations) => {
|
||||||
|
loadMapStations(stations);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
|
||||||
|
console.error(`Failed to load stations`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showMap = function () {
|
||||||
|
if (typeof L === "undefined") { // sin internet el CDN de Leaflet no cargó
|
||||||
|
setMapMessage("Map unavailable — no internet connection.<br>The map needs internet to load (OpenStreetMap).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapAutoFitDone = false; // re-encuadrar en cada apertura del Map
|
||||||
|
|
||||||
|
if (!mapInstance) {
|
||||||
|
mapInstance = L.map("map");
|
||||||
|
|
||||||
|
const tiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: "© OpenStreetMap"
|
||||||
|
}).addTo(mapInstance);
|
||||||
|
|
||||||
|
tiles.on("tileerror", function () { // tiles no cargan (sin salida a internet)
|
||||||
|
if (!mapTileErrShown) {
|
||||||
|
mapTileErrShown = true;
|
||||||
|
if (window.showToast) showToast("Map tiles could not load — check internet connection.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mapMarkers = L.layerGroup().addTo(mapInstance);
|
||||||
|
|
||||||
|
const FitControl = L.Control.extend({ // botón "Fit All Stations" (control nativo de Leaflet)
|
||||||
|
options: { position: "topright" },
|
||||||
|
onAdd: function () {
|
||||||
|
const btn = L.DomUtil.create("button", "");
|
||||||
|
btn.type = "button";
|
||||||
|
btn.title = "Fit all stations";
|
||||||
|
btn.textContent = "Fit All Stations";
|
||||||
|
btn.style.cssText = "background:#fff;color:#333;border:2px solid rgba(0,0,0,.2);border-radius:4px;padding:5px 9px;cursor:pointer;font:600 12px sans-serif;box-shadow:0 1px 4px rgba(0,0,0,.3);";
|
||||||
|
L.DomEvent.on(btn, "click", (e) => {
|
||||||
|
L.DomEvent.stop(e);
|
||||||
|
fitMapToStations();
|
||||||
|
});
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mapInstance.addControl(new FitControl());
|
||||||
|
|
||||||
|
fetch("/configuration.json") // centrar y marcar el iGate
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((config) => {
|
||||||
|
const lat = (config.beacon && config.beacon.latitude) || 0;
|
||||||
|
const lon = (config.beacon && config.beacon.longitude) || 0;
|
||||||
|
const callsign = config.callsign || "iGate";
|
||||||
|
mapInstance.setView([lat, lon], (lat || lon) ? 12 : 2);
|
||||||
|
|
||||||
|
if (lat || lon) { // rombo rojo en la posición del iGate
|
||||||
|
iGateLatLng = L.latLng(lat, lon); // guardar para incluirlo en el auto-fit
|
||||||
|
const iGateIcon = L.divIcon({
|
||||||
|
className: "igate-marker",
|
||||||
|
html: '<div style="width:14px;height:14px;background:#e23b3b;border:2px solid #fff;transform:rotate(45deg);box-shadow:0 0 4px rgba(0,0,0,.6);"></div>',
|
||||||
|
iconSize: [18, 18],
|
||||||
|
iconAnchor: [9, 9]
|
||||||
|
});
|
||||||
|
L.marker([lat, lon], { icon: iGateIcon })
|
||||||
|
.bindPopup(`<b>${callsign}</b><br>This station`)
|
||||||
|
.addTo(mapInstance); // directo al mapa: no se borra en los refrescos
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
mapInstance.setView([0, 0], 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(function () { // el div estuvo oculto: recalcular tamaño
|
||||||
|
if (mapInstance) mapInstance.invalidateSize();
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
fetchMapStations();
|
||||||
|
|
||||||
|
if (mapTimer) clearInterval(mapTimer);
|
||||||
|
|
||||||
|
mapTimer = setInterval(function () { // refrescar solo si la sección está visible
|
||||||
|
const section = document.getElementById("sec-maps");
|
||||||
|
if (section && section.classList.contains("active")) {
|
||||||
|
fetchMapStations();
|
||||||
|
}
|
||||||
|
}, 15000);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -86,6 +86,7 @@ public:
|
|||||||
int txCodingRate4;
|
int txCodingRate4;
|
||||||
long txSignalBandwidth;
|
long txSignalBandwidth;
|
||||||
int power;
|
int power;
|
||||||
|
bool cadActive;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Display {
|
class Display {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
|
*
|
||||||
|
* This file is part of LoRa APRS iGate.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAP_UTILS_H_
|
||||||
|
#define MAP_UTILS_H_
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
|
struct MapStation {
|
||||||
|
char callsign[10]; // "XX9XXX-NN" + null -> clave para deduplicar
|
||||||
|
float latitude; // grados decimales (+N / -S)
|
||||||
|
float longitude; // grados decimales (+E / -W)
|
||||||
|
char symbol[3]; // tabla + codigo APRS (ej "/>"), "" si no hay
|
||||||
|
int16_t rssi; // ultimo RSSI
|
||||||
|
float snr; // ultimo SNR
|
||||||
|
uint16_t count; // nro de paquetes oidos de esa estacion
|
||||||
|
char lastHeard[10]; // "HH:MM:SS" si hay NTP valido, "" si no
|
||||||
|
uint32_t lastHeardMillis; // millis() -> ordenar / expirar (no se muestra)
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace MAP_Utils {
|
||||||
|
|
||||||
|
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr);
|
||||||
|
String getStationsJson();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
|
*
|
||||||
|
* This file is part of LoRa APRS iGate.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ board_build.embed_files =
|
|||||||
data_embed/script.js.gz
|
data_embed/script.js.gz
|
||||||
data_embed/bootstrap.css.gz
|
data_embed/bootstrap.css.gz
|
||||||
data_embed/bootstrap.js.gz
|
data_embed/bootstrap.js.gz
|
||||||
|
data_embed/leaflet.css.gz
|
||||||
|
data_embed/leaflet.js.gz
|
||||||
data_embed/favicon.png.gz
|
data_embed/favicon.png.gz
|
||||||
extra_scripts =
|
extra_scripts =
|
||||||
pre:tools/compress.py
|
pre:tools/compress.py
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -68,8 +68,8 @@ ___________________________________________________________________*/
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
String versionDate = "2026-04-21";
|
String versionDate = "2026-07-14";
|
||||||
String versionNumber = "3.2.4";
|
String versionNumber = "4.0.0";
|
||||||
Configuration Config;
|
Configuration Config;
|
||||||
WiFiClient aprsIsClient;
|
WiFiClient aprsIsClient;
|
||||||
WiFiClient mqttClient;
|
WiFiClient mqttClient;
|
||||||
@@ -196,7 +196,7 @@ void loop() {
|
|||||||
APRS_IS_Utils::processLoRaPacket(packet); // Send received packet to APRSIS
|
APRS_IS_Utils::processLoRaPacket(packet); // Send received packet to APRSIS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.loramodule.txActive && (Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
|
if (Config.loramodule.txActive && (Config.digi.mode == 1 || Config.digi.mode == 2 || Config.digi.mode == 3 || backupDigiMode)) { // If Digi enabled
|
||||||
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
|
DIGI_Utils::processLoRaPacket(packet); // Send received packet to Digi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <APRSPacketLib.h>
|
#include <APRSPacketLib.h>
|
||||||
#include <WiFiClient.h>
|
#include <WiFiClient.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
#include "network_manager.h"
|
||||||
@@ -47,7 +47,6 @@ extern bool modemLoggedToAPRSIS;
|
|||||||
extern bool backupDigiMode;
|
extern bool backupDigiMode;
|
||||||
extern String versionNumber;
|
extern String versionNumber;
|
||||||
|
|
||||||
uint32_t lastRxTime = millis();
|
|
||||||
bool passcodeValid = false;
|
bool passcodeValid = false;
|
||||||
uint32_t lastServerCheck = 0;
|
uint32_t lastServerCheck = 0;
|
||||||
|
|
||||||
@@ -403,7 +402,6 @@ namespace APRS_IS_Utils {
|
|||||||
String aprsisPacket = aprsIsClient.readStringUntil('\r');
|
String aprsisPacket = aprsIsClient.readStringUntil('\r');
|
||||||
aprsisPacket.trim(); // Serial.println(aprsisPacket);
|
aprsisPacket.trim(); // Serial.println(aprsisPacket);
|
||||||
processAPRSISPacket(aprsisPacket);
|
processAPRSISPacket(aprsisPacket);
|
||||||
lastRxTime = millis();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+16
-10
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -106,6 +106,7 @@ bool Configuration::writeFile() {
|
|||||||
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
|
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
|
||||||
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
|
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
|
||||||
data["lora"]["power"] = loramodule.power;
|
data["lora"]["power"] = loramodule.power;
|
||||||
|
data["lora"]["cadActive"] = loramodule.cadActive;
|
||||||
|
|
||||||
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
|
int rxSpreadingFactor = loramodule.rxSpreadingFactor;
|
||||||
int txSpreadingFactor = loramodule.txSpreadingFactor;
|
int txSpreadingFactor = loramodule.txSpreadingFactor;
|
||||||
@@ -290,7 +291,6 @@ bool Configuration::readFile() {
|
|||||||
#endif
|
#endif
|
||||||
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
|
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
|
||||||
|
|
||||||
|
|
||||||
if (data["lora"]["rxActive"].isNull() ||
|
if (data["lora"]["rxActive"].isNull() ||
|
||||||
data["lora"]["rxFreq"].isNull() ||
|
data["lora"]["rxFreq"].isNull() ||
|
||||||
data["lora"]["rxSpreadingFactor"].isNull() ||
|
data["lora"]["rxSpreadingFactor"].isNull() ||
|
||||||
@@ -301,7 +301,8 @@ bool Configuration::readFile() {
|
|||||||
data["lora"]["txSpreadingFactor"].isNull() ||
|
data["lora"]["txSpreadingFactor"].isNull() ||
|
||||||
data["lora"]["txCodingRate4"].isNull() ||
|
data["lora"]["txCodingRate4"].isNull() ||
|
||||||
data["lora"]["txSignalBandwidth"].isNull() ||
|
data["lora"]["txSignalBandwidth"].isNull() ||
|
||||||
data["lora"]["power"].isNull()) needsRewrite = true;
|
data["lora"]["power"].isNull() ||
|
||||||
|
data["lora"]["cadActive"].isNull()) needsRewrite = true;
|
||||||
loramodule.rxActive = data["lora"]["rxActive"] | true;
|
loramodule.rxActive = data["lora"]["rxActive"] | true;
|
||||||
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
|
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
|
||||||
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
|
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
|
||||||
@@ -313,6 +314,7 @@ bool Configuration::readFile() {
|
|||||||
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
|
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
|
||||||
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
|
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
|
||||||
loramodule.power = data["lora"]["power"] | 20;
|
loramodule.power = data["lora"]["power"] | 20;
|
||||||
|
loramodule.cadActive = data["lora"]["cadActive"] | true;
|
||||||
|
|
||||||
if (data["display"]["alwaysOn"].isNull() ||
|
if (data["display"]["alwaysOn"].isNull() ||
|
||||||
data["display"]["timeout"].isNull() ||
|
data["display"]["timeout"].isNull() ||
|
||||||
@@ -503,6 +505,7 @@ void Configuration::setDefaultValues() {
|
|||||||
loramodule.txCodingRate4 = 5;
|
loramodule.txCodingRate4 = 5;
|
||||||
loramodule.txSignalBandwidth = 125000;
|
loramodule.txSignalBandwidth = 125000;
|
||||||
loramodule.power = 20;
|
loramodule.power = 20;
|
||||||
|
loramodule.cadActive = true;
|
||||||
|
|
||||||
display.alwaysOn = true;
|
display.alwaysOn = true;
|
||||||
display.timeout = 4;
|
display.timeout = 4;
|
||||||
@@ -567,14 +570,17 @@ void Configuration::setDefaultValues() {
|
|||||||
|
|
||||||
void Configuration::setup() {
|
void Configuration::setup() {
|
||||||
if (!SPIFFS.begin(false)) {
|
if (!SPIFFS.begin(false)) {
|
||||||
Serial.println("SPIFFS Mount Failed");
|
Serial.println("SPIFFS Mount Failed, formatting...");
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
Serial.println("SPIFFS Mounted");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool exists = SPIFFS.exists("/igate_conf.json");
|
if (!SPIFFS.begin(true)) {
|
||||||
if (!exists) {
|
Serial.println("SPIFFS Format Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("SPIFFS Ready");
|
||||||
|
|
||||||
|
if (!SPIFFS.exists("/igate_conf.json")) {
|
||||||
|
Serial.println("Config not found, creating default...");
|
||||||
setDefaultValues();
|
setDefaultValues();
|
||||||
writeFile();
|
writeFile();
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|||||||
+48
-10
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -42,8 +42,8 @@ extern bool backupDigiMode;
|
|||||||
|
|
||||||
namespace DIGI_Utils {
|
namespace DIGI_Utils {
|
||||||
|
|
||||||
String cleanPathAsterisks(String path) {
|
String cleanPath(String path) {
|
||||||
String terms[] = {",WIDE1*", ",WIDE2*", "*"};
|
String terms[] = {"WIDE1*,", "WIDE2*,", "*"};
|
||||||
for (String term : terms) {
|
for (String term : terms) {
|
||||||
int index = path.indexOf(term);
|
int index = path.indexOf(term);
|
||||||
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
|
if (index != -1) path.remove(index, term.length()); // less memory than: tempPath.replace("*", "");
|
||||||
@@ -51,6 +51,35 @@ namespace DIGI_Utils {
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String processMode3Path(const String& path, const String& stationCallsign) {
|
||||||
|
int start = 0;
|
||||||
|
bool prevTokensAllStar = true;
|
||||||
|
int ownTokenEnd = -1;
|
||||||
|
|
||||||
|
while (start < path.length()) {
|
||||||
|
int delim = path.indexOf(',', start);
|
||||||
|
if (delim == -1) delim = path.length(); // busca todo hasta lograr encontra una coma o el final del string
|
||||||
|
|
||||||
|
String token = path.substring(start, delim);
|
||||||
|
bool tokenIsOwn = (token == stationCallsign) || (token == stationCallsign + "*");
|
||||||
|
bool tokenStar = token.endsWith("*");
|
||||||
|
|
||||||
|
if (tokenIsOwn) {
|
||||||
|
if (tokenStar) return ""; // already digipeated
|
||||||
|
if (!prevTokensAllStar) return ""; // earlier tokens must be marked
|
||||||
|
ownTokenEnd = delim;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tokenStar) prevTokensAllStar = false;
|
||||||
|
start = delim + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownTokenEnd == -1) return "";
|
||||||
|
String tempPacket = cleanPath(path.substring(0, ownTokenEnd));
|
||||||
|
return tempPacket + "*" + path.substring(ownTokenEnd);
|
||||||
|
}
|
||||||
|
|
||||||
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
|
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq) {
|
||||||
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||||
String suffix = thirdParty ? ":}" : ":";
|
String suffix = thirdParty ? ":}" : ":";
|
||||||
@@ -60,11 +89,11 @@ namespace DIGI_Utils {
|
|||||||
int digiMode = Config.digi.mode;
|
int digiMode = Config.digi.mode;
|
||||||
String tempPath = path;
|
String tempPath = path;
|
||||||
|
|
||||||
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 2 || digiMode == 3)) { // WIDE1-1
|
if (tempPath.indexOf("WIDE1-1") != -1 && (digiMode == 1 || digiMode == 2)) { // WIDE1-1
|
||||||
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
|
if (tempPath.indexOf("*") != -1 ) return ""; // "*" shouldn't be in WIDE1-1 (only) type of packet
|
||||||
tempPath.replace("WIDE1-1", stationCallsign + "*");
|
tempPath.replace("WIDE1-1", stationCallsign + "*");
|
||||||
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 3) { // WIDE2-n Digipeater
|
} else if (tempPath.indexOf("WIDE2-") != -1 && digiMode == 2) { // WIDE2-n Digipeater
|
||||||
tempPath = cleanPathAsterisks(path);
|
tempPath = cleanPath(path);
|
||||||
if (tempPath.indexOf("WIDE2-1") != -1) {
|
if (tempPath.indexOf("WIDE2-1") != -1) {
|
||||||
tempPath.replace("WIDE2-1", stationCallsign + "*");
|
tempPath.replace("WIDE2-1", stationCallsign + "*");
|
||||||
} else if (tempPath.indexOf("WIDE2-2") != -1) {
|
} else if (tempPath.indexOf("WIDE2-2") != -1) {
|
||||||
@@ -72,11 +101,14 @@ namespace DIGI_Utils {
|
|||||||
} else {
|
} else {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
} else if (digiMode == 3) { // Repeat if station callsign is in path (free to repeat).
|
||||||
|
tempPath = processMode3Path(tempPath, stationCallsign);
|
||||||
|
if (tempPath == "") return "";
|
||||||
}
|
}
|
||||||
packetToRepeat = packet.substring(0, packet.indexOf(",") + 1);
|
packetToRepeat = packet.substring(0, packet.indexOf(",") + 1);
|
||||||
packetToRepeat += tempPath;
|
packetToRepeat += tempPath;
|
||||||
} else { // CrossFreq Digipeater
|
} else { // CrossFreq Digipeater
|
||||||
packetToRepeat = cleanPathAsterisks(packet.substring(0, suffixIndex));
|
packetToRepeat = cleanPath(packet.substring(0, suffixIndex));
|
||||||
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
|
if (packetToRepeat.indexOf(stationCallsign) != -1) return ""; // stationCallsign shouldn't be in path
|
||||||
packetToRepeat += ",";
|
packetToRepeat += ",";
|
||||||
packetToRepeat += stationCallsign;
|
packetToRepeat += stationCallsign;
|
||||||
@@ -100,14 +132,14 @@ namespace DIGI_Utils {
|
|||||||
|
|
||||||
if (commaIndex > 2) { // "path" found
|
if (commaIndex > 2) { // "path" found
|
||||||
const String& path = temp.substring(commaIndex + 1);
|
const String& path = temp.substring(commaIndex + 1);
|
||||||
if (digiMode == 2 || backupDigiMode) {
|
if (digiMode == 1 || backupDigiMode) {
|
||||||
bool hasWide = path.indexOf("WIDE1-1") != -1;
|
bool hasWide = path.indexOf("WIDE1-1") != -1;
|
||||||
if (hasWide || crossFreq) {
|
if (hasWide || crossFreq) {
|
||||||
return buildPacket(path, packet, thirdParty, !hasWide);
|
return buildPacket(path, packet, thirdParty, !hasWide);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (digiMode == 3) {
|
if (digiMode == 2) {
|
||||||
int wide1Index = path.indexOf("WIDE1-1");
|
int wide1Index = path.indexOf("WIDE1-1");
|
||||||
int wide2Index = path.indexOf("WIDE2-");
|
int wide2Index = path.indexOf("WIDE2-");
|
||||||
bool hasWide1 = wide1Index != -1;
|
bool hasWide1 = wide1Index != -1;
|
||||||
@@ -121,10 +153,16 @@ namespace DIGI_Utils {
|
|||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
if (digiMode == 3) {
|
||||||
|
String stationCallsign = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||||
|
bool containsOwnCall = path.indexOf(stationCallsign) != -1;
|
||||||
|
if (containsOwnCall) return buildPacket(path, packet, thirdParty, false);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commaIndex == -1 && (digiMode == 2 || backupDigiMode || digiMode == 3) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
|
if (commaIndex == -1 && (digiMode == 1 || backupDigiMode || digiMode == 2) && crossFreq) return buildPacket("", packet, thirdParty, true); // no "path" but is CrossFreq Digi
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+11
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -185,6 +185,16 @@ namespace GPS_Utils {
|
|||||||
return " _ / _ / _ ";
|
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() {
|
void setup() {
|
||||||
#ifdef HAS_GPS
|
#ifdef HAS_GPS
|
||||||
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) {
|
if (Config.beacon.gpsActive && Config.digi.ecoMode != 1) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+57
-6
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <APRSPacketLib.h>
|
||||||
#include <RadioLib.h>
|
#include <RadioLib.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "network_manager.h"
|
#include "network_manager.h"
|
||||||
@@ -23,6 +24,7 @@
|
|||||||
#include "station_utils.h"
|
#include "station_utils.h"
|
||||||
#include "board_pinout.h"
|
#include "board_pinout.h"
|
||||||
#include "syslog_utils.h"
|
#include "syslog_utils.h"
|
||||||
|
#include "map_utils.h"
|
||||||
#include "ntp_utils.h"
|
#include "ntp_utils.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@@ -30,13 +32,15 @@
|
|||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern NetworkManager *networkManager;
|
extern NetworkManager *networkManager;
|
||||||
extern uint32_t lastRxTime;
|
|
||||||
extern bool packetIsBeacon;
|
extern bool packetIsBeacon;
|
||||||
|
|
||||||
extern std::vector<ReceivedPacket> receivedPackets;
|
extern std::vector<ReceivedPacket> receivedPackets;
|
||||||
|
|
||||||
bool operationDone = true;
|
bool operationDone = true;
|
||||||
bool transmitFlag = true;
|
bool transmitFlag = true;
|
||||||
|
|
||||||
|
#define DIFS_SLOTS 2 // Number of secuential CAD slots to consider a free channel to Tx
|
||||||
|
int backoffMax = 4; // Max Backoff value (number of CAD slots to wait before Tx)
|
||||||
|
|
||||||
#ifdef HAS_SX1262
|
#ifdef HAS_SX1262
|
||||||
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
|
||||||
@@ -65,6 +69,15 @@ float snr;
|
|||||||
|
|
||||||
namespace LoRa_Utils {
|
namespace LoRa_Utils {
|
||||||
|
|
||||||
|
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++) {
|
||||||
|
uint8_t c = (uint8_t)out[i];
|
||||||
|
if (c < 32 || c == 127) out.setCharAt(i, '.');
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
void setFlag(void) {
|
void setFlag(void) {
|
||||||
operationDone = true;
|
operationDone = true;
|
||||||
}
|
}
|
||||||
@@ -167,6 +180,34 @@ namespace LoRa_Utils {
|
|||||||
radio.setBandwidth(signalBandwidth);
|
radio.setBandwidth(signalBandwidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool doCAD() { // CAD (Channel Activity Detection)
|
||||||
|
return radio.scanChannel() != RADIOLIB_CHANNEL_FREE; // false=channel free | true=RADIOLIB_LORA_DETECTED or CAD failed
|
||||||
|
}
|
||||||
|
|
||||||
|
bool doDIFS() {
|
||||||
|
for (uint8_t i = DIFS_SLOTS; i > 0; i--) {
|
||||||
|
if (doCAD()) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void waitForDIFS() {
|
||||||
|
while (!doDIFS()) {
|
||||||
|
Serial.println("CAD/DIFS failed, retry...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void doBEB() {
|
||||||
|
int backoffCounter = random(1, backoffMax + 1);
|
||||||
|
while (backoffCounter > 0) {
|
||||||
|
if (doCAD()) {
|
||||||
|
waitForDIFS(); // busy channel: freeze backoff and restart DIFS
|
||||||
|
} else {
|
||||||
|
backoffCounter--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void sendNewPacket(const String& newPacket) {
|
void sendNewPacket(const String& newPacket) {
|
||||||
if (!Config.loramodule.txActive) return;
|
if (!Config.loramodule.txActive) return;
|
||||||
|
|
||||||
@@ -179,6 +220,12 @@ namespace LoRa_Utils {
|
|||||||
#ifdef INTERNAL_LED_PIN
|
#ifdef INTERNAL_LED_PIN
|
||||||
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
|
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (Config.loramodule.cadActive) {
|
||||||
|
waitForDIFS(); // DIFS (Distributed Inter-Frame Space)
|
||||||
|
doBEB(); // BEB (Binary Exponential Backoff)
|
||||||
|
}
|
||||||
|
|
||||||
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
int state = radio.transmit("\x3c\xff\x01" + newPacket);
|
||||||
transmitFlag = true;
|
transmitFlag = true;
|
||||||
if (state == RADIOLIB_ERR_NONE) {
|
if (state == RADIOLIB_ERR_NONE) {
|
||||||
@@ -238,10 +285,15 @@ namespace LoRa_Utils {
|
|||||||
}
|
}
|
||||||
ReceivedPacket receivedPacket;
|
ReceivedPacket receivedPacket;
|
||||||
receivedPacket.rxTime = NTP_Utils::getFormatedTime();
|
receivedPacket.rxTime = NTP_Utils::getFormatedTime();
|
||||||
receivedPacket.packet = packet.substring(3);
|
receivedPacket.packet = sanitizeForWeb(packet.substring(3));
|
||||||
receivedPacket.RSSI = rssi;
|
receivedPacket.RSSI = rssi;
|
||||||
receivedPacket.SNR = snr;
|
receivedPacket.SNR = snr;
|
||||||
receivedPackets.push_back(receivedPacket);
|
receivedPackets.push_back(receivedPacket);
|
||||||
|
|
||||||
|
APRSPacket aprsPacket = APRSPacketLib::processReceivedPacket(packet.substring(3), rssi, snr, freqError);
|
||||||
|
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (los que traen posición)
|
||||||
|
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.rssi, aprsPacket.snr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Config.syslog.active && networkManager->isConnected()) {
|
if (Config.syslog.active && networkManager->isConnected()) {
|
||||||
@@ -250,7 +302,6 @@ namespace LoRa_Utils {
|
|||||||
} else {
|
} else {
|
||||||
packet = "";
|
packet = "";
|
||||||
}
|
}
|
||||||
lastRxTime = millis();
|
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
|
*
|
||||||
|
* This file is part of LoRa APRS iGate.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include "map_utils.h"
|
||||||
|
#include "ntp_utils.h"
|
||||||
|
|
||||||
|
#define MAX_MAP_STATIONS 50
|
||||||
|
#define STATION_TTL_MS 3600000UL // 1 hora (en milisegundos)
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<MapStation> mapStations;
|
||||||
|
|
||||||
|
|
||||||
|
namespace MAP_Utils {
|
||||||
|
|
||||||
|
// Copia la hora NTP "HH:MM:SS" si es valida; si no hay red (NTP devuelve
|
||||||
|
// un texto de fallback) deja el campo vacio para no guardar hora falsa.
|
||||||
|
void writeFormatedTime(char* dst, size_t size) {
|
||||||
|
String t = NTP_Utils::getFormatedTime();
|
||||||
|
if (t.length() == 8 && t.charAt(2) == ':') {
|
||||||
|
t.toCharArray(dst, size);
|
||||||
|
} else {
|
||||||
|
dst[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inserta una estacion nueva o actualiza la existente (dedup por callsign).
|
||||||
|
// Llamar solo cuando el parser ya decodifico una posicion valida.
|
||||||
|
void upsert(const String& callsign, float latitude, float longitude, const String& symbol, int rssi, float snr) {
|
||||||
|
for (auto &s : mapStations) { // 1) ya existe -> actualizar
|
||||||
|
if (callsign.equals(s.callsign)) {
|
||||||
|
s.latitude = latitude;
|
||||||
|
s.longitude = longitude;
|
||||||
|
s.rssi = rssi;
|
||||||
|
s.snr = snr;
|
||||||
|
s.count++;
|
||||||
|
symbol.toCharArray(s.symbol, sizeof(s.symbol));
|
||||||
|
writeFormatedTime(s.lastHeard, sizeof(s.lastHeard));
|
||||||
|
s.lastHeardMillis = millis();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapStations.size() >= MAX_MAP_STATIONS) { // 2) llena -> descartar la mas vieja
|
||||||
|
size_t oldest = 0;
|
||||||
|
for (size_t i = 1; i < mapStations.size(); i++) {
|
||||||
|
if (mapStations[i].lastHeardMillis < mapStations[oldest].lastHeardMillis) {
|
||||||
|
oldest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapStations.erase(mapStations.begin() + oldest);
|
||||||
|
}
|
||||||
|
|
||||||
|
MapStation st = {}; // 3) insertar nueva
|
||||||
|
callsign.toCharArray(st.callsign, sizeof(st.callsign));
|
||||||
|
symbol.toCharArray(st.symbol, sizeof(st.symbol));
|
||||||
|
st.latitude = latitude;
|
||||||
|
st.longitude = longitude;
|
||||||
|
st.rssi = rssi;
|
||||||
|
st.snr = snr;
|
||||||
|
st.count = 1;
|
||||||
|
writeFormatedTime(st.lastHeard, sizeof(st.lastHeard));
|
||||||
|
st.lastHeardMillis = millis();
|
||||||
|
mapStations.push_back(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
void purgeOldStations() {
|
||||||
|
uint32_t now = millis();
|
||||||
|
for (int i = (int)mapStations.size() - 1; i >= 0; i--) {
|
||||||
|
if (now - mapStations[i].lastHeardMillis > STATION_TTL_MS) {
|
||||||
|
mapStations.erase(mapStations.begin() + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getStationsJson() {
|
||||||
|
purgeOldStations();
|
||||||
|
JsonDocument data;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < mapStations.size(); i++) {
|
||||||
|
data[i]["callsign"] = mapStations[i].callsign;
|
||||||
|
data[i]["lat"] = mapStations[i].latitude;
|
||||||
|
data[i]["lon"] = mapStations[i].longitude;
|
||||||
|
data[i]["symbol"] = mapStations[i].symbol;
|
||||||
|
data[i]["RSSI"] = mapStations[i].rssi;
|
||||||
|
data[i]["SNR"] = mapStations[i].snr;
|
||||||
|
data[i]["count"] = mapStations[i].count;
|
||||||
|
data[i]["lastHeard"] = mapStations[i].lastHeard;
|
||||||
|
}
|
||||||
|
|
||||||
|
String buffer;
|
||||||
|
|
||||||
|
serializeJson(data, buffer);
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
|
*
|
||||||
|
* This file is part of LoRa APRS iGate.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
#include "network_manager.h"
|
#include "network_manager.h"
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -82,7 +82,7 @@ namespace QUERY_Utils {
|
|||||||
int radioTxActive = Config.loramodule.txActive;
|
int radioTxActive = Config.loramodule.txActive;
|
||||||
bool onlyRadioActive = radioTxActive && Config.loramodule.rxActive && !Config.aprs_is.active;
|
bool onlyRadioActive = radioTxActive && Config.loramodule.rxActive && !Config.aprs_is.active;
|
||||||
if (queryQuestion.startsWith("?EM=OFF")) {
|
if (queryQuestion.startsWith("?EM=OFF")) {
|
||||||
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
||||||
if (digiEcoMode == 1 || digiEcoMode == 2) { // Exit Digipeater EcoMode or Digipeater without WiFiAP
|
if (digiEcoMode == 1 || digiEcoMode == 2) { // Exit Digipeater EcoMode or Digipeater without WiFiAP
|
||||||
answer = (digiEcoMode == 1) ? "DigiEcoMode:OFF" : "Digipeater + WiFiAP enabled";
|
answer = (digiEcoMode == 1) ? "DigiEcoMode:OFF" : "Digipeater + WiFiAP enabled";
|
||||||
Config.digi.ecoMode = 0;
|
Config.digi.ecoMode = 0;
|
||||||
@@ -97,7 +97,7 @@ namespace QUERY_Utils {
|
|||||||
answer = "Digipeater Mode control not possible";
|
answer = "Digipeater Mode control not possible";
|
||||||
}
|
}
|
||||||
} else if (queryQuestion.startsWith("?EM=ON")) {
|
} else if (queryQuestion.startsWith("?EM=ON")) {
|
||||||
if ((digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
if ((digiMode == 1 || digiMode == 2 || digiMode == 3) && onlyRadioActive) {
|
||||||
if (digiEcoMode == 0) { // Start Digipeater EcoMode
|
if (digiEcoMode == 0) { // Start Digipeater EcoMode
|
||||||
answer = "DigiEcoMode:ON";
|
answer = "DigiEcoMode:ON";
|
||||||
Config.digi.ecoMode = 1;
|
Config.digi.ecoMode = 1;
|
||||||
@@ -139,7 +139,7 @@ namespace QUERY_Utils {
|
|||||||
|
|
||||||
if (answer == "") return "";
|
if (answer == "") return "";
|
||||||
|
|
||||||
String queryAnswer = Config.callsign;
|
String queryAnswer = (Config.tacticalCallsign == "" ? Config.callsign : Config.tacticalCallsign);
|
||||||
queryAnswer += ">APLRG1";
|
queryAnswer += ">APLRG1";
|
||||||
if (queryFromAPRSIS) {
|
if (queryFromAPRSIS) {
|
||||||
queryAnswer += ",TCPIP,qAC";
|
queryAnswer += ",TCPIP,qAC";
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+2
-10
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -25,15 +25,11 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define SECS_TO_WAIT 3 // soon to be deleted...
|
|
||||||
|
|
||||||
|
|
||||||
extern Configuration Config;
|
extern Configuration Config;
|
||||||
extern uint32_t lastRxTime;
|
|
||||||
extern String fourthLine;
|
extern String fourthLine;
|
||||||
extern bool shouldSleepLowVoltage;
|
extern bool shouldSleepLowVoltage;
|
||||||
|
|
||||||
uint32_t lastTxTime = millis();
|
|
||||||
std::vector<LastHeardStation> lastHeardStations;
|
std::vector<LastHeardStation> lastHeardStations;
|
||||||
std::vector<String> blacklist;
|
std::vector<String> blacklist;
|
||||||
std::vector<String> managers;
|
std::vector<String> managers;
|
||||||
@@ -214,15 +210,11 @@ namespace STATION_Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void processOutputPacketBuffer() {
|
void processOutputPacketBuffer() {
|
||||||
int timeToWait = SECS_TO_WAIT * 1000; // 3 segs between packet Tx and also Rx ???
|
if (outputPacketBuffer.size() > 0) {
|
||||||
uint32_t lastRx = millis() - lastRxTime;
|
|
||||||
uint32_t lastTx = millis() - lastTxTime;
|
|
||||||
if (outputPacketBuffer.size() > 0 && lastTx > timeToWait && lastRx > timeToWait) {
|
|
||||||
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
|
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
|
||||||
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
|
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
|
||||||
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
|
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
|
||||||
outputPacketBuffer.erase(outputPacketBuffer.begin());
|
outputPacketBuffer.erase(outputPacketBuffer.begin());
|
||||||
lastTxTime = millis();
|
|
||||||
}
|
}
|
||||||
if (shouldSleepLowVoltage) {
|
if (shouldSleepLowVoltage) {
|
||||||
while (outputPacketBuffer.size() > 0) {
|
while (outputPacketBuffer.size() > 0) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -79,11 +79,15 @@ namespace SYSLOG_Utils {
|
|||||||
syslogPacket.concat(sender);
|
syslogPacket.concat(sender);
|
||||||
syslogPacket.concat(" ---> ");
|
syslogPacket.concat(" ---> ");
|
||||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
syslogPacket.concat(packet.substring(colonIndex + 2));
|
||||||
} else if (nextChar == '`') {
|
} else if (nextChar == '`' || nextChar == '\'') { // both Mic-E DTIs are valid
|
||||||
syslogPacket.concat("MIC-E / ");
|
syslogPacket.concat("MIC-E / ");
|
||||||
syslogPacket.concat(sender);
|
syslogPacket.concat(sender);
|
||||||
syslogPacket.concat(" ---> ");
|
syslogPacket.concat(" ---> ");
|
||||||
|
syslogPacket.concat(String(nextChar)); // restore the actual indicator received
|
||||||
syslogPacket.concat(packet.substring(colonIndex + 2));
|
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 == ';') {
|
} else if (nextChar == ';') {
|
||||||
syslogPacket.concat("OBJECT / ");
|
syslogPacket.concat("OBJECT / ");
|
||||||
syslogPacket.concat(sender);
|
syslogPacket.concat(sender);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+33
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "ota_utils.h"
|
#include "ota_utils.h"
|
||||||
#include "web_utils.h"
|
#include "web_utils.h"
|
||||||
|
#include "map_utils.h"
|
||||||
#include "display.h"
|
#include "display.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
@@ -48,6 +49,14 @@ extern const char web_bootstrap_js[] asm("_binary_data_embed_bootstrap_js_gz_sta
|
|||||||
extern const char web_bootstrap_js_end[] asm("_binary_data_embed_bootstrap_js_gz_end");
|
extern const char web_bootstrap_js_end[] asm("_binary_data_embed_bootstrap_js_gz_end");
|
||||||
extern const size_t web_bootstrap_js_len = web_bootstrap_js_end - web_bootstrap_js;
|
extern const size_t web_bootstrap_js_len = web_bootstrap_js_end - web_bootstrap_js;
|
||||||
|
|
||||||
|
extern const char web_leaflet_css[] asm("_binary_data_embed_leaflet_css_gz_start");
|
||||||
|
extern const char web_leaflet_css_end[] asm("_binary_data_embed_leaflet_css_gz_end");
|
||||||
|
extern const size_t web_leaflet_css_len = web_leaflet_css_end - web_leaflet_css;
|
||||||
|
|
||||||
|
extern const char web_leaflet_js[] asm("_binary_data_embed_leaflet_js_gz_start");
|
||||||
|
extern const char web_leaflet_js_end[] asm("_binary_data_embed_leaflet_js_gz_end");
|
||||||
|
extern const size_t web_leaflet_js_len = web_leaflet_js_end - web_leaflet_js;
|
||||||
|
|
||||||
// Declare external symbols for the embedded image data
|
// Declare external symbols for the embedded image data
|
||||||
extern const unsigned char favicon_data[] asm("_binary_data_embed_favicon_png_gz_start");
|
extern const unsigned char favicon_data[] asm("_binary_data_embed_favicon_png_gz_start");
|
||||||
extern const unsigned char favicon_data_end[] asm("_binary_data_embed_favicon_png_gz_end");
|
extern const unsigned char favicon_data_end[] asm("_binary_data_embed_favicon_png_gz_end");
|
||||||
@@ -114,6 +123,10 @@ namespace WEB_Utils {
|
|||||||
request->send(200, "application/json", buffer);
|
request->send(200, "application/json", buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleStations(AsyncWebServerRequest *request) {
|
||||||
|
request->send(200, "application/json", MAP_Utils::getStationsJson());
|
||||||
|
}
|
||||||
|
|
||||||
void handleWriteConfiguration(AsyncWebServerRequest *request) {
|
void handleWriteConfiguration(AsyncWebServerRequest *request) {
|
||||||
Serial.println("Got new Configuration Data from www");
|
Serial.println("Got new Configuration Data from www");
|
||||||
|
|
||||||
@@ -213,6 +226,8 @@ namespace WEB_Utils {
|
|||||||
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
|
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
|
||||||
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
|
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
|
||||||
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
|
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
|
||||||
|
if (Config.loramodule.txActive)
|
||||||
|
Config.loramodule.cadActive = request->hasParam("lora.cadActive", true);
|
||||||
|
|
||||||
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
|
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
|
||||||
if (!Config.display.alwaysOn) {
|
if (!Config.display.alwaysOn) {
|
||||||
@@ -356,11 +371,26 @@ namespace WEB_Utils {
|
|||||||
request->send(response);
|
request->send(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleLeafletStyle(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse(200, "text/css", (const uint8_t*)web_leaflet_css, web_leaflet_css_len);
|
||||||
|
response->addHeader("Content-Encoding", "gzip");
|
||||||
|
response->addHeader("Cache-Control", "max-age=3600");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleLeafletScript(AsyncWebServerRequest *request) {
|
||||||
|
AsyncWebServerResponse *response = request->beginResponse(200, "text/javascript", (const uint8_t*)web_leaflet_js, web_leaflet_js_len);
|
||||||
|
response->addHeader("Content-Encoding", "gzip");
|
||||||
|
response->addHeader("Cache-Control", "max-age=3600");
|
||||||
|
request->send(response);
|
||||||
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
if (Config.digi.ecoMode == 0) {
|
if (Config.digi.ecoMode == 0) {
|
||||||
server.on("/", HTTP_GET, handleHome);
|
server.on("/", HTTP_GET, handleHome);
|
||||||
server.on("/status", HTTP_GET, handleStatus);
|
server.on("/status", HTTP_GET, handleStatus);
|
||||||
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
|
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
|
||||||
|
server.on("/stations.json", HTTP_GET, handleStations);
|
||||||
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
|
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
|
||||||
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
|
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
|
||||||
server.on("/action", HTTP_POST, handleAction);
|
server.on("/action", HTTP_POST, handleAction);
|
||||||
@@ -368,6 +398,8 @@ namespace WEB_Utils {
|
|||||||
server.on("/script.js", HTTP_GET, handleScript);
|
server.on("/script.js", HTTP_GET, handleScript);
|
||||||
server.on("/bootstrap.css", HTTP_GET, handleBootstrapStyle);
|
server.on("/bootstrap.css", HTTP_GET, handleBootstrapStyle);
|
||||||
server.on("/bootstrap.js", HTTP_GET, handleBootstrapScript);
|
server.on("/bootstrap.js", HTTP_GET, handleBootstrapScript);
|
||||||
|
server.on("/leaflet.css", HTTP_GET, handleLeafletStyle);
|
||||||
|
server.on("/leaflet.js", HTTP_GET, handleLeafletScript);
|
||||||
server.on("/favicon.png", HTTP_GET, handleFavicon);
|
server.on("/favicon.png", HTTP_GET, handleFavicon);
|
||||||
|
|
||||||
OTA_Utils::setup(&server); // Include OTA Updater for WebServer
|
OTA_Utils::setup(&server); // Include OTA Updater for WebServer
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
+14
-12
@@ -1,17 +1,17 @@
|
|||||||
# Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
# Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
#
|
#
|
||||||
# This file is part of LoRa APRS iGate.
|
# This file is part of LoRa APRS iGate.
|
||||||
#
|
#
|
||||||
# LoRa APRS iGate is free software: you can redistribute it and/or modify
|
# LoRa APRS iGate is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
# (at your option) any later version.
|
# (at your option) any later version.
|
||||||
#
|
#
|
||||||
# LoRa APRS iGate is distributed in the hope that it will be useful,
|
# LoRa APRS iGate is distributed in the hope that it will be useful,
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
# GNU General Public License for more details.
|
# GNU General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
# along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ files = [
|
|||||||
'data_embed/style.css',
|
'data_embed/style.css',
|
||||||
'data_embed/bootstrap.js',
|
'data_embed/bootstrap.js',
|
||||||
'data_embed/bootstrap.css',
|
'data_embed/bootstrap.css',
|
||||||
|
'data_embed/leaflet.js',
|
||||||
|
'data_embed/leaflet.css',
|
||||||
'data_embed/favicon.png',
|
'data_embed/favicon.png',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -40,20 +42,20 @@ with open('src/LoRa_APRS_iGate.cpp', encoding='utf-8') as cpp_file:
|
|||||||
if start > 0 and end > start:
|
if start > 0 and end > start:
|
||||||
versionDate = line[start:end]
|
versionDate = line[start:end]
|
||||||
break
|
break
|
||||||
|
|
||||||
for src in files:
|
for src in files:
|
||||||
out = src + ".gz"
|
out = src + ".gz"
|
||||||
|
|
||||||
|
|
||||||
with open(src, 'rb') as f:
|
with open(src, 'rb') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
if src == 'data_embed/index.html':
|
if src == 'data_embed/index.html':
|
||||||
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
|
env_vars = env["BOARD"] + "<br>" + ','.join(env["BUILD_FLAGS"]).replace('-Werror -Wall,', '').replace(',-DELEGANTOTA_USE_ASYNC_WEBSERVER=1', '') + "<br>" + "Version date: " + versionDate
|
||||||
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
|
current_date = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + " UTC"
|
||||||
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
|
build_info = f'{env_vars}<br>Build date: {current_date}'.encode()
|
||||||
|
|
||||||
content = content.replace(b'%BUILD_INFO%', build_info)
|
content = content.replace(b'%BUILD_INFO%', build_info)
|
||||||
|
|
||||||
with open(out, 'wb') as f:
|
with open(out, 'wb') as f:
|
||||||
f.write(gzip.compress(content, compresslevel=9))
|
f.write(gzip.compress(content, compresslevel=9))
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
|
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
|
||||||
*
|
*
|
||||||
* This file is part of LoRa APRS iGate.
|
* This file is part of LoRa APRS iGate.
|
||||||
*
|
*
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user