Manual

do

Maker

.

com

Transferir uma imagem jpg por serial - ESP8266 e ESP32

Transferir uma imagem jpg por serial - ESP8266 e ESP32

Nesse penúltimo artigo da série sobre manipulação de dados binários, vamos ver como transferir uma imagem jpg por serial para o sistema de arquivos padrão do ESP8266/ESP32, o SPIFFS. Para ficar mais interessante, vamos transferir uma imagem jpg e exibí-la no browser.

Conceitos

Atualmente o sistema de arquivos do ESP8266/ESP32 é o LittleFS. A sintaxe é a mesma, basta trocar a referência, caso já esteja utilizando-o. Se ainda estiver usando SPIFFS, o código permanece inalterado.

Nesse e também nesse artigo você encontra informações importantes sobre manipulação de dados binários. Agora, para transferir dados binários por um meio, precisamos escrever e ler em formato binário (óbvio?).

No SPIFFS não foi possível utilizar o estilo POSIX para escrever arquivos, apesar de disponível. Provavelmente esse recurso pode ser utilizado para escrever em um cartão SD, mas no SPIFFS devemos utilizar os recursos da própria API.

Normalmente utilizamos o modo "wb" para escrever arquivos binários, mas no SPIFFS simplesmente utilizamos "w" e para escrever o dado binário, utilizamos write.

Na escrita da imagem jpg por serial, optei por escrever byte a byte invés de passar o buffer. Também escrevi uma singela condicional para comparar os dois últimos bytes do buffer a partir do quinto byte, afim de procurar a assinatura do footer para pressupor que a transferência do arquivo de imagem foi completamente transferido. É só uma prova de conceito, não espere nenhum tratamento especial no código.

Uma coisa importante a se ter em mente é que, após subir um firmware para o ESP32 (poderia ser o 8266, mas estou utilizando o 32), se abrir a serial ainda terá mensagem do último estado. Isso significa que deve-se reiniciar o ESP32 para releitura das rotinas desse código.

Transferir uma imagem jpg por serial

O primeiro passo é definir como provar o conceito. Para tal, configurei um servidor web no ESP32. Estou utilizando a IDE Atom com PlatformIO para programar, devido aos recursos de auto-completação, abas paralelas etc. Instale a biblioteca ESPAsyncWebServer antes de compilar o código disposto mais adiante.

Uma imagem jpg é um arquivo binário. Poderia ser qualquer coisa que desejasse; um firmware, um executável de windows, um arquivo com compressão etc. A imagem utilizada foi essa:

bb.webp

 

Ela tem 3940 bytes e sua transferência deve levar em torno de 12 segundos. É um tempo altíssimo para transferir 4kB, mas  lembre-se que estamos fazendo uma transferência por serial com baudrate de 115200 (115200 0's e 1's por segundos). Coloque-a  no mesmo diretório que for escrever o código Python.

Eu utilizei o bpython3 para escrever em fluxo, mas poderia ser um arquivo a ser executado, sem problemas, desde que esteja no mesmo nível de diretório do arquivo ou que o caminho completo para o arquivo seja passado.

Para monitorar a serial, criei uma task (se não sabe como criar uma task no ESP32, dê uma lida nos artigos relacionados através desse menu). Uma função escrever os dados para arquivo quando o buffer condizer com uma imagem jpg. Após subir a imagem, reinicie o ESP32 (sem problemas repetir o processo antes de reiniciar).

O código para transferir a imagem jpg por serial no ESP32 ficou assim:

#include <Arduino.h>
#include <WiFi.h>
#include "FS.h"
#include "SPIFFS.h"
#include "ESPAsyncWebServer.h"
#include <stdio.h>

const char* ssid     = "meu_ssid";
const char* password = "minha_senha";

byte d1[3] = {0xFF,0xD8,0xFF};

uint8_t buf[4000] = {0};
int buf_len = 0;

AsyncWebServer server(80);

void deleteFile(fs::FS &fs);

void writeFile(fs::FS &fs){
    const char *path = "/img.jpg";

    if (SPIFFS.exists(path)){
        deleteFile(SPIFFS);
    }

    File file = fs.open("/img.jpg", "w");
    if(!file){
        return;
    }

    for (int i=0;i<buf_len;i++){
        file.write(buf[i]);
    }
    file.close();

    for (byte i=0; i<10;i++){
      digitalWrite(2,HIGH);
      delay(300);
      digitalWrite(2,LOW);
      delay(300);
    }

    for (int i=0;i<4000;i++){
      buf[i] = 0;
      buf_len = 0;
    }
}

void readFile(fs::FS &fs){
    const char *path = "/img.jpg";
    Serial.printf("Reading file: %s\n", path);

    if (path[0] != '/'){
      Serial.println("File path needs start with /. Change it.");
    }

    File file = fs.open(path,FILE_READ);
    if(!file || file.isDirectory()){
        Serial.println("Failed to open file for reading");
        return;
    }

    Serial.println("Read from file: ");
    memset(buf,0,4000);
    while(file.available()){
        file.readBytesUntil(0xD9, buf, 4000);
    }
    file.close();
    Serial.write((unsigned int)buf);
}

void deleteFile(fs::FS &fs){
    const char * path = "/img.jpg";
    if (path[0] != '/'){
      Serial.println("File path needs start with /. Change it.");
    }
    if(fs.remove(path)){
        return;
    }
}

void appendFile(fs::FS &fs, byte message){
    const char *path = "/img.jpg";

    Serial.printf("Appending to file: %s\n", path);

    File file = fs.open(path, FILE_APPEND);
    if(!file){
        Serial.println("Failed to open file for appending");
        return;
    }

    if(file.write(message)){
        Serial.println("Message appended");
    } else {
        Serial.println("Append failed");
    }
    file.close();
}

void pisca(int d){
  for (byte i=0;i<10;i++){
    digitalWrite(2,HIGH);
    vTaskDelay(pdMS_TO_TICKS(d));
    digitalWrite(2,LOW);
    vTaskDelay(pdMS_TO_TICKS(d));
  }
}

void sentinel(void *pvParameters){
  while (true){
      while (Serial.available()){
        digitalWrite(2,HIGH);
          buf[buf_len] = Serial.read();
          if (buf_len > 5){ //no minimo tem que ter header e footer
              if (buf[buf_len-2] == 0xFF && buf[buf_len-1] == 0xD9){
                  //Serial.write(*buf);
                  writeFile(SPIFFS);
              }
          }
          buf_len += 1;
      }
      digitalWrite(2,LOW);
      vTaskDelay(pdMS_TO_TICKS(10));
  }
}

void setup() {
  pinMode(2,OUTPUT);
  digitalWrite(2,LOW);
     Serial.begin(115200);
     WiFi.begin(ssid, password);
     while (WiFi.status() != WL_CONNECTED) {
         delay(500);
     }
     Serial.println(WiFi.localIP());

     if (!SPIFFS.begin(true)){
        Serial.println("Couldn't mount the filesystem.");
     }
     if (SPIFFS.exists("/img.jpg")){
       Serial.println("exists!!!!!");
     }
     server.on("/img", HTTP_GET, [](AsyncWebServerRequest *request){
         request->send(SPIFFS, "/img.jpg", "image/jpeg");
       });

     server.begin();
     byte bb[1];
     bb[0] = 0xD8;

     if (SPIFFS.exists("/img.jpg")){
        Serial.println("/img.jpg exists!");
        File t = SPIFFS.open("/img.jpg","r");
        uint8_t v[2];
        t.Stream::readBytes(v, 1);
        t.close();
        Serial.Print::write(v, 1);
        Serial.println(v[0]);
      }

     xTaskCreatePinnedToCore(sentinel,"sentinel",10000,NULL,0,NULL,0);
}

void loop() {}

Após subir o firmware e reiniciar o ESP32, abra um terminal (se estiver utilizando Linux), instale a biblioteca serial e execute o comando bpython3.

sudo su
apt-get install python3-bpython python3-pyserial
exit
bpython3

Antes de seguir, abra um browser e digite a URL (substituindo o IP pelo do seu ESP32):

http://192.168.1.110/img

Como ainda não tem arquivo, deve aparecer isso:

img_jpg-300x217.webp

Deixe o browser aberto e siga com o resto do processo.

O código para transferir a imagem jpg por serial com Python em um PC não deve funcionar em  Python2:

import serial
s          = serial.Serial()
s.baudrate = 115200
s.port     = '/dev/ttyUSB0' #coloque a porta referente ao seu sistema

f    = open("bb.jpg","wb")
data = f.read()

f.close()
s.open()

for i in data:
    s.write(i.to_bytes(1,'big'))

s.close()

Preferi enviar byte a byte. Se quiser modificar, fazer melhor, mais bonito, mais eficiente, fique à vontade. Eu tive um bug que nem estou interessado em resolver; tive que enviar duas vezes seguidas (as duas linhas do laço for). Funcionou, para o conceito é o que importa.

Após enviar a imagem, reinicie o ESP32 e atualize o browser. Se tudo der certo, você verá isso:

img_jpg-good-300x220.webp

Próximo artigo relacionado

Pretendo finalizar a série no próximo artigo, onde devemos ser capazes de transferir a mesma imagem por rádio frequência, possivelmente utilizando LoRa, mas poderia ser também um NRF24L01, veremos.

Inscreva-se no nosso canal Manual do Maker no YouTube.

Também estamos no Instagram.

Nome do Autor

Djames Suhanko

Autor do blog "Do bit Ao Byte / Manual do Maker".

Viciado em embarcados desde 2006.
LinuxUser 158.760, desde 1997.