First Connection
openocd-python provides two ways to talk to OpenOCD: connect to an already-running instance, or spawn a new OpenOCD process and connect to it. Both use the Session class as the entry point.
Prerequisites
Section titled “Prerequisites”Before connecting, you need a running OpenOCD instance or a valid OpenOCD configuration file. Start OpenOCD in a separate terminal:
openocd -f interface/cmsis-dap.cfg -f target/stm32f1x.cfgYou should see output ending with something like:
Info : Listening on port 6666 for tcl connectionsInfo : Listening on port 4444 for telnet connectionsInfo : Listening on port 3333 for gdb connectionsPort 6666 is the TCL RPC port that openocd-python uses.
Connect to a running instance
Section titled “Connect to a running instance”The most common pattern is connecting to an OpenOCD instance that is already running. Use Session.connect() for async code or Session.connect_sync() for synchronous scripts.
import asynciofrom openocd import Session
async def main(): async with Session.connect() as ocd: state = await ocd.target.state() print(f"Target: {state.name}") print(f"State: {state.state}") if state.current_pc is not None: print(f"PC: 0x{state.current_pc:08X}")
asyncio.run(main())from openocd import Session
with Session.connect_sync() as ocd: state = ocd.target.state() print(f"Target: {state.name}") print(f"State: {state.state}") if state.current_pc is not None: print(f"PC: 0x{state.current_pc:08X}")Connection parameters
Section titled “Connection parameters”Session.connect() accepts three optional arguments:
| Parameter | Default | Description |
|---|---|---|
host | "localhost" | Hostname or IP address of the OpenOCD instance |
port | 6666 | TCL RPC port number |
timeout | 10.0 | Connection timeout in seconds |
# Connect to OpenOCD on a remote machineasync with Session.connect(host="192.168.1.50", port=6666, timeout=5.0) as ocd: state = await ocd.target.state()Spawn and connect
Section titled “Spawn and connect”If you want openocd-python to manage the OpenOCD process for you, use Session.start(). This spawns OpenOCD as a subprocess, waits for the TCL RPC port to become available, and connects to it. When the context manager exits, the process is stopped automatically.
import asynciofrom openocd import Session
async def main(): async with Session.start("interface/cmsis-dap.cfg -f target/stm32f1x.cfg") as ocd: state = await ocd.target.state() print(f"Target: {state.name}, State: {state.state}")
asyncio.run(main())from openocd import Session
with Session.start_sync("interface/cmsis-dap.cfg -f target/stm32f1x.cfg") as ocd: state = ocd.target.state() print(f"Target: {state.name}, State: {state.state}")Start parameters
Section titled “Start parameters”Session.start() accepts these arguments:
| Parameter | Default | Description |
|---|---|---|
config | (required) | Config file path or -f/-c flags as a string |
tcl_port | 6666 | TCL RPC port for the spawned process |
openocd_bin | None | Path to OpenOCD binary (auto-detected from PATH if None) |
timeout | 10.0 | Seconds to wait for OpenOCD to start and become ready |
extra_args | None | Additional CLI arguments passed to OpenOCD |
The config parameter is flexible. You can pass a plain filename (which gets wrapped with -f), or include -f and -c flags explicitly:
# Plain config file -- automatically wrapped as "-f interface/cmsis-dap.cfg"await Session.start("interface/cmsis-dap.cfg")
# Multiple config files with explicit flagsawait Session.start("-f interface/cmsis-dap.cfg -f target/stm32f1x.cfg")
# Inline TCL commandsawait Session.start("-f interface/cmsis-dap.cfg -f target/stm32f1x.cfg -c 'adapter speed 4000'")
# With extra argsawait Session.start( "interface/cmsis-dap.cfg -f target/stm32f1x.cfg", extra_args=["-d2"], # debug level 2)The TCL RPC protocol
Section titled “The TCL RPC protocol”openocd-python communicates over OpenOCD’s TCL RPC protocol on port 6666. The protocol is straightforward:
- The client sends a command string followed by a
\x1a(ASCII SUB) byte - The server responds with the result string followed by a
\x1abyte
The library uses a dual-socket design: one TCP connection for command/response pairs, and a separate connection for asynchronous notifications (target halt events, reset events, etc.). This prevents notification messages from corrupting the command stream.
Sending raw commands
Section titled “Sending raw commands”Every Session (and SyncSession) exposes a command() method that sends an arbitrary OpenOCD TCL command and returns the raw response string. This is the escape hatch when you need functionality that is not yet wrapped by a subsystem.
async with Session.connect() as ocd: version = await ocd.command("version") print(version)
# Any valid OpenOCD TCL command works await ocd.command("adapter speed 4000")with Session.connect_sync() as ocd: version = ocd.command("version") print(version)
ocd.command("adapter speed 4000")Context managers and cleanup
Section titled “Context managers and cleanup”Both Session.connect() and Session.start() return a Session object that acts as an async context manager. Using async with (or with for sync) ensures the connection is closed and any spawned OpenOCD process is terminated when you are done:
# The connection is closed automatically at the end of the blockasync with Session.connect() as ocd: await ocd.target.halt()# Connection is now closed
# If you started OpenOCD, the process is also terminatedasync with Session.start("interface/cmsis-dap.cfg -f target/stm32f1x.cfg") as ocd: await ocd.target.halt()# OpenOCD process has been stoppedIf you need manual lifecycle control, you can call close() directly:
ocd = await Session.connect()try: state = await ocd.target.state()finally: await ocd.close()Next steps
Section titled “Next steps”- Quick Start — complete working examples for common tasks
- Session Lifecycle — deep dive into session management, lazy subsystems, and the process manager
- Async vs Sync — understand when to use each API style