TTGO T-camの画像データをクラウドサーバー(さくらインターネット)に転送する記事をネット検索したところ、下記のサイトに詳細があったので、それを参考にプログラムを作成した。世界には頭の良い人がたくさんいて凄い。彼らに感謝しながらプログラムを使わせていただいた。
参考記事:
ESP32-CAM Post Images to Local or Cloud Server using PHP (Photo Manager)
システム構成イラスト
1、ArduinoIDEによるTTGO用のスケッチ
①参考サイトにあるESP32-CAM Codeをコピーし、esp-cam-code-with-pir.inoファイルを作成
②コピーコードはCAMERA_MODEL_AI_THINKER用なので、TTGO T-cam V17 のピン配列に変更
③さくらサーバーのuploadディレクトリをiot-camにしたので、サーバーパスを変更
String serverPath = “/upload.php”; → ”/iot-cam/upload.php”
そのほか、ルーターのSSID, PASSWD、さくらインターネットのサーバーネームの設定
④ 人感センサーのスクリプト設定
モーションを検出したら写真を撮りさくらインターネットに送信する。
3秒間の不感時間をおいて、これを繰り返すスクリプトをvoid loop()に設定。
int pir=LOW; // 人感センサ値
int count=0;
void loop() {
pir = digitalRead(AS312_PIN);
Serial.print( "pir=");
Serial.println( pir);
if ( pir == HIGH) {
Serial.println( "Motion dected");
sendPhoto();
count = count +1 ;
Serial.print( "photo count = ");
Serial.println( count);
}
pir = LOW;
delay(3000);
}
PIRセンサーをアクティブにするために、piModeでINPUT設定
void setup() {
.......
pinMode(AS312_PIN, INPUT);
......
}
④撮影画像をQVGAに設定(送信スピードの観点より)
// init with high specs to pre-allocate larger buffers
if(psramFound()){
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 10; //0-63 lower number means higher quality
config.fb_count = 2;
2、さくらインターネット側の設定
①TTGOからHTTP送信されるデータを受けて画像を保存するPHPプログラム「upload.php]を作成
画像データの保存ディレクトリを「$target_dir = “../iot-cam/uploads/”;」に設定
ディレクトリiot-camに、upload.phpを保存し属性を755に設定
これで「hogehoge.sakura.ne.jp/www/iot-cam/uploads」に画像が保存される。
②uploadsに保存された写真ギャラリー用のPHPプログラム「gellery.php」をコピーし、iot-camのディレクトリに保存。これにより、http://hogehoge.sakura.ne.jp/iot-cam/gellery.phpで写真の一覧をブラウズすることができる。
各プログラムのありか
TTGO T-cam側のスケッチ:
C:\Users\user\Documents\Arduino\esp32-cam-code-with-pir¥esp32-cam-code-with-pir.ino
さくらインターネット側:
www/iot-cam/upload.php
www/iot-cam/gallery.php
仕様プログラムコード
esp32-cam-code-with-pir.ino
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
// 2021-09-10 rev1.0
// forked by https://randomnerdtutorials.com/esp32-cam-post-image-photo-server/
// change pin aasingnmet from origina to iot-cam camera model TTGO-CAMERA V17
#include <OLEDDisplay.h>
//#include <OLEDDisplayFonts.h>
#include <OLEDDisplayUi.h>
//#include <SSD1306I2C.h>
//#include <SSD1306Wire.h>
#include <Wire.h>
#include <Arduino.h>
#include <WiFi.h>
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include "esp_camera.h"
#include "SSD1306.h"//ディスプレイ用ライブラリを読み込み
// program version: esp32-cam-code-with-pir-oled-20210916-01;
String code_name = "pir-oled-20210916-01";
const char* ssid = "aterm-217843-g";
const char* password = "3328d93051562";
//String serverName = "192.168.1.XXX"; // REPLACE WITH YOUR Raspberry Pi IP ADDRESS
String serverName = "miyasan.sakura.ne.jp"; // OR REPLACE WITH YOUR DOMAIN NAME
//String serverPath = "/upload.php"; // The default serverPath should be upload.php
String serverPath = "/iot-cam/upload.php"; // The default sakura-serverPath should be upload.php
const int serverPort = 80;
WiFiClient client;
/* CAMERA_MODEL_AI_THINKER ---> TTGO */
// iot-cam camera model TTGO-CAMERA V17
#define PWDN_GPIO_NUM 26
#define RESET_GPIO_NUM -1
#define XCLK_GPIO_NUM 32
#define SIOD_GPIO_NUM 13
#define SIOC_GPIO_NUM 12
#define Y9_GPIO_NUM 39
#define Y8_GPIO_NUM 36
#define Y7_GPIO_NUM 23
#define Y6_GPIO_NUM 18
#define Y5_GPIO_NUM 15
#define Y4_GPIO_NUM 4
#define Y3_GPIO_NUM 14
#define Y2_GPIO_NUM 5
#define VSYNC_GPIO_NUM 27
#define HREF_GPIO_NUM 25
#define PCLK_GPIO_NUM 19
#define AS312_PIN 33
#define BUTTON_1 34
#define I2C_SDA 21
#define I2C_SCL 22
//
// const int timerInterval = 30000; // time between each HTTP POST image
// unsigned long previousMillis = 0; // last time image was sent
SSD1306 display(0x3c, I2C_SDA, I2C_SCL); //SSD1306インスタンスの作成(I2Cアドレス,SDA,SCL)
void setup() {
//ESP32のArduino開発環境だと,電源電圧が2.4V以下になるとリセットがかかるような初期設定になっています
//これを.低電圧保護を無効にする
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
Serial.begin(115200);
WiFi.mode(WIFI_STA); //ステーションモード(無線LAN子機)
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
Serial.print("ESP32-CAM IP Address: ");
Serial.println(WiFi.localIP());
//OLED display the local IP address
Wire.begin(I2C_SDA, I2C_SCL);
display.init(); //ディスプレイを初期化
display.setFont(ArialMT_Plain_10); //フォントを設定
display.clear();
display.drawString(0, 0, WiFi.localIP().toString()); //(0,0)の位置にipを表示
display.drawString(0, 10, code_name); //(0,10)の位置にprogram nameを表示
display.display(); //指定された情報を描画
pinMode(AS312_PIN, INPUT);
//iot-cam camera model TTGO-CAMERA V17
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.frame_size = FRAMESIZE_UXGA; //init with high specs to pre-allocate larger buffers
//config.jpeg_quality = 10;
//config.fb_count = 2;
// init with high specs to pre-allocate larger buffers
if(psramFound()){
config.frame_size = FRAMESIZE_XGA;
config.jpeg_quality = 10; //0-63 lower number means higher quality
config.fb_count = 2;
} else {
config.frame_size = FRAMESIZE_CIF;
config.jpeg_quality = 12; //0-63 lower number means higher quality
config.fb_count = 1;
}
// camera init
esp_err_t err = esp_camera_init(&config);
if (err != ESP_OK) {
Serial.printf("Camera init failed with error 0x%x", err);
delay(1000);
ESP.restart();
}
sensor_t * s = esp_camera_sensor_get();
s->set_framesize(s, FRAMESIZE_QVGA);
sendPhoto();
}
/////
//////////////////////////////
int pir=0; // 人感センサ値
int count=0;
int state = 0; // by default, no motion detected
int val = 0; // variable to store the sensor status (value)
void loop() {
/*
pir = digitalRead(AS312_PIN);
Serial.print( "pir=");
Serial.println( pir);
if ( pir == 1) {
sendPhoto();
pir = 0;
Serial.println( "Motion dected");
count = count +1 ;
Serial.print( "photo count = ");
Serial.println( count);
Serial.println("-------------------");
}
delay(5000);
*/
val = digitalRead(AS312_PIN); // read sensor value
if (val == 1) { // check if the sensor is HIGH
if (state == 0) {
sendPhoto();
Serial.println("Motion detected!");
state = 1; // update variable state to HIGH
count = count +1 ;
Serial.print( "photo count = ");
Serial.println( count);
Serial.println("-------------------------");
}
}
else {
if (state == 1){
Serial.println("Motion stopped!");
Serial.println("");
state = 0; // update variable state to LOW
}
}
delay(3000);
/*
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= timerInterval) {
sendPhoto();
previousMillis = currentMillis;
}
*/
}
/////////////////////////////
String sendPhoto() {
String getAll;
String getBody;
camera_fb_t * fb = NULL;
fb = esp_camera_fb_get();
if(!fb) {
Serial.println("Camera capture failed");
delay(1000);
ESP.restart();
}
Serial.println("Connecting to server: " + serverName);
if (client.connect(serverName.c_str(), serverPort)) {
Serial.println("Connection successful!");
String head = "--RandomNerdTutorials\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
String tail = "\r\n--RandomNerdTutorials--\r\n";
uint32_t imageLen = fb->len;
uint32_t extraLen = head.length() + tail.length();
uint32_t totalLen = imageLen + extraLen;
client.println("POST " + serverPath + " HTTP/1.1");
client.println("Host: " + serverName);
client.println("Content-Length: " + String(totalLen));
client.println("Content-Type: multipart/form-data; boundary=RandomNerdTutorials");
client.println();
client.print(head);
uint8_t *fbBuf = fb->buf;
size_t fbLen = fb->len;
for (size_t n=0; n<fbLen; n=n+1024) {
if (n+1024 < fbLen) {
client.write(fbBuf, 1024);
fbBuf += 1024;
}
else if (fbLen%1024>0) {
size_t remainder = fbLen%1024;
client.write(fbBuf, remainder);
}
}
client.print(tail);
esp_camera_fb_return(fb);
int timoutTimer = 10000; // orginal setting
//int timoutTimer = 3000;
long startTimer = millis();
boolean state = false;
while ((startTimer + timoutTimer) > millis()) {
Serial.print(".");
delay(100);
while (client.available()) {
char c = client.read();
if (c == '\n') {
if (getAll.length()==0) { state=true; }
getAll = "";
}
else if (c != '\r') { getAll += String(c); }
if (state==true) { getBody += String(c); }
startTimer = millis();
}
if (getBody.length()>0) { break; }
}
Serial.println();
client.stop();
Serial.println(getBody);
}
else {
getBody = "Connection to " + serverName + " failed.";
Serial.println(getBody);
}
return getBody;
}
upload.php
<?php
// Rui Santos
// Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
// Code Based on this example: w3schools.com/php/php_file_upload.asp
// 2021-09-10 ver1.0 miyagwa
// froked by https://randomnerdtutorials.com/esp32-cam-post-image-photo-server/
/////////////////////////////////////////////////////
//$target_dir = "uploads/";
$target_dir = "../iot-cam/uploads/";
$datum = mktime(date('H')+0, date('i'), date('s'), date('m'), date('d'), date('y'));
$target_file = $target_dir . date('Ymd_His_', $datum) . basename($_FILES["imageFile"]["name"]);
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
$check = getimagesize($_FILES["imageFile"]["tmp_name"]);
if($check !== false) {
echo "File is an image - " . $check["mime"] . ".";
$uploadOk = 1;
}
else {
echo "File is not an image.";
$uploadOk = 0;
}
}
// Check if file already exists
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
// Check file size
if ($_FILES["imageFile"]["size"] > 500000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
// Allow certain file formats
if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
&& $imageFileType != "gif" ) {
echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
$uploadOk = 0;
}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
}
else {
if (move_uploaded_file($_FILES["imageFile"]["tmp_name"], $target_file)) {
echo "The file ". basename( $_FILES["imageFile"]["name"]). " has been uploaded.";
}
else {
echo "Sorry, there was an error uploading your file.";
}
}
?>
gallery.php
<!--
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp32-cam-post-image-photo-server/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
-->
<!DOCTYPE html>
<html>
<head>
<title>TTGO-CAM Photo Gallery</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.flex-container {
display: flex;
flex-wrap: wrap;
}
.flex-container > div {
text-align: center;
margin: 10px;
}
</style>
</head><body>
<h2>ESP32-CAM Photo Gallery</h2>
<?php
// Image extensions
$image_extensions = array("png","jpg","jpeg","gif");
// Check delete HTTP GET request - remove images
if(isset($_GET["delete"])){
$imageFileType = strtolower(pathinfo($_GET["delete"],PATHINFO_EXTENSION));
if (file_exists($_GET["delete"]) && ($imageFileType == "jpg" || $imageFileType == "png" || $imageFileType == "jpeg") ) {
echo "File found and deleted: " . $_GET["delete"];
unlink($_GET["delete"]);
}
else {
echo 'File not found - <a href="gallery.php">refresh</a>';
}
}
// Target directory
$dir = 'uploads/';
if (is_dir($dir)){
echo '<div class="flex-container">';
$count = 1;
$files = scandir($dir);
rsort($files);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {?>
<div>
<p><a href="gallery.php?delete=<?php echo $dir . $file; ?>">Delete file</a> - <?php echo $file; ?></p>
<a href="<?php echo $dir . $file; ?>">
<img src="<?php echo $dir . $file; ?>" style="width: 350px;" alt="" title=""/>
</a>
</div>
<?php
$count++;
}
}
}
if($count==1) { echo "<p>No images found</p>"; }
?>
</div>
</body>
</html>
撮影Gallery
追記:gallery.phpは常に公開されてしまうので、セキュリティー上問題になる。
そこでHome Pageの固定ページから画像を参照するようにして、固定ページにパスワードをかけられるようにした。gallery.phpを改変し、Wordpressで画像表示させるようにしたPHPプログラムは以下の通り。
ファイルをテーマファンクションのディレクトリに入れてショートコードで呼び込む
ttgo-gellery.php
<!DOCTYPE html>
<html>
<head>
<title>TTGO-CAMERA Photo Gallery</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.flex-container {
display: flex;
flex-wrap: wrap;
}
.flex-container > div {
text-align: center;
margin: 10px;
}
</style>
</head><body>
<h2>TTGO-CAMERA Photo Gallery Latest 20 images</h2>
<?php
// Image extensions
$image_extensions = array("png","jpg","jpeg","gif");
// Target directory
$dir = '/***********/iot-cam/uploads/'; //画像の保存ディレクトリ(絶対パス)
$dir3 = '***********/iot-cam/uploads/'; //画像の参照パス
if (is_dir($dir)){
echo '<div class="flex-container">';
$count = 1;
$files = scandir($dir);
rsort($files);
foreach ($files as $file) {
if ($file != '.' && $file != '..') {?>
<div>
<?php echo $file; ?></p>
<a href="<?php echo $dir3 . $file; ?>">
<img src="<?php echo $dir3 . $file; ?>" style="width: 350px;" alt="" title=""/>
</a>
</div>
<?php
$count++;
if($count>20) { break; // 表示MAX 20枚
}
}
}
}
if($count==1) { echo "<p>No images found</p>"; }
?>
</div>
</body>
</html>
コメント