M5Cameraを初めてさわる方は下記の記事でまず標準のサンプルを動かしてみてください。
https://qiita.com/nakazawaken1/items/57ea26d6981ff25912c9
工夫したポイントや注意点
- 最小限のコードにしました。
- 送信と送信の間はディープスリープします。
- WiFi接続中はLEDが点灯します。
- CPU温度を送信するようにしました。
- WiFi接続は他のスケッチでM5CameraがWiFiに繋がっていればSSID、PASSWORDなしでつながるので指定は省略しています。
- LINE_TOKENの部分のみ設定すれば動作します。
////
// 指定した送信間隔で写真を撮影してLineに送る
////
#include <WiFiClientSecure.h>
#include <esp_camera.h>
#include <esp_sleep.h>
// 送信間隔(分)
#define INTERVAL 60
// 通知メッセージ(必須です)
#define MESSAGE "CPU温度 %.1f"
// 上下反転する(コメントにするとしない)
#define VFLIP
// 左右反転する(コメントにするとしない)
#define HMIRROR
// LINE通知用トークン
#define LINE_TOKEN "(ご自身で取得したものを設定してください)"
// 区切り用のランダム文字列
#define BOUNDARY "123456789000000000000987654321"
// メッセージヘッダ
#define MESSAGE_HEADER "\r\n--" BOUNDARY "\r\nContent-Disposition: form-data; name=\"message\"\r\n\r\n"
// 画像ヘッダ
#define IMAGE_HEADER "\r\n--" BOUNDARY "\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"image.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"
// 最後の区切り
#define BOUNDARY_LAST "\r\n--" BOUNDARY "--\r\n"
void setup()
{
// シリアル通信開始
Serial.begin(9600);
Serial.setDebugOutput(true);
// LED初期化
Serial.println("led on");
pinMode(GPIO_NUM_14, OUTPUT); // GPIO14番を出力モードに設定
// WiFi接続
Serial.println("WiFi connecting...");
WiFi.mode(WIFI_STA);
WiFi.begin(); //必要に応じてssid, passwordを指定してください。省略した場合は前回接続先に繋がります。
for (auto i = 0; i < 10; i++)
{
delay(1000);
if (WiFi.status() == WL_CONNECTED)
{
break;
}
Serial.print(".");
}
if (WiFi.status() != WL_CONNECTED)
{
esp_restart();
}
//カメラ初期化
Serial.println("camera initalizing...");
camera_config_t config = {
.pin_pwdn = -1,
.pin_reset = 15,
.pin_xclk = 27,
.pin_sscb_sda = 22,
.pin_sscb_scl = 23,
.pin_d7 = 19,
.pin_d6 = 36,
.pin_d5 = 18,
.pin_d4 = 39,
.pin_d3 = 5,
.pin_d2 = 34,
.pin_d1 = 35,
.pin_d0 = 32,
.pin_vsync = 25,
.pin_href = 26,
.pin_pclk = 21,
.xclk_freq_hz = 20000000, // 20MHz
.ledc_timer = LEDC_TIMER_0, // 0番のタイマー使用
.ledc_channel = LEDC_CHANNEL_0, // 0番のチャネル使用
.pixel_format = PIXFORMAT_JPEG, // JPEG
.frame_size = FRAMESIZE_UXGA, // 解像度
.jpeg_quality = 8, // JPGE画質(小さいほど高画質)
.fb_count = 1 // フレームバッファ数(2つあれば2倍で処理できる?)
};
auto result = esp_camera_init(&config);
if (result != ESP_OK)
{
Serial.printf("esp_camera_init error 0x%x\n", result);
esp_restart();
}
#ifdef VFLIP
{
auto sensor = esp_camera_sensor_get();
if (sensor != NULL)
{
sensor->set_vflip(sensor, 1); //上下反転
}
}
#endif
#ifdef HMIRROR
{
auto sensor = esp_camera_sensor_get();
if (sensor != NULL)
{
sensor->set_hmirror(sensor, 1); //左右反転
}
}
#endif
// スリープ設定
Serial.println("sleep setting...");
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_MAX, ESP_PD_OPTION_OFF);
digitalWrite(GPIO_NUM_14, HIGH); // LED消灯
Serial.println("led off");
}
void loop()
{
// 写真を撮影
auto frame = esp_camera_fb_get();
if (frame == NULL)
{
Serial.println("esp_camera_fb_get failed.");
esp_restart();
}
Serial.printf("image size: %u\n", frame->len);
// Line Notifyに写真とCPU温度を送信
WiFiClientSecure client;
for (auto t = millis(); millis() - t < 15000 && !client.connect("notify-api.line.me", 443);)
;
if (!client.connected())
{
Serial.println("https connect failed.");
esp_restart();
}
char message[64];
auto messageLength = snprintf(message, sizeof(message), MESSAGE, temperatureRead());
auto contentLength = strlen(MESSAGE_HEADER) + messageLength + strlen(IMAGE_HEADER) + frame->len + strlen(BOUNDARY_LAST);
client.println("POST /api/notify HTTP/1.0");
client.println("Authorization: Bearer " LINE_TOKEN);
client.println("Content-Type: multipart/form-data;boundary=" BOUNDARY);
client.println("Content-Length: " + String(contentLength));
client.println();
Serial.printf("content length: %u\n", contentLength);
Serial.printf("write message header: %u\n", client.write((uint8_t *)MESSAGE_HEADER, strlen(MESSAGE_HEADER)));
Serial.printf("write message data: %u\n", client.write((uint8_t *)message, messageLength));
Serial.printf("write image header: %u\n", client.write((uint8_t *)IMAGE_HEADER, strlen(IMAGE_HEADER)));
auto p = frame->buf;
auto rest = frame->len;
while (rest > 0 && client.connected())
{
auto n = client.write(p, rest > 2048 ? 2048 : rest); // 一度に大きなサイズを書き込むと失敗する
p += n;
rest -= n;
}
Serial.printf("write image data: %u\n", frame->len - rest);
Serial.printf("write last boundary: %u\n", client.write((uint8_t *)BOUNDARY_LAST, strlen(BOUNDARY_LAST)));
client.flush();
while (client.connected())
{
Serial.print(client.readStringUntil('\n') + '\n');
}
client.stop();
esp_camera_fb_return(frame);
esp_camera_deinit();
// スリープ
Serial.printf("esp_sleep_enable_timer_wakeup: %d\n", esp_sleep_enable_timer_wakeup((uint64_t)(INTERVAL)*60ULL * 1000ULL * 1000ULL)); //単位はマイクロ秒
esp_deep_sleep_start();
}
もう少し大きい画像を送信したくて、
.frame_size = FRAMESIZE_SVGA
の部分を
.frame_size = FRAMESIZE_UXGA
してみましたが、どうも WiFiClientSecure で64KB以上送信しようとするとエラーになるようです。
Line側の制限を見るとは2048×2048ピクセルの画像まではOKのようです。
Ruby等では600KBぐらいの画像が送信できるので送信先の問題ではなさそうです。
※解決方法をご存知な方は是非コメントください。
client.write で1024や2048バイトずつに区切って送信すればうまくいくようです。
情報をいただきました皆様ありがとうございます。