mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 06:06:23 +01:00
Multi-AP support!
Multi-AP support!
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 111 KiB |
BIN
ESP32_AP-Flasher/data/alignment.jpg
Normal file
BIN
ESP32_AP-Flasher/data/alignment.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
@@ -47,6 +47,60 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="apconfigbox">
|
||||
<div class="closebtn">✖</div>
|
||||
<h3 id="cfgmac">Access Point config</h3>
|
||||
<p>
|
||||
<label for="apcfgalias">Alias</label>
|
||||
<input id="apcfgalias" type="text">
|
||||
</p>
|
||||
<p>
|
||||
<label for="apcfgchid">Channel</label>
|
||||
<select id="apcfgchid">
|
||||
<option value="0">auto</option>
|
||||
<option value="11">11</option>
|
||||
<option value="15">15</option>
|
||||
<option value="20">20</option>
|
||||
<option value="25">25</option>
|
||||
<option value="26">26</option>
|
||||
<option value="27">27</option>
|
||||
</select>
|
||||
</p>
|
||||
<p>
|
||||
<input type="button" value="Save" id="apcfgsave">
|
||||
</p>
|
||||
<!--
|
||||
<p><pre>
|
||||
work in progress
|
||||
- upload tagDB
|
||||
- update webinterface from github
|
||||
- update APtag FW from github
|
||||
- update all tag FW from github
|
||||
- update esp32 fw
|
||||
</pre>
|
||||
</p>
|
||||
-->
|
||||
<p>
|
||||
Active access points:<br>
|
||||
<table id="aptable">
|
||||
<tr>
|
||||
<th>ip</th>
|
||||
<th>alias</th>
|
||||
<th>tags</th>
|
||||
<th>ch</th>
|
||||
<th>fw ver</th>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
<p>
|
||||
<span id="rebootbutton">reboot AP</span>
|
||||
<a href="/backup_db" id="downloadDBbutton">download tagDB</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://github.com/jjwbruijn/OpenEPaperLink" target="_new">Github OpenEPaperLink</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form>
|
||||
<div class="container">
|
||||
|
||||
@@ -55,8 +109,8 @@
|
||||
<div class="actionbox">
|
||||
<div>
|
||||
<div>Currently active tags:</div>
|
||||
<div class="rebootbtn"><span id="rebootbutton">reboot AP</span></div>
|
||||
<div class="editbtn"><a href="/edit" target="littlefs" class="filebutton">edit littleFS</a></div>
|
||||
<div><span id="apconfigbutton">AP config</span></div>
|
||||
<div><a href="/edit" target="littlefs" class="filebutton">edit littleFS</a></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ label {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#rebootbutton {
|
||||
#rebootbutton, #downloadDBbutton, #apconfigbutton, .filebutton {
|
||||
padding: 2px 5px;
|
||||
background-color: #cccccc;
|
||||
text-decoration: none;
|
||||
@@ -72,13 +72,6 @@ label {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.filebutton {
|
||||
padding: 2px 5px;
|
||||
background-color: #cccccc;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.columns div {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -89,14 +82,14 @@ input[type="text"],
|
||||
input[type="button"],
|
||||
input[type="submit"],
|
||||
button {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
border: solid 1px #666666;
|
||||
padding: 4px;
|
||||
-webkit-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
input[type=button] {
|
||||
@@ -109,10 +102,10 @@ input[type=button]:hover {
|
||||
}
|
||||
select {
|
||||
padding: 4px;
|
||||
-webkit-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
#configbox {
|
||||
#configbox, #apconfigbox {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
@@ -124,24 +117,53 @@ select {
|
||||
box-shadow: 7px 10px 52px -19px rgba(0, 0, 0, 0.63);
|
||||
}
|
||||
|
||||
#configbox p {
|
||||
#configbox p, #apconfigbox p {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#configbox h3 {
|
||||
#configbox h3, #apconfigbox h3 {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#configbox input {
|
||||
#configbox input, #apconfigbox input {
|
||||
border: solid 1px #666666;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
#configbox label {
|
||||
#configbox label, #apconfigbox label {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
#apconfigbox {
|
||||
background-color: #e6f0d3;
|
||||
}
|
||||
|
||||
#aptable {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
#aptable th {
|
||||
text-align: left;
|
||||
background-color: #00000020;
|
||||
padding: 0px 3px;
|
||||
}
|
||||
|
||||
#aptable th, #aptable td {
|
||||
border-right: 1px solid #000010;
|
||||
padding: 0px 3px;
|
||||
}
|
||||
|
||||
#aptable td:nth-child(1), #aptable th:nth-child(1) {
|
||||
border-left: 1px solid #000010;
|
||||
}
|
||||
|
||||
#aptable td:nth-child(3),
|
||||
#aptable td:nth-child(4) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#cfgdelete {
|
||||
position: absolute;
|
||||
bottom: 15px;
|
||||
|
||||
@@ -8,7 +8,7 @@ const WAKEUP_REASON_FIRSTBOOT = 0xFC;
|
||||
const WAKEUP_REASON_NETWORK_SCAN = 0xFD;
|
||||
const WAKEUP_REASON_WDT_RESET = 0xFE;
|
||||
|
||||
const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast", "RSS feed", "QR code", "Calendar"];
|
||||
const contentModes = ["Static image", "Current date", "Counting days", "Counting hours", "Current weather", "Firmware update", "Memo text", "Image url", "Weather forecast", "RSS feed", "QR code", "Calendar", "Remote AP"];
|
||||
const models = ["1.54\" 152x152px", "2.9\" 296x128px", "4.2\" 400x300px"];
|
||||
const displaySizeLookup = { 0: [152, 152], 1: [128, 296], 2: [400, 300] };
|
||||
const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [255, 0, 0] };
|
||||
@@ -25,6 +25,7 @@ contentModeOptions[8] = ["location"];
|
||||
contentModeOptions[9] = ["title", "url", "interval"];
|
||||
contentModeOptions[10] = ["title", "qr-content"];
|
||||
contentModeOptions[11] = ["title", "apps_script_url", "interval"];
|
||||
contentModeOptions[12] = [];
|
||||
|
||||
const imageQueue = [];
|
||||
let isProcessing = false;
|
||||
@@ -33,7 +34,14 @@ let servertimediff = 0;
|
||||
let socket;
|
||||
connect();
|
||||
setInterval(updatecards, 1000);
|
||||
window.addEventListener("load", function () { loadTags(0) });
|
||||
window.addEventListener("load", function () {
|
||||
fetch("/get_ap_list")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.alias) $(".logo").innerHTML = data.alias;
|
||||
})
|
||||
loadTags(0)
|
||||
});
|
||||
|
||||
function loadTags(pos) {
|
||||
fetch("/get_db?pos="+pos)
|
||||
@@ -67,7 +75,14 @@ function connect() {
|
||||
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;
|
||||
console.log("timediff: " + servertimediff);
|
||||
}
|
||||
if (msg.apitem) {
|
||||
var row = $("#aptable").insertRow();
|
||||
row.insertCell(0).innerHTML = "<a href=\"http://" + msg.apitem.ip + "\" target=\"_new\">" + msg.apitem.ip + "</a>";
|
||||
row.insertCell(1).innerHTML = msg.apitem.alias;
|
||||
row.insertCell(2).innerHTML = msg.apitem.count;
|
||||
row.insertCell(3).innerHTML = msg.apitem.channel;
|
||||
row.insertCell(4).innerHTML = msg.apitem.version;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -89,12 +104,15 @@ function processTags(tagArray) {
|
||||
div.dataset.mac = tagmac;
|
||||
div.dataset.hwtype = -1;
|
||||
$('#taglist').appendChild(div);
|
||||
|
||||
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
|
||||
}
|
||||
|
||||
div.style.display = 'block';
|
||||
|
||||
if (element.isexternal) {
|
||||
$('#tag' + tagmac + ' .mac').innerHTML = tagmac + " via ext AP";
|
||||
} else {
|
||||
$('#tag' + tagmac + ' .mac').innerHTML = tagmac;
|
||||
}
|
||||
let alias = element.alias;
|
||||
if (!alias) alias = tagmac;
|
||||
$('#tag' + tagmac + ' .alias').innerHTML = alias;
|
||||
@@ -205,9 +223,11 @@ $('#clearlog').onclick = function () {
|
||||
$('#messages').innerHTML='';
|
||||
}
|
||||
|
||||
$('.closebtn').onclick = function (event) {
|
||||
event.target.parentNode.style.display='none';
|
||||
}
|
||||
document.querySelectorAll('.closebtn').forEach(button => {
|
||||
button.addEventListener('click', (event) => {
|
||||
event.target.parentNode.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
$('#taglist').addEventListener("click", (event) => {
|
||||
let currentElement = event.target;
|
||||
@@ -227,7 +247,6 @@ $('#taglist').addEventListener("click", (event) => {
|
||||
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;
|
||||
@@ -240,26 +259,27 @@ $('#taglist').addEventListener("click", (event) => {
|
||||
})
|
||||
|
||||
$('#cfgsave').onclick = function () {
|
||||
|
||||
let contentMode = $('#cfgcontent').value;
|
||||
let extraoptions = contentModeOptions[contentMode];
|
||||
let obj={};
|
||||
extraoptions.forEach(element => {
|
||||
obj[element] = $('#opt' + element).value;
|
||||
});
|
||||
if (contentMode) {
|
||||
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("modecfgjson", JSON.stringify(obj));
|
||||
fetch("/save_cfg", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => showMessage(data))
|
||||
.catch(error => showMessage('Error: ' + error));
|
||||
let formData = new FormData();
|
||||
formData.append("mac", $('#cfgmac').dataset.mac);
|
||||
formData.append("alias", $('#cfgalias').value);
|
||||
formData.append("contentmode", contentMode);
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -288,6 +308,36 @@ $('#rebootbutton').onclick = function () {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
$('#apconfigbutton').onclick = function () {
|
||||
var table = document.getElementById("aptable");
|
||||
var rowCount = table.rows.length;
|
||||
for (var i = rowCount - 1; i > 0; i--) {
|
||||
table.deleteRow(i);
|
||||
}
|
||||
$('#apconfigbox').style.display = 'block'
|
||||
fetch("/get_ap_list")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
$('#apcfgalias').value = data.alias;
|
||||
$('#apcfgchid').value = data.channel;
|
||||
})
|
||||
}
|
||||
|
||||
$('#apcfgsave').onclick = function () {
|
||||
let formData = new FormData();
|
||||
formData.append("alias", $('#apcfgalias').value);
|
||||
formData.append("channel", $('#apcfgchid').value);
|
||||
fetch("/save_apcfg", {
|
||||
method: "POST",
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.text())
|
||||
.then(data => showMessage(data))
|
||||
.catch(error => showMessage('Error: ' + error));
|
||||
$(".logo").innerHTML = $('#apcfgalias').value;
|
||||
$('#apconfigbox').style.display = 'none';
|
||||
}
|
||||
|
||||
function contentselected() {
|
||||
let contentMode = $('#cfgcontent').value;
|
||||
let extraoptions = contentModeOptions[contentMode];
|
||||
@@ -296,20 +346,21 @@ function contentselected() {
|
||||
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);
|
||||
});
|
||||
if (contentMode) {
|
||||
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,iserr) {
|
||||
|
||||
@@ -86,4 +86,20 @@ struct pendingData {
|
||||
#define BLOCK_DATA_SIZE 4096
|
||||
#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData)
|
||||
|
||||
#define PKT_AVAIL_DATA_REQ 0xE5
|
||||
#define PKT_AVAIL_DATA_INFO 0xE6
|
||||
#define PKT_XFER_COMPLETE 0xEA
|
||||
#define PKT_XFER_TIMEOUT 0xED
|
||||
#define PKT_CANCEL_XFER 0xEC
|
||||
#define PKT_APLIST_REQ 0x80
|
||||
#define PKT_APLIST_REPLY 0x81
|
||||
|
||||
struct APlist {
|
||||
uint32_t src;
|
||||
char alias[32];
|
||||
uint8_t channelId;
|
||||
uint8_t tagCount;
|
||||
uint16_t version;
|
||||
} __packed;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -22,8 +22,8 @@ void drawString(TFT_eSprite &spr, String content, uint16_t posx, uint16_t posy,
|
||||
void initSprite(TFT_eSprite &spr, int w, int h);
|
||||
void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawWeather(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawForecast(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams);
|
||||
void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams);
|
||||
bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imageParams);
|
||||
bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams);
|
||||
@@ -34,3 +34,4 @@ String urlEncode(const char *msg);
|
||||
int windSpeedToBeaufort(float windSpeed);
|
||||
String windDirectionIcon(int degrees);
|
||||
String mac62hex(uint8_t *mac);
|
||||
void getLocation(JsonObject &cfgobj);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
struct imgParam {
|
||||
bool hasRed;
|
||||
uint8_t dataType;
|
||||
bool dither;
|
||||
};
|
||||
|
||||
void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams);
|
||||
|
||||
@@ -6,7 +6,9 @@ extern bool checkCRC(void* p, uint8_t len);
|
||||
extern void processBlockRequest(struct espBlockRequest* br);
|
||||
extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin);
|
||||
extern bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin);
|
||||
extern void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP);
|
||||
extern void processXferComplete(struct espXferComplete* xfc);
|
||||
extern void processXferTimeout(struct espXferComplete* xfc);
|
||||
extern void processDataReq(struct espAvailDataReq* adr);
|
||||
void refreshAllPending();
|
||||
void refreshAllPending();
|
||||
void setAPchannel();
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#pragma pack(push, 1)
|
||||
#pragma once
|
||||
|
||||
class pendingdata {
|
||||
public:
|
||||
String filename;
|
||||
uint64_t ver;
|
||||
uint32_t timeout;
|
||||
uint8_t datatimeout;
|
||||
uint8_t* data = nullptr;
|
||||
uint32_t len;
|
||||
static pendingdata* findByVer(uint64_t ver);
|
||||
static void garbageCollection();
|
||||
};
|
||||
|
||||
void garbageCollection(void* parameter);
|
||||
extern std::vector<pendingdata*> pendingfiles;
|
||||
|
||||
#pragma pack(pop)
|
||||
@@ -1,5 +1,8 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
extern struct espSetChannelPower curChannel;
|
||||
extern uint16_t version;
|
||||
|
||||
void zbsTx(uint8_t* packetdata, uint8_t len);
|
||||
void zbsRxTask(void* parameter);
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
class tagRecord {
|
||||
public:
|
||||
uint16_t nextCheckinpending;
|
||||
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0) {}
|
||||
tagRecord() : mac{0}, alias(""), lastseen(0), nextupdate(0), contentMode(0), pending(false), md5{0}, md5pending{0}, expectedNextCheckin(0), modeConfigJson(""), LQI(0), RSSI(0), temperature(0), batteryMv(0), hwType(0), wakeupReason(0), capabilities(0), lastfullupdate(0), isExternal(false), pendingIdle(0),
|
||||
filename(""), data(nullptr), len(0) {}
|
||||
|
||||
uint8_t mac[6];
|
||||
String alias;
|
||||
@@ -38,14 +39,26 @@ class tagRecord {
|
||||
uint8_t wakeupReason;
|
||||
uint8_t capabilities;
|
||||
uint32_t lastfullupdate;
|
||||
bool isExternal;
|
||||
uint16_t pendingIdle;
|
||||
|
||||
String filename;
|
||||
uint8_t* data;
|
||||
uint32_t len;
|
||||
|
||||
static tagRecord* findByMAC(uint8_t mac[6]);
|
||||
};
|
||||
|
||||
extern std::vector<tagRecord*> tagDB;
|
||||
extern DynamicJsonDocument APconfig;
|
||||
String tagDBtoJson(uint8_t mac[6] = nullptr, uint8_t startPos = 0);
|
||||
bool deleteRecord(uint8_t mac[6]);
|
||||
void fillNode(JsonObject &tag, tagRecord* &taginfo);
|
||||
void saveDB(String filename);
|
||||
void loadDB(String filename);
|
||||
uint8_t getTagCount();
|
||||
void clearPending(tagRecord* taginfo);
|
||||
void initAPconfig();
|
||||
void saveAPconfig();
|
||||
|
||||
#pragma pack(pop)
|
||||
26
ESP32_AP-Flasher/include/udp.h
Normal file
26
ESP32_AP-Flasher/include/udp.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "AsyncUDP.h"
|
||||
|
||||
#ifndef defudpcomm
|
||||
#define defudpcomm
|
||||
|
||||
class UDPcomm {
|
||||
public:
|
||||
UDPcomm();
|
||||
~UDPcomm();
|
||||
void init();
|
||||
void getAPList();
|
||||
void netProcessDataReq(struct espAvailDataReq* eadr);
|
||||
void netProcessXferComplete(struct espXferComplete* xfc);
|
||||
void netProcessXferTimeout(struct espXferComplete* xfc);
|
||||
void netSendDataAvail(struct pendingData* pending);
|
||||
|
||||
private:
|
||||
AsyncUDP udp;
|
||||
void processPacket(AsyncUDPPacket packet);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
void init_udp();
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
#include<Arduino.h>
|
||||
#include <Arduino.h>
|
||||
#include <AsyncTCP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
@@ -11,9 +11,10 @@ void wsLog(String text);
|
||||
void wsErr(String text);
|
||||
void wsSendTaginfo(uint8_t mac[6]);
|
||||
void wsSendSysteminfo();
|
||||
void wsSendAPitem(struct APlist* apitem);
|
||||
|
||||
extern uint64_t swap64(uint64_t x);
|
||||
extern AsyncWebSocket ws; //("/ws");
|
||||
extern AsyncWebSocket ws;
|
||||
|
||||
extern SemaphoreHandle_t wsMutex;
|
||||
extern TaskHandle_t websocketUpdater;
|
||||
@@ -131,6 +131,7 @@ board = esp32dev
|
||||
board_build.partitions = no_ota.csv
|
||||
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=0
|
||||
-D SIMPLE_AP
|
||||
|
||||
-D FLASHER_AP_SS=5
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "makeimage.h"
|
||||
#include "newproto.h"
|
||||
#include "qrcode.h"
|
||||
#include "settings.h"
|
||||
#include "web.h"
|
||||
|
||||
#define PAL_BLACK 0
|
||||
@@ -32,6 +33,7 @@ enum contentModes {
|
||||
RSSFeed,
|
||||
QRcode,
|
||||
Calendar,
|
||||
RemoteAP,
|
||||
};
|
||||
|
||||
void contentRunner() {
|
||||
@@ -42,15 +44,26 @@ void contentRunner() {
|
||||
tagRecord *taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
|
||||
if (taginfo->RSSI && (now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_GPIO)) {
|
||||
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));
|
||||
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));
|
||||
|
||||
if (taginfo->RSSI && (now >= taginfo->nextupdate || taginfo->wakeupReason == WAKEUP_REASON_GPIO)) {
|
||||
drawNew(src, (taginfo->wakeupReason == WAKEUP_REASON_GPIO), taginfo);
|
||||
taginfo->wakeupReason = 0;
|
||||
}
|
||||
|
||||
if (taginfo->expectedNextCheckin > now - 10 && taginfo->expectedNextCheckin < now + 30 && taginfo->pendingIdle == 0 && taginfo->pending == false) {
|
||||
uint16_t minutesUntilNextUpdate = 0;
|
||||
minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
|
||||
if (minutesUntilNextUpdate > MIN_RESPONSE_TIME) minutesUntilNextUpdate = MIN_RESPONSE_TIME;
|
||||
if (minutesUntilNextUpdate > 1) {
|
||||
taginfo->pendingIdle = minutesUntilNextUpdate;
|
||||
prepareIdleReq(src, minutesUntilNextUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(1/portTICK_PERIOD_MS); // add a small delay to allow other threads to run
|
||||
}
|
||||
}
|
||||
@@ -85,11 +98,13 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
|
||||
imgParam imageParams;
|
||||
imageParams.hasRed = false;
|
||||
imageParams.dataType = DATATYPE_IMG_RAW_1BPP;
|
||||
imageParams.dither = true;
|
||||
|
||||
switch (taginfo->contentMode) {
|
||||
case Image:
|
||||
|
||||
if (cfgobj["filename"].as<String>() && cfgobj["filename"].as<String>() != "null" && !cfgobj["#fetched"].as<bool>()) {
|
||||
if (cfgobj["dither"] && cfgobj["dither"].as<bool>() == false) imageParams.dither = false;
|
||||
jpg2buffer(cfgobj["filename"].as<String>(), filename, imageParams);
|
||||
if (imageParams.hasRed) imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
|
||||
if (prepareDataAvail(&filename, imageParams.dataType, mac, cfgobj["timetolive"].as<int>())) {
|
||||
@@ -97,8 +112,8 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
|
||||
} else {
|
||||
wsErr("Error accessing " + filename);
|
||||
}
|
||||
taginfo->nextupdate = 3216153600;
|
||||
}
|
||||
taginfo->nextupdate = 3216153600;
|
||||
break;
|
||||
|
||||
case Today:
|
||||
@@ -133,14 +148,14 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
|
||||
// https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41¤t_weather=true
|
||||
// https://github.com/erikflowers/weather-icons
|
||||
|
||||
drawWeather(filename, cfgobj["location"], taginfo, imageParams);
|
||||
drawWeather(filename, cfgobj, taginfo, imageParams);
|
||||
taginfo->nextupdate = now + 3600;
|
||||
updateTagImage(filename, mac, 15, imageParams);
|
||||
break;
|
||||
|
||||
case Forecast:
|
||||
|
||||
drawForecast(filename, cfgobj["location"], taginfo, imageParams);
|
||||
drawForecast(filename, cfgobj, taginfo, imageParams);
|
||||
taginfo->nextupdate = now + 3 * 3600;
|
||||
updateTagImage(filename, mac, 15, imageParams);
|
||||
break;
|
||||
@@ -206,6 +221,11 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
|
||||
taginfo->nextupdate = now + 300;
|
||||
}
|
||||
break;
|
||||
|
||||
case RemoteAP:
|
||||
|
||||
taginfo->nextupdate = 3216153600;
|
||||
break;
|
||||
}
|
||||
|
||||
taginfo->modeConfigJson = doc.as<String>();
|
||||
@@ -265,6 +285,13 @@ void drawDate(String &filename, tagRecord *&taginfo, imgParam &imageParams) {
|
||||
drawString(spr, Dag[timeinfo.tm_wday], 152 / 2, 10, "fonts/calibrib30", TC_DATUM);
|
||||
drawString(spr, String(Maand[timeinfo.tm_mon]), 152 / 2, 120, "fonts/calibrib30", TC_DATUM);
|
||||
drawString(spr, String(timeinfo.tm_mday), 152 / 2, 42, "fonts/numbers2-1", TC_DATUM, PAL_RED);
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
|
||||
initSprite(spr, 400, 300);
|
||||
drawString(spr, Dag[timeinfo.tm_wday], 400 / 2, 30, "fonts/calibrib62", TC_DATUM, PAL_RED);
|
||||
drawString(spr, String(timeinfo.tm_mday) + " " + Maand[timeinfo.tm_mon], 400 / 2, 113, "fonts/calibrib50", TC_DATUM);
|
||||
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
@@ -305,246 +332,332 @@ void drawNumber(String &filename, int32_t count, int32_t thresholdred, tagRecord
|
||||
spr.loadFont(font, LittleFS);
|
||||
spr.drawString(String(count), 152 / 2, 152 / 2 + 7);
|
||||
spr.unloadFont();
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
|
||||
initSprite(spr, 400, 300);
|
||||
spr.setTextDatum(MC_DATUM);
|
||||
if (count > thresholdred) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
String font = "fonts/numbers1-2";
|
||||
if (count > 999) font = "fonts/numbers2-2";
|
||||
if (count > 9999) font = "fonts/numbers3-2";
|
||||
spr.loadFont(font, LittleFS);
|
||||
spr.drawString(String(count), 400 / 2, 300 / 2 + 7);
|
||||
spr.unloadFont();
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
|
||||
void drawWeather(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams) {
|
||||
void drawWeather(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) {
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
|
||||
wsLog("get weather");
|
||||
|
||||
getLocation(cfgobj);
|
||||
HTTPClient http;
|
||||
http.begin("https://geocoding-api.open-meteo.com/v1/search?name=" + urlEncode(location.c_str()) + "&count=1");
|
||||
http.setTimeout(5000); // timeout in ms
|
||||
|
||||
String lat = cfgobj["#lat"];
|
||||
String lon = cfgobj["#lon"];
|
||||
String tz = cfgobj["#tz"];
|
||||
http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "¤t_weather=true&windspeed_unit=ms&timezone=" + tz);
|
||||
http.setTimeout(5000);
|
||||
int httpCode = http.GET();
|
||||
Serial.printf("Got code %d for this location\n", httpCode);
|
||||
Serial.print(http.errorToString(httpCode));
|
||||
Serial.printf("Got code %d for this OpenMeteo\n", httpCode);
|
||||
|
||||
if (httpCode == 200) {
|
||||
StaticJsonDocument<200> filter;
|
||||
filter["current_weather"]["temperature"] = true;
|
||||
filter["current_weather"]["windspeed"] = true;
|
||||
filter["current_weather"]["winddirection"] = true;
|
||||
filter["current_weather"]["weathercode"] = true;
|
||||
|
||||
StaticJsonDocument<1000> doc;
|
||||
DeserializationError error = deserializeJson(doc, http.getStream());
|
||||
http.end();
|
||||
|
||||
http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + doc["results"][0]["latitude"].as<String>() + "&longitude=" + doc["results"][0]["longitude"].as<String>() + "¤t_weather=true&windspeed_unit=ms&timezone=" + doc["results"][0]["timezone"].as<String>());
|
||||
http.setTimeout(5000); // timeout in ms
|
||||
int httpCode = http.GET();
|
||||
Serial.printf("Got code %d for this OpenMeteo\n", httpCode);
|
||||
|
||||
if (httpCode == 200) {
|
||||
StaticJsonDocument<200> filter;
|
||||
filter["current_weather"]["temperature"] = true;
|
||||
filter["current_weather"]["windspeed"] = true;
|
||||
filter["current_weather"]["winddirection"] = true;
|
||||
filter["current_weather"]["weathercode"] = true;
|
||||
|
||||
doc.clear();
|
||||
DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter));
|
||||
if (error) {
|
||||
Serial.println(F("deserializeJson() failed: "));
|
||||
Serial.println(error.c_str());
|
||||
}
|
||||
|
||||
auto temperature = doc["current_weather"]["temperature"].as<double>();
|
||||
auto windspeed = doc["current_weather"]["windspeed"].as<int>();
|
||||
auto winddirection = doc["current_weather"]["winddirection"].as<int>();
|
||||
uint8_t weathercode = doc["current_weather"]["weathercode"].as<int>();
|
||||
if (weathercode > 40) weathercode -= 40;
|
||||
int wind = windSpeedToBeaufort(windspeed);
|
||||
|
||||
String weatherIcons[] = {"\uf00d", "\uf00c", "\uf002", "\uf013", "\uf013", "\uf014", "-", "-", "\uf014", "-", "-",
|
||||
"\uf01a", "-", "\uf01a", "-", "\uf01a", "\uf017", "\uf017", "-", "-", "-",
|
||||
"\uf019", "-", "\uf019", "-", "\uf019", "\uf015", "\uf015", "-", "-", "-",
|
||||
"\uf01b", "-", "\uf01b", "-", "\uf01b", "-", "\uf076", "-", "-", "\uf01a",
|
||||
"\uf01a", "\uf01a", "-", "-", "\uf064", "\uf064", "-", "-", "-", "-",
|
||||
"-", "-", "-", "-", "\uf01e", "\uf01d", "-", "-", "\uf01e"};
|
||||
if (1 == 0) { // nacht
|
||||
weatherIcons[0] = "\0uf02e";
|
||||
weatherIcons[1] = "\0uf083";
|
||||
weatherIcons[2] = "\0uf086";
|
||||
}
|
||||
|
||||
doc.clear();
|
||||
|
||||
LittleFS.begin();
|
||||
tft.setTextWrap(false, false);
|
||||
|
||||
if (taginfo->hwType == SOLUM_29_033) {
|
||||
initSprite(spr, 296, 128);
|
||||
|
||||
drawString(spr, location, 5, 5, "fonts/bahnschrift30");
|
||||
drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
char tmpOutput[5];
|
||||
dtostrf(temperature, 2, 1, tmpOutput);
|
||||
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons70", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
|
||||
spr.setCursor(185, 32);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
spr.unloadFont();
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(235, -3);
|
||||
spr.printToSprite(windDirectionIcon(winddirection));
|
||||
if (weathercode > 10) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
spr.setCursor(190, 0);
|
||||
spr.printToSprite("\uf084");
|
||||
}
|
||||
spr.unloadFont();
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_154_033) {
|
||||
initSprite(spr, 152, 152);
|
||||
spr.setTextDatum(TL_DATUM);
|
||||
|
||||
spr.setTextFont(2);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.drawString(location, 10, 130);
|
||||
|
||||
char tmpOutput[5];
|
||||
dtostrf(temperature, 2, 1, tmpOutput);
|
||||
drawString(spr, String(tmpOutput), 10, 10, "fonts/bahnschrift30", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons78", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
|
||||
spr.setCursor(30, 33);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
spr.unloadFont();
|
||||
|
||||
drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(100, -2);
|
||||
spr.printToSprite(windDirectionIcon(winddirection));
|
||||
if (weathercode > 10) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
spr.setCursor(115, 110);
|
||||
spr.printToSprite("\uf084");
|
||||
}
|
||||
spr.unloadFont();
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
spr.deleteSprite();
|
||||
DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter));
|
||||
if (error) {
|
||||
Serial.println(F("deserializeJson() failed: "));
|
||||
Serial.println(error.c_str());
|
||||
}
|
||||
|
||||
auto temperature = doc["current_weather"]["temperature"].as<double>();
|
||||
auto windspeed = doc["current_weather"]["windspeed"].as<int>();
|
||||
auto winddirection = doc["current_weather"]["winddirection"].as<int>();
|
||||
uint8_t weathercode = doc["current_weather"]["weathercode"].as<int>();
|
||||
if (weathercode > 40) weathercode -= 40;
|
||||
int wind = windSpeedToBeaufort(windspeed);
|
||||
|
||||
String weatherIcons[] = {"\uf00d", "\uf00c", "\uf002", "\uf013", "\uf013", "\uf014", "-", "-", "\uf014", "-", "-",
|
||||
"\uf01a", "-", "\uf01a", "-", "\uf01a", "\uf017", "\uf017", "-", "-", "-",
|
||||
"\uf019", "-", "\uf019", "-", "\uf019", "\uf015", "\uf015", "-", "-", "-",
|
||||
"\uf01b", "-", "\uf01b", "-", "\uf01b", "-", "\uf076", "-", "-", "\uf01a",
|
||||
"\uf01a", "\uf01a", "-", "-", "\uf064", "\uf064", "-", "-", "-", "-",
|
||||
"-", "-", "-", "-", "\uf01e", "\uf01d", "-", "-", "\uf01e"};
|
||||
if (1 == 0) { // nacht
|
||||
weatherIcons[0] = "\0uf02e";
|
||||
weatherIcons[1] = "\0uf083";
|
||||
weatherIcons[2] = "\0uf086";
|
||||
}
|
||||
|
||||
doc.clear();
|
||||
|
||||
LittleFS.begin();
|
||||
tft.setTextWrap(false, false);
|
||||
|
||||
if (taginfo->hwType == SOLUM_29_033) {
|
||||
initSprite(spr, 296, 128);
|
||||
|
||||
drawString(spr, cfgobj["location"], 5, 5, "fonts/bahnschrift30");
|
||||
drawString(spr, String(wind), 280, 5, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
char tmpOutput[5];
|
||||
dtostrf(temperature, 2, 1, tmpOutput);
|
||||
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons70", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
|
||||
spr.setCursor(185, 32);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
spr.unloadFont();
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(235, -3);
|
||||
spr.printToSprite(windDirectionIcon(winddirection));
|
||||
if (weathercode > 10) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
spr.setCursor(190, 0);
|
||||
spr.printToSprite("\uf084");
|
||||
}
|
||||
spr.unloadFont();
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_154_033) {
|
||||
initSprite(spr, 152, 152);
|
||||
spr.setTextDatum(TL_DATUM);
|
||||
|
||||
spr.setTextFont(2);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.drawString(cfgobj["location"].as<String>(), 10, 130);
|
||||
|
||||
char tmpOutput[5];
|
||||
dtostrf(temperature, 2, 1, tmpOutput);
|
||||
drawString(spr, String(tmpOutput), 10, 10, "fonts/bahnschrift30", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons78", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
|
||||
spr.setCursor(30, 33);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
spr.unloadFont();
|
||||
|
||||
drawString(spr, String(wind), 140, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(100, -2);
|
||||
spr.printToSprite(windDirectionIcon(winddirection));
|
||||
if (weathercode > 10) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
spr.setCursor(115, 110);
|
||||
spr.printToSprite("\uf084");
|
||||
}
|
||||
spr.unloadFont();
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
|
||||
initSprite(spr, 400, 300);
|
||||
|
||||
drawString(spr, cfgobj["location"], 10, 10, "fonts/bahnschrift30");
|
||||
drawString(spr, String(wind), 280, 10, "fonts/bahnschrift30", TR_DATUM, (wind > 4 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
char tmpOutput[5];
|
||||
dtostrf(temperature, 2, 1, tmpOutput);
|
||||
drawString(spr, String(tmpOutput), 5, 65, "fonts/bahnschrift70", TL_DATUM, (temperature < 0 ? PAL_RED : PAL_BLACK));
|
||||
|
||||
spr.loadFont("fonts/weathericons70", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
|
||||
spr.setCursor(185, 32);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
spr.unloadFont();
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(235, -3);
|
||||
spr.printToSprite(windDirectionIcon(winddirection));
|
||||
if (weathercode > 10) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
spr.setCursor(190, 0);
|
||||
spr.printToSprite("\uf084");
|
||||
}
|
||||
spr.unloadFont();
|
||||
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
|
||||
void drawForecast(String &filename, String location, tagRecord *&taginfo, imgParam &imageParams) {
|
||||
void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, imgParam &imageParams) {
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
|
||||
wsLog("get weather");
|
||||
getLocation(cfgobj);
|
||||
HTTPClient http;
|
||||
http.begin("https://geocoding-api.open-meteo.com/v1/search?name=" + urlEncode(location.c_str()) + "&count=1");
|
||||
http.setTimeout(5000); // timeout in ms
|
||||
|
||||
String lat = cfgobj["#lat"];
|
||||
String lon = cfgobj["#lon"];
|
||||
String tz = cfgobj["#tz"];
|
||||
|
||||
http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max,winddirection_10m_dominant&windspeed_unit=ms&timeformat=unixtime&timezone=" + tz);
|
||||
|
||||
http.setTimeout(5000);
|
||||
int httpCode = http.GET();
|
||||
|
||||
if (httpCode == 200) {
|
||||
StaticJsonDocument<500> filter;
|
||||
filter["daily"]["time"][0] = true;
|
||||
filter["daily"]["weathercode"][0] = true;
|
||||
filter["daily"]["temperature_2m_max"][0] = true;
|
||||
filter["daily"]["temperature_2m_min"][0] = true;
|
||||
filter["daily"]["precipitation_sum"][0] = true;
|
||||
filter["daily"]["windspeed_10m_max"][0] = true;
|
||||
filter["daily"]["winddirection_10m_dominant"][0] = true;
|
||||
|
||||
DynamicJsonDocument doc(2000);
|
||||
DeserializationError error = deserializeJson(doc, http.getStream());
|
||||
http.end();
|
||||
DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter));
|
||||
if (error) {
|
||||
Serial.println(F("deserializeJson() failed: "));
|
||||
Serial.println(error.c_str());
|
||||
}
|
||||
|
||||
http.begin("https://api.open-meteo.com/v1/forecast?latitude=" + doc["results"][0]["latitude"].as<String>() + "&longitude=" + doc["results"][0]["longitude"].as<String>() + "&daily=weathercode,temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max,winddirection_10m_dominant&windspeed_unit=ms&timeformat=unixtime&timezone=" + doc["results"][0]["timezone"].as<String>());
|
||||
static const char *weekday_name[] = {"ZO", "MA", "DI", "WO", "DO", "VR", "ZA"};
|
||||
|
||||
doc.clear();
|
||||
http.setTimeout(5000); // timeout in ms
|
||||
int httpCode = http.GET();
|
||||
String weatherIcons[] = {"\uf00d", "\uf00c", "\uf002", "\uf013", "\uf013", "\uf014", "-", "-", "\uf014", "-", "-",
|
||||
"\uf01a", "-", "\uf01a", "-", "\uf01a", "\uf017", "\uf017", "-", "-", "-",
|
||||
"\uf019", "-", "\uf019", "-", "\uf019", "\uf015", "\uf015", "-", "-", "-",
|
||||
"\uf01b", "-", "\uf01b", "-", "\uf01b", "-", "\uf076", "-", "-", "\uf01a",
|
||||
"\uf01a", "\uf01a", "-", "-", "\uf064", "\uf064", "-", "-", "-", "-",
|
||||
"-", "-", "-", "-", "\uf01e", "\uf01d", "-", "-", "\uf01e"};
|
||||
|
||||
if (httpCode == 200) {
|
||||
StaticJsonDocument<500> filter;
|
||||
filter["daily"]["time"][0] = true;
|
||||
filter["daily"]["weathercode"][0] = true;
|
||||
filter["daily"]["temperature_2m_max"][0] = true;
|
||||
filter["daily"]["temperature_2m_min"][0] = true;
|
||||
filter["daily"]["precipitation_sum"][0] = true;
|
||||
filter["daily"]["windspeed_10m_max"][0] = true;
|
||||
filter["daily"]["winddirection_10m_dominant"][0] = true;
|
||||
LittleFS.begin();
|
||||
tft.setTextWrap(false, false);
|
||||
|
||||
// DeserializationError error = deserializeJson(doc, http.getString(), DeserializationOption::Filter(filter));
|
||||
DeserializationError error = deserializeJson(doc, http.getString());
|
||||
if (error) {
|
||||
Serial.println(F("deserializeJson() failed: "));
|
||||
Serial.println(error.c_str());
|
||||
}
|
||||
if (taginfo->hwType == SOLUM_29_033) {
|
||||
initSprite(spr, 296, 128);
|
||||
|
||||
static const char *weekday_name[] = {"ZO", "MA", "DI", "WO", "DO", "VR", "ZA"};
|
||||
spr.setTextFont(2);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.drawString(cfgobj["location"].as<String>(), 5, 0);
|
||||
|
||||
String weatherIcons[] = {"\uf00d", "\uf00c", "\uf002", "\uf013", "\uf013", "\uf014", "-", "-", "\uf014", "-", "-",
|
||||
"\uf01a", "-", "\uf01a", "-", "\uf01a", "\uf017", "\uf017", "-", "-", "-",
|
||||
"\uf019", "-", "\uf019", "-", "\uf019", "\uf015", "\uf015", "-", "-", "-",
|
||||
"\uf01b", "-", "\uf01b", "-", "\uf01b", "-", "\uf076", "-", "-", "\uf01a",
|
||||
"\uf01a", "\uf01a", "-", "-", "\uf064", "\uf064", "-", "-", "-", "-",
|
||||
"-", "-", "-", "-", "\uf01e", "\uf01d", "-", "-", "\uf01e"};
|
||||
for (uint8_t dag = 0; dag < 5; dag++) {
|
||||
time_t weatherday = doc["daily"]["time"][dag].as<time_t>();
|
||||
struct tm *datum = localtime(&weatherday);
|
||||
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, PAL_BLACK);
|
||||
|
||||
LittleFS.begin();
|
||||
tft.setTextWrap(false, false);
|
||||
|
||||
if (taginfo->hwType == SOLUM_29_033) {
|
||||
initSprite(spr, 296, 128);
|
||||
|
||||
spr.setTextFont(2);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.drawString(location, 5, 0);
|
||||
|
||||
for (uint8_t dag = 0; dag < 5; dag++) {
|
||||
time_t weatherday = doc["daily"]["time"][dag].as<time_t>();
|
||||
struct tm *datum = localtime(&weatherday);
|
||||
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, PAL_BLACK);
|
||||
|
||||
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
|
||||
if (weathercode > 40) weathercode -= 40;
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
spr.setTextDatum(TL_DATUM);
|
||||
spr.setCursor(12 + dag * 59, 58);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
|
||||
if (weathercode > 40) weathercode -= 40;
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(17 + dag * 59, 27);
|
||||
spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]));
|
||||
spr.unloadFont();
|
||||
}
|
||||
spr.setTextDatum(TL_DATUM);
|
||||
spr.setCursor(12 + dag * 59, 58);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
|
||||
int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as<double>());
|
||||
int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as<double>());
|
||||
uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as<double>());
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(17 + dag * 59, 27);
|
||||
spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]));
|
||||
spr.unloadFont();
|
||||
|
||||
spr.loadFont("fonts/GillSC20", LittleFS);
|
||||
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
|
||||
spr.unloadFont();
|
||||
if (dag > 0) {
|
||||
for (int i = 20; i < 128; i += 3) {
|
||||
spr.drawPixel(dag * 59, i, PAL_BLACK);
|
||||
}
|
||||
int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as<double>());
|
||||
int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as<double>());
|
||||
uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as<double>());
|
||||
|
||||
spr.loadFont("fonts/GillSC20", LittleFS);
|
||||
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
|
||||
spr.unloadFont();
|
||||
if (dag > 0) {
|
||||
for (int i = 20; i < 128; i += 3) {
|
||||
spr.drawPixel(dag * 59, i, PAL_BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
|
||||
initSprite(spr, 400, 300);
|
||||
spr.setTextFont(2);
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.drawString(cfgobj["location"].as<String>(), 5, 0);
|
||||
|
||||
for (uint8_t dag = 0; dag < 5; dag++) {
|
||||
time_t weatherday = doc["daily"]["time"][dag].as<time_t>();
|
||||
struct tm *datum = localtime(&weatherday);
|
||||
drawString(spr, String(weekday_name[datum->tm_wday]), dag * 59 + 30, 18, "fonts/twbold20", TC_DATUM, PAL_BLACK);
|
||||
|
||||
uint8_t weathercode = doc["daily"]["weathercode"][dag].as<int>();
|
||||
if (weathercode > 40) weathercode -= 40;
|
||||
|
||||
spr.loadFont("fonts/weathericons30", LittleFS);
|
||||
if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 99) {
|
||||
spr.setTextColor(PAL_RED, PAL_WHITE);
|
||||
} else {
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
}
|
||||
spr.setTextDatum(TL_DATUM);
|
||||
spr.setCursor(12 + dag * 59, 58);
|
||||
spr.printToSprite(weatherIcons[weathercode]);
|
||||
|
||||
spr.setTextColor(PAL_BLACK, PAL_WHITE);
|
||||
spr.setCursor(17 + dag * 59, 27);
|
||||
spr.printToSprite(windDirectionIcon(doc["daily"]["winddirection_10m_dominant"][dag]));
|
||||
spr.unloadFont();
|
||||
|
||||
int8_t tmin = round(doc["daily"]["temperature_2m_min"][dag].as<double>());
|
||||
int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as<double>());
|
||||
uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as<double>());
|
||||
|
||||
spr.loadFont("fonts/GillSC20", LittleFS);
|
||||
drawString(spr, String(tmin) + " ", dag * 59 + 30, 108, "", TR_DATUM, (tmin < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(tmax), dag * 59 + 30, 108, "", TL_DATUM, (tmax < 0 ? PAL_RED : PAL_BLACK));
|
||||
drawString(spr, String(" ") + String(wind), dag * 59 + 30, 43, "", TL_DATUM, (wind > 5 ? PAL_RED : PAL_BLACK));
|
||||
spr.unloadFont();
|
||||
if (dag > 0) {
|
||||
for (int i = 20; i < 128; i += 3) {
|
||||
spr.drawPixel(dag * 59, i, PAL_BLACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
@@ -563,6 +676,11 @@ void drawIdentify(String &filename, tagRecord *&taginfo, imgParam &imageParams)
|
||||
initSprite(spr, 152, 152);
|
||||
drawString(spr, taginfo->alias, 5, 5, "fonts/bahnschrift20");
|
||||
drawString(spr, mac62hex(taginfo->mac), 10, 50, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
|
||||
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
initSprite(spr, 400, 300);
|
||||
drawString(spr, taginfo->alias, 20, 20, "fonts/bahnschrift20");
|
||||
drawString(spr, mac62hex(taginfo->mac), 20, 70, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
@@ -615,7 +733,6 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo,
|
||||
const char *url = URL.c_str();
|
||||
const char *tag = "title";
|
||||
const int rssArticleSize = 128;
|
||||
const int rssNumArticle = 8;
|
||||
|
||||
TFT_eSPI tft = TFT_eSPI();
|
||||
TFT_eSprite spr = TFT_eSprite(&tft);
|
||||
@@ -641,11 +758,31 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo,
|
||||
// u8g2_font_miranda_nbp_tr
|
||||
u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
|
||||
int n = reader.getArticles(url, tag, rssArticleSize, rssNumArticle);
|
||||
int n = reader.getArticles(url, tag, rssArticleSize, 8);
|
||||
for (int i = 0; i < n; i++) {
|
||||
u8f.setCursor(5, 34 + i * 13); // start writing at this position
|
||||
u8f.print(reader.itemData[i]);
|
||||
}
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
initSprite(spr, 400, 300);
|
||||
if (title == "" || title == "null") title = "RSS feed";
|
||||
drawString(spr, title, 5, 5, "fonts/bahnschrift20", TL_DATUM, PAL_RED);
|
||||
|
||||
u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
u8f.setFontMode(0);
|
||||
u8f.setFontDirection(0);
|
||||
u8f.setForegroundColor(PAL_BLACK);
|
||||
u8f.setBackgroundColor(PAL_WHITE);
|
||||
u8f.setCursor(220, 20);
|
||||
u8f.print(header);
|
||||
|
||||
u8f.setFont(u8g2_font_glasstown_nbp_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
|
||||
int n = reader.getArticles(url, tag, rssArticleSize, 10);
|
||||
for (int i = 0; i < n; i++) {
|
||||
u8f.setCursor(5, 34 + i * 14);
|
||||
u8f.print(reader.itemData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
@@ -718,13 +855,6 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo,
|
||||
u8f.setCursor(5, 16);
|
||||
u8f.print(title);
|
||||
|
||||
// u8g2_font_nine_by_five_nbp_tr
|
||||
// u8g2_font_7x14_tr
|
||||
// u8g2_font_crox1h_tr
|
||||
// u8g2_font_miranda_nbp_tr
|
||||
// u8g2_font_glasstown_nbp_tr
|
||||
// select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
|
||||
int n = doc.size();
|
||||
if (n > 7) n = 7;
|
||||
for (int i = 0; i < n; i++) {
|
||||
@@ -748,6 +878,41 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo,
|
||||
u8f.setCursor(50, 32 + i * 15);
|
||||
u8f.print(eventtitle);
|
||||
}
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
initSprite(spr, 400, 300);
|
||||
if (title == "" || title == "null") title = "Calendar";
|
||||
|
||||
u8f.setFont(u8g2_font_t0_22b_tr); // select u8g2 font from here: https://github.com/olikraus/u8g2/wiki/fntlistall
|
||||
u8f.setFontMode(0);
|
||||
u8f.setFontDirection(0);
|
||||
u8f.setForegroundColor(PAL_BLACK);
|
||||
u8f.setBackgroundColor(PAL_WHITE);
|
||||
u8f.setCursor(5, 16);
|
||||
u8f.print(title);
|
||||
|
||||
int n = doc.size();
|
||||
if (n > 8) n = 8;
|
||||
for (int i = 0; i < n; i++) {
|
||||
JsonObject obj = doc[i];
|
||||
String eventtitle = obj["title"];
|
||||
String startz = obj["start"];
|
||||
time_t starttime = obj["start"];
|
||||
time_t endtime = obj["end"];
|
||||
if (starttime < now && endtime > now) {
|
||||
u8f.setFont(u8g2_font_t0_14b_tr);
|
||||
u8f.setForegroundColor(PAL_WHITE);
|
||||
u8f.setBackgroundColor(PAL_RED);
|
||||
spr.fillRect(0, i * 15 + 21, 296, 14, PAL_RED);
|
||||
} else {
|
||||
u8f.setFont(u8g2_font_t0_14_tr);
|
||||
u8f.setForegroundColor(PAL_BLACK);
|
||||
u8f.setBackgroundColor(PAL_WHITE);
|
||||
}
|
||||
u8f.setCursor(5, 32 + i * 15);
|
||||
u8f.print(epoch_to_display(obj["start"]));
|
||||
u8f.setCursor(50, 32 + i * 15);
|
||||
u8f.print(eventtitle);
|
||||
}
|
||||
}
|
||||
|
||||
spr2buffer(spr, filename, imageParams);
|
||||
@@ -783,6 +948,12 @@ void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginf
|
||||
dotsize = int((152 - 20) / size);
|
||||
xpos = 76 - dotsize * size / 2;
|
||||
ypos = 20;
|
||||
} else if (taginfo->hwType == SOLUM_42_033) {
|
||||
initSprite(spr, 400, 300);
|
||||
drawString(spr, title, 10, 10, "fonts/bahnschrift20");
|
||||
dotsize = int((300 - 30) / size);
|
||||
xpos = 200 - dotsize * size / 2;
|
||||
ypos = 30;
|
||||
}
|
||||
|
||||
for (int y = 0; y < size; y++) {
|
||||
@@ -848,3 +1019,31 @@ String mac62hex(uint8_t *mac) {
|
||||
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
return (String)buffer;
|
||||
}
|
||||
|
||||
void getLocation(JsonObject &cfgobj) {
|
||||
HTTPClient http;
|
||||
StaticJsonDocument<1000> doc;
|
||||
|
||||
String lat = cfgobj["#lat"];
|
||||
String lon = cfgobj["#lon"];
|
||||
String tz = cfgobj["#tz"];
|
||||
|
||||
if (lat == "null" || lon == "null") {
|
||||
http.begin("https://geocoding-api.open-meteo.com/v1/search?name=" + urlEncode(cfgobj["location"]) + "&count=1");
|
||||
http.setTimeout(5000);
|
||||
int httpCode = http.GET();
|
||||
Serial.printf("Got code %d for this location\n", httpCode);
|
||||
Serial.print(http.errorToString(httpCode));
|
||||
|
||||
if (httpCode == 200) {
|
||||
DeserializationError error = deserializeJson(doc, http.getStream());
|
||||
http.end();
|
||||
lat = doc["results"][0]["latitude"].as<String>();
|
||||
lon = doc["results"][0]["longitude"].as<String>();
|
||||
tz = doc["results"][0]["timezone"].as<String>();
|
||||
cfgobj["#lat"] = lat;
|
||||
cfgobj["#lon"] = lon;
|
||||
cfgobj["#tz"] = tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "flasher.h"
|
||||
//#include "hal/wdt_hal.h"
|
||||
#include "makeimage.h"
|
||||
#include "pendingdata.h"
|
||||
#include "serial.h"
|
||||
#include "settings.h"
|
||||
#include "tag_db.h"
|
||||
@@ -17,7 +16,7 @@
|
||||
#endif
|
||||
|
||||
#include "web.h"
|
||||
|
||||
#include "udp.h"
|
||||
#include "leds.h"
|
||||
|
||||
void timeTask(void* parameter) {
|
||||
@@ -28,7 +27,7 @@ void timeTask(void* parameter) {
|
||||
if (!getLocalTime(&tm)) {
|
||||
Serial.println("Waiting for valid time from NTP-server");
|
||||
} else {
|
||||
if (now % 10 == 0) wsSendSysteminfo();
|
||||
if (now % 5 == 0) wsSendSysteminfo();
|
||||
if (now % 30 == 3) Ping();
|
||||
if (now % 300 == 6) saveDB("/current/tagDB.json");
|
||||
|
||||
@@ -51,12 +50,22 @@ void setup() {
|
||||
Serial.printf("Flash Size %d, Flash Speed %d\n", ESP.getFlashChipSize(), ESP.getFlashChipSpeed());
|
||||
Serial.println("##################################\n\n");
|
||||
|
||||
Serial.println(ESP.getFreeHeap());
|
||||
Serial.printf("Total heap: %d\n", ESP.getHeapSize());
|
||||
Serial.printf("Free heap: %d\n", ESP.getFreeHeap());
|
||||
Serial.printf("Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
Serial.printf("Free PSRAM: %d\n\n", ESP.getFreePsram());
|
||||
|
||||
Serial.printf("Total heap: %d", ESP.getHeapSize());
|
||||
Serial.printf("Free heap: %d", ESP.getFreeHeap());
|
||||
Serial.printf("Total PSRAM: %d", ESP.getPsramSize());
|
||||
Serial.printf("Free PSRAM: %d", ESP.getFreePsram());
|
||||
Serial.printf("ESP32 Partition table:\n");
|
||||
Serial.printf("| Type | Sub | Offset | Size | Label |\n");
|
||||
Serial.printf("| ---- | --- | -------- | -------- | ---------------- |\n");
|
||||
esp_partition_iterator_t pi = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
|
||||
if (pi != NULL) {
|
||||
do {
|
||||
const esp_partition_t* p = esp_partition_get(pi);
|
||||
Serial.printf("| %02x | %02x | 0x%06X | 0x%06X | %-16s |\r\n",
|
||||
p->type, p->subtype, p->address, p->size, p->label);
|
||||
} while (pi = (esp_partition_next(pi)));
|
||||
}
|
||||
|
||||
#ifdef HAS_USB
|
||||
xTaskCreate(usbFlasherTask, "flasher", 10000, NULL, configMAX_PRIORITIES - 10, NULL);
|
||||
@@ -65,11 +74,13 @@ void setup() {
|
||||
configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "0.nl.pool.ntp.org", "europe.pool.ntp.org", "time.nist.gov");
|
||||
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
|
||||
|
||||
initAPconfig();
|
||||
init_web();
|
||||
init_udp();
|
||||
|
||||
loadDB("/current/tagDB.json");
|
||||
|
||||
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);
|
||||
xTaskCreate(timeTask, "timed tasks", 10000, NULL, 2, NULL);
|
||||
xTaskCreate(ledTask, "handles leds", 5000, NULL, 10, NULL);
|
||||
|
||||
@@ -24,27 +24,29 @@ void jpg2buffer(String filein, String fileout, imgParam &imageParams) {
|
||||
filein = "/" + filein;
|
||||
}
|
||||
TJpgDec.getFsJpgSize(&w, &h, filein, LittleFS);
|
||||
if (w==0 && h==0) {
|
||||
wsErr("invalid jpg");
|
||||
return;
|
||||
}
|
||||
Serial.println("jpeg conversion " + String(w) + "x" + String(h));
|
||||
|
||||
spr.setColorDepth(8);
|
||||
spr.createSprite(w, h);
|
||||
if (spr.getPointer() == nullptr) {
|
||||
//no heap space for 8bpp, fallback to 1bpp
|
||||
wsErr("fallback to 1bpp");
|
||||
spr.setColorDepth(1);
|
||||
spr.createSprite(w, h);
|
||||
}
|
||||
if (spr.getPointer() == nullptr) {
|
||||
wsErr("Failed to create sprite in jpg2buffer");
|
||||
}
|
||||
spr.fillSprite(TFT_WHITE);
|
||||
TJpgDec.drawFsJpg(0, 0, filein, LittleFS);
|
||||
} else {
|
||||
spr.fillSprite(TFT_WHITE);
|
||||
TJpgDec.drawFsJpg(0, 0, filein, LittleFS);
|
||||
|
||||
spr2buffer(spr, fileout, imageParams);
|
||||
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;
|
||||
spr2buffer(spr, fileout, imageParams);
|
||||
spr.deleteSprite();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct Color {
|
||||
@@ -76,7 +78,7 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
bool dither = true, rotated = false;
|
||||
long bufw = spr.width(), bufh = spr.height();
|
||||
|
||||
if (bufw > bufh) {
|
||||
if (bufw > bufh && bufw!=400 && bufh!=300) {
|
||||
rotated = true;
|
||||
bufw = spr.height();
|
||||
bufh = spr.width();
|
||||
@@ -95,12 +97,12 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
};
|
||||
int num_colors = palette.size();
|
||||
Color color;
|
||||
Error error_bufferold[bufw];
|
||||
Error error_buffernew[bufw];
|
||||
Error *error_bufferold = new Error[bufw];
|
||||
Error *error_buffernew = new Error[bufw];
|
||||
|
||||
memset(error_bufferold, 0, sizeof(error_bufferold));
|
||||
memset(error_bufferold, 0, bufw * sizeof(Error));
|
||||
for (uint16_t y = 0; y < bufh; y++) {
|
||||
memset(error_buffernew, 0, sizeof(error_buffernew));
|
||||
memset(error_buffernew, 0, bufw * sizeof(Error));
|
||||
for (uint16_t x = 0; x < bufw; x++) {
|
||||
if (rotated) {
|
||||
color = Color(spr.readPixel(bufh - 1 - y, x));
|
||||
@@ -127,30 +129,34 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
|
||||
redBuffer[byteIndex] |= (1 << bitIndex);
|
||||
}
|
||||
|
||||
Error error = {
|
||||
((float)color.r + error_bufferold[x].r - palette[best_color_index].r) / 16.0f,
|
||||
((float)color.g + error_bufferold[x].g - palette[best_color_index].g) / 16.0f,
|
||||
((float)color.b + error_bufferold[x].b - palette[best_color_index].b) / 16.0f};
|
||||
if (imageParams.dither) {
|
||||
Error error = {
|
||||
((float)color.r + error_bufferold[x].r - palette[best_color_index].r) / 16.0f,
|
||||
((float)color.g + error_bufferold[x].g - palette[best_color_index].g) / 16.0f,
|
||||
((float)color.b + error_bufferold[x].b - palette[best_color_index].b) / 16.0f};
|
||||
|
||||
error_buffernew[x].r += error.r * 5.0f;
|
||||
error_buffernew[x].g += error.g * 5.0f;
|
||||
error_buffernew[x].b += error.b * 5.0f;
|
||||
if (x > 0) {
|
||||
error_buffernew[x - 1].r += error.r * 3.0f;
|
||||
error_buffernew[x - 1].g += error.g * 3.0f;
|
||||
error_buffernew[x - 1].b += error.b * 3.0f;
|
||||
}
|
||||
if (x < bufw - 1) {
|
||||
error_buffernew[x + 1].r += error.r * 1.0f;
|
||||
error_buffernew[x + 1].g += error.g * 1.0f;
|
||||
error_buffernew[x + 1].b += error.b * 1.0f;
|
||||
error_bufferold[x + 1].r += error.r * 7.0f;
|
||||
error_bufferold[x + 1].g += error.g * 7.0f;
|
||||
error_bufferold[x + 1].b += error.b * 7.0f;
|
||||
error_buffernew[x].r += error.r * 5.0f;
|
||||
error_buffernew[x].g += error.g * 5.0f;
|
||||
error_buffernew[x].b += error.b * 5.0f;
|
||||
if (x > 0) {
|
||||
error_buffernew[x - 1].r += error.r * 3.0f;
|
||||
error_buffernew[x - 1].g += error.g * 3.0f;
|
||||
error_buffernew[x - 1].b += error.b * 3.0f;
|
||||
}
|
||||
if (x < bufw - 1) {
|
||||
error_buffernew[x + 1].r += error.r * 1.0f;
|
||||
error_buffernew[x + 1].g += error.g * 1.0f;
|
||||
error_buffernew[x + 1].b += error.b * 1.0f;
|
||||
error_bufferold[x + 1].r += error.r * 7.0f;
|
||||
error_bufferold[x + 1].g += error.g * 7.0f;
|
||||
error_bufferold[x + 1].b += error.b * 7.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
memcpy(error_bufferold, error_buffernew, sizeof(error_buffernew));
|
||||
memcpy(error_bufferold, error_buffernew, bufw * sizeof(Error));
|
||||
}
|
||||
delete[] error_buffernew;
|
||||
delete[] error_bufferold;
|
||||
|
||||
f_out.write(blackBuffer, bufferSize);
|
||||
if (imageParams.hasRed) f_out.write(redBuffer, bufferSize);
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
#include "newproto.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <FS.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <LittleFS.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 "udp.h"
|
||||
#include "web.h"
|
||||
|
||||
extern uint16_t sendBlock(const void* data, const uint16_t len);
|
||||
extern UDPcomm udpsync;
|
||||
|
||||
void addCRC(void* p, uint8_t len) {
|
||||
uint8_t total = 0;
|
||||
@@ -34,6 +38,7 @@ bool checkCRC(void* p, uint8_t len) {
|
||||
uint8_t* getDataForFile(fs::File* file) {
|
||||
uint8_t* ret = nullptr;
|
||||
ret = (uint8_t*)malloc(file->size());
|
||||
Serial.println("malloc " + String(file->size()));
|
||||
if (ret) {
|
||||
file->seek(0);
|
||||
file->readBytes((char*)ret, file->size());
|
||||
@@ -43,36 +48,47 @@ uint8_t* getDataForFile(fs::File* file) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void prepareCancelPending(uint64_t ver) {
|
||||
void prepareCancelPending(uint8_t dst[8]) {
|
||||
struct pendingData pending = {0};
|
||||
pending.availdatainfo.dataVer = ver;
|
||||
memcpy(pending.targetMac, dst, 8);
|
||||
sendCancelPending(&pending);
|
||||
|
||||
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) {
|
||||
wsErr("Tag not found, this shouldn't happen.");
|
||||
return;
|
||||
}
|
||||
clearPending(taginfo);
|
||||
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
|
||||
void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin) {
|
||||
if (nextCheckin > MIN_RESPONSE_TIME) {
|
||||
// to prevent very long sleeps of the tag
|
||||
nextCheckin = MIN_RESPONSE_TIME;
|
||||
if (nextCheckin > MIN_RESPONSE_TIME) nextCheckin = MIN_RESPONSE_TIME;
|
||||
if (nextCheckin > 0) {
|
||||
struct pendingData pending = {0};
|
||||
memcpy(pending.targetMac, dst, 8);
|
||||
pending.availdatainfo.dataType = DATATYPE_NOUPDATE;
|
||||
pending.availdatainfo.nextCheckIn = nextCheckin;
|
||||
pending.attemptsLeft = 10 + MIN_RESPONSE_TIME;
|
||||
|
||||
char buffer[64];
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)dst));
|
||||
sprintf(buffer, "< %02X%02X%02X%02X%02X%02X idle req\n\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
Serial.print(buffer);
|
||||
|
||||
sendDataAvail(&pending);
|
||||
}
|
||||
struct pendingData pending = {0};
|
||||
memcpy(pending.targetMac, dst, 8);
|
||||
pending.availdatainfo.dataType = DATATYPE_NOUPDATE;
|
||||
pending.availdatainfo.nextCheckIn = nextCheckin;
|
||||
pending.attemptsLeft = 10 + MIN_RESPONSE_TIME;
|
||||
|
||||
char buffer[64];
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)dst));
|
||||
Serial.print(buffer);
|
||||
|
||||
sendDataAvail(&pending);
|
||||
}
|
||||
|
||||
bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin) {
|
||||
if (nextCheckin > MIN_RESPONSE_TIME) {
|
||||
//to prevent very long sleeps of the tag
|
||||
nextCheckin = MIN_RESPONSE_TIME;
|
||||
}
|
||||
if (nextCheckin > MIN_RESPONSE_TIME) nextCheckin = MIN_RESPONSE_TIME;
|
||||
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)dst));
|
||||
@@ -138,18 +154,22 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
|
||||
LittleFS.rename(*filename, dst_path);
|
||||
*filename = String(dst_path);
|
||||
|
||||
wsLog("new image pending: " + String(dst_path));
|
||||
wsLog("new image: " + String(dst_path));
|
||||
time_t now;
|
||||
time(&now);
|
||||
taginfo->pending = true;
|
||||
taginfo->expectedNextCheckin = now + nextCheckin * 60 + 60;
|
||||
taginfo->filename = *filename;
|
||||
taginfo->len = filesize;
|
||||
clearPending(taginfo);
|
||||
taginfo->pending = true;
|
||||
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
|
||||
|
||||
} else {
|
||||
wsLog("firmware upload pending");
|
||||
taginfo->filename = *filename;
|
||||
taginfo->len = filesize;
|
||||
}
|
||||
|
||||
// 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);
|
||||
pending.availdatainfo.dataType = dataType;
|
||||
@@ -158,66 +178,121 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
|
||||
pending.availdatainfo.dataTypeArgument = lut;
|
||||
pending.availdatainfo.nextCheckIn = nextCheckin;
|
||||
pending.attemptsLeft = attempts;
|
||||
sendDataAvail(&pending);
|
||||
|
||||
// data for the cache on the esp32; needs to hold the data longer than the maximum timeout on the AP
|
||||
pendingdata* pendinginfo = nullptr;
|
||||
pendinginfo = new pendingdata;
|
||||
pendinginfo->filename = *filename;
|
||||
pendinginfo->ver = pending.availdatainfo.dataVer;
|
||||
pendinginfo->len = pending.availdatainfo.dataSize;
|
||||
pendinginfo->data = nullptr;
|
||||
pendinginfo->timeout = PENDING_TIMEOUT;
|
||||
pendingfiles.push_back(pendinginfo);
|
||||
if (taginfo->isExternal == false) {
|
||||
sendDataAvail(&pending);
|
||||
} else {
|
||||
udpsync.netSendDataAvail(&pending);
|
||||
}
|
||||
|
||||
wsSendTaginfo(mac);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void prepareExternalDataAvail(struct pendingData* pending, IPAddress remoteIP) {
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)pending->targetMac));
|
||||
uint8_t mac[6];
|
||||
memcpy(mac, src + 2, sizeof(mac));
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (taginfo->isExternal == false) {
|
||||
LittleFS.begin();
|
||||
|
||||
char buffer[64];
|
||||
sprintf(buffer, "%02X%02X%02X%02X%02X%02X\0", src[2], src[3], src[4], src[5], src[6], src[7]);
|
||||
String filename = "/current/" + (String)buffer + ".pending";
|
||||
String imageUrl = "http://" + remoteIP.toString() + filename;
|
||||
wsLog("GET " + imageUrl);
|
||||
HTTPClient http;
|
||||
http.begin(imageUrl);
|
||||
int httpCode = http.GET();
|
||||
if (httpCode == 200) {
|
||||
File file = LittleFS.open(filename, "w");
|
||||
http.writeToStream(&file);
|
||||
file.close();
|
||||
}
|
||||
http.end();
|
||||
|
||||
fs::File file = LittleFS.open(filename);
|
||||
uint32_t filesize = file.size();
|
||||
if (filesize == 0) {
|
||||
file.close();
|
||||
wsErr("File has size 0. " + filename);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t md5bytes[16];
|
||||
{
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.addStream(file, filesize);
|
||||
md5.calculate();
|
||||
md5.getBytes(md5bytes);
|
||||
}
|
||||
|
||||
file.close();
|
||||
sendDataAvail(pending);
|
||||
|
||||
taginfo->filename = filename;
|
||||
taginfo->len = filesize;
|
||||
clearPending(taginfo);
|
||||
taginfo->pending = true;
|
||||
memcpy(taginfo->md5pending, md5bytes, sizeof(md5bytes));
|
||||
taginfo->contentMode = 12;
|
||||
taginfo->nextupdate = 3216153600;
|
||||
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
}
|
||||
|
||||
void processBlockRequest(struct espBlockRequest* br) {
|
||||
if (!checkCRC(br, sizeof(struct espBlockRequest))) {
|
||||
Serial.print("Failed CRC on a blockrequest received by the AP");
|
||||
return;
|
||||
}
|
||||
|
||||
pendingdata* pd = pendingdata::findByVer(br->ver);
|
||||
if (pd == nullptr) {
|
||||
prepareCancelPending(br->ver);
|
||||
Serial.printf("Couldn't find pendingdata info for ver %llu", br->ver);
|
||||
uint8_t src[8];
|
||||
*((uint64_t*)src) = swap64(*((uint64_t*)br->src));
|
||||
uint8_t mac[6];
|
||||
memcpy(mac, src + 2, sizeof(mac));
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo == nullptr) {
|
||||
prepareCancelPending(br->src);
|
||||
Serial.printf("blockrequest: couldn't find taginfo %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", br->src[7], br->src[6], br->src[5], br->src[4], br->src[3], br->src[2], br->src[1], br->src[0]);
|
||||
return;
|
||||
} else {
|
||||
if (pd->data == nullptr) {
|
||||
// not cached. open file, cache the data
|
||||
fs::File file = LittleFS.open(pd->filename);
|
||||
if (!file) {
|
||||
Serial.print("Dunno how this happened... File pending but deleted in the meantime?\n");
|
||||
prepareCancelPending(br->ver);
|
||||
return;
|
||||
}
|
||||
pd->data = getDataForFile(&file);
|
||||
pd->datatimeout = PENDING_DATA_TIMEOUT;
|
||||
file.close();
|
||||
} else {
|
||||
// file is already cached, refresh the timeout
|
||||
//pd->datatimeout = PENDING_DATA_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
if (taginfo->data == nullptr) {
|
||||
// not cached. open file, cache the data
|
||||
fs::File file = LittleFS.open(taginfo->filename);
|
||||
if (!file) {
|
||||
Serial.print("Dunno how this happened... File pending but deleted in the meantime?\n");
|
||||
prepareCancelPending(br->src);
|
||||
return;
|
||||
}
|
||||
taginfo->data = getDataForFile(&file);
|
||||
file.close();
|
||||
}
|
||||
|
||||
// check if we're not exceeding max blocks (to prevent sendBlock from exceeding its boundary)
|
||||
uint8_t totalblocks = (pd->len / BLOCK_DATA_SIZE);
|
||||
if (pd->len % BLOCK_DATA_SIZE) totalblocks++;
|
||||
uint8_t totalblocks = (taginfo->len / BLOCK_DATA_SIZE);
|
||||
if (taginfo->len % BLOCK_DATA_SIZE) totalblocks++;
|
||||
if (br->blockId >= totalblocks) {
|
||||
br->blockId = totalblocks - 1;
|
||||
}
|
||||
|
||||
uint32_t len = pd->len - (BLOCK_DATA_SIZE * br->blockId);
|
||||
uint32_t len = taginfo->len - (BLOCK_DATA_SIZE * br->blockId);
|
||||
if (len > BLOCK_DATA_SIZE) len = BLOCK_DATA_SIZE;
|
||||
uint16_t checksum = sendBlock(pd->data + (br->blockId * BLOCK_DATA_SIZE), len);
|
||||
uint16_t checksum = sendBlock(taginfo->data + (br->blockId * BLOCK_DATA_SIZE), len);
|
||||
char buffer[150];
|
||||
sprintf(buffer, "< Block Request received for file %s block %d, len %d checksum %u\0", pd->filename.c_str(), br->blockId, len, checksum);
|
||||
sprintf(buffer, "< Block Request received for file %s block %d, len %d checksum %u\0", taginfo->filename.c_str(), br->blockId, len, checksum);
|
||||
wsLog((String)buffer);
|
||||
Serial.printf("< Block Request received for MD5 %llu, file %s block %d, len %d checksum %u", br->ver, pd->filename.c_str(), br->blockId, len, checksum);
|
||||
Serial.printf(" from mac %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X\n", br->src[7],br->src[6],br->src[5],br->src[4],br->src[3],br->src[2],br->src[1],br->src[0]);
|
||||
Serial.printf("< Block Request received for file %s block %d, len %d checksum %u\0", taginfo->filename.c_str(), br->blockId, len, checksum);
|
||||
}
|
||||
|
||||
void processXferComplete(struct espXferComplete* xfc) {
|
||||
@@ -239,28 +314,15 @@ void processXferComplete(struct espXferComplete* xfc) {
|
||||
}
|
||||
if (LittleFS.exists(src_path)) {
|
||||
LittleFS.rename(src_path, dst_path);
|
||||
} else {
|
||||
wsErr("hm, weird, no pending image found after xfercomplete.");
|
||||
}
|
||||
}
|
||||
|
||||
time_t now;
|
||||
time(&now);
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo != nullptr) {
|
||||
|
||||
uint16_t minutesUntilNextUpdate = 0;
|
||||
if (taginfo->nextupdate > now + 2 * 60) {
|
||||
minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
|
||||
if (minutesUntilNextUpdate > MIN_RESPONSE_TIME) minutesUntilNextUpdate = MIN_RESPONSE_TIME;
|
||||
taginfo->expectedNextCheckin = now + 60 * minutesUntilNextUpdate + 60;
|
||||
if (minutesUntilNextUpdate > 1) prepareIdleReq (xfc->src, minutesUntilNextUpdate);
|
||||
} else {
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
}
|
||||
|
||||
taginfo->pending = false;
|
||||
memcpy(taginfo->md5, taginfo->md5pending, sizeof(taginfo->md5pending));
|
||||
clearPending(taginfo);
|
||||
}
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
@@ -281,8 +343,8 @@ void processXferTimeout(struct espXferComplete* xfc) {
|
||||
taginfo = tagRecord::findByMAC(mac);
|
||||
if (taginfo != nullptr) {
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
taginfo->pending = false;
|
||||
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
|
||||
clearPending(taginfo);
|
||||
}
|
||||
wsSendTaginfo(mac);
|
||||
}
|
||||
@@ -305,18 +367,21 @@ void processDataReq(struct espAvailDataReq* eadr) {
|
||||
}
|
||||
time_t now;
|
||||
time(&now);
|
||||
taginfo->lastseen = now;
|
||||
|
||||
uint16_t minutesUntilNextUpdate = 0;
|
||||
if (taginfo->nextupdate > now + 2 * 60) {
|
||||
minutesUntilNextUpdate = (taginfo->nextupdate - now) / 60;
|
||||
if (minutesUntilNextUpdate > MIN_RESPONSE_TIME) minutesUntilNextUpdate = MIN_RESPONSE_TIME;
|
||||
taginfo->expectedNextCheckin = now + 60 * minutesUntilNextUpdate + 60;
|
||||
if (minutesUntilNextUpdate > 1 && taginfo->pending == false) prepareIdleReq(eadr->src, minutesUntilNextUpdate);
|
||||
if (eadr->src[7] == 0xFF) {
|
||||
taginfo->isExternal = true;
|
||||
} else {
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
taginfo->isExternal = false;
|
||||
}
|
||||
|
||||
if (taginfo->pendingIdle == 0) {
|
||||
taginfo->expectedNextCheckin = now + 60;
|
||||
} else {
|
||||
taginfo->expectedNextCheckin = now + 60 * taginfo->pendingIdle;
|
||||
taginfo->pendingIdle = 0;
|
||||
}
|
||||
taginfo->lastseen = now;
|
||||
|
||||
if (eadr->adr.lastPacketRSSI != 0) {
|
||||
taginfo->LQI = eadr->adr.lastPacketLQI;
|
||||
taginfo->hwType = eadr->adr.hwType;
|
||||
@@ -343,11 +408,24 @@ void refreshAllPending() {
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
if (taginfo->pending) {
|
||||
taginfo->pending = false;
|
||||
clearPending(taginfo);
|
||||
taginfo->nextupdate = 0;
|
||||
memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
|
||||
memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
|
||||
wsSendTaginfo(taginfo->mac);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void setAPchannel() {
|
||||
if (APconfig["channel"].as<int>() == 0) {
|
||||
// trigger channel autoselect
|
||||
UDPcomm udpsync;
|
||||
udpsync.getAPList();
|
||||
} else {
|
||||
if (curChannel.channel != APconfig["channel"].as<int>()) {
|
||||
curChannel.channel = APconfig["channel"].as<int>();
|
||||
if (version) sendChannelPower(&curChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#include "pendingdata.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "settings.h"
|
||||
|
||||
std::vector<pendingdata*> pendingfiles;
|
||||
|
||||
void garbageCollection(void* parameter) {
|
||||
while (1) {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
pendingdata::garbageCollection();
|
||||
}
|
||||
}
|
||||
|
||||
pendingdata* pendingdata::findByVer(uint64_t ver) {
|
||||
for (int16_t c = 0; c < pendingfiles.size(); c++) {
|
||||
pendingdata* pending = nullptr;
|
||||
pending = pendingfiles.at(c);
|
||||
if (pending->ver == ver) {
|
||||
return pending;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void pendingdata::garbageCollection() {
|
||||
for (int16_t c = 0; c < pendingfiles.size(); c++) {
|
||||
pendingdata* pending = pendingfiles.at(c);
|
||||
if (pending->datatimeout > 1) {
|
||||
pending->datatimeout--;
|
||||
} else if (pending->datatimeout == 1) {
|
||||
if (pending->data != nullptr) {
|
||||
free(pending->data);
|
||||
}
|
||||
pending->data = nullptr;
|
||||
pending->datatimeout == 0;
|
||||
}
|
||||
|
||||
if (pending->timeout) {
|
||||
pending->timeout--;
|
||||
} else {
|
||||
if (pending->data != nullptr) {
|
||||
free(pending->data);
|
||||
}
|
||||
pending->data = nullptr;
|
||||
delete pending;
|
||||
pendingfiles.erase(pendingfiles.begin() + c);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "newproto.h"
|
||||
#include "powermgt.h"
|
||||
#include "settings.h"
|
||||
#include "udp.h"
|
||||
#include "web.h"
|
||||
#include "zbs_interface.h"
|
||||
|
||||
@@ -24,6 +25,7 @@
|
||||
|
||||
QueueHandle_t rxCmdQueue;
|
||||
SemaphoreHandle_t txActive;
|
||||
extern UDPcomm udpsync;
|
||||
|
||||
#define CMD_REPLY_WAIT 0x00
|
||||
#define CMD_REPLY_ACK 0x01
|
||||
@@ -33,6 +35,9 @@ volatile uint8_t cmdReplyValue = CMD_REPLY_WAIT;
|
||||
|
||||
#define AP_SERIAL_PORT Serial1
|
||||
|
||||
uint8_t channelList[6];
|
||||
struct espSetChannelPower curChannel = {0, 11, 8};
|
||||
|
||||
bool txStart() {
|
||||
while (1) {
|
||||
if (xPortInIsrContext()) {
|
||||
@@ -151,9 +156,9 @@ void sendCancelPending(struct pendingData* pending) {
|
||||
AP_SERIAL_PORT.write(((uint8_t*)pending)[c]);
|
||||
}
|
||||
if (waitCmdReply()) goto cxdsent;
|
||||
AP_SERIAL_PORT.printf("CXD send failed in try %d\n", attempt);
|
||||
Serial.printf("CXD send failed in try %d\n", attempt);
|
||||
}
|
||||
AP_SERIAL_PORT.print("CXD failed to send...\n");
|
||||
Serial.print("CXD failed to send...\n");
|
||||
txEnd();
|
||||
return;
|
||||
cxdsent:
|
||||
@@ -170,9 +175,9 @@ bool sendChannelPower(struct espSetChannelPower* scp) {
|
||||
AP_SERIAL_PORT.write(((uint8_t*)scp)[c]);
|
||||
}
|
||||
if (waitCmdReply()) goto scpSent;
|
||||
AP_SERIAL_PORT.printf("SCP send failed in try %d\n", attempt);
|
||||
Serial.printf("SCP send failed in try %d\n", attempt);
|
||||
}
|
||||
AP_SERIAL_PORT.print("SCP failed to send...\n");
|
||||
Serial.print("SCP failed to send...\n");
|
||||
txEnd();
|
||||
return false;
|
||||
scpSent:
|
||||
@@ -216,12 +221,15 @@ void rxCmdProcessor(void* parameter) {
|
||||
break;
|
||||
case RX_CMD_ADR:
|
||||
processDataReq((struct espAvailDataReq*)rxcmd->data);
|
||||
udpsync.netProcessDataReq((struct espAvailDataReq*)rxcmd->data);
|
||||
break;
|
||||
case RX_CMD_XFC:
|
||||
processXferComplete((struct espXferComplete*)rxcmd->data);
|
||||
udpsync.netProcessXferComplete((struct espXferComplete*)rxcmd->data);
|
||||
break;
|
||||
case RX_CMD_XTO:
|
||||
processXferTimeout((struct espXferComplete*)rxcmd->data);
|
||||
udpsync.netProcessXferTimeout((struct espXferComplete*)rxcmd->data);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -397,6 +405,8 @@ void zbsRxTask(void* parameter) {
|
||||
vTaskDelay(2 / portTICK_PERIOD_MS);
|
||||
rampTagPower(FLASHER_AP_POWER, true);
|
||||
wsErr("The AP tag crashed. Restarting tag, regenerating all pending info.");
|
||||
vTaskDelay(3000 / portTICK_PERIOD_MS);
|
||||
sendChannelPower(&curChannel);
|
||||
refreshAllPending();
|
||||
}
|
||||
} else {
|
||||
@@ -418,9 +428,8 @@ void zbsRxTask(void* parameter) {
|
||||
} else {
|
||||
Serial.println("Failed to update version on the AP :(");
|
||||
}
|
||||
} else if (!fsversion) {
|
||||
Serial.println("No ZBS/Zigbee FW binary found on SPIFFS, please upload a zigbeebase000X.bin - format binary to enable flashing");
|
||||
}
|
||||
sendChannelPower(&curChannel);
|
||||
firstrun = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <FS.h>
|
||||
#include <vector>
|
||||
|
||||
#include "LittleFS.h"
|
||||
|
||||
std::vector<tagRecord*> tagDB;
|
||||
DynamicJsonDocument APconfig(150);
|
||||
|
||||
tagRecord* tagRecord::findByMAC(uint8_t mac[6]) {
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
@@ -25,6 +26,10 @@ bool deleteRecord(uint8_t mac[6]) {
|
||||
tagRecord* tag = nullptr;
|
||||
tag = tagDB.at(c);
|
||||
if (memcmp(tag->mac, mac, 6) == 0) {
|
||||
if (tag->data != nullptr) {
|
||||
free(tag->data);
|
||||
}
|
||||
tag->data = nullptr;
|
||||
delete tagDB[c];
|
||||
tagDB.erase(tagDB.begin() + c);
|
||||
return true;
|
||||
@@ -88,6 +93,7 @@ void fillNode(JsonObject &tag, tagRecord* &taginfo) {
|
||||
tag["wakeupReason"] = taginfo->wakeupReason;
|
||||
tag["capabilities"] = taginfo->capabilities;
|
||||
tag["modecfgjson"] = taginfo->modeConfigJson;
|
||||
tag["isexternal"] = taginfo->isExternal;
|
||||
}
|
||||
|
||||
void saveDB(String filename) {
|
||||
@@ -180,6 +186,7 @@ void loadDB(String filename) {
|
||||
taginfo->wakeupReason = tag["wakeupReason"];
|
||||
taginfo->capabilities = tag["capabilities"];
|
||||
taginfo->modeConfigJson = tag["modecfgjson"].as<String>();
|
||||
taginfo->isExternal = tag["isexternal"].as<bool>();
|
||||
}
|
||||
} else {
|
||||
Serial.print(F("deserializeJson() failed: "));
|
||||
@@ -196,3 +203,47 @@ void loadDB(String filename) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t getTagCount() {
|
||||
uint8_t tagcount = 0;
|
||||
for (int16_t c = 0; c < tagDB.size(); c++) {
|
||||
tagRecord* taginfo = nullptr;
|
||||
taginfo = tagDB.at(c);
|
||||
if (taginfo->isExternal == false) tagcount++;
|
||||
}
|
||||
return tagcount;
|
||||
}
|
||||
|
||||
void clearPending(tagRecord* taginfo) {
|
||||
if (taginfo->data != nullptr) {
|
||||
free(taginfo->data);
|
||||
Serial.println("free taginfo->data");
|
||||
taginfo->data = nullptr;
|
||||
}
|
||||
taginfo->pending = false;
|
||||
}
|
||||
|
||||
void initAPconfig() {
|
||||
LittleFS.begin(true);
|
||||
File configFile = LittleFS.open("/current/apconfig.json", "r");
|
||||
if (!configFile) {
|
||||
//default values'
|
||||
Serial.println("APconfig not found");
|
||||
APconfig["channel"] = 0;
|
||||
APconfig["alias"] = String();
|
||||
return;
|
||||
}
|
||||
DeserializationError error = deserializeJson(APconfig, configFile);
|
||||
if (error) {
|
||||
configFile.close();
|
||||
Serial.println("apconfig.json file corrupted, or not enough space in apconfig to hold it");
|
||||
return;
|
||||
}
|
||||
configFile.close();
|
||||
}
|
||||
|
||||
void saveAPconfig() {
|
||||
fs::File configFile = LittleFS.open("/current/apconfig.json", "w");
|
||||
serializeJson(APconfig, configFile);
|
||||
configFile.close();
|
||||
}
|
||||
168
ESP32_AP-Flasher/src/udp.cpp
Normal file
168
ESP32_AP-Flasher/src/udp.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "AsyncUDP.h"
|
||||
#include "commstructs.h"
|
||||
#include "newproto.h"
|
||||
#include "tag_db.h"
|
||||
#include "web.h"
|
||||
#include "serial.h"
|
||||
|
||||
#define UDPIP IPAddress(239, 10, 0, 1)
|
||||
#define UDPPORT 16033
|
||||
|
||||
UDPcomm udpsync;
|
||||
|
||||
extern uint8_t channelList[6];
|
||||
extern espSetChannelPower curChannel;
|
||||
extern uint16_t version;
|
||||
|
||||
void init_udp() {
|
||||
udpsync.init();
|
||||
}
|
||||
|
||||
UDPcomm::UDPcomm() {
|
||||
// Constructor
|
||||
}
|
||||
|
||||
UDPcomm::~UDPcomm() {
|
||||
// Destructor
|
||||
}
|
||||
|
||||
void UDPcomm::init() {
|
||||
if (udp.listenMulticast(UDPIP, UDPPORT)) {
|
||||
udp.onPacket([this](AsyncUDPPacket packet) {
|
||||
if (packet.remoteIP() != WiFi.localIP()) {
|
||||
this->processPacket(packet);
|
||||
}
|
||||
});
|
||||
}
|
||||
setAPchannel();
|
||||
}
|
||||
|
||||
void UDPcomm::processPacket(AsyncUDPPacket packet) {
|
||||
switch (packet.data()[0]) {
|
||||
case PKT_AVAIL_DATA_INFO: {
|
||||
espAvailDataReq* adr = (espAvailDataReq*)&packet.data()[1];
|
||||
adr->src[7] = 0xFF;
|
||||
processDataReq(adr);
|
||||
break;
|
||||
}
|
||||
case PKT_XFER_COMPLETE: {
|
||||
espXferComplete* xfc = (espXferComplete*)&packet.data()[1];
|
||||
processXferComplete(xfc);
|
||||
break;
|
||||
}
|
||||
case PKT_XFER_TIMEOUT: {
|
||||
espXferComplete* xfc = (espXferComplete*)&packet.data()[1];
|
||||
processXferTimeout(xfc);
|
||||
break;
|
||||
}
|
||||
case PKT_AVAIL_DATA_REQ: {
|
||||
pendingData* pending = (pendingData*)&packet.data()[1];
|
||||
prepareExternalDataAvail(pending, packet.remoteIP());
|
||||
break;
|
||||
}
|
||||
case PKT_APLIST_REQ: {
|
||||
IPAddress senderIP = packet.remoteIP();
|
||||
|
||||
APlist APitem;
|
||||
APitem.src = WiFi.localIP();
|
||||
strcpy(APitem.alias, APconfig["alias"]);
|
||||
APitem.channelId = curChannel.channel;
|
||||
APitem.tagCount = getTagCount();
|
||||
APitem.version = version;
|
||||
|
||||
uint8_t buffer[sizeof(struct APlist) + 1];
|
||||
buffer[0] = PKT_APLIST_REPLY;
|
||||
memcpy(buffer + 1, &APitem, sizeof(struct APlist));
|
||||
udp.writeTo(buffer, sizeof(buffer), senderIP, UDPPORT);
|
||||
break;
|
||||
}
|
||||
case PKT_APLIST_REPLY: {
|
||||
APlist* APreply = (APlist*)&packet.data()[1];
|
||||
//remove active channel from list
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (channelList[i] == APreply->channelId) channelList[i] = 0;
|
||||
}
|
||||
wsSendAPitem(APreply);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void autoselect(void* pvParameters) {
|
||||
// reset channel list
|
||||
uint8_t values[] = {11, 15, 20, 25, 26, 27};
|
||||
memcpy(channelList, values, sizeof(values));
|
||||
// wait 5s for channelList to collect all AP's
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
|
||||
curChannel.channel = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
if (channelList[i] > 0) {
|
||||
curChannel.channel = channelList[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (curChannel.channel == 0) {
|
||||
curChannel.channel = 11;
|
||||
}
|
||||
APconfig["channel"] = curChannel.channel;
|
||||
do {
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
} while (!version);
|
||||
|
||||
sendChannelPower(&curChannel);
|
||||
saveAPconfig();
|
||||
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void UDPcomm::getAPList() {
|
||||
APlist APitem;
|
||||
APitem.src = WiFi.localIP();
|
||||
strcpy(APitem.alias, APconfig["alias"]);
|
||||
APitem.channelId = curChannel.channel;
|
||||
APitem.tagCount = getTagCount();
|
||||
APitem.version = version;
|
||||
wsSendAPitem(&APitem);
|
||||
|
||||
if (APconfig["channel"].as<int>() == 0) {
|
||||
xTaskCreate(autoselect, "autoselect", 10000, NULL, configMAX_PRIORITIES - 10, NULL);
|
||||
}
|
||||
|
||||
uint8_t buffer[sizeof(struct APlist) + 1];
|
||||
buffer[0] = PKT_APLIST_REQ;
|
||||
memcpy(buffer + 1, &APitem, sizeof(struct APlist));
|
||||
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
|
||||
}
|
||||
|
||||
void UDPcomm::netProcessDataReq(struct espAvailDataReq* eadr) {
|
||||
uint8_t buffer[sizeof(struct espAvailDataReq) + 1];
|
||||
buffer[0] = PKT_AVAIL_DATA_INFO;
|
||||
memcpy(buffer + 1, eadr, sizeof(struct espAvailDataReq));
|
||||
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
|
||||
}
|
||||
|
||||
void UDPcomm::netProcessXferComplete(struct espXferComplete* xfc) {
|
||||
uint8_t buffer[sizeof(struct espXferComplete) + 1];
|
||||
buffer[0] = PKT_XFER_COMPLETE;
|
||||
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
|
||||
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
|
||||
}
|
||||
|
||||
void UDPcomm::netProcessXferTimeout(struct espXferComplete* xfc) {
|
||||
uint8_t buffer[sizeof(struct espXferComplete) + 1];
|
||||
buffer[0] = PKT_XFER_TIMEOUT;
|
||||
memcpy(buffer + 1, xfc, sizeof(struct espXferComplete));
|
||||
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
|
||||
}
|
||||
|
||||
void UDPcomm::netSendDataAvail(struct pendingData* pending) {
|
||||
uint8_t buffer[sizeof(struct pendingData) + 1];
|
||||
buffer[0] = PKT_AVAIL_DATA_REQ;
|
||||
memcpy(buffer + 1, pending, sizeof(struct pendingData));
|
||||
udp.writeTo(buffer, sizeof(buffer), UDPIP, UDPPORT);
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "newproto.h"
|
||||
#include "settings.h"
|
||||
#include "tag_db.h"
|
||||
#include "udp.h"
|
||||
|
||||
extern uint8_t data_to_send[];
|
||||
|
||||
@@ -168,6 +169,24 @@ void wsSendTaginfo(uint8_t mac[6]) {
|
||||
xSemaphoreGive(wsMutex);
|
||||
}
|
||||
|
||||
void wsSendAPitem(struct APlist* apitem) {
|
||||
DynamicJsonDocument doc(250);
|
||||
JsonObject ap = doc.createNestedObject("apitem");
|
||||
|
||||
char version_str[6];
|
||||
sprintf(version_str, "%04X", apitem->version);
|
||||
|
||||
ap["ip"] = ((IPAddress)apitem->src).toString();
|
||||
ap["alias"] = apitem->alias;
|
||||
ap["count"] = apitem->tagCount;
|
||||
ap["channel"] = apitem->channelId;
|
||||
ap["version"] = version_str;
|
||||
|
||||
if (wsMutex) xSemaphoreTake(wsMutex, portMAX_DELAY);
|
||||
ws.textAll(doc.as<String>());
|
||||
if (wsMutex) xSemaphoreGive(wsMutex);
|
||||
}
|
||||
|
||||
void init_web() {
|
||||
LittleFS.begin(true);
|
||||
|
||||
@@ -273,6 +292,36 @@ void init_web() {
|
||||
}
|
||||
});
|
||||
|
||||
server.on("/get_ap_list", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
UDPcomm udpsync;
|
||||
udpsync.getAPList();
|
||||
File configFile = LittleFS.open("/current/apconfig.json", "r");
|
||||
if (!configFile) {
|
||||
request->send(500, "text/plain", "Error opening apconfig.json file");
|
||||
return;
|
||||
}
|
||||
request->send(configFile, "application/json");
|
||||
configFile.close();
|
||||
});
|
||||
|
||||
server.on("/save_apcfg", HTTP_POST, [](AsyncWebServerRequest *request) {
|
||||
if (request->hasParam("alias", true) && request->hasParam("channel", true)) {
|
||||
APconfig["alias"] = request->getParam("alias", true)->value();
|
||||
APconfig["channel"] = request->getParam("channel", true)->value();
|
||||
saveAPconfig();
|
||||
setAPchannel();
|
||||
}
|
||||
request->send(200, "text/plain", "Ok, saved");
|
||||
});
|
||||
|
||||
server.on("/backup_db", HTTP_GET, [](AsyncWebServerRequest *request) {
|
||||
saveDB("/current/tagDB.json");
|
||||
File file = LittleFS.open("/current/tagDB.json", "r");
|
||||
AsyncWebServerResponse *response = request->beginResponse(file, "tagDB.json", String(), true);
|
||||
request->send(response);
|
||||
file.close();
|
||||
});
|
||||
|
||||
server.onNotFound([](AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/" || request->url() == "index.htm") {
|
||||
request->send(200, "text/html", "-");
|
||||
@@ -291,7 +340,6 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
|
||||
} else {
|
||||
filename = "unknown.jpg";
|
||||
}
|
||||
Serial.print((String) "UploadStart: " + filename);
|
||||
request->_tempFile = LittleFS.open("/" + filename, "w");
|
||||
}
|
||||
if (len) {
|
||||
@@ -299,7 +347,6 @@ void doImageUpload(AsyncWebServerRequest *request, String filename, size_t index
|
||||
request->_tempFile.write(data, len);
|
||||
}
|
||||
if (final) {
|
||||
Serial.println((String) " End: " + filename + ", " + index + len);
|
||||
request->_tempFile.close();
|
||||
if (request->hasParam("mac", true)) {
|
||||
String dst = request->getParam("mac", true)->value();
|
||||
|
||||
Reference in New Issue
Block a user