mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 05:06:39 +01:00
Merge branch 'master' of https://github.com/jjwbruijn/solum-esl-alternative-proto
This commit is contained in:
54
esp32_fw/.vscode/settings.json
vendored
54
esp32_fw/.vscode/settings.json
vendored
@@ -1,3 +1,55 @@
|
||||
{
|
||||
"C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0}"
|
||||
"C_Cpp.clang_format_fallbackStyle": "{ BasedOnStyle: Google, IndentWidth: 4, ColumnLimit: 0}",
|
||||
"files.associations": {
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"*.tcc": "cpp",
|
||||
"bitset": "cpp",
|
||||
"cctype": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"cstdarg": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"cwctype": "cpp",
|
||||
"deque": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"vector": "cpp",
|
||||
"exception": "cpp",
|
||||
"algorithm": "cpp",
|
||||
"functional": "cpp",
|
||||
"iterator": "cpp",
|
||||
"map": "cpp",
|
||||
"memory": "cpp",
|
||||
"memory_resource": "cpp",
|
||||
"numeric": "cpp",
|
||||
"optional": "cpp",
|
||||
"random": "cpp",
|
||||
"regex": "cpp",
|
||||
"string": "cpp",
|
||||
"string_view": "cpp",
|
||||
"system_error": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"utility": "cpp",
|
||||
"fstream": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"istream": "cpp",
|
||||
"limits": "cpp",
|
||||
"new": "cpp",
|
||||
"ostream": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"cinttypes": "cpp",
|
||||
"typeinfo": "cpp"
|
||||
}
|
||||
}
|
||||
BIN
esp32_fw/data/bmp24bpp-h.bmp
Normal file
BIN
esp32_fw/data/bmp24bpp-h.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
esp32_fw/data/bmp24bpp-v.bmp
Normal file
BIN
esp32_fw/data/bmp24bpp-v.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
BIN
esp32_fw/data/calibrib50.vlw
Normal file
BIN
esp32_fw/data/calibrib50.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/calibrib62.vlw
Normal file
BIN
esp32_fw/data/calibrib62.vlw
Normal file
Binary file not shown.
@@ -1,66 +1,103 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0" />
|
||||
|
||||
<title>Solum - alternative proto AP</title>
|
||||
<link rel="stylesheet" href="main.css" type="text/css"/>
|
||||
<title>Solum - alternative proto AP</title>
|
||||
<link rel="stylesheet" href="main.css" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<div class="logo">Solum - alternative proto AP</div>
|
||||
</header>
|
||||
<header>
|
||||
<div class="logo">Solum - alternative proto AP</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div id="configbox">
|
||||
<div class="closebtn">✖</div>
|
||||
<h3 id="cfgmac">00000000</h3>
|
||||
<p>
|
||||
<label for="cfgalias">Alias</label>
|
||||
<input id="cfgalias" type="text">
|
||||
</p>
|
||||
<p>
|
||||
<label for="cfgmodel">Model</label>
|
||||
<select id="cfgmodel">
|
||||
<option value="0">unknown</option>
|
||||
<option value="1">1.54" 152x152px</option>
|
||||
<option value="2">2.9" 296x128px</option>
|
||||
<option value="3">4.2" 400x300px</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<label for="cfgcontent">Content</label>
|
||||
<select id="cfgcontent" onchange="contentselected()">
|
||||
<option value="0">static image</option>
|
||||
<option value="1">current date</option>
|
||||
<option value="2">count days</option>
|
||||
<option value="3">count hours</option>
|
||||
<option value="4" disabled>current weather</option>
|
||||
<option value="6" disabled>memo text</option>
|
||||
<option value="7">image url</option>
|
||||
<option value="5">firmware update</option>
|
||||
</select>
|
||||
</p>
|
||||
<div id="customoptions"></div>
|
||||
<p>
|
||||
<input type="button" value="Save" id="cfgsave">
|
||||
<span id="cfgdelete"><img src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
|
||||
"></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="blocks"><ul></ul></div>
|
||||
<form>
|
||||
<div class="container">
|
||||
|
||||
<div class="window">
|
||||
|
||||
<div class="actionbox">
|
||||
<div>
|
||||
Currently active tags:<br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="taglist" class="taglist">
|
||||
<div class="tagcard" id="tagtemplate">
|
||||
<div class="currimg"><img class="tagimg" src=""></div>
|
||||
<div class="mac"></div>
|
||||
<div class="alias"></div>
|
||||
<div class="model"></div>
|
||||
<div class="contentmode"></div>
|
||||
<div class="pending"></div>
|
||||
<div class="lastseen"></div>
|
||||
<div class="nextcheckin"></div>
|
||||
<div class="nextupdate"></div>
|
||||
<div class="corner">
|
||||
<div class="warningicon">⚠</div>
|
||||
<div class="configicon"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logbox">
|
||||
<p>
|
||||
<span>logging</span>
|
||||
<span><img id="clearlog" src="data:image/gif;base64,R0lGODlhEAAQAPMAANXV1e3t7d/f39HR0dvb2/Hx8dTU1OLi4urq6mZmZpmZmf///wAAAAAAAAAAAAAAACH5BAEAAAwALAAAAAAQABAAAARBkMlJq71Yrp3ZXkr4WWCYnOZSgQVyEMYwJCq1nHhe20qgCAoA7QLyAYU7njE4JPV+zOSkCEUSFbmTVPPpbjvgTAQAOw==
|
||||
"></span>
|
||||
<span id="sysinfo"></span>
|
||||
</p>
|
||||
<ul id="messages" class="messages">
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="window">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<iframe name="empty" style="border:1px solid darkgrey; width:100%; height:4em;"></iframe>
|
||||
|
||||
<div class="actionbox">
|
||||
<form action="send_image" method="POST" target="empty">
|
||||
DST:<input name="dst" class="dst" size=12 maxlength=12 type="text" placeholder="001122334455">
|
||||
Filename:<input name="filename" type="text" placeholder="default.bmp">
|
||||
Next check-in:<input name="ttl" type="text" placeholder="1"> min
|
||||
<input type="submit" value="Send Image">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="actionbox">
|
||||
<form action="send_fw" method="POST" target="empty">
|
||||
DST:<input name="dst" class="dst" size=12 maxlength=12 type="text" placeholder="001122334455">
|
||||
Filename:<input name="filename" type="text" placeholder="update.bin">
|
||||
<input type="submit" value="Send FW Update">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="actionbox">
|
||||
<form action="req_checkin" method="POST" target="empty">
|
||||
DST:<input name="dst" class="dst" size=12 maxlength=12 type="text" placeholder="001122334455">
|
||||
<input type="submit" value="Request Check-in">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="messages">
|
||||
<ul class="messages">
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="jquery.js"></script>
|
||||
<script src="main.js"></script>
|
||||
<script src="main.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
BIN
esp32_fw/data/jpeg-h.jpg
Normal file
BIN
esp32_fw/data/jpeg-h.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
esp32_fw/data/jpeg-v.jpg
Normal file
BIN
esp32_fw/data/jpeg-v.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
6
esp32_fw/data/jquery.js
vendored
6
esp32_fw/data/jquery.js
vendored
File diff suppressed because one or more lines are too long
BIN
esp32_fw/data/kat-bw29.jpg
Normal file
BIN
esp32_fw/data/kat-bw29.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
@@ -3,7 +3,6 @@
|
||||
padding:0;
|
||||
border:0;
|
||||
list-style-type: none;
|
||||
-webkit-appearance: none;
|
||||
outline: none;
|
||||
font-weight: 400;
|
||||
-webkit-box-sizing: border-box;
|
||||
@@ -16,6 +15,7 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 12px;
|
||||
font-family: Helvetica, Arial, Verdana, sans-serif;
|
||||
@@ -28,144 +28,217 @@ body {
|
||||
header {
|
||||
height: 50px;
|
||||
background-color: #666;
|
||||
|
||||
}
|
||||
|
||||
label {
|
||||
width:100px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 auto;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
text-indent: 100px;
|
||||
text-indent: 50px;
|
||||
overflow:hidden;
|
||||
background: url('p2000.svg') center center no-repeat;
|
||||
background-size: 50px 50px;
|
||||
font-size: 2.5em;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.container {
|
||||
|
||||
}
|
||||
|
||||
.blocks {
|
||||
padding: 21px 0;
|
||||
overflow-x: auto;
|
||||
text-align:center;
|
||||
}
|
||||
.blocks ul {
|
||||
display:table;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.blocks ul li {
|
||||
display:table-cell;
|
||||
vertical-align:top;
|
||||
padding: 0 4px;
|
||||
position: relative;
|
||||
}
|
||||
.blocks ul li:nth-child(6) div.b:before, .blocks ul li:nth-child(6) div.b:after {
|
||||
content:"";
|
||||
position:absolute;
|
||||
margin-left: -5px;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.blocks ul li:nth-child(6) div.b:before {
|
||||
top: -5px;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid #c30;
|
||||
}
|
||||
.blocks ul li:nth-child(6) div.b:after {
|
||||
bottom: -5px;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-bottom: 5px solid #c30;
|
||||
}
|
||||
.blocks ul li, .blocks ul li div {
|
||||
|
||||
}
|
||||
.blocks ul li div {
|
||||
width: 80px;
|
||||
padding: 0 4px;
|
||||
|
||||
}
|
||||
.blocks ul li div.b {
|
||||
position:relative;
|
||||
padding: 6px 0;
|
||||
background: #fff;
|
||||
}
|
||||
.blocks ul li div.rxt {
|
||||
background-color: #e5f9ea;
|
||||
}
|
||||
.blocks ul li div.txt {
|
||||
background-color: #f9eae5;
|
||||
}
|
||||
.blocks ul li div.b {
|
||||
|
||||
}
|
||||
.window {
|
||||
margin: 0 auto;
|
||||
max-width: 94%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
form {
|
||||
padding: 3px;
|
||||
.actionbox>div:first-child {
|
||||
padding: 10px;
|
||||
background-color: white;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.actionbox .dst {
|
||||
size:12em;
|
||||
.actionbox p {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.actionbox .columns {
|
||||
display:flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.columns div {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.actionbox div div {
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.actionbox input {
|
||||
border: solid 1px black;
|
||||
padding: 2px;
|
||||
input {
|
||||
border: solid 1px #666666;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
input.actionbox {
|
||||
border: solid 1px black;
|
||||
input[type=button] {
|
||||
border: 0px;
|
||||
padding: 4px 10px;
|
||||
cursor:pointer;
|
||||
}
|
||||
input[type=button]:hover {
|
||||
background-color:#aaaaaa;
|
||||
}
|
||||
select {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#configbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
left: 50px;
|
||||
width: 500px;
|
||||
padding: 15px;
|
||||
background-color: #f0e6d3;
|
||||
z-index: 999;
|
||||
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
|
||||
}
|
||||
|
||||
#configbox p {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#configbox h3 {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#configbox input {
|
||||
border: solid 1px #666666;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#configbox label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
#cfgdelete {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
right: 15px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.closebtn {
|
||||
border: 1px solid black;
|
||||
float: right;
|
||||
width: 19px;
|
||||
height: 20px;
|
||||
font-size: 1.1em;
|
||||
text-align: center;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logbox {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.logbox p {
|
||||
background-color: #ffffff;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.logbox img {
|
||||
vertical-align: bottom;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.logbox #sysinfo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.taglist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#tagtemplate {
|
||||
display:none;
|
||||
}
|
||||
|
||||
.tagcard {
|
||||
width: 225px;
|
||||
position: relative;
|
||||
height: 170px;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
background-color: #dddddd;
|
||||
}
|
||||
|
||||
.tagcard .pending {
|
||||
padding-bottom:15px;
|
||||
}
|
||||
|
||||
.currimg {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.currimg img {
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.mac {
|
||||
font-size: 0.9em;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.alias {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.corner {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.configicon {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
cursor:pointer;
|
||||
background-image: url("data: image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAQAAAAngNWGAAABXklEQVQoz43SP2tUQRQF8N99ecFKFEVh0Y1ZbFQ2xEoIQkoLW0t7C/OVLGzyHay1EAQjRFGrLMkjKSwWsRGV3WvxZvc9XQtnivl35sydc048nfuvVkW0k8XYtT936pVja5jJv/Z7wPZo0wNzLzQSHWnVwTZtqY1su2to3bZhj7dewEYeu+iDawi7bhn7Yl9TWGsikrzksnNxr9zfsIErcaEAMysyZXrvE5mp6w7zc8rMzFJjGNlxFWHmWKMVd2DHsHuadQ+NkcKxZ2pPXC8FHHhu1v91X6ZcUZHYy8xwwyju5wAzJypDFdHka5OiaN1yHDl100BaMyq84cwrs1JjtTBu7E5xIpeebLndOZOlnK859d1bZ0JoHPiRU986ZxYZmdjP8/HRrkfSS2+MTTXlycyawhlHJBOH5k789A6x/H/sZQuMKKNatjGLvkoreUy/lsnsq1mXJOe/Ut6tM38DZpmDFxwTi8EAAAAASUVORK5CYII=");
|
||||
}
|
||||
|
||||
.warningicon {
|
||||
display:none;
|
||||
font-size: 1.3em;
|
||||
background-color: yellow;
|
||||
color: black;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
vertical-align: top;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
ul.messages {
|
||||
padding: 12px 0;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
ul.messages li {
|
||||
line-height: 22px;
|
||||
position: relative;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
ul.messages li.new {
|
||||
-webkit-animation-name: new;
|
||||
-webkit-animation-duration: 1400ms;
|
||||
-webkit-animation-iteration-count: 1;
|
||||
-webkit-animation-timing-function: ease-in-out;
|
||||
animation-name: new;
|
||||
animation-duration: 1400ms;
|
||||
animation-iteration-count: 1;
|
||||
animation-timing-function: ease-in-out;
|
||||
}
|
||||
|
||||
ul.messages li div.message {
|
||||
padding-left: 58px;
|
||||
}
|
||||
ul.messages li div.date {
|
||||
position: absolute;
|
||||
left: 12px
|
||||
}
|
||||
span.pending {
|
||||
padding-left: 28px;
|
||||
}
|
||||
span.pending:before {
|
||||
content:"";
|
||||
position:absolute;
|
||||
display:inline-block;
|
||||
top: 0;
|
||||
left: 12px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
background: url('roll.svg') center center no-repeat;
|
||||
background-size: 22px 22px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@media(max-width: 460px) {
|
||||
@@ -183,14 +256,7 @@ span.pending:before {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@-webkit-keyframes new {
|
||||
@keyframes new {
|
||||
0% {
|
||||
background-color: rgba(255, 255, 204, 1);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,273 @@
|
||||
$.fn.htmlTo = function(elem) {
|
||||
return this.each(function() {
|
||||
$(elem).html($(this).html());
|
||||
const $ = document.querySelector.bind(document);
|
||||
|
||||
const contentModes = ["static image", "current date", "counting days", "counting hours", "current weather", "firmware update", "memo text", "image url"];
|
||||
const models = ["unknown type", "1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"];
|
||||
const contentModeOptions = [];
|
||||
contentModeOptions[0] = ["filename","timetolive"];
|
||||
contentModeOptions[1] = [];
|
||||
contentModeOptions[2] = ["counter", "thresholdred"];
|
||||
contentModeOptions[3] = ["counter", "thresholdred"];
|
||||
contentModeOptions[4] = ["location"];
|
||||
contentModeOptions[5] = ["filename"];
|
||||
contentModeOptions[6] = ["text"];
|
||||
contentModeOptions[7] = ["url","interval"];
|
||||
|
||||
const imageQueue = [];
|
||||
let isProcessing = false;
|
||||
let servertimediff = 0;
|
||||
|
||||
let socket;
|
||||
connect();
|
||||
setInterval(updatecards, 1000);
|
||||
window.addEventListener("load", function () { loadTags(0) });
|
||||
|
||||
function loadTags(pos) {
|
||||
fetch("/get_db?pos="+pos)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
processTags(data.tags);
|
||||
if (data.continu && data.continu>pos) loadTags(data.continu);
|
||||
})
|
||||
//.catch(error => showMessage('loadTags error: ' + error));
|
||||
}
|
||||
|
||||
function connect() {
|
||||
socket = new WebSocket("ws://" + location.host + "/ws");
|
||||
|
||||
socket.addEventListener("open", (event) => {
|
||||
showMessage("websocket connected");
|
||||
});
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
console.log(event.data);
|
||||
const msg = JSON.parse(event.data);
|
||||
if (msg.logMsg) {
|
||||
showMessage(msg.logMsg);
|
||||
}
|
||||
if (msg.tags) {
|
||||
processTags(msg.tags);
|
||||
}
|
||||
if (msg.sys) {
|
||||
$('#sysinfo').innerHTML = 'free heap: ' + msg.sys.heap + ' bytes ┇ db size: ' + msg.sys.dbsize + ' bytes ┇ db record count: ' + msg.sys.recordcount + ' ┇ littlefs free: ' + msg.sys.littlefsfree + ' bytes';
|
||||
servertimediff = (Date.now() / 1000) - msg.sys.currtime;
|
||||
}
|
||||
});
|
||||
|
||||
socket.addEventListener("close", (event) => {
|
||||
showMessage(`websocket closed ${event.code}`);
|
||||
setTimeout(connect, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
let socket = new WebSocket("ws://" + location.host + "/ws");
|
||||
function processTags(tagArray) {
|
||||
for (const element of tagArray) {
|
||||
tagmac = element.mac;
|
||||
|
||||
socket.onmessage = function(event) {
|
||||
let incomingMessage = event.data;
|
||||
showMessage(incomingMessage);
|
||||
};
|
||||
var div = $('#tag' + tagmac);
|
||||
if (div == null) {
|
||||
|
||||
socket.onclose = event => console.log(`Closed ${event.code}`);
|
||||
div = $('#tagtemplate').cloneNode(true);
|
||||
div.setAttribute('id', 'tag'+tagmac);
|
||||
div.dataset.mac = tagmac;
|
||||
$('#taglist').appendChild(div);
|
||||
|
||||
function showMessage(message) {
|
||||
$( "<li/>", {
|
||||
"class": "new",
|
||||
html: message
|
||||
}).prependTo( "ul.messages" );
|
||||
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
|
||||
var img = $('#tag' + tagmac + ' .tagimg');
|
||||
img.addEventListener('error', function handleError() {
|
||||
img.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
div.style.display = 'block';
|
||||
|
||||
let alias = element.alias;
|
||||
if (!alias) alias = tagmac;
|
||||
$('#tag' + tagmac + ' .alias').innerHTML = alias;
|
||||
|
||||
if (div.dataset.hash != element.hash) loadImage(tagmac, '/current/' + tagmac + '.bmp?' + (new Date()).getTime());
|
||||
|
||||
$('#tag' + tagmac + ' .contentmode').innerHTML = contentModes[element.contentmode];
|
||||
$('#tag' + tagmac + ' .model').innerHTML = models[element.model];
|
||||
|
||||
if (element.nextupdate > 1672531200 && element.nextupdate!=3216153600) {
|
||||
var date = new Date(element.nextupdate * 1000);
|
||||
var options = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||
$('#tag' + tagmac + ' .nextupdate').innerHTML = "next update: " + date.toLocaleString('nl-NL', options);
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .nextupdate').innerHTML = "";
|
||||
}
|
||||
|
||||
if (element.nextcheckin > 1672531200) {
|
||||
div.dataset.nextcheckin = element.nextcheckin;
|
||||
} else {
|
||||
div.dataset.nextcheckin = element.lastseen + 1800;
|
||||
}
|
||||
|
||||
div.dataset.lastseen = element.lastseen;
|
||||
div.dataset.hash = element.hash;
|
||||
$('#tag' + tagmac + ' .warningicon').style.display = 'none';
|
||||
|
||||
if (element.pending) $('#tag' + tagmac + ' .pending').innerHTML = "pending update..."; else $('#tag' + tagmac + ' .pending').innerHTML = "";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updatecards() {
|
||||
document.querySelectorAll('[data-mac]').forEach(item => {
|
||||
let tagmac = item.dataset.mac;
|
||||
|
||||
if (item.dataset.lastseen && item.dataset.lastseen > 1672531200) {
|
||||
let idletime = (Date.now() / 1000) + servertimediff - item.dataset.lastseen;
|
||||
$('#tag' + tagmac + ' .lastseen').innerHTML = "last seen: "+displayTime(Math.floor(idletime))+" ago";
|
||||
if ((Date.now() / 1000) + servertimediff > item.dataset.nextcheckin) $('#tag' + tagmac + ' .warningicon').style.display='inline-block';
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .lastseen').innerHTML = ""
|
||||
}
|
||||
|
||||
if (item.dataset.nextcheckin > 1672531200) {
|
||||
let nextcheckin = item.dataset.nextcheckin - ((Date.now() / 1000) + servertimediff);
|
||||
$('#tag' + tagmac + ' .nextcheckin').innerHTML = "expecting next checkin: " + displayTime(Math.floor(nextcheckin));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$('#clearlog').onclick = function () {
|
||||
$('#messages').innerHTML='';
|
||||
}
|
||||
|
||||
$('.closebtn').onclick = function (event) {
|
||||
event.target.parentNode.style.display='none';
|
||||
}
|
||||
|
||||
$('#taglist').addEventListener("click", (event) => {
|
||||
let currentElement = event.target;
|
||||
while (currentElement !== $('#taglist')) {
|
||||
if (currentElement.classList.contains("tagcard")) {
|
||||
break;
|
||||
}
|
||||
currentElement = currentElement.parentNode;
|
||||
}
|
||||
if (!currentElement.classList.contains("tagcard")) {
|
||||
return;
|
||||
}
|
||||
const mac = currentElement.dataset.mac;
|
||||
if (event.target.classList.contains("mac")) {
|
||||
$('#dstmac').value=mac;
|
||||
}
|
||||
if (event.target.classList.contains("configicon")) {
|
||||
$('#cfgmac').innerHTML = mac;
|
||||
$('#cfgmac').dataset.mac = mac;
|
||||
fetch("/get_db?mac=" + mac)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
var tagdata = data.tags[0];
|
||||
$('#cfgalias').value = tagdata.alias;
|
||||
$('#cfgcontent').value = tagdata.contentmode;
|
||||
$('#cfgmodel').value = tagdata.model;
|
||||
$('#cfgcontent').dataset.json = tagdata.modecfgjson;
|
||||
contentselected();
|
||||
$('#configbox').style.display = 'block';
|
||||
})
|
||||
.catch(error => showMessage('Error: ' + error));
|
||||
}
|
||||
})
|
||||
|
||||
$('#cfgsave').onclick = function () {
|
||||
|
||||
let contentmode = $('#cfgcontent').value;
|
||||
let extraoptions = contentModeOptions[contentmode];
|
||||
let obj={};
|
||||
extraoptions.forEach(element => {
|
||||
obj[element] = $('#opt' + element).value;
|
||||
});
|
||||
|
||||
let formData = new FormData();
|
||||
formData.append("mac", $('#cfgmac').dataset.mac);
|
||||
formData.append("alias", $('#cfgalias').value);
|
||||
formData.append("contentmode", contentmode);
|
||||
formData.append("model", $('#cfgmodel').value);
|
||||
formData.append("modecfgjson", JSON.stringify(obj));
|
||||
fetch("/save_cfg", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => showMessage(data))
|
||||
.catch(error => showMessage('Error: ' + error));
|
||||
$('#configbox').style.display = 'none';
|
||||
}
|
||||
|
||||
$('#cfgdelete').onclick = function () {
|
||||
let mac = $('#cfgmac').dataset.mac;
|
||||
}
|
||||
|
||||
function contentselected() {
|
||||
let contentmode=$('#cfgcontent').value;
|
||||
let extraoptions = contentModeOptions[contentmode];
|
||||
$('#customoptions').innerHTML="";
|
||||
var obj = {};
|
||||
if ($('#cfgcontent').dataset.json && ($('#cfgcontent').dataset.json!="null")) {
|
||||
obj = JSON.parse($('#cfgcontent').dataset.json);
|
||||
}
|
||||
console.log(obj);
|
||||
extraoptions.forEach(element => {
|
||||
var label = document.createElement("label");
|
||||
label.innerHTML = element;
|
||||
label.setAttribute("for", 'opt' + element);
|
||||
var input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.id = 'opt' + element;
|
||||
if (obj[element]) input.value = obj[element];
|
||||
var p = document.createElement("p");
|
||||
p.appendChild(label);
|
||||
p.appendChild(input);
|
||||
$('#customoptions').appendChild(p);
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(message) {
|
||||
const messages = $('#messages');
|
||||
var date = new Date(),
|
||||
time = date.toLocaleTimeString('en-US', {hour12: false, hour: '2-digit', minute:'2-digit', second:'2-digit'});
|
||||
messages.insertAdjacentHTML("afterbegin", '<li class="new">'+htmlEncode(time+' '+message)+'</li>');
|
||||
}
|
||||
|
||||
function htmlEncode(input) {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.innerText = input;
|
||||
return textArea.innerHTML.split("<br>").join("\n");
|
||||
}
|
||||
|
||||
function loadImage(id, imageSrc) {
|
||||
imageQueue.push({ id, imageSrc });
|
||||
if (!isProcessing) {
|
||||
processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
function processQueue() {
|
||||
if (imageQueue.length === 0) {
|
||||
isProcessing = false;
|
||||
return;
|
||||
}
|
||||
isProcessing = true;
|
||||
const { id, imageSrc } = imageQueue.shift();
|
||||
const image = $('#tag' + id + ' .tagimg');
|
||||
image.onload = function () {
|
||||
image.style.display = 'block';
|
||||
processQueue();
|
||||
}
|
||||
image.onerror = function () {
|
||||
image.style.display = 'none';
|
||||
processQueue();
|
||||
};
|
||||
image.src = imageSrc;
|
||||
}
|
||||
|
||||
function displayTime(seconds) {
|
||||
let hours = Math.floor(Math.abs(seconds) / 3600);
|
||||
let minutes = Math.floor((Math.abs(seconds) % 3600) / 60);
|
||||
let remainingSeconds = Math.abs(seconds) % 60;
|
||||
return (seconds < 0 ? '-' : '') + (hours > 0 ? `${hours}:${String(minutes).padStart(2, '0')}` : `${minutes}`) + `:${String(remainingSeconds).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
BIN
esp32_fw/data/numbers1-1.vlw
Normal file
BIN
esp32_fw/data/numbers1-1.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/numbers1-2.vlw
Normal file
BIN
esp32_fw/data/numbers1-2.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/numbers2-1.vlw
Normal file
BIN
esp32_fw/data/numbers2-1.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/numbers2-2.vlw
Normal file
BIN
esp32_fw/data/numbers2-2.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/numbers3-1.vlw
Normal file
BIN
esp32_fw/data/numbers3-1.vlw
Normal file
Binary file not shown.
BIN
esp32_fw/data/numbers3-2.vlw
Normal file
BIN
esp32_fw/data/numbers3-2.vlw
Normal file
Binary file not shown.
@@ -56,4 +56,6 @@ struct pendingData {
|
||||
} __packed;
|
||||
|
||||
#define BLOCK_DATA_SIZE 4096
|
||||
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
|
||||
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
|
||||
|
||||
#pragma pack(pop)
|
||||
15
esp32_fw/include/contentmanager.h
Normal file
15
esp32_fw/include/contentmanager.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <LittleFS.h>
|
||||
#include "makeimage.h"
|
||||
#include <time.h>
|
||||
#include "tag_db.h"
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
void contentRunner();
|
||||
void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo);
|
||||
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin);
|
||||
void drawDate(String &filename);
|
||||
void drawNumber(String &filename, int32_t count, int32_t thresholdred);
|
||||
bool getImgURL(String &filename, String URL, time_t fetched);
|
||||
char *formatHttpDate(time_t t);
|
||||
35
esp32_fw/include/makeimage.h
Normal file
35
esp32_fw/include/makeimage.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <Arduino.h>
|
||||
#include <TFT_eSPI.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
struct BitmapFileHeader {
|
||||
uint8_t sig[2];
|
||||
uint32_t fileSz;
|
||||
uint8_t rfu[4];
|
||||
uint32_t dataOfst;
|
||||
uint32_t headerSz; //40
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint16_t colorplanes; //must be one
|
||||
uint16_t bpp;
|
||||
uint32_t compression;
|
||||
uint32_t dataLen; //may be 0
|
||||
uint32_t pixelsPerMeterX;
|
||||
uint32_t pixelsPerMeterY;
|
||||
uint32_t numColors; //if zero, assume 2^bpp
|
||||
uint32_t numImportantColors;
|
||||
|
||||
} __attribute__((packed));
|
||||
|
||||
enum EinkClut {
|
||||
EinkClutTwoBlacks = 0,
|
||||
EinkClutTwoBlacksAndRed,
|
||||
EinkClutFourBlacks,
|
||||
EinkClutThreeBlacksAndRed,
|
||||
};
|
||||
|
||||
void spr2grays(TFT_eSprite &spr, long w, long h, String fileout);
|
||||
void jpg2grays(String filein, String fileout);
|
||||
void bmp2grays(String filein, String fileout);
|
||||
|
||||
@@ -21,3 +21,5 @@ class pendingdata {
|
||||
|
||||
void garbageCollection(void* parameter);
|
||||
extern std::vector<pendingdata*> pendingfiles;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -28,19 +28,15 @@
|
||||
// flasher options
|
||||
#define CUSTOM_MAC_HDR 0x0000
|
||||
|
||||
// connections to the tag
|
||||
//#define RXD1 16 //was 16 - 13
|
||||
//#define TXD1 17 // was 17 - 12
|
||||
#define RXD1 16
|
||||
#define TXD1 17
|
||||
|
||||
#define RXD1 13 // 1st
|
||||
#define TXD1 12 // 2nd
|
||||
|
||||
#define ZBS_SS 21
|
||||
#define ZBS_SS 5
|
||||
#define ZBS_CLK 18
|
||||
#define ZBS_MoSi 22
|
||||
#define ZBS_MoSi 23
|
||||
#define ZBS_MiSo 19
|
||||
#define ZBS_Reset 5
|
||||
#define ZBS_POWER1 15
|
||||
#define ZBS_POWER2 2
|
||||
#define ZBS_Reset 2
|
||||
#define ZBS_POWER1 13
|
||||
#define ZBS_POWER2 15
|
||||
|
||||
#define MAX_WRITE_ATTEMPTS 5
|
||||
#define MAX_WRITE_ATTEMPTS 5
|
||||
|
||||
47
esp32_fw/include/tag_db.h
Normal file
47
esp32_fw/include/tag_db.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
#pragma once
|
||||
|
||||
enum contentModes {
|
||||
Image,
|
||||
Today,
|
||||
CountDays,
|
||||
CountHours,
|
||||
Weather,
|
||||
Firmware,
|
||||
Memo,
|
||||
ImageUrl,
|
||||
};
|
||||
|
||||
class tagRecord {
|
||||
public:
|
||||
uint16_t nextCheckinpending;
|
||||
tagRecord() : mac{0}, model(0), alias(""), lastseen(0), nextupdate(0), contentMode(Image), pending(false), button(false), md5{0}, md5pending{0}, CheckinInMinPending(0), expectedNextCheckin(0), modeConfigJson("") {}
|
||||
|
||||
uint8_t mac[6];
|
||||
u_int8_t model;
|
||||
String alias;
|
||||
uint32_t lastseen;
|
||||
uint32_t nextupdate;
|
||||
contentModes contentMode;
|
||||
bool pending;
|
||||
bool button;
|
||||
uint8_t md5[16];
|
||||
uint8_t md5pending[16];
|
||||
uint16_t CheckinInMinPending;
|
||||
uint32_t expectedNextCheckin;
|
||||
String modeConfigJson;
|
||||
static tagRecord* findByMAC(uint8_t mac[6]);
|
||||
};
|
||||
|
||||
extern std::vector<tagRecord*> tagDB;
|
||||
String tagDBtoJson(uint8_t mac[6] = nullptr, uint8_t startPos = 0);
|
||||
void fillNode(JsonObject &tag, tagRecord* &taginfo);
|
||||
void saveDB(String filename);
|
||||
void loadDB(String filename);
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -1,13 +1,18 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include<Arduino.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
void init_web();
|
||||
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
|
||||
extern void webSocketSendProcess(void *parameter);
|
||||
extern void wsString(String text);
|
||||
void wsString(String text);
|
||||
void wsSendTaginfo(uint8_t mac[6]);
|
||||
void wsSendSysteminfo();
|
||||
|
||||
extern uint64_t swap64(uint64_t x);
|
||||
extern AsyncWebSocket ws;//("/ws");
|
||||
extern AsyncWebSocket ws; //("/ws");
|
||||
|
||||
extern SemaphoreHandle_t wsMutex;
|
||||
extern TaskHandle_t websocketUpdater;
|
||||
@@ -8,20 +8,21 @@
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[env:esp32cam]
|
||||
[env:lolin32_lite]
|
||||
platform = espressif32
|
||||
board = esp32cam
|
||||
board = esp32dev
|
||||
framework = arduino
|
||||
;board_build.partitions = min_spiffs.csv
|
||||
board_build.partitions = no_ota.csv
|
||||
platform_packages =
|
||||
monitor_filters = esp32_exception_decoder
|
||||
monitor_speed = 115200
|
||||
board_build.f_cpu = 240000000L
|
||||
board_build.filesystem = littlefs
|
||||
lib_deps =
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer
|
||||
https://github.com/tzapu/WiFiManager.git#feature_asyncwebserver
|
||||
bblanchon/ArduinoJson
|
||||
|
||||
upload_port = COM12
|
||||
monitor_port = COM12
|
||||
bodmer/TFT_eSPI
|
||||
https://github.com/Bodmer/TJpg_Decoder.git
|
||||
upload_port = COM5
|
||||
monitor_port = COM5
|
||||
|
||||
228
esp32_fw/src/contentmanager.cpp
Normal file
228
esp32_fw/src/contentmanager.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
#include "contentmanager.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
#include "newproto.h"
|
||||
#include <MD5Builder.h>
|
||||
#include <locale.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "commstructs.h"
|
||||
#include "makeimage.h"
|
||||
#include "web.h"
|
||||
|
||||
void contentRunner() {
|
||||
time_t now;
|
||||
time(&now);
|
||||
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
|
||||
if (now >= taginfo->nextupdate || taginfo->button) {
|
||||
uint8_t mac8[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
memcpy(mac8 + 2, taginfo->mac, 6);
|
||||
uint8_t src[8];
|
||||
*((uint64_t *)src) = swap64(*((uint64_t *)mac8));
|
||||
|
||||
drawNew(src, taginfo->button, taginfo);
|
||||
taginfo->button = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm *time_info = gmtime(&now);
|
||||
|
||||
char buffer[64];
|
||||
uint8_t src[8];
|
||||
*((uint64_t *)src) = swap64(*((uint64_t *)mac));
|
||||
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
String dst = (String)buffer;
|
||||
|
||||
String filename = "/" + dst + ".bmp";
|
||||
|
||||
time_info->tm_hour = 0;
|
||||
time_info->tm_min = 0;
|
||||
time_info->tm_sec = 0;
|
||||
time_info->tm_mday++;
|
||||
time_t midnight = mktime(time_info);
|
||||
|
||||
DynamicJsonDocument doc(500);
|
||||
deserializeJson(doc, taginfo->modeConfigJson);
|
||||
JsonObject cfgobj = doc.as<JsonObject>();
|
||||
|
||||
switch (taginfo->contentMode) {
|
||||
case Image:
|
||||
|
||||
filename = cfgobj["filename"].as<String>();
|
||||
if (filename && filename !="null" && !cfgobj["#fetched"].as<bool>()) {
|
||||
if (prepareDataAvail(&filename, DATATYPE_IMGRAW, mac, cfgobj["timetolive"].as<int>())) {
|
||||
cfgobj["#fetched"] = true;
|
||||
} else {
|
||||
wsString("Error accessing " + filename);
|
||||
}
|
||||
taginfo->nextupdate = 3216153600;
|
||||
}
|
||||
break;
|
||||
|
||||
case Today:
|
||||
|
||||
drawDate(filename);
|
||||
updateTagImage(filename, mac, (midnight - now) / 60 - 10);
|
||||
taginfo->nextupdate = midnight;
|
||||
break;
|
||||
|
||||
case CountDays:
|
||||
|
||||
if (buttonPressed) cfgobj["counter"] = 0;
|
||||
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"]);
|
||||
updateTagImage(filename, mac, (midnight - now) / 60 - 5);
|
||||
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
|
||||
taginfo->nextupdate = midnight;
|
||||
break;
|
||||
|
||||
case CountHours:
|
||||
|
||||
if (buttonPressed) cfgobj["counter"] = 0;
|
||||
drawNumber(filename, (int32_t)cfgobj["counter"], (int32_t)cfgobj["thresholdred"]);
|
||||
// updateTagImage(&filename, mac, (3600 - now % 3600) / 60);
|
||||
// taginfo->nextupdate = now + 3600 - (now % 3600);
|
||||
updateTagImage(filename, mac, 3);
|
||||
cfgobj["counter"] = (int32_t)cfgobj["counter"] + 1;
|
||||
taginfo->nextupdate = now + 300;
|
||||
break;
|
||||
|
||||
case Weather:
|
||||
|
||||
// https://open-meteo.com/
|
||||
break;
|
||||
|
||||
case Firmware:
|
||||
|
||||
filename = cfgobj["filename"].as<String>();
|
||||
if (filename && filename != "null" && !cfgobj["#fetched"].as<bool>()) {
|
||||
if (prepareDataAvail(&filename, DATATYPE_UPDATE, mac, cfgobj["timetolive"].as<int>())) {
|
||||
cfgobj["#fetched"] = true;
|
||||
} else {
|
||||
wsString("Error accessing " + filename);
|
||||
}
|
||||
taginfo->nextupdate = 3216153600;
|
||||
taginfo->contentMode = Image;
|
||||
}
|
||||
break;
|
||||
|
||||
case Memo:
|
||||
break;
|
||||
case ImageUrl:
|
||||
|
||||
if (getImgURL(filename, cfgobj["url"], (time_t)cfgobj["#fetched"])) {
|
||||
updateTagImage(filename, mac, cfgobj["interval"].as<int>());
|
||||
cfgobj["#fetched"] = now;
|
||||
}
|
||||
taginfo->nextupdate = now + 60 * cfgobj["interval"].as<int>();
|
||||
break;
|
||||
}
|
||||
|
||||
taginfo->modeConfigJson = doc.as<String>();
|
||||
}
|
||||
|
||||
bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin) {
|
||||
prepareDataAvail(&filename, DATATYPE_IMGRAW, dst, nextCheckin);
|
||||
return true;
|
||||
}
|
||||
|
||||
void drawDate(String &filename) {
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
time_t now;
|
||||
time(&now);
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
String Dag[] = {"zondag","maandag","dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"};
|
||||
String Maand[] = {"januari", "februari", "maart", "april", "mei", "juni","juli", "augustus", "september", "oktober", "november", "december"};
|
||||
int weekday_number = timeinfo.tm_wday;
|
||||
int month_number = timeinfo.tm_mon;
|
||||
|
||||
LittleFS.begin();
|
||||
long w = 296, h = 128; // mag staand of liggend
|
||||
spr.createSprite(w, h);
|
||||
spr.setColorDepth(8);
|
||||
spr.fillSprite(TFT_WHITE);
|
||||
spr.setTextDatum(TC_DATUM);
|
||||
spr.loadFont("calibrib62", LittleFS);
|
||||
spr.setTextColor(TFT_RED, TFT_WHITE);
|
||||
spr.drawString(Dag[timeinfo.tm_wday], w / 2, 10);
|
||||
spr.loadFont("calibrib50", LittleFS);
|
||||
spr.setTextColor(TFT_BLACK, TFT_WHITE);
|
||||
spr.drawString(String(timeinfo.tm_mday) + " " + Maand[timeinfo.tm_mon], w / 2, 73);
|
||||
spr.unloadFont();
|
||||
|
||||
spr2grays(spr, w, h, filename);
|
||||
|
||||
spr.deleteSprite();
|
||||
}
|
||||
|
||||
void drawNumber(String &filename, int32_t count, int32_t thresholdred) {
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
|
||||
LittleFS.begin();
|
||||
long w = 296, h = 128;
|
||||
spr.createSprite(w, h);
|
||||
spr.setColorDepth(8);
|
||||
spr.fillSprite(TFT_WHITE);
|
||||
spr.setTextDatum(MC_DATUM);
|
||||
if (count > thresholdred) {
|
||||
spr.setTextColor(TFT_RED, TFT_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(TFT_BLACK, TFT_WHITE);
|
||||
}
|
||||
String font = "numbers1-2";
|
||||
if (count>999) font="numbers2-2";
|
||||
if (count>9999) font="numbers3-2";
|
||||
spr.loadFont(font, LittleFS);
|
||||
spr.drawString(String(count), w/2, h/2+10);
|
||||
spr.unloadFont();
|
||||
|
||||
spr2grays(spr, w, h, filename);
|
||||
|
||||
spr.deleteSprite();
|
||||
}
|
||||
|
||||
bool getImgURL(String &filename, String URL, time_t fetched) {
|
||||
// https://images.klari.net/kat-bw29.jpg
|
||||
|
||||
LittleFS.begin();
|
||||
|
||||
Serial.println("get external " + URL);
|
||||
HTTPClient http;
|
||||
http.begin(URL);
|
||||
http.addHeader("If-Modified-Since", formatHttpDate(fetched));
|
||||
http.setTimeout(5000); //timeout in ms
|
||||
int httpCode = http.GET();
|
||||
if (httpCode == 200) {
|
||||
File f = LittleFS.open(filename, "w");
|
||||
if (f) {
|
||||
http.writeToStream(&f);
|
||||
f.close();
|
||||
jpg2grays(filename, filename);
|
||||
}
|
||||
} else {
|
||||
Serial.println("http " + String(httpCode));
|
||||
}
|
||||
http.end();
|
||||
return (httpCode == 200);
|
||||
}
|
||||
|
||||
char *formatHttpDate(time_t t) {
|
||||
static char buf[40];
|
||||
struct tm *timeinfo;
|
||||
timeinfo = gmtime(&t);
|
||||
strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S GMT", timeinfo);
|
||||
return buf;
|
||||
}
|
||||
@@ -3,50 +3,44 @@
|
||||
#include <WiFiManager.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "contentmanager.h"
|
||||
#include "flasher.h"
|
||||
#include "makeimage.h"
|
||||
#include "pendingdata.h"
|
||||
#include "serial.h"
|
||||
#include "soc/rtc_wdt.h"
|
||||
#include "tag_db.h"
|
||||
#include "web.h"
|
||||
|
||||
void freeHeapTask(void* parameter) {
|
||||
void timeTask(void* parameter) {
|
||||
while (1) {
|
||||
//Serial.printf("Free heap=%d\n", ESP.getFreeHeap());
|
||||
vTaskDelay(30000 / portTICK_PERIOD_MS);
|
||||
time_t now;
|
||||
time(&now);
|
||||
tm tm;
|
||||
if (!getLocalTime(&tm)) {
|
||||
Serial.println("Failed to obtain time");
|
||||
} else {
|
||||
if (now % 10 == 0) wsSendSysteminfo();
|
||||
contentRunner();
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
Serial.print(">\n");
|
||||
|
||||
configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "europe.pool.ntp.org", "time.nist.gov");
|
||||
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
|
||||
|
||||
init_web();
|
||||
loadDB("/tagDB.json");
|
||||
|
||||
long timezone = 2;
|
||||
byte daysavetime = 1;
|
||||
configTime(0, 3600, "time.nist.gov", "0.pool.ntp.org", "1.pool.ntp.org");
|
||||
struct tm tmstruct;
|
||||
delay(2000);
|
||||
tmstruct.tm_year = 0;
|
||||
getLocalTime(&tmstruct, 5000);
|
||||
Serial.printf("\nNow is : %d-%02d-%02d %02d:%02d:%02d\n", (tmstruct.tm_year) + 1900, (tmstruct.tm_mon) + 1, tmstruct.tm_mday, tmstruct.tm_hour, tmstruct.tm_min, tmstruct.tm_sec);
|
||||
Serial.println("");
|
||||
|
||||
// WiFiManager wm;
|
||||
xTaskCreate(freeHeapTask, "print free heap", 10000, NULL, 2, NULL);
|
||||
xTaskCreate(timeTask, "timed tasks", 10000, NULL, 2, NULL);
|
||||
xTaskCreate(zbsRxTask, "zbsRX Process", 10000, NULL, 2, NULL);
|
||||
xTaskCreate(garbageCollection, "pending-data cleanup", 5000, NULL, 1, NULL);
|
||||
xTaskCreate(webSocketSendProcess, "ws", 5000, NULL,configMAX_PRIORITIES-10, NULL);
|
||||
|
||||
/*
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
wm.setConfigPortalTimeout(180);
|
||||
bool res = wm.autoConnect("ESP32ZigbeeBase", "password"); // password protected ap
|
||||
if (!res) {
|
||||
Serial.println("Failed to connect");
|
||||
ESP.restart();
|
||||
}
|
||||
wm.setWiFiAutoReconnect(true);
|
||||
*/
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
428
esp32_fw/src/makeimage.cpp
Normal file
428
esp32_fw/src/makeimage.cpp
Normal file
@@ -0,0 +1,428 @@
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <LittleFS.h>
|
||||
#include <TFT_eSPI.h>
|
||||
#include <TJpg_Decoder.h>
|
||||
#include <makeimage.h>
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
|
||||
bool spr_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {
|
||||
spr.pushImage(x, y, w, h, bitmap);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void jpg2grays(String filein, String fileout) {
|
||||
TJpgDec.setJpgScale(1);
|
||||
TJpgDec.setCallback(spr_output);
|
||||
uint16_t w = 0, h = 0;
|
||||
TJpgDec.getFsJpgSize(&w, &h, filein);
|
||||
Serial.println("jpeg conversion " + String(w) + "x" + String(h));
|
||||
|
||||
spr.createSprite(w, h);
|
||||
spr.setColorDepth(8);
|
||||
spr.fillSprite(TFT_WHITE);
|
||||
TJpgDec.drawFsJpg(0, 0, filein);
|
||||
|
||||
spr2grays(spr, w, h, fileout);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
|
||||
static uint32_t repackPackedVals(uint32_t val, uint32_t pixelsPerPackedUnit, uint32_t packedMultiplyVal) {
|
||||
uint32_t ret = 0, i;
|
||||
for (i = 0; i < pixelsPerPackedUnit; i++) {
|
||||
ret = ret * packedMultiplyVal + val % packedMultiplyVal;
|
||||
val /= packedMultiplyVal;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void spr2grays(TFT_eSprite &spr, long w, long h, String fileout) {
|
||||
// based on bmp2grays function by Dmitry.GR
|
||||
|
||||
Serial.println("start writing BMP");
|
||||
long t = millis();
|
||||
LittleFS.begin();
|
||||
|
||||
fs::File f_out = LittleFS.open(fileout, "w");
|
||||
|
||||
uint32_t c, rowBytesOut, rowBytesIn, outBpp, i, numRows, pixelsPerPackedUnit = 1, packedMultiplyVal = 0x01000000, packedOutBpp = 0;
|
||||
uint32_t numGrays, extraColor = 0;
|
||||
struct BitmapFileHeader hdr;
|
||||
memset(&hdr, 0, sizeof(hdr));
|
||||
enum EinkClut clutType;
|
||||
uint8_t clut[256][3];
|
||||
bool dither = false, rotated = false;
|
||||
int skipBytes;
|
||||
|
||||
clutType = EinkClutTwoBlacksAndRed;
|
||||
|
||||
if (w > h) {
|
||||
hdr.width = h;
|
||||
hdr.height = w;
|
||||
rotated = true;
|
||||
} else {
|
||||
hdr.width = w;
|
||||
hdr.height = h;
|
||||
}
|
||||
hdr.bpp = 24;
|
||||
hdr.sig[0] = 'B';
|
||||
hdr.sig[1] = 'M';
|
||||
hdr.colorplanes = 1;
|
||||
|
||||
switch (clutType) {
|
||||
case EinkClutTwoBlacks:
|
||||
numGrays = 2;
|
||||
outBpp = 1;
|
||||
break;
|
||||
|
||||
case EinkClutTwoBlacksAndRed:
|
||||
extraColor = 0xff0000;
|
||||
numGrays = 2;
|
||||
outBpp = 2;
|
||||
break;
|
||||
|
||||
case EinkClutFourBlacks:
|
||||
numGrays = 4;
|
||||
outBpp = 2;
|
||||
break;
|
||||
|
||||
case EinkClutThreeBlacksAndRed:
|
||||
numGrays = 3;
|
||||
extraColor = 0xff0000;
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
packedOutBpp = outBpp;
|
||||
|
||||
rowBytesIn = (hdr.width * hdr.bpp + 31) / 32 * 4;
|
||||
rowBytesOut = ((hdr.width + pixelsPerPackedUnit - 1) / pixelsPerPackedUnit) * packedOutBpp;
|
||||
rowBytesOut = (rowBytesOut + 31) / 32 * 4;
|
||||
|
||||
numRows = hdr.height < 0 ? -hdr.height : hdr.height;
|
||||
hdr.bpp = outBpp;
|
||||
hdr.numColors = 1 << outBpp;
|
||||
hdr.numImportantColors = 1 << outBpp;
|
||||
hdr.dataOfst = sizeof(struct BitmapFileHeader) + 4 * hdr.numColors;
|
||||
hdr.dataLen = numRows * rowBytesOut;
|
||||
hdr.fileSz = hdr.dataOfst + hdr.dataLen;
|
||||
hdr.headerSz = 40;
|
||||
hdr.compression = 0;
|
||||
|
||||
f_out.write((uint8_t *)&hdr, sizeof(hdr));
|
||||
|
||||
// emit & record grey clut entries
|
||||
for (i = 0; i < numGrays; i++) {
|
||||
uint32_t val = 255 * i / (numGrays - 1);
|
||||
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
|
||||
clut[i][0] = val;
|
||||
clut[i][1] = val;
|
||||
clut[i][2] = val;
|
||||
}
|
||||
|
||||
if (extraColor) {
|
||||
f_out.write((extraColor >> 0) & 0xff); // B
|
||||
f_out.write((extraColor >> 8) & 0xff); // G
|
||||
f_out.write((extraColor >> 16) & 0xff); // R
|
||||
f_out.write(0x00); // A
|
||||
|
||||
clut[i][0] = (extraColor >> 0) & 0xff;
|
||||
clut[i][1] = (extraColor >> 8) & 0xff;
|
||||
clut[i][2] = (extraColor >> 16) & 0xff;
|
||||
}
|
||||
|
||||
// pad clut to size
|
||||
for (i = numGrays + (extraColor ? 1 : 0); i < hdr.numColors; i++) {
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
}
|
||||
|
||||
while (numRows--) {
|
||||
uint32_t pixelValsPackedSoFar = 0, numPixelsPackedSoFar = 0, valSoFar = 0, bytesIn = 0, bytesOut = 0, bitsSoFar = 0;
|
||||
|
||||
for (c = 0; c < hdr.width; c++, bytesIn += 3) {
|
||||
int64_t bestDist = 0x7fffffffffffffffll;
|
||||
uint8_t bestIdx = 0;
|
||||
int32_t ditherFudge = 0;
|
||||
uint16_t color565;
|
||||
if (rotated) {
|
||||
color565 = spr.readPixel(hdr.height - 1 - numRows, c);
|
||||
} else {
|
||||
color565 = spr.readPixel(c, numRows);
|
||||
}
|
||||
|
||||
uint8_t red = ((color565 >> 11) & 0x1F) * 8;
|
||||
uint8_t green = ((color565 >> 5) & 0x3F) * 4;
|
||||
uint8_t blue = (color565 & 0x1F) * 8;
|
||||
|
||||
if (dither)
|
||||
ditherFudge = (rand() % 255 - 127) / (int)numGrays;
|
||||
|
||||
for (i = 0; i < hdr.numColors; i++) {
|
||||
int64_t dist = 0;
|
||||
|
||||
dist += (blue - clut[i][0] + ditherFudge) * (blue - clut[i][0] + ditherFudge) * 4750ll;
|
||||
dist += (green - clut[i][1] + ditherFudge) * (green - clut[i][1] + ditherFudge) * 47055ll;
|
||||
dist += (red - clut[i][2] + ditherFudge) * (red - clut[i][2] + ditherFudge) * 13988ll;
|
||||
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// pack pixels as needed
|
||||
pixelValsPackedSoFar = pixelValsPackedSoFar * packedMultiplyVal + bestIdx;
|
||||
if (++numPixelsPackedSoFar != pixelsPerPackedUnit)
|
||||
continue;
|
||||
|
||||
numPixelsPackedSoFar = 0;
|
||||
|
||||
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
|
||||
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
|
||||
|
||||
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
|
||||
pixelValsPackedSoFar = 0;
|
||||
bitsSoFar += packedOutBpp;
|
||||
|
||||
if (bitsSoFar >= 8) {
|
||||
f_out.write(valSoFar >> (bitsSoFar -= 8));
|
||||
valSoFar &= (1 << bitsSoFar) - 1;
|
||||
bytesOut++;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we have unfinished pixel packages to write
|
||||
if (numPixelsPackedSoFar) {
|
||||
while (numPixelsPackedSoFar++ != pixelsPerPackedUnit)
|
||||
pixelValsPackedSoFar *= packedMultiplyVal;
|
||||
|
||||
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
|
||||
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
|
||||
|
||||
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
|
||||
pixelValsPackedSoFar = 0;
|
||||
bitsSoFar += packedOutBpp;
|
||||
|
||||
if (bitsSoFar >= 8) {
|
||||
f_out.write(valSoFar >> (bitsSoFar -= 8));
|
||||
valSoFar &= (1 << bitsSoFar) - 1;
|
||||
bytesOut++;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitsSoFar) {
|
||||
valSoFar <<= 8 - bitsSoFar; // left-align it as is expected
|
||||
f_out.write(valSoFar);
|
||||
bytesOut++;
|
||||
}
|
||||
|
||||
while (bytesOut++ < rowBytesOut)
|
||||
f_out.write(0);
|
||||
}
|
||||
f_out.close();
|
||||
Serial.println(millis() - t);
|
||||
Serial.println("finished writing BMP");
|
||||
}
|
||||
|
||||
void bmp2grays(String filein, String fileout) {
|
||||
// based on bmp2grays function by Dmitry.GR
|
||||
|
||||
Serial.println("start writing BMP2");
|
||||
long t = millis();
|
||||
LittleFS.begin();
|
||||
|
||||
fs::File f_in = LittleFS.open(filein, "r");
|
||||
fs::File f_out = LittleFS.open(fileout, "w");
|
||||
|
||||
uint32_t c, rowBytesOut, rowBytesIn, outBpp, i, numRows, pixelsPerPackedUnit = 1, packedMultiplyVal = 0x01000000, packedOutBpp = 0;
|
||||
uint32_t numGrays, extraColor = 0;
|
||||
struct BitmapFileHeader hdr;
|
||||
enum EinkClut clutType;
|
||||
uint8_t clut[256][3];
|
||||
bool dither = false;
|
||||
int skipBytes;
|
||||
|
||||
clutType = EinkClutTwoBlacksAndRed;
|
||||
|
||||
f_in.read((uint8_t *)&hdr, sizeof(hdr));
|
||||
|
||||
if (hdr.sig[0] != 'B' || hdr.sig[1] != 'M' || hdr.headerSz < 40 || hdr.colorplanes != 1 || hdr.bpp != 24 || hdr.compression) {
|
||||
Serial.println("BITMAP HEADER INVALID, use uncompressed 24 bits RGB");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (clutType) {
|
||||
case EinkClutTwoBlacks:
|
||||
numGrays = 2;
|
||||
outBpp = 1;
|
||||
break;
|
||||
|
||||
case EinkClutTwoBlacksAndRed:
|
||||
extraColor = 0xff0000;
|
||||
numGrays = 2;
|
||||
outBpp = 2;
|
||||
break;
|
||||
|
||||
case EinkClutFourBlacks:
|
||||
numGrays = 4;
|
||||
outBpp = 2;
|
||||
break;
|
||||
|
||||
case EinkClutThreeBlacksAndRed:
|
||||
numGrays = 3;
|
||||
extraColor = 0xff0000;
|
||||
outBpp = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
packedOutBpp = outBpp;
|
||||
|
||||
skipBytes = hdr.dataOfst - sizeof(hdr);
|
||||
if (skipBytes < 0) {
|
||||
fprintf(stderr, "file header was too short!\n");
|
||||
exit(-1);
|
||||
}
|
||||
f_in.read(NULL, skipBytes);
|
||||
|
||||
rowBytesIn = (hdr.width * hdr.bpp + 31) / 32 * 4;
|
||||
rowBytesOut = ((hdr.width + pixelsPerPackedUnit - 1) / pixelsPerPackedUnit) * packedOutBpp;
|
||||
rowBytesOut = (rowBytesOut + 31) / 32 * 4;
|
||||
|
||||
numRows = hdr.height < 0 ? -hdr.height : hdr.height;
|
||||
hdr.bpp = outBpp;
|
||||
hdr.numColors = 1 << outBpp;
|
||||
hdr.numImportantColors = 1 << outBpp;
|
||||
hdr.dataOfst = sizeof(struct BitmapFileHeader) + 4 * hdr.numColors;
|
||||
hdr.dataLen = numRows * rowBytesOut;
|
||||
hdr.fileSz = hdr.dataOfst + hdr.dataLen;
|
||||
hdr.headerSz = 40;
|
||||
hdr.compression = 0;
|
||||
|
||||
f_out.write((uint8_t *)&hdr, sizeof(hdr));
|
||||
|
||||
// emit & record grey clut entries
|
||||
for (i = 0; i < numGrays; i++) {
|
||||
uint32_t val = 255 * i / (numGrays - 1);
|
||||
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
f_out.write(val);
|
||||
|
||||
clut[i][0] = val;
|
||||
clut[i][1] = val;
|
||||
clut[i][2] = val;
|
||||
}
|
||||
|
||||
// if there is a color CLUT entry, emit that
|
||||
if (extraColor) {
|
||||
f_out.write((extraColor >> 0) & 0xff); // B
|
||||
f_out.write((extraColor >> 8) & 0xff); // G
|
||||
f_out.write((extraColor >> 16) & 0xff); // R
|
||||
f_out.write(0x00); // A
|
||||
|
||||
clut[i][0] = (extraColor >> 0) & 0xff;
|
||||
clut[i][1] = (extraColor >> 8) & 0xff;
|
||||
clut[i][2] = (extraColor >> 16) & 0xff;
|
||||
}
|
||||
|
||||
// pad clut to size
|
||||
for (i = numGrays + (extraColor ? 1 : 0); i < hdr.numColors; i++) {
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
f_out.write(0x00);
|
||||
}
|
||||
|
||||
while (numRows--) {
|
||||
uint32_t pixelValsPackedSoFar = 0, numPixelsPackedSoFar = 0, valSoFar = 0, bytesIn = 0, bytesOut = 0, bitsSoFar = 0;
|
||||
|
||||
for (c = 0; c < hdr.width; c++, bytesIn += 3) {
|
||||
int64_t bestDist = 0x7fffffffffffffffll;
|
||||
uint8_t rgb[3], bestIdx = 0;
|
||||
int32_t ditherFudge = 0;
|
||||
|
||||
f_in.read(rgb, sizeof(rgb));
|
||||
|
||||
if (dither)
|
||||
ditherFudge = (rand() % 255 - 127) / (int)numGrays;
|
||||
|
||||
for (i = 0; i < hdr.numColors; i++) {
|
||||
int64_t dist = 0;
|
||||
|
||||
dist += (rgb[0] - clut[i][0] + ditherFudge) * (rgb[0] - clut[i][0] + ditherFudge) * 4750ll;
|
||||
dist += (rgb[1] - clut[i][1] + ditherFudge) * (rgb[1] - clut[i][1] + ditherFudge) * 47055ll;
|
||||
dist += (rgb[2] - clut[i][2] + ditherFudge) * (rgb[2] - clut[i][2] + ditherFudge) * 13988ll;
|
||||
|
||||
if (dist < bestDist) {
|
||||
bestDist = dist;
|
||||
bestIdx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// pack pixels as needed
|
||||
pixelValsPackedSoFar = pixelValsPackedSoFar * packedMultiplyVal + bestIdx;
|
||||
if (++numPixelsPackedSoFar != pixelsPerPackedUnit)
|
||||
continue;
|
||||
|
||||
numPixelsPackedSoFar = 0;
|
||||
|
||||
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
|
||||
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
|
||||
|
||||
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
|
||||
pixelValsPackedSoFar = 0;
|
||||
bitsSoFar += packedOutBpp;
|
||||
|
||||
if (bitsSoFar >= 8) {
|
||||
f_out.write(valSoFar >> (bitsSoFar -= 8));
|
||||
valSoFar &= (1 << bitsSoFar) - 1;
|
||||
bytesOut++;
|
||||
}
|
||||
}
|
||||
|
||||
// see if we have unfinished pixel packages to write
|
||||
if (numPixelsPackedSoFar) {
|
||||
while (numPixelsPackedSoFar++ != pixelsPerPackedUnit)
|
||||
pixelValsPackedSoFar *= packedMultiplyVal;
|
||||
|
||||
// it is easier to display when low val is first pixel. currently last pixel is low - reverse this
|
||||
pixelValsPackedSoFar = repackPackedVals(pixelValsPackedSoFar, pixelsPerPackedUnit, packedMultiplyVal);
|
||||
|
||||
valSoFar = (valSoFar << packedOutBpp) | pixelValsPackedSoFar;
|
||||
pixelValsPackedSoFar = 0;
|
||||
bitsSoFar += packedOutBpp;
|
||||
|
||||
if (bitsSoFar >= 8) {
|
||||
f_out.write(valSoFar >> (bitsSoFar -= 8));
|
||||
valSoFar &= (1 << bitsSoFar) - 1;
|
||||
bytesOut++;
|
||||
}
|
||||
}
|
||||
|
||||
if (bitsSoFar) {
|
||||
valSoFar <<= 8 - bitsSoFar; // left-align it as is expected
|
||||
f_out.write(valSoFar);
|
||||
bytesOut++;
|
||||
}
|
||||
|
||||
while (bytesIn++ < rowBytesIn)
|
||||
f_in.read(NULL, 1);
|
||||
while (bytesOut++ < rowBytesOut)
|
||||
f_out.write(0);
|
||||
}
|
||||
f_in.close();
|
||||
f_out.close();
|
||||
Serial.println(millis() - t);
|
||||
Serial.println("finished writing BMP2");
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
#pragma pack(push, 1)
|
||||
#include "newproto.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <makeimage.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "LittleFS.h"
|
||||
#include "commstructs.h"
|
||||
#include "pendingdata.h"
|
||||
#include "serial.h"
|
||||
#include "settings.h"
|
||||
#include "tag_db.h"
|
||||
#include "web.h"
|
||||
|
||||
extern void sendBlock(const void* data, const uint16_t len);
|
||||
@@ -29,7 +31,7 @@ bool checkCRC(void* p, uint8_t len) {
|
||||
return ((uint8_t*)p)[0] == total;
|
||||
}
|
||||
|
||||
uint8_t* getDataForFile(File* file) {
|
||||
uint8_t* getDataForFile(fs::File* file) {
|
||||
uint8_t* ret = nullptr;
|
||||
ret = (uint8_t*)malloc(file->size());
|
||||
if (ret) {
|
||||
@@ -48,15 +50,53 @@ void prepareCancelPending(uint64_t ver) {
|
||||
}
|
||||
|
||||
bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin) {
|
||||
|
||||
if (nextCheckin > 1440) {
|
||||
//to prevent very long sleeps of the tag
|
||||
nextCheckin = 0;
|
||||
}
|
||||
|
||||
*filename = "/" + *filename;
|
||||
if (!LittleFS.exists(*filename)) return false;
|
||||
File file = LittleFS.open(*filename);
|
||||
fs::File file = LittleFS.open(*filename);
|
||||
|
||||
if (file.size() == 0) {
|
||||
Serial.print("opened a file with size 0??\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filename->endsWith(".bmp") || filename->endsWith(".BMP")) {
|
||||
struct BitmapFileHeader hdr;
|
||||
file.read((uint8_t*)&hdr, sizeof(hdr));
|
||||
if (hdr.width == 296 && hdr.height == 128) {
|
||||
//sorry, can't rotate
|
||||
Serial.println("when using BMP files, remember to only use 128px width and 296px height");
|
||||
wsString("when using BMP files, remember to only use 128px width and 296px height");
|
||||
return false;
|
||||
}
|
||||
if (hdr.sig[0] == 'B' && hdr.sig[1] == 'M' && hdr.bpp == 24) {
|
||||
Serial.println("converting 24bpp bmp to grays");
|
||||
wsString("converting 24bpp bmp to grays");
|
||||
char fileout[64];
|
||||
sprintf(fileout, "/temp/%02X%02X%02X%02X%02X%02X.bmp\0", dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
|
||||
bmp2grays(*filename,(String)fileout);
|
||||
*filename = (String)fileout;
|
||||
file.close();
|
||||
file = LittleFS.open(*filename);
|
||||
}
|
||||
}
|
||||
|
||||
if (filename->endsWith(".jpg") || filename->endsWith(".JPG")) {
|
||||
Serial.println("converting jpg to grays");
|
||||
wsString("converting jpg to grays");
|
||||
char fileout[64];
|
||||
sprintf(fileout, "/temp/%02X%02X%02X%02X%02X%02X.bmp\0", dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
|
||||
jpg2grays(*filename, (String)fileout);
|
||||
*filename = (String)fileout;
|
||||
file.close();
|
||||
file = LittleFS.open(*filename);
|
||||
}
|
||||
|
||||
uint8_t md5bytes[16];
|
||||
{
|
||||
MD5Builder md5;
|
||||
@@ -66,6 +106,20 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
|
||||
md5.getBytes(md5bytes);
|
||||
}
|
||||
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)dst));
|
||||
uint8_t mac[6];
|
||||
memcpy(mac, src + 2, sizeof(mac));
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo != nullptr) {
|
||||
if (memcmp(md5bytes, taginfo->md5pending, 16) == 0) {
|
||||
wsString("new image is the same as current image. not updating tag.");
|
||||
wsSendTaginfo(mac);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// the message that will be sent to the AP to tell the tag there is data pending
|
||||
struct pendingData pending = {0};
|
||||
memcpy(pending.targetMac, dst, 8);
|
||||
@@ -84,10 +138,30 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
|
||||
pendinginfo->len = pending.availdatainfo.dataSize;
|
||||
pendinginfo->data = nullptr;
|
||||
pendinginfo->timeout = PENDING_TIMEOUT;
|
||||
// pendinginfo->data = getDataForFile(&file);
|
||||
pendinginfo->data = getDataForFile(&file);
|
||||
file.close();
|
||||
pendinginfo->timeout = 1800;
|
||||
pendingfiles.push_back(pendinginfo);
|
||||
|
||||
if (dataType != DATATYPE_UPDATE) {
|
||||
char dst_path[64];
|
||||
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X.pending\0", dst[5], dst[4], dst[3], dst[2], dst[1], dst[0]);
|
||||
file = LittleFS.open(dst_path, "w");
|
||||
int bytes_written = file.write(pendinginfo->data, pendinginfo->len);
|
||||
file.close();
|
||||
|
||||
wsString("new image pending: " + String(dst_path));
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->pending = true;
|
||||
taginfo->CheckinInMinPending = nextCheckin + 1;
|
||||
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
|
||||
}
|
||||
} else {
|
||||
Serial.println("firmware upload pending");
|
||||
}
|
||||
|
||||
wsSendTaginfo(mac);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -105,7 +179,7 @@ void processBlockRequest(struct espBlockRequest* br) {
|
||||
} else {
|
||||
if (pd->data == nullptr) {
|
||||
// not cached. open file, cache the data
|
||||
File file = LittleFS.open(pd->filename);
|
||||
fs::File file = LittleFS.open(pd->filename);
|
||||
if (!file) {
|
||||
Serial.print("Dunno how this happened... File pending but deleted in the meantime?\n");
|
||||
}
|
||||
@@ -140,12 +214,59 @@ void processXferComplete(struct espXferComplete* xfc) {
|
||||
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X reports xfer complete\n\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
wsString((String)buffer);
|
||||
Serial.print(buffer);
|
||||
uint8_t mac[6];
|
||||
memcpy(mac, src + 2, sizeof(mac));
|
||||
|
||||
char src_path[64];
|
||||
char dst_path[64];
|
||||
char tmp_path[64];
|
||||
sprintf(src_path, "/current/%02X%02X%02X%02X%02X%02X.pending\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
sprintf(dst_path, "/current/%02X%02X%02X%02X%02X%02X.bmp\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
sprintf(tmp_path, "/temp/%02X%02X%02X%02X%02X%02X.bmp\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
if (LittleFS.exists(dst_path)) {
|
||||
LittleFS.remove(dst_path);
|
||||
}
|
||||
LittleFS.rename(src_path, dst_path);
|
||||
if (LittleFS.exists(tmp_path)) {
|
||||
LittleFS.remove(tmp_path);
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->pending = false;
|
||||
taginfo->expectedNextCheckin = now + 60 * taginfo->CheckinInMinPending + 30;
|
||||
memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending));
|
||||
}
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
|
||||
void processDataReq(struct espAvailDataReq* eadr) {
|
||||
char buffer[64];
|
||||
uint8_t src[8];
|
||||
sprintf(buffer, "<ADR %02X%02X%02X%02X%02X%02X%02X%02X button: %02X\n\0", eadr->src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], eadr->adr.buttonState);
|
||||
wsString((String)buffer);
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)eadr->src));
|
||||
|
||||
tagRecord* taginfo = nullptr;
|
||||
uint8_t mac[6];
|
||||
memcpy(mac, src + 2, sizeof(mac));
|
||||
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo == nullptr) {
|
||||
taginfo = new tagRecord;
|
||||
memcpy(taginfo->mac, src + 2, sizeof(taginfo->mac));
|
||||
taginfo->pending = false;
|
||||
tagDB.push_back(taginfo);
|
||||
}
|
||||
time_t now;
|
||||
time(&now);
|
||||
taginfo->lastseen = now;
|
||||
taginfo->expectedNextCheckin = now + 300;
|
||||
taginfo->button = (eadr->adr.buttonState == 1);
|
||||
|
||||
sprintf(buffer, "<ADR %02X%02X%02X%02X%02X%02X\n\0", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
Serial.print(buffer);
|
||||
//wsString((String)buffer);
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma pack(push, 1)
|
||||
#include <Arduino.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
168
esp32_fw/src/tag_db.cpp
Normal file
168
esp32_fw/src/tag_db.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include "tag_db.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "LittleFS.h"
|
||||
|
||||
std::vector<tagRecord*> tagDB;
|
||||
|
||||
tagRecord* tagRecord::findByMAC(uint8_t mac[6]) {
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
tagRecord* tag = nullptr;
|
||||
tag = tagDB.at(c);
|
||||
if (memcmp(tag->mac, mac, 6) == 0) {
|
||||
return tag;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
String tagDBtoJson(uint8_t mac[6], uint8_t startPos) {
|
||||
DynamicJsonDocument doc(2500);
|
||||
JsonArray tags = doc.createNestedArray("tags");
|
||||
|
||||
for (int16_t c = startPos; c < tagDB.size(); c++) {
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
|
||||
bool select = false;
|
||||
if (mac) {
|
||||
if (memcmp(taginfo->mac, mac, 6) == 0) {
|
||||
select = true;
|
||||
}
|
||||
} else {
|
||||
select = true;
|
||||
}
|
||||
if (select) {
|
||||
JsonObject tag = tags.createNestedObject();
|
||||
fillNode(tag, taginfo);
|
||||
if (mac) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (doc.capacity()-doc.memoryUsage() < doc.memoryUsage()/(c+1) + 100) {
|
||||
doc["continu"] = c+1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return doc.as<String>();
|
||||
}
|
||||
|
||||
void fillNode(JsonObject &tag, tagRecord* &taginfo) {
|
||||
char buffer[16];
|
||||
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", taginfo->mac[0], taginfo->mac[1], taginfo->mac[2], taginfo->mac[3], taginfo->mac[4], taginfo->mac[5]);
|
||||
tag["mac"] = (String)buffer;
|
||||
char hex[7];
|
||||
sprintf(hex, "%02x%02x%02x\0", taginfo->md5[0], taginfo->md5[1], taginfo->md5[2]);
|
||||
tag["hash"] = hex;
|
||||
tag["lastseen"] = taginfo->lastseen;
|
||||
tag["nextupdate"] = taginfo->nextupdate;
|
||||
tag["nextcheckin"] = taginfo->expectedNextCheckin;
|
||||
tag["model"] = taginfo->model;
|
||||
tag["pending"] = taginfo->pending;
|
||||
tag["button"] = taginfo->button;
|
||||
tag["alias"] = taginfo->alias;
|
||||
tag["contentmode"] = taginfo->contentMode;
|
||||
tag["modecfgjson"] = taginfo->modeConfigJson;
|
||||
}
|
||||
|
||||
void saveDB(String filename) {
|
||||
DynamicJsonDocument doc(2500);
|
||||
|
||||
Serial.println("start writing DB to file");
|
||||
long t = millis();
|
||||
|
||||
LittleFS.begin();
|
||||
fs::File file = LittleFS.open(filename, "w");
|
||||
if (!file) {
|
||||
Serial.println("saveDB: Failed to open file");
|
||||
return;
|
||||
}
|
||||
|
||||
file.write('[');
|
||||
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
doc.clear();
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
|
||||
JsonObject tag = doc.createNestedObject();
|
||||
fillNode(tag, taginfo);
|
||||
if (c > 0) {
|
||||
file.write(',');
|
||||
}
|
||||
serializeJson(doc, file);
|
||||
}
|
||||
file.write(']');
|
||||
|
||||
file.close();
|
||||
Serial.println(millis() - t);
|
||||
Serial.println("finished writing file");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void loadDB(String filename) {
|
||||
StaticJsonDocument<400> doc;
|
||||
|
||||
Serial.println("start reading DB from file");
|
||||
long t = millis();
|
||||
|
||||
LittleFS.begin();
|
||||
fs::File readfile = LittleFS.open(filename, "r");
|
||||
if (!readfile) {
|
||||
Serial.println("loadDB: Failed to open file");
|
||||
return;
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
bool parsing = true;
|
||||
|
||||
if (readfile.find("[")) {
|
||||
while (parsing) {
|
||||
DeserializationError err = deserializeJson(doc, readfile);
|
||||
if (!err) {
|
||||
JsonObject tag = doc[0];
|
||||
String dst = tag["mac"].as<String>();
|
||||
uint8_t mac[12];
|
||||
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo == nullptr) {
|
||||
taginfo = new tagRecord;
|
||||
memcpy(taginfo->mac, mac, sizeof(taginfo->mac));
|
||||
tagDB.push_back(taginfo);
|
||||
}
|
||||
//taginfo->lastseen = (uint32_t)tag["lastseen"];
|
||||
taginfo->lastseen = 0;
|
||||
taginfo->nextupdate = (uint32_t)tag["nextupdate"];
|
||||
taginfo->expectedNextCheckin = (uint16_t)tag["nextcheckin"];
|
||||
if (taginfo->expectedNextCheckin < now - 1800) {
|
||||
taginfo->expectedNextCheckin = now + 1800;
|
||||
}
|
||||
taginfo->model = (uint8_t)tag["model"];
|
||||
taginfo->pending = false;
|
||||
taginfo->button = false;
|
||||
taginfo->alias = tag["alias"].as<String>();
|
||||
taginfo->contentMode = static_cast<contentModes>(tag["contentmode"]);
|
||||
taginfo->modeConfigJson = tag["modecfgjson"].as<String>();
|
||||
}
|
||||
} else {
|
||||
Serial.print(F("deserializeJson() failed: "));
|
||||
Serial.println(err.c_str());
|
||||
parsing = false;
|
||||
}
|
||||
parsing = parsing && readfile.find(",");
|
||||
}
|
||||
}
|
||||
|
||||
readfile.close();
|
||||
Serial.println(millis() - t);
|
||||
Serial.println("finished reading file");
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "commstructs.h"
|
||||
#include "newproto.h"
|
||||
#include "settings.h"
|
||||
#include "tag_db.h"
|
||||
|
||||
extern uint8_t data_to_send[];
|
||||
|
||||
@@ -53,62 +55,15 @@ void webSocketSendProcess(void *parameter) {
|
||||
// sendStatus(STATUS_WIFI_ACTIVITY);
|
||||
DynamicJsonDocument doc(1500);
|
||||
if (ulNotificationValue & 2) { // WS_SEND_MODE_STATUS) {
|
||||
/* doc["rxActive"] = status.rxActive;
|
||||
doc["txActive"] = status.txActive;
|
||||
doc["freq"] = status.freq;
|
||||
doc["txMode"] = status.currentmode;
|
||||
*/
|
||||
}
|
||||
/*
|
||||
JsonArray statusframes = doc.createNestedArray("frames");
|
||||
for (uint8_t c = 0; c < STATUSFRAMELISTSIZE; c++) {
|
||||
if (statusframearr[c]) {
|
||||
JsonObject statusframe = statusframes.createNestedObject();
|
||||
statusframe["frame"] = statusframearr[c]->frameno;
|
||||
statusframe["isTX"] = statusframearr[c]->isTX;
|
||||
statusframe["freq"] = statusframearr[c]->freq;
|
||||
statusframe["txSkipped"] = statusframearr[c]->txCancelled;
|
||||
switch (statusframearr[c]->rxtype) {
|
||||
case flexsynctype::SYNC_FLEX_1600:
|
||||
statusframe["rxType"] = "FLEX_1600";
|
||||
break;
|
||||
case flexsynctype::SYNC_FLEX_3200_2:
|
||||
statusframe["rxType"] = "FLEX_3200_2";
|
||||
break;
|
||||
case flexsynctype::SYNC_FLEX_3200_4:
|
||||
statusframe["rxType"] = "FLEX_3200_4";
|
||||
break;
|
||||
case flexsynctype::SYNC_FLEX_6400:
|
||||
statusframe["rxType"] = "FLEX_3200_4";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (statusframearr[c]->txformat) {
|
||||
case txframe::FORMAT_FLEX:
|
||||
statusframe["txType"] = "FLEX";
|
||||
break;
|
||||
case txframe::FORMAT_POCSAG:
|
||||
statusframe["txType"] = "POCSAG";
|
||||
break;
|
||||
case txframe::FORMAT_IDLE:
|
||||
statusframe["txType"] = "IDLE";
|
||||
break;
|
||||
case txframe::FORMAT_BLOCKED:
|
||||
statusframe["txType"] = "BLOCKED";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
size_t len = measureJson(doc);
|
||||
xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
auto buffer = std::make_shared<std::vector<uint8_t>>(len);
|
||||
serializeJson(doc, buffer->data(), len);
|
||||
// ws.textAll((char*)buffer->data());
|
||||
ws.textAll("ohai");
|
||||
xSemaphoreGive(wsMutex);
|
||||
}
|
||||
}
|
||||
@@ -188,16 +143,56 @@ void onEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType
|
||||
}
|
||||
}
|
||||
|
||||
void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final);
|
||||
|
||||
void wsString(String text) {
|
||||
DynamicJsonDocument doc(100);
|
||||
doc["logMsg"] = text;
|
||||
xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(text);
|
||||
ws.textAll(doc.as<String>());
|
||||
xSemaphoreGive(wsMutex);
|
||||
}
|
||||
|
||||
void wsSendSysteminfo() {
|
||||
DynamicJsonDocument doc(250);
|
||||
JsonObject sys = doc.createNestedObject("sys");
|
||||
time_t now;
|
||||
time(&now);
|
||||
sys["currtime"] = now;
|
||||
sys["heap"] = ESP.getFreeHeap();
|
||||
sys["recordcount"] = tagDB.size();
|
||||
sys["dbsize"] = tagDB.size() * sizeof(tagRecord);
|
||||
sys["littlefsfree"] = LittleFS.totalBytes() - LittleFS.usedBytes();
|
||||
|
||||
xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(doc.as<String>());
|
||||
xSemaphoreGive(wsMutex);
|
||||
}
|
||||
|
||||
void wsSendTaginfo(uint8_t mac[6]) {
|
||||
|
||||
String json = "";
|
||||
json = tagDBtoJson(mac);
|
||||
|
||||
xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(json);
|
||||
xSemaphoreGive(wsMutex);
|
||||
|
||||
}
|
||||
|
||||
void init_web() {
|
||||
LittleFS.begin(true);
|
||||
|
||||
if (!LittleFS.exists("/.exclude.files")) {
|
||||
Serial.println("littlefs exclude.files aanmaken");
|
||||
File f = LittleFS.open("/.exclude.files", "w");
|
||||
f.close();
|
||||
}
|
||||
if (!LittleFS.exists("/current")) {
|
||||
LittleFS.mkdir("/current");
|
||||
}
|
||||
if (!LittleFS.exists("/temp")) {
|
||||
LittleFS.mkdir("/temp");
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_STA);
|
||||
WiFiManager wm;
|
||||
bool res;
|
||||
@@ -214,10 +209,6 @@ void init_web() {
|
||||
ws.onEvent(onEvent);
|
||||
server.addHandler(&ws);
|
||||
|
||||
server.on("/heap", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/plain", String(ESP.getFreeHeap()));
|
||||
});
|
||||
|
||||
server.on("/reboot", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
request->send(200, "text/plain", "OK Reboot");
|
||||
ESP.restart();
|
||||
@@ -231,70 +222,6 @@ void init_web() {
|
||||
},
|
||||
doImageUpload);
|
||||
|
||||
server.on("/send_image", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
String filename;
|
||||
String dst;
|
||||
uint16_t nextCheckin;
|
||||
if (request->hasParam("filename", true) && request->hasParam("dst", true)) {
|
||||
filename = request->getParam("filename", true)->value();
|
||||
dst = request->getParam("dst", true)->value();
|
||||
nextCheckin = request->getParam("ttl",true)->value().toInt();
|
||||
uint8_t mac_addr[12]; // I expected this to return like 8 values, but if I make the array 8 bytes long, things die.
|
||||
mac_addr[0] = 0x00;
|
||||
mac_addr[1] = 0x00;
|
||||
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X",
|
||||
&mac_addr[2],
|
||||
&mac_addr[3],
|
||||
&mac_addr[4],
|
||||
&mac_addr[5],
|
||||
&mac_addr[6],
|
||||
&mac_addr[7]) != 6) {
|
||||
request->send(200, "text/plain", "Something went wrong trying to parse the mac address");
|
||||
} else {
|
||||
*((uint64_t *)mac_addr) = swap64(*((uint64_t *)mac_addr));
|
||||
if (prepareDataAvail(&filename, DATATYPE_IMGRAW, mac_addr, nextCheckin)) {
|
||||
request->send(200, "text/plain", "Sending to " + dst);
|
||||
} else {
|
||||
request->send(200, "text/plain", "Couldn't find filename :(");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
request->send(200, "text/plain", "Didn't get the required filename + dst");
|
||||
return;
|
||||
});
|
||||
|
||||
server.on("/send_fw", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
String filename;
|
||||
String dst;
|
||||
if (request->hasParam("filename", true) && request->hasParam("dst", true)) {
|
||||
filename = request->getParam("filename", true)->value();
|
||||
dst = request->getParam("dst", true)->value();
|
||||
uint8_t mac_addr[12]; // I expected this to return like 8 values, but if I make the array 8 bytes long, things die.
|
||||
mac_addr[0] = 0x00;
|
||||
mac_addr[1] = 0x00;
|
||||
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X",
|
||||
&mac_addr[2],
|
||||
&mac_addr[3],
|
||||
&mac_addr[4],
|
||||
&mac_addr[5],
|
||||
&mac_addr[6],
|
||||
&mac_addr[7]) != 6) {
|
||||
request->send(200, "text/plain", "Something went wrong trying to parse the mac address");
|
||||
} else {
|
||||
*((uint64_t *)mac_addr) = swap64(*((uint64_t *)mac_addr));
|
||||
if (prepareDataAvail(&filename, DATATYPE_UPDATE, mac_addr, 0)) {
|
||||
request->send(200, "text/plain", "Sending FW to " + dst);
|
||||
} else {
|
||||
request->send(200, "text/plain", "Couldn't find filename :(");
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
request->send(200, "text/plain", "Didn't get the required filename + dst");
|
||||
return;
|
||||
});
|
||||
|
||||
server.on("/req_checkin", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
String filename;
|
||||
String dst;
|
||||
@@ -323,60 +250,53 @@ void init_web() {
|
||||
return;
|
||||
});
|
||||
|
||||
server.on("/get_db", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
String json = "";
|
||||
if (request->hasParam("mac")) {
|
||||
String dst = request->getParam("mac")->value();
|
||||
uint8_t mac[6];
|
||||
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5])==6) {
|
||||
json = tagDBtoJson(mac);
|
||||
}
|
||||
} else {
|
||||
uint8_t startPos=0;
|
||||
if (request->hasParam("pos")) {
|
||||
startPos = atoi(request->getParam("pos")->value().c_str());
|
||||
}
|
||||
json = tagDBtoJson(nullptr,startPos);
|
||||
}
|
||||
request->send(200, "application/json", json);
|
||||
});
|
||||
|
||||
server.on("/save_cfg", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
if (request->hasParam("mac", true)) {
|
||||
String dst = request->getParam("mac", true)->value();
|
||||
uint8_t mac[6];
|
||||
if (sscanf(dst.c_str(), "%02X%02X%02X%02X%02X%02X", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) == 6) {
|
||||
tagRecord *taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->alias = request->getParam("alias", true)->value();
|
||||
taginfo->modeConfigJson = request->getParam("modecfgjson", true)->value();
|
||||
taginfo->contentMode = (contentModes)atoi(request->getParam("contentmode", true)->value().c_str());
|
||||
taginfo->model = atoi(request->getParam("model", true)->value().c_str());
|
||||
taginfo->nextupdate = 0;
|
||||
wsSendTaginfo(mac);
|
||||
saveDB("/tagDB.json");
|
||||
request->send(200, "text/plain", "Ok, saved");
|
||||
} else {
|
||||
request->send(200, "text/plain", "Error while saving: mac not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
request->send(200, "text/plain", "Ok, saved");
|
||||
});
|
||||
|
||||
server.onNotFound([](AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/" || request->url() == "index.htm") {
|
||||
request->send(200, "text/html", "-");
|
||||
return;
|
||||
}
|
||||
Serial.printf("NOT_FOUND: ");
|
||||
|
||||
switch (request->method()) {
|
||||
case HTTP_GET:
|
||||
Serial.printf("GET");
|
||||
break;
|
||||
case HTTP_POST:
|
||||
Serial.printf("POST");
|
||||
break;
|
||||
case HTTP_DELETE:
|
||||
Serial.printf("DELETE");
|
||||
break;
|
||||
case HTTP_PUT:
|
||||
Serial.printf("PUT");
|
||||
break;
|
||||
case HTTP_PATCH:
|
||||
Serial.printf("PATCH");
|
||||
break;
|
||||
case HTTP_HEAD:
|
||||
Serial.printf("HEAD");
|
||||
break;
|
||||
case HTTP_OPTIONS:
|
||||
Serial.printf("OPTIONS");
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.printf("UNKNOWN");
|
||||
break;
|
||||
}
|
||||
Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());
|
||||
|
||||
if (request->contentLength()) {
|
||||
Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
|
||||
Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
|
||||
}
|
||||
for (int i = 0; i < request->headers(); i++) {
|
||||
AsyncWebHeader *h = request->getHeader(i);
|
||||
Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
|
||||
}
|
||||
for (int i = 0; i < request->params(); i++) {
|
||||
AsyncWebParameter *p = request->getParam(i);
|
||||
if (p->isFile()) {
|
||||
Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
|
||||
} else if (p->isPost()) {
|
||||
Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
|
||||
} else {
|
||||
Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
|
||||
}
|
||||
}
|
||||
request->send(404);
|
||||
});
|
||||
|
||||
|
||||
28
tag_fw/make.bat
Normal file
28
tag_fw/make.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
del fs154.bin
|
||||
del fw29.bin
|
||||
del fw42.bin
|
||||
makeit clean
|
||||
makeit BUILD=zbs154v033 CPU=8051 SOC=zbs243
|
||||
pause
|
||||
ren main.bin fw154.bin
|
||||
makeit clean
|
||||
makeit BUILD=zbs29v033 CPU=8051 SOC=zbs243
|
||||
pause
|
||||
ren main.bin fw29.bin
|
||||
makeit clean
|
||||
makeit BUILD=zbs42v033 CPU=8051 SOC=zbs243
|
||||
pause
|
||||
ren main.bin fw42.bin
|
||||
|
||||
del /s *.asm
|
||||
del /s *.lst
|
||||
del /s *.rst
|
||||
del /s *.sym
|
||||
del /s *.map
|
||||
del /s *.mem
|
||||
del /s *.ihx
|
||||
del /s *.adb
|
||||
del /s *.rel
|
||||
del /s *.omf
|
||||
|
||||
Reference in New Issue
Block a user