腳本執行的機制
這一頁把前面幾頁提到、但沒有細講的「為什麼」集中說明:為什麼執行中面板會暫停 更新、大概能期待多快的速度、腳本能做什麼不能做什麼,以及什麼時候該寫腳本、 什麼時候該考慮寫一顆 Plugin。
執行中獨佔硬體存取
每一個 dongle 各自有一條負責它自己 I2C/GPIO 溝通的執行緒;平常背景輪詢、
使用者手動下的每一個指令,只要是對著同一個 dongle,就都是輪流排隊使用那一條
執行緒。腳本執行的整段期間,這條執行緒整個被這支腳本佔用:同一個 dongle 上
的背景輪詢不會更新、監看面板不會更新,使用者這時候對同一個 dongle 手動下的指令
也要等腳本結束才輪得到。這就是「等待與中止」提過的「面板暫時看不到
新資料是預期行為」的完整原因,也是上一頁提到「watch.add() 登記的清單要等腳本
結束才會真的被讀」的原因。如果你的環境掛了不只一個 dongle,其他 dongle 各自
有自己的執行緒,完全不受這支腳本影響,該怎麼更新就怎麼更新。
UART 是例外:每個開啟中的 UART 埠都有自己獨立的收訊執行緒,不受腳本佔用
I2C 執行緒影響,收到的資料照樣持續進到 Log 裡——這正是GPIO 與 UART
的 uart.read()/uart.expect() 可以在腳本執行期間等到裝置新送來的資料的原因。
腳本結束後(不論通過、失敗、出錯,或被中止),這條執行緒就交還給背景輪詢與後續 的使用者操作,畫面會恢復正常更新。
大概多快
真正花時間的幾乎都是硬體交易本身(I2C 的每一次讀寫、delay_ms() 的等待),
不是 Lua 這個語言本身的運算速度——Lua 執行一般的變數運算、流程控制,速度遠比
任何硬體交易快得多。想加快一支腳本,該檢查的通常是「有沒有讀寫了不必要的位移」
「delay_ms() 的時間是不是設得比實際需要的長」,而不是擔心 Lua 本身慢。
delay_ms() 與中止檢查一樣,實際精度受作業系統排程影響,不保證跟你要求的毫秒數
分毫不差;差距通常很小,一般用途不需要在意。
沙盒限制:能用什麼、不能用什麼
腳本能用的函式庫列在API 參考的「開放的標準函式庫」一節:base、
table、string、math,加上 Nexus 自己的 i2c/uart/gpio/poll/
watch/ic/file/bit 這幾組,以及 log/check/fail/print/
delay_ms/dump/dump_all/timestamp 這些全域函式。
沒有開放 io、os、package、require——腳本沒辦法讀寫任意路徑的檔案。
能寫檔的只有 file.append()(自己指定檔名)與 dump_all()(檔名固定的格式,
見記錄與傾印),而且兩者都固定寫進目前資料夾底下的 logs/
資料夾,不能指定別的路徑。腳本也沒辦法呼叫系統指令、載入外部的 .lua 模組或
DLL。這是刻意的限制:腳本應該只做「跟這台裝置對話、判斷結果、留下記錄」這件事,
不該變成一個可以任意操作這台電腦的管道。
腳本 vs Plugin:怎麼選
兩者都能讓程式跟硬體互動,但服務的場景不一樣:
| Lua 腳本 | Plugin | |
|---|---|---|
| 怎麼做出來 | 寫一個 .lua 檔,存檔就能跑,不用編譯 | 寫成一顆 DLL,要編譯 |
| 有沒有自己的畫面 | 沒有,只能寫 Log | 可以宣告自己的 Widget,或整個自畫面板 |
| 執行時機 | 你叫它跑的時候才跑一次,跑完就結束 | 常駐:程式開著、面板存在,它就在 |
| 適合做什麼 | 一次性或重複執行的動作序列:量產測試、依序驗證一組暫存器 | 需要持續呈現狀態、提供自訂控制項、或要包裝給其他人安裝使用的功能 |
一句話的判斷方式:只是想把一串操作自動化、跑完看結果,用腳本;想要一個一直 存在、有自己畫面或控制項、能被安裝散布給別人用的東西,考慮寫 Plugin。 兩者 不互斥——量產測試腳本裡也完全可以呼叫由 Plugin 提供的自訂指令。Plugin 開發從 Plugin 開發看起。
接下來
前面把腳本能做的事一一介紹過了,下一頁用三支比較完整的範例把它們串在一起: 掃描一段暫存器、多欄位產線判定,以及一個帶壓力測試性質的寫讀迴圈。