Skip to content

Commit 6622d9d

Browse files
authored
support debug_page to view raw CAN data and be able to send CAN packet (#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
1 parent 4cc939f commit 6622d9d

14 files changed

+681
-125
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ winapi = { version = "0.3.9", features = ["winuser"] }
1717
pcan-basic = { git = "https://github.com/TuEmb/pcan-basic.git", branch="main"}
1818

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

2323
[build-dependencies]

src/event_handler/can_handler.rs

Lines changed: 202 additions & 87 deletions
Large diffs are not rendered by default.

src/event_handler/debug.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
use std::{
2+
collections::HashMap,
3+
rc::Rc,
4+
sync::mpsc::{self, Receiver},
5+
time::Duration,
6+
};
7+
8+
use crate::slint_generatedAppWindow::{raw_can, AppWindow};
9+
use chrono::Local;
10+
#[cfg(target_os = "windows")]
11+
use pcan_basic::socket::CanFrame;
12+
use slint::{Model, SharedString, VecModel, Weak};
13+
#[cfg(target_os = "linux")]
14+
use socketcan::{CanFrame, EmbeddedFrame, Frame};
15+
16+
const MAX_LEN: usize = 1000;
17+
pub struct DebugHandler<'a> {
18+
pub ui_handle: &'a Weak<AppWindow>,
19+
pub bitrate: String,
20+
pub filter: (u32, u32),
21+
pub can_rx: Receiver<CanFrame>,
22+
}
23+
24+
impl<'a> DebugHandler<'a> {
25+
pub fn run(&mut self) {
26+
let (tx, rx) = mpsc::channel();
27+
let mut debug_enable = false;
28+
let tx_clone = tx.clone();
29+
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
30+
ui.on_change_state(move |state| {
31+
let _ = tx_clone.send(state);
32+
});
33+
});
34+
loop {
35+
if let Ok(en) = rx.try_recv() {
36+
debug_enable = en;
37+
}
38+
if debug_enable {
39+
if let Ok(frame) = self.can_rx.try_recv() {
40+
#[cfg(target_os = "windows")]
41+
let frame_id = frame.can_id() & !0x80000000;
42+
#[cfg(target_os = "linux")]
43+
let frame_id = frame.raw_id() & !0x80000000;
44+
if frame_id >= self.filter.0 && frame_id <= self.filter.1 {
45+
let bitrate = self.bitrate().unwrap();
46+
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
47+
ui.set_bitrate(bitrate as i32);
48+
let raw_data = ui.get_raw_data();
49+
let mut vec_data = Vec::default();
50+
for data in raw_data.iter() {
51+
vec_data.push(data);
52+
}
53+
if vec_data.len() > MAX_LEN {
54+
vec_data.remove(MAX_LEN);
55+
}
56+
vec_data.insert(
57+
0,
58+
raw_can {
59+
time: SharedString::from(
60+
Local::now().to_string().replace('"', "").to_string(),
61+
),
62+
data: SharedString::from(format!("{:?}", frame.data())),
63+
id: SharedString::from(format!("0x{:08X}", frame_id)),
64+
#[cfg(target_os = "linux")]
65+
len: frame.len() as i32,
66+
#[cfg(target_os = "windows")]
67+
len: frame.dlc() as i32,
68+
},
69+
);
70+
let message_vec: Rc<VecModel<raw_can>> =
71+
Rc::new(VecModel::from(vec_data));
72+
ui.set_raw_data(message_vec.into());
73+
});
74+
}
75+
} else {
76+
std::thread::sleep(Duration::from_millis(1));
77+
}
78+
} else {
79+
std::thread::sleep(Duration::from_millis(50));
80+
}
81+
}
82+
}
83+
84+
fn bitrate(&self) -> Option<u32> {
85+
let bitrate_map: HashMap<&str, u32> = [
86+
("1 Mbit/s", 1_000_000),
87+
("800 kbit/s", 800_000),
88+
("500 kbit/s", 500_000),
89+
("250 kbit/s", 250_000),
90+
("125 kbit/s", 125_000),
91+
("100 kbit/s", 100_000),
92+
("95.238 kbit/s", 95_238),
93+
("83.333 kbit/s", 83_333),
94+
("50 kbit/s", 50_000),
95+
("47.619 kbit/s", 47_619),
96+
("33.333 kbit/s", 33_333),
97+
("20 kbit/s", 20_000),
98+
("10 kbit/s", 10_000),
99+
("5 kbit/s", 5_000),
100+
]
101+
.iter()
102+
.cloned()
103+
.collect();
104+
105+
bitrate_map.get(self.bitrate.as_str()).copied()
106+
}
107+
}
File renamed without changes.

src/event_handler/init.rs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use crate::slint_generatedAppWindow::{socket_info, AppWindow};
22
#[cfg(target_os = "windows")]
33
use pcan_basic::hw::attached_channels as available_interfaces;
4-
use slint::{ComponentHandle, ModelRc, SharedString, VecModel, Weak};
4+
use slint::{ModelRc, SharedString, VecModel, Weak};
55
#[cfg(target_os = "linux")]
66
use socketcan::available_interfaces;
7-
use std::{process::exit, time::Duration};
7+
use std::time::Duration;
88
pub struct Init<'a> {
99
pub ui_handle: &'a Weak<AppWindow>,
1010
}
@@ -95,12 +95,7 @@ impl<'a> Init<'a> {
9595
});
9696
}
9797
};
98-
let _ = self.ui_handle.upgrade_in_event_loop(move |ui| {
99-
if !ui.window().is_visible() {
100-
exit(1);
101-
}
102-
});
103-
std::thread::sleep(Duration::from_micros(50));
98+
std::thread::sleep(Duration::from_millis(50));
10499
}
105100
}
106101
}

src/event_handler/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
pub(crate) mod can_handler;
22
pub(crate) mod dbc_file;
3+
pub(crate) mod debug;
4+
pub(crate) mod filter;
35
pub(crate) mod init;
4-
pub(crate) mod packet_filter;
56

67
pub use can_handler::CanHandler;
78
pub use dbc_file::DBCFile;
9+
pub use debug::DebugHandler;
10+
pub use filter::PacketFilter;
811
pub use init::Init;
9-
pub use packet_filter::PacketFilter;
1012
#[cfg(target_os = "windows")]
1113
use pcan_basic::socket::Baudrate;
1214
use slint::Color;

src/main.rs

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ use std::sync::{Arc, Mutex};
44

55
mod event_handler;
66
use can_dbc::DBC;
7-
use event_handler::{CanHandler, DBCFile, Init, PacketFilter};
7+
use event_handler::{CanHandler, DBCFile, DebugHandler, Init, PacketFilter};
88
#[cfg(target_os = "windows")]
9-
use pcan_basic::{bus::UsbBus, socket::usb::UsbCanSocket};
9+
use pcan_basic::bus::UsbBus;
1010
#[cfg(target_os = "linux")]
1111
use privilege_rs::privilege_request;
1212
#[cfg(target_os = "windows")]
@@ -20,7 +20,10 @@ slint::include_modules!();
2020
#[tokio::main]
2121
async fn main() -> io::Result<()> {
2222
#[cfg(target_os = "linux")]
23-
privilege_request();
23+
if privilege_request()? == privilege_rs::Privilege::User {
24+
println!("Failed to request the privilege");
25+
std::process::exit(0);
26+
}
2427
let ui = AppWindow::new().unwrap();
2528

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

43-
let (start_tx, start_rx) = mpsc::channel();
46+
let (start_tx_1, start_rx_1) = mpsc::channel();
47+
let (start_tx_2, start_rx_2) = mpsc::channel();
48+
4449
// Handle start event
4550
let ui_handle = ui.as_weak();
4651
ui.on_start(move |_name, _index, bitrate| {
47-
// start_tx.send((_name, _index));
4852
#[cfg(target_os = "linux")]
4953
{
5054
let ui = ui_handle.unwrap();
5155
if _name.is_empty() {
5256
ui.set_init_string(SharedString::from("No device found!!!"));
5357
} else {
5458
ui.set_is_init(true);
55-
let _ = start_tx.send((_name, bitrate));
59+
let _ = start_tx_1.send((_name.clone(), bitrate.clone()));
60+
let _ = start_tx_2.send((_name, bitrate));
5661
}
5762
}
5863
#[cfg(target_os = "windows")]
5964
{
60-
use event_handler::p_can_bitrate;
6165
let ui = ui_handle.unwrap();
6266
let get_device_handle = match ui.get_can_sockets().index.row_data(_index as usize) {
6367
Some(device) => device,
@@ -67,25 +71,16 @@ async fn main() -> io::Result<()> {
6771
}
6872
};
6973
let usb_can = UsbBus::try_from(get_device_handle as u16).unwrap();
70-
let ui_handle = ui.as_weak();
71-
let baudrate = p_can_bitrate(&bitrate).unwrap();
72-
match UsbCanSocket::open(usb_can, baudrate) {
73-
Ok(socket) => {
74-
ui_handle.unwrap().set_is_init(true);
75-
let _ = start_tx.send((socket, bitrate));
76-
}
77-
Err(e) => {
78-
ui_handle
79-
.unwrap()
80-
.set_init_string(SharedString::from(format!("Failed to start: {:?}", e)));
81-
}
82-
}
74+
let _ = start_tx_1.send((usb_can, bitrate.clone()));
75+
let _ = start_tx_2.send((usb_can, bitrate));
76+
ui.set_is_init(true);
8377
}
8478
});
8579

80+
let (can_tx, can_rx) = mpsc::channel();
8681
let ui_handle = ui.as_weak();
8782
tokio::spawn(async move {
88-
if let Ok((can_if, bitrate)) = start_rx.recv() {
83+
if let Ok((can_if, bitrate)) = start_rx_1.recv() {
8984
let mut can_handler = CanHandler {
9085
#[cfg(target_os = "windows")]
9186
iface: can_if,
@@ -94,13 +89,30 @@ async fn main() -> io::Result<()> {
9489
ui_handle: &ui_handle,
9590
mspc_rx: &rx,
9691
bitrate: bitrate.to_string(),
92+
dbc: None,
93+
can_tx,
9794
};
9895
loop {
9996
can_handler.process_can_messages();
10097
}
10198
}
10299
});
103100

101+
let ui_handle = ui.as_weak();
102+
tokio::spawn(async move {
103+
if let Ok((_can_if, bitrate)) = start_rx_2.recv() {
104+
let mut can_handler = DebugHandler {
105+
ui_handle: &ui_handle,
106+
bitrate: bitrate.to_string(),
107+
filter: (0, 0xFFFFFFFF),
108+
can_rx,
109+
};
110+
loop {
111+
can_handler.run();
112+
}
113+
}
114+
});
115+
104116
// Handle open file event
105117
let ui_handle = ui.as_weak();
106118
ui.on_open_dbc_file(move || {
@@ -122,6 +134,40 @@ async fn main() -> io::Result<()> {
122134
};
123135
packet_filter.process_filter();
124136
});
137+
138+
ui.window().on_close_requested(|| {
139+
println!("Closing the application...");
140+
std::process::exit(0);
141+
});
142+
143+
ui.on_can_id_check_string(move |is_extended, can_id| is_valid_can_id(is_extended, &can_id));
144+
145+
ui.on_can_data_check_string(move |can_data| is_valid_can_data(&can_data));
146+
125147
ui.run().unwrap();
126148
Ok(())
127149
}
150+
151+
fn is_valid_can_id(is_extended: bool, can_id: &str) -> bool {
152+
// Try to parse the string as a hex number
153+
match u32::from_str_radix(can_id, 16) {
154+
Ok(id) => {
155+
if is_extended {
156+
id <= 0x1FFFFFFF // Extended CAN ID (29-bit max)
157+
} else {
158+
id <= 0x7FF // Standard CAN ID (11-bit max)
159+
}
160+
}
161+
Err(_) => false, // If parsing fails, it's not a valid hex string
162+
}
163+
}
164+
165+
fn is_valid_can_data(can_data: &str) -> bool {
166+
// CAN data is valid if it's a hex string of even length up to 16 characters (8 bytes)
167+
if can_data.len() % 2 != 0 || can_data.len() > 16 {
168+
return false;
169+
}
170+
171+
// Try to parse the data as hex
172+
can_data.chars().all(|c| c.is_ascii_hexdigit())
173+
}

ui/app.slint

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,31 @@ import { viewPage } from "view_page.slint";
77
import { filterPage } from "filter_page.slint";
88
import { selectPage } from "page_selection.slint";
99
import { initPage, socket_info } from "init_page.slint";
10+
import { raw_can, debugPage } from "debug_page.slint";
1011

1112
export component AppWindow inherits Window {
1213
in property <bool> is_filter: false;
1314
in property <bool> is_new_dbc: false;
14-
in property <bool> is_first_open: true;
1515
in property <bool> is_init: false;
16+
out property <bool> is_debug_en: false;
1617
in property <string> init_string: "Please select CAN device to start";
1718
in property <socket_info> can_sockets;
1819
in property <[CanData]> messages;
1920
in property <[CanData]> filter_messages;
2021
in-out property <string> state;
2122
in-out property <int> bus_load;
2223
in-out property <int> bitrate;
24+
in property <[raw_can]> raw_data;
2325

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

2628
callback open_dbc_file();
2729
callback filter_id(CanData, bool);
2830
callback start(string, int, string);
31+
callback can_transmit(bool, string, string);
32+
callback can_id_check_string(bool, string) -> bool;
33+
callback can_data_check_string(string) -> bool;
34+
callback change_state(bool);
2935
title: @tr("CAN VIEWER (version 0.2.2)");
3036
icon: @image-url("images/can_viewer_128px.png");
3137
background: #1a1f2b;
@@ -110,6 +116,27 @@ export component AppWindow inherits Window {
110116
open_dbc_file()
111117
}
112118
}
119+
if root.active-page == 2:
120+
debugPage {
121+
en: is_debug_en;
122+
state: state;
123+
bus_load: bus_load;
124+
bitrate: bitrate;
125+
raw_data: raw_data;
126+
change_state(en) => {
127+
is_debug_en = en;
128+
change_state(en);
129+
}
130+
can_transmit(is_extended, can_id, can_data) => {
131+
can_transmit(is_extended, can_id, can_data)
132+
}
133+
can_id_check_string(is_extended, id) => {
134+
can_id_check_string(is_extended, id)
135+
}
136+
can_data_check_string(data) => {
137+
can_data_check_string(data)
138+
}
139+
}
113140
}
114141
}
115142
}

0 commit comments

Comments
 (0)