Bump up dependecies and added non blocking barcode reader

This commit is contained in:
Robin Löhn 2025-10-29 16:31:24 +01:00
parent 80f9037f42
commit 235b66d582
No known key found for this signature in database
GPG key ID: 3628294D3FA576AD
2 changed files with 500 additions and 540 deletions

723
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,10 @@ use reqwest::Client;
use serde::Deserialize; use serde::Deserialize;
use serial::prelude::*; use serial::prelude::*;
use std::fs::File; use std::fs::File;
use std::io::Read;
use std::io::{Cursor, copy}; use std::io::{Cursor, copy};
use std::str::from_utf8; use std::str::from_utf8;
use std::sync::mpsc::{self, Receiver, Sender};
use std::time::Duration; use std::time::Duration;
use std::{env, fs}; use std::{env, fs};
use tokio; use tokio;
@ -14,189 +16,230 @@ pub mod image;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct BookData { struct BookData {
title: String, title: String,
authors: Vec<String>, authors: Vec<String>,
thumbnail: String, thumbnail: String,
description: String, description: String,
} }
#[allow(unused)] #[allow(unused)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct BookRoot { struct BookRoot {
items: Vec<Item>, items: Vec<Item>,
} }
#[allow(unused, non_snake_case)] #[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct Item { struct Item {
volumeInfo: VolumeInfo, volumeInfo: VolumeInfo,
} }
#[allow(unused, non_snake_case)] #[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct VolumeInfo { struct VolumeInfo {
title: String, title: String,
authors: Option<Vec<String>>, authors: Option<Vec<String>>,
description: Option<String>, description: Option<String>,
imageLinks: Option<ImageLinks>, imageLinks: Option<ImageLinks>,
} }
#[allow(unused, non_snake_case)] #[allow(unused, non_snake_case)]
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct ImageLinks { struct ImageLinks {
thumbnail: Option<String>, thumbnail: Option<String>,
smallThumbnail: Option<String>, smallThumbnail: Option<String>,
} }
fn main() -> eframe::Result { fn main() -> eframe::Result<()> {
env_logger::init(); env_logger::init();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 880.0]), viewport: egui::ViewportBuilder::default().with_inner_size([320.0, 880.0]),
..Default::default() ..Default::default()
}; };
eframe::run_native( eframe::run_native(
"Barcode Scanner", "Barcode Scanner",
options, options,
Box::new(|cc| { Box::new(|cc| {
egui_extras::install_image_loaders(&cc.egui_ctx); egui_extras::install_image_loaders(&cc.egui_ctx);
Ok(Box::<BarcodeScanner>::default()) Ok(Box::<BarcodeScanner>::default())
}), }),
) )
} }
fn get_ean13_data() -> String { fn get_ean13_data() -> String {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let path_to_scanner = &args[1]; let path_to_scanner = &args[1];
let mut port = serial::open(path_to_scanner).unwrap(); let mut port = serial::open(path_to_scanner).unwrap();
interact(&mut port).to_string() interact(&mut port).to_string()
} }
fn interact<T: SerialPort>(port: &mut T) -> String { fn interact<T: SerialPort>(port: &mut T) -> String {
port port.reconfigure(&|settings| {
.reconfigure(&|settings| { settings.set_baud_rate(serial::Baud9600).unwrap();
settings.set_baud_rate(serial::Baud9600).unwrap(); settings.set_char_size(serial::Bits8);
settings.set_char_size(serial::Bits8); settings.set_parity(serial::ParityNone);
settings.set_parity(serial::ParityNone); settings.set_stop_bits(serial::Stop1);
settings.set_stop_bits(serial::Stop1); settings.set_flow_control(serial::FlowNone);
settings.set_flow_control(serial::FlowNone); Ok(())
Ok(())
}) })
.unwrap(); .unwrap();
port.set_timeout(Duration::from_millis(1000000000)).unwrap(); port.set_timeout(Duration::from_millis(1000000000)).unwrap();
let buf: &mut [u8; 14] = &mut [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let buf: &mut [u8; 14] = &mut [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
port.read_exact(buf).unwrap(); port.read_exact(buf).unwrap();
let barcode_id = from_utf8(buf).unwrap(); let barcode_id = from_utf8(buf).unwrap();
debug!("{}", &barcode_id); debug!("{}", &barcode_id);
barcode_id.to_string() barcode_id.to_string()
} }
fn get_book_data(isbn: String) -> BookData { fn get_book_data(isbn: String) -> BookData {
let runtime = tokio::runtime::Runtime::new().unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap();
let lookup_url = format!( let lookup_url = format!(
"https://www.googleapis.com/books/v1/volumes?q=isbn:{}", "https://www.googleapis.com/books/v1/volumes?q=isbn:{}",
isbn isbn
); );
let cleint = Client::new(); let cleint = Client::new();
let book_data = runtime let book_data = runtime
.block_on(cleint.get(lookup_url).send()) .block_on(cleint.get(lookup_url).send())
.unwrap() .unwrap()
.json::<BookRoot>(); .json::<BookRoot>();
let book_data = runtime.block_on(book_data).unwrap(); let book_data = runtime.block_on(book_data).unwrap();
let book_metadata: Vec<BookData> = book_data.items.into_iter().map(BookData::from).collect(); let book_metadata: Vec<BookData> = book_data.items.into_iter().map(BookData::from).collect();
println!("{}", &book_metadata[0]); println!("{}", &book_metadata[0]);
return book_metadata[0].clone(); return book_metadata[0].clone();
} }
impl From<Item> for BookData { impl From<Item> for BookData {
fn from(item: Item) -> Self { fn from(item: Item) -> Self {
let info = item.volumeInfo; let info = item.volumeInfo;
BookData { BookData {
title: info.title, title: info.title,
authors: info.authors.unwrap_or_default(), authors: info.authors.unwrap_or_default(),
description: info.description.as_deref().unwrap_or("").to_string(), description: info.description.as_deref().unwrap_or("").to_string(),
thumbnail: info thumbnail: info
.imageLinks .imageLinks
.as_ref() .as_ref()
.and_then(|links| links.thumbnail.as_deref()) .and_then(|links| links.thumbnail.as_deref())
.unwrap_or("") .unwrap_or("")
.to_string(), .to_string(),
}
} }
}
} }
impl BookData { impl BookData {
fn new() -> BookData { fn new() -> BookData {
BookData { BookData {
title: "Unknown".to_string(), title: "Unknown".to_string(),
authors: vec!["Unknown".to_string()], authors: vec!["Unknown".to_string()],
thumbnail: "https://www.rust-lang.org/logos/rust-logo-512x512.png".to_string(), thumbnail: "https://www.rust-lang.org/logos/rust-logo-512x512.png".to_string(),
description: "No description available".to_string(), description: "No description available".to_string(),
}
} }
}
} }
impl std::fmt::Display for BookData { impl std::fmt::Display for BookData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"Title: {}\nAuthors: {}\nDescription: {}\nThumbnail: {}", "Title: {}\nAuthors: {}\nDescription: {}\nThumbnail: {}",
self.title, self.title,
if self.authors.is_empty() { if self.authors.is_empty() {
"Unknown".to_string() "Unknown".to_string()
} else { } else {
self.authors.join(", ") self.authors.join(", ")
}, },
if self.description.is_empty() { if self.description.is_empty() {
"No description available".to_string() "No description available".to_string()
} else { } else {
self.description.clone() self.description.clone()
}, },
self.thumbnail self.thumbnail
) )
} }
} }
fn get_thumbnail(url: String, isbn: String) { fn get_thumbnail(url: String, isbn: String) {
let runtime = Runtime::new().unwrap(); let runtime = Runtime::new().unwrap();
let url = if url.is_empty() { let url = if url.is_empty() {
"https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%2Fid%2FOIP.pluN_8e5GuVyqRmW8xWs1gHaET%3Fcb%3D12%26pid%3DApi&f=1&ipt=bf48e49e5df83d58cdee7752f4e2095f5b0ad37e5122d447ff774631db3de4a9&ipo=images".to_string() "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%2Fid%2FOIP.pluN_8e5GuVyqRmW8xWs1gHaET%3Fcb%3D12%26pid%3DApi&f=1&ipt=bf48e49e5df83d58cdee7752f4e2095f5b0ad37e5122d447ff774631db3de4a9&ipo=images".to_string()
} else { } else {
url url
}; };
debug!("The isbn of the img is: {isbn}"); debug!("The isbn of the img is: {isbn}");
let isbn = isbn.trim().to_string(); let isbn = isbn.trim().to_string();
fs::create_dir_all("cache").unwrap(); fs::create_dir_all("cache").unwrap();
let mut dest = File::create(format!("cache/{isbn}.jpeg")).unwrap(); let mut dest = File::create(format!("cache/{isbn}.jpeg")).unwrap();
let image = runtime.block_on(reqwest::get(url)).unwrap(); let image = runtime.block_on(reqwest::get(url)).unwrap();
let bytes = runtime.block_on(image.bytes()).unwrap(); let bytes = runtime.block_on(image.bytes()).unwrap();
let mut output = Cursor::new(bytes); let mut output = Cursor::new(bytes);
copy(&mut output, &mut dest).unwrap(); copy(&mut output, &mut dest).unwrap();
image::convert_to_webp(isbn); image::convert_to_webp(isbn);
} }
#[derive(Default)] struct BarcodeScanner {
struct BarcodeScanner {} barcode_rx: Receiver<String>,
last_barcode: Option<String>,
}
impl eframe::App for BarcodeScanner { impl eframe::App for BarcodeScanner {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) -> () { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) -> () {
egui_extras::install_image_loaders(ctx); egui_extras::install_image_loaders(ctx);
if let Ok(code) = self.barcode_rx.try_recv() {
egui::CentralPanel::default().show(ctx, |ui| { self.last_barcode = Some(code);
ui.heading("Barcode Scanner"); }
ui.group(|ui| { egui::CentralPanel::default().show(ctx, |ui| {
//loop { ui.heading("Barcode Scanner");
let barcode = get_ean13_data(); ui.group(|ui| {
ui.label(format!("EAN13: {}", barcode)); //loop {
let book_metadata = if barcode.starts_with("978") { let barcode = get_ean13_data();
get_book_data(barcode.clone()) ui.label(format!("EAN13: {}", barcode));
} else { let book_metadata = if barcode.starts_with("978") {
BookData::new() get_book_data(barcode.clone())
}; } else {
get_thumbnail(book_metadata.thumbnail.clone(), barcode); BookData::new()
ui.label(format!("Title: {}", book_metadata.title)); };
ui.label(format!("Description: {}", book_metadata.description)); get_thumbnail(book_metadata.thumbnail.clone(), barcode);
ui.label(format!("Authors: {:?}", book_metadata.authors)); ui.label(format!("Title: {}", book_metadata.title));
ui.image(egui::include_image!("cache/img_name.webp")) ui.label(format!("Description: {}", book_metadata.description));
.on_hover_text_at_pointer("WebP"); ui.label(format!("Authors: {:?}", book_metadata.authors));
//} ui.image(egui::include_image!("cache/img_name.webp"))
}) .on_hover_text_at_pointer("WebP");
}); //}
} })
});
ctx.request_repaint_after(Duration::from_millis(100));
}
}
impl Default for BarcodeScanner {
fn default() -> Self {
let (tx, rx) = mpsc::channel();
std::thread::spawn(move || {
let mut port = serial::open("/dev/ttyACM0").unwrap();
port.reconfigure(&|settings| {
settings.set_baud_rate(serial::Baud9600)?;
settings.set_char_size(serial::Bits8);
settings.set_parity(serial::ParityNone);
settings.set_stop_bits(serial::Stop1);
settings.set_flow_control(serial::FlowNone);
Ok(())
})
.unwrap();
port.set_timeout(Duration::from_millis(1000)).unwrap();
loop {
let mut buf = [0u8; 14];
if let Ok(_) = port.read_exact(&mut buf) {
if let Ok(code) = std::str::from_utf8(&buf) {
let barcode = code.trim().to_string();
if !barcode.is_empty() {
let _ = tx.send(barcode);
}
}
}
}
});
Self {
barcode_rx: rx,
last_barcode: None,
}
}
} }