From a182179e56528e026634406da53c0462fa7bc4e2 Mon Sep 17 00:00:00 2001 From: arthomnix Date: Mon, 17 Feb 2025 18:17:19 +0000 Subject: [PATCH] eepy-serial-host: new crate eepytool: use eepy-serial-host eepy-launcher: handle serial MaybeRefresh --- .idea/epd_firmware.iml | 1 + Cargo.toml | 1 + eepy-launcher/src/serial.rs | 45 +++++---- eepy-serial-host/Cargo.toml | 9 ++ eepy-serial-host/src/image.rs | 16 ++++ eepy-serial-host/src/input.rs | 16 ++++ eepy-serial-host/src/lib.rs | 126 +++++++++++++++++++++++++ eepy-serial-host/src/program_upload.rs | 16 ++++ eepy-serial/Cargo.toml | 3 +- eepy-serial/src/lib.rs | 57 +++-------- eepytool/Cargo.toml | 5 +- eepytool/src/main.rs | 85 +++++++---------- 12 files changed, 264 insertions(+), 116 deletions(-) create mode 100644 eepy-serial-host/Cargo.toml create mode 100644 eepy-serial-host/src/image.rs create mode 100644 eepy-serial-host/src/input.rs create mode 100644 eepy-serial-host/src/lib.rs create mode 100644 eepy-serial-host/src/program_upload.rs diff --git a/.idea/epd_firmware.iml b/.idea/epd_firmware.iml index 99b4861..0021fd7 100644 --- a/.idea/epd_firmware.iml +++ b/.idea/epd_firmware.iml @@ -17,6 +17,7 @@ + diff --git a/Cargo.toml b/Cargo.toml index 16f7481..4795997 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "eepy", "eepy-sys", "eepy-serial", + "eepy-serial-host", "eepy-gui", "eepy-example-app", "elf2epb", diff --git a/eepy-launcher/src/serial.rs b/eepy-launcher/src/serial.rs index 5737654..ed15405 100644 --- a/eepy-launcher/src/serial.rs +++ b/eepy-launcher/src/serial.rs @@ -7,7 +7,7 @@ use eepy_serial::{Response, SerialCommand}; use eepy_sys::flash::erase_and_program; use eepy_sys::header::{slot, slot_ptr, Programs}; use eepy_sys::image::refresh; -use eepy_sys::{header, IMAGE_BYTES}; +use eepy_sys::{header, image, IMAGE_BYTES}; use eepy_sys::input::{next_event, set_touch_enabled}; use eepy_sys::misc::{debug, info, trace}; use eepy_sys::usb::UsbBus; @@ -20,6 +20,7 @@ enum SerialState { ReceivingImage { fast_refresh: bool, + maybe_refresh: bool, index: usize, }, @@ -96,35 +97,37 @@ pub(crate) extern "C" fn usb_handler() { } if HOST_APP.load(Ordering::Relaxed) { - match SerialCommand::try_from(cmd_buf[0]) { - Ok(SerialCommand::RefreshNormal) => *state = SerialState::ReceivingImage { fast_refresh: false, index: 0 }, - Ok(SerialCommand::RefreshFast) => *state = SerialState::ReceivingImage { fast_refresh: true, index: 0 }, - Ok(SerialCommand::ExitHostApp) => { + match SerialCommand::from_repr(cmd_buf[0]) { + Some(SerialCommand::RefreshNormal) => *state = SerialState::ReceivingImage { fast_refresh: false, maybe_refresh: false, index: 0 }, + Some(SerialCommand::RefreshFast) => *state = SerialState::ReceivingImage { fast_refresh: true, maybe_refresh: false, index: 0 }, + Some(SerialCommand::MaybeRefreshNormal) => *state = SerialState::ReceivingImage { fast_refresh: false, maybe_refresh: true, index: 0 }, + Some(SerialCommand::MaybeRefreshFast) => *state = SerialState::ReceivingImage { fast_refresh: true, maybe_refresh: true, index: 0 }, + Some(SerialCommand::ExitHostApp) => { set_touch_enabled(true); HOST_APP.store(false, Ordering::Relaxed); NEEDS_REFRESH.store(true, Ordering::Relaxed); write_all(serial, &[Response::Ack as u8]); }, - Ok(SerialCommand::NextEvent) => { + Some(SerialCommand::NextEvent) => { write_all(serial, &[Response::Ack as u8]); write_all(serial, &postcard::to_vec::<_, 32>(&next_event()).unwrap()); }, - Ok(SerialCommand::EnableTouch) => { + Some(SerialCommand::EnableTouch) => { set_touch_enabled(true); write_all(serial, &[Response::Ack as u8]); }, - Ok(SerialCommand::DisableTouch) => { + Some(SerialCommand::DisableTouch) => { set_touch_enabled(false); write_all(serial, &[Response::Ack as u8]); }, - Ok(SerialCommand::EnterHostApp | SerialCommand::GetProgramSlot | SerialCommand::UploadProgram) => { + Some(SerialCommand::EnterHostApp | SerialCommand::GetProgramSlot | SerialCommand::UploadProgram) => { write_all(serial, &[Response::IncorrectMode as u8]); } - Err(_) => write_all(serial, &[Response::UnknownCommand as u8]), + None => write_all(serial, &[Response::UnknownCommand as u8]), } } else { - match SerialCommand::try_from(cmd_buf[0]) { - Ok(SerialCommand::GetProgramSlot) => { + match SerialCommand::from_repr(cmd_buf[0]) { + Some(SerialCommand::GetProgramSlot) => { if let Some(slot) = best_slot() { write_all(serial, &[Response::Ack as u8, slot]); PROG_SLOT.store(slot, Ordering::Relaxed); @@ -132,7 +135,7 @@ pub(crate) extern "C" fn usb_handler() { write_all(serial, &[Response::ProgramSlotsFull as u8]); } }, - Ok(SerialCommand::UploadProgram) => { + Some(SerialCommand::UploadProgram) => { if PROG_SLOT.load(Ordering::Relaxed) == 0 { write_all(serial, &[Response::NoProgramSlot as u8]); } else { @@ -141,31 +144,37 @@ pub(crate) extern "C" fn usb_handler() { write_all(serial, &[Response::Ack as u8]); } }, - Ok(SerialCommand::EnterHostApp) => { + Some(SerialCommand::EnterHostApp) => { HOST_APP.store(true, Ordering::Relaxed); refresh(&[0u8; IMAGE_BYTES], false); set_touch_enabled(false); write_all(serial, &[Response::Ack as u8]); }, - Ok( + Some( SerialCommand::RefreshNormal | SerialCommand::RefreshFast + | SerialCommand::MaybeRefreshNormal + | SerialCommand::MaybeRefreshFast | SerialCommand::ExitHostApp | SerialCommand::NextEvent | SerialCommand::DisableTouch | SerialCommand::EnableTouch ) => write_all(serial, &[Response::IncorrectMode as u8]), - Err(_) => write_all(serial, &[Response::UnknownCommand as u8]), + None => write_all(serial, &[Response::UnknownCommand as u8]), } } } } - SerialState::ReceivingImage { fast_refresh, index } => { + SerialState::ReceivingImage { fast_refresh, maybe_refresh, index } => { if let Ok(count) = serial.read(&mut buf[*index..]) { *index += count; if *index == IMAGE_BYTES { - refresh(buf, *fast_refresh); + if *maybe_refresh { + image::maybe_refresh(buf, *fast_refresh); + } else { + image::refresh(buf, *fast_refresh); + } write_all(serial, &[Response::Ack as u8]); *state = SerialState::ReadyForCommand; } diff --git a/eepy-serial-host/Cargo.toml b/eepy-serial-host/Cargo.toml new file mode 100644 index 0000000..4dff859 --- /dev/null +++ b/eepy-serial-host/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "eepy-serial-host" +version = "0.1.0" +edition = "2021" + +[dependencies] +eepy-serial = { path = "../eepy-serial" } +serialport.workspace = true +postcard.workspace = true \ No newline at end of file diff --git a/eepy-serial-host/src/image.rs b/eepy-serial-host/src/image.rs new file mode 100644 index 0000000..9b83938 --- /dev/null +++ b/eepy-serial-host/src/image.rs @@ -0,0 +1,16 @@ +use eepy_serial::SerialCommand; +use crate::{Error, HostApp, Serial}; + +pub const IMAGE_BYTES: usize = (240 * 416) / 8; + +impl Serial { + pub fn refresh(&mut self, fast: bool, image: &[u8; IMAGE_BYTES]) -> Result<(), Error> { + let cmd = if fast { SerialCommand::RefreshFast } else { SerialCommand::RefreshNormal }; + self.write(cmd, image) + } + + pub fn maybe_refresh(&mut self, fast: bool, image: &[u8; IMAGE_BYTES]) -> Result<(), Error> { + let cmd = if fast { SerialCommand::MaybeRefreshFast } else { SerialCommand::MaybeRefreshNormal }; + self.write(cmd, image) + } +} \ No newline at end of file diff --git a/eepy-serial-host/src/input.rs b/eepy-serial-host/src/input.rs new file mode 100644 index 0000000..12438ab --- /dev/null +++ b/eepy-serial-host/src/input.rs @@ -0,0 +1,16 @@ +use eepy_serial::{Event, SerialCommand}; +use crate::{Error, HostApp, Serial}; + +impl Serial { + pub fn set_touch_enabled(&mut self, enabled: bool) -> Result<(), Error> { + let cmd = if enabled { SerialCommand::EnableTouch } else { SerialCommand::DisableTouch }; + self.write(cmd, &[]) + } + + pub fn next_event(&mut self) -> Result, Error> { + self.write(SerialCommand::NextEvent, &[])?; + let mut event_buf = [0u8; 32]; + self.port.read(&mut event_buf)?; + Ok(postcard::from_bytes(&event_buf)?) + } +} \ No newline at end of file diff --git a/eepy-serial-host/src/lib.rs b/eepy-serial-host/src/lib.rs new file mode 100644 index 0000000..bd2c92e --- /dev/null +++ b/eepy-serial-host/src/lib.rs @@ -0,0 +1,126 @@ +pub mod image; +pub mod input; +pub mod program_upload; + +use std::fmt::{Display, Formatter}; +use std::io; +use std::io::{Read, Write}; +use std::marker::PhantomData; +use std::time::Duration; +use serialport::SerialPort; +use eepy_serial::{Response, SerialCommand, SerialError}; + +#[derive(Debug)] +pub enum Error { + InvalidResponse, + DeserializeFailed(postcard::Error), + Serial(SerialError), + Io(io::Error), +} + +impl From for Error { + fn from(value: SerialError) -> Self { + Self::Serial(value) + } +} + +impl From for Error { + fn from(value: io::Error) -> Self { + Self::Io(value) + } +} + +impl From for Error { + fn from(value: postcard::Error) -> Self { + Self::DeserializeFailed(value) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidResponse => write!(f, "received invalid response"), + Self::DeserializeFailed(e) => e.fmt(f), + Self::Serial(e) => e.fmt(f), + Self::Io(e) => e.fmt(f), + } + } +} + +impl std::error::Error for Error {} + +pub trait SerialState {} +pub trait CanEnter: SerialState {} + +pub enum Unknown {} +impl SerialState for Unknown {} +impl CanEnter for Unknown {} +impl CanEnter for Unknown {} + +pub enum Normal {} +impl SerialState for Normal {} +impl CanEnter for Normal {} + +pub enum HostApp {} +impl SerialState for HostApp {} +impl CanEnter for HostApp {} + +pub struct Serial { + port: Box, + _marker: PhantomData, +} + +impl Serial { + pub(crate) fn write(&mut self, command: SerialCommand, data: &[u8]) -> Result<(), Error> { + self.port.write_all(&[command as u8])?; + self.port.write_all(data)?; + let mut response_buf = [0u8]; + self.port.read_exact(&mut response_buf)?; + Response::from_repr(response_buf[0]) + .map(|resp| Result::<(), SerialError>::from(resp).map_err(|e| Error::Serial(e))) + .ok_or(Error::InvalidResponse)? + } +} + +impl Serial { + pub fn new(port: &str) -> Result { + let port = serialport::new(port, 0) + .timeout(Duration::from_secs(60)) + .open()?; + + Ok(Self { + port, + _marker: PhantomData::default(), + }) + } +} + +impl> Serial { + pub fn normal(mut self) -> Result, Error> { + let res = self.write(SerialCommand::ExitHostApp, &[]); + match res { + Ok(()) | Err(Error::Serial(SerialError::IncorrectMode)) => { + Ok(Serial { + port: self.port, + _marker: PhantomData::default(), + }) + }, + Err(e) => Err(e), + } + } +} + +impl> Serial { + pub fn host_app(mut self) -> Result, Error> { + let res = self.write(SerialCommand::EnterHostApp, &[]); + match res { + Ok(()) | Err(Error::Serial(SerialError::IncorrectMode)) => { + Ok(Serial { + port: self.port, + _marker: PhantomData::default(), + }) + }, + Err(e) => Err(e), + } + } +} \ No newline at end of file diff --git a/eepy-serial-host/src/program_upload.rs b/eepy-serial-host/src/program_upload.rs new file mode 100644 index 0000000..e0ef1ad --- /dev/null +++ b/eepy-serial-host/src/program_upload.rs @@ -0,0 +1,16 @@ +use std::io::Read; +use eepy_serial::SerialCommand; +use crate::{Error, Normal, Serial}; + +impl Serial { + pub fn get_slot(&mut self) -> Result { + self.write(SerialCommand::GetProgramSlot, &[])?; + let mut slot_n = [0u8]; + self.port.read_exact(&mut slot_n)?; + Ok(slot_n[0]) + } + + pub fn upload(&mut self, program: &[u8]) -> Result<(), Error> { + self.write(SerialCommand::UploadProgram, &program) + } +} \ No newline at end of file diff --git a/eepy-serial/Cargo.toml b/eepy-serial/Cargo.toml index 79f7b42..587f3a2 100644 --- a/eepy-serial/Cargo.toml +++ b/eepy-serial/Cargo.toml @@ -4,4 +4,5 @@ version = "0.1.0" edition = "2021" [dependencies] -eepy-sys = { path = "../eepy-sys" } \ No newline at end of file +eepy-sys = { path = "../eepy-sys" } +strum.workspace = true \ No newline at end of file diff --git a/eepy-serial/src/lib.rs b/eepy-serial/src/lib.rs index 0b17d6c..d267fb7 100644 --- a/eepy-serial/src/lib.rs +++ b/eepy-serial/src/lib.rs @@ -5,60 +5,43 @@ pub use eepy_sys::input_common::Event; use core::fmt::{Display, Formatter}; #[repr(u8)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::FromRepr)] pub enum SerialCommand { /// Refresh the screen. Must be followed by exactly 12480 bytes of image data. RefreshNormal = 0, /// Fast refresh the screen. Must be followed by exactly 12480 bytes of image data. RefreshFast = 1, + MaybeRefreshNormal = 2, + MaybeRefreshFast = 3, + /// Enter Host App mode. In this mode, events will not be processed by the builtin UI and can /// instead be retrieved by host programs using the [SerialCommand::NextEvent] command. This /// mode should be used by all host programs writing images to the display. - EnterHostApp = 2, + EnterHostApp = 4, /// Exit Host App mode (see [SerialCommand::EnterHostApp]). - ExitHostApp = 3, + ExitHostApp = 5, /// Get the next event. Only works in Host App mode. - NextEvent = 4, + NextEvent = 6, /// Disable touch. Touch events will no longer be added to the event queue. Only works in Host /// App mode. - DisableTouch = 5, + DisableTouch = 7, /// Enable touch. - EnableTouch = 6, + EnableTouch = 8, /// Get the program slot that will be used to store the next uploaded program. This command is /// only available when not in Host App mode. - GetProgramSlot = 7, + GetProgramSlot = 9, /// Upload a program. The program will be stored in the slot returned by the previous /// GetProgramSlot call. The program uploaded must be linked correctly for the /// slot. Only available when not in Host App mode. - UploadProgram = 8, -} - -impl TryFrom for SerialCommand { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - x if x == SerialCommand::RefreshNormal as u8 => Ok(SerialCommand::RefreshNormal), - x if x == SerialCommand::RefreshFast as u8 => Ok(SerialCommand::RefreshFast), - x if x == SerialCommand::EnterHostApp as u8 => Ok(SerialCommand::EnterHostApp), - x if x == SerialCommand::ExitHostApp as u8 => Ok(SerialCommand::ExitHostApp), - x if x == SerialCommand::NextEvent as u8 => Ok(SerialCommand::NextEvent), - x if x == SerialCommand::DisableTouch as u8 => Ok(SerialCommand::DisableTouch), - x if x == SerialCommand::EnableTouch as u8 => Ok(SerialCommand::EnableTouch), - x if x == SerialCommand::GetProgramSlot as u8 => Ok(SerialCommand::GetProgramSlot), - x if x == SerialCommand::UploadProgram as u8 => Ok(SerialCommand::UploadProgram), - - _ => Err(()), - } - } + UploadProgram = 10, } #[repr(u8)] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, strum::FromRepr)] pub enum Response { UnknownCommand = 0x00, IncorrectMode = 0x01, @@ -91,22 +74,6 @@ impl Display for Response { } } -impl TryFrom for Response { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - x if x == Response::UnknownCommand as u8 => Ok(Response::UnknownCommand), - x if x == Response::IncorrectMode as u8 => Ok(Response::IncorrectMode), - x if x == Response::ProgramSlotsFull as u8 => Ok(Response::ProgramSlotsFull), - x if x == Response::NoProgramSlot as u8 => Ok(Response::NoProgramSlot), - x if x == Response::Ack as u8 => Ok(Response::Ack), - - _ => Err(()), - } - } -} - #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SerialError { UnknownCommand, diff --git a/eepytool/Cargo.toml b/eepytool/Cargo.toml index 2896360..8246fa5 100644 --- a/eepytool/Cargo.toml +++ b/eepytool/Cargo.toml @@ -4,9 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -eepy-serial = { path = "../eepy-serial" } -postcard.workspace = true +eepy-serial-host = { path = "../eepy-serial-host" } +color-eyre = "0.6.3" clap.workspace = true -serialport.workspace = true tar.workspace = true zstd.workspace = true \ No newline at end of file diff --git a/eepytool/src/main.rs b/eepytool/src/main.rs index f68a958..3d2cfee 100644 --- a/eepytool/src/main.rs +++ b/eepytool/src/main.rs @@ -1,11 +1,10 @@ use std::fs::File; -use std::io::{Read, Write}; +use std::io::Read; use std::path::PathBuf; -use std::time::Duration; use clap::{Parser, Subcommand}; -use serialport::SerialPort; use tar::Archive; -use eepy_serial::{Event, Response, SerialCommand, SerialError}; +use eepy_serial_host::{Normal, Serial}; +use eepy_serial_host::image::IMAGE_BYTES; #[derive(Parser, Debug)] #[command(version, about, long_about = None)] @@ -25,6 +24,8 @@ enum Subcommands { Refresh { #[arg(long, action)] fast: bool, + #[arg(long, action)] + maybe: bool, #[arg(short, long)] image: PathBuf, }, @@ -40,37 +41,19 @@ enum Subcommands { use Subcommands::*; -fn write(serial: &mut Box, command: SerialCommand, data: &[u8]) -> Result<(), SerialError> { - serial.write_all(&[command as u8]).unwrap(); - serial.write_all(data).unwrap(); - let mut response_buf = [0u8]; - serial.read_exact(&mut response_buf).unwrap(); - Response::try_from(response_buf[0]).unwrap().into() -} - -fn next_event(serial: &mut Box) -> Result, SerialError> { - write(serial, SerialCommand::NextEvent, &[])?; - let mut event_buf = [0u8; 32]; - serial.read(&mut event_buf).unwrap(); - Ok(postcard::from_bytes(&event_buf).unwrap()) -} - -fn upload_program(serial: &mut Box, path: PathBuf) -> Result<(), SerialError> { - write(serial, SerialCommand::GetProgramSlot, &[])?; - let mut slot_n = [0u8]; - serial.read_exact(&mut slot_n).unwrap(); - let slot_n = slot_n[0]; +fn upload_program(serial: &mut Serial, path: PathBuf) -> color_eyre::Result<()> { + let slot_n = serial.get_slot()?; - let file = File::open(path).unwrap(); - let zstd_reader = zstd::stream::read::Decoder::new(file).unwrap(); + let file = File::open(path)?; + let zstd_reader = zstd::stream::read::Decoder::new(file)?; let mut tar = Archive::new(zstd_reader); - for file in tar.entries().unwrap() { - let mut file = file.unwrap(); - if file.path().unwrap().to_str().unwrap().ends_with(&format!(".s{slot_n:02}.epb")) { - println!("Uploading {}", file.path().unwrap().to_str().unwrap()); + for file in tar.entries()? { + let mut file = file?; + if file.path()?.to_str().unwrap().ends_with(&format!(".s{slot_n:02}.epb")) { + println!("Uploading {}", file.path()?.to_str().unwrap()); let mut buf = vec![0u8; file.size() as usize]; - file.read_exact(&mut buf).unwrap(); - write(serial, SerialCommand::UploadProgram, &buf)?; + file.read_exact(&mut buf)?; + serial.upload(&buf)?; return Ok(()); } } @@ -78,26 +61,30 @@ fn upload_program(serial: &mut Box, path: PathBuf) -> Result<(), panic!("App package did not contain binary for slot {slot_n}"); } -fn main() { +fn main() -> color_eyre::Result<()> { let args = Args::parse(); - // Baud rate setting doesn't matter for pure USB serial so use 0 - let mut port = serialport::new(&args.serial_port, 0) - .timeout(Duration::from_secs(60)) - .open() - .expect(&format!("Failed to open serial port {}", args.serial_port)); + let port = Serial::new(&args.serial_port)?; match args.command { - EnterHostApp => write(&mut port, SerialCommand::EnterHostApp, &[]).unwrap(), - ExitHostApp => write(&mut port, SerialCommand::ExitHostApp, &[]).unwrap(), - Refresh { fast, image } => { - let data = std::fs::read(image).unwrap(); - let cmd = if fast { SerialCommand::RefreshFast } else { SerialCommand::RefreshNormal }; - write(&mut port, cmd, &data).unwrap(); + EnterHostApp => { port.host_app()?; }, + ExitHostApp => { port.normal()?; }, + Refresh { fast, maybe, image } => { + let mut buf = [0u8; IMAGE_BYTES]; + let mut file = File::open(image)?; + file.read_exact(&mut buf)?; + + if maybe { + port.host_app()?.maybe_refresh(fast, &buf)?; + } else { + port.host_app()?.refresh(fast, &buf)?; + } }, - DisableTouch => write(&mut port, SerialCommand::DisableTouch, &[]).unwrap(), - EnableTouch => write(&mut port, SerialCommand::EnableTouch, &[]).unwrap(), - NextEvent => println!("{:?}", next_event(&mut port).unwrap()), - UploadProgram { package } => upload_program(&mut port, package).unwrap(), - }; + DisableTouch => port.host_app()?.set_touch_enabled(false)?, + EnableTouch => port.host_app()?.set_touch_enabled(true)?, + NextEvent => println!("{:?}", port.host_app()?.next_event()?), + UploadProgram { package } => upload_program(&mut port.normal()?, package)?, + } + + Ok(()) } -- 2.51.2