สร้าง Board Upload ESP32 Cam + Config ssid , password ,Line token ด้วย IOTwebconf ESP32 Cam

สำหรับโพสต์นี้นะครับก็จะเป็นการสร้างบอร์ดเพื่อใช้สำหรับการอัพโหลด esp32 Cam ซึ่งโดยปกติแล้วที่ศึกษาจากเว็บไซต์ต่างๆ จะเจอปัญหาที่ค่อนข้างจะยุ่งยาก และทำให้ project ประสบความสำเร็จค่อนข้างยาก ดังนั้นผมจึงสร้างบอร์ดที่ใช้อัพโหลด esp32 Cam แบบง่ายที่สุดเอาไว้

โดย Project นี้ผมได้นำโค้ดจากเว็บไซต์หนึ่งมาเป็นโค้ดที่ใช้ esp32 Capture ภาพแล้วก็ส่ง line แต่ยังไม่เคยเห็นว่ามีใครใช้การตั้งค่า SSID password และ LINE Token แบบการใช้การตั้งค่าจากภายนอก ดังนั้นผมจึงได้ทำชุดการตั้งค่าหรือการ Config ค่าต่างๆจากภายนอกขึ้นมา โดยมีลักษณะคล้ายกับ WiFi Manager ให้ทุกท่านได้นำไปใช้งานดูครับ

1 Like

// Santi&be  Youtube
// https://www.youtube.com/watch?v=I-HbdRWwMVY


#include <IotWebConf.h>

const char thingName[] = "PUY IOT FIRST";
const char wifiInitialApPassword[] = "111111111";
#include <TridentTD_LineNotify.h>

String LineText;
String string1 = "CAM System เริ่มทำงาน MCU RESET ครั้งที่  ";

#define Line_Token_1 45

#define CONFIG_VERSION "dem2"

// -- Callback method declarations.
void configSaved();
boolean formValidator();

DNSServer dnsServer;
WebServer server(80);

char LINE_TOKEN[Line_Token_1];

IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword, CONFIG_VERSION);
IotWebConfParameter Token_Line = IotWebConfParameter("Line_token", "Line_token", LINE_TOKEN, Line_Token_1);

const char* host = "www.google.co.th";
int Internet;

String SSID_NAME;
String PASSWORD_NAME;
String IP;
String Sub_M;
String Gate_way;

String SSID_AP;
String PASSWORD_AP;


#include <EEPROM.h>

int MCU_Reset = 0;
int count_MCU_Rset;
int Amount_Mcu_Reset;
int reset_MCU;





//#include <WiFi.h>
#include "esp_camera.h"
#include "esp_system.h"

hw_timer_t *timer = NULL;
void IRAM_ATTR resetModule() {
  ets_printf("reboot\n");
  esp_restart();
}




// Pin definition for CAMERA_MODEL_AI_THINKER กำหนดการต่อขาให้กับกล้อง รุ่น AI_THINKER
#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27

#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

const int Led_Flash = 4; //ไฟ LED ESP32 CAM  GPIO4
const int Led_run = 13;//ใช้ GPIO13 จะเป็นไฟกระพริบแสดงให้ผู้ใช้ทราบว่าขณะนี้กล้องพร้อมทำงานถ้าไฟดับแสดงว่ากล้องไม่พร้อมทำงาน
int Temp_High = 12;//จ่ายไฟ 3.3 หรือ 5Vdc เข้าที่ขา GPIO12 เพื่อทำให้ให้เกิดการ Copture ภาพ + ไฟ Led flash ทำงาน
int Temp_Normal = 2; //จ่ายไฟ 3.3 หรือ 5Vdc เข้าที่ขา GPIO15 เพื่อทำให้ให้เกิดการ Copture ภาพ + ไฟ Led flash ทำงาน

boolean startTimer = false;//ให้การนับเวลามีค่าเป็น เท็จ ก่อน
unsigned long time_now = 0;


int time_capture = 0; //ให้เวลาการ จับภาพ = 0

//------------------------------------------------------------------------------------------------------------
TaskHandle_t Task1;//IoTCONF
TaskHandle_t Task2;//Internet



void setup() {

  Serial.begin(115200);

  EEPROM.begin(1024);

  while (!Serial) {
    ;
  }
  pinMode(Led_Flash, OUTPUT);
  pinMode(Led_run, OUTPUT);

  xTaskCreatePinnedToCore(
    Task1code,   /* Task function. */
    "Task1",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task1,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);

  xTaskCreatePinnedToCore(
    Task2code,   /* Task function. */
    "Task2",     /* name of task. */
    10000,       /* Stack size of task */
    NULL,        /* parameter of the task */
    1,           /* priority of the task */
    &Task2,      /* Task handle to keep track of created task */
    0);          /* pin task to core 0 */
  delay(500);


  LINE.setToken(LINE_TOKEN);








  //แบ่งค่านี้โดย 80 (ใช้ 80 เป็นค่า prescaler)จะได้รับสัญญาณที่มีความถี่ 1 MHz ที่จะเพิ่มเคาน์เตอร์จับเวลา1 000 000 ครั้งต่อวินาที
  //แต่ก่อนที่จะเปิดใช้งานตัวจับเวลาเราจำเป็นต้องเชื่อมโยงกับฟังก์ชันการจัดการซึ่งจะดำเนินการเมื่อมีการสร้างการขัดจังหวะ
  //สิ่งนี้ทำได้ด้วยการเรียกใช้ฟังก์ชันtimerAttachInterrupt

  timer = timerBegin(0, 80, true); //timer 0, div 80Mhz
  timerAttachInterrupt(timer, &resetModule, true);
  timerAlarmWrite(timer, 20000000, false); //set time in us 15s ตั้งค่าเวลาของเราเป็น 15 วินาที
  timerAlarmEnable(timer); //enable interrupt  สั่งให้ Interrupt ทำงาน



  camera_config_t config; //ตั้งค่า กล้อง มี code มาให้แล้ว
  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;//เวลาเป็น 15 วินาที
  config.pixel_format = PIXFORMAT_JPEG; //รูปแบบภาพเป็น JPEG

  // เริ่มต้นด้วยคุณสมบัติที่สูงเพื่อจัดสรรบัฟเฟอร์ขนาดใหญ่ไว้ล่วงหน้า

  if (psramFound()) { //ถ้ากล้องมีภาพเข้ามาจะกำหนดให้แสดงภาพตาม Resolutotion ที่กำหนดไว้
    // FRAMESIZE_ +
    //QQVGA/160x120   //QQVGA2/128x160   //QCIF/176x144   //HQVGA/240x176
    //QVGA/320x240    //CIF/400x296      //VGA/640x480     //SVGA/800x600//XGA/1024x768
    //SXGA/1280x1024  //UXGA/1600x1200   //QXGA/2048*1536

    config.frame_size = FRAMESIZE_VGA; //Resolutotion แบบ VGA // 640x480
    config.jpeg_quality = 10;//คุณภาพของไฟล์ภาพ =10
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_QQVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  // Init Camera // เริ่มต้นกล้อง
  esp_err_t err = esp_camera_init(&config);//
  if (err != ESP_OK) {
    //การเริ่มต้นกล้องล้มเหลวโดยมีข้อผิดพลาด
    Serial.printf("Camera init failed with error 0x%x", err);
    return;
  }










}// จบ void setup

//-------------------------------------------------------------------------------------




//Task1code  IoTCONF
void Task1code( void * pvParameters ) {
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());

  iotWebConf.addParameter(&Token_Line);
  iotWebConf.setConfigSavedCallback(&configSaved);
  iotWebConf.setFormValidator(&formValidator);
  iotWebConf.getApTimeoutParameter()->visible = true;

  // -- Initializing the configuration.
  iotWebConf.init();

  // -- Set up required URL handlers on the web server.
  server.on("/", handleRoot);
  server.on("/config", [] { iotWebConf.handleConfig(); });
  server.onNotFound([]() {
    iotWebConf.handleNotFound();
    iotWebConf.handleCaptivePortal();
  });

  Serial.println("Ready");
  Serial.println("Finish");



  SSID_NAME = iotWebConf.getWifiSsidParameter()->valueBuffer;
  PASSWORD_NAME = iotWebConf.getWifiPasswordParameter()->valueBuffer;
  SSID_AP = iotWebConf.getThingNameParameter()->valueBuffer;
  PASSWORD_AP = iotWebConf.getApPasswordParameter()->valueBuffer;



  count_MCU_Rset = EEPROM.get(150, Amount_Mcu_Reset);
  Serial.print("count_MCU_Rset =  ");
  Serial.println(count_MCU_Rset);

  reset_MCU = count_MCU_Rset + 1;
  Serial.print("reset_MCU =  ");
  Serial.println(reset_MCU);
  Amount_Mcu_Reset = EEPROM.put(101, reset_MCU);
  EEPROM.commit();
  Serial.print("Amount_Mcu_Reset =  ");
  Serial.println(Amount_Mcu_Reset);

  count_MCU_Rset = EEPROM.put(150, Amount_Mcu_Reset);
  EEPROM.commit();
  Serial.print("count_MCU_Rset =  ");
  Serial.println(count_MCU_Rset);

 



  for (;;) {

    if (Internet  == 1 && MCU_Reset == 0) {

      Serial.println("MCU_Reset = 1");

      LineText = string1 + count_MCU_Rset;
      LINE.notify(LineText);
      delay (200);


      MCU_Reset = 1;


    }


    iotWebConf.doLoop();
    delay(500);


  }
}





//Task2code INTERNET
void Task2code( void * pvParameters ) {
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());


  for (;;) {

    WiFiClient client;
    if (client.connect(host, 80))
    {
      // Serial.println("connected");
      Internet = 1;
      client.stop();
      delay(100);


    }
    else {
      //Serial.println("connection failed!");
      Internet = 0;
      client.stop();

    }
    delay(400);

  }
}

















void loop() {













  timerWrite(timer, 0); //reset timer (feed watchdog)สั่ง reset เวลา เพื่อป้องกัน เวลา error จน watch เจอแล้ว Reboot ใหม่ หรือค้าง
  long tme = millis();


  //----------- กรณี temperature High------------------------------------------------------------------------

  if (digitalRead(Temp_High) == 1 && startTimer != true) { //มีไฟ 3.3/5 Vdc  เข้ามาที่ขานี้   ให้ตัวแปรนี้มีค่า = 1
    Camera_capture();//ไป ฟังก์ชั่น Camera_capture เพื่อทำการจับภาพ
    //Serial.println("OK");
    startTimer = true;    //ให้คำสั่งนี้ ให้คำสั่งเริ่มต้นนับเวลาทำงาน


  } else if (digitalRead(Temp_High) == 0) { //ถ้าไม่มีไฟ 3.3/5 Vdc เข้ามา ให้ตัวแปรนี้มีค่า = 0
    startTimer = false;
    time_capture = 0;
  }

  //--------------------------------------------------------------------------------------------------------


  //----------- กรณี temperature Normal-----------------------------------------------------------------------

  if (digitalRead(Temp_Normal ) == 1 && startTimer != true) { //มีไฟ 3.3/5 Vdc  เข้ามาที่ขานี้   ให้ตัวแปรนี้มีค่า = 1
    Camera_capture1();//ไป ฟังก์ชั่น Camera_capture เพื่อทำการจับภาพ
    //Serial.println("OK");
    startTimer = true;    //ให้คำสั่งนี้ ให้คำสั่งเริ่มต้นนับเวลาทำงาน


  } else if (digitalRead(Temp_Normal ) == 0) { //ถ้าไม่มีไฟ 3.3/5 Vdc เข้ามา ให้ตัวแปรนี้มีค่า = 0
    startTimer = false;
    time_capture = 0;
  }

  //--------------------------------------------------------------------------------------------------------






  //-------- ไฟแสดงสถานะความพร้อมของกล้อง ถ้ากระพริบแสดงว่ากล้องพร้อมทำงาน --------------------
  if (millis() > time_now + 1000) {
    time_now = millis();
    digitalWrite(Led_run, HIGH);
    delay(20);
    digitalWrite(Led_run, LOW);
  }


  //-------------------------------------------------------------------------------------

  //----------- กรณี temperature High----
  tme = millis() - tme;

  if (digitalRead(Temp_High) == 1) {
    if (++time_capture > 60) {// กรณีที่ขานี้มีค่าเท่ากับ 1 แต่ใช้เวลาเกินกว่า 60  จะให้ Time Capture = 0 และเข้าไป Capture ภาพใหม่ในฟังก์ชั่น Camera Capture
      time_capture = 0;
      Camera_capture();
      Serial.println("Over Time");//และมีข้อความบอกว่าเวลาเกินกำหนด
    }
  }


  //----------- กรณี temperature Normal----
  if (digitalRead(Temp_Normal ) == 1) {
    if (++time_capture > 60) {// กรณีที่ขานี้มีค่าเท่ากับ 1 แต่ใช้เวลาเกินกว่า 60  จะให้ Time Capture = 0 และเข้าไป Capture ภาพใหม่ในฟังก์ชั่น Camera Capture
      time_capture = 0;
      Camera_capture1();
      Serial.println("Over Time");//และมีข้อความบอกว่าเวลาเกินกำหนด
    }
  }


  Serial.println(digitalRead(Temp_High));
  Serial.println(digitalRead(Temp_Normal));
  iotWebConf.doLoop();
  delay(200);





}//จบ void loop

//-------------------------------------------------------------------------------------



//----------- กรณี temperature High----
void Camera_capture() {//มีไฟ 3.3/5 Vdc  เข้ามาที่ขานี้  โปรแกรมสั่งให้มาทำงานที่นี่

  //สั่งชุดไฟ LED ของ ESP32 CAM ทำงานแบบกระพริบเหมือนไฟ Flash
  digitalWrite(Led_Flash, HIGH);
  delay(100);
  digitalWrite(Led_Flash, LOW);
  delay(100);
  digitalWrite(Led_Flash, HIGH);//สั่งไฟติดค้างจนกว่า จนกว่าการถ่ายภาพจะเสร็จ


  camera_fb_t * fb = NULL;
  delay(200);
  // Take Picture with Camera  ถ่ายภาพด้วยกล้อง
  //รับเฟรม
  fb = esp_camera_fb_get();

  // ถ้าจับภาพไม่สำเร็จ
  if (!fb) {
    Serial.println("Camera capture failed");
    ESP.restart();
    delay(2000);
    return;
  }


  digitalWrite(Led_Flash, LOW);////สั่งไฟดับ
  Send_line(fb->buf, fb->len);//ส่งค่าเข้า Line notify โดยนำข้อมูล Buffer ของ fb ซึ่งเป็นข้อมูลภาพและความยาวข้อมูล
  esp_camera_fb_return(fb);

  // Serial.println("Going to sleep now");
  // esp_deep_sleep_start();  //สั่ง esp ไม่ให้ Deep sleep   คำสั่งนี้ไม่ใช้  แสดงว่าให้มันทำงานตลอดเวลา
  // Serial.println("This will never be printed");

}//End void Camera_capture  //กลับไปทำคำสั่งต่อที่ void loop

//-------------------------------------------------------------------------------------








void Send_line(uint8_t *image_data, size_t   image_size) {// โค้ดการส่ง LINE โดยการแปลงค่า fb->buf, fb->len เป็น uint8_t *image_data, size_t   image_size
  LINE.notifyPicture("A", image_data, image_size);
  Serial.println("เข้า loop A");
}


//-------------------------------------------------------------------------------------









//----------- กรณี temperature Normal----
void Camera_capture1() {//มีไฟ 3.3/5 Vdc  เข้ามาที่ขานี้  โปรแกรมสั่งให้มาทำงานที่นี่

  //สั่งชุดไฟ LED ของ ESP32 CAM ทำงานแบบกระพริบเหมือนไฟ Flash
  digitalWrite(Led_Flash, HIGH);
  delay(100);
  digitalWrite(Led_Flash, LOW);
  delay(100);
  digitalWrite(Led_Flash, HIGH);//สั่งไฟติดค้างจนกว่า จนกว่าการถ่ายภาพจะเสร็จ


  camera_fb_t * fb = NULL;
  delay(200);
  // Take Picture with Camera  ถ่ายภาพด้วยกล้อง
  //รับเฟรม
  fb = esp_camera_fb_get();

  // ถ้าจับภาพไม่สำเร็จ
  if (!fb) {
    Serial.println("Camera capture failed");
    return;
  }


  digitalWrite(Led_Flash, LOW);////สั่งไฟดับ
  Send_line1(fb->buf, fb->len);//ส่งค่าเข้า Line notify โดยนำข้อมูล Buffer ของ fb ซึ่งเป็นข้อมูลภาพและความยาวข้อมูล
  esp_camera_fb_return(fb);

  // Serial.println("Going to sleep now");
  // esp_deep_sleep_start();  //สั่ง esp ไม่ให้ Deep sleep   คำสั่งนี้ไม่ใช้  แสดงว่าให้มันทำงานตลอดเวลา
  // Serial.println("This will never be printed");

}//End void Camera_capture  //กลับไปทำคำสั่งต่อที่ void loop

//-------------------------------------------------------------------------------------








void Send_line1(uint8_t *image_data, size_t   image_size) {// โค้ดการส่ง LINE โดยการแปลงค่า fb->buf, fb->len เป็น uint8_t *image_data, size_t   image_size
  LINE.notifyPicture("B", image_data, image_size);
  Serial.println("เข้า loop B");
}


//-------------------------------------------------------------------------------------






/**
   Handle web requests to "/" path.
*/
void handleRoot()
{


  Serial.println(" NOW! CONFIG AP ");

  // -- Let IotWebConf test and handle captive portal requests.
  if (iotWebConf.handleCaptivePortal())
  {
    // -- Captive portal request were already served.
    return;
  }
  String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>";
  s += "<title>IotWebConf 03 Custom Parameters</title></head><body>ESP32 CAM";
  s += "<ul>";

  s += "<li>String param value1: ";
  s += LINE_TOKEN;

  s += "</ul>";
  s += "Go to <a href='config'>configure page</a> to change values.";
  s += "</body></html>\n";

  server.send(200, "text/html", s);
}







void configSaved()
{
  Serial.println("Configuration was updated.");


  Serial.println(" SAVE! CONFIG AP");

  Serial.println(" DISCON. CONFIG!");




  SSID_AP = iotWebConf.getThingNameParameter()->valueBuffer;

  PASSWORD_AP = iotWebConf.getApPasswordParameter()->valueBuffer;

}

boolean formValidator()
{
  Serial.println("Validating form.");
  boolean valid = true;

  int l = server.arg(Token_Line.getId()).length();
  if (l < 3)
  {
    Token_Line.errorMessage = "Please provide at least 3 characters for this test!";
    valid = false;
  }



  return valid;
}
















จุดที่น่าสนใจคือ การย้าย core config AP ไปยัง Core 0 เพื่อให้ทำงานแยกส่วนกัน ดดยไม่มีปัญหา

2 Likes

สุดยอดครับ