2022年1月9日 星期日

IOT物聯網之--當ESPxxx遇到Line





一、使用Line Notify之環境溫/溼度監控裝置


二、使用Line Notify傳送照片之安全監控系統


三、使用Line Notify傳送照片之安全監控系統

        之二---低功耗篇













一、使用Line Notify之環境溫溼度監控裝置

在這個範例中會示範如何使用ESP8266或ESP32及一個溫/溼度感測器(在此為DHT11)建構一環境溫/溼度監控裝置,而此裝置所量測到的溫/濕度值會透過網際網路(Internet)傳送到使用者的Line社群通訊軟體上。在使用這個裝置之前,你必須先申請及取得一Line軟體的Notify通訊傳輸用憑證(Token),同時在你的Line上建立一可接收此憑證(Token)的群組,以作為接收量測到訊息之用。


功能與動作說明

在我們的系統中將具備下面的功能與動作

1•可設定會發出警示訊息的溫/濕度上限值當量測到的溫/濕度值超過其中之一

   時便會發出警示訊息提醒使用者也可以設定定時將量測到的溫/濕度值送

   到使用者Line軟體特定的群組上

2•在發送的訊息中會包括測量或觸發警訊的時間(以格林威治時間為準)。

3•如果是超過溫/濕度上限值的警示訊息其中除了量測到的溫/濕度值之外

  會包括警示的種類即溫度或濕度


電路圖

在這個範例中我們的系統中除了核心的ESP8266或ESP32模組板之外還使用了DHT11這個兼具溫/濕度量測功的感測器如果想要具有更高的精確度可以改用更高階的DHT22除此之外還使用了一個開關用來控制是否使用定時發送量測到的溫/濕度值的功能;如果只是想做個實驗看看可以改用導線以直接短接的方式(接地)來取代開關。對於ESP8266或ESP32模組板來說我們共使用了2根I/O接腳,分別是接到DHT11資料線(S) 的GPIO14腳和控制定時發送開關的GPIO12腳。由於本範例可同時適用於ESP6266與ESP32兩種晶片模組板,因此後面把一般常會使用的ESP8266或ESP32模組板的連接電路一一展示出來。

在下面這個NodeMcu  ESP8266模組電路來說系統中DHT11的資料線(S)是接在標示為D5但也是原來的GPIO14這隻腳上,而開關則是接在D6/GPIO12腳上。


NodeMcu 與DHT11接線圖


如果是使用長得像Arduino Uno的WeMos D1/R1 ESP8266模組板,它板子上面的接腳同時標示了GPIO14/D5及GPIO12/D6所以在接線上應該是不會混淆才是。


WeMos D1/R1與DHT11接線圖

到了ESP32以後的類NodeMcu模組板,其IO接腳的標示則又有些不同,以下圖來說,GPIO14就是標示為D14的接腳,而同樣的發送控制開關則是接在D12上面。


ESP32 NodeMcu與DHT11接線圖


至於同樣長得像Arduino Uno的WeMos D1/R32模組板,上面則是直接標示了GPIO14及GPIO12所以直接連接DHT11跟開關就可以了!


WeMos D1/R32與DHT11接線圖


程式列表與說明

  1. #ifdef ESP32

  2. #include <WiFi.h>

  3. int ledOn = 1;

  4. int ledOff = 0;

  5. #else

  6. #include <ESP8266WiFi.h>

  7. int ledOn = 0;

  8. int ledOff = 1;

  9. #endif

  10.  

  11. #ifdef ESP8266

  12. String  ESPtype = "ESP8266-" + String(ESP.getChipId());

  13. #else

  14. String  ESPtype = "ESP32";

  15. #endif

  16.  

  17. #include <TridentTD_LineNotify.h> // Line Notify專用的引入函示庫

  18. #include "DHT.h" // DHTxx系列感測器函示庫

  19. #include <NTPClient.h> // 國際標準時間網路擷取函示庫

  20. #include <WiFiUdp.h>

  21.  

  22. #define LED 2

  23. #define sendEnPin 12 // 定時發送選擇控制腳

  24. #define DHTPIN  14 // DHTxx感測器資料信號接腳

  25. #define DHTTYPE DHT11 // 溫溼度感測器種類型號

  26. DHT dht(DHTPIN, DHTTYPE);

  27.  

  28. #define WIFI_SSID "YourWiFiRouter"

  29. #define WIFI_PASSWORD "YourWiFiPassword"

  30.  

  31. #define LINE_TOKEN  "YourLineTokenID:xxxxxxxxxxxxxx"

  32.  

  33. WiFiUDP ntpUDP;

  34. NTPClient timeClient(ntpUDP, "pool.ntp.org"); // 國際標準時間網站

  35.  

  36. float humLimit = 90;    // 濕度警示上限值

  37. float tempLimit = 32;   // 溫度警示上限值

  38. unsigned long currentTime;

  39. int count = 0;

  40. int sendTime=10;        // DHT11量測結果發送時間以秒為單位

  41. int checkTime=2;        // DHT11量測結果檢查時間以秒為單位

  42.  

  43. void setup() {

  44.   Serial.begin(115200); Serial.println();

  45.   Serial.println(LINE.getVersion());

  46.   pinMode(LED, OUTPUT);

  47.   pinMode(sendEnPin, INPUT_PULLUP);

  48.   Serial.print("連線到 Wifi SSID ");

  49.   Serial.print(WIFI_SSID);

  50.   WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  51.   bool  outState = false;

  52.   while (WiFi.status() != WL_CONNECTED)

  53.   {

  54.     Serial.print(".");

  55.     outState = !outState;

  56.     if (outState)

  57.       digitalWrite(LED, ledOn);

  58.     else

  59.       digitalWrite(LED, ledOff);

  60.     delay(500);

  61.   }

  62.   digitalWrite(LED, ledOn);

  63.   Serial.println();

  64.   Serial.print("\nWiFi 連線成功! IP address: ");

  65.   Serial.println(WiFi.localIP());

  66.   Serial.print("目前使用的模組為 => ");

  67.   Serial.println(ESPtype);

  68.  

  69.   dht.begin(); // 初始化溫溼度感測器

  70.  

  71.   timeClient.begin();

  72.   timeClient.setTimeOffset(28800);  // 加上本地時間(距國際標準格林威治時間8小時)偏差的補償值

  73.  

  74.   //  初始化Line Token

  75.   LINE.setToken(LINE_TOKEN);

  76.  

  77.   Serial.println("開始傳送DHT11/22的量測資料!");

  78.   LINE.notify("開始傳送DHT11/22的量測資料!");  // 系統啟動後先送出一提示訊息給Line使用者提醒系統已經開始工作

  79.   delay(2000);

  80.   currentTime = millis(); // 取得我們系統目前的時間(以mS為單位)

  81. }

  82.  

  83. void loop() {

    1. // 先測試是否已到達檢查環境溫濕度的時間:

  84.   if ((millis() - currentTime) >= checkTime*1000)   

  85.   {

  86.     count+=checkTime;

  87.     currentTime = millis();

  88.     timeClient.update();  // 取得目前的格林威治標準時間

  89.     // 取出其中帶有紀元時間中(自 1970 年 1 月 1 日 格林威治標準時間午夜以來)經過的秒數;

  90.     unsigned long epochTime = timeClient.getEpochTime();

  91.  

  92.     //  轉換並取得一時間結構資料:

  93.     struct tm *ptm = gmtime ((time_t *)&epochTime);

  94.  

  95.     int monthDay = ptm->tm_mday; // 取得目前日期中的天數

  96.     int currentMonth = ptm->tm_mon + 1; // 取得目前日期中的月份

  97.     int currentYear = ptm->tm_year + 1900; // 取得目前日期中的年

  98.  

  99.     //Print complete date:

  100.     String currentDate = String(currentYear) + "-" + String(currentMonth) + "-" + String(monthDay);

  101.     // 將前面取得的目前日期的年月日轉成一個日期字串

  102.     String formattedTime = timeClient.getFormattedTime();

  103.     String msg = "測量的日期與時間為: " + currentDate + "  " + formattedTime + "\n";

  104.  

  105.     float  hum = dht.readHumidity(); // 取得感測器所測量的濕度值

  106.     float  tem = dht.readTemperature(); // 取得感測器所測量的溫度值

  107.  

  108.     msg += ESPtype + "上的DHT11/22:\n溫度 = " + String(tem) + " 'C\n";

  109.     msg += "濕度 = " + String(hum) + " %\n";

  110.     String  hint="";

  111.     if (tem >= tempLimit) 

  112.       hint="!!! 溫度超過上限值 !!!\n";

  113.     if (hum >= humLimit) 

  114.       hint+="!!! 濕度超過上限值 !!!\n";

  115.  

  116. // 測試目前的溫/溼度值是否超標

  117.     if ((hum >= humLimit) || (tem >= tempLimit))

  118.     {

  119.       msg=hint+msg; // 將警示訊息合併到溫/溼度量測值訊息

  120.       LINE.notify(msg);   // 若超過警戒值則發送警示訊息到對應的Line群組

  121.     }  

  122.     if (count >= sendTime) // 測試是否已達定時發送的時間

  123.     {

  124.       if (digitalRead(sendEnPin) == 0) // 測試定時發送功能是否被致能

  125.         LINE.notify(msg); // 若是則將量測到的溫/濕度訊息發送到Line群組

    1. // 將將量測到的溫/濕度訊息顯示在序列監控視窗上

  126.       Serial.print("今天的日期為(年/月/日): ");

  127.       Serial.println(currentDate);

  128.       Serial.print("現在時間是: ");

  129.       Serial.println(formattedTime);

  130.       Serial.println("DHT11測量結果為");

  131.       Serial.print("溫度 = ");

  132.       Serial.print(tem);

  133.       Serial.println("*C");

  134.       Serial.print("濕度 = ");

  135.       Serial.print(hum);

  136.       Serial.print("%\t");

  137.       Serial.println();

  138.       count = 0;

  139.     }

  140.   }

  141. }  


在文章的前面就已經說過,這個範例可同時適用於ESP6266與ESP32這兩種系列的模組板,因此在我的程式中一開始就加上了下面這幾行程式去宣告不同模組板要使用的WiFi引入函式庫,這樣Arduino IDE這個開發軟體就會自動以你所使用的模組板去選取對應的函式庫。

此由於系統電路中會使用內建的LED(接在GPIO2上)來指示是否連上了外部上網際網路(Internet)用的WiFi分享器,不過這顆LED在ESP8266系列的模組上是低態(Low)點亮,可是到了ESP32系列的開發板卻改為高態(High),為了相容兩種不同開發板的差異,就順便在這裡定義它們驅動LED亮滅的輸出位準值。


  1. #ifdef ESP32

  2.   #include <WiFi.h>

  3.   int ledOn = 1;

  4.   int ledOff = 0;

  5. #else

  6.   #include <ESP8266WiFi.h>

  7.   int ledOn = 0;

  8.   int ledOff = 1;

  9. #endif


下面接著的是宣告並引入程式中會用到的一些函式庫依序是Line Notify專用的函式庫< TridentTD_LineNotify.h >DHTxx系列感測器函式庫及擷取網路上國際標準時間的函式庫:


#include <TridentTD_LineNotify.h> // 引入Line Notify專用的函示庫

#include "DHT.h" // 引入DHTxx系列感測器函示庫

#include <NTPClient.h> // 引入國際標準時間網路擷取函示庫

#include <WiFiUdp.h>


< TridentTD_LineNotify.h >這個Line Notify專用的函式庫並不是Arduino IDE中內建的程式庫所以必須另外自行去擷取其方法如下:

先點選下圖Arduino IDE中標記1的[草稿碼]然後🡪2[匯入程式庫]🡪再點選3[管理程式庫]便可看到[程式庫管理員]這個跳出視窗。



然後在[程式庫管理員]這個跳出視窗的標記1搜尋欄位中輸入”Linenotify”這個特徵字,便可以找到< TridentTD_LineNotify.h >這個函式庫,最後按下標記2中的【安裝】按鈕便可完成安裝的動作。


下面這幾行程式在定義電路中會用到的幾隻I/O接腳並且定義所使用的溫/濕度感測器型號如果想要使用比較精確的感測器可以將其中的「DHT11」改成🡪「DHT22」即可。


#define LED 2 // ESPxxx開發板內建的LED接腳

#define sendEnPin 12 // 定時發送選擇控制腳

#define DHTPIN  14 // DHTxx感測器資料信號接腳

#define DHTTYPE DHT11 // 溫溼度感測器種類型號

DHT dht(DHTPIN, DHTTYPE);


由於我們的系統必須連上網際網路(Internet),才能把測量到的溫/溼度訊息傳到使用者的Line群組上,因此不要忘了把下面WiFi分享器的SSID名稱與密碼改成你自己的,並在「LINE_TOKEN」後面填上你申請到的Line群組通訊用憑證(Token)。


#define WIFI_SSID "YourWiFiRouter"

#define WIFI_PASSWORD "YourWiFiPassword"

 

#define LINE_TOKEN  "YourLineTokenID:xxxxxxxxxxxxxx"

 


在本範例中我們設定的溫度警示上限為32度C濕度則為90%如果想要使用其他的上限值只要修改下面程式的前兩行內容就可以了。在我們的系統中會每隔2秒(即”checkTime”)檢查一次測量到的溫/濕度是否超過警戒上限如果有啟動定時發送的功能則發送的間隔時間由”sendTime”這個變數來決定在此為10秒;當然我們可以自行修改這兩個的時間值不過一般發送的間隔時間要大於檢查的時間才合理!


float humLimit = 90;    // 濕度警示上限值

float tempLimit = 32;   // 溫度警示上限值

unsigned long currentTime;

int count = 0;

int sendTime=10;        // DHT11量測結果發送時間以秒為單位

int checkTime=2;        // DHT11量測結果檢查時間以秒為單位


在實際設定溫/溼度的警戒值時要注意你所使用的感測器是否能正常的工作你所設定的範圍內下面圖表是我們常會使用編號為DHT11/22的兩款感測器規格表以DHT11來說它的溫度測量範圍為0~50∘C大概就是一般的室溫範圍再高就不行了!至於濕度則是從20%~80%也就是說我們上面的設定是有問題的在實際的使用時可以看到DHT11最高量測到的溼度輸出值為95%只是準確度就僅供參考了!如果想要有更高更準確的溫/濕度量測範圍最好就是改用DHT22了。


接下來的程式是屬於初始化設定(即”setup()”)的部分,下面幾行的程式除了在設定Arduino IDE的序列監控視窗的傳輸速率之外,主要在設定會使用到的兩隻I/O接腳模式,一般來說如果是輸入腳是不必特別去設定它;在我們的系統中用到一根接腳(名稱為”sendEnPin”)去選擇是否要定時發送量測到的環境溫/濕度,由於它是低態動作,為了節省外部接到+電源的提升電阻,所以在此特別將這隻接腳設定為有內建提升電阻的輸入模式。


Serial.begin(115200); Serial.println();

  Serial.println(LINE.getVersion());

  pinMode(LED, OUTPUT);

  pinMode(sendEnPin, INPUT_PULLUP);


接著的程式是一般常見用來連線WiFi分享器的程式,為了方便使用者的觀測,在此把它改成在等待連線時除了在Arduino IDE的序列監控視窗顯示符號’.’之外,還會讓開發板上那顆內建接在GPIO2的LED跟著閃爍,等到連線上WiFi分享器之後,這顆LED便會一直亮著。


bool  outState = false;

  while (WiFi.status() != WL_CONNECTED)

  {

    Serial.print(".");

    outState = !outState;

    if (outState)

      digitalWrite(LED, ledOn);

    else

      digitalWrite(LED, ledOff);

    delay(500);

  }

  digitalWrite(LED, ledOn);

  Serial.println();

  Serial.print("\nWiFi 連線成功! IP address: ");

  Serial.println(WiFi.localIP());


在連線上WiFi分享器之後又是一連串的初始化動作,包括了溫/濕度感測器DHT11的初始化,及網路標準時間網站連線等;由於台灣和國際標準時間所在地的英國格林威治時區差了8個小時,所以在程式中必須先加上這個偏差值「28800」(以秒為單位)。最後再以使用者所取得的憑證(Token)去初始化Line Notify的動作,並且發出第一筆訊息到使用者的群組中,通知這個系統已經開始啟動了。


   dht.begin(); // 初始化溫溼度感測器

 

  timeClient.begin();

  timeClient.setTimeOffset(28800);  // 加上本地時間(距國際標準格林威治時間8小時)偏差的補償值

 

  //  初始化Line Token

  LINE.setToken(LINE_TOKEN);

 

  Serial.println("開始傳送DHT11/22的量測資料!");

  LINE.notify("開始傳送DHT11/22的量測資料!");  // 系統啟動後先送出一提示訊息給Line使用者提醒系統已經開始工作


接下來的程式都是屬於主迴圈程式區(即”loop()”)的部分,其實整個主迴圈程式就是由下面這個定時(即由”checkTime”所決定的時間)檢查的程式所構成:


// 先測試是否已到達檢查環境溫濕度的時間:

  if ((millis() - currentTime) >= checkTime*1000)   

  {…………………..}


每當到達檢測時間,系統會先從網路上的標準時間網站取得現在的日期及時間資料,不過由於<NTPClient.h>這個網路標準時間函式庫中只提供取得時間(包括時、分、秒)及星期的函式,並沒有直接關於的日期的函式,所以我們必須取出其中的UNIX時間”epochTime”(即自 1970 年 1 月 1 日午夜以來經過的秒數)再自行轉換。


    count+=checkTime;

    currentTime = millis();

    timeClient.update();  // 取得目前的格林威治標準時間

    // 取出其中的UNIX時間(自 1970 年 1 月 1 日午夜以來經過的秒數)

    unsigned long epochTime = timeClient.getEpochTime();


由於這個轉換出來的日期/時間資料結構(即變數”ptm”)都是整數型態的資料,其中月份是由0開始,所以我們取出來的時候要先加1;而年份則是從1900年算起,所以實際的年份也是要加上這個偏移量才行。下面程式剩下的部分會把這些日期跟時間資料打包轉換成單一的字串變數「msg」以供後面的程式使用。


 

    //  轉換UNIX時間並取得一時間結構資料:

    struct tm *ptm = gmtime ((time_t *)&epochTime);

 

    int monthDay = ptm->tm_mday; // 取得目前日期中的天數

    int currentMonth = ptm->tm_mon + 1; // 取得目前日期中的月份

    int currentYear = ptm->tm_year + 1900; // 取得目前日期中的年

 

    //Print complete date:

    String currentDate = String(currentYear) + "-" + String(currentMonth) + "-" + String(monthDay);

    // 將前面取得的目前日期的年月日轉成一個日期字串

    String formattedTime = timeClient.getFormattedTime();

    String msg = "測量的日期與時間為: " + currentDate + "  " + formattedTime + "\n";


接下來的程式將開始啟動感測器DHT11測量目前環境的溫/溼度,並將所得到的結果合併到訊息字串”msg”上。然後再測試目前所測量到的溫/濕度值是否超過使用者設定的上限值如果是則再依照種類分別加上不同的警示訊息到字串”msg”上當然如果已經超過使用者設定的上限值就會把訊息傳送到使用者的Line群組上提醒使用者溫/溼度已經超標了


   float  hum = dht.readHumidity(); // 取得感測器所測量的濕度值

    float  tem = dht.readTemperature(); // 取得感測器所測量的溫度值

 

    msg += ESPtype + "上的DHT11/22:\n溫度 = " + String(tem) + " 'C\n";

    msg += "濕度 = " + String(hum) + " %\n";

    String  hint="";

    if (tem >= tempLimit) 

      hint="!!! 溫度超過上限值 !!!\n";

    if (hum >= humLimit) 

      hint+="!!! 濕度超過上限值 !!!\n";

String  hint="";

    if (tem >= tempLimit) 

      hint="!!! 溫度超過上限值 !!!\n";

    if (hum >= humLimit) 

      hint+="!!! 濕度超過上限值 !!!\n";

 

// 測試目前的溫/溼度值是否超標

    if ((hum >= humLimit) || (tem >= tempLimit))

    {

      msg=hint+msg; // 將警示訊息合併到溫/溼度量測值訊息

      LINE.notify(msg);   // 若超過警戒值則發送警示訊息到對應的Line群組

    }  

 


前面的程式會在”checkTime”所設定的時間較快速的檢測周圍環境的溫溼度是否超過額定值若是則會發出警示訊息給使用者不然系統是不會傳送任何訊息的而下面的程式則會依使用者是否選擇定時發送測量資料(即「sendEnPin=0」)功能固定的在到達”sendTime”時間之後把測量到的目前環境溫/溼度與測量的日期及時間一併傳送給使用者當然這些訊息也會一起顯示在Arduino IDE的監控視窗上


    if (count >= sendTime) // 測試是否已達定時發送的時間

    {

      if (digitalRead(sendEnPin) == 0) // 測試定時發送功能是否被致能

        LINE.notify(msg); // 若是則將量測到的溫/濕度訊息發送到Line群組

// 將將量測到的溫/濕度訊息顯示在序列監控視窗上

      Serial.print("今天的日期為(年/月/日): ");

      Serial.println(currentDate);

      Serial.print("現在時間是: ");

      Serial.println(formattedTime);

      Serial.println("DHT11測量結果為");

      Serial.print("溫度 = ");

      Serial.print(tem);

      Serial.println("*C");

      Serial.print("濕度 = ");

      Serial.print(hum);

      Serial.print("%\t");

      Serial.println();

      count = 0;

    }



執行結果:

下面的畫面是系統啟動時會在Arduino IDE的監控視窗上看到的內容此時已經啟動定時發送的功能所以訊息的內容依序為🡪連線到WiFi分享器 🡪連線上後顯示系統所分配到的本地IP位址🡪 發送系統啟動訊息到使用者的Line群組上🡪 顯示定時測量到的溫/濕度值及測量日期與時間



而下面的畫面則是使用者Line群組(在此名稱為【ESPxxx_myLine】)在系統啟動後會看到的內容分別為:🡪系統發送到使用者的Line群組上的啟動訊息🡪 定時測量到的溫/濕度值及測量日期與時間


如果所測量到的溫/濕度值超過使用者所設定的上限值則會在Arduino IDE的監控視窗上看到下面的內容


如果所測量到的溫/濕度值超過使用者所設定的上限值則會在使用者的Line群組上看到下面的內容



沒有留言:

張貼留言