2026年8月22日 星期六

一個封包如何從ESP32走到Django?

封包旅行記 EP01|大學部網際網路應用

一個封包如何從
ESP32走到Django?

按下按鈕、讀到水溫或播放一段故事之後,資料如何穿越Wi-Fi、路由器、DNS、Internet與HTTPS,最後被Django接收並寫進Database?

ESP32Wi-FiDNSTCP/TLSHTTP/JSONDjango

一個按鈕按下後,真正發生了什麼?

在智慧生活系統中,我們常看到ESP32序列監控視窗顯示「POST成功」,Django Dashboard也出現一筆新資料。看起來只是幾行程式,但在這短短幾秒內,資料其實經過多個設備、位址與協定。

本篇的重點不是再做一個能上傳資料的Demo,而是能清楚回答:資料從哪裡產生、每一層加了什麼資訊、封包如何找到伺服器,以及失敗時應該檢查哪一層。

學習目標

說得清架構

說明ESP32、AP、Router、DNS、Internet、Django與Database的責任。

看得懂封包

分辨MAC、IP、TCP、TLS、HTTP與JSON各自負責的工作。

找得到故障

使用分層診斷,不再把所有問題都歸因於「Wi-Fi不好」。

先看完整路線

感測器/按鈕
ESP32
Wi-Fi AP
Router/NAT
DNS
Internet
Django
Database

回應則沿著已建立的連線反方向返回:Database → Django → HTTPS Response → Internet → Router → ESP32。

五層看懂同一筆資料

應用層HTTP+JSON+REST API定義要送什麼資料、送到哪一個功能入口
安全/傳輸層TLS+TCP加密、建立可靠連線、排序、重送與確認
網路層IP+Routing決定封包從來源IP送往目的IP的路徑
資料連結層Wi-Fi+MAC在目前區域網路的一跳內傳送Frame
實體層2.4GHz無線電波把位元轉成無線訊號,在空氣中傳遞
重要:日常口語常把所有傳輸資料都叫「封包」,但精確來說,應用層是資料、TCP是Segment、IP是Packet、Wi-Fi/Ethernet是Frame。本篇在描述整體旅程時沿用「封包」作為易懂稱呼。

封包出發前:先定義這次事件

以水井三寶智慧互動展覽為例,使用者按下「烏龜故事」按鈕後,ESP32可以把事件整理成JSON。這份JSON是應用層要傳遞的內容,不包含MAC、IP或TCP資訊。

JSON Payload
{
  "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?

保留韌體版本、確認狀態及診斷資訊,方便日後比較不同設備與版本的行為。

八站完成一次封包旅行

01

ESP32加入Wi-Fi,取得網路身分

Association → Authentication → DHCP

ESP32先以STA模式加入無線基地台。連線成功後,通常透過DHCP取得四項重要資訊:

資訊範例用途
本機IP192.168.1.119ESP32在目前LAN中的位址
Subnet Mask255.255.255.0判斷目的地是否位於同一區域網路
Default Gateway192.168.1.1前往其他網路與Internet的出口
DNS Server192.168.1.1或ISP DNS將網域名稱解析成IP位址
檢查證據:序列監控應輸出SSID、本機IP、Gateway、DNS與RSSI。只有顯示 WiFi.status()==WL_CONNECTED,還不能證明Internet或Django已正常。
02

DNS把網域名稱翻譯成IP

Domain Name → DNS Query → Server IP

程式使用的網址可能是:

https://shuijingtreasures.pythonanywhere.com/api/events/

ESP32並不知道這個字串位於世界哪裡,因此會向DNS Server詢問:shuijingtreasures.pythonanywhere.com對應哪一個IP?DNS回覆後,ESP32才能建立後續連線。

診斷觀念:STA=ONLINE只代表取得區域網路連線。若DNS失敗,ESP32仍然無法用網域名稱連到Django。測試時可分別檢查「能否到Gateway」、「能否解析Domain」與「能否連上Server」。
03

ARP找到Gateway的MAC位址

下一跳不是遠端Django,而是本地Router

Django不在同一個Subnet,所以ESP32不會直接找Django的MAC。它先透過ARP找出Default Gateway的MAC位址,再把Wi-Fi Frame交給Router。

Wi-Fi來源MACESP32的MAC
Wi-Fi目的MACAP/Gateway在這一跳使用的MAC
IP來源位址192.168.1.119
IP目的位址DNS解析得到的Django主機IP

每經過一個路由節點,Frame的MAC資訊可能改變;但IP Packet的目的IP仍指向遠端伺服器。

04

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:隨機來源PortServer-IP:443
Internet側Public-IP:轉換後PortServer-IP:443

伺服器回應抵達Router後,Router依NAT表把資料交回原本的ESP32。

05

TCP建立可靠連線

SYN → SYN-ACK → ACK

HTTPS一般建立在TCP之上。ESP32與伺服器先進行三向交握,確認雙方都能收發資料。TCP負責:

可靠性

透過Sequence Number、ACK、逾時與重送,降低資料遺失造成的錯誤。

有序傳輸

即使Segment經過不同路徑或抵達順序不同,也能重新排列成正確資料。

Timeout不一定是Django錯誤:TCP連線建立失敗,可能來自DNS錯誤、Port被阻擋、Server未回應、路由問題或訊號品質不穩。
06

TLS確認伺服器並加密資料

Certificate → Key Exchange → Encrypted Channel

因為網址使用HTTPS,TCP建立後還要進行TLS Handshake。ESP32檢查憑證是否可信、網域是否相符、憑證是否在有效期限內,再協商加密金鑰。

不要把正式系統設成「不驗證憑證」。使用不安全Client或略過憑證驗證,雖可能暫時連線成功,卻失去確認伺服器身分的能力。教學測試與正式部署應清楚區分。
07

HTTP把JSON送進Django API

POST+Header+Body

安全通道建立後,ESP32送出HTTP Request。概念上包含以下內容:

Request LinePOST /api/events/ HTTP/1.1
Hostshuijingtreasures.pythonanywhere.com
Content-Typeapplication/json
AuthorizationBearer DEVICE_TOKEN(範例,依系統設計)
Body{"event_uuid":"...","event_type":"STORY_START",...}

HTTP定義「怎麼送」,JSON定義「送什麼」,REST API定義「送到哪一個功能入口」。

08

Django驗證、處理並寫入Database

URL → View → Validation → Model → Response

Django收到Request後,通常依序進行:

  1. URL Router將 /api/events/交給對應View。
  2. 驗證Method、Content-Type、Token與JSON格式。
  3. 檢查必要欄位、資料型別及event_uuid是否重複。
  4. 使用Model寫入Database。
  5. 回傳JSON與適當HTTP Status Code。
Django View示意
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
        )
成功不只看200:新增資源通常可回傳201 Created;重複事件可回200並標示created:false;格式錯誤用400;未授權用401;伺服器錯誤用500

ESP32送出HTTPS POST的示意程式

以下程式聚焦資料旅程,憑證、Token、逾時、重送與離線Queue仍需依正式系統補齊。不得把真實密鑰直接寫進公開文章或GitHub。

Arduino/ESP32示意
#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
HTTPMethod、Path、Header、Body、Status CodeHTTPClient、Web Server、Django
TLS憑證、加密參數、加密後內容ESP32安全Client與HTTPS伺服器
TCP來源/目的Port、Sequence、ACK兩端作業系統或網路堆疊
IP來源IP、目的IP、TTLESP32、Router及Internet路由器
Wi-Fi Frame本地一跳使用的MAC與錯誤檢查ESP32、AP與區域網路

出錯時,從哪裡開始查?

PowerDeviceWi-FiIPGatewayDNSTCP/TLSHTTPAPIDatabase
現象可能層級應取得的證據
無法取得IPWi-Fi/DHCPSSID、密碼、WiFi狀態、DHCP紀錄
有IP但Domain解析失敗DNSDNS Server、解析結果、其他Domain測試
連線逾時Routing/TCP/FirewallGateway、目的IP、Port 443、Timeout位置
TLS失敗憑證/時間/網域憑證錯誤、系統時間、CA與Host名稱
HTTP 400JSON/API驗證Request Body、Content-Type、Django錯誤回應
HTTP 401/403身分與權限Token、Header、裝置權限與ACL
HTTP 500Django/DatabaseServer Log、Exception、Migration與資料庫狀態
ESP32顯示成功但Dashboard沒資料API/Database/Dashboard QueryResponse、資料表紀錄、查詢條件與時間範圍
工程判斷要用證據:不要只說「網路不穩」或「伺服器壞了」。應記錄失敗在哪一層、看到什麼狀態碼、哪一個測試成功、哪一個測試失敗。

三項學生任務

No-AI|畫出封包旅行圖

標示ESP32、AP、Router、DNS、Internet、Django與Database,並為每段寫出主要協定。

AI Pair|解釋封裝

請AI協助比較Frame、Packet、Segment與HTTP資料,再以自己的話修正與重畫。

Challenge|故障闖關

教師設定錯誤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把事件保存成可分析的資訊。

真正學會網際網路,不是只讓資料送成功,而是能解釋每一站、驗證每一層、修復每一種失敗。

系列定位:《封包旅行記》EP01。適用於大學部網際網路應用、物聯網與智慧生活、ESP32聯網、Django雲端平台及USR場域型專題。範例中的網址、Token、IP與資料內容均為教學示意,正式系統應採安全憑證、環境變數、裝置身分、權限控管與完整異常處理。

沒有留言:

張貼留言