From 7be6f770263b4a3bbaab32ab4aa7d88ca43089fa Mon Sep 17 00:00:00 2001 From: Romain Porte Date: Sat, 11 Jan 2020 12:34:49 +0100 Subject: initial version --- .gitignore | 3 + Cargo.toml | 9 +++ examples/all_methods.rs | 49 ++++++++++++ src/codes.rs | 157 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 419 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 examples/all_methods.rs create mode 100644 src/codes.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6936990 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2c6276c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "charlcd" +version = "0.1.0" +authors = ["Romain Porte "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/examples/all_methods.rs b/examples/all_methods.rs new file mode 100644 index 0000000..2052469 --- /dev/null +++ b/examples/all_methods.rs @@ -0,0 +1,49 @@ +use std::io::Write; + +use std::{thread, time}; + +use charlcd::Screen; + +macro_rules! test_method { + ($screen: ident, $method:ident) => { + $screen.clear()?; + $screen.flush()?; + $screen.write(stringify!($method).as_bytes())?; + $screen.write(b"..")?; + $screen.flush()?; + + thread::sleep(time::Duration::from_secs(2)); + + $screen.$method()?; + $screen.write(b"ok")?; + $screen.flush()?; + + thread::sleep(time::Duration::from_secs(2)); + }; +} + +fn main() -> std::io::Result<()> { + let mut screen = Screen::default()?; + + test_method!(screen, reinit); + test_method!(screen, display_off); + test_method!(screen, display_on); + test_method!(screen, backlight_on); + test_method!(screen, backlight_off); + test_method!(screen, cursor_off); + test_method!(screen, cursor_on); + test_method!(screen, blink_on); + test_method!(screen, blink_off); + test_method!(screen, shift_cursor_left); + test_method!(screen, shift_cursor_right); + test_method!(screen, shift_display_left); + test_method!(screen, kill_eol); + test_method!(screen, shift_display_right); + test_method!(screen, one_line); + test_method!(screen, two_lines); + + // reinit everything back after the test + test_method!(screen, reinit); + + Ok(()) +} 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 +where + W: Write, +{ + fn write_into(self, writer: &mut W) -> Result; +} + +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, Option), +} + +fn write_goto_xy_sym(writer: &mut T, sym: char, value: Option) -> Result { + 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(writer: &mut T, x: Option, y: Option) -> Result { + 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(writer: &mut T, char_index: u8, value: u64) -> Result { + 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 WriteInto for SpecialCode +where + W: Write, +{ + fn write_into(self, writer: &mut W) -> Result { + 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 { + writer: T, +} + +macro_rules! write_simple_code { + ($self:expr, $code:expr) => {{ + $code.write_into(&mut $self.writer)?; + Ok(()) + }}; +} + +impl Screen +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 { + 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. + /// + /// ![test](https://blog.microjoe.org/images/hd44780-lcd-i2c-screen-using-linux-mainline-charlcd-driver/linux_419.medium.jpg) + 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 Write for Screen +where + T: Write, +{ + fn write(&mut self, buf: &[u8]) -> Result { + 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>; + +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 { + 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 { + Screen::from_dev_path(&Path::new(DEFAULT_SCREEN_DEV_PATH)) + } +} -- cgit v1.2.3