> For the complete documentation index, see [llms.txt](https://docs.anthriq.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.anthriq.com/bxi-studio/developer/overview/features/firmware.md).

# Update firmware

> **Applies to:** [Instinct](/bxi-studio/developer/overview/devices/instinct.md) and [xSys](/bxi-studio/developer/overview/devices/xsys.md). Not available on [xBud](/bxi-studio/developer/overview/devices/xbud.md).

The `firmware` feature performs over-the-air updates and manages boot partitions. It is declared by `anthriq-instinct` and not by xBud.

| Operation         | Purpose                              |
| ----------------- | ------------------------------------ |
| `get_partitions`  | List the device's boot partitions    |
| `boot_partition`  | Select the partition to boot from    |
| `start_update`    | Begin an over-the-air update         |
| `update_progress` | Subscription reporting update phases |

## Inspect the partitions

The device holds firmware in two partitions and boots from one of them. An update writes to the partition that is *not* running, so a failed write cannot brick a device that is currently working: the previous image is still intact and bootable.

That is why `start_update` accepts `target: "auto"` — it resolves to the inactive partition.

{% tabs %}
{% tab title="Node.js" %}

```typescript
const response = await client.features.executeOperation("firmware", "get_partitions", {});

if (response.success) {
  console.log(response.data);
}
```

{% endtab %}

{% tab title="Python" %}

```python
response = await client.execute_operation("firmware", "get_partitions", {})

if response.success:
    print(response.data)
```

{% endtab %}
{% endtabs %}

## Subscribe before starting

Subscribe to `update_progress` **before** calling `start_update`. The update begins immediately and early phases are missed by a subscription attached afterwards.

{% tabs %}
{% tab title="Node.js" %}

```typescript
const phases = new Set<string>();
let finished = false;
let succeeded = false;

await client.features.subscribeToOperation("firmware", "update_progress", (update) => {
  const details = update?.data ?? {};
  const phase = details.phase ?? details.details?.phase;
  const percent = details.percent ?? details.details?.percent;
  const done = details.done ?? details.details?.done;

  if (phase) {
    phases.add(phase);
    console.log(`${phase} ${percent ?? ""}`);
  }

  if (done === "true") {
    finished = true;
    succeeded = (details.success ?? details.details?.success) === "true";
  }
}, {});
```

{% endtab %}

{% tab title="Python" %}

```python
phases = set()
finished = False
succeeded = False

def on_progress(update):
    global finished, succeeded
    details = update.data or {}
    inner = details.get("details", {})
    phase = details.get("phase") or inner.get("phase")
    percent = details.get("percent") or inner.get("percent")
    done = details.get("done") or inner.get("done")

    if phase:
        phases.add(phase)
        print(phase, percent or "")

    if done == "true":
        finished = True
        succeeded = (details.get("success") or inner.get("success")) == "true"

await client.subscribe_to_operation("firmware", "update_progress", on_progress, {})
```

{% endtab %}
{% endtabs %}

> **Note:** Progress fields arrive as **strings**, not numbers or booleans. Compare `done` and `success` against `"true"` rather than testing truthiness, since the string `"false"` is truthy in JavaScript.

## Start the update

{% tabs %}
{% tab title="Node.js" %}

```typescript
const result = await client.features.executeOperation("firmware", "start_update", {
  filePath: "/path/to/cerbex_firmware.bin",
  target: "auto",
});

if (!result.success) {
  throw new Error(`start_update failed: ${result.error?.message}`);
}
```

{% endtab %}

{% tab title="Python" %}

```python
result = await client.execute_operation("firmware", "start_update", {
    "filePath": "/path/to/cerbex_firmware.bin",
    "target": "auto",
})

if not result.success:
    raise RuntimeError(f"start_update failed: {result.error.message}")
```

{% endtab %}
{% endtabs %}

| Field      | Type     | Description                                           |
| ---------- | -------- | ----------------------------------------------------- |
| `filePath` | `string` | Path to the firmware image, readable by the bridge    |
| `target`   | `string` | Partition to write; `"auto"` selects the inactive one |

The path is resolved by the **bridge**, not by your process. A path that exists locally but not where the bridge runs fails at transfer.

`start_update` returns as soon as the update is accepted. Completion arrives through the subscription.

## Follow the phases

An update passes through six phases in order:

| Phase      | Meaning                                 |
| ---------- | --------------------------------------- |
| `boot`     | Preparing the bootloader                |
| `root`     | Establishing the update session         |
| `transfer` | Sending the image to the device         |
| `erase`    | Erasing the target partition            |
| `flash`    | Writing the image                       |
| `done`     | Finished; `success` reports the outcome |

Treat the update as complete only on `done`, and as successful only when `success` is `"true"`. Reaching `flash` is not a result.

{% tabs %}
{% tab title="Node.js" %}

```typescript
const deadline = Date.now() + 40000;
while (!finished && Date.now() < deadline) {
  await new Promise((resolve) => setTimeout(resolve, 200));
}

const required = ["boot", "root", "transfer", "erase", "flash", "done"];
const missing = required.filter((phase) => !phases.has(phase));

if (!finished) {
  throw new Error("Update did not report completion within the timeout");
}
if (missing.length > 0) {
  throw new Error(`Update skipped phases: ${missing.join(", ")}`);
}
if (!succeeded) {
  throw new Error("Update reported failure");
}
```

{% endtab %}

{% tab title="Python" %}

```python
import asyncio, time

deadline = time.monotonic() + 40
while not finished and time.monotonic() < deadline:
    await asyncio.sleep(0.2)

required = ["boot", "root", "transfer", "erase", "flash", "done"]
missing = [p for p in required if p not in phases]

if not finished:
    raise RuntimeError("Update did not report completion within the timeout")
if missing:
    raise RuntimeError(f"Update skipped phases: {', '.join(missing)}")
if not succeeded:
    raise RuntimeError("Update reported failure")
```

{% endtab %}
{% endtabs %}

Checking for missing phases catches an update that reported `done` without having written anything, which a success flag alone would not reveal.

> **Warning:** Do not disconnect the device or shut the client down during an update. Interrupting the transfer or flash phase can leave the target partition incomplete and require recovery through the other partition.

## Allow enough time

A full update takes several seconds against an emulator and longer against hardware over a real link. Size the wait against the image and the connection rather than the default request timeout, which governs only the `start_update` acknowledgement.

## Select the boot partition

{% tabs %}
{% tab title="Node.js" %}

```typescript
await client.features.executeOperation("firmware", "boot_partition", {
  target: "auto",
});
```

{% endtab %}

{% tab title="Python" %}

```python
await client.execute_operation("firmware", "boot_partition", {"target": "auto"})
```

{% endtab %}
{% endtabs %}

Re-query capabilities after an update that changes what the device supports:

{% tabs %}
{% tab title="Node.js" %}

```typescript
const capabilities = await client.getCapabilities();
```

{% endtab %}

{% tab title="Python" %}

```python
capabilities = await client.get_capabilities()
```

{% endtab %}
{% endtabs %}

See [Read device information](/bxi-studio/developer/overview/get-started/device-info.md).

## Next steps

* [Read device information](/bxi-studio/developer/overview/get-started/device-info.md)
* [Configure channels](/bxi-studio/developer/overview/features/channels.md)
