diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/codes.rs | 157 | ||||
| -rw-r--r-- | src/lib.rs | 201 | 
2 files changed, 358 insertions, 0 deletions
diff --git a/src/codes.rs b/src/codes.rs new file mode 100644 index 0000000..2111df7 --- /dev/null +++ b/src/codes.rs @@ -0,0 +1,157 @@ +use std::io::{Error, ErrorKind, Result, Write}; + +const ESCAPE_CODE: &[u8] = "\x1b[L".as_bytes(); +const GENERATOR_MAX_CHAR_INDEX: u8 = 7; + +macro_rules! write_char { +    ($writer:ident, $char:expr) => { +        $writer.write(&[$char as u8]) +    }; +} + +macro_rules! write_digit { +    ($writer:ident, $num:expr) => { +        $writer.write(&[$num + '0' as u8]) +    }; +} + +pub trait WriteInto<W> +where +    W: Write, +{ +    fn write_into(self, writer: &mut W) -> Result<usize>; +} + +pub enum SpecialCode { +    DisplayOn, +    DisplayOff, +    CursorOn, +    CursorOff, +    BlinkOn, +    BlinkOff, +    BacklightOn, +    BacklightOff, +    FlashBacklight, +    SmallFont, +    LargeFont, +    OneLine, +    TwoLines, +    ShiftCursorLeft, +    ShiftCursorRight, +    ShiftDisplayLeft, +    ShiftDisplayRight, +    KillEndOfLine, +    ReinitializeDisplay, +    Generator(u8, u64), +    GotoXY(Option<u32>, Option<u32>), +} + +fn write_goto_xy_sym<T: Write>(writer: &mut T, sym: char, value: Option<u32>) -> Result<usize> { +    let mut total = 0; +    if let Some(value) = value { +        total += write_char!(writer, sym)?; +        total += writer.write(value.to_string().as_bytes())?; +    } +    Ok(total) +} + +fn write_goto_xy<T: Write>(writer: &mut T, x: Option<u32>, y: Option<u32>) -> Result<usize> { +    let mut total = 0; +    total += write_goto_xy_sym(writer, 'x', x)?; +    total += write_goto_xy_sym(writer, 'y', y)?; +    total += write_char!(writer, ';')?; +    Ok(total) +} + +fn write_generator<T: Write>(writer: &mut T, char_index: u8, value: u64) -> Result<usize> { +    if char_index > GENERATOR_MAX_CHAR_INDEX { +        return Err(Error::new( +            ErrorKind::Other, +            format!( +                "char index cannot be greater than {}", +                GENERATOR_MAX_CHAR_INDEX +            ), +        )); +    } + +    let mut total = 0; +    total += write_char!(writer, 'G')?; +    total += write_digit!(writer, char_index)?; +    total += writer.write(format!("{:>016x}", value).as_bytes())?; +    total += write_char!(writer, ';')?; +    Ok(total) +} + +impl<W> WriteInto<W> for SpecialCode +where +    W: Write, +{ +    fn write_into(self, writer: &mut W) -> Result<usize> { +        let mut total = 0; + +        total += writer.write(ESCAPE_CODE)?; +        total += match self { +            SpecialCode::DisplayOn => write_char!(writer, 'D')?, +            SpecialCode::DisplayOff => write_char!(writer, 'd')?, +            SpecialCode::CursorOn => write_char!(writer, 'C')?, +            SpecialCode::CursorOff => write_char!(writer, 'c')?, +            SpecialCode::BlinkOn => write_char!(writer, 'B')?, +            SpecialCode::BlinkOff => write_char!(writer, 'b')?, +            SpecialCode::BacklightOn => write_char!(writer, '+')?, +            SpecialCode::BacklightOff => write_char!(writer, '-')?, +            SpecialCode::FlashBacklight => write_char!(writer, '*')?, +            SpecialCode::SmallFont => write_char!(writer, 'f')?, +            SpecialCode::LargeFont => write_char!(writer, 'F')?, +            SpecialCode::OneLine => write_char!(writer, 'n')?, +            SpecialCode::TwoLines => write_char!(writer, 'N')?, +            SpecialCode::ShiftCursorLeft => write_char!(writer, 'l')?, +            SpecialCode::ShiftCursorRight => write_char!(writer, 'r')?, +            SpecialCode::ShiftDisplayLeft => write_char!(writer, 'L')?, +            SpecialCode::ShiftDisplayRight => write_char!(writer, 'R')?, +            SpecialCode::KillEndOfLine => write_char!(writer, 'k')?, +            SpecialCode::ReinitializeDisplay => write_char!(writer, 'I')?, +            SpecialCode::GotoXY(x, y) => write_goto_xy(writer, x, y)?, +            SpecialCode::Generator(c, x) => write_generator(writer, c, x)?, +        }; + +        Ok(total) +    } +} + +#[cfg(test)] +mod tests { +    use super::*; + +    fn assert_code_eq(code: SpecialCode, expected: &str) { +        use std::io::Cursor; +        let mut buf = Cursor::new(Vec::new()); +        let count = code.write_into(&mut buf).unwrap(); +        let expected = expected.as_bytes(); + +        assert_eq!(count, expected.len()); +        assert_eq!(&buf.get_ref()[0..count], expected); +    } + +    #[test] +    fn simple_codes() { +        assert_code_eq(SpecialCode::DisplayOff.into(), "\x1b[Ld"); +        assert_code_eq(SpecialCode::DisplayOn.into(), "\x1b[LD"); +    } + +    #[test] +    fn special_code_goto_xy() { +        assert_code_eq(SpecialCode::GotoXY(None, None), "\x1b[L;"); +        assert_code_eq(SpecialCode::GotoXY(Some(42), None), "\x1b[Lx42;"); +        assert_code_eq(SpecialCode::GotoXY(None, Some(42)), "\x1b[Ly42;"); +        assert_code_eq(SpecialCode::GotoXY(Some(42), Some(32)), "\x1b[Lx42y32;"); +    } + +    #[test] +    fn special_code_gen() { +        assert_code_eq( +            SpecialCode::Generator(7, 0xdeadbeefdecacafe), +            "\x1b[LG7deadbeefdecacafe;", +        ); +        assert_code_eq(SpecialCode::Generator(7, 0xff), "\x1b[LG700000000000000ff;"); +    } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f849712 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,201 @@ +mod codes; + +use std::fs::{File, OpenOptions}; +use std::path::Path; + +use std::io::BufWriter; +use std::io::Result; +use std::io::Write; + +use codes::SpecialCode; +use codes::WriteInto; + +/// A screen that allows you to send commands to a charlcd driver (or whatever +/// that implements the `Write` trait). +/// +/// Nominal usage: +/// +/// ```rust +/// use charlcd::Screen; +/// +/// fn main() -> std::io::Result<()> { +///     let mut screen = Screen::default(); // will use "/dev/lcd" charlcd driver +/// +///     screen.clear()?; +///     screen.write(b"hello, world!")?; +///     screen.flash_backlight()?; +///     screen.flush()?; // send all the previous commands to the driver at once +/// } +/// ``` +pub struct Screen<T> { +    writer: T, +} + +macro_rules! write_simple_code { +    ($self:expr, $code:expr) => {{ +        $code.write_into(&mut $self.writer)?; +        Ok(()) +    }}; +} + +impl<T> Screen<T> +where +    T: Write, +{ +    /// Create a new display instance that will use the provided Writer under +    /// the hood to send commands. +    pub fn new(writer: T) -> Screen<T> { +        Screen { writer } +    } + +    /// Clean the rest of the current line, from cursor's position. +    pub fn kill_eol(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::KillEndOfLine) +    } + +    /// Reinitialize the display to its default values. +    pub fn reinit(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::ReinitializeDisplay) +    } + +    /// Enable the display text output. +    pub fn display_on(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::DisplayOn) +    } + +    /// Disable the display text output. +    pub fn display_off(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::DisplayOff) +    } + +    /// Enable the underscore cursor (independent of blinking cursor). +    pub fn cursor_on(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::CursorOn) +    } + +    /// Disable the underscore cursor (independent of blinking cursor). +    pub fn cursor_off(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::CursorOff) +    } + +    /// Enable the blinking cursor (independent of underscore cursor). +    pub fn blink_on(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::BlinkOn) +    } + +    /// Disable the blinking cursor (independent of underscore cursor). +    pub fn blink_off(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::BlinkOff) +    } + +    /// Enable the backlight. +    pub fn backlight_on(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::BacklightOn) +    } + +    /// Disable the backlight. +    pub fn backlight_off(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::BacklightOff) +    } + +    /// Flash the backlight during a small duration. +    pub fn flash_backlight(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::FlashBacklight) +    } + +    /// Clear the screen and return the cursor at original (0, 0) XY position. +    pub fn clear(&mut self) -> std::io::Result<()> { +        self.write(&[0x0c])?; // '\f' escape not defined in Rust +        Ok(()) +    } + +    /// Move the cursor back one character. +    pub fn back(&mut self) -> std::io::Result<()> { +        self.write(&[0x08])?; // '\b' escape not defined in Rust +        Ok(()) +    } + +    // Less-used (and some non-working?) methods below + +    /// Shift cursor left. +    pub fn shift_cursor_left(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::ShiftCursorLeft) +    } + +    /// Shift cursor right. +    pub fn shift_cursor_right(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::ShiftCursorRight) +    } + +    /// Shift display left. +    pub fn shift_display_left(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::ShiftDisplayLeft) +    } + +    /// Shift display right. +    pub fn shift_display_right(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::ShiftDisplayRight) +    } + +    /// Enable one line mode. +    /// +    ///  +    pub fn one_line(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::OneLine) +    } + +    /// Enable two lines mode. +    pub fn two_lines(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::TwoLines) +    } + +    /// Enable small font mode. +    /// +    /// Seems to have no effect on the screen given the tests with multiple +    /// screen variants. +    pub fn small_font(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::SmallFont) +    } + +    /// Enable big font mode. +    /// +    /// Seems to have no effect on the screen given the tests with multiple +    /// screen variants. +    pub fn large_font(&mut self) -> std::io::Result<()> { +        write_simple_code!(self, SpecialCode::LargeFont) +    } +} + +// Reimplement Write trait for Screen, so that user can call the write and +// flush methods of the inner writer. +impl<T> Write for Screen<T> +where +    T: Write, +{ +    fn write(&mut self, buf: &[u8]) -> Result<usize> { +        self.writer.write(buf) +    } +    fn flush(&mut self) -> Result<()> { +        self.writer.flush() +    } +} + +// Concrete screen based on a File, to write to the real charlcd driver (or to +// another file). +type FileScreen = Screen<BufWriter<File>>; + +const DEFAULT_SCREEN_DEV_PATH: &str = "/dev/lcd"; + +impl FileScreen { +    /// Create a Screen instance based on the passed path to the device. +    pub fn from_dev_path(path: &Path) -> std::io::Result<FileScreen> { +        let file = OpenOptions::new().write(true).open(path)?; +        let buf = BufWriter::new(file); +        Ok(Screen::new(buf)) +    } + +    /// Create a default Screen instance based on `"/dev/lcd"` path. +    pub fn default() -> std::io::Result<FileScreen> { +        Screen::from_dev_path(&Path::new(DEFAULT_SCREEN_DEV_PATH)) +    } +}  | 
