Browse Source

WiFi support for configuration

pull/16/head
Łukasz Nidecki 7 months ago
parent
commit
df6a468e21
  1. 5
      .gitignore
  2. 132
      data_embed/index.html
  3. 42
      data_embed/js.js
  4. 843
      data_embed/style.css
  5. 36
      include/taskWebServer.h
  6. 10
      platformio.ini
  7. 137
      src/TTGO_T-Beam_LoRa_APRS.ino
  8. 13
      src/TTGO_T-Beam_LoRa_APRS_config.h
  9. 192
      src/taskWebServer.cpp
  10. 39
      tools/buildscript_versioning.py
  11. 13
      tools/compress_assets.py

5
.gitignore

@ -8,4 +8,7 @@
CMakeLists.txt
CMakeListsPrivate.txt
CMakeListsUser.txt
cmake-build-*
cmake-build-*
/include/version.h
/data_embed/*.out
/src/TTGO_T-Beam_LoRa_APRS.ino.cpp

132
data_embed/index.html

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>TTGO-T-Beam-LoRa-APRS <!--VERSION--></title>
<link rel="stylesheet" href="/style.css" type="text/css">
<script src="/js.js" type="text/javascript"></script>
<link rel="icon" href="data:,">
</head>
<body>
<div class="container">
<section>
<div class="grid-container full">
<h2 class="u-full-width">WiFi Settings</h2>
</div>
<article>
<form action="/save_wifi_cfg" method="post">
<div class="grid-container quarters">
<div>
<div id="wifi_list">
</div>
<input type="button" value="Scan WiFi" id="scan_wifi_btn" onclick="scanWifi();">
</div>
<div>
<label for="wifi_ssid">SSID</label>
<input class="u-full-width" type="text" name="wifi_ssid" placeholder="Your Wifi SSID"
id="wifi_ssid">
</div>
<div>
<label for="wifi_password">Password</label>
<input class="u-full-width" type="password" name="wifi_password" id="wifi_password">
</div>
<div>
<input class="button-primary" type="submit" value="Save">
</div>
</div>
</form>
</article>
</section>
<section>
<div class="grid-container full">
<h2 class="u-full-width">APRS Settings</h2>
</div>
<article>
<form action="/save_aprs_cfg" method="post">
<div class="grid-container quarters">
<div>
<label for="aprs_callsign">Callsign and SSID</label>
<input class="u-full-width" type="text" minlength="3" name="aprs_callsign"
placeholder="NOCALL-1" id="aprs_callsign">
</div>
<div>
<label for="aprs_relay_path">Relay Path</label>
<input class="u-full-width" type="text" minlength="0" name="aprs_relay_path"
id="aprs_relay_path">
</div>
<div>
<label for="aprs_s_table">Symbol Table</label>
<input class="u-full-width" type="text" minlength="1" maxlength="1" name="aprs_s_table"
id="aprs_s_table">
</div>
<div>
<label for="aprs_symbol">Symbol</label>
<input class="u-full-width" type="text" minlength="1" maxlength="1" name="aprs_symbol"
id="aprs_symbol">
</div>
<div>
<label for="aprs_comment">Comment</label>
<input class="u-full-width" type="text" minlength="0" maxlength="64" name="aprs_comment"
id="aprs_comment">
</div>
<div>
<label for="aprs_batt">Show Battery</label>
<input name="aprs_batt" id="aprs_batt" type="checkbox" value="1">
</div>
<div>
<label for="aprs_alt">Show Altitude</label>
<input name="aprs_alt" id="aprs_alt" type="checkbox" value="1">
</div>
</div>
<div class="grid-container quarters">
<div>
<label for="aprs_fixed_beac">Fixed Beacon (when&nbsp;no&nbsp;GPS)</label>
<input name="aprs_alt" id="aprs_fixed_beac" type="checkbox" value="1">
</div>
<div>
<label for="aprs_fb_interv">Fixed Beacon Interval (s)</label>
<input name="aprs_fb_interv" id="aprs_fb_interv" type="number" min="120">
</div>
<div>
<label for="aprs_lat_p">Latitude Preset</label>
<input class="u-full-width" type="text" minlength="0" name="aprs_lat_p" id="aprs_lat_p">
</div>
<div>
<label for="aprs_lon_p">Longitude Preset</label>
<input class="u-full-width" type="text" minlength="0" name="aprs_lon_p" id="aprs_lon_p">
</div>
</div>
<div class="grid-container full">
<div>
<input class="button-primary u-full-width" type="submit" value="Save">
</div>
</div>
</form>
</article>
</section>
<section>
<div class="grid-container full">
<h2 class="u-full-width">Actions</h2>
</div>
<article>
<div class="grid-container quarters">
<form action="/reboot" method="post">
<div>
<input class="button-primary" type="submit" value="Reboot">
</div>
</form>
<form action="/restore" method="post">
<div>
<input class="button-primary" type="submit" value="Factory reset">
</div>
</form>
</div>
</article>
</section>
</div>
<footer>
<p><!--VERSION--></p>
</footer>
</body>
</html>

42
data_embed/js.js

@ -0,0 +1,42 @@
/**/
function scanWifi() {
let scanBtn = document.getElementById('scan_wifi_btn');
let wifiListContainer = document.getElementById("wifi_list");
wifiListContainer.innerHTML = 'Scanning...';
scanBtn.disabled = true;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
scanBtn.disabled = false;
if (this.readyState == 4 && this.status == 200) {
wifiListContainer.innerHTML = this.responseText;
const networks_found_list = document.querySelector('#networks_found_list');
networks_found_list.addEventListener('change', event => {
document.getElementById('wifi_ssid').value = networks_found_list.value;
});
}
};
xhttp.open("GET", "/scan_wifi", true);
xhttp.send();
}
window.onload = function () {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
const response = JSON.parse(this.responseText);
for (const [key, value] of Object.entries(response)) {
let element = document.getElementById(key);
if (element){
if (element.type && element.type == "checkbox"){
element.checked = value;
} else {
element.value = value;
}
}
}
}
};
xhttp.open("GET", "/cfg", true);
xhttp.send();
};

843
data_embed/style.css

@ -0,0 +1,843 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in iOS.
*/
html {
line-height: 1.15; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers.
*/
body {
margin: 0;
}
/**
* Render the `main` element consistently in IE.
*/
main {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* Remove the gray background on active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* 1. Remove the bottom border in Chrome 57-
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Remove the border on images inside links in IE 10.
*/
img {
border-style: none;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers.
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* Correct the inability to style clickable types in iOS and Safari.
*/
button,
[type="button"],
[type="reset"],
[type="submit"] {
-webkit-appearance: button;
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
vertical-align: baseline;
}
/**
* Remove the default vertical scrollbar in IE 10+.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10.
* 2. Remove the padding in IE 10.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in Edge, IE 10+, and Firefox.
*/
details {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Misc
========================================================================== */
/**
* Add the correct display in IE 10+.
*/
template {
display: none;
}
/**
* Add the correct display in IE 10.
*/
[hidden] {
display: none;
}
/*
* Barebones V3
* Copyright 2019 Steve Cochran
*
* Based of Skeleton by Dave Gamache
*
* Free to use under the MIT license.
*/
/* Table of contents
- Media Breakpoints
- Variables
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
*/
/* ENV Variables
*/
/* Media breakpoint variables for use in media queries
* Note: this section is currently commented out pending release of
* final CSS env() spec
* Breakpoints based on
* https://medium.freecodecamp.org/the-100-correct-way-to-do-css-breakpoints-88d6a5ba1862
*
*/
/* CSS Variables
*/
html {
/* default theme: light background, dark text, blue accent */
--theme-hue: 0; /* white */
--accent-hue: 194; /* blue */
--text-color-richer: hsl(var(--theme-hue), 0%, 5%); /* #0d0d0d */
--text-color-normal: hsl(var(--theme-hue), 0%, 13%); /* #222222 text color; button:hover:focus color */
--text-color-softer: hsl(var(--theme-hue), 0%, 33%); /* #555555 button color; button:hover border */
--accent-color: hsl(var(--accent-hue), 86%, 57%); /* #33C3F0 link; button-primary bg+border; textarea,select:focus border */
--accent-color-hover: hsl(var(--accent-hue), 76%, 49%); /* #1EAEDB link hover; button-primary:hover:focus bg+border */
--border-color: hsl(var(--theme-hue), 0%, 73%); /* #bbbbbb button border */
--border-color-softer: hsl(var(--theme-hue), 0%, 82%); /* #d1d1d1 textarea,select,code,td,hr border */
--background-color: white; /* transparent body background; textarea,select background */
--background-color-softer: hsl(var(--theme-hue), 0%, 95%);
--code-background: hsl(var(--theme-hue), 0%, 95%); /* #f1f1f1 code background*/
--button-primary-color: white;
/* Note: Skeleton was based off a 10px font sizing for REM */
/* 62.5% of typical 16px browser default = 10px */
--base-font-size: 62.5%;
/* Grid Defaults - default to match orig skeleton settings */
--grid-max-width: 960px;
}
/* Dark Theme
Note: prefers-color-scheme selector support is still limited, but
included for future and an example of defining a different base 'theme'
*/
@media (prefers-color-scheme: dark) {
:html {
/* dark theme: light background, dark text, blue accent */
--theme-hue: 0; /* black */
--accent-hue: 194; /* blue */
--text-color-richer: hsl(var(--theme-hue), 0%, 95%); /* */
--text-color-normal: hsl(var(--theme-hue), 0%, 80%); /* text color; button:hover:focus color */
--text-color-softer: hsl(var(--theme-hue), 0%, 67%); /* button color; button:hover border */
--accent-color: hsl(var(--accent-hue), 76%, 49%); /* link; button-primary bg+border; textarea,select:focus border */
--accent-color-hover: hsl(var(--accent-hue), 86%, 57%); /* link hover; button-primary:hover:focus bg+border */
--border-color: hsl(var(--theme-hue), 0%, 27%); /* button border */
--border-color-softer: hsl(var(--theme-hue), 0%, 20%); /* textarea,select,code,td,hr border */
--background-color: hsl(var(--theme-hue), 0%, 12%); /* body background; textarea,select background */
--background-color-softer: hsl(var(--theme-hue), 0%, 18%);
--code-background: hsl(var(--theme-hue), 0%, 5%); /* code background*/
--button-primary-color: white;
}
img.value-img {
filter: invert(0.8);
}
/* TODO - test dialing back image intensity on dark background
img {
opacity: .80;
transition: opacity .5s ease-in-out;
}
img:hover {
opacity: 1;
}
*/
}
/* Grid
*/
/* CSS Grid depends much more on CSS than HTML, so there is less boilerplate
than with skeleton. Only basic 1-4 column grids are included.
Any additional needs should be made using custom CSS directives */
.grid-container {
position: relative;
max-width: var(--grid-max-width);
margin: 0 auto;
padding: 20px;
text-align: center;
display: grid;
grid-gap: 20px;
gap: 20px;
/* by default use min 200px wide columns auto-fit into width */
grid-template-columns: minmax(200px, 1fr);
}
/* grids to 3 columns above mobile sizes */
@media (min-width: 600px) {
.grid-container {
grid-template-columns: repeat(3, 1fr);
padding: 10px 0;
}
/* basic grids */
.grid-container.fifths {
grid-template-columns: repeat(5, 1fr);
}
.grid-container.quarters {
grid-template-columns: repeat(4, 1fr);
}
.grid-container.thirds {
grid-template-columns: repeat(3, 1fr);
}
.grid-container.halves {
grid-template-columns: repeat(2, 1fr);
}
.grid-container.full {
grid-template-columns: 1fr;
}
}
/* Base Styles
*/
html {
font-size: var(--base-font-size);
scroll-behavior: smooth;
}
body {
font-size: 1.6rem; /* changed from 15px in orig skeleton */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: var(--text-color-normal);
background-color: var(--background-color);;
}
/* Typography
*/
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 600px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
*/
a {
color: var(--accent-color); }
a:hover {
color: var(--accent-color-hover); }
/* Buttons
*/
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: var(--text-color-softer);
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid var(--border-color);
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: var(--text-color-normal);
border-color: var(--text-color-softer);
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: var(--button-primary-color);
background-color: var(--accent-color);
border-color: var(--accent-color); }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: var(--button-primary-color);
background-color: var(--accent-color-hover);
border-color: var(--accent-color-hover); }
/* Forms
*/
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: var(--background-color);
border: 1px solid var(--border-color-softer);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
input[type="button"],
input[type="submit"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid var(--accent-color);
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
*/
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul, ul ol, ol ol, ol ul {
font-size: 100%;
margin: 1rem 0 1rem 3rem;
color: var(--text-color-softer);
}
li {
margin-bottom: 0.5rem; }
/* Code
*/
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: var(--code-background);
border: 1px solid var(--border-color-softer);
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre;
overflow: auto; }
/* Tables
*/
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid var(--border-color-softer); }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
*/
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
.u-align-left {
text-align: left; }
.u-align-right {
text-align: right; }
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid var(--border-color-softer); }
/* Clearing
*/
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
*/
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile (default point when grid becomes active) */
@media (min-width: 600px) {
}
/* Larger than phablet */
@media (min-width: 900px) {
.container {
max-width: 900px;
}
}
/* Larger than tablet */
@media (min-width: 1200px) {}
footer {
text-align: center
}
.container {
position: relative;
margin: 0 auto;
}
section {
border-radius: 4px;
border: 1px solid var(--border-color);
margin-top: 5px;
padding: 5px;
}

36
include/taskWebServer.h

@ -0,0 +1,36 @@
#include <Arduino.h>
#include "TTGO_T-Beam_LoRa_APRS_config.h"
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Preferences.h>
#define ENABLE_PREFERENCES
extern Preferences preferences;
// MAX 15 chars for preferenece key!!!
static const char *const PREF_APRS_CALLSIGN = "aprs_callsign";
static const char *const PREF_APRS_RELAY_PATH = "aprs_relay_path";
static const char *const PREF_APRS_RELAY_PATH_INIT = "aprs_relay_init";
static const char *const PREF_APRS_SYMBOL_TABLE = "aprs_s_table";
static const char *const PREF_APRS_SYMBOL = "aprs_symbol";
static const char *const PREF_APRS_COMMENT = "aprs_comment";
static const char *const PREF_APRS_COMMENT_INIT = "aprs_comm_init";
static const char *const PREF_APRS_SHOW_ALTITUDE = "aprs_alt";
static const char *const PREF_APRS_SHOW_ALTITUDE_INIT = "aprs_alt_init";
static const char *const PREF_APRS_SHOW_BATTERY = "aprs_batt";
static const char *const PREF_APRS_SHOW_BATTERY_INIT = "aprs_batt_init";
static const char *const PREF_APRS_LATITUDE_PRESET = "aprs_lat_p";
static const char *const PREF_APRS_LATITUDE_PRESET_INIT = "aprs_lat_p_init";
static const char *const PREF_APRS_LONGITUDE_PRESET = "aprs_lon_p";
static const char *const PREF_APRS_LONGITUDE_PRESET_INIT = "aprs_lon_p_init";
static const char *const PREF_APRS_FIXED_BEACON_PRESET = "aprs_fixed_beac";
static const char *const PREF_APRS_FIXED_BEACON_PRESET_INIT = "aprs_fix_b_init";
static const char *const PREF_APRS_FIXED_BEACON_INTERVAL_PRESET = "aprs_fb_interv";
static const char *const PREF_APRS_FIXED_BEACON_INTERVAL_PRESET_INIT = "aprs_fb_in_init";
typedef struct {
String callsign;
} tWebServerCfg;
[[noreturn]] void taskWebServer(void *parameter);

10
platformio.ini

@ -14,7 +14,15 @@ board = ttgo-t-beam
framework = arduino
monitor_speed = 115200
build_flags = -Wl,--gc-sections,--relax
lib_deps =
board_build.partitions = no_ota.csv
board_build.embed_files =
data_embed/index.html.out
data_embed/style.css.out
data_embed/js.js.out
extra_scripts =
pre:tools/buildscript_versioning.py
pre:tools/compress_assets.py
lib_deps =
RadioHead
TinyGPSPlus
#DHT sensor library for ESPx

137
src/TTGO_T-Beam_LoRa_APRS.ino

@ -25,7 +25,10 @@
#ifdef KISS_PROTOCOL
#include "taskTNC.h"
#endif
#ifdef ENABLE_WIFI
#include "taskWebServer.h"
#endif
#include "version.h"
// I2C LINES
#define I2C_SDA 21
@ -66,6 +69,16 @@ boolean gps_state = true;
boolean key_up = true;
boolean t_lock = false;
boolean fixed_beacon_enabled = false;
#ifdef SHOW_ALT
boolean showAltitude = true;
#else
boolean showAltitude = false;
#endif
#ifdef SHOW_BATT
boolean showBattery = true;
#else
boolean showBattery = false;
#endif
// Variables and Constants
String loraReceivedFrameString = ""; //data on buff is copied to this string
@ -115,6 +128,11 @@ float average_course[ANGLE_AVGS];
float avg_c_y, avg_c_x;
uint8_t txPower = TXdbmW;
#ifdef ENABLE_WIFI
tWebServerCfg webServerCfg;
#endif
static const adc_atten_t atten = ADC_ATTEN_DB_6;
static const adc_unit_t unit = ADC_UNIT_1;
@ -153,10 +171,9 @@ void prepareAPRSFrame(){
double Tspeed=0, Tcourse=0;
String Speedx, Coursex;
int i;
#ifdef SHOW_ALT
String Altx;
int Talt;
#endif
String Altx;
int Talt;
Tlat=gps.location.lat();
Tlon=gps.location.lng();
@ -183,31 +200,31 @@ void prepareAPRSFrame(){
outString += ">APLM0:!";
}
if(gps_state && gps.location.isValid()){
if(gps_state && gps.location.isValid()) {
outString += aprsSymbolTable;
ax25_base91enc(helper_base91, 4, aprs_lat);
for (i=0; i<4; i++) {
for (i = 0; i < 4; i++) {
outString += helper_base91[i];
}
ax25_base91enc(helper_base91, 4, aprs_lon);
for (i=0; i<4; i++) {
for (i = 0; i < 4; i++) {
outString += helper_base91[i];
}
outString += aprsSymbol;
ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse/4 );
ax25_base91enc(helper_base91, 1, (uint32_t) Tcourse / 4);
outString += helper_base91[0];
ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed)/0.07696));
ax25_base91enc(helper_base91, 1, (uint32_t) (log1p(Tspeed) / 0.07696));
outString += helper_base91[0];
outString += "H";
#ifdef SHOW_ALT
Talt=gps.altitude.meters() * 3.28d;
if (showAltitude) {
Talt = gps.altitude.meters() * 3.28d;
Altx = Talt;
outString += "/A=";
for (i = 0; i < (6-Altx.length()); ++i) {
for (i = 0; i < (6 - Altx.length()); ++i) {
outString += "0";
}
outString += Talt;
#endif
}
}else{
outString += aprsLonPreset;
outString += aprsSymbolTable;
@ -217,11 +234,11 @@ void prepareAPRSFrame(){
outString += aprsComment;
#ifdef SHOW_BATT // battery is not frame part move after comment
if (showBattery) {
outString += " Batt=";
outString += String(BattVolts, 2);
outString += ("V");
#endif
}
#ifdef KISS_PROTOCOL
sendToTNC(outString);
@ -373,8 +390,77 @@ void sendTelemetryFrame() {
// + SETUP --------------------------------------------------------------+//
void setup(){
#ifdef DIGI_PATH
relay_path = DIGI_PATH;
#else
relay_path = "";
#endif
#ifdef FIXED_BEACON_EN
fixed_beacon_enabled = true;
fixed_beacon_enabled = true;
#endif
#ifdef ENABLE_PREFERENCES
preferences.begin("cfg", false);
aprsSymbolTable = preferences.getString(PREF_APRS_SYMBOL_TABLE);
if (aprsSymbolTable.isEmpty()){
preferences.putString(PREF_APRS_SYMBOL_TABLE, APRS_SYMBOL_TABLE);
aprsSymbolTable = preferences.getString(PREF_APRS_SYMBOL_TABLE);
}
aprsSymbol = preferences.getString(PREF_APRS_SYMBOL);
if (aprsSymbol.isEmpty()){
preferences.putString(PREF_APRS_SYMBOL, APRS_SYMBOL);
aprsSymbol = preferences.getString(PREF_APRS_SYMBOL, APRS_SYMBOL);
}
if (!preferences.getBool(PREF_APRS_COMMENT_INIT)){
preferences.putBool(PREF_APRS_COMMENT_INIT, true);
preferences.putString(PREF_APRS_COMMENT, MY_COMMENT);
}
aprsComment = preferences.getString(PREF_APRS_COMMENT);
if (!preferences.getBool(PREF_APRS_RELAY_PATH_INIT)){
preferences.putBool(PREF_APRS_RELAY_PATH_INIT, true);
preferences.putString(PREF_APRS_RELAY_PATH, DIGI_PATH);
}
relay_path = preferences.getString(PREF_APRS_RELAY_PATH);
if (!preferences.getBool(PREF_APRS_SHOW_ALTITUDE_INIT)){
preferences.putBool(PREF_APRS_SHOW_ALTITUDE_INIT, true);
preferences.putBool(PREF_APRS_SHOW_ALTITUDE, showAltitude);
}
showAltitude = preferences.getBool(PREF_APRS_SHOW_ALTITUDE);
if (!preferences.getBool(PREF_APRS_SHOW_BATTERY_INIT)){
preferences.putBool(PREF_APRS_SHOW_BATTERY_INIT, true);
preferences.putBool(PREF_APRS_SHOW_BATTERY, showBattery);
}
showBattery = preferences.getBool(PREF_APRS_SHOW_BATTERY);
if (!preferences.getBool(PREF_APRS_LATITUDE_PRESET_INIT)){
preferences.putBool(PREF_APRS_LATITUDE_PRESET_INIT, true);
preferences.putString(PREF_APRS_LATITUDE_PRESET, LATIDUDE_PRESET);
}
aprsLatPreset = preferences.getString(LATIDUDE_PRESET);
if (!preferences.getBool(PREF_APRS_LONGITUDE_PRESET_INIT)){
preferences.putBool(PREF_APRS_LONGITUDE_PRESET_INIT, true);
preferences.putString(PREF_APRS_LONGITUDE_PRESET, LONGITUDE_PRESET);
}
aprsLonPreset = preferences.getString(PREF_APRS_LONGITUDE_PRESET);
if (!preferences.getBool(PREF_APRS_FIXED_BEACON_PRESET_INIT)){
preferences.putBool(PREF_APRS_FIXED_BEACON_PRESET_INIT, true);
preferences.putBool(PREF_APRS_FIXED_BEACON_PRESET, fixed_beacon_enabled);
}
fixed_beacon_enabled = preferences.getBool(PREF_APRS_FIXED_BEACON_PRESET);
if (!preferences.getBool(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET_INIT)){
preferences.putBool(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET_INIT, true);
preferences.putInt(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET, fix_beacon_interval/1000);
}
fix_beacon_interval = preferences.getInt(PREF_APRS_FIXED_BEACON_PRESET) * 1000;
#endif
for (int i=0;i<ANGLE_AVGS;i++) { // set average_course to "0"
@ -406,11 +492,14 @@ void setup(){
}
writedisplaytext("LoRa-APRS","","Init:","Display OK!","","",1000);
Tcall = prepareCallsign(String(CALLSIGN));
#ifdef DIGI_PATH
relay_path = DIGI_PATH;
#else
relay_path = "";
#ifdef ENABLE_PREFERENCES
Tcall = preferences.getString(PREF_APRS_CALLSIGN);
if (Tcall.isEmpty()){
preferences.putString(PREF_APRS_CALLSIGN, String(CALLSIGN));
Tcall = preferences.getString(PREF_APRS_CALLSIGN);
}
#endif
if (!rf95.init()) {
@ -446,6 +535,12 @@ void setup(){
SerialBT.begin(String("TTGO LORA APRS ") + Tcall);
writedisplaytext("LoRa-APRS","","Init:","BT OK!","","",250);
#endif
#ifdef ENABLE_WIFI
webServerCfg = {.callsign = Tcall};
xTaskCreate(taskWebServer, "taskWebServer", 50000, (void*)(&webServerCfg), 1, nullptr);
writedisplaytext("LoRa-APRS","","Init:","WiFi task started"," =:-) ","",250);
#endif
writedisplaytext("LoRa-APRS","","Init:","FINISHED OK!"," =:-) ","",250);
writedisplaytext("","","","","","",0);
time_to_refresh = millis() + showRXTime;

13
src/TTGO_T-Beam_LoRa_APRS_config.h

@ -7,18 +7,18 @@
// USER DATA - USE THESE LINES TO MODIFY YOUR PREFERENCES
#define KISS_PROTOCOL // If enabled send and receive data in SIMPLE KISS format to serial port
#define CALLSIGN "SQ9MDD-11" // enter your callsign here - less then 6 letter callsigns please add "spaces" so total length is 6 (without SSID)
#define CALLSIGN "NOCALL-0" // enter your callsign here - less then 6 letter callsigns please add "spaces" so total length is 6 (without SSID)
#define DIGI_PATH "WIDE1-1" // one hope please (WIDE1-1)
#define FIXED_BEACON_EN // allows cyclic sending of a bicon when GPS is turned off
#define LATIDUDE_PRESET "5215.03N" // please in APRS notation: DDMM.mmN or DDMM.mmS (used for manual or fixed beacon sending)
#define LONGITUDE_PRESET "02055.59E" // please in APRS notation: DDDMM.mmE or DDDMM.mmW (used for manual or fixed beacon sending)
#define LATIDUDE_PRESET "0000.00N" // please in APRS notation: DDMM.mmN or DDMM.mmS (used for manual or fixed beacon sending)
#define LONGITUDE_PRESET "00000.00E" // please in APRS notation: DDDMM.mmE or DDDMM.mmW (used for manual or fixed beacon sending)
#define APRS_SYMBOL_TABLE "/"
#define APRS_SYMBOL "[" // other symbols are: "[" => RUNNER, "b" => BICYCLE, "<" => MOTORCYCLE, "R" => Recreation Vehicle
#define MY_COMMENT "LoRa tracker" // add your coment here - if empty then no comment is sent
//#define SHOW_ALT // send Altitude in frame
#define MY_COMMENT "TTGO LoRa APRS Tracker" // add your coment here - if empty then no comment is sent
#define SHOW_ALT // send Altitude in frame
#define SHOW_BATT // send battery voltage at the end of comment (we need beggining for QSY message format)
#define SHOW_RX_PACKET // uncomment to show received LoRa APS packets for the time given below
#define SHOW_RX_TIME 10000 // show RX packet for milliseconds (5000 = 5secs)
#define SHOW_RX_TIME 2000 // show RX packet for milliseconds (5000 = 5secs)
#define TXFREQ 433.775 // Transmit frequency in MHz
#define TXdbmW 20 // Transmit power in dBm 17-50mW, 18-63mW, 19-80mW, 20-100mW
//#define SHOW_GPS_DATA // uncomment to show on serial port, received data from GPS and debug information
@ -28,6 +28,7 @@
//#define LOCAL_KISS_ECHO // echoing KISS frame back
#define T_BEAM_V1_0 // if enabled t-beam v1.0 disabled t-beam V.0.7
//#define KISS_DEBUG
#define ENABLE_WIFI
#define MAX_TIME_TO_NEXT_TX 360000L // TRANSMIT INTERVAL set here MAXIMUM time in ms(!) for smart beaconing - minimum time is always 1 min = 60 secs = 60000L !!!
#define FIX_BEACON_INTERVAL 1800000L // Fixed beacon interwal (when GPS is disabled and FIXED_BEACON_EN is enabled) 30min default

192
src/taskWebServer.cpp

@ -0,0 +1,192 @@
#include "taskWebServer.h"
/**
* @see board_build.embed_txtfiles in platformio.ini
*/
extern const char web_index_html[] asm("_binary_data_embed_index_html_out_start");
extern const char web_index_html_end[] asm("_binary_data_embed_index_html_out_end");
extern const char web_style_css[] asm("_binary_data_embed_style_css_out_start");
extern const char web_style_css_end[] asm("_binary_data_embed_style_css_out_end");
extern const char web_js_js[] asm("_binary_data_embed_js_js_out_start");
extern const char web_js_js_end[] asm("_binary_data_embed_js_js_out_end");
String apSSID = "";
String apPassword = "xxxxxxxxxx";
WebServer server(80);
Preferences preferences;
void sendCacheHeader() { server.sendHeader("Cache-Control", "max-age=3600"); }
void sendGzipHeader() { server.sendHeader("Content-Encoding", "gzip"); }
String jsonEscape(String s){
s.replace("\"", "\\\"");
s.replace("\\", "\\\\");
return s;
}
String jsonLineFromPreferenceString(const char *preferenceName, bool last=false){
return String("\"") + preferenceName + "\":\"" + jsonEscape(preferences.getString(preferenceName)) + (last ? + R"(")" : + R"(",)");
}
String jsonLineFromPreferenceBool(const char *preferenceName, bool last=false){
return String("\"") + preferenceName + "\":" + (preferences.getBool(preferenceName) ? "true" : "false") + (last ? + R"()" : + R"(,)");
}
String jsonLineFromPreferenceInt(const char *preferenceName, bool last=false){
return String("\"") + preferenceName + "\":" + (preferences.getInt(preferenceName)) + (last ? + R"()" : + R"(,)");
}
void handle_NotFound(){
sendCacheHeader();
server.send(404, "text/plain", "Not found");
}
void handle_Index() {
sendGzipHeader();
server.send_P(200, "text/html", web_index_html, web_index_html_end - web_index_html);
}
void handle_Style() {
sendCacheHeader();
sendGzipHeader();
server.send_P(200, "text/css", web_style_css, web_style_css_end - web_style_css);
}
void handle_Js() {
sendCacheHeader();
sendGzipHeader();
server.send_P(200, "text/javascript", web_js_js, web_js_js_end-web_js_js);
}
void handle_ScanWifi() {
String listResponse = R"(<label for="networks_found_list">Networks found:</label><select class="u-full-width" id="networks_found_list">)";
int n = WiFi.scanNetworks();
listResponse += "<option value=\"\">Select Network</option>";
for (int i = 0; i < n; ++i) {
listResponse += "<option value=\""+WiFi.SSID(i)+"\">" + WiFi.SSID(i) + "</option>";
}
listResponse += "</select>";
server.send(200,"text/html", listResponse);
}
void handle_SaveWifiCfg() {
if (!server.hasArg("wifi_ssid") || !server.hasArg("wifi_password")){
server.send(500, "text/plain", "Invalid request");
}
if (!server.arg("wifi_ssid").length() || !server.arg("wifi_password").length()){
server.send(403, "text/plain", "Empty SSID or Password");
}
preferences.putString("wifi_ssid", server.arg("wifi_ssid"));
preferences.putString("wifi_password", server.arg("wifi_password"));
server.sendHeader("Location", "/");
server.send(302,"text/html", "");
}
void handle_Reboot() {
server.sendHeader("Location", "/");
server.send(302,"text/html", "");
ESP.restart();
}
void handle_Restore() {
server.sendHeader("Location", "/");
server.send(302,"text/html", "");
preferences.clear();
preferences.end();
ESP.restart();
}
void handle_Cfg() {
String jsonData = "{";
jsonData += R"("wifi_ssid":")" + jsonEscape(preferences.getString("wifi_ssid")) + R"(",)";
jsonData += R"("wifi_password":")" + jsonEscape((preferences.getString("wifi_password").isEmpty() ? String("") : "*")) + R"(",)";
jsonData += jsonLineFromPreferenceString(PREF_APRS_CALLSIGN);
jsonData += jsonLineFromPreferenceString(PREF_APRS_RELAY_PATH);
jsonData += jsonLineFromPreferenceString(PREF_APRS_SYMBOL_TABLE);
jsonData += jsonLineFromPreferenceString(PREF_APRS_SYMBOL);
jsonData += jsonLineFromPreferenceString(PREF_APRS_COMMENT);
jsonData += jsonLineFromPreferenceString(PREF_APRS_LATITUDE_PRESET);
jsonData += jsonLineFromPreferenceString(PREF_APRS_LONGITUDE_PRESET);
jsonData += jsonLineFromPreferenceInt(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET);
jsonData += jsonLineFromPreferenceBool(PREF_APRS_SHOW_BATTERY);
jsonData += jsonLineFromPreferenceBool(PREF_APRS_FIXED_BEACON_PRESET);
jsonData += jsonLineFromPreferenceBool(PREF_APRS_SHOW_ALTITUDE, true);
jsonData += "}";
server.send(200,"application/json", jsonData);
}
void handle_SaveAPRSCfg() {
if (server.hasArg(PREF_APRS_CALLSIGN) && !server.arg(PREF_APRS_CALLSIGN).isEmpty()){
preferences.putString(PREF_APRS_CALLSIGN, server.arg(PREF_APRS_CALLSIGN));
}
if (server.hasArg(PREF_APRS_SYMBOL_TABLE) && !server.arg(PREF_APRS_SYMBOL_TABLE).isEmpty()){
preferences.putString(PREF_APRS_SYMBOL_TABLE, server.arg(PREF_APRS_SYMBOL_TABLE));
}
if (server.hasArg(PREF_APRS_SYMBOL) && !server.arg(PREF_APRS_SYMBOL).isEmpty()){
preferences.putString(PREF_APRS_SYMBOL, server.arg(PREF_APRS_SYMBOL));
}
if (server.hasArg(PREF_APRS_RELAY_PATH)){
preferences.putString(PREF_APRS_RELAY_PATH, server.arg(PREF_APRS_RELAY_PATH));
}
if (server.hasArg(PREF_APRS_COMMENT)){
preferences.putString(PREF_APRS_COMMENT, server.arg(PREF_APRS_COMMENT));
}
if (server.hasArg(PREF_APRS_LATITUDE_PRESET)){
preferences.putString(PREF_APRS_LATITUDE_PRESET, server.arg(PREF_APRS_LATITUDE_PRESET));
}
if (server.hasArg(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET)){
preferences.putInt(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET, server.arg(PREF_APRS_FIXED_BEACON_INTERVAL_PRESET).toInt()<