diff --git a/ESP32_AP-Flasher/data/alignment.bmp b/ESP32_AP-Flasher/data/alignment.bmp deleted file mode 100644 index 3e302cbe..00000000 Binary files a/ESP32_AP-Flasher/data/alignment.bmp and /dev/null differ diff --git a/ESP32_AP-Flasher/data/alignment.jpg b/ESP32_AP-Flasher/data/alignment.jpg new file mode 100644 index 00000000..58750d98 Binary files /dev/null and b/ESP32_AP-Flasher/data/alignment.jpg differ diff --git a/ESP32_AP-Flasher/data/kat-bw29.jpg b/ESP32_AP-Flasher/data/kat-bw29.jpg deleted file mode 100644 index ae6c765e..00000000 Binary files a/ESP32_AP-Flasher/data/kat-bw29.jpg and /dev/null differ diff --git a/ESP32_AP-Flasher/data/test154.bmp b/ESP32_AP-Flasher/data/test154.bmp deleted file mode 100644 index 489d9442..00000000 Binary files a/ESP32_AP-Flasher/data/test154.bmp and /dev/null differ diff --git a/ESP32_AP-Flasher/data/test29.bmp b/ESP32_AP-Flasher/data/test29.bmp deleted file mode 100644 index 68d5e880..00000000 Binary files a/ESP32_AP-Flasher/data/test29.bmp and /dev/null differ diff --git a/ESP32_AP-Flasher/data/test42.bmp b/ESP32_AP-Flasher/data/test42.bmp deleted file mode 100644 index ab0f0fbd..00000000 Binary files a/ESP32_AP-Flasher/data/test42.bmp and /dev/null differ diff --git a/ESP32_AP-Flasher/data/www/index.html b/ESP32_AP-Flasher/data/www/index.html index 9b70dc31..d2dbafa6 100644 --- a/ESP32_AP-Flasher/data/www/index.html +++ b/ESP32_AP-Flasher/data/www/index.html @@ -47,6 +47,60 @@

+
+
+

Access Point config

+

+ + +

+

+ + +

+

+ +

+ +

+ Active access points:
+ + + + + + + + +
ipaliastagschfw ver
+

+

+ reboot AP + download tagDB +

+

+ Github OpenEPaperLink +

+
+
@@ -55,8 +109,8 @@
Currently active tags:
-
reboot AP
- +
AP config
+
edit littleFS
diff --git a/ESP32_AP-Flasher/data/www/main.css b/ESP32_AP-Flasher/data/www/main.css index d24cf8c6..be547bdc 100644 --- a/ESP32_AP-Flasher/data/www/main.css +++ b/ESP32_AP-Flasher/data/www/main.css @@ -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; diff --git a/ESP32_AP-Flasher/data/www/main.js b/ESP32_AP-Flasher/data/www/main.js index 531d7ad0..d350d958 100644 --- a/ESP32_AP-Flasher/data/www/main.js +++ b/ESP32_AP-Flasher/data/www/main.js @@ -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 = "" + msg.apitem.ip + ""; + 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) { diff --git a/ESP32_AP-Flasher/include/commstructs.h b/ESP32_AP-Flasher/include/commstructs.h index ac73dd81..a1a15e05 100644 --- a/ESP32_AP-Flasher/include/commstructs.h +++ b/ESP32_AP-Flasher/include/commstructs.h @@ -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) \ No newline at end of file diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h index fa9a1441..99711256 100644 --- a/ESP32_AP-Flasher/include/contentmanager.h +++ b/ESP32_AP-Flasher/include/contentmanager.h @@ -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); diff --git a/ESP32_AP-Flasher/include/makeimage.h b/ESP32_AP-Flasher/include/makeimage.h index d6d33029..d7509fc0 100644 --- a/ESP32_AP-Flasher/include/makeimage.h +++ b/ESP32_AP-Flasher/include/makeimage.h @@ -6,6 +6,7 @@ struct imgParam { bool hasRed; uint8_t dataType; + bool dither; }; void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams); diff --git a/ESP32_AP-Flasher/include/newproto.h b/ESP32_AP-Flasher/include/newproto.h index 9ecafc9e..92f3417f 100644 --- a/ESP32_AP-Flasher/include/newproto.h +++ b/ESP32_AP-Flasher/include/newproto.h @@ -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(); \ No newline at end of file +void refreshAllPending(); +void setAPchannel(); diff --git a/ESP32_AP-Flasher/include/pendingdata.h b/ESP32_AP-Flasher/include/pendingdata.h deleted file mode 100644 index eced4e64..00000000 --- a/ESP32_AP-Flasher/include/pendingdata.h +++ /dev/null @@ -1,23 +0,0 @@ -#include - -#include - -#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 pendingfiles; - -#pragma pack(pop) \ No newline at end of file diff --git a/ESP32_AP-Flasher/include/serial.h b/ESP32_AP-Flasher/include/serial.h index bc597799..d3911058 100644 --- a/ESP32_AP-Flasher/include/serial.h +++ b/ESP32_AP-Flasher/include/serial.h @@ -1,5 +1,8 @@ #include +extern struct espSetChannelPower curChannel; +extern uint16_t version; + void zbsTx(uint8_t* packetdata, uint8_t len); void zbsRxTask(void* parameter); diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h index 50606a72..c5321444 100644 --- a/ESP32_AP-Flasher/include/tag_db.h +++ b/ESP32_AP-Flasher/include/tag_db.h @@ -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 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) \ No newline at end of file diff --git a/ESP32_AP-Flasher/include/udp.h b/ESP32_AP-Flasher/include/udp.h new file mode 100644 index 00000000..f2a25a08 --- /dev/null +++ b/ESP32_AP-Flasher/include/udp.h @@ -0,0 +1,26 @@ +#include + +#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(); \ No newline at end of file diff --git a/ESP32_AP-Flasher/include/web.h b/ESP32_AP-Flasher/include/web.h index 524234df..991d9e13 100644 --- a/ESP32_AP-Flasher/include/web.h +++ b/ESP32_AP-Flasher/include/web.h @@ -1,5 +1,5 @@ -#include +#include #include #include @@ -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; \ No newline at end of file diff --git a/ESP32_AP-Flasher/platformio.ini b/ESP32_AP-Flasher/platformio.ini index b87f2863..b7cd9bb3 100644 --- a/ESP32_AP-Flasher/platformio.ini +++ b/ESP32_AP-Flasher/platformio.ini @@ -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 diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index f67aeb8f..846c643a 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -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() && cfgobj["filename"].as() != "null" && !cfgobj["#fetched"].as()) { + if (cfgobj["dither"] && cfgobj["dither"].as() == false) imageParams.dither = false; jpg2buffer(cfgobj["filename"].as(), filename, imageParams); if (imageParams.hasRed) imageParams.dataType = DATATYPE_IMG_RAW_2BPP; if (prepareDataAvail(&filename, imageParams.dataType, mac, cfgobj["timetolive"].as())) { @@ -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(); @@ -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() + "&longitude=" + doc["results"][0]["longitude"].as() + "¤t_weather=true&windspeed_unit=ms&timezone=" + doc["results"][0]["timezone"].as()); - 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(); - auto windspeed = doc["current_weather"]["windspeed"].as(); - auto winddirection = doc["current_weather"]["winddirection"].as(); - uint8_t weathercode = doc["current_weather"]["weathercode"].as(); - 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(); + auto windspeed = doc["current_weather"]["windspeed"].as(); + auto winddirection = doc["current_weather"]["winddirection"].as(); + uint8_t weathercode = doc["current_weather"]["weathercode"].as(); + 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(), 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() + "&longitude=" + doc["results"][0]["longitude"].as() + "&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()); + 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(), 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(); + 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(); - 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(); - 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(); + 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()); - int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); - uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as()); + 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()); + int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); + uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as()); + + 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(), 5, 0); + + for (uint8_t dag = 0; dag < 5; dag++) { + time_t weatherday = doc["daily"]["time"][dag].as(); + 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(); + 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()); + int8_t tmax = round(doc["daily"]["temperature_2m_max"][dag].as()); + uint8_t wind = windSpeedToBeaufort(doc["daily"]["windspeed_10m_max"][dag].as()); + + 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(); + lon = doc["results"][0]["longitude"].as(); + tz = doc["results"][0]["timezone"].as(); + cfgobj["#lat"] = lat; + cfgobj["#lon"] = lon; + cfgobj["#tz"] = tz; + } + } +} \ No newline at end of file diff --git a/ESP32_AP-Flasher/src/main.cpp b/ESP32_AP-Flasher/src/main.cpp index 54885b13..27e7a984 100644 --- a/ESP32_AP-Flasher/src/main.cpp +++ b/ESP32_AP-Flasher/src/main.cpp @@ -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); diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp index e1179081..743729b0 100644 --- a/ESP32_AP-Flasher/src/makeimage.cpp +++ b/ESP32_AP-Flasher/src/makeimage.cpp @@ -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); diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp index ba6d6499..d1a4bdf0 100644 --- a/ESP32_AP-Flasher/src/newproto.cpp +++ b/ESP32_AP-Flasher/src/newproto.cpp @@ -1,19 +1,23 @@ #include "newproto.h" #include +#include +#include +#include #include #include #include #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); } } -}; \ No newline at end of file +}; + +void setAPchannel() { + if (APconfig["channel"].as() == 0) { + // trigger channel autoselect + UDPcomm udpsync; + udpsync.getAPList(); + } else { + if (curChannel.channel != APconfig["channel"].as()) { + curChannel.channel = APconfig["channel"].as(); + if (version) sendChannelPower(&curChannel); + } + } +} diff --git a/ESP32_AP-Flasher/src/pendingdata.cpp b/ESP32_AP-Flasher/src/pendingdata.cpp deleted file mode 100644 index 9b350152..00000000 --- a/ESP32_AP-Flasher/src/pendingdata.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "pendingdata.h" - -#include - -#include - -#include "settings.h" - -std::vector 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); - } - } -} diff --git a/ESP32_AP-Flasher/src/serial.cpp b/ESP32_AP-Flasher/src/serial.cpp index 4629af69..0dd4d74a 100644 --- a/ESP32_AP-Flasher/src/serial.cpp +++ b/ESP32_AP-Flasher/src/serial.cpp @@ -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; } } diff --git a/ESP32_AP-Flasher/src/tag_db.cpp b/ESP32_AP-Flasher/src/tag_db.cpp index 2f585d24..b1fe45ec 100644 --- a/ESP32_AP-Flasher/src/tag_db.cpp +++ b/ESP32_AP-Flasher/src/tag_db.cpp @@ -2,12 +2,13 @@ #include #include - +#include #include #include "LittleFS.h" std::vector 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(); + taginfo->isExternal = tag["isexternal"].as(); } } 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(); +} \ No newline at end of file diff --git a/ESP32_AP-Flasher/src/udp.cpp b/ESP32_AP-Flasher/src/udp.cpp new file mode 100644 index 00000000..8ec0c4c8 --- /dev/null +++ b/ESP32_AP-Flasher/src/udp.cpp @@ -0,0 +1,168 @@ +#include +#include + +#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() == 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); +} \ No newline at end of file diff --git a/ESP32_AP-Flasher/src/web.cpp b/ESP32_AP-Flasher/src/web.cpp index ef1e21bf..51d16795 100644 --- a/ESP32_AP-Flasher/src/web.cpp +++ b/ESP32_AP-Flasher/src/web.cpp @@ -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()); + 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();