用 AutoHotKey 偵測 Smart Card 讀卡機狀態

放在 Smart Card 讀卡機裡的醫事人員卡

因科內最近在注意報告的 24 小時簽章率,這個 KPI 要求正式報告發出後,在 24 小時內使用醫事人員卡做簽章動作的完成率。目前科裡最常遇到的情形是假日沒有帶卡回家,或是家裡電腦沒有讀卡機、沒有安裝背景簽章程式等等,導致未及時簽章。

有一個妥協的方式是,把報告先發成「初步報告」,臨床可以看到報告內容,而又不會造成 24 小時未簽章,等進辦公室可以正常簽章時,再改為正式報告。所以我在會議中便提議,是否直接在報告系統中去偵測,如果沒有插卡,便不讓使用者發正式報告,無奈遭到主管的反對,反對理由是「不可能!簽章系統不是我們做的,無法整合」,但真的一點辦法也沒有嗎?

想法

因為以我的權限,能動的東西有限,所以打算用 AutoHotKey 來做橋樑,以下的介面流程都是從 AutoHotKey 的角度來考量。

一開始的想法是用 AutoHotKey 去偵測簽章程式是否有正常的執行,當然這應該是最完美的解法,但因為實行上有難度,簽章程式似乎不會發送自身的執行狀態,也沒有公開的 API 可以呼叫,就像主管反對的理由,「不是我們做的,無法整合」,所以退一步想,去和讀卡機溝通,有個標準也有 API,比較可行。

Windows 上和 Smart Card 有關的 API 是在 Winscard.dll 裡,如果要做到偵測卡片狀態,流程大概是:

  1. SCardEstablishContext
  2. SCardListReaders
  3. SCardConnect
  4. SCardStatus
  5. SCardDisconnect
  6. SCardReleaseContext

在 AutoHotKey 可以用 DllCall() 來呼叫 Windows API,但其實我之前沒有 Windows API 的經驗,也不懂 Smart Card 的概念,所以花了好長一段時間摸索,後來又得到強大網友的幫忙,才克服這個小小的功能。

實作細節可見 smart-card.ahk

討論

為了達成 24 小時簽章率,多數人的做法是透過一台不關機的主機,安裝背景簽章程式,定時批次簽章,如此即使不在辦公室裡發出的報告,也可以定時被簽章,但為人詬病的是持續開機,尤其是連續假日時,浪費不少的能源,不符合環保的概念,所以才會有「沒帶卡就改發初步報告」這種 workaround 的出現。

因為報告系統 SmartWonder 是 web-based,要在網頁裡用 javascript 去控制讀卡機可能不像 AutoHotKey 那麼容易,2016 年有人提出了 WebUSB API 的概念或許是一種可行的解法,但目前要 Chrome 54 以上的版本才可以運作,而 SmartWonder 還得相容到 IE8+,最有可能實現的方式大概是 ActiveX 吧!另外,目前有一些 FireBreath framework 的 plugin, 例如 WebCard 也可以是嘗試的方向。

用 AutoHotKey 來實作主要是可以和我個人的報告流程整合,我原先就會 binding 一組 hotkey 來做送出報告的動作(詳見 click-confirm.ahk,只要把上述檢查讀卡機的動作加入流程裡,就可以輕鬆的擴充這個功能。

原本在測試 API 時,似乎會需要一小段時間來做讀卡機連線,但實際整合到報告流程裡後,幾乎是感受不到時間差的,若不想在每次發送報告的當下都去 check,或許可以改用一些定時 check 的機制,畢竟我們想要達到的目的只是儘量的達成 24 小時簽章率的 KPI,而不是 100% 無失誤,有少許的 miss 應該是還可以接受的。

限制

因為這個 script 只是很單純的去偵測有沒有卡放在讀卡機裡,所以不論插的是真正的醫事人員卡,還是健保卡、金融卡…只要有插卡都會成功,要真的去讀卡片的內容並驗證或許也是可行,但對我來說時間成本過高。

即使插的是正確的醫事人員卡,也不能確認背景簽章程式是否有正常運作,所以也是有可能會造成超過 24 小時未簽章的狀況。但如前所述,要確認簽章程式的運作狀態無法達成,只好退而以檢查讀卡機狀態代替之。

因為檢查讀卡機這個動作必須額外去觸發,所以必須改以快速鍵取代網頁控制鈕來發送報告,對於原先沒有習慣使用快速鍵的使用者,可能會有不適應的情形。

網頁上的報告發送按鈕
▲ 這個 script 必須搭配快速鍵發送報告,而無法直接點選網頁上的按鈕

結論

目前的版本功能還算陽春,也有一些限制,但因為可以和「發送報告」的流程整合,讓使用者不需要去花費額外的心力去注意是不是有帶卡、有插卡,有沒有不小心發成「正式報告」,所以在單純的使用環境下,應該還是有助益。

後記 – 測試時踢到的鐵板

一開始選定使用 WinSCard API 並沒有花太多的時間,但真正呼叫起來,卻是問題重重,因為之前對 Windows API 就不熟,一些型別到底對應到 AutoHotKey 上會是什麼也不確定,網路上也沒有完整的範例可以參考,亂試了一通後卡在 SCardListReaders 就下不去了,後來上 AHK Forum 求救才得到解答,根據 @qwerty12 的解釋,是因為一開始沒有把 WinSCard load 進來,所以每次呼叫 DllCall 都是全新的狀態,便無法從中取得 handle該討論串

許多人遇到 error 6 (ERROR_INVALID_HANDLE) 多是因為 32-bit, 64-bit OS 或 driver 造成的問題,但 @qwerty12 提供的範例已經有針對 64-bit OS 做處理,省去我另一個踢鐵板的機會。

第二個鐵板是 connect 後沒有正確的 disconnect。開始執行一段時間後,如果背景簽章程式是正常運作的,便會出現如下圖的錯誤訊息:

因為沒有 disconnect 使得資源被佔用而造成背景簽章程式的錯誤
▲ 因為沒有 disconnect 使得資源被佔用而造成背景簽章程式的錯誤

檢查起來,發現自己 connect 讀卡機,check 完狀態後,並沒有把它 disconnect,可能是高階語言寫多了,已經忘記 C 語言的習慣,誤以為它會自動幫我 close… ^^a。因為背景簽章程式有暫存認證的資訊,所以雖然有錯誤,還是可以繼續執行下去,只是三不五時跳出通知視窗很礙眼就是了。


Leave a Reply