Update
Replace either chip's firmware over the open connectionWrite a new image with stage_firmware and commit it with activate_firmware; update_firmware does both for one chip, and abort_update throws a transfer away. Read what each chip is running with firmware_info. Nothing reboots into ROM download and no second port is involved; the wire is UPDATE.
| Update a... | Write it | Write and commit it |
|---|---|---|
| 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 into a chip's spare slot, without booting itfn 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 with ON_PROBATION.
| Parameter | Type | Description |
|---|---|---|
target | UpdateTarget | 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) | 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 first.
A staged image is inert. Nothing boots it until activate_firmware, so a power cut in between brings the running firmware back.
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 itfn 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 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.
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 each staged target first.
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 awayfn 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 | Which chip to clear: Device or Host. |
Sent while an activate is waiting on the host chip, it 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 one image and activate it in a single callfn update_firmware(&self, target: UpdateTarget, image: &[u8], progress: &mut dyn FnMut(UpdateProgress)) -> Result<()>
Blocks
stage_firmware followed by 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 | 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());
})?;On AsyncDevice
The same calls, awaitableasync 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<()>
Blocks
cargo add medius --features asyncEach 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.
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 |_| {}))?;