working set image api
This commit is contained in:
parent
d8447ee361
commit
0dc9d38d42
55
src/api.rs
55
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<Mutex<Box<dyn EInkPanel + Send>>>,
|
||||
display_channel: Sender<DisplaySetCommand>,
|
||||
display_task: Arc<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
#[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 {
|
||||
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`.
|
||||
struct AppError(anyhow::Error);
|
||||
|
||||
|
@ -58,6 +88,7 @@ pub fn router() -> Router<Context> {
|
|||
Router::new().route("/setimage", post(set_image))
|
||||
}
|
||||
|
||||
#[instrument(skip(ctx))]
|
||||
async fn set_image(
|
||||
State(ctx): State<Context>,
|
||||
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(())
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
use thiserror;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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?;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue