--> -->

skimemo


skimemo - 日記/2021-06-27/ESP32-CAMで写真を撮ってサーバーに転送する の変更点


#blog2navi()
*ESP32-CAMで写真を撮ってサーバーに転送する [#g666f0c4]

#title(ESP32-CAMで写真を撮ってサーバーに転送する)

[[前回の記事:https://www.webdb.co.jp/~atsumi/skimemo/index.php?skimemo%20-%20%C6%FC%B5%AD%2F2021-05-14%2FRENOGY%20ROVER%20ELITE%A4%CE%BE%F0%CA%F3%A4%F2RS-485%B7%D0%CD%B3%A4%C7ESP32-WROOM%A4%CB%BC%F5%A4%B1%A4%C6WEB%A5%B5%A1%BC%A5%D0%A1%BC%A4%CB%C8%F4%A4%D0%A4%B9]]で味を占め、ESP32-CAMを使って定期的に写真を撮ってサーバーに転送してみました。~

** ESP32-CAM [#bd08fbc4]
小さいのに写真が撮れて、フラッシュも点いて、Wifi通信もできる優れものです。~
例によってAliExpressで送料込みで900円ほどです。~
https://ja.aliexpress.com/item/1005001970568846.html ~
#ref(camera.png)~
~
このESP32-CAM(単体)は以下の点が要注意です。
+ 技適未対応((■約800円のESP32-CAMで乾電池駆動のWebカメラサーバーを立てる&br;https://qiita.com/Nabeshin/items/b195cad1afe99ce29f1e))~
+ USB接続には別ボードが必要~

上記の商品はESP32-CAM-MBという開発用ボードがセットになっており、ここにUSB接続することでプログラムを書き込めます。~

** ソース [#k40cdb00]
HW的には特にすることは無いのでソースです。

#code2(c){{{
#include "esp_camera.h"
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <SPIFFS.h>
#include <FS.h>

#define WIFI_SSID        "[SSID]"
#define WIFI_PASSWORD    "[PASSWORD]"
#define UPLOAD_INTERVAL  15000  // WEB upload interval(ms)
#define STRING_BOUNDARY "ABCDEFG0123456789"
#define STRING_MULTIHEAD1 "Content-Disposition: form-data; name=\"uploadFile\"; filename=\"./camera.jpg\""
#define STRING_MULTIHEAD2 "Content-Type: image/jpeg"

#define STATUS_SUCCESS 0;
#define STATUS_CAMERA_FAILED 1;
#define STATUS_SAVE_FAILED 2;

WiFiMulti wifiMulti;

//TEST
const int ledPin = 4;
constexpr int kCameraPin_PWDN   =  32;
constexpr int kCameraPin_RESET  =  -1;  // NC
constexpr int kCameraPin_XCLK   =   0;
constexpr int kCameraPin_SIOD   =  26;
constexpr int kCameraPin_SIOC   =  27;
constexpr int kCameraPin_Y9     =  35;
constexpr int kCameraPin_Y8     =  34;
constexpr int kCameraPin_Y7     =  39;
constexpr int kCameraPin_Y6     =  36;
constexpr int kCameraPin_Y5     =  21;
constexpr int kCameraPin_Y4     =  19;
constexpr int kCameraPin_Y3     =  18;
constexpr int kCameraPin_Y2     =   5;
constexpr int kCameraPin_VSYNC  =  25;
constexpr int kCameraPin_HREF   =  23;
constexpr int kCameraPin_PCLK   =  22;

void setupCamera() {
  const camera_config_t config = {
    .pin_pwdn     = kCameraPin_PWDN,
    .pin_reset    = kCameraPin_RESET,
    .pin_xclk     = kCameraPin_XCLK,
    .pin_sscb_sda = kCameraPin_SIOD,
    .pin_sscb_scl = kCameraPin_SIOC,
    .pin_d7       = kCameraPin_Y9,
    .pin_d6       = kCameraPin_Y8,
    .pin_d5       = kCameraPin_Y7,
    .pin_d4       = kCameraPin_Y6,
    .pin_d3       = kCameraPin_Y5,
    .pin_d2       = kCameraPin_Y4,
    .pin_d1       = kCameraPin_Y3,
    .pin_d0       = kCameraPin_Y2,
    .pin_vsync    = kCameraPin_VSYNC,
    .pin_href     = kCameraPin_HREF,
    .pin_pclk     = kCameraPin_PCLK,
    .xclk_freq_hz = 20000000,
    .ledc_timer   = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,
    .pixel_format = PIXFORMAT_JPEG,
//    .frame_size   = FRAMESIZE_96X96,
//    .frame_size   = FRAMESIZE_QQVGA,
//    .frame_size   = FRAMESIZE_QCIF,
//    .frame_size   = FRAMESIZE_QCIF,
//    .frame_size   = FRAMESIZE_HQVGA,
//    .frame_size   = FRAMESIZE_240X240,
//    .frame_size   = FRAMESIZE_QVGA,
//    .frame_size   = FRAMESIZE_CIF,
//    .frame_size   = FRAMESIZE_HVGA,
    .frame_size   = FRAMESIZE_VGA,
//    .frame_size   = FRAMESIZE_SVGA,
//    .frame_size   = FRAMESIZE_XGA,
//    .frame_size   = FRAMESIZE_HD,
//    .frame_size   = FRAMESIZE_SXGA,
//    .frame_size   = FRAMESIZE_UXGA,
    .jpeg_quality = 12,
    .fb_count     = 1,
  };

  if(kCameraPin_PWDN != -1){
    pinMode(kCameraPin_PWDN, OUTPUT);
    digitalWrite(kCameraPin_PWDN, LOW);
  }
  
  esp_err_t err = esp_camera_init(&config);
  Serial.printf("esp_camera_init: 0x%x\n", err);

  // init flash pin
  pinMode(ledPin, OUTPUT);
}

void flash(bool flag) {
  if(flag) {
    digitalWrite(ledPin, HIGH);
  } else {
    digitalWrite(ledPin, LOW);
  }
}

int post(camera_fb_t *fb, uint8_t msg) {
  String str = "";
   // Wifi
  if ((wifiMulti.run() == WL_CONNECTED)) {
    String post = "status=" + String(msg);
    HTTPClient http;
    uint8_t httpCode;
    // POST
    http.begin("http://[SERVER_NAME]/post.php");
    if(!fb) {
      char buf[512];
      String post = "status=false";
      post.toCharArray(buf, post.length());
      http.addHeader("Content-Type", "application/x-www-form-urlencoded", false, true);
      httpCode = http.POST((uint8_t*)buf, strlen(buf));
    } else {
      str = "multipart/form-data; boundary=";
      str += STRING_BOUNDARY;
      http.addHeader("Content-Type", str);
  
      // 構造定義
      String head1 = "--";
        head1 +=STRING_BOUNDARY;
        head1 += "\r\n";
        head1 += STRING_MULTIHEAD1;
        head1 += "\r\n";
        head1 += STRING_MULTIHEAD2;
        head1 += "\r\n\r\n";
      uint32_t clen1 = head1.length();
      String head2 = "\r\n--";
        head2 += STRING_BOUNDARY;
        head2 += "--\r\n\r\n";
      uint32_t clen2 = head2.length();
      uint32_t clen = clen1 + clen2 + fb->len;
      uint8_t *uiB = (uint8_t *)malloc(sizeof(uint8_t)*clen);
      Serial.printf("length: head1=%d, head2=%d, fb=%d, total=%d",clen1,clen2,fb->len,clen);
  
      // POST
      for(int pos=0;pos<clen1;pos++){
        uiB[0+pos]=head1[pos];
      }
      for(int pos=0;pos<(fb->len);pos++){
        uiB[clen1+pos]=(fb->buf)[pos];
      }
      for(int pos=0;pos<clen2;pos++){
        uiB[clen1+(fb->len)+pos]=head2[pos];
      }
      httpCode = http.POST(uiB,clen);
      free(uiB);
    }

    // 結果確認
    if(httpCode > 0) {
      Serial.printf("[HTTP] POST... code: %d\n", httpCode);
      // HTTP OK
      if(httpCode == HTTP_CODE_OK) {
        String payload = http.getString();
        Serial.println(payload);
      }
    } else {
      Serial.printf("[HTTP] POST... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  } else {
    Serial.printf("WIFI NOT CONNECTED.\n");
  }
}

void setup() {
  Serial.begin(115200, SERIAL_8N1);

  // init camera
  setupCamera();
      
  // Wifi
  wifiMulti.addAP(WIFI_SSID, WIFI_PASSWORD);
}

void loop() {

  uint16_t msg = 0;
  
  Serial.println("-----------------");

  // take a picture
  Serial.println("Taking a photo...");
  flash(true);
  camera_fb_t *fb = esp_camera_fb_get();
  flash(false);
  if (!fb) {
    Serial.println("Camera capture failed");
    msg = STATUS_CAMERA_FAILED;
    post(fb, msg);
  } else {
    msg = STATUS_SUCCESS;
      
    // post to wifi
    Serial.printf("Picture taken.Its size was: %zu bytes.\n",fb->len);
    post(fb, msg);
      
    esp_camera_fb_return(fb);
  }

  delay(UPLOAD_INTERVAL);
}
}}}

~カメラ部分はこのあたり((■ESP32-CAMで撮影してみた&br;https://qiita.com/yuyakato/items/dff67d3e97277666778d ))や、GitHubのサンプル((https://github.com/espressif/esp32-camera/blob/master/examples/take_picture.c ))を参考にすれば難しくありません。
~POST部分は、ファイル転送は自前で作らないといけません。こちらのサイトを参考にしました。~
~
■Arduino HTTPClientでファイルのバイナリ送信~
https://qiita.com/dzonesasaki/items/1417b917751443994ffc

** 結果 [#fbbab58d]
こんな感じで撮れます。
#ref(picture.png)
#ref(picture.jpg)

** トラブル [#o73caedc]
 17:21:33.824 -> Taking a photo...
 17:21:37.806 -> [E][camera.c:1483] esp_camera_fb_get(): Failed to get the frame on time!
~当初、原因不明のエラーが出て、パラメータを試行錯誤でいじったりしましたが、原因はカメラの接続不良でした。カメラ部は未接続で送られてくるので、黒いパーツを垂直に起こしてカメラをはめ込み、再び戻すのですが、正しく装着できていないと上記エラーが出ます。
~指で押さえてエラーが解消する場合は、それが原因です。

#htmlinsert(twitterbutton.html)
RIGHT:Category: [[[修理>日記/Category/修理]]] - 09:09:48
----
#htmlinsert(20210627_camera.html)
----
RIGHT:&blog2trackback();
#comment(above)
#blog2navi()