# 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.成品展示
### 前端網站

### arduino執行掃描畫面

### qrcode掃描器和arduino實體

### 包裹掃描後查詢結果

## 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/)