2026年8月15日 星期六

[水井村USR] ESP32 同時當基地台又能上網! 水井三寶智慧互動展覽系統 AP+STA 共存實測

ESP32 同時當基地台又能上網!
水井三寶智慧互動展覽系統 AP+STA 共存實測

ESP32 Arduino SoftAP STA Edge Web IoT USR

在「水井三寶智慧互動展覽系統」的開發過程中,有一個很重要的網路問題: ESP32 能不能一方面提供現場手機連線控制,另一方面又同時連上 Internet?

經過實際測試,答案是:可以。 本次成功讓 ESP32 的 SoftAP 與 STA 同時運作,而且 AP 端的手機控制與 STA 端的 Internet 連線可以共存。

一、為什麼水井三寶故事機需要 AP+STA?

一般 ESP32 IoT 專題常見的做法,是讓 ESP32 連接既有 Wi-Fi,也就是 STA(Station)模式。 但「水井三寶智慧互動展覽系統」的使用情境不同。

展覽現場可能有大量參觀者,而且展場 Internet 並不一定穩定。因此,我們希望故事機即使沒有 Internet, 工作人員仍然可以拿手機直接連上 ESP32,進入內嵌控制網站進行播放、診斷與設備管理。

另一方面,如果展場具有 Internet,ESP32 又應該能將匿名互動事件傳送到遠端 Django 平台, 形成長期的展覽數據。

【參觀者/工作人員手機】

│ Wi-Fi

【ESP32 SoftAP】
Shuijing-Treasures
192.168.4.1

├──── Edge Web 現場控制

└──── ESP32 同時使用 STA
    │
    ▼
【手機熱點/展場 Wi-Fi】
    │
    ▼
   Internet
    │
    ▼
【Django 雲端平台】

二、AP 與 STA 分別做什麼?

模式 用途 水井三寶應用
AP ESP32 自己建立 Wi-Fi 手機直接連入故事機
SoftAP IP ESP32 本地控制網址 192.168.4.1
STA ESP32 連接外部 Wi-Fi 取得 Internet
Internet 連接遠端伺服器 Django 匿名事件平台

三、這次實測成功

本次使用 ESP32 實際測試 AP+STA 共存。 ESP32 一方面建立自己的 Wi-Fi:

SSID:Shuijing-Treasures
Local control:http://192.168.4.1

另一方面,再透過 STA 連接名為 ChiYuan 的外部 Wi-Fi。 實際取得:

STA CONNECTED
STA SSID : ChiYuan
STA IP   : 192.168.1.119
RSSI     : -21 dBm

==============================
SYSTEM READY
==============================

Local control: http://192.168.4.1
Internet: YES

▲ 圖1 ESP32 AP+STA 共存實測。 ESP32 本地 AP 維持 192.168.4.1,同時透過 STA 取得 192.168.1.119,Internet 狀態為 YES。
這張測試畫面證明了一件很重要的事:

ESP32 不需要在「本地控制」與「Internet」之間二選一。
它可以同時提供 192.168.4.1 Edge Web, 又透過另一個 Wi-Fi 介面連上 Internet。

四、連線過程

程式的核心概念其實很簡單:先建立 SoftAP,再使用 WiFi.begin() 建立 STA 連線。

STEP 1|ESP32 建立 SoftAP

if (!WiFi.softAP(AP_SSID, AP_PASSWORD)) {

    Serial.println("SoftAP FAILED");

    while (1) {
        delay(1000);
    }
}

Serial.println("SoftAP OK");

Serial.print("AP IP : ");
Serial.println(WiFi.softAPIP());

成功後,ESP32 本身就是一個 Wi-Fi 基地台。 手機可以搜尋到:

Shuijing-Treasures

連線後即可進入:

http://192.168.4.1

STEP 2|ESP32 再連接 Internet Wi-Fi

WiFi.begin(STA_SSID, STA_PASSWORD);

這時 ESP32 又以 STA 身分連接外部 AP。 程式最多等待 15 秒:

unsigned long start = millis();

while (
    WiFi.status() != WL_CONNECTED &&
    millis() - start < 15000
) {

    Serial.print(".");
    delay(500);
}

STEP 3|確認兩個 IP 同時存在

這是理解 AP+STA 最重要的地方。 ESP32 此時具有兩個不同用途的 IP。

介面 本次實測 IP 功能
SoftAP 192.168.4.1 手機連 ESP32
STA 192.168.1.119 ESP32 連 Internet

五、還能知道有幾台手機連進 ESP32

程式使用:

WiFi.softAPgetStationNum()

取得目前連接 ESP32 AP 的裝置數。 本次測試畫面持續出現:

AP Clients = 1

代表測試當下確實有一台裝置連著 Shuijing-Treasures

六、RSSI 也能成為展覽維護資訊

程式每五秒輸出 STA 狀態:

AP Clients = 1 | STA = ONLINE | IP = 192.168.1.119 | RSSI = -28
AP Clients = 1 | STA = ONLINE | IP = 192.168.1.119 | RSSI = -21
AP Clients = 1 | STA = ONLINE | IP = 192.168.1.119 | RSSI = -29

這些資訊未來可以直接整合進水井三寶 Edge Web 的「設備診斷」頁面, 讓工作人員不必接電腦,就可以從手機知道:

  • AP 是否正常
  • 目前有多少裝置連線
  • STA 是否 ONLINE
  • STA IP
  • Wi-Fi RSSI
  • Internet 是否可用

七、完整 Arduino 測試程式

以下是本次 AP+STA 共存實際測試使用的 Arduino 程式。 正式使用時只需要修改 STA 的 Wi-Fi 名稱與密碼。

#include <Arduino.h>
#include <WiFi.h>
#include <NetworkClient.h>
#include <WiFiAP.h>

// ===============================
// ESP32 AP + Internet STA TEST
// ===============================

// ESP32 本地 AP
const char *AP_SSID = "Shuijing-Treasures";
const char *AP_PASSWORD = "12345678";

// 可上網的 Wi-Fi
// 先用手機熱點測試最容易排除展場網路問題
const char *STA_SSID = "您的手機熱點名稱";
const char *STA_PASSWORD = "您的手機熱點密碼";

NetworkServer server(80);

void setup() {

  Serial.begin(115200);
  delay(1500);

  Serial.println();
  Serial.println("==============================");
  Serial.println("SHUIJING AP + STA TEST");
  Serial.println("==============================");

  // --------------------------------
  // STEP 1
  // 建立 SoftAP
  // --------------------------------

  Serial.println();
  Serial.println("[1] Starting SoftAP...");

  if (!WiFi.softAP(AP_SSID, AP_PASSWORD)) {

    Serial.println("SoftAP FAILED");

    while (1) {
      delay(1000);
    }
  }

  Serial.println("SoftAP OK");

  Serial.print("AP SSID : ");
  Serial.println(AP_SSID);

  Serial.print("AP IP   : ");
  Serial.println(WiFi.softAPIP());


  // --------------------------------
  // STEP 2
  // 建立 STA Internet
  // --------------------------------

  Serial.println();
  Serial.println("[2] Starting STA...");

  WiFi.begin(STA_SSID, STA_PASSWORD);

  unsigned long start = millis();

  while (
    WiFi.status() != WL_CONNECTED &&
    millis() - start < 15000
  ) {

    Serial.print(".");
    delay(500);
  }

  Serial.println();


  // --------------------------------
  // STEP 3
  // 檢查 STA
  // --------------------------------

  if (WiFi.status() == WL_CONNECTED) {

    Serial.println("STA CONNECTED");

    Serial.print("STA SSID : ");
    Serial.println(WiFi.SSID());

    Serial.print("STA IP   : ");
    Serial.println(WiFi.localIP());

    Serial.print("RSSI     : ");
    Serial.print(WiFi.RSSI());
    Serial.println(" dBm");

  } else {

    Serial.println("STA NOT CONNECTED");
    Serial.println("Local AP should still work.");
  }


  // --------------------------------
  // STEP 4
  // 啟動本地 Web Server
  // --------------------------------

  server.begin();

  Serial.println();
  Serial.println("==============================");
  Serial.println("SYSTEM READY");
  Serial.println("==============================");

  Serial.print("Local control: http://");
  Serial.println(WiFi.softAPIP());

  if (WiFi.status() == WL_CONNECTED) {

    Serial.println("Internet: YES");

  } else {

    Serial.println("Internet: NO");
  }
}


void loop() {

  // ===============================
  // 本地 AP Web Server
  // ===============================

  NetworkClient client = server.accept();

  if (client) {

    String currentLine = "";

    while (client.connected()) {

      if (client.available()) {

        char c = client.read();

        if (c == '\n') {

          if (currentLine.length() == 0) {

            client.println("HTTP/1.1 200 OK");
            client.println(
              "Content-Type: text/html; charset=utf-8"
            );
            client.println("Connection: close");
            client.println();

            client.println(
              "<!DOCTYPE html>"
              "<html>"
              "<head>"
              "<meta charset='UTF-8'>"
              "<meta name='viewport' "
              "content='width=device-width,initial-scale=1'>"
              "</head>"
              "<body>"
            );

            client.println(
              "<h1>水井三寶 AP + STA 測試</h1>"
            );

            client.print("<p>AP IP:");
            client.print(WiFi.softAPIP());
            client.println("</p>");

            client.print("<p>STA:");

            if (WiFi.status() == WL_CONNECTED) {

              client.println("CONNECTED</p>");

              client.print("<p>Internet IP:");
              client.print(WiFi.localIP());
              client.println("</p>");

              client.print("<p>RSSI:");
              client.print(WiFi.RSSI());
              client.println(" dBm</p>");

            } else {

              client.println("OFFLINE</p>");
            }

            client.println("</body></html>");

            break;

          } else {

            currentLine = "";
          }

        } else if (c != '\r') {

          currentLine += c;
        }
      }
    }

    client.stop();
  }


  // ===============================
  // STA 斷線診斷
  // ===============================

  static unsigned long lastPrint = 0;

  if (millis() - lastPrint > 5000) {

    lastPrint = millis();

    Serial.print("AP Clients = ");
    Serial.print(WiFi.softAPgetStationNum());

    Serial.print(" | STA = ");

    if (WiFi.status() == WL_CONNECTED) {

      Serial.print("ONLINE");

      Serial.print(" | IP = ");
      Serial.print(WiFi.localIP());

      Serial.print(" | RSSI = ");
      Serial.println(WiFi.RSSI());

    } else {

      Serial.println("OFFLINE");
    }
  }
}

八、為什麼這次測試對「水井三寶」很重要?

這不只是一個 ESP32 Wi-Fi 技術測試,而是決定「水井三寶智慧互動展覽系統」 能不能真正進入展覽現場的一個重要基礎。

第二層|智慧互動層
ESP32+ToF+三按鈕+JQ6500+RGB, 負責故事播放與現場互動。
第三層|Edge Web 層
ESP32 提供 192.168.4.1, 即使 Internet 中斷仍可現場控制。
第四層|Django Data 層
ESP32 透過 STA 與 Internet, 將匿名互動事件送往遠端平台。
內容層|水井三寶網站
白馬、烏龜、姻緣花、三寶故事與創作者資訊, 提供更完整的數位延伸閱讀。

九、最重要的展覽設計原則:Internet 不是故事機的開關

展覽系統不應該因為 Internet 斷線,就讓作品停止說故事。

因此,水井三寶智慧互動展覽系統採取的是 Edge First 的思考方式。

ToF、三個實體按鈕、JQ6500 故事播放、RGB 燈光與 192.168.4.1 本地控制都應該在 ESP32 本機完成。

STA 與 Internet 則是「加值能力」,主要負責匿名事件同步與遠端資料分析。

未來即使展場 Wi-Fi 暫時中斷:

JQ6500        → 繼續播放
三按鈕        → 繼續操作
ToF           → 繼續偵測
RGB           → 繼續互動
192.168.4.1   → 繼續控制

Internet      → 暫時離線
Django Event  → 暫存,恢復後補傳

十、下一步:從「會上網」進入「真正的四層系統」

本次測試已經完成一個重要的技術驗證: ESP32 SoftAP 與 Internet STA 可以穩定共存。

下一階段就可以把這項成果正式整合進「水井三寶智慧互動展覽系統」:

水井三寶作品
      │
      ▼
ESP32 智慧互動層
ToF+Button+JQ6500+WS2812
      │
      ▼
Edge Web
192.168.4.1
      │
      ├──────── 現場手機控制
      │
      ▼
STA / Internet
      │
      ▼
Django
匿名事件資料庫
      │
      ▼
展覽 Dashboard

當地方工藝遇上 ESP32、Edge Web 與 Django, 科技不再只是放在作品旁邊的設備, 而是開始成為地方故事與參觀者之間的互動媒介。

白馬行土地、烏龜守清水、姻緣花牽人情;
三寶同行,水井共生。

沒有留言:

張貼留言