Compare commits
2 commits
07bc399bbc
...
35ef64a829
Author | SHA1 | Date | |
---|---|---|---|
saji | 35ef64a829 | ||
saji | 0e3d324748 |
88
Cargo.lock
generated
88
Cargo.lock
generated
|
@ -17,6 +17,18 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
|
@ -587,6 +599,18 @@ dependencies = [
|
|||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fast-srgb8"
|
||||
version = "1.0.0"
|
||||
|
@ -722,6 +746,18 @@ name = "hashbrown"
|
|||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
|
@ -997,6 +1033,17 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-embedded-hal"
|
||||
version = "0.4.0"
|
||||
|
@ -1416,6 +1463,7 @@ dependencies = [
|
|||
"mime",
|
||||
"minijinja",
|
||||
"palette",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
"strum",
|
||||
"thiserror",
|
||||
|
@ -1709,6 +1757,20 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.24"
|
||||
|
@ -2231,6 +2293,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
|
@ -2442,6 +2510,26 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
|
|
|
@ -16,6 +16,7 @@ linux-embedded-hal = { version = "0.4.0"}
|
|||
mime = "0.3.17"
|
||||
minijinja = "2.1.0"
|
||||
palette = "0.7.6"
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
thiserror = "1.0.63"
|
||||
|
|
37
src/api.rs
37
src/api.rs
|
@ -1,4 +1,4 @@
|
|||
use crate::display::EInkPanel;
|
||||
use crate::display::{create_display_thread, EInkPanel};
|
||||
use crate::dither::{DitherMethod, DitheredImage};
|
||||
use crate::eink::Palette;
|
||||
use axum::async_trait;
|
||||
|
@ -10,10 +10,10 @@ use image::{ImageReader, RgbImage};
|
|||
use std::io::Cursor;
|
||||
use std::str;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc;
|
||||
use std::sync::Arc;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{error, info, instrument};
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
|
@ -24,18 +24,17 @@ pub enum ApiError {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
display_channel: Sender<DisplaySetCommand>,
|
||||
display_channel: mpsc::Sender<Box<DitheredImage>>,
|
||||
display_task: Arc<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
#[must_use]
|
||||
pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
|
||||
let (tx, rx) = mpsc::channel(2);
|
||||
let task = tokio::spawn(display_task(rx, disp));
|
||||
let (handle, tx) = create_display_thread(disp);
|
||||
Self {
|
||||
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
|
||||
/// Start with the basics: Send an image, crop it, dither, and upload.
|
||||
/// 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();
|
||||
dither.dither(&img_req.image, &mut buf);
|
||||
}
|
||||
let cmd = DisplaySetCommand { img: Box::new(buf) };
|
||||
ctx.display_channel
|
||||
.send_timeout(cmd, Duration::from_secs(10))
|
||||
.await?;
|
||||
ctx.display_channel.send(Box::new(buf))?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
// Manages display hardware
|
||||
use crate::dither::DitheredImage;
|
||||
use anyhow::Result;
|
||||
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::SpidevOptions;
|
||||
use linux_embedded_hal::{CdevPin, Delay, SpidevDevice};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use tracing::instrument;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use anyhow::Result;
|
||||
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
|
||||
|
||||
use crate::dither::DitheredImage;
|
||||
use tracing::span;
|
||||
use tracing::{debug, error, info, warn, Level};
|
||||
|
||||
pub trait EInkPanel {
|
||||
fn display(&mut self, buf: &DitheredImage) -> Result<()>;
|
||||
|
@ -88,3 +90,34 @@ impl EInkPanel for FakeEInk {
|
|||
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)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
pub mod api;
|
||||
pub mod display;
|
||||
pub mod dither;
|
||||
pub mod errors;
|
||||
pub mod eink;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
@ -18,9 +17,11 @@ use tracing::{error, info};
|
|||
|
||||
|
||||
/// Application config, including sqlite db path, scan folders, and scheduling.
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Config {
|
||||
|
||||
database_path: PathBuf,
|
||||
search_paths: Vec<PathBuf>,
|
||||
|
||||
}
|
||||
|
||||
/// Display images on E-Ink Displays
|
||||
|
|
Loading…
Reference in a new issue