Learning Objectives
- 把一個專題需求寫成規格與接線表,說明多個 I2C 裝置怎麼共用匯流排
- 寫出「讀取、判斷、顯示、記錄」的主迴圈,加上門檻警示與人體感測控制背光
- 把系統拆成記錄服務與網頁服務,讓網頁 API 讀取記錄檔,並用 MQTT 發布資料
- 用 systemd 部署兩個服務,依測試清單檢查系統在異常情況下的行為
- 說明環境監測資料涉及的隱私考量
專題目標:一台會自己工作的監測站
這一課把整個樹莓派軌學過的東西組成一個完整的系統:放在教室角落的環境監測站。
需求(先用一般的話寫下來):
- 測量溫度與光線,判斷教室裡有沒有人。
- 在現場的 LCD 顯示目前的數值;沒有人的時候關掉背光。
- 溫度超過 28°C 時亮紅色警示燈。
- 每一筆資料記錄下來,之後可以分析。
- 同一個網路上的電腦可以用網址查最新數值與統計;管理室可以透過 MQTT 收到資料。
- 停電復電後自動恢復運作,不需要有人去按。
規格(把需求變成可以檢查的數字與介面):
| 項目 | 規格 |
|---|---|
| 取樣間隔 | 每 60 秒一次(下面的模擬為了好看,縮短成 1 秒) |
| 警示門檻 | 溫度 > 28.0°C 亮燈,回到門檻以下熄滅 |
| 記錄格式 | CSV:UTC 秒數, 溫度, 光線(0~100), 有沒有人(0/1) |
| 網頁 API | GET /api/latest、GET /api/summary,回傳 JSON |
| MQTT 主題 | classroom/101/env,內容是 JSON |
| 自動執行 | 兩個 systemd 服務,Restart=on-failure |
把需求寫成規格,做完之後才有辦法逐項檢查「到底完成了沒有」。
零件與接線表
| 零件 | 介面 | 接到樹莓派 | 學過的課 |
|---|---|---|---|
| TMP102 溫度感測器 | I2C,位址 0x48 | SDA=GPIO2、SCL=GPIO3 | 第 09 課 |
| LCD1602(I2C 轉接板) | I2C,位址 0x27 | 和 TMP102 接在同一組 SDA、SCL | 第 10 課 |
| 光敏電阻+MCP3008 | SPI,第 0 通道 | SPI0(CE0) | 第 08 課 |
| PIR 人體感測器 | 數位輸入 | GPIO4 | 第 12 課 |
| 紅色 LED+220Ω 電阻 | 數位輸出 | GPIO17 | 第 05 課 |
I2C 是匯流排:好幾個裝置可以接在同樣兩條線上,靠位址區分。只要位址不重複,TMP102(0x48)和 LCD(0x27)就能共用。
接好後用 i2cdetect -y 1 檢查,應該看到 48 與 27 兩個位址(第 09 課)。
接線前先關機;LCD 模組的供電電壓與訊號電位照第 10 課的說明處理,不要讓 5V 的訊號直接進到樹莓派 3.3V 的腳位。
系統架構:拆成兩個服務
如果把「每 60 秒讀感測器」和「回應網頁請求」寫在同一支程式,兩件事會互相卡住:
app.run() 一直在等請求,迴圈就跑不到;迴圈在 sleep,網頁就沒有回應。要同時做就得用執行緒,程式變得複雜。
比較簡單、也比較穩定的做法是拆成兩支程式,各自是一個 systemd 服務,透過檔案交換資料:
┌──────── logger.py(記錄服務)────────┐ ┌──── web.py(網頁服務)────┐
│ 讀感測器 → 判斷 → LCD、警示燈 │ │ /api/latest │
│ → 附加一列到 env.csv ──────────┼──────▶ │ /api/summary 讀 env.csv │
│ → MQTT 發布 classroom/101/env │ └───────────────────────────┘
└──────────────────────────────────────┘
好處:網頁服務當掉不影響記錄;改網頁時重新啟動 web 服務就好,記錄不會中斷。 資料量變大之後,把 env.csv 換成第 14 課的 SQLite,兩邊的介面不變。
逐步看:記錄服務的主迴圈
電路上有五個零件。溫度在 4 秒內從 27°C 升到 29°C,人體感測在 2.5 秒後就沒有人了。 注意兩個變化:溫度超過 28 時第 22 行讓警示燈亮起;沒有人時第 23 行關掉 LCD 背光。
逐步看:網頁服務讀記錄檔
這支程式不讀感測器,只讀記錄服務寫好的 env.csv(已經有四筆資料)。請求時間表:0.7 秒 /api/latest、1.7 秒 /api/summary、2.7 秒 /alert/on、3.5 秒 /alert/off。
看每一個請求執行了哪幾行,以及電路畫面下方的 JSON 回應。
部署:兩個服務、開機自動執行
在樹莓派上把專題放進 /home/student/station,照第 17 課的方法部署。輸出經過節錄。
-
1
專題資料夾的內容
程式、虛擬環境、記錄檔放在同一個資料夾;unit 檔的
WorkingDirectory指到這裡,程式裡的env.csv就會存在這裡。 -
2
確認 I2C 裝置都在
應該看到 27(LCD)與 48(溫度感測器)。少了哪一個,先檢查那個零件的接線。
-
3
寫兩個 unit 檔
記錄服務的內容如下;網頁服務的
station-web.service一樣寫法,只是ExecStart改成執行web.py,Description改成網頁服務。 -
4
啟用並啟動
一個指令可以同時啟用好幾個服務。
-
5
從另一台電腦測試網頁 API
在同一個網路的電腦上用
curl(或瀏覽器)打開網址。raspberrypi.local是樹莓派的主機名稱,也可以直接用 IP。
測試清單:故意讓它出錯
系統在一切正常時能動,只是起點。放到教室之前,照下面的清單故意製造問題,確認每一項的反應都在預期內:
| 測試 | 怎麼做 | 預期 |
|---|---|---|
| 斷電復電 | 拔掉電源再插上 | 兩個服務自動啟動,記錄接續寫在 env.csv 後面 |
| 程式當掉 | sudo kill -9 記錄服務的PID |
約 5 秒後自動重新啟動 |
| 感測器鬆脫 | 關機拔掉 TMP102 的線再開機 | 程式記下錯誤、繼續執行(第 17 課的 try/except),不是一直重啟 |
| 溫度超標 | 用手握住感測器 | 超過 28°C 時警示燈亮,放開後熄滅 |
| 沒有人 | 離開 PIR 的範圍 | 一段時間後 LCD 背光關閉 |
| 網路斷線 | 拔掉網路 | 記錄照常寫檔;MQTT 重新連線後恢復發布 |
每一項都寫下「做了什麼、看到什麼、是否符合預期」。不符合的項目就是下一輪要改的地方。
隱私
PIR 記錄的是「這個時間教室裡有沒有人」,看起來無害,累積下來卻能看出某間教室、某個人的作息。
- 在教室貼出說明:測量什麼、存多久、誰看得到。
- 只收需要的資料:只需要溫度與光線時,就不要記錄有沒有人。
- 對外分享時用彙總後的數字(例如每小時平均),而不是逐筆的原始紀錄。
- 網頁與 MQTT 只在校內網路開放,不要直接放上網際網路(第 15、16 課)。
小測驗:設計與接線
TMP102(位址 0x48)與 LCD1602 轉接板(位址 0x27)接在同一組 SDA、SCL 上,會怎樣?
下面的程式模擬記錄服務的判斷,會印出什麼?
LIMIT = 28.0
for temp, people in [(27.5, True), (28.0, True), (28.6, False)]:
alert = temp > LIMIT
backlight = people
print(int(alert), int(backlight), end=" ")
網頁服務的 /api/summary 讀到 env.csv 的溫度是 27.0、27.6、28.3、28.9,回傳的平均(四捨五入到小數 2 位)是多少?
小測驗:部署、測試與隱私
測試時拔掉 TMP102 再開機,發現記錄服務每 5 秒重新啟動一次、什麼都沒記到。最好的改法是?
監測站的資料要分享給全校看,下面哪一種做法最符合隱私原則?
重點整理與延伸
- 先把需求寫成可以檢查的規格(間隔、門檻、格式、介面),再畫接線表;I2C 裝置靠位址共用匯流排。
- 記錄服務的主迴圈:讀取 → 判斷(警示、背光)→ 顯示 → 記錄 →(發布)→ 等待。
- 一直等請求的網頁與一直迴圈的記錄拆成兩個服務,透過檔案或資料庫交換資料,各自用 systemd 管理。
- 放到現場之前,照測試清單故意製造斷電、當機、感測器鬆脫、網路中斷,確認每一項的反應。
- 記錄與人有關的資料前先想隱私:告知、只收需要的、分享彙總結果、不直接對外開放。
延伸題目:加上溫溼度感測器或二氧化碳感測器;把 CSV 換成 SQLite 並在網頁畫出一天的溫度曲線(Chart.js); 用 MQTT 把好幾間教室的監測站集中到一台樹莓派,比較各教室的差異;溫度持續超標時用 MQTT 通知管理室。