hpgl_xy/fwtool/src/main.rs
2023-09-23 13:15:09 +02:00

216 lines
6.3 KiB
Rust

use clap::{Parser, Subcommand};
use pbr::{ProgressBar, Units};
use punt::Operation;
use rusb::{self, DeviceHandle, UsbContext};
use std::{error::Error, fs::File, io::Read};
const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(500);
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// List connected devices
List,
/// Flash the adaptor
Flash {
#[arg()]
file: String,
},
/// Enter the bootloader
EnterBootloader,
/// Exit from the bootloader
ExitBootloader,
}
fn match_usb_device<T: UsbContext>(
device: rusb::Device<T>,
vendor_id: u16,
product_id: u16,
vendor_string: &str,
product_string: &str,
) -> rusb::Result<Option<rusb::DeviceHandle<T>>> {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() != vendor_id || device_desc.product_id() != product_id {
return Ok(None);
}
let device_handle = device.open()?;
// Choose first language (the punt bootloader only supports English anyway)
let language = device_handle.read_languages(TIMEOUT)?[0];
let v_string = device_handle.read_manufacturer_string(language, &device_desc, TIMEOUT)?;
let p_string = device_handle.read_product_string(language, &device_desc, TIMEOUT)?;
if v_string != vendor_string || p_string != product_string {
return Ok(None);
}
Ok(Some(device_handle))
}
fn enter_bootloader<T: UsbContext>(handle: rusb::DeviceHandle<T>) -> rusb::Result<()> {
match handle.write_control(
rusb::request_type(
rusb::Direction::Out,
rusb::RequestType::Vendor,
rusb::Recipient::Device,
),
0xb0,
0,
0,
&[0u8; 0],
std::time::Duration::ZERO,
) {
// We expect a communication failure here since this command immediately stops all USB
// communications
Err(rusb::Error::Io) => Ok(()),
_ => Err(rusb::Error::Io),
}
}
fn application_targets<T: rusb::UsbContext>(context: &T) -> rusb::Result<Vec<DeviceHandle<T>>> {
Ok(context
.devices()?
.iter()
.filter_map(|device| {
match_usb_device(device, 0x16c0, 0x05e1, "25120.org", "HPGL XY").ok()?
})
.collect())
}
fn bootloader_targets<T: rusb::UsbContext + punt::UsbContext>(
context: &T,
) -> rusb::Result<Vec<punt::TargetHandle<T>>> {
Ok(context
.devices()?
.iter()
.filter_map(|device| {
match_usb_device(device, 0x16c0, 0x05dc, "25120.org", "punt")
.ok()
.flatten()
.map(TryInto::try_into)?
.ok()
})
.collect())
}
fn main() -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
let context = rusb::Context::new()?;
match cli.command {
Commands::List => {
println!("Connected devices in application mode:");
for handle in application_targets(&context)? {
println!(
"{}",
handle.read_serial_number_string(
handle.read_languages(TIMEOUT)?[0],
&handle.device().device_descriptor()?,
TIMEOUT
)?
);
}
println!("Connected devices in bootloader mode:");
for handle in bootloader_targets(&context)? {
println!("{}", handle.serial());
}
}
Commands::EnterBootloader => {
for handle in application_targets(&context)? {
enter_bootloader(handle)?;
}
}
Commands::ExitBootloader => {
for mut handle in bootloader_targets(&context)? {
handle.exit_bootloader()?;
}
}
Commands::Flash { file } => {
let mut handles;
loop {
handles = bootloader_targets(&context)?;
if !handles.is_empty() {
break;
}
for handle in application_targets(&context)? {
enter_bootloader(handle)?;
}
}
for mut handle in handles {
println!("Flashing {}.", handle.serial());
let bootloader_info = handle.bootloader_info()?;
let start = bootloader_info.application_base;
let mut file = File::open(&file)?;
let mut buff = Vec::new();
file.read_to_end(&mut buff)?;
let erase = handle.erase_area(start, buff.len())?;
println!("Erasing {} pages.", erase.total());
let mut pb = ProgressBar::new(erase.total() as u64);
pb.show_speed = false;
pb.show_time_left = false;
for status in erase {
pb.set(status? as u64);
}
pb.finish();
let program = handle.program_at(buff.as_slice(), start)?;
println!("Flashing {} bytes.", program.total());
let mut pb = ProgressBar::new(program.total() as u64);
pb.set_units(Units::Bytes);
for status in program {
pb.set(status? as u64);
}
pb.finish();
match handle.verify(buff.as_slice(), start) {
Ok(()) => println!("CRC verification successful."),
Err(e) => {
println!("CRC verification error: {}", e);
return Err(e.into());
}
}
println!("Exiting bootloader.");
handle.exit_bootloader()?;
}
}
}
// let targets: Vec<_> = context
// .devices()?
// .iter()
// .filter_map(|device| {
// match_usb_device(device, 0x16c0, 0x05e1, "25120.org", "HPGL XY").ok()?
// })
// .collect();
// for mut handle in targets {
// println!("Found device: {}", handle.device().bus_number());
// // device_handle.claim_interface(0)?;
// println!("Entering Bootloader");
// enter_bootloader(handle)?;
// }
Ok(())
}