Skip to Content
Plugin 開發參考文件API 參考:IcPlugin

API 參考:IcPlugin

本頁內容與 PDK 內的 docs\NEXUS_TUI_PLUGIN_API_REFERENCE.md 相同(依章節分頁,連結已改成站內連結)。

逐支 API 的精確契約:簽章、執行緒、使用時機、最小範例、常見錯誤。 流程與設計理由在 PLUGIN_GUIDE。


0. 怎麼讀這份文件

若尚未讀過 PLUGIN_GUIDE 第 3 章與第 4 章,先去讀——本文件假設你已知道自己的 callback 跑在哪條執行緒、掛載型態收得到什麼。

0.1 閱讀順序

閱讀順序(硬性)

  1. docs/NEXUS_TUI_PLUGIN_GUIDE.md 第 1 章:十分鐘上手(1a 移植既有韌體/1b 從零寫 Plugin)。
  2. 同一份指南的第 3 章(三態表:headless/widget/canvas)與第 4 章(執行緒歸屬)。
  3. 需要某一支 API 的精確契約時,才查 docs/NEXUS_TUI_PLUGIN_API_REFERENCE.md 的對應小節。

0.2 兩級模板與命名規則

每一個公開成員各佔一節,標題逐字是它的名字:

對象標題
結構成員(含函式指標)結構名::成員名,例如 NxHostApi::ExecSync
結構的全部 reserved* 欄位結構名::reserved(一節列出全部)
Port Shim 函式與巨集名字本身,例如 nx_i2c_reg_read、NX_PORT_PIN
Port Shim 相容別名巨集合併為一節「Port Shim 相容別名巨集」

函式、callback 與 Port Shim 用完整模板,固定六段:簽章、用途、執行緒(三行:正確的執行緒/在錯的執行緒呼叫會怎樣/怎麼確認;IcPlugin callback 另有一行「可達掛載型態」)、使用時機(含「不該用時改用誰」)、最小範例、常見錯誤與症狀。

資料欄位用精簡模板,固定四段:型別、意義、合法值/哨兵、何時填。

列舉值與 #define 常數不各自成節,在用到它們的欄位節內以表格列出。

0.3 與 PLUGIN_GUIDE 的分工

問題去哪裡
怎麼開始、生命週期、為什麼這樣設計PLUGIN_GUIDE
callback 跑在哪條執行緒(唯一真相)PLUGIN_GUIDE 第 4 章
哪種掛載型態收得到哪些 callbackPLUGIN_GUIDE 第 3 章與 sdk/nexus_plugin_api.h 的「Callback 派發狀態總表」
某支 API 的參數、回傳值、錯誤碼、範例本文件

0.4 ABI 現況與版本規則

  1. 目前 NX_PLUGIN_ABI_MAJOR 為 4、NX_PLUGIN_ABI_MINOR 為 1(sdk/ABI_VERSION.h)。4.0 把 widget 介面整批換成「.nxlay 版面檔+屬性+事件」(見 2.7 Widget);4.1 在結尾追加 Dongle 專屬服務(INT 事件 IcPlugin::on_int_event、2.11 的五支 HostAPI,以及 NX_JOB_VENDOR/NxJobRequest::vendor_op),4.0 的 Plugin 不用重編也能在 4.1 Host 上跑;major 不符的 DLL 一律拒載,ERR_ABI_MISMATCH 的 Log 訊息會說明方向:比 Host 舊的 DLL 要求「用 abi 4.x 的 PDK 重編」,比 Host 新的 DLL 要求「更新 Host」。
  2. Plugin 端:abi_major/abi_minor 直接填 ABI_VERSION.h 的兩個巨集;size_of_struct 填 (NxDword)sizeof(IcPlugin)。
  3. 凍結起點是第一顆外部 Plugin 發佈日。在那之前 abi_major 仍可能變動;收到新版 PDK 時,一律用新標頭重新編譯你的 Plugin。
  4. 版本落差的降級只發生在「Host 比 Plugin 新」的方向,由 Core 依你填的 size_of_struct 處理。只要你使用的欄位都屬於 ABI 4.0 的既有基線,就不需要使用 NX_ABI_HAS_FIELD;abi_minor 在 4.0 之後只會往上加(見 NxHostApi::abi_minor),你的 Plugin 若使用了某個較晚的 minor 才新增的欄位(例如 4.1 的 IntConfigure 等服務),跑在較舊 4.x Host 上時就需要用 NX_ABI_HAS_FIELD 確認該欄位真的落在對方的 size_of_struct 範圍內,樣板見 PLUGIN_GUIDE 第 10 章。

0.5 字串編碼

Host 與 Plugin 之間流動的每一條字串都是 UTF-8,兩個方向相同;ABI 版本與所有結構都沒有因此變動(這是文件約定,不是欄位)。

方向涵蓋的字串
Host 給 Plugin自訂指令的 argv、on_widget_event 的文字值、GetResolvedBackendName() 等回傳的名稱、Log 文字、路徑
Plugin 給 HostIcPlugin::name/description、NxSlaveDecl::label、widget 的標題與文字值、LogWrite() 的 msg、指令的說明與用法
  • 只含 ASCII 的字串完全不受影響。
  • 舊 Plugin 送出的非 ASCII 系統碼頁位元組(CP950、Shift-JIS…)不是合法 UTF-8;Host 一律把非法序列顯示成 U+FFFD(有損,不猜碼頁),不會拒載。
  • 例外:NxHostApi::LogWriteStyled 的 msg 契約不變,仍必須是純 ASCII(spans[] 位移是位元組)。
  • 以位元組計的上限與位移(NX_WLABEL_MAX_BYTES、NxLogSpan::start/len)仍是位元組,不是字元數或畫面欄數。
  • Plugin 端的轉換、以 UTF-8 路徑開檔、量欄寬與在字元邊界截斷,用 sdk/nexus_utf8.h(NxUtf8ToW/NxWToUtf8/NxFopenUtf8/NxUtf8DisplayWidth/NxUtf8Truncate)。
  • 重編不會自動讓 Plugin 的字串變成 UTF-8(原始檔碼頁、MSVC 的 /utf-8、VC6 的 \x 跳脫、*A API 與 fopen);各情況的改法見 PLUGIN_GUIDE 第 14 章。

1. IcPlugin

1.1 資料欄位

IcPlugin::abi_major

型別:NxDword

意義:本 Plugin 是依哪一個 Plugin ABI 主版本編譯的。Core 載入 DLL Plugin 時以此判定「能不能安全解讀這份介面的記憶體佈局」;不符即整顆拒絕載入。

合法值/哨兵:填 NX_PLUGIN_ABI_MAJOR(sdk/ABI_VERSION.h)。DLL Plugin 的 abi_major 與 Host 目前的主版本不符時,Core 拒絕載入並記一筆 ERROR,訊息同時印出雙方的 major/minor/size_of_struct,然後繼續掃描其餘 DLL。靜態 Plugin(編譯進主程式、不經 DLL 掃描載入者)目前不會被比對 abi_major——這個比對只發生在 DLL 載入路徑;靜態 Plugin 的註冊路徑只驗證 name/cleanup/slaves 系列欄位。

何時填:NexusPluginGetInterface() 回傳前。ABI 相容規則見 0.4 ABI 現況與版本規則。

IcPlugin::abi_minor

型別:NxDword

意義:本 Plugin 是依哪一個 Plugin ABI 次版本編譯的,供 Core 判定這份介面比自己新還是舊,以決定要不要降級讀取。

合法值/哨兵:填 NX_PLUGIN_ABI_MINOR。落後或超前皆接受,不是拒絕載入的理由:落後時 Core 依 size_of_struct 只讀取自己認得的欄位範圍;超前時(僅 DLL 路徑會檢查這個方向)Core 記一筆 WARN,說明超出 Host 已知大小的欄位會被忽略,但仍然接受載入。

何時填:同 abi_major。降級規則見 0.4 ABI 現況與版本規則。

IcPlugin::size_of_struct

型別:NxDword

意義:整個 IcPlugin 結構的位元組數,是 Core 判定「這份介面涵蓋到哪個欄位」的唯一依據。

合法值/哨兵:填 (NxDword)sizeof(IcPlugin)。DLL 與靜態 Plugin 兩條路徑要求的下限相同:必須至少涵蓋到 cleanup(ABI 明文唯二強制的欄位中位置較後的那一個;cleanup 在結構內的偏移比 slave_decl_size 更深)。DLL 載入路徑先驗過一次 ABI 相容性;兩條路徑最終都會進入靜態與 DLL 共用的註冊邏輯,先驗 cleanup 是否在範圍內,再驗 slaves/slave_count/slave_decl_size 是否在範圍內——後者的下限(slave_decl_size)比 cleanup 淺,故只要 cleanup 檢查通過,slave_decl_size 檢查必然也通過,不會單獨造成拒絕。上界不檢查,比 Host 新的 Plugin 本來就該有更大的結構。

何時填:同上。

IcPlugin::name

型別:const char *

意義:Plugin 的唯一名稱,供 plugin 指令族、Tab 標題(IC:<name>)與名稱衝突判定使用。

合法值/哨兵:靜態字串,長度須 < NX_PLUGIN_NAME_MAX(32,含結尾 NUL);不得為 NULL 或空字串,否則註冊時回 ERR_INVALID_PARAM。名稱衝突時先註冊者優先:靜態 Plugin 一律先於 DLL Plugin 註冊,後到者被拒絕並記一筆 WARN。

何時填:同上。

IcPlugin::description

型別:const char *

意義:一行說明文字,供列出已註冊 Plugin 的介面使用。

合法值/哨兵:不需要時填 NULL。是否在 size_of_struct 範圍內以降級判定決定;不在範圍內視為未提供,不會去讀取結構之外的記憶體。

何時填:同上。

IcPlugin::panel_mode

型別:NxByte

意義:本 Plugin 自訂面板的呈現方式。

合法值/哨兵:

值常數意義
0NX_PANEL_MODE_NONE不提供自訂面板(headless)
1NX_PANEL_MODE_WIDGETWidget 模式;跨 Host 完整可攜,建議首選
2NX_PANEL_MODE_CANVASCanvas 模式;字元格自由繪製,需 Host 宣告支援 Canvas 能力——本專案兩個官方 Host 皆支援,故選用 Canvas 不會在官方 Host 上損失功能;第三方 Host 未宣告時該面板顯示提示訊息,Plugin 不會因此被標記下線

較舊 Plugin 的 size_of_struct 未涵蓋本欄位時,Core 一律視為 NX_PANEL_MODE_NONE。

何時填:同上。三態的完整行為對照見 PLUGIN_GUIDE 第 3 章。

IcPlugin::slaves

型別:const NxSlaveDecl *

意義:本 Plugin 對外宣告的 slave 位址表;slaves[0] 是面板預設顯示對象(相當於舊版單一 default_addr 欄位的角色),slaves[1] 起是同一顆 IC 對外露出的其他位址(例如同一顆晶片的另一個功能窗口)。

合法值/哨兵:靜態儲存期,Core 只讀不釋放;不得為 NULL,否則拒絕載入並記一筆 ERROR,訊息會指出 slave_count 宣稱的數量但指標從未被賦值。slaves[] 每個元素的 stride 必須與 Host 編譯期的 sizeof(NxSlaveDecl) 逐位元組相符(見 IcPlugin::slave_decl_size),否則 slaves[1] 起每個元素都會落在錯的位移上。

何時填:同上。

IcPlugin::slave_count

型別:NxByte

意義:slaves[] 的元素數。

合法值/哨兵:1 ~ NX_MAX_SLAVES_PER_PLUGIN(16);0 或超過上限一律拒絕載入並記一筆 ERROR。

何時填:同上。

IcPlugin::slave_decl_size

型別:NxDword

意義:slaves[] 每個元素的位元組數,讓 Core 知道 NxSlaveDecl 的元素步長(size_of_struct 描述的是 IcPlugin 本身,無法描述陣列元素)。

合法值/哨兵:必須等於 (NxDword)sizeof(NxSlaveDecl);不符時 Core 拒絕載入這顆 Plugin 並記一筆 ERROR,訊息會同時印出雙方認知的大小,因為這代表 Plugin 端的 NxSlaveDecl 定義與本 Host 編譯期看到的不一致。

何時填:Plugin 在 NexusPluginGetInterface() 回傳前填好;Core 於載入時讀一次。ABI 相容規則見 0.4 ABI 現況與版本規則。

IcPlugin::commands

型別:const NxCommandDef *

意義:本 Plugin 的自訂指令表。headless 掛載沒有 Widget 面板也沒有 Canvas,自訂指令是它唯一的互動手段。

合法值/哨兵:不需要時填 NULL。靜態儲存期,Core 只讀不釋放。指令名稱與內建指令同名(含別名與簡碼,如 r、p)時內建優先:該筆自訂指令照常註冊但永遠叫不到,Host 在 Plugin 載入時對它記一筆 WARN。

何時填:同上。

IcPlugin::command_count

型別:NxWord

意義:commands 的元素數。

合法值/哨兵:commands 為 NULL 時填 0。

何時填:同上。

IcPlugin::reg_semantics

型別:const NxRegSemantic *

意義:暫存器語意表(選用)。標頭文件描述的設計意圖是供 UI 呈現「這個位址是什麼」,但目前沒有任何程式碼讀取本欄位——Core 不會將其轉發給 Host,也未與 TABLE widget 整合;宣告本表目前不會在畫面上產生任何可觀察效果。需要在畫面上呈現暫存器語意,現階段仍須改用 NxHostApi::SetWidgetProp 設 NX_WP_ROWS 自行組出 TABLE 的列資料。

合法值/哨兵:不需要時填 NULL。NxRegSemantic 陣列,靜態生命期。

何時填:同上。

IcPlugin::reg_semantic_count

型別:NxWord

意義:reg_semantics 的元素數;與 reg_semantics 本身一樣,目前沒有任何程式碼讀取。

合法值/哨兵:reg_semantics 為 NULL 時填 0。

何時填:同上。

IcPlugin::reserved

型別:依結構內出現順序——reserved0:NxByte [3](緊接 panel_mode 之後);reserved3:NxByte [3](緊接 slave_count 之後);reserved1:NxWord(緊接 command_count 之後);reserved2:NxWord(緊接 reg_semantic_count 之後)。

意義:保留給日後在結構結尾追加欄位使用。四個保留欄位的編號不連續(reserved0/reserved3/reserved1/reserved2),是欄位歷史插入順序造成的,不影響用法——各自只在自己所在的位置補齊到下一個對齊邊界。

合法值/哨兵:一律填 0(memset 整個結構即可)。

何時填:宣告靜態表時;Core 不讀取這些欄位的內容。

1.2 callback

IcPlugin::init

簽章

NxLong (NX_CALL *init)(NxPluginCtx ctx, const NxHostApi *host);

用途:Plugin 掛載時的初始化:保存 ctx/host、視需要宣告 Widget 面板、記一筆啟動 Log。可為 NULL(不需要初始化時)。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫,不存在「呼叫錯執行緒」的情境;但若在 init() 內呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取——需要硬體存取請改在 IcPlugin::probe 裡做。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表中你所在 callback 的那一列。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:唯一能保存 ctx/host、呼叫 LoadLayout() 載入面板版面的時機——此時掛載槽已標記為使用中,回頭呼叫 HostAPI 能查到自己。需要存取硬體時改在 IcPlugin::probe/IcPlugin::poll_fn 等 I2C Worker Thread callback 內用 NxHostApi::ExecSync。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 init 的函式裡 */ static NxLong NX_CALL my_init(NxPluginCtx ctx, const NxHostApi *host) { s_ctx = ctx; s_host = host; host->LogWrite(ctx, NX_LOG_INFO, "my_plugin loaded"); return ERR_OK; }

常見錯誤與症狀

  • 回傳非 ERR_OK:Core 視為「Plugin 自己決定不掛載」,記一筆 ERROR(不是崩潰),這顆 Plugin 的整組掛載進入失敗狀態保留(Tab 標題顯示失敗標記、已宣告的 Widget 變灰、Core 管理的 GPIO/UART 立即收回,probe() 不會被送出),使用者可用 plugin restart 把同一顆 Plugin 重新掛回同一個槽位(再走一次 init()),或卸載才釋放(plugin switch/plugin restart 的回傳值仍是你回傳的錯誤碼;Log 末尾會附上 plugin restart 的提示)。不會呼叫 cleanup()(init() 從未成功過)——因此回傳失敗之前,請自己釋放已經配置的資源(記憶體、執行緒、已開的檔案),Core 只回收它自己管的 GPIO/UART 與 Widget 儲存格。
  • 崩潰(SEH 接住):同上進入失敗狀態並記一筆 ERROR,同樣不呼叫 cleanup();clangmingw x86 建置下沒有例外隔離,崩潰會直接終止整個程式。
  • 在此呼叫 ExecSync:回 ERR_WRONG_THREAD。
  • 忘記保存 ctx/host:後續任何 HostAPI 呼叫都無從進行——ctx 沒有其他取得管道,init() 是它唯一一次被交付的時機。

IcPlugin::cleanup

簽章

void (NX_CALL *cleanup)(NxPluginCtx ctx);

用途:Plugin 卸載時釋放自有資源。強制實作,不得為 NULL——註冊時檢查,缺少者整顆 Plugin 被拒絕註冊。

執行緒

  • 正確執行緒:該掛載所綁定後端自己的 I2C Worker Thread(與 IcPlugin::probe/IcPlugin::poll_fn 同一條)——偵測到「本 Plugin 的 Job 佇列已清空、可以安全卸載」的正是這條 Worker Thread 自己,交給 UI Thread 收尾等於要求 UI 迴圈輪詢一個只有 Worker 才知道的狀態,而 Batch 模式根本沒有 UI 迴圈。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫;只有在「Job Queue 中屬於本 Plugin 的工作全部清空後」才會被呼叫(先標記待移除,期間拒絕同名 Plugin 頂替載入)。掛載槽在呼叫本 callback 之前就已經被清空(狀態改回未掛載,連同它綁定的面板一併釋放)——故呼叫期間以 ctx 反查掛載的 HostAPI 呼叫都會失敗,唯一例外是 Log:NxHostApi::LogWrite/NxHostApi::LogWriteStyled 照常寫進 Log(歸屬卸載前的 Tab,前綴為本 Plugin 名稱)。其餘回傳 void 的函式會被靜默忽略(不輸出、不報錯),回傳 NxLong 的函式回 ERR_INVALID_CONTEXT(例如 SubmitJob()——收尾中的 Plugin 不得再產生 Job);NxHostApi::ShouldAbort 回 TRUE(視為應中止),NxHostApi::GetHostCaps 回 0。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。init() 不隨之變更,仍在 UI Thread——掛載發生在本 Plugin 成為作用中之前,此時尚無任何屬於它的 Job。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:唯一能安全釋放 init()/後續 callback 配置的資源、清掉保存的 ctx/host 的時機。ctx 在呼叫期間就已經無法反查掛載(不是「返回後才失效」——掛載槽在呼叫本 callback 之前就先被清空了),因此本 callback 內部不要倚賴 Log 以外任何以 ctx 解析掛載的 HostAPI(SubmitJob()、ExecSync() 等)——它們會靜默失敗或回 ERR_INVALID_CONTEXT;LogWrite()/LogWriteStyled() 是例外,可以用來留一筆下線紀錄。除此之外只清掉自己保存的靜態變數即可。返回後這個 ctx 位址更是完全不能再使用,任何遲到的非同步回呼若保存了它去呼叫 HostAPI 都是錯誤用法。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 cleanup 的函式裡 */ static void NX_CALL my_cleanup(NxPluginCtx ctx) { NX_UNUSED_ARG(ctx); s_ctx = (NxPluginCtx)0; s_host = (const NxHostApi *)0; }

常見錯誤與症狀

  • 未實作(NULL):註冊階段即拒絕註冊此 Plugin,不會等到執行期才發現。
  • init() 從未成功(回傳失敗或崩潰):卸載時不會呼叫本 callback,Core 只做自己那一份資源回收;probe() 失敗而進入失敗狀態者,本 callback 延到使用者卸載時才呼叫。
  • 崩潰:卸載照常完成(槽位已在呼叫前釋放),只記一筆 ERROR 說明「crashed in cleanup(); unloaded anyway」——這是本 callback 與其餘全部 callback 的關鍵差異:其餘 callback 崩潰會把掛載標記為故障並停止呼叫,cleanup() 崩潰不會、也不能,因為它本來就是最後一次呼叫。
  • 在本 callback 內呼叫 Log 以外、以 ctx 反查掛載的 HostAPI(例如 SubmitJob() 想在下線前再寫一次暫存器):失敗——回傳 NxLong 的函式回 ERR_INVALID_CONTEXT,回傳 void 的函式直接不做事、也不報錯,ShouldAbort() 回 TRUE,GetHostCaps() 回 0。成因是掛載槽在呼叫本 callback 之前就已經被清空,這不是計時問題,每一次呼叫都會如此。LogWrite()/LogWriteStyled() 不在此列,照常寫進 Log。
  • 假設仍在 UI Thread(例如呼叫了只能在 UI Thread 用的 HostAPI):本 callback 的執行緒歸屬曾經是 UI Thread,後來改到 I2C Worker Thread;沿用舊假設寫的 Plugin 需要複查。

IcPlugin::probe

簽章

NxLong (NX_CALL *probe)(NxPluginCtx ctx);

用途:探測目標 IC 是否存在於匯流排上。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫;它是以 Job 的形式派給 Worker Thread 執行的(switch 流程在 UI Thread 上組好掛載狀態後,送一個高優先級 Job 過去,不會在 UI Thread 上直接呼叫)。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:plugin switch/啟動注入掛載成功、IcPlugin::init 成功、[Plugin.<name>] 設定逐鍵送達(見 IcPlugin::on_config_load)之後,Core 自動派出的一次性判定。只派給主掛載(slaves[0])——它是「這顆 Plugin 的硬體在不在」的一次判定,不是逐位址探測;逐位址的存在性判定應在 probe() 內用 NxHostApi::ExecSync 自行對每個宣告的位址各做一次讀取。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 probe 的函式裡 */ static NxLong NX_CALL my_probe(NxPluginCtx ctx) { NxJobRequest req; NxByte id_reg; NxLong rc; memset(&req, 0, sizeof(req)); req.size_of_struct = (NxDword)sizeof(req); req.op = NX_JOB_READ; req.offset = 0x00; req.length = 1; rc = s_host->ExecSync(ctx, &req, &id_reg, (NxWord)sizeof(id_reg)); return rc; }

常見錯誤與症狀

  • 回傳非 ERR_OK(探測到「硬體不在」):未帶強制旗標時記一筆 WARN(「probe failed…plugin kept in failed state」),這顆 Plugin 的整組掛載進入失敗狀態保留(不再自動卸載):不再收到任何 callback、Core 管理的 GPIO/UART 立即收回、Widget 變灰,直到使用者卸載,或用 plugin restart 重新掛載同一顆 Plugin(Log 末尾附有該指令的提示);Core 偵測到它所用的 dongle 離線、之後又重新連線時,Host 會自動重跑一次(輪詢關閉時不會;Core 只在傳輸失敗後才察覺離線,沒有任何東西在讀寫該 dongle 時拔插不一定被察覺,仍需手動 plugin restart)。此時不會呼叫 IcPlugin::cleanup——cleanup() 延到使用者卸載時才呼叫(init() 成功過,所以仍會呼叫一次);此分支不會把掛載標記為「探測失敗但仍保留」,手動(互動)掛載時設定檔也不會記住這顆 Plugin 的名稱;若名稱原本就寫在設定檔裡(啟動時自動載入),失敗後仍保留,下次啟動會再試一次。帶強制旗標時記一筆 WARN(「probe failed…but ‘force’ was given; plugin kept loaded」)並維持掛載,這個分支才會把掛載標記為「探測失敗但仍保留」,供面板呈現。
  • 崩潰:該掛載被標記為故障,Tab 標題顯示故障後綴,自此不再被呼叫任何 callback。
  • 佇列滿載使 Job 派不出去:不視為掛載失敗——Plugin 已經 init() 成功、狀態也已建立,只是這次沒有探測成功,記一筆 WARN 並標記為「探測失敗但仍保留」。
  • 在此假設自己在 UI Thread(例如不呼叫 ExecSync 而直接做別的阻塞等待):probe() 恆在 I2C Worker Thread 上,直接讀寫硬體本來就是它存在的理由,不需要繞路。

IcPlugin::poll_fn

簽章

NxLong (NX_CALL *poll_fn)(NxPluginCtx ctx);

用途:背景輪詢,依本掛載目前設定的輪詢週期呼叫;NxHostApi::SetPollingInterval 目前是未完工的骨架、呼叫它對這個週期沒有作用。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread——poll_fn 不是以 Job 形式派發的,Worker 的主迴圈在判定「該掛載已到期」後於自己的執行緒上直接呼叫它。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫;poll off 狀態(或該後端的暫停旗標)下,Worker 主迴圈判定下一次到期時間時直接視為「永不到期」,本 callback 完全不會被呼叫——閘門設在到期時間的計算處,不是呼叫了才被攔截。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要定期讀取狀態暫存器、更新 Widget 顯示值時使用。純軟體、不需要依賴硬體輪詢狀態的定時動作改用 IcPlugin::on_tick。設定了 INT(ABI 4.1 的 NxHostApi::IntConfigure,或 GpioConfigure(NX_PIN_FUNC_INT))之後,poll_fn 不會自動停止——Core 不替你判斷「有 INT 就不用輪詢」;請在 poll_fn 內自行節流(INT 生效時直接回 ERR_OK,或每 N 次才做一次保險讀),或由使用者把該掛載的輪詢週期設成 0(NxHostApi::SetPollingInterval 目前未實作,不能用來停);INT 涵蓋不全的 IC、wired-OR 線漏掉的邊緣都需要保險輪詢。INT 事件的取回本身也是背景輪詢,受 poll off 管制。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 poll_fn 的函式裡 */ static NxLong NX_CALL my_poll(NxPluginCtx ctx) { NxJobRequest req; NxByte status; NxLong rc; memset(&req, 0, sizeof(req)); req.size_of_struct = (NxDword)sizeof(req); req.op = NX_JOB_READ; req.offset = 0x01; req.length = 1; rc = s_host->ExecSync(ctx, &req, &status, (NxWord)sizeof(status)); if (rc == ERR_OK) { set_status_from_byte(status); } return rc; }

常見錯誤與症狀

  • 回傳非 ERR_OK:目前完全不影響任何行為——呼叫端把回傳值指標傳 NULL,Plugin 的回傳碼不會被讀取,也不會因此記任何 Log 或使掛載下線;只有「崩潰」這一種失敗會被 Core 觀察到。
  • 崩潰:該掛載被標記為故障,自下一次輪詢週期起不再被呼叫。
  • 以為 poll off 時仍會被呼叫:不會——閘門在判定「該掛載是否到期」的那一步就直接視為永不到期,poll_fn 本身根本不會被排程執行。

IcPlugin::interrupt_handler

簽章

NxLong (NX_CALL *interrupt_handler)(NxPluginCtx ctx);

用途:中斷處理。唯一的觸發來源是背景 GPIO 輪詢 tick(讀到硬體 INT 腳斷言,或上一輪有掛載回報「我消費了」而需要再掃一輪)——沒有使用者指令會直接觸發本 callback。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫;本 callback 是廣播的——中斷成立時,本匯流排上所有實作了它的掛載都會被呼叫一次,不限於宣告中斷能力的那一個,因為 dongle 的 INT 腳不攜帶任何身分資訊。poll off(或該後端的暫停旗標)下本 callback 完全不會被呼叫——閘門與 IcPlugin::poll_fn 同樣設在到期時間的計算處。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:判斷「是我的中斷」並讀取自己那顆 IC 的中斷狀態暫存器時使用。回傳 NX_EVT_CONSUMED 表示「我確實處理了東西」,會讓 Core 在下一個輪詢週期再廣播一輪,直到某一輪全部回 ERR_OK 為止——這是共享線加邊緣觸發的必要收斂,不要為了保險總是回 NX_EVT_CONSUMED。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 interrupt_handler 的函式裡 */ static NxLong NX_CALL my_interrupt(NxPluginCtx ctx) { NxJobRequest req; NxByte int_status; NxLong rc; memset(&req, 0, sizeof(req)); req.size_of_struct = (NxDword)sizeof(req); req.op = NX_JOB_READ; req.offset = 0x02; req.length = 1; rc = s_host->ExecSync(ctx, &req, &int_status, (NxWord)sizeof(int_status)); if (rc != ERR_OK || int_status == 0) { return ERR_OK; /* 不是我的/沒事做 */ } /* [範本] 讀清中斷旗標、更新狀態 */ return (NxLong)NX_EVT_CONSUMED; }

常見錯誤與症狀

  • 總是回 NX_EVT_CONSUMED:讓輪詢永不停歇——Core 會在連續多輪後記一筆 WARN,但那則 WARN 指名的是匯流排(哪個後端的中斷線收斂不了),不是哪一顆 Plugin 在洗;仍會繼續掃描,不會自動停止呼叫它。
  • 崩潰:該掛載被標記為故障;收斂掃描的終止條件(「崩潰→故障→退出廣播名單」)因此仍然成立,不會讓整輪廣播卡住。
  • 誤以為只有自己會被呼叫:共享中斷線上全部實作了本 callback 的掛載都會被叫到一次,必須先讀自己的中斷狀態暫存器確認再處理。

IcPlugin::on_key_press

簽章

int (NX_CALL *on_key_press)(NxPluginCtx ctx, int key, NxDword mods);

用途:按鍵攔截。TUI 與 GUI 兩個 Host 都會派發;差異:GUI 的 widget 面板上,焦點 widget 吃掉的鍵不會送達本 callback——焦點在 TEXTBOX/FILEPICK/SPIN 時所有鍵都由它消費,焦點在 BUTTON/CHECKBOX/RADIO/DROPDOWN 時只有 Space/Enter/方向鍵由它消費,其餘鍵(含 Tab)照常送達(依焦點 widget 的型別判定;視窗版的 widget 全部自繪);DROPDOWN 的清單展開期間,除 Tab(先收起清單、再照常送達)外所有鍵都由清單消費(下拉清單自繪後的行為,舊版焦點在 DROPDOWN 上會吞掉所有鍵);對話框、選單、版面配置器或 REG_TABLE 就地編輯開啟期間兩個 Host 都不派發。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。ESC 鍵不會被送達本 callback,不可攔截。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要攔截、消費特定按鍵的面板互動時使用(TUI 與 GUI 皆生效,GUI 焦點 widget 吃掉的鍵例外,見上)。key 取值來自 sdk/nexus_plugin_keys.h 的 NX_PKEY_*——不是 Win32 虛擬鍵碼,也不是 Host 內部鍵碼。只送給目前 Tab 的作用中 Plugin。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_key_press 的函式裡 */ static int NX_CALL my_on_key_press(NxPluginCtx ctx, int key, NxDword mods) { NX_UNUSED_ARG(ctx); NX_UNUSED_ARG(mods); if (key == NX_PKEY_F5) { /* [範本] 觸發一次手動更新 */ return NX_EVT_CONSUMED; } return NX_EVT_PASS; }

常見錯誤與症狀

  • 在 GUI Host 上期待焦點 widget 吃掉的鍵也送到本 callback:不會(見上,依焦點 widget 的型別判定);需要的鍵請避開輸入型 widget 有焦點時使用。
  • 把 key 當成 Win32 虛擬鍵碼比較:值域是 NX_PKEY_*,直接拿 Win32 的 VK_* 常數比對永遠不會相符。
  • 崩潰:該掛載被標記為故障,預設事件為 NX_EVT_PASS(不吞按鍵),使用者仍能繼續操作其餘功能。

IcPlugin::on_mouse_event

簽章

int (NX_CALL *on_mouse_event)(NxPluginCtx ctx, int rel_x, int rel_y, int button, int action);

用途:攔截 Canvas 面板上的滑鼠事件:按下、放開、按住拖曳、滾輪。座標是面板內容區內的相對抽象格(左上角為 0,0),不是像素。button 取自 sdk/nexus_plugin_keys.h 的 NX_PMOUSE_BTN_*(NONE/LEFT/RIGHT/MIDDLE),action 取自 NX_PMOUSE_ACT_*(DOWN/UP/DRAG/WHEEL_UP/WHEEL_DOWN)。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✗、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:Canvas 面板需要滑鼠互動(點選、拖曳、滾輪)時使用。規則:

  • 只送給 Canvas 面板。Widget 面板不送——它的互動走 IcPlugin::on_widget_event。標題列與框線上的點擊屬於 Host,不會送達。
  • 按下即捕獲:在 Canvas 內容區按下之後,直到放開為止,DRAG 與 UP 一律送給同一個面板,即使游標已移出面板——此時 rel_x/rel_y 可為負數或大於等於面板寬高,必須自行處理越界座標。UP 一定送達:最遲在下一筆會走到 Plugin 路徑的輸入、或 Host 失去滑鼠捕獲時補送。若期間該面板已不是 Canvas 面板或已換了另一顆 Plugin,該次不會送出。
  • DRAG 只在游標所在的格改變時送出(不是每個像素);button 是按住的那一顆。
  • 沒有 hover:沒有按住任何鍵的移動不會送達。
  • 滾輪只在「游標下的 Canvas 面板同時是焦點面板」時送出;點擊會先把焦點切到該面板再派發。
  • 回 NX_EVT_CONSUMED 表示整段手勢由 Plugin 處理,Host 不再處理;回 NX_EVT_PASS 則 Host 照常處理(後續 DRAG/UP 仍會送達)。
  • 不支援雙擊與修飾鍵。左/右/中鍵與滾輪兩個 Host 都送(TUI 的中鍵需主控台回報);回 NX_EVT_PASS 時 Host 的既有行為照舊(例如右鍵貼上)。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_mouse_event 的函式裡 */ static int NX_CALL my_on_mouse(NxPluginCtx ctx, int rel_x, int rel_y, int button, int action) { NX_UNUSED_ARG(ctx); if (action == NX_PMOUSE_ACT_WHEEL_DOWN) { /* [範本] 往下捲動一列 */ return NX_EVT_CONSUMED; } if (button == NX_PMOUSE_BTN_LEFT && (action == NX_PMOUSE_ACT_DOWN || action == NX_PMOUSE_ACT_DRAG)) { if (rel_x >= 0 && rel_y >= 0) /* DRAG 出界時座標可為負 */ { /* [範本] 選取或塗抹 (rel_x, rel_y) 那一格 */ } return NX_EVT_CONSUMED; } return NX_EVT_PASS; }

常見錯誤與症狀

  • 在 Widget 面板期待收到滑鼠事件:不會發生,Widget 面板只會收到 on_widget_event。
  • 沒處理越界座標:拖曳出面板後 rel_x/rel_y 為負或超出寬高,直接拿去當陣列索引會越界。
  • 期待 hover 或雙擊或修飾鍵:都不提供。
  • 把座標當像素:單位是抽象格(TUI 一格=一個字元格,GUI 一格=一個字元格的像素尺寸)。
  • 吞掉不屬於自己的事件(例如右鍵):使用者會發現該面板上右鍵貼上等 Host 功能莫名失效。
  • 崩潰:該掛載被標記為故障,事件視為 NX_EVT_PASS(Host 照常處理)。

IcPlugin::on_command_pre_execute

簽章

int (NX_CALL *on_command_pre_execute)(NxPluginCtx ctx, const char *in, char *out, int out_size);

用途:指令竊聽與改寫。TUI 與 GUI 兩個 Host 都會派發:使用者在 cmd> 打的指令,以及對話框、選單、chip 替使用者組出來的指令,都在解析前先送到這裡(in 是即將執行的那條指令)。收到的只有指令目標 Tab 上焦點象限所綁定的 Plugin;沒有焦點象限、或焦點象限沒有掛載時沒有任何 Plugin 收到。不會派發的情況:監看觸發(trigger ... do <command>)執行的命令、空字串(僅 NULL/"";只含空白的字串照常派發)、GUI Plugin Manager 的非降版 install(降版 install 與 remove 經確認框,照常進歷史並派發)。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要在使用者輸入的指令真正執行前攔截或改寫它時使用(兩個 Host 皆生效)。↑ 歷史記的是改寫前的原文;回傳 NX_EVT_CONSUMED 時指令不執行,但仍會進歷史。回傳 NX_EVT_REWRITTEN 時改寫內容一律寫進 out(Core 提供的緩衝區,容量恆為 NX_CMD_REWRITE_MAX)——禁止跨 DLL 配置或釋放記憶體。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_command_pre_execute 的函式裡 */ static int NX_CALL my_cmd_pre(NxPluginCtx ctx, const char *in, char *out, int out_size) { static const char reply[] = "read 0x00"; NX_UNUSED_ARG(ctx); if (strcmp(in, "shortcut") == 0) { if ((int)sizeof(reply) > out_size) { return NX_EVT_PASS; /* 裝不下就放棄改寫,讓原指令照常執行 */ } memcpy(out, reply, sizeof(reply)); return NX_EVT_REWRITTEN; } return NX_EVT_PASS; }

常見錯誤與症狀

  • 在 GUI Host 上依賴本 callback:目前完全不會被呼叫,不是缺陷回報對象——這是已知的尚未接線狀態。
  • 改寫內容超出 out_size:緩衝區由 Core 提供且容量固定,寫超過會破壞其他記憶體;務必使用有界的字串函式並檢查長度。
  • 崩潰:該掛載被標記為故障,原指令維持 NX_EVT_PASS(照常執行),不會使使用者的指令被吞掉。
  • 誤用 malloc 配置改寫結果再指派給 out 指標:out 是呼叫端的緩衝區,不是可替換的指標,這種寫法在跨 DLL 邊界必然造成記憶體錯誤。

IcPlugin::on_widget_event

簽章

void (NX_CALL *on_widget_event)(NxPluginCtx ctx, NxByte panel_id, NxWord widget_id, const NxWidgetEvent *evt);

用途:Widget 互動事件(ABI 4.0 簽章;3.x 的 int evt, const NxWidgetValue *value 已改為單一 NxWidgetEvent,見 4.1 NxWidgetEvent 與 NxPropVal)。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread——Plugin 對按鈕之類事件的典型反應是讀寫 I2C,而那本來就不該在 UI Thread 上做。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫,它經 Job Queue 非同步派發,不是同步呼叫。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✓、canvas ✗(見 PLUGIN_GUIDE 第 3 章)

使用時機:Widget 模式下回應使用者互動(勾選、下拉選擇、輸入、按鈕觸發)的唯一入口。evt->kind 只會是下列兩種,不認得的 kind 一律忽略(其餘編號保留給未來):

  • NX_WEVT_CHANGED:使用者改了值。evt->prop 是變動的屬性(目前只會是 NX_WP_VALUE),evt->val 是新值——CHECKBOX/DROPDOWN/CHOICE/SPIN 為 NX_WKIND_I64(SPIN 為有號值),TEXTBOX/FILEPICK 為 NX_WKIND_STR(u.str.ptr 以 NUL 結尾,指向 Core 的緩衝區,只在呼叫期間有效,要保留請自行複製)。
  • NX_WEVT_ACTIVATED:widget 被觸發。evt->val 一律是 NX_WKIND_U64 的位置封裝 (v << 16) | h,v、h 各 0 ~ 65535,是觸發點相對於該 widget 矩形的比例(BUTTON 的滑鼠點擊也帶位置,用不到的 Plugin 忽略即可);鍵盤觸發固定為 NX_WPOS_KEYBOARD(0x80008000,widget 中心)。位置封裝是無號值,不得當負數運算。

事件是非同步交付的:使用者操作在 UI Thread 排入佇列,由上述 Worker 依序呼叫本函式。

  • 合併只發生在佇列尾端:同一個面板上,新的 NX_WEVT_CHANGED 只在佇列尾端合併——該面板最後一筆尚未交付的 widget 事件若正是同一個 widget 的 NX_WEVT_CHANGED,新的取代它(只有最後一個值有意義);中間夾了其他事件(同一 widget 的 NX_WEVT_ACTIVATED、或其他 widget 的事件)就依序排在後面,Plugin 看到的順序與使用者操作一致。
  • 作廢:面板重新載入(內容不同的 LoadLayout()/LoadLayoutData())或 DestroyWidget() 之前排入的事件作廢,不會送達。
  • evt->user_data 是排入佇列當下該 widget 的 NX_WP_USER_DATA,不是交付當下的值。
  • MAX_LENGTH 截短的 CHANGED:調小 TEXTBOX 的 NX_WP_MAX_LENGTH 使值被截短時送出的 NX_WEVT_CHANGED 也走同一條非同步路徑:它在 SetWidgetProp() 返回之後才交付(在 handler 內寫入則在 handler 返回之後)。
  • ITEMS 縮小:在途的 NX_WEVT_CHANGED 若是 NX_WP_ITEMS 縮小之前排入、選項索引已超出新範圍,會被丟棄(不送錯誤的索引給 Plugin)。
  • Plugin 自己呼叫 SetWidgetProp() 不會產生事件(上述 MAX_LENGTH 截短為唯一例外)。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_widget_event 的函式裡 */ static void NX_CALL my_on_widget_event(NxPluginCtx ctx, NxByte panel_id, NxWord widget_id, const NxWidgetEvent *evt) { NX_UNUSED_ARG(panel_id); if (widget_id == WIDGET_ID_READ && evt->kind == NX_WEVT_ACTIVATED) { NxJobRequest req; NxByte val; NxLong rc; memset(&req, 0, sizeof(req)); req.size_of_struct = (NxDword)sizeof(req); req.op = NX_JOB_READ; req.offset = 0x00; req.length = 1; rc = s_host->ExecSync(ctx, &req, &val, (NxWord)sizeof(val)); if (rc != ERR_OK) { s_host->LogWrite(ctx, NX_LOG_WARN, "read failed"); } } }

常見錯誤與症狀

  • 把 NX_WEVT_ACTIVATED 的 evt->val.u.u64 當成有號整數運算:它是 (v << 16) | h 的位置封裝,需要先拆高低 16 位再各自當無號值處理。
  • 假設事件同步:在 SetWidgetProp() 之後立刻期待 handler 已被呼叫——事件一律在呼叫返回之後才交付。
  • 在 handler 外保留 evt 或 evt->val.u.str.ptr:兩者只在本次呼叫期間有效。
  • 以為連續兩次 NX_WEVT_CHANGED 一定合併:只有尾端相鄰的同 widget 事件才會合併,中間夾了別的事件就是兩筆。
  • 崩潰:該掛載被標記為故障,之後同一顆 Widget 的事件都不會再送達。
  • 假設在 UI Thread 上收到本事件因而直接做長時間阻塞:本 callback 恆在 I2C Worker Thread,阻塞它只會拖慢背景 Job 佇列,不會凍結 UI,但仍應避免不必要的長時間阻塞。

IcPlugin::on_layout_changed

簽章

void (NX_CALL *on_layout_changed)(NxPluginCtx ctx, NxByte panel_id, int width, int height);

用途:通知 Plugin 其面板的可用尺寸變了。width/height 是面板內容區的抽象格數,與 IcPlugin::ui_render_fn 收到的 width/height 是同一份幾何;面板不存在或零尺寸時皆為 0。panel_id 是面板 uid:GUI 為 0~63、TUI 為 0~3(型別仍是 NxByte,ABI 不變);請勿以固定 8 格陣列當 panel_id 的索引。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;請只快取尺寸,不要在此做阻塞操作。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:Plugin 需要依面板大小自適應繪圖或 widget 宣告時使用。只在尺寸有變時送:面板綁定後首次(必送)、主控台/視窗縮放、面板分割/合併/zoom、切回此 Tab 時尺寸與上次不同;面板解除綁定、不再是 Plugin 面板、或掛載進入卸載/故障狀態時送一次 0,0(同一面板之後換上新實例會再收到首次通知)。Widget 與 Canvas 面板都會送,headless 掛載沒有面板,不送。版面配置器預覽期間不送。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_layout_changed 的函式裡 */ static int s_panel_w; static int s_panel_h; static void NX_CALL my_on_layout_changed(NxPluginCtx ctx, NxByte panel_id, int width, int height) { NX_UNUSED_ARG(ctx); NX_UNUSED_ARG(panel_id); s_panel_w = width; /* 快取起來,之後繪圖與宣告 widget 時使用 */ s_panel_h = height; }

常見錯誤與症狀

  • 假設每次縮放都會收到通知:只有內容區尺寸真的改變才會送;重複相同尺寸不送。
  • 把 0,0 當成錯誤:那是「面板已不存在」的正常通知,應停止依賴尺寸繪圖。
  • 在此呼叫 Draw*:本 callback 不在 ui_render_fn 呼叫堆疊內,一律回 ERR_INVALID_CONTEXT。
  • 崩潰:該掛載被標記為故障,之後不再收到任何事件。

IcPlugin::on_tab_activate

簽章

void (NX_CALL *on_tab_activate)(NxPluginCtx ctx);

用途:本 Plugin 所屬的 Tab 被切換為作用中。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)
  • 本事件是「整個 Tab 級」的存在通知,不是 UI 來源事件:Core 對該 Tab 上每個 Plugin 實例各送一次,不看 panel_id,headless 掛載一樣收得到。宣告了多個 slave 位址的 Plugin 也只收一次,ctx 恆為主掛載(slaves[0])的上下文,與 IcPlugin::init 交付的那一個相同。

使用時機:需要在自己的 Tab 進入前景時做一次性動作(例如恢復輪詢頻率、刷新顯示)時使用。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_tab_activate 的函式裡 */ static void NX_CALL my_on_tab_activate(NxPluginCtx ctx) { s_host->LogWrite(ctx, NX_LOG_INFO, "tab activated"); }

常見錯誤與症狀

  • 崩潰:該掛載被標記為故障。
  • 誤以為只有 Widget/Canvas 面板的掛載會收到:headless 掛載一樣會收到,也在廣播範圍內。

IcPlugin::on_tab_deactivate

簽章

void (NX_CALL *on_tab_deactivate)(NxPluginCtx ctx);

用途:本 Plugin 所屬的 Tab 離開作用中狀態。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。與 on_tab_activate 同一次廣播機制的另一半,兩者必定成對出現,不會只送其中一個。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)
  • 與 IcPlugin::on_tab_activate 同一套派發規則:每個 Plugin 實例各一次(多 slave 亦同),含 headless,ctx 恆為主掛載的上下文,與 init() 交付的相同。

使用時機:需要在自己的 Tab 離開前景時做一次性動作(例如降低輪詢頻率)時使用。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_tab_deactivate 的函式裡 */ static void NX_CALL my_on_tab_deactivate(NxPluginCtx ctx) { s_host->LogWrite(ctx, NX_LOG_INFO, "tab deactivated"); }

常見錯誤與症狀

  • 崩潰:該掛載被標記為故障。
  • 只實作 on_tab_activate 而不實作本 callback:兩者各自獨立宣告,缺一個不會影響另一個被呼叫,但 Plugin 自己的狀態機若假設兩者成對,遺漏這一半會讓狀態卡在「以為還在前景」。

IcPlugin::on_config_load

簽章

void (NX_CALL *on_config_load)(NxPluginCtx ctx, const char *key, const char *value);

用途:逐筆交付本 Plugin 持久化設定中的一個鍵值。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;若在此呼叫 NxHostApi::ExecSync,那次呼叫會回 ERR_WRONG_THREAD 且不做任何硬體存取。在 IcPlugin::init 成功之後、IcPlugin::probe 送出之前,依設定段的行序逐鍵呼叫一次,同名鍵只送第一次出現者。只送給主掛載(slaves[0])——呼叫端固定以主掛載的掛載槽送出本 callback,衍生掛載(slaves[1..])根本不是這次呼叫的目標,故不會各自收到一份(不是因為它們與主掛載共用同一個 ctx——衍生掛載其實各自有自己的 ctx,只是這個 callback 從未以它們的 ctx 被呼叫過)。Batch 模式也會送(只讀不寫回)。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)——本 callback 由「掛載成功」這個生命週期事件驅動,不看有沒有象限。

使用時機:在 probe() 需要用到的設定值(例如「哪一支 GPIO 接了 reset」)應在此收下並存起來。想維持向前相容的 Plugin,應把收到的每一個鍵都存起來(含目前不認得的),供 IcPlugin::on_config_save 原樣連同自己更新過的鍵一起輸出。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_config_load 的函式裡 */ static void NX_CALL my_on_config_load(NxPluginCtx ctx, const char *key, const char *value) { NX_UNUSED_ARG(ctx); if (strcmp(key, "reset_gpio") == 0) { s_reset_gpio = (int)strtol(value, NULL, 10); } }

常見錯誤與症狀

  • 崩潰:本次掛載(含背後的 init())仍視為成功,但主掛載立即被標記為故障,且不送 probe()——剩餘尚未送出的鍵也不再送,故障訊息會指名是在 on_config_load() 內崩潰。
  • 只挑自己認得的鍵存起來、丟掉其餘鍵:下一次 on_config_save() 若只回傳自己認得的子集,會把使用者或其他工具寫入、本版本不認得的鍵永久刪除——這是整段取代而非差量合併的直接後果。
  • 假設鍵值已去除前後空白之外的其他處理:key/value 只保證已去前後空白,內容本身仍是原始設定字串。

IcPlugin::on_config_save

簽章

NxLong (NX_CALL *on_config_save)(NxPluginCtx ctx, char *buf, int buf_size);

用途:要求 Plugin 輸出待持久化的設定。

執行緒

  • 正確執行緒:UI Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在 UI Thread 上呼叫;呼叫時機在卸載前、Host 結束存檔前——後者落在關閉流程的收尾寬限期之內(約 5 秒且不等待任何 Job),這支 callback 必須快、不可阻塞,它擋在整個關閉流程正中央。ExecSync 在這裡不可用(UI Thread 上呼叫一律回 ERR_WRONG_THREAD)——不要嘗試在這裡最後同步讀一次硬體,只回傳早就持有在記憶體裡的狀態。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:卸載一顆已改過設定的 Plugin 之前、或整個 Host 結束存檔前,把目前要持久化的內容寫進 buf(Core 提供、已清為空字串的緩衝區,容量恆為 NX_PLUGIN_CONFIG_MAX)。這是整段取代,不是差量合併:這次沒回傳的鍵一律被視為移除。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_config_save 的函式裡 */ static NxLong NX_CALL my_on_config_save(NxPluginCtx ctx, char *buf, int buf_size) { char line[64]; size_t len; NX_UNUSED_ARG(ctx); sprintf(line, "reset_gpio=%d\n", s_reset_gpio); /* 32-bit int,line 容量足夠 */ len = strlen(line); if ((int)len > buf_size - 2) /* 有效上限是 buf_size - 2,不是 buf_size - 1 */ { return ERR_BUFFER_TOO_SMALL; } memcpy(buf, line, len + 1); return ERR_OK; }

常見錯誤與症狀

  • 回傳非 ERR_OK:本次收集作廢,維持先前已收集(或已從設定檔載入)的內容不變,Core 記一筆 WARN;不是「清空設定」。
  • 輸出內容含控制字元、鍵以 [/;/# 開頭、或長度超出容量:整份輸出作廢,記一筆 WARN,行為同上。有效文字長度上限是 buf_size - 2(NX_PLUGIN_CONFIG_MAX 下為 4094),不是 buf_size - 1:Core 返回後一律把 buf[buf_size - 1] 覆寫成 NUL,只有在此之前自己就寫了結尾 NUL 的輸出才確定沒被截斷;以 len >= buf_size 當界線會少擋兩個位元組。
  • 在此呼叫 ExecSync:回 ERR_WRONG_THREAD,因為本 callback 恆在 UI Thread。
  • 執行時間過長:本 callback 擋在關閉流程正中央,過久的阻塞會拖慢整個程式的結束時間。

IcPlugin::on_uart_rx

簽章

void (NX_CALL *on_uart_rx)(NxPluginCtx ctx, NxByte tab_id, const NxByte *data, int len);

用途:永久停用:Core 全庫零呼叫點,改用 IcPlugin::on_uart_rx_ex。本欄位保留位置僅為 ABI 相容,不會被呼叫。

執行緒

  • 正確執行緒:不適用——本 callback 永久停用,不會被呼叫。
  • 在錯的執行緒呼叫會怎樣:不適用。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✗、canvas ✗(見 PLUGIN_GUIDE 第 3 章)

使用時機:— 永久停用,請改實作 IcPlugin::on_uart_rx_ex。

最小範例

— 永久停用,不提供範例。

常見錯誤與症狀

  • 只實作本欄位、沒有實作 on_uart_rx_ex:等同完全沒有接收 UART 資料的能力,不會有任何錯誤訊息提示——本欄位存在但從未被呼叫。

IcPlugin::on_log_generated

簽章

void (NX_CALL *on_log_generated)(NxPluginCtx ctx, NxDword level, const char *msg);

用途:有新的 Log 產生時通知 Plugin(廣播型、不可攔截)。所有等級、所有來源(含 Plugin 自己寫的 Log)都會送;每個 Plugin 實例每筆只收一次,宣告多個 slave 也一樣。

執行緒

  • 正確執行緒:Core 專屬的 Log 廣播執行緒——不是產生該筆 Log 的執行緒,也不是 UI Thread 或 I2C Worker。非同步:呼叫時該筆 Log 已進 Log 面板的 Ring,且可能略晚於產生時刻。Plugin 仍須自行假設多執行緒進入。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在廣播執行緒上呼叫;時序保證:init() 成功返回之前不會收到(init() 失敗的 Plugin 永遠收不到,init() 期間寫的 Log 也不補送);cleanup() 開始之後不會收到——runtime 卸載與 Core_Shutdown() 都會等在飛的廣播 callback 返回才呼叫 cleanup()。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要旁聽全域 Log(鏡射到檔案、依關鍵字告警)時。本 callback 內呼叫 LogWrite() 會被直接丟棄以防無限遞迴。callback 執行太久會讓廣播落後——Ring 已覆寫而未送達的筆會被略過(Core 記一則 WARN),不會拖慢 Log 的寫入端;請保持 callback 輕量。

最小範例

static void NX_CALL my_on_log(NxPluginCtx ctx, NxDword level, const char *msg) { NX_UNUSED_ARG(ctx); if (level == NX_LOG_ERROR) { /* 只做輕量處理(例如記到自己的佇列);不要在此呼叫 LogWrite()。 */ my_queue_push(msg); } }

常見錯誤與症狀

  • 在本 callback 內呼叫 LogWrite():被直接丟棄,不會出現在 Log 面板,也不會遞迴。
  • 假設本 callback 與產生 Log 的動作同步、或在同一條執行緒:不成立;需要跨執行緒共享的狀態要自行加鎖。
  • callback 崩潰:只讓該 Plugin 下線(Log 會出現一筆「faulted in on_log_generated」ERROR),廣播與其他 Plugin 不受影響。

IcPlugin::on_watch_triggered

簽章

void (NX_CALL *on_watch_triggered)(NxPluginCtx ctx, NxWord watch_id, NxByte bank, NxByte offset, NxByte value);

用途:使用者(或腳本)為該 Plugin 的掛載登記的 Watch 條件觸發(rise/fall/any)時通知 Plugin。watch_id 是該 Tab 內的 Watch 槽索引;位址 > 0xFF 時拆成 bank/offset;value 是觸發當下讀到的新值。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:只送該 Watch 所屬掛載上的 Plugin,不廣播給其他 Plugin;被節流(同一條 Watch 1 秒內的重複觸發)擋下的觸發不送。poll off 時 WatchList 停止更新,本 callback 在該狀態下不會被呼叫,這是預期行為而非缺陷。因在 I2C Worker 上執行,可以直接使用 ExecSync(),但不要長時間阻塞。

最小範例

static void NX_CALL my_on_watch(NxPluginCtx ctx, NxWord watch_id, NxByte bank, NxByte offset, NxByte value) { NX_UNUSED_ARG(ctx); NX_UNUSED_ARG(watch_id); /* 例如:只在 bank 0 的 0x10 變成非零時反應。 */ if (bank == 0 && offset == 0x10 && value != 0) { my_flag = 1; } }

常見錯誤與症狀

  • 期待收到其他掛載(其他 Plugin 或使用者自己登記)的 Watch:不會——只送 Watch 所屬掛載上的 Plugin。
  • 期待每次變化都收到:節流窗口內的重複觸發不送(次數仍會累計在 Watch 面板)。

IcPlugin::on_tick

簽章

void (NX_CALL *on_tick)(NxPluginCtx ctx);

用途:純軟體定時脈動,不受硬體 polling 狀態影響。

執行緒

  • 正確執行緒:該掛載所綁定後端的 I2C Worker Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫;poll off 時仍會執行,其中若呼叫 SubmitJob() 送出低優先級 Job 會得到「輪詢已停用」的錯誤(ERR_POLL_DISABLED)——高優先級 Job 不受影響照常入列。該掛載所綁定後端目前處於暫停/離線狀態時,低優先級 SubmitJob() 同樣被拒絕,閘門邏輯與 poll off 共用同一處判斷,不是各自獨立的兩套規則。「不主動發起低優先級 Job」不等於「停止一切軟體活動」。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要一個不隨 poll off 停止、單純的軟體時脈時使用(例如倒數計時、UI 動畫節拍)。每個 Plugin 實例只收到一次,即使宣告了多個 slave 位址也一樣——原因是派發只鎖定主掛載:廣播迴圈會先過濾掉全部衍生掛載(slaves[1..]),只對主掛載呼叫一次,不是因為衍生掛載與主掛載共用 ctx(衍生掛載其實各自有自己的 ctx,只是根本不會被這個迴圈呼叫到)。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_tick 的函式裡 */ static void NX_CALL my_on_tick(NxPluginCtx ctx) { NX_UNUSED_ARG(ctx); s_tick_count++; }

常見錯誤與症狀

  • 假設 poll off 時完全不會被呼叫:仍會被呼叫,只是其中低優先級的 SubmitJob() 會被拒絕(高優先級不受影響);ExecSync 完全不受 poll off 限制,因為它繞過 Job Queue,不經過這道 gating。
  • 崩潰:該掛載被標記為故障,自下一次 tick 起不再被呼叫。

IcPlugin::ui_render_fn

簽章

void (NX_CALL *ui_render_fn)(NxPluginCtx ctx, NxByte panel_id, int width, int height);

用途:繪製本 Plugin 的 Canvas 面板。僅 panel_mode == NX_PANEL_MODE_CANVAS 時會被呼叫。panel_id 是面板 uid:GUI 為 0~63、TUI 為 0~3(型別仍是 NxByte,ABI 不變);請勿以固定 8 格陣列當 panel_id 的索引。

執行緒

  • 正確執行緒:Core 擁有的 render 執行緒(每個 Canvas 掛載一條;不是 UI Thread),進入前 Core 已釋放全部共享鎖。
  • 與其他 callback 的關係:本 callback 與你的 UI Thread callback(on_key_press、on_mouse_event、on_layout_changed)以及 I2C Worker 上的 callback 並行執行。兩邊共用的狀態必須由你自己加鎖(例如 CRITICAL_SECTION,init 建立、cleanup 刪除);建議持鎖時只做記憶體複製,放鎖後才呼叫 Draw*。
  • 允許與禁止:允許呼叫 NxHostApi::ExecSync(Core 轉交 I2C Worker 以高優先級執行,最多等 5 秒);禁止 Sleep 與 malloc。
  • 耗時保護:超過 10 毫秒只記一次 WARN,不下線;超過 2 秒記 ERROR,該面板顯示 render 無回應 並暫停派發新請求,本函式返回後恢復;卡死的執行緒不會被終止、Plugin DLL 不會被卸載。崩潰仍使該掛載下線。繪圖指令被錄下後由 Host 在 UI Thread 重播(畫面晚一幀),每面板上限 64 KB。Core 進入本 callback 前會先確認自己沒有持有任何共享鎖,若持有則拒絕呼叫並記一筆 ERROR(避免必然的死結)。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✗、widget ✗、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:Canvas 模式下每一幀繪製面板內容的唯一入口。渲染路徑禁止動態記憶體配置;同一幀內只准提交一次繪圖結果。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 ui_render_fn 的函式裡 */ static void NX_CALL my_ui_render_fn(NxPluginCtx ctx, NxByte panel_id, int width, int height) { NX_UNUSED_ARG(panel_id); NX_UNUSED_ARG(width); NX_UNUSED_ARG(height); /* [範本] 用先前快照到 s_snapshot 的暫存器資料畫圖,不在此讀硬體 */ NX_UNUSED_ARG(ctx); }

常見錯誤與症狀

  • 沒有替與 UI callback 共用的變數加鎖:ui_render_fn 與 on_key_press 等並行,讀到寫到一半的值(撕裂讀),症狀是偶發的畫面錯亂,很難重現。
  • 持鎖時呼叫 Draw*/NotifyUiRefresh()/SubmitJob():可能與其他執行緒形成鎖序死結;先在鎖內複製,放鎖後再呼叫。
  • 繪製耗時超過 10 毫秒:只記一次 WARN,不影響掛載狀態;超過 2 秒則面板顯示 render 無回應 並暫停派發,返回後自動恢復。
  • 崩潰:該掛載被標記為故障,訊息指出是在 ui_render_fn 崩潰。
  • 在此做動態記憶體配置:違反渲染路徑不得配置的限制,即使沒有立即症狀也應避免。

IcPlugin::on_uart_rx_ex

簽章

void (NX_CALL *on_uart_rx_ex)(NxPluginCtx ctx, NxByte tab_id, NxByte panel_id, const NxByte *data, NxDword len);

用途:UART 收到資料,來源以 (tab_id, panel_id) 識別(廣播型,不可攔截)。panel_id 是面板 uid:GUI 為 0~63、TUI 為 0~3(型別仍是 NxByte,ABI 不變);請勿以固定 8 格陣列當 panel_id 的索引。

執行緒

  • 正確執行緒:該埠自己的 UART Worker Thread,不是 I2C Worker Thread。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫。只有面板(象限)擁有的埠會觸發本 callback——廣播對象是全部已掛載的 Plugin;Plugin 以自己的 API 私下開啟的埠永遠不會觸發本 callback,只能由擁有者自己主動讀取,這是刻意避免未經同意就讓其他 Plugin 看到私有埠資料。
  • 如何確認:查 PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:需要觀察畫面上某個 UART 面板收到的原始資料(未經行切分)時使用;每個 Plugin 實例只收到一次廣播,不論宣告了多少個 slave 位址。data 僅在本次呼叫期間有效,需要保留必須自行複製。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_uart_rx_ex 的函式裡 */ static void NX_CALL my_on_uart_rx_ex(NxPluginCtx ctx, NxByte tab_id, NxByte panel_id, const NxByte *data, NxDword len) { NX_UNUSED_ARG(ctx); NX_UNUSED_ARG(tab_id); NX_UNUSED_ARG(panel_id); NX_UNUSED_ARG(data); s_uart_byte_count += (unsigned long)len; }

常見錯誤與症狀

  • 保存 data 指標供之後使用:僅在本次呼叫期間有效,返回後即失效,需要的話必須複製到自己的緩衝區。
  • 期待私有埠(自己以 API 開啟)的資料也會廣播到這裡:不會,私有埠只能由擁有者自己讀取。
  • 崩潰:該掛載被標記為故障,不影響其餘 Plugin 繼續收到廣播。

IcPlugin::version_major

型別:NxWord

意義:Plugin 自身版本號主版,與 ABI 版本無關——ABI 版本(abi_major/abi_minor)描述的是「這份介面的記憶體佈局是哪一版」,本欄描述的是「Plugin 作者自己的版本號」,供 Plugin Manager 顯示與比對更新用。

合法值/哨兵:0 ~ 65535。ABI 3.1 起結尾新增(4.0 的基線);Core 以 NX_ABI_HAS_FIELD(size_of_struct, IcPlugin, version_patch)(四個新欄位裡最後一個,涵蓋到它即涵蓋全部四個)判斷 size_of_struct 是否涵蓋這批新欄位。size_of_struct 未涵蓋到 version_patch(即更舊 ABI 的 Plugin)時,Core 視版本為未知,一律不讀取結構之外的記憶體。version_major/version_minor/version_patch 三者全為 0 同樣視為「未宣告」(不是一個合法的真實版本號 0.0.0)——即使 size_of_struct 完整涵蓋這三個欄位,Core 仍會回報版本未知;至少要有一段非零(建議從 1.0.0,或至少 0.0.1 開始編號)。

何時填:NexusPluginGetInterface() 回傳前。

IcPlugin::version_minor

型別:NxWord

意義:Plugin 自身版本號次版,見 version_major。

合法值/哨兵:0 ~ 65535。

何時填:同上。

IcPlugin::version_patch

型別:NxWord

意義:Plugin 自身版本號修訂版,見 version_major。

合法值/哨兵:0 ~ 65535。

何時填:同上。

IcPlugin::version_rsv

型別:NxWord

意義:保留欄位,供未來擴充。

合法值/哨兵:必須填 0;不得挪作他用。

何時填:同上。

IcPlugin::on_int_event

簽章

void (NX_CALL *on_int_event)(NxPluginCtx ctx, const NxIntEvent *events, NxDword count, NxDword dropped);

用途:Dongle 的外部中斷(INT)腳偵測到邊緣時,交付一批逐邊緣事件(廣播型)。ABI 4.1 在 IcPlugin 結尾追加;4.0 大小的 Plugin 看不到本欄位,Core 視為未提供(不呼叫、也不當成錯誤)。NxIntEvent 成員:t_us(Dongle 本地微秒時間戳,約 71.6 分鐘繞回一次:算間隔一律用 (NxDword)(b.t_us - a.t_us),不要當絕對時間)、count(該邊緣之後的累計邊緣數)、rx_tick(Core 從 Dongle 讀回該事件時的毫秒計時;同一次讀回的事件共用,一次 callback 的陣列可跨多次讀回,所以同一次呼叫裡的 rx_tick 可以不同;poll off 累積很久之後拿它估計絕對時間會差很遠,只能當粗略參考)、level(邊緣之後的電位)、reserved0/reserved1(保留,唯讀)。

執行緒

  • 正確執行緒:本掛載綁定後端的 I2C Worker Thread(與 poll_fn 同一條,彼此不並行)。呼叫時 Core 不持有任何受管理的鎖,callback 內可以呼叫 ExecSync()、GpioRead() 等。
  • 在錯的執行緒呼叫會怎樣:本 callback 恆由 Core 在正確的執行緒上呼叫。
  • 如何確認:PLUGIN_GUIDE 第 4 章執行緒歸屬表。
  • 可達掛載型態:headless ✓、widget ✓、canvas ✓(見 PLUGIN_GUIDE 第 3 章)

使用時機:IC 的中斷輸出接到 Dongle 的 INT 腳,且你已用 NxHostApi::IntConfigure 啟用邊緣偵測時使用。事件由舊到新排列,events 只在本次呼叫期間有效。count 為 1~NX_INT_EVENT_BATCH_MAX(64);一次輪詢最多取回 1024 筆,超過的分批連續呼叫。dropped 是自上一次交付以來新增的丟棄數(增量,不是累計):非零表示本批與上一批之間有邊緣缺漏,處理方式通常是重新讀一次 IC 的狀態暫存器。

事件被消耗一次,並且廣播給該後端上所有實作本 callback 的掛載(共享的 INT 線上是誰拉的,要各自讀暫存器才知道)。有 INT 時 poll_fn 不會自動停止——建議停掉它,或改成慢速保險輪詢(INT 涵蓋不全的 IC、wired-OR 線漏掉的邊緣都需要保險);事件的取回本身也是背景輪詢,受 poll off 管制,poll on 後一次交出累積。細節見 2.11 Dongle 專屬服務。

最小範例

/* 放在 NexusPluginGetInterface() 具名賦值給 on_int_event 的函式裡 */ static void NX_CALL my_on_int_event(NxPluginCtx ctx, const NxIntEvent *events, NxDword count, NxDword dropped) { NX_UNUSED_ARG(events); if (dropped != 0) { s_need_full_resync = 1; /* [範本] 有邊緣缺漏:下一輪重讀狀態暫存器 */ } s_edge_total += (unsigned long)count; (void)ctx; }

常見錯誤與症狀

  • 保存 events 指標供之後使用:返回後即失效,需要的話自行複製。
  • 設定了 INT 卻沒收到事件:確認 IntConfigure() 成功且 edge 不是 NX_INT_EDGE_NONE,而且目前不是 poll off。
  • 以為只有自己收到事件:廣播,同一後端上其他實作者也各收一份。
  • 崩潰:該掛載被標記為故障(只讓本 Plugin 下線),其餘 Plugin 照常。