# Ellisys Analyzer Python SDK Guide

version 1.0, June 8, 2026

## 1. Introduction

`ellisys_analysis_automation` is a Pythonic SDK for the Ellisys analyzer Remote Control automation API. The automation API itself is based on the ZeroC ICE library (see the Analyzer Remote Control User Guide for the raw, language-neutral API): the Ellisys analysis software is the server and your script is the client.

The SDK wraps that API so you write idiomatic Python instead of dealing with Ice directly:

- `connect()` returns an `Analyzer` you use as a context manager — it hides all Ice setup (proxy creation, the mandatory encoding, the checked cast).

- The Overview is navigated through bounded cursors, so a trace with millions of items is safe to traverse by construction — you can never accidentally pull everything into memory.

- Product-specific operations live on facets (`analyzer.bluetooth`, `.wifi`, `.wpan`, `.usb30`); exports are typed; times are plain integers (picoseconds) or `datetime.timedelta`; and no Ice type ever crosses the SDK surface.

- Errors are a small, catchable hierarchy rooted at `RemoteControlError`.

In practice the SDK covers every non-deprecated operation of the API.

| This guide is the recommended entry point for Python automation. If you are working in C++ or .NET, or want the underlying operation-level reference, see the Analyzer Remote Control User Guide and the product manuals.

## 2. Installing the Remote Control plugin

The remote control plugin is available on Ellisys website and is linked from the user manual of the analysis software. Please follow these steps to install the plugin:

1. Install the latest analysis software release.

2. Copy the Binary\RemoteControl folder and its content to the installation directory, typically C:\Program Files (x86)\Ellisys\…​Analyzer\RemoteControl.

   Please note that Windows tends to block files downloaded from the Internet. Make sure to unblock them in the file Properties dialog, as shown below:

3. Start the analysis application and go to menu "Tools", then "Options…​". If step 2 above was successful you should see a panel named "Remote Control".

4. Check the box "Enable Remote Control on TCP/UDP port", and optionally change the port according to your network parameters. The software is now ready to accept incoming commands.

The port can be overridden by the command line with /remote_control_port=54321.

## 3. Installing the Python SDK

The SDK targets CPython 3.12-3.14 and depends on `zeroc-ice` (3.8.x). Install it from the source tree in editable mode:

    python -m pip install -e Sdk/Analysis/Python

This pulls in `zeroc-ice` and registers the `ellisys_analysis_automation` package. The SDK ships pre-generated Ice bindings and a `py.typed` marker, so editors and type-checkers see full type information with no extra steps.

## 4. Getting started

Connect to a running analyzer (with the Remote Control plug-in enabled), load a trace, and read the first few Overview items:

    import ellisys_analysis_automation as ea

    with ea.connect("localhost", 12345) as analyzer:
        analyzer.load(r"C:\traces\capture.btt")        # blocking by default; path is on the analyzer

        with analyzer.overview("BR/EDR Overview") as ov:
            for item in ov.root.cursor().stream():     # bounded, batched, page-released
                print(item.time, item.description)
                if item.time_ps > 5_000_000_000_000:   # 5 s, in picoseconds
                    break

The `with` blocks are important: leaving the `analyzer` block disconnects, and leaving the `overview` block releases all of that overview’s server-side item handles.

## 5. Key concepts and design

This section is the mental model. The internal rationale (handle generations, the global release primitive, the slice2py mapping) lives in the SDK’s `docs/` design notes; here we cover only what a user needs to use the SDK well.

### 5.1. The Analyzer and the connection

`connect()` returns an `Analyzer`. It is a context manager; use `with` (or call `close()`). Every call is synchronous and blocks until the analyzer replies. Operations that take time (loading a trace, exporting) block accordingly; `load()` waits for completion by default and raises `LoadTimeout` if it exceeds the timeout.

### 5.2. Overviews, scopes and handles

An Overview is a tree of protocol items. You open one as a scope:

    print(analyzer.overviews())            # available overview names
    with analyzer.overview("BR/EDR Overview") as ov:
        print(ov.root.child_count)         # a cheap count, even at millions of items

The server addresses items by opaque handles that live until released, and the only release primitive is global. So the SDK ties handle lifetime to the `OverviewScope`: when the `with` block exits, every handle minted inside it is freed. Touching an item after its scope closed raises `ScopeClosed` before any network call.

### 5.3. Cursors: never read everything by accident

You never get a plain list of children — that could be millions. Instead you traverse through a bounded cursor:

    cur = ov.root.cursor()                 # a ChildCursor; page_size defaults to 256
    for item in cur.stream():              # one page at a time, under the hood
        ...

    page = ov.root.cursor().page()         # a single bounded Page (<= page_size)
    first50 = ov.root.cursor().take(50)    # explicit, bounded; raises BoundExceeded past 100k
    slice_ = ov.root.cursor()[1000:1050]   # finite slice; open-ended slices are refused

- `stream()` yields live `OverviewItem` objects, paging transparently. By default each page’s handles are released at the page boundary, so a full scan keeps server memory at `O(page_size)`. The trade-off: do not hold a live item across a page boundary (it would raise `ScopeClosed`) — snapshot it instead (next bullet), or pass `release_mode="defer"` to `cursor()` to keep handles for the scope.

- `stream_records(*fields)` yields handle-free `ItemRecord` snapshots (description, time, data, XML — whichever fields you ask for). This is the safe full-scan extractor: constant client memory, page-released, and the records never go stale. Ideal for CSV/pandas.

- `take(n)`, `page()`, and `cursor[a:b]` are explicit, bounded reads; `materialize()` and `to_table()` pull everything (raising `BoundExceeded` past the cap).

### 5.4. The growth guard (loading and recording)

A trace loads asynchronously and a live capture grows the Overview, so its item count is a moving target. A full traversal that snapshotted the count once would stop short and silently drop later items. To prevent that, `stream`, `stream_records`, `walk`, `iter_children` and `materialize` raise `OverviewIncomplete` while a trace is loading or recording — unless you say how to handle it:

    # Wait for a complete trace, then traverse (the simplest, safest path):
    analyzer.load(path)                          # load(wait=True) blocks until fully loaded
    for item in ov.root.cursor().stream():
        ...

    # Or follow the growth, tailing items as they arrive (e.g. during a live capture):
    for item in ov.root.cursor().stream(follow=True):
        if matches_condition(item):
            break                                # stop when your condition is met

    # Or take an explicit snapshot of what exists right now:
    for item in ov.root.cursor().stream(snapshot=True):
        ...

Bounded reads (`take`, `page`, `cursor[a:b]`) are exempt — they are explicitly partial by request, so nothing is lost silently.

### 5.5. Searching

Search is space-windowed, depth-bounded, capped, and observable — bounded like everything else:

    sc = ov.search_cursor(description=["*Paging*"], max_depth=8, result_cap=50_000)
    for hit in sc.stream():
        hit.add_marker("found", color=ea.MarkerColor.RED)
    print(sc.found, "of", sc.scanned, "scanned")

### 5.6. Product facets

Product-specific operations are grouped on facets, reached as properties on the analyzer. They attach lazily and raise `NotAvailable` if the connected analyzer is not that product:

    summaries = analyzer.bluetooth.channel_summaries()      # per-RF-channel tallies
    analyzer.wifi.add_wifi_key_by_ap_ssid("HomeNet", "secret")
    analyzer.wpan.add_zigbee_network_key(0x1234, key16)
    analyzer.usb30.connect_link()

### 5.7. Typed exports

`Export` is one server operation selected by a mode name; the SDK gives you typed wrappers and typed option carriers, and renders the option values to the wire for you. Output paths are on the analyzer machine.

    import datetime
    from ellisys_analysis_automation import ExportModes, BluetoothExportModes, BluetoothAudioOptions, BluetoothPacketLossMode

    analyzer.export_filtered_trace_time_range(
        r"C:\out\first5s.btt", max_duration_ps=datetime.timedelta(seconds=5))
    analyzer.export_throughput(r"C:\out\tput.csv")

    # Any mode via the generic method + a typed carrier:
    analyzer.export(r"C:\out\audio", BluetoothExportModes.AUDIO,
                    options=BluetoothAudioOptions(packet_loss=BluetoothPacketLossMode.SILENCE_SMOOTH))

    # Inspect the exact wire options before committing a long export:
    print(analyzer.export_dry_run(r"C:\out\x", ExportModes.THROUGHPUT))

### 5.8. Errors

All SDK errors derive from `RemoteControlError`:

- `ConnectionFailed`— transport/connection problems (or the endpoint is not an analyzer).

- `OperationError`— the analyzer rejected the operation; subclasses: `LoadTimeout`, `ScopeClosed`, `BoundExceeded`, `OverviewIncomplete`.

- `NotAvailable`— a facet/feature is not present on this analyzer.

- `ExportOptionError` (a `ValueError`) — a bad export option, caught client-side.

## 6. How-to recipes

### 6.1. Extract a whole trace to CSV (constant memory)

    import csv
    from ellisys_analysis_automation import ItemField

    with analyzer.overview("BR/EDR Overview") as ov, open("out.csv", "w", newline="") as f:
        w = csv.writer(f)
        for rec in ov.root.cursor().stream_records(ItemField.TIME, ItemField.DESCRIPTION):
            w.writerow([rec.time_seconds, rec.description])

### 6.2. Wait for a condition during a live capture

    with analyzer.recording(r"C:\traces\live.btt"):     # start; saved when the block exits
        with analyzer.overview("BR/EDR Overview") as ov:
            for item in ov.root.cursor().stream(follow=True):
                if "Disconnect" in item.description:
                    break                                # got what we waited for; stop recording

### 6.3. Annotate items found by a search

    with analyzer.overview("BR/EDR Overview") as ov:
        for hit in ov.search_cursor(description=["*Error*"]).stream():
            hit.add_marker("error here", color=ea.MarkerColor.RED)

### 6.4. Build a table for pandas

    with analyzer.overview("BR/EDR Overview") as ov:
        rows = ov.root.cursor().to_table("Status", "Opcode", cap=5000)   # list[dict], raises past cap
    # import pandas; pandas.DataFrame(rows)

### 6.5. Survey an unknown trace

Protocol layers are per-protocol views of an overview; switching is instant (no re-processing). Counting root items across overviews and layers fingerprints what traffic a trace contains:

    with ea.connect() as analyzer:
        for name in analyzer.overviews():
            with analyzer.overview(name) as ov:
                if not ov.root.child_count:
                    continue                                  # no traffic in this overview
                print(f"{name}: {ov.root.child_count} items")
                original = ov.protocol_layer
                for layer in ov.available_protocol_layers():
                    ov.set_protocol_layer(layer)              # instant; invalidates handles
                    print(f"    {layer}: {ov.root.child_count}")
                ov.set_protocol_layer(original)

The packaged version of this (plus vocabulary, field-name and device discovery) is `ellisys_analysis_automation.discovery.explore(analyzer)`— see the API reference.

## 7. Overriding Connection Properties

### 7.1. By file

ZeroC ICE server offers plenty of parameters to optimize different applications. It is possible to override the default properties by placing a file named Ice.props in the RemoteControl folder. The file shall be of this format:
https://doc.zeroc.com/display/Ice35/Configuration+File+Syntax

### 7.2. By command line

It is also possible to override properties with the command line. Any ICE property can be set on the command line, such as /Ice.ThreadPool.Server.SizeMax=8.

### 7.3. Priority order

The priority is given as follow:

1. Defaults in the application

2. File Ice.props

3. Command line

## 8. Running the provided samples

### 8.1. C# / .net sample

1. The Samples folder contains a solution compatible with Visual Studio 2017 or higher.

2. Follow these steps to run the sample:

   1. Start the analysis software previously configured in the above section. Please ensure an analyzer unit is connected to this computer.

   2. Compile and execute the provided sample from Visual Studio.

   3. The analysis software should start recording and will wait for a key before stopping the recording.

3. The provided sample can be used as starting point for custom control software.

### 8.2. Python sample

1. Install the latest Python environment from https://www.python.org/downloads/

2. Install the sample’s dependencies with the command: python -m pip install -r requirements.txt

3. Follow these steps to run the sample:

   1. Start the analysis software previously configured in the above section. Please ensure an analyzer unit is connected to this computer.

   2. Run the sample with the command: python sample.py

   3. The analysis software should start recording and will wait for a key before stopping the recording.

4. The provided sample can be used as starting point for custom control software.

### 8.3. Using a different language or platform

The sample is provided with language-agnostic definition of the Ellisys API. The ICE files can be converted to the various supporting languages such as Ruby, JavaScript, and more, with a tool named slice provided by ZeroC. Please consult ZeroC documentation for creating a wrapper for your language and platform of choice:
https://doc.zeroc.com/ice/latest/the-slice-language/slice-compilation

## 9. Overview queries

A query filters the overview to the lines matching criteria on any column of the overview or any field of the Details view— the filtering runs inside the analyzer, so it is the most efficient way to narrow millions of items down to the lines of interest before traversing them. Queries are the same feature as the application’s query toolbar; through the API they are set with `SetOverviewQuery` (SDKs: Python `overview.set_query(…​)`, C# `overview.SetQuery(…​)`).

### 9.1. Syntax

    Field or column name = [!]value[, value, ...], Another field = [!]value[, ...]

- A term is `Field = value[, value, …​]`. Values separated by commas are alternatives: the field must match at least one of them. An exclamation mark before a value creates a NOT condition. Terms separated by commas must all match; terms can also be put in parentheses and combined with `&&` (and) / `||` (or) instead.

- Comparators: `=`, `!=`, `<`, `>`, `<=`, `>=`.

- Field and column names are written as displayed, unquoted — multi-word names work (`RF Channel Number >= 40`). Fields that appear several times can be indexed: `Data[1]` is the second `Data` field.

- Values:

  - Text in double quotes; `` matches any run of characters: `"Text"`, `"error"`.

  - Numbers: decimal `123`, hex `0xABCD`, binary `0b010101`; inclusive ranges `7..10`; computations using operators and other fields (`4+5`, `0x0F << 2`).

  - Data patterns: `0x[A1 B2 C3 ]`— bytes in hex, `##` matches any single byte, a trailing `` matches any remaining bytes. Use a leaf data field (e.g. `Raw Data`), not a grouping field.

  - Regular expressions: `RegEx("^[hc]at")`.

- Functions: `ByteAt(Field, n)`— the field’s `n`-th byte (0-based) as a number.

### 9.2. Examples

Query | Matches lines where
--- | ---
`Item = "Text*"` | the Item column starts with `Text`
`Item = !"Text"` | the Item column does not contain `Text`
`Status = "OK"` | the Status column is exactly `OK`
`Foo = 1, 0x03, 7..10` | Foo is 1, 3, 7, 8, 9 or 10
`Foo >= 0x0F << 2, 4+5 && (Bar = 1 || Status != "OK")` | Foo is at least 9, and either Bar is 1 or Status is not OK
`Item = "AVDTP*" && Status = "OK"` | the Item starts with `AVDTP` and the Status is OK
`Payload = 0x[0A ## 0C *]` | the Payload’s 1st byte is 0x0A and its 3rd byte is 0x0C
`ByteAt(Data[1], 3) = 0` | the second Data field’s 4th byte is 0
`Item = RegEx("^[hc]at")` | the Item starts with `hat` or `cat`

### 9.3. Behavior notes (important for automation)

- A line matches when any of its field occurrences match. Overview lines summarize the items beneath them, so a field can occur several times per line with different values (a line spanning several packets has several `RF Channel Number` values, and matches both `= 0..39` and `>= 40` if its packets span both ranges).

- The query is applied asynchronously. `SetOverviewQuery` returns at once and the overview re-filters in the background, reported as a running task (e.g. "Filtering data" in `GetRunningTasks`— which can take a moment to appear) while `IsLoading` stays false; existing item handles are invalidated, and counts read in the meantime may reflect the previous state. Raw-API users should poll `GetRunningTasks` until the analyzer stays idle. The SDKs handle this: `set_query` / `SetQuery` block until the re-filtering finishes by default (pass `wait=False` / `wait: false` to return immediately and settle later with `wait_until_idle()` / `WaitUntilIdle()`).

- Syntax errors are reported, unknown names are not. A malformed query (e.g. unbalanced parentheses, missing value) fails the call with `Invalid query`; a misspelled field or column name is accepted and simply matches nothing (0 lines). If a query unexpectedly returns 0, verify the field name against the overview columns / Details view of a matching item first.

- An empty query (`""`) clears the filter and restores the full overview.

## 10. Using with AI agents

The Remote Control distribution is designed to be driven by AI coding agents as well as by humans. The release archive includes three layers of agent-oriented material:

- Archive index—`llms.txt` at the archive root: a Markdown link list describing what the archive contains and where everything is. An agent pointed at the unzipped archive discovers the rest from there.

- SDK agent guides—`Sdk/Analysis/Python/ellisys_analysis_automation/llms.txt` and `Sdk/Analysis/CSharp/Ellisys.Analysis.Automation/llms.txt`: dense, authoritative API guides with the rules the bounded API enforces and canonical code recipes. They are bundled inside the SDK packages, so they are also available wherever the SDK is installed.

- Agent skill—`Skill/ellisys-analysis/`: a ready-to-install skill for Claude Code packaging the complete workflow: `SKILL.md` (the mental model, a starter recipe and a troubleshooting table), `scripts/probe.py` (connectivity diagnosis with actionable hints) and `scripts/setup_env.py` (environment bootstrap).

### 10.1. Installing the agent skill

To enable the skill for a given project, copy the `Skill/ellisys-analysis` folder into the project’s `.claude/skills/` directory:

    <project>/
        .claude/
            skills/
                ellisys-analysis/
                    SKILL.md
                    scripts/          (probe.py, setup_env.py, explore.py)
                    sdk/python/       (the bundled Python SDK source)
                    reference/        (the Python SDK guide, Markdown)

To enable it for all projects of the current user instead, copy the same folder to `~/.claude/skills/` (`%USERPROFILE%\.claude\skills\` on Windows). Claude Code activates the skill automatically whenever a task involves controlling an Ellisys analyzer; no further configuration is needed.

The skill folder is self-contained: it bundles the Python SDK source (`sdk/python/`) and the SDK guide (`reference/`), so it keeps working when copied on its own, away from this archive. Network access to PyPI is only needed for the `zeroc-ice` dependency at environment setup.

### 10.2. The MCP server

For AI hosts without shell access — chat applications such as Claude Desktop — Ellisys provides an MCP (Model Context Protocol) server, `ellisys-analysis-mcp`, distributed on PyPI. It exposes four tools: `analysis_guide` (the SDK agent guide and canonical recipes), `analysis_status` (connectivity diagnosis, application/trace state, markers), `analysis_explore` (the discovery pass: overview/protocol-layer survey, vocabulary, devices, anchors) and `analysis_run_python` (a persistent Python session with the SDK and a connected `analyzer` preloaded — all actions and analysis happen here). The session includes three helpers: `install("pkg", …​)` adds extra libraries (pandas, matplotlib, …​) on demand, `show(figure)` returns a matplotlib figure to the chat as an image, and `attach(path)` returns a small file (e.g. a CSV export; up to 4 MB) with the result. To have matplotlib and pandas preinstalled instead, configure the package with the `analysis` extra: `uvx "ellisys-analysis-mcp[analysis]@latest"`.

The only prerequisite is uv, a single small executable that provisions Python and the packages automatically — no Python installation is required. Install it once:

- Windows: `winget install astral-sh.uv`

- macOS: `brew install uv`

- Linux (and macOS without Homebrew): `curl -LsSf https://astral.sh/uv/install.sh | sh`

Then verify with `uvx --version` in a new terminal — the installer updates the PATH for new processes only — and (re)start the MCP host application afterwards so it sees the updated PATH.

The analyzer application must be running with the Remote Control plugin enabled; the server connects to `localhost:12345` by default (override with `--host`/`--port` arguments or the `ELLISYS_HOST`/`ELLISYS_PORT` environment variables).

Claude Code (terminal):

    claude mcp add ellisys-analysis -- uvx ellisys-analysis-mcp@latest

Claude Desktop— add to `claude_desktop_config.json` (Settings > Developer > Edit Config):

    {
      "mcpServers": {
        "ellisys-analysis": { "command": "uvx", "args": ["ellisys-analysis-mcp@latest"] }
      }
    }

The `@latest` suffix makes `uvx` check for the newest release every time the server starts, so updating is just a restart of the host application. Without a suffix, `uvx` keeps reusing the first version it resolved until its cache is cleared manually (`uv cache clean ellisys-analysis-mcp`); a fixed version can be pinned with `ellisys-analysis-mcp@0.1.1`.

| If the host reports the server as failed (often error `-32000`), the usual cause is that `uvx` is not on the PATH of the host process: verify `uvx --version` works in a new terminal, and restart the host application after installing uv (it inherits the PATH from its launch). Alternatively, configure the full path to `uvx` instead of the bare command.

Other MCP hosts (Cursor, VS Code, ChatGPT desktop, …​): register a custom/local MCP server with the command `uvx` and the argument `ellisys-analysis-mcp@latest` in the host’s MCP configuration. Hosts that only accept remote (HTTP) MCP servers are not yet supported (a streamable-HTTP transport is planned).

### 10.3. Choosing between the skill and the MCP server

Both deliver the same capability — they share the SDK, its agent guide and its discovery module — so the choice is purely about the host:

- Use the skill with coding agents that have shell and filesystem access (Claude Code and similar). The agent works in its own environment: it can install extra packages alongside the SDK (pandas, matplotlib, …​), produce files in your project (CSV analyses, plots, reusable scripts) and integrate the analyzer into larger codebases and CI. This is the most capable option for engineering work.

- Use the MCP server with chat applications and other hosts that cannot run scripts (Claude Desktop, ChatGPT desktop, …​). Code runs inside the server’s session and results come back as text — ideal for interactive exploration and Q&A about a trace.

- Hosts that support both (e.g. Claude Code) can use either; the skill offers more headroom there.

The skill is plain Markdown plus two small Python scripts, so agents other than Claude Code can use `SKILL.md` directly as instructions; the scripts run with any Python 3.10+ on Windows, Linux or macOS.

| The agent (the client side) can run on any platform, and the analyzer application runs on Windows natively and on Linux and macOS through the Ellisys-provided runtime — the whole chain is multiplatform. File paths passed to load, save and export operations are resolved by the analyzer application on its machine and must be valid for that installation.

## 11. API reference

The reference below is generated from the SDK’s docstrings; edit those and run `python make.py build` to regenerate.

### 11.1. Connecting and the Analyzer

#### 11.1.1. connect(host='localhost', port=12345, *, message_size_max_mb=256)

Connect to a running Ellisys analyzer that has Remote Control enabled.

Hides all ZeroC Ice setup: it loads the pre-generated bindings, forces the mandatory Ice encoding 1.0, raises the message-size limit, builds the proxy and performs the checked cast. Returns an `Analyzer`; use it as a context manager or call `close()`.

Raises `ConnectionFailed` if the analyzer cannot be reached or the endpoint is not an analyzer.

#### 11.1.2. Analyzer

A connected Ellisys analyzer. Obtain one from `connect`.

Supports the context-manager protocol; leaving the `with` block disconnects.

##### is_recording (property)

`True` while a capture is in progress.

##### start_recording()

Start a capture (equivalent to Record > Start in the GUI).

##### stop_and_save(filename, *, overwrite=False)

Stop the capture and save the trace to filename.

The path is interpreted on the analyzer machine, not the client running this script. Set `overwrite=True` to replace an existing file.

##### abort_recording()

Stop the capture and discard the trace (nothing is saved).

##### recording(save_to, *, overwrite=False)

Record for the duration of the `with` block, then stop and save to save_to.

On a normal exit the trace is saved. If the body raises, the trace is still saved (best effort) and the original exception propagates — a capture is rarely worth losing because the script that triggered it failed.

##### overviews()

Names of the available overviews (→ GetAvailableOverviews). No scope needed.

##### active_overview()

Name of the currently selected overview, or `None` (→ GetSelectedOverview).

##### overview(name=None, *, query=None, protocol_layer=None, restore_active=True)

Open a navigation scope on name (or the already-active overview if `None`).

Mirrors `recording`: on entry it snapshots the active overview, selects name, and optionally applies query / protocol_layer; it yields an `OverviewScope` for navigating the tree. On exit it frees all item handles once (`ReleaseAllOverviewItemHandles`) and, unless `restore_active=False`, re-selects the previously active overview.

Only one scope may be open at a time — handle release is global, so a nested scope would free this one’s handles. Opening a second raises `OperationError`.

##### add_marker_at_time(time_ps, text, *, color=MarkerColor.YELLOW)

Add a marker at time_ps picoseconds (→ AddMarkerAtTime).

color is a `MarkerColor` (or its int value).

##### add_marker_on_selected_overview_item(text, *, color=MarkerColor.YELLOW)

Add a marker on the currently selected overview item (→ AddMarkerOnSelectedOverviewItem).

##### markers()

Every marker in the trace as handle-free `Marker`\ s (→ GetMarkers).

##### export(output, mode, *, options=None)

Run any export mode (→ Export). Generic primary method AND escape hatch.

output is a path on the analyzer machine (like `stop_and_save`). mode is the server `exportName`— an `ExportMode` member or any raw string (including modes newer than this SDK). options is a typed carrier (e.g. `BluetoothAudioOptions`) or a raw `{OptionName: value}` mapping keyed by the EXACT server tokens (PascalCase). Values may be `int`, `bool`, `str`, `datetime.timedelta`, or an `Enum`; `None` values are omitted. A product mode used on the wrong analyzer raises `OperationError`.

##### export_dry_run(output, mode, *, options=None)

Render the exact `(OptionName, OptionValue)` wire pairs WITHOUT sending (offline).

Calls `options.validate()` if present, then returns what `export` would send. Makes no proxy call — useful for tests and for inspecting omit-None / bool / enum / picosecond rendering before committing a long export. (output is accepted for signature symmetry with `export`; it is not part of the option pairs.)

##### export_filtered_trace_time_range(output, *, start_time_ps=None, max_size_bytes=None, max_duration_ps=None, max_items=None)

Export a filtered trace by time range (BASE; any analyzer).

`*_ps` accept int picoseconds or a `datetime.timedelta`; `max_size_bytes` is a byte count. `None` omits the option (server defaults: StartTime 0, the rest = full trace).

##### export_filtered_trace_active_overview(output, *, start_time_ps=None, max_size_bytes=None, max_duration_ps=None, max_items=None)

Export a filtered trace by the active overview (BASE). Same options as the time-range form.

##### export_throughput(output, *, range_start_time_ps=None, range_end_time_ps=None)

Export throughput over a time range (BASE). `None` → server default (trace start/end).

##### app_info()

Information about the remote application (→ GetAppInfo).

##### exit_app()

Ask the remote application to exit (→ ExitApp).

##### recording_status()

Status of the current recording (→ GetRecordingStatus).

Only valid while a recording is active; the analyzer rejects it otherwise (surfacing as `OperationError`). Gate it on `is_recording`.

##### get_recording_options(*, relevant_only=False)

The recording options as an opaque string (→ GetRecordingOptions).

##### configure_recording_options(options)

Apply recording options from an opaque string (→ ConfigureRecordingOptions).

##### data_sources()

Available data source unique IDs (→ GetAvailableDataSources).

##### select_data_source(data_source_unique_id)

Select the data source by its unique ID (→ SelectDataSource).

##### selected_data_source()

The selected data source unique ID, or `None` (→ GetSelectedDataSource).

##### is_loading (property)

`True` while a trace file is loading (→ IsLoading).

##### start_loading(filename)

Begin loading filename and return immediately (→ StartLoading).

Loading is asynchronous: poll `is_loading` (or use `wait_until_loaded` / `load`) to know when it finishes. filename is a path on the analyzer machine.

##### wait_until_loaded(*, timeout=120.0, poll_interval=0.25)

Block until `is_loading` is `False` (raises `LoadTimeout` on timeout).

Polls every poll_interval seconds against a monotonic deadline. `timeout=None` waits forever. poll_interval must be > 0.

##### load(filename, *, wait=True, timeout=120.0, poll_interval=0.25)

Load a trace file (→ StartLoading); by default blocks until it finishes.

The primary entry point: it calls `start_loading` and then, unless `wait=False`, `wait_until_loaded`, so the next statement can use `trace_file_info`. Raises `LoadTimeout` if loading exceeds timeout (`None` waits forever). The path is on the analyzer machine.

##### trace_file_info()

Information about the loaded trace file (→ GetTraceFileInfo).

##### close_trace_file()

Close the loaded trace file (→ CloseTraceFile).

##### is_modified (property)

`True` if the trace has unsaved changes (→ IsModified).

##### save_changes()

Save changes to the loaded trace file (→ SaveChanges).

##### insert_message(severity, message)

Insert a message in the Message Log (→ InsertMessage).

severity is a `MessageSeverity` (or its int value).

##### running_tasks()

Background tasks currently running (→ GetRunningTasks).

##### wait_until_idle(*, timeout=600.0, poll_interval=0.25, settle=1.0)

Block until no background task is running (polls → GetRunningTasks).

Filter changes (overview queries, device filters) re-process the trace in the background, reported as a running task (e.g. `"Filtering data"`) while `is_loading` stays `False`— so this, not `wait_until_loaded`, is the wait that matches them. The background task can take a moment to appear after the triggering call, so idleness must persist for settle seconds before this returns (`settle=0` returns on the first idle observation). `timeout=None` waits forever; raises `OperationError` on timeout. poll_interval must be > 0.

##### abort_running_task(name)

Abort the running task named name (→ AbortRunningTask).

##### get_settings()

The application settings as an opaque `bytes` blob (→ GetSettings).

##### configure_settings(settings)

Apply an opaque settings `bytes` blob (→ ConfigureSettings).

##### cancel_user_interaction()

Cancel a pending user interaction on the analyzer (→ CancelUserInteraction).

##### logic_signals_state(time_ps)

Bitmask of all logic-signal levels at time_ps (bit N == signal N) (→ GetLogicSignalsState).

##### find_logic_signal_transition(from_time_ps, to_time_ps, signals_mask, transition_type=LogicSignalTransitionType.ANY)

Search [from_time_ps, to_time_ps] for a transition on the masked signals.

signals_mask selects signals by bit (set bit N to include signal N); transition_type is a `LogicSignalTransitionType`. Returns a `LogicSignalTransition` (state bitmask + time). Raises `OperationError` if no matching transition is found. (→ FindLogicSignalsTransition)

##### bluetooth (property)

The Bluetooth-specific facet (→ BluetoothAnalyzerRemoteControl), cached per analyzer.

Valid only on a Bluetooth analyzer; raises `NotAvailable` if the connected analyzer is a different product.

##### wifi (property)

The Wi-Fi facet (→ EthernetAnalyzerRemoteControl), cached; `NotAvailable` if not a Wi-Fi analyzer.

##### wpan (property)

The WPAN facet (→ WpanAnalyzerRemoteControl), cached; `NotAvailable` if not a WPAN analyzer.

##### usb30 (property)

The USB 3.0 facet (→ Usb30AnalyzerRemoteControl), cached; `NotAvailable` if not a USB 3.0 analyzer.

##### close()

Disconnect and release the Ice communicator. Idempotent.

### 11.2. Overview navigation

#### 11.2.1. ScopeClosed

A handle was used after its `OverviewScope` released it.

Raised when an item/page is touched after its scope exited, after `OverviewScope.release_handles`, or after a page-streaming cursor released the page that minted it. Subclasses `OperationError`, so existing `except OperationError` handlers still catch it.

#### 11.2.2. BoundExceeded

A bounded read was asked to materialize more than its cap.

Raised by `take(n)` / `cursor[a:b]` / `materialize(cap=)` / search `result_cap` when the requested or discovered size would exceed the limit, and by constructing a `Page` larger than `HARD_CAP`. Fail-loud by design: there is no silent truncation.

#### 11.2.3. OverviewIncomplete

A full traversal was attempted while the overview is still being populated.

The overview grows while a trace is loading or while the analyzer is recording, so a plain `stream` / `stream_records` / `walk` / `iter_children` / `materialize` would stop at whatever count happened to exist when it started and silently drop the rest. Rather than truncate quietly, those ops raise this unless the caller says how to handle the growth:

- wait until it is complete first (`analyzer.load(wait=True)` / `wait_until_loaded()`),

- pass `follow=True` to keep traversing as items arrive (live-tail), or

- pass `snapshot=True` to deliberately traverse only what exists right now.

Bounded reads (`take(n)` / `page()` / `cursor[a:b]`) are exempt — they are explicitly partial by request, so nothing is lost silently. Subclasses `OperationError`.

#### 11.2.4. ItemField

Selects which batch operation a reader issues. One member == one plural Ice op.

Members: `DESCRIPTION`, `TIME`, `DATA`, `XML`

##### xml_filtered(*field_names: str) → XmlFilter

Filtered Details XML (→ GetOverviewItemsXmlReportFiltered).

#### 11.2.5. XmlFilter

A request for the filtered Details XML of the given field names.

Fields:

- `field_names`: `tuple[str, …​]`

#### 11.2.6. ItemRecord

A pure-data, handle-FREE snapshot of one item — safe to keep after the scope closes.

Only the fields you requested are populated; the rest stay `None`. `position` is the item’s index within its parent / result set (for traceability) — it is not a handle.

Fields:

- `position`: `int`

- `description`: `str | None`

- `time_ps`: `int | None`

- `data`: `bytes | None`

- `xml`: `str | None`

##### time (property) → datetime.timedelta | None

`time_ps` as a `datetime.timedelta` (microsecond resolution), or `None`.

##### time_seconds (property) → float | None

`time_ps` in seconds as a float, or `None`.

#### 11.2.7. OverviewItem

A lazy, scope-bound wrapper around one opaque handle.

Constructing one is free. Each single-item field property does one Ice call on first access then caches it. For many items, navigate with `cursor` (bounded) or stream with `iter_children` / `walk`.

`_path` is the item’s index path from the overview root (e.g. `(3, 7)` == root’s child 3, its child 7). It lets a page-releasing cursor re-acquire the item after a global release. Items whose path is unknown (search results, the selected item) carry `None` and cannot be re-seated.

##### handle (property) → int

The raw server handle — an escape hatch for ops the SDK doesn’t wrap yet.

##### position (property) → int

Index of this item within its parent / result set.

##### depth (property) → int

Depth relative to where a `walk` started (0 for that level’s children).

##### description (property) → str

Text of the Item column (→ GetOverviewItemDescription).

##### time_ps (property) → int

Timestamp in picoseconds, lossless 64-bit (→ GetOverviewItemTimeInPicoseconds).

##### time (property) → datetime.timedelta

Timestamp as a `datetime.timedelta` (microsecond resolution).

##### data (property) → bytes

Bytes shown in the Raw data view (→ GetOverviewItemData).

##### xml_report(*, fields: Sequence[str] | None=None) → str

Details view as XML (→ GetOverviewItemXmlReport / …​Filtered).

##### child_count (property) → int

Number of direct children (→ GetOverviewItemChildCount, cached). Cheap even at millions.

##### cursor(*, page_size: int=PAGE_DEFAULT, prefetch: Sequence[ItemField] | None=DEFAULT_PREFETCH, release_mode: Literal['page', 'defer']='page') → ChildCursor

A bounded cursor over this item’s children (replaces the old unbounded `children()`).

The cursor yields one `Page` at a time. `page_size` is clamped to `PAGE_MAX`. With `release_mode="page"` (default) each page’s handles are freed at the page boundary, so a full scan keeps server memory at `O(page_size)`— at the cost that a live item must not be held across a page boundary (it raises `ScopeClosed`; use `ChildCursor.stream_records` to keep data). Use `release_mode="defer"` to retain handles (e.g. while holding live items or running concurrent cursors).

##### child(index: int) → OverviewItem

The single child at index (→ GetOverviewItemChild).

##### iter_children(*, batch_size: int=PAGE_DEFAULT, follow: bool=False, snapshot: bool=False, poll_interval: float=_FOLLOW_POLL) → Iterator[OverviewItem]

Lazily page through direct children (`ceil(N/batch_size)` calls, not N).

A true generator for bounded exploration: break early and only fetched pages cost round-trips. It does NOT release handles per page (it yields live items), so for a full multi-million-item scan prefer `ChildCursor.stream_records`, which is page-released.

While the overview is growing (loading/recording) this raises `OverviewIncomplete` unless you pass `follow=True` (live-tail the children as they arrive) or `snapshot=True` (only the children that exist now).

##### walk(*, max_depth: int | None=None, batch_size: int=PAGE_DEFAULT, follow: bool=False, snapshot: bool=False, poll_interval: float=_FOLLOW_POLL) → Iterator[OverviewItem]

Lazy depth-first walk of descendants; sets each item’s `depth`.

Yields this item’s children at depth 0, their children at depth 1, etc. max_depth limits the levels (`None` = unbounded). Built on `iter_children` (no per-page release, since it holds ancestors while descending) — intended for bounded exploration with early `break`, not for materializing a whole huge tree.

On a growing overview this raises `OverviewIncomplete` unless you pass `snapshot=True` (walk what exists now) or `follow=True` (wait until loading / recording stops, then walk the complete tree — live-tailing a recursive walk is not well-defined, so `follow` here means "let it finish first").

##### select() → None

Highlight this item in the analyzer GUI (→ SelectOverviewItem).

##### add_marker(text: str, *, color: MarkerColor=MarkerColor.YELLOW) → None

Select this item and drop a marker on it (→ SelectOverviewItem + AddMarkerOnSelectedOverviewItem).

##### record(*fields: ItemField | XmlFilter) → ItemRecord

Snapshot this item to a handle-free `ItemRecord` (single-item ops).

#### 11.2.8. OverviewItemList

An ordered, scope-bound, batch-backed sequence of `OverviewItem` (base of `Page`).

No longer constructed directly by users — a bounded `Page` is what cursors hand out. `len` / indexing / slicing / iteration are pure-local; each batch reader is one bounded round-trip over the list’s handles. Construction is capped at `HARD_CAP`.

##### handles (property) → tuple[int, …​]

The raw handles, in order (escape hatch).

##### descriptions() → list[str]

Item-column text for every item (→ GetOverviewItemsDescription).

##### times_ps() → list[int]

Timestamps in picoseconds (→ GetOverviewItemsTimeInPicoseconds).

##### times() → list[datetime.timedelta]

Timestamps as timedeltas (microsecond resolution).

##### data() → list[bytes]

Raw-data-view bytes for every item (→ GetOverviewItemsData).

##### xml_reports(*, fields: Sequence[str] | None=None) → list[str]

Details XML for every item (→ GetOverviewItemsXmlReport / …​Filtered).

##### records(*fields: ItemField | XmlFilter) → list[ItemRecord]

Snapshot the whole list to handle-free records (one batch per requested field).

##### to_rows(*field_names: str) → list[dict[str, str]]

Filtered Details fields parsed into dicts (for pandas / CSV).

##### filter(predicate: Callable[[OverviewItem], bool]) → OverviewItemList

A new list of the items matching predicate (reuses the same item objects).

#### 11.2.9. Page

One bounded window of children/matches — the only object carrying whole-set readers.

A `Page` is an `OverviewItemList` (so it has the batch readers / slicing / filter) whose length is capped by construction (`⇐ PAGE_MAX` for a streamed page, `⇐ HARD_CAP` for a `take`/slice result). It also knows its `position` in the parent and whether more follow (`has_more`).

##### next_page() → Page | None

Advance the owning cursor and return the next page (`None` at the end).

#### 11.2.10. ChildCursor

A bounded, page-at-a-time cursor over one item’s children. Built by `OverviewItem.cursor`.

Streaming (`stream`, `stream_records`) and manual paging (`page`, `next_page`, `seek`, `reset`) page in windows of `page_size` (clamped to `PAGE_MAX`). Materializing (`take`, `cursor[a:b]`, `materialize`) is bounded and raises `BoundExceeded` past `HARD_CAP`. It is NOT a `Sequence`; `total` is the cheap count, not a fetch of items.

##### total (property) → int

Number of children (→ GetOverviewItemChildCount). Cheap; not a fetch of items.

##### position (property) → int

Index of the next unread child.

##### page(size: int | None=None) → Page

The page at the current position (peek; does not advance). `size` clamped to PAGE_MAX.

##### next_page() → Page | None

Advance past the current page (releasing it if `release_mode='page'`); `None` at end.

##### reset() → None

Release any live page and seek back to the start.

##### seek(index: int) → None

Release any live page and jump the position to index.

##### stream(*, prefetch: Sequence[ItemField] | None=None, follow: bool=False, snapshot: bool=False, poll_interval: float=_FOLLOW_POLL) → Iterator[OverviewItem]

Yield children one at a time, paging under the hood (batched, bounded, page-released).

The obvious loop on a 10M-child node: `O(page_size)` handles in flight, ~3 ops/page, early `break` stops fetching. With `release_mode='page'` an item must be consumed within its page — don’t stash live items across iterations (use `stream_records`).

While the overview is still growing (loading/recording) this raises `OverviewIncomplete` unless you pass `follow=True` (re-read the count at the end and keep yielding new items as they arrive, until growth stops or you `break`) or `snapshot=True` (traverse only the items that exist now).

##### stream_records(*fields: ItemField | XmlFilter, page_size: int | None=None, follow: bool=False, snapshot: bool=False, poll_interval: float=_FOLLOW_POLL) → Iterator[ItemRecord]

Yield handle-free `ItemRecord`\ s, page by page, releasing each page.

The safe full-scan extractor: constant client memory, `O(page_size)` server handles, batched (one round-trip per field per page). Records survive the per-page release. Defaults to description + time. `follow` / `snapshot` behave as in `stream` (a growing overview otherwise raises `OverviewIncomplete`).

##### take(n: int) → Page

A `Page` of the first `min(n, total)` children. Raises if `n > HARD_CAP`.

##### materialize(*, cap: int=HARD_CAP, fields: Sequence[ItemField | XmlFilter]=DEFAULT_PREFETCH, follow: bool=False, snapshot: bool=False, poll_interval: float=_FOLLOW_POLL) → list[ItemRecord]

All children as handle-free records (page-released). Raises if `total > cap`.

On a growing overview this raises `OverviewIncomplete` unless you pass `snapshot=True` (materialize what exists now) or `follow=True` (wait until loading / recording stops, then materialize the complete set — so do not `follow` an open-ended live recording here; use `stream` and `break` for that).

##### to_table(*field_names: str, cap: int=HARD_CAP, follow: bool=False, snapshot: bool=False) → list[dict[str, str]]

All children’s filtered Details fields as dicts (for pandas / CSV). Raises if over cap.

`follow` / `snapshot` are forwarded to `materialize` (a growing overview otherwise raises `OverviewIncomplete`).

#### 11.2.11. SearchCursor

A bounded cursor over search matches. Built by `OverviewScope.search_cursor`.

`SearchOverviewItems` has no result paging, so this bounds the search SPACE: it windows the base’s DIRECT children in steps of `search_fanout`, issuing one search per window and (by default) releasing each window’s match handles before the next. `max_depth` is finite by default; `result_cap` raises `BoundExceeded` if total matches exceed it. `scanned` / `found` expose progress.

Residual risk (inherent to the server): a single windowed child whose subtree alone holds millions of matches still returns them in one array; `max_depth` and `result_cap` cap the damage to a loud raise rather than an OOM, but cannot subdivide one subtree.

##### scanned (property) → int

Direct children of the base searched so far.

##### found (property) → int

Matches yielded so far.

##### next_window() → Page | None

The next window’s matches as a `Page` (`None` when the search space is exhausted).

##### stream(*, snapshot: bool=False) → Iterator[OverviewItem]

Yield matches one at a time, windowing the search space (consume within each window).

Like the other full traversals this raises `OverviewIncomplete` while the overview is loading or recording: the searched child count is captured once, so a live search would silently miss later arrivals. Pass `snapshot=True` to search just what exists now.

##### reset() → None

Release matches and restart the search from the first window.

##### take(n: int) → Page

A `Page` of the first `min(n, matches)` matches. Raises if `n > HARD_CAP`.

##### materialize(*, cap: int=HARD_CAP, fields: Sequence[ItemField | XmlFilter]=DEFAULT_PREFETCH, snapshot: bool=False) → list[ItemRecord]

All matches as handle-free records (window-released). Raises if matches exceed cap.

Raises `OverviewIncomplete` while loading or recording; pass `snapshot=True` to materialize just what exists now.

#### 11.2.12. OverviewScope

Owns one server-side handle session for one active overview.

Constructed only by `Analyzer.overview`— never directly. It is the single place that calls `ReleaseAllOverviewItemHandles` (on exit, via `release_handles`, or at a page-streaming cursor’s page boundaries). Every item / page / cursor it mints checks the scope is live before any network call.

##### name (property) → str | None

The overview this scope is bound to.

##### closed (property) → bool

`True` once the scope has exited.

##### root (property) → OverviewItem

The active overview’s root item (→ OverviewRootItem; re-fetched if released).

##### query (property) → str

The active overview’s filter query (→ GetOverviewQuery).

##### set_query(query: str, *, wait: bool=True, timeout: float | None=600.0) → None

Apply a filter query (→ SetOverviewQuery). Invalidates existing handles.

Queries filter the overview, inside the analyzer, on any overview column or Details field — e.g. `Item = "AVDTP*" && Status != "OK"`. Comparators `= != < > ⇐ >=`; comma-separated alternative values; `!` before a value for NOT; `&&`/`||` and parentheses; numbers (`123`, `0xABCD`, `0b0101`, inclusive ranges `7..10`); data patterns `0x[A1 ]` (`` = any byte, trailing `` = any rest); `RegEx("…​")`; `ByteAt(Field, n)`. The analyzer re-filters the overview in the background (a "Filtering data" running task; `is_loading` stays `False`): by default this call blocks until that finishes. Pass `wait=False` to return immediately and settle later via `Analyzer.wait_until_idle`. A malformed query raises `OperationError` ("Invalid query"); an unknown field name silently matches nothing. `""` clears the filter. See the SDK guide’s "Overview queries" section.

##### protocol_layer (property) → str

The active overview’s protocol layer (→ GetSelectedProtocolLayer).

##### set_protocol_layer(layer: str) → None

Switch the protocol layer (→ SelectProtocolLayer). Invalidates existing handles.

Protocol layers are per-protocol views of the overview; switching is instant (no background re-processing, unlike queries/device filters). Iterating `available_protocol_layers` and reading `root.child_count` per layer is the quickest way to fingerprint what traffic a trace contains.

##### available_protocol_layers() → list[str]

Protocol layers selectable on the active overview (→ GetAvailableProtocolLayers).

##### search_cursor(*, description: Sequence[str]=(), field_name: Sequence[str]=(), field_value: Sequence[str]=(), within: OverviewItem | None=None, search_fanout: int=DEFAULT_SEARCH_FANOUT, max_depth: int | None=DEFAULT_SEARCH_DEPTH, result_cap: int=DEFAULT_RESULT_CAP, prefetch: Sequence[ItemField] | None=DEFAULT_PREFETCH) → SearchCursor

A bounded cursor over matches under within (default: the scope root).

Filters are glob patterns (`` / `?`) or a .NET regex prefixed `regex:`; a bare literal must match the *whole value. `field_name` / `field_value` filter the Details fields, pairing by index when both are given. `max_depth` defaults to a finite `DEFAULT_SEARCH_DEPTH` (pass `None` for the whole subtree); `result_cap` bounds the total matches before `BoundExceeded`.

##### selected (property) → OverviewItem | None

The item highlighted in the GUI, or `None` (→ GetSelectedOverviewItem).

##### select(item: OverviewItem) → None

Highlight item in the GUI (→ SelectOverviewItem).

##### release_handles() → None

Free all item handles now without closing the scope (→ ReleaseAllOverviewItemHandles).

Global by nature — there is no per-handle release. Every live item/page minted in this scope raises `ScopeClosed` on next use; already-extracted `ItemRecord`\ s stay valid. This is also the primitive a page-streaming cursor uses at page boundaries.

### 11.3. Bluetooth facet

#### 11.3.1. BluetoothChannelSummary

Per-RF-channel packet tallies (→ Slice struct `ChannelSummary`).

Fields:

- `ok`: `int`

- `retransmitted`: `int`

- `payload_error`: `int`

- `header_error`: `int`

- `not_applicable`: `int`

##### errors (property) → int

Header + payload errors.

##### total (property) → int

Classified packets on this channel (excludes `not_applicable`).

##### error_rate (property) → float | None

Fraction of classified packets in error, or `None` if the channel had no data.

#### 11.3.2. BluetoothChannelStat

An (RF channel, summary) pair — the explicit-index view of channel summaries.

Fields:

- `rf_channel`: `int`

- `summary`: `BluetoothChannelSummary`

#### 11.3.3. BluetoothSpectrumSample

One RSSI sample (→ GetSpectrumRssi).

Fields:

- `rssi_dbm`: `float`

- `start_ps`: `int`

- `stop_ps`: `int`

##### window (property) → datetime.timedelta

Length of the sampling window (`stop_ps - start_ps`) as a timedelta.

#### 11.3.4. BluetoothSpectrumRange

A series of RSSI samples over a window (→ GetSpectrumRssiRange).

Fields:

- `rssi_dbm`: `tuple[float, …​]`

- `start_ps`: `int`

- `stop_ps`: `int`

##### window (property) → datetime.timedelta

Length of the sampling window (`stop_ps - start_ps`) as a timedelta.

##### mean_dbm (property) → float | None

Mean RSSI across the samples in dBm, or `None` if there are none.

##### min_dbm (property) → float | None

Lowest RSSI sample in dBm, or `None` if there are none.

##### max_dbm (property) → float | None

Highest RSSI sample in dBm, or `None` if there are none.

#### 11.3.5. format_bluetooth_bdaddr(addr: int) → str

Format a 48-bit BDADDR int as `"AA:BB:CC:DD:EE:FF"` (a convenience; the API uses ints).

#### 11.3.6. BluetoothPacketLossMode

`bluetooth_audio` PacketLoss option; each member’s value is the exact server token.

Members: `NEGATIVE_DC`, `SILENCE`, `SILENCE_SMOOTH`

#### 11.3.7. BluetoothExportModes

Bluetooth export modes (valid only on a Bluetooth analyzer). Enumerate via `ALL`.

#### 11.3.8. BluetoothAudioOptions

Options for `bluetooth_audio` (Bluetooth analyzers only).

Fields:

- `add_raw_payload_files`: `bool | None`

- `synchro_buffer_ms`: `int | None`

- `force_isochronous_buffering`: `bool | None`

- `packet_loss`: `BluetoothPacketLossMode | None`

##### as_options() → dict[str, object]

The `{server OptionName: value}` mapping for this mode (unset options are dropped).

##### validate() → None

No range constraints to check.

#### 11.3.9. BluetoothChannelsOptions

Options for `bluetooth_channels` (Bluetooth analyzers only).

Fields:

- `range_start_time_ps`: `int | datetime.timedelta | None`

- `range_end_time_ps`: `int | datetime.timedelta | None`

##### as_options() → dict[str, object]

The `{server OptionName: value}` mapping for this mode (unset options are dropped).

##### validate() → None

Validate the range — raises `ExportOptionError` if range end precedes start.

#### 11.3.10. BluetoothAirtimeOptions

Options for `airtime` (Bluetooth analyzers only).

Fields:

- `range_start_time_ps`: `int | datetime.timedelta | None`

- `range_end_time_ps`: `int | datetime.timedelta | None`

##### as_options() → dict[str, object]

The `{server OptionName: value}` mapping for this mode (unset options are dropped).

##### validate() → None

Validate the range — raises `ExportOptionError` if range end precedes start.

#### 11.3.11. BluetoothFacet

Bluetooth-only operations on a connected analyzer. Obtain via `analyzer.bluetooth`.

##### split_trace_and_continue(filename) → None

Save the current trace and keep recording — "Save & Continue" (→ SplitTraceFileAndContinueRecording). filename is on the analyzer machine.

##### spectrum_rssi(time_ps, rf_channel: int) → BluetoothSpectrumSample

RSSI at time_ps on rf_channel (0-78) (→ GetSpectrumRssi).

##### spectrum_rssi_range(from_time_ps, to_time_ps, rf_channel: int) → BluetoothSpectrumRange

RSSI samples across [from_time_ps, to_time_ps] on rf_channel (→ GetSpectrumRssiRange).

##### channel_summaries(from_time_ps=0, to_time_ps=None) → list[BluetoothChannelSummary]

Per-RF-channel summaries; list index == RF channel (→ GetChannelsSummary).

Defaults to the whole trace (`from_time_ps=0`, `to_time_ps=None` → trace end).

##### channels(from_time_ps=0, to_time_ps=None) → list[BluetoothChannelStat]

Per-channel summaries as explicit `(rf_channel, summary)` pairs.

##### configure_device_filter(mode: DeviceFilterMode, device_addrs: Sequence[int | str]=(), *, wait: bool=True, timeout: float | None=600.0) → None

Set which devices are kept/excluded (→ ConfigureDeviceFilter).

mode is a `DeviceFilterMode`; device_addrs are 48-bit BDADDRs — ints or strings as the analyzer displays them (`"AA:BB:CC:DD:EE:FF"`); ignored for `KEEP_ALL` / `EXCLUDE_BACKGROUND`. The analyzer re-filters in the background: by default this call blocks until that finishes (`wait=False` returns immediately; settle later via `Analyzer.wait_until_idle`).

##### add_link_key(bdaddr1: int | str, bdaddr2: int | str, link_key) → None

Add a 16-byte link key for the bdaddr1<→*bdaddr2* link (→ AddLinkKey).

Addresses are 48-bit BDADDRs — ints or strings (`"AA:BB:CC:DD:EE:FF"`).

##### export_audio(output, *, add_raw_payload_files=None, synchro_buffer_ms=None, force_isochronous_buffering=None, packet_loss: 'BluetoothPacketLossMode | None'=None) → None

Export Bluetooth audio via the base `Export` pipeline (mode `bluetooth_audio`).

Builds the `BluetoothAudioOptions` carrier and delegates to `Analyzer.export`. (The legacy dedicated `ExportAudio` op is intentionally not wrapped; this is the supported audio export.)

##### export_channels(output, *, range_start_time_ps=None, range_end_time_ps=None) → None

Export per-channel statistics (mode `bluetooth_channels`).

##### export_airtime(output, *, range_start_time_ps=None, range_end_time_ps=None) → None

Export airtime usage (mode `airtime`).

##### export_mobile_phone_data(output) → None

Export Bluetooth mobile-phone data (mode `bluetooth_mobile_phone_data`; no options).

### 11.4. Wi-Fi facet

#### 11.4.1. WifiFacet

Wi-Fi-only operations on a connected analyzer. Obtain via `analyzer.wifi`.

##### add_wifi_key_by_ap_ssid(ap_ssid: str, key: str) → None

Add a Wi-Fi key for the access point with SSID ap_ssid (→ AddWifiKeyByApSsid).

##### add_wifi_key_by_ap_mac(ap_mac_addr: int | str, key: str) → None

Add a Wi-Fi key for the access point with BSSID ap_mac_addr— a 48-bit int or a string like `"AA:BB:CC:DD:EE:FF"` (→ AddWifiKeyByApMacAddr).

##### configure_device_filter(mode: DeviceFilterMode, device_addrs: Sequence[int | str]=(), *, wait: bool=True, timeout: float | None=600.0) → None

Set which devices are kept/excluded (→ ConfigureDeviceFilter).

mode is a `DeviceFilterMode`; device_addrs are 48-bit MACs — ints or strings (`"AA:BB:CC:DD:EE:FF"`); ignored for `KEEP_ALL` / `EXCLUDE_BACKGROUND`. The analyzer re-filters in the background: by default this call blocks until that finishes (`wait=False` returns immediately).

### 11.5. WPAN / 802.15.4 facet

#### 11.5.1. WpanFacet

WPAN-only operations on a connected analyzer. Obtain via `analyzer.wpan`.

##### add_thread_master_key(pan_id: int, key128, sequence_counter: int) → None

Add a Thread network master key (16 bytes) for pan_id (→ AddThreadNetworkMasterKey).

##### remove_all_thread_master_keys() → None

Remove all Thread network master keys (→ RemoveThreadNetworkAllMasterKeys).

##### remove_thread_master_keys(pan_id: int) → None

Remove all Thread master keys for pan_id (→ RemoveThreadNetworkMasterKeys).

##### remove_thread_master_key(pan_id: int, key128) → None

Remove one Thread master key for pan_id (→ RemoveThreadNetworkMasterKey).

##### add_zigbee_aps_key(destination_address64, source_address64, key128) → None

Add a Zigbee APS link key (16 bytes) for a device pair (→ AddZigbeeApsKey).

##### remove_all_zigbee_aps_keys() → None

Remove all Zigbee APS link keys (→ RemoveZigbeeApsAllKeys).

##### remove_zigbee_aps_keys(destination_address64, source_address64) → None

Remove all Zigbee APS link keys for a device pair (→ RemoveZigbeeApsKeys).

##### remove_zigbee_aps_key(destination_address64, source_address64, key128) → None

Remove one Zigbee APS link key for a device pair (→ RemoveZigbeeApsKey).

##### add_zigbee_network_key(pan_id: int, key128) → None

Add a Zigbee network key (16 bytes) for pan_id (→ AddZigbeeNetworkKey).

##### remove_all_zigbee_network_keys() → None

Remove all Zigbee network keys (→ RemoveZigbeeNetworkAllKeys).

##### remove_zigbee_network_keys(pan_id: int) → None

Remove all Zigbee network keys for pan_id (→ RemoveZigbeeNetworkKeys).

##### remove_zigbee_network_key(pan_id: int, key128) → None

Remove one Zigbee network key for pan_id (→ RemoveZigbeeNetworkKey).

### 11.6. USB 3.0 facet

#### 11.6.1. Usb30Facet

USB 3.0-only operations on a connected analyzer. Obtain via `analyzer.usb30`.

##### connect_link(disconnect_super_speed: bool=False) → None

Connect the analyzer’s USB link to the device under test (→ ConnectLink).

disconnect_super_speed keeps only the USB 2.0 link connected when `True`.

##### disconnect_link() → None

Disconnect the analyzer’s USB link from the device under test (→ DisconnectLink).

##### save_usb20_packets(filename) → None

Save the captured USB 2.0 packets to filename on the analyzer (→ SaveUsb20Packets).

##### save_usb30_symbols(filename, upstream_symbols: bool) → None

Save captured USB 3.0 symbols to filename (→ SaveUsb30Symbols).

upstream_symbols selects the upstream (`True`) or downstream (`False`) direction.

### 11.7. Exports

#### 11.7.1. ExportOptionError

A client-side export-option problem (bad value type, failed validation).

Distinct from `OperationError` (server-side, e.g. a wrong-product mode) and `ConnectionFailed`.

#### 11.7.2. ExportMode

A known `exportName` token plus its metadata.

`value` is the exact wire token; `product` is `"base"` / `"bluetooth"` / `""` (unknown). Equality and hashing are by `value` only, and `str(mode)` is the token, so a mode compares equal to its raw string and methods accept `str | ExportMode`— modes newer than this SDK stay reachable by passing the raw token. Use the `ExportModes` (base) and product catalogues (e.g. `BluetoothExportModes`) for the known ones.

Fields:

- `value`: `str`

- `product`: `str`

- `description`: `str`

#### 11.7.3. ExportModes

The base export modes (valid on any analyzer). Enumerate them via `ALL`.

#### 11.7.4. render_option_value(value: object) → str | None

Render ONE typed option value to its exact wire string, or `None` to OMIT it.

The single source of truth for the wire rules: * `None` → `None` (unset; never sent, server default applies) * `bool` → `"true"` / `"false"` (.NET `bool.Parse` form) * `enum.Enum` → `str(value.value)` (exact token, e.g. `"SilenceSmooth"`) * `timedelta` → integer picoseconds as a decimal string * `int` → decimal string * `str` → verbatim

`bool` is checked before `int` (`bool` subclasses `int`) and `enum` before `int` (an `IntEnum` is an `int`).

#### 11.7.5. ExportOptions

Anything that can supply server-spelled option pairs (every carrier implements it).

##### as_options() → dict[str, object]

Return `{ServerOptionName: typed value}` (the snake→Pascal mapping lives here).

##### validate() → None

Validate the options, raising `ExportOptionError` on a contradictory value (e.g. a time range whose end precedes its start). Carriers without constraints are a no-op.

#### 11.7.6. FilteredTraceOptions

Options for `filtered_trace_time_range` and `filtered_trace_active_overview`.

Fields:

- `start_time_ps`: `int | datetime.timedelta | None`

- `max_size_bytes`: `int | None`

- `max_duration_ps`: `int | datetime.timedelta | None`

- `max_items`: `int | None`

##### as_options() → dict[str, object]

The `{server OptionName: value}` mapping for this mode (unset options are dropped).

##### validate() → None

No range constraints to check.

#### 11.7.7. ThroughputOptions

Options for `throughput`.

Fields:

- `range_start_time_ps`: `int | datetime.timedelta | None`

- `range_end_time_ps`: `int | datetime.timedelta | None`

##### as_options() → dict[str, object]

The `{server OptionName: value}` mapping for this mode (unset options are dropped).

##### validate() → None

Validate the options — raises `ExportOptionError` if range end precedes start.

### 11.8. Markers

#### 11.8.1. MarkerColor

A marker colour (values match the analyzer’s `MarkerColor` Slice enum).

Members: `YELLOW`, `BLUE`, `RED`, `GREEN`, `ORANGE`, `PURPLE`

#### 11.8.2. Marker

A marker read from the trace (→ GetMarkers). Handle-free; `time_ps` is picoseconds.

Fields:

- `color`: `MarkerColor`

- `text`: `str`

- `time_ps`: `int`

##### time (property) → datetime.timedelta

`time_ps` as a `datetime.timedelta` (microsecond resolution).

##### time_seconds (property) → float

`time_ps` in seconds as a float.

### 11.9. Session, trace files and info

#### 11.9.1. MessageSeverity

Severity for `Analyzer.insert_message` (mirrors the Slice enum; 1-based).

Members: `INFO`, `WARNING`, `ERROR`

#### 11.9.2. AppInfo

Information about the remote application (→ GetAppInfo).

Fields:

- `id`: `str`

- `description`: `str`

- `version`: `str`

- `file_ext`: `str`

- `interactive`: `bool`

#### 11.9.3. RecordingStatus

Status of the current recording (→ GetRecordingStatus).

Fields:

- `data_source`: `str`

- `duration_seconds`: `int`

- `file_size`: `int`

##### duration (property) → datetime.timedelta

`duration_seconds` as a `datetime.timedelta` (exact).

#### 11.9.4. RunningTask

A background task running in the analyzer (→ GetRunningTasks).

Fields:

- `name`: `str`

- `progress_percent`: `int`

- `progress_applicable`: `bool`

##### progress (property) → int | None

Percent complete, or `None` when the server says progress isn’t meaningful.

#### 11.9.5. TraceFileInfo

Information about the loaded trace file (→ GetTraceFileInfo).

Fields:

- `path`: `str`

- `file_size`: `int`

- `start_datetime_utc_raw`: `int`

- `software_version`: `str`

- `data_source`: `str`

- `unique_id`: `str`

- `recording_options`: `str`

##### started_at_utc (property) → datetime.datetime | None

The trace start time as a tz-aware UTC datetime, or `None` when the raw value is non-positive (unset). `start_datetime_utc_raw` is milliseconds since 2000-01-01 UTC (the epoch the analyzer’s own trace indexer uses); reinterpret the raw value yourself if you ever need a different one.

### 11.10. Logic signals

#### 11.10.1. LogicSignalTransitionType

Which edge to search for (mirrors the Slice `LogicSignalTransitionType` enum).

Members: `ANY`, `RISING_EDGE`, `FALLING_EDGE`

#### 11.10.2. LogicSignalTransition

A found logic-signal transition (→ FindLogicSignalsTransition).

Fields:

- `state`: `int`

- `time_ps`: `int`

##### time (property) → datetime.timedelta

The transition time as a `datetime.timedelta` (microsecond resolution).

### 11.11. Errors

#### 11.11.1. RemoteControlError

Base class for every error raised by the SDK.

#### 11.11.2. ConnectionFailed

The analyzer could not be reached or the connection was lost.

Common causes: the Ellisys software is not running, the Remote Control plugin is not enabled, or the host/port is wrong.

#### 11.11.3. OperationError

The analyzer accepted the request but rejected the operation.

Wraps the Slice `OperationFailed` exception; the message carries the analyzer’s reason when one is available.

#### 11.11.4. NotAvailable

A requested product facet is not available on the connected analyzer.

Raised by `analyzer.bluetooth` (and future product facets) when the endpoint is reachable and healthy but is a different product, so the product interface’s `checkedCast` returns no proxy. Distinct from `ConnectionFailed` (transport) and `OperationError` (the server rejected an operation, e.g. a wrong-product `export`); still catchable as `RemoteControlError`.

#### 11.11.5. LoadTimeout

A trace load did not finish within the timeout.

Raised by `Analyzer.load` / `Analyzer.wait_until_loaded` when `IsLoading` stays `True` past the deadline. Subclasses `OperationError` so existing `except OperationError` handlers still catch it; the message carries the filename (when known), the elapsed time, and the timeout.

## Revisions History

Date | Rev | Changes
--- | --- | ---
June 8, 2026 | 1.0 | Initial Python SDK guide.
June 10, 2026 | 1.1 | Added the Using with AI agents and Overview queries sections.

## Copyright and Intellectual Property

Copyright Disclaimer

Copyright © Ellisys 2026. All Rights Reserved.

No part of this document or any of its contents may be reproduced, copied, modified or adapted, without the prior written consent of Ellisys.

Intellectual Property Disclaimer

This document is provided to you "as is" with no warranties whatsoever, including any warranty of merchantability, non-infringement, or fitness for any particular purpose. Ellisys disclaims all liability, including liability for infringement of any proprietary rights, relating to use or implementation of information in this document. The provision of this document to you does not provide you with any license, express or implied, by estoppel or otherwise, to any intellectual property rights.

Open Source Disclaimer

This Ellisys analysis software automation API plugin is based on a third-party networking library from ZeroC named ICE (https://zeroc.com/products/ice). This library is licensed under the GNU GPL v2 or later. This plugin is also licensed under the GPL, and its source code can be requested at gpl@ellisys.com.

This plugin is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.