# Arduino UNO R4 WiFi 物流專題開發日誌 **日期:2025年11月25日** ## 摘要 本報告記錄了從零開始對 Arduino UNO R4 WiFi 開發板進行功能測試與物聯網 (IoT) 應用開發的完整過程。內容涵蓋了硬體基本測試、常見開發環境錯誤的排除、Wi-Fi 模組功能驗證,以及 QR Code 掃描模組的硬體整合。**此文件將作為專案的主要開發日誌,隨著進度持續更新與完善。** ## 第一階段:硬體與開發環境初始設定 ### 1.1 目標 驗證 Arduino UNO R4 WiFi 開發板的基本功能,並確保開發環境 (Arduino IDE) 設定正確。初步測試目標為實現板載 12x8 LED 矩陣的**呼吸燈**效果。 ### 1.2 遭遇問題與解決方案 在這個階段,我們經歷了一連串典型的環境設定與硬體通訊問題,以下是詳細的除錯歷程: | 問題點 | 錯誤訊息 | 原因分析 | 解決方案 | | :--- | :--- | :--- | :--- | | **找不到標頭檔** | `fatal error: Arduino_LED_Matrix.h: No such file or directory` | Arduino IDE 中的開發板選擇錯誤,選成了舊版的 "Arduino Uno",導致無法載入 R4 專屬的核心函式庫。 | 在「工具 > 開發板」中,正確選擇 "Arduino UNO R4 WiFi",並透過「開發板管理員」安裝對應的核心套件。 | | **類別名稱錯誤** | `'Arduino_LED_Matrix' does not name a type` | 程式碼中用來宣告物件的類別名稱 `Arduino_LED_Matrix` (有底線) 與函式庫定義的 `ArduinoLEDMatrix` (無底線) 不符。 | 將宣告物件的程式碼從 `Arduino_LED_Matrix matrix;` 修正為 `ArduinoLEDMatrix matrix;`。 | | **系統權限不足** | `Cannot create temporary file... Permission denied` | Arduino IDE 在編譯時,沒有權限在 Windows 系統的暫存資料夾中寫入檔案,通常是被防毒軟體或系統權限設定阻擋。 | 將 Arduino IDE 的捷徑按右鍵,選擇**「以系統管理員身分執行」**,賦予其足夠的權限。 | | **工具鏈損壞** | `arm-none-eabi-ar: could not create temporary file` | 編譯工具鏈中的封裝工具 `ar` 發生錯誤,暗示 IDE 的安裝可能已損壞或不完整。 | **徹底重新安裝 Arduino IDE**。建議升級至最新的 2.x 版本,並刪除舊的設定檔資料夾 (`%LOCALAPPDATA%/Arduino15`),以建立一個乾淨的開發環境。 | | **上傳失敗 (最棘手)** | `No device found on COM3/COM6` / `上傳失敗: exit status 1` | 這是 UNO R4 WiFi 的一個特性:在上傳過程中,它會短暫斷開並以一個新的 COM 埠重新連接進入 Bootloader 模式,但 IDE 未能及時捕捉到這個新的埠號。 | 採用**「手動進入 Bootloader 模式」**的技巧:1. 在 IDE 點擊上傳。2. 當看到「正在上傳...」時,**快速按兩下**板載的「RESET」按鈕。這使得開發板強制停留在可接收程式的模式,讓 IDE 有足夠時間找到它並完成上傳。 | ### 1.3 最終成果:LED 矩陣呼吸燈 在克服以上所有挑戰後,我們成功上傳了呼吸燈程式,透過控制 LED 矩陣的整體亮度,實現了由暗到亮再到暗的平滑過渡效果,驗證了開發環境與硬體基本功能皆已正常。 ```cpp #include "Arduino_LED_Matrix.h" // 建立 LED 矩陣控制物件 ArduinoLEDMatrix matrix; // 定義一個全亮的畫面 (1代表亮,0代表滅) // 12x8 的矩陣,每個 byte 代表一行 (這裡簡化為意示圖) byte solidFrame[8][12] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }; void setup() { // 啟動 LED 矩陣 matrix.begin(); // 載入全亮圖案 matrix.renderBitmap(solidFrame, 8, 12); } void loop() { // 呼吸燈效果:從暗變亮 // brightness 範圍 0-255 for (int brightness = 0; brightness <= 255; brightness++) { matrix.on(brightness); // 設定當前亮度 (註:新版函式庫可能使用不同指令,此為示意) delay(5); // 延遲讓眼睛能感覺到變化 } // 呼吸燈效果:從亮變暗 for (int brightness = 255; brightness >= 0; brightness--) { matrix.on(brightness); delay(5); } } ``` ## 第二階段:Wi-Fi 硬體功能驗證 ### 2.1 目標 使用 `WiFiS3` 函式庫掃描周遭的無線網路,並將結果輸出到序列埠監控視窗。此舉能 100% 確認 Wi-Fi 模組的硬體功能正常,且主控晶片與其通訊無誤。 ### 2.2 最終成果:Wi-Fi 網路掃描 成功執行內建的 `ScanNetworks` 範例,並在序列埠監控視窗中看到周圍 Wi-Fi 熱點的 SSID 名稱與訊號強度列表。 ```cpp #include "WiFiS3.h" void setup() { // 初始化序列埠,用於顯示掃描結果 Serial.begin(9600); while (!Serial) { ; } // 等待序列埠連線 // 檢查 Wi-Fi 模組是否存在 if (WiFi.status() == WL_NO_MODULE) { Serial.println("與 Wi-Fi 模組通訊失敗!"); while (true); // 如果失敗就卡在這裡 } // 檢查韌體版本 String fv = WiFi.firmwareVersion(); if (fv < WIFI_FIRMWARE_LATEST_VERSION) { Serial.println("建議更新 Wi-Fi 模組韌體"); } } void loop() { Serial.println("** 開始掃描網路 **"); // 執行掃描,回傳找到的網路數量 int numSsid = WiFi.scanNetworks(); if (numSsid == -1) { Serial.println("無法取得 Wi-Fi 連線"); } else { // 印出找到的網路數量 Serial.print("掃描完成,共找到 "); Serial.print(numSsid); Serial.println(" 個網路"); // 逐一印出每個網路的資訊 for (int i = 0; i < numSsid; i++) { Serial.print(i + 1); Serial.print(") "); Serial.print(WiFi.SSID(i)); // 網路名稱 (SSID) Serial.print("\t訊號強度: "); Serial.print(WiFi.RSSI(i)); // 訊號強度 (RSSI) Serial.println(" dBm"); } } // 等待 10 秒後再次掃描 delay(10000); } ``` ## 第三階段:整合 QR Code 掃描器 (GM65) ### 3.1 目標 將 GM65 條碼/QR Code 掃描模組整合至 Arduino UNO R4 WiFi,實現以下功能: 1. Arduino 能正確讀取掃描器透過 UART (Serial1) 傳來的條碼資料。 2. 掃描成功後,在 Arduino 板載的 LED 矩陣上顯示「綠色勾勾」(因硬體限制顯示為紅色)作為視覺回饋。 3. 隨後以動畫方式顯示掃描到的內容狀態,確認系統運作中。 ### 3.2 硬體接線 使用 Arduino UNO R4 WiFi 的硬體序列埠 `Serial1` (Pin 0 & 1) 與掃描器通訊,保留 USB `Serial` 用於電腦除錯。 | GM65 模組腳位 | Arduino UNO R4 腳位 | 功能說明 | | :--- | :--- | :--- | | **VCC (紅)** | **5V** | 供電 (模組工作電壓) | | **GND (黑)** | **GND** | 接地 | | **TX (綠/藍)** | **RX (Pin 0)** | 模組傳送資料 → Arduino 接收 | | **RX (白/黃)** | **TX (Pin 1)** | Arduino 傳送指令 → 模組接收 (選用) | ### 3.3 遭遇問題與解決方案 在整合過程中,我們遇到了一個關鍵的技術障礙:**掃描器有動作(蜂鳴器有響),但 Arduino 卻收不到任何資料。** | 問題點 | 錯誤現象 | 原因分析 | 解決方案 | | :--- | :--- | :--- | :--- | | **通訊模式錯誤** | 掃描器讀取條碼時有「嗶」聲,但 Arduino 序列埠監控視窗完全空白,無資料傳入。 | GM65 模組出廠預設為 **USB HID 模式**(模擬鍵盤),此模式下不會透過 TX/RX 腳位傳送資料,導致 Arduino `Serial1.available()` 永遠為假。 | 1. 查閱 GM65 技術手冊 (User Manual)。<br>2. 找到 **"Interface (TTL-232)"** 或 **"Serial Mode"** 章節。<br>3. 掃描 **"Restore Factory Defaults"** (恢復原廠) 設定碼。<br>4. 掃描 **"Enable Serial Mode"** (開啟序列埠模式) 設定碼。<br>5. 設定完成後,模組成功切換至 UART 通訊模式。 | | **圖形函式庫版本相容性** | 編譯時出現 `'class ArduinoLEDMatrix' has no member named 'beginDraw'` 錯誤。 | 欲使用的文字跑馬燈功能依賴於較新版本的 Arduino UNO R4 Core 或 `ArduinoGraphics` 函式庫,而目前的開發環境版本較舊。 | 採用**降級相容策略**:放棄依賴新版 API 的文字跑馬燈功能,改用最基礎的 `renderBitmap` 函式繪製靜態圖案(勾勾)與簡單動畫(閃爍點),確保在任何版本的 IDE 環境下皆能穩定運作。 | ### 3.4 軟體實作邏輯 為了提供良好的使用者體驗,我們設計了以下互動流程: 1. **待機模式**:LED 矩陣全滅,等待掃描。 2. **掃描觸發**:當 `Serial1` 接收到資料(`\n` 結尾)。 3. **視覺回饋 (成功)**:LED 矩陣顯示一個「勾勾」圖案,停留 1.5 秒。 4. **處理狀態**:顯示「傳送中」動畫(三個點依序亮起),模擬資料處理過程。 5. **除錯輸出**:同時將掃描到的原始字串透過 USB `Serial` 印出至電腦螢幕。 ### 3.5 最終測試程式碼 ```cpp #include "Arduino_LED_Matrix.h" // 建立 LED 矩陣物件 ArduinoLEDMatrix matrix; // 定義「勾勾」圖案 (使用 0/1 繪製,1=亮,0=滅) // 雖然硬體只能顯示紅色,但這代表「成功」的符號 byte checkMark[8][12] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, // 勾勾長邊 {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, // 轉折點 {0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0} // 勾勾短邊 }; // 定義動畫用的三個點 (初始化為全滅,稍後在 setup 中設定位置) byte dot1[8][12] = {0}; byte dot2[8][12] = {0}; byte dot3[8][12] = {0}; void setup() { // 1. 初始化序列埠 (電腦端) Serial.begin(115200); // 2. 初始化掃描器序列埠 (GM65 預設鮑率為 9600) Serial1.begin(9600); // 3. 啟動 LED 矩陣 matrix.begin(); // 設定動畫點的位置 (模擬資料傳輸中的進度條) // 點1 (左) dot1[3][4] = 1; dot1[4][4] = 1; // 點2 (左 + 中) dot2[3][4] = 1; dot2[4][4] = 1; dot2[3][6] = 1; dot2[4][6] = 1; // 點3 (左 + 中 + 右) dot3[3][4] = 1; dot3[4][4] = 1; dot3[3][6] = 1; dot3[4][6] = 1; dot3[3][8] = 1; dot3[4][8] = 1; Serial.println("系統就緒!請開始掃描 QR Code..."); } void loop() { // 檢查是否有掃描資料傳入 if (Serial1.available()) { // 讀取掃描字串 (直到換行符號) String qrData = Serial1.readStringUntil('\n'); qrData.trim(); // 移除前後空白字元 // 確保讀到的資料不為空 if (qrData.length() > 0) { // 除錯訊息:印出掃到的資料 Serial.print("掃描成功: "); Serial.println(qrData); // ------------------------- // 步驟一:顯示勾勾 (確認掃描成功) // ------------------------- matrix.renderBitmap(checkMark, 8, 12); delay(1500); // 停留 1.5 秒讓使用者看清楚 // ------------------------- // 步驟二:顯示傳送動畫 (模擬資料處理) // ------------------------- // 閃爍三次,模擬資料正在上傳的過程 for(int i=0; i<3; i++) { matrix.renderBitmap(dot1, 8, 12); delay(200); // 顯示1個點 matrix.renderBitmap(dot2, 8, 12); delay(200); // 顯示2個點 matrix.renderBitmap(dot3, 8, 12); delay(200); // 顯示3個點 // 短暫清空畫面造成閃爍效果 byte clear[8][12] = {0}; matrix.renderBitmap(clear, 8, 12); delay(100); } // ------------------------- // 步驟三:清除畫面 (回到待機狀態) // ------------------------- byte clear[8][12] = {0}; matrix.renderBitmap(clear, 8, 12); } } } ``` *** ## 第四階段:系統架構升級與雲端整合 (2025/11/24) ### 4.1 架構調整緣起 為提升系統的可展示性,我們決定對系統架構進行重大升級。原定計畫是自建後端伺服器,但為了展現 FHIR 生態系的優勢,我們轉而採用**標準公開 FHIR 伺服器**作為後端資料庫,並自行開發**前端網頁**來即時查詢物流狀態。 **新系統架構圖:** `Arduino (硬體採集)` → `HTTP POST (FHIR JSON)` → `HAPI FHIR Public Server (雲端資料庫)` ← `HTTP GET` ← `GitHub Pages (自製前端網頁)` ### 4.2 實作內容:前端網頁開發 我們使用 VS Code 開發了一個輕量級的物流追蹤網頁,並部署於 GitHub Pages,實現跨裝置的即時查詢。 * **技術堆疊**:HTML5, CSS3, JavaScript (Fetch API)。 * **功能實作**: * 透過 `fetch()` 向 HAPI FHIR Server 發送查詢請求。 * 解析回傳的 FHIR `SupplyDelivery` 資源 JSON。 * 將物流時間、地點與狀態動態顯示於網頁上。 * **部署平台**:GitHub Pages (提供免費 HTTPS 託管,解決安全連線問題)。 ### 4.3 實作內容:Arduino 雲端上傳整合 此階段最艱鉅的任務是讓 Arduino UNO R4 WiFi 直接與 HTTPS 伺服器通訊,並組裝出正確的 FHIR 格式資料。 * **核心技術**: * 使用 `WiFiSSLClient` 處理 HTTPS 加密連線(連接 port 443)。 * 手動組裝 JSON 字串,將掃描到的 `packageId` 嵌入 FHIR `identifier` 欄位。 * 發送標準 HTTP POST 請求,並設定 `Content-Type: application/json+fhir`。 ### 4.4 遭遇問題與解決方案 在整合 Arduino 與公開 FHIR 伺服器的過程中,我們克服了以下挑戰: | 問題點 | 錯誤現象 | 原因分析 | 解決方案 | | :--- | :--- | :--- | :--- | | **HTTPS 連線失敗** | 使用一般的 `WiFiClient` 連接 `hapi.fhir.org` 時無法建立連線。 | 公開 FHIR 伺服器強制要求 HTTPS 加密連線,而基礎 `WiFiClient` 僅支援未加密的 HTTP。 | 改用 **`WiFiSSLClient`** 類別。UNO R4 WiFi 的 ESP32 模組內建 SSL/TLS 支援,能直接處理公開憑證的 HTTPS 連線,無需額外複雜設定。 | | **JSON 格式錯誤** | 伺服器回傳 `400 Bad Request` 或 `422 Unprocessable Entity`。 | Arduino 手動組裝 JSON 字串時,引號跳脫 (`\"`) 處理不當,或缺少 FHIR 規範的必要欄位。 | 使用線上 JSON Validator 驗證 Arduino 輸出的字串,並仔細校對 FHIR R4 `SupplyDelivery` 資源定義。 | | **跨網域 (CORS) 問題** | 前端網頁在查詢時出現 CORS 錯誤,無法讀取資料。 | 前端網頁託管於 GitHub (HTTPS),若呼叫 HTTP 的 API 會被瀏覽器因「混合內容」安全策略擋下。 | 確保前端 `fetch` 請求與 Arduino `POST` 請求皆統一指向 **HTTPS** 版本的 FHIR Server URL (`https://hapi.fhir.org/baseR4`)。 | ### 4.5 程式碼附錄:Arduino 完整實作 (Scanner + Wi-Fi Upload) ```cpp #include "Arduino_LED_Matrix.h" #include "WiFiS3.h" #include "WiFiSSLClient.h" // 關鍵:用於 HTTPS 連線 // --- Wi-Fi 與伺服器設定 --- const char* ssid = "Damon"; const char* pass = "xxxxxxxxx"; const char* serverAddress = "hapi.fhir.org"; // 公開 FHIR Server const int serverPort = 443; // HTTPS port ArduinoLEDMatrix matrix; WiFiSSLClient client; // 建立 SSL 客戶端 // LED 矩陣圖案:勾勾 (代表成功) byte checkMark[8][12] = { {0,0,0,0,0,0,0,0,0,0,0,1}, {0,0,0,0,0,0,0,0,0,0,1,0}, {0,0,0,0,0,0,0,0,0,1,0,0}, {0,0,0,0,0,0,0,0,1,0,0,0}, {0,0,0,0,0,0,0,1,0,0,0,0}, {1,0,0,0,0,0,1,0,0,0,0,0}, {0,1,0,0,0,1,0,0,0,0,0,0}, {0,0,1,1,1,0,0,0,0,0,0,0} }; // LED 矩陣圖案:叉叉 (代表失敗) byte crossMark[8][12] = { {1,0,0,0,0,0,0,0,0,0,0,1}, {0,1,0,0,0,0,0,0,0,0,1,0}, {0,0,1,0,0,0,0,0,0,1,0,0}, {0,0,0,1,0,0,0,0,1,0,0,0}, {0,0,0,0,1,0,0,1,0,0,0,0}, {0,0,0,0,0,1,1,0,0,0,0,0}, {0,0,0,0,1,0,0,1,0,0,0,0}, {0,0,0,1,0,0,0,0,1,0,0,0} }; void setup() { Serial.begin(115200); Serial1.begin(9600); // GM65 掃描器 matrix.begin(); // 連接 Wi-Fi Serial.print("連接 Wi-Fi: "); Serial.println(ssid); WiFi.begin(ssid, pass); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi 連線成功!"); Serial.print("IP 位址: "); Serial.println(WiFi.localIP()); } void loop() { if (Serial1.available()) { String qrData = Serial1.readStringUntil('\n'); qrData.trim(); if (qrData.length() > 0) { Serial.print("掃描到包裹: "); Serial.println(qrData); if (uploadToFHIR(qrData)) { Serial.println("上傳成功!"); matrix.renderBitmap(checkMark, 8, 12); } else { Serial.println("上傳失敗!"); matrix.renderBitmap(crossMark, 8, 12); } delay(2000); byte clear[8][12] = {0}; matrix.renderBitmap(clear, 8, 12); } } } bool uploadToFHIR(String packageId) { Serial.println("正在連接伺服器..."); if (client.connect(serverAddress, serverPort)) { // 組裝 FHIR JSON Payload String jsonPayload = "{"; jsonPayload += "\"resourceType\": \"SupplyDelivery\","; jsonPayload += "\"status\": \"completed\","; jsonPayload += "\"identifier\": [{\"system\": \"http://my-hospital.org/tracking-id\", \"value\": \"" + packageId + "\"}],"; jsonPayload += "\"destination\": {\"display\": \"Arduino Station\"}"; jsonPayload += "}"; // 發送 HTTPS POST 請求 client.println("POST /baseR4/SupplyDelivery HTTP/1.1"); client.println("Host: " + String(serverAddress)); client.println("Content-Type: application/json+fhir"); client.print("Content-Length: "); client.println(jsonPayload.length()); client.println(); // 表頭結束 client.println(jsonPayload); // 發送內容 // 等待回應 unsigned long timeout = millis(); while (client.available() == 0) { if (millis() - timeout > 5000) { client.stop(); return false; } } String response = client.readStringUntil('\n'); Serial.println("伺服器回應: " + response); client.stop(); // 檢查 HTTP 狀態碼 (200 OK 或 201 Created) return (response.indexOf("200") != -1 || response.indexOf("201") != -1); } return false; } ``` ### 4.6 程式碼附錄:前端網頁完整原始碼 (index.html) 此網頁託管於 GitHub Pages,提供查詢與模擬上傳功能。 ```html <!DOCTYPE html> <html lang="zh-TW"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>醫療包裹追蹤系統 (FHIR)</title> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; background-color: #f5f5f5; } .container { background-color: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } h1 { color: #2c3e50; text-align: center; } .input-group { display: flex; gap: 10px; margin-bottom: 20px; } input { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 16px; } button { padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; transition: 0.3s; } .btn-search { background-color: #3498db; color: white; } .btn-search:hover { background-color: #2980b9; } .btn-upload { background-color: #27ae60; color: white; } .btn-upload:hover { background-color: #219150; } #result-area { margin-top: 20px; } .log-item { background: #ecf0f1; padding: 15px; margin-bottom: 10px; border-left: 5px solid #3498db; border-radius: 4px; } .log-time { color: #7f8c8d; font-size: 0.9em; margin-bottom: 5px; } .log-status { font-weight: bold; color: #2c3e50; } .loading { text-align: center; color: #7f8c8d; display: none; } </style> </head> <body> <div class="container"> <h1>📦 醫療包裹追蹤系統</h1> <p style="text-align: center; color: #666;">連接 HAPI FHIR Public Server</p> <!-- 查詢區塊 --> <div class="input-group"> <input type="text" id="trackId" placeholder="請輸入包裹編號 (例如: PKG-8888)" value="PKG-8888"> <button class="btn-search" onclick="searchPackage()">🔍 查詢包裹編號</button> </div> <!-- 模擬 Arduino 上傳區塊 (方便你測試用) --> <div style="text-align: right; margin-bottom: 20px;"> <button class="btn-upload" onclick="simulateArduinoUpload()">📤 模擬 Arduino 上傳一筆資料</button> </div> <div id="loading" class="loading">資料讀取中...</div> <div id="result-area"></div> </div> <script> // 設定 FHIR Server 網址 const FHIR_BASE_URL = "https://hapi.fhir.org/baseR4"; // 1. 查詢功能 (GET) async function searchPackage() { const id = document.getElementById('trackId').value; if(!id) return alert("請輸入包裹編號!"); document.getElementById('loading').style.display = 'block'; document.getElementById('result-area').innerHTML = ''; try { // 向 FHIR Server 查詢 identifier 等於輸入編號的 SupplyDelivery 資源 const response = await fetch(`${FHIR_BASE_URL}/SupplyDelivery?identifier=${id}&_sort=-_lastUpdated`); const data = await response.json(); document.getElementById('loading').style.display = 'none'; if (!data.entry || data.entry.length === 0) { document.getElementById('result-area').innerHTML = '<p style="text-align:center">查無此包裹資料,請先模擬上傳。</p>'; return; } // 顯示每一筆物流紀錄 data.entry.forEach(item => { const resource = item.resource; // 修改後的程式碼:優先讀取 occurrenceDateTime,如果沒有,就讀取 meta.lastUpdated (伺服器收到的時間) let displayTime = "時間未紀錄"; if (resource.occurrenceDateTime) { displayTime = new Date(resource.occurrenceDateTime).toLocaleString(); } else if (resource.meta && resource.meta.lastUpdated) { displayTime = new Date(resource.meta.lastUpdated).toLocaleString() + " (接收時間)"; } const time = displayTime; const location = resource.destination ? resource.destination.display : "未知地點"; const status = resource.status || "未知狀態"; const html = ` <div class="log-item"> <div class="log-time">${time}</div> <div class="log-status">📍 地點:${location}</div> <div>狀態:${status}</div> <div style="font-size:0.8em; color:#999;">Resource ID: ${resource.id}</div> </div> `; document.getElementById('result-area').innerHTML += html; }); } catch (error) { console.error(error); alert("查詢失敗,請檢查網路或 Console"); document.getElementById('loading').style.display = 'none'; } } // 2. 模擬上傳功能 (POST) - 這段邏輯之後要寫在 Arduino 裡 async function simulateArduinoUpload() { const id = document.getElementById('trackId').value; const now = new Date().toISOString(); // 取得現在時間 // 建立符合 FHIR 標準的 JSON 資料 const fhirData = { "resourceType": "SupplyDelivery", "status": "completed", "identifier": [ { "system": "http://my-hospital.org/tracking-id", "value": id } ], "occurrenceDateTime": now, "destination": { "display": "Arduino Station (Web Test)" } }; try { const response = await fetch(`${FHIR_BASE_URL}/SupplyDelivery`, { method: "POST", headers: { "Content-Type": "application/json+fhir" }, body: JSON.stringify(fhirData) }); if(response.ok) { alert(`✅ 成功上傳一筆 ${id} 的紀錄!\n現在請點擊「查詢包裹編號」來查看。`); searchPackage(); // 上傳完自動重整 } else { alert("上傳失敗"); } } catch (error) { console.error(error); alert("上傳發生錯誤"); } } </script> </body> </html> ``` ## 5.成品展示 ### 前端網站 ![IMG_0191](https://hackmd.io/_uploads/BkNya65-Wl.jpg) ### arduino執行掃描畫面 ![IMG_0192](https://hackmd.io/_uploads/S1Px6T9--e.jpg) ### qrcode掃描器和arduino實體 ![IMG_0193](https://hackmd.io/_uploads/HJwZppcbZl.jpg) ### 包裹掃描後查詢結果 ![IMG_0194](https://hackmd.io/_uploads/HJ_GTp5b-g.jpg) ## 6. 結語與未來展望 本專案成功實踐了「硬體採集、標準傳輸、即時視覺化」的物聯網開發流程。透過導入 HL7 FHIR 標準,我們證明了即便是 Arduino 這樣的邊緣裝置,也能輕鬆融入國際醫療資訊生態系,解決傳統醫療院所的「資訊孤島」問題。未來我們計畫進一步強化系統的資安機制,並探索與醫院現有 HIS 系統的深層對接,讓這套低成本的物資追蹤方案能應用於更廣泛的真實場景。 ## 7. 專案連結 * **GitHub Repository**: [GitHub](https://github.com/Damonshih/fhir-package-tracker.git) * **Demo 網站**: [GitHub Pages](https://damonshih.github.io/fhir-package-tracker/)