--- title: 自動化溫室監控系統 slideOptions: transition: slide transitionSpeed: 'fast' theme: league --- <!-- .slide: data-background="https://i.imgur.com/gJLfldF.jpg" data-background-color="#111111" data-background-opacity="0.2" --> ###### tags: `iot-greenhouse` `lab` 返回[物聯網自動化溫室監控系統](/s/PJIS24sFR76axKI0xOrnjQ) ## <span style="color:#F9BF45;">自動化溫室監控系統</span> ###### [點我開啟簡報模式](/@BEExANT-ta/H1fe1bq_F#) ###### <kbd>ESC</kbd> 鍵進入總覽模式 ###### <kbd>&#8592;</kbd> <kbd>&#8593;</kbd> <kbd>&#8595;</kbd> <kbd>&#8594;</kbd> 切換頁面 --- ## 目標 **整合前面所學的 ==感測器資料取得==、==電磁閥控制==、==遠程控制==、==排程控制==、==回報Line訊息== 這些功能,設計一套能自動化控制的溫室系統,定期發送裝置訊息至Line上,並在異常狀況發生時自動停止馬達運作且回報自定義訊息至Line上。** --- ## 設計原理 - 將三台裝置的外部網路設定為手機分享的網路。 - 將==排程控制==移轉到==溫室端==網頁執行,不同的溫室所設定的排程會有差異。 - 在溫室端中根據==感測器資料設定條件==,當觸發時發送==遠端命令請求至幫浦端==。 - 當每個溫室執行程式時,皆會發送Line訊息告知管理者。 - 由感測器觸發的控制會優先於排程控制。 - 當壓力異常時,幫浦端會關閉所有控制並拒絕所有請求,再發送異常回報的訊息告知管理者。 --- ## 範例程式碼 新增程式檔並命名 ==自動化溫室監控系統==,將以下程式碼複製貼上程式編輯區執行。 ### 溫室端(1號控制5,6號電磁閥,2號控制7,8號,每隔一定時間啟動排程) ```javascript= let id = ""; let key = ""; function postData(data) { return fetch("https://maker.ifttt.com/trigger/line/with/key/" + key, { body: "value1="+data, headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded", "Access-Control-Allow-Origin": "*", }), method: 'POST', mode: 'no-cors', }) } window.count = 0; window.finish = false; window.start = new Date("12/29/2021 08:40:00"); window.state = "初始化"; window.report_count = 0; let date = new Date(Date.now()); let time = 600 * 1000; let step = 5 * 1000; let limit_temp = 25; let limit_hum = 50; let limit_soil = 50; if(state == "初始化") { state = "排程模式"; postData(id + ": 裝置程式開始執行"); } if((DATA.air_T_output > limit_temp && DATA.air_H_output < limit_hum) || DATA.soil_H_output < limit_soil) { console.log("環境感測已達設定條件,開始各站灑水。"); postData(id + ": 環境感測已達設定條件,開始各站灑水。"+"\n空氣溫度: "+DATA.air_T_output + "\n空氣濕度: " + DATA.air_H_output + "\n土壤濕度: " + DATA.soil_H_output); valveOn_req.set(5); delay(5000); valveOff_req.set(5); delay(1000); valveOn_req.set(6); delay(5000); valveOff_req.set(6); delay(1000); console.log("環境感測灑水結束。"); postData(id + ": 環境感測灑水結束。"); delay(1000); } if(date.getTime() >= start.getTime() && !finish) { console.log("已達啟動時間,開始各站灑水。"); postData(id + ": 已達啟動時間,開始各站灑水。"); valveOn_req.set(5); delay(step); valveOff_req.set(5); delay(1000); valveOn_req.set(6); delay(step); valveOff_req.set(6); delay(1000); finish = true; count = count + 1; } if(finish) { finish = false; date = new Date(Date.now()); start = new Date(date.getTime() + time); console.log("第 "+count+" 次排程結束,下次啟動時間為:\n" + start); postData(id+ ": 第 "+count+" 次排程結束,下次啟動時間為:\n" + start); } if(report_count > 10000) { report_count = 0; postData(id + ": 定期回報裝置狀態" + "\n空氣溫度: " + DATA.air_T_output + "\n空氣濕度: " + DATA.air_H_output + "\n土壤濕度: " + DATA.soil_H_output + "\n風扇狀態: " + DATA.fan_raw); } report_count += 300; ``` ### 幫浦端 ```javascript= let id = ""; let key = ""; function postData(data) { return fetch("https://maker.ifttt.com/trigger/line/with/key/" + key, { body: "value1="+data, headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded", "Access-Control-Allow-Origin": "*", }), method: 'POST', mode: 'no-cors', }) } window.state = "初始化"; window.error_count = 0; window.request_count = 0; window.report_count = 0; let limit_pump = 1; if(state == "初始化") { state = "監控模式"; postData(id + ": 裝置程式開始執行"); } if((DATA.valve_raw[4] > 0 || DATA.valve_raw[5] > 0 || DATA.valve_raw[6] > 0 || DATA.valve_raw[7] > 0) && DATA.pump_P_output < limit_pump) { error_count += 1; } if(error_count >= 10) { state = "壓力異常"; console.log("灑水中壓力低於設定值,關閉所有電磁閥。"); postData(id + ": 灑水中壓力低於設定值,關閉所有電磁閥。"); valveOff.set(5); delay(1000); valveOff.set(6); delay(1000); valveOff.set(7); delay(1000); valveOff.set(8); delay(1000); } if(state == "壓力異常") { delay(10000); error_count = 0; request_queue = []; state = "監控模式"; console.log("恢復監控模式,嘗試重新啟動。"); postData(id + ": 恢復監控模式,嘗試重新啟動。"); } if(request_queue.length > 0 && state == "監控模式" && request_count > 1000) { let data = request_queue[0]; request_count = 0; delay(300); request_accept.set(1); postData(id + ": 接受控制請求: \n" + data); } if(report_count > 10000) { report_count = 0; postData(id + ": 定期回報裝置狀態" + "\n電磁閥1: " + DATA.valve_raw[4] + "\n電磁閥2: " + DATA.valve_raw[5] + "\n電磁閥3: " + DATA.valve_raw[6] + "\n電磁閥4: " + DATA.valve_raw[7]); } request_count += 300; report_count += 300; ``` --- ## 程式解說 逐行講解程式意義。 ---- ### 溫室端 ```javascript= let id = ""; let key = ""; function postData(data) { return fetch("https://maker.ifttt.com/trigger/line/with/key/" + key, { body: "value1="+data, headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded", "Access-Control-Allow-Origin": "*", }), method: 'POST', mode: 'no-cors', }) } ``` - 定義id作為發送Line訊息的名稱,填入網頁上的裝置名稱。 - 定義key為IFTTT上取得的key值,呼叫webhooks時會使用到。 - 定義函式postData用於發送Line訊息。 ---- ```javascript= window.count = 0; window.finish = false; window.start = new Date("12/29/2021 08:40:00"); window.state = "初始化"; window.report_count = 0; ``` - 定義全域變數count,記錄已啟動排程的次數。 - 定義全域變數finish,儲存是否完成排程的變數。 - 定義全域變數start,填入要啟動排程的時間。 - 定義全域變數state,儲存目前控制模式的變數。 - 定義全域變數report_count,用於自動回報的計時器。 ---- ```javascript= let date = new Date(Date.now()); let time = 600 * 1000; let step = 5 * 1000; let limit_temp = 25; let limit_hum = 50; let limit_soil = 50; ``` - date 為目前時間,每次迴圈執行時皆會重新取得目前時間。 - time 表示排程間的間隔時間,單位為毫秒,預設為600秒。 - step 表示排程中每一個電磁閥開啟的時間,單位為毫秒,預設為5秒。 - limit_temp 表示 空氣溫度上限,依據環境與作物不同調整,當超過這個數值時可控制灑水降溫。 - limit_hum 表示 空氣濕度下限,依據環境與作物不同調整,當低於這個數值時可控制灑水增濕。 - limit_soil 表示 土壤濕度下限,依據環境與作物不同調整,當低於這個數值時可控制灑水增濕。 ---- ```javascript= if(state == "初始化") { state = "排程模式"; postData(id + ": 裝置程式開始執行"); } ``` - 一開始執行程式state等於 初始化 時,state設為 排程模式,發送 程式開始執行的Line訊息至管理者手機。 ---- ```javascript= if((DATA.air_T_output > limit_temp && DATA.air_H_output < limit_hum) || DATA.soil_H_output < limit_soil) { console.log("環境感測已達設定條件,開始各站灑水。"); postData(id + ": 環境感測已達設定條件,開始各站灑水。"+"\n空氣溫度: "+DATA.air_T_output + "\n空氣濕度: " + DATA.air_H_output + "\n土壤濕度: " + DATA.soil_H_output); valveOn_req.set(5); delay(5000); valveOff_req.set(5); delay(1000); valveOn_req.set(6); delay(5000); valveOff_req.set(6); delay(1000); console.log("環境感測灑水結束。"); postData(id + ": 環境感測灑水結束。"); delay(1000); } ``` - 當 空氣溫度 大於 溫度上限值 且 空氣濕度 小於 濕度下限值 或 土壤濕度 小於 土壤濕度下限值時,發送 Line訊息通知啟動灑水,依序發送控制請求至幫浦端,結束後再送一次Line訊息通知灑水結束。 ---- ```javascript= if(date.getTime() >= start.getTime() && !finish) { console.log("已達啟動時間,開始各站灑水。"); postData(id + ": 已達啟動時間,開始各站灑水。"); valveOn_req.set(5); delay(step); valveOff_req.set(5); delay(1000); valveOn_req.set(6); delay(step); valveOff_req.set(6); delay(1000); finish = true; count = count + 1; } ``` - 當目前時間到達設定的啟動時間時 且 finish 為 false 時,發送Line訊息通知排程啟動,依序發送控制請求至幫浦端,結束後finish設為true表示灑水完畢,count值加1。 ---- ```javascript= if(finish) { finish = false; date = new Date(Date.now()); start = new Date(date.getTime() + time); console.log("第 "+count+" 次排程結束,下次啟動時間為:\n" + start); postData(id+ ": 第 "+count+" 次排程結束,下次啟動時間為:\n" + start); } ``` - 當finish為true時,將finish設為false,重新設定date取得目前時間,重新設定啟動時間為目前時間加上排程間隔時間time,發送Line訊息通知 排程結束及下次啟動時間。 ---- ```javascript= if(report_count > 10000) { report_count = 0; postData(id + ": 定期回報裝置狀態" + "\n空氣溫度: " + DATA.air_T_output + "\n空氣濕度: " + DATA.air_H_output + "\n土壤濕度: " + DATA.soil_H_output + "\n風扇狀態: " + DATA.fan_raw); } ``` - 當報告計時器report_count大於10000(10秒)時,重設為0,發送Line訊息通知 裝置目前狀態訊息。 ---- ```javascript= report_count += 300; ``` - 由於每次迴圈結束後300毫秒進入下次迴圈,故每次迴圈結束後累加300至report_count中。 ---- ### 幫浦端 ```javascript= let id = ""; let key = ""; function postData(data) { return fetch("https://maker.ifttt.com/trigger/line/with/key/" + key, { body: "value1="+data, headers: new Headers({ "Content-Type": "application/x-www-form-urlencoded", "Access-Control-Allow-Origin": "*", }), method: 'POST', mode: 'no-cors', }) } ``` - 同溫室端解釋。 ---- ```javascript= window.state = "初始化"; window.error_count = 0; window.request_count = 0; window.report_count = 0; ``` - 定義全域變數state,儲存目前控制模式的變數。 - 定義全域變數error_count,用於記錄壓力異常次數的變數。 - 定義全域變數request_count,用於記錄接受請求的間隔時間。 - 定義全域變數report_count,用於自動回報的計時器。 ---- ```javascript= let limit_pump = 1; ``` - limit_pump 為 壓力感測的下限值,單位為 $kg/cm^2$。 ---- ```javascript= if(state == "初始化") { state = "監控模式"; postData(id + ": 裝置程式開始執行"); } ``` - 同溫室端解釋。 ---- ```javascript= if((DATA.valve_raw[4] > 0 || DATA.valve_raw[5] > 0 || DATA.valve_raw[6] > 0 || DATA.valve_raw[7] > 0) && DATA.pump_P_output < limit_pump) { error_count += 1; } ``` - 判斷 DATA.valve_raw[] 電磁閥是否是啟動的狀態,且 壓力感測值 小於 壓力下限值時,表示可能管線破裂造成壓力低於正常啟動時的值,異常計數error_count加1。 ---- ```javascript= if(error_count >= 10) { state = "壓力異常"; console.log("灑水中壓力低於設定值,關閉所有電磁閥。"); postData(id + ": 灑水中壓力低於設定值,關閉所有電磁閥。"); valveOff.set(5); delay(1000); valveOff.set(6); delay(1000); valveOff.set(7); delay(1000); valveOff.set(8); delay(1000); } ``` - 當error_count大於等於10時,表示啟動後過一段時間壓力還是沒在設定的範圍內,將state設為 壓力異常,發送Line訊息通知 壓力異常,並關閉啟動中的電磁閥與馬達。 ---- ```javascript= if(state == "壓力異常") { delay(10000); error_count = 0; request_queue = []; state = "監控模式"; console.log("恢復監控模式,嘗試重新啟動。"); postData(id + ": 恢復監控模式,嘗試重新啟動。"); } ``` - 當 state 為 壓力異常 時,延遲一段時間(10秒)後,重設error_count為0,清空請求清單,將state 設回 監控模式,發送Line訊息通知 嘗試重新啟動。 ---- ```javascript= if(request_queue.length > 0 && state == "監控模式" && request_count > 1000) { let data = request_queue[0]; request_count = 0; delay(300); request_accept.set(1); postData(id + ": 接受控制請求: \n" + data); } ``` - 當請求清單request_queue內有請求 且 state為監控模式 且 request_count已累積超過1秒時,接受請求並執行對應命令,發送Line訊息通知 接受控制請求。 ---- ```javascript= if(report_count > 10000) { report_count = 0; postData(id + ": 定期回報裝置狀態" + "\n電磁閥1: " + DATA.valve_raw[4] + "\n電磁閥2: " + DATA.valve_raw[5] + "\n電磁閥3: " + DATA.valve_raw[6] + "\n電磁閥4: " + DATA.valve_raw[7]); } ``` - 同溫室端解釋。 ---- ```javascript= request_count += 300; report_count += 300; ``` - 同溫室端解釋。 --- ## 參數修改 為方便實作,以下會將範例程式中可修改的參數標示出來,進行實作時只需修改對應參數,並觀察結果即可。 :::warning :zap: 詳細內建JS參數參考 - [內建Js參數及功能總覽](/s/wlfjvQBzRPCmJ8LCL3f2Fg) ::: ---- :::success **let id = ""; let key = "";** ::: - id 填入目前網頁中的裝置名稱。 - key 填入從IFTTT中webhooks得到的key值。 ---- :::success **window.start = new Date("12/29/2021 08:40:00");** ::: - 表示啟動排程的時間,可修改""中的時間。 ---- :::success **let time = 600 * 1000; let step = 5 * 1000;** ::: - time 表示排程間的間隔時間,單位為毫秒。 - step 表示排程中每一個電磁閥開啟的時間,單位為毫秒。 ---- :::success **let limit_temp = 25; let limit_hum = 50; let limit_soil = 50;** ::: - limit_temp 表示 空氣溫度上限,依據環境與作物不同調整,當超過這個數值時可控制灑水降溫。 - limit_hum 表示 空氣濕度下限,依據環境與作物不同調整,當低於這個數值時可控制灑水增濕。 - limit_soil 表示 土壤濕度下限,依據環境與作物不同調整,當低於這個數值時可控制灑水增濕。 ---- :::success **if(report_count > 10000)** ::: - 表示每10秒發送一次目前裝置狀態的訊息,可修改秒數。 ---- :::success **let limit_pump = 1;** ::: - limit_pump 為 壓力感測的下限值,單位為 $kg/cm^2$。 ---- :::success **if(error_count >= 10)** ::: - 表示累積10次以上異常狀態時的狀況,可修改次數。 --- ## 範例影片 {%youtube CBhpgMde8MY %} <a class="btn btn-warning" style="width:100%;color:#333333;" href="/s/PJIS24sFR76axKI0xOrnjQ" role="button"> 物聯網自動化溫室監控系統 **&#8680;** </a> <a class="btn btn-primary" style="width:100%;" href="/s/plbQC05nQROOJRu_QgnIfw" role="button"> **&#8678;** Line訊息異常通報 </a>