make display thread dedicated, move to display.rs

display thread should be dedicated to not steal resources from tokio
This commit is contained in:
saji 2024-08-01 16:41:20 -05:00
parent 07bc399bbc
commit 0e3d324748
2 changed files with 46 additions and 36 deletions

View file

@ -1,4 +1,4 @@
use crate::display::EInkPanel; use crate::display::{create_display_thread, EInkPanel};
use crate::dither::{DitherMethod, DitheredImage}; use crate::dither::{DitherMethod, DitheredImage};
use crate::eink::Palette; use crate::eink::Palette;
use axum::async_trait; use axum::async_trait;
@ -10,10 +10,10 @@ use image::{ImageReader, RgbImage};
use std::io::Cursor; use std::io::Cursor;
use std::str; use std::str;
use std::str::FromStr; use std::str::FromStr;
use std::sync::mpsc;
use std::sync::Arc; use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration; use std::time::Duration;
use tokio::sync::mpsc::{self, Receiver, Sender};
use tokio::task::JoinHandle;
use tracing::{error, info, instrument}; use tracing::{error, info, instrument};
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -24,18 +24,17 @@ pub enum ApiError {
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
display_channel: Sender<DisplaySetCommand>, display_channel: mpsc::Sender<Box<DitheredImage>>,
display_task: Arc<JoinHandle<()>>, display_task: Arc<JoinHandle<()>>,
} }
impl AppState { impl AppState {
#[must_use] #[must_use]
pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self { pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
let (tx, rx) = mpsc::channel(2); let (handle, tx) = create_display_thread(disp);
let task = tokio::spawn(display_task(rx, disp));
Self { Self {
display_channel: tx, 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<DitheredImage>,
}
#[instrument(skip_all)]
pub async fn display_task(
mut rx: Receiver<DisplaySetCommand>,
mut display: Box<dyn EInkPanel + Send>,
) {
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 /// API routes for axum
/// Start with the basics: Send an image, crop it, dither, and upload. /// Start with the basics: Send an image, crop it, dither, and upload.
/// we defer the upload to a separate task. /// 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(); let mut dither = img_req.dither_method.get_ditherer();
dither.dither(&img_req.image, &mut buf); dither.dither(&img_req.image, &mut buf);
} }
let cmd = DisplaySetCommand { img: Box::new(buf) }; ctx.display_channel.send(Box::new(buf))?;
ctx.display_channel
.send_timeout(cmd, Duration::from_secs(10))
.await?;
Ok(StatusCode::OK) Ok(StatusCode::OK)
} }

View file

@ -1,14 +1,16 @@
// Manages display hardware
use crate::dither::DitheredImage;
use anyhow::Result;
use epd_waveshare::{epd7in3f::Epd7in3f, prelude::WaveshareDisplay}; 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::SpiModeFlags;
use linux_embedded_hal::spidev::SpidevOptions; use linux_embedded_hal::spidev::SpidevOptions;
use linux_embedded_hal::{CdevPin, Delay, SpidevDevice}; use linux_embedded_hal::{CdevPin, Delay, SpidevDevice};
use std::sync::mpsc;
use std::thread;
use tracing::instrument; use tracing::instrument;
use tracing::{debug, warn}; use tracing::span;
use tracing::{debug, error, info, warn, Level};
use anyhow::Result;
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
use crate::dither::DitheredImage;
pub trait EInkPanel { pub trait EInkPanel {
fn display(&mut self, buf: &DitheredImage) -> Result<()>; fn display(&mut self, buf: &DitheredImage) -> Result<()>;
@ -88,3 +90,34 @@ impl EInkPanel for FakeEInk {
Ok(()) 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)
}