Switch to persistent printer with queue

This commit is contained in:
Joscha 2025-02-27 01:28:08 +01:00
parent 0ea4cf1d22
commit 6555e9c0bd
14 changed files with 221 additions and 76 deletions

View file

@ -1,3 +1,4 @@
mod backlog;
mod calendar;
mod cells;
mod chat_message;
@ -11,21 +12,23 @@ mod typst;
use showbits_common::widgets::{FontStuff, HasFontStuff};
use tokio::sync::mpsc;
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
pub use self::{
calendar::CalendarDrawing, cells::CellsDrawing, chat_message::ChatMessageDrawing,
egg::EggDrawing, image::ImageDrawing, photo::PhotoDrawing, text::TextDrawing,
tictactoe::TicTacToeDrawing, typst::TypstDrawing,
backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing,
chat_message::ChatMessageDrawing, egg::EggDrawing, image::ImageDrawing, photo::PhotoDrawing,
text::TextDrawing, tictactoe::TicTacToeDrawing, typst::TypstDrawing,
};
pub const FEED: f32 = 64.0;
#[derive(Default)]
pub struct Context {
font_stuff: FontStuff,
}
pub trait Drawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()>;
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()>;
}
pub struct Command(Box<dyn Drawing + Send>);
@ -44,12 +47,12 @@ impl HasFontStuff for Context {
pub struct Drawer {
rx: mpsc::Receiver<Command>,
printer: Printer,
printer: PersistentPrinter,
ctx: Context,
}
impl Drawer {
pub fn new(rx: mpsc::Receiver<Command>, printer: Printer) -> Self {
pub fn new(rx: mpsc::Receiver<Command>, printer: PersistentPrinter) -> Self {
Self {
rx,
printer,

View file

@ -0,0 +1,12 @@
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
pub struct BacklogDrawing;
impl Drawing for BacklogDrawing {
fn draw(&self, printer: &mut PersistentPrinter, _ctx: &mut Context) -> anyhow::Result<()> {
printer.print_backlog()?;
Ok(())
}
}

View file

@ -9,9 +9,9 @@ use taffy::{
style_helpers::{length, percent, repeat},
};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct CalendarDrawing {
pub year: i16,
@ -19,7 +19,7 @@ pub struct CalendarDrawing {
}
impl Drawing for CalendarDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut date = civil::Date::new(self.year, self.month, 1)?;
let mut tree = Tree::<Context>::new(WHITE);
@ -86,6 +86,7 @@ impl Drawing for CalendarDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -94,7 +95,6 @@ impl Drawing for CalendarDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -3,11 +3,11 @@ use image::{
imageops::{self, FilterType},
};
use showbits_common::{Node, Tree, WidgetExt, color, widgets::Image};
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent};
use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent};
use crate::printer::Printer;
use crate::{persistent_printer::PersistentPrinter, printer::Printer};
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
const BLACK: Rgba<u8> = Rgba([0, 0, 0, 255]);
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
@ -49,8 +49,8 @@ pub struct CellsDrawing {
}
impl Drawing for CellsDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
let mut image = RgbaImage::new(Printer::WIDTH / self.scale, self.rows);
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut image: image::ImageBuffer<Rgba<u8>, Vec<u8>> = RgbaImage::new(Printer::WIDTH / self.scale, self.rows);
// Initialize first line randomly
for x in 0..image.width() {
@ -79,6 +79,7 @@ impl Drawing for CellsDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -86,7 +87,6 @@ impl Drawing for CellsDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -8,7 +8,7 @@ use taffy::{
style_helpers::{length, percent},
};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
@ -18,7 +18,7 @@ pub struct ChatMessageDrawing {
}
impl Drawing for ChatMessageDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE);
let max_username_width_in_chars = 32.0;

View file

@ -6,11 +6,11 @@ use showbits_common::{
color::{self, WHITE},
widgets::{Image, Text},
};
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent};
use taffy::{prelude::length, style_helpers::percent, AlignItems, Display, FlexDirection};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct EggDrawing;
@ -21,7 +21,7 @@ fn load_image(bytes: &[u8]) -> RgbaImage {
}
impl Drawing for EggDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut rng = rand::rng();
// Choose which set of egg images to use
@ -84,6 +84,7 @@ impl Drawing for EggDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -92,7 +93,6 @@ impl Drawing for EggDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -5,11 +5,11 @@ use showbits_common::{
color::{self, BLACK, WHITE},
widgets::{DitherAlgorithm, Image},
};
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent};
use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct ImageDrawing {
pub image: RgbaImage,
@ -19,7 +19,7 @@ pub struct ImageDrawing {
}
impl Drawing for ImageDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut image = self.image.clone();
if self.bright {
for pixel in image.pixels_mut() {
@ -40,6 +40,7 @@ impl Drawing for ImageDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -47,7 +48,6 @@ impl Drawing for ImageDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -9,9 +9,9 @@ use taffy::{
style_helpers::{length, percent},
};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct PhotoDrawing {
pub image: RgbaImage,
@ -19,7 +19,7 @@ pub struct PhotoDrawing {
}
impl Drawing for PhotoDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE);
let mut image = self.image.clone();
@ -45,6 +45,7 @@ impl Drawing for PhotoDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -54,7 +55,6 @@ impl Drawing for PhotoDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -1,14 +1,14 @@
use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Text};
use taffy::style_helpers::percent;
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
pub struct TextDrawing(pub String);
impl Drawing for TextDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE);
let text = Text::new()

View file

@ -8,14 +8,14 @@ use taffy::{
style_helpers::{length, percent, repeat},
};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct TicTacToeDrawing;
impl Drawing for TicTacToeDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let block_size = length(128.0);
let width = length(2.0);
@ -57,6 +57,7 @@ impl Drawing for TicTacToeDrawing {
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center))
@ -66,7 +67,6 @@ impl Drawing for TicTacToeDrawing {
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -1,25 +1,25 @@
use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Typst};
use taffy::style_helpers::percent;
use taffy::{prelude::length, style_helpers::percent};
use crate::printer::Printer;
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
use super::{Context, Drawing, FEED};
pub struct TypstDrawing(pub String);
impl Drawing for TypstDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> {
fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE);
let typst = Typst::new(self.0.clone()).node().register(&mut tree)?;
let root = Node::empty()
.with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.and_child(typst)
.register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(())
}
}

View file

@ -1,19 +1,24 @@
mod drawer;
mod persistent_printer;
mod printer;
mod server;
use std::path::PathBuf;
use std::{path::PathBuf, time::Duration};
use clap::Parser;
use drawer::Drawer;
use printer::Printer;
use drawer::{BacklogDrawing, Command};
use tokio::{runtime::Runtime, sync::mpsc};
use self::{drawer::Drawer, persistent_printer::PersistentPrinter};
#[derive(Parser)]
struct Args {
/// Address the web server will listen at.
addr: String,
/// Path to the queue directory.
queue: PathBuf,
/// Path to the printer's USB device file.
///
/// Usually, this is located at `/dev/usb/lp0` or a similar location.
@ -30,11 +35,17 @@ fn main() -> anyhow::Result<()> {
let (tx, rx) = mpsc::channel(3);
let printer = Printer::new(args.printer, args.export)?;
let printer = PersistentPrinter::new(args.printer, args.export, args.queue);
let mut drawer = Drawer::new(rx, printer);
let runtime = Runtime::new()?;
runtime.spawn(server::run(tx, args.addr));
runtime.spawn(server::run(tx.clone(), args.addr));
runtime.spawn(async move {
loop {
let _ = tx.send(Command::draw(BacklogDrawing)).await;
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
println!("Running");
drawer.run()?;

View file

@ -0,0 +1,135 @@
use std::{fs, io::ErrorKind, path::PathBuf};
use anyhow::{Context, bail};
use image::RgbaImage;
use jiff::Timestamp;
use showbits_common::Tree;
use taffy::{AvailableSpace, NodeId, Size};
use crate::printer::Printer;
pub struct PersistentPrinter {
printer_file: Option<PathBuf>,
export_file: Option<PathBuf>,
queue_dir: PathBuf,
printer: Option<Printer>,
}
impl PersistentPrinter {
pub fn new(
printer_file: Option<PathBuf>,
export_file: Option<PathBuf>,
queue_dir: PathBuf,
) -> Self {
Self {
printer_file,
export_file,
queue_dir,
printer: None,
}
}
fn render_tree_to_image<C>(
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<RgbaImage> {
let available = Size {
width: AvailableSpace::Definite(Printer::WIDTH as f32),
// TODO Maybe MinContent? If not, why not?
height: AvailableSpace::MaxContent,
};
tree.render(ctx, root, available)
}
fn print_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
let Some(printer) = &mut self.printer else {
bail!("no printer found");
};
printer.print_image(image)?;
Ok(())
}
fn reconnect_printer(&mut self) -> anyhow::Result<()> {
let printer = Printer::new(self.printer_file.clone(), self.export_file.clone())?;
self.printer = Some(printer);
Ok(())
}
fn print_image_robustly(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
println!("Printing image");
if self.print_image(image).is_ok() {
return Ok(());
}
println!("First attempt failed, reconnecting and retrying");
self.reconnect_printer()?;
self.print_image(image)?;
Ok(())
}
fn enqueue_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
let now = Timestamp::now();
let path = self.queue_dir.join(format!("{now}.png"));
println!("Enqueuing image {}", path.display());
fs::create_dir_all(&self.queue_dir)
.with_context(|| format!("At {}", self.queue_dir.display()))
.context("Failed to create queue directory")?;
image
.save(&path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to save image to queue")?;
Ok(())
}
pub fn print_tree<C>(
&mut self,
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<()> {
let image = Self::render_tree_to_image(tree, ctx, root)?;
if self.print_image_robustly(&image).is_err() {
self.enqueue_image(&image)?;
}
Ok(())
}
pub fn print_backlog(&mut self) -> anyhow::Result<()> {
let mut files = vec![];
match self.queue_dir.read_dir() {
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => Err(err)
.with_context(|| format!("At {}", self.queue_dir.display()))
.context("Failed to open queue dir")?,
Ok(dir) => {
for entry in dir {
let entry = entry?;
if entry.file_type()?.is_file() {
files.push(entry.path());
}
}
}
}
files.sort_unstable();
for file in files {
println!("Dequeuing image {}", file.display());
let image: RgbaImage = image::open(&file)?.into_rgba8();
if self.print_image_robustly(&image).is_err() {
return Ok(());
}
fs::remove_file(&file)?;
}
Ok(())
}
}

View file

@ -1,5 +1,6 @@
use std::path::PathBuf;
use anyhow::Context;
use escpos::{
driver::FileDriver,
printer::Printer as EPrinter,
@ -7,8 +8,7 @@ use escpos::{
utils::{GS, PageCode, Protocol},
};
use image::{Rgba, RgbaImage};
use showbits_common::{Tree, color};
use taffy::{AvailableSpace, NodeId, Size};
use showbits_common::color;
pub struct Printer {
printer: Option<EPrinter<FileDriver>>,
@ -44,7 +44,9 @@ impl Printer {
export_path: Option<PathBuf>,
) -> anyhow::Result<Self> {
let printer = if let Some(path) = printer_path {
let driver = FileDriver::open(&path)?;
let driver = FileDriver::open(&path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to open printer driver")?;
let protocol = Protocol::default();
let mut options = PrinterOptions::default();
options.page_code(Some(Self::PAGE_CODE));
@ -59,34 +61,16 @@ impl Printer {
})
}
pub fn feed(&mut self) -> anyhow::Result<()> {
if let Some(printer) = &mut self.printer {
printer.init()?.feeds(3)?.print()?;
}
Ok(())
}
pub fn print_tree<C>(
&mut self,
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<()> {
let available = Size {
width: AvailableSpace::Definite(Self::WIDTH as f32),
// TODO Maybe MinContent? If not, why not?
height: AvailableSpace::MaxContent,
};
let image = tree.render(ctx, root, available)?;
pub fn print_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
if let Some(path) = &self.export_path {
image.save(path)?;
image
.save(path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to export to-be-printed image")?;
}
if let Some(printer) = &mut self.printer {
Self::print_image_to_printer(printer, &image)?;
Self::print_image_to_printer(printer, image).context("Failed to print image")?;
}
Ok(())