pi-frame-server/src/display.rs

124 lines
3.8 KiB
Rust
Raw Normal View History

// Manages display hardware
use crate::dither::DitheredImage;
use anyhow::Result;
2024-07-02 15:57:29 +00:00
use epd_waveshare::{epd7in3f::Epd7in3f, prelude::WaveshareDisplay};
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
2024-07-02 15:57:29 +00:00
use linux_embedded_hal::spidev::SpiModeFlags;
use linux_embedded_hal::spidev::SpidevOptions;
2024-07-03 23:33:04 +00:00
use linux_embedded_hal::{CdevPin, Delay, SpidevDevice};
use std::sync::mpsc;
use std::thread;
2024-07-28 04:01:57 +00:00
use tracing::instrument;
use tracing::span;
use tracing::{debug, error, info, warn, Level};
2024-07-29 17:51:51 +00:00
pub trait EInkPanel {
fn display(&mut self, buf: &DitheredImage) -> Result<()>;
}
2024-07-16 23:55:59 +00:00
pub struct Wrapper {
2024-07-02 15:57:29 +00:00
spi: SpidevDevice,
gpiochip: Chip,
delay: Delay,
2024-07-18 20:51:07 +00:00
panel: Epd7in3f<SpidevDevice, CdevPin, CdevPin, CdevPin, Delay>,
2024-07-02 15:57:29 +00:00
}
2024-07-16 23:55:59 +00:00
impl Wrapper {
pub fn new() -> Result<Self> {
2024-07-02 15:57:29 +00:00
let mut spi = SpidevDevice::open("/dev/spidev0.0")?;
let spi_options = SpidevOptions::new()
.bits_per_word(8)
.max_speed_hz(10_000_000)
.mode(SpiModeFlags::SPI_MODE_0)
.build();
spi.configure(&spi_options)?;
let mut gpiochip = Chip::new("/dev/gpiochip0")?;
let busy_pin = CdevPin::new(gpiochip.get_line(24)?.request(
LineRequestFlags::INPUT,
0,
"frametool",
)?)?;
let dc_pin = CdevPin::new(gpiochip.get_line(25)?.request(
LineRequestFlags::OUTPUT,
0,
"frametool",
)?)?;
let rst_pin = CdevPin::new(gpiochip.get_line(17)?.request(
LineRequestFlags::OUTPUT,
0,
"frametool",
)?)?;
let mut delay = Delay {};
let panel = Epd7in3f::new(&mut spi, busy_pin, dc_pin, rst_pin, &mut delay, None)?;
Ok(Self {
2024-07-02 15:57:29 +00:00
spi,
gpiochip,
delay,
2024-07-18 20:51:07 +00:00
panel,
2024-07-02 15:57:29 +00:00
})
}
pub fn test(&mut self) -> Result<()> {
self.panel.show_7block(&mut self.spi, &mut self.delay)?;
self.panel.sleep(&mut self.spi, &mut self.delay)?;
Ok(())
}
}
impl EInkPanel for Wrapper {
2024-07-28 04:01:57 +00:00
#[instrument(skip_all)]
fn display(&mut self, img: &DitheredImage) -> Result<()> {
2024-07-29 17:51:51 +00:00
let buf = img.into_display_buffer();
2024-07-18 20:51:07 +00:00
self.panel
2024-07-29 17:51:51 +00:00
.update_and_display_frame(&mut self.spi, &buf, &mut self.delay)?;
debug!("Finished updating frame");
2024-07-17 05:24:11 +00:00
self.panel.sleep(&mut self.spi, &mut self.delay)?;
debug!("Display entered sleep mode");
2024-07-17 05:24:11 +00:00
Ok(())
}
}
2024-07-29 17:51:51 +00:00
/// A Fake EInk display for testing purposes.
/// Saves the output as `display.bmp`
pub struct FakeEInk();
impl EInkPanel for FakeEInk {
fn display(&mut self, img: &DitheredImage) -> Result<()> {
2024-07-29 17:51:51 +00:00
warn!("Fake display was called: saving to display.bmp");
2024-07-30 15:45:29 +00:00
img.into_rgbimage().save("display.bmp")?;
2024-07-29 17:51:51 +00:00
2024-07-02 15:57:29 +00:00
Ok(())
}
}
/// Create a thread that can take dithered images and display them. This allows the display to be
/// used in an async context since updating the display can take ~30 seconds.
#[must_use]
pub fn create_display_thread(
mut display: Box<dyn EInkPanel + Send>,
) -> (thread::JoinHandle<()>, mpsc::Sender<Box<DitheredImage>>) {
let (tx, rx) = mpsc::channel::<Box<DitheredImage>>();
let handle = thread::spawn(move || {
let span = span!(Level::INFO, "display_thread");
let _enter = span.enter();
loop {
let res = rx.recv();
match res {
Ok(img) => {
info!("Received an image to display");
if let Err(e) = display.display(&img) {
error!("Error displaying image: {e}");
return;
}
info!("Successfully set display image");
}
Err(e) => {
error!("Error reading image from channel: {e}");
return;
}
}
}
});
(handle, tx)
}