Compare commits

...

10 Commits

17 changed files with 446 additions and 55 deletions

View File

35
Cargo.lock generated
View File

@ -286,6 +286,15 @@ dependencies = [
"cmake",
]
[[package]]
name = "fltk-theme"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3539d27a815514b56af2afa6b8e7c6d6b9274a103239487d5a60daa6340a4868"
dependencies = [
"fltk",
]
[[package]]
name = "fluid-parser"
version = "0.1.15"
@ -376,6 +385,12 @@ dependencies = [
"mach2",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
@ -769,6 +784,12 @@ dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scopeguard"
version = "0.3.3"
@ -816,6 +837,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serialport"
version = "4.4.0"
@ -1104,11 +1136,14 @@ dependencies = [
"chrono",
"fl2rust",
"fltk",
"fltk-theme",
"lazy_static",
"libsqlite3-sys",
"log",
"once_cell 0.1.8",
"rusqlite",
"serde",
"serde_json",
"serialport",
]

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
anyhow = "^1.0.86"
fltk = { version = "^1.4", features = ["fltk-bundled"] }
fltk-theme = "0.7.2"
calamine = "0.25.0"
serialport = "4.4.0"
rusqlite = "0.25.4"
@ -16,6 +17,13 @@ chrono = "0.4.38"
log = "0.4.22"
once_cell = "0.1.8"
lazy_static = "1.5.0"
serde= { version = "1.0.203", features = ["derive"] }
serde_json="1.0.120"
[build-dependencies]
fl2rust = "0.5.19"
[package.metadata.windows]
# For windows subsystem
link-args = ["/SUBSYSTEM:windows", "/ENTRY:mainCRTStartup"]
subsystem = "windows"

BIN
IMEI_SN_INDEX.xlsx Normal file

Binary file not shown.

View File

@ -1,10 +1,13 @@
use fltk::{prelude::*, window::Window, *};
use fltk::{*};
use crate::services::ui_service::UiService;
use fltk_theme::{WidgetTheme, ThemeType};
mod services;
mod models;
mod ui;
fn main() {
let app = app::App::default();
let widget_theme = WidgetTheme::new(ThemeType::Metro);
widget_theme.apply();
let mut ui_service = UiService::new();
ui_service.init_components();
app.run().unwrap();

View File

@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct ConfigModel{
pub current_line: usize,
pub excel_path: String,
pub last_com: String,
}
impl Default for ConfigModel {
fn default() -> Self {
ConfigModel {
current_line: 0,
excel_path: "".to_string(),
last_com: "".to_string(),
}
}
}

View File

@ -1 +1,2 @@
pub(crate) mod work_model;
pub(crate) mod work_model;
pub(crate) mod config_model;

View File

@ -0,0 +1,21 @@
use std::fs;
use anyhow::Result;
use crate::models::config_model::ConfigModel;
pub(crate) struct ConfigService;
impl ConfigService {
pub fn save_config(model: ConfigModel) ->Result<()> {
let json = serde_json::to_string(&model)?;
fs::write("config.json", json)?;
Ok(())
}
pub fn load_config() -> Result<ConfigModel> {
let json = fs::read_to_string("config.json")?;
let model: ConfigModel = serde_json::from_str(&json)?;
Ok(model)
}
}

View File

@ -1,12 +1,10 @@
use std::fs::File;
use std::io::BufReader;
use calamine::{Reader, open_workbook, Xlsx, Data, DataType};
use anyhow::Result;
use calamine::{Data, DataType, open_workbook, Reader, Xlsx};
use crate::models::work_model::WorkModel;
pub(crate) struct ExcelService {
current_line: usize,
work_book: Xlsx<BufReader<File>>,
sheet: calamine::Range<Data>,
}
@ -25,7 +23,6 @@ impl ExcelService {
};
let mut service = ExcelService {
current_line: 0,
work_book,
sheet,
};
service.current_line = service.find_first_useful_line();
@ -52,6 +49,15 @@ impl ExcelService {
current_line
}
pub fn set_current_line(&mut self, line: usize)->Result<(), String>{
self.current_line = line;
Ok(())
}
pub fn roll_back(&mut self){
self.current_line -= 1;
}
pub fn get_next_work_model(&mut self) -> Result<WorkModel, String> {
let mut work_model = WorkModel::default();
if let Some(imei) = self.sheet.get((self.current_line, 0)) {

View File

@ -0,0 +1,52 @@
use std::sync::{Arc, Mutex};
use chrono::Local;
use lazy_static::lazy_static;
use log::{Level, Metadata, Record, SetLoggerError};
type LogCallback = Box<dyn Fn(&str) + Send + Sync>;
pub struct FltkLogger {
callback: Arc<Mutex<Option<LogCallback>>>,
}
impl FltkLogger {
pub fn new() -> Self {
FltkLogger {
callback: Arc::new(Mutex::new(None)),
}
}
pub fn set_callback<F>(&self, callback: F)
where
F: Fn(&str) + 'static + Send + Sync,
{
let mut cb = self.callback.lock().unwrap();
*cb = Some(Box::new(callback));
}
}
impl log::Log for FltkLogger {
fn enabled(&self, metadata: &Metadata) -> bool {
metadata.level() <= Level::Info
}
fn log(&self, record: &Record) {
if self.enabled(record.metadata()) {
if let Some(callback) = self.callback.lock().unwrap().as_ref() {
callback(&format!("{}: {} - {}\r\n",Local::now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.args()));
}
}
}
fn flush(&self) {}
}
lazy_static! {
pub static ref LOGGER: FltkLogger = FltkLogger::new();
}
pub fn init_logger() -> Result<(), SetLoggerError> {
log::set_logger(&*LOGGER)
.map(|()| log::set_max_level(log::LevelFilter::Info))
}

View File

@ -3,4 +3,5 @@ pub(crate) mod sqlite_service;
pub(crate) mod excel_service;
pub(crate) mod ui_service;
pub(crate) mod work_service;
pub(crate) mod log_service;
pub(crate) mod log_service;
pub(crate) mod config_service;

View File

@ -1,21 +1,23 @@
use anyhow::Result;
use serialport::*;
use std::io::prelude::*;
use std::thread::sleep;
use std::time::Duration;
use anyhow::Result;
use serialport::*;
pub struct SerialService {
port: Box<dyn SerialPort>,
}
impl SerialService {
pub fn new(port_name: &str) -> Result<Box<Self>> {
let port = serialport::new(port_name, 115_200)
let port = new(port_name, 115_200)
.timeout(Duration::from_millis(200))
.open()?;
Ok(Box::new(SerialService { port }))
}
#[allow(dead_code)]
pub fn get_ports() -> Result<Vec<String>> {
let portinfos = serialport::available_ports()?;
let mut portnames: Vec<String> = Vec::new();
@ -54,6 +56,10 @@ impl SerialService {
Err(err) => Err(err)
};
}
#[allow(dead_code)]
pub fn close_serial(&mut self)->Result<()>{
todo!()
}
}
#[test]

View File

@ -1,7 +1,8 @@
use anyhow::Result;
use crate::models::work_model::*;
use rusqlite::{Connection};
use chrono::Local;
use rusqlite::Connection;
use crate::models::work_model::*;
pub(crate) struct SqliteService {
conn: Connection,
@ -19,7 +20,9 @@ impl Default for SqliteService {
impl SqliteService {
pub fn new(conn_str: String) -> Result<Self> {
let conn = Connection::open(conn_str)?;
Ok(Self { conn })
let this = Self { conn };
this.create_table()?;
return Ok(this);
}
pub fn create_table(&self) -> Result<()> {
@ -45,20 +48,14 @@ impl SqliteService {
Ok(())
}
pub fn check_sn(&self, sn: &str) -> Result<bool> {
pub fn check_sn_has_insert(&self, sn: &str) -> Result<bool> {
let mut stmt = self.conn.prepare("SELECT sn FROM write_results WHERE sn = ?1")?;
let mut rows = stmt.query(&[&sn])?;
Ok(rows.next()?.is_some())
}
pub fn check_imei(&self, imei: &str) -> Result<bool> {
let mut stmt = self.conn.prepare("SELECT sn FROM write_results WHERE sn = ?1")?;
pub fn check_imei_has_insert(&self, imei: &str) -> Result<bool> {
let mut stmt = self.conn.prepare("SELECT imei FROM write_results WHERE imei = ?1")?;
let mut rows = stmt.query(&[&imei])?;
Ok(rows.next()?.is_some())
}
}
#[test]
fn create_sql_service_test() {
let service = SqliteService::default();
}

View File

@ -1,9 +1,12 @@
use std::cell::RefCell;
use std::path::Path;
use std::process;
use std::rc::Rc;
use fltk::prelude::{InputExt, WidgetExt};
use crate::models::work_model::WorkModel;
use crate::services::excel_service::ExcelService;
use crate::services::serial_service::SerialService;
use std::sync::{Arc, Mutex};
use fltk::prelude::{DisplayExt, InputExt, MenuExt, WidgetExt};
use fltk::text::TextBuffer;
use crate::services::log_service::LOGGER;
use crate::services::work_service::WorkService;
use crate::ui::main_ui;
use crate::ui::main_ui::*;
@ -11,27 +14,106 @@ use crate::ui::main_ui::*;
pub(crate) struct UiService {
ui: Rc<RefCell<UserInterface>>,
work_service: Rc<RefCell<WorkService>>,
current_com: String
}
impl UiService {
pub(crate) fn new() -> Self {
// Create the Rc<RefCell<>> instances
let ui = Rc::new(RefCell::new(main_ui::UserInterface::make_window()));
UiService { ui, work_service: Rc::new(RefCell::new(WorkService::new())), current_com: "".to_string() }
let work_service_rc = Rc::new(RefCell::new(WorkService::new()));
// Clone the Rc<RefCell<>> to avoid moving them
let work_service_clone = Rc::clone(&work_service_rc);
let current_com;
let should_current_line;
let comports = WorkService::get_port_names();
for com in comports {
ui.borrow_mut().com_choice.add_choice(&com);
}
{
let work_service = work_service_clone.borrow_mut();
current_com = work_service.config_model.last_com.clone();
should_current_line = work_service.config_model.current_line;
}
if Path::exists(work_service_rc.borrow().config_model.excel_path.as_ref()) {
ui.borrow_mut().excel_file_textbox.set_value(&work_service_rc.borrow().config_model.excel_path);
ui.borrow_mut().current_line_textbox.set_value(&work_service_rc.borrow().config_model.current_line.to_string());
let index = ui.borrow().com_choice.find_index(&current_com);
ui.borrow_mut().com_choice.set_value(index);
work_service_rc.borrow_mut().excel_service.as_mut().unwrap().set_current_line(should_current_line).unwrap();
}
let ui_service = UiService {
ui,
work_service: work_service_rc,
};
ui_service
}
pub(crate) fn init_components(&mut self) {
self.init_excel();
self.init_start_btn();
self.init_set_line_btn();
self.init_log();
self.init_refresh_com_button();
self.init_exit_callback();
}
pub fn init_start_btn(&mut self) {
fn init_refresh_com_button(&mut self) {
let ui_rc = Rc::clone(&self.ui);
let ui_rc_clone = Rc::clone(&ui_rc);
self.ui.borrow_mut().refresh_com_btn.set_callback(move |_| {
let comports = WorkService::get_port_names();
let mut ui = ui_rc.borrow_mut();
ui.com_choice.clear();
for com in comports {
ui.com_choice.add_choice(&com);
}
});
}
fn init_exit_callback(&mut self) {
let work_service_rc = Rc::clone(&self.work_service);
ui_rc.borrow_mut().start_btn.set_callback(move |_| {
self.ui.borrow_mut().main_window.set_callback(move |_| {
let work_service = work_service_rc.borrow_mut();
work_service.save_config();
process::exit(0);
});
}
fn init_set_line_btn(&mut self) {
let ui_rc = Rc::clone(&self.ui);
let work_service_rc = Rc::clone(&self.work_service);
self.ui.borrow_mut().set_lines_btn.set_callback(move |_| {
let ui_clone = ui_rc.clone();
let mut set_dialog = UserInterface1::make_set_line_window();
let work_service_rc_clone = work_service_rc.clone();
set_dialog.ok_btn.set_callback(move |_| {
if let Ok(line) = set_dialog.line_textbox.value().parse::<usize>() {
match work_service_rc_clone.borrow_mut().set_current_line(line) {
Ok(_) => {
ui_clone.borrow_mut().current_line_textbox.set_value(&line.to_string());
}
Err(msg) => fltk::dialog::message_default(&msg)
}
} else {
fltk::dialog::message_default(&"Only Support Number")
}
})
});
}
fn init_start_btn(&mut self) {
let ui_rc_clone = Rc::clone(&self.ui);
let work_service_rc = Rc::clone(&self.work_service);
self.ui.borrow_mut().start_btn.set_callback(move |_| {
let mut ui = ui_rc_clone.borrow_mut();
let mut work_service = work_service_rc.borrow_mut();
if let Some(mut buffer) = ui.log_content.buffer() {
buffer.set_text("");
}
match work_service.excel_service {
None => {
fltk::dialog::message_default("Please Select Excel File First;");
@ -39,44 +121,54 @@ impl UiService {
}
Some(_) => {}
}
let selected_com = ui.com_textbox.value();
let selected_com = ui.com_choice.choice().unwrap();
if let Err(msg) = work_service.init_serial_service(selected_com) {
fltk::dialog::message_default(&msg);
return;
}
if let Some(mut model) = work_service.get_next_work_model() {
if let Some(model) = work_service.get_next_work_model() {
ui.current_write_model.set_value(&format!("{}: {}_{}", &model.line_number, &model.imei, &model.sn));
match work_service.write_and_check(model) {
Ok(_) => {}
Err(msg) => { fltk::dialog::message_default(&msg) }
Ok(_) => {
let current_line = &work_service.get_current_line().to_string();
ui.current_line_textbox.set_value(current_line);
}
Err(msg) => {
work_service.roll_back();
fltk::dialog::message_default(&msg)
}
}
}
});
}
fn init_log(&mut self) {
let text_buffer = TextBuffer::default();
self.ui.borrow_mut().log_content.set_buffer(text_buffer.clone());
let text_buffer_arc = Arc::new(Mutex::new(text_buffer));
LOGGER.set_callback(move |msg| {
let mut text_buffer = text_buffer_arc.lock().unwrap();
text_buffer.append(&msg);
});
}
fn init_excel(&mut self) {
let ui_rc = Rc::new(&self.ui);
let ui_clone = Rc::clone(&self.ui);
let ui_rc = Rc::clone(&self.ui);
let work_service_clone = Rc::clone(&self.work_service);
ui_rc.borrow_mut().select_file_btn.set_callback(move |_| {
let mut ui = ui_clone.borrow_mut();
self.ui.borrow_mut().select_file_btn.set_callback(move |_| {
let mut ui = ui_rc.borrow_mut();
let mut work_service = work_service_clone.borrow_mut();
let mut dialog = fltk::dialog::FileDialog::new(fltk::dialog::FileDialogType::BrowseFile);
dialog.show();
let binding = dialog.filename();
let path = binding.to_str().unwrap();
let current_line = &work_service.get_current_line().to_string();
if path == "" { return; }
work_service.init_excel_service(path.to_string()).unwrap();
let current_line = &work_service.get_current_line().to_string();
work_service.config_model.excel_path = path.to_string();
work_service.config_model.current_line = current_line.parse::<usize>().unwrap();
ui.excel_file_textbox.set_value(path);
ui.current_line_textbox.set_value(current_line);
});
}
fn init_serial_service(&mut self) {
let port_name = self.ui.borrow().com_textbox.value();
match self.work_service.borrow_mut().init_serial_service(port_name) {
Ok(_) => {}
Err(_) => { fltk::dialog::message_default("Cant Open serial") }
}
}
}

View File

@ -1,21 +1,34 @@
use std::ptr::null;
use anyhow::Result;
use crate::models::config_model::ConfigModel;
use crate::models::work_model::WorkModel;
use crate::services::{serial_service::SerialService, sqlite_service::SqliteService, excel_service::ExcelService};
use crate::services::{config_service::ConfigService, excel_service::ExcelService, serial_service::SerialService, sqlite_service::SqliteService};
use crate::services::log_service::{init_logger};
pub struct WorkService {
pub(crate) serial_service: Option<SerialService>,
sqlite_service: SqliteService,
pub(crate) excel_service: Option<ExcelService>,
sqlite_service: SqliteService,
pub(crate) config_model: ConfigModel,
}
impl WorkService {
pub fn new() -> Self {
WorkService {
init_logger().unwrap();
let config = WorkService::load_config().unwrap_or_else(|_| ConfigModel::default());
let mut this = WorkService {
serial_service: Option::None,
sqlite_service: SqliteService::new("data.db".to_string()).unwrap(),
excel_service: Option::None,
config_model: config,
};
if this.config_model.excel_path != "" {
this.init_excel_service(this.config_model.excel_path.clone()).unwrap();
}
return this;
}
#[allow(dead_code)]
pub fn get_port_names() -> Vec<String> {
//maybe should not use unwrap
return SerialService::get_ports().unwrap();
@ -24,6 +37,7 @@ impl WorkService {
pub fn init_excel_service(&mut self, path: String) -> Result<bool, String> {
return if let Ok(excel_service) = ExcelService::new(&path) {
self.excel_service = Some(excel_service);
self.config_model.excel_path = path;
Ok(true)
} else {
Err("Cant Init ExcelService".to_string())
@ -31,7 +45,51 @@ impl WorkService {
}
pub fn write_and_check(&mut self, model: WorkModel) -> Result<bool, String> {
fltk::dialog::message_default(&format!("{}:{}:{}", model.imei, model.sn, model.line_number));
log::info!("Write Start");
match self.sqlite_service.check_imei_has_insert(&model.imei) {
Ok(true) => {
log::error!("Imei Exist: {}", model.imei);
self.serial_service = None;
return Err("Imei Exist".to_string());
}
_ => {log::info!("Check Imei From Db Pass")}
}
match self.sqlite_service.check_sn_has_insert(&model.sn) {
Ok(true) => {
log::error!("Imei Exist: {}", model.sn);
self.serial_service = None;
return Err("Imei Exist".to_string());
}
_ => {log::info!("Check Sn From Db Pass")}
}
if let Err(msg) = self.write_imei(model.imei.clone()) {
log::error!("Write Imei Error:{}", msg);
return Err(msg);
}
log::info!("Write Imei:{}", model.imei);
if let Err(msg) = self.write_sn(model.sn.clone()) {
log::error!("Write Sn Error:{}", msg);
return Err(msg);
}
log::info!("Write Imei:{}", model.sn);
log::info!("Write End");
log::info!("Check Start");
if let Err(msg) = self.check_imei(&model.imei) {
log::error!("Check Imei Error:{}", msg);
return Err(msg);
}
log::info!("Check Imei:{}", model.imei);
if let Err(msg) = self.check_sn(&model.sn) {
log::error!("Check Sn Error:{}", msg);
return Err(msg);
}
log::info!("Check Sn:{}", model.sn);
log::info!("Check End");
self.serial_service = None;
match self.sqlite_service.insert(model) {
Err(msg) => { log::error!("Insert Error:{}",msg); }
_ => {}
}
Ok(true)
}
@ -42,15 +100,31 @@ impl WorkService {
return 0;
}
pub fn set_current_line(&mut self, line: usize) -> Result<(), String> {
if let Some(service) = &mut self.excel_service {
return service.set_current_line(line);
} else {
Err("Cant Set Line".to_string())
}
}
pub fn get_next_work_model(&mut self) -> Option<WorkModel> {
if let Some(service) = &mut self.excel_service {
if let Ok(model) = service.get_next_work_model() {
self.config_model.current_line = service.get_current_line();
return Some(model);
} else { return None; }
}
return None;
}
pub fn roll_back(&mut self) {
return if let Some(service) = &mut self.excel_service {
service.roll_back();
};
}
#[allow(dead_code)]
pub fn init_sqlite_service(&mut self, db_path: String) -> Result<bool, String> {
return if let Ok(sqlite_service) = SqliteService::new(db_path) {
self.sqlite_service = sqlite_service;
@ -63,6 +137,7 @@ impl WorkService {
pub fn init_serial_service(&mut self, port_name: String) -> Result<bool, String> {
return if let Ok(serial_service) = SerialService::new(&port_name) {
self.serial_service = Some(*serial_service);
self.config_model.last_com = port_name;
Ok(true)
} else {
Err("Cant Init SerialService".to_string())
@ -99,4 +174,12 @@ impl WorkService {
}
return Err("Cant Init ServialService".to_string());
}
pub fn save_config(&self) {
ConfigService::save_config(self.config_model.clone()).unwrap()
}
fn load_config() -> Result<ConfigModel> {
return ConfigService::load_config();
}
}

68
src/ui/main.fl Normal file
View File

@ -0,0 +1,68 @@
# data file for the Fltk User Interface Designer (fluid)
version 1.0400
header_name {.h}
code_name {.cxx}
class UserInterface {open
} {
Function {make_window()} {open
} {
Fl_Window main_window {
label WriteTool_RS open
xywh {721 399 693 467} type Double visible
} {
Fl_Output current_write_model {
label LastImeiAndSn
xywh {135 107 375 35}
}
Fl_Output excel_file_textbox {
label {CurrentExcelFile:}
xywh {135 20 375 35}
}
Fl_Button select_file_btn {
label SelectFile
xywh {535 20 90 30}
}
Fl_Output current_line_textbox {
label CurrentLine
xywh {135 62 375 35}
}
Fl_Button set_lines_btn {
label SetLines
xywh {535 60 90 30}
}
Fl_Button start_btn {
label Start
xywh {532 156 138 35} shortcut 0xff0d color 51 labeltype ENGRAVED_LABEL labelsize 18
}
Fl_Text_Display log_content {selected
xywh {10 210 670 250}
}
Fl_Choice com_choice {
label {Com:} open
xywh {135 156 235 35} down_box BORDER_BOX
} {}
Fl_Button refresh_com_btn {
label Refresh
xywh {390 156 120 35}
}
}
}
}
class UserInterface1 {open
} {
Function {make_set_line_window()} {} {
Fl_Window {} {open
xywh {1171 275 228 43} type Double visible
} {
Fl_Input line_textbox {
label {Line:}
xywh {40 5 130 35}
}
Fl_Button ok_btn {
label ok
xywh {175 5 45 35}
}
}
}
}

Binary file not shown.