diff --git a/ESP32_AP-Flasher/data/gradient.jpg b/ESP32_AP-Flasher/data/gradient.jpg
new file mode 100644
index 00000000..34300c0a
Binary files /dev/null and b/ESP32_AP-Flasher/data/gradient.jpg differ
diff --git a/ESP32_AP-Flasher/data/www/content_cards.json b/ESP32_AP-Flasher/data/www/content_cards.json
index 4012c520..5a35dd7f 100644
--- a/ESP32_AP-Flasher/data/www/content_cards.json
+++ b/ESP32_AP-Flasher/data/www/content_cards.json
@@ -344,5 +344,22 @@
"type": "text"
}
]
+ },
+ {
+ "id": 15,
+ "name": "Send custom LUT",
+ "desc": "EXPERIMENTAL. Don't use. YOU RISK DAMAGING YOUR SCREEN.",
+ "hwtype": [
+ 1
+ ],
+ "capabilities": 4,
+ "param": [
+ {
+ "key": "bytes",
+ "name": "bytes",
+ "desc": "70 bytes, formatted as 0x00,0x00,...",
+ "type": "text"
+ }
+ ]
}
]
\ No newline at end of file
diff --git a/ESP32_AP-Flasher/data/www/main.js b/ESP32_AP-Flasher/data/www/main.js
index 25662212..0a979ae6 100644
--- a/ESP32_AP-Flasher/data/www/main.js
+++ b/ESP32_AP-Flasher/data/www/main.js
@@ -13,7 +13,7 @@ models[240] = "Segmented tag"
models[17] = "2.9\" 296x128px (UC8151)"
const displaySizeLookup = { 0: [152, 152], 1: [128, 296], 2: [400, 300] };
displaySizeLookup[17] = [128, 296];
-const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [255, 0, 0] };
+const colorTable = { 0: [255, 255, 255], 1: [0, 0, 0], 2: [255, 0, 0], 3: [150, 150, 150] };
const imageQueue = [];
let isProcessing = false;
@@ -64,7 +64,7 @@ function connect() {
});
socket.addEventListener("message", (event) => {
- //console.log(event.data)
+ console.log(event.data)
const msg = JSON.parse(event.data);
if (msg.logMsg) {
showMessage(msg.logMsg, false);
@@ -213,7 +213,7 @@ function updatecards() {
if (item.dataset.lastseen && item.dataset.lastseen > 1672531200) {
let idletime = (Date.now() / 1000) - servertimediff - item.dataset.lastseen;
$('#tag' + tagmac + ' .lastseen').innerHTML = "last seen" + displayTime(Math.floor(idletime)) + " ago";
- if ((Date.now() / 1000) - servertimediff - 300 > item.dataset.nextcheckin) {
+ if ((Date.now() / 1000) - servertimediff - 600 > item.dataset.nextcheckin) {
$('#tag' + tagmac + ' .warningicon').style.display = 'inline-block';
$('#tag' + tagmac).classList.remove("tagpending")
$('#tag' + tagmac).style.background = '#e0e0a0';
diff --git a/ESP32_AP-Flasher/include/commstructs.h b/ESP32_AP-Flasher/include/commstructs.h
index 3d02ec51..73732d25 100644
--- a/ESP32_AP-Flasher/include/commstructs.h
+++ b/ESP32_AP-Flasher/include/commstructs.h
@@ -48,6 +48,7 @@ struct espAvailDataReq {
#define EPD_LUT_NO_REPEATS 1
#define EPD_LUT_FAST_NO_REDS 2
#define EPD_LUT_FAST 3
+#define EPD_LUT_OTA 0x10
struct AvailDataInfo {
uint8_t checksum;
diff --git a/ESP32_AP-Flasher/include/contentmanager.h b/ESP32_AP-Flasher/include/contentmanager.h
index 3cbc0f2c..2af05e3e 100644
--- a/ESP32_AP-Flasher/include/contentmanager.h
+++ b/ESP32_AP-Flasher/include/contentmanager.h
@@ -33,5 +33,6 @@ char *formatHttpDate(time_t t);
String urlEncode(const char *msg);
int windSpeedToBeaufort(float windSpeed);
String windDirectionIcon(int degrees);
-String mac62hex(uint8_t *mac);
void getLocation(JsonObject &cfgobj);
+void prepareNFCReq(uint8_t* dst, const char* url);
+void prepareLUTreq(uint8_t *dst, String input);
\ No newline at end of file
diff --git a/ESP32_AP-Flasher/include/makeimage.h b/ESP32_AP-Flasher/include/makeimage.h
index b9369d18..165a2f2e 100644
--- a/ESP32_AP-Flasher/include/makeimage.h
+++ b/ESP32_AP-Flasher/include/makeimage.h
@@ -7,6 +7,8 @@ struct imgParam {
bool hasRed;
uint8_t dataType;
bool dither;
+ bool grayLut = false;
+
char segments[12];
uint16_t symbols;
bool invert;
diff --git a/ESP32_AP-Flasher/include/newproto.h b/ESP32_AP-Flasher/include/newproto.h
index e8758464..a0348c8f 100644
--- a/ESP32_AP-Flasher/include/newproto.h
+++ b/ESP32_AP-Flasher/include/newproto.h
@@ -5,7 +5,7 @@ extern bool checkCRC(void* p, uint8_t len);
extern void processBlockRequest(struct espBlockRequest* br);
extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin);
-extern void prepareNFCReq(uint8_t* dst, const char* url);
+extern void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* dst);
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, bool local);
diff --git a/ESP32_AP-Flasher/include/tag_db.h b/ESP32_AP-Flasher/include/tag_db.h
index 3ce6c409..b49ae093 100644
--- a/ESP32_AP-Flasher/include/tag_db.h
+++ b/ESP32_AP-Flasher/include/tag_db.h
@@ -13,7 +13,7 @@
class tagRecord {
public:
- 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),
+ 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), hasCustomLUT(false),
filename(""), data(nullptr), len(0) {}
uint8_t mac[8];
@@ -36,7 +36,7 @@ class tagRecord {
uint32_t lastfullupdate;
bool isExternal;
uint16_t pendingIdle;
-
+ bool hasCustomLUT;
String filename;
uint8_t* data;
uint32_t len;
diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp
index 452f7e89..4db48a59 100644
--- a/ESP32_AP-Flasher/src/contentmanager.cpp
+++ b/ESP32_AP-Flasher/src/contentmanager.cpp
@@ -1,19 +1,30 @@
#include "contentmanager.h"
+// possibility to turn off, to save space if needed
+#define CONTENT_QR
+#define CONTENT_RSS
+#define CONTENT_CAL
+
#include
#include
#include
#include
#include
+#ifdef CONTENT_RSS
#include
+#endif
#include
#include
+#if defined CONTENT_RSS || defined CONTENT_CAL
#include "U8g2_for_TFT_eSPI.h"
+#endif
#include "commstructs.h"
#include "makeimage.h"
#include "newproto.h"
+#ifdef CONTENT_QR
#include "qrcode.h"
+#endif
#include "tag_db.h"
#include "settings.h"
#include "web.h"
@@ -95,6 +106,7 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
imageParams.hasRed = false;
imageParams.dataType = DATATYPE_IMG_RAW_1BPP;
imageParams.dither = true;
+ if (taginfo->hasCustomLUT) imageParams.grayLut = true;
imageParams.invert = false;
imageParams.symbols = 0;
@@ -108,7 +120,7 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
if (imageParams.hasRed) imageParams.dataType = DATATYPE_IMG_RAW_2BPP;
if (prepareDataAvail(&filename, imageParams.dataType, mac, cfgobj["timetolive"].as())) {
cfgobj["#fetched"] = true;
- if (cfgobj["delete"].as()) LittleFS.remove("/"+cfgobj["filename"].as());
+ if (cfgobj["delete"].as()=="1") LittleFS.remove("/"+cfgobj["filename"].as());
} else {
wsErr("Error accessing " + filename);
}
@@ -237,10 +249,15 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) {
case 14: // NFC URL
taginfo->nextupdate = 3216153600;
- memset(taginfo->md5, 0, 16 * sizeof(uint8_t));
- memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
prepareNFCReq(mac, cfgobj["url"].as());
break;
+
+ case 15: // send gray LUT
+
+ taginfo->nextupdate = 3216153600;
+ prepareLUTreq(mac, cfgobj["bytes"]);
+ taginfo->hasCustomLUT = true;
+ break;
}
taginfo->modeConfigJson = doc.as();
@@ -783,9 +800,12 @@ bool getImgURL(String &filename, String URL, time_t fetched, imgParam &imagePara
return (httpCode == 200 || httpCode == 304);
}
+#ifdef CONTENT_RSS
rssClass reader;
+#endif
bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) {
+#ifdef CONTENT_RSS
// https://github.com/garretlab/shoddyxml2
// http://feeds.feedburner.com/tweakers/nieuws
@@ -854,6 +874,7 @@ bool getRssFeed(String &filename, String URL, String title, tagRecord *&taginfo,
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
+#endif
return true;
}
@@ -878,6 +899,7 @@ char *epoch_to_display(time_t utc) {
}
bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo, imgParam &imageParams) {
+#ifdef CONTENT_CAL
// google apps scripts method to retrieve calendar
// see /data/calendar.txt for description
@@ -988,11 +1010,12 @@ bool getCalFeed(String &filename, String URL, String title, tagRecord *&taginfo,
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
-
+#endif
return true;
}
void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginfo, imgParam &imageParams) {
+#ifdef CONTENT_QR
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite spr = TFT_eSprite(&tft);
LittleFS.begin();
@@ -1037,6 +1060,7 @@ void drawQR(String &filename, String qrcontent, String title, tagRecord *&taginf
spr2buffer(spr, filename, imageParams);
spr.deleteSprite();
+#endif
}
char *formatHttpDate(time_t t) {
@@ -1111,4 +1135,41 @@ void getLocation(JsonObject &cfgobj) {
cfgobj["#tz"] = tz;
}
}
-}
\ No newline at end of file
+}
+
+void prepareNFCReq(uint8_t *dst, const char *url) {
+ uint8_t *data;
+ size_t len = strlen(url);
+ data = new uint8_t[len + 8];
+
+ // TLV
+ data[0] = 0x03; // NDEF message (TLV type)
+ data[1] = 4 + len + 1;
+ // ndef record
+ data[2] = 0xD1;
+ data[3] = 0x01; // well known record type
+ data[4] = len + 1; // payload length
+ data[5] = 0x55; // payload type (URI record)
+ data[6] = 0x00; // URI identifier code (no prepending)
+
+ memcpy(data + 7, reinterpret_cast(url), len);
+ len = 7 + len;
+ data[len] = 0xFE;
+ len = 1 + len;
+ prepareDataAvail(data, len, DATATYPE_NFC_RAW_CONTENT, dst);
+}
+
+void prepareLUTreq(uint8_t *dst, String input) {
+ const char *delimiters = ", \t";
+ const int maxValues = 70;
+ uint8_t waveform[maxValues];
+ char *ptr = strtok(const_cast(input.c_str()), delimiters);
+ int i = 0;
+ while (ptr != nullptr && i < maxValues) {
+ waveform[i++] = static_cast(strtol(ptr, nullptr, 16));
+ ptr = strtok(nullptr, delimiters);
+ }
+ Serial.println(String(i) + " bytes found");
+ size_t waveformLen = sizeof(waveform);
+ prepareDataAvail(waveform, waveformLen, DATATYPE_CUSTOM_LUT_OTA, dst);
+}
diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp
index 3408b6b6..168af98f 100644
--- a/ESP32_AP-Flasher/src/makeimage.cpp
+++ b/ESP32_AP-Flasher/src/makeimage.cpp
@@ -116,6 +116,11 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
{0, 0, 0}, // Black
{255, 0, 0} // Red
};
+ if (imageParams.grayLut) {
+ Color newColor = {150, 150, 150};
+ palette.push_back(newColor);
+ Serial.println("rendering with gray");
+ }
int num_colors = palette.size();
Color color;
Error *error_bufferold = new Error[bufw];
@@ -143,13 +148,40 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) {
uint16_t bitIndex = 7 - (x % 8);
uint16_t byteIndex = (y * bufw + x) / 8;
+
+ // this looks a bit ugly, but it's performing better than shorter notations
+ switch (best_color_index) {
+ case 1:
+ blackBuffer[byteIndex] |= (1 << bitIndex);
+ break;
+ case 2:
+ imageParams.hasRed = true;
+ redBuffer[byteIndex] |= (1 << bitIndex);
+ break;
+ case 3:
+ blackBuffer[byteIndex] |= (1 << bitIndex);
+ redBuffer[byteIndex] |= (1 << bitIndex);
+ imageParams.hasRed = true;
+ break;
+ }
+ /*
+ alt 1:
+
if (best_color_index & 1) {
blackBuffer[byteIndex] |= (1 << bitIndex);
- } else if (best_color_index & 2) {
+ }
+ if (best_color_index & 2) {
imageParams.hasRed = true;
redBuffer[byteIndex] |= (1 << bitIndex);
}
+ alt 2:
+
+ blackBuffer[byteIndex] |= ((best_color_index & 1) << bitIndex);
+ redBuffer[byteIndex] |= ((best_color_index & 2) << bitIndex);
+ imageParams.hasRed |= (best_color_index & 2);
+ */
+
if (imageParams.dither) {
Error error = {
((float)color.r + error_bufferold[x].r - palette[best_color_index].r) / 16.0f,
diff --git a/ESP32_AP-Flasher/src/newproto.cpp b/ESP32_AP-Flasher/src/newproto.cpp
index 4bbfd12d..ea9ecb76 100644
--- a/ESP32_AP-Flasher/src/newproto.cpp
+++ b/ESP32_AP-Flasher/src/newproto.cpp
@@ -79,7 +79,7 @@ void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin) {
}
}
-void prepareNFCReq(uint8_t* dst, const char* url) {
+void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* dst) {
tagRecord* taginfo = nullptr;
taginfo = tagRecord::findByMAC(dst);
if (taginfo == nullptr) {
@@ -88,32 +88,22 @@ void prepareNFCReq(uint8_t* dst, const char* url) {
}
clearPending(taginfo);
- size_t len = strlen(url);
- taginfo->data = new uint8_t[len + 8];
-
- // TLV
- taginfo->data[0] = 0x03; // NDEF message (TLV type)
- taginfo->data[1] = 4 + len + 1;
-
- // ndef record
- taginfo->data[2] = 0xD1;
- taginfo->data[3] = 0x01; // well known record type
- taginfo->data[4] = len + 1; // payload length
- taginfo->data[5] = 0x55; // payload type (URI record)
- taginfo->data[6] = 0x00; // URI identifier code (no prepending)
-
- memcpy(taginfo->data + 7, reinterpret_cast(url), len);
- len = 7 + len;
- taginfo->data[len] = 0xFE;
- len = 1 + len;
-
+ taginfo->data = (uint8_t*)malloc(len);
+ if (taginfo->data == nullptr) {
+ wsErr("no memory allocation for data");
+ return;
+ }
+ memcpy(taginfo->data, data, len);
taginfo->pending = true;
taginfo->len = len;
+ taginfo->expectedNextCheckin = 0;
+ taginfo->filename = String();
+ memset(taginfo->md5pending, 0, 16 * sizeof(uint8_t));
struct pendingData pending = {0};
memcpy(pending.targetMac, dst, 8);
pending.availdatainfo.dataSize = len;
- pending.availdatainfo.dataType = DATATYPE_NFC_RAW_CONTENT;
+ pending.availdatainfo.dataType = dataType;
pending.availdatainfo.nextCheckIn = 0;
pending.availdatainfo.dataVer = millis();
pending.attemptsLeft = 10;
@@ -182,6 +172,10 @@ bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t
lut = EPD_LUT_DEFAULT; // full update once a day
taginfo->lastfullupdate = now;
}
+ if (taginfo->hasCustomLUT && taginfo->capabilities & CAPABILITY_SUPPORTS_CUSTOM_LUTS) {
+ Serial.println("using custom LUT");
+ lut = EPD_LUT_OTA;
+ }
if (dataType != DATATYPE_FW_UPDATE) {
char dst_path[64];