2026年8月27日 星期四

[水井村USR] 從一顆按鈕到雲端維運:HUB 8735 Ultra樹藝AI故事機的網際網路學習地圖

《網際網路應用實務》補充教材總整理

從一顆按鈕到雲端維運:HUB 8735 Ultra樹藝AI故事機的網際網路學習地圖

把14篇開發紀錄與1個品牌網站,整理成一條從裝置連線、雲端維運,走向電子商務與網路行銷的完整學習路徑。

HUB 8735 UltraJQ6500AP/STAHTTP/HTTPSDjango API電子商務內容行銷水井村USR

一、這些文章為什麼可以成為「網際網路」教材?

這一系列不是單純的開發板操作筆記。它以水井三寶樹藝AI故事機為真實場域,完整呈現資料如何從實體按鈕產生,經由UART與邊緣裝置處理,再透過Wi-Fi、IP、Web、HTTP/HTTPS與Django進入雲端,最後形成設備狀態、使用統計、異常告警與遠端維護。學生看到的不只是名詞,而是每一層協定為何存在、故障會發生在哪裡,以及如何留下可驗證的紀錄。

從裝置、服務到地方品牌的完整路徑

按鈕/感測
Edge Web
Wi-Fi/IP
Django雲端
品牌內容
商城/合作

二、先分類:最適合放進哪些章?

核心章節CH02無線網路、CH03網際網路原理、CH05網頁技術、CH08物聯網、CH09雲端與邊緣運算。
基礎支援CH01用GPIO、UART、Wi-Fi與Client/Server說明裝置、媒介、介面、分層與協定。
品牌與服務CH11電子商務、CH12網路行銷、CH14數位素養,由水井三寶品牌故事、線上閱讀、工藝商城、購物車與合作表單補強。
編排原則:本系列與CH04、CH06、CH07也能產生連結,但不必為了涵蓋14章而勉強歸類;教材應讓真實問題決定所需知識。

三、14篇文章+1個網站 × 教科書章節對照表

文章主要章節最值得教的網路概念定位
第一次燒錄實測:COM埠、LED巨集與Flash ModeCH01、CH04USB序列埠、裝置驅動、開發工具與故障分層;先確認本機連線,再談網路。基礎
V1.0-01:功能鍵控制板載LEDCH01、CH08輸入、處理、輸出的最小物聯網模型;GPIO事件與狀態觀察。基礎
V1.0-02:三個按鈕選擇水井三寶故事CH01、CH08按鍵除彈跳、事件編碼、腳位衝突與可靠輸入;把實體操作轉成資料事件。基礎
microSD卡與三個MP3故事測試CH01、CH08邊緣儲存、檔名規則、資料路徑與本機離線服務。基礎
JQ6500+AP:連得上,為何開機語音反覆重播?CH02、CH08AP區域連線、UART周邊控制、供電與重啟循環;「能連線」不等於「系統穩定」。核心
從Web一開按鈕就停,到雙介面穩定運作CH05、CH08Web Client/Server、非阻塞程式、輪詢、狀態機與多介面資源競爭。核心
展場整合、斷電恢復與LED狀態CH08、CH09UART、BUSY回饋、狀態可視化、持久化與邊緣自治。核心
從故事播放器走向可管理的展場系統CH05、CH08、CH13管理後台、PIN驗證、事件紀錄、Flash保存,以及「功能」走向「服務管理」。整合
從AP/STA衝突走到穩定上線CH02、CH03AP與STA角色、IP配置、路由器、Internet接取、NTP校時及網路模式切換。核心
長按進入AP+自動重啟+網路診斷CH02、CH04、CH13Captive/設定入口概念、故障備援、現場重新配置與可恢復設計。維運
從HTTP 400到HTTP 201:設備身分與Django事件同步CH03、CH05、CH09URL、HTTP POST、JSON、API欄位契約、400與201狀態碼、Client/Server除錯。核心
設備心跳+線上狀態+雲端健康監控CH08、CH09Heartbeat、ONLINE/OFFLINE判定、RSSI、週期傳送、設備遙測與儀表板。雲端
看得見538 Bytes,卻讀不到HTTPCH01、CH03、CH13位元組不等於應用層訊息;TCP資料流、TLS/SSL、HTTP狀態列、NUL位元組與封包解析。除錯
從遠端控制走向自主維運CH09、CH13遠端命令、ACK、診斷快照、健康分數、維護鎖定、最小權限與稽核證據鏈。維運

說明:原始清單中的「HUB 8735 Ultra+JQ6500實測:AP可以連線」網址重複一次,因此共15筆網址、14篇不重複文章;再加入水井三寶品牌網站作為CH11與CH12核心案例。

網站案例主要章節最值得教的網路應用概念定位
水井三寶品牌網站CH05、CH06、CH11、CH12、CH14品牌故事、線上閱讀、智慧學習、商品分類、商品頁、購物車、訂單、合作表單、內容行銷漏斗與數位文化責任。品牌服務

四、建議不要照發表日期教,而要分成七個學習階段

階段1|先讓裝置活起來Flash Mode → LED → 三按鈕。學會COM埠、GPIO、輸入輸出與基本除錯。
階段2|建立邊緣功能microSD → MP3 → JQ6500。理解本機儲存、UART與沒有Internet仍可服務。
階段3|讓手機連進來AP → Edge Web → 實體按鈕與Web雙介面。理解區域網路與Client/Server。
階段4|讓設備上InternetAP/STA切換 → 路由器 → IP → NTP。釐清Wi-Fi連上與Internet可用的差異。
階段5|讓資料進雲端HTTP POST → JSON → Django → 400/201 → Flash離線補送。建立API契約觀念。
階段6|讓系統能長期運作Heartbeat → HTTPS封包除錯 → 告警 → 遠端診斷 → 維護模式與ACK。
階段7|讓地方價值形成服務品牌故事 → 內容體驗 → 工藝商城 → 購物車/訂單 → 合作轉換 → 成效衡量。

五、對應14章的精準歸類

章別可使用的故事機案例建議教學問題
CH01 電腦網路基本概念COM埠、UART、資料事件、Client/Server、封包分層「538 Bytes已收到,為什麼還不能說HTTP成功?」
CH02 無線網路與行動通訊手機連AP、設備連STA、AP/STA衝突、RSSI「Wi-Fi已連線,為什麼仍可能上不了Internet?」
CH03 網際網路原理IP、Gateway、NTP、URL、HTTP狀態碼「192.168.x.x是誰分配的?封包要怎麼離開區域網路?」
CH04 網路連線技術USB設定、展場換分享器、長按回AP、網路診斷「沒有電腦時,設備如何重新取得上網設定?」
CH05 網頁技術與應用Edge Web、按鈕請求、管理頁、Django API「網頁按鈕按下後,命令經過哪些元件?」
CH06 網路資源應用地方故事數位內容、Web分享與儀表板可作成果發布延伸,但不是本系列核心技術章。
CH07 生成式AI應用地方故事整理、圖像與語音內容生成可串接樹藝AI內容創作;需另補AI倫理與查核。
CH08 物聯網與大數據設備事件、狀態機、心跳、RSSI、互動統計「哪些資料是事件、哪些是狀態、哪些值得永久保存?」
CH09 雲端與邊緣運算離線播放、Flash佇列、Django、雲端監控與遠端命令「哪些工作留在HUB,哪些交給Django?」
CH10 科技發展新趨勢邊緣AI、智慧文化展覽作為應用場景延伸,不必牽強連結區塊鏈。
CH11 電子商務水井工藝商城、商品分類與詳細頁、價格、接單狀態、購物車、建立訂單及管理確認「網站有商品與購物車,就算完整電商嗎?金流、物流、售後與交易安全還缺什麼?」
CH12 網路行銷品牌故事、三寶角色、繪本、影音、智慧學習、最新消息、商城與合作表單「使用者如何從認識地方故事,逐步走向互動、信任、購買、合作與分享?」
CH13 資訊安全與網路議題PIN、API Key、HTTPS、維護鎖定、ACK與稽核「能遠端控制設備後,如何避免未授權操作?」
CH14 數位素養地方故事、創作者資訊、個資、肖像、著作權與AI倫理「可以上傳,不代表可以公開;可以生成,不代表可以取代授權。」

六、水井三寶網站如何補強電子商務與網路行銷?

CH11|電子商務流程商品瀏覽 → 商品詳細頁 → 加入購物車 → 建立訂單 → 管理確認 → 製作與交付。學生可以盤點資訊流、商流、金流與物流。
CH12|內容行銷漏斗地方故事 → 角色與繪本 → 智慧學習/影音 → 工藝作品 → 商城/合作表單,呈現從認識到轉換的完整路徑。
CH14|責任與信任地方記憶、創作者、肖像、著作權、個資、訂單資料及AI協作內容,都需要授權、告知、查核與妥善保護。

電子商務不是只有購物車

商品資訊
購物車
建立訂單
付款確認
製作/出貨
售後服務

目前網站適合定位為「地方型電子商務原型」:商品、價格、接單狀態、購物車與訂單流程已能觀察;線上金流、配送追蹤、退換貨、會員與交易安全則可成為下一版需求分析。

網路行銷不是只投廣告

認識 Awareness
興趣 Interest
互動 Engagement
信任 Trust
轉換 Conversion
分享 Advocacy

網站先用柴井車與水井三寶建立文化認識,再以繪本、影音和智慧學習形成互動,最後導向工藝商城與合作表單;這正是「故事行銷+內容行銷+體驗行銷」的地方品牌案例。

七、同一套教材,如何區隔專一與大一?

面向專一:看得懂、做得到、用得安全大一:說得清、能分析、整合成作品
連線依步驟完成Flash、AP連線與Web操作。畫出AP、STA、路由器、Django與手機之間的拓樸並解釋資料流。
協定能辨認GPIO、UART、Wi-Fi、HTTP各自用途。能用分層觀點說明「收到TCP Bytes」與「正確解析HTTP」的差異。
除錯依檢查表確認電源、接線、IP與狀態碼。根據Log提出假設、設計交叉測試,判斷故障位於裝置、網路或應用層。
成果完成可操作的故事播放與手機控制。完成含Django API、離線補送、心跳與安全維護的可管理系統。
電商/行銷完成一次商品瀏覽、加入購物車與訂單流程,辨認品牌故事與購買入口。分析電子商務四流與內容行銷漏斗,提出可量測的網站改版方案。
表達繳交操作照片、接線表與測試結果。繳交架構圖、API規格、狀態碼分析、維運情境與改版紀錄。

八、可直接使用的八個課堂任務

  1. 畫出資料旅行圖:從按下白馬按鈕開始,標出GPIO、UART、JQ6500、Web、Wi-Fi與Django可能經過的路徑。
  2. 比較AP與STA:說明手機為何能連上故事機卻不一定能上Internet,以及STA取得IP後Gateway與DNS的用途。
  3. 讀懂HTTP狀態碼:比較200201400401與逾時,決定設備應重試、丟棄或保留事件。
  4. 建立API契約:替故事事件定義device_idevent_uuidevent_type與時間欄位,說明必填與驗證規則。
  5. 分層除錯:針對「Web可開但沒有聲音」「有538 Bytes但無HTTP」「ONLINE卻無事件」各提出檢查順序。
  6. 設計可維護系統:決定心跳週期、OFFLINE門檻、維護模式允許的命令,以及必須保留的現場停止功能。
  7. 盤點電子商務四流:操作水井工藝商城,標出資訊流、商流、金流與物流目前已完成及仍待補充的環節。
  8. 建立內容行銷漏斗:將品牌故事、角色、繪本、影音、智慧學習、商品頁與合作表單分別放入認識、互動、信任、轉換與分享階段。

九、建議的學習成果與評量

25%|能連線完成裝置燒錄、AP/STA設定、Web操作與基本IP檢查。
25%|能解釋正確說明Client/Server、UART、HTTP、JSON、狀態碼與Edge/Cloud分工。
20%|能除錯以Log與交叉測試定位問題,提出證據,不用猜測取代驗證。
20%|能轉化把地方故事轉為內容體驗、商品服務與可量測的行銷漏斗。
10%|能負責處理密碼、API Key、個資、訂單資料、著作權、設備安全與維運紀錄。

十、這套教材真正要教的,不是一塊板子,也不只是一個網站

HUB 8735 Ultra只是載體,水井三寶故事機才是真實問題。學生最值得學會的是:如何把地方需求拆成可驗證的輸入、狀態、通訊、API、資料與維運機制;如何在失敗時讀Log、分層定位;以及如何把一次實作整理成下一位學生可以搜尋、閱讀、操作與再利用的教材。

這條路徑也呈現了網際網路課程的轉變:從背誦協定名稱,走向看見封包如何移動;從做出一次能跑的作品,走向建構能離線、能上線、能回報、能復原、能安全維護的服務;再從一項技術作品,走向可被認識、體驗、購買、合作與持續經營的地方品牌。地方是教室,系統與網站都是教材,而每一次除錯與每一次使用者轉換,都是理解網際網路應用的入口。

教材定位:搭配《網際網路應用實務》使用。共同核心一致,但依學習階段採不同鷹架、任務與成果深度;專一以「看得懂、做得到、用得安全」為主,大一以「說得清、能分析、整合成作品」為主。

2026年8月26日 星期三

[水井村USR] 從遠端控制走向自主維運:異常告警、雲端診斷與安全維護模式實測

水井三寶故事機|V1.0-14

從遠端控制走向自主維運:異常告警、雲端診斷與安全維護模式實測

HUB 8735 Ultra+JQ6500+Django,不只會播放地方故事,更能自行回報健康、接受安全維護命令,並在展場端保留必要的實體停止能力。

100設備健康分數
GOOD健康等級
-27 dBmWi-Fi RSSI
0離線待送事件

前一版 V1.0-13 已完成安全遠端命令、執行結果回報與逾時保護;V1.0-14 再向真正的展場自主維運前進。這次測試不是只確認「雲端按鈕能不能動」,而是驗證設備能否回答三個更重要的問題:設備現在健康嗎?需要維護時能否安全鎖定?維護結束後能否立即恢復服務?

一、V1.0-14增加了什麼?

功能用途展場價值
設備健康分數依網路、播放器、事件佇列、復原次數與告警計算健康狀態管理者不必到現場逐台檢查
異常告警將故障或維護狀態同步到Django儀表板快速辨認需要處理的設備
RUN_DIAGNOSTIC由雲端要求設備產生完整診斷快照遠端取得韌體、RSSI、按鈕、BUSY及JQ6500狀態
MAINTENANCE_ON進入維護模式並阻擋一般播放與音量命令避免維修時被觀眾誤觸啟動
MAINTENANCE_OFF解除維護鎖定,恢復故事播放服務完成維護後不必重新燒錄程式
復原頻率限制限制單位時間內的播放器自動復原次數防止故障設備陷入無限重置

二、雲端儀表板已成為展場維運中心

Django儀表板同時呈現展覽互動成果與設備健康資訊。管理者可以看見故事啟動、完整播放率、設備在線狀態、健康分數、健康等級、告警數、復原層級、最新診斷,以及最近一筆遠端命令結果。


圖:SHUIJING-002在線,健康分數100、等級GOOD、告警0;下方可直接建立V1.0-14自主維運命令。
發表圖片提醒:ChatGPT工作區圖片不是公開網址。請在Blogger編輯器上傳本次儀表板截圖,取得Blogger圖片網址後,把本文唯一的 BLOGGER_IMAGE_URL 換掉,圖片就能穩定顯示。

三、第一階段:遠端完整診斷成功

我們先在Django後台建立 RUN_DIAGNOSTIC 命令。設備透過Heartbeat領取命令後,立即產生診斷快照,再把執行結果送回雲端。

[REMOTE] Heartbeat received RUN_DIAGNOSTIC
[DIAGNOSTIC] fw=V1.0-14,health=100/GOOD,state=IDLE,
track=0,vol=16,rssi=-27,queue=0,jq=0,busy=0,
btn=111111,alert=NONE
[REMOTE] Executed RUN_DIAGNOSTIC | result queued SUCCESS
[REMOTE] Result ACK SUCCESS

診斷資料如何解讀?

health=100/GOOD設備健康,沒有需要立即處理的異常。
state=IDLE故事機處於待機狀態。
vol=16JQ6500目前音量為16。
rssi=-27Wi-Fi訊號非常良好。
queue=0沒有尚未同步的離線事件。
jq=0本次運作尚未發生JQ6500復原。
busy=0播放器沒有正在播放。
btn=111111六個按鈕皆為釋放狀態,沒有腳位異常拉低。
alert=NONE目前沒有作用中的設備告警。

真正關鍵的不是看到 Executed,而是最後收到 Result ACK SUCCESS。這代表「雲端建立命令、設備領取、設備執行、結果回送、伺服器確認」五個環節全部完成。

四、第二階段:從雲端開啟維護模式

Django建立命令
Heartbeat領取
設備啟用鎖定
結果ACK確認
[REMOTE] Heartbeat received MAINTENANCE_ON
[MAINTENANCE] Enabled by cloud
[REMOTE] Executed MAINTENANCE_ON | result queued SUCCESS
[REMOTE] Result ACK SUCCESS

維護模式不是關閉整台設備,而是選擇性鎖定具有風險的操作。故事播放及音量調整會被拒絕,但停止功能仍保留,讓現場人員遇到播放器異常時仍能立即處理。

維護模式中的操作處理結果設計理由
故事按鈕拒絕防止維修時意外啟動音訊
Web故事播放拒絕避免遠端使用者干擾現場維護
音量+/-拒絕維持檢修時的固定條件
停止按鈕保留確保現場仍可立即停止播放器
遠端診斷保留維護期間仍需觀察設備狀態
解除維護保留讓雲端可以安全恢復服務

五、實體按鈕真的被鎖住了嗎?

進入維護模式後,我們分別按下GPIO11烏龜故事與GPIO10白馬故事,系統確實讀到按鍵,但沒有啟動JQ6500:

[BUTTON] Pressed GPIO11
[MAINTENANCE] Story command rejected
[BUTTON] Pressed GPIO10
[MAINTENANCE] Story command rejected

接著按下GPIO20停止鍵,系統仍然接受停止操作;因為當時播放器原本就在待機,所以正確回報:

[BUTTON] Pressed GPIO20
[STOP] Ignored: already idle

這個結果十分重要:維護模式沒有讓整個按鍵掃描停止,而是由命令層判斷哪些動作可以執行。設備仍持續掃描實體輸入,因此不會重演早期版本「網路工作時按鈕失去反應」的問題。

六、解除維護後立即恢復播放

[REMOTE] Heartbeat received MAINTENANCE_OFF
[MAINTENANCE] Disabled by cloud
[REMOTE] Executed MAINTENANCE_OFF | result queued SUCCESS
[REMOTE] Result ACK SUCCESS

解除維護後再次按下GPIO10,白馬故事成功播放,雲端事件也正常寫入本機佇列:

[BUTTON] Pressed GPIO10
[CLOUD QUEUE] + SHUIJING-002-G68F044D7-0000000033
| STORY_START | pending=1
[JQ] Play track 1
[STATE] STORY_PLAYING | 白馬故事
[BUSY] 0 -> 1 | idle=0

這表示解除維護不需要重新開機,也不需要重新燒錄程式;實體按鈕、JQ6500播放、BUSY狀態與雲端事件佇列一起恢復正常。

七、為什麼一直看到NUL filtered=1?

[HEARTBEAT HTTP] NUL filtered=1
[HEARTBEAT] Accepted HTTP 200

這不是錯誤。實測發現Ameba SSL接收的HTTP封包偶爾會夾帶一個 0x00 NUL位元組。早期版本會因此無法解析HTTP狀態列或JSON;現在程式會先過濾NUL,再解析完整回應。

判讀原則:只要NUL過濾後緊接著出現 Accepted HTTP 200,就代表封包已修正並成功處理。這一行是防護機制的工作紀錄,不是通訊失敗。

八、這次完整測試結果

  • STA網路連線與HTTPS Heartbeat正常。
  • Django成功送出RUN_DIAGNOSTIC命令。
  • 設備回傳完整健康診斷快照。
  • 命令執行結果收到雲端ACK。
  • MAINTENANCE_ON成功啟用。
  • 維護期間實體故事按鈕受到阻擋。
  • 維護期間停止按鈕仍可使用。
  • MAINTENANCE_OFF成功解除鎖定。
  • 解除維護後白馬故事成功播放。
  • STORY_START事件成功進入離線保護佇列。
  • JQ6500 BUSY腳位正確由0切換至1。
  • 設備健康分數100、等級GOOD、告警NONE。
實測結論:V1.0-14核心功能全部通過。

水井三寶故事機已從「可以被遠端操作的播放器」,進一步成為「可以自我回報、接受安全維護、保留現場控制並完成雲端稽核」的展場智慧設備。

九、這次最寶貴的工程經驗

展場設備的可靠性,不只取決於功能多不多,而是發生異常時能不能被看見、被限制、被診斷、被恢復。V1.0-14建立的健康分數、異常告警、維護鎖定、遠端診斷與結果ACK,形成一條完整的維運證據鏈。

感知設備健康
雲端建立命令
邊緣安全執行
回報並完成稽核

這也讓地方故事、樹藝作品與智慧展覽不再只是一次性的展示,而是可以長期運作、跨場域部署並由遠端團隊共同維護的數位文化服務。

測試平台:HUB 8735 Ultra、JQ6500、實體按鈕、LED、STA Wi-Fi、HTTPS、Django/PythonAnywhere。版本:V1.0-14「展場自主維運+異常告警+遠端診斷版」。

2026年8月25日 星期二

[水井村USR] 看得見538 Bytes,卻讀不到HTTP:一次珍貴的Ameba SSL封包除錯紀錄

水井三寶故事機|V1.0-13 R3.3

看得見538 Bytes,卻讀不到HTTP:一次珍貴的Ameba SSL封包除錯紀錄

本次測試完成了HUB 8735 Ultra故事機的安全遠端命令、執行結果回報與重新啟動,也找到一個非常隱密的封包問題:SSL回應中只要混入一個NUL(0x00),Arduino的字串解析就可能看似收到資料,實際上卻找不到HTTP狀態列。

最終結果:六種遠端命令全部成功,Django後台均顯示「成功」,每筆命令只領取一次;設備重新啟動後,開機語音也正常播放。

一、這一版要完成什麼?

水井三寶故事機先前已具備實體按鈕、手機Web控制、JQ6500播放、STA主要連線、AP故障備援、NTP校時、Flash離線事件佇列與設備心跳。V1.0-13再向前一步:讓管理者可以從Django雲端安全地下達維護命令,設備執行後還必須回報結果。

Django建立命令
心跳領取命令
8735執行命令
結果ACK後結案
安全領取命令包含Device ID、API Key、UUID與逾期時間。
只執行一次透過命令UUID避免同一指令被重複執行。
結果可追蹤Django必須收到SUCCESS或FAILED,才能將命令結案。

二、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
問題焦點:如果完全沒有收到資料,長度應該是0;現在明明收到538 bytes,為何預覽是空白,而且找不到HTTP/1.1 200 OK

答案是回應內容最前方混入了NUL,也就是數值為0的位元組:

第1 byte00 NUL控制字元
後續文字HTTP/1.1 200 OK
自訂HeaderX-Ameba-Result-Ack: 1
JSON Body{"ok":true,...}

Arduino的String仍可能把這個0x00算入長度,所以看到538 bytes;但許多字串函式會把NUL視為C字串結尾。於是:

  • raw.length()仍顯示收到資料。
  • raw.indexOf("HTTP/")可能找不到後面的HTTP狀態列。
  • Serial.println(raw)從第一個NUL就停止,所以Preview看起來完全空白。
  • HTTP狀態碼最後被判定為0。
這次最重要的除錯觀念:「收到幾個bytes」不等於「收到可直接當成文字處理的bytes」。網路除錯不能只看字串,也要保留位元組、控制字元與十六進位的觀點。

四、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_TIMENTP校時;失敗時切換HTTP時間成功
SET_VOLUME音量20調整為16並延遲保存成功
PLAYER_RESET重新初始化JQ6500播放器成功
DEVICE_RESTART回報成功並保存資料後重新啟動成功

六、安全重新啟動不是「收到就重開」

DEVICE_RESTART尤其值得記錄。設備不是一收到命令便立刻重新啟動,而是依序完成:

  1. 領取具有UUID的重新啟動命令。
  2. 將執行結果排入下一次心跳。
  3. 等待Django回傳X-Ameba-Result-Ack: 1
  4. 保存Flash中的統計、音量與佇列資料。
  5. 最後才執行系統重新啟動。
[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已記錄

八、這次經驗教會我們什麼?

1. HTTP仍是Bytes即使最終看到的是文字協定,底層傳輸仍可能包含控制字元。
2. 長度不能代表內容538 bytes可能以NUL開頭,字串函式看到的卻是空字串。
3. 要有HEX診斷文字預覽失效時,十六進位輸出是最可靠的證據。
4. ACK後才能破壞性操作重啟前先回報與保存,才能避免雲端永遠停在「已領取」。
5. UUID保證冪等同一命令不因重送而執行兩次,對展場設備非常重要。
6. 現場功能優先網路同步與心跳都不能阻塞實體按鈕及故事播放。

九、版本結論

V1.0-13 R3.3已完成從「會播放故事的單機」到「可被雲端安全維護的展場設備」的重要跨越。它不只會執行遠端命令,還能辨識命令、避免重複、回報結果、等待ACK、保存狀態,並在必要時安全重啟。

本次最珍貴的發現:真正讓系統卡住的,不是538 bytes的大問題,而是藏在最前面、肉眼看不見的1 byte——NUL(0x00)。物聯網系統的可靠性,常常就建立在願不願意把「看似空白」繼續追查到底。

[水井村USR] 設備心跳+線上狀態+雲端健康監控版:讓故事機自己回報「我還正常運作」

樹藝AI故事機|V1.0-12

設備心跳+線上狀態+雲端健康監控版:讓故事機自己回報「我還正常運作」

V1.0-11已完成設備身分、Django事件同步與Flash離線佇列;V1.0-12再向前一步,加入低優先權設備心跳,讓管理者從雲端儀表板確認每台故事機是ONLINE或OFFLINE,以及目前狀態與韌體版本。

60秒自動心跳週期
HTTP 200Django更新成功
ONLINE雲端即時判定

一、為什麼故事機需要「心跳」?

故事播放正常,不代表管理者隨時知道設備是否健康。故事機部署在社區、展場或兒童館後,可能遇到斷電、分享器失效、Wi-Fi中斷、程式重啟或設備長時間沒有回報等狀況。

若沒有心跳,雲端只能看到「最後一筆故事事件」,卻無法判斷設備是安靜待機,還是已經離線。V1.0-12因此讓HUB 8735 Ultra每隔60秒主動向Django報到。

1故事機待機
2檢查STA與時間
3確認事件佇列為0
4送出設備心跳
5Django更新ONLINE

二、V1.0-12心跳包含哪些資訊?

心跳不是故事事件,不需要永久保存在Flash。它是一份設備當下的健康摘要:

{
  "device_id": "SHUIJING-002",
  "state": "IDLE",
  "firmware": "V1.0-12",
  "rssi": -24,
  "uptime": 3600,
  "volume": 16,
  "queue_count": 0,
  "current_track": 0,
  "jq_recoveries": 0,
  "last_cloud_http": 201,
  "last_error": ""
}
欄位用途管理意義
device_id設備唯一編號區分不同展場與故事機
stateIDLE、PLAYING或ERROR知道設備正在待機、播放或異常
firmware韌體版本確認設備是否完成升級
rssiWi-Fi訊號強度找出連線不穩的場域
queue_count尚未同步事件數判斷Django或網路是否阻塞
jq_recoveries播放器復原次數觀察JQ6500長期穩定性

三、心跳不能影響故事播放

展場系統最重要的仍是使用者操作。因此V1.0-12把心跳放在最低優先層級:

  1. 實體按鈕與停止操作。
  2. JQ6500播放與BUSY狀態機。
  3. 手機Web控制。
  4. Flash離線事件補送。
  5. 設備心跳。
設計原則:只要播放器不是IDLE、BUSY仍在播放、離線事件尚未補完,或使用者突然按下按鈕,心跳就延後或取消。心跳失敗也不寫入Flash,避免累積大量沒有長期保存價值的資料。

四、開機測試:先送重要事件,再送心跳

V1.0-12沿用V1.0-11的Flash資料結構,開機後成功恢復音量、STA設定、事件序號與佇列:

[FLASH] V1.0-11 data restored
[FLASH] Volume = 16
[FLASH] Boot count = 54
[CLOUD] Device ID: SHUIJING-002
[CLOUD] Offline queue: 0

接著建立本次開機事件:

[CLOUD QUEUE] + SHUIJING-002-0000000016 | DEVICE_BOOT | pending=1

STA與NTP完成後,系統先把重要的DEVICE_BOOT送到Django:

[TIME] Synced by NTP | 2026-08-25 10:44:06
[MODE] STA online service ready
[CLOUD] POST SHUIJING-002-0000000016 | pending=1
[CLOUD] Accepted HTTP 201 | remaining=0

只有在事件佇列回到0以後,才開始傳送心跳:

[HEARTBEAT] POST | state=IDLE | queue=0
[HEARTBEAT] Accepted HTTP 200
這個順序非常重要:HTTP 201代表開機事件已建立;HTTP 200代表設備健康資料已更新。事件資料不會被頻繁心跳搶在前面。

五、連續心跳實測

序列監控顯示,故事機約每分鐘傳送一次心跳,而且每次均獲得Django的HTTP 200:

HUB 8735 Ultra V1.0-12每分鐘設備心跳均獲得HTTP 200
圖1:10:45至10:49連續心跳測試,每分鐘均成功回傳HTTP 200。
10:46:07 [HEARTBEAT] POST | state=IDLE | queue=0
10:46:09 [HEARTBEAT] Accepted HTTP 200
10:47:07 [HEARTBEAT] POST | state=IDLE | queue=0
10:47:09 [HEARTBEAT] Accepted HTTP 200
10:48:07 [HEARTBEAT] POST | state=IDLE | queue=0
10:48:09 [HEARTBEAT] Accepted HTTP 200
10:49:07 [HEARTBEAT] POST | state=IDLE | queue=0
10:49:09 [HEARTBEAT] Accepted HTTP 200

從紀錄可觀察到每次HTTPS請求約在兩秒內完成;下一次心跳不會緊接著大量重複送出,而是依照週期執行。

六、Django儀表板成功判斷ONLINE

Django以設備最後收到心跳的時間判斷線上狀態。實測畫面中,舊設備SHUIJING-001因長時間沒有心跳而顯示OFFLINE;目前測試設備SHUIJING-002則顯示ONLINE,並正確呈現IDLE / V1.0-12

Django水井三寶儀表板顯示SHUIJING-002為ONLINE且韌體為V1.0-12
圖2:雲端儀表板的裝置健康區,能同時比較不同設備的ONLINE/OFFLINE狀態。

儀表板實測結果

  • SHUIJING-001:OFFLINE,顯示舊韌體資訊。
  • SHUIJING-002:ONLINE,狀態IDLE,韌體V1.0-12。
  • 故事啟動:6次。
  • 完整播放:6次。
  • 播放完成率:100%。
  • 互動來源:實體按鈕5次、Edge Web 1次。

七、Django如何判斷設備是否在線?

API收到心跳後更新last_seenlast_statefirmware_versionlast_rssi。儀表板再依最後回報時間判定設備狀態:

online_cutoff = timezone.now() - timedelta(minutes=10)

online = bool(
    device.last_seen and
    device.last_seen >= online_cutoff
)

正式展場可依網路品質調整門檻。若心跳每60秒一次,常見做法是:

最後心跳距今建議狀態管理判讀
2分鐘內ONLINE設備正常
2至5分鐘WARNING可能網路不穩
超過5分鐘OFFLINE需要遠端或現場檢查

八、HTTP狀態碼如何判讀?

紀錄意義處理方式
HTTP 200心跳更新成功設備維持ONLINE
HTTP 201事件建立成功從Flash佇列移除該事件
HTTP 400JSON或欄位不合API規則查看Django回應本文
HTTP 401設備驗證失敗檢查Device ID、API Key與啟用狀態
-1/-2連線或等待逾時檢查STA、DNS、TLS與伺服器
-3心跳被現場操作取消正常設計,稍後自動再試

九、V1.0-12驗收結果

  • V1.0-11的Flash設定與統計成功延續。
  • STA連線與NTP自動校時正常。
  • DEVICE_BOOT取得HTTP 201並從佇列移除。
  • 事件佇列為0後才開始傳送心跳。
  • 連續多次心跳均取得HTTP 200。
  • Django正確顯示SHUIJING-002為ONLINE。
  • Django正確顯示IDLE與V1.0-12。
  • 不同設備可以同時顯示ONLINE與OFFLINE。
  • 心跳不寫入Flash,不增加Flash耗損。
  • 按鈕、播放、停止、Web與事件補送仍具有較高優先權。

十、從「會播放」走向「可維護」

早期的故事機只要按下按鈕會播放,就可以稱為功能完成。但真正進入社區與展場後,管理者需要知道:設備是否在線、使用哪一版韌體、Wi-Fi訊號是否穩定、離線事件是否堆積,以及播放器是否經常復原。

V1.0-12加入的不是一個華麗功能,而是一項非常重要的營運能力:讓設備主動說明自己的健康狀況。這也是IoT系統從作品原型走向可管理、可維護及可擴充服務的重要分界。

[水井村USR] 從HTTP 400到HTTP 201:HUB 8735 Ultra設備身分、Django同步與離線佇列實測

樹藝AI故事機|V1.0-11

從HTTP 400到HTTP 201:HUB 8735 Ultra設備身分、Django同步與離線佇列實測

這次測試讓「水井三寶故事機」跨出重要一步:故事不只在現場播放,還能以設備身分將匿名互動事件送到Django;網路或API暫時異常時,資料先留在Flash,待問題排除後再依序補送。

SHUIJING-002獨立設備身分
Flash Queue離線事件保存
HTTP 201Django接收成功

一、V1.0-11要解決什麼問題?

前一版已完成實體按鈕、手機Web控制、JQ6500播放、STA主要連線、AP故障備援及NTP校時。但展場系統若要長期營運,還必須回答三個問題:

  1. 是哪一台故事機送出的資料?每台設備必須有獨立Device ID與API Key。
  2. 斷網時互動紀錄會不會消失?事件必須先寫入本機Flash。
  3. 恢復連線後如何補送?佇列必須按照事件順序逐筆送往Django。
實體按鈕/手機Web
HUB 8735事件建立
Flash離線佇列
HTTPS+設備金鑰
Django事件資料庫

二、設備身分與事件格式

本次測試設備編號為:

[CLOUD] Device ID: SHUIJING-002

韌體送出請求時,以Header攜帶設備身分與個別金鑰。正式API Key不應出現在部落格、序列紀錄或公開程式庫。

POST /api/exhibition/events/ HTTP/1.1
Content-Type: application/json
X-Device-ID: SHUIJING-002
X-Device-Key: ********

每一筆事件都有唯一的event_uuid,例如:

SHUIJING-002-0000000014

如此即使網路重試,也能由Django利用UUID避免同一筆事件被重複建立。

三、第一次上線:本機正常,雲端卻回傳400

故事機能連上STA、取得IP、完成NTP校時,按鈕與Web也都能播放故事,但離線事件始終停留在佇列:

[TIME] Synced by NTP | 2026-08-25 09:29:21
[MODE] STA online service ready
[CLOUD] POST SHUIJING-002-0000000001 | pending=1
[CLOUD] Failed code=400
重要判斷:HTTP 400不代表Wi-Fi失敗。它表示HUB 8735已成功連到Django,但Django認為送來的內容不符合API規則。

當時韌體只顯示狀態碼,無法知道伺服器拒絕哪個欄位。因此加入「安全截取Django回應本文」診斷,並限制最大長度,避免錯誤頁占用過多記憶體。

[CLOUD] Failed code=400
[CLOUD] Django response: {"ok": false, "error": "invalid event_type"}

這一行就是整次除錯的關鍵證據:網路、TLS、網址、設備驗證都已經通過,真正問題是Arduino與Django使用了不同的事件名稱

四、找出兩端的事件名稱差異

原Arduino事件Django模型事件最後處理
SYSTEMDEVICE_BOOTArduino改送DEVICE_BOOT
STORY_STARTSTORY_START保持不變
STORY_ENDSTORY_COMPLETEArduino改送STORY_COMPLETE
STORY_INTERRUPTEDSTOPSTOP表示中止,利用story與completed區分
STOPSTOP保持不變

Arduino最後使用的轉換函式如下:

const char *cloudEventTypeName(uint8_t type)
{
    if (type == CLOUD_STORY_START) return "STORY_START";
    if (type == CLOUD_STORY_END) return "STORY_COMPLETE";
    if (type == CLOUD_STORY_INTERRUPTED) return "STOP";
    if (type == CLOUD_STOP) return "STOP";
    if (type == CLOUD_SYSTEM) return "DEVICE_BOOT";
    return "DEVICE_ERROR";
}

五、Django端實際採取的修正

本次正式測試沒有更換views.py,只調整models.py的事件選項,使其與韌體送出的正式名稱一致。原View原本就透過Model動態檢查:

if event_type not in dict(ExhibitionEvent.EVENT_TYPES):
    return None, "invalid event_type"

因此Model選項更新後,View會自動採用新選項,不必再維護第二份事件清單。這也避免Model與View日後再次出現名稱不一致。

EVENT_TYPES = [
    ("DEVICE_BOOT", "設備啟動"),
    ("EXHIBIT_WAKE", "展區喚醒"),
    ("STORY_START", "故事開始"),
    ("STORY_COMPLETE", "故事完成"),
    ("PLAY_TIMEOUT", "播放逾時"),
    ("STOP", "停止播放"),
    ("DEVICE_ERROR", "設備錯誤"),
    ("QR_OPEN", "QR延伸閱讀"),
]

六、為什麼舊的10筆事件不用清除?

這次設計最有價值的地方,是Flash佇列保存的不是最後的JSON文字,而是精簡的事件型別代碼、故事編號、來源、時間與序號。真正準備上傳時,才由cloudEventTypeName()轉成目前的正式名稱。

因此更新韌體後,原本保存在Flash裡的舊CLOUD_SYSTEM事件,送出時會自動變成DEVICE_BOOT;舊CLOUD_STORY_END則會變成STORY_COMPLETE。資料不必刪除,也不必重新製造。

重新開機後的證據

[FLASH] V1.0-11 data restored
[CLOUD] Offline queue: 10
[CLOUD QUEUE] + SHUIJING-002-0000000011 | DEVICE_BOOT | pending=11

前10筆失敗事件仍然存在;重新開機又新增第11筆設備啟動事件,證明Flash恢復與事件序號持續運作。

七、HTTP 201:離線事件開始依序補送

修正事件名稱後,從管理後台按下「立即補送」,Django開始接受事件:

[CLOUD] POST SHUIJING-002-0000000001 | pending=13
[CLOUD] Accepted HTTP 201 | remaining=12
[CLOUD] POST SHUIJING-002-0000000002 | pending=12
[CLOUD] Accepted HTTP 201 | remaining=11
[CLOUD] POST SHUIJING-002-0000000003 | pending=11
[CLOUD] Accepted HTTP 201 | remaining=10

後續事件繼續補送:

[CLOUD] POST SHUIJING-002-0000000007 | pending=9
[CLOUD] Accepted HTTP 201 | remaining=8
[CLOUD] POST SHUIJING-002-0000000008 | pending=8
[CLOUD] Accepted HTTP 201 | remaining=7
HTTP 201代表建立成功。這證明設備金鑰、Device ID、JSON、事件類型、Story對應與Django資料建立流程均已通過。

八、補送期間仍可正常播放故事

雲端補送不能犧牲展場體驗。測試過程中,使用者仍可從手機Web播放白馬故事,也能按下GPIO11播放烏龜故事:

[WEB] GET /api/play/horse HTTP/1.1
[CLOUD QUEUE] + SHUIJING-002-0000000012 | STORY_START | pending=12
[JQ] Play track 1
[STATE] STORY_PLAYING | 白馬故事

[BUTTON] Pressed GPIO11
[CLOUD QUEUE] + SHUIJING-002-0000000014 | STORY_START | pending=11
[JQ] Play track 2
[STATE] STORY_PLAYING | 烏龜故事

故事播放完成後,再建立STORY_COMPLETE

[CLOUD QUEUE] + SHUIJING-002-0000000015 | STORY_COMPLETE | pending=12
[STATE] IDLE | 待機中

這表示系統採取的不是「先等網路完成才能播放」,而是現場互動優先、事件同步在後

九、這次測試建立的除錯方法

看到的現象代表意義優先檢查
無法取得STA IP尚未連上區域網路SSID、密碼、Channel、訊號
NTP失敗可能無Internet或UDP受限Gateway、DNS、HTTP時間備援
HTTP 401設備驗證失敗Device ID、API Key、啟用狀態
HTTP 400API收到請求但內容不合規Django回應本文、JSON欄位、事件名稱
HTTP 201事件建立成功確認佇列remaining持續下降
HTTP 409UUID可能已存在視為冪等成功或檢查重複事件

十、展場驗收清單

  • 每台設備使用獨立Device ID與API Key。
  • API Key不出現在序列監控、部落格與公開GitHub。
  • STA連線後能自動取得DHCP位址並完成NTP校時。
  • 實體按鈕與手機Web均能播放故事。
  • 開始播放建立STORY_START
  • 正常播完建立STORY_COMPLETE
  • 斷網或API錯誤時,事件保留於Flash。
  • 恢復正常後,舊事件依UUID順序補送。
  • 佇列最終回到0,Django後台筆數一致。

十一、這次經驗最重要的價值

這次測試不是單純把HTTP 400修成HTTP 201,而是建立了一套更接近真實展場的IoT資料策略:

Local First:按鈕與故事播放永遠優先,事件先安全留在本機。

Cloud Later:網路正常後再送Django,失敗不丟資料。

Evidence-based Debugging:不只看Wi-Fi是否連上,而是沿著IP、NTP、HTTP狀態碼、伺服器回應及資料模型逐層定位。

Shared Contract:Arduino與Django必須共用相同事件語彙;一個名稱不同,就可能讓整條FIFO佇列停住。

2026年8月24日 星期一

[水井村USR] 長按進入AP+自動重啟+網路診斷:展場維護版實測成功

水井三寶故事機|V1.0-10

長按進入AP+自動重啟+網路診斷:展場維護版實測成功

這一版讓 HUB 8735 Ultra 故事機即使沒有電腦、沒有 Arduino IDE,也能透過一顆實體停止按鈕完成網路救援。短按用來停止播放;長按5秒進入AP設定;長按10秒則清除舊Wi-Fi、自動重新啟動並開放備援設定入口。

5秒進入AP設定
10秒清除STA設定
自動重新啟動
192.168.4.1備援入口

一、為什麼需要V1.0-10?

故事機進入展場、社區或教學現場後,最大的維護問題往往不是故事能不能播放,而是現場Wi-Fi環境改變時,設備要如何重新設定。如果每次都要接上電腦、重新編譯並燒錄程式,維護成本會非常高。

因此,V1.0-10將原本的GPIO20停止按鈕擴充成「維護入口」,保留日常操作,同時提供不需電腦的網路救援功能。

設計原則

  • STA為主要連線:平時連接現場分享器並由DHCP取得IP。
  • AP為故障備援:只有STA設定不存在、連線失敗或使用者主動要求時才啟動。
  • 按鈕優先:Web、網路校時與播放程序都不能阻塞實體按鈕掃描。
  • 可現場復原:不連電腦也能清除舊Wi-Fi並重新設定。

二、GPIO20一顆按鈕,三種操作

操作方式系統動作使用情境
短按GPIO20立即停止目前播放一般使用
長按5~9秒後放開切換至AP設定模式臨時開啟維護入口,但保留原STA資料
長按10秒以上後放開清除STA的SSID、密碼與頻道,寫入Flash後自動重啟更換分享器、密碼錯誤或完全無法連線
關鍵設計:系統在「放開按鈕」時才決定執行5秒或10秒功能。如此一來,使用者長按超過5秒後仍可繼續按到10秒,不會在第5秒就被迫切換模式。

三、這次除錯最重要的發現

第一次測試時,按住停止按鈕只看到:

[BUTTON] Pressed GPIO20
[STOP] Ignored: already idle

當時容易懷疑GPIO20接線、按鈕彈跳或長按計時程式有問題。但進一步檢查開機標題後,發現設備實際執行的仍是:

HUB 8735 Ultra + JQ6500 Story Player V1.0-09h
STA Primary + AP Fallback Configuration Portal

也就是說,硬體沒有故障,真正原因是板子仍在執行舊版韌體。重新燒錄V1.0-10後,開機標題正確顯示:

HUB 8735 Ultra + JQ6500 Story Player V1.0-10
Long-Press AP + Auto Restart + Network Diagnostics
經驗結論:測試新功能前,第一步不是先拆接線,而是先核對序列監控視窗中的版本標題。這個小動作可以節省大量除錯時間。

四、如何確認長按真的被持續掃描?

管理後台開啟測試模式後,序列監控會定期顯示六顆按鈕的原始電位:

[BUTTON RAW] G10=1 G11=1 G12=1 G20=1 G7=1 G6=1

GPIO按鈕採用上拉輸入並接到GND,因此:

數值按鈕狀態
1未按下
0已按下

實測長按期間,GPIO20持續保持為0:

[BUTTON RAW] G10=1 G11=1 G12=1 G20=0 G7=1 G6=1
[BUTTON] Stop held 5s: release to enter AP setup mode
[BUTTON RAW] G10=1 G11=1 G12=1 G20=0 G7=1 G6=1
[BUTTON] Stop held 10s: release to clear Wi-Fi and restart

這證明按鈕、接線、上拉邏輯與非阻塞掃描均正常,主迴圈沒有因STA、NTP、Web或音訊狀態機而停止更新。

五、10秒清除Wi-Fi的完整實測流程

GPIO20
持續按住10秒
放開後
清除STA資料並存入Flash
自動重啟
開放AP備援入口

放開按鈕後,系統依序完成:

[MAINTENANCE] Clearing STA credentials
[FLASH] Batch saved: STA credentials cleared
[SYSTEM] Restart scheduled: STA credentials cleared
[SYSTEM] Restarting: STA credentials cleared

重新啟動後,Flash資料區仍可正常讀取音量、開機次數與統計資料,但STA憑證已被清除:

[FLASH] Volume = 16
[FLASH] Boot count = 48
[STA CONFIG] Not configured
[STA CONFIG] Missing; AP fallback queued

這也驗證了程式並非清空整個Flash,而是只清除指定的STA網路設定。

六、重新啟動後自動進入AP備援

系統完成開機語音006、準備完成語音007及JQ6500狀態判定後,自動啟動純AP模式:

[AP] Starting pure fallback AP mode
[AP NET] IP: 192.168.4.1
[AP NET] DHCP: 192.168.4.x
[AP NET] Configuration OK
[AP] SSID: Shuijing-Story
[WEB] http://192.168.4.1
[ADMIN] http://192.168.4.1/admin
[MODE] AP fallback configuration portal

手機維護資訊

AP名稱Shuijing-Story
AP密碼shuijing8735
故事控制首頁http://192.168.4.1/
管理後台http://192.168.4.1/admin
管理PIN8735

七、展場人員的標準維護程序

  1. 確認故事機已正常供電,避免使用供電不穩定的PC USB孔。
  2. 按住停止按鈕至少10秒,看到燈號快速同步閃爍後再放開。
  3. 等待設備自動重新啟動。
  4. 手機連上 Shuijing-Story
  5. 在瀏覽器輸入 http://192.168.4.1/admin
  6. 輸入管理PIN,重新設定現場分享器的SSID、密碼與頻道。
  7. 儲存後等待設備自動重啟,再由分享器查詢故事機取得的新STA IP。

八、V1.0-10完成的展場可靠性

項目實測結果
實體故事按鈕正常
Web故事控制正常
GPIO20短按停止正常
GPIO20長按5秒可辨識AP維護動作
GPIO20長按10秒可清除STA設定
Flash延遲保存正常,其他統計資料保留
自動重新啟動正常
純AP備援正常,固定為192.168.4.1
STA主要連線與NTP有憑證時自動連線與校時

九、這次測試留下的寶貴經驗

  • 先看韌體版本,再查硬體:序列監控的版本標題是除錯起點。
  • 原始GPIO紀錄很重要:[BUTTON RAW]能區分接線問題與程式狀態問題。
  • 長按功能要在放開時決策:才能同時支援5秒與10秒兩種命令。
  • 網路程序必須非阻塞:即使STA連線、NTP校時或無手機連入,按鈕仍應持續掃描。
  • STA與AP不要勉強同時運作:RTL8735B在本案採「STA主要、AP故障備援」比並行模式穩定。
  • Flash只清必要欄位:清除網路憑證時仍保留音量、開機次數及故事統計。
  • 穩定供電是系統的一部分:改用可靠市電電源後,可避免USB供電不足造成反覆重置與開機語音重播。

測試平台:HUB 8735 Ultra(RTL8735B)+JQ6500|韌體版本:V1.0-10|測試日期:2026年8月24日

[水井村USR] 從AP/STA衝突走到穩定上線:HUB 8735 Ultra故事機的網路與時間校正實戰

水井三寶故事機|V1.0-09h

從AP/STA衝突走到穩定上線:HUB 8735 Ultra故事機的網路與時間校正實戰

這次測試不是單純把Wi-Fi連上,而是一步步找出RTL8735B在AP+STA共存時的限制,最後確立「STA為主、AP為故障備援」的穩定架構。實測結果顯示,故事機能透過分享器取得IP、自動完成NTP校時,手機控制頁、管理後台、播放統計與實體按鈕皆能正常運作。

一、這一版完成了什麼?

  • 以STA模式連接既有Wi-Fi,IP由分享器DHCP自動分配。
  • STA連線成功後自動執行NTP,不需由手機按鈕觸發。
  • NTP失敗時自動嘗試HTTP Date;手機時間只作最後備援。
  • STA連線、上網或校時失敗時,才啟動192.168.4.1備援AP設定入口。
  • STA與AP互斥,不再使用不穩定的Concurrent Mode。
  • 首頁可播放五組內容、停止播放、調整音量並查看即時狀態。
  • 管理後台可查看時間來源、STA狀態、統計、最近事件及下載CSV。
  • 保留JQ6500非阻塞播放、中途切歌、停止及提示音寬容Watchdog。

二、最終網路架構:STA為主,AP為輔

開機讀取Flash
連接STA
DHCP取得IP
自動NTP/HTTP校時
STA Web服務

若沒有STA設定、Wi-Fi連線逾時、連線中斷,或NTP與HTTP都無法驗證外網,系統才切換到:

關閉STA
啟動備援AP
SSID:Shuijing-Story
192.168.4.1
重新設定Wi-Fi
設計原則:正常展場使用STA,讓手機與故事機同在既有區域網路;只有網路故障時才開AP。如此可避開RTL8735B Concurrent Mode造成的socket、路由及UDP不穩定問題。

三、為什麼不再同時開啟AP與STA?

早期版本曾讓AP與STA同時運作。手機剛連上192.168.4.1時,Web短暫正常;但STA連上分享器後,即使把預設路由切回AP,Web仍然失效。這表示問題不只在IP路由,而是STA加入時可能重建底層網路介面,使原本的TCP監聽socket失效。

另一個現象是Concurrent Mode執行NTP UDP請求時,曾直接重新開機。反覆修正路由並不能真正解決底層模式切換問題,因此最後不再勉強共存,而是採用互斥模式。

架構測試結果決策
AP固定192.168.1.1+STAAP與分享器Gateway同網段,路由衝突淘汰
AP固定192.168.4.1+STA ConcurrentSTA加入後Web只能短暫使用,NTP亦可能不穩淘汰
STA主要+AP故障備援STA Web、NTP、播放與統計均正常採用

四、NTP自動校時測試成功

RTL8735B STA取得IP並成功完成NTP校時的序列監控畫面
STA取得192.168.1.122,NTP自動校時成功,系統進入STA online service ready。
[STA] IP: 192.168.1.122
[STA] Gateway: 192.168.1.1
[TIME] STA connected; automatic NTP queued
[NTP] Attempting UDP time sync
[TIME] Synced by NTP | 2026-08-24 21:02:08
[MODE] STA online service ready

這段紀錄證明NTP不是由手機按鈕決定,而是STA取得DHCP位址後由狀態機自動排程。分享器IP仍維持原設定,故事機不會要求使用者修改Gateway。

五、手機故事控制首頁

手機與故事機連接同一部分享器後,以序列監控器顯示的STA IP進入首頁。本次測試網址為http://192.168.1.122/。這個IP由DHCP分配,實際部署時可能不同。

六、管理後台:從播放控制走向可維護系統

後台不只是遠端按鈕,而是展場維運工具:可以確認現在是否CONNECTED、時間是否來自NTP、RSSI是否足夠、各故事被播放幾次,以及播放器是否曾進行自動復原。

七、後台畫面的JavaScript除錯經驗

V1.0-09h初版曾出現:

null is not an object
(evaluating $('channel').textContent=s.staChannel||1)

原因是新版不再要求AP與STA同頻道,因此畫面移除了「頻道」欄位,但JavaScript仍更新channel元素。API其實已正常回傳,卻因前端找不到DOM元素而中斷。

修正方式:保留隱藏的相容欄位,讓舊JavaScript仍可安全更新;使用者不必再設定頻道。這提醒我們:後端欄位、HTML元素與JavaScript選擇器必須同步修改。

八、JQ6500播放與Watchdog經驗

JQ6500的BUSY腳位實測定義為:

BUSY狀態
LOW(0)待機
HIGH(1)播放中

故事曲目001~005採嚴格Watchdog;若沒有進入播放狀態,程式會復原播放器。但006~009屬短提示音,部分JQ6500不一定可靠回報BUSY,因此採「寬容Watchdog」:提示音未回報BUSY時只推進狀態,不再重複Pause、Reset與播放008,以免提示音造成系統鎖定。

檔案內容用途
001.mp3白馬故事故事
002.mp3烏龜故事故事
003.mp3姻緣花故事故事
004.mp3水井三寶總故事故事
005.mp3創作者資訊故事
006.mp3開機提示系統提示
007.mp3準備完成系統提示
008.mp3錯誤提示系統提示
009.mp3停止提示系統提示

九、主要接線

HUB 8735 Ultra連接裝置功能
GPIO8/Serial2 TXJQ6500 RX播放命令
GPIO15/Serial2 RXJQ6500 TX播放器回傳
GPIO4JQ6500 BUSY播放狀態
GPIO10白馬按鈕→GND001.mp3
GPIO11烏龜按鈕→GND002.mp3
GPIO12姻緣花按鈕→GND003.mp3
GPIO20停止按鈕→GND中斷播放
GPIO7音量+→GND增加音量
GPIO6音量-→GND降低音量
供電經驗:PC USB供電曾造成反覆重置與重播開機提示;改用穩定、合格的市電轉低壓電源後,系統才真正穩定。市電不可直接接入開發板。

十、測試結論

  1. 故事機成功連接STA並取得192.168.1.122
  2. NTP由STA連線後自動啟動並校時成功。
  3. 手機控制首頁可正常播放、停止、切歌及調整音量。
  4. 管理後台可正確顯示CONNECTED、NTP、RSSI、統計及事件。
  5. 實體按鈕與Web操作皆維持非阻塞。
  6. STA與AP互斥後,不再發生Concurrent Mode導致的Web短暫可用問題。

這次最寶貴的經驗,不是找到一個能編譯的Wi-Fi函式,而是從真實紀錄判斷:能連線不代表架構穩定;展場系統應優先選擇可預測、可復原、可維護的模式。

測試平台:HUB 8735 Ultra(RTL8735B)+JQ6500;測試日期:2026年8月24日。