diff --git a/src/api.rs b/src/api.rs index d32be55..ecc8b3f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,12 +1,17 @@ +use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither}; use axum::extract::Multipart; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::{extract::State, response::Response, routing::post, Router}; -use image::ImageReader; +use image::{ImageReader, RgbImage}; use std::io::Cursor; +use std::time::Duration; +use tracing::{error, info, debug, instrument}; use crate::display::EInkPanel; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio::task::JoinHandle; pub enum ImageFormFields { DitherType, @@ -15,17 +20,42 @@ pub enum ImageFormFields { #[derive(Clone)] pub struct Context { - display: Arc>>, + display_channel: Sender, + display_task: Arc>, } + impl Context { #[must_use] - pub fn new(w: Box) -> Self { + pub fn new(disp: Box) -> Self { + let (tx, rx) = mpsc::channel(2); + let task = tokio::spawn(display_task(rx, disp)); Self { - display: Arc::new(Mutex::new(w)), + display_channel: tx, + display_task: Arc::new(task), } } } + +#[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"); + let raw_buf = cmd.img.into_display_buffer(); + if let Err(e) = display.display(&raw_buf) { + error!("Error displaying command {e}"); + } + info!("Done setting display"); + } +} + // Make our own error that wraps `anyhow::Error`. struct AppError(anyhow::Error); @@ -58,6 +88,7 @@ pub fn router() -> Router { Router::new().route("/setimage", post(set_image)) } +#[instrument(skip(ctx))] async fn set_image( State(ctx): State, mut parts: Multipart, @@ -65,13 +96,21 @@ async fn set_image( while let Some(field) = parts.next_field().await? { let name = field.name().expect("fields always have names").to_string(); let data = field.bytes().await?; - println!("Length of `{}` is {} bytes", name, data.len()); + debug!("Length of `{}` is {} bytes", name, data.len()); if &name == "image" { let reader = ImageReader::new(Cursor::new(data)) .with_guessed_format() .expect("Cursor io never fails"); - println!("Guessed format: {:?}", reader.format()); - let _image = reader.decode()?; + debug!("Guessed format: {:?}", reader.format()); + + let image = reader.decode()?; + let mut buf = EInkImage::new(800, 480); + let dither = ErrorDiffusionDither::new(DiffusionMatrix::Atkinson); + dither.dither(&image.into(), &mut buf); + let cmd = DisplaySetCommand { + img: Box::new(buf), + }; + ctx.display_channel.send_timeout(cmd, Duration::from_secs(10)).await?; } } Ok(()) diff --git a/src/display.rs b/src/display.rs index 99dc44a..bb842cd 100644 --- a/src/display.rs +++ b/src/display.rs @@ -2,8 +2,8 @@ use epd_waveshare::{epd7in3f::Epd7in3f, prelude::WaveshareDisplay}; use linux_embedded_hal::spidev::SpiModeFlags; use linux_embedded_hal::spidev::SpidevOptions; use linux_embedded_hal::{CdevPin, Delay, SpidevDevice}; -use tracing::debug_span; -use tracing::{info, instrument, error, warn, debug}; +use tracing::instrument; +use tracing::{debug, debug_span, warn}; use anyhow::Result; use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; @@ -12,7 +12,6 @@ pub trait EInkPanel { fn display(&mut self, buf: &[u8]) -> Result<()>; } - pub struct Wrapper { spi: SpidevDevice, gpiochip: Chip, @@ -64,9 +63,8 @@ impl Wrapper { } impl EInkPanel for Wrapper { + #[instrument(skip_all)] fn display(&mut self, buf: &[u8]) -> Result<()> { - let span = debug_span!("display"); - let _enter = span.enter(); self.panel .update_and_display_frame(&mut self.spi, buf, &mut self.delay)?; debug!("Finished updating frame"); @@ -78,8 +76,9 @@ impl EInkPanel for Wrapper { pub struct FakeEInk(); impl EInkPanel for FakeEInk { - fn display(&mut self, buf: &[u8]) -> Result<()> { + fn display(&mut self, _buf: &[u8]) -> Result<()> { // Do nothing. + warn!("Fake display was called"); Ok(()) } } diff --git a/src/errors.rs b/src/errors.rs index 5aed0b5..8b13789 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1 +1 @@ -use thiserror; + diff --git a/src/imageproc.rs b/src/imageproc.rs index cddb912..90fb844 100644 --- a/src/imageproc.rs +++ b/src/imageproc.rs @@ -1,5 +1,5 @@ use image::RgbImage; -use tracing::{info, debug, instrument}; +use tracing::instrument; use image::Rgb as imgRgb; use palette::color_difference::Ciede2000; diff --git a/src/main.rs b/src/main.rs index 728dea3..2cfde58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ pub mod imageproc; use crate::display::{EInkPanel, FakeEInk, Wrapper}; use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither}; +use tracing::info; use clap::{Parser, Subcommand}; use image::RgbImage; @@ -50,11 +51,13 @@ async fn main() -> anyhow::Result<()> { } if matches!(cli.command, Command::Serve) { - let display = FakeEInk {}; + let display = Wrapper::new()?; + let ctx = api::Context::new(Box::new(display)); let app = api::router().with_state(ctx); let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; + info!("Listening on 0.0.0.0:3000"); axum::serve(listener, app).await?; } diff --git a/test.hurl b/test.hurl index 7b110a8..aaeece0 100644 --- a/test.hurl +++ b/test.hurl @@ -1,3 +1,3 @@ -POST http://localhost:3000/setimage +POST http://192.168.0.185:3000/setimage [MultipartFormData] image: file,image.png;