working set image api

This commit is contained in:
saji 2024-07-27 23:01:57 -05:00
parent d8447ee361
commit 0dc9d38d42
6 changed files with 59 additions and 18 deletions

View file

@ -1,12 +1,17 @@
use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither};
use axum::extract::Multipart; use axum::extract::Multipart;
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::{extract::State, response::Response, routing::post, Router}; use axum::{extract::State, response::Response, routing::post, Router};
use image::ImageReader; use image::{ImageReader, RgbImage};
use std::io::Cursor; use std::io::Cursor;
use std::time::Duration;
use tracing::{error, info, debug, instrument};
use crate::display::EInkPanel; 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 { pub enum ImageFormFields {
DitherType, DitherType,
@ -15,17 +20,42 @@ pub enum ImageFormFields {
#[derive(Clone)] #[derive(Clone)]
pub struct Context { pub struct Context {
display: Arc<Mutex<Box<dyn EInkPanel + Send>>>, display_channel: Sender<DisplaySetCommand>,
display_task: Arc<JoinHandle<()>>,
} }
impl Context { impl Context {
#[must_use] #[must_use]
pub fn new(w: Box<dyn EInkPanel + Send>) -> Self { pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
let (tx, rx) = mpsc::channel(2);
let task = tokio::spawn(display_task(rx, disp));
Self { Self {
display: Arc::new(Mutex::new(w)), display_channel: tx,
display_task: Arc::new(task),
} }
} }
} }
#[derive(Debug)]
pub struct DisplaySetCommand {
img: Box<EInkImage>,
}
#[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");
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`. // Make our own error that wraps `anyhow::Error`.
struct AppError(anyhow::Error); struct AppError(anyhow::Error);
@ -58,6 +88,7 @@ pub fn router() -> Router<Context> {
Router::new().route("/setimage", post(set_image)) Router::new().route("/setimage", post(set_image))
} }
#[instrument(skip(ctx))]
async fn set_image( async fn set_image(
State(ctx): State<Context>, State(ctx): State<Context>,
mut parts: Multipart, mut parts: Multipart,
@ -65,13 +96,21 @@ async fn set_image(
while let Some(field) = parts.next_field().await? { while let Some(field) = parts.next_field().await? {
let name = field.name().expect("fields always have names").to_string(); let name = field.name().expect("fields always have names").to_string();
let data = field.bytes().await?; let data = field.bytes().await?;
println!("Length of `{}` is {} bytes", name, data.len()); debug!("Length of `{}` is {} bytes", name, data.len());
if &name == "image" { if &name == "image" {
let reader = ImageReader::new(Cursor::new(data)) let reader = ImageReader::new(Cursor::new(data))
.with_guessed_format() .with_guessed_format()
.expect("Cursor io never fails"); .expect("Cursor io never fails");
println!("Guessed format: {:?}", reader.format()); debug!("Guessed format: {:?}", reader.format());
let _image = reader.decode()?;
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(()) Ok(())

View file

@ -2,8 +2,8 @@ use epd_waveshare::{epd7in3f::Epd7in3f, prelude::WaveshareDisplay};
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 tracing::debug_span; use tracing::instrument;
use tracing::{info, instrument, error, warn, debug}; use tracing::{debug, debug_span, warn};
use anyhow::Result; use anyhow::Result;
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags}; use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
@ -12,7 +12,6 @@ pub trait EInkPanel {
fn display(&mut self, buf: &[u8]) -> Result<()>; fn display(&mut self, buf: &[u8]) -> Result<()>;
} }
pub struct Wrapper { pub struct Wrapper {
spi: SpidevDevice, spi: SpidevDevice,
gpiochip: Chip, gpiochip: Chip,
@ -64,9 +63,8 @@ impl Wrapper {
} }
impl EInkPanel for Wrapper { impl EInkPanel for Wrapper {
#[instrument(skip_all)]
fn display(&mut self, buf: &[u8]) -> Result<()> { fn display(&mut self, buf: &[u8]) -> Result<()> {
let span = debug_span!("display");
let _enter = span.enter();
self.panel self.panel
.update_and_display_frame(&mut self.spi, buf, &mut self.delay)?; .update_and_display_frame(&mut self.spi, buf, &mut self.delay)?;
debug!("Finished updating frame"); debug!("Finished updating frame");
@ -78,8 +76,9 @@ impl EInkPanel for Wrapper {
pub struct FakeEInk(); pub struct FakeEInk();
impl EInkPanel for FakeEInk { impl EInkPanel for FakeEInk {
fn display(&mut self, buf: &[u8]) -> Result<()> { fn display(&mut self, _buf: &[u8]) -> Result<()> {
// Do nothing. // Do nothing.
warn!("Fake display was called");
Ok(()) Ok(())
} }
} }

View file

@ -1 +1 @@
use thiserror;

View file

@ -1,5 +1,5 @@
use image::RgbImage; use image::RgbImage;
use tracing::{info, debug, instrument}; use tracing::instrument;
use image::Rgb as imgRgb; use image::Rgb as imgRgb;
use palette::color_difference::Ciede2000; use palette::color_difference::Ciede2000;

View file

@ -5,6 +5,7 @@ pub mod imageproc;
use crate::display::{EInkPanel, FakeEInk, Wrapper}; use crate::display::{EInkPanel, FakeEInk, Wrapper};
use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither}; use crate::imageproc::{DiffusionMatrix, Ditherer, EInkImage, ErrorDiffusionDither};
use tracing::info;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use image::RgbImage; use image::RgbImage;
@ -50,11 +51,13 @@ async fn main() -> anyhow::Result<()> {
} }
if matches!(cli.command, Command::Serve) { if matches!(cli.command, Command::Serve) {
let display = FakeEInk {}; let display = Wrapper::new()?;
let ctx = api::Context::new(Box::new(display)); let ctx = api::Context::new(Box::new(display));
let app = api::router().with_state(ctx); let app = api::router().with_state(ctx);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; 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?; axum::serve(listener, app).await?;
} }

View file

@ -1,3 +1,3 @@
POST http://localhost:3000/setimage POST http://192.168.0.185:3000/setimage
[MultipartFormData] [MultipartFormData]
image: file,image.png; image: file,image.png;