# [Legato] Legato 框架基本介紹 - Legato Application Framework 是由 Sierra Wireless Inc. 公司所開發的框架,主要是用來開發 Internet of Everything。 - Qualcomm 的 TelAF 是基於 Legato 所開發的,所以在使用 TelAF 前要先稍微瞭解 Legato 才能比較快上手 ## Legato 的 System、App、Executable、以及 Component 架構 ![image](https://hackmd.io/_uploads/rJRLwi6a0.png) (圖片擷取自 Legato 官網:https://docs.legato.io/latest/conceptsEnvironment.html) Legato 框架有幾個重要的概念,分別是 Component、Executable、App、以及 System。 ![image](https://hackmd.io/_uploads/ryB6PjpT0.png) 從上圖可以看到,使用 Legato Application Framework 的話,我們的系統裡面會有一個一個的 App,而每個 App 裡面可以由一個或多個 Executable 組成,而每個 Executable 可以由一個或多個 Component 組成。 ### Component - 一個 Component 是一個 Legato App 最小的組成單位,其 entry point 是 `COMPONENT_INIT`。 - 寫在 `COMPONENT_INIT` 內的東西,在程式剛執行時的 start-up 階段就會馬上被該程式的 main thread 執行,主要是用來初始化該 Component 用的,所以一個 Component 只會有一個 `COMPONENT_INIT`。 此外,`COMPONENT_INIT` 不可 return 東西。 - 一個 Component 會以一個資料夾表示,該資料夾的名稱就是該 Component 的名稱,該資料夾內的檔案都是屬於該 Component 的,這些檔案可以是 source code、pre-compiled binary files、resource files(如音檔、圖片檔等等)、或任何該 Component 所需的檔案。 - 每個 Component 都一定要有一個 `Component.cdef` 檔案(`cdef`:**C**omponent **DEF**inition),用來告訴 build tools 該怎麼 build 這個 component。 - `Component.cdef` 檔案也可用來描述說這個 Component 提供了怎樣的 API 或者是需要什麼 API。 - 更多關於 Component 細節的說明可參考官網的說明: - https://docs.legato.io/latest/conceptsComponents.html ### Executable - 一個 Executable 由一個或多個 Component 組成。 - 一個 Executable 會由一個 process 去執行。 - 所以一個 Executable 可以由多個 Component 組成,而這些 Component 都執行在同一個 process 內,所以這些 Component 可互相存取彼此的資源。 - Executable 是由 `.adef` 檔案做設定(後面會介紹)。 - Executable 若要去存取另一個 Executable(不論是否在同一個 App 內)的東西,僅能透過 IPC 機制(後面會再說明)。 ### App Legato Application Framework 的一個 App 跟平常說的一個應用程式(application)是不太一樣的。 - 一個 App 由一個或多個 Executable 組成,在同一個 App 內的 Executable 都是在相同的執行環境內執行,也就是執行在同一個 sandbox,他們的環境變數和資源訪問權限都是一樣的。 - App 之間被隔離,無法直接存取其他 App 的資源。若需存取,僅可透過IPC 機制(後面會再說明)。這樣的設計可以增加系統的安全性。 - 每個 App 都會有一個 `.adef` 檔案(**A**pplication **DEF**inition),定義這個 App 該怎麼執行。 - `.adef` 的名稱就會是這個 App 的名稱,例如 `helloPrintApp.adef`,則使用 `app status` 所看到的該 App 名稱就會是 `helloPrintApp` :::info - Legato 使用 sandbox 為執行在同一個系統的 App 提供安全防護,sandbox 會將 App 與系統的其他部分隔離,使得 original equipment manufacturer(OEM)和 independent software vendor(ISV)的 Apps 可以安全地共存在同一個裝置,而不用擔心互相干擾。 - 每個 App 都有自己獨特的 user ID、group ID、以及 root directory。 - Legato sandbox 使用 `chroot` 來限制一個 App 只能看到其 directory 下的檔案和目錄。檔案使用 bind mounts 機制來跟 `chroot` 產生的隔離環境做連結。Processes 只能存取其 root directory 下的檔案和目錄。 - 只有開啟特定功能的 processes 可以看到其 root directory 以外的東西;若是 sandboxed Apps 則無這種能力。 - 我們可以在 `.adef` 檔案的 `sandboxed` section 設定該 App 是否要執行在 sandbox。 - unsandboxed app 可以看到 target device 的 root file system;sandboxed app 則無法看到 target device 的 root file system,因為它會有一個隔離的 root file system。每個 app 都有自己獨特的 user 和 primary groups IDs。App 的 user name 和 primary group name 都是 `appxxxx`,其中的 `xxxx` 是 App 的名稱。 - 更多 sandbox 機制的細節可參考官網說明: - https://docs.legato.io/latest/conceptsSecurity.html - https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_sandboxed ::: ### System 由於目前我在使用上都沒用到 System 層級的東西,所以對他較不熟,有需要可以參考官網的說明(後續摸熟的話會再補充 >_\<): - https://docs.legato.io/latest/conceptsEnvironment.html#conceptsEnvironment__systems - https://docs.legato.io/latest/defFiles.html#defFilesOverview_sdef - https://docs.legato.io/latest/defFilesSdef.html :::info 前面簡單的介紹完了 Component、Executable、以及 App,來看一下在 target machine 上的 Legato App 會怎麼呈現~ 我們可以在前面安裝的 TelAF simulation docker container 內執行 `app status`,就可以看到目前系統內有安裝的 App 有哪些,以及他們的狀態,如下圖中一項一項的都是一個 App: ![Screen Shot 2024-09-22 at 11.02.15 PM (2) (1)](https://hackmd.io/_uploads/S1k3-n66C.png) 其中 `taf` 開頭的 App 都是 TelAF 提供的 service,例如 `tafDataCallSvc` 就會提供跟 Data call 有關的 service,若我們的 App 想要使用 Data call 的 API,就必須要去存取這個 App,並且確保它是 `running` 的狀態。 ::: ## Basic Legato App Structure 接下來,來看一個簡單的 Legato App 的架構會是長怎樣~ 假設目前有一個 Legato App 的資料夾,叫做 `helloServer`,其架構如下圖: ![legato_basic_example_structure](https://hackmd.io/_uploads/ByhOMn66R.png) 首先,一個 Legato App 一定會有: - 一個 `.adef` 檔案:其檔名就會是該 App 的名稱,也就是執行 `app status` 所看到的名稱,所以這個 App 的名稱是 `helloServerApp` - 一個或多個 Component 資料夾: - Component 資料夾的名稱就會是該 Component 的名稱,如此例,該 Component 名稱即為 `ServerComp`。 - 每個 Component 的資料夾內一定會有一個 `Component.cdef` 檔案 - `Component.cdef` 的名稱是固定的,不可擅自修改。 - 與該 Component 相關的檔案都會放在此資料夾內,像是 `Component.cdef` 和 source code file(如 `server.c`)等等。 此外,一個 Legato App 若有提供 service 給其他人使用,那就還會有 `.api` 檔案: - `.api` 檔案使用 Legato 的 interface definition language(IDL)撰寫 API 介面。 - 我們在 `.api` 檔案內定義每個 API 的名稱、參數、return type、以及其他資訊等等。 - 此例子是因為 `serverComp` 有提供 API 給其他 App 使用,所以才會有 `.api` 檔案。 上圖右側則是一個簡單的示意圖: - 這個 App 的名稱是 `helloServerApp`,其內含有一個 Executable,名稱是 `serverExec`(Executable 的名稱由 `helloServerApp.adef` 內定義) - 此 Executable 內有一個 Component,其名稱是 `serverComp`。 - `serverComp` 這個 Component 有提供一個 API 介面,它會被 expose 出來讓其他 App 可以使用(這部分是由 `helloServerApp.adef` 內的 `extern` section 所定義),該 API interface 名稱是 `server_interface`,想使用這個 API 的 App 只要連接到 `helloServerApp` 的 `server_interface` 即可(怎麼連接到別人的 API,後面會再介紹~)。 ### Application Definition Files & Component Definition Files 接下來,從前面這個 `helloServerApp` 例子來簡單介紹一下 `.adef` 和 `Component.cdef` 裡面會長怎樣~ (這裡只是做基本的簡單介紹,讓第一次接觸的人有些概念,詳細內容還是要參閱官網喔~) ![image](https://hackmd.io/_uploads/HyClbII0R.png) Application definition files 和 component definition files 都是由一個一個的 section 所組成,上圖僅列出部分 section,各 section 詳細的說明可參閱官網說明。 (各 section 的擺放順序,我目前測試起來好像沒有一定要特定的順序,官網也沒特別提到) #### Application Definition File ( `.adef` ) - Application definition file 由許多 section 構成,一個最簡單的 App 至少一定要有 `executables` 和 `processes` 這兩個 sections。 - `executables` section: - 列出要建立哪些 Executable,以及這些 Executable 由哪些 Component 構成。這些 Component 的 `COMPONENT_INIT` 會在 process 的 start-up 階段被執行。  - 建立好的 Executable 會被移到 App 的 `bin` directory 內。 - `processes` section: - 當 App 啟動時,有哪些 processes 要被執行。 - 一個 Executable 由一個 process 執行。 - 我們可以為同一個 `processes` section 內的 process 設定環境變數(藉由 `envVar` 這個 subsection),但若要給不同 process 設定不同的環境變數,則要寫在不同的 `processes` section。 - `extern` section: - 此例子由於此 App 有提供 API 給其他 App 使用,所以會用到 `extern` section 將其 API interface 露出來給其他 App 使用。 其他 App 若要使用此 App 的 API,則必須在其 `.adef` 的 `bindings` section 做設定(後面會再提到)。 - 其他重要的 section 還有 `bindings`、`bundles` 等等,更多 Application Definition 的細節請參閱 Legato 官網的說明: - https://docs.legato.io/latest/defFiles.html#defFilesOverview_adef - https://docs.legato.io/latest/defFilesAdef.html :::danger 在 `processes` section 底下有 [`faultAction`](https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_processFaultAction) 這個 subsection,千萬不要在 TelAF simulation 內測試: ```adef faultAction: reboot ``` 也就是如果有錯誤發生,process crash 了,那要採取什麼行動,`reboot` 則是重新開機。 我這樣測試後,我的 TelAF simulation 就壞掉惹QQ,刪掉重新安裝 TelAF simulation 也沒用 😭 最後只能靠著之前 VirtualBox 的 snapshot 才救回來 @_@ ::: #### Component Definition File ( `.cdef` ) - Component definition file 也是由許多 section 構成,一個最簡單的 Component 一定要有 `sources` section 來指定這個 Component 的 source files 有誰。 - 更多 Component Definition 的細節請參閱 Legato 官網的說明: - https://docs.legato.io/latest/defFiles.html#defFilesOverview_cdef - https://docs.legato.io/latest/defFilesCdef.html - https://docs.legato.io/latest/conceptsComponents.html ### API Files & Source Code Files 接下來來看 `helloServerApp` 的 `server_api.api` 以及 `server.c`。 PS. `.api` 檔的命名並沒有特別規範,這裡只是為了幫助識別,才命名成 `server_api`。 ![legato_basic_example_api_sourceCode](https://hackmd.io/_uploads/BkCOS3T6A.png) 這個 `server_api.api` 檔案有定義兩個 API,第一個是 `server_func1`,第二個是 `server_func2`: - `server_func1` - 沒有寫出 return type,所以是 return `void`。 - return type 會寫在 `FUNCTION` 跟 API name 之間,也就是 `FUNCTION` 跟 `server_func1` 之間。 - 此 API 有一個參數,是 `string` 型態的參數,且是 `IN` 參數。 - 在 `server.c` 裡面,API 的 function name 的組成規則是 `<interface_name>_<API_name>` - `interface_name` 是在 `Component.cdef` 的 `provides` section 的 `server_interface = server_api.api` 所定義的 interface name。 這樣宣告的話 `interface_name` 就會是 `server_interface`。 - 所以在 `server.c` 裡面,此 API 的 function name 就是 `server_interface_server_func1`,該 function 的參數是 `const char *message` - `server_func2` - 此 API 有 return 東西,是 return `int32` - 且此 API 有三個參數,分別是 `n1`、`n2`、和 `res`,其中 `n1` 和 `n2` 是 `IN` 參數,而 `res` 是 `OUT` 參數 :::info 若要確認 API 在 C 語言確切的 function prototype,可以用以下方式確認: 假如在 `~/test/` 內有 `<yourApp>.adef`、`.api` 檔案以及 Component 的資料夾。 接下來在 `~/test/` 內執行:`mkapp -t simulation <yourApp>.adef`。 則會在執行 `mkapp` 的資料夾內產生 `_build_<yourApp>` 資料夾,查看 `_build_<yourApp>/simulation/api/<一串數字>/server/<interface_name>_server.h` 這份檔案的內容即可確認在 C source code 裡面該使用怎樣的 function prototype。 我們的 source file 要依據這份檔案內的 function prototype 去實作 API 的內容。 ![image](https://hackmd.io/_uploads/ryBercLCA.png) ::: Legato 官網有 API 檔更多的說明: - https://docs.legato.io/latest/apiFiles.html - https://docs.legato.io/latest/apiFilesSyntax.html ## Communication Mechanisms in Legato ![legato_3_types_of_communications](https://hackmd.io/_uploads/rJZQDnapA.png) Legato 的 communication 的機制可以探討以下三種情況: 1. 同一個 Executable 內不同 Components 之間的溝通 2. 同一個 App 內的 Executables 之間的溝通 3. 不同 Apps 之間的溝通 ### 1. 同一個 Executable 內不同 Components 之間的溝通 由於是位在同一個 Executable 內的 Components,也就是都在同一個 process 內,所以可以存取彼此的東西。 如下例,有兩個 Component,`serverComp` 和 `clientComp`,server 提供 `server_showMsg` 這個 function,並將 function prototype 放在 `server.h`。 Client 若要使用的話,就要在 source code 內 include 該 header file,並且在 client 的 `Component.cdef` 的 `cflags` section 內將 `serverComp` 的路徑加入 search path,讓 build tool 能找到 `server.h` 即可。 ![legato_communication_type1](https://hackmd.io/_uploads/B1r2P3p60.png) #### `helloIPC1App.adef` ```adef executables: { helloExec = ( clientComp serverComp ) } processes: { run: { helloProc = ( helloExec ) } } ``` #### `server.h` Server 端把欲提供出去的東西寫在一個 header file,`server.h`。以此例而言,server 分享了一個 `int` 型態的變數 `num`,以及一個 function `server_ShorMsg`。 欲分享出去的東西前面都要加上 `LE_SHARED` 這個 macro,此 macro 可將 symbol 分享出去 - `LE_SHARED`:https://docs.legato.io/latest/le__basics_8h.html#a1661bfbb29c2e0372ca9f0c556a8fc24 ```cpp #ifndef SERVER_H_INC #define SERVER_H_INC LE_SHARED int num; LE_SHARED void server_showMsg(const char *message); #endif ``` #### `server.c` ```cpp #include "legato.h" #include "interfaces.h" #include "server.h" int num = 100; void server_showMsg(const char *msg) { LE_INFO("Client says: '%s', and the value of num is %d", msg, num); } COMPONENT_INIT { } ``` #### `serverComp/Component.cdef` ```cpp sources: { server.c } ``` #### `client.c` ```cpp #include "legato.h" #include "interfaces.h" #include "server.h" // 記得要 include server.h COMPONENT_INIT { LE_INFO("Asking server to print 'Hello, world!' and print the value of num"); server_showMsg("Hello, world!"); LE_INFO("client printing the value of num: %d", num); LE_INFO("client changes the value of num to 777"); num = 777; server_showMsg("Changed num to 777"); } ``` #### `clientComp/Component.cdef` ```cdef sources: { client.c } cflags: { // 將以下這個新的 directory 加入到 search path,這樣 build tool 就會知道去哪找 server.h -I $CURDIR/../serverComp } ``` #### Result ![image](https://hackmd.io/_uploads/By5oSqLCC.png) ### 2. 同一個 App 內不同 Executables 之間的溝通 此情況需要透過 IPC 的機制來溝通,且需要定義 API。 如下例,有兩個 Component,`clientComp` 和 `serverComp`。在 `helloIPC2App.adef` 內,將這兩個 Component 各自放到一個 Executable 內執行,由於這樣就是執行在不同的 process 內,無法直接存取彼此的東西,因此需要定義 API 檔案,並透過 IPC 的機制來溝通。 ![image](https://hackmd.io/_uploads/H1ggIcLCR.png) #### `helloIPC2App.adef` ```cpp executables: { clientExec = ( clientComp ) serverExec = ( serverComp ) } processes: { run: { serverProc = ( serverExec ) clientProc = ( clientExec ) } } bindings: // bindings section 讓 client 端的 IPC API interface 能連結到 server 端的 interface // client 端的 interface 就是寫在 client Component.cdef 內 requires section 內的 // server 端的 interface 就是寫在 server Component.cdef 內 provides section 內的 // 若是同一個 App 內的 interfaces 的 binding,稱做 internal binding,也就是這個例子 // 若是 App 跟 App 之間的 interfaces 的 binding,則稱做 external binding { // binding 的寫法是 [client 端的 interface] -> [server 端的 interface] // 由於此例子是 internal binding,所以寫法會是 clientExe.clientComponent.clientInterface -> serverExe.serverComponent.serverInterface clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface // 這裡的 client_interface 是定義在 client 的 Component.cdef 內的 requires section // 若是 external binding,由於 client app 理論上看不到 server app 內 component 的結構,所以應該是寫 client 的 interface 要連接到某個 App 的 interface // 所以寫法會是: // clientExe.clientComponent.clientInterface -> otherApp.serverInterface // 更多細節請參閱官網 https://docs.legato.io/latest/defFilesAdef.html#defFilesAdef_bindings } ``` #### `server_api.api` ```api DEFINE MESSAGE_LEN = 100; FUNCTION showMsg ( string msg[MESSAGE_LEN] IN ); ``` #### `serverComp/Component.cdef` ```cpp provides: { api: { // server component 提供的 API 被定義在 server_api.api // 我們可以給這個 API 定義一個 interface 名稱,這裡命名為 server_interface server_interface = server_api.api // 這也是為什麼 helloIPC2App.adef 的 bindings section 的 clientExec.clientComp.client_interface -> serverExec.serverComp.server_interface 的 server_interface 的由來 // server.c 內的 function 名稱也因此是 server_interface_showMsg // 我們也可以不自己定義一個 interface 名稱,直接用以下寫法: server_api.api // 這樣的話 server 端的 interface 名稱就是該 api 的檔名,也就是 server_api // adef 內的 bindings section 就要改成:clientExec.clientComp.client_interface -> serverExec.serverComp.server_api // server.c 內的 function 名稱也要改為 server_api_showMsg } } sources: { server.c } ``` #### `server.c` ```cpp #include "legato.h" #include "interfaces.h" // function 名稱的組成規則是 <interface_name>_<API_name> // interface_name 就是定義在 server Component.cdef 內 provides section 所定義的 interface 名稱 void server_interface_showMsg(const char *msg) { LE_INFO("Client says '%s'", msg); } COMPONENT_INIT { } ``` #### `clientComp/Component.cdef` ```cpp requires: { api: { // client 這個 component 需要 server_api.api // 我們可以自己重新命名 interface 名稱,這裡重新命名成 client_interface // 這樣 client.c 內使用 server_api.api 裡面的 API 時,function 名稱會是 client_interface_<API name> // 這也是 adef 的 bindings section 的 clientExec.clientComp.client_interface 的 client_interface 的由來 client_interface = server_api.api // 我們也可以不自己定義 interface 名稱,直接用以下寫法: server_api.api // 這樣產生的 interface 名稱就會是該 .api 檔的檔名,也就是 server_api // 這樣寫的話記得要修改 helloIPC2App.adef 內 bindings section 的 client 的 interface 名稱: // clientExec.clientComp.server_api -> serverExec.serverComp.server_interface // 此外,client.c 內 function 名稱也會改為 server_api_showMsg } } sources: { client.c } ``` #### `client.c` ```cpp #include "legato.h" #include "interfaces.h" COMPONENT_INIT { LE_INFO("Asking server to print 'Hello, world!'"); // function 名稱是 <interface_name>_<API_name> // interface_name 就是 client 的 Component.cdef 內 requires section 所定義的 interface 名稱 client_interface_showMsg("Hello, world!"); } ``` #### Result ![image2024-9-11_15-33-37_compressed](https://hackmd.io/_uploads/BkFqvcUAC.png) ### 3. 不同 App 之間的溝通 此情況僅可透過 IPC 溝通,且需要 API。這是最安全的溝通方式,也是 Legato 官方比較推薦的作法。 以下例子,server 和 client 都有各自的 App: - Server 藉由其 `.adef` 的 `extern` section 將其想給其他 App 使用的 IPC API interface 露出來,其他 App 若想使用該 interface 則需藉由其 `.adef` 的 `bindings` section 來連接到該 interface。 - Client 藉由其 `.adef` 的 `bindings` section 使其 interface 和 server 的 interface 做連結。 ![image2024-9-20_10-29-26](https://hackmd.io/_uploads/r1pOd98R0.png) #### `helloIPC3ServerApp.adef` ```cpp executables: { serverExec = ( serverComp ) } processes: { run: { serverProc = ( serverExec ) } } extern: { // 可以給 interface 設定一個別名,也可以不設定 // aliasName = exeName.componentName.interfaceName serverInterfaceAliasName = serverExec.serverComp.server_interface // 有設定別名的話,client 的 adef 的 bindings section 就是寫: // clientExec.clientComp.client_interface = serverApp.serverInterfaceAliasName // 若沒有要給別名,就這樣寫: serverExec.serverComp.server_interface // 這樣的話,client 的 adef 的 bindings section 就是寫: // clientExec.clientComp.client_interface = serverApp.server_interface } ``` #### `server_api.api` ```api DEFINE MESSAGE_LEN = 100; FUNCTION showMsg ( string msg[MESSAGE_LEN] IN ); ``` #### `serverComp/Component.cdef` ```cpp provides: { api: { // 這個 Component 有提供 API,API 定義在 server_api.api // 可以自己設定 server 端的 interface,這裡將 server 端的 interface 設定成 server_interface server_interface = server_api.api // server.c 裡的 function 名稱 就是 server_interface_showMsg // 若沒有要自己設定 server 端的 interface,這樣寫就好: server_api.api // 這樣 server 端的 interface 就是 server_api // server.c 裡的 function 名稱 就是 server_api_showMsg } } sources: { server.c } ``` #### `server.c` ```cpp #include "legato.h" #include "interfaces.h" // function name 的組成規則是 <interface_name>_<API_name> // interface_name 就是定義在 Component.cdef 的 provides section 內的 api section 的 void server_interface_showMsg(const char *msg) { LE_INFO("Client says: '%s'", msg); } COMPONENT_INIT { } ``` #### `helloIPC3ClientApp.adef` ```cpp executables: { clientExec = ( clientComp ) } processes: { run: { clientProc = ( clientExec ) } } bindings: { // 由於 clientApp 會用到 serverApp 所提供的 API,所以要先將 client 的 interface 連接到 server 的 interface // 這是 App 跟 App 間的 communication,是 external binding,所以連接的規則是: // clientExec.clientComp.clientInterface -> serverApp.serverInterface clientExec.clientComp.client_interface -> helloIPC3ServerApp.serverInterfaceAliasName } ``` #### `clientComp/Component.cdef` ```cpp requires: { api: { // client 需要用到 server_api.api // 這裡有自己設定 client 端的 interface 名稱為 client_interface client_interface = server_api.api // 若沒自己設定 interface 名稱,就這樣寫: server_api.api // 這樣 client 端的 interface 名稱就是 server_api } } sources: { client.c } ``` #### `client.c` ```cpp #include "legato.h" #include "interfaces.h" COMPONENT_INIT { LE_INFO("Asking server to print 'Hello, world!'"); // function name 的組成規則是 <interface_name>_<API_name> // interface_name 就是寫在 Component.cdef 的 requires section 的 api section 內的 client_interface client_interface_showMsg("Hello, world!"); } ``` #### Make App 此例子在 make app 時,記得要 include `server_api.api` 所在的 path: ```bash ~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Server/helloIPC3ServerApp.adef ~/helloIPC3$ mkapp -t simulation -i ./ helloIPC3Client/helloIPC3ClientApp.adef ``` #### Result ![image2024-9-11_17-27-58_compressed](https://hackmd.io/_uploads/ryiVq9L0C.png) ### Overview 由於一開始在接觸 Legato 時搞不太懂 interface name 的規則,所以自己做了以下小整理~ ![image](https://hackmd.io/_uploads/r1r8c5L00.png) ![image](https://hackmd.io/_uploads/ry1wccICR.png) 我們在使用 TelAF 時,若要使用 TelAF 的某個 service,就是用第三種方式 bind 到該 service 的 API interface。 譬如說若要用 Data Call 相關的 API,官網( https://docs.qualcomm.com/bundle/publicresource/topics/80-41102-2/page_c_tafDataCallSvc.html?product=1601111740010418 )就會告知說我們自己開發的 App 的 `.adef` 的 `bindings` section 要做怎樣的設定才能連結到他們 Data Call 的 service。 ![image2024-9-12_8-57-47](https://hackmd.io/_uploads/SJf1s5UCC.png)