第 08 章 - 取得感測器數據與 python 簡易繪圖
上次更新日期 2022/11/05
前一章我們使用了光敏電阻,然後使用 gpiozero 取得光敏電阻的資料後,判斷光線是否明亮, 然後在透過其他設備,點亮或關閉燈光,這就是一種感測器的應用。在這個章節中,我們會透過樹莓派, 取得簡單的溫溼度資料後,加以一些簡單的繪圖應用功能,並發送到網頁上,讓大家可以直接看到相關的圖示。
學習目標:
- 了解如何偵測 DHT11 溫溼度計
- 了解 python 繪製簡易圖示 (time series plot)
- 結合溫溼度用於網頁圖示輸出
- 8.1: 使用溫溼度計 DHT11 為例
- 8.2: 使用 python 的 Matplotlib 做簡易繪圖
- 8.3: 以溫溼度計資料繪製時間序列圖示 (time series plot)
- 8.4: 當週實做
8.1: 使用溫溼度計 DHT11 為例
溫溼度感測器品牌眾多,不過,目前使用度較廣的,應該是 DHT 這個品牌,這個感測器主要有兩種形式, 包括簡易型的 DHT11 以及比較複雜的 DHT22,詳細的圖示請參考文末的參考資料。為了簡化數據資料的處理, DHT11 通常會加上背板,設計成為只有 3 隻腳的形式,分別是 5V 電力、接地與資料輸出 3 個腳位~不過, 每種背板的配置不太一樣,相對的針腳請自行參考你手邊的 DHT 產品喔!在我們提供的小元件,中間腳位是資料輸出, 兩邊則是 5V(+) 供電及接地(-) 喔!
- 開始連接到感測器
由於 DHT11 使用 5V 電力,透過樹莓派的 pinout 指令,我們找到樹莓派 J8:4, J8:6, J8:8 這三個腳位來連接! 連接方式很簡單,請先將 DHT11 感測器三隻腳安插到不同群組的麵包板上面,然後將上述的三個樹莓派的針腳與 DHT11 對應, 連接之後就等待後續程式的處理了。
- 取得 python 支援模組
DHT11 感測器官方有提供支援 python 的讀取模組,只是我們需要手動安裝起來才行!在樹莓派底下,安裝方式很簡單:
$ sudo pip install Adafruit-DHT
這樣就直接安裝到 python 模組中了!接下來,請參考文末的 Adafruit Github 說明文件,將程式碼轉化一下,簡化一下, 做出如下的程式資料來即可:
$ mkdir ~/dht_plot $ cd ~/dht_plot $ vim dht-1.py import Adafruit_DHT as DHT import datetime from time import sleep DHT_sensor = DHT.DHT11 DHT_pin = 14 while True: today = datetime.datetime.now().strftime("%Y-%m-%d %X") rh, tmp = DHT.read_retry( DHT_sensor, DHT_pin ) if rh is not None and tmp is not None: print('日期={2} 溫度={0:0.1f}度C 濕度={1:0.1f}%'.format(tmp, rh, today)) else: print('讀取失敗,重新讀取。') sleep(1) $ python dht-1.py
網路上有相當多的文件可以參考,上面只是其中一種範例,你可以參考文末的原始函式庫提供的範例程式來進行修改即可。
8.2: 使用 python 的 Matplotlib 做簡易繪圖
python 可以透過 Matploblib 這個模組進行繪圖功能!繪製的圖示相當多!可以是 XY 散佈圖、 折線圖、柱狀圖與圓形圖等等,功能相當龐大!只是,預設的功能只能將圖示繪出到圖形界面而已, 對於我們的終端機應用來說,恐怕有點不是很方便。因此,底下我們會將圖示繪出到網頁上, 我們可以透過網頁來查詢剛剛繪製的圖示喔!這樣比較方便!
- 確認網頁入口與圖示連結
我們預計要讓圖示使用網頁的模式輸出,因此需要確定網頁伺服器是有啟動的,可以簡單這樣查閱:
$ sudo systemctl status apache2 ● apache2.service - The Apache HTTP Server Loaded: loaded (/lib/systemd/system/apache2.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2022-11-03 09:16:36 CST; 14h ago Docs: https://httpd.apache.org/docs/2.4/ Process: 588 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS) Main PID: 637 (apache2) Tasks: 55 (limit: 779) CPU: 5.992s CGroup: /system.slice/apache2.service ├─637 /usr/sbin/apache2 -k start ├─638 /usr/sbin/apache2 -k start └─639 /usr/sbin/apache2 -k start $ sudo ufw status Status: active To Action From -- ------ ---- 80/tcp ALLOW Anywhere Anywhere ALLOW 192.168.0.0/16 22/tcp ALLOW 172.31.0.0/16 Samba ALLOW 10.0.0.0/8 21/tcp ALLOW Anywhere 80/tcp (v6) ALLOW Anywhere (v6) 21/tcp (v6) ALLOW Anywhere (v6)
看起來網頁伺服器是有啟動的,同時防火牆也有放行 port 80 給用戶瀏覽。如果有任何一個地方出錯,請前往第四章與第三章查閱相關動作。 處理完畢之後,再次確認個人家目錄網頁是成功建立的,假設用戶的帳號名稱是 rasppi 的話,那查詢方法如下:
# 1. 確認用戶家目錄存在 www 目錄,並建立首頁檔 $ mkdir ~/www $ vim ~/www/index.html <ul> <li>name: VBird Tsai</li> <li>ID: 4090cxxx</li> <li><a href="demo.png">demo.png</a></li> </ul> # 2. 確認這個用戶在 /var/www/html 裡面有設定連結 $ cd /var/www/html $ sudo ln -sf /home/rasppi/www rasppi # 3. 確認首頁檔案存在 $ curl http://localhost/rasppi/
上面最後一個步驟會列出你剛剛在 index.html 裡面所建立的資料。我們預設有個名為 demo.png 的圖形檔, 等等測試的圖檔都會放置到 ~/www/demo.png 這個檔名喔!
- 安裝 Matplotlib 與初次匯入模組
Matplotlib 是個非常有趣的模組,它可以讓我們透過 python 配合數據來簡易的畫出需要的圖示!常見的圖示當然是 X, Y 分佈圖。 現在,先讓我們將模組安裝起來!
$ sudo pip install matplotlib $ sudo pip install numpy
因為這個模組會用到其他很多額外的模組,所以安裝過程會比較冗長!請稍待一陣子。另外,因為許多圖示都會用到數值陣列, 所以最好確認一下安裝了 numpy 這個模組!安裝完畢之後,我們先來繪製一個簡單的圖示。假設有底下 5 個 (x, y) 數值:
( 5, 6 ) ( 10, 9 ) ( 15, 17 ) ( 20, 21 ) ( 25, 25 )
想要繪製出正確的圖示,你應該要這樣處理繪圖腳本:
$ vim plot-1.py # 匯入 matplotlib 的 pyplot ,並且取名為 plt ,方便應用 import matplotlib.pyplot as plt import numpy as np xps = [ 5, 10, 15, 20, 25 ] yps = [ 6, 9, 17, 21, 25 ] xps = np.array(xps) yps = np.array(yps) plt.plot(xps, yps) outfile = '/home/rasppi/www/demo.png' plt.savefig(outfile, dpi=100, bbox_inches='tight') plt.close() # 如果要放大圖示,可以將 dpi 改到 300 左右即可! $ python plot-1.py $ ll ~/www -rw-r--r-- 1 rasppi rasppi 71826 11月 4 22:32 demo.png -rw-r--r-- 1 rasppi rasppi 103 11月 3 23:27 index.html
請打開瀏覽器,連線到你樹莓派的 IP 上面,點選你的個人首頁,就會看到 demo.png 檔名,點下去之後, 應該會看到有點像底下的圖示才對:
非常簡單快速的就將你的圖檔建置妥當!不過你得要注意的是,上圖的 X 與 Y 都是數值,而且可能需要透過 numpy 轉成陣列, 會比較好繪圖。那如果 X 不是數值呢?舉例來說, X 是時間,Y 才是數值!這時會變什麼呢?
- 繪製簡易時間序列圖示
繪製時間序列圖示其實很簡單,如上一題提到的,只要將 X 軸的資料轉成字串而不是數值,就可以變成時間序列了! 例如底下的範例:
$ vim plot-2.py import matplotlib.pyplot as plt import numpy as np mytime = [ '10:10', '10:11', '10:12', '10:13', '10:14' ] mytemp = [ 27.3, 27.2, 28, 28.5, 30.1 ] plt.plot(mytime, mytemp) outfile = '/home/rasppi/www/demo.png' plt.savefig(outfile, dpi=100, bbox_inches='tight') plt.close() $ python plot-2.py
順利運作完成之後,圖示會有點像底下這樣喔:
不過,這個圖示不怎麼好看!原因是數據資料範圍有點怪異~而且,沒有數據點,只有線段而已。如果要加上一些限制的話, 可以這樣嘗試看看:
$ vim plot-3.py import matplotlib.pyplot as plt import numpy as np mytime = [ '10:10', '10:11', '10:12', '10:13', '10:14' ] mytemp = [ 27.3, 27.2, 28, 28.5, 30.1 ] plt.ylim([0, 31]) plt.plot(mytime, mytemp, marker='o', markersize=10, color='b') outfile = '/home/rasppi/www/demo.png' plt.savefig(outfile, dpi=100, bbox_inches='tight') plt.close() $ python plot-3.py
如上所示,我們可以新增兩個限制,分別是:
- .ylim([最小值, 最大值]):限制 y 的數值
- .xlim([最小值, 最大值]):限制 x 的數值
至於 .plot 內的參數就簡單了! marker 就是數值點, markersize 就是大小值,color 當然就是顏色了! 如此一來,看起來樣式就舒服許多。當然啦,你可能要縮小 markersize,不然看起來數值點太大了!
- 中文顯示問題
看起來圖示還好,但是我們可能還需要一些展示用途的說明,包括圖表的標題,X, Y 座標軸的意義等。 但是,預設的 python 在 matploblib 裡面,對於中文字型的支援度實在相當有限!一般來說,你可能無法順利的顯示出正常中文的! 你的系統裡面 python 有哪些字型呢?我們可以這樣先找一下:
$ python Python 3.9.2 (default, Feb 28 2021, 17:03:44) >>> import matplotlib.font_manager >>> a = sorted ([f.name for f in matplotlib.font_manager.fontManager.ttflist]) >>> for i in a: ... print(i) ... <==這裡按下[enter] C059 C059 .... cmsy10 cmtt10 >>> exit()
在這一大串資料中,其實你找不到中文字型!你也可以將上面的特殊字體複製成為 myfont.py 腳本, 執行之後去擷取資料,也能夠找到你想要找的字型!例如這樣處理:
$ vim myfont.py import matplotlib.font_manager a = sorted ([f.name for f in matplotlib.font_manager.fontManager.ttflist]) for i in a: print(i) $ python myfont.py | grep -i Noto Noto Mono Noto Sans Mono Noto Sans Mono
這樣就能找到你想要找的關鍵字字型。其實,這些字型是放置到某個 python 目錄的,你可以查詢一下底下的目錄:
$ ll /usr/local/lib/python3.9/dist-packages/matplotlib/mpl-data/fonts/ttf/
那能不能下載中文字型以資利用?是可以的!你可以先前往台北黑體的官網下載 TTF 的字型檔,可以參考如下網頁來下載:
下載之後的檔案請放置到上述的 python 字型目錄中,並且更新你個人的 matplotlib 的字型快取! 這樣才能夠持續使用新的字型!
$ sudo cp /home/rasppi/TaipeiSansTCBeta-Regular.ttf \ > /usr/local/lib/python3.9/dist-packages/matplotlib/mpl-data/fonts/ttf/ $ rm ~/.cache/matplotlib/fontlist-v330.json $ python myfont.py | grep -i taipei Matplotlib is building the font cache; this may take a moment. Taipei Sans TC Beta
終於!字型檔可以被使用了!
- 加上圖示、座標等資料
接下來當然就是開始使用中文啦!使用的方式也很簡單!如下所示,需要加上一些特別的參數才行:
$ vim plot-4.py import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties import numpy as np mytime = [ '10:10', '10:11', '10:12', '10:13', '10:14' ] mytemp = [ 27.3, 27.2, 28, 28.5, 30.1 ] plt.ylim([0, 31]) plt.plot(mytime, mytemp, marker='o', markersize=10, color='b') plt.rcParams['font.sans-serif'] = ['Taipei Sans TC Beta'] plt.title("溫度時間序列") plt.xlabel("小時:分鐘") plt.ylabel("溫度 C") outfile = '/home/rasppi/www/demo.png' plt.savefig(outfile, dpi=100, bbox_inches='tight') plt.close() $ python plot-4.py
圖示會有點像底下這樣,可以順利顯示出中英文,顯示效果當然比起全英文要好很多!
另外,如上圖所示,如果你的 X 說明資料太長,例如我們使用 DHT11 顯示的時間效果,那可能需要旋轉個角度才行! 如何進行刻度的旋轉呢?可以透過類似底下的作法:
plt.xticks(rotation=90, ha='right')
進行一個角度的旋轉,同時給予對齊,這樣就可以產生不同刻度的展示效果了!如下圖所示:
- 兩個 Y 軸刻度
但是有時候我們可能會有需要兩個刻度,例如溫度與濕度!繪製兩張圖又感覺搭配不起來!想要繪製在一起。 這時,就有需要用到子圖示 (subplots()) 的功能!簡單的說,我們可以這樣處理一下:
$ vim plot-5.py import matplotlib.pyplot as plt from matplotlib.font_manager import FontProperties import numpy as np mytime = [ '10:10', '10:11', '10:12', '10:13', '10:14' ] mytemp = [ 27.3, 27.2, 28, 28.5, 30.1 ] myrh = [ 68.9, 70.2, 73, 75.5, 80.1 ] plt.rcParams['font.sans-serif'] = ['Taipei Sans TC Beta'] fig, ax1 = plt.subplots() ax1.plot(mytime, mytemp, marker='o', markersize=5, color='b') ax1.set_ylim([0,31]) ax1.set_xlabel("小時:分鐘") ax1.set_ylabel("溫度 C", color='b') plt.xticks(rotation=90, ha='right') plt.title("溫度時間序列") ax2 = ax1.twinx() ax2.plot(mytime, myrh, marker='^', markersize=5, color='r') ax2.set_ylim([0,100]) ax2.set_ylabel("相對濕度 %", color='r') outfile = '/home/rasppi/www/demo.png' plt.savefig(outfile, dpi=100, bbox_inches='tight') plt.close() $ python plot-5.py
簡單的說,你得要先製作出 fig 與 ax1 這個第一個 Y 軸的數值,然後將全部的圖示資料通通繪製妥當。 之後再繪製第二個 Y 軸的 ax2!只是,要告知 ax2 使用的是另一個刻度表!最終就可以產生類似底下的圖示了:
8.3: 以溫溼度計資料繪製時間序列圖示 (time series plot)
從前面一個小節,我們知道繪製圖示的基本條件,大概就是需要陣列!將數據丟到陣列去。 但是,如果要繪製溫溼度圖示,我們使用 DHT11 來取得數據時,這些數據得要一段時間去收集, 所以,在不使用資料庫的情況下,我們就得要將數據慢慢的『新增到陣列』中!這就需要一點小技巧。
- 設計陣列取得時間、溫度、濕度陣列值
將資料『新增』到陣列裡面,那麼就得要先設定一個陣列變數才行!我們將 dht-1.py 複製成為 dht-2.py, 然後增加一些設定如下:
$ vim dht-2.py import Adafruit_DHT as DHT import datetime from time import sleep DHT_sensor = DHT.DHT11 DHT_pin = 14 # 新增三個陣列變數@ mytime = [] mytemp = [] myrh = [] # 暫時只取 10 個值就好 for i in range(10): today = datetime.datetime.now().strftime("%Y-%m-%d %X") rh, tmp = DHT.read_retry( DHT_sensor, DHT_pin ) if rh is not None and tmp is not None: print('日期={2} 溫度={0:0.1f}度C 濕度={1:0.1f}%'.format(tmp, rh, today)) else: print('讀取失敗,重新讀取。') # 將上面抓到的三個值累加入陣列當中! mytime.append(today) mytemp.append(tmp) myrh.append(rh) sleep(1) # 單純的印出陣列值! print(mytime) print(mytemp) print(myrh) $ python dht-2.py
上面這個腳本會產生 10 筆紀錄,同時將這 10 筆紀錄加入三個陣列中!這三個陣列就是我們要應用的數據啦!
- 繪製圖示
我們現在有時間參數 (X 軸),也有溫度 (第一個 Y 刻度) 與濕度 (第二個 Y 刻度),所以,請結合 dht-2.py 與 plot-5.py 這兩個腳本內容,產生有點像底下圖示的資料:
- 檔名設定為 dht-plot.py
- X 軸是時間
- 左側 Y 軸是溫度,顏色以藍色顯示
- 右側 Y 軸是濕度,顏色已綠色顯示
- 檔案檔名儲存為 ~/www/mydht11.png
- 最終可用 http://your.raspberry.pi/yourname/mydht11.png 讀到這個圖檔
- 定期繪製圖示
在不考慮時間長短的情境下,我們可以讓這張圖示每 1 分鐘進行一次自動圖示的處理!當然,你要 5 分鐘一次也可以! 處理方式很簡單,透過一個名為 crontab 的指令來處理即可,另外,不需要使用 root 權限,使用你自己的帳號權限即可。 只是,可能需要使用所謂的『絕對路徑』來執行才行。首先,先來背一背 crontab 的時間參數口訣:
代表意義 | 分鐘 | 小時 | 日期 | 月份 | 週 | 指令 |
數字範圍 | 0-59 | 0-23 | 1-31 | 1-12 | 0-7 | 指令最好使用絕對路徑 |
執行方法為使用『 crontab -e 』進入 vim 編輯模式後,將上述的口訣帶入即可!指令最好也能加上資料流重導向, 讓你的執行過程不會有太多垃圾產生! ( 指令 &> /dev/null )
8.4: 當週實做
告知老師您的網頁網址,讓老師可以取得你今日實際溫溼度圖形資料,即可確定你的執行成果。
- 參考資料
- DHT 溫溼度計說明: https://learn.adafruit.com/dht
- DHT 感測器模組使用: https://github.com/adafruit/Adafruit_Python_DHT/blob/master/examples/AdafruitDHT.py
- W3C school Matplotlib 繪圖: https://www.w3schools.com/python/matplotlib_intro.asp
- 中文字型下載:https://sites.google.com/view/jtfoundry/zh-tw/downloads?authuser=0
- matplotlib 中文字型解決:https://pyecontech.com/2020/03/27/python_matplotlib_chinese/
- matploblib 官網:https://matplotlib.org/3.6.2/tutorials/introductory/quick_start.html
...