use crate::imageproc::{DitherMethod, EInkImage, }; use axum::extract::Multipart; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::{extract::State, response::Response, routing::post, Router}; use image::{DynamicImage, ImageReader}; use std::io::Cursor; use std::time::Duration; use tracing::{debug, error, info, instrument}; use crate::display::EInkPanel; use std::sync::Arc; use tokio::sync::mpsc::{self, Receiver, Sender}; use tokio::task::JoinHandle; pub enum ImageFormFields { DitherType, ImageFile, } #[derive(Clone)] pub struct Context { display_channel: Sender, display_task: Arc>, } impl Context { #[must_use] pub fn new(disp: Box) -> Self { let (tx, rx) = mpsc::channel(2); let task = tokio::spawn(display_task(rx, disp)); Self { display_channel: tx, display_task: Arc::new(task), } } } // Make our own error that wraps `anyhow::Error`. struct AppError(anyhow::Error); // Tell axum how to convert `AppError` into a response. impl IntoResponse for AppError { fn into_response(self) -> Response { ( StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", self.0), ) .into_response() } } // This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into // `Result<_, AppError>`. That way you don't need to do that manually. impl From for AppError where E: Into, { fn from(err: E) -> Self { Self(err.into()) } } #[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. pub fn router() -> Router { Router::new() .route("/setimage", post(set_image)) .route("/process_image", post(process_image)) } #[derive(Debug)] pub struct ImageRequest { image: Box, } #[instrument(skip(ctx))] #[axum::debug_handler] async fn set_image( State(ctx): State, mut parts: Multipart, ) -> Result { while let Some(field) = parts.next_field().await? { let name = field.name().expect("fields always have names").to_string(); let data = field.bytes().await?; 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"); debug!("Guessed format: {:?}", reader.format()); let mut buf = EInkImage::default(); { let image = reader.decode()?; let mut dither = DitherMethod::Atkinson.get_ditherer(); 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(()) } async fn process_image(mut parts: Multipart) -> Result { Ok(StatusCode::OK) }