M5cameraでhttp postによる画像送信

# 1. はじめに
M5cameraはカメラとESP32 wroverが内蔵された製品です．これを使って，定期的にhttp postでサーバにカメラ画像をアップロードしてみます．開発は，Arduino-IDEで行います．ライブラリはHTTPClientとesp_cameraを使います．サーバ側はnode-redを使います．

## 1.1. 利用したバージョン 
- Arduino-IDE : ver 1.8.13
- Arduino core for the ESP32 : ver 1.0.4 (2019.10月版) 
(ESP32ライブラリ(ボードマネージャで設定)が古いと動かないことがあります)
- Node-red : ver 1.1.2

# 2. m5cameraのコーディング

処理は次のような流れになります．
1. Wi-Fi設定
2. HTTPClient設定
3. カメラ初期設定
4. カメラ画像取得
5. HTTP post実施

以前作成した[HTTPClientでバイナリファイルを送信](https://qiita.com/dzonesasaki/items/1417b917751443994ffc)を利用します．
まずは，カメラ画像の取得について記載します．

## 2.1. カメラ画像取得
カメラからの画像取得は非常に簡単です．
esp_camera.hを使い，初期設定後，このたった1行で画像取得できます．

```c++
 camera_fb_t * fb = esp_camera_fb_get();
```

fbは次のようになっています．(esp_camera.hより)

```c++
typedef struct {
    uint8_t * buf;              /*!< Pointer to the pixel data */
    size_t len;                 /*!< Length of the buffer in bytes */
    size_t width;               /*!< Width of the buffer in pixels */
    size_t height;              /*!< Height of the buffer in pixels */
    pixformat_t format;         /*!< Format of the pixel data */
    struct timeval timestamp;   /*!< Timestamp since boot of the first DMA buffer of the frame */
} camera_fb_t;
```

JPEGで取得すれば，必要なのはバッファの開始位置と長さとなります．例えば，バッファの最後の内容は下記のように記述できます．
```c++
fb->buf[fb->len-1]
```
あとは，バッファの中身をHTTP postするだけです．ここでは，以前作成した[HTTPClientでバイナリファイルを送信](https://qiita.com/dzonesasaki/items/1417b917751443994ffc)の関数httppostを利用します．

```c++
httppost( fb->buf , fb->len);
```

## 2.2. httppostの修正

但し，関数httppost内で，バッファを複製しhttp.POSTに渡していますので，mallocではなく，ps_mallocで大きいPSDRAM内に複製しています．
その点だけ変更した関数httppostです．
<details><summary>関数httppost</summary><div>

```c++
int32_t httppost( uint8_t * ui8BufJpg, uint32_t iNumDat ){

  String stMyURL="";
  stMyURL+=URL1;
  stMyURL+=String(servC);
  stMyURL+=URL2;
  myHttp.begin(stMyURL);
    
  String stConType ="";
  stConType +="multipart/form-data; boundary=";
  stConType +=STRING_BOUNDARY;
  myHttp.addHeader("Content-Type", stConType);

  String stMHead="";
  stMHead += "--";
  stMHead += STRING_BOUNDARY;
  stMHead += "\r\n";
  stMHead += STRING_MULTIHEAD02;
  stMHead += "\r\n";
  stMHead += STRING_MULTIHEAD03;
  stMHead += "\r\n";
  stMHead += "\r\n";
  uint32_t iNumMHead = stMHead.length();

  String stMTail="";
  stMTail += "\r\n";
  stMTail += "--";
  stMTail += STRING_BOUNDARY;
  stMTail += "--";
  stMTail += "\r\n";
  stMTail += "\r\n";  
  uint32_t iNumMTail = stMTail.length();

  uint32_t iNumTotalLen = iNumMHead + iNumMTail + iNumDat;

  uint8_t *uiB = (uint8_t *)ps_malloc(sizeof(uint8_t)*iNumTotalLen);

  for(int uilp=0;uilp<iNumMHead;uilp++){
    uiB[0+uilp]=stMHead[uilp];
  }
  for(int uilp=0;uilp<iNumDat;uilp++){
    uiB[iNumMHead+uilp]=ui8BufJpg[uilp];
  }
  for(int uilp=0;uilp<iNumMTail;uilp++){
    uiB[iNumMHead+iNumDat+uilp]=stMTail[uilp];
  }

  int32_t httpResponseCode = (int32_t)myHttp.POST(uiB,iNumTotalLen);
  myHttp.end();
  free(uiB);
  return (httpResponseCode);
}
```

</div></details>

## 2.3. カメラ初期設定

説明の流れが逆になりましたが，初期設定は次のように行います．

これはサンプルコードの CameraWebServerを利用します．m5 camera model Bは次のような設定です．

<details><summary>設定部分</summary><div>

```c++
#define PWDN_GPIO_NUM     -1
#define RESET_GPIO_NUM    15
#define XCLK_GPIO_NUM     27
#define SIOD_GPIO_NUM     22 //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    25 //22
#define HREF_GPIO_NUM     26
#define PCLK_GPIO_NUM     21

  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.jpeg_quality = 10;
  config.frame_size = FRAMESIZE_UXGA; 
  //FRAMESIZE_ + QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
  config.fb_count = 1;
```
</div></details>

ポイントは，JPEGで取得する点です．JPEGのバイナリがフレームバッファに入るため，加工不要です．サイズは，好きなものを選択するのが良いと思います．ここでは最大のUXGAにしています．


## 2.4. Arduinoのボード側設定
- ボードは， ESP32 Wrover Module
- Partition Schemeは，Huge App(3MB No OTA/1MB SPIFFS)を選択します


# 3. サーバー側

Node-Redを使います．
次のようなノードを配置します．
![node-red_httppost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/213a0bc5-bc43-ebbf-b6f7-712d37f5b494.png)


http inノードは，メソッドをpostにし，ファイルをアップロードにチェックを入れ，URLを決めます．
アップロードされたファイルは，fileノードを使い，サーバ側へ一旦保存します．
fileノードは，payloadに入っているものを保存します．
そこで，次のように関数を書いて，payloadに流し込みました．

```javascript
let mymsgB={};
var myArray = [];
myArray=msg.req.files[0].buffer;
mymsgB.payload=myArray;
mymsgB.filename="test.jpg";

return [msg,mymsgB];
```
関数を二股にしている理由は特になく，http inから二つに分岐しても良いです．

画像の確認には，http getを利用しています．
postする側はESP32なのでその画像をESP32に見せる必要はなく，上げられた画像を別で見たいはずですので，getで見ます．
こちらのhttp inノードは，メソッドをgetにします．ファイルは，先ほど一次保存したものと整合をとるようにします．あとは，そのバイナリを応答として返します．その返信の前にchangeノードで次のようにヘッダを追加します．
![node-red_changenode.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/698531/4648aa72-4f0d-141a-1578-8fcdfc34e4dc.png)

ESP32のHTTPClientでpostメソッドを使う際にヘッダを追加しましたがそれと同じですね．

# 4. サンプル

下記に置きました．
https://github.com/dzonesasaki/m5camera_httppost_sample


# 5. むすび
M5cameraはとても高機能で，たったこれだけの事にだけに使うのは勿体ない感じがします．画像をエッジ側(ここではM5camera)で解析する処理しても良いのでは？と感じます．しかし，一方，エッジ側は負荷を軽減したいというニーズもあるように思います．例えば，エッジ側の電源が十分に確保できず，省エネ化したい場合などです．そういう場合は，今回の手法は価値があるように思います．

なお，省エネ化のためには，ループ処理にあるものを全てsetup関数内に持って行き，deepやlightなスリープを行うという方法があると思います．これは撮影の間隔次第かもしれません．
あとは，電力消費の主役はWi-Fiです．残したい画像が来た時だけ，Wi-Fiを接続し，サーバにアップロードするという方法もあるかもしれません．例えば，人の顔を認識したらその画像をアップするなどです．Haar-Like程度であれば，ESP32で処理できるように思います．