raspberrypi 官網 raspberrypi 官網

互動 IoT 系統應用 - 上課教材

互動 IoT 系統應用 > 課程內容 > 第 08 章 - 取得感測器數據與 python 簡易繪圖

第 08 章 - 取得感測器數據與 python 簡易繪圖

上次更新日期 2022/11/05

前一章我們使用了光敏電阻,然後使用 gpiozero 取得光敏電阻的資料後,判斷光線是否明亮, 然後在透過其他設備,點亮或關閉燈光,這就是一種感測器的應用。在這個章節中,我們會透過樹莓派, 取得簡單的溫溼度資料後,加以一些簡單的繪圖應用功能,並發送到網頁上,讓大家可以直接看到相關的圖示。

學習目標:

  1. 了解如何偵測 DHT11 溫溼度計
  2. 了解 python 繪製簡易圖示 (time series plot)
  3. 結合溫溼度用於網頁圖示輸出

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 檔名,點下去之後, 應該會看到有點像底下的圖示才對:

python 繪圖示意

非常簡單快速的就將你的圖檔建置妥當!不過你得要注意的是,上圖的 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

順利運作完成之後,圖示會有點像底下這樣喔:

python 繪圖示意

不過,這個圖示不怎麼好看!原因是數據資料範圍有點怪異~而且,沒有數據點,只有線段而已。如果要加上一些限制的話, 可以這樣嘗試看看:

$ 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
python 繪圖示意

如上所示,我們可以新增兩個限制,分別是:

  • .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

圖示會有點像底下這樣,可以順利顯示出中英文,顯示效果當然比起全英文要好很多!

python 繪圖示意

另外,如上圖所示,如果你的 X 說明資料太長,例如我們使用 DHT11 顯示的時間效果,那可能需要旋轉個角度才行! 如何進行刻度的旋轉呢?可以透過類似底下的作法:

plt.xticks(rotation=90, ha='right')

進行一個角度的旋轉,同時給予對齊,這樣就可以產生不同刻度的展示效果了!如下圖所示:

python 繪圖示意
  • 兩個 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 使用的是另一個刻度表!最終就可以產生類似底下的圖示了:

python 繪圖示意

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 這兩個腳本內容,產生有點像底下圖示的資料:

例題 8.3.1:製作 DHT11 溫溼度圖示
  • 檔名設定為 dht-plot.py
  • X 軸是時間
  • 左側 Y 軸是溫度,顏色以藍色顯示
  • 右側 Y 軸是濕度,顏色已綠色顯示
  • 檔案檔名儲存為 ~/www/mydht11.png
  • 最終可用 http://your.raspberry.pi/yourname/mydht11.png 讀到這個圖檔
python 繪圖示意
  • 定期繪製圖示

在不考慮時間長短的情境下,我們可以讓這張圖示每 1 分鐘進行一次自動圖示的處理!當然,你要 5 分鐘一次也可以! 處理方式很簡單,透過一個名為 crontab 的指令來處理即可,另外,不需要使用 root 權限,使用你自己的帳號權限即可。 只是,可能需要使用所謂的『絕對路徑』來執行才行。首先,先來背一背 crontab 的時間參數口訣:

代表意義分鐘小時 日期月份指令
數字範圍0-590-231-311-120-7 指令最好使用絕對路徑

執行方法為使用『 crontab -e 』進入 vim 編輯模式後,將上述的口訣帶入即可!指令最好也能加上資料流重導向, 讓你的執行過程不會有太多垃圾產生! ( 指令 &> /dev/null )

例題 8.3.2: 每 5 分鐘執行一次上述的溫溼度時間序列圖示,因此,每五分鐘上線查看一下,你會發現 mydht11.png 圖檔內容是會變化的!

8.4: 當週實做

告知老師您的網頁網址,讓老師可以取得你今日實際溫溼度圖形資料,即可確定你的執行成果。

  • 參考資料
  1. DHT 溫溼度計說明: https://learn.adafruit.com/dht
  2. DHT 感測器模組使用: https://github.com/adafruit/Adafruit_Python_DHT/blob/master/examples/AdafruitDHT.py
  3. W3C school Matplotlib 繪圖: https://www.w3schools.com/python/matplotlib_intro.asp
  4. 中文字型下載:https://sites.google.com/view/jtfoundry/zh-tw/downloads?authuser=0
  5. matplotlib 中文字型解決:https://pyecontech.com/2020/03/27/python_matplotlib_chinese/
  6. matploblib 官網:https://matplotlib.org/3.6.2/tutorials/introductory/quick_start.html

...