Import teepee code
This commit is contained in:
parent
6b62c3fd54
commit
a2867b7188
9 changed files with 1439 additions and 5 deletions
1213
Cargo.lock
generated
1213
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
anyhow = "1.0.80"
|
||||||
palette = "0.7.5"
|
palette = "0.7.5"
|
||||||
showbits-common.path = "./showbits-common"
|
showbits-common.path = "./showbits-common"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.80"
|
anyhow.workspace = true
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,14 @@ version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
axum = "0.7.4"
|
||||||
|
clap = { version = "4.5.1", features = ["derive", "deprecated"] }
|
||||||
|
escpos = { version = "0.7.2", features = ["full"] }
|
||||||
palette.workspace = true
|
palette.workspace = true
|
||||||
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
showbits-common.workspace = true
|
showbits-common.workspace = true
|
||||||
|
tokio = { version = "1.36.0", features = ["full"] }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
|
||||||
7
showbits-thermal-printer/src/command.rs
Normal file
7
showbits-thermal-printer/src/command.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
pub enum Command {
|
||||||
|
Stop,
|
||||||
|
Test,
|
||||||
|
Rip,
|
||||||
|
Text(String),
|
||||||
|
ChatMessage { username: String, content: String },
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
mod color;
|
mod command;
|
||||||
|
mod printer;
|
||||||
|
mod server;
|
||||||
|
mod util;
|
||||||
|
|
||||||
use showbits_common::Vec2;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
fn main() {
|
use clap::Parser;
|
||||||
println!("{:?}", Vec2::EAST);
|
use printer::Printer;
|
||||||
|
use tokio::{runtime::Runtime, sync::mpsc};
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
path: PathBuf,
|
||||||
|
addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let args = Args::parse();
|
||||||
|
|
||||||
|
let (tx, rx) = mpsc::channel(3);
|
||||||
|
let mut printer = Printer::new(rx, &args.path)?;
|
||||||
|
|
||||||
|
let runtime = Runtime::new()?;
|
||||||
|
runtime.spawn(server::run(tx, args.addr));
|
||||||
|
|
||||||
|
println!("Running");
|
||||||
|
printer.run()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
showbits-thermal-printer/src/printer.rs
Normal file
104
showbits-thermal-printer/src/printer.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use escpos::{
|
||||||
|
driver::FileDriver,
|
||||||
|
printer::Printer as EPrinter,
|
||||||
|
utils::{PageCode, Protocol, ESC},
|
||||||
|
};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
use crate::{command::Command, util};
|
||||||
|
|
||||||
|
pub struct Printer {
|
||||||
|
rx: mpsc::Receiver<Command>,
|
||||||
|
printer: EPrinter<FileDriver>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Printer {
|
||||||
|
pub fn new(rx: mpsc::Receiver<Command>, path: &Path) -> anyhow::Result<Self> {
|
||||||
|
let driver = FileDriver::open(path)?;
|
||||||
|
|
||||||
|
// Experimentation has determined that the printer uses PC437 and the
|
||||||
|
// page code can't be changed.
|
||||||
|
// https://en.wikipedia.org/wiki/Code_page_437
|
||||||
|
// https://www.epson-biz.com/modules/ref_charcode_en/index.php?content_id=10
|
||||||
|
let printer = EPrinter::new(driver, Protocol::default(), Some(PageCode::PC437));
|
||||||
|
|
||||||
|
Ok(Self { rx, printer })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> anyhow::Result<()> {
|
||||||
|
while let Some(command) = self.rx.blocking_recv() {
|
||||||
|
match command {
|
||||||
|
Command::Stop => break,
|
||||||
|
Command::Test => self.on_test()?,
|
||||||
|
Command::Rip => self.on_rip()?,
|
||||||
|
Command::Text(text) => self.on_text(text)?,
|
||||||
|
Command::ChatMessage { username, content } => {
|
||||||
|
self.on_chat_message(username, content)?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_test(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.printer.init()?;
|
||||||
|
|
||||||
|
let x = 48; // bytes
|
||||||
|
let y = 48; // dots
|
||||||
|
|
||||||
|
let m = 0;
|
||||||
|
let x_l = x as u8;
|
||||||
|
let x_h = (x >> 8) as u8;
|
||||||
|
let y_l = y as u8;
|
||||||
|
let y_h = (y >> 8) as u8;
|
||||||
|
let mut command = vec![0x1D, b'v', b'0', m, x_l, x_h, y_l, y_h];
|
||||||
|
for y in 0..y {
|
||||||
|
for x in 0..x {
|
||||||
|
// command.push((x + y) as u8);
|
||||||
|
command.push(0b0000_0011);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.printer.custom(&command)?;
|
||||||
|
|
||||||
|
self.printer.print()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_rip(&mut self) -> anyhow::Result<()> {
|
||||||
|
self.printer.init()?.feeds(6)?.print()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_text(&mut self, text: String) -> anyhow::Result<()> {
|
||||||
|
let text = util::sanitize(&text);
|
||||||
|
self.printer.init()?.write(&text)?.print()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_chat_message(&mut self, username: String, content: String) -> anyhow::Result<()> {
|
||||||
|
let username = util::sanitize(&username);
|
||||||
|
let content = util::sanitize(&content);
|
||||||
|
|
||||||
|
let username = username
|
||||||
|
.chars()
|
||||||
|
.map(|c| if c.is_ascii_whitespace() { '_' } else { c })
|
||||||
|
.take(16)
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let content = content.chars().take(300).collect::<String>();
|
||||||
|
|
||||||
|
self.printer
|
||||||
|
.init()?
|
||||||
|
.reverse(true)?
|
||||||
|
.write(&username)?
|
||||||
|
.reverse(false)?
|
||||||
|
.write(" ")?
|
||||||
|
.writeln(&content)?
|
||||||
|
.print()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
61
showbits-thermal-printer/src/server.rs
Normal file
61
showbits-thermal-printer/src/server.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use axum::{extract::State, routing::post, Form, Router};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tokio::{net::TcpListener, sync::mpsc};
|
||||||
|
|
||||||
|
use crate::command::Command;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Server {
|
||||||
|
tx: mpsc::Sender<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(tx: mpsc::Sender<Command>, addr: String) -> anyhow::Result<()> {
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/stop", post(post_stop))
|
||||||
|
.route("/test", post(post_test))
|
||||||
|
.route("/rip", post(post_rip))
|
||||||
|
.route("/text", post(post_text))
|
||||||
|
.route("/chat_message", post(post_chat_message))
|
||||||
|
.with_state(Server { tx });
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
|
axum::serve(listener, app).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_stop(server: State<Server>) {
|
||||||
|
let _ = server.tx.send(Command::Stop).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_test(server: State<Server>) {
|
||||||
|
let _ = server.tx.send(Command::Test).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_rip(server: State<Server>) {
|
||||||
|
let _ = server.tx.send(Command::Rip).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PostTextForm {
|
||||||
|
text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_text(server: State<Server>, request: Form<PostTextForm>) {
|
||||||
|
let _ = server.tx.send(Command::Text(request.0.text)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct PostChatMessageForm {
|
||||||
|
username: String,
|
||||||
|
content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_chat_message(server: State<Server>, request: Form<PostChatMessageForm>) {
|
||||||
|
let _ = server
|
||||||
|
.tx
|
||||||
|
.send(Command::ChatMessage {
|
||||||
|
username: request.0.username,
|
||||||
|
content: request.0.content,
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
18
showbits-thermal-printer/src/util.rs
Normal file
18
showbits-thermal-printer/src/util.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
const CODE_PAGE_437_SECOND_HALF: &str = concat!(
|
||||||
|
"ÇüéâäàåçêëèïîìÄÅ",
|
||||||
|
"ÉæÆôöòûùÿÖÜ¢£¥₧ƒ",
|
||||||
|
"áíóúñѪº¿⌐¬½¼¡«»",
|
||||||
|
"░▒▓│┤╡╢╖╕╣║╗╝╜╛┐",
|
||||||
|
"└┴┬├─┼╞╟╚╔╩╦╠═╬╧",
|
||||||
|
"╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀",
|
||||||
|
"αßΓπΣσµτΦΘΩδ∞φε∩",
|
||||||
|
"≡±≥≤⌠⌡÷≈°∙·√ⁿ²■",
|
||||||
|
);
|
||||||
|
|
||||||
|
fn is_safe(c: char) -> bool {
|
||||||
|
c.is_ascii_graphic() || c == ' ' || c == '\n' || CODE_PAGE_437_SECOND_HALF.contains(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sanitize(text: &str) -> String {
|
||||||
|
text.chars().filter(|&c| is_safe(c)).collect::<String>()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue