2017年6月21日 星期三

MicroPython on ESP8266 (十三) : DHT11 溫溼度感測器測試

月初做完 MicroPython 的 PIR 移動偵測實驗後, 我的魔爪就伸向了 DHT11 溫溼度感測器. 但因為這實驗的進階版是要把溫溼度資料上傳到物聯網伺服器如 ThingSpeak 或 Xively 等, 必須要用到 HTTP 應用層協定, 所以我將進行一半的 DHT11 實驗暫停, 轉而先測試 MicroPython 的網路功能, 花了三周先後測試了 socket, urllib.request, 以及 requests 模組, 主要聚焦在 HTTP 的 GET 與 POST 方法測試. 而現在正是回過頭來把 DHT11 物聯網實驗做完的時候了.

DHT11 + ThingSpeak 是物聯網實驗典型必做項目, 只要一個 DHT11 模組即可架構一個小小氣象測候站. 參考我之前在 Arduino 上所做的測試紀錄 :

# Arduino 溫濕度感測器 DHT11 測試
# 以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗

現在則是要把同樣的試驗移植到我的新歡 ~ MicroPython + ESP8266 上面, 這是因為 ESP8266 就是一個內建 WiFi 網路功能的 32 位元 MPU, 比起 8 位元的 Arduino 而言功能與效能強多了, 加上 MicroPython 與檔案系統的加持, 不僅使整個系統最小化, Python 程式可隨時改隨時上傳, 不需要像 Arduino 每次都要覆寫韌體, 操作介面與環境方便多了.

本系列之前的測試紀錄參考 :

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 on ESP8266 (十) : socket 模組測試
MicroPython on ESP8266 (十一) : urllib.urequest 模組測試
# MicroPython on ESP8266 (十二) : urequests 模組測試

DHT11 模組有四隻腳, 包含電源 Vcc 與 GND, 數據輸出 Data 以及一隻用不到的腳 (在 I2C 模式才會用到). 電源 Vcc 可吃 3~5V, 因此吃 3.3V 的 ESP8266 可直用 (Data 腳可直連 GPIO 腳), 其接腳定義如下 :
為了使測量數據穩定, 最好在 Vcc 與 Data 腳之間並接一個 10K 歐姆電阻 (棕黑橙); 在 Vcc 與 GND 之間並接一個 0.1uF 電容 (104).

在 MicroPython 上使用 DHT11 很方便, 因為其功能已經被寫進 MicroPython 的內建模組 dht 了, 參考 :

12. Temperature and Humidity

首先匯入 dht 模組與 Pin 模組 (綁定哪一支 GPIO 腳擷取 DHT11 輸出之數據) :

import dht
from machine import Pin

接著就可以呼叫 dht 模組的 DHT 類別建構式來建立 DHT11 物件, 傳入要綁定之 GPIO 腳之 Pin 物件 :

d=dht.DHT11(Pin(0))    #綁定 GPIO 0 為 DHT11 數據輸入腳

不過在讀取溫濕度數據前必須先呼叫 DHT11 物件的 measure() 方法來擷取測量結果, 這樣才能讀取到最新的數據 :

d.measure()    #更新測量結果

這樣就可以讀取溫溼度數據了 :

t=d.temperature()     #傳回攝氏溫度
h=d.humidity()         #傳回相對溼度 (%)

注意, DHT11 的測量頻率是至多每秒一次, 亦即 measure() 方法每秒只能呼叫一次 :

"The DHT11 can be called no more than once per second and the DHT22 once every two seconds for most accurate results."

下面程式以每兩秒一次頻率更新所測量到之溫溼度 :

測試 1 : 連續測量溫溼度

from machine import Pin
import dht    
import time

p0=Pin(0, Pin.IN)
d=dht.DHT11(p0)        #建立 DHT11 物件

while True:
    d.measure()                  #重新測量溫溼度
    t=d.temperature()        #讀取攝氏溫度
    h=d.humidity()             #讀取相對溼度
    print('Temperature=', t, 'C', 'Humidity=', h, '%')
    time.sleep(2)                  #暫停 2 秒

將檔案存成 main.py 後用 ampy 等工具上傳 ESP8266 模組, 參考 :

MicroPython on ESP8266 (六) : 檔案系統測試

然後開啟 Putty 按 Ctrl+D 軟開機即可看到每兩秒輸出的溫溼度測量結果 :


用吹風機吹一下, 就會看到濕度下降, 溫度上升的變化了.

接下來我想把溫濕度測量資料透過 WiFi 網路記錄在物聯網網站 ThingSpeak, 參考以前 Arduino 的測試紀錄 :

ESP8266 WiFi 模組與 Arduino 連線測試 (ThingSpeak 初體驗)
以 Arduino+ESP8266 物聯網模組重作 DHT11 溫溼度實驗

我的 ThingSpeak 帳號已經設定好如下五個資料通道 (channels) 了 :


這裡會用到 field1 (攝氏溫度), field1 (華氏溫度) 與 field3 (濕度) 這三個通道, 由於 MicroPython 的 dht 模組的 DHT11.temperature() 僅提供攝氏溫度, 所以如果要記錄華氏溫度於 filed2 的話須另外用公式轉換, 換算公式如下 :

F=C*9/5 + 32

亦即華氏度數是攝氏的 9/5 倍再加 32.

物聯網伺服器 ThingSpeak 通道的 HTTP GET 寫入格式例如 :

GET /update?api_key=NO5N8C7T2KINFCQE&field1=29.00&field2=84.20&field3=73.00

亦即是利用 HTTP 的 GET 方法傳遞溫濕度資料給 ThingSpeak 伺服器的 update 程式執行資料庫寫入動作, 這裡 api_key 是我在 ThingSpeak 上一個帳號的的資料庫寫入認證碼 (讀取認證碼是另外一個, 用於向 ThingSpeak 讀取資料用).

此處我們要用 urequests 模組的 get() 方法向 ThingSpeak 伺服器提出 GET 請求, 必須將 url 傳進 get() 裡面, 因此必須知道 ThingSpeak 伺服器的 IP 或網域名稱, 可以使用 http://184.106.153.149 或 http://api.thingspeak.com, 這樣就可以組成 URL 字串了 :

host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'
url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
r=urequests.get(url)

這裡 t, f, h 就是上面測試 1 中取得的攝氏溫度, 華氏溫度, 以及濕度.

完整的程式如下列測試 2 :

測試 2 : 將溫濕度記錄在 ThingSpeak 網站

#main.py
ffrom machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
d=dht.DHT11(p0)
host='http://api.thingspeak.com'      
api_key='NO5N8C7T2KINFCQE'  

while True:
    d.measure()                
    t=d.temperature()      
    f=round(t * 9/5 + 32)
    h=d.humidity()
    url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)  
    print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
    #print('url=', url)  
    r=urequests.get(url)  
    print('response=', r.text)
    time.sleep(16)  

注意, 這裡迴圈裡的延遲改為 16 秒是為了符合 ThingSpeak 的要求, 即每個通道的資料更新週期必須大於 15 秒, 參考 :

Data points and frequency

"there's no limit to the number of data points, but a Channel can only be updated every 15s"

將此程式存成 main.py 後用 ampy 上傳到 ESP8266 模組 :

D:\test>ampy --port COM4 put main.py

然後開啟 Putty, 按 Ctrl+D 軟啟動後就會重新執行剛上傳的 main.py.

REPL 介面輸出訊息如下 :

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.
>>>
PYB: soft reboot
#22 ets_task(40100164, 3, 3fff829c, 4)
Temperature= 31 C / 88 F Humidity= 38 %
response= 29884
Temperature= 31 C / 88 F Humidity= 37 %
response= 29885
Temperature= 31 C / 88 F Humidity= 37 %
response= 29886
Temperature= 31 C / 88 F Humidity= 37 %
response= 29887
Temperature= 31 C / 88 F Humidity= 37 %
response= 29888
Temperature= 31 C / 88 F Humidity= 43 %
response= 29889
Temperature= 31 C / 88 F Humidity= 37 %
response= 29890
Temperature= 31 C / 88 F Humidity= 37 %
response= 29891
Temperature= 31 C / 88 F Humidity= 37 %
response= 29892

這裡 response 為伺服器新增一筆紀錄後所傳回的該筆資料之 key, 可見已經儲存近 3 萬筆資料了. ThingSpeak 網頁上的溫濕度動態圖表如下 :


接下來為了在電腦關機時能知道整個程式還有在跑沒當掉, 我在 GPIO 2 腳上接了一個 220 歐姆電阻串接一個 LED 後接地, 然後參考下列這篇 :

MicroPython on ESP8266 (九) : PIR 紅外線移動偵測

我定義了一個 LED_blink() 函數的進階版, 可以傳一個設定 LED 要持續閃爍幾秒之參數進去, 用 LED_blink(16) 取代等待 ThingSpeak 更新週期限制的 time.sleep(16), 亦即同樣是等待 16 秒, 但現在等待期間是由閃爍 LED 來代替, 如下列測試 3 所示 :

測試 3 : 等待 ThingSpeak 更新週期時閃爍 LED

from machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
p2=Pin(2, Pin.OUT)    
d=dht.DHT11(p0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'

def LED_blink(s):    
    for i in range(1,10*s):    
        p2.value(1)   
        time.sleep_ms(50)        
        p2.value(0)
        time.sleep_ms(50)    

while True:
    d.measure()                
    t=d.temperature()      
    f=round(t * 9/5 + 32)
    h=d.humidity()
    url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
    print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
    try:
        r=urequests.get(url)
        print('response=', r.text)
    except:
        print('urequests.get() exception occurred!')
    LED_blink(16)

這裡將 urequests.get() 放在 try except 裡面是因為網路存取跟檔案存取一樣可能會出現例外, 當例外出現時將使整個程式停止執行. 為了避免一次 GET 要求失敗就讓程式垮掉, 務必將其放在 try except 裡面偵錯.

實際測試發現在量測溫濕度並上傳 ThingSpeak 伺服器時 LED 會停止閃爍約 1 秒, 傳完又繼續快速閃爍 16 秒. 這樣即使電腦關機無法從 REPL 或 ThingSpeak 網站觀察系統是否正常運作, 看到 LED 閃爍即知 ESP8266 還在正常運作.

如果要在手機上觀看 ThingSpeak 資料, 可以到 Google Play 下載 ThingView 這個 App :


開啟 App 後按底下的 + ㄒ新增頻道 (Channel) :



輸入 ThingSpeak 的 Channel ID, 就會顯示我的 Atmosphere 通道了.


點擊 Atmosphere 通道就會顯示此通道的各 field 圖表. 不過此多欄位圖形不會自動更新, 必須按右下角藍色 Refresh 鍵才會更新資料 (只有顯示單一欄位時才會自動更新).


點擊任何一張 field 的圖即顯示單一 field :


同樣地, 按右下角藍色 Refresh 鍵會更新此欄位資料 (但也不是很靈光), 但也可以在 Settings 中設定自動更新週期 , 勾選 "Auto refresh charts", 點 "Auto refresh time" 可設定週期 :

這個 DHT11 實驗延宕了三周了, 昨晚因業務需要去加班, 早上回來補眠卻睡不著, 乾脆就一口氣把 ThingSpeak 實驗做完了, 使用 ESP8266+MicroPython 真是簡潔啊! 難怪之前有網友說我用 Arduino+ESP8266 是 "見樹不見林", 何不單用 ESP8266+NodeMCU? 我覺得他說的是有道理的. 雖然以後不太可能這麼用, 不過那時我的目的是要藉此學習 Arduino 的各種可能罷了. 人生也是如此, 有時無用是為大用. 

參考 :

# Interfacing the DHT11/DHT22 Humidity/Temperature sensor
update data to thingspeak
MicroPython on ESP8266: sending data to ThingSpeak
5.1. Star Wars Asciimation
https://www.slideshare.net/ParshwadeepLahane/remote-temperature-monitor-dht11
micropython socket example
# Controlling relays using Micropython and an ESP8266

2017-06-24 補充 :

上面測試 3 的程式跑了一天後突然停住了, 觀察 REPL 介面, 發現是在執行 main.py 的第 20 行時發生例外, 導致程式跳掉 :

response= 37003
Temperature= 30 C / 86 F Humidity= 50 %
response= 37004
Traceback (most recent call last):
  File "main.py", line 20, in <module>
  File "dht.py", line 13, in measure
OSError: [Errno 110] ETIMEDOUT

MicroPython v1.9.1-8-g7213e78d on 2017-06-12; ESP module with ESP8266
Type "help()" for more information.

測試 3 的第 20 行是 d.measure() 指令, 可見是測量 DHT11 的指令因 timeout 而跳掉了. 因此應該把 try 的範圍擴大到 d.measure() 才行, 如下列測試 4 :

from machine import Pin
import dht  
import time
import urequests

p0=Pin(0, Pin.IN)
p2=Pin(2, Pin.OUT)
d=dht.DHT11(p0)
host='http://api.thingspeak.com'
api_key='NO5N8C7T2KINFCQE'

def LED_blink(s):
    for i in range(1,10*s):
        p2.value(1)
        time.sleep_ms(50)  
        p2.value(0)
        time.sleep_ms(50)

while True:
    try:
        d.measure()                  
        t=d.temperature()        
        f=round(t * 9/5 + 32)   
        h=d.humidity() 
        url='%s/update?api_key=%s&field1=%s&field2=%s&field3=%s' %(host, api_key, t, f, h)
        print('Temperature=', t, 'C', '/', f, 'F', 'Humidity=', h, '%')
        r=urequests.get(url)
        print('response=', r.text)
    except:
        print('urequests.get() exception occurred!')  
    LED_blink(16)

其實應該用 WDT (watchdog timer) 來監控系統是否有正常執行才對, 應用程式必須在時限內通知 WDT, 否則 WDT 會認為程式已崩潰而 Reset 系統.

MicroPython 的 machine 模組有支援 WDT, 參考 :

# class WDT – watchdog timer
http://docs.micropython.org/en/latest/micropython-esp8266.pdf  (搜尋 WDT)

但此類別底下註明 "Availability of this class: pyboard, WiPy.", 似乎不支援 ESP-01, 我實際測試雖然可建立 WDT 實體, 但卻無法設定 timeout, 目前在 ESP-01 確實無作用 :

>>> from machine import WDT
>>> dir(WDT)                               #顯示 WDT 型別的方法
['feed', 'deinit']                                               #實作 feed() 與 deinit() 方法
>>> wdt=WDT(timeout=5000)     #無法設定 timeout
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function does not take keyword arguments     #不需參數?
>>> wdt=WDT(id=-1,timeout=2000)    #無法設定 timeout
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: function does not take keyword arguments
>>> wdt=WDT()                            #無參數就可以順利建立 WDT 物件
>>> wdt
<WDT>

參考 :

ESP8266: Watchdog functions
https://forum.micropython.org/viewtopic.php?f=16&t=2788&p=16576
https://www.tapatalk.com/topic/182447-micropython-forum/3221-watchdog-in-esp8266

沒有留言 :