Compare commits

...
Author SHA1 Message Date
Ricardo Guzman 463dd6342b Merge branch 'pr-458' into TestPRJohnRokicki 2026-09-07 17:31:25 -03:00
John Rokicki 505f0d6f41 renames as requested 2026-09-07 16:02:59 -04:00
John Rokicki 01befdef92 fix mobile device formatting 2026-09-07 15:13:11 -04:00
John Rokicki f14b6b3cd9 Station status support 2026-08-27 21:28:34 -04:00
21 changed files with 1131 additions and 34 deletions

No files matched your search

+516 -11
View File
@@ -95,7 +95,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
fill-rule="evenodd"
d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1"
/>
</svg></span><span class="label">Station</span></button>
</svg></span><span class="label">General</span></button>
<button class="side-link" type="button" data-target="sec-beaconing"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M15.964.686a.5.5 0 0 0-.65-.65L.767 5.855H.766l-.452.18a.5.5 0 0 0-.082.887l.41.26.001.002 4.995 3.178 3.178 4.995.002.002.26.41a.5.5 0 0 0 .886-.083zm-1.833 1.89L6.637 10.07l-.215-.338a.5.5 0 0 0-.154-.154l-.338-.215 7.494-7.494 1.178-.471z"/></svg></span><span class="label">Beaconing</span></button>
<button class="side-link" type="button" data-target="sec-connectivity"><span class="side-ico"><svg
xmlns="http://www.w3.org/2000/svg"
@@ -186,6 +186,8 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
<button class="side-link" type="button" data-target="sec-maps"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M15.817.113A.5.5 0 0 1 16 .5v14a.5.5 0 0 1-.402.49l-5 1a.5.5 0 0 1-.196 0L5.5 15.01l-4.902.98A.5.5 0 0 1 0 15.5v-14a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0L10.5.99l4.902-.98a.5.5 0 0 1 .415.103M10 1.91l-4-.8v12.98l4 .8zm1 12.98 4-.8V1.11l-4 .8zm-6-.8V1.11l-4 .8v12.98z"/></svg></span><span class="label">Map</span></button>
<button class="side-link" type="button" id="rpToggle"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg></span><span class="label">Received Packets</span></button>
<a href="/received-packets" id="rpDummy" style="display:none" aria-hidden="true"></a>
<button class="side-link" type="button" id="ssToggle"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2"/></svg></span><span class="label">Station</span></button>
<a href="/station-status" id="ssDummy" style="display:none" aria-hidden="true"></a>
<a class="side-link side-ota" href="/update"><span class="side-ico"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor" viewBox="0 0 16 16"><path d="M8 2a5.53 5.53 0 0 0-3.594 1.342c-.766.66-1.321 1.52-1.464 2.383C1.266 6.095 0 7.555 0 9.318 0 11.366 1.708 13 3.781 13h8.906C14.502 13 16 11.57 16 9.773c0-1.636-1.242-2.969-2.834-3.194C12.923 3.999 10.69 2 8 2m2.354 5.146a.5.5 0 0 1-.708.708L8.5 6.707V10.5a.5.5 0 0 1-1 0V6.707L6.354 7.854a.5.5 0 1 1-.708-.708l2-2a.5.5 0 0 1 .708 0z"/></svg></span><span class="label">Update OTA</span></a>
</aside>
<main class="app-main">
@@ -212,6 +214,503 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
</div>
<hr>
</div>
<div class="container d-none" id="station-status">
<div class="row my-5 d-flex align-items-top">
<div class="col-12">
<h3>Current station status</h3>
<div id="stationStatus.uptime">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-heart-pulse-fill"
viewBox="0 0 16 16"
>
<path
d="M1.475 9C2.702 10.84 4.779 12.871 8 15c3.221-2.129 5.298-4.16 6.525-6H12a.5.5 0 0 1-.464-.314l-1.457-3.642-1.598 5.593a.5.5 0 0 1-.945.049L5.889 6.568l-1.473 2.21A.5.5 0 0 1 4 9z"
/>
<path
d="M.88 8C-2.427 1.68 4.41-2 7.823 1.143q.09.083.176.171a3 3 0 0 1 .176-.17C11.59-2 18.426 1.68 15.12 8h-2.783l-1.874-4.686a.5.5 0 0 0-.945.049L7.921 8.956 6.464 5.314a.5.5 0 0 0-.88-.091L3.732 8z"
/>
</svg>
Up-time
</h5>
<small>Current device up-time</small>
</div>
<div class="col-lg-9 col-sm-12">
<div class="row mt-2">
<div class="col-6 mt-1">
<label for="stationStatus.elapsedTime" class="form-label"
>Elapsed time</label
>
<div class="input-group">
<input
type="text"
name="stationStatus.elapsedTime"
id="stationStatus.elapsedTime"
placeholder="000d 00h 00m 00s"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.voltage">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-battery-fill"
viewBox="0 0 16 16">
<path d="M2 4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2zm1 2h8v4H3V6z"/>
<path d="M15 6.5a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1V6.5z"/>
<path d="M4 7h6v2H4V7z"/>
</svg>
Battery
</h5>
<small>Current Battery status</small>
</div>
<div class="col-lg-9 col-sm-12">
<div class="row mt-2">
<div class="col-6 mt-1">
<label
for="stationStatus.voltageInternal"
class="form-label">
Internal Voltage
</label>
<div class="input-group">
<input
type="text"
name="stationStatus.voltageInternal"
id="stationStatus.voltageInternal"
placeholder="0.00"
class="form-control"
readonly
/>
<span class="input-group-text"
>Volts
</span>
</div>
</div>
<div class="col-6 mt-1">
<label for="stationStatus.voltageExternal" class="form-label"
>External voltage</label
>
<div class="input-group">
<input
type="text"
name="stationStatus.voltageExternal"
id="stationStatus.voltageExternal"
placeholder="0.00"
class="form-control"
readonly
/>
<span class="input-group-text"
>Volts</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.connectivity">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-router-fill"
viewBox="0 0 16 16"
>
<path
d="M5.525 3.025a3.5 3.5 0 0 1 4.95 0 .5.5 0 1 0 .707-.707 4.5 4.5 0 0 0-6.364 0 .5.5 0 0 0 .707.707"
/>
<path
d="M6.94 4.44a1.5 1.5 0 0 1 2.12 0 .5.5 0 0 0 .708-.708 2.5 2.5 0 0 0-3.536 0 .5.5 0 0 0 .707.707Z"
/>
<path
d="M2.974 2.342a.5.5 0 1 0-.948.316L3.806 8H1.5A1.5 1.5 0 0 0 0 9.5v2A1.5 1.5 0 0 0 1.5 13H2a.5.5 0 0 0 .5.5h2A.5.5 0 0 0 5 13h6a.5.5 0 0 0 .5.5h2a.5.5 0 0 0 .5-.5h.5a1.5 1.5 0 0 0 1.5-1.5v-2A1.5 1.5 0 0 0 14.5 8h-2.306l1.78-5.342a.5.5 0 1 0-.948-.316L11.14 8H4.86zM2.5 11a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1m4.5-.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m2.5.5a.5.5 0 1 1 0-1 .5.5 0 0 1 0 1m1.5-.5a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0m2 0a.5.5 0 1 1 1 0 .5.5 0 0 1-1 0"
/>
<path
d="M8.5 5.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0"
/>
</svg>
Connectivity
</h5>
<small>Current device connectivity and service status</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusWifi" class="form-label"
>WiFi status</label
>
<div class="input-group">
<input
name="stationStatus.statusWifi"
id="stationStatus.statusWifi"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.statusIS" class="form-label"
>IS status</label
>
<div class="input-group">
<input
name="stationStatus.statusIS"
id="stationStatus.statusIS"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusNTP" class="form-label"
>NTP status</label
>
<div class="input-group">
<input
name="stationStatus.statusNTP"
id="stationStatus.statusNTP"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.statusSyslog" class="form-label"
>Syslog status</label
>
<div class="input-group">
<input
name="stationStatus.statusSyslog"
id="stationStatus.statusSyslog"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.statusMQTT" class="form-label"
>MQTT status</label
>
<div class="input-group">
<input
name="stationStatus.statusMQTT"
id="stationStatus.statusMQTT"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.traffic">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-broadcast"
viewBox="0 0 16 16"
>
<path
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707m2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708m5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708m2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM10 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0"
/>
</svg>
Traffic
</h5>
<small>Current packet traffic status. Resets every 24 hours.</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsRFSendSuccess" class="form-label"
>RF send success</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFSendSuccess"
id="stationStatus.packetsRFSendSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsRFSendFail" class="form-label"
>RF send failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFSendFail"
id="stationStatus.packetsRFSendFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsRFReceiveSuccess" class="form-label"
>RF receive success</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFReceiveSuccess"
id="stationStatus.packetsRFReceiveSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsRFReceiveFail" class="form-label"
>RF receive failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsRFReceiveFail"
id="stationStatus.packetsRFReceiveFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsISSendSuccess" class="form-label"
>IS send success</label
>
<div class="input-group">
<input
name="stationStatus.packetsISSendSuccess"
id="stationStatus.packetsISSendSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsISSendFail" class="form-label"
>IS send failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsISSendFail"
id="stationStatus.packetsISSendFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsISReceiveSuccess" class="form-label"
>IS receive success</label
>
<div class="input-group">
<input
name="stationStatus.packetsISReceiveSuccess"
id="stationStatus.packetsISReceiveSuccess"
class="form-control"
readonly
/>
</div>
</div>
<div class="col-6">
<label for="stationStatus.packetsISReceiveFail" class="form-label"
>IS receive failures</label
>
<div class="input-group">
<input
name="stationStatus.packetsISReceiveFail"
id="stationStatus.packetsISReceiveFail"
class="form-control"
readonly
/>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.packetsBlacklisted" class="form-label"
>Blacklisted packets</label
>
<div class="input-group">
<input
name="stationStatus.packetsBlacklisted"
id="stationStatus.packetsBlacklisted"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.login">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16"><path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2M2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>
Login Events
</h5>
<small>Current login statistics</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.loginsInvalid" class="form-label"
>Invalid logins</label
>
<div class="input-group">
<input
name="stationStatus.loginsInvalid"
id="stationStatus.loginsInvalid"
class="form-control"
readonly
/>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="stationStatus.weather">
<div class="row my-5 d-flex align-items-top">
<div class="col-lg-3 col-sm-12">
<h5>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
fill="currentColor"
class="bi bi-broadcast-pin"
viewBox="0 0 16 16"
>
<path
d="M3.05 3.05a7 7 0 0 0 0 9.9.5.5 0 0 1-.707.707 8 8 0 0 1 0-11.314.5.5 0 0 1 .707.707m2.122 2.122a4 4 0 0 0 0 5.656.5.5 0 1 1-.708.708 5 5 0 0 1 0-7.072.5.5 0 0 1 .708.708m5.656-.708a.5.5 0 0 1 .708 0 5 5 0 0 1 0 7.072.5.5 0 1 1-.708-.708 4 4 0 0 0 0-5.656.5.5 0 0 1 0-.708m2.122-2.12a.5.5 0 0 1 .707 0 8 8 0 0 1 0 11.313.5.5 0 0 1-.707-.707 7 7 0 0 0 0-9.9.5.5 0 0 1 0-.707zM6 8a2 2 0 1 1 2.5 1.937V15.5a.5.5 0 0 1-1 0V9.937A2 2 0 0 1 6 8"
/>
</svg>
Weather
</h5>
<small>Current Weather status</small>
</div>
<div class="col-9">
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.weatherTemperature" class="form-label"
>Temperature</label
>
<div class="input-group">
<input
name="stationStatus.weatherTemperature"
id="stationStatus.weatherTemperature"
class="form-control"
readonly
/>
<span class="input-group-text"
>C°</span
>
</div>
</div>
<div class="col-6">
<label for="stationStatus.weatherHumidity" class="form-label"
>Humidity</label
>
<div class="input-group">
<input
name="stationStatus.weatherHumidity"
id="stationStatus.weatherHumidity"
class="form-control"
readonly
/>
<span class="input-group-text"
>percent</span
>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-6">
<label for="stationStatus.weatherBarometricPressure" class="form-label"
>Barometric Pressure</label
>
<div class="input-group">
<input
name="stationStatus.weatherBarometricPressure"
id="stationStatus.weatherBarometricPressure"
class="form-control"
readonly
/>
<span class="input-group-text"
>Mb</span
>
</div>
</div>
<div class="col-6">
<label for="stationStatus.weatherGas" class="form-label"
>Gas</label
>
<div class="input-group">
<input
name="stationStatus.weatherGas"
id="stationStatus.weatherGas"
class="form-control"
readonly
/>
<span class="input-group-text"
>Kohms</span
>
</div>
</div>
</div>
</div>
</div>
</div>
<span>Status updates automatically every 15 seconds.</span><br>
</div>
</div>
<hr>
</div>
<div id="configuration">
<section class="panel-section" id="sec-maps">
<div class="row my-5">
@@ -1823,15 +2322,11 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
width="20"
height="20"
fill="currentColor"
class="bi bi-heart-pulse-fill"
viewBox="0 0 16 16"
>
<path
d="M1.475 9C2.702 10.84 4.779 12.871 8 15c3.221-2.129 5.298-4.16 6.525-6H12a.5.5 0 0 1-.464-.314l-1.457-3.642-1.598 5.593a.5.5 0 0 1-.945.049L5.889 6.568l-1.473 2.21A.5.5 0 0 1 4 9z"
/>
<path
d="M.88 8C-2.427 1.68 4.41-2 7.823 1.143q.09.083.176.171a3 3 0 0 1 .176-.17C11.59-2 18.426 1.68 15.12 8h-2.783l-1.874-4.686a.5.5 0 0 0-.945.049L7.921 8.956 6.464 5.314a.5.5 0 0 0-.88-.091L3.732 8z"
/>
class="bi bi-battery-fill"
viewBox="0 0 16 16">
<path d="M2 4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H2zm1 2h8v4H3V6z"/>
<path d="M15 6.5a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1V6.5z"/>
<path d="M4 7h6v2H4V7z"/>
</svg>
Battery
</h5>
@@ -2400,7 +2895,7 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
function paint(name){var r=document.documentElement;if(name==="night"){r.setAttribute("data-bs-theme","dark");r.setAttribute("data-variant","night");}else{r.setAttribute("data-bs-theme",name);r.removeAttribute("data-variant");}if(btn)btn.innerHTML=ICONS[name]||MOON;}
paint(logical());
if(btn)btn.addEventListener("click",function(){var c=logical();var n=ORDER[(ORDER.indexOf(c)+1)%ORDER.length];try{localStorage.setItem("igateTheme",n);}catch(e){}paint(n);});
function showConfig(){document.getElementById("configuration").classList.remove("d-none");var rp=document.getElementById("received-packets");if(rp)rp.classList.add("d-none");}
function showConfig(){document.getElementById("configuration").classList.remove("d-none");var rp=document.getElementById("received-packets");if(rp)rp.classList.add("d-none");var ss=document.getElementById("station-status");if(ss)ss.classList.add("d-none");}
document.querySelectorAll(".side-link[data-target]").forEach(function(b){
b.addEventListener("click",function(){
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
@@ -2415,9 +2910,19 @@ body{background:var(--msh-bg);color:var(--msh-text);font-family:"Inter",system-u
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
rpBtn.classList.add("active");
document.getElementById("configuration").classList.add("d-none");
document.getElementById("station-status").classList.add("d-none");
var rp=document.getElementById("received-packets");if(rp)rp.classList.remove("d-none");
if(window.fetchReceivedPackets)window.fetchReceivedPackets();
});
var ssBtn=document.getElementById("ssToggle");
if(ssBtn)ssBtn.addEventListener("click",function(){
document.querySelectorAll(".side-link").forEach(function(x){x.classList.remove("active");});
ssBtn.classList.add("active");
document.getElementById("configuration").classList.add("d-none");
document.getElementById("received-packets").classList.add("d-none");
var ss=document.getElementById("station-status");if(ss)ss.classList.remove("d-none");
if(window.fetchStationStatus)window.fetchStationStatus();
});
var bi=document.getElementById("buildInfo");
if(bi){var h=bi.innerHTML; if(/Version date/i.test(h)){h=h.replace(/(<br\s*\/?>)[\s\S]*?(?=Version date)/i,"$1");bi.innerHTML="Board / Environment = "+h;}}
if(window.loadReceivedPackets){var _o=window.loadReceivedPackets;window.loadReceivedPackets=function(p){if(p&&p.slice){p=p.slice().reverse();}return _o(p);};}
+82
View File
@@ -566,6 +566,7 @@ function checkConnection() {
}, 3000);
fetchSettings();
fetchStationStatus();
})
.catch((err) => {
setTimeout(checkConnection, 0);
@@ -635,12 +636,93 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none');
document.getElementById('station-status').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchReceivedPackets();
})
function loadStationStatus(stationStatus) {
if (stationStatus) {
document.getElementById("stationStatus.voltageInternal").value = stationStatus.stationStatus.voltageInternal;
document.getElementById("stationStatus.voltageExternal").value = stationStatus.stationStatus.voltageExternal;
document.getElementById("stationStatus.voltage").style.display = "block";
document.getElementById("stationStatus.elapsedTime").value = stationStatus.stationStatus.elapsedTime;
document.getElementById("stationStatus.uptime").style.display = "block";
document.getElementById("stationStatus.statusWifi").value = stationStatus.stationStatus.statusWifi;
document.getElementById("stationStatus.statusIS").value = stationStatus.stationStatus.statusIS;
document.getElementById("stationStatus.statusNTP").value = stationStatus.stationStatus.statusNTP;
document.getElementById("stationStatus.statusSyslog").value = stationStatus.stationStatus.statusSyslog;
document.getElementById("stationStatus.statusMQTT").value = stationStatus.stationStatus.statusMQTT;
document.getElementById("stationStatus.connectivity").style.display = "block";
if (stationStatus.stationStatus.webAdminActive) {
document.getElementById("stationStatus.loginsInvalid").value = stationStatus.stationStatus.loginsInvalid;
document.getElementById("stationStatus.login").style.display = "block";
} else {
document.getElementById("stationStatus.login").style.display = "none";
}
document.getElementById("stationStatus.packetsRFSendSuccess").value = stationStatus.stationStatus.packetsRFSendSuccess;
document.getElementById("stationStatus.packetsRFReceiveSuccess").value = stationStatus.stationStatus.packetsRFReceiveSuccess;
document.getElementById("stationStatus.packetsISSendSuccess").value = stationStatus.stationStatus.packetsISSendSuccess;
document.getElementById("stationStatus.packetsISReceiveSuccess").value = stationStatus.stationStatus.packetsISReceiveSuccess;
document.getElementById("stationStatus.packetsRFSendFail").value = stationStatus.stationStatus.packetsRFSendFail;
document.getElementById("stationStatus.packetsRFReceiveFail").value = stationStatus.stationStatus.packetsRFReceiveFail;
document.getElementById("stationStatus.packetsISSendFail").value = stationStatus.stationStatus.packetsISSendFail;
document.getElementById("stationStatus.packetsISReceiveFail").value = stationStatus.stationStatus.packetsISReceiveFail;
document.getElementById("stationStatus.packetsBlacklisted").value = stationStatus.stationStatus.packetsBlacklisted;
document.getElementById("stationStatus.traffic").style.display = "block";
if (stationStatus.stationStatus.weatherModulePresent) {
document.getElementById("stationStatus.weatherTemperature").value = stationStatus.stationStatus.weatherTemperature;
document.getElementById("stationStatus.weatherHumidity").value = stationStatus.stationStatus.weatherHumidity;
document.getElementById("stationStatus.weatherBarometricPressure").value = stationStatus.stationStatus.weatherBarometricPressure;
document.getElementById("stationStatus.weatherGas").value = stationStatus.stationStatus.weatherGas;
document.getElementById("stationStatus.weather").style.display = "block";
} else {
document.getElementById("stationStatus.weather").style.display = "none";
}
} else {
document.getElementById("stationStatus.voltage").style.display = "none";
document.getElementById("stationStatus.weather").style.display = "none";
document.getElementById("stationStatus.uptime").style.display = "none";
document.getElementById("stationStatus.connectivity").style.display = "none";
document.getElementById("stationStatus.traffic").style.display = "none";
document.getElementById("stationStatus.login").style.display = "none";
}
setTimeout(fetchStationStatus, 15000);
}
fetchStationStatus();
function fetchStationStatus() {
fetch("/station-status.json")
.then((response) => response.json())
.then((deviceTelemetry) => {
loadStationStatus(deviceTelemetry);
})
.catch((err) => {
console.error(err);
console.error(`Failed to load station status`);
});
}
document.querySelector('a[href="/station-status"]').addEventListener('click', function (e) {
e.preventDefault();
document.getElementById('station-status').classList.remove('d-none');
document.getElementById('received-packets').classList.add('d-none');
document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchDeviceTelemetry();
})
/* ---------- Stations Map (Leaflet, CDN) ---------- */
+14
View File
@@ -40,6 +40,20 @@ namespace APRS_IS_Utils {
void firstConnection();
String getAPRSISState();
String getWifiState();
void incrementPacketSendSuccess();
unsigned int getPacketSendSuccess();
void incrementPacketSendFail();
unsigned int getPacketSendFail();
void incrementPacketReceiveSuccess();
unsigned int getPacketReceiveSuccess();
void incrementPacketReceiveFail();
unsigned int getPacketReceiveFail();
bool isBeyondWindow(unsigned long time);
void resetPacketCounters(unsigned long time);
}
#endif
+13
View File
@@ -33,6 +33,19 @@ namespace LoRa_Utils {
void wakeRadio();
void sleepRadio();
void incrementPacketSendSuccess();
unsigned int getPacketSendSuccess();
void incrementPacketSendFail();
unsigned int getPacketSendFail();
void incrementPacketReceiveSuccess();
unsigned int getPacketReceiveSuccess();
void incrementPacketReceiveFail();
unsigned int getPacketReceiveFail();
void incrementPacketBlacklisted();
unsigned int getPacketBlacklisted();
bool isBeyondWindow(unsigned long time);
void resetPacketCounters(unsigned long time);
}
#endif
+1
View File
@@ -28,6 +28,7 @@ namespace MQTT_Utils {
void connect();
void loop();
void setup();
String getMQTTStatus();
}
+2
View File
@@ -27,6 +27,8 @@ namespace NTP_Utils {
bool setup();
void update();
String getFormatedTime();
unsigned long getTimeMillis();
String getNTPStatus();
}
+2
View File
@@ -41,6 +41,8 @@ namespace STATION_Utils {
void processOutputPacketBufferUltraEcoMode();
void processOutputPacketBuffer();
void addToOutputPacketBuffer(const String& packet, bool flag = false);
void markBootTime();
unsigned long getBootTime();
}
+1
View File
@@ -26,6 +26,7 @@ namespace SYSLOG_Utils {
void log(const uint8_t type ,const String& packet, const int rssi, const float snr, const int freqError);
void setup();
String getSyslogStatus();
}
+32
View File
@@ -0,0 +1,32 @@
/* 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 TIME_UTILS_H_
#define TIME_UTILS_H_
#include <Arduino.h>
namespace TIME_Utils {
String getFormattedTime(unsigned long timeMillis);
String getFormattedDateTime(unsigned long timeMillis);
String generateDuration(unsigned long startMillis, unsigned long endMillis);
boolean isValid(unsigned long timeMillis);
}
#endif
+2
View File
@@ -38,6 +38,8 @@ namespace WEB_Utils {
void setup();
unsigned long getLoginFailCount();
}
#endif
+5 -1
View File
@@ -36,7 +36,11 @@ namespace WX_Utils {
String generateHumString(const float sensorHum);
String generatePresString(const float sensorPres);
String readDataSensor();
float getHumidity();
float getTemperature();
float getBarometricPressure();
float getGas();
boolean isWeatherModulePresent();
}
#endif
+3 -1
View File
@@ -63,12 +63,13 @@ ___________________________________________________________________*/
#include "wx_utils.h"
#include "display.h"
#include "utils.h"
#include "telemetry_utils.h"
#ifdef HAS_A7670
#include "A7670_utils.h"
#endif
String versionDate = "2026-09-07";
String versionDate = "2026-09-08";
String versionNumber = "4.0.1";
Configuration Config;
WiFiClient aprsIsClient;
@@ -128,6 +129,7 @@ void setup() {
Utils::checkRebootMode();
APRS_IS_Utils::firstConnection();
SLEEP_Utils::checkSerial();
STATION_Utils::markBootTime(); // should be last
}
void loop() {
+113 -14
View File
@@ -50,7 +50,6 @@ extern String versionNumber;
bool passcodeValid = false;
uint32_t lastServerCheck = 0;
#ifdef HAS_A7670
extern bool stationBeacon;
#endif
@@ -58,7 +57,14 @@ uint32_t lastServerCheck = 0;
namespace APRS_IS_Utils {
unsigned int packetSendSuccess = 0;
unsigned int packetSendFail = 0;
unsigned int packetReceiveSuccess = 0;
unsigned int packetReceiveFail = 0;
unsigned long packetCountStartTime = 0;
void upload(const String& line) {
incrementPacketSendSuccess();
aprsIsClient.print(line + "\r\n");
}
@@ -93,7 +99,30 @@ namespace APRS_IS_Utils {
}
void checkStatus() {
String wifiState, aprsisState;
String wifiState = getWifiState();
String aprsisState = getAPRSISState();
if (!networkManager->isWiFiConnected()) {
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
lastScreenOn = millis();
}
if (Config.aprs_is.active) {
if(aprsisState == "--" && !Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
lastScreenOn = millis();
}
}
secondLine = "WiFi: ";
secondLine += wifiState;
secondLine += " APRS-IS: ";
secondLine += aprsisState;
}
String getWifiState() {
String wifiState;
if (networkManager->isWiFiConnected()) {
wifiState = "OK";
} else {
@@ -102,12 +131,14 @@ namespace APRS_IS_Utils {
} else {
wifiState = "AP";
}
if (!Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
}
lastScreenOn = millis();
}
return wifiState;
}
String getAPRSISState() {
String aprsisState;
if (!Config.aprs_is.active) {
aprsisState = "OFF";
} else {
@@ -124,15 +155,8 @@ namespace APRS_IS_Utils {
aprsisState = "--";
}
#endif
if(aprsisState == "--" && !Config.display.alwaysOn && Config.display.timeout != 0) {
displayToggle(true);
lastScreenOn = millis();
}
}
secondLine = "WiFi: ";
secondLine += wifiState;
secondLine += " APRS-IS: ";
secondLine += aprsisState;
return aprsisState;
}
String checkForStartingBytes(const String& packet) {
@@ -225,6 +249,7 @@ namespace APRS_IS_Utils {
lastScreenOn = millis();
#ifdef HAS_A7670
stationBeacon = true;
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(aprsPacket);
stationBeacon = false;
#else
@@ -299,6 +324,7 @@ namespace APRS_IS_Utils {
ackPacket += ackMessage;
#ifdef HAS_A7670
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(ackPacket);
#else
upload(ackPacket);
@@ -322,6 +348,7 @@ namespace APRS_IS_Utils {
if (packet.startsWith("#")) {
if (Config.digi.backupDigiMode) lastServerCheck = currentTime;
} else {
incrementPacketReceiveSuccess();
int doubleColonIndex = packet.indexOf("::");
if (Config.aprs_is.messagesToRF && doubleColonIndex > 0) {
String Sender = packet.substring(0, packet.indexOf(">"));
@@ -347,6 +374,7 @@ namespace APRS_IS_Utils {
}
lastScreenOn = currentTime;
#ifdef HAS_A7670
incrementPacketReceiveSuccess();
A7670_Utils::uploadToAPRSIS(queryAnswer);
#else
upload(queryAnswer);
@@ -416,4 +444,75 @@ namespace APRS_IS_Utils {
}
}
bool isBeyondWindow(unsigned long time) {
unsigned long window = 24*60*60*1000;
if (time > packetCountStartTime + window) {
return true;
}
return false;
}
void resetPacketCounters(unsigned long time) {
packetCountStartTime = time;
packetSendSuccess = 0;
packetSendFail = 0;
packetReceiveSuccess = 0;
packetReceiveFail = 0;
}
void incrementPacketSendSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
resetPacketCounters(now);
packetSendSuccess = 1;
} else {
packetSendSuccess++;
}
}
unsigned int getPacketSendSuccess() {
return packetSendSuccess;
}
void incrementPacketSendFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetSendFail = 1;
} else {
packetSendFail++;
}
}
unsigned int getPacketSendFail() {
return packetSendFail;
}
void incrementPacketReceiveSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveSuccess = 1;
} else {
packetReceiveSuccess++;
}
}
unsigned int getPacketReceiveSuccess() {
return packetReceiveSuccess;
}
void incrementPacketReceiveFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveFail = 1;
} else {
packetReceiveFail++;
}
}
unsigned int getPacketReceiveFail() {
return packetReceiveFail;
}
}
+105 -4
View File
@@ -20,6 +20,7 @@
#include <RadioLib.h>
#include "configuration.h"
#include "network_manager.h"
#include "lora_utils.h"
#include "aprs_is_utils.h"
#include "station_utils.h"
#include "board_pinout.h"
@@ -66,9 +67,15 @@ int backoffMax = 4; // Max Backoff value (number of CAD slots to wai
int rssi, freqError;
float snr;
namespace LoRa_Utils {
unsigned int packetSendSuccess = 0;
unsigned int packetSendFail = 0;
unsigned int packetReceiveSuccess = 0;
unsigned int packetReceiveFail = 0;
unsigned int packetBlacklisted = 0;
unsigned long packetCountStartTime = 0;
static String sanitizeForWeb(const String& s) { // replaces non-printable ASCII with '.' for WebUI display
String out = s;
for (int i = 0; i < (int)out.length(); i++) {
@@ -234,9 +241,11 @@ namespace LoRa_Utils {
}
Utils::print("---> LoRa Packet Tx : ");
Utils::println(newPacket);
incrementPacketSendSuccess();
} else {
Utils::print(F("failed, code "));
Utils::println(String(state));
incrementPacketSendFail();
}
#ifdef INTERNAL_LED_PIN
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, LOW); // disabled in Ultra Eco Mode
@@ -272,7 +281,8 @@ namespace LoRa_Utils {
if (packet != "") {
String sender = packet.substring(3, packet.indexOf(">"));
if (packet.substring(0,3) == "\x3c\xff\x01" && !STATION_Utils::isBlacklisted(sender)) { // avoid processing BlackListed stations
bool isBlacklisted = STATION_Utils::isBlacklisted(sender);
if (packet.substring(0,3) == "\x3c\xff\x01" && !isBlacklisted) { // avoid processing BlackListed stations
rssi = radio.getRSSI();
snr = radio.getSNR();
freqError = radio.getFrequencyError();
@@ -291,8 +301,9 @@ namespace LoRa_Utils {
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 (only ones with position)
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.path, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.rssi, aprsPacket.snr);
incrementPacketReceiveSuccess();
if (aprsPacket.type == 0 || aprsPacket.type == 4) { // 0 = GPS, 4 = Mic-E (los que traen posición)
MAP_Utils::upsert(aprsPacket.sender, aprsPacket.latitude, aprsPacket.longitude, aprsPacket.overlay + aprsPacket.symbol, aprsPacket.path, aprsPacket.rssi, aprsPacket.snr);
}
}
@@ -300,6 +311,9 @@ namespace LoRa_Utils {
SYSLOG_Utils::log(1, packet, rssi, snr, freqError); // RX
}
} else {
if (isBlacklisted) {
incrementPacketBlacklisted();
}
packet = "";
}
return packet;
@@ -313,10 +327,12 @@ namespace LoRa_Utils {
SYSLOG_Utils::log(0, packet, rssi, snr, freqError); // CRC
}
packet = "";
incrementPacketReceiveFail();
} else {
Utils::print(F("failed, code "));
Utils::println(String(state));
packet = "";
incrementPacketReceiveFail();
}
}
}
@@ -331,4 +347,89 @@ namespace LoRa_Utils {
radio.sleep();
}
bool isBeyondWindow(unsigned long time) {
unsigned long window = 24*60*60*1000;
if (time > packetCountStartTime + window) {
return true;
}
return false;
}
void resetPacketCounters(unsigned long time) {
packetCountStartTime = time;
packetSendSuccess = 0;
packetSendFail = 0;
packetReceiveSuccess = 0;
packetReceiveFail = 0;
}
void incrementPacketSendSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
resetPacketCounters(now);
packetSendSuccess = 1;
} else {
packetSendSuccess++;
}
}
unsigned int getPacketSendSuccess() {
return packetSendSuccess;
}
void incrementPacketSendFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetSendFail = 1;
} else {
packetSendFail++;
}
}
unsigned int getPacketSendFail() {
return packetSendFail;
}
void incrementPacketReceiveSuccess() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveSuccess = 1;
} else {
packetReceiveSuccess++;
}
}
unsigned int getPacketReceiveSuccess() {
return packetReceiveSuccess;
}
void incrementPacketReceiveFail() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetReceiveFail = 1;
} else {
packetReceiveFail++;
}
}
unsigned int getPacketReceiveFail() {
return packetReceiveFail;
}
void incrementPacketBlacklisted() {
unsigned int now = millis();
if (isBeyondWindow(now)) {
packetCountStartTime = now;
packetBlacklisted = 1;
} else {
packetBlacklisted++;
}
}
unsigned int getPacketBlacklisted() {
return packetBlacklisted;
}
}
+9
View File
@@ -98,4 +98,13 @@ namespace MQTT_Utils {
pubSub.setCallback(receivedFromMqtt);
}
String getMQTTStatus() {
if (!Config.mqtt.active) {
return "Inactive";
}
if (pubSub.connected()) {
return "Connected";
}
return "Not connected";
}
}
+19
View File
@@ -63,4 +63,23 @@ namespace NTP_Utils {
return "DigiEcoMode Active";
}
unsigned long getTimeMillis() {
if (networkManager->isConnected() && Config.digi.ecoMode == 0 && timeClient != nullptr) {
return timeClient->getEpochTime()*1000;
}
return millis();
}
String getNTPStatus() {
if ((networkManager == nullptr) || (timeClient == nullptr)) {
return "Not connected";
}
if (networkManager->isConnected() && Config.digi.ecoMode != 0) {
return "DigiEcoMode Active";
}
if (timeClient->isTimeSet()) {
return "OK";
}
return "Error";
}
}
+9
View File
@@ -50,6 +50,7 @@ std::vector<Packet25SegBuffer> packet25SegBuffer;
bool saveNewDigiEcoModeConfig = false;
bool packetIsBeacon = false;
uint32_t bootTimeMillis = 0;
namespace STATION_Utils {
@@ -237,4 +238,12 @@ namespace STATION_Utils {
outputPacketBuffer.emplace_back(OutputPacketBuffer{packet, flag});
}
void markBootTime() {
bootTimeMillis = millis();
}
unsigned long getBootTime() {
return bootTimeMillis;
}
}
+9
View File
@@ -151,4 +151,13 @@ namespace SYSLOG_Utils {
}
}
String getSyslogStatus() {
if (Config.syslog.active) {
if (networkManager->isConnected()) {
return "Active";
}
return "Not connected";
}
return "Inactive";
}
}
+92
View File
@@ -0,0 +1,92 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include "time_utils.h"
#include <iostream>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
namespace TIME_Utils {
String getFormattedTime(unsigned long timeMillis) {
// Split into total seconds and remaining milliseconds
std::time_t seconds = timeMillis / 1000;
// Convert seconds to UTC calendar structure safely
std::tm time_info;
gmtime_r(&seconds, &time_info); // POSIX safe variant
// Format the base date/time structure
char buffer[30];
std::strftime(buffer, sizeof(buffer), "%H:%M:%S", &time_info);
// Append fractional milliseconds and UTC anchor 'Z'
std::ostringstream oss;
oss << buffer;
return String(oss.str().c_str());
}
String getFormattedDateTime(unsigned long timeMillis) {
if (!isValid(timeMillis)) {
return "Not set";
}
// Split into total seconds and remaining milliseconds
std::time_t seconds = timeMillis / 1000;
// Convert seconds to UTC calendar structure safely
std::tm time_info;
gmtime_r(&seconds, &time_info); // POSIX safe variant
// Format the base date/time structure
char buffer[30];
std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%S", &time_info);
// Append fractional milliseconds and UTC anchor 'Z'
std::ostringstream oss;
oss << buffer;
return String(String(oss.str().c_str())+" "+String(timeMillis));
}
boolean isValid(unsigned long timeMillis) {
if (timeMillis < 1767225600) { // 2026-01-01T00:00:00
return false;
}
return true;
}
String generateDuration(unsigned long startMillis, unsigned long endMillis) {
unsigned long diff = endMillis - startMillis;
diff /= 1000; // convert to seconds
unsigned long seconds = diff - ((diff/60) * 60);
diff -= seconds;
diff /= 60; // convert to minutes
unsigned long minutes = diff - ((diff/60) * 60);
diff -= minutes;
diff /= 60; // convert to hours
unsigned long hours = diff - ((diff/24) * 24);
diff -= hours;
diff /= 24; // convert to days
unsigned long days = diff;
return String(days) + " d " + String(hours) + " h " + String(minutes) + " m " + String(seconds) + " s" ;
}
}
+85 -3
View File
@@ -23,6 +23,15 @@
#include "map_utils.h"
#include "display.h"
#include "utils.h"
#include "battery_utils.h"
#include "wx_utils.h"
#include "station_utils.h"
#include "ntp_utils.h"
#include "time_utils.h"
#include "aprs_is_utils.h"
#include "syslog_utils.h"
#include "mqtt_utils.h"
#include "lora_utils.h"
extern Configuration Config;
@@ -78,6 +87,7 @@ extern const size_t aprs_symbols_24_2_data_len = aprs_symbols_24_2_data_end - ap
namespace WEB_Utils {
AsyncWebServer server(80);
unsigned int loginFailCount = 0;
void handleNotFound(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(404, "text/plain", "Not found");
@@ -90,9 +100,10 @@ namespace WEB_Utils {
}
void handleHome(AsyncWebServerRequest *request) {
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str()))
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str())) {
loginFailCount++;
return request->requestAuthentication();
}
AsyncWebServerResponse *response = request->beginResponse(200, "text/html", (const uint8_t*)web_index_html, web_index_html_len);
response->addHeader("Content-Encoding", "gzip");
request->send(response);
@@ -141,8 +152,10 @@ namespace WEB_Utils {
}
void handleReadConfiguration(AsyncWebServerRequest *request) {
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str()))
if(Config.webadmin.active && !request->authenticate(Config.webadmin.username.c_str(), Config.webadmin.password.c_str())) {
loginFailCount++;
return request->requestAuthentication();
}
File file = SPIFFS.open("/igate_conf.json");
@@ -171,6 +184,74 @@ namespace WEB_Utils {
request->send(200, "application/json", buffer);
}
void handleStationStatus(AsyncWebServerRequest *request) {
JsonDocument data;
// voltage
char voltageInfo[10]; // Enough to hold "xx.xx\0"
snprintf(voltageInfo, sizeof(voltageInfo), "%.2f", BATTERY_Utils::checkInternalVoltage());
data["stationStatus"]["voltageInternal"] = voltageInfo;
snprintf(voltageInfo, sizeof(voltageInfo), "%.2f", BATTERY_Utils::checkExternalVoltage());
data["stationStatus"]["voltageExternal"] = voltageInfo;
// weather
boolean weatherModulePresent = WX_Utils::isWeatherModulePresent();
data["stationStatus"]["weatherModulePresent"] = weatherModulePresent;
if (weatherModulePresent) {
data["stationStatus"]["weatherTemperature"] = WX_Utils::getTemperature();
data["stationStatus"]["weatherHumidity"] = WX_Utils::getHumidity();
data["stationStatus"]["weatherBarometricPressure"] = WX_Utils::getBarometricPressure();
data["stationStatus"]["weatherGas"] = WX_Utils::getGas();
} else {
data["stationStatus"]["weatherTemperature"] = "Unknown";
data["stationStatus"]["weatherHumidity"] = "Unknown";
data["stationStatus"]["weatherBarometricPressure"] = "Unknown";
data["stationStatus"]["weatherGas"] = "Unknown";
}
// elapsed time
unsigned long now = millis();
unsigned long bootTime = STATION_Utils::getBootTime();
data["stationStatus"]["elapsedTime"] = TIME_Utils::generateDuration(bootTime, now);
// logins
data["stationStatus"]["loginsInvalid"] = getLoginFailCount();
data["stationStatus"]["webAdminActive"] = Config.webadmin.active;
// packets
data["stationStatus"]["packetsRFSendSuccess"] = LoRa_Utils::getPacketSendSuccess();
data["stationStatus"]["packetsRFReceiveSuccess"] = LoRa_Utils::getPacketReceiveSuccess();
data["stationStatus"]["packetsISSendSuccess"] = APRS_IS_Utils::getPacketSendSuccess();
data["stationStatus"]["packetsISReceiveSuccess"] = APRS_IS_Utils::getPacketReceiveSuccess();
data["stationStatus"]["packetsRFSendFail"] = LoRa_Utils::getPacketSendFail();
data["stationStatus"]["packetsRFReceiveFail"] = LoRa_Utils::getPacketReceiveFail();
data["stationStatus"]["packetsISSendFail"] = APRS_IS_Utils::getPacketSendFail();
data["stationStatus"]["packetsISReceiveFail"] = APRS_IS_Utils::getPacketReceiveFail();
data["stationStatus"]["packetsBlacklisted"] = LoRa_Utils::getPacketBlacklisted();
// connectivity / service status
data["stationStatus"]["statusWifi"] = APRS_IS_Utils::getWifiState();
data["stationStatus"]["statusIS"] = APRS_IS_Utils::getAPRSISState();
data["stationStatus"]["statusNTP"] = NTP_Utils::getNTPStatus();
data["stationStatus"]["statusSyslog"] = SYSLOG_Utils::getSyslogStatus();
data["stationStatus"]["statusMQTT"] = MQTT_Utils::getMQTTStatus();
// misc
data["stationStatus"]["currentTimeRaw"] = now;
data["stationStatus"]["bootTimeRaw"] = bootTime;
data["stationStatus"]["elapsedTimeRaw"] = now - bootTime;
String buffer;
serializeJson(data, buffer);
request->send(200, "application/json", buffer);
}
unsigned long getLoginFailCount() {
return loginFailCount;
}
void handleStations(AsyncWebServerRequest *request) {
request->send(200, "application/json", MAP_Utils::getStationsJson());
}
@@ -438,6 +519,7 @@ namespace WEB_Utils {
server.on("/", HTTP_GET, handleHome);
server.on("/status", HTTP_GET, handleStatus);
server.on("/received-packets.json", HTTP_GET, handleReceivedPackets);
server.on("/station-status.json", HTTP_GET, handleStationStatus);
server.on("/stations.json", HTTP_GET, handleStations);
server.on("/configuration.json", HTTP_GET, handleReadConfiguration);
server.on("/configuration.json", HTTP_POST, handleWriteConfiguration);
+17
View File
@@ -39,6 +39,7 @@ int wxModuleType = 0;
uint8_t wxModuleAddress = 0x00;
float newHum, newTemp, newPress, newGas;
bool weatherModulePresent = false;
Adafruit_BME280 bme280;
@@ -175,6 +176,7 @@ namespace WX_Utils {
break;
}
}
weatherModulePresent = wxModuleFound;
}
}
}
@@ -334,4 +336,19 @@ namespace WX_Utils {
}
}
float getHumidity() {
return newHum;
}
float getTemperature() {
return newTemp;
}
float getBarometricPressure() {
return newPress;
}
float getGas() {
return newGas;
}
boolean isWeatherModulePresent() {
return weatherModulePresent;
}
}