0
votes

I'm trying to send an image from a node.js server to ESP32 chip using WIFI and ESP-IDF. I believe this transfer happens over a TCP/IP connection. I'm able to send a regular text data using a http get request or the http_perform_as_stream_reader function shown below. But when it comes to transferring an image from the node.js server, I'm unable to do so. I seem to be receiving garbage on the client side. The ultimate goal is to save the image into a spiffs file on ESP32. Here's my code:

Server side:

const {imageprocess, convertToBase64} = require('../canvas');
const fs = require('fs');
const express = require('express');
const router = express.Router();
const path = require('path');

const imageFile = path.resolve(__dirname, "../images/file31.png")

router.get('/', funcAllStream)

module.exports = router;


function funcAllStream(req, res, next){
        newStream(res, imageFile)
}

function newStream(res, imageFile){
  var readStream = fs.createReadStream(imageFile);
  readStream.on('data', chunk => {
    res.send(chunk.toString('hex'));
  })
}

Client side (C++):

static void http_perform_as_stream_reader(void)
{
    char *buffer = malloc(MAX_HTTP_RECV_BUFFER + 1);
    esp_http_client_config_t config = {
        .url = "http://192.168.1.155:8085/api?file=image1.png"
    };
    esp_http_client_handle_t client = esp_http_client_init(&config); //calls esp_http_client_init from standard ESP32 component esp_http_client
    esp_err_t err;
    if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
        return;
    }
    int content_length =  esp_http_client_fetch_headers(client); //calls esp_http_client_fetch_headers from standard ESP32 component esp_http_client
    int total_read_len = 0, read_len;
    if (total_read_len < content_length && content_length <= MAX_HTTP_RECV_BUFFER) {//MAX_HTTP_RECV_BUFFER defined to be larger than image1.png file on server side
        read_len = esp_http_client_read(client, buffer, content_length); //calls esp_http_client_read from standard ESP32 component esp_http_client
        if (read_len <= 0) {
            ESP_LOGE(TAG, "Error read data");
        }
        buffer[read_len] = 0;
    }
    esp_http_client_close(client);
    esp_http_client_cleanup(client);
    free(buffer);
}

http_perform_as_stream_reader();

On the client side, I've made sure buffer has been allocated more space than the image file size. When I print out what's been stored in buffer on the client side, I see absolute garbage. Server side sends buffer stream that looks like this:

<Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 64 00 00 00 64 08 06 00 00 00 70 e2 95 54 00 00 00 06 62 4b 47 44 00 ff 00 ff 00 ff a0 bd a7 ...>

This is what the client buffer looks like before receiving data:

���?Lv�~sN\
L��8J��s-�z���a�{�;�����\���~�Y���Di�2���]��|^,�x��N�݁�����`2g����n�w��b�Y�^���a���&��wtD�>n@�PQT�(�.z��(9,�?İ�

This is what client buffer looks like after receiving data:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/octet-stream
Content-Length: 4517
ETag: W/"11a5-IhqwFPYLgC+NRfikTwS2exLtCWQ"
Date: Mon, 19 Apr 2021 16:15:48 GMT
Connection: keep-alive

�PNG

So how do I ensure that the client side buffer actually receives correct data in the correct format from the server side? Ultimately, I want to store the data in a spiffs file on the client side and open the file to display the image transmitted from the node.js server.

Update: After converting data to hex format on the server side, I can confirm that the client receives the correct hex string. This is what I see on both the server and client side:

89504e470d0a1a0a0000000d4948445200000064000000 ... 0fcc3c0ff03b8c85cc0643044ae0000000049454e44ae426082

Starting and ending signatures are consistent with that of a png file.

I've set the client side buffer at 10000 (content length is 9037 bytes). Still, I receive the hex string in two chunks. In my client side code, function http_perform_as_stream_reader calls esp_http_client_fetch_headers from the ESP component esp_http_client, which in turn calls the lwip_rec_tcp function from lwip/src/api/sockets.c. Since I've set the buffer capacity to 10000 (a rather large amount), esp_http_client_fetch_headers fetches a rather large chunk of the hex string along with the headers. Then when the http_perform_as_stream_reader function calls the esp_http_client_read function, it again calls lwip_rec_tcp which now runs a do ... while loop until it retrieves the remaining server side data. Since lwip_rec_tcp stores all the data in the buffer, which has low storage capacity (certainly not up to the 10000 that I set for it in the code), the first chunk gets overwritten by the final chunk of data. So how do I ensure that the client->response->buffer pointer captures all the chunks of data without modifying lwip_rec_tcp to include uploading data to the spiffs file inside the do ... while loop?

1
@rustyx2: That's what the server sends. That's not what the client buffer receives. That's what I'd like for client buffer to display. Is there something else the server should send as part of headers before sending the buffer data over to the client? - coder101
The server certainly doesn't send the PNG in hex. It's sending it as raw binary. The code in it simply displays it as hex. Presumably raw binary is what you saw, but because you didn't include the code that output what you received, we can't say for sure. - romkey
@romkey: Updated question to show what client side buffer looks like before and after receiving data from server - coder101
You're outputting raw binary without converting it to hex, so of course it looks like garbage. Image files look like garbage if you just dump the raw bytes to a terminal. - romkey
@coder101 How large is the picture? The usable memory size on an esp is usually not more than 200-300k. And please add a check whether your malloc call returns 0. Better use new since you're using C++. I think the comments before are misleading. Please print out the buffer as hex, like you did on the server side, then you can compare. If you print out binary with printf or so, it will stop at any 0 byte which might just be part of your image. The out put of the program on the ESP would also be nice :) - StrawHat

1 Answers

1
votes

Application should not assume that esp_http_client_read reads same number of bytes specified in the length argument. Instead, application should check the return value of this API which indicates number of bytes read by corresponding call. If the return value is zero, it indicates that complete data is read.

To work correctly, application should call esp_http_client_read in a while loop with a check for return value of esp_http_client_read.

You can also use esp_http_client_read_response, which is a helper API for esp_http_client_read to internally handle the while loop and read complete data in one go.

Please refer http_native_request example which uses esp_http_client_read_response() API.