summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRomain Porte <microjoe@microjoe.org>2020-01-11 12:34:49 +0100
committerRomain Porte <microjoe@microjoe.org>2020-02-08 12:26:23 +0100
commit7be6f770263b4a3bbaab32ab4aa7d88ca43089fa (patch)
tree05e915d5303b5744cb107c0904ce2ff3aa79d7b4
downloadcharlcd-7be6f770263b4a3bbaab32ab4aa7d88ca43089fa.tar.gz
charlcd-7be6f770263b4a3bbaab32ab4aa7d88ca43089fa.zip
initial version
-rw-r--r--.gitignore3
-rw-r--r--Cargo.toml9
-rw-r--r--examples/all_methods.rs49
-rw-r--r--src/codes.rs157
-rw-r--r--src/lib.rs201
5 files changed, 419 insertions, 0 deletions
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 <microjoe@microjoe.org>"]
+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<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.
+ ///
+ /// ![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<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))
+ }
+}