Skip to Content
Plugin 開發架構總覽

架構總覽

寫 Plugin 之前,先花五分鐘弄清楚三件事:主程式怎麼分層、你的 Plugin 站在哪裡、 你寫的每一段程式碼會跑在哪個執行緒上。第三件事是新手最常踩坑的地方。

分層

TUI Hostnexus_tui.exeGUI Hostnexus_gui.exehost_common版面、指令列、設定(兩個 Host 共用)Core.dllJob Queue、Worker 執行緒nexus_hal.dll硬體抽象層,以名稱選後端dongles\*.dll各家 USB-I2C 轉接器(含模擬用的 mock)你的 Pluginplugins\*.dll

由上往下:

  1. Host:使用者看到的程式,終端機版與視窗版兩種。它們只負責「畫出來」與「收輸入」。
  2. host_common:兩個 Host 共用的部分——指令列怎麼解析、面板怎麼排版、設定檔怎麼讀寫。
  3. Core.dll:核心。有專門的 I2C Worker 執行緒(每支 dongle 一條),硬體存取由它執行 (背景輪詢等工作排進 Job Queue 由它依序處理,ExecSync() 則直接在它上面同步執行);你的 Plugin 也是由 Core 載入、由 Core 呼叫。
  4. nexus_hal.dll:硬體抽象層,把「讀一個暫存器」翻成各家轉接器的操作。
  5. dongles:每一支 .dll 對應一種 USB 轉 I2C 硬體。mock 是不需要硬體的模擬版。

Plugin 站在哪裡

Plugin 跟 Core 之間隔著兩張表,這就是 Plugin API 的全部:

  • IcPlugin:你填一張表交給 Core,表裡放名字、I2C 位址、以及一堆 callback (Core 在特定時機呼叫你的函式)。除了 cleanup(必填)之外,每個 callback 都是選用的,不提供就填 0。
  • NxHostApi(HostAPI):Core 交給你一張函式表,你要讀寫暫存器、寫 Log、 更新面板,都是呼叫這張表裡的函式。

你的 Plugin 看不到、也不該去碰 Host、host_common、HAL。 PDK 裡只有 sdk/ 的標頭檔, 沒有其他原始碼,這是刻意的:沒給的東西就沒辦法 #include,邊界就不會被不小心越過。

三種掛載模式

同一顆 Plugin DLL,依使用者怎麼掛載它,會落在三種模式之一:

模式怎麼進入有沒有面板適合
headless不需要使用者做什麼:由 Plugin 自己決定(它宣告「沒有面板」),plugin switch <名稱> 就是 headless,帶了 @p<N> 也會被忽略;批次模式不帶 @p<N> 時也是 headless沒有只要自訂指令與背景輪詢
widgetplugin switch <名稱> @p<N>:主程式會把那一格自動改成 Plugin Widget 面板有:你宣告控制項,主程式排版大多數 Plugin
canvasplugin switch <名稱> @p<N>:那一格自動改成 Plugin Canvas 面板有:你自己畫每一格熱力圖、波形

@p<N> 是象限編號,@p0 是左上角那一格。要有面板的 Plugin(widget、canvas)一律帶 @p<N>: 互動模式不帶的話,它會掛在目前有焦點的面板上並把那一格改型;沒有任何面板有焦點時則報錯 plugin switch: no focused panel; pass @p<N>(後面會附上可用的 @p 清單)。本教學的 hello_ic 步驟 1~3 宣告沒有面板,所以是 headless;步驟 4 宣告了 widget 面板,要帶 @p0。Widget 與 Canvas 的差別見 Plugin Widget 與 Plugin Canvas。

三種模式收到的 callback 不完全一樣:例如 headless 沒有面板,所以收不到按鍵與 Widget 事件。完整對照表見生命週期與執行緒。

執行緒:哪一段程式跑在哪裡

Plugin 的 callback 會落在兩條執行緒之一:

執行緒跑這些 callback能做什麼
UI Thread(畫面與輸入)init、on_key_press、on_layout_changed、設定的載入與收集等不能讀寫 I2C,也不能睡或做任何耗時的事,否則畫面會卡住
I2C Worker Threadprobe、poll_fn、自訂指令、on_widget_event、cleanup可以直接讀寫 I2C

新手最容易記錯的兩點:

  • init 在 UI Thread,cleanup 卻在 Worker Thread。 兩者不對稱。
  • 讀寫硬體的 ExecSync() 在 UI Thread 上呼叫會直接回傳錯誤 ERR_WRONG_THREAD(-85), 而且不會碰硬體。 所以「想在 init 裡讀一下暫存器」是行不通的;要讀就放進自訂指令、 probe 或 on_widget_event。步驟 3 會親手做這件事。

這樣設計是因為讀一次 I2C 可能要幾毫秒,甚至逾時等上更久。放在 UI Thread 上,使用者 的每一次打字、每一次捲動都會跟著卡。所以 Core 把所有硬體存取集中到 Worker 執行緒上執行, 你的 callback 落在哪條執行緒,就已經決定了你能做什麼。

完整的執行緒歸屬表在生命週期與執行緒,是所有 callback 的單一真相。

下一步

先準備編譯器。