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.

This post is also available in English: Frame by frame

Hozzáfűznél valamit?

Dobj egy emailt a blog kukac deadlime pont hu címre.

Feliratkoznál?

Az RSS feed-et ajánljuk, ha kedveled a régi jó dolgokat.