Implemented basic webserver
This commit is contained in:
parent
b2662dadb7
commit
e9d0580f7e
4 changed files with 149 additions and 56 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
|
@ -97,6 +97,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"form_urlencoded",
|
||||
"futures-util",
|
||||
|
|
@ -145,6 +146,17 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.76"
|
||||
|
|
@ -413,6 +425,12 @@ version = "0.3.31"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.31"
|
||||
|
|
@ -470,6 +488,25 @@ version = "0.32.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.6.0"
|
||||
|
|
@ -548,6 +585,7 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
|
|
@ -1412,6 +1450,19 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2024"
|
|||
[dependencies]
|
||||
image = {version = "0.25.8", features = ["png"]}
|
||||
|
||||
axum = { version = "0.8.4", features = ["multipart"] }
|
||||
# axum = { version = "0.8.4", features = ["multipart", "debug"] }
|
||||
axum = { version = "0.8.4", features = ["multipart", "http2", "macros"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
tower-http = { version = "0.6.1", features = ["limit", "trace"] }
|
||||
|
|
|
|||
|
|
@ -108,30 +108,41 @@ impl Job {
|
|||
return Err(EscPosError::InvalidBitmapMode);
|
||||
}
|
||||
|
||||
let (w, h) = (img.width(), img.height());
|
||||
let swap = matches!(orientation, Some(ImageOrientation::Largest))
|
||||
&& h.min(self.max_width as u32) * w > w.min(self.max_width as u32) * h;
|
||||
let (iw, ih) = (img.width(), img.height());
|
||||
let mw = self.max_width as u32;
|
||||
|
||||
let scale = if swap {
|
||||
self.max_width as f32 / h as f32
|
||||
} else {
|
||||
self.max_width as f32 / w as f32
|
||||
let rotate = matches!(orientation, Some(ImageOrientation::Largest)) && {
|
||||
let scale_a = mw as f32 / iw as f32;
|
||||
let out_h_a = (ih as f32 * scale_a).round() as u32;
|
||||
let area_a = (mw as u64) * (out_h_a as u64);
|
||||
let scale_b = mw as f32 / ih as f32;
|
||||
let out_h_b = (iw as f32 * scale_b).round() as u32;
|
||||
let area_b = (mw as u64) * (out_h_b as u64);
|
||||
area_b > area_a
|
||||
};
|
||||
|
||||
let (w, h) = if swap {
|
||||
((h as f32 * scale) as u32, self.max_width as u32)
|
||||
} else {
|
||||
(self.max_width as u32, (h as f32 * scale) as u32)
|
||||
};
|
||||
let (pw, ph) = if rotate { (ih, iw) } else { (iw, ih) };
|
||||
let target_w = mw;
|
||||
let target_h = (ph as f32 * (mw as f32 / pw as f32)).round() as u32;
|
||||
|
||||
let img =
|
||||
DynamicImage::ImageRgba8(image::imageops::resize(img, w, h, FilterType::Triangle));
|
||||
let mut rotated: Option<DynamicImage> = None;
|
||||
if rotate {
|
||||
rotated = Some(img.rotate90());
|
||||
}
|
||||
let src: &DynamicImage = rotated.as_ref().unwrap_or(img);
|
||||
let resized = DynamicImage::ImageRgba8(image::imageops::resize(
|
||||
src,
|
||||
target_w,
|
||||
target_h,
|
||||
FilterType::Triangle,
|
||||
));
|
||||
|
||||
let img = if swap { img.rotate90() } else { img };
|
||||
let mut gray = img.to_luma8();
|
||||
let mut gray = resized.to_luma8();
|
||||
auto_brighten(&mut gray);
|
||||
|
||||
let mono = dither(&mut gray);
|
||||
|
||||
let w = target_w;
|
||||
let h = target_h;
|
||||
let width_bytes = ((w + 7) >> 3) as u16;
|
||||
|
||||
let buf = &mut self.content;
|
||||
|
|
@ -143,10 +154,10 @@ impl Job {
|
|||
0x76,
|
||||
0x30,
|
||||
mode,
|
||||
width_bytes as u8,
|
||||
(width_bytes >> 8) as u8,
|
||||
h as u8,
|
||||
(h >> 8) as u8,
|
||||
(width_bytes & 0xFF) as u8,
|
||||
((width_bytes >> 8) & 0xFF) as u8,
|
||||
(h & 0xFF) as u8,
|
||||
((h >> 8) & 0xFF) as u8,
|
||||
]);
|
||||
buf.extend_from_slice(&mono);
|
||||
|
||||
|
|
|
|||
98
src/main.rs
98
src/main.rs
|
|
@ -5,60 +5,90 @@ use std::{io::Write, sync::Arc};
|
|||
|
||||
use axum::{
|
||||
Router,
|
||||
body::{Body, Bytes},
|
||||
extract::{Extension, Multipart},
|
||||
http::StatusCode,
|
||||
http::{Response, StatusCode, header},
|
||||
response::IntoResponse,
|
||||
routing::post,
|
||||
routing::{get, post},
|
||||
};
|
||||
use image::DynamicImage;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::escpos::printer::Printer;
|
||||
use crate::escpos::{errors::EscPosError, job::ImageOrientation, printer::Printer};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let printer = Arc::new(Mutex::new(Printer::new(380)));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", post(upload))
|
||||
.route("/upload", post(upload))
|
||||
.route("/print", get(print))
|
||||
.layer(Extension(printer));
|
||||
|
||||
println!("Listening on http://127.0.0.1:8000");
|
||||
axum::Server::bind(&"192.168.1.112:8000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap();
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn print(
|
||||
Extension(printer): Extension<Arc<Mutex<Printer>>>,
|
||||
) -> impl axum::response::IntoResponse {
|
||||
let mut printer = printer.lock().await;
|
||||
let bytes = match printer.export_job() {
|
||||
Ok(data) => data,
|
||||
Err(EscPosError::EmptyQueue) => {
|
||||
println!("requested but no queue");
|
||||
return (
|
||||
StatusCode::NO_CONTENT,
|
||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
Vec::new(),
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
return (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
Vec::new(),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||
bytes,
|
||||
)
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn upload(
|
||||
Extension(printer): Extension<Arc<Mutex<Printer>>>,
|
||||
mut multipart: Multipart,
|
||||
Extension(state): Extension<Arc<Mutex<Printer>>>,
|
||||
) -> Result<impl IntoResponse, (StatusCode, &'static str)> {
|
||||
let mut field = match multipart.next_field().await {
|
||||
Ok(Some(f)) => f,
|
||||
Ok(None) => return (StatusCode::BAD_REQUEST, "no multipart field").into_response(),
|
||||
Err(_) => return (StatusCode::BAD_REQUEST, "multipart parse error").into_response(),
|
||||
};
|
||||
) -> Result<Response<Body>, StatusCode> {
|
||||
let field = multipart
|
||||
.next_field()
|
||||
.await
|
||||
.map_err(|_| StatusCode::BAD_REQUEST)?
|
||||
.ok_or(StatusCode::BAD_REQUEST)?;
|
||||
let data = field
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.to_vec();
|
||||
|
||||
let data = match field.bytes().await {
|
||||
Ok(b) => b,
|
||||
Err(_) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, "read field bytes error").into_response();
|
||||
}
|
||||
};
|
||||
let img: DynamicImage =
|
||||
image::load_from_memory(&data).map_err(|_| StatusCode::UNSUPPORTED_MEDIA_TYPE)?;
|
||||
|
||||
let img: DynamicImage = match image::load_from_memory(data.chunk()) {
|
||||
Ok(i) => i,
|
||||
Err(_) => {
|
||||
return (StatusCode::UNSUPPORTED_MEDIA_TYPE, "image decode error").into_response();
|
||||
}
|
||||
};
|
||||
println!("{}x{}", img.width(), img.height());
|
||||
|
||||
let s = state.lock().await;
|
||||
let mut job = s.new_job().unwrap();
|
||||
job.write_bitmap(&img, None, None);
|
||||
let mut printer = printer.lock().await;
|
||||
let job = printer.new_job().unwrap();
|
||||
job.write_bitmap(&img, Some(ImageOrientation::Largest), None)
|
||||
.unwrap();
|
||||
job.ready = true;
|
||||
|
||||
let mut file = std::fs::File::create("/dev/usb/lp0").unwrap();
|
||||
s.print_job(&mut file);
|
||||
(StatusCode::OK).into_response()
|
||||
// let mut file = std::fs::File::create("/dev/usb/lp0").unwrap();
|
||||
// printer.print_job(&mut file).unwrap();
|
||||
|
||||
Ok((StatusCode::OK).into_response())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue