skimemo


skimemo - 日記/2021-06-27

_ ESP32-CAMで写真を撮ってサーバーに転送する

前回の記事で味を占め、ESP32-CAMを使って定期的に写真を撮ってサーバーに転送してみました。

_ ESP32-CAM

小さいのに写真が撮れて、フラッシュも点いて、Wifi通信もできる優れものです。
例によってAliExpressで送料込みで900円ほどです。
https://ja.aliexpress.com/item/1005001970568846.html

camera.png


このESP32-CAM(単体)は以下の点が要注意です。

  1. 技適未対応*1
  2. USB接続には別ボードが必要

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

_ ソース

HW的には特にすることは無いのでソースです。

#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);
}

カメラ部分はこのあたり*2や、GitHubのサンプル*3を参考にすれば難しくありません。

POST部分は、ファイル転送は自前で作らないといけません。こちらのサイトを参考にしました。

■Arduino HTTPClientでファイルのバイナリ送信
https://qiita.com/dzonesasaki/items/1417b917751443994ffc

_ 結果

こんな感じで撮れます。

picture.jpg

_ トラブル

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!

当初、原因不明のエラーが出て、パラメータを試行錯誤でいじったりしましたが、原因はカメラの接続不良でした。カメラ部は未接続で送られてくるので、黒いパーツを垂直に起こしてカメラをはめ込み、再び戻すのですが、正しく装着できていないと上記エラーが出ます。

指で押さえてエラーが解消する場合は、それが原因です。

Category: [修理] - 09:09:48

日本で買いたい方はこういうのもあります。3,000円は安心料。





 
 
Last-modified: 2021-06-27 (日) 09:09:48 (369d)