顯示具有 Node-Red 標籤的文章。 顯示所有文章
顯示具有 Node-Red 標籤的文章。 顯示所有文章

2023年11月8日 星期三

ESP32 water level monitoring system

Purpose:

This project is constructed uses ESP32 with HC-SR04 ultrasonic sensor and water sensor to detect the position of water in the water storage tank. If the water level is lower than the set water level limit, use relay to start the pumping motor to pump water into the water storage tank. When the water sensor senses the water level stops pumping, and the value is sent to the MQTT server and displayed through the Node-red dashboard.

這個專題是使用 ESP322搭配HC-SR04 超音波感測器和water sensor來檢測儲水箱中水的位置, 若低於設定的水位就利用relay去開啟抽水馬達抽水進入儲水箱當water sensor感測到水位就停止抽水, 然後將值發送到 MQTT 伺服器並透過 Node-red 儀表板顯示其數值.

Fundamental:

Water Level Sensor

A water level sensor is a module designed to sense water, even a small amount of moisture. Such a function can be useful in detecting leaks, rain, and even other moisture sensing functions other than water levels. Looking at the water level sensor, it has a series of parallel copper strips on the length of the module. Five of these are power strips and the other five are sensor strips. When the module is not submerged in water, there is a high resistance between the strips. As the strips come into contact with water, the resistance is lowered and conductivity is increased. The sensor has three pins, one is the VCC pin, the other is the GND pin, and the third is the SIGNAL pin to be attached to the pin where we will take our reading. On the module, the pins are designated as “+”, ”-”, and “S” respectively. 

Water level Sensor Pin Description:
1. S: is an analog output.
2. VCC: The Supply voltage Pin is 3.3v to 5v.
3. GND: is simply a ground connection.

之前的文章列表(previously project list):
HC-SR04 Ultrasonic Sensor
ESP32-CAM video streaming with Node Red 
Node-RED with ESP32 communication via Serial Port

BOM(Bill Of Material):
圖一: Water Sensorter Sensor
圖二: LCD 20*2

圖三:HC-SR40

圖四:  Real


Circuit:

YouTub Demo:

ESP32 code:
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <LiquidCrystal.h>
#include <ArduinoJson.h>
//--------- Flag structure --------------------------------------
typedef struct _vFlag
{
  uint8_t PumpFlag = 0;
  uint8_t HCSR04Flag = 1;
  uint8_t sensor_Flag = 1;
  uint8_t FunctionFlag = 0;
} vFlag;
vFlag *flag_Ptr;
vFlag flag;
//----------ssid---------------
// ------ 以下修改成你自己的WiFi帳號密碼 ------
char* ssid = "Winson_Y52";
char* password = "8888888888";
//---LCD -----------
//LiquidCrystal_I2C LCD = LiquidCrystal_I2C(0x27, 16, 2);
LiquidCrystal LCD(22,23,5,18,19,21);
//------LED------------------
#define LED_BUILTIN  2
//-----Relay------------------
#define RELAY1       15
//-----Water sensor------------------
#define POWER        34
#define SIGNAL       35
int value=0;
int level=0;
//-----switch------------------
#define MODE_PIN      27
#define LASER_PIN     26
//-----hcsr04 sensor------------------
#define TRIGPIN_PIN12 12
#define ECHO_PIN14    14
long duration;
int distance;
// ------ MQTT setting------
//char* MQTTServer = "broker.mqttgo.io";//https://broker.mqttgo.io/
char* MQTTServer = "test.mosquitto.org";
int MQTTPort = 1883;//MQTT Port
char* MQTTUser = "";//
char* MQTTPassword = "";//
//main level
char* MQTTPubTopic1 = "winsondiy/ESP32/container";

char* MQTTSubTopic1 = "winsondiy/ESP32/relay1";
long MQTTLastPublishTime;//此變數用來記錄推播時間
long MQTTPublishInterval = 1000;//每5秒推撥一次
WiFiClient WifiClient;
PubSubClient MQTTClient(WifiClient);
// Example MQTT Json message
const char* sensor = "HCSR04";
const char* exampleMQTT = "{\"sensor\":\"HCSR04\",\"data\":[20,2]}";
//const char* exampleMQTT = "{\"data\":[20,3]}";
// Calculate needed JSON document bytes with example message
const size_t CAPACITY = JSON_OBJECT_SIZE(sizeof(exampleMQTT) + 20);
//--------- uart structure --------------------------------------
//----------uart--------------
#define LINE_BUFFER_LENGTH 64
typedef struct _vUart
{
  char c;
  int lineIndex = 0;
  int line1Index = 0;
  int BTlineIndex = 0;
  bool lineIsComment;
  bool lineSemiColon;
  //char *line;
  char line[128];
  //char line1[128];
  char BTline[20];
  //char R_line[20];
  //char L_line[20];
  String inputString;
  String BTinputString;
  String S1inputString;
  int V[16];
  char ctemp[30];
  char I2C_Data[80];
  int DC_Spped = 50;
  float Voltage[16];
  int Buffer[128];
  int StartCnt = 0;
  int ReadCnt = 0;
  int sensorValue = 0;
} vUart;
vUart *Uart_Ptr;
vUart Uart;
//-------------------------------------
TaskHandle_t huart;
TaskHandle_t hfunction;

void vUARTTask(void *pvParameters);
void vFunctionTask(void *pvParameters);
//------------------------------------------------------------------------------
void initial()
{
  Serial.println(F("Create Task"));
  //----------------------------------------------------------------------
  xTaskCreatePinnedToCore(
    vUARTTask, "UARTTask" // A name just for humans
    ,
    1024 // This stack size can be checked & adjusted by reading the Stack Highwater
    ,
    NULL, 3 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,
    &huart //handle
    ,
    0);

  xTaskCreatePinnedToCore(
    vFunctionTask, "FunctionTask"
    ,
    1024 // Stack size
    ,
    NULL, 1 // Priority
    ,
    &hfunction
    ,
    0);

  //----------------------------------------------------------------------
  //vTaskSuspend(hfunction); //暫停TASK運行
  //----------------------------------------------------------------------
}
//-------------------------------------------------
void setup()
{
  Serial.begin(9600);
  Serial.println(F("init"));
  initial();
  pinMode(LED_BUILTIN, OUTPUT);
  //LCD.init();
  LCD.begin(20, 2);
  LCD.clear();
  //LCD.backlight();
  LCD.setCursor(0, 0);
  LCD.print("Distance (cm) ");
  LCD.setCursor(0, 1);
  LCD.print("------------- ");
  pinMode(TRIGPIN_PIN12, OUTPUT);
  pinMode(ECHO_PIN14, INPUT);
  pinMode(LASER_PIN, OUTPUT);
  pinMode(MODE_PIN, INPUT_PULLUP);
  pinMode(RELAY1, OUTPUT);
  digitalWrite(RELAY1, HIGH);
  pinMode(POWER,OUTPUT);
  digitalWrite(POWER,LOW);
}
//-----------------------------------------
void loop()
{
  Serial.print(F("Main at core:"));
  Serial.println(xPortGetCoreID());
  while(1)
  {
    if (WiFi.status() != WL_CONNECTED)
    {
      WifiConnect();
    }
   
    if (!MQTTClient.connected())
    {
      MQTTConnect();
    }
    //如果距離上次傳輸已經超過10秒,則Publish溫溼度
    if ((millis() - MQTTLastPublishTime) >= MQTTPublishInterval )
    {
      StaticJsonDocument<CAPACITY> doc;
      doc["HCSR04"] = distance;
      doc["Water_sensor"] = level;
      doc["Relay1"] = flag.PumpFlag;
      // Serialize JSON doc to char buffer with variable capacity (MQTT client needs char / char*)
      char JSONmessageBuffer[CAPACITY];
      //serializeJson(doc, Serial);
      serializeJson(doc, JSONmessageBuffer);
      MQTTClient.publish(MQTTPubTopic1, JSONmessageBuffer);
      //Serial.println("STATUS: Sent data via MQTT");
      Serial.println("water Data Publish to MQTT Broker");
      doc = NULL;
      MQTTLastPublishTime = millis();
    }
    MQTTClient.loop();//update status
    delay(50);
    if(flag.sensor_Flag==1)
    {
      if(flag.PumpFlag==1)
      {
        if(level>0)
        {
          digitalWrite(RELAY1, HIGH);
          flag.PumpFlag=0;
        }
      }
    }

    if(flag.HCSR04Flag==1)
    {
      if(flag.PumpFlag==0)
      {
        if(distance >= 5)
        {
          digitalWrite(RELAY1, LOW);
          flag.PumpFlag=1;
        }
      }    
    }
    //vTaskDelay(5);
  }
}
//----------------------------------------
void WifiConnect()
{
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }
  Serial.println("WiFi連線成功");
  Serial.print("IP Address:");
  Serial.println(WiFi.localIP());
}
//-----------------------------------------------
void MQTTConnect()
{
  MQTTClient.setServer(MQTTServer, MQTTPort);
  MQTTClient.setCallback(MQTTCallback);
  while (!MQTTClient.connected())
  {
    //以亂數為ClietID
    String MQTTClientid = "esp32-" + String(random(1000000, 9999999));
    if (MQTTClient.connect(MQTTClientid.c_str(), MQTTUser, MQTTPassword))
    {
      //連結成功,顯示「已連線」。
      Serial.println("MQTT已連線");
      //訂閱SubTopic1主題
      MQTTClient.subscribe(MQTTSubTopic1);
    }
    else
    {
      //若連線不成功,則顯示錯誤訊息,並重新連線
      Serial.print("MQTT連線失敗,狀態碼=");
      Serial.println(MQTTClient.state());
      Serial.println("五秒後重新連線");
      delay(5000);
    }
  }
}
//------------------------------------------
//接收到訂閱時
void MQTTCallback(char* topic, byte* payload, unsigned int length)
{
  Serial.print(topic); Serial.print("訂閱通知:");
  String payloadString;//將接收的payload轉成字串
  //顯示訂閱內容
  for (int i = 0; i < length; i++)
  {
    payloadString = payloadString + (char)payload[i];
  }
  Serial.println(payloadString);
  //比對主題是否為訂閱主題1
  if (strcmp(topic, MQTTSubTopic1) == 0)
  {
    Serial.println("改變燈號:" + payloadString);
    if (payloadString == "ON")
    {
      digitalWrite(16, HIGH);
    }
    if (payloadString == "OFF")
    {
      digitalWrite(16, LOW);
    }
  }
}
//-------------------------------------------
void processCommand(char *data)
{
  int len, xlen, ylen, zlen, alen;
  int tempDIO;
  String stemp;

  len = Uart.inputString.length();
  //---------------------------------------
  if (strstr(data, "VER") != NULL)
  {
    Serial.println(F("ESP32_20230710"));
  }
  //-------------- I2C --------------------
  if (strstr(data, "HCSR04_ON") != NULL)
  {
    flag.HCSR04Flag = 1;
    digitalWrite(LASER_PIN, HIGH);
    Serial.println(F("HCSR04_ON"));
  }
  if (strstr(data, "HCSR04_OFF") != NULL)
  {
    flag.HCSR04Flag = 0;
    digitalWrite(LASER_PIN, LOW);
    Serial.println(F("HCSR04_OFF"));
  }  
  if (strstr(data, "WATER_ON") != NULL)
  {
    flag.sensor_Flag = 1;
    Serial.println(F("WATER_ON"));
  }
  if (strstr(data, "WATER_OFF") != NULL)
  {
    flag.sensor_Flag = 0;
    Serial.println(F("WATER_OFF"));
  }
}
//-----------------------------------------
//-------------------------------------------
void vUARTTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.print(F("UARTTask at core:"));
  Serial.println(xPortGetCoreID());
  for (;;)
  {
    while (Serial.available() > 0)
    {
      Uart.c = Serial.read();
 
      if ((Uart.c == '\n') || (Uart.c == '\r'))
      { // End of line reached
        if (Uart.lineIndex > 0)
        { // Line is complete. Then execute!
          Uart.line[Uart.lineIndex] = '\0'; // Terminate string
          //Serial.println( F("Debug") );
          //Serial.println( Uart.inputString );
          processCommand(Uart.line); // do something with the command
          Uart.lineIndex = 0;
          Uart.inputString = "";
        }
        else
        {
          // Empty or comment line. Skip block.
        }
        Uart.lineIsComment = false;
        Uart.lineSemiColon = false;
        Serial.println(F("ok>"));
      }
      else
      {
        //Serial.println( c );
        if ((Uart.lineIsComment) || (Uart.lineSemiColon))
        {
          if (Uart.c == ')')
            Uart.lineIsComment = false; // End of comment. Resume line.
        }
        else
        {
          if (Uart.c == '/')
          { // Block delete not supported. Ignore character.
          }
          else if (Uart.c == '~')
          { // Enable comments flag and ignore all characters until ')' or EOL.
            Uart.lineIsComment = true;
          }
          else if (Uart.c == ';')
          {
            Uart.lineSemiColon = true;
          }
          else if (Uart.lineIndex >= LINE_BUFFER_LENGTH - 1)
          {
            Serial.println("ERROR - lineBuffer overflow");
            Uart.lineIsComment = false;
            Uart.lineSemiColon = false;
          }
          else if (Uart.c >= 'a' && Uart.c <= 'z')
          { // Upcase lowercase
            Uart.line[Uart.lineIndex] = Uart.c - 'a' + 'A';
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += (char)(Uart.c - 'a' + 'A');
          }
          else
          {
            Uart.line[Uart.lineIndex] = Uart.c;
            Uart.lineIndex = Uart.lineIndex + 1;
            Uart.inputString += Uart.c;
          }
        }
      }
    } //while (Serial.available() > 0)
    vTaskDelay(10);
  }
}
void vFunctionTask(void *pvParameters)
{
  (void)pvParameters;

  Serial.print(F("FunctionTask at core:"));
  Serial.println(xPortGetCoreID());
  for (;;) // A Task shall never return or exit.
  {
    if(flag.sensor_Flag==1)
    {
      level=waterSensor();
      level=map(level, 0, 1060, 0, 4);
      Serial.print("Water Level:");
      Serial.println(level);
      delay(10);
    }
    if(flag.HCSR04Flag==1)
    {
      digitalWrite(TRIGPIN_PIN12, LOW);  //關閉超音波
      delayMicroseconds(5);  //sustain at least 10us HIGH pulse
      digitalWrite(TRIGPIN_PIN12, HIGH); //啟動超音波
      delayMicroseconds(10);  //sustain at least 10us HIGH pulse
      digitalWrite(TRIGPIN_PIN12, LOW);  //關閉超音波
      duration= pulseIn(ECHO_PIN14, HIGH);
      distance= duration/29/2;

      if (duration==0)
      {
        Serial.println("No pulse is from sensor");
      }
      else {
        Serial.print("Ultrasonic sensor is shown distance:");
        Serial.print(distance);
        Serial.println("cm");
        LCD.clear();
        LCD.setCursor(0, 0);
        LCD.print("Distance (cm) -- ");
        LCD.print(distance);
        LCD.setCursor(0, 1);
        LCD.print("Water Sensor -- ");
        LCD.print(level);
      }
      delay(10);
    }
    vTaskDelay(1);
  }
}
//--------------------------------------------
int waterSensor()
{
  value=analogRead(SIGNAL);
  return value;
}


2023年10月19日 星期四

ESP32CAM Four-in-one function

Purpose:

結合之前利用ESP32CAM所做的一些功能, 如Line notify, PIR detect, MQTT, Web Stream等等. 把這些ESP32CAM的code寫在一起方便以後maintain.

Combined with some functions previously project done using ESP32CAM, such as Line notify, PIR detect, MQTT, Web Stream, etc. Write these ESP32CAM codes together for easy maintenance in the future.

之前的文章列表(previously project list):

ESP32CAM PIR DETECTION WITH LINE Notify

ESP32-CAM video streaming with Node Red

ESP32 MQTT Publish/Subscribe DS18B20 Temperature to Node-Red

ESP32-CAM Video Stream by C#

Fundamental:

ESP32CAM circuit




Arduino IDE2

MQTT

請參照之前文章ESP32 MQTT Publish/Subscribe DS18B20 Temperature to Node-Red



Web-Stream Function:

請參照之前文章 ESP32-CAM Video Stream by C#

PIR Detect to Linenotify:

請參照之前文章ESP32CAM PIR DETECTION WITH LINE Notify

YouTubeDemo:


ESP32CAM Code:

#include <PubSubClient.h>
#include <HTTPClient.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include "soc/soc.h"             //用於電源不穩不重開機
#include "soc/rtc_cntl_reg.h"    //用於電源不穩不重開機
#include "esp_camera.h"          //視訊函式

//ESP32-CAM 安信可模組腳位設定
#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
// Enter your WiFi ssid and password
const char* ssid     = "your network SSID";   //your network SSID
const char* password = "your network password";   //your network password

String lineNotifyToken = "Your Totken";    //Line Notify Token
//----MQTT--------
//------ 以下修改成你MQTT設定 ------
char* MQTTServer = "mqttgo.io";
int MQTTPort = 1883;//MQTT Port
char* MQTTUser = "";//
char* MQTTPassword = "";//
//-----publish-----
char* MQTTTopicPic1  = "yourpublish/pic";  
//subscribe燈號
char* MQTTSubTopic1 = "yoursubscribe/led";
char* MQTTSubTopic2 = "yoursubscribe/flash";
long MQTTLastPublishTime;//此變數用來記錄推播時間
long MQTTPublishInterval = 300;//每0.5秒推撥一次

WiFiClient WifiClient;
PubSubClient MQTTClient(WifiClient);
//--------------------------------------------------------------------------
#define LED_IN 33
const int Led_Flash = 4;
const int Led_run = 13;
int PIR_Sensor = 12;
boolean startTimer = false;
unsigned long time_now=0;
int time_capture=0;
int wifi_Flag=0;
int wifi_cnt=0;
int MqttFlag=0;
int LinenotifyFlag=0;

#define CAMERA_MODEL_AI_THINKER // Has PSRAM
void startCameraServer();

void setup()
{
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);  //關閉電源不穩就重開機的設定
   
  Serial.begin(9600);
  Serial.setDebugOutput(true);  //開啟診斷輸出
  Serial.println();
  setupCam();

  pinMode(Led_Flash, OUTPUT);
  pinMode(Led_run, OUTPUT);
  pinMode(LED_IN, OUTPUT);
  pinMode(PIR_Sensor, INPUT_PULLDOWN);

  //閃光燈(GPIO4)
  ledcAttachPin(Led_Flash, 4);  
  ledcSetup(Led_Flash, 5000, 8);
 
  WiFi.mode(WIFI_AP_STA);  //其他模式 WiFi.mode(WIFI_AP); WiFi.mode(WIFI_STA);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
    wifi_cnt++;
    Serial.println(wifi_cnt);
    if(wifi_cnt==20)
    {
      //change another AP
      WiFi.begin("ras_2.4", "181415181415");
      while (WiFi.status() != WL_CONNECTED)
      {
        delay(500);
        Serial.print(".");
      }
    }
    if (WiFi.status() == WL_CONNECTED) {    //若連線成功
      Serial.println("");
      Serial.println("STAIP address: ");
      Serial.println(WiFi.localIP());
      Serial.println("");

      wifi_Flag=1;
      startCameraServer();
      break;
    }
  }
 
  digitalWrite(Led_Flash, LOW);
 
}

void loop()
{
  int v = digitalRead(PIR_Sensor);
  if (v == 1) {
    //傳照片
    String result = SendImageMQTT();
    Serial.println(result);
    ledcWrite(4,10);
    delay(1000);
    Serial.println("starting to Line");
    sendCapturedImage2LineNotify(lineNotifyToken);
    ledcWrite(4,0);
  }
  if(wifi_Flag==1)
  {
    digitalWrite(LED_IN, LOW);
    delay(200);
    digitalWrite(LED_IN, HIGH);
    delay(200);
  }
  if (!MQTTClient.connected())
  {
    MQTTConnecte();
  }
  if ((millis() - MQTTLastPublishTime) >= MQTTPublishInterval )
  {
    //String result = SendImageMQTT();
    //Serial.println(result);
    MQTTLastPublishTime = millis(); //更新最後傳輸時間
  }
  MQTTClient.loop();//更新訂閱狀態
  delay(100);  //You could only send up to 50 images to Line Notify in one hour.
}

//鏡頭設定
void setupCam() {  
  // #define CAMERA_MODEL_AI_THINKER
  //視訊組態設定  https://github.com/espressif/esp32-camera/blob/master/driver/include/esp_camera.h
  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;

  if(psramFound()){  //是否有PSRAM(Psuedo SRAM)記憶體IC
    config.frame_size = FRAMESIZE_UXGA;
    config.jpeg_quality = 10;
    config.fb_count = 2;
  } else {
    config.frame_size = FRAMESIZE_SVGA;
    config.jpeg_quality = 12;
    config.fb_count = 1;
  }

  //視訊初始化
  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x", err);
    ESP.restart();
  }

  //可自訂視訊框架預設大小(解析度大小)
  sensor_t * s = esp_camera_sensor_get();
  // initial sensors are flipped vertically and colors are a bit saturated
  if (s->id.PID == OV3660_PID) {
    s->set_vflip(s, 1); // flip it back
    s->set_brightness(s, 1); // up the brightness just a bit
    s->set_saturation(s, -2); // lower the saturation
  }
  // drop down frame size for higher initial frame rate
  s->set_framesize(s, FRAMESIZE_SVGA);    //解析度 UXGA(1600x1200), SXGA(1280x1024), XGA(1024x768), SVGA(800x600), VGA(640x480), CIF(400x296), QVGA(320x240), HQVGA(240x176), QQVGA(160x120), QXGA(2048x1564 for OV3660)

  //s->set_vflip(s, 1);  //垂直翻轉
  //s->set_hmirror(s, 1);  //水平鏡像
  //s->set_pRotation(s, 1);  //0=不旋轉 1-轉90度 2- 轉180度 3-轉270
}

String sendCapturedImage2LineNotify(String token) {
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("Camera capture failed");
    delay(1000);
    ESP.restart();
    return "Camera capture failed";
  }
   
  WiFiClientSecure client_tcp;  //啟動SSL wificlient
  client_tcp.setInsecure();   //run version 1.0.5 or above
  Serial.println("Connect to notify-api.line.me");
  if (client_tcp.connect("notify-api.line.me", 443)) {
    Serial.println("Connection successful");
   
    String message = "ESP32-CAM";
    String head = "--Taiwan\r\nContent-Disposition: form-data; name=\"message\"; \r\n\r\n" + message + "\r\n--Taiwan\r\nContent-Disposition: form-data; name=\"imageFile\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
    String tail = "\r\n--Taiwan--\r\n";

    uint16_t imageLen = fb->len;
    uint16_t extraLen = head.length() + tail.length();
    uint16_t totalLen = imageLen + extraLen;
    //開始POST傳送訊息
    client_tcp.println("POST /api/notify HTTP/1.1");
    client_tcp.println("Connection: close");
    client_tcp.println("Host: notify-api.line.me");
    client_tcp.println("Authorization: Bearer " + token);
    client_tcp.println("Content-Length: " + String(totalLen));
    client_tcp.println("Content-Type: multipart/form-data; boundary=Taiwan");
    client_tcp.println();
    client_tcp.print(head);
   
    uint8_t *fbBuf = fb->buf;
    size_t fbLen = fb->len;
    Serial.println("Data Sending....");
    //檔案太大,分段傳送
    for (size_t n=0;n<fbLen;n=n+1024) {
      if (n+1024<fbLen) {
        client_tcp.write(fbBuf, 1024);
        fbBuf += 1024;
      }
      else if (fbLen%1024>0) {
        size_t remainder = fbLen%1024;
        client_tcp.write(fbBuf, remainder);
      }
    }  
   
    client_tcp.print(tail);
    client_tcp.println();
    esp_camera_fb_return(fb);

    String getResponse="",Feedback="";
    int waitTime = 10000;   // timeout 10 seconds
    long startTime = millis();
    boolean state = false;
   
    while ((startTime + waitTime) > millis()) {
      Serial.print(".");
      delay(100);      
      while (client_tcp.available()) {  //當有收到回覆資料時
          char c = client_tcp.read();
          if (state==true) Feedback += String(c);        
          if (c == '\n') {
            if (getResponse.length()==0) state=true;
            getResponse = "";
          }
          else if (c != '\r')
            getResponse += String(c);
          startTime = millis();
       }
       if (Feedback.length()>0) break;
    }
    Serial.println();
    client_tcp.stop();
    return Feedback;
  }
  else {
    return "Connected to notify-api.line.me failed.";
  }
}

String sendRequest2LineNotify(String token, String request) {
  request.replace("%","%25");
  request.replace(" ","%20");
  //request.replace("&","%20");
  request.replace("#","%20");
  //request.replace("\'","%27");
  request.replace("\"","%22");
  request.replace("\n","%0D%0A");
  request.replace("%3Cbr%3E","%0D%0A");
  request.replace("%3Cbr/%3E","%0D%0A");
  request.replace("%3Cbr%20/%3E","%0D%0A");
  request.replace("%3CBR%3E","%0D%0A");
  request.replace("%3CBR/%3E","%0D%0A");
  request.replace("%3CBR%20/%3E","%0D%0A");
  request.replace("%20stickerPackageId","&stickerPackageId");
  request.replace("%20stickerId","&stickerId");    

  WiFiClientSecure client_tcp;
  client_tcp.setInsecure();   //run version 1.0.5 or above
 
  Serial.println("Connect to notify-api.line.me");  
 
  if (client_tcp.connect("notify-api.line.me", 443)) {
    Serial.println("Connection successful");
       
    Serial.println(request);    
    client_tcp.println("POST /api/notify HTTP/1.1");
    client_tcp.println("Connection: close");
    client_tcp.println("Host: notify-api.line.me");
    client_tcp.println("User-Agent: ESP8266/1.0");
    client_tcp.println("Authorization: Bearer " + token);
    client_tcp.println("Content-Type: application/x-www-form-urlencoded");
    client_tcp.println("Content-Length: " + String(request.length()));
    client_tcp.println();
    client_tcp.println(request);
    client_tcp.println();
   
    String getResponse="",Feedback="";
    boolean state = false;
    int waitTime = 3000;   // timeout 3 seconds
    long startTime = millis();
    while ((startTime + waitTime) > millis()) {
      Serial.print(".");
      delay(100);      
      while (client_tcp.available()) {
          char c = client_tcp.read();
          if (state==true) Feedback += String(c);        
          if (c == '\n') {
            if (getResponse.length()==0) state=true;
            getResponse = "";
          }
          else if (c != '\r')
            getResponse += String(c);
          startTime = millis();
       }
       if (getResponse.length()>0) break;
    }
    Serial.println();
    client_tcp.stop();
    return Feedback;
  }
  else
    return "Connection failed";  
}

//開始MQTT連線伺服器
void MQTTConnecte()
{
  MQTTClient.setServer(MQTTServer, MQTTPort);
  MQTTClient.setCallback(MQTTCallback);
  while (!MQTTClient.connected()) {
    //以亂數為ClientID
    String  MQTTClientid = "esp32-" + String(random(1000000, 9999999));
    if (MQTTClient.connect(MQTTClientid.c_str(), MQTTUser, MQTTPassword)) {
      //連結成功,顯示「已連線」。
      Serial.println("MQTT已連線");
      //訂閱SubTopic1主題
      MQTTClient.subscribe(MQTTSubTopic1);
      MQTTClient.subscribe(MQTTSubTopic2);
    } else {
      //若連線不成功,則顯示錯誤訊息,並重新連線
      Serial.print("MQTT連線失敗,狀態碼=");
      Serial.println(MQTTClient.state());
      Serial.println("五秒後重新連線");
      delay(5000);
    }
  }
}

//拍照傳送到MQTT
String SendImageMQTT()
{
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();  
  if(!fb) {
    Serial.println("MQTT capture failed");
    delay(1000);
    ESP.restart();
    return "MQTT capture failed";
  }
  size_t fbLen = fb->len;
  //int ps = 512;
  //開始傳遞影像檔,批次傳黨
  MQTTClient.beginPublish(MQTTTopicPic1, fbLen, false);
  uint8_t *fbBuf = fb->buf;
  for (size_t n = 0; n < fbLen; n = n + 2048) {
    if (n + 2048 < fbLen) {
      MQTTClient.write(fbBuf, 2048);
      fbBuf += 2048;
    } else if (fbLen % 2048 > 0) {
      size_t remainder = fbLen % 2048;
      MQTTClient.write(fbBuf, remainder);
    }
  }
  boolean isPublished = MQTTClient.endPublish();
  esp_camera_fb_return(fb);//清除緩衝區
  if (isPublished) {
    return "MQTT傳輸成功";
  }
  else {
    return "MQTT傳輸失敗,請檢查網路設定";
  }
}

//接收到訂閱時
void MQTTCallback(char* topic, byte* payload, unsigned int length)
{
  Serial.print(topic); Serial.print("訂閱通知:");
  String payloadString;//將接收的payload轉成字串
  //顯示訂閱內容
  for (int i = 0; i < length; i++) {
    payloadString = payloadString + (char)payload[i];
  }
  Serial.println(payloadString);
  //比對主題是否為訂閱主題1
  if (strcmp(topic, MQTTSubTopic1) == 0)
  {
    Serial.println("LED:" + payloadString);
    if (payloadString == "ON")
    {
      digitalWrite(16, HIGH);
    }
    if (payloadString == "OFF")
    {
      digitalWrite(16, LOW);
    }
  }
  //比對主題是否為訂閱主題1
  if (strcmp(topic, MQTTSubTopic2) == 0) {
    Serial.println("Flash:" + payloadString);
    if (payloadString == "ON")
    {
      ledcWrite(4,10);
      delay(1000);
      String result = SendImageMQTT();
      Serial.println(result);
      delay(1000);
      Serial.println("starting to Line");
      sendCapturedImage2LineNotify(lineNotifyToken);
      for (int i=0;i<3;i++) {  
        ledcWrite(4,10);
        delay(200);
        ledcWrite(4,0);
        delay(200);    
      }
    }
    if (payloadString == "OFF")
    {
      ledcWrite(4,0);
    }
  }
}