Skip to content

Commit

Permalink
support debug_page to view raw CAN data and be able to send CAN packet (
Browse files Browse the repository at this point in the history
#8)

* first commit for debug page feature

* remove all threads when the app is closed

* reduce CPU load by adding sleep time

* using close event from slint to force exit the app

* only 1 task handles CAN data and upgrade privilege-rs

* receive can frame from beginning

* update UI for transmit CAN packets

* support send CAN packet from user inputs

* Add checking CAN ID and CAN data input of transmission

* fixed size of CAN input text to 200px

* support debug page for windows also

* disable filter check when in another page

* also verify the id when toggling checkbox
  • Loading branch information
TuEmb committed Sep 20, 2024
1 parent 4cc939f commit 6622d9d
Show file tree
Hide file tree
Showing 14 changed files with 681 additions and 125 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ winapi = { version = "0.3.9", features = ["winuser"] }
pcan-basic = { git = "https://github.com/TuEmb/pcan-basic.git", branch="main"}

[target.'cfg(unix)'.dependencies]
privilege-rs = "0.1.2"
privilege-rs = "0.1.3"
socketcan = { git = "https://github.com/socketcan-rs/socketcan-rs.git", rev="e0d7760eca8085b247f37ea22f0aa41e00fa25fa", features = ["enumerate"] }

[build-dependencies]
Expand Down
289 changes: 202 additions & 87 deletions src/event_handler/can_handler.rs

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions src/event_handler/debug.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::{
collections::HashMap,
rc::Rc,
sync::mpsc::{self, Receiver},
time::Duration,
};

use crate::slint_generatedAppWindow::{raw_can, AppWindow};
use chrono::Local;
#[cfg(target_os = "windows")]
use pcan_basic::socket::CanFrame;
use slint::{Model, SharedString, VecModel, Weak};
#[cfg(target_os = "linux")]
use socketcan::{CanFrame, EmbeddedFrame, Frame};

const MAX_LEN: usize = 1000;
pub struct DebugHandler<'a> {
pub ui_handle: &'a Weak<AppWindow>,
pub bitrate: String,
pub filter: (u32, u32),
pub can_rx: Receiver<CanFrame>,
}

impl<'a> DebugHandler<'a> {
pub fn run(&mut self) {
let (tx, rx) = mpsc::channel();
let mut debug_enable = false;
let tx_clone = tx.clone();
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
ui.on_change_state(move |state| {
let _ = tx_clone.send(state);
});
});
loop {
if let Ok(en) = rx.try_recv() {
debug_enable = en;
}
if debug_enable {
if let Ok(frame) = self.can_rx.try_recv() {
#[cfg(target_os = "windows")]
let frame_id = frame.can_id() & !0x80000000;
#[cfg(target_os = "linux")]
let frame_id = frame.raw_id() & !0x80000000;
if frame_id >= self.filter.0 && frame_id <= self.filter.1 {
let bitrate = self.bitrate().unwrap();
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
ui.set_bitrate(bitrate as i32);
let raw_data = ui.get_raw_data();
let mut vec_data = Vec::default();
for data in raw_data.iter() {
vec_data.push(data);
}
if vec_data.len() > MAX_LEN {
vec_data.remove(MAX_LEN);
}
vec_data.insert(
0,
raw_can {
time: SharedString::from(
Local::now().to_string().replace('"', "").to_string(),
),
data: SharedString::from(format!("{:?}", frame.data())),
id: SharedString::from(format!("0x{:08X}", frame_id)),
#[cfg(target_os = "linux")]
len: frame.len() as i32,
#[cfg(target_os = "windows")]
len: frame.dlc() as i32,
},
);
let message_vec: Rc<VecModel<raw_can>> =
Rc::new(VecModel::from(vec_data));
ui.set_raw_data(message_vec.into());
});
}
} else {
std::thread::sleep(Duration::from_millis(1));
}
} else {
std::thread::sleep(Duration::from_millis(50));
}
}
}

fn bitrate(&self) -> Option<u32> {
let bitrate_map: HashMap<&str, u32> = [
("1 Mbit/s", 1_000_000),
("800 kbit/s", 800_000),
("500 kbit/s", 500_000),
("250 kbit/s", 250_000),
("125 kbit/s", 125_000),
("100 kbit/s", 100_000),
("95.238 kbit/s", 95_238),
("83.333 kbit/s", 83_333),
("50 kbit/s", 50_000),
("47.619 kbit/s", 47_619),
("33.333 kbit/s", 33_333),
("20 kbit/s", 20_000),
("10 kbit/s", 10_000),
("5 kbit/s", 5_000),
]
.iter()
.cloned()
.collect();

bitrate_map.get(self.bitrate.as_str()).copied()
}
}
File renamed without changes.
11 changes: 3 additions & 8 deletions src/event_handler/init.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::slint_generatedAppWindow::{socket_info, AppWindow};
#[cfg(target_os = "windows")]
use pcan_basic::hw::attached_channels as available_interfaces;
use slint::{ComponentHandle, ModelRc, SharedString, VecModel, Weak};
use slint::{ModelRc, SharedString, VecModel, Weak};
#[cfg(target_os = "linux")]
use socketcan::available_interfaces;
use std::{process::exit, time::Duration};
use std::time::Duration;
pub struct Init<'a> {
pub ui_handle: &'a Weak<AppWindow>,
}
Expand Down Expand Up @@ -95,12 +95,7 @@ impl<'a> Init<'a> {
});
}
};
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
if !ui.window().is_visible() {
exit(1);
}
});
std::thread::sleep(Duration::from_micros(50));
std::thread::sleep(Duration::from_millis(50));
}
}
}
6 changes: 4 additions & 2 deletions src/event_handler/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
pub(crate) mod can_handler;
pub(crate) mod dbc_file;
pub(crate) mod debug;
pub(crate) mod filter;
pub(crate) mod init;
pub(crate) mod packet_filter;

pub use can_handler::CanHandler;
pub use dbc_file::DBCFile;
pub use debug::DebugHandler;
pub use filter::PacketFilter;
pub use init::Init;
pub use packet_filter::PacketFilter;
#[cfg(target_os = "windows")]
use pcan_basic::socket::Baudrate;
use slint::Color;
Expand Down
88 changes: 67 additions & 21 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use std::sync::{Arc, Mutex};

mod event_handler;
use can_dbc::DBC;
use event_handler::{CanHandler, DBCFile, Init, PacketFilter};
use event_handler::{CanHandler, DBCFile, DebugHandler, Init, PacketFilter};
#[cfg(target_os = "windows")]
use pcan_basic::{bus::UsbBus, socket::usb::UsbCanSocket};
use pcan_basic::bus::UsbBus;
#[cfg(target_os = "linux")]
use privilege_rs::privilege_request;
#[cfg(target_os = "windows")]
Expand All @@ -20,7 +20,10 @@ slint::include_modules!();
#[tokio::main]
async fn main() -> io::Result<()> {
#[cfg(target_os = "linux")]
privilege_request();
if privilege_request()? == privilege_rs::Privilege::User {
println!("Failed to request the privilege");
std::process::exit(0);
}
let ui = AppWindow::new().unwrap();

let (tx, rx) = mpsc::channel::<DBC>();
Expand All @@ -40,24 +43,25 @@ async fn main() -> io::Result<()> {
init_event.run();
});

let (start_tx, start_rx) = mpsc::channel();
let (start_tx_1, start_rx_1) = mpsc::channel();
let (start_tx_2, start_rx_2) = mpsc::channel();

// Handle start event
let ui_handle = ui.as_weak();
ui.on_start(move |_name, _index, bitrate| {
// start_tx.send((_name, _index));
#[cfg(target_os = "linux")]
{
let ui = ui_handle.unwrap();
if _name.is_empty() {
ui.set_init_string(SharedString::from("No device found!!!"));
} else {
ui.set_is_init(true);
let _ = start_tx.send((_name, bitrate));
let _ = start_tx_1.send((_name.clone(), bitrate.clone()));
let _ = start_tx_2.send((_name, bitrate));
}
}
#[cfg(target_os = "windows")]
{
use event_handler::p_can_bitrate;
let ui = ui_handle.unwrap();
let get_device_handle = match ui.get_can_sockets().index.row_data(_index as usize) {
Some(device) => device,
Expand All @@ -67,25 +71,16 @@ async fn main() -> io::Result<()> {
}
};
let usb_can = UsbBus::try_from(get_device_handle as u16).unwrap();
let ui_handle = ui.as_weak();
let baudrate = p_can_bitrate(&bitrate).unwrap();
match UsbCanSocket::open(usb_can, baudrate) {
Ok(socket) => {
ui_handle.unwrap().set_is_init(true);
let _ = start_tx.send((socket, bitrate));
}
Err(e) => {
ui_handle
.unwrap()
.set_init_string(SharedString::from(format!("Failed to start: {:?}", e)));
}
}
let _ = start_tx_1.send((usb_can, bitrate.clone()));
let _ = start_tx_2.send((usb_can, bitrate));
ui.set_is_init(true);
}
});

let (can_tx, can_rx) = mpsc::channel();
let ui_handle = ui.as_weak();
tokio::spawn(async move {
if let Ok((can_if, bitrate)) = start_rx.recv() {
if let Ok((can_if, bitrate)) = start_rx_1.recv() {
let mut can_handler = CanHandler {
#[cfg(target_os = "windows")]
iface: can_if,
Expand All @@ -94,13 +89,30 @@ async fn main() -> io::Result<()> {
ui_handle: &ui_handle,
mspc_rx: &rx,
bitrate: bitrate.to_string(),
dbc: None,
can_tx,
};
loop {
can_handler.process_can_messages();
}
}
});

let ui_handle = ui.as_weak();
tokio::spawn(async move {
if let Ok((_can_if, bitrate)) = start_rx_2.recv() {
let mut can_handler = DebugHandler {
ui_handle: &ui_handle,
bitrate: bitrate.to_string(),
filter: (0, 0xFFFFFFFF),
can_rx,
};
loop {
can_handler.run();
}
}
});

// Handle open file event
let ui_handle = ui.as_weak();
ui.on_open_dbc_file(move || {
Expand All @@ -122,6 +134,40 @@ async fn main() -> io::Result<()> {
};
packet_filter.process_filter();
});

ui.window().on_close_requested(|| {
println!("Closing the application...");
std::process::exit(0);
});

ui.on_can_id_check_string(move |is_extended, can_id| is_valid_can_id(is_extended, &can_id));

ui.on_can_data_check_string(move |can_data| is_valid_can_data(&can_data));

ui.run().unwrap();
Ok(())
}

fn is_valid_can_id(is_extended: bool, can_id: &str) -> bool {
// Try to parse the string as a hex number
match u32::from_str_radix(can_id, 16) {
Ok(id) => {
if is_extended {
id <= 0x1FFFFFFF // Extended CAN ID (29-bit max)
} else {
id <= 0x7FF // Standard CAN ID (11-bit max)
}
}
Err(_) => false, // If parsing fails, it's not a valid hex string
}
}

fn is_valid_can_data(can_data: &str) -> bool {
// CAN data is valid if it's a hex string of even length up to 16 characters (8 bytes)
if can_data.len() % 2 != 0 || can_data.len() > 16 {
return false;
}

// Try to parse the data as hex
can_data.chars().all(|c| c.is_ascii_hexdigit())
}
29 changes: 28 additions & 1 deletion ui/app.slint
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,31 @@ import { viewPage } from "view_page.slint";
import { filterPage } from "filter_page.slint";
import { selectPage } from "page_selection.slint";
import { initPage, socket_info } from "init_page.slint";
import { raw_can, debugPage } from "debug_page.slint";

export component AppWindow inherits Window {
in property <bool> is_filter: false;
in property <bool> is_new_dbc: false;
in property <bool> is_first_open: true;
in property <bool> is_init: false;
out property <bool> is_debug_en: false;
in property <string> init_string: "Please select CAN device to start";
in property <socket_info> can_sockets;
in property <[CanData]> messages;
in property <[CanData]> filter_messages;
in-out property <string> state;
in-out property <int> bus_load;
in-out property <int> bitrate;
in property <[raw_can]> raw_data;

in-out property <int> active-page: 0;

callback open_dbc_file();
callback filter_id(CanData, bool);
callback start(string, int, string);
callback can_transmit(bool, string, string);
callback can_id_check_string(bool, string) -> bool;
callback can_data_check_string(string) -> bool;
callback change_state(bool);
title: @tr("CAN VIEWER (version 0.2.2)");
icon: @image-url("images/can_viewer_128px.png");
background: #1a1f2b;
Expand Down Expand Up @@ -110,6 +116,27 @@ export component AppWindow inherits Window {
open_dbc_file()
}
}
if root.active-page == 2:
debugPage {
en: is_debug_en;
state: state;
bus_load: bus_load;
bitrate: bitrate;
raw_data: raw_data;
change_state(en) => {
is_debug_en = en;
change_state(en);
}
can_transmit(is_extended, can_id, can_data) => {
can_transmit(is_extended, can_id, can_data)
}
can_id_check_string(is_extended, id) => {
can_id_check_string(is_extended, id)
}
can_data_check_string(data) => {
can_data_check_string(data)
}
}
}
}
}
Loading

0 comments on commit 6622d9d

Please sign in to comment.