Streaming Images from ESP32-CAM for viewing on a CYD-esp32

I recently had a few wifi-enabled microcontrollers (ESP32) dropped into my lap, and with a bit of free time, set out to find a use for them, and barring that, at least have some fun playing around with them.

There are many, many different development boards centered around Esperiff's ESP32 chip. In todays post I'm going to be taking a look at two of them: The ESP32-CAM, which sports an onboard OV5660 3MP camera, and the ESP32 CYD (Cheap Yellow Display*), with its integrated 240x320 TFT.

The project has two main components operating in a client/server architecture.  The client code will run on the ESP32-CYD and communcate with the server to fetch and display an image from the ESP32-CAM. To facilitate this, ESP32-CAM is running an HTTP server with an API end point which triggers the camera and returns the image encoded as a base64 string.

The Client

The client will establish a connection to the server and repeatedly fetch an updated image from the camera and display it on the CYD's screen.

To control the networking side of things:

- WiFi

- HTTPClient

To control decoding/displaying the image:

- mbedtls/base64.h - for decoding the base64 string used for representing the image

- TJpg_Decoder - for decoding the image from jpg to bitmap

- TFT_eSPI - for controlling the TFT display.

#include <TJpg_Decoder.h>
#include <WiFi.h>
#include <TFT_eSPI.h>
#include <HTTPClient.h>
#include "mbedtls/base64.h"

const int width = 320;
const int height = 240;
const char* ssid = "WIFI_NETWORK_NAME";
const char* password = "WIFI_PASSWORD";

TFT_eSPI tft = TFT_eSPI();
HTTPClient http;

bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t *bitmap) {
    tft.pushImage(x, y, w, h, bitmap);
    return 1;
}

void setupTFT() {
  Serial.begin(112500);
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.setTextWrap(true, true);
  tft.drawString("Dip SET!", 5,10,  2);
}

void setupWiFi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  tft.drawString("Connecting", 5, 50, 2);
  tft.setCursor(tft.getCursorX(), tft.getCursorY());
  while (WiFi.status() != WL_CONNECTED) {
    tft.print(".");
    delay(1000);
  }
  tft.print("Connected!");
}

void setup() {
  setupTFT();
  setupWiFi();
  TJpgDec.setCallback(tft_output);
  TJpgDec.setSwapBytes(true);
}


void loop() {
 http.begin("http://192.168.4.1/img");
  int respCode = http.GET();

  if (respCode == HTTP_CODE_OK) {

    String payload = http.getString();
    size_t out_len;

    mbedtls_base64_decode(
        NULL,
        0,
        &out_len,
        (const unsigned char*)payload.c_str(),
        payload.length()
    );

    unsigned char* imgbytes = (unsigned char*) malloc(out_len);

    if (imgbytes) {

      mbedtls_base64_decode(
          imgbytes,
          out_len,
          &out_len,
          (const unsigned char*)payload.c_str(),
          payload.length()
      );
      TJpgDec.drawJpg(0,0,imgbytes,out_len);
      free(imgbytes);
    } else {
      Serial.print("Malloc failed");
    }
  } else {
    Serial.println("HTTP request failed");
  }

  http.end();

  delay(1600);
}

 

Server

#include <esp32cam.h>
#include <WiFi.h>
#include <WebServer.h>
#include <base64.h>

const byte DNS_PORT = 53;
const int WEBSERVER_PORT = 80;

IPAddress apIP(192, 168, 4, 1);
IPAddress netMsk(255, 255, 255, 0);

WebServer server(WEBSERVER_PORT);

char* netname = "WIFI_NETWORK_NAME";
char* netpass = "WIFI_PASSWORD";

const auto RES = esp32cam::Resolution::find(320, 240);

TaskHandle_t webServerTaskHandle = NULL;

void handleFourOhFour() {
  server.send(404, "text/plain", "yo whats really good with you fam? DIP SET");
}

void handleJustImg() {
  server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
  server.sendHeader("Pragma", "no-cache");
  server.sendHeader("Expires", "-1");
  auto frame = esp32cam::capture();
  if (frame != nullptr) {
    String retval = base64::encode(frame->data(),frame->size());
    server.send(200, "text/plain", retval);
    Serial.println("Sent down the line...");
    Serial.println(retval);
  } else {
    Serial.println("Frame failed.");
  }
}

void setupWebServer() {
  server.on("/", handleJustImg);
  server.on("/img", handleJustImg);
  server.onNotFound(handleFourOhFour);
  server.begin();
}
void setupCamera() {
    using namespace esp32cam;
    Config cfg;
    cfg.setPins(pins::AiThinker);
    cfg.setResolution(RES);
    cfg.setJpeg(80);
    bool ok = Camera.begin(cfg);
    Serial.println(ok ? "CAMERA OK" : "CAMERA FAIL");
}

void setupWifi() {
  // put your setup code here, to run once:
  WiFi.softAP(netname,netpass);
  Serial.print("Starting webserver on ");
  Serial.println( WiFi.softAPIP());
}

void webServerTask(void* pvParameters) {
  setupWebServer();
  while (true) {
    server.handleClient();
    vTaskDelay(pdMS_TO_TICKS(50));
  }
}

void setup() {
  Serial.begin(115200);
  setupCamera();
  setupWifi();
  xTaskCreatePinnedToCore(
    webServerTask,
    "WebServerTask",
    8192,
    NULL,
    1,
    &webServerTaskHandle,
    1
  );
}

void loop() {
  delay(2);
}


Leave A Comment