> ## Documentation Index
> Fetch the complete documentation index at: https://docs.embedder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hardware scripts

> Organize hardware probes into scripts, hold device leases, run background monitoring tasks, and preserve investigation evidence across sessions.

Embedder writes Python hardware automation under `.embedder/hardware/`. Run these files with `hardware_script_run` so provider helpers, bridge routing, validation, capture publishing, and hardware leases all apply.

Do not run a hardware script with plain Python or a shell command. Injected helpers will be missing, and the process will not participate in hardware coordination.

## Choose a script location

Use a loose script for one probe:

```text theme={"system"}
.embedder/hardware/uart_boot_check.py
```

Open a case as soon as the investigation needs a second script:

```text theme={"system"}
.embedder/hardware/cases/spi2-nack/
├── CASE.md
├── capture_bus.py
└── inspect_pinctrl.py
```

Case IDs use lowercase letters, digits, and hyphens. They start with a letter or digit and contain at most 64 characters.

Reuse or edit an existing script. Do not create numbered copies such as `capture2.py` or `capture_v2.py`; stale variants make it unclear which probe produced the evidence.

## Keep a durable case note

`CASE.md` stores one bug across context compaction and later sessions. Use this structure:

```markdown .embedder/hardware/cases/spi2-nack/CASE.md theme={"system"}
---
title: "SPI2 sensor returns intermittent NACK"
status: open
opened: "2026-08-21T22:50:00Z"
hardware: "STM32F407 / custom controller"
tags: [spi, sensor]
---

## Symptom

The sensor NACKs during the first transfer after wake.

## Hypotheses

| Hypothesis | Verdict | Evidence |
| --- | --- | --- |
| Chip select rises before the final clock | open | Capture pending |
| SPI2 clock is disabled after wake | open | Register read pending |

## Findings

No settled findings yet.

## Next probe

Capture chip select and clock while breaking in the SPI error callback.
```

Valid statuses are `open`, `resolved`, and `abandoned`. You may also add an `updated` timestamp. Do not delete a closed case; change its status and keep the evidence.

Update hypothesis verdicts and findings when a probe settles them. Keep the short in-session checklist in the task list instead of duplicating it in the note.

## Write against injected helpers

The script runner inspects the file and selects providers from the detected hardware and helper names it uses. Provider preludes inject helpers before your code runs.

Examples include:

* `gdb.*`, `serial_send`, `serial_read`, and `serial_read_history`
* `rtt_send`, `rtt_read`, and `rtt_read_history`
* `la_*`, `scope_*`, `ppk2_*`, and `joulescope_*`
* `ble_*`

Use the generic `la_*` interface for Saleae, Digilent, and PicoScope logic capture. Direct Digilent vendor helper calls are rejected.

Most provider libraries must not be imported directly. The J-Trace SDK is the exception: import `jtrace` in a script when you need its custom API.

## Return structured results

Print a final one-line JSON object with `success` and `summary`. Add `data` and `metadata` when they help the next step.

```python .embedder/hardware/read_boot.py theme={"system"}
import asyncio
import json

async def main():
    lines = await serial_read(
        "/dev/tty.usbmodem2101",
        timeout_seconds=10,
        baud_rate=115200,
        stop_string="READY",
    )
    print(json.dumps({
        "success": any("READY" in line for line in lines),
        "summary": f"Captured {len(lines)} boot lines",
        "data": {"lines": lines},
    }))

asyncio.run(main())
```

Wrap asynchronous UART or RTT reads in `asyncio.run`. The runner parses the last valid result object from stdout.

Provider publish helpers can add instrument captures to the Logic, Power, or Oscilloscope views. BLE helpers stream scan, GATT, and notification events to the Bluetooth view. The tool result lists each published instrument capture.

## Run tools on the hardware host

Call `run_tool` inside a hardware script when you need a flasher or debugger executable:

```python theme={"system"}
result = run_tool(["probe-rs", "list"])
```

It runs on the machine that owns the hardware, which is the paired bridge when one is active and the local host otherwise. A direct subprocess would run on the wrong machine in a bridge session.

The result includes `exit_code`, `output`, and `host`.

## Understand hardware leases

Every `hardware_script_run` requests exclusive access.

### Foreground lease

A foreground script holds the hardware for the tool operation. Other sessions wait until the script and provider cleanup finish.

The default timeout is 60 seconds. You can set a foreground timeout from 1 second through 10 minutes.

### Background lease

Set `run_in_background=true` for a soak, endurance test, or long capture:

```text theme={"system"}
Run cases/uart-drift/soak.py in the background and return the task ID. Keep the
board leased until the script exits.
```

The tool returns immediately with a task ID and output path. The Python process keeps the board leased to that session. The foreground `timeout` value is ignored; a 24-hour backstop prevents a dead script from holding the board forever.

Only one background hardware script may run in a session. A second script is refused until the first exits.

### Cross-session protection

The arbiter queues other sessions behind an active owner. It also prevents a session from running an unchanged script whose latest successful run belongs to another session. Rewrite it for your task or use another filename.

When a script programs firmware, Embedder records the load so other sessions know their previous hardware observations may be stale.

## Wait for or stop a background run

Use the task ID returned by `hardware_script_run`:

```text theme={"system"}
wait_task task_abc123
```

`wait_task` listens for completion. If its wait times out, the script keeps running and the lease remains held.

Stop a run with:

```text theme={"system"}
stop_task task_abc123
```

The runner sends SIGINT so Python cleanup and provider cleanup can release devices. Do not poll with sleep commands or start a duplicate run while you wait.

## Review validation before running

File writes return hardware-script diagnostics. Fix them before you execute the script.

Validation checks include:

* Unknown or vendor-internal helper names
* A provider action used without its connection helper
* Unsafe or unsupported argument combinations
* Direct imports that bypass provider setup

Runtime preparation can still ask you to install a Python package or managed instrument runtime. Approving one script does not approve every future script; permission is scoped by script name.

## Find existing scripts

The debug-session toolbox lists loose scripts, open cases, closed cases, and each script's leading docstring. `.embedder` is normally ignored by project search, so open a listed script by its exact path or list `.embedder/hardware/` directly.

Pass the path relative to `.embedder/hardware` as `script_name`:

```text theme={"system"}
cases/spi2-nack/capture_bus.py
```

## Fix script problems

<AccordionGroup>
  <Accordion title="The helper is undefined">
    Run the file with `hardware_script_run`, not Python or shell. Confirm that `hardware_status` reports the provider and that the script uses its documented helper name.
  </Accordion>

  <Accordion title="No provider is available">
    Refresh `hardware_status` and follow the provider's dependency or connection reason before rerunning.
  </Accordion>

  <Accordion title="The board is busy">
    Check active trace sessions and background tasks. Wait for the owner or stop your session's task; do not bypass the lease.
  </Accordion>

  <Accordion title="A bridge tool cannot see the probe">
    Replace subprocess use with `run_tool` so the command runs on the hardware host.
  </Accordion>

  <Accordion title="The script completed but no result was parsed">
    Print a final JSON object containing a boolean `success` and string `summary` on one line.
  </Accordion>
</AccordionGroup>

<CardGroup cols={2}>
  <Card title="Combined workflows" icon="diagram-project" href="/debug-mode/combined-workflows">
    Coordinate several observations in one script.
  </Card>

  <Card title="Debug mode overview" icon="bug" href="/debug-mode/overview">
    Choose a provider for the next probe.
  </Card>
</CardGroup>
