Update
stage_firmware writes an image and activate_firmware commits it. update_firmware does both for one chip. abort_update discards a transfer, and wait_firmware_confirmed waits out probation. All run over the open control link as UPDATE frames, with no ROM download reboot.
These calls need a box the library can open. A box it refuses, such as a 3.4.1 box on protocol 8 (Error::BadProtoVer), is updated from the dashboard's Update page.
| Target | Write | Write and commit |
|---|---|---|
| single chip | stage_firmware | update_firmware |
| both chips | stage_firmware twice | then 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
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 to a chip's spare slot
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 is disconnected from the game PC meanwhile. A chip on probation refuses another update, so this first waits for both chips to confirm their booted image. If they don't, it returns Error::Update with ON_PROBATION.
| Parameter | Type | Description |
|---|---|---|
target | UpdateTarget | Which chip to write: Device or Host. |
image | &[u8] | The whole .bin. An image larger than slot_size is refused with TOO_BIG before any byte is sent. |
progress | &mut dyn FnMut(UpdateProgress) | Called once per acknowledged window, not once per chunk. |
Returns the bytes the box wrote. A box on the single-app layout replies NO_SLOT and needs one ROM download flash first.
A staged image is inert until activate_firmware. A power cut in between boots the running firmware.
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
fn activate_firmware(&self) -> Result<()>
Blocks
Commits every staged image, reboots into it (host chip first), then reconnects. This takes tens of seconds when the host chip is involved. With nothing staged, it returns Error::Update with NOTHING_STAGED.
The bootloader reverts a chip whose image cannot run, so a bad image costs a reboot (rollback).
If the host chip refuses, the device image stays staged and armed. The next call would commit it alone and leave the chips on different versions. Retry the whole update, or abort_update each staged target first.
device.activate_firmware()?;
let fw = device.firmware_info()?;
println!("now on ota_{} ({})", fw.device.slot, fw.device.state);abort_update Discard a staged or in-flight transfer
fn abort_update(&self, target: UpdateTarget) -> Result<()>
Blocks
Drops what is staged or in flight for one target. The clone returns without a reboot, and the running slot is untouched. An abandoned session times out on the box after 10 s.
| Parameter | Type | Description |
|---|---|---|
target | UpdateTarget | Which chip to clear: Device or Host. |
If an activate is waiting on the host chip, abort_update abandons that wait and disarms both chips, whichever target it names.
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 and activate one image
fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>
Blocks
stage_firmware then activate_firmware, for one chip. To update both chips together, call the two separately. If the activate refuses, the staged image is cleared before the error returns.
| Parameter | Type | Description |
|---|---|---|
target | UpdateTarget | Which chip to write: Device or Host. |
image | &[u8] | The whole .bin. |
progress | &mut dyn FnMut(UpdateProgress) | Called once per acknowledged window. |
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());
})?;wait_firmware_confirmed Block until neither chip is on probation
fn wait_firmware_confirmed(&self) -> Result<FirmwareInfo>
Blocks
A chip on probation refuses another update, so stage_firmware waits on this first. The device chip confirms after 10 s of running. The host chip confirms on a clock exchange over the inter-chip link. This call keeps asking while the box reboots after an activate.
| After 55 s | Returns |
|---|---|
| A chip still on probation | Error::Update: op 0 (begin), ON_PROBATION |
| No reply from the box | Error::QueryTimeout |
device.update_firmware(UpdateTarget::Device, &image, &mut |_| {})?;
let fw = device.wait_firmware_confirmed()?;
println!("now on ota_{} ({})", fw.device.slot, fw.device.state);On AsyncDevice Same calls, awaitable
async fn stage_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<u32>
async fn activate_firmware(&self) -> Result<()>
async fn abort_update(&self, target: UpdateTarget) -> Result<()>
async fn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>
async fn wait_firmware_confirmed(&self) -> Result<FirmwareInfo>
Blocks
cargo add medius --features asyncEach runs the synchronous transfer on a separate thread and resolves when it finishes, so the crate stays runtime-agnostic. Dropping the future doesn't stop a transfer the box has begun.
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 |_| {}))?;