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 GoogleDrive API [API].
Outline: Please see “Save JPEG image to GoogleDrive[API] with ESP32 and OV2640 (Overview)“
There are three major works as follows.
- [1] Google Cloud Setting
Please see “Google Cloud Cloud APIs [GoogleDrive for Arduino program]“. - [2] Electric Work [Hardware]
Please see “IoT camera production using ESP32 and OV2640 [Hardware]“ - [3] Program [Software]
This Post. Details are provided below.
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]
This program works as below.
1) POST the refresh token, client ID, and secret key to get the access token.
2) POST the access token and image file information and upload the file.
Create a new file in ArduinoIDE and copy and paste the following program. Please rewrite the CHANGE REQUIRED parameter to your environment and then write it to ESP32.
//*************************************************************************
// ESP32CamGdriveAPI Ver2022.5.2
// Arduino Board : ESP32 by Espressif Systems ver 1.0.6
// Written by IT-Taro
//***********************************************************************
#include <WiFiClientSecure.h>
#include "esp_camera.h"
// ##################### Setting[Google,Wi-Fi] #####################
String clientId = "413$$[omission]$$googleusercontent.com"; // $$$ CHANGE REQUIRED $$$
String clientSecret = "GOC$$[omission]$$mdgijDWEOzAIBl85PxxDQ"; // $$$ CHANGE REQUIRED $$$
String refreshToken = "1//$$[omission]$$K_OQvtejx2P3V8JG8M-lk"; // $$$ CHANGE REQUIRED $$$
String driveFolder = "1fp$$[omission]$$Ma9eVQasfsrrg3skub38q"; // Folder ID of GoogleDrive $$$ CHANGE REQUIRED $$$
const char *ssid = "##### SSID #####"; // $$$ CHANGE REQUIRED $$$
const char *password = "### PASSWORD ###"; // $$$ CHANGE REQUIRED $$$
// ###################################################################
const char* refreshServer = "oauth2.googleapis.com";
const char* refreshUri = "/token";
const char* apiServer = "www.googleapis.com";
const char* apiUri = "/upload/drive/v3/files?uploadType=multipart";
String accessToken = "";
// Pin Settings
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() {
// ####### Start Setup #######
Serial.begin(115200);
Serial.println();
Serial.println("Start!");
// ####### Setting of Camera #######
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;
// Newly Added! for "No PSRAM" Board "ESP32 ver 2.0.2" and "M5Stack-Unit-Cam"
// ESP32 1.0.6 not required
//config.fb_location = CAMERA_FB_IN_DRAM;
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();
}
// ####### Green LED #######
pinMode ( LED_PIN, OUTPUT );
// ####### Wi-Fi #######
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 );
// ####### NTP setting #######
configTime(9 * 3600L, 0, "ntp.nict.jp", "time.google.com", "ntp.jst.mfeed.ad.jp");
// ####### 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();
// ####### get Access Token #######
Serial.println("Start get AccessToken");
getAccessToken();
// ####### Save JPEG to GoogleDrive #######
Serial.println("Start Post GoogleDrive");
postGoogleDriveByAPI();
Serial.println("API Completed!!!");
}
// Loop task
void loop() {
delay(100);
}
// Send JPEG by Http POST
void postGoogleDriveByAPI() {
Serial.println("Connect to " + String(apiServer));
if (httpsClient.connect(apiServer, 443)) {
Serial.println("Connection successful");
// Get Time for save file name
struct tm timeInfo;
char preFilename[16];
getLocalTime(&timeInfo);
sprintf(preFilename, "%04d%02d%02d_%02d%02d%02d",
timeInfo.tm_year + 1900, timeInfo.tm_mon + 1, timeInfo.tm_mday,
timeInfo.tm_hour, timeInfo.tm_min, timeInfo.tm_sec);
String saveFilename = "esp32_" + String(preFilename) + ".jpg";
String metadata = "--foo_bar_baz\r\n"
"Content-Type: application/json; charset=UTF-8\r\n\r\n"
"{\"name\":\"" + saveFilename + "\",\"parents\":[\"" + driveFolder + "\"]}\r\n\r\n"; // parents:save folder
//"{\"name\":\"" + saveFilename + "\"}\r\n\r\n"; // parerents is Optional
String startBoundry = "--foo_bar_baz\r\n"
"Content-Type:image/jpeg\r\n\r\n";
String endBoundry = "\r\n--foo_bar_baz--";
unsigned long contentsLength = metadata.length() + startBoundry.length() + fb->len + endBoundry.length();
String header = "POST " + String(apiUri) + " HTTP/1.1\r\n" +
"HOST: " + String(apiServer) + "\r\n" +
"Connection: close\r\n" +
"content-type: multipart/related; boundary=foo_bar_baz\r\n" +
"content-length: " + String(contentsLength) + "\r\n" +
"authorization: Bearer " + accessToken + "\r\n\r\n";
Serial.println("Send JPEG DATA by API");
httpsClient.print(header);
httpsClient.print(metadata);
httpsClient.print(startBoundry);
// JPEG data is separated into 1000 bytes and POST
unsigned long dataLength = fb->len;
uint8_t* bufAddr = fb->buf;
for(unsigned long i = 0; i < dataLength ;i=i+1000) {
if ( (i + 1000) < dataLength ) {
httpsClient.write(( bufAddr + i ), 1000);
} else if (dataLength%1000 != 0) {
httpsClient.write(( bufAddr + i ), dataLength%1000);
}
}
httpsClient.print(endBoundry);
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()));
}
/*Serial.println("Recieving Reply");
while (httpsClient.connected()) {
String retLine = httpsClient.readStringUntil('\n');
//Serial.println("retLine:" + retLine);
int okStartPos = retLine.indexOf("200 OK");
if (okStartPos >= 0) {
Serial.println("200 OK");
break;
}
}*/
} else {
Serial.println("Connected to " + String(refreshServer) + " failed.");
}
httpsClient.stop();
}
// Update access token with refresh token (access token valid time is 1 hour)
void getAccessToken() {
accessToken = "None";
// ####### Get Access Token #######
Serial.println("Connect to " + String(refreshServer));
if (httpsClient.connect(refreshServer, 443)) {
Serial.println("Connection successful");
String body = "client_id=" + clientId + "&" +
"client_secret=" + clientSecret + "&" +
"refresh_token=" + refreshToken + "&" +
"grant_type=refresh_token";
// Send Header
httpsClient.println("POST " + String(refreshUri) + " HTTP/1.1");
httpsClient.println("Host: " + String(refreshServer));
httpsClient.println("content-length: " + (String)body.length());
httpsClient.println("Content-Type: application/x-www-form-urlencoded");
httpsClient.println();
// Send Body
httpsClient.println(body);
Serial.println("Recieving Token");
while (httpsClient.connected()) {
String retLine = httpsClient.readStringUntil('\n');
//Serial.println("retLine:" + retLine);
int tokenStartPos = retLine.indexOf("access_token");
if (tokenStartPos >= 0) {
tokenStartPos = retLine.indexOf("\"", tokenStartPos) + 1;
tokenStartPos = retLine.indexOf("\"", tokenStartPos) + 1;
int tokenEndPos = retLine.indexOf("\"", tokenStartPos);
accessToken = retLine.substring(tokenStartPos, tokenEndPos);
Serial.println("AccessToken:"+accessToken);
break;
}
}
} else {
Serial.println("Connected to " + String(refreshServer) + " failed.");
}
httpsClient.stop();
if (accessToken == "None") {
Serial.println("Get AccessToken Failed. Restart ESP32!");
delay(3000);
ESP.restart();
}
}
// 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");
}
// Shooting end processing
esp_camera_fb_return(fb);
Serial.printf("JPG: %uB ", (uint32_t)(fb->len));
}
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.
Comments