下载逻辑完成, ui通知逻辑完成,

This commit is contained in:
JIe 2024-12-10 18:03:31 +08:00
parent b39853af95
commit 7f059c7782
5 changed files with 1811 additions and 80 deletions

1353
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,10 +4,18 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
iced = "0.13.1" iced = {version = "0.13.1", features = ["tokio", "debug"]}
anyhow = "1.0.91" anyhow = "1.0.91"
serde = {version = "1.0.213", features = ["derive"]} serde = {version = "1.0.213", features = ["derive"]}
serde_json = "1.0.132" serde_json = "1.0.132"
mysql = "25.0.1"
chrono="0.4.38"
tokio = { version = "1.42", features = ["full"] }
log = "0.4.22"
lazy_static = "1.5.0"
time = "0.3.37"
crossbeam = "0.8"
[package.metadata.windows] [package.metadata.windows]
link-args=["/SUBSYSTEM:WINDOWS"] link-args=["/SUBSYSTEM:WINDOWS"]

249
src/download_wrapper.rs Normal file
View File

@ -0,0 +1,249 @@
use crate::download_wrapper::DownloadError::JlinkNotFindError;
use anyhow::Error;
use std::path::{Path, PathBuf};
use std::process::Command;
pub struct DownloadWrapper {
commander_path: PathBuf,
bin_path: PathBuf,
bootloader_name: String,
app_name: String,
rail_name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DownloadType {
Bootloader,
App,
Rail,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DownloadError {
JlinkNotFindError,
DeviceNotFindError,
EraseError,
DownloadBootloaderError,
DownloadAppError,
DownloadRailError,
}
impl Default for DownloadWrapper {
fn default() -> Self {
Self {
commander_path: PathBuf::from(std::env::current_dir().unwrap())
.join("commander/Simplicity Commander/commander.exe"),
bin_path: PathBuf::from(std::env::current_dir().unwrap()).join("bin/"),
bootloader_name: String::from("RAWM00-2-0-0_silicon-wisun_bootloader_D20241008.s37"),
app_name: String::from("RAWM00-2-0-0_silicon-wisun_APP-V1_D20240927.s37"),
rail_name: String::from("rail_soc_railtest_sisdk.s37"),
}
}
}
impl DownloadWrapper {
pub(crate) fn new() -> Self {
Self::default()
}
pub fn check_jlink(&self) -> Result<Vec<String>, DownloadError> {
let commander_path_str = self.commander_path.to_str().unwrap();
let output = Command::new(commander_path_str)
.arg("adapter")
.arg("list")
.output()
.expect("Failed to execute command");
if output.status.success() {
let result_str = String::from_utf8_lossy(&output.stdout).to_string();
let result = result_str
.split("\r\n")
.collect::<Vec<&str>>();
let device_index_str: Vec<String> = result
.iter()
.filter(|&&str| str.starts_with("deviceCount"))
.map(|&str| str.to_string())
.collect();
if device_index_str.is_empty()
|| device_index_str[0].split("=").collect::<Vec<&str>>()[1] == "0"
{
Err(JlinkNotFindError)
} else {
let device_ids = result
.iter()
.filter(|&&str| str.trim().starts_with("serialNumber"))
.map(|&str| {
let temp = str.to_string();
return temp.split("=").collect::<Vec<&str>>()[1].to_string();
})
.collect::<Vec<String>>();
Ok(device_ids)
}
} else {
Err(JlinkNotFindError)
}
}
pub fn check_device(&self) -> Result<(), DownloadError> {
let commander_path_str = self.commander_path.to_str().unwrap();
let output = Command::new(commander_path_str)
.arg("device")
.arg("info")
.arg("--device")
.arg("Cortex-M4")
.output()
.expect("Failed to execute command");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let result = stdout.split("\r\n").collect::<Vec<&str>>();
let error_log = result
.iter()
.filter(|&&str| str.trim().starts_with("ERROR"))
.collect::<Vec<&&str>>();
if error_log.is_empty() {
Ok(())
} else {
Err(DownloadError::DeviceNotFindError)
}
}
pub fn erase(&self) -> Result<(), DownloadError> {
let commander_path_str = self.commander_path.to_str().unwrap();
let output = Command::new(commander_path_str)
.arg("device")
.arg("masserase")
.arg("--device")
.arg("Cortex-M4")
.output()
.expect("Failed to execute command");
let result_str = String::from_utf8_lossy(&output.stdout).to_string();
let result = result_str
.split("\r\n")
.collect::<Vec<&str>>();
if !result
.iter()
.filter(|&&str| str.trim().starts_with("ERROR"))
.collect::<Vec<&&str>>()
.is_empty()
|| result
.iter()
.filter(|&&str| str.trim().contains("successfully"))
.collect::<Vec<&&str>>()
.is_empty()
{
Err(DownloadError::EraseError)
} else {
Ok(())
}
}
pub fn download(&self, download_type: DownloadType) -> Result<(), DownloadError> {
let mut binding: PathBuf;
match download_type {
DownloadType::Bootloader=> {
binding = self.bin_path.join(&self.bootloader_name);
}
DownloadType::App=>{
binding = self.bin_path.join(&self.app_name);
}
DownloadType::Rail=>{
binding = self.bin_path.join(&self.rail_name);
}
}
let bin_path = binding.to_str().unwrap();
println!("{:?}", bin_path);
let commander_path_str = self.commander_path.to_str().unwrap();
let output = Command::new(commander_path_str)
.arg("flash")
.arg(bin_path)
.arg("--device")
.arg("Cortex-M4")
.output()
.expect("Failed to execute command");
let result_str = String::from_utf8_lossy(&output.stdout).to_string();
let result = result_str
.split("\r\n")
.collect::<Vec<&str>>();
println!("{:?}", result);
if !result
.iter()
.filter(|&&str| str.trim().starts_with("ERROR"))
.collect::<Vec<&&str>>()
.is_empty()
|| result
.iter()
.filter(|&&str| str.trim().contains("successfully"))
.collect::<Vec<&&str>>()
.is_empty()
{
Err(DownloadError::DownloadBootloaderError)
} else {
Ok(())
}
}
pub fn download_app(&self) -> Result<bool, DownloadError> {
todo!()
}
pub fn download_rail(&self) -> Result<bool, DownloadError> {
todo!()
}
}
#[cfg(test)]
mod test {
use crate::download_wrapper::DownloadWrapper;
use crate::download_wrapper::DownloadType;
#[test]
fn test_download_wrapper() {
let dw = DownloadWrapper::new();
println!("{:?}", dw.commander_path);
println!("{:?}", dw.bin_path);
println!("{:?}", dw.bootloader_name);
println!("{:?}", dw.app_name);
println!("{:?}", dw.rail_name);
}
#[test]
fn test_check_jlink() {
let dw = DownloadWrapper::new();
let result = dw.check_jlink();
assert_eq!(result.is_ok(), true);
println!("ids:{:?}", result.unwrap());
}
#[test]
fn test_check_device() {
let dw = DownloadWrapper::new();
let result = dw.check_device();
assert_eq!(result.is_ok(), true);
}
#[test]
fn test_erase_device() {
let dw = DownloadWrapper::new();
let result = dw.erase();
assert_eq!(result.is_ok(), true);
}
#[test]
fn test_download_bootloader(){
let dw = DownloadWrapper::new();
let result = dw.download(DownloadType::Bootloader);
assert_eq!(result.is_ok(), true);
}
#[test]
fn test_download_app(){
let dw = DownloadWrapper::new();
let result = dw.download(DownloadType::App);
assert_eq!(result.is_ok(), true);
}
#[test]
fn test_download_rail(){
let dw = DownloadWrapper::new();
let result = dw.download(DownloadType::Rail);
assert_eq!(result.is_ok(), true);
}
}

View File

@ -1,11 +1,19 @@
mod download_wrapper; mod download_wrapper;
mod mes_service;
use std::any::Any;
use crossbeam::channel;
use std::sync::{Arc, Mutex, LazyLock};
use crossbeam::channel::Sender;
use iced::widget::{button, checkbox, column, container, radio, row, text_editor, text_input}; use iced::widget::{button, checkbox, column, container, radio, row, text_editor, text_input};
use iced::{event, window, Element, Event, Length, Subscription, Task}; use iced::{event, window, Element, Event, Length, Subscription, Task, time};
use iced::application::Title;
use iced::widget::text_editor::Action::Scroll; use iced::widget::text_editor::Action::Scroll;
use iced::widget::text_editor::Edit; use iced::widget::text_editor::Edit;
use download_wrapper::DownloadType;
use crate::mes_service::MesService;
pub fn main() -> iced::Result { pub fn main() -> iced::Result {
iced::application("WisunDownload V0.1", MainWindow::update, MainWindow::view) iced::application("WisunDownload V0.1", MainWindow::update, MainWindow::view)
.subscription(MainWindow::subscription) .subscription(MainWindow::subscription)
@ -14,23 +22,31 @@ pub fn main() ->iced::Result {
.run() .run()
} }
static logs: LazyLock<Arc<Mutex<Vec<String>>>> = LazyLock::new(|| {
Arc::new(Mutex::new(Vec::new()))
});
struct MainWindow { struct MainWindow {
is_online: bool, is_online: bool,
mysql_config: MysqlConfig, mysql_config: MysqlConfig,
selection: Option<DownloadType>, selection: Option<DownloadType>,
label: String, label: String,
log_content: text_editor::Content, log_content: text_editor::Content,
sender: Sender<bool>,
receiver: crossbeam::channel::Receiver<bool>,
} }
impl Default for MainWindow { impl Default for MainWindow {
fn default() -> Self { fn default() -> Self {
let (sender, receiver) = crossbeam::channel::unbounded();
Self { Self {
is_online: true, is_online: true,
mysql_config: MysqlConfig::default(), mysql_config: MysqlConfig::default(),
selection: None, selection: None,
label: String::new(), label: String::new(),
log_content: text_editor::Content::default(), log_content: text_editor::Content::default(),
sender,
receiver
} }
} }
} }
@ -59,32 +75,22 @@ enum Message{
AddLog(String), AddLog(String),
LogContentChanged(text_editor::Action), LogContentChanged(text_editor::Action),
SaveConfig, SaveConfig,
WindowEvent(Event) DownloadEnd(bool),
WindowEvent(Event),
Tick,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub fn add_log<'a>(msg: String) {
enum DownloadType{ let time_now = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
BootLoader, let really_msg = format!("{time_now} [Info]: {msg}");
App, logs.lock().unwrap().push(really_msg);
Cal
} }
impl MainWindow { impl MainWindow {
pub fn add_log(&mut self, msg: String) -> Task<Message>{
self.update(Message::AddLog(msg))
}
fn update(&mut self, message: Message) -> Task<Message> { fn update(&mut self, message: Message) -> Task<Message> {
match message { match message {
Message::Start() => { Message::Start() => {
println!("Start: {:?}", self.mysql_config); self.start(self.sender.clone());
println!("Start: {:?}", self.is_online);
println!("Start: {:?}", self.selection);
self.add_log(self.label.clone());
self.add_log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string());
self.add_log("bbbbbbbbbbbbbbbb".to_string());
self.add_log("cccccccccccccccc".to_string());
println!("{:?}", self.log_content.text());
Task::none() Task::none()
} }
Message::IpChanged(ip) => { Message::IpChanged(ip) => {
@ -132,18 +138,53 @@ impl MainWindow{
self.log_content.perform(Scroll { lines: line_size as i32 }); self.log_content.perform(Scroll { lines: line_size as i32 });
Task::none() Task::none()
} }
Message::LogContentChanged(action)=>{ Message::Tick => {
if action.is_edit(){ for log in logs.lock().unwrap().iter() {
return Task::none() for c in log.chars() {
self.log_content.perform(text_editor::Action::Edit(Edit::Insert(c)))
}
self.log_content.perform(text_editor::Action::Edit(Edit::Enter));
let line_size = self.log_content.lines().count();
self.log_content.perform(Scroll { lines: line_size as i32 });
}
logs.lock().unwrap().clear();
match self.receiver.try_recv() {
Ok(res) => {
self.update(Message::DownloadEnd(res));
}
Err(channel::TryRecvError::Empty) => {
}
Err(channel::TryRecvError::Disconnected) => {
println!("Disconnected");
}
};
Task::none()
}
Message::LogContentChanged(action) => {
match action{
Scroll { lines } => {
self.log_content.perform(Scroll { lines });
Task::none()
}
_=>{
Task::none()
}
}
}
Message::DownloadEnd(res)=>{
if res{
add_log("下载成功 By Ui Thread".to_string());
}else{
add_log("下载失败 By Ui Thread".to_string());
} }
self.log_content.perform(action);
Task::none() Task::none()
} }
Message::WindowEvent(event) => { Message::WindowEvent(event) => {
println!("{:?}", event); //println!("{:?}", event);
if let Event::Window(window::Event::CloseRequested) = event { if let Event::Window(window::Event::CloseRequested) = event {
println!("Request Close"); println!("Request Close");
return window::get_latest().and_then(window::close) return window::get_latest().and_then(window::close);
}; };
Task::none() Task::none()
} }
@ -162,32 +203,32 @@ impl MainWindow{
).on_input(Message::PortChanged); ).on_input(Message::PortChanged);
let username_input = text_input( let username_input = text_input(
"Username", "Username",
&self.mysql_config.username &self.mysql_config.username,
).on_input(Message::UsernameChanged); ).on_input(Message::UsernameChanged);
let password_input = text_input( let password_input = text_input(
"Password", "Password",
&self.mysql_config.password &self.mysql_config.password,
).on_input(Message::PasswordChanged); ).on_input(Message::PasswordChanged);
let database_input = text_input( let database_input = text_input(
"Database", "Database",
&self.mysql_config.database &self.mysql_config.database,
).on_input(Message::DatabaseChanged); ).on_input(Message::DatabaseChanged);
let work_order_input = text_input( let work_order_input = text_input(
"WorkOrder", "WorkOrder",
&self.mysql_config.work_order &self.mysql_config.work_order,
).on_input(Message::WorkOrderChanged); ).on_input(Message::WorkOrderChanged);
let label_input = text_input( let label_input = text_input(
"label", "label",
&self.label &self.label,
).on_input(Message::LabelChanged).on_submit(Message::Start()).width(Length::Fill); ).on_input(Message::LabelChanged).on_submit(Message::Start()).width(Length::Fill);
let content = column![ let content = column![
row![ip_input, port_input].spacing(5), row![ip_input, port_input].spacing(5),
row![username_input, password_input].spacing(5), row![username_input, password_input].spacing(5),
row![database_input, work_order_input].spacing(5), row![database_input, work_order_input].spacing(5),
row![ row![
radio("BootLoader", DownloadType::BootLoader,self.selection,Message::TypeSelected), radio("BootLoader", DownloadType::Bootloader,self.selection,Message::TypeSelected),
radio("App", DownloadType::App,self.selection,Message::TypeSelected), radio("App", DownloadType::App,self.selection,Message::TypeSelected),
radio("Cal", DownloadType::Cal,self.selection,Message::TypeSelected), radio("Cal", DownloadType::Rail,self.selection,Message::TypeSelected),
checkbox("online", self.is_online).on_toggle(Message::OnlineChecked) checkbox("online", self.is_online).on_toggle(Message::OnlineChecked)
].spacing(10), ].spacing(10),
row![label_input,button("Start").on_press(Message::Start())].spacing(10), row![label_input,button("Start").on_press(Message::Start())].spacing(10),
@ -199,6 +240,96 @@ impl MainWindow{
} }
fn subscription(&self) -> Subscription<Message> { fn subscription(&self) -> Subscription<Message> {
event::listen().map(Message::WindowEvent) Subscription::batch(vec![
event::listen().map(Message::WindowEvent),
time::every(time::Duration::from_millis(50))
.map(|_| Message::Tick)
])
}
fn start(&mut self, sender: Sender<bool>) -> (){
let mes_config = self.mysql_config.clone();
let label = self.label.clone();
let download_type: DownloadType = self.selection.unwrap();
let online = self.is_online.clone();
std::thread::spawn(move || {
let mut download_wrapper = download_wrapper::DownloadWrapper::new();
let mut mes_service: MesService;
if online {
add_log("当前为在线模式, 正在连接数据库".to_string());
mes_service = MesService::new(mes_config.ip.clone(), mes_config.port.clone(), mes_config.username.clone(), mes_config.password.clone(), mes_config.database.clone());
add_log("连接成功".to_string());
add_log("正在过站检测".to_string());
let check_result = mes_service.check_station(mes_config.work_order.clone(), label.clone(), download_type);
if let Err(res) = check_result {
add_log(format!("过站检测失败: {:?}", res));
sender.send(false).unwrap();
} else if let Ok(res) = check_result {
if !res {
add_log("过站检测失败".to_string());
sender.send(false).unwrap();
}
add_log("过站检测成功".to_string());
}
}
add_log("正在检查JLink连接".to_string());
if let Err(e) = download_wrapper.check_jlink() {
add_log("JLink检查失败, 请检查是否安装驱动或连接是否异常".to_string());
sender.send(false).unwrap();
}
add_log("JLink检查成功".to_string());
add_log("正在检查设备连接".to_string());
if let Err(e) = download_wrapper.check_device() {
add_log("设备检查失败, 请检查设备是否连接".to_string());
sender.send(false).unwrap();
}
add_log("设备检查成功".to_string());
match download_type {
DownloadType::Bootloader =>{
add_log("正在擦除Flash".to_string());
if let Err(e) = download_wrapper.erase(){
add_log("擦除失败".to_string());
sender.send(false).unwrap();
}
add_log("擦除成功".to_string());
add_log("正在下载BootLoader".to_string());
if let Err(e) = download_wrapper.download(download_type){
add_log("下载BootLoader失败".to_string());
sender.send(false).unwrap();
}
add_log("下载成功".to_string());
}
DownloadType::App =>{
add_log("正在下载App".to_string());
if let Err(e) = download_wrapper.download(download_type){
add_log("下载App失败".to_string());
sender.send(false).unwrap();
}
add_log("下载成功".to_string());
}
DownloadType::Rail=>{
add_log("正在下载Rail".to_string());
if let Err(e) = download_wrapper.download(download_type){
add_log("下载Rail失败".to_string());
sender.send(false).unwrap();
}
add_log("下载成功".to_string());
}
}
return if online {
mes_service = MesService::new(mes_config.ip, mes_config.port, mes_config.username, mes_config.password, mes_config.database);
add_log("正在上传数据".to_string());
let update_result = mes_service.update_station(mes_config.work_order, label, download_type);
if let Err(e) = update_result {
add_log(format!("上传失败: {:?}", e));
sender.send(false).unwrap();
}
sender.send(true).unwrap();
} else {
add_log("当前为离线模式".to_string());
sender.send(true).unwrap();
}
});
} }
} }

View File

@ -2,12 +2,12 @@ use crate::download_wrapper::DownloadType;
use anyhow::Error; use anyhow::Error;
use mysql::prelude::*; use mysql::prelude::*;
struct MesService { pub(crate) struct MesService {
pool: mysql::Pool, pool: mysql::Pool,
} }
impl MesService { impl MesService {
fn new(ip: String, port: String, username: String, password: String, database: String) -> Self { pub fn new(ip: String, port: String, username: String, password: String, database: String) -> Self {
let url = format!( let url = format!(
"mysql://{}:{}@{}:{}/{}", "mysql://{}:{}@{}:{}/{}",
username, password, ip, port, database username, password, ip, port, database
@ -16,14 +16,14 @@ impl MesService {
Self { pool } Self { pool }
} }
fn get_work_orders(&self) -> Result<Vec<String>, Error> { pub fn get_work_orders(&self) -> Result<Vec<String>, Error> {
let mut conn = self.pool.get_conn()?; let mut conn = self.pool.get_conn()?;
let work_orders: Vec<String> = let work_orders: Vec<String> =
conn.query("SELECT `OrderId` FROM wisun_ordertables".to_string())?; conn.query("SELECT `OrderId` FROM wisun_ordertables".to_string())?;
Ok(work_orders) Ok(work_orders)
} }
fn check_station( pub fn check_station(
&self, &self,
work_order: String, work_order: String,
label: String, label: String,
@ -63,7 +63,7 @@ impl MesService {
} }
} }
fn update_station(&self, work_order: String, label: String, download_type: DownloadType) -> Result<(), Error> { pub fn update_station(&self, work_order: String, label: String, download_type: DownloadType) -> Result<(), Error> {
match download_type { match download_type {
DownloadType::Bootloader => { DownloadType::Bootloader => {
let mut conn = self.pool.get_conn()?; let mut conn = self.pool.get_conn()?;