Save JPEG image to GoogleDrive[GAS] with ESP32 and OV2640 (Software)

In the Video

Japanese【日本語】

English

1. Overview

Using ESP32 and OV2640, I do electronic work and a program that saves JPEG images captured by the camera to GoogleDrive. In this case, I use the Google App Acript [GAS].

Outline: Please see “Save JPEG image to GoogleDrive[GAS] with ESP32 and OV2640 (Google Setting)“.

There are three major works as follows.

2. Development environment [Arduino]

Arduino is used for software development. Arduino is available for free and runs on personal computers such as Windows, Mac, and Linux.
The computer and ESP32 development board are connected with a micro USB cable.

The Arduino IDE settings are as follows.
Please select “ESP32 Dev Module” for the motherboard to be used this time. Motherboard package ESP32 uses “1.0.6”.

See below for ArduinoIDE settings such as “Partition Scheme”.

3. Program [Software]

There are 3 files in this case. see below.

1) ESP32CamGdriveGAS.ino
2) Base64.h
3) Base64.ino

1) describes the operation of the IoT camera in the main operation program. Wi-Fi connection, camera image acquisition, image upload to GoogleDrive, and operation are described here.
2) and 3) are programs for Base64 encoding, which I am using from an external site. Since it is used for the purpose of encoding, the operation itself is not described in this file.

The program is below.

1) ESP32CamGdriveGAS.ino

//*************************************************************************
//  ESP32CamGdriveGAS Ver2022.5.2
//  Arduino Board : ESP32 by Espressif Systems ver 1.0.6
//  Written by IT-Taro
//***********************************************************************
#include <WiFiClientSecure.h>
#include "esp_camera.h"

// ##################### Google,Wi-Fi Setting #####################
const char* gasServer     = "script.google.com";
const char* gasUri        = "/macros/s/AKfycbxW### omit ###CnZHg/exec"; // $$$ CHANGE REQUIRED $$$

const char *ssid        = "##### SSID #####"; // $$$ CHANGE REQUIRED $$$
const char *password    = "### PASSWORD ###"; // $$$ CHANGE REQUIRED $$$
// ###################################################################

// Pin Setting
const byte LED_PIN      = 4;  // Green LED

// CAMERA_MODEL_M5_UNIT_CAM
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     25
#define SIOC_GPIO_NUM     23

#define Y9_GPIO_NUM       19
#define Y8_GPIO_NUM       36
#define Y7_GPIO_NUM       18
#define Y6_GPIO_NUM       39
#define Y5_GPIO_NUM        5
#define Y4_GPIO_NUM       34
#define Y3_GPIO_NUM       35
#define Y2_GPIO_NUM       32
#define VSYNC_GPIO_NUM    22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

WiFiClientSecure httpsClient;
camera_fb_t * fb;
bool ledFlag          = true; // LED Control Flag
int waitingTime      = 30000; //Wait 30 seconds to google response.

void setup() {
  // disable brownout detector 【Stop because ESP32 will restart under 2.4V】
  //WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
  
  // ####### Start setup #######
  Serial.begin(115200);
  Serial.println();
  Serial.println("Start!"); 

  // ####### Camera Setting #######
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.pixel_format = PIXFORMAT_JPEG;
  config.frame_size = FRAMESIZE_VGA;  // UXGA|SXGA|XGA|SVGA|VGA|CIF|QVGA|HQVGA|QQVGA
  config.jpeg_quality = 10;
  config.fb_count = 1;
  
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    delay(3000);
    ESP.restart();
  }

  // ####### LED PIN Setting #######
  pinMode ( LED_PIN, OUTPUT );

  // ####### Wi-Fi Connect #######
  WiFi.begin ( ssid, password );
  while ( WiFi.status() != WL_CONNECTED ) { // Loop Up to Wi-Fi connection
    // Blinks every second Up to Wi-Fi connection
    ledFlag = !ledFlag;
    digitalWrite(LED_PIN, ledFlag);
    delay ( 1000 );
    Serial.print ( "." );
  }
  // Wi-Fi connection Successfull [Display IP address]
  Serial.print ( "Wi-Fi Connected! IP address: " );
  Serial.println ( WiFi.localIP() );
  Serial.println ( );
  // LED lights when Wi-Fi is connected
  digitalWrite ( LED_PIN, true );

  // ####### Skip HTTPS Server certificate #######
  // Skip HTTPS Server certificate [ or need to set rootCA】
  httpsClient.setInsecure();//skip verification
  //httpsClient.setCACert(rootCA);// or set roootCA

  // ####### Get JPEG picture #######
  Serial.println("Start get JPG");
  getCameraJPEG();
  // ####### Save JPEG to GoogleDrive #######
  Serial.println("Start Post GoogleDrive");
  postGoogleDriveByGAS(); 

  Serial.println("GAS Completed!!!");
}

void loop() {

}

void postGoogleDriveByGAS() {

  Serial.println("Connect to " + String(gasServer));
  if (httpsClient.connect(gasServer, 443)) {
    Serial.println("Connection successful");

    String bodyTop = "filename=ESP32-CAM.jpg&"
                        "mimetype=image/jpeg&"
                        "data=";
    String imageFile = createBase64Encode((char *)fb->buf, fb->len);

    unsigned long contentsLength = bodyTop.length() + imageFile.length();
    String header = "POST " + String(gasUri) + " HTTP/1.1\r\n" +
                    "HOST: " + String(gasServer) + "\r\n" +
                    "Connection: close\r\n" +
                    "content-length: " + String(contentsLength) + "\r\n" +
                    "Content-Type: application/x-www-form-urlencoded\r\n\r\n";

    Serial.println("Send JPEG DATA by GAS");
    httpsClient.print(header);
    httpsClient.print(bodyTop);
    //  JPEGデータは1000bytesに区切ってPOST
    for (int i = 0; i < imageFile.length(); i = i+1000) {
      httpsClient.print(imageFile.substring(i, i+1000));
    }
    
    Serial.println("Waiting for response.");
    long int StartTime=millis();
    while (!httpsClient.available()) {
      Serial.print(".");
      delay(100);
      if ((StartTime+waitingTime) < millis()) {
        Serial.println();
        Serial.println("No response.");
        break;
      }
    }
    Serial.println();
    while (httpsClient.available()) {
      Serial.print(char(httpsClient.read()));
    }
  } else {
    Serial.println("Connected to " + String(gasServer) + " failed.");
  }
  httpsClient.stop();
}

// JPEG image acquisition with OV2640
void getCameraJPEG(){
  // Shoot several times and adjust AE (automatic exposure)
  esp_camera_fb_get();
  esp_camera_fb_get();
  fb = esp_camera_fb_get();  // Get JPEG
  if (!fb) {
    Serial.printf("Camera capture failed");
  }
  // 撮影終了処理
  esp_camera_fb_return(fb);
  Serial.printf("JPG: %uB ", (uint32_t)(fb->len));
}

// I referred to the following sites
// https://github.com/adamvr/arduino-base64 
String createBase64Encode(char *input, int input_len) {
  char output[base64_enc_len(3)];
  String imageFile = "";
  for (int i=0;i<input_len;i++) {
    base64_encode(output, (input++), 3);
    if (i%3==0) imageFile += urlencode(String(output));
  }
  return imageFile;
}

// I referred to the following sites
// https://github.com/zenmanenergy/ESP8266-Arduino-Examples/
String urlencode(String str)
{
    String encodedString="";
    char c;
    char code0;
    char code1;
    char code2;
    for (int i =0; i < str.length(); i++){
      c=str.charAt(i);
      if (c == ' '){
        encodedString+= '+';
      } else if (isalnum(c)){
        encodedString+=c;
      } else{
        code1=(c & 0xf)+'0';
        if ((c & 0xf) >9){
            code1=(c & 0xf) - 10 + 'A';
        }
        c=(c>>4)&0xf;
        code0=c+'0';
        if (c > 9){
            code0=c - 10 + 'A';
        }
        code2='\0';
        encodedString+='%';
        encodedString+=code0;
        encodedString+=code1;
        //encodedString+=code2;
      }
      yield();
    }
    return encodedString;
}

2) Base64.h

/*
 * Copyright (c) 2013 Adam Rudd.
 * See LICENSE for more information
 * https://github.com/adamvr/arduino-base64 
 */
#ifndef _BASE64_H
#define _BASE64_H

/* b64_alphabet:
 *     Description: Base64 alphabet table, a mapping between integers
 *           and base64 digits
 *    Notes: This is an extern here but is defined in Base64.c
 */
extern const char b64_alphabet[];

/* base64_encode:
 *    Description:
 *      Encode a string of characters as base64
 *    Parameters:
 *      output: the output buffer for the encoding, stores the encoded string
 *      input: the input buffer for the encoding, stores the binary to be encoded
 *      inputLen: the length of the input buffer, in bytes
 *    Return value:
 *      Returns the length of the encoded string
 *    Requirements:
 *      1. output must not be null or empty
 *      2. input must not be null
 *      3. inputLen must be greater than or equal to 0
 */
int base64_encode(char *output, char *input, int inputLen);

/* base64_decode:
 *    Description:
 *      Decode a base64 encoded string into bytes
 *    Parameters:
 *      output: the output buffer for the decoding,
 *          stores the decoded binary
 *      input: the input buffer for the decoding,
 *           stores the base64 string to be decoded
 *      inputLen: the length of the input buffer, in bytes
 *    Return value:
 *      Returns the length of the decoded string
 *    Requirements:
 *      1. output must not be null or empty
 *      2. input must not be null
 *      3. inputLen must be greater than or equal to 0
 */
int base64_decode(char *output, char *input, int inputLen);

/* base64_enc_len:
 *    Description:
 *      Returns the length of a base64 encoded string whose decoded
 *      form is inputLen bytes long
 *    Parameters:
 *      inputLen: the length of the decoded string
 *    Return value:
 *      The length of a base64 encoded string whose decoded form
 *      is inputLen bytes long
 *    Requirements:
 *      None
 */
int base64_enc_len(int inputLen);

/* base64_dec_len:
 *    Description:
 *      Returns the length of the decoded form of a
 *      base64 encoded string
 *    Parameters:
 *      input: the base64 encoded string to be measured
 *      inputLen: the length of the base64 encoded string
 *    Return value:
 *      Returns the length of the decoded form of a
 *      base64 encoded string
 *    Requirements:
 *      1. input must not be null
 *      2. input must be greater than or equal to zero
 */
int base64_dec_len(char *input, int inputLen);

#endif // _BASE64_H

3) Base64.ino

/*
 * Copyright (c) 2013 Adam Rudd.
 * See LICENSE for more information
 * https://github.com/adamvr/arduino-base64 
 */
#if (defined(__AVR__))
#include <avr\pgmspace.h>
#else
#include <pgmspace.h>
#endif

const char PROGMEM b64_alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

/* 'Private' declarations */
inline void a3_to_a4(unsigned char * a4, unsigned char * a3);
inline void a4_to_a3(unsigned char * a3, unsigned char * a4);
inline unsigned char b64_lookup(char c);

int base64_encode(char *output, char *input, int inputLen) {
  int i = 0, j = 0;
  int encLen = 0;
  unsigned char a3[3];
  unsigned char a4[4];

  while(inputLen--) {
    a3[i++] = *(input++);
    if(i == 3) {
      a3_to_a4(a4, a3);

      for(i = 0; i < 4; i++) {
        output[encLen++] = pgm_read_byte(&b64_alphabet[a4[i]]);
      }

      i = 0;
    }
  }

  if(i) {
    for(j = i; j < 3; j++) {
      a3[j] = '\0';
    }

    a3_to_a4(a4, a3);

    for(j = 0; j < i + 1; j++) {
      output[encLen++] = pgm_read_byte(&b64_alphabet[a4[j]]);
    }

    while((i++ < 3)) {
      output[encLen++] = '=';
    }
  }
  output[encLen] = '\0';
  return encLen;
}

int base64_decode(char * output, char * input, int inputLen) {
  int i = 0, j = 0;
  int decLen = 0;
  unsigned char a3[3];
  unsigned char a4[4];


  while (inputLen--) {
    if(*input == '=') {
      break;
    }

    a4[i++] = *(input++);
    if (i == 4) {
      for (i = 0; i <4; i++) {
        a4[i] = b64_lookup(a4[i]);
      }

      a4_to_a3(a3,a4);

      for (i = 0; i < 3; i++) {
        output[decLen++] = a3[i];
      }
      i = 0;
    }
  }

  if (i) {
    for (j = i; j < 4; j++) {
      a4[j] = '\0';
    }

    for (j = 0; j <4; j++) {
      a4[j] = b64_lookup(a4[j]);
    }

    a4_to_a3(a3,a4);

    for (j = 0; j < i - 1; j++) {
      output[decLen++] = a3[j];
    }
  }
  output[decLen] = '\0';
  return decLen;
}

int base64_enc_len(int plainLen) {
  int n = plainLen;
  return (n + 2 - ((n + 2) % 3)) / 3 * 4;
}

int base64_dec_len(char * input, int inputLen) {
  int i = 0;
  int numEq = 0;
  for(i = inputLen - 1; input[i] == '='; i--) {
    numEq++;
  }

  return ((6 * inputLen) / 8) - numEq;
}

inline void a3_to_a4(unsigned char * a4, unsigned char * a3) {
  a4[0] = (a3[0] & 0xfc) >> 2;
  a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4);
  a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6);
  a4[3] = (a3[2] & 0x3f);
}

inline void a4_to_a3(unsigned char * a3, unsigned char * a4) {
  a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4);
  a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2);
  a3[2] = ((a4[2] & 0x3) << 6) + a4[3];
}

inline unsigned char b64_lookup(char c) {
  if(c >='A' && c <='Z') return c - 'A';
  if(c >='a' && c <='z') return c - 71;
  if(c >='0' && c <='9') return c + 4;
  if(c == '+') return 62;
  if(c == '/') return 63;
  return -1;
}

Please rewrite the following settings in the “ESP32CamGdriveGAS.ino” file in the above state.

const char* gasUri        = "/macros/s/AKfycbx(省略)9ACWCnZHg/exec"; // ★変更要

const char *ssid        = "##### SSID #####"; // ★変更要
const char *password    = "### PASSWORD ###"; // ★変更要

For the setting value of gasUri, it is necessary to enter the URI obtained in “Save JPEG image to GoogleDrive[GAS] with ESP32 and OV2640 (Google Setting)“ in another post.

Write to ESP32 with the above settings rewritten.

When the above software has been written and operates normally, images will be acquired with the camera when the ESP32 is started and the files will be saved in GoogleDrive.
Check if the image file is saved in the GoogleDrive.

reference

Base64 related

GitHub - gsampallo/esp32cam-gdrive: Upload an image directly from ESP32-CAM to Google Drive
Upload an image directly from ESP32-CAM to Google Drive - GitHub - gsampallo/esp32cam-gdrive: Upload an image directly f...

Google Developer Guide (below is about “utilities”)

Class Utilities  |  Apps Script  |  Google for Developers

Comments

Copied title and URL