一個封包如何從
ESP32走到Django?
按下按鈕、讀到水溫或播放一段故事之後,資料如何穿越Wi-Fi、路由器、DNS、Internet與HTTPS,最後被Django接收並寫進Database?
一個按鈕按下後,真正發生了什麼?
在智慧生活系統中,我們常看到ESP32序列監控視窗顯示「POST成功」,Django Dashboard也出現一筆新資料。看起來只是幾行程式,但在這短短幾秒內,資料其實經過多個設備、位址與協定。
學習目標
說明ESP32、AP、Router、DNS、Internet、Django與Database的責任。
分辨MAC、IP、TCP、TLS、HTTP與JSON各自負責的工作。
使用分層診斷,不再把所有問題都歸因於「Wi-Fi不好」。
先看完整路線
回應則沿著已建立的連線反方向返回:Database → Django → HTTPS Response → Internet → Router → ESP32。
五層看懂同一筆資料
封包出發前:先定義這次事件
以水井三寶智慧互動展覽為例,使用者按下「烏龜故事」按鈕後,ESP32可以把事件整理成JSON。這份JSON是應用層要傳遞的內容,不包含MAC、IP或TCP資訊。
{
"event_uuid": "SHUIJING-001-0000000062",
"event_type": "STORY_START",
"story": "002",
"source": "button",
"network_status": "online",
"metadata": {
"firmware": "Layer4-V1.4",
"jq_confirmed": true
}
}
為什麼需要event_uuid?
網路逾時後ESP32可能重送同一事件。Django可用唯一識別碼判斷是否重複,避免同一筆資料寫入兩次。
為什麼要有metadata?
保留韌體版本、確認狀態及診斷資訊,方便日後比較不同設備與版本的行為。
八站完成一次封包旅行
ESP32加入Wi-Fi,取得網路身分
Association → Authentication → DHCP
ESP32先以STA模式加入無線基地台。連線成功後,通常透過DHCP取得四項重要資訊:
| 資訊 | 範例 | 用途 |
|---|---|---|
| 本機IP | 192.168.1.119 | ESP32在目前LAN中的位址 |
| Subnet Mask | 255.255.255.0 | 判斷目的地是否位於同一區域網路 |
| Default Gateway | 192.168.1.1 | 前往其他網路與Internet的出口 |
| DNS Server | 192.168.1.1或ISP DNS | 將網域名稱解析成IP位址 |
WiFi.status()==WL_CONNECTED,還不能證明Internet或Django已正常。DNS把網域名稱翻譯成IP
Domain Name → DNS Query → Server IP
程式使用的網址可能是:
https://shuijingtreasures.pythonanywhere.com/api/events/
ESP32並不知道這個字串位於世界哪裡,因此會向DNS Server詢問:shuijingtreasures.pythonanywhere.com對應哪一個IP?DNS回覆後,ESP32才能建立後續連線。
ARP找到Gateway的MAC位址
下一跳不是遠端Django,而是本地Router
Django不在同一個Subnet,所以ESP32不會直接找Django的MAC。它先透過ARP找出Default Gateway的MAC位址,再把Wi-Fi Frame交給Router。
每經過一個路由節點,Frame的MAC資訊可能改變;但IP Packet的目的IP仍指向遠端伺服器。
Router執行NAT並選擇路徑
Private IP → Public IP → Internet
192.168.1.119是私有IP,不能直接在全球Internet上被路由。Router會執行NAT/PAT,把ESP32的私有IP與來源Port轉換成路由器的公網IP與暫時Port,並記錄對應關係。
| 位置 | 來源 | 目的 |
|---|---|---|
| LAN內 | 192.168.1.119:隨機來源Port | Server-IP:443 |
| Internet側 | Public-IP:轉換後Port | Server-IP:443 |
伺服器回應抵達Router後,Router依NAT表把資料交回原本的ESP32。
TCP建立可靠連線
SYN → SYN-ACK → ACK
HTTPS一般建立在TCP之上。ESP32與伺服器先進行三向交握,確認雙方都能收發資料。TCP負責:
可靠性
透過Sequence Number、ACK、逾時與重送,降低資料遺失造成的錯誤。
有序傳輸
即使Segment經過不同路徑或抵達順序不同,也能重新排列成正確資料。
TLS確認伺服器並加密資料
Certificate → Key Exchange → Encrypted Channel
因為網址使用HTTPS,TCP建立後還要進行TLS Handshake。ESP32檢查憑證是否可信、網域是否相符、憑證是否在有效期限內,再協商加密金鑰。
HTTP把JSON送進Django API
POST+Header+Body
安全通道建立後,ESP32送出HTTP Request。概念上包含以下內容:
HTTP定義「怎麼送」,JSON定義「送什麼」,REST API定義「送到哪一個功能入口」。
Django驗證、處理並寫入Database
URL → View → Validation → Model → Response
Django收到Request後,通常依序進行:
- URL Router將
/api/events/交給對應View。 - 驗證Method、Content-Type、Token與JSON格式。
- 檢查必要欄位、資料型別及
event_uuid是否重複。 - 使用Model寫入Database。
- 回傳JSON與適當HTTP Status Code。
import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from .models import DeviceEvent
@require_POST
def create_event(request):
try:
data = json.loads(request.body)
event_uuid = data["event_uuid"]
event, created = DeviceEvent.objects.get_or_create(
event_uuid=event_uuid,
defaults={
"event_type": data["event_type"],
"story": data.get("story", ""),
"source": data.get("source", "unknown"),
"metadata": data.get("metadata", {}),
},
)
return JsonResponse(
{"ok": True, "created": created, "id": event.id},
status=201 if created else 200,
)
except (KeyError, json.JSONDecodeError) as exc:
return JsonResponse(
{"ok": False, "error": str(exc)}, status=400
)
201 Created;重複事件可回200並標示created:false;格式錯誤用400;未授權用401;伺服器錯誤用500。ESP32送出HTTPS POST的示意程式
以下程式聚焦資料旅程,憑證、Token、逾時、重送與離線Queue仍需依正式系統補齊。不得把真實密鑰直接寫進公開文章或GitHub。
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <HTTPClient.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* API_URL =
"https://shuijingtreasures.pythonanywhere.com/api/events/";
void postEvent(const String& payload) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("NETWORK ERROR: Wi-Fi disconnected");
return; // 正式版應排入離線Queue
}
WiFiClientSecure client;
// 正式版:設定並驗證正確CA憑證
// client.setCACert(ROOT_CA);
HTTPClient https;
https.setTimeout(8000);
if (!https.begin(client, API_URL)) {
Serial.println("HTTPS BEGIN FAILED");
return;
}
https.addHeader("Content-Type", "application/json");
https.addHeader("Authorization", "Bearer DEVICE_TOKEN");
int statusCode = https.POST(payload);
String response = https.getString();
Serial.printf("HTTP STATUS: %d\n", statusCode);
Serial.println("RESPONSE: " + response);
https.end();
}
封包裡到底包了什麼?
「封裝」可以想像成一層一層加上信封。最裡面是JSON,外面依序包上HTTP、TLS、TCP、IP與Wi-Fi Frame。
| 由內到外 | 主要資訊 | 在哪裡被處理 |
|---|---|---|
| JSON資料 | 事件類型、故事編號、裝置資訊 | ESP32程式與Django View |
| HTTP | Method、Path、Header、Body、Status Code | HTTPClient、Web Server、Django |
| TLS | 憑證、加密參數、加密後內容 | ESP32安全Client與HTTPS伺服器 |
| TCP | 來源/目的Port、Sequence、ACK | 兩端作業系統或網路堆疊 |
| IP | 來源IP、目的IP、TTL | ESP32、Router及Internet路由器 |
| Wi-Fi Frame | 本地一跳使用的MAC與錯誤檢查 | ESP32、AP與區域網路 |
出錯時,從哪裡開始查?
| 現象 | 可能層級 | 應取得的證據 |
|---|---|---|
| 無法取得IP | Wi-Fi/DHCP | SSID、密碼、WiFi狀態、DHCP紀錄 |
| 有IP但Domain解析失敗 | DNS | DNS Server、解析結果、其他Domain測試 |
| 連線逾時 | Routing/TCP/Firewall | Gateway、目的IP、Port 443、Timeout位置 |
| TLS失敗 | 憑證/時間/網域 | 憑證錯誤、系統時間、CA與Host名稱 |
| HTTP 400 | JSON/API驗證 | Request Body、Content-Type、Django錯誤回應 |
| HTTP 401/403 | 身分與權限 | Token、Header、裝置權限與ACL |
| HTTP 500 | Django/Database | Server Log、Exception、Migration與資料庫狀態 |
| ESP32顯示成功但Dashboard沒資料 | API/Database/Dashboard Query | Response、資料表紀錄、查詢條件與時間範圍 |
三項學生任務
標示ESP32、AP、Router、DNS、Internet、Django與Database,並為每段寫出主要協定。
請AI協助比較Frame、Packet、Segment與HTTP資料,再以自己的話修正與重畫。
教師設定錯誤DNS、錯誤API Path、無效Token或重複event_uuid,學生用證據定位與修復。
學習檢核
| 問題 | 學生應能回答的重點 |
|---|---|
| ESP32已取得IP,為何仍可能無法連到Django? | Gateway、DNS、Routing、TCP、TLS、HTTP與API仍可能失敗。 |
| 為什麼目的MAC不是Django伺服器的MAC? | MAC只負責本地一跳;跨網路時先交給Gateway。 |
| NAT做了什麼? | 把私有IP與Port轉換為公網IP與Port,並保存回程對應。 |
| HTTP、JSON與REST API如何分工? | HTTP定義傳送方式,JSON定義資料格式,REST API定義資源入口與操作。 |
| 為何需要event_uuid? | 讓伺服器辨識重送與重複事件,支援冪等處理。 |
| 如何證明事件已成功? | 同時檢查ESP32 Log、HTTP Status、Django Response、Database與Dashboard。 |
一個封包的旅程,就是一套系統的縮影
ESP32負責把實體事件轉成資料;Wi-Fi與Router把資料送出場域;DNS找到服務;TCP與TLS提供可靠且安全的通道;HTTP與JSON定義溝通;Django與Database把事件保存成可分析的資訊。
真正學會網際網路,不是只讓資料送成功,而是能解釋每一站、驗證每一層、修復每一種失敗。
沒有留言:
張貼留言