Skip to main content
Control embedded debug operations including device connection, firmware flashing, reset, and memory access using J-Link debug probes.

Import

Methods

The Net-based API provides methods for embedded debugging operations.

Method Reference

Net.get(name, type=NetType.Debug)

Get a debug net by name.
Parameters: Returns: Debug Net instance Note: The debug net must be configured with the target device name stored in the channel field (e.g., ‘NRF52840_XXAA’, ‘R7FA0E107’). Connect to the target device (start the gdbserver for this probe). The backend (J-Link or OpenOCD) is chosen automatically from the probe.
Parameters: Returns: dict - Status dictionary with connection information
A script override takes effect only when the gdbserver relaunches. If a server is already up, pass force=True to restart it with the new script. ignore_if_connected=True returns early and does not relaunch, but it still repoints the file for later operations. Invalid input means a missing path that is not valid base64, or an empty string. The net ignores such input silently, and the script that it materialized earlier stays in effect.The override is scoped to this net and this session. Lager writes it to a per-net path rather than to the box-wide config that the net record and the HTTP debug service share. disconnect() clears the override. Two debug nets that connect with different scripts therefore no longer clobber one another.
An openocd_config override must be a complete cfg, not a fragment. Lager applies it after the interface and target configs. The launch line still carries lager’s own -c 'ftdi channel N' whenever the net has a probe channel. OpenOCD does not recognize that command unless a cfg first selects the ftdi adapter driver. A cfg holding only, say, adapter speed 1000 dies at startup with invalid command name "ftdi".

disconnect()

Disconnect from the target device.
Returns: dict - Status dictionary

reset(halt=False)

Reset the device.
Parameters: Returns: str - Combined output from reset operation Self-heal (both backends): Right after a flash() there is a short window where the debug server is not reachable yet. On J-Link the cause is that the restarted GDB server’s PID is not observable yet. On OpenOCD the cause is a transient daemon or RPC fault. A bare call raises inside that window. reset() retries with bounded backoff on both the J-Link and OpenOCD backends, and it restarts a server only when one is genuinely down. It never tears down a server that is already up, so an attached RTT session stays intact. Callers no longer need their own retry wrappers. DA1469x exception: On DA1469x, flash() deliberately leaves the server down. The flash ends in a software reset rather than a server restart, and the documented flow is an explicit, halt-aware reconnect. So the self-heal still retries on DA1469x, but it never auto-starts a server. A server that is genuinely down still surfaces the original error, exactly as before. The self-heal never brings a server up unhalted, because an unhalted server can return garbage QSPI-XIP reads.

halt()

Halt the target where it is, without a reset. OpenOCD only.
Returns: str - Combined output from the halt operation This is not the same as reset(halt=True). That runs OpenOCD’s reset halt, which pulses nRESET and re-enters through the reset vector. halt() issues a bare halt instead, so the core stops where it is, and nRESET is never touched. The distinction matters on parts that execute in place out of QSPI. After you program such a part, reset(halt=True) re-runs the bootloader rather than stopping on the image you just wrote. An unhalted re-attach risks garbage XIP reads. Halting in place is how you attach after a program without disturbing the image.
J-Link has no standalone halt-in-place primitive, because reset_device and gdb_reset both reset first. On that backend halt() raises, and the error names the halt-first .JLinkScript as the supported route.

flash(firmware_path, flash_address=None)

Flash firmware to the device.
Parameters: Returns: str - Combined output from flash operation Note: A .bin has no embedded address, so flash() raises without flash_address rather than defaulting to 0x0. Pass the target’s flash base: STM32 0x08000000, nRF52 0x00000000, DA1469x QSPI 0x16000000. DA1469x on OpenOCD: mainline OpenOCD has no flash driver for the DA1469x’s external QSPI. On that family, flash() and erase() drive the RAM-resident flash_loader instead, the same path lager debug <net> flash takes. Pass the absolute XIP address (0x16000000), exactly as on J-Link. The loader artefacts live on the box under /home/www-data/customer-binaries/openocd/flash-loaders/da1469x/. A missing loader raises rather than falling back to OpenOCD’s program, which cannot reach QSPI.

erase()

Perform full chip erase. This erases ALL flash memory including protection settings. On a DA1469x behind an OpenOCD probe this is the flash_loader’s range erase of the first 1 MiB of QSPI, matching the J-Link path.
Returns: str - Combined output from erase operation

read_memory(address, length)

Read memory from the target device.
Parameters: Returns: bytes - Memory data Self-heal: like reset(), read_memory() retries with bounded backoff across the brief post-flash() settling window on both backends. It reconnects only when no server is up, and it never disturbs a live session. erase() behaves the same way. The same DA1469x exception applies. No server is auto-started, so a post-flash DA1469x read raises clearly rather than returning unhalted-XIP garbage.

status()

Get the current connection status.
Returns: dict - running (bool), pid and backend. This reports whether a gdbserver process is up for the probe. It is not a statement about the target: a server can outlive the part it was attached to. The CLI’s lager debug <net> status reports both states separately.

session(speed=None, transport=None, connect=True, ignore_if_connected=True, disconnect_on_exit=True)

Scoped debug session. It connects on entry and guarantees teardown on exit. The safe flash → attach-RTT → reset ordering is therefore encoded once, rather than rediscovered in every script. The with target is the net itself, so the full surface (flash, rtt_defmt, reset, read_memory, …) is available inside the block.
Parameters: Returns: a context manager yielding the debug net. Why it pairs with RTT: the in-process RTT reader is reconnect-aware (see below). A flash() or reset() inside the session can bounce the GDB server. Such a bounce does not kill a log stream that you opened in the same block.

rtt(channel=0, search_addr=None, search_size=None, chunk_size=None)

Create an RTT (Real-Time Transfer) session for bidirectional communication with the target device.
Parameters: Returns: RTT context manager with methods:
  • read_some(timeout) - Read available data with timeout (returns bytes or None)
  • write(data) - Write data to target (accepts bytes or str)
Note: Debug connection must be active before using RTT. Call connect() first. Reconnect-aware (both backends): the RTT reader re-attaches by itself when the GDB server or daemon restarts under it.
  • J-Link. A flash() briefly frees the probe’s USB and restarts the GDB server on the same ports, which drops the RTT socket. A reset() does the same through its Commander grab. The reader re-attaches to the same RTT telnet port instead of going silent. A long-lived read_some() or rtt_defmt() loop therefore keeps producing across a flash.
  • OpenOCD. The daemon stays up across an ordinary flash, so the socket rarely drops. If it does drop, from a daemon force-restart or an rtt-server bounce, the reader re-runs rtt setup and rtt server start and re-attaches.
Reconnection is bounded on both backends, and the default is 30 s. The reader re-attaches only once the server or daemon is back up, and it never starts one. A flash that deliberately leaves the server down, such as on DA1469x, therefore cannot make the reader spin forever. The reader also cannot disturb a DA1469x that you left down on purpose. Pass reconnect=False for the legacy one-shot behavior.

Examples

Flash Firmware and Reset

Chip Erase Before Programming

Read Memory

For most use cases, the CLI provides a simpler interface:
See the CLI Debug Reference for full CLI documentation.

RTT Streaming

SEGGER Real-Time Transfer (RTT) enables high-speed bidirectional communication with embedded devices during debugging (faster than UART, no timing impact).
RTT Methods:
rtt().read_some() returns raw, still-encoded bytes. Firmware that logs with defmt (the de-facto standard for embedded Rust) emits a compressed binary format — calling .decode('utf-8') on it yields garbage. For defmt firmware, use rtt_defmt() below or the CLI pipe, both of which decode through defmt-print.

Decoding defmt logs with rtt_defmt()

rtt_defmt(elf, channel=0) opens an RTT session and pipes it through defmt-print (preinstalled on the Lager Box), yielding decoded log lines instead of raw bytes. The elf must be the exact firmware flashed on the target — defmt needs its symbol metadata to decode.
rtt_defmt() returns a context manager exposing: Like the CLI pipe, the RTT stream never ends on its own. Bound your read loop with a time budget or a line count, then exit the with block.

Driving the firmware while decoding its logs

write() makes a decoding session bi-directional, so a script can send a command and assert on the decoded response. Decoding is one-way — defmt-print only sees the up-channel — so writes bypass it and go straight to the target. This is the only way to do both at once. The RTT telnet port accepts a single client, so you cannot open a raw rtt() alongside a rtt_defmt().
This requires the firmware to declare an RTT down buffer on the channel you opened. defmt-rtt alone only sets up the up buffer. With no down buffer, the target silently discards whatever you write. That looks like a host-side failure, and it is not one.
Parameters: CLI Alternative: For interactive tailing, pipe the CLI directly: lager debug <net> gdbserver --box <box> --rtt 2>/dev/null | defmt-print -e build/app.elf. See the CLI Debug Reference. Use rtt_defmt() when you need to assert on log content inside a test script; use the pipe when you just want to watch logs.

Supported Devices

J-Link supports a wide range of ARM Cortex-M and other microcontrollers. Common device names: For a complete list, see SEGGER’s supported devices.

Supported Hardware

Notes

  • Debug nets must be configured with the target device name in the channel field
  • The CLI (lager debug) is recommended for most use cases
  • Python Net API is intended for advanced automation scripts running on the Lager Box
  • Always call disconnect() when finished to release the debug probe
  • Use erase() to perform a full chip erase and clear protection settings
  • RTT requires an active debug connection (see RTT Streaming section above)