從接線施工到手機控制:
水井三寶 ESP32 擴充底板 × GPIO19 WS2812B × Web 音量控制實作
水井三寶智慧互動展覽系統|Layer 2~Layer 3 實作紀錄
ESP32 × WS2812B × JQ6500 × Edge Web × Realtime Status × Mobile Volume Control
「水井三寶智慧互動展覽系統」在實作過程中,除了要考慮程式能不能執行,更重要的是:
設備真正裝進展覽箱之後,好不好接線?好不好維修?現場人員能不能不用重新燒錄程式,就完成基本設定?
因此,本次系統在原有功能穩定之後,又進行兩項很重要的實務調整:
第一項是為了配合 ESP32 擴充底板與 WS2812B 三線式插頭,重新調整 LED DATA GPIO;
第二項則是在 V1.8 Realtime Web Status 基礎上加入手機音量控制,形成
Layer 4 V1.8.1「Realtime Status+Mobile Volume Control」。
一、為什麼已經可以動了,還要修改GPIO?
早期測試 WS2812B 時,DATA 使用 GPIO13。從程式的角度來看完全沒有問題,
但當系統開始由麵包板實驗走向正式展覽箱施工時,問題就不再只是「GPIO能不能輸出」。
真正的問題變成:哪一支GPIO最適合施工?
- 能不能直接配合擴充底板的三針插座?
- 能不能使用現有 WS2812B 三線插頭?
- 是否會和三顆實體按鈕衝突?
- 是否會影響 JQ6500 UART?
- 日後拆裝與維修是否方便?
二、從 GPIO13 改成 GPIO19
經過重新檢查擴充底板可使用的腳位後,
GPIO19 可以直接配合板上的插座,而且不需要搬動目前已經測試穩定的按鈕與 JQ6500 接腳,
因此最後決定:
WS2812B DATA:GPIO13 → GPIO19
| 功能 |
GPIO |
說明 |
| 白馬按鈕 |
GPIO32 |
維持原接線 |
| 烏龜按鈕 |
GPIO33 |
維持原接線 |
| 姻緣花按鈕 |
GPIO25 |
維持原接線 |
| JQ6500 UART |
GPIO26 / GPIO27 |
已完成穩定測試,不再更動 |
| ToF VL53L0X |
GPIO21 / GPIO22 |
I2C SDA / SCL |
| WS2812B DATA |
GPIO19 |
配合擴充底板三針插座 |
三、程式其實只需要改一個地方
這正是軟硬體模組化的好處。
LED控制邏輯沒有改變,只是將資料輸出的GPIO重新指定。
原來版本
#define LED_PIN 13
新版
#define LED_PIN 19
Adafruit NeoPixel 的初始化方式仍然相同:
#define LED_PIN 19
#define LED_COUNT 61
Adafruit_NeoPixel strip(
LED_COUNT,
LED_PIN,
NEO_GRB + NEO_KHZ800
);
這次修改的重要經驗:
GPIO選擇不能只從「程式能不能跑」來思考。
當作品進入正式施工階段,還要把接頭、底板、線材、維修方式與模組位置一起納入設計。
四、WS2812B 如何接到 ESP32 擴充底板?
WS2812B 基本上需要三條線:
ESP32 擴充底板
GPIO19 / Signal
│
├────────────→ WS2812B DIN
│
5V / VCC
├────────────→ WS2812B +5V
│
GND
└────────────→ WS2812B GND
如果 LED 數量較多,正式展覽系統建議讓 WS2812B 使用獨立 5V 電源,
ESP32只提供 DATA,但兩邊的 GND 必須共地。
ESP32 GPIO19 ─────────→ WS2812B DIN
外接 5V + ───────────→ WS2812B +5V
外接 5V GND ─────┬───→ WS2812B GND
│
ESP32 GND ────────┘
必須共地
施工注意:
三針插頭的線色不能直接當成腳位依據,
一定要確認擴充板的 Signal / VCC / GND,
以及 WS2812B 的 DIN / 5V / GND 順序。
五、61顆WS2812B不是只拿來「發亮」
目前系統將 61 顆 WS2812B 分成不同區域:
#define BASE_START 0
#define BASE_COUNT 9
#define HORSE_START 9
#define HORSE_COUNT 16
#define TURTLE_START 25
#define TURTLE_COUNT 12
#define FLOWER_START 37
#define FLOWER_COUNT 24
| 區域 |
LED數量 |
燈光意義 |
| 底座 |
9 |
系統基礎氛圍 |
| 白馬 |
16 |
行動、力量、向前 |
| 烏龜 |
12 |
水、生態、守護 |
| 姻緣花 |
24 |
祝福、緣分、生命與地方情感 |
尤其姻緣花已由原本的固定色彩改成 HSV 彩虹色,
讓不同 LED 呈現五顏六色的效果,再搭配逐漸綻放與收縮的動畫。
uint16_t hue =
colorOffset +
(65535UL * i / FLOWER_COUNT);
strip.setPixelColor(
FLOWER_START + i,
strip.gamma32(
strip.ColorHSV(
hue,
255,
100
)
)
);
因此燈光不只是裝飾,而是成為展覽系統的一種
視覺回饋介面。
六、第二個問題:展場音量不能每次都重新燒錄
JQ6500 原本的音量是在 Arduino 程式中設定:
uint8_t currentVolume = 20;
JQ6500 的控制範圍設定為:
0 ~ 30
如果要從 20 改成 25 或 30,最簡單的方法當然是修改程式再重新燒錄。
但是到了真正的展覽現場,這種方式非常不方便。
不同場域需要不同音量:
- 安靜教室可能只需要 15~20。
- 兒童館可能需要 20~25。
- 大型展覽空間可能需要更高音量。
- 閉館測試時可能希望立即靜音。
因此 V1.8.1 的設計目標很直接:
讓手機成為水井三寶的音量遙控器。
七、V1.8.1:手機直接控制 JQ6500 音量
手機連上 ESP32 的:
Wi-Fi SSID:
Shuijing-Treasures
↓
瀏覽器
↓
http://192.168.4.1
就可以在 Edge Web 控制頁看到:
🔊 音量控制
0 ─────────── ● ─────────── 30
目前音量:20 / 30
【靜音】 【15】 【20】 【25】 【最大30】
八、HTML使用 Range Slider
Web介面使用 HTML 的 range 元件:
<input
type="range"
id="volumeSlider"
min="0"
max="30"
value="20"
>
這讓手機可以直接拖曳調整:
0
↓
5
↓
10
↓
15
↓
20
↓
25
↓
30
九、手機不是直接控制JQ6500
這一點非常適合拿來作為《網際網路應用》課程教材。
手機調整音量時,真正的資料流程是:
手機瀏覽器
│
│ HTTP Request
▼
ESP32 Edge Web
│
│ /volume?v=25
▼
Web Handler
│
▼
jqSetVolume(25)
│
│ UART Command
▼
JQ6500
│
▼
喇叭音量改變
也就是:
Web介面 → HTTP → ESP32 → UART → JQ6500 → 喇叭
十、Web端如何送出音量設定?
JavaScript 可以使用 fetch():
async function setVolume(v)
{
const r = await fetch(
'/volume?v=' + encodeURIComponent(v),
{
cache:'no-store'
}
);
const d = await r.json();
document.getElementById(
'volumeValue'
).textContent = d.volume;
}
例如使用者選擇 25,瀏覽器送出:
GET /volume?v=25
十一、ESP32如何接收?
ESP32 Web Server 收到 /volume Request 後,
讀取參數並限制在 0~30:
int volume =
server.arg("v").toInt();
if (volume < 0)
{
volume = 0;
}
if (volume > 30)
{
volume = 30;
}
jqSetVolume(
(uint8_t)volume
);
完成後再回傳 JSON:
{
"ok": true,
"volume": 25
}
十二、為什麼回傳JSON,而不是重新整理網頁?
早期 Web 控制很容易採用:
按按鈕
↓
ESP32執行
↓
Redirect
↓
整個網頁重新載入
V1.8.1 則改成:
拖曳音量
↓
fetch()
↓
/volume?v=25
↓
ESP32設定JQ6500
↓
回傳JSON
↓
只更新音量數字
因此操作更接近現代 Web App,
也不會因為每次調整音量就讓整個手機畫面閃動。
十三、V1.8的重要改進:Realtime Web Status
在早期版本中曾經出現一個很有意思的問題:
故事已經播放完畢,ESP32 Serial Monitor 也顯示 PLAY COMPLETE,
但是手機網頁仍然顯示:
PLAYING
原因不是 JQ6500,也不是 ESP32 狀態機,
而是瀏覽器看到的是載入網頁那一刻的狀態。
因此 V1.8 新增:
/api/status
手機每秒讀取一次最新狀態:
setInterval(
updateStatus,
1000
);
十四、Realtime Status與音量控制整合
V1.8.1 再把音量加入 Status JSON。
概念上可以得到:
{
"state":"PLAYING",
"story":"白馬故事",
"jq_busy":true,
"volume":25,
"cloud_ok":18,
"cloud_fail":0
}
因此手機控制頁可以同時知道:
- 現在是不是正在播放。
- 目前播放哪一個故事。
- JQ6500 BUSY 是否有效。
- 目前音量是多少。
- ESP32目前的即時狀態。
十五、播放完成後,手機會自己變化
現在操作流程變成:
手機按下「白馬」
↓
PLAYING
白馬故事
JQ BUSY:ACTIVE
↓
語音播放完成
↓
COOLDOWN
白馬故事
JQ BUSY:IDLE
↓
約2秒
↓
READY / IDLE
待機
使用者不再需要手動按「重新整理」。
十六、為什麼Status Polling不能算成「使用者操作」?
這是 V1.8 開發過程中另一個很重要的系統設計問題。
水井三寶採用:
Local First, Cloud Later
現場互動優先,雲端同步延後。
如果瀏覽器每秒查詢 /api/status,
ESP32每次都把它當成「使用者正在操作」,
Cloud Sync 就可能永遠等不到真正的 Idle 時間。
因此:
void handleStatus()
{
// 不呼叫 markLocalActivity()
...
}
也就是把:
「查詢狀態」
和
「真正操作設備」
分開處理。
十七、V1.8.1的完整控制關係
手機
│
│ Wi-Fi
▼
ESP32 Edge Web
192.168.4.1
│
┌─────────┼─────────┐
│ │ │
▼ ▼ ▼
故事控制 音量控制 即時狀態
/play /volume /api/status
│ │ ▲
▼ ▼ │
ESP32 ────────────────┘
│
├──── UART ───→ JQ6500 ───→ 喇叭
│
├──── GPIO19 ─→ WS2812B
│
├──── I2C ────→ VL53L0X
│
└──── Wi-Fi STA
│
▼
Internet
│
▼
Django / Cloud
十八、從「接腳修改」看工程設計
GPIO13改成GPIO19,看起來只修改了一行程式:
#define LED_PIN 19
但背後其實代表系統已經從:
「麵包板能動」
進入
「正式作品能施工」
再進入
「現場人員能維護」
這也是 IoT 專題很重要的一課:
好的系統不只是功能正確,也要考慮安裝、操作與維護。
十九、從「音量控制」看Edge Web的價值
同樣地,音量原本只是 Arduino 裡的一個變數:
uint8_t currentVolume = 20;
但是加入 Edge Web 之後,
這個變數就成為使用者可以透過手機操作的系統參數。
Arduino Variable
currentVolume
↓
ESP32 Web API
/volume?v=25
↓
HTML / JavaScript
Volume Slider
↓
手機操作介面
這就是 Edge Web 很重要的價值:
把嵌入式系統內部的控制功能,
轉換成一般使用者也能操作的 Web 服務。
二十、從水井三寶學「網際網路應用」
這次修改雖然從「換一支GPIO」和「增加音量滑桿」開始,
但其實可以串起很多《網際網路應用》的重要概念。
| 實作 |
可以學到的概念 |
| GPIO19控制WS2812B |
Embedded I/O、硬體介面、模組化設計 |
| 手機連ESP32 |
SoftAP、Wi-Fi、IP、Client / Server |
| 192.168.4.1 |
Private IP、Edge Web Server |
| /volume?v=25 |
URL、Path、Query Parameter、HTTP GET |
| fetch() |
非同步Web Request |
| JSON Response |
Web資料交換格式 |
| /api/status |
API、Realtime Status、Polling |
| JQ6500 |
UART、裝置控制 |
| WS2812B |
數位燈光與視覺回饋 |
| Django同步 |
Edge → Internet → Cloud |
二十一、版本演進
| 版本 |
主要功能 |
| 早期版本 |
ESP32+JQ6500基本故事播放 |
| Layer 3 |
ESP32 Edge Web手機控制 |
| Layer 4 V1.7 |
AP+STA+Django+Queue+Cloud Sync |
| Layer 4 V1.8 |
Realtime Web Status |
| Layer 4 V1.8.1 |
Realtime Status+Mobile Volume Control+GPIO19 WS2812B施工配置 |
二十二、結語:真正的IoT,是讓實體作品、網路與使用者連在一起
水井三寶智慧互動展覽系統的開發不是一次完成,
而是在實際接線、播放、展示、手機操作與雲端同步的過程中不斷修正。
從 GPIO13 改成 GPIO19,
是為了讓硬體更適合正式施工;
從固定音量改成手機控制,
是為了讓系統更適合真實展覽環境;
從靜態網頁改成 Realtime Status,
則是讓使用者真正看見設備當下的狀態。
文化作品是起點,ESP32負責互動,Edge Web負責控制,
Internet負責連結,Django負責資料,而未來的AI/RAG則負責讓地方知識持續被理解與使用。
這也正是「水井三寶智慧互動展覽系統」作為
大學《網際網路應用》與USR實作教材最重要的價值:
不是只教學生把程式寫出來,而是讓學生從一個真實地方文化作品出發,
一路理解硬體、網路、Web、資料與使用者之間如何形成一個完整系統。
水井三寶
ESP32
GPIO19
WS2812B
JQ6500
Edge Web
Realtime Status
Mobile Volume Control
HTTP
JSON
IoT
Django
USR