從接線施工到手機控制:
水井三寶 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 接腳, 因此最後決定:
| 功能 | 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 基本上需要三條線:
如果 LED 數量較多,正式展覽系統建議讓 WS2812B 使用獨立 5V 電源, ESP32只提供 DATA,但兩邊的 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 的:
就可以在 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"
>
這讓手機可以直接拖曳調整:
九、手機不是直接控制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 控制很容易採用:
V1.8.1 則改成:
因此操作更接近現代 Web App, 也不會因為每次調整音量就讓整個手機畫面閃動。
十三、V1.8的重要改進:Realtime Web Status
在早期版本中曾經出現一個很有意思的問題:
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目前的即時狀態。
十五、播放完成後,手機會自己變化
現在操作流程變成:
使用者不再需要手動按「重新整理」。
十六、為什麼Status Polling不能算成「使用者操作」?
這是 V1.8 開發過程中另一個很重要的系統設計問題。
水井三寶採用:
現場互動優先,雲端同步延後。
如果瀏覽器每秒查詢 /api/status,
ESP32每次都把它當成「使用者正在操作」,
Cloud Sync 就可能永遠等不到真正的 Idle 時間。
因此:
void handleStatus()
{
// 不呼叫 markLocalActivity()
...
}
也就是把:
十七、V1.8.1的完整控制關係
十八、從「接腳修改」看工程設計
GPIO13改成GPIO19,看起來只修改了一行程式:
#define LED_PIN 19
但背後其實代表系統已經從:
這也是 IoT 專題很重要的一課: 好的系統不只是功能正確,也要考慮安裝、操作與維護。
十九、從「音量控制」看Edge Web的價值
同樣地,音量原本只是 Arduino 裡的一個變數:
uint8_t currentVolume = 20;
但是加入 Edge Web 之後, 這個變數就成為使用者可以透過手機操作的系統參數。
這就是 Edge 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, 則是讓使用者真正看見設備當下的狀態。
這也正是「水井三寶智慧互動展覽系統」作為 大學《網際網路應用》與USR實作教材最重要的價值: 不是只教學生把程式寫出來,而是讓學生從一個真實地方文化作品出發, 一路理解硬體、網路、Web、資料與使用者之間如何形成一個完整系統。
水井三寶 ESP32 GPIO19 WS2812B JQ6500 Edge Web Realtime Status Mobile Volume Control HTTP JSON IoT Django USR
沒有留言:
張貼留言