Как сохранить шестнадцатеричную строку в файл png в spiffs для создания изображения

#node.js #c #png #esp32 #spiffs

Вопрос:

Я пытаюсь отправить файл изображения с node.js сервер через соединение TCP/IP. Я преобразовал файл изображения в шестнадцатеричную строку с помощью fs.createReadStream и получил шестнадцатеричную строку, как и ожидалось на стороне клиента. Теперь мне нужно выяснить, как восстановить изображение на стороне клиента с помощью шестнадцатеричной строки.

Node.js код:

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

Код на стороне клиента (на языке C), используемый на плате разработки ESP32 для сохранения данных в файл png в spiffs:

 #define MAX_HTTP_OUTPUT_BUFFER 512
int max_buff = 512;

static void http_native_request(void)
{
    char output_buffer[MAX_HTTP_OUTPUT_BUFFER] = {0};
    int content_length = 0;
    int track_length = 0;

    esp_http_client_config_t config = {
        .url = "http://192.168.1.122/api?file=file01",
    };
    esp_http_client_handle_t client = esp_http_client_init(amp;config);

    // GET Request
    esp_http_client_set_method(client, HTTP_METHOD_GET);
    esp_err_t err = esp_http_client_open(client, 0);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
    } else {
        content_length = esp_http_client_fetch_headers(client);
        track_length = content_length;
        if (content_length < 0) {
            ESP_LOGE(TAG, "HTTP client fetch headers failed");
        } else {
            do {
            int data_read = esp_http_client_read_response(client, output_buffer, max_buff);
            output_buffer[max_buff] = '';
            create_file_app(output_buffer); //saves output_buffer to a newly created png file
            if (data_read >= 0) {
                track_length -= data_read;
                if (max_buff > track_length){
                    max_buff = track_length;
                }
            } else {
                ESP_LOGE(TAG, "Failed to read response");
            }
            } while (
                track_length>0
            );
        }
    }
    esp_http_client_close(client);
}
 

Шестнадцатеричная строка с сервера выглядит следующим образом (имеет подпись файла png):

 89504e470d0a1a0a0000000d494844520000006400000064080600 ... f03b8c85cc0643044ae0000000049454e44ae426082
 

Мое исследование показывает, что мне нужно преобразовать эту шестнадцатеричную строку в двоичные данные на стороне клиента. Но когда я это делаю (код для преобразования здесь не показан), файл png, созданный функцией create_file_app, просто отображает двоичную строку (которая правильно соответствует шестнадцатеричной строке), полученную клиентом, в отличие от отображения изображения, которое, как я думал, я загрузил с сервера. Как сохранить эти шестнадцатеричные данные, чтобы получить изображение, которое я ожидал, когда открою файл png, созданный на стороне клиента? Или есть библиотека C, которая может помочь в этом?

Edit 1:

My code for sending image data from the node.js server as is, without converting to hex or another format:

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

Here’s what the node.js console shows for «chunk»:

 <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 ... >
 

On the client side, here’s ESP’s code for esp_http_client_read_response():

 int esp_http_client_read_response(esp_http_client_handle_t client, char *buffer, int len)
{
    int read_len = 0;
    while (read_len < len) {
        int data_read = esp_http_client_read(client, buffer   read_len, len - read_len);
        if (data_read <= 0) {
            return read_len;
        }
        read_len  = data_read;
    }
    return read_len;
}
 

Esp_http_client_read_response вызывает esp_http_client_read (опять же, это было опубликовано ESP) — вот код для этого:

 int esp_http_client_read(esp_http_client_handle_t client, char *buffer, int len)
{
    esp_http_buffer_t *res_buffer = client->response->buffer;

    int rlen = ESP_FAIL, ridx = 0;
    if (res_buffer->raw_len) {
        int remain_len = client->response->buffer->raw_len;
        if (remain_len > len) {
            remain_len = len;
        }
        memcpy(buffer, res_buffer->raw_data, remain_len);
        res_buffer->raw_len -= remain_len;
        res_buffer->raw_data  = remain_len;
        ridx = remain_len;
    }
    int need_read = len - ridx;
    bool is_data_remain = true;
    while (need_read > 0 amp;amp; is_data_remain) {
        if (client->response->is_chunked) {
            is_data_remain = !client->is_chunk_complete;
        } else {
            is_data_remain = client->response->data_process < client->response->content_length;
        }

        if (!is_data_remain) {
            break;
        }
        int byte_to_read = need_read;
        if (byte_to_read > client->buffer_size_rx) {
            byte_to_read = client->buffer_size_rx;
        }
        errno = 0;
        rlen = esp_transport_read(client->transport, res_buffer->data, byte_to_read, client->timeout_ms);
        ESP_LOGD(TAG, "need_read=%d, byte_to_read=%d, rlen=%d, ridx=%d", need_read, byte_to_read, rlen, ridx);

        if (rlen <= 0) {
            if (errno != 0) {
                esp_log_level_t sev = ESP_LOG_WARN;
                /* On connection close from server, recv should ideally return 0 but we have error conversion
                 * in `tcp_transport` SSL layer which translates it `-1` and hence below additional checks */
                if (rlen == -1 amp;amp; errno == ENOTCONN amp;amp; client->response->is_chunked) {
                    /* Explicit call to parser for invoking `message_complete` callback */
                    http_parser_execute(client->parser, client->parser_settings, res_buffer->data, 0);
                    /* ...and lowering the message severity, as closed connection from server side is expected in chunked transport */
                    sev = ESP_LOG_DEBUG;
                }
                ESP_LOG_LEVEL(sev, TAG, "esp_transport_read returned:%d and errno:%d ", rlen, errno);
            }
            if (rlen < 0 amp;amp; ridx == 0) {
                return ESP_FAIL;
            } else {
                return ridx;
            }
        }
        res_buffer->output_ptr = buffer   ridx;
        http_parser_execute(client->parser, client->parser_settings, res_buffer->data, rlen);
        ridx  = res_buffer->raw_len;
        need_read -= res_buffer->raw_len;

        res_buffer->raw_len = 0; //clear
        res_buffer->output_ptr = NULL;
    }

    return ridx;
}
 

Вот мой код для create_file_app (), который сохраняет полученные данные в файле spiffs:

 void create_file_app(char *buffer)
{
    ESP_LOGI(TAG, "Opening file");
    FILE* f = fopen("/spiffs/hello1.png", "a ");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    fprintf(f, buffer);
    fclose(f);
}
 

Комментарии:

1. Как вы отображаете файл PNG? Присутствует ли шестнадцатеричная строка в виде текста в текстовом редакторе? Если у вас возникли проблемы с преобразованием шестнадцатеричной строки в двоичные данные, почему вы думаете, что функция преобразования может быть неуместной? Похоже, это самое первое место, на которое стоит посмотреть. для меня.

2. Когда я открываю файл png в браузере (скажем, Chrome), он просто показывает двоичную строку, связанную с шестнадцатеричными данными). У меня нет проблем с преобразованием шестнадцатеричной строки в двоичную строку. Но правильно ли сохранять двоичную строку в файле png или мне нужно какое-то другое расположение двоичных данных? Я очень новичок в этом деле и ценю какое-то направление.

3. Зачем вообще вам нужно конвертировать изображение в шестнадцатеричную строку? Разве вы не можете просто отправить байты как таковые? Затем создать изображение так же просто, как сохранить полученные байты.

4. @thebusybee отправка изображения только в виде буфера с сервера приводит к его получению в виде символов мусора (как и ожидалось) на стороне клиента, считываемых функцией esp_http_client_read_response ESP. Когда я сохраняю это в файл spiffs, размер файла нигде не приближается к длине данных буфера на стороне сервера. Если у вас есть идея о том, как это исправить, я был бы признателен за некоторые рекомендации, так как я относительно новичок в C.

5. Тогда вы делаете что-то не так с тем, как вы с этим справляетесь. Нет необходимости преобразовывать изображение во что-либо; вы можете просто передавать байты по HTTP. Так это обычно делается.

Ответ №1:

Вместо того, чтобы звонить fprintf() , вам нужно позвонить fwrite() .

Вы получаете двоичные байты с esp_http_client_read_response() . Сохраните возвращенную длину.

Затем используйте эту функцию для создания файла:

 void create_file_app(char *buffer, size_t length)
{
    ESP_LOGI(TAG, "Opening file");
    FILE* f = fopen("/spiffs/hello1.png", "wb");
    if (f == NULL) {
        ESP_LOGE(TAG, "Failed to open file for writing");
        return;
    }
    fwrite(buffer, 1, length, f);
    fclose(f);
}
 

Почему это fprintf() неправильная функция?

Потому что он работает только с C-строками. Это последовательности символов, которые означают тип char[] . Длина такой строки определяется специальным символом '' , который обычно имеет значение 0. И в полученных байтах много нулей. Этот символ обозначает конец строки, независимо от размера массива символов.

Массивы в C не «переносят» свою длину с собой, если вы передаете их адрес другим функциям. Вам нужно указать длину отдельно. Другие языки делают это другими способами.