fuchsia-controller (fuchsia_controller_py) is a host-side Python library for interacting with Fuchsia target devices over FDomain/RCS and invoking FIDL protocols directly from Python.
//src/developer/fuchsia-controller/BUILD.gn defines PEP 427 Python wheel targets that package fuchsia-controller and the Fuchsia FIDL SDK (partner_idk, prebuilt, and host_tool FIDL libraries) so they can be installed into any virtual environment pinned to fuchsia-vendored-python:
//src/developer/fuchsia-controller:fuchsia_controller_combined_wheel (alias :fuchsia_controller_with_fidl_wheel): Builds a single self-contained fuchsia_controller-*.whl containing fuchsia_controller_py, fuchsia_controller_internal, fidl, and all ~270 SDK fidl_fuchsia_* Python modules.//src/developer/fuchsia-controller:fuchsia_controller_wheel and //src/developer/fuchsia-controller:fidl_sdk_wheel (alias :fidl_wheel): Separate wheels if you prefer to install fuchsia-controller and fidl-sdk independently.Build the combined wheel once:
fx build --host //src/developer/fuchsia-controller:fuchsia_controller_combined_wheel
The generated wheel is placed at:
$(fx get-build-dir)/host_x64/obj/src/developer/fuchsia-controller/fuchsia_controller_combined_wheel/fuchsia_controller-0.1.0-*.whl
uv or venv)Because fuchsia_controller_internal is a native C++ extension compiled against Fuchsia‘s prebuilt Python interpreter (fuchsia-vendored-python), the virtual environment must use Fuchsia’s vendored Python binary rather than the system /usr/bin/python3.
uv (Recommended)FUCHSIA_PYTHON="$(fx get-workspace-dir)/prebuilt/third_party/python3/linux-x64/bin/python3" WHEEL_DIR="$(fx get-build-dir)/host_x64/obj/src/developer/fuchsia-controller/fuchsia_controller_combined_wheel" # Create a persistent virtual environment and install the combined wheel: uv venv /tmp/fc_venv --python "$FUCHSIA_PYTHON" uv pip install --python /tmp/fc_venv/bin/python \ --no-index --find-links "$WHEEL_DIR" fuchsia-controller # Launch an interactive Python REPL (python -m asyncio for top-level await): /tmp/fc_venv/bin/python -m asyncio # Or run a standalone script ephemerally in a single command without creating a named venv: uv run --no-project --python "$FUCHSIA_PYTHON" \ --no-index --find-links "$WHEEL_DIR" \ --with fuchsia-controller my_script.py
fuchsia-vendored-python -m venvWHEEL_DIR="$(fx get-build-dir)/host_x64/obj/src/developer/fuchsia-controller/fuchsia_controller_combined_wheel" fuchsia-vendored-python -m venv /tmp/fc_venv /tmp/fc_venv/bin/pip install --no-index --find-links "$WHEEL_DIR" fuchsia-controller # Launch an interactive async REPL or run a script: /tmp/fc_venv/bin/python -m asyncio /tmp/fc_venv/bin/python my_script.py
By default, Context() creates a temporary isolated IsolateDir(), which does not load ~/.local/share/Fuchsia/ffx/config/.ffx_user_config.json and creates a local testing FDomain if target=None. When connecting to a live target (such as an emulator, SSH target, or USB/Cadmium device), pass an explicit target nodename/address and load .ffx_user_config.json:
import asyncio import json import pathlib import subprocess from typing import Any from fuchsia_controller_py import Context import fidl_fuchsia_buildinfo as f_buildinfo import fidl_fuchsia_developer_remotecontrol as f_remotecontrol def create_device_context(target: str | None = None) -> Context: """Creates a Context preserving user-level ffx connectivity settings.""" user_cfg_path = ( pathlib.Path.home() / ".local/share/Fuchsia/ffx/config/.ffx_user_config.json" ) config: dict[str, Any] = ( json.loads(user_cfg_path.read_text()) if user_cfg_path.exists() else {} ) config.pop("target.default", None) if isinstance(config.get("target"), dict): config["target"].pop("default", None) if not config["target"]: config.pop("target", None) else: config.pop("target", None) if not target: out = subprocess.check_output( ["ffx", "target", "list", "--format", "json"], text=True ) targets = json.loads(out) defaults = [t for t in targets if t.get("is_default")] chosen = defaults[0] if defaults else (targets[0] if targets else None) if not chosen: raise RuntimeError("No Fuchsia target found via `ffx target list`") target = chosen["nodename"] return Context(target=target, config=config) async def main() -> None: ctx = create_device_context() # Query RemoteControl (RCS) rcs = f_remotecontrol.RemoteControlClient(ctx.connect_remote_control_proxy()) ident = (await rcs.identify_host()).unwrap() print(f"Connected to nodename: {ident.response.nodename}") # Connect to any component capability by moniker and Marker build_info = f_buildinfo.ProviderClient( ctx.connect_device_proxy("core/build-info", f_buildinfo.ProviderMarker) ) info = await build_info.get_build_info() print(f"Product: {info.build_info.product_config}, Board: {info.build_info.board_config}") if __name__ == "__main__": asyncio.run(main())