use serialport::SerialPort;
use std::io::{Read, Write};
use std::str::FromStr;
use strum::{Display, EnumString};
#[derive(EnumString, Display, Debug, Clone, PartialEq, Eq)]
pub enum Command {
#[strum(to_string = "burn_erase_all 0x{address:08x}\r")]
EraseAll { address: u32 },
#[strum(to_string = "burn_verify 0x{address:08x} 0x{len:08x} 0x{crc:08x}\r")]
Verify { address: u32, len: u32, crc: u32 },
#[strum(to_string = "burn_erase 0x{address:08x} 0x{len:08x}\r")]
Erase { address: u32, len: u32 },
#[strum(to_string = "burn_erase_write 0x{address:08x} 0x{len:08x}\r")]
WriteAndErase { address: u32, len: u32 },
#[strum(to_string = "burn_write 0x{address:08x} 0x{len:08x}\r")]
Write { address: u32, len: u32 },
#[strum(to_string = "burn_read 0x{address:08x} 0x{len:08x}\r")]
Read { address: u32, len: u32 },
#[strum(to_string = "burn_reset\r")]
SoftReset,
#[strum(to_string = "burn_speed {baud} {delay}\r")]
SetBaud { baud: u32, delay: u32 },
}
#[derive(EnumString, Display, Debug, Clone, PartialEq, Eq)]
pub enum Response {
#[strum(serialize = "OK")]
Ok,
#[strum(serialize = "Fail")]
Fail,
#[strum(serialize = "RX_WAIT")]
RxWait,
}
pub const RESPONSE_STR_TABLE: [&str; 3] = ["OK", "Fail", "RX_WAIT"];
pub trait RamCommand {
fn command(&mut self, cmd: Command) -> Result<Response, std::io::Error>;
fn send_data(&mut self, data: &[u8]) -> Result<Response, std::io::Error>;
}
pub trait DownloadStub {
fn download_stub(&mut self) -> Result<(), std::io::Error>;
}
pub struct CommandConfig {
pub compat_mode: bool,
pub chunk_size: usize,
pub chunk_delay_ms: u64,
}
impl Default for CommandConfig {
fn default() -> Self {
Self {
compat_mode: false,
chunk_size: 256,
chunk_delay_ms: 10,
}
}
}
pub struct RamOps;
impl RamOps {
const DEFAULT_TIMEOUT_MS: u128 = 4000;
const ERASE_ALL_TIMEOUT_MS: u128 = 30 * 1000;
pub fn send_command_and_wait_response(
port: &mut Box<dyn SerialPort>,
cmd: Command,
memory_type: &str,
) -> Result<Response, std::io::Error> {
tracing::debug!("command: {:?}", cmd);
port.write_all(cmd.to_string().as_bytes())?;
port.flush()?;
let timeout = match cmd {
Command::EraseAll { .. } => Self::ERASE_ALL_TIMEOUT_MS,
_ => Self::DEFAULT_TIMEOUT_MS,
};
let timeout = if memory_type == "sd" {
timeout * 3
} else {
timeout
};
match cmd {
Command::SetBaud { .. } | Command::Read { .. } | Command::Erase { .. } => {
return Ok(Response::Ok);
}
_ => (),
}
Self::wait_for_response(port, timeout)
}
pub fn send_data_and_wait_response(
port: &mut Box<dyn SerialPort>,
data: &[u8],
config: &CommandConfig,
) -> Result<Response, std::io::Error> {
if !config.compat_mode {
port.write_all(data)?;
port.flush()?;
} else {
for chunk in data.chunks(config.chunk_size) {
port.write_all(chunk)?;
port.flush()?;
std::thread::sleep(std::time::Duration::from_millis(config.chunk_delay_ms));
}
}
Self::wait_for_response(port, Self::DEFAULT_TIMEOUT_MS)
}
fn wait_for_response(
port: &mut Box<dyn SerialPort>,
timeout_ms: u128,
) -> Result<Response, std::io::Error> {
let mut buffer = Vec::new();
let now = std::time::SystemTime::now();
loop {
let elapsed = now.elapsed().unwrap().as_millis();
if elapsed > timeout_ms {
tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "Timeout"));
}
let mut byte = [0];
let ret = port.read_exact(&mut byte);
if ret.is_err() {
continue;
}
buffer.push(byte[0]);
for response_str in RESPONSE_STR_TABLE.iter() {
let response_bytes = response_str.as_bytes();
let exists = buffer
.windows(response_bytes.len())
.any(|window| window == response_bytes);
if exists {
tracing::debug!("Response buffer: {:?}", String::from_utf8_lossy(&buffer));
return Response::from_str(response_str).map_err(|e| {
std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
});
}
}
}
}
pub fn wait_for_shell_prompt(
port: &mut Box<dyn SerialPort>,
prompt: &[u8],
retry_interval_ms: u64,
max_retries: u32,
) -> Result<(), std::io::Error> {
let mut buffer = Vec::new();
let mut now = std::time::SystemTime::now();
let mut retry_count = 0;
port.write_all(b"\r\n")?;
port.flush()?;
loop {
let elapsed = now.elapsed().unwrap().as_millis();
if elapsed > retry_interval_ms as u128 {
tracing::warn!(
"Wait for shell Failed, retry. buffer: {:?}",
String::from_utf8_lossy(&buffer)
);
port.clear(serialport::ClearBuffer::All)?;
tracing::debug!("Retrying to find shell prompt...");
std::thread::sleep(std::time::Duration::from_millis(100));
retry_count += 1;
now = std::time::SystemTime::now();
port.write_all(b"\r\n")?;
port.flush()?;
buffer.clear();
}
if retry_count > max_retries {
return Err(std::io::Error::new(std::io::ErrorKind::TimedOut, "Timeout"));
}
let mut byte = [0];
let ret = port.read_exact(&mut byte);
if ret.is_err() {
continue;
}
buffer.push(byte[0]);
if buffer.windows(prompt.len()).any(|window| window == prompt) {
break;
}
}
Ok(())
}
}