<!-- Source: https://medius.k4tech.net/library/update -->
# Update

_Replace either chip's firmware over the open connection_

Write a new image with [`stage_firmware`](/library/update.md#stage-firmware) and commit it with [`activate_firmware`](/library/update.md#activate-firmware); [`update_firmware`](/library/update.md#update-firmware) does both for one chip, and [`abort_update`](/library/update.md#abort-update) throws a transfer away. Read what each chip is running with [`firmware_info`](/library/requests.md#firmware-info). Nothing reboots into ROM download and no second port is involved; the wire is [`UPDATE`](/native/commands/update.md).

| Update a... | Write it | Write and commit it |
| --- | --- | --- |
| single chip | [`stage_firmware`](/library/update.md#stage-firmware) | [`update_firmware`](/library/update.md#update-firmware) |
| both chips | [`stage_firmware`](/library/update.md#stage-firmware) twice | then [`activate_firmware`](/library/update.md#activate-firmware) once |

```
  stage_firmware(Host, ..)   --> host image into the host chip's spare slot
  stage_firmware(Device, ..) --> device image into the device chip's spare slot
  activate_firmware()        --> commit both, host chip reboots first
```

#### EXAMPLE

```rust
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let host = std::fs::read("medius_host.bin")?;
let dev = std::fs::read("medius_device.bin")?;

// The host chip first: its image travels through the device chip.
device.stage_firmware(UpdateTarget::Host, &host, &mut |p| {
    println!("host {}%", p.percent());
})?;
device.stage_firmware(UpdateTarget::Device, &dev, &mut |p| {
    println!("device {}%", p.percent());
})?;
device.activate_firmware()?;
```

## stage_firmware

_Write an image into a chip's spare slot, without booting it_

```text
fn stage_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<u32>
```

_Blocks_

Blocks for the whole transfer, a few seconds per chip. The clone disconnects from the game PC for the duration and comes back afterwards. Waits for both chips to confirm the image they booted before it starts, because a chip on probation refuses to open another update and returns [`Error::Update`](/library/types/errors.md) with `ON_PROBATION`.

| Parameter | Type | Description |
| --- | --- | --- |
| `target` | [`UpdateTarget`](/library/types/enums.md#update-target) | Which chip to write: `Device` or `Host`. |
| `image` | `&[u8]` | The whole `.bin`. Larger than `slot_size` is refused with `TOO_BIG` before a byte is sent. |
| `progress` | `&mut dyn FnMut(`[`UpdateProgress`](/library/types/structs.md#update-progress)`)` | Called once per acknowledged window, not once per chunk. |

Returns the number of bytes the box wrote. A box on the single-app layout answers `NO_SLOT` and needs one flash over [ROM download](/native/flashing.md) first.

> **Note**
>
> A staged image is inert. Nothing boots it until [`activate_firmware`](/library/update.md#activate-firmware), so a power cut in between brings the running firmware back.

#### EXAMPLE

```rust
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let image = std::fs::read("medius_device.bin")?;

let written = device.stage_firmware(UpdateTarget::Device, &image, &mut |p| {
    print!("\r{}%", p.percent());          // one call per acknowledged window
})?;
println!("\nstaged {written} bytes, not yet booted");
```

## activate_firmware

_Commit every staged image and boot into it_

```text
fn activate_firmware(&self) -> Result<()>
```

_Blocks_

Commits every staged image and reboots into it, host chip first, then reconnects. Takes tens of seconds if the host chip is involved. With nothing staged it returns [`Error::Update`](/library/types/errors.md) with `NOTHING_STAGED`.

A chip that boots an image which cannot run is reverted by the bootloader without anyone asking, so a bad image costs a reboot rather than a box. See [rollback](/native/commands/update.md#rollback).

> **Warning**
>
> A refusal stops at the host chip and leaves the device image staged and armed, so the next call would commit it alone and put the two chips on different versions. Either retry the whole update or [`abort_update`](/library/update.md#abort-update) each staged target first.

#### EXAMPLE

```rust
device.activate_firmware()?;
let fw = device.firmware_info()?;
println!("now on ota_{} ({})", fw.device.slot, fw.device.state);
```

## abort_update

_Throw a staged or in-flight transfer away_

```text
fn abort_update(&self, target: UpdateTarget) -> Result<()>
```

_Blocks_

Drops whatever is staged or in flight for one target. The clone comes back without a reboot, and the running slot is untouched. A session left alone times out on the box after ten seconds, so this is a courtesy rather than a requirement.

| Parameter | Type | Description |
| --- | --- | --- |
| `target` | [`UpdateTarget`](/library/types/enums.md#update-target) | Which chip to clear: `Device` or `Host`. |

Sent while an [activate](/native/commands/update.md#activate) is waiting on the host chip, it abandons that wait and disarms both chips whichever target it names.

#### EXAMPLE

```rust
use medius::UpdateTarget;

if let Err(e) = device.activate_firmware() {
    device.abort_update(UpdateTarget::Host)?;    // disarm what the refusal left behind
    device.abort_update(UpdateTarget::Device)?;
    return Err(e);
}
```

## update_firmware

_Stage one image and activate it in a single call_

```text
fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>
```

_Blocks_

[`stage_firmware`](/library/update.md#stage-firmware) followed by [`activate_firmware`](/library/update.md#activate-firmware), for the one-chip case. Use the two calls separately to update both chips together. If the activate refuses, the staged image is cleared before the error is returned.

| Parameter | Type | Description |
| --- | --- | --- |
| `target` | [`UpdateTarget`](/library/types/enums.md#update-target) | Which chip to write: `Device` or `Host`. |
| `image` | `&[u8]` | The whole `.bin`. |
| `progress` | `&mut dyn FnMut(`[`UpdateProgress`](/library/types/structs.md#update-progress)`)` | Called once per acknowledged window. |

#### EXAMPLE

```rust
use medius::{Device, UpdateTarget};

let device = Device::find()?;
let image = std::fs::read("medius_device.bin")?;
device.update_firmware(UpdateTarget::Device, &image, &mut |p| {
    print!("\r{}%", p.percent());
})?;
```

## On AsyncDevice

_The same calls, awaitable_

```text
async fn stage_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<u32>
```

```text
async fn activate_firmware(&self) -> Result<()>
```

```text
async fn abort_update(&self, target: UpdateTarget) -> Result<()>
```

```text
async fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>
```

_Blocks_

```bash
cargo add medius --features async
```

Each one runs the synchronous transfer on its own thread and resolves when it finishes, so the crate stays runtime-agnostic. They are not cancellable: dropping the future does not stop a transfer the box has already begun.

#### EXAMPLE

```rust
use futures::executor::block_on;
use medius::{Device, UpdateTarget};

let device = Device::find()?.into_async();
let image = std::fs::read("medius_device.bin")?;
block_on(device.update_firmware(UpdateTarget::Device, &image, &mut |_| {}))?;
```
