看得見538 Bytes,卻讀不到HTTP:一次珍貴的Ameba SSL封包除錯紀錄
本次測試完成了HUB 8735 Ultra故事機的安全遠端命令、執行結果回報與重新啟動,也找到一個非常隱密的封包問題:SSL回應中只要混入一個NUL(0x00),Arduino的字串解析就可能看似收到資料,實際上卻找不到HTTP狀態列。
一、這一版要完成什麼?
水井三寶故事機先前已具備實體按鈕、手機Web控制、JQ6500播放、STA主要連線、AP故障備援、NTP校時、Flash離線事件佇列與設備心跳。V1.0-13再向前一步:讓管理者可以從Django雲端安全地下達維護命令,設備執行後還必須回報結果。
二、R3.1已經成功,為何後面又失敗?
R3.1第一次證明「心跳夾帶命令」的架構可行。設備成功收到並執行狀態回報:
[HEARTBEAT] Accepted HTTP 200 [REMOTE] Heartbeat received REPORT_STATUS | bba846e1-... [REMOTE] Executed REPORT_STATUS | result queued SUCCESS
但是下一次心跳要把執行結果送回Django時,卻出現:
[HEARTBEAT] POST | state=IDLE | queue=0 [HEARTBEAT] Failed code=0
這不是命令沒有執行,也不是Django拒絕請求,而是RTL8735B端沒有正確解析伺服器回傳的HTTP狀態。
三、最關鍵的線索:538 Bytes與空白Preview
R3.2先改成收完整HTTP Header,並加入原始資料長度診斷。結果出現非常矛盾的訊息:
[HEARTBEAT HTTP] Unparsed bytes=538 | preview= [HEARTBEAT] Failed code=0
HTTP/1.1 200 OK?答案是回應內容最前方混入了NUL,也就是數值為0的位元組:
Arduino的String仍可能把這個0x00算入長度,所以看到538 bytes;但許多字串函式會把NUL視為C字串結尾。於是:
raw.length()仍顯示收到資料。raw.indexOf("HTTP/")可能找不到後面的HTTP狀態列。Serial.println(raw)從第一個NUL就停止,所以Preview看起來完全空白。- HTTP狀態碼最後被判定為0。
四、R3.3如何修正?
R3.3不再把讀到的0x00直接加入HTTP文字緩衝區,而是在SSL讀取階段排除NUL,同時計算排除數量:
int value = client.read();
if (value == 0) {
nulCount++;
} else if (value > 0) {
raw += (char)value;
}
若解析仍失敗,程式還會輸出前32 bytes的HEX資料。這使除錯從「猜測HTTP為何不見」變成「直接觀察真正收到的位元組」。
[HEARTBEAT HTTP] Unparsed bytes=... | NUL filtered=... | preview=... [HEARTBEAT HTTP] HEX: 48 54 54 50 2F 31 2E 31 ...
其中48 54 54 50就是ASCII的HTTP。這種診斷方式未來也適合用於UART、MQTT、Modbus、WebSocket及其他嵌入式通訊問題。
五、R3.3實機測試結果
更新R3.3後,日誌第一次直接證實問題來源:
[HEARTBEAT HTTP] NUL filtered=1 [HEARTBEAT] Accepted HTTP 200 [REMOTE] Result ACK SUCCESS | d7d05b25-...
只是一個NUL,就足以讓前一版完全讀不到HTTP;排除後,命令領取、執行與ACK全部恢復正常。
六種遠端命令測試
| 遠端命令 | 設備動作 | 結果 |
|---|---|---|
| REPORT_STATUS | 立即回報設備狀態 | 成功 |
| SYNC_EVENTS | 立即補送Flash離線事件 | 成功 |
| SYNC_TIME | NTP校時;失敗時切換HTTP時間 | 成功 |
| SET_VOLUME | 音量20調整為16並延遲保存 | 成功 |
| PLAYER_RESET | 重新初始化JQ6500播放器 | 成功 |
| DEVICE_RESTART | 回報成功並保存資料後重新啟動 | 成功 |
六、安全重新啟動不是「收到就重開」
DEVICE_RESTART尤其值得記錄。設備不是一收到命令便立刻重新啟動,而是依序完成:
- 領取具有UUID的重新啟動命令。
- 將執行結果排入下一次心跳。
- 等待Django回傳
X-Ameba-Result-Ack: 1。 - 保存Flash中的統計、音量與佇列資料。
- 最後才執行系統重新啟動。
[REMOTE] Executed DEVICE_RESTART | result queued SUCCESS [HEARTBEAT] Accepted HTTP 200 [REMOTE] Result ACK SUCCESS | 0a815d53-... [FLASH] Batch saved: before remote restart [SYSTEM] Restart scheduled: heartbeat command restart [SYSTEM] Restarting: heartbeat command restart
重新開機後,音量16成功保留,開機提示語音正常播放,STA重新連上Wi-Fi,證明「命令回報、Flash保存、重啟復原」三個環節都通過。
七、Django後台驗證
後台六筆命令全部顯示「成功」,Delivery Attempts均為1,Completed At也有完成時間。這代表:
- 命令沒有因輪詢或網路延遲而重複執行。
- 每筆命令都由相同UUID貫穿領取、執行與結果確認。
- 設備重新啟動前,雲端已收到成功結果。
- 心跳既是健康監控,也是低負擔的安全命令通道。
| 命令類型 | 狀態 | 領取次數 | 完成確認 |
|---|---|---|---|
| 重新啟動設備 | 成功 | 1 | 已記錄 |
| 重設播放器 | 成功 | 1 | 已記錄 |
| 設定音量 | 成功 | 1 | 已記錄 |
| 立即網路校時 | 成功 | 1 | 已記錄 |
| 立即補送事件 | 成功 | 1 | 已記錄 |
| 立即回報狀態 | 成功 | 1 | 已記錄 |
八、這次經驗教會我們什麼?
九、版本結論
V1.0-13 R3.3已完成從「會播放故事的單機」到「可被雲端安全維護的展場設備」的重要跨越。它不只會執行遠端命令,還能辨識命令、避免重複、回報結果、等待ACK、保存狀態,並在必要時安全重啟。
沒有留言:
張貼留言