ESP32でGoogle Apps Scriptに接続できない場合の対処法

ESP32-camのHTTPS接続の設定 マイコン

AI Thinker ESP32-CAM で使用するオブジェクトの仕様変更により、ver3.0.1では Google Apps Script (GAP)に接続できなくなりました。
ボードバージョン1.0.4なら問題なく接続できるので、修正が面倒な人はこちらをどうぞ。
【解決済み】ESP32-camがGoogleドライブに繋がらなくなった

HTTPS接続の設定で、WiFiClientSecureに対して証明書の設定が必要になったようです。

とりあえず証明書対応版をコーディングしてみたので公開します。

PR

Webカメラ(ESP32-CAM)のコーディング

サイト(HTPPS)の証明書をダウンロード

Googleのページを開いて次の手順で証明書をダウンロードします。

アドレスバー左のアイコンをクリック → この接続は保護されています
サイトから証明書の発行手順

 

証明書は有効ですをクリック
サイトから証明書の発行手順2

詳細タブ → google.com又は一番上の階層を選択 → エクスポート
HTTPS接続の設定用の証明書のダウンロード

証明書がダウンロードされます。
拡張子が .crt となっていますが、メモ帳で開くと証明書が表示されます。

-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
(略)
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----

ダウンロードした証明書をコードへ追加

証明書は改行が面倒なので、EOF でひとまとめにしています。

WiFiClientSecure オブジェクトに対して証明書を設定します。
WiFiClientSecure client; の後に 取得した証明書であるclient.setCACert(google_root_ca);を追加します。

#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "Base64.h"
#include "esp_camera.h"

const char* ssid = "0123456789"; // your network SSID
const char* password = "0123456789"; // your network password

const char* myDomain = "script.google.com";

String myScript = "/macros/s/0ABCDEFG123456789/exec"; //Replace with you
String myFilename = "filename=ESP32-CAM.jpg";
String mimeType = "&mimetype=image/jpeg";
String myImage = "&data=";

int waitingTime = 30000; //Googleスクリプトのレスポンス待機時間(30秒)

#define PWDN_GPIO_NUM 32
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 0
#define SIOD_GPIO_NUM 26
#define SIOC_GPIO_NUM 27

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

// Google certificate
const char* google_root_ca PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo
27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w
Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw
TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl
qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH
szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8
Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk
MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92
wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p
aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN
VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID
AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E
FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb
C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe
QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy
h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4
7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J
ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef
MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/
Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT
6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ
0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm
2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb
bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c
-----END CERTIFICATE-----
)EOF";
void setup()
{
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);

Serial.begin(115200);
delay(10);

WiFi.mode(WIFI_STA);

Serial.println("");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);

while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}

Serial.println("");
Serial.println("STAIP address: ");
Serial.println(WiFi.localIP());

Serial.println("");

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(1000);
ESP.restart();
}
}

boolean enviar = true;

void loop() {
if (enviar) {
saveCapturedImage();
enviar = false;
} else {
delay(180000); // 180秒待機
enviar = true;
}
}

void saveCapturedImage() {
Serial.println("Connect to " + String(myDomain));

WiFiClientSecure client;
client.setCACert(google_root_ca);

if (client.connect(myDomain, 443)) {
Serial.println("Connection successful");

camera_fb_t * fb = NULL;
fb = esp_camera_fb_get(); 
if(!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
return;
}

char *input = (char *)fb->buf;
char output[base64_enc_len(3)];
String imageFile = "";
for (int i=0;i<fb->len;i++) {
base64_encode(output, (input++), 3);
if (i%3==0) imageFile += urlencode(String(output));
}
String Data = myFilename+mimeType+myImage;

esp_camera_fb_return(fb);

Serial.println("Send a captured image to Google Drive.");

client.println("POST " + myScript + " HTTP/1.1");
client.println("Host: " + String(myDomain));
client.println("Content-Length: " + String(Data.length()+imageFile.length()));
client.println("Content-Type: application/x-www-form-urlencoded");
client.println();

client.print(Data);
int Index;
for (Index = 0; Index < imageFile.length(); Index = Index+1000) {
client.print(imageFile.substring(Index, Index+1000));
}

Serial.println("Waiting for response.");
long int StartTime=millis();
while (!client.available()) {
Serial.print(".");
delay(100);
if ((StartTime+waitingTime) < millis()) {
Serial.println();
Serial.println("No response.");
//If you have no response, maybe need a greater value of waitingTime
break;
}
}
Serial.println(); 
while (client.available()) {
Serial.print(char(client.read()));
} 
} else { 
Serial.println("Connected to " + String(myDomain) + " failed.");
}
client.stop();
}

//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;
}

これで接続できると思います。

しかし、Google Apps Script の動作が安定しません。
体感的に10%の割合でエラーとなります。 暫定的に待機時間をデフォルトの30秒から60秒にして対応しますが、30秒で問題が無ければOKかと思います。
少し安定した気もしますが、ボードバージョン1.0.4に比べると不安定ですね。

PR

追伸(2024/6/15)

60時間ほど稼働してみましたが、最初の立ち上げ時の不安定さが無くなりました。
現在では、ボードバージョン1.0.4と同等です。

よって、今後のことを考えると、ボードバージョンは3.0.1を使用することをお勧めします。
証明書の取得と「client.setCACert(google_root_ca);」の記述だけなので、手間はそんなにかからないと思います。

PR

関連記事

参考にさせていただいたサイト

Arduino – ESP32 WiFiClientSecure ライブラリで、安定して https ( SSL )記事をGETする方法

 

コメント

タイトルとURLをコピーしました