M5Cameraで写真撮影しつつ温度や湿度を測る

# 概要
過去の記事で実現した以下を組み合わせ、M5Cameraで写真撮影しつつ温度や湿度を測れるようにした。

1. [M5Cameraで環境センサユニット ver.2(ENV II)を使う][link-1] 
2. [M5Cameraを極めて融通の利かない写真専門家にする][link-2]

[link-1]:https://qiita.com/mr_amichan/items/3dd00ce23450960c7015
[link-2]:https://qiita.com/mr_amichan/items/bd289b0b5f9bcf29b708

今回は、同じ名前の型`sensor_t`が複数のライブラリで定義されてて、これに対処するのに苦労した。とりあえず動くようにすることが目的なので、どう見てもソースが汚すぎるのは一旦無視する。

# 試した環境

* M5Camera Version B
* M5Stack用環境センサユニット ver.2(ENV II)
* Arduino IDE 1.8.15
* Arduino core for the ESP stable 1.0.6

# 手順
## ソースの統合
[1.][link-1]と[2.][link-2]のソースを1つのスケッチブックに統合する。どちらかのスケッチブックを別名で保存して一旦閉じる(今回は**M5Camera-Image-ENVII**にした)。これによって新しくできたフォルダにもう一方のスケッチブックの中身(.inoなど)を全部コピーして、もう一度開きなおせばOK。ここでは以下のソースがある前提で書いていく。

* M5Camera-Image-ENVII.ino (もとはM5Camera-Image.ino)
* app_httpd.cpp
* camera_pins.h
* M5Camera-ENVII.ino
* Adafruit_Sensor.h
* SHT3X.cpp
* SHT3X.h

## M5Camera-Image-ENVIIにM5Camera-ENVIIを埋め込む
**M5Camera-ENVII**で処理されている、環境センサーのライブラリ読み込みや動作確認の部分を、**M5Camera-Image-ENVII**に移植する。

* ヘッダファイルの読み込み(ソースの先頭)

```cpp:M5Camera-Image-ENVII.ino
// For ENVII
#include <Wire.h>
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "SHT3X.h"

#include "esp_camera.h" # ここからは元々ある
#include <WiFi.h>
```
* グローバル変数の定義(他のグローバル変数の定義の後ろ辺り)

```cpp:M5Camera-Image-ENVII.ino
const char* password = "********" # ここまでは元々ある

// For ENVII
SHT3X sht30;
Adafruit_BMP280 bme;

void startCameraServer(); # ここからは元々ある
```

* ENV IIの動作確認(WiFi.beginの前辺り)

```cpp:M5Camera-Image-ENVII.ino
void setup() {
  ....
  // For ENVII
  Wire.begin(4,13);
  Serial.println(F("ENV Unit(SHT30 and BMP280) test..."));
  while (!bme.begin(0x76)){  
    Serial.println("Could not find a valid BMP280 sensor, check wiring!");
  }

  WiFi.begin(ssid, password); # ここからは元々ある
  ...
}
```

## app_httpd.cppにM5Camera-ENVIIを埋め込む
実際にセンサーからデータを読み込むのは**app_httpd.cpp**の方になる。よって、こちらにも同じように移植する。

* ヘッダファイルの読み込み(ソースの先頭)

```cpp:app_httpd.cpp
// For ENVII
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#include "SHT3X.h"

#include "esp_http_server.h" # ここからは元々ある
```

* グローバル変数の定義(他のグローバル変数の定義の後ろ辺り)

```cpp:app_httpd.cpp
httpd_handle_t camera_httpd = NULL; #ここまでは元々ある

// For ENVII
extern SHT3X sht30;
extern Adafruit_BMP280 bme;

static esp_err_t capture_handler(httpd_req_t *req){ # ここからは元々ある
```
`extern`を付けているのは**M5Camera-Image-ENVII.ino**で定義されてるから。

## sensor_t型の二重定義に応急処置を施す

実は`sensor_t`という名前の型は**esp_camera.h**と**Adafruit_Sensor.h**の両方で定義されていて、このままだとコンパイルで以下のエラーが出て怒られる。

```
conflicting declaration 'typedef struct sensor_t sensor_t'
```

なので、どちらかの型名を変更しないといけない。でも、相手はどちらもライブラリ。手は入れたくない。該当するヘッダファイルをスケッチブック内にコピーして改変する手もあるけど面倒。ということで、`#define`と`#undef`を使って、この場をしのぐ。修正するのは**M5Camera-Image-ENVII**と**app_httpd.cpp**に追加した、ヘッダファイルの読み込み部分。

```cpp:M5Camera-Image-ENVII.ino
// For ENVII
#include <Wire.h>
#define sensor_t sensor_t_    # 追加
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#undef sensor_t               # 追加
#include "SHT3X.h"
```

```cpp:app_httpd.cpp
// For ENVII
#define sensor_t sensor_t_    # 追加
#include "Adafruit_Sensor.h"
#include <Adafruit_BMP280.h>
#undef sensor_t               # 追加
#include "SHT3X.h"
```

これによって、**Adafruit_Sensor.h**と**Adafruit_BMP280.h**は、`sensor_t`が`sensor_t_`に置換される(んだと思う)。これらのヘッダファイルの読み込みをソースの最初に置いた理由は、**esp_camera.h**で定義されている`sensor_t`は**app_httpd.cpp**の中でも使われてて、生かしておかないといけないと思ったから。

ソースの汚さがにじみ出てるけど、それは一旦横に置いておく。この魔法については、以下のページを参考にした。

https://lang-ship.com/blog/work/ttgo-t-camera/

https://mattn.kaoriya.net/software/lang/c/20091005223759.htm

## 環境データを読み込むためのハンドラやURIを定義する
あとは**app_httpd.cpp**にWebサーバ用のハンドラ等を定義する。今回は特定のURL(/env)にアクセスすると温度、湿度、気圧を返してくれるようにした。

* ハンドラの定義(capture_handlerの後ろ辺り)

```cpp:app_httpd.cpp
// For ENVII
static esp_err_t env_handler(httpd_req_t *req){
  static char json_response[1024];
  
  float tmp = 0.0;
  float hum = 0.0;
  float pressure = 0.0;
    
  pressure = bme.readPressure();
  if(sht30.get()==0){
    tmp = sht30.cTemp;
    hum = sht30.humidity;
  }

  char * p = json_response;
  *p++ = '{';
  p+=sprintf(p, "\"temperatura\":%2.2f,", tmp);
  p+=sprintf(p, "\"humedad\":%0.2f%,", hum);
  p+=sprintf(p, "\"pressure\":%0.2f", pressure);
  *p++ = '}';
  *p++ = 0;

  httpd_resp_set_type(req, "application/json");
  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  return httpd_resp_send(req, json_response, strlen(json_response));
}
```
一応、JSONで返す。オリジナルの**app_httpd.cpp**にあった`status_handler`を引用して作った。

* URIの定義(他のURIの定義の周り)

```cpp:app_httpd.cpp
void startCameraServer(){
    ...
    // For ENVII
    httpd_uri_t env_uri = {
        .uri       = "/env",
        .method    = HTTP_GET,
        .handler   = env_handler,
        .user_ctx  = NULL
    };
    
    Serial.printf("Starting web server on port: '%d'\n", config.server_port); # ここからは元々ある
    ...
}
```

* URIの登録(他のURIの登録の周り)

```cpp:app_httpd.cpp
void startCameraServer(){
    ...
    if (httpd_start(&camera_httpd, &config) == ESP_OK) {
        ....
        httpd_register_uri_handler(camera_httpd, &capture_uri); # ここまでは元々ある

        // For ENVII
        httpd_register_uri_handler(camera_httpd, &env_uri);
    }
}
```

## M5Camera-ENVII.inoを削除する
感謝の念を込めながら、さよならする。

## 動かす
何ら構造化もされてないソースではあるが、これでも何となく動く。以下のURLにアクセス。

|URL|機能|
|:-|:-|
|/|写真を撮る|
|/env|環境データ(温度、湿度、気圧)を測る|

# 最後に
今回はド級初心者の自分には結構辛かった。C++とか分からないし(大昔に趣味レベルでCを触ったことはある)。これでひとまず**「M5Cameraで写真撮影+環境データ測定」**という、意味不明ではあるがやりたいことはできるようになった。気が向いたら、ソースをキレイにしたい。