前四篇已完成 I²C、四種水質感測器、ESP32 讀值,以及 Wi-Fi AP 與 NVS 設定。EP05 將把這些成果送上雲端:ESP32 透過 Wi-Fi 將水溫、pH、溶氧與鹽度組成 JSON,再以 HTTP POST 傳送到 Django REST API,最後顯示於智慧養殖 Dashboard。
一、這一篇要完成什麼?
任務 1|建立 JSON把感測值轉成 API 可接受的資料格式。
任務 2|HTTP POSTESP32 將 JSON 傳送到 Django。
任務 3|REST APIDjango 驗證並儲存資料。
任務 4|Dashboard顯示各養殖戶最新水質狀況。
二、完整系統架構
Atlas 感測器
→
ESP32
→
Wi-Fi
→
HTTP POST
→
Django REST API
→
資料庫
→
Dashboard
ESP32 不需要知道資料庫帳號密碼,也不需要直接操作資料表,只要把資料送到 API 即可。
核心觀念:ESP32 是資料提供者;Django REST API 是資料入口;資料庫負責保存;Dashboard 負責呈現。
三、實際使用的 API 與 Dashboard
本專案 API 端點:
Django REST API
https://shuijingusr.pythonanywhere.com/api/pond/sensor-reading/
智慧養殖 Dashboard:
四、什麼是 REST API?
REST API 是讓不同裝置或程式透過 HTTP 交換資料的方式。ESP32、手機 App、網站與 Python 程式,都可以使用相同 API。
| HTTP 方法 | 用途 | 本篇是否使用 |
|---|---|---|
| GET | 讀取資料 | Dashboard 查詢資料時使用 |
| POST | 新增資料 | ESP32 上傳感測資料 |
| PUT/PATCH | 修改資料 | 本篇未使用 |
| DELETE | 刪除資料 | 本篇未使用 |
五、JSON 是什麼?
JSON 是 IoT 最常見的資料交換格式。每筆資料由 Key 與 Value 組成:
本專案 JSON 格式
{
"token": "abc123",
"farm_name": "湖虎戰隊",
"pond_code": "1",
"water_temperature": 30.01,
"salinity": 0.44,
"ph": 6.80,
"dissolved_oxygen": 31.92,
"water_source": "直流變頻水車運轉中",
"recorded_at": "2026-08-03T21:30:00+08:00"
}
數值欄位應使用數字,不要加上引號。例如 "ph": 6.80,不要寫成 "ph": "6.80"。
六、ESP32 建立 JSON
程式碼 1:建立 Payload
String buildPayload() {
String payload;
payload += "{";
payload += "\"token\":\"abc123\",";
payload += "\"farm_name\":\"湖虎戰隊\",";
payload += "\"pond_code\":\"1\",";
payload += "\"water_temperature\":30.01,";
payload += "\"salinity\":0.44,";
payload += "\"ph\":6.80,";
payload += "\"dissolved_oxygen\":31.92,";
payload += "\"water_source\":\"直流變頻水車運轉中\"";
payload += "}";
return payload;
}
正式程式中,不應把感測值寫死,而是帶入 RTD、pH、DO 與 EC 讀值。
七、確認 Wi-Fi 已連線
程式碼 2:連線檢查
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Wi-Fi 尚未連線,本次不上傳");
return;
}
若 Wi-Fi 尚未連線,程式應停止本次 POST,避免重複產生連線錯誤。
八、ESP32 使用 HTTPS POST
程式碼 3:HTTP POST 核心流程
#include <WiFi.h>
#include <HTTPClient.h>
#include <NetworkClientSecure.h>
const char* API_URL =
"https://shuijingusr.pythonanywhere.com/"
"api/pond/sensor-reading/";
void uploadData(String payload) {
if (WiFi.status() != WL_CONNECTED) {
return;
}
NetworkClientSecure client;
// 測試階段使用
client.setInsecure();
HTTPClient http;
http.begin(client, API_URL);
http.addHeader(
"Content-Type",
"application/json"
);
http.addHeader(
"Accept",
"application/json"
);
int httpCode =
http.POST(payload);
String response =
http.getString();
Serial.print("HTTP Code:");
Serial.println(httpCode);
Serial.print("Response:");
Serial.println(response);
http.end();
}
測試時可使用 setInsecure(),正式部署時建議改用伺服器根憑證驗證 HTTPS。
九、Content-Type 為什麼重要?
Django 需要知道 Request Body 是 JSON,因此必須設定:
HTTP Header
Content-Type: application/json
若沒有這個 Header,後端可能無法正確解析資料。
十、Django REST API 的資料流程
Request JSON
→
View
→
Serializer
→
Model
→
Database
Model定義資料庫欄位。
Serializer驗證 JSON 與轉換資料。
View處理 POST Request。
URL設定 API 路徑。
十一、Django Model 概念
models.py 範例
from django.db import models
class SensorReading(models.Model):
token = models.CharField(max_length=100)
farm_name = models.CharField(max_length=100)
pond_code = models.CharField(max_length=30)
water_temperature = models.FloatField(
null=True,
blank=True
)
salinity = models.FloatField(
null=True,
blank=True
)
ph = models.FloatField(
null=True,
blank=True
)
dissolved_oxygen = models.FloatField(
null=True,
blank=True
)
water_source = models.CharField(
max_length=200,
blank=True
)
recorded_at = models.DateTimeField()
十二、Serializer 的工作
serializers.py 範例
from rest_framework import serializers
from .models import SensorReading
class SensorReadingSerializer(
serializers.ModelSerializer
):
class Meta:
model = SensorReading
fields = "__all__"
Serializer 會檢查:
- 欄位是否存在。
- 數值格式是否正確。
- 時間格式是否可解析。
- 必填資料是否缺少。
十三、API View 概念
views.py 範例
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
@api_view(["POST"])
def sensor_reading(request):
serializer = SensorReadingSerializer(
data=request.data
)
if serializer.is_valid():
serializer.save()
return Response(
serializer.data,
status=status.HTTP_201_CREATED
)
return Response(
serializer.errors,
status=status.HTTP_400_BAD_REQUEST
)
十四、URL 設定
urls.py 範例
from django.urls import path
from . import views
urlpatterns = [
path(
"api/pond/sensor-reading/",
views.sensor_reading,
name="sensor-reading"
),
]
十五、HTTP 狀態碼如何判斷?
| 狀態碼 | 意義 | 處理方式 |
|---|---|---|
| 200 | 請求成功 | 後端已接受或回傳資料 |
| 201 | 新增成功 | 感測資料已寫入資料庫 |
| 400 | 資料格式錯誤 | 檢查 JSON、欄位與資料型態 |
| 401/403 | 驗證失敗 | 檢查 token 或權限 |
| 500 | 後端錯誤 | 檢查 Django Log、Model、Serializer |
| 負值 | ESP32 連線錯誤 | 檢查 Wi-Fi、DNS、HTTPS 與網址 |
十六、使用 Postman 先測試 API
在 ESP32 上傳前,可先用 Postman 測試:
- Method 選擇 POST。
- 輸入 API 網址。
- Body 選擇 raw。
- 格式選擇 JSON。
- 貼入 Payload。
- 按 Send。
若 Postman 可成功,但 ESP32 不行,問題通常在 Wi-Fi、HTTPS、Header 或 ESP32 JSON 字串。
十七、實際上傳結果
序列監控範例
========== Django API 上傳 ==========
[API] URL:
https://shuijingusr.pythonanywhere.com/
api/pond/sensor-reading/
[API] HTTP 狀態碼:201
[API] 上傳成功
====================================
十八、Dashboard 即時顯示
資料寫入資料庫後,Dashboard 可以顯示:
- 養殖戶與魚塭。
- 水溫。
- 鹽度。
- pH。
- 溶氧。
- 最新資料時間。
圖:ESP32 上傳後,Dashboard 顯示多家養殖戶的最新水質狀態。
十九、常見問題與排除
| 問題 | 可能原因 | 建議 |
|---|---|---|
| POST 回傳 -1 | Wi-Fi、DNS、HTTPS 失敗 | 先確認 ESP32 能否取得 IP |
| 400 Bad Request | JSON 或欄位格式不符 | 查看 Django 回應內容 |
| 403 Forbidden | Token 或權限錯誤 | 檢查 token |
| 500 Internal Server Error | Django 程式錯誤 | 查看 PythonAnywhere Error Log |
| Dashboard 沒更新 | 查詢不是最新資料 | 檢查排序與最新一筆查詢 |
| 時間不正確 | NTP 未同步或時區錯誤 | 使用 ISO 8601+08:00 |
二十、本篇重點
- ESP32 使用 JSON 封裝感測資料。
- 透過 HTTP POST 傳送到 Django REST API。
- Content-Type 必須設定為 application/json。
- Serializer 負責驗證資料。
- 201 表示資料新增成功。
- Dashboard 從資料庫取得最新監測值。
結語
完成 EP05 後,智慧養殖系統已經建立從感測器到雲端的完整資料鏈。ESP32 透過 I²C 取得水質資料,使用 Wi-Fi 與 HTTPS 將 JSON 傳送到 Django REST API,再由資料庫與 Dashboard 完成保存與呈現。這也是 IoT 系統中最重要的核心能力:把真實世界的感測資料,轉化為可查詢、可分析、可視化的數位資訊。
AI 協作聲明:本文由作者主導系統設計、ESP32 程式開發、Django REST API 建置、資料格式規劃、API 測試與 Dashboard 整合,並使用生成式 AI 協助文字整理、程式碼說明、版面設計與教學資訊圖生成;所有 API 欄位、測試流程與系統架構均依本專案實際開發成果整理,並由作者審核修訂。

沒有留言:
張貼留言