Skip to content

Connection Layer

The connection layer provides the transport between openocd-python and the OpenOCD process. Most users never interact with these classes directly — the Session facade handles everything. This reference is for contributors, advanced users, and anyone debugging connection issues.

All connection backends implement the abstract base class in openocd.connection.base:

class Connection(ABC):
async def connect(self, host: str, port: int) -> None: ...
async def send(self, command: str) -> str: ...
async def close(self) -> None: ...
async def enable_notifications(self) -> None: ...
def on_notification(self, callback: Callable[[str], None]) -> None: ...
MethodDescription
connect(host, port)Open a TCP connection to the given host and port
send(command)Send a command string and return the response
close()Close the connection and release resources
enable_notifications()Enable asynchronous event notifications from OpenOCD
on_notification(callback)Register a callback for incoming notification messages

Module: openocd.connection.tcl_rpc

The primary connection backend. Speaks OpenOCD’s TCL RPC binary protocol on port 6666.

The TCL RPC protocol uses a simple framing scheme:

  • Client sends: command_bytes + \x1a
  • Server replies: response_bytes + \x1a

The \x1a byte (ASCII SUB / Ctrl-Z) acts as an unambiguous message delimiter. Commands and responses are UTF-8 encoded strings.

TclRpcConnection(timeout: float = 10.0)
ParameterTypeDefaultDescription
timeoutfloat10.0Timeout in seconds for connect and send operations

TclRpcConnection maintains two separate TCP connections to OpenOCD:

  1. Command socket — handles request/response pairs. An async lock serializes all commands to prevent interleaving.
  2. Notification socket — opened by enable_notifications(). Sends tcl_notifications on and then exclusively reads unsolicited event messages.

This separation prevents notifications from corrupting the command response stream, which would happen if both shared a single socket with two concurrent readers.

  1. Acquire the async lock
  2. Write command.encode("utf-8") + b"\x1a" to the command socket
  3. Read from the socket until \x1a is found in the response stream
  4. Any bytes after the separator are preserved in a remainder buffer for the next call
  5. Release the lock
  6. Return the response decoded as UTF-8
ConstantValueDescription
SEPARATORb"\x1a"Message delimiter byte
DEFAULT_TIMEOUT10.0Default timeout in seconds
MAX_RESPONSE_SIZE10 * 1024 * 102410 MB guard against runaway reads

When enable_notifications() is called:

  1. A second TCP connection opens to the same host:port
  2. tcl_notifications on\x1a is sent and the acknowledgement consumed
  3. A background asyncio.Task enters _notification_loop(), which reads messages delimited by \x1a
  4. Each message is dispatched to all registered callbacks
  5. If the notification connection drops, _notification_failed is set to True and subsequent send() calls log a warning
  • Connection closed: If send() reads zero bytes, ConnectionError is raised
  • Response too large: If the response buffer exceeds MAX_RESPONSE_SIZE without a separator, ConnectionError is raised (likely connected to the wrong port)
  • Timeout: If the response does not arrive within the configured timeout, TimeoutError is raised

Module: openocd.connection.telnet

A fallback connection backend that speaks to OpenOCD’s human-oriented telnet interface on port 4444.

  • Client sends: command\n
  • Server replies: response text ending with "> " prompt

The telnet connection:

  • Reads until the "> " prompt after each command
  • Strips the echoed command from the first line of the response
  • Does not support notifications (enable_notifications() logs a warning and does nothing)
TelnetConnection(timeout: float = 10.0)
FeatureTclRpcConnectionTelnetConnection
Default port66664444
Binary framing\x1a delimiter"> " prompt
NotificationsSupported (dual-socket)Not supported
Output consistencyStructuredVaries by version
RecommendedYesFallback only

Module: openocd.process

Spawns and manages an OpenOCD subprocess. Used internally by Session.start().

OpenOCDProcess()
PropertyTypeDescription
pidint | NoneProcess ID, or None if not started
runningboolWhether the process is still alive
tcl_portintThe TCL RPC port (default 6666)
async def start(
config: str | list[str],
extra_args: list[str] | None = None,
tcl_port: int = 6666,
openocd_bin: str | None = None,
) -> None

Build the command line and spawn openocd as an async subprocess.

The config parameter accepts:

  • A string like "interface/cmsis-dap.cfg -f target/stm32f1x.cfg" (automatically split and prefixed with -f where needed)
  • A pre-split list like ["-f", "my board/config.cfg"] (used as-is, preserving paths with spaces)

The method always appends -c "tcl_port <port>" to ensure the TCL RPC port matches what Session will connect to.

OpenOCD binary detection:

  1. Uses openocd_bin if provided
  2. Otherwise calls shutil.which("openocd")
  3. Raises ProcessError if not found
async def wait_ready(timeout: float = 10.0) -> None

Poll the TCL RPC port every 0.25 seconds until it accepts a TCP connection, or raise TimeoutError. Also checks whether the process has died and reports stderr output if so.

async def stop() -> None

Graceful shutdown sequence:

  1. Send SIGTERM
  2. Wait up to 5 seconds for the process to exit
  3. If still running after 5 seconds, send SIGKILL
  4. Wait for final exit