How to post ESP32CAM image and data to Node-Red so it arrives as payload?

I have a Kodular (Android) App that posts images and related data to Node-Red.
Both image and data arrive as payload.
I also have a html script that does the same (script below) and works perfectly.
Now I would like to use ESP32CAM to post images and related data to the same Node-Red flow.

Image and data actually arrive at Node-Red but instead of arriving as msg.payload they arrives as msg.req.headers(for the data) and msg.req.files[0].buffer (for the image).

HTML script (works OK image and data inside msg.payload)

    <!DOCTYPE html>
    <html lang="">
      <link rel="icon" type="image/x-icon" href="">
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
    <body onload='startFunc()'>
    <form id="myForm">
    <table align='center' style="border-collapse: collapse;" border="1">
    <tr><td align="center"><font size = "5"><b>Leitura de Placas Direto ALPRBR</b></font><br>
    Escolher imagem onde apareça legível a placa de um veículo:<br>
    PadrĂŁo Mercosul: LLLNLNN, padrĂŁo antigo LLL-NNNN</td></tr>
    <tr><td align='center'>
    <input id="inpFile" type="file" accept="image/jpeg, image/png, image/jpg" onclick='enableUpload()'>
    <button id='btn_uploadfile' name='btn_uploadfile' type="submit">Enviar</button>
    <tr><td align='center'>Seu Token: <input id="myToken" name="myToken" minlength='32' maxlength='32'>
    <a href="" title="Cadastro de Usuários">Não Tenho</a>
    <br>Incluir Dados do VeĂ­culo? 
    <input id="vehicledata" type="checkbox" checked />
    <tr><td align="center"><img src="#" id="localImage" width="400"></td></tr>
    <tr><td align='center'>      
        <table align='center' style="border-collapse: collapse;" border="1" width="100%">
        <td><label id='credits'>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label></td></tr>
        <tr><td><b>Placa</b></td><td><label id='property'></label></td></tr>
        <tr><td><b>Chassi_final</b></td><td><label id='chassi_final'></label></td></tr>
        <tr><td><b>Cidade</b></td><td><label id='cidade'></label></td></tr>
        <tr><td><b>Estado</b></td><td><label id='uf'></label></td></tr>
        <tr><td><b>Marca</b></td><td><label id='marca'></label></td></tr>
        <tr><td><b>Modelo</b></td><td><label id='modelo'></label></td></tr>
        <tr><td><b>Ano</b></td><td><label id='ano'></label></td></tr>
        <tr><td><b>Cor</b></td><td><label id='cor'></label></td></tr>
        <td><label id="situacao"></label></td></tr>
    <div align="center" id="myData" name="myData"></div>
        var token='';
        var vehicledata = "true";
        const checkbox = document.getElementById('vehicledata')
        checkbox.addEventListener('change', (event) => {
            if ( {
                vehicledata = "true";
            } else {
                vehicledata = "false";
        const imgInput = document.querySelector('input')
        const imgEl = document.querySelector('img')
        function enableUpload(){document.getElementById("btn_uploadfile").disabled = false;}
        function startFunc(){
            document.getElementById('localImage').style.visibility = 'hidden';
        imgInput.addEventListener('change', () => {
          if (imgInput.files && imgInput.files[0]) {
            const reader = new FileReader();
            reader.onload = (e) => {
              imgEl.src =;
            document.getElementById('localImage').style.visibility = 'visible';
            document.getElementById("credits").innerHTML      = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
            document.getElementById("property").innerHTML     = '';
            document.getElementById("chassi_final").innerHTML = '';
            document.getElementById("cidade").innerHTML       = '';
            document.getElementById("uf").innerHTML           = '';
            document.getElementById("marca").innerHTML        = '';
            document.getElementById("modelo").innerHTML       = '';
            document.getElementById("ano").innerHTML          = '';
            document.getElementById("cor").innerHTML          = '';
            document.getElementById("situacao").innerHTML     = '';        
        const myForm  = document.getElementById("myForm");
        const inpFile = document.getElementById("inpFile");
        myForm.addEventListener("submit", e => {
          token = document.getElementById('myToken').value;
          token = token.replace(/\s/g, '');
          if (token.length<32){
              alert("Token não pode conter espaços.");
          document.getElementById('btn_uploadfile').innerHTML = 'Processando, Aguarde...'; 
          document.getElementById("btn_uploadfile").disabled = true;
          const endpoint = "";
          const form_data = new FormData();
          form_data.append("inpFile", inpFile.files[0]);
            fetch(endpoint, {method: "post", body: form_data })
            .then(function (response) {
                return response.json();
            .then(function (data) {
            .catch(function (err) {
                console.log('erro: ' + err);
                document.getElementById('btn_uploadfile').innerHTML = 'Enviar';
            function appendData(data) {
               document.getElementById("credits").innerHTML      = data.credits;
               document.getElementById("property").innerHTML     =;
               document.getElementById("chassi_final").innerHTML = data.chassi_final;
               document.getElementById("cidade").innerHTML       = data.cidade;
               document.getElementById("uf").innerHTML           = data.uf;
               document.getElementById("marca").innerHTML        = data.marca;
               document.getElementById("modelo").innerHTML       = data.modelo;
               document.getElementById("ano").innerHTML          = data.ano;
               document.getElementById("cor").innerHTML          = data.cor;
               document.getElementById("situacao").innerHTML     = data.situacao;
               document.getElementById('btn_uploadfile').innerHTML = 'Enviar';           

Below is the function of the ESP32CAM sketch that posts image and data to Node-Red but arrive in the wrong place:
The image arrives at: msg.req.files[0].buffer
and the data: msg.req.headers.countrycode, msg.req.headers.token, etc...

What must change at the ESP32CAM sketch below so all those variable and image that are under msg.req. are arriving under msg.peyload. ?

    String postImage() {                      //post image directly to ALPRBR_API no storage
      String getAll;
      String getBody;
      camera_fb_t * fb = NULL;
      fb = esp_camera_fb_get();
      if(!fb) {
        Serial.println("Camera capture failed");
      Serial.println("Connecting to server: " + preferences.getString("serverName"));
      preferences.begin("AppParms", true); 
      if (client.connect(preferences.getString("serverName").c_str(), preferences.getString("serverPort").toInt())) {
        Serial.println("Connection successful!");    
        String head = "--ALPRBR\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
        String tail = "\r\n--ALPRBR--\r\n";
        uint32_t imageLen = fb->len;
        uint32_t extraLen = head.length() + tail.length();
        uint32_t totalLen = imageLen + extraLen;
        client.println("POST " + preferences.getString("serverPath") + " HTTP/1.1");
        client.println("Host: " + preferences.getString("serverName"));
        client.println("Content-Length: " + String(totalLen));
        client.println("Content-Type: multipart/form-data; boundary=ALPRBR");
        client.println("token: " + preferences.getString("token"));
        client.println("countrycode: " + preferences.getString("countrycode"));
        client.println("vehicledata: true");  
        client.println("imageurl: ESP32CAM");  
        uint8_t *fbBuf = fb->buf;
        size_t fbLen = fb->len;
        for (size_t n=0; n<fbLen; n=n+1024) {
          if (n+1024 < fbLen) {
            client.write(fbBuf, 1024);
            fbBuf += 1024;
          else if (fbLen%1024>0) {
            size_t remainder = fbLen%1024;
            client.write(fbBuf, remainder);
        int timoutTimer = 10000;
        long startTimer = millis();
        boolean state = false;
        while ((startTimer + timoutTimer) > millis()) {
          while (client.available()) {
            char c =;
            if (c == '\n') {
              if (getAll.length()==0) { state=true; }
              getAll = "";
            else if (c != '\r') { getAll += String(c); }
            if (state==true) { getBody += String(c); }
            startTimer = millis();
          if (getBody.length()>0) { break; }
      else {
        getBody = "Connection to " + String(preferences.getString("serverName")) +  " failed.";
      return getBody;

Why not use a change node to move the data to the msg loop location you need it to be in?

1 Like

Hi, I would like to keep the flow standard.
I originally designed the flow to Kodular (Android), when I included the possibility of requests from a Browser I also had some difficulty formatting it to meet the flow requirements but now it is posting data in the same way as the Android App.
With ESP32 C++ I am having some more difficulties but there must be a way to arrange things to fit the flow design so I will have it simple for future maintenance.
I did post this question on a ESP32 forum as well but decided to post it here in case someone that certainly know more than me could give a clue.

At the end I sorted out the problem by following zenofmod's sugestion:
The code to transfer image and data to Node-Red is:

String postImage() {
  String imageurl = "esp32cam";
  String httpResponse, targetURL;
  int httpResponseCode;

  camera_fb_t *fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");

  String imgBuffer;
  char *input = (char *)fb->buf;
  char output[base64_enc_len(3)];
  for (int i=0;i<fb->len;i++) {
    base64_encode(output, (input++), 3);
    //if (i%3==0) imgBuffer += urlencode(String(output));
    if (i%3==0) imgBuffer += String(output);


  preferences.begin("AppParms", true);
  pDBG("Connecting to server: ");
  //client.setInsecure(); //skip https verification
  HTTPClient http;
  targetURL = "http://" + preferences.getString("serverName") + ":" + preferences.getString("serverPort") + preferences.getString("serverPath");
  pDBGln("Conecting to: " + targetURL);
  http.begin(targetURL);  //Specify destination for HTTP request
  http.addHeader("Content-Type", "text/plain");
  http.addHeader("imageurl", imageurl);
  http.addHeader("token", preferences.getString("token"));
  http.addHeader("countrycode", preferences.getString("countrycode"));
  http.addHeader("vehicledata", "true");
  http.addHeader("base64image", imgBuffer);

  pDBGln("Posting Now...");      
  httpResponseCode = http.POST("imageurl=" + imageurl + "&token=" + preferences.getString("token") + "&countrycode=" + preferences.getString("countrycode") + "&vehicledata=true");
  httpResponse     = http.getString();
  http.end();  //Free the resources
  return httpResponse;

At the top I declared the library as follows:

#include "Base64.h" 

//Arduino/ESP32-CAM_Base64.ino at master · fustyles/Arduino · GitHub

I added the libraries Base64.h and Base64.cpp as part of the sketch in tabs.

All this data + image arrive at Node-Red at msg.req.headers so some code was required to adjust for that.

1 Like

This looks complicated.
I use three tasmota webcams and put them in a framed area to show the stream / video.
If you want just images then why not use the webcam-url and trigger an event to grab the picture?
As far as I know you can grab them from here:
and streams from here:

Hi, I don't want the images, they are to be analyzed and discarded, that is the reason I wanted them submited straight to Node-red with some supporting data. The result would be sent to MySQL.
Also the Node-Red server is virtual machine somewhere and the cameras garbing the images are somewhere else.
The solution with ESP32CAM is unstable, I put too much functionality on it and libraries may conflict with each other so aleatorily images arrive missing a part or not at all.
I am trying now to implement the same remote image capture with Raspberry Pi Zero and it is working as intended and is a lot more stable.
Thanks for your comment.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.