Skip to content

Usage

For repeatable, strictly read-only development evidence, see Read-only capture.

Installation

Install the maintained release from PyPI:

pip install xpwebapi

Source and issue tracking are available in the canonical TV Productions repository.

Connection lifecycle

Use context managers for REST clients so the underlying HTTP session is closed even when a request fails.

import xpwebapi


with xpwebapi.rest_api(host="127.0.0.1", port=8086, api_version="v2") as api:
    print(api.capabilities)

    clock = api.dataref("sim/cockpit2/clock_timer/local_time_seconds")
    print(api.dataref_value(clock))

Async REST clients follow the same pattern with async with.

import asyncio

import xpwebapi


async def main() -> None:
    async with xpwebapi.async_rest_api(api_version="v2") as api:
        clock = api.dataref("sim/cockpit2/clock_timer/local_time_seconds")
        value = await api.dataref_value(clock)
        print(value)


asyncio.run(main())

When creating many short-lived synchronous REST API objects, opt in to shared HTTP connection pooling. Instances with matching pool configuration reuse the same underlying httpx.Client; the shared client closes after the last pooled API instance is closed.

import xpwebapi


first = xpwebapi.rest_api(
    api_version="v2",
    pool_connections=True,
    max_connections=8,
    max_keepalive_connections=4,
    keepalive_expiry=30.0,
    timeout=5.0,
)
second = xpwebapi.rest_api(
    api_version="v2",
    pool_connections=True,
    max_connections=8,
    max_keepalive_connections=4,
    keepalive_expiry=30.0,
    timeout=5.0,
)

try:
    print(first.capabilities)
    clock = second.dataref("sim/cockpit2/clock_timer/local_time_seconds")
    print(second.dataref_value(clock))
finally:
    first.close()
    second.close()

For WebSocket and UDP clients, explicitly stop monitoring/listener threads before disconnecting when you do not use a context manager.

import xpwebapi


ws = xpwebapi.ws_api(api_version="v2")
try:
    ws.connect()
    ws.wait_connection()
    ws.start(release=True)
finally:
    ws.stop()
    ws.disconnect()

Monitoring datarefs

WebSocket monitoring is callback-driven. Register the callback, connect, subscribe to the datarefs, then start the listener. Use monitor_datarefs() when subscribing to more than one dataref; it sends one WebSocket dataref_subscribe_values request for all newly monitored datarefs instead of one request per dataref. The matching unmonitor_datarefs() helper batches unsubscribe requests for datarefs whose monitor count reaches zero. Selected array elements that share the same dataref id are grouped into one request with an index list.

from typing import Any

import xpwebapi


def dataref_monitor(dataref: str, value: Any) -> None:
    print(f"{dataref}={value}")


ws = xpwebapi.ws_api(api_version="v2")
ws.add_callback(
    cbtype=xpwebapi.CALLBACK_TYPE.ON_DATAREF_UPDATE,
    callback=dataref_monitor,
)

ws.connect()
ws.wait_connection()

datarefs = [
    ws.dataref("sim/cockpit2/clock_timer/local_time_seconds"),
    ws.dataref("sim/flightmodel/position/latitude"),
    ws.dataref("sim/flightmodel/position/longitude"),
]
ws.monitor_datarefs(datarefs=datarefs, reason="example")
ws.start(release=True)

UDP monitoring uses the X-Plane beacon to discover the simulator before subscribing.

from typing import Any

import xpwebapi


def dataref_monitor(dataref: str, value: Any) -> None:
    print(f"{dataref}={value}")


beacon = xpwebapi.beacon()
beacon.start_monitor()
beacon.wait_for_beacon()

udp = xpwebapi.udp_api(beacon=beacon)
udp.add_callback(callback=dataref_monitor)
udp.monitor_dataref(udp.dataref("sim/flightmodel/position/indicated_airspeed"))
udp.start(release=True)

Executing commands

Create a command object from the API instance that will execute it. REST and UDP calls return immediate success/failure values; WebSocket command execution can return a queued request id.

import xpwebapi


with xpwebapi.rest_api(api_version="v2") as api:
    mapview = api.command("sim/map/show_current")
    result = mapview.execute()
    print(result)

Long-running commands can pass an explicit duration when the transport supports it.

import xpwebapi


with xpwebapi.ws_api(api_version="v2") as ws:
    ws.connect()
    ws.wait_connection()

    brakes = ws.command("sim/flight_controls/brakes_toggle_max")
    request_id = ws.execute_command(brakes, duration=0.5)
    print(request_id)

REST API

import xpwebapi


with xpwebapi.rest_api(api_version="v2") as api:
    print(api.capabilities)

    dataref = api.dataref("sim/cockpit2/clock_timer/local_time_seconds")
    print(dataref)

    mapview = api.command("sim/map/show_current")
    mapview.execute()

Async REST API

import asyncio

import xpwebapi


async def main() -> None:
    async with xpwebapi.async_rest_api(api_version="v2") as api:
        dataref = api.dataref("sim/cockpit2/clock_timer/local_time_seconds")
        value = await api.dataref_value(dataref)
        print(value)

        mapview = api.command("sim/map/show_current")
        await api.execute_command(mapview)


asyncio.run(main())

WebSocket API

from typing import Any

import xpwebapi


def dataref_monitor(dataref: str, value: Any) -> None:
    print(f"dataref updated: {dataref}={value}")


def command_active_monitor(command: str, active: bool) -> None:
    print(f"command activated: {command}={active}")


ws = xpwebapi.ws_api(api_version="v2")
ws.add_callback(cbtype=xpwebapi.CALLBACK_TYPE.ON_DATAREF_UPDATE, callback=dataref_monitor)
ws.add_callback(cbtype=xpwebapi.CALLBACK_TYPE.ON_COMMAND_ACTIVE, callback=command_active_monitor)

ws.connect()
ws.wait_connection()

dataref = ws.dataref("sim/cockpit2/clock_timer/local_time_seconds")
ws.monitor_dataref(dataref)
ws.monitor_command_active(ws.command("sim/map/show_current"))

ws.start(release=True)

UDP API

from typing import Any

import xpwebapi


def dataref_monitor(dataref: str, value: Any) -> None:
    print(f"{dataref}={value}")


beacon = xpwebapi.beacon()
beacon.start_monitor()
beacon.wait_for_beacon()

udp = xpwebapi.udp_api(beacon=beacon)
udp.add_callback(callback=dataref_monitor)

mapview = udp.command("sim/map/show_current")
udp.execute_command(mapview)

udp.monitor_dataref(udp.dataref(path="sim/flightmodel/position/indicated_airspeed"))
udp.monitor_dataref(udp.dataref(path="sim/flightmodel/position/latitude"))
udp.start(release=True)