Fuchsia Controller 教學課程

本教學課程逐步說明如何在 Fuchsia 來源結帳 (fuchsia.git) 設定中使用 Fuchsia Controller (fuchsia-controller) 編寫簡單的指令碼。

Fuchsia Controller 包含一組程式庫,可讓使用者連線至 Fuchsia 裝置,並透過 FIDL 與裝置互動。Fuchsia Controller 最初是用於測試不過,若要建立能與 Fuchsia 裝置上的 FIDL 介面互動的可編寫程式碼,這個做法也很實用。舉例來說,使用者可以利用 Fuchsia Controller 編寫指令碼,以便執行簡單的裝置互動,而不必在 Rust 中編寫 ffx 外掛程式。

Fuchsia 控制器的主要兩個部分為:

  • fuchsia-controller.so 檔案 (包含 ABI 的標頭)
  • 較高層級的語言繫結 (在 .so 檔案上使用 ABI 建構)

    目前 Fuchsia Controller 的較高層級語言繫結僅以 Python 編寫。

如要使用 Fuchsia Controller,最快的方法是編寫使用 fuchsia-controller 程式碼的 Python 指令碼。在 Fuchsia 來源結帳設定中,您可以將 Python 二進位檔建構為 .pyz 檔案,然後從 out 目錄 (例如 $FUCHSIA_DIR/out/default) 執行該檔案。

如要編寫第一個 Fuchsia Controller 指令碼,請按照下列步驟操作:

  1. 必備條件
  2. 更新 BUILD.gn 中的依附元件
  3. 編寫您的第一個程式
  4. 與 Fuchsia 裝置通訊
  5. 實作 FIDL 伺服器

如果您發現錯誤,或是有任何疑問或建議,請回報錯誤

先決條件

本教學課程需要下列必備項目:

  • 您必須使用 Fuchsia 來源結帳 (fuchsia.git) 開發環境。

  • 您需要執行 Fuchsia 裝置。裝置可以是實體裝置或模擬器。

  • 這部裝置必須連結至 ffx,並正確連結遠端控制服務 (RCS)。

    如果執行 ffx target listRCS 下的欄位必須讀取 Y,例如:

    NAME                    SERIAL       TYPE       STATE      ADDRS/IP                       RCS
    fuchsia-emulator        <unknown>    Unknown    Product    [fe80::5054:ff:fe63:5e7a%4]    Y
    

    (詳情請參閱與目標裝置互動)。

  • 如要在已啟用網路但不支援圖形使用者介面的情況下啟動 Fuchsia 模擬器,請執行 ffx emu start --headless。(詳情請參閱「啟動 Fuchsia 模擬器」。)

  • 裝置必須執行 core 產品

更新 BUILD.gn 中的依附元件

更新 BUILD.gn 檔案,加入下列依附元件:

import("//build/python/python_binary.gni")

assert(is_host)

python_binary("your_binary") {
    main_source = "path/to/your/main.py"
    deps = [
        "//src/developer/ffx:host",
        "//src/developer/ffx/lib/fuchsia-controller:fidl_bindings",
        "//src/developer/ffx/lib/fuchsia-controller:fuchsia_controller_py",
    ]
}

fidl_bindings 規則包含必要的 Python 和 .so 繫結程式碼。也必須納入 ffx 工具,讓 ffx Daemon 連線至 Fuchsia 裝置。

編寫第一個程式

在本節中,我們會建立一項簡易程式,但該程式尚未連線至 Fuchsia 裝置,但會連線至 ffx Daemon 來驗證裝置已啟動並執行。為此,我們會利用現有的 ffx FIDL 程式庫與 //src/developer/ffx/fidl 中定義的 Daemon 互動。

包含 FIDL 依附元件

Fuchsia Controller 使用 FIDL 中繼表示法 (FIDL IR) 在執行階段產生其 FIDL 繫結。因此,您需要在 fidlc 目標的 BUILD.gn 中加入下列依附元件,才能建立這些 FIDL 繫結:

"//src/developer/ffx/fidl:fuchsia.developer.ffx($fidl_toolchain)"

這個做法也需要匯入 $fidl_toolchain 變數:

import("//build/fidl/toolchain.gni")

編寫測試時,您必須加入主機測試資料 (因為基礎架構也需要存取測試執行器上的 IR,因此基礎架構測試也能正確執行),例如:

"//src/developer/ffx/fidl:fuchsia.developer.ffx_host_test_data"

納入主機測試資料規則也會包含 FIDL IR,因此不需要同時包含這兩個依附元件。

新增 Python 匯入區塊

所有依附元件都納入後,我們就可以在 Python 主檔案中加入下列程式庫:

from fuchsia_controller_py import Context, IsolateDir
import fidl.fuchsia_developer_ffx as ffx
import asyncio

以下各節涵蓋此程式碼區塊中的每個程式庫。

Context 和 IsolateDir

from fuchsia_controller_py import Context, IsolateDir

第一行包含 Context 物件,表示使用者可透過該物件執行 ffx 指令。此外,由於這個物件也能提供下列連線,因此您可以執行更多工作:

  • ffx Daemon
  • 富奇亞目標

IsolateDir 物件與 ffx 隔離相關,指的是執行 ffx Daemon 的方式,其所有中繼資料 (例如設定值) 都包含在特定目錄中。隔離主要用於防止 ffx 狀態的汙染,以及設定較不活躍的裝置探索預設值 (這可能會導致在測試基礎架構中執行 ffx 時發生問題)。

IsolateDir 是一般用途指令的選用項目,但如果您打算使用程式進行測試,則為必要參數。IsolateDir 物件會建立 (並指向) 目錄,允許隔離的 ffx Daemon 執行個體執行。(如要進一步瞭解 ffx 隔離,請參閱整合測試一文)。

必須在初始化期間將 IsolateDir 物件傳遞至 Context 物件。IsolateDir 物件也可在 Context 物件之間共用。在進行垃圾收集作業後,會清理 IsolateDir 物件,進而關閉 ffx Daemon。

FIDL IR

import fidl.fuchsia_developer_ffx as ffx

第二行來自上一節編寫的 FIDL IR 程式碼。fidl. 之後寫入的部分 (例如 fuchsia_developer_ffx) 需要 fuchsia.developer.ffx 程式庫的 FIDL IR。所有 FIDL 匯入行都應如此。匯入 fidl.example_fuchsia_library 時,必須產生名為 example.fuchsia.library 的程式庫的 FIDL IR。使用 as 關鍵字可讓這個程式庫輕鬆使用。

這個 fuchsia.developer.ffx 程式庫包含 FIDL 繫結預期提供的所有結構,本教學課程稍後會介紹。

非同步

import asyncio

從 FIDL IR 產生的物件使用非同步繫結,因此需要使用 asyncio 程式庫。在本教學課程中,我們使用 echo.fidl 中定義的 echo 通訊協定。

編寫主要實作

除了 async_mainmain 的樣板以外,我們主要著重於 echo_func 定義:

async def echo_func():
   isolate = IsolateDir()
   config = {"sdk.root": "."}
   ctx = Context(config=config, isolate_dir=isolate)
   echo_proxy = ffx.Echo.Client(ctx.connect_daemon_protocol(ffx.Echo.MARKER))
   echo_string = "foobar"
   print(f"Sending string for echo: {echo_string}")
   result = await echo_proxy.echo_string(value="foobar")
   print(f"Received result: {result.response}")


async def async_main():
    await echo_func()


def main():
    asyncio.run(async_main())


if __name__ == "__main__":
    main()

由於使用隔離,必須建立並傳遞至 Context 物件的 config 物件。若是不再適用於 ffx 預設設定的區隔 (根據預設,ffx 知道可以在 Fuchsia 來源結帳設定中找到 SDK 的位置),您必須將要使用的任何設定值提供給 Context 物件。

執行程式碼

我們必須先建構程式碼,才能執行程式碼。BUILD.gn 檔案看起來可能會像這樣:

import("//build/python/python_binary.gni")

assert(is_host)

python_binary("example_echo") {
    main_source = "main.py"
    deps = [
        "//src/developer/ffx:host",
        "//src/developer/ffx/lib/fuchsia-controller:fidl_bindings",
        "//src/developer/ffx/fidl:fuchsia.developer.ffx_compile_fidlc($fidl_toolchain)",
    ]
}

假設此 BUILD.gn 位於 src/developer/example_py_thing 目錄中。接著,只要 fx set 正確無誤,您就可以使用主機目標建構這個程式碼。如果您的主機是 x64,建構指令可能會如下所示:

fx build host_x64/obj/src/developer/example_py_thing/example_echo.pyz

建構已完成,您可以在 out 目錄中找到程式碼 (預設為精確,預設為 out/default)。您也可以直接從該目錄執行 .pyz 檔案。請務必使用 out/default 目錄中的完整路徑,讓 pyz 檔案可以找到並開啟適當的 .so 檔案,例如:

$ cd $FUCHSIA_DIR/out/default
$ ./host_x64/obj/src/developer/example_py_thing/example_echo.pyz
Sending string for echo: foobar
Received result: foobar
$

與 Fuchsia 裝置通訊

如果程式碼目前為止建構和執行,我們便可開始編寫程式碼,透過 FIDL 介面向 Fuchsia 裝置傳達語音訊息。大部分的程式碼都很類似,但本節中會有一些細微的差異。

尋找元件魔物

如要繫結至 Fuchsia 元件,目前必須瞭解元件的路徑名稱。可使用 ffx 完成此操作。如要取得建構資訊提供者的路徑名稱,例如:

ffx component capability fuchsia.buildinfo.Provider

這個指令會顯示類似以下的輸出內容:

Declarations:
  `core/build-info` declared capability `fuchsia.buildinfo.Provider`

Exposes:
  `core/build-info` exposed `fuchsia.buildinfo.Provider` from self to parent

Offers:
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#cobalt`
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#remote-control`
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#sshd-host`
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#test_manager`
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#testing`
  `core` offered `fuchsia.buildinfo.Provider` from child `#build-info` to child `#toolbox`
  `core/sshd-host` offered `fuchsia.buildinfo.Provider` from parent to collection `#shell`

Uses:
  `core/remote-control` used `fuchsia.buildinfo.Provider` from parent
  `core/sshd-host/shell:sshd-0` used `fuchsia.buildinfo.Provider` from parent
  `core/cobalt` used `fuchsia.buildinfo.Provider` from parent

您想要的捷徑位於 Exposes 宣告中:core/build-info

取得版本資訊

我們一開始可以取得裝置的版本資訊,

首先,我們必須加入建構資訊 FIDL 通訊協定的依附元件:

"//sdk/fidl/fuchsia.buildinfo:fuchsia.buildinfo_compile_fidlc($fidl_toolchain)"

然後需要編寫程式碼,以便從 Fuchsia 裝置取得 Proxy。 目前,藉由連線至建構資訊路徑名稱來完成此操作 (但這只是我們即將變更):

isolate = IsolateDir()
config = {"sdk.root": "."}
target = "foo-target-emu" # Replace with the target nodename.
ctx = Context(config=config, isolate_dir=isolate, target=target)
build_info_proxy = fuchsia_buildinfo.Provider.Client(
    ctx.connect_device_proxy("/core/build-info", fuchsia_buildinfo.Provider.MARKER))
build_info = await build_info_proxy.get_build_info()
print(f"{target} build info: {build_info}")

如果執行上述程式碼,程式碼會如下所示:

foo-target-emu build info: ProviderGetBuildInfoResponse(build_info=BuildInfo(product_config='core', board_config='x64', version='2023-08-18T23:28:37+00:00', latest_commit_date='2023-08-18T23:28:37+00:00'))

如果繼續執行,可以建立類似於 ffx target show 指令的內容:

results = await asyncio.gather(
    build_info_proxy.get_build_info(),
    board_proxy.get_info(),
    device_proxy.get_info(),
    ...
)

由於每次叫用 FIDL 方法都會傳回協同程式,因此這些協同程式可以用工作的形式啟動,並平行等待,就像使用其他 FIDL 繫結一樣。

重新啟動裝置

重新啟動裝置的方法不只一種。如要重新啟動裝置,其中一種方法是連線至執行 fuchsia.hardware.power.statecontrol/Admin 通訊協定的元件,該元件位於 /bootstrap/shutdown_shim 下方。

使用這個方法時,通訊協定應於方法執行期間結束,並顯示 PEER_CLOSED 錯誤:

        ch = self.device.ctx.connect_device_proxy(
            "bootstrap/shutdown_shim", power_statecontrol.Admin.MARKER
        )
        admin = power_statecontrol.Admin.Client(ch)
        # Makes a coroutine to ensure that a PEER_CLOSED isn't received from attempting
        # to write to the channel.
        coro = admin.reboot(reason=power_statecontrol.RebootReason.USER_REQUEST)
        try:
            await coro
        except ZxStatus as status:
            if status.args[0] != ZxStatus.ZX_ERR_PEER_CLOSED:
                raise status

不過,在需要判斷裝置是否已恢復連線時,還有一個棘手的部分。這種情況通常是嘗試連線至通訊協定 (通常是 RemoteControl 通訊協定),直到逾時為止。

另一種做法是連線至 ffx Daemon 的 Target 通訊協定,藉此減少程式碼:

ch = ctx.connect_target_proxy()
target_proxy = fuchsia_developer_ffx.Target.Client(ch)
await target_proxy.reboot(state=fuchsia_developer_ffx.TargetRebootState.PRODUCT)

執行元件

您可以使用 RemoteControl 通訊協定啟動元件,其中包含下列步驟:

  1. 連線至生命週期控制器:

    ch = ctx.connect_to_remote_control_proxy()
    remote_control = fuchsia_developer_remotecontrol.RemoteControl.Client(ch)
    client, server = fuchsia_controller_py.Channel.create()
    await remote_control.root_lifecycle_controller(server=server.take())
    lifecycle_ctrl = fuchsia_sys2.LifecycleController.Client(client)
    
  2. 嘗試啟動元件的例項:

    client, server = fuchsia_controller_py.Channel.create()
    await lifecycle_ctrl.start_instance("some_moniker", server=server.take())
    binder = fuchsia_component.Binder.Client(client)
    

    使用者可透過 binder 物件得知元件是否仍保持連線。但沒有任何方法。尚未實作可判斷元件是否已解除繫結的支援 (使用繫結器通訊協定)。

取得數據匯報

如要從 fuchsia 裝置取得快照,您需要執行快照,並繫結用於讀取的 File 通訊協定:

        client, server = Channel.create()
        file = io.File.Client(client)
        params = feedback.GetSnapshotParameters(
            # Two minutes of timeout time.
            collection_timeout_per_data=(2 * 60 * 10**9),
            response_channel=server.take(),
        )
        assert self.device.ctx is not None
        ch = self.device.ctx.connect_device_proxy(
            "/core/feedback", "fuchsia.feedback.DataProvider"
        )
        provider = feedback.DataProvider.Client(ch)
        await provider.get_snapshot(params=params)
        attr_res = await file.get_attr()
        asserts.assert_equal(attr_res.s, ZxStatus.ZX_OK)
        data = bytearray()
        while True:
            response = await file.read(count=io.MAX_BUF)
            asserts.assert_not_equal(response.response, None, extras=response)
            response = response.response
            if not response.data:
                break
            data.extend(response.data)

實作 FIDL 伺服器

Fuchsia Controller 的重要工作 (處理傳遞的繫結或測試複雜的用戶端程式碼) 的一項重要工作是執行 FIDL 伺服器。對於本教學課程涵蓋的所有 FIDL 通訊協定,有一個用戶端接受管道。針對這種情況,您必須使用 Server 類別。

在本節中,我們將返回 echo 範例並實作 echo 伺服器。您需要覆寫的函式衍生自 FIDL 檔案定義。因此 echo 伺服器 (使用 ffx 通訊協定) 如下所示:

class TestEchoer(ffx.Echo.Server):
    def echo_string(self, request: ffx.EchoEchoStringRequest):
        return ffx.EchoEchoStringResponse(response=request.value)

如要正確實作,您必須匯入適當的程式庫。和之前一樣,我們將會匯入 fidl.fuchsia_developer_ffx。不過,由於我們要執行 echo 伺服器,因此測試這個伺服器最快的方法是使用 fuchsia_controller_py 程式庫中的 Channel 物件:

import fidl.fuchsia_developer_ffx as ffx
from fuchsia_controller_py import Channel

這個 Channel 物件的行為與其他語言類似。以下程式碼是使用 echo 伺服器的簡易程式:

import asyncio
import unittest
import fidl.fuchsia_developer_ffx as ffx
from fuchsia_controller_py import Channel


class TestEchoer(ffx.Echo.Server):
    def echo_string(self, request: ffx.EchoEchoStringRequest):
        return ffx.EchoEchoStringResponse(response=request.value)


class TestCases(unittest.IsolatedAsyncioTestCase):

    async def test_echoer_example(self):
        (tx, rx) = Channel.create()
        server = TestEchoer(rx)
        client = ffx.Echo.Client(tx)
        server_task = asyncio.get_running_loop().create_task(server.serve())
        res = await client.echo_string(value="foobar")
        self.assertEqual(res.response, "foobar")
        server_task.cancel()

實作伺服器時,請注意以下幾點:

  • 方法定義可以是 syncasync
  • serve() 工作將處理要求,並在伺服器實作中呼叫必要的方法,直到工作完成或基礎管道物件關閉為止。
  • 如果服務工作執行時發生例外狀況,用戶端管道會收到 PEER_CLOSED 錯誤。然後您必須檢查提供工作的結果。
  • 與 Rust 的非同步程式碼不同,建立非同步工作時,您必須保留傳回的物件,直到處理完成為止。否則,系統可能會垃圾收集並取消該工作。

常見的 FIDL 伺服器代碼模式

相較於上述簡單的 echo 伺服器範例,本節則說明不同類型的伺服器互動。

建立 FIDL 伺服器類別

使用下列 FIDL 通訊協定來建立伺服器:

library fuchsia.exampleserver;

type SomeGenericError = flexible enum {
    THIS = 1;
    THAT = 2;
    THOSE = 3;
};

closed protocol Example {
    strict CheckFileExists(struct {
        path string:255;
        follow_symlinks bool;
    }) -> (struct {
        exists bool;
    }) error SomeGenericError;
};

FIDL 方法名稱的衍生方式是將方法名稱從 Camel 案例變更為更小寫。因此,Python 中的 CheckFileExists 方法會變更為 check_file_exists

匿名結構類型衍生自完整的通訊協定名稱和方法。因此可能相當冗長。輸入法的輸入參數定義為名為 ExampleCheckFileExistsRequest 的類型。回應稱為 ExampleCheckFileExistsResponse

將這些項目合併在一起後,Python 中的 FIDL 伺服器實作方式如下所示:

import fidl.fuchsia_exampleserver as fe

class ExampleServerImpl(fe.Example.Server):

    def some_file_check_function(path: str) -> bool:
        # Just pretend a real check happens here.
        return True

    def check_file_exists(self, req: fe.ExampleCheckFileExistsRequest) -> fe.ExampleCheckFileExistsResponse:
        return fe.ExampleCheckFileExistsResponse(
            exists=ExampleServerImpl.some_file_check_function()
        )

您也可以將方法以 async 實作,沒有任何問題。

此外,傳回錯誤也必須將錯誤納入 FIDL DomainError 物件中,例如:

import fidl.fuchsia_exampleserver as fe

from fidl import DomainError

class ExampleServerImpl(fe.Example.Server):

    def check_file_exists(self, req: fe.ExampleCheckFileExistsRequests) -> fe.ExampleCheckFileExistsResponse | DomainError:
        return DomainError(error=fe.SomeGenericError.THIS)

處理事件

事件處理常式寫入與伺服器類似,但衍生自另一個名為 EventHandler 的基礎類別。事件會在管道的用戶端處理,因此必須傳遞用戶端才能建構事件處理常式。

首先使用下列 FIDL 程式碼來建立範例:

library fuchsia.exampleserver;

closed protocol Example {
    strict -> OnFirst(struct {
        message string:128;
    });
    strict -> OnSecond();
};

這個 FIDL 範例包含事件處理常式需要處理的兩個不同事件。編寫簡單的類別,但輸出內容看起來會像這樣:

import fidl.fuchsia_exampleserver as fe

class ExampleEventHandler(fe.Example.EventHandler):

    def on_first(self, req: fe.ExampleOnFirstRequest):
        print(f"Got a message on first: {req.message}")

    def on_second(self):
        print(f"Got an 'on second' event")

如要停止處理事件且不發生錯誤,可以提出 fidl.StopEventHandler

您可以使用一些現有的 Fuchsia 控制器測試程式碼來測試這個事件的範例。首先,請確認 Fuchsia 控制器測試已新增至您的 Fuchsia 版本設定,例如:

fx set ... --with-host //src/developer/ffx/lib/fuchsia-controller:tests

透過 fuchsia.controller.test 的通訊協定 (如 fuchsia_controller.test.fidl 所定義),您可以編寫使用 ExampleEvents 通訊協定的程式碼,例如:

import asyncio
import fidl.fuchsia_controller_test as fct

from fidl import StopEventHandler
from fuchsia_controller_py import Channel

class ExampleEventHandler(fct.ExampleEvents.EventHandler):

    def on_first(self, req: fct.ExampleEventsOnFirstRequest):
        print(f"Got on-first event message: {req.message}")

    def on_second(self):
        print(f"Got on-second event")
        raise StopEventHandler

async def main():
    client_chan, server_chan = Channel.create()
    client = fct.ExampleEvents.Client(client_chan)
    server = fct.ExampleEvents.Server(server_chan)
    event_handler = ExampleEventHandler(client)
    event_handler_task = asyncio.get_running_loop().create_task(
        event_handler.serve()
    )
    server.on_first(message="first message")
    server.on_second()
    server.on_complete()
    await event_handler_task

if __name__ == "__main__":
    asyncio.run(main())

接著,完成下一節的 Python 環境設定步驟才能執行。執行時,它會輸出下列輸出內容並結束:

Got on-first event message: first message
Got on-second event

如需更多伺服器測試的範例,請參閱這個 server.py 檔案。

嘗試使用 Python 解譯器

如果您不確定如何建構某些類型,且不想建構及執行執行檔,可以使用 Python 解譯器檢查 FIDL 結構。

首先,請確認您已建構必要的 FIDL 程式庫,且可在 Python 中使用 (如上一節所述),因為 Python 需要 FIDL IR 的存取權才能運作。

如要設定 Python 解譯器,請執行下列指令 (不過這些指令取決於您的 Fuchsia 版本目錄,預設目錄為 $FUCHSIA_DIR/out/default):

FUCHSIA_BUILD_DIR="$FUCHSIA_DIR/out/default" # Change depending on build dir.
export FIDL_IR_PATH="$FUCHSIA_BUILD_DIR/fidling/gen/ir_root"
__PYTHONPATH="$FUCHSIA_BUILD_DIR/host_x64:$FUCHSIA_DIR/src/developer/ffx/lib/fuchsia-controller/python"
if [ ! -z PYTHONPATH ]; then
    __PYTHONPATH="$PYTHONPATH:$__PYTHONPATH"
fi
export PYTHONPATH="$__PYTHONPATH"

接著,您可以從任何位置啟動 Python 解譯器,它也支援檢查各種類型的 Tab 鍵完成,例如:

$ python3
Python 3.11.8 (main, Feb  7 2024, 21:52:08) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import fidl.fuchsia_hwinfo
>>> fidl.fuchsia_hwinfo.<TAB><TAB>
fidl.fuchsia_hwinfo.Architecture(
fidl.fuchsia_hwinfo.Board()
fidl.fuchsia_hwinfo.BoardGetInfoResponse(
fidl.fuchsia_hwinfo.BoardInfo(
fidl.fuchsia_hwinfo.Device()
fidl.fuchsia_hwinfo.DeviceGetInfoResponse(
fidl.fuchsia_hwinfo.DeviceInfo(
fidl.fuchsia_hwinfo.MAX_VALUE_SIZE
fidl.fuchsia_hwinfo.Product()
fidl.fuchsia_hwinfo.ProductGetInfoResponse(
fidl.fuchsia_hwinfo.ProductInfo(
fidl.fuchsia_hwinfo.fullname

您可以查看由這個模組匯出的所有值。如要在 IPython 中進行非同步實驗,您也可以按照上述相同的環境設定執行,並執行 IPython。請先確認您已安裝 python3-ipython

sudo apt install python3-ipython

然後,您可以執行 IPython。以下範例假設您執行名為 fuchsia-emulator 的模擬器,並從 Fuchsia 預設建構目錄執行 (否則需要變更 "sdk.root"):

Python 3.11.8 (main, Feb  7 2024, 21:52:08) [GCC 13.2.0]
Type 'copyright', 'credits' or 'license' for more information
IPython 8.20.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: from fuchsia_controller_py import Context

In [2]: import fidl.fuchsia_buildinfo

In [3]: ctx = Context(target="fuchsia-emulator", config={"sdk.root": "./sdk/exported/core"})

In [4]: hdl = ctx.connect_device_proxy("/core/build-info", fidl.fuchsia_buildinfo.Provider.MARKER)

In [5]: provider = fidl.fuchsia_buildinfo.Provider.Client(hdl)

In [6]: await provider.get_build_info()
Out[6]: ProviderGetBuildInfoResponse(build_info=BuildInfo(product_config='core', board_config='x64', version='2024-04-04T18:15:05+00:00', latest_commit_date='2024-04-04T18:15:05+00:00'))

...

如要進一步瞭解如何使用 Fuchsia Controller 撰寫非同步 Python 程式碼,請參閱這個非同步 Python 頁面。