ESP32-CAMでGoogleDriveへ画像送信するのにドハマりした件

備忘録として残しておく。
ほかにも同じハマりをしている方の参考になれば幸甚。


ESP32-CAMを使い、画像をGoogle Driveへ保存しようと思い立つ。
調べると、まんま同じことをやられている先達がいらっしゃるので参考にすることに。

https://s-fishing.com/resthouse/view/414?pathinfo=view/414

https://www.filipeflop.com/blog/tire-fotos-com-esp32-cam-e-armazene-no-google-drive/

※Google自動翻訳機能のおかげでスペイン語？ポルトガル語？でも無問題。

GASでやれるって話なので、ESP用コードとGASコードはまんまコピペすることに。

ESP用のコード

```php
#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     = "XXXX";   //your network SSID
const char* password = "XXXX";   //your network password
const char* myDomain = "script.google.com";
String myScript = "/macros/s/XXXXXX/exec";    //Replace with your own url
String myFilename = "filename=ESP32-CAM.jpg";
String mimeType = "&mimetype=image/jpeg";
String myImage = "&data=";

int waitingTime = 30000; //Wait 30 seconds to google response.

#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

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;
    delay(60000);
  //}
}

void saveCapturedImage() {
  Serial.println("Connect to " + String(myDomain));
  WiFiClientSecure client;
  
  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;
}
```

GASのコード

```php
function doPost(e) { 
  var data = Utilities.base64Decode(e.parameters.data);
  var nombreArchivo = Utilities.formatDate(new Date(), "Asia/Tokyo", "yyyyMMdd_HHmmss")+".jpg";
  var blob = Utilities.newBlob(data, e.parameters.mimetype, nombreArchivo );
  
  
  var folder, folders = DriveApp.getFoldersByName("ESP32-CAM");
  if (folders.hasNext()) {
    folder = folders.next();
  } else {
    folder = DriveApp.createFolder("ESP32-CAM");
  }
  var file = folder.createFile(blob); 
  
  return ContentService.createTextOutput("Completo.")
}
```

ここまで整ってたら楽勝やな～。
これで書き込み＆デプロイすれば完了や～…
と思ったら、うまく画像保存されない。
調べていくと、

```php
client.connect(myDomain, 443)
```
がダメ。
接続成功で1を返すはずが、出力させると0になってる。

[GAS側かESP側か]
過去に作った、スプレッドシートに温度入力するプログラムを掘り起こして、
そちらを現環境で書き込んでもNG。
ってことはGAS問題ではなく、ESP側問題と絞り込み。
ふと振り返ると、"script.google.com"へのアクセスで引っかかってるので、
GASのコードやデプロイ状況は全く関係ないやね…

[ハードかソフトか]
ESPをもう1個の別ESP32-CAMにしたり、普通のESP32使ったりにしても、全部NG。
同じく出力0状態で変わらず。
よってソフト問題と絞り込み。

[問題特定]
ESPコードを何度も見返したが、特に問題はない。
あと残るは環境。最近PC変えたのでそのせいかも。。。
と考え、旧PCを用意しつつ、ふとライブラリのバージョンはどうなんだろと思いつく。
client.connectは#include <WiFiClientSecure.h>で展開されているので、
このバージョンを調べる。
どうもESPボードの更新と一緒についてくるらしい。
ESPボードのバージョンは1.0.6で3月に更新されている。
ダメ元で旧バージョンを試してみることに。

1.0.5 ⇒やっぱダメか…　もう一段階さかのぼってみるか…
1.0.4 ⇒ｷﾀ━━━━(ﾟ∀ﾟ)━━━━!!

[結論]
WiFiClientSecure.h のバージョンが問題点だったと特定。
何がダメなのかはとりあえずもう知らん。
これからはひとまず1.0.4を使っとく。
二日間はまったけど、とりあえずちゃんと動くようになって良かった。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1112433/22359a24-f233-d32b-f186-3090723fbcd6.png)