Compare commits

..

2 commits

Author SHA1 Message Date
saji 35ef64a829 add rusqlite
All checks were successful
cargo_test_bench / Run Tests (push) Successful in 1m36s
cargo_test_bench / Run Benchmarks (push) Successful in 3m3s
2024-08-01 16:42:08 -05:00
saji 0e3d324748 make display thread dedicated, move to display.rs
display thread should be dedicated to not steal resources from tokio
2024-08-01 16:42:08 -05:00
5 changed files with 139 additions and 39 deletions

88
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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)
} }

View file

@ -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)
}

View file

@ -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