---
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>←</kbd> <kbd>↑</kbd> <kbd>↓</kbd> <kbd>→</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"> 物聯網自動化溫室監控系統 **⇨** </a>
<a class="btn btn-primary" style="width:100%;" href="/s/plbQC05nQROOJRu_QgnIfw" role="button"> **⇦** Line訊息異常通報
</a>