mod display_handler;
mod output;
mod parse;
use anyhow::Context;
use display_handler::DisplayHandler;
use log::{error, info};
use std::sync::Mutex;
use stm32cubeprogrammer::probe;
#[derive(Default)]
enum ConnectionState<'a> {
#[default]
Disconnected,
Connected(Option<stm32cubeprogrammer::ConnectedProgrammer<'a>>),
ConnectedFus(Option<stm32cubeprogrammer::ConnectedFusProgrammer<'a>>),
}
struct ProgrammerConnection<'a> {
api: &'a stm32cubeprogrammer::CubeProgrammer,
connection_state: ConnectionState<'a>,
probe_serial: &'a stm32cubeprogrammer::probe::Serial,
probe_connection_parameters: stm32cubeprogrammer::probe::ConnectionParameters,
protocol: stm32cubeprogrammer::probe::Protocol,
}
impl<'a> ProgrammerConnection<'a> {
fn new(
api: &'a stm32cubeprogrammer::CubeProgrammer,
probe_serial: &'a stm32cubeprogrammer::probe::Serial,
protocol: stm32cubeprogrammer::probe::Protocol,
) -> Self {
Self {
api,
connection_state: ConnectionState::Disconnected,
probe_serial,
probe_connection_parameters: stm32cubeprogrammer::probe::ConnectionParameters::default(
),
protocol,
}
}
fn connection(&mut self) -> Result<&stm32cubeprogrammer::ConnectedProgrammer, anyhow::Error> {
match &mut self.connection_state {
ConnectionState::Disconnected => {
let programmer = self
.api
.connect_to_target(
self.probe_serial,
&self.protocol,
&self.probe_connection_parameters,
)
.with_context(|| "Failed to connect to target")?;
self.connection_state = ConnectionState::Connected(Some(programmer));
}
ConnectionState::ConnectedFus(connected_fus_cube_programmer) => {
let inner = connected_fus_cube_programmer.take().unwrap();
inner.disconnect();
let programmer = self
.api
.connect_to_target(
self.probe_serial,
&self.protocol,
&self.probe_connection_parameters,
)
.with_context(|| "Failed to connect to target")?;
self.connection_state = ConnectionState::Connected(Some(programmer));
}
_ => {}
}
match &self.connection_state {
ConnectionState::Connected(programmer) => Ok(programmer.as_ref().unwrap()),
_ => unreachable!(),
}
}
fn fus_connection(
&mut self,
) -> Result<&stm32cubeprogrammer::ConnectedFusProgrammer, anyhow::Error> {
match &mut self.connection_state {
ConnectionState::Disconnected => {
let programmer = self
.api
.connect_to_target_fus(self.probe_serial, &probe::Protocol::Swd)
.with_context(|| "Failed to connect to fus target")?;
self.connection_state = ConnectionState::ConnectedFus(Some(programmer));
}
ConnectionState::Connected(connected_programmer) => {
let inner = connected_programmer.take().unwrap();
inner.disconnect();
let programmer = self
.api
.connect_to_target_fus(self.probe_serial, &probe::Protocol::Swd)
.with_context(|| "Failed to connect to target")?;
self.connection_state = ConnectionState::ConnectedFus(Some(programmer));
}
_ => {}
}
match &self.connection_state {
ConnectionState::ConnectedFus(programmer) => Ok(programmer.as_ref().unwrap()),
_ => unreachable!(),
}
}
}
fn select_probe<'a>(
probes: &'a [stm32cubeprogrammer::probe::Serial],
probe_serial: &'a Option<stm32cubeprogrammer::probe::Serial>,
) -> Result<&'a stm32cubeprogrammer::probe::Serial, anyhow::Error> {
if let Some(serial) = probe_serial {
probes.iter().find(|probe| *probe == serial).ok_or_else(|| {
error!("No ST-Link probe found with serial number: {}", serial);
anyhow::anyhow!("No ST-Link probe found with serial number: {}", serial)
})
} else {
log::info!("No probe serial provided. Using the first connected probe");
Ok(&probes[0])
}
}
fn init_display_handler(verbosity: log::LevelFilter) -> std::sync::Arc<Mutex<DisplayHandler>> {
let logger = env_logger::Builder::new()
.filter_level(verbosity)
.filter_module("stm32cubeprogrammer::api_log", log::LevelFilter::Off) .build();
std::sync::Arc::new(Mutex::new(DisplayHandler::new(logger)))
}
fn main_inner() -> Result<crate::output::Output, anyhow::Error> {
let options = parse::options().run();
let mut cli_output =
output::Output::new(std::env::args_os(), &options.stm32_cube_programmer_dir);
let verbosity = if options.quiet {
log::LevelFilter::Error
} else {
options.verbose
};
let display_handler = init_display_handler(verbosity);
let api = stm32cubeprogrammer::CubeProgrammer::builder()
.cube_programmer_dir(&options.stm32_cube_programmer_dir)
.display_callback(display_handler.clone())
.build()
.with_context(|| "Failed to create CubeProgrammer API instance")?;
let probes = api
.list_available_probes()
.with_context(|| "Failed to list available probes")?;
cli_output.add_probe_list(&probes);
if options.list_probes {
if probes.is_empty() {
info!("No ST-Link probes found");
} else {
for probe in &probes {
info!("{}", probe);
}
}
return Ok(cli_output);
}
if probes.is_empty() {
error!("No ST-Link probes found");
return Err(anyhow::anyhow!("No ST-Link probes found"));
}
let selected_probe = select_probe(&probes, &options.probe_serial)?;
cli_output.add_selected_probe(selected_probe);
let mut programmer_connection =
ProgrammerConnection::new(&api, selected_probe, options.protocol.into());
cli_output.add_general_information(
programmer_connection
.connection()
.map_err(|x| {
error!("Failed to connect to target: {:?}", x);
x
})?
.general_information(),
);
if options
.target_commands
.iter()
.any(|command| matches!(command, parse::TargetCommand::BleUpdate(_)))
&& !programmer_connection
.connection()?
.general_information()
.fus_support
{
error!("The target does not support FUS commands");
return Err(anyhow::anyhow!("The target does not support FUS commands"));
}
for command in options.target_commands {
let command_output = match command {
parse::TargetCommand::FlashBin(bin_file_info) => {
log::info!("Flash binary file: {}", bin_file_info);
display_handler
.lock()
.unwrap()
.set_message("Flashing binary file");
programmer_connection
.connection()?
.download_bin_file(&bin_file_info.file, bin_file_info.address.0, false, true)
.with_context(|| "Failed to flash binary file")?;
output::CommandOutput::FlashBin {
file: bin_file_info.file,
address: bin_file_info.address.0,
}
}
parse::TargetCommand::FlashHex { file } => {
log::info!("Flash hex file: `{:?}`", file);
display_handler
.lock()
.unwrap()
.set_message("Flashing hex file");
programmer_connection
.connection()?
.download_hex_file(&file, false, true)
.with_context(|| "Failed to flash hex file")?;
output::CommandOutput::FlashHex { file }
}
parse::TargetCommand::BleUpdate(ble_stack_info) => {
log::info!("Update BLE stack: {}", ble_stack_info);
display_handler
.lock()
.unwrap()
.set_message("Updating BLE stack");
let fus_programmer = programmer_connection.fus_connection()?;
let flash_reason = if ble_stack_info.force {
Some(output::BleStackUpdateReason::Force)
} else if let Some(stack_version) = ble_stack_info.version {
if fus_programmer.fus_info().wireless_stack_version == stack_version {
None
} else {
Some(output::BleStackUpdateReason::VersionNotEqual {
expected: stack_version,
on_target: fus_programmer.fus_info().wireless_stack_version,
})
}
} else {
Some(output::BleStackUpdateReason::NoVersionProvided)
};
if let Some(ref flash_reason) = flash_reason {
info!("Updating BLE stack: {:?}", flash_reason);
fus_programmer
.upgrade_wireless_stack(
&ble_stack_info.file,
ble_stack_info.address.0,
false,
true,
true,
)
.with_context(|| "Failed to update BLE stack")?;
} else {
info!("BLE stack is up to date");
fus_programmer
.start_wireless_stack()
.with_context(|| "Failed to start BLE stack")?;
}
output::CommandOutput::UpdateBleStack {
file: ble_stack_info.file,
address: ble_stack_info.address.0,
ble_stack_updated: flash_reason
.map(output::BleStackUpdated::Updated)
.unwrap_or(output::BleStackUpdated::NotUpdated),
}
}
parse::TargetCommand::BleInfo { compare } => {
display_handler
.lock()
.unwrap()
.set_message("Get BLE stack information");
let fus_programmer = programmer_connection.fus_connection()?;
let target_version = fus_programmer.fus_info().wireless_stack_version;
log::info!("FUS info: {}", fus_programmer.fus_info());
if let Some(compare) = compare {
log::info!("Comparing BLE stack versions. Given version: {} ; Version on target: {} ; Stack up to date: {}", compare, target_version, compare == target_version);
}
fus_programmer
.start_wireless_stack()
.with_context(|| "Failed to start BLE stack")?;
output::CommandOutput::BleStackInfo(*fus_programmer.fus_info())
}
parse::TargetCommand::Reset(reset_mode) => {
log::info!("Resetting target: {:?}", reset_mode);
display_handler
.lock()
.unwrap()
.set_message("Resetting target");
programmer_connection
.connection()?
.reset_target(reset_mode.clone().into())
.with_context(|| "Failed to reset target")?;
output::CommandOutput::Reset {
reset_mode: reset_mode.into(),
}
}
parse::TargetCommand::MassErase => {
log::info!("Mass erase");
display_handler
.lock()
.unwrap()
.set_message("Mass erasing target");
programmer_connection
.connection()?
.mass_erase()
.with_context(|| "Failed to mass erase target")?;
output::CommandOutput::MassErase
}
parse::TargetCommand::Protect => {
log::info!("Enable read protection");
display_handler
.lock()
.unwrap()
.set_message("Enabling read protection");
programmer_connection
.connection()?
.enable_read_out_protection()
.with_context(|| "Failed to enable read protection")?;
output::CommandOutput::Protect
}
parse::TargetCommand::Unprotect => {
log::info!("Disable read protection");
display_handler
.lock()
.unwrap()
.set_message("Disabling read protection");
programmer_connection
.connection()?
.disable_read_out_protection()
.with_context(|| "Failed to disable read protection")?;
output::CommandOutput::Unprotect
}
};
cli_output.add_command_output(command_output);
display_handler.lock().unwrap().set_finish();
}
Ok(cli_output)
}
fn main() -> Result<(), anyhow::Error> {
let output = main_inner()?;
println!("{}", serde_json::to_string_pretty(&output)?);
Ok(())
}