更新紀錄(僅初版,2026年03月20日 發布)
- 初次發布
引用本文(DOI: 10.5281/zenodo.21616344)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
小村 豪(2026)。〈外部設備狀態檢查與顯示的最佳實務 - 別只用『連線中』就交差的設計〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616344 https://staging.comcomponent.com/zh-TW/blog/2026/03/20/002-external-device-state-check-display-best-practices/
- DOI(最新版本)
- 10.5281/zenodo.21616344
- DOI(此版本)
- 10.5281/zenodo.21616345
工業相機、條碼掃描器、PLC、量測儀、印表機、序列埠設備、USB 設備。 跟外部設備連動的 Windows 應用上,比起實際的故障本身,更常出事的是畫面狀態與真實狀況對不上。
舉幾個情境:
- OS 看得到它,但另一個行程正在占用,無法使用
open成功,但歸零、暖機、認證還沒完成- 設備還掛著,回應其實已經停了
- 抓取的執行緒掛了,畫面還留著最後一次的值
- 遇到非預期機型或 firmware,卻單純地顯示「連線中」
這時真正要知道的,不只是 有沒有連上。 而是 現在可以安全做什麼。
1. 先下結論
外部設備狀態檢查與顯示最有效的做法,就是 不要把狀態壓成一個 boolean。
至少要拆開下列幾個:
- 存在:OS 是否看得到
- 連線建立:本應用是否已完成 open/login/initialize
- 回應性:heartbeat 或 status query 有沒有回應
- 功能備妥:此刻是否能接受實際動作
- 資料新鮮度:畫面上的值是否是新的
- 組態一致:是否為預期的個體、型號、firmware
- 監控健全性:監控處理本身是否活著
用粗略的話說就是:
存在用 OS 側判斷,可用性用應用側判斷,新鮮度用畫面側判斷。
光把這 3 件事不混在一起,狀態顯示就穩很多。
2. 為什麼「連線中」很危險
「連線中」這一個文字,一個詞要擔起好幾個意思。
實際上,至少混了下列問題:
- OS 看得到該設備的 interface 嗎
- 本應用能對它做 open/login/initialize 嗎
- 輕量查詢能在時限內回應嗎
- 此刻要求的動作能安全執行嗎
- 畫面上的值是新的嗎
- 是預期的個體、型號、firmware 嗎
這 6 題各自滿足哪些,「能用」就有不同意思。
例如下列 4 種狀態是完全不同的:
- 未連線 OS 根本找不到目標 interface
- 已連線/確認中 物理上看得到,但初始化或認證尚未完成
- 已連線/無法使用 有回應,但 warming up、busy、interlock、媒體缺失等無法動作
- 值已過期 之前能抓到,但畫面上的值超出 freshness budget 了
全部用「連線中」壓下去,operator 根本不知道該怎麼處理。
3. 先拆開的狀態
建議做法是:內部用多軸狀態,UI 視需要摘要。
3.1 內部想拆開的狀態軸
| 軸 | 意義 | 典型檢查方式 | 在 UI 上想呈現的例子 |
|---|---|---|---|
| 存在 | OS 看得到嗎 | 啟動時列舉、arrival / removal 通知 | 未連線 / 已連線 |
| 連線 | 應用是否已 open / login / initialize | handle / SDK 初始化結果 | 確認中 / 初始化中 |
| 回應性 | status query 或 heartbeat 會回應嗎 | 附 timeout 的輕量查詢 | 有回應 / 回應遲延 / 無回應 |
| 功能備妥 | 現在能動作嗎 | device-specific status | 可用 / busy / warming up |
| 資料新鮮度 | 顯示值是新的嗎 | timestamp / sequence | 最新 / 過期 |
| 組態一致 | 是否為預期設備 | model / serial / firmware / profile | 目標設備 / 非預期設備 |
| 監控健全性 | 監控路徑還活著嗎 | worker heartbeat / loop lag | 監控中 / 監控停擺 |
重點是:設備有問題與 應用觀察不到 要分開。
3.2 UI 不必把所有狀態平鋪出來
把內部狀態做成多軸,聽起來畫面會吵。 但 UI 不需要把每一項都用同樣份量秀出來。
建議用 3 層:
- 上面放 摘要狀態
- 下面放 理由
- 需要時再拉 詳細面板
例如:
- 摘要:
已連線 / 無法使用 - 理由:
Warming up約剩 18 秒 - 詳細:
modelserialfirmwarelast heartbeatlast frame time
這樣分層,資訊量多也讀得動。
4. 狀態檢查的最佳實務
4.1 啟動時列舉 + 到達 / 移除通知
在 Windows 上處理外部設備的基礎是:啟動時列舉既有設備,之後接收 arrival / removal 通知。
特別要注意:
- 只靠通知抓不到既有設備
- runtime 通訊用 interface class 比 setup class 自然
- remove 通知與 I/O error 的先後順序可能不同
實務規則很單純:
- 啟動時列舉
- 訂閱通知
- 收到通知時,重列舉並和內部狀態 reconcile
4.2 分清「存在」「可開啟」「有回應」「可用」
把這幾件事混在一起處理,外部設備的事故最容易增加。
- 存在 OS 看得到 interface
- 可開啟 不被其他行程卡住、也沒權限問題,能取得 handle / session
- 有回應 輕量查詢能在 timeout 內回應
- 可用 能真正執行動作
這 4 件事並不相同。
4.3 event 與 poll 搭配使用
全走 event 或全走 poll 都不太理想。實務上 偵測走 event、健全性確認走 poll 最好用。
- arrival / removal 走 event
- heartbeat / status query 走 poll
- freshness 用 timestamp / sequence
這樣拆開之後,偵測連線與實際能否使用就比較能分開。
4.4 把監控處理與 UI 分離
在 UI thread 直接做 open / read / status query,顯示的需求與監控的需求就會糾在一起。
建議:
- 監控 worker 更新 state store
- UI 訂閱 state store 畫面
- UI 的操作以 command 丟給監控層
這樣就能把「監控停擺」與「設備停擺」分開處理。
4.5 把個體識別穩定化
只靠 friendly name 或 COM3 這類表面識別碼追狀態,很容易追錯個體。
最好內部保留:
- serial number
- logical device id
- stable device path
- 設備端的個體 ID
這類 不易動搖的 key。
5. 顯示的最佳實務
5.1 一張判斷表
| 實際狀態 | UI 摘要 | 補充顯示 |
|---|---|---|
| 沒 interface | 未連線 | 確認線材、電源、USB |
| 有 interface、初始化中 | 已連線 / 確認中 | 初始化中、認證中、warming up |
| 有回應、但條件不足 | 已連線 / 無法使用 | busy、缺媒體、interlock 開啟 |
| 有回應、值已過期 | 已連線 / 值已過期 | 最近更新 12 秒前 |
| 無回應 | 無回應 | 重連中、通訊 timeout |
| 非預期個體 | 非預期設備 | model / serial / firmware 不符 |
| 監控處理停擺 | 監控異常 | 監控 worker 停擺,需重啟 |
5.2 文字採「狀態 + 理由 + 下一步動作」
只寫 錯誤、異常 太弱。訊息應該收斂成這 3 個要素,operator 比較不會迷路。
- 狀態:發生了什麼
- 理由:為何這樣判斷
- 下一步:該做什麼
例如:
已連線 / 無法使用 - Warming up - 請稍候約 18 秒無回應 - heartbeat timeout - 請確認線材與電源非預期設備 - 需要 Firmware 2.1.0 - 請確認目標設備
5.3 不要掩蓋 stale data
last known value 有用,但 不要用 live value 的臉孔呈現。
建議:
- 值旁邊標 timestamp
- 顯示 age
- 變 stale 就換顏色或標籤
- 超過一定時間就不納入「可操作」判定
5.4 依重要性放在不同位置
status bar 方便,但容易被略過。 critical 異常不要只丟到 status bar 角落。
- 輕微狀態變化:status bar
- 可以繼續作業的注意:inline notice
- 需要停止操作的異常:主畫面、dialog、banner
這樣的分級比較自然。
5.5 多台時把摘要與詳細分開
多台設備的畫面,若所有詳細都一直顯示會很難看。
- 上方放 整體摘要
- 下方放 每台的列
- 選取時放 詳細面板
用 3 層能兼顧整體把握與個別切分。
6. 重連與運維的最佳實務
6.1 重連要有 backoff
回應一旦停掉,別用最短迴圈狂打重連。
- 會對裝置/驅動/SDK 造成壓力
- log 會淹掉
- 放大暫時性的不穩定
- UI 會狂抖
建議:
- 第一次立刻 retry
- 不行就逐步拉長間隔
- 設上限
- 保留手動
重連選項
6.2 消除 flapping
USB 接觸不良、網路瞬斷時,狀態會在短時間跳來跳去。 原始事件直接丟給 UI 會很難看。
所以:
- 內部 log 保留原始事件
- UI 給一點確認期,再確定顯示
- 但 critical 異常立刻呈現
這種分工比較好操作。
6.3 最低限度要留的 log
狀態顯示的改善,跟 log 設計是綁在一起的。
| 項目 | 例 |
|---|---|
| timestamp | 2026-03-20T10:23:41.512+09:00 |
| stable device key | camera:A1B2C3 |
| 顯示名稱 | 前工程相機 |
| 舊狀態 -> 新狀態 | Ready -> Stale |
| 理由 | heartbeat timeout firmware mismatch |
| 錯誤碼 | HRESULT Win32 SDK code |
| last success | 2026-03-20T10:23:36.011+09:00 |
| age / RTT | 5.5s 320ms |
| retry count | 3 |
| app / firmware version | App 1.8.2 / FW 2.4.1 |
其中最重要的是 狀態轉移 log。
6.4 不要把監控停擺與設備停擺搞混
- poll loop 被例外打死
- SDK callback 停了
- acquisition worker 發生 deadlock
- 只有 state store 更新停了
這些情況下,設備可能還活著,只是應用無法觀察。
若只以 未連線 或 無回應 呈現,使用者會誤以為是設備有問題。
所以 監控路徑的健全性 要做為獨立軸來管。
7. 依設備類型容易漏掉的點
7.1 USB / PnP 設備
- 只看通知抓不到 existing device
- runtime 用 interface class 比較自然
- composite device 會帶出多個 interface
- remove 通知與 I/O error 的先後可能倒過來
7.2 序列埠設備
只看 COMx 存在還不夠安全:
- port 有但目標設備沒掛
- 被別的行程 open
- 回應早就停
- read / write 會被 timeout 卡死
序列埠上更需要把 存在、回應、可用 分開。
7.3 網路設備
ping 通過不等於應用能用:
- 能否名稱解析
- 能否 TCP 連線
- 能否通過應用層 handshake
- status 是否 ready
- 值是否 fresh
是一系列階段。
7.4 依賴 SDK 的相機/量測儀器
SDK callback 有來不代表 live:
- callback thread 自己停了
- frame 還來但 timestamp 不前進
- image stream 來了,control channel 死了
- reconnect 後的設定重套沒跑完
所以除了 SDK,還要從外部角度檢查健全性。
8. 不該做的事
- 狀態只有
連線中 / 未連線 / 錯誤三種 - 以為只靠通知就能抓到 existing device
- 把
open成功直接等同「可用」 - 把 last known value 用 fresh 的臉秀給使用者
- 不顯示 timestamp
- 在 UI thread 做 open / read / status query
- 用最短迴圈硬 retry
- 把 critical 異常只放到 status bar
- 混淆
未連線與監控停擺 - 只用 friendly name 或
COM3識別個體
9. 總結
外部設備整合應用真正重要的,是決定 確認到哪種程度才敢說什麼話。
特別有效的分法:
存在 本應用可開啟 有回應 此刻能做這個動作 畫面上的值是新的
把這 5 件事拆開。
在此之上,粗略地說:
- 啟動時列舉、之後靠通知
- 可用性以 heartbeat + device-specific status 決定
- 顯示值附 timestamp 與 age
- critical 異常放在不容易被略過的地方
- 別讓監控系異常看起來像設備異常
能寫出「連線中」並不重要,重要的是 這個顯示與現實的偏離程度有多小——這才是實務真正在乎的。
10. 參考資料
- Microsoft Learn, CM_Register_Notification
- Microsoft Learn, Registering for Notification of Device Interface Arrival and Device Removal
- Microsoft Learn, Registering for Device Notification
- Microsoft Learn, Comparison of setup classes and interface classes
- Microsoft Learn, Device Information Sets
- Microsoft Learn, SetupDiEnumDeviceInterfaces
- Microsoft Learn, Communications functions
- Microsoft Learn, ClearCommError
- Microsoft Learn, COMMTIMEOUTS structure
- Microsoft Learn, WaitCommEvent
- Microsoft Learn, Monitoring Communications Events
- Microsoft Learn, Status Bars (Design basics)
- Microsoft Learn, UX checklist for desktop applications
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
如何理解 Windows 的工作階段隔離 ── Session 0・RDP・多使用者同時執行
整理 Windows 應用程式開發者容易混淆的「工作階段」概念。說明服務無法顯示 UI 的 Session 0 隔離原因、RDP 連線時工作階段的行為、具名物件的工作階段隔離,以及共用 PC・RDS 環境常見的設計失誤,從實務角度解說。
防止 Windows 應用程式重複啟動 ── 具名 Mutex 與重複執行時的視窗前置
整理業務用 Windows 應用程式的常見需求「不讓同一個應用程式啟動兩次」,如何用具名 Mutex 實作。涵蓋 Global\ 與 Local\ 命名空間差異在 RDP 環境中的陷阱、擁有執行緒限制與 AbandonedMutexException、考量 SetForeg...
在 WinForms/WPF 應用程式中導入 Entra ID 驗證 ── MSAL.NET 與 WAM 代理的實務架構
從實務角度整理在 WinForms/WPF 桌面應用程式中導入 Entra ID(原 Azure AD)驗證的做法。內容涵蓋公用客戶端的概念、ROPC 廢除現況、應用程式註冊、MSAL.NET 的 AcquireTokenSilent 模式、WAM 代理、權杖快取持久化,以...
業務應用程式的日期時間與時區 ── 從 DateTime 的陷阱到 UTC 儲存原則、測試設計
伺服器搬遷後時間整整差了 9 個小時、只有海外據點的日期會變成前一天──本文從 DateTime 的 Kind 與隱式轉換整理日期時間事故的根本原因。內容涵蓋與 DateTimeOffset 的取捨、UTC 儲存與 ISO 8601 原則、TimeZoneInfo 與夏令時...
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
常見問題
整理諮詢這個主題時常見的問題。
- 為什麼設備狀態只顯示「連線中」很危險?
- 因為「連線中」一個詞混了多個意思:OS 是否看得到 interface、應用能否 open/login/initialize、輕量查詢能否在時限內回應、此刻要求的動作能否安全執行、畫面上的值是否新、是否為預期的個體或 firmware。這些各自滿足哪些,「能用」就有不同意思,全部壓成一個 boolean,operator 根本不知道該怎麼處理。至少要把存在、連線、回應性、功能備妥、資料新鮮度、組態一致、監控健全性拆開管理。
- 設備狀態檢查該用 event 還是 polling?
- 兩者搭配使用最好:偵測走 event、健全性確認走 poll。arrival/removal 通知走 event,但只靠通知抓不到啟動前既有的設備,所以啟動時要先列舉既有設備、訂閱通知、收到通知時再重列舉並與內部狀態 reconcile。heartbeat 或 status query 用附 timeout 的輪詢,資料新鮮度則以 timestamp 或 sequence 判斷。
- 畫面上顯示的舊值(stale data)該怎麼處理?
- last known value 有用,但不要用 live value 的臉孔呈現。建議在值旁邊標 timestamp、顯示 age、變 stale 就換顏色或標籤,超過一定時間就不納入「可操作」的判定。抓取執行緒掛了但畫面還留著最後一次的值,是外部設備應用典型的事故,所以監控處理本身是否活著也要作為獨立的軸來管。
- 設備無回應時的重連該怎麼設計?
- 回應一旦停掉,別用最短迴圈狂打重連,那會對裝置、驅動、SDK 造成壓力,還會淹掉 log、放大暫時性的不穩定、讓 UI 狂抖。建議第一次立刻 retry,不行就逐步拉長間隔(backoff)、設上限,並保留手動重連的選項。另外要消除 flapping:內部 log 保留原始事件,UI 給一點確認期再確定顯示,但 critical 異常要立刻呈現。