這實驗可以說是對過去四個月學習 MicroPython on ESP8266 的小結, 綜合光敏電阻的亮度測量, DHT11 的溫濕度測量, 以及 PIR 紅外線移動偵測控制照明, 然後將測量資料透過網路傳送到 ThingSpeak 物聯網網站記錄起來.
供電來源為今年初在舊豬舍廁所屋頂裝置的 20W 小型太陽能板, 經過充電控制器向一顆湯淺 7 AH 蓄電池充電, 充電控制器輸出端經過一個降壓模組將 12~13V 直流轉成 5V 電源. 主控裝置是 ESP-12E 開發板, 所需模組與零組件列之如下 :
- esp-12 開發板/D1 Mini 開發板/NodeMCU 開發板
- DHT11 溫溼度感測器
- CdS 光敏電阻與 10K 電阻
- PIR 紅外線移動偵測模組 HC-SR501
- 1 路繼電器
- 12V-5V 降壓模組
- 12V LED 照明燈
- 小型麵包板
- 杜邦線
但如果使用 WeMOS D1 Mini 開發板的話就不需要 220+100 歐姆的分壓電路, 直接將 CdS+10K 歐姆的分壓電路接到 3.3V 電源即可, 因為 D1 Mini 的 ADC 腳飽和電壓是 3.3V 而非 1V.
另外, 由於繼電器是 5V 驅動的, ESP8266 的 GPIO 是 3.3V, 無法驅動繼電器, 必須使用位準轉換器將 3.3V 轉成 5, 一個通道不需要用到模組, 因為模組通常是 4 或 8 個通道, 其他通道用不到就浪費了. 參考下列這篇, 只需要一個 MOSFET 2N7000 與兩個 4.7K 歐姆電阻即可做出單一通道的雙向位準轉換器 :
# 用 MOFFET 2N7000 做 5V 與 3.3V 位準轉換
本實驗主要參考下面一系列測試紀錄中底色為黃色的文章 :
# MicroPython on ESP8266 (一) : 燒錄韌體
# MicroPython on ESP8266 (二) : 數值型別測試
# MicroPython on ESP8266 (三) : 序列型別測試
# MicroPython on ESP8266 (四) : 字典與集合型別測試
# MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試
# MicroPython on ESP8266 (六) : 檔案系統測試
# MicroPython on ESP8266 (七) : 時間日期測試
# MicroPython on ESP8266 (八) : GPIO 測試
# MicroPython on ESP8266 (九) : PIR 紅外線移動偵測
# MicroPython v1.9.1 版韌體測試
# MicroPython on ESP8266 (十) : socket 模組測試
# MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
# MicroPython on ESP8266 (十二) : urequests 模組測試
# MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試
# MicroPython 使用 ampy 突然無法上傳檔案問題
# MicroPython on ESP8266 (十四) : 網頁伺服器測試
# WeMOS D1 Mini 開發板測試
# MicroPython on ESP8266 (十五) : 光敏電阻與 ADC 測試
# MicroPython on ESP8266 (十六) : 蜂鳴器測試
# MicroPython on ESP8266 (十七) : 液晶顯示器 1602A 測試
# MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試
# MicroPython v1.9.2 版釋出
MicroPython 文件參考 :
# MicroPython tutorial for ESP8266 (官方教學)
# http://docs.micropython.org/en/latest/micropython-esp8266.pdf
# http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
# http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket
# https://gist.github.com/xyb/9a4c8d7fba92e6e3761a (驅動程式)
from machine import Pin,ADC
import dht
import time
import urequests
PIRPIN=Pin(4, Pin.IN)
RELAYPIN=Pin(5, Pin.OUT, Pin.PULL_UP)
RELAYPIN.value(1)
LAMP_state=0
triggerCount=0
triggerLimit=5
def IRQ(p):
global state
global triggerCount
global triggerLimit
global LAMP_state
LAMP_state=1
RELAYPIN.value(0)
if triggerCount < triggerLimit:
triggerCount=triggerCount + 1
print(p,"IRQ Triggered! Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
PIRPIN.irq(trigger=Pin.IRQ_RISING, handler=IRQ)
def LED_blink(pin,s):
for i in range(1,s):
pin.value(1)
time.sleep_ms(500)
pin.value(0)
time.sleep_ms(500)
DHTPIN=Pin(0, Pin.IN)
LEDPIN=Pin(2, Pin.OUT)
d=dht.DHT11(DHTPIN)
adc=ADC(0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'
while True:
if LAMP_state==1:
RELAYPIN.value(0)
triggerCount=triggerCount - 1
if triggerCount <= 0:
LAMP_state=0
triggerCount=0
else:
RELAYPIN.value(1)
print("IRQ Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
try:
d.measure()
t=d.temperature()
f=round(t * 9/5 + 32)
h=d.humidity()
a=adc.read()
a=int(0.3*a + 0.7*adc.read())
a=round(a*100/1024)
url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s&field4=%s&field5=%d' %(host, api_key, t, f, h, a, triggerCount)
print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%', 'Luminance=', a, '%')
r=urequests.get(url)
print('response=', r.text)
except:
print('urequests.get() exception occurred!')
LED_blink(LEDPIN,20)
此程式中每個迴圈改為 20 秒 (加上前面的處理時間大約 21 秒), 所以每分鐘約傳送 5 次資料給 ThingSpeak, 每小時 300 次, 每天傳送 7200 次. 由於迴圈時間約 20 秒, 因此在 PIR 觸發中斷時須馬上點亮 LED 燈, 否則最糟情況是在迴圈一開始時觸發, 還要等 20 秒才點亮 LED 燈, 這樣就不實用了. 此外, 由於這次使用的繼電器是 LOW 觸發, 所以點亮燈須對 GPIO 輸出 0 : RELAYPIN.value(0); 反之熄滅是輸出 1 : RELAYPIN.value(1), 與一般邏輯剛好相反.
由於 triggerLimit 設為 5, 因此碰頂後若 PIR 未再偵測到人體移動, 理論上經過 5 次迴圈後, triggerCount 就會歸零, 使得 LED 燈熄滅, 大約延時 5*20=100 分鐘, 即一分半鐘內未再觸發就會熄燈, 欲延長時間可加大 triggerLimit 設定值.
上面測試 1 沒有考慮時間因素, 亦即不論白天黑夜 LED 照明燈都會在感應到有人經過時點亮, 其實白天並不需要, 這樣會浪費蓄電池的電量. 在下面的測試 2 裡, 我加上了時間控制功能, 利用 NTP 伺服器取得現在本地的時間, 如果是傍晚 6 點後或者早上 6 點之前, 偵測到有人經過才會點亮 LED 照明燈, 其餘時間不會亮燈, 但 PIR 觸發次數仍然會傳送到 ThingSpeak 伺服器 :
測試 2 : 太陽能測候站 + 使用 PIR 延時控制 LED 照明燈 (指定時段)
from machine import Pin,ADC
import dht
import time
import urequests
import ntptime
LAMP_enabled=False
def fill_zero(n):
if n<10:
return '0' + str(n)
else:
return str(n)
PIRPIN=Pin(4, Pin.IN)
RELAYPIN=Pin(5, Pin.OUT, Pin.PULL_UP)
RELAYPIN.value(1)
LAMP_state=0
triggerCount=0
triggerLimit=5
def IRQ(p):
global state
global triggerCount
global triggerLimit
global LAMP_state
global LAMP_enabled
LAMP_state=1
if LAMP_enabled:
RELAYPIN.value(0)
if triggerCount < triggerLimit:
triggerCount=triggerCount + 1
print(p,"IRQ Triggered! Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
PIRPIN.irq(trigger=Pin.IRQ_RISING, handler=IRQ)
def LED_blink(pin,s):
for i in range(1,s):
pin.value(1)
time.sleep_ms(500)
pin.value(0)
time.sleep_ms(500)
DHTPIN=Pin(0, Pin.IN)
LEDPIN=Pin(2, Pin.OUT)
d=dht.DHT11(DHTPIN)
adc=ADC(0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'
try:
ntptime.settime()
except:
pass
while True:
utc_epoch=time.mktime(time.localtime())
Y,M,D,H,m,S,ms,W=time.localtime(utc_epoch + 28800)
t='%s-%s-%s %s:%s:%s' % (str(Y),fill_zero(M),fill_zero(D),\
fill_zero(H),fill_zero(m),fill_zero(S))
print(t)
if m==0:
try:
ntptime.settime()
except:
pass
if H > 18 or H < 6:
LAMP_enabled=True
else:
LAMP_enabled=False
print('LAMP_enabled:',LAMP_enabled)
if LAMP_state==1:
if LAMP_enabled:
RELAYPIN.value(0)
triggerCount=triggerCount - 1
if triggerCount <= 0:
LAMP_state=0
triggerCount=0
else:
RELAYPIN.value(1)
print("IRQ Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
try:
d.measure()
t=d.temperature()
f=round(t * 9/5 + 32)
h=d.humidity()
a=adc.read()
a=int(0.3*a + 0.7*adc.read())
a=round(a*100/1024)
url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s&field4=%s&field5=%d' %(host, api_key, t, f, h, a, triggerCount)
print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%', 'Luminance=', a, '%')
r=urequests.get(url)
print('response=', r.text)
except:
print('urequests.get() exception occurred!')
LED_blink(LEDPIN,20)
此程式中我增加了一個全域變數 LAMP_enabled 來表示是否為 LED 可點亮之狀態, True 表示傍晚六點至早上六點時段, 主迴圈中從 NTP 取得之時間資訊中擷取現在是幾點 (H) 幾分 (m), 其中的 H 用來更新 LAMP_enabled 的狀態, 然後依此狀態判別於 LAMP_state 為 1 時是否要點亮 LED 燈; 而 m 用來在整點時與 NTP 同步一次, 以免 ESP8266 內部時鐘久了之後產生時間誤差. 這個做法上回測試 1602 與 SSD1360 時有用到, 參考 :
# MicroPython on ESP8266 (十七) : 液晶顯示器 1602A 測試
# MicroPython on ESP8266 (十八) : SSD1306 液晶顯示器測試
另外, 當 PIR 偵測到有人移動時, 在 IRQ() 函數中也是要先判斷 LAMP_enabled 是否為 True, 是的話才點亮 LED.
最後還有一個考量是陰雨天時天色較暗, 雖然不是在指定的 PM 06:00 ~ AM 06:00 的亮燈時段, 有人經過時也應該讓 LED 點亮才對, 經測試亮度變數 a 若低於 15 時通常天色已夠暗, 為此我又增加了一個全域變數 Luminance 來記錄最近的亮度值, 程式碼修改如下 :
測試 3 : 太陽能測候站 + 使用 PIR 延時控制 LED 照明燈 (指定時段 + 亮度控制)
from machine import Pin,ADC
import dht
import time
import urequests
import ntptime
LAMP_enabled=False
Luminance=0
def fill_zero(n):
if n<10:
return '0' + str(n)
else:
return str(n)
PIRPIN=Pin(4, Pin.IN)
RELAYPIN=Pin(5, Pin.OUT, Pin.PULL_UP)
RELAYPIN.value(1)
LAMP_state=0
triggerCount=0
triggerLimit=5
def IRQ(p):
global state
global triggerCount
global triggerLimit
global LAMP_state
global LAMP_enabled
LAMP_state=1
if LAMP_enabled:
RELAYPIN.value(0)
if triggerCount < triggerLimit:
triggerCount=triggerCount + 1
print(p,"IRQ Triggered! Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
PIRPIN.irq(trigger=Pin.IRQ_RISING, handler=IRQ)
def LED_blink(pin,s):
for i in range(1,s):
pin.value(1)
time.sleep_ms(500)
pin.value(0)
time.sleep_ms(500)
DHTPIN=Pin(0, Pin.IN)
LEDPIN=Pin(2, Pin.OUT)
d=dht.DHT11(DHTPIN)
adc=ADC(0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'
try:
ntptime.settime()
except:
pass
while True:
utc_epoch=time.mktime(time.localtime())
Y,M,D,H,m,S,ms,W=time.localtime(utc_epoch + 28800)
t='%s-%s-%s %s:%s:%s' % (str(Y),fill_zero(M),fill_zero(D),\
fill_zero(H),fill_zero(m),fill_zero(S))
print(t)
if m==0:
try:
ntptime.settime()
except:
pass
if H > 18 or H < 6 or Luminance < 15:
LAMP_enabled=True
else:
LAMP_enabled=False
print('LAMP_enabled:',LAMP_enabled)
if LAMP_state==1:
if LAMP_enabled:
RELAYPIN.value(0)
triggerCount=triggerCount - 1
if triggerCount <= 0:
LAMP_state=0
triggerCount=0
else:
RELAYPIN.value(1)
print("IRQ Trigger Count:",triggerCount,'LAMP State:',LAMP_state)
try:
d.measure()
t=d.temperature()
f=round(t * 9/5 + 32)
h=d.humidity()
a=adc.read()
a=int(0.3*a + 0.7*adc.read())
a=round(a*100/1024)
Luminance=a
url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s&field4=%s&field5=%d' %(host, api_key, t, f, h, a, triggerCount)
print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%', 'Luminance=', a, '%')
r=urequests.get(url)
print('response=', r.text)
except:
print('urequests.get() exception occurred!')
LED_blink(LEDPIN,20)
這裡要注意的是, 在指定時段 PM 06:00 ~ AM 06:00 之外, 當亮度低於 15% 時, 若 PIR 偵測到有人移動不會馬上亮燈, 而是要等 20 秒後進入下一個迴圈時才會亮, 這是因為受到要將資料紀錄到 ThingSpeak, 必須遵守更新週期大於 15 秒之限制 (此處取整數 20 秒), 使用 LED_blink() 來達成之故. 如果不需要紀錄到 ThingSpeak, 則可以將迴圈週期拉低到 1 秒, 因為 DHT11 的讀取頻率最高為 1Hz, 這樣亮度低於 15% 時就會迅速反應了. 參考 :
# Using the DHT11/DHT22 Temperature/Humidity Sensor with a FRDM Board
呵呵, 拖了快一個月的作業就這樣大功告成了.
參考 :
# Controlling relays using Micropython and an ESP8266 (網頁伺服器)
沒有留言 :
張貼留言