diff --git a/src/api.rs b/src/api.rs index 90f584b..15042eb 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,4 @@ -use crate::display::EInkPanel; +use crate::display::{create_display_thread, EInkPanel}; use crate::dither::{DitherMethod, DitheredImage}; use crate::eink::Palette; use axum::async_trait; @@ -10,10 +10,10 @@ use image::{ImageReader, RgbImage}; use std::io::Cursor; use std::str; use std::str::FromStr; +use std::sync::mpsc; use std::sync::Arc; +use std::thread::JoinHandle; use std::time::Duration; -use tokio::sync::mpsc::{self, Receiver, Sender}; -use tokio::task::JoinHandle; use tracing::{error, info, instrument}; #[derive(thiserror::Error, Debug)] @@ -24,18 +24,17 @@ pub enum ApiError { #[derive(Clone)] pub struct AppState { - display_channel: Sender, + display_channel: mpsc::Sender>, display_task: Arc>, } impl AppState { #[must_use] pub fn new(disp: Box) -> Self { - let (tx, rx) = mpsc::channel(2); - let task = tokio::spawn(display_task(rx, disp)); + let (handle, tx) = create_display_thread(disp); Self { display_channel: tx, - display_task: Arc::new(task), + display_task: Arc::new(handle), } } } @@ -65,25 +64,6 @@ where } } -#[derive(Debug)] -pub struct DisplaySetCommand { - img: Box, -} - -#[instrument(skip_all)] -pub async fn display_task( - mut rx: Receiver, - mut display: Box, -) { - while let Some(cmd) = rx.recv().await { - info!("Got a display set command"); - if let Err(e) = display.display(&cmd.img) { - error!("Error displaying command {e}"); - } - info!("Done setting display"); - } -} - /// API routes for axum /// Start with the basics: Send an image, crop it, dither, and upload. /// we defer the upload to a separate task. @@ -163,10 +143,7 @@ async fn set_image( let mut dither = img_req.dither_method.get_ditherer(); dither.dither(&img_req.image, &mut buf); } - let cmd = DisplaySetCommand { img: Box::new(buf) }; - ctx.display_channel - .send_timeout(cmd, Duration::from_secs(10)) - .await?; + ctx.display_channel.send(Box::new(buf))?; Ok(StatusCode::OK) } diff --git a/src/display.rs b/src/display.rs index 79121f6..e8e0dc5 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,14 +1,16 @@ +// Manages display hardware +use crate::dither::DitheredImage; +use anyhow::Result; use epd_waveshare::{epd7in3f::Epd7in3f, prelude::WaveshareDisplay}; +use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; use linux_embedded_hal::spidev::SpiModeFlags; use linux_embedded_hal::spidev::SpidevOptions; use linux_embedded_hal::{CdevPin, Delay, SpidevDevice}; +use std::sync::mpsc; +use std::thread; use tracing::instrument; -use tracing::{debug, warn}; - -use anyhow::Result; -use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; - -use crate::dither::DitheredImage; +use tracing::span; +use tracing::{debug, error, info, warn, Level}; pub trait EInkPanel { fn display(&mut self, buf: &DitheredImage) -> Result<()>; @@ -88,3 +90,34 @@ impl EInkPanel for FakeEInk { 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, +) -> (thread::JoinHandle<()>, mpsc::Sender>) { + let (tx, rx) = mpsc::channel::>(); + 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) +}