如何将十六进制字符串保存到spiffs中的png文件以创建图像

How to save hex string to png file in spiffs to create image

我正在尝试通过 TCP/IP 连接从 node.js 服务器发送图像文件。我使用 fs.createReadStream 将图像文件转换为十六进制字符串,并在客户端收到了预期的十六进制字符串。现在我需要弄清楚如何使用十六进制字符串在客户端重建图像。

Node.js代码:

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

ESP32 开发板上用于将数据保存到 spiffs 中的 png 文件的客户端代码(C 语言):

#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(&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] = '[=11=]';
            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

我的研究表明我需要在客户端将这个十六进制字符串转换为二进制数据。但是当我这样做时(此处未显示转换代码),由 create_file_app 函数创建的 png 文件仅显示客户端接收到的二进制字符串(正确对应于十六进制字符串),而不是显示我生成的图像以为我是从服务器下载的。如何保存此十六进制数据,以便在打开客户端创建的 png 文件时获得预期的图像?或者是否有 C 库可以帮助解决这个问题?

编辑 1:

我的代码用于从 node.js 服务器按原样发送图像数据,而不转换为十六进制或其他格式:

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

以下是 node.js 控制台针对“块”显示的内容:

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

在客户端,这是 esp_http_client_read_response() 的 ESP 代码:

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 && 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 && errno == ENOTCONN && 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 && 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);
}

您需要调用 fwrite().

而不是调用 fprintf()

您收到带有 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[] 类型的意思。这种字符串的长度由一个特殊字符'[=16=]'决定,它的值通常为0。接收到的字节中有很多零。该字符标志着字符串的结束,与字符数组的大小无关。

如果您将数组的地址交给其他函数,C 中的数组不会“携带”它们的长度。您需要单独提供长度。其他语言以其他方式做到这一点。