Keretes szerkezet
E-papír alapú digitális képkeret
Kaptam ajándékba egy e-papír kijelzőt és egy képkeretet, úgyhogy építünk egy otthoni klasztert használt mobiltelefonokból. Ja, nem. Egy digitális képkeretet építünk. Akit a klaszter jobban érdekelt volna, az így járt annak itt ez a cikk.
A dolog (e-)papíron egyszerű. Egy ESP32 van rákötve a kijelzőre, ami fent lóg a hálózaton és várja az új képeket, hogy befrissíthesse a kijelzőt. De valahogy elő is kell állítanunk a megfelelő képeket, mert a kijelző csak a fekete és a fehér színeket ismeri.
Összeszerelés
A paszpartu (új szó, amit a projekt során tanultam, előtte "az az izé, ami a kép körül van a kereten belül" néven ismertem), amit a kerethez adtak, nem egészen stimmelt méretben az e-papír kijelzőhöz. Kereshettem volna hozzá egy megfelelő vastagságú kartonpapírt, de inkább megnyitottam a FreeCAD-et, terveztem egy újat és kinyomtattam.
A kijelző tökéletesen illeszkedik bele (2-3 próbanyomtatás után) és a szalagkábelnek is van egy kis hely.
Szemfüles olvasók talán azt is meg tudják mondani, hogy melyik xkcd van épp a kijelzőn
A keret hátlapján vágnom kellett egy kis lyukat, ahol a kábelt ki lehet vezetni, hogy rá lehessen kötni az ESP32-re.
A szalagkábellel óvatosan kell bánni, elég törékeny. Ne kérdezd, hogy honnan tudom. És biztos nem azért kell ilyen rövid kábellel vacakolnom, mert a hosszabbító egyik csatlakozóját valahogy sikerült elrontanom.
Végül az ESP32-t rögzítettem egy minden esztétikát nélkülöző megoldással.
Az egészet megpróbáltam meghajtani egy IKEA-s powerbank-ről, de mint kiderült ezek a szerkezetek lekapcsolják saját magukat, ha nincsen rájuk dugva valami fogyasztó. Az e-papír kijelző pedig úgy tűnik nem fogyaszt eleget, hogy azt érzékelje.
Szoftver
Először is próbáljunk meg megjeleníteni valamit, hogy lássuk, hogy minden jól működik. Az ESP32-t a gyártó dokumentációja alapján az Arduino IDE-vel lőttem össze, majd szintén a dokumentáció alapján megjelenítettem egy képet.
#include <DEV_Config.h>
#include <EPD.h>
#include <GUI_Paint.h>
UBYTE *image;
UWORD imageSize = ((EPD_7IN5_V2_WIDTH % 8 == 0) ? (EPD_7IN5_V2_WIDTH / 8) : (EPD_7IN5_V2_WIDTH / 8 + 1)) * EPD_7IN5_V2_HEIGHT;
uint8_t *imageData = nullptr;
void setup() {
DEV_Module_Init();
EPD_7IN5_V2_Init();
EPD_7IN5_V2_Clear();
DEV_Delay_ms(500);
if ((image = (UBYTE *)malloc(imageSize)) == NULL) {
Serial.println("Failed to apply for image memory");
while (1);
}
Paint_NewImage(image, EPD_7IN5_V2_WIDTH, EPD_7IN5_V2_HEIGHT, 0, WHITE);
Paint_SelectImage(image);
Paint_Clear(WHITE);
Paint_DrawBitMap(imageData);
EPD_7IN5_V2_Display(image);
DEV_Delay_ms(2000);
}
Az imageData-nak jönnie kell valahonnan. Beégethetünk a kódba egy (vagy több) byte tömböt, de az nem túl rugalmas. Így ideje elgondolkodni azon, hogy hogyan is szeretnénk frissíteni a képet a kijelzőn.
Mivel van wifi, két irányba is elindulhatunk, vagy szerver leszünk vagy kliens. Én az előbbit választottam, kerestem egy webszerver library-t, ami működik a hardveren és csináltam egy /upload endpoint-ot.
// ...
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
// ...
static AsyncWebServer server(80);
bool imageChanged = false;
void on_request(AsyncWebServerRequest *request) {
if (request->getResponse()) {
return;
}
if (!request->_tempObject) {
return request->send(400, "text/plain", "Nothing uploaded");
}
imageData = reinterpret_cast<uint8_t *>(request->_tempObject);
imageChanged = true;
request->_tempObject = nullptr;
request->send(200, "text/plain", "OK");
}
void on_upload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
Serial.printf("Upload[%s]: start=%u, len=%u, final=%d\n", filename.c_str(), index, len, final);
if (!index) {
size_t size = request->header("Content-Length").toInt();
if (!size) {
request->send(400, "text/plain", "No Content-Length");
} else {
Serial.printf("Allocating buffer of %u bytes\n", size);
uint8_t *buffer = new (std::nothrow) uint8_t[size];
if (!buffer) {
request->abort();
} else {
request->_tempObject = buffer;
}
}
}
if (len) {
memcpy(reinterpret_cast<uint8_t *>(request->_tempObject) + index, data, len);
}
}
void update_image() {
Paint_SelectImage(image);
Paint_Clear(WHITE);
Paint_DrawBitMap(imageData);
EPD_7IN5_V2_Display(image);
DEV_Delay_ms(2000);
delete imageData;
imageData = nullptr;
imageChanged = false;
}
void setup() {
// ...
server.on("/upload", HTTP_POST, on_request, on_upload);
server.begin();
}
void loop() {
if (imageChanged) {
update_image();
}
delay(1000);
}
Ezek után egy egyszerű curl paranccsal frissíthetjük a képet:
$ curl -v -F "data=@something.wbm" "http://192.168.255.50/upload"
Már ha rendelkezünk a megfelelő formátumú képpel ugyebár. Ez a kijelző csak a feketét és a fehéret ismeri, még szürkeárnyalataink sincsenek, úgyhogy szükségünk lesz egy kis trükközésre.
Kép generálás
Először is fogjuk a 800x480 pixel méretű képünket (mert ez a kijelző felbontása), amit meg akarunk jeleníteni. Megnyitjuk Krita-ban, a Settings > Dockers > Palette menüponttal előhozzuk a palettákat. A bal alsó sarokban lévő ikonra kattintva megnyitjuk a paletta választót, ott a + jellel hozzáadunk egy újat, amit mondjuk elnevezünk "1bit"-nek és beállítjuk rajta a fekete és a fehér színt.
Ezek után a palettás ablakok bezárhatóak, nem lesz már rá szükség. A Filter > Map > Palettize... menüpontra megyünk át, itt kiválasztjuk a Palette résznél az "1bit"-et, bekattintjuk a pipát a Dither előtt és választunk a Pattern alatt lévő minták közül egy olyat, amivel elfogadhatóan néz ki a képünk.
Érdemes még a Color Mode és az Offset Scale beállításokkal játszani.
Mint már korábban is, a WBMP most is egy remek formátum lenne nekünk, de a Krita nem ismeri, úgyhogy jobb híján mentsük el a képet PNG-ként. Aztán az ImageMagick convert parancsával csinálhatunk belőle WBMP-t:
$ convert image.png image.wbmp
Az így elkészült fájl elején lesz pontosan 6 bájtnyi felesleges WBMP header, amire nekünk semmi szükségünk, úgyhogy szabaduljunk is meg tőle.
$ tail -c +7 image.wbmp >image_no_header.wbmp
Ezt pedig már rögtön fel is tölthetjük a korábban ismertetett módon:
$ curl -v -F "data=@image_no_header.wbmp" "http://192.168.255.50/upload"
Végül egy közeli az e-papír pixelekről:
A projekt tetszőlegesen bonyolítható tovább, például egy programmal, ami bármilyen képet átalakít a megfelelő formátumra és feltölti a képkeretre. Vannak már komolyabb e-papír kijelzők szürkeárnyalatokkal, sőt színekkel is, de mi a bejegyzésre szánt keret végére értünk.