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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
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]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
@ -587,6 +599,18 @@ dependencies = [
|
||||||
"zune-inflate",
|
"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]]
|
[[package]]
|
||||||
name = "fast-srgb8"
|
name = "fast-srgb8"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -722,6 +746,18 @@ name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
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]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
|
@ -997,6 +1033,17 @@ dependencies = [
|
||||||
"once_cell",
|
"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]]
|
[[package]]
|
||||||
name = "linux-embedded-hal"
|
name = "linux-embedded-hal"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -1416,6 +1463,7 @@ dependencies = [
|
||||||
"mime",
|
"mime",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"palette",
|
"palette",
|
||||||
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
"strum",
|
"strum",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
@ -1709,6 +1757,20 @@ dependencies = [
|
||||||
"bytemuck",
|
"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]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.24"
|
version = "0.1.24"
|
||||||
|
@ -2231,6 +2293,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "vcpkg"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "version-compare"
|
name = "version-compare"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2442,6 +2510,26 @@ dependencies = [
|
||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "zune-core"
|
name = "zune-core"
|
||||||
version = "0.4.12"
|
version = "0.4.12"
|
||||||
|
|
|
@ -16,6 +16,7 @@ linux-embedded-hal = { version = "0.4.0"}
|
||||||
mime = "0.3.17"
|
mime = "0.3.17"
|
||||||
minijinja = "2.1.0"
|
minijinja = "2.1.0"
|
||||||
palette = "0.7.6"
|
palette = "0.7.6"
|
||||||
|
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||||
serde = { version = "1.0.204", features = ["derive"] }
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
thiserror = "1.0.63"
|
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::dither::{DitherMethod, DitheredImage};
|
||||||
use crate::eink::Palette;
|
use crate::eink::Palette;
|
||||||
use axum::async_trait;
|
use axum::async_trait;
|
||||||
|
@ -10,10 +10,10 @@ use image::{ImageReader, RgbImage};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::sync::mpsc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::thread::JoinHandle;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::mpsc::{self, Receiver, Sender};
|
|
||||||
use tokio::task::JoinHandle;
|
|
||||||
use tracing::{error, info, instrument};
|
use tracing::{error, info, instrument};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -24,18 +24,17 @@ pub enum ApiError {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
display_channel: Sender<DisplaySetCommand>,
|
display_channel: mpsc::Sender<Box<DitheredImage>>,
|
||||||
display_task: Arc<JoinHandle<()>>,
|
display_task: Arc<JoinHandle<()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
|
pub fn new(disp: Box<dyn EInkPanel + Send>) -> Self {
|
||||||
let (tx, rx) = mpsc::channel(2);
|
let (handle, tx) = create_display_thread(disp);
|
||||||
let task = tokio::spawn(display_task(rx, disp));
|
|
||||||
Self {
|
Self {
|
||||||
display_channel: tx,
|
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
|
/// API routes for axum
|
||||||
/// Start with the basics: Send an image, crop it, dither, and upload.
|
/// Start with the basics: Send an image, crop it, dither, and upload.
|
||||||
/// we defer the upload to a separate task.
|
/// 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();
|
let mut dither = img_req.dither_method.get_ditherer();
|
||||||
dither.dither(&img_req.image, &mut buf);
|
dither.dither(&img_req.image, &mut buf);
|
||||||
}
|
}
|
||||||
let cmd = DisplaySetCommand { img: Box::new(buf) };
|
ctx.display_channel.send(Box::new(buf))?;
|
||||||
ctx.display_channel
|
|
||||||
.send_timeout(cmd, Duration::from_secs(10))
|
|
||||||
.await?;
|
|
||||||
Ok(StatusCode::OK)
|
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 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::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 std::sync::mpsc;
|
||||||
|
use std::thread;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
use tracing::{debug, warn};
|
use tracing::span;
|
||||||
|
use tracing::{debug, error, info, warn, Level};
|
||||||
use anyhow::Result;
|
|
||||||
use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
|
|
||||||
|
|
||||||
use crate::dither::DitheredImage;
|
|
||||||
|
|
||||||
pub trait EInkPanel {
|
pub trait EInkPanel {
|
||||||
fn display(&mut self, buf: &DitheredImage) -> Result<()>;
|
fn display(&mut self, buf: &DitheredImage) -> Result<()>;
|
||||||
|
@ -88,3 +90,34 @@ impl EInkPanel for FakeEInk {
|
||||||
Ok(())
|
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 api;
|
||||||
pub mod display;
|
pub mod display;
|
||||||
pub mod dither;
|
pub mod dither;
|
||||||
pub mod errors;
|
|
||||||
pub mod eink;
|
pub mod eink;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -18,9 +17,11 @@ use tracing::{error, info};
|
||||||
|
|
||||||
|
|
||||||
/// Application config, including sqlite db path, scan folders, and scheduling.
|
/// Application config, including sqlite db path, scan folders, and scheduling.
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct Config {
|
struct Config {
|
||||||
|
database_path: PathBuf,
|
||||||
|
search_paths: Vec<PathBuf>,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Display images on E-Ink Displays
|
/// Display images on E-Ink Displays
|
||||||
|
|
Loading…
Reference in a new issue