use crate::common::serial_io::SerialIo;
use crate::{Error, Result};
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>;
fn send_data(&mut self, data: &[u8]) -> Result<Response>;
fn format_command(&self, cmd: &Command) -> String {
cmd.to_string()
}
}
pub trait DownloadStub {
fn download_stub(&mut self) -> Result<()>;
}
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;
pub fn is_sd_memory(memory_type: &str) -> bool {
let memory_type = memory_type.to_ascii_lowercase();
memory_type == "sd" || memory_type.starts_with("sd_")
}
impl RamOps {
const DEFAULT_TIMEOUT_MS: u128 = 4000;
const ERASE_ALL_TIMEOUT_MS: u128 = 30 * 1000;
pub fn send_command_and_wait_response(
io: &mut SerialIo<'_>,
cmd: Command,
command_str: &str,
memory_type: &str,
) -> Result<Response> {
tracing::debug!("command: {:?}", cmd);
io.write_all(command_str.as_bytes())?;
io.flush()?;
let timeout = match cmd {
Command::EraseAll { .. } => Self::ERASE_ALL_TIMEOUT_MS,
_ => Self::DEFAULT_TIMEOUT_MS,
};
let timeout = if is_sd_memory(memory_type) {
timeout * 3
} else {
timeout
};
match cmd {
Command::SetBaud { .. } | Command::Read { .. } | Command::Erase { .. } => {
return Ok(Response::Ok);
}
_ => (),
}
Self::wait_for_response(io, timeout)
}
pub fn send_data_and_wait_response(
io: &mut SerialIo<'_>,
data: &[u8],
config: &CommandConfig,
) -> Result<Response> {
if !config.compat_mode {
io.write_all(data)?;
io.flush()?;
} else {
for chunk in data.chunks(config.chunk_size) {
io.write_all(chunk)?;
io.flush()?;
io.sleep(std::time::Duration::from_millis(config.chunk_delay_ms))?;
}
}
Self::wait_for_response(io, Self::DEFAULT_TIMEOUT_MS)
}
fn wait_for_response(io: &mut SerialIo<'_>, timeout_ms: u128) -> Result<Response> {
let matched = io.wait_for_patterns(
&RESPONSE_STR_TABLE.map(str::as_bytes),
std::time::Duration::from_millis(timeout_ms as u64),
"RAM command response",
)?;
tracing::debug!(
"Response buffer: {:?}",
String::from_utf8_lossy(&matched.buffer)
);
Response::from_str(RESPONSE_STR_TABLE[matched.index])
.map_err(|e| Error::invalid_input(e.to_string()))
}
pub fn wait_for_shell_prompt(
io: &mut SerialIo<'_>,
prompt: &[u8],
retry_interval_ms: u64,
max_retries: u32,
) -> Result<()> {
io.wait_for_prompt(
prompt,
std::time::Duration::from_millis(retry_interval_ms),
max_retries,
)
}
}