Huge changes

* Add socket CAN support
* Add J2534 support
* Increase flashing speed
* Add solenoid live view (Oscil mode)
* Fix UI deadlock
This commit is contained in:
Ashcon Mohseninia 2022-05-15 13:29:23 +01:00
parent adb8c7a65e
commit 4cc26bc9c2
11 changed files with 351 additions and 108 deletions

View File

@ -97,7 +97,7 @@ impl InterfacePage for CrashAnalyzerUI {
ui.label(
RichText::new("Caution! Only use when car is off").color(Color32::from_rgb(255, 0, 0)),
);
let state = self.read_state.read().unwrap();
let state = self.read_state.read().unwrap().clone();
if state.is_done() {
if ui.button("Read coredump ELF").clicked() {
let c = self.server.clone();
@ -145,14 +145,14 @@ impl InterfacePage for CrashAnalyzerUI {
ui.ctx().request_repaint();
}
match self.read_state.read().unwrap().clone() {
match &state {
ReadState::None => {},
ReadState::Prepare => {
egui::widgets::ProgressBar::new(0.0).show_percentage().desired_width(300.0).ui(ui);
egui::widgets::ProgressBar::new(0.0).show_percentage().animate(true).desired_width(300.0).ui(ui);
ui.label("Preparing ECU...");
},
ReadState::ReadingBlock { id, out_of, bytes_written } => {
egui::widgets::ProgressBar::new((id as f32) / (out_of as f32)).show_percentage().desired_width(300.0).ui(ui);
egui::widgets::ProgressBar::new((*id as f32) / (*out_of as f32)).show_percentage().animate(true).desired_width(300.0).ui(ui);
ui.label(format!("Bytes read: {}", bytes_written));
},
ReadState::Completed => {

View File

@ -14,6 +14,7 @@ use crate::window::{PageAction, StatusBar};
pub mod rli;
pub mod data;
pub mod solenoids;
use crate::usb_hw::diag_usb::Nag52USB;
use ecu_diagnostics::{kwp2000::*, bcd_decode};
use crate::ui::diagnostics::rli::{DataCanDump, DataGearboxSensors, DataSolenoids, LocalRecordData, RecordIdents};

View File

@ -140,7 +140,7 @@ impl ChartData {
}
#[bitfield]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct DataSolenoids {
pub spc_pwm: u16,
pub mpc_pwm: u16,

View File

@ -0,0 +1,141 @@
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}, RwLock}, thread, time::Duration, char::MAX};
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, SessionType};
use egui::plot::{Plot, Legend, Line, Values, Value};
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
use super::rli::{DataSolenoids, RecordIdents, LocalRecordData};
pub struct SolenoidPage{
bar: MainStatusBar,
query_ecu: Arc<AtomicBool>,
curr_values: Arc<RwLock<Option<DataSolenoids>>>
}
impl SolenoidPage {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
let run = Arc::new(AtomicBool::new(true));
let run_t = run.clone();
let store = Arc::new(RwLock::new(None));
let store_t = store.clone();
thread::spawn(move|| {
let _ = server.lock().unwrap().set_diagnostic_session_mode(SessionType::Normal);
while run_t.load(Ordering::Relaxed) {
if let Ok(r) = RecordIdents::SolenoidStatus.query_ecu(&mut *server.lock().unwrap()) {
if let LocalRecordData::Solenoids(s) = r {
*store_t.write().unwrap() = Some(s);
}
}
std::thread::sleep(Duration::from_millis(100));
}
});
Self {
bar,
query_ecu: run,
curr_values: store
}
}
}
const GRAPH_TIME_MS: u16 = 100;
const MAX_DUTY: u16 = 0xFFF; // 12bit pwm (4096)
const VOLTAGE_HIGH: u16 = 12;
const VOLTAGE_LOW: u16 = 0;
fn make_line_duty_pwm(duty: u16, freq: u16, x_off: f64, y_off: f64) -> Values {
let num_pulses = freq / GRAPH_TIME_MS as u16;
let pulse_width = GRAPH_TIME_MS as f32 / num_pulses as f32;
let pulse_on_width = (duty as f32/4096f32) * pulse_width;
let pulse_off_width = pulse_width - pulse_on_width;
let mut points: Vec<Value> = Vec::new();
let mut curr_x_pos = 0f32;
// Shortcut
if duty == MAX_DUTY {
points.push(Value::new(0, VOLTAGE_LOW));
points.push(Value::new(GRAPH_TIME_MS, VOLTAGE_LOW));
} else if duty == 0 {
points.push(Value::new(0, VOLTAGE_HIGH));
points.push(Value::new(GRAPH_TIME_MS, VOLTAGE_HIGH));
} else {
for i in 0..num_pulses {
// Start at 12V (High - Solenoid off)
points.push(Value::new(curr_x_pos, VOLTAGE_HIGH)); // High, left
curr_x_pos += pulse_off_width;
points.push(Value::new(curr_x_pos, VOLTAGE_HIGH)); // High, right
// Now vertical line
points.push(Value::new(curr_x_pos, VOLTAGE_LOW));
curr_x_pos += pulse_on_width;
points.push(Value::new(curr_x_pos, VOLTAGE_LOW));
// Now draw at 0V (Low - Solenoid on)
}
}
for p in points.iter_mut() {
p.x += x_off;
p.y += y_off;
}
Values::from_values(points)
}
impl crate::window::InterfacePage for SolenoidPage {
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) -> crate::window::PageAction {
ui.heading("Solenoid live view");
let curr = self.curr_values.read().unwrap().clone().unwrap_or_default();
let mut lines = Vec::new();
let mut legend = Legend::default();
let c_height = ui.available_height()/6.0;
lines.push(("MPC", Line::new(make_line_duty_pwm(curr.mpc_pwm(), 1000, 0.0, 0.0)).name("MPC").width(2.0)));
lines.push(("SPC", Line::new(make_line_duty_pwm(curr.spc_pwm(), 1000, 0.0, 0.0)).name("SPC").width(2.0)));
lines.push(("TCC", Line::new(make_line_duty_pwm(curr.tcc_pwm(), 100, 0.0, 0.0)).name("TCC").width(2.0)));
lines.push(("Y3", Line::new(make_line_duty_pwm(curr.y3_pwm(), 1000, 0.0, 0.0)).name("Y3").width(2.0)));
lines.push(("Y4", Line::new(make_line_duty_pwm(curr.y4_pwm(), 1000, 0.0, 0.0)).name("Y4").width(2.0)));
lines.push(("Y5", Line::new(make_line_duty_pwm(curr.y5_pwm(), 1000, 0.0, 0.0)).name("Y5").width(2.0)));
for line in lines {
let mut plot = Plot::new(format!("Solenoid {}", line.0))
.allow_drag(false)
.height(c_height)
.legend(legend.clone());
plot = plot.include_y(13);
plot = plot.include_y(-1);
plot = plot.include_x(0);
plot = plot.include_x(100);
plot.show(ui, |plot_ui| {
plot_ui.line(line.1)
});
}
PageAction::None
}
fn get_title(&self) -> &'static str {
"Solenoid view"
}
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
Some(Box::new(self.bar.clone()))
}
}
impl Drop for SolenoidPage {
fn drop(&mut self) {
self.query_ecu.store(false, Ordering::Relaxed);
}
}

View File

@ -4,7 +4,7 @@ use std::{
sync::{
atomic::{AtomicBool, AtomicU32, Ordering},
Arc, Mutex, RwLock,
}, num::Wrapping, ops::DerefMut,
}, num::Wrapping, ops::DerefMut, time::Instant,
};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagServerResult, DiagnosticServer, DiagError};
@ -17,26 +17,6 @@ use crate::{
window::{InterfacePage, PageAction},
};
pub struct FlashDataFormatted {
freq: String,
size: String,
features: String,
mac: String,
revision: String,
}
impl Default for FlashDataFormatted {
fn default() -> Self {
Self {
freq: "Unknown Mhz".into(),
size: "Unknown MB".into(),
features: "N/A".into(),
mac: "00-00-00-00-00-00".into(),
revision: "N/A".into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum FlashState {
None,
@ -65,6 +45,10 @@ pub struct FwUpdateUI {
elf_path: Option<String>,
firmware: Option<Firmware>,
flash_state: Arc<RwLock<FlashState>>,
flash_start: Instant,
flash_measure: Instant,
flash_speed: u32,
flash_eta: u32,
}
pub struct FlasherMutate {}
@ -76,6 +60,10 @@ impl FwUpdateUI {
elf_path: None,
firmware: None,
flash_state: Arc::new(RwLock::new(FlashState::None)),
flash_start: Instant::now(),
flash_measure: Instant::now(),
flash_speed: 0,
flash_eta: 0
}
}
}
@ -143,7 +131,7 @@ firmware.header.get_idf_version(),
firmware.header.get_time(),
firmware.header.get_date()
)));
let state = self.flash_state.read().unwrap();
let state = self.flash_state.read().unwrap().clone();
if state.is_done() {
if ui.button("Flash firmware").clicked() {
let c = self.server.clone();
@ -190,17 +178,29 @@ firmware.header.get_date()
} else {
ui.label("Flashing in progress...");
ui.label("DO NOT EXIT THE APP");
ui.ctx().request_repaint();
}
match self.flash_state.read().unwrap().clone() {
match &state {
FlashState::None => {},
FlashState::Prepare => {
egui::widgets::ProgressBar::new(0.0).show_percentage().desired_width(300.0).ui(ui);
egui::widgets::ProgressBar::new(0.0).show_percentage().animate(true).desired_width(300.0).ui(ui);
ui.label("Preparing ECU...");
self.flash_start = Instant::now();
self.flash_measure = Instant::now();
self.flash_speed = 0;
self.flash_eta = 0;
},
FlashState::WritingBlock { id, out_of, bytes_written } => {
egui::widgets::ProgressBar::new((id as f32)/(out_of as f32)).show_percentage().desired_width(300.0).animate(true).ui(ui);
ui.label(format!("Bytes written: {}", bytes_written));
egui::widgets::ProgressBar::new((*id as f32)/(*out_of as f32)).show_percentage().desired_width(300.0).animate(true).ui(ui);
let spd = (1000.0 * *bytes_written as f32 / self.flash_start.elapsed().as_millis() as f32) as u32;
if self.flash_measure.elapsed().as_millis() > 1000 {
self.flash_measure = Instant::now();
self.flash_speed = spd;
self.flash_eta = (firmware.raw.len() as u32 - *bytes_written) / spd;
}
ui.label(format!("Bytes written: {}. Avg {:.0} bytes/sec", bytes_written, self.flash_speed));
ui.label(format!("ETA: {} seconds remaining", self.flash_eta));
},
FlashState::Verify => {
egui::widgets::ProgressBar::new(100.0).show_percentage().desired_width(300.0).ui(ui);
@ -213,7 +213,7 @@ firmware.header.get_date()
ui.label(RichText::new(format!("Flashing was ABORTED! Reason: {}", r)).color(Color32::from_rgb(255, 0, 0)));
},
}
return PageAction::SetBackButtonState(false);
return PageAction::SetBackButtonState(state.is_done());
}
return PageAction::SetBackButtonState(true);
}

View File

View File

@ -1,12 +1,15 @@
use std::sync::{Arc, Mutex};
use std::{sync::{Arc, Mutex, mpsc}, ops::Deref};
use ecu_diagnostics::hardware::{HardwareResult, HardwareScanner};
use ecu_diagnostics::{hardware::{HardwareResult, HardwareScanner, passthru::{PassthruScanner, PassthruDevice}, Hardware, HardwareInfo}, channel::{PayloadChannel, IsoTPChannel}};
use egui::*;
use epi::*;
#[cfg(unix)]
use ecu_diagnostics::hardware::socketcan::{SocketCanScanner, SocketCanDevice};
use crate::{
ui::main::MainPage,
usb_hw::{diag_usb::Nag52USB, scanner::Nag52UsbScanner},
usb_hw::{diag_usb::{Nag52USB, EspLogMessage}, scanner::Nag52UsbScanner},
window::{InterfacePage, PageAction},
};
@ -16,8 +19,51 @@ pub struct Launcher {
selected: String,
old_selected: String,
launch_err: Option<String>,
scanner: Nag52UsbScanner,
usb_scanner: Nag52UsbScanner,
pt_scanner: PassthruScanner,
#[cfg(unix)]
scan_scanner: SocketCanScanner,
selected_device: String,
curr_api_type: DeviceType
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum DeviceType {
Passthru,
#[cfg(unix)]
SocketCan,
Usb
}
pub enum DynamicDevice {
Passthru(Arc<Mutex<PassthruDevice>>),
Usb(Arc<Mutex<Nag52USB>>),
#[cfg(unix)]
SocketCAN(Arc<Mutex<SocketCanDevice>>)
}
impl DynamicDevice {
pub fn get_logger(&mut self) -> Option<mpsc::Receiver<EspLogMessage>> {
match self {
DynamicDevice::Usb(usb) => usb.lock().unwrap().get_logger_receiver(),
_ => None
}
}
pub fn create_isotp_channel(&mut self) -> HardwareResult<Box<dyn IsoTPChannel>> {
match self {
DynamicDevice::Passthru(pt) => {
Hardware::create_iso_tp_channel(pt.clone())
},
DynamicDevice::Usb(usb) => {
Hardware::create_iso_tp_channel(usb.clone())
},
#[cfg(unix)]
DynamicDevice::SocketCAN(s) => {
Hardware::create_iso_tp_channel(s.clone())
},
}
}
}
impl Launcher {
@ -26,20 +72,28 @@ impl Launcher {
selected: "".into(),
old_selected: "".into(),
launch_err: None,
scanner: Nag52UsbScanner::new(),
usb_scanner: Nag52UsbScanner::new(),
pt_scanner: PassthruScanner::new(),
#[cfg(unix)]
scan_scanner: SocketCanScanner::new(),
selected_device: String::new(),
curr_api_type: DeviceType::Usb
}
}
}
impl Launcher {
pub fn open_device(&self, name: &str) -> HardwareResult<Arc<Mutex<Nag52USB>>> {
self.scanner.open_device_by_name(name)
pub fn open_device(&self, name: &str) -> HardwareResult<DynamicDevice> {
Ok(match self.curr_api_type {
DeviceType::Passthru => DynamicDevice::Passthru(self.pt_scanner.open_device_by_name(name)?),
#[cfg(unix)]
DeviceType::SocketCan => DynamicDevice::SocketCAN(self.scan_scanner.open_device_by_name(name)?),
DeviceType::Usb => DynamicDevice::Usb(self.usb_scanner.open_device_by_name(name)?),
})
}
pub fn get_device_list(&self) -> Vec<String> {
return self
.scanner
pub fn get_device_list<T, X: Hardware>(scanner: &T) -> Vec<String> where T: HardwareScanner<X> {
return scanner
.list_devices()
.iter()
.map(|x| (x.name.clone()))
@ -50,18 +104,30 @@ impl Launcher {
impl InterfacePage for Launcher {
fn make_ui(&mut self, ui: &mut Ui, frame: &epi::Frame) -> crate::window::PageAction {
ui.label("Ultimate-Nag52 configuration utility!");
ui.label("Please plug in your TCM via USB and select the correct port");
ui.label("Please plug in your TCM via USB and select the correct port, or select another API");
ui.radio_value(&mut self.curr_api_type, DeviceType::Usb, "USB connection");
ui.radio_value(&mut self.curr_api_type, DeviceType::Passthru, "Passthru OBD adapter");
#[cfg(unix)]
{
ui.radio_value(&mut self.curr_api_type, DeviceType::SocketCan, "SocketCAN device");
}
ui.heading("Devices");
let dev_list = match self.curr_api_type {
DeviceType::Passthru => Self::get_device_list(&self.pt_scanner),
#[cfg(unix)]
DeviceType::SocketCan => Self::get_device_list(&self.scan_scanner),
DeviceType::Usb => Self::get_device_list(&self.usb_scanner),
};
if self.get_device_list().len() == 0 {
if dev_list.len() == 0 {
} else {
egui::ComboBox::from_label("Select device")
.width(400.0)
.selected_text(&self.selected_device)
.show_ui(ui, |cb_ui| {
for dev in self.get_device_list() {
for dev in dev_list {
cb_ui.selectable_value(&mut self.selected_device, dev.clone(), dev);
}
});
@ -69,15 +135,22 @@ impl InterfacePage for Launcher {
if !self.selected_device.is_empty() && ui.button("Launch configuration app").clicked() {
match self.open_device(&self.selected_device) {
Ok(dev) => {
return PageAction::Overwrite(Box::new(MainPage::new(dev)));
Ok(mut dev) => {
if let Ok(channel) = dev.create_isotp_channel() {
return PageAction::Overwrite(Box::new(MainPage::new(channel, dev.get_logger(), self.selected_device.clone())));
}
}
Err(e) => self.launch_err = Some(format!("Cannot open device: {}", e)),
}
}
if ui.button("Refresh device list").clicked() {
self.scanner = Nag52UsbScanner::new();
self.pt_scanner = PassthruScanner::new();
self.usb_scanner = Nag52UsbScanner::new();
#[cfg(unix)]
{
self.scan_scanner = SocketCanScanner::new();
}
self.selected_device.clear();
}

View File

@ -1,37 +1,42 @@
use std::sync::{Arc, Mutex};
use std::sync::{Arc, Mutex, mpsc};
use ecu_diagnostics::{
hardware::Hardware,
DiagnosticServer,
kwp2000::{self, Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler},
kwp2000::{self, Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler}, channel::IsoTPChannel,
};
use egui::*;
use epi::Frame;
use crate::{
usb_hw::diag_usb::Nag52USB,
usb_hw::diag_usb::{Nag52USB, EspLogMessage},
window::{InterfacePage, PageAction, StatusBar},
};
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI};
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI, diagnostics::solenoids::SolenoidPage, };
use ecu_diagnostics::kwp2000::*;
use crate::ui::diagnostics::DiagnosticsPage;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct DevInfo {
pub compat_mode: String,
pub fw_version: String,
pub fw_date: String
}
pub struct MainPage {
dev: Arc<Mutex<Nag52USB>>,
bar: MainStatusBar,
show_about_ui: bool,
diag_server: Arc<Mutex<Kwp2000DiagnosticServer>>
diag_server: Arc<Mutex<Kwp2000DiagnosticServer>>,
dev_info: DevInfo
}
impl MainPage {
pub fn new(dev: Arc<Mutex<Nag52USB>>) -> Self {
let mut channel = Hardware::create_iso_tp_channel(dev.clone()).unwrap();
pub fn new(mut channel: Box<dyn IsoTPChannel>, logger: Option<mpsc::Receiver<EspLogMessage>>, hw_name: String) -> Self {
let channel_cfg = ecu_diagnostics::channel::IsoTPSettings {
block_size: 8,
st_min: 20,
block_size: 0,
st_min: 0,
extended_addressing: false,
pad_frame: true,
can_speed: 500_000,
@ -40,8 +45,8 @@ impl MainPage {
let server_settings = Kwp2000ServerOptions {
send_id: 0x07E1,
recv_id: 0x07E9,
read_timeout_ms: 2000,
write_timeout_ms: 2000,
read_timeout_ms: 5000,
write_timeout_ms: 5000,
global_tp_id: 0,
tester_present_interval_ms: 2000,
tester_present_require_response: true,
@ -55,10 +60,10 @@ impl MainPage {
).unwrap();
Self {
dev: dev.clone(),
bar: MainStatusBar::new(dev),
bar: MainStatusBar::new(logger, hw_name),
show_about_ui: false,
diag_server: Arc::new(Mutex::new(kwp))
diag_server: Arc::new(Mutex::new(kwp)),
dev_info: DevInfo { compat_mode: "UNKNOWN".into(), fw_version: "UNKNOWN".into(), fw_date: "UNKNOWN".into() }
}
}
}
@ -76,12 +81,21 @@ impl InterfacePage for MainPage {
//TODO
}
if x.button("About").clicked() {
// Query info (Update it)
if let Ok (info) = self.diag_server.lock().unwrap().read_daimler_mmc_identification() {
self.dev_info = DevInfo {
compat_mode: if info.supplier == 7 { "EGS52".into() } else { "EGS53".into() },
fw_version: info.sw_version,
fw_date: "UNKNOWN".into()
};
} else {
self.dev_info = DevInfo { compat_mode: "UNKNOWN".into(), fw_version: "UNKNOWN".into(), fw_date: "UNKNOWN".into() };
}
self.show_about_ui = true;
}
})
});
ui.add(egui::Separator::default());
let mut create_page = None;
ui.vertical(|v| {
v.heading("Utilities");
@ -94,6 +108,9 @@ impl InterfacePage for MainPage {
if v.button("Diagnostics").clicked() {
create_page = Some(PageAction::Add(Box::new(DiagnosticsPage::new(self.diag_server.clone(), self.bar.clone()))));
}
if v.button("Solenoid live view").clicked() {
create_page = Some(PageAction::Add(Box::new(SolenoidPage::new(self.diag_server.clone(), self.bar.clone()))));
}
if v.button("Map tuner").clicked() {}
if v.button("Configure drive profiles").clicked() {}
if v.button("Configure vehicle / gearbox").clicked() {
@ -116,9 +133,9 @@ impl InterfacePage for MainPage {
"Configuration app version: {}",
env!("CARGO_PKG_VERSION")
));
about_cols.label(format!("TCM firmware version: UNKNOWN"));
about_cols.label(format!("TCM firmware build: UNKNOWN"));
about_cols.label(format!("TCM EGS compatibility mode: EGS52"));
about_cols.label(format!("TCM firmware version: {}", self.dev_info.fw_version));
about_cols.label(format!("TCM firmware build: {}", self.dev_info.fw_date));
about_cols.label(format!("TCM EGS compatibility mode: {}", self.dev_info.compat_mode));
about_cols.separator();
about_cols.heading("Open source");
about_cols.add(egui::Hyperlink::from_label_and_url(

View File

@ -9,6 +9,7 @@ pub mod status_bar;
pub mod diagnostics;
pub mod configuration;
pub mod crashanalyzer;
pub mod kwp_event;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum StatusText {

View File

@ -3,7 +3,7 @@ use egui::*;
use epi::*;
use std::{
collections::VecDeque,
sync::{Arc, Mutex, RwLock}, borrow::BorrowMut,
sync::{Arc, Mutex, RwLock, mpsc}, borrow::BorrowMut,
};
use crate::{
@ -13,33 +13,37 @@ use crate::{
#[derive(Clone)]
pub struct MainStatusBar {
dev: Arc<Mutex<Nag52USB>>,
show_log_view: bool,
msgs: VecDeque<EspLogMessage>,
receiver: Option<Arc<mpsc::Receiver<EspLogMessage>>>,
hw_name: String,
auto_scroll: bool,
use_light_theme: bool
}
impl MainStatusBar {
pub fn new(dev: Arc<Mutex<Nag52USB>>) -> Self {
pub fn new(logger: Option<mpsc::Receiver<EspLogMessage>>, hw_name: String) -> Self {
Self {
dev,
show_log_view: false,
msgs: VecDeque::new(),
auto_scroll: true,
use_light_theme: false
use_light_theme: false,
receiver: match logger {
Some(l) => Some(Arc::new(l)),
None => None,
},
hw_name
}
}
}
impl StatusBar for MainStatusBar {
fn draw(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
match self.dev.lock().unwrap().is_connected() {
true => ui.label("Connected"),
false => ui.label("Disconnected"),
};
if ui.button("TCM log view").clicked() {
self.show_log_view = true;
ui.label(format!("Connected via {}", self.hw_name));
if self.receiver.is_some() {
if ui.button("TCM log view").clicked() {
self.show_log_view = true;
}
}
if ui.checkbox(&mut self.use_light_theme, "Light theme").clicked() {
@ -86,12 +90,12 @@ impl StatusBar for MainStatusBar {
);
}
});
while let Some(msg) = self.dev.lock().unwrap().read_log_msg() {
if self.msgs.len() >= 1000 {
self.msgs.pop_front();
}
self.msgs.push_back(msg);
}
//while let Some(msg) = self.dev.lock().unwrap().read_log_msg() {
// if self.msgs.len() >= 1000 {
// self.msgs.pop_front();
// }
// self.msgs.push_back(msg);
//}
});
if log_view.button("Clear log view").clicked() {

View File

@ -6,17 +6,18 @@ use std::{
io::{BufRead, BufReader, BufWriter, Write},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, Receiver},
mpsc::{self},
Arc,
},
time::Instant, ops::{Deref, DerefMut}, thread::JoinHandle,
time::{Instant, Duration},
};
use std::fmt::Write as SWrite;
use ecu_diagnostics::{
channel::{IsoTPChannel, PayloadChannel, ChannelError},
hardware::{HardwareInfo, HardwareResult, HardwareError},
};
use serial_rs::{SerialPort, PortInfo, SerialPortSettings, ByteSize, FlowControl};
use egui::mutex::Mutex;
use serial_rs::{SerialPort, PortInfo, SerialPortSettings, FlowControl};
#[derive(Debug, Clone, Copy)]
pub enum EspLogLevel {
@ -38,8 +39,8 @@ pub struct EspLogMessage {
pub struct Nag52USB {
port: Option<Box<dyn SerialPort>>,
info: HardwareInfo,
rx_log: mpsc::Receiver<EspLogMessage>,
rx_diag: mpsc::Receiver<(u32, Vec<u8>)>,
rx_log: Option<mpsc::Receiver<EspLogMessage>>,
is_running: Arc<AtomicBool>,
tx_id: u32,
rx_id: u32,
@ -51,7 +52,7 @@ unsafe impl Send for Nag52USB {}
impl Nag52USB {
pub fn new(path: &str, info: PortInfo) -> HardwareResult<Self> {
let mut port = serial_rs::new_from_path(path, Some(SerialPortSettings::default()
.baud(115200)
.baud(921600)
.read_timeout(Some(100))
.write_timeout(Some(100))
.set_flow_control(FlowControl::None)))
@ -71,16 +72,18 @@ impl Nag52USB {
let reader_thread = std::thread::spawn(move || {
println!("Serial reader start");
while is_running_r.load(Ordering::Relaxed) {
let reader = BufReader::new(&mut port_clone);
for line in reader.lines().filter_map(|x| x.ok()) {
if !line.is_empty() {
let mut reader = BufReader::new(&mut port_clone);
let mut line = String::new();
loop {
if reader.read_line(&mut line).is_ok() {
let _ = line.pop();
if line.chars().next().unwrap() == '#' {
// First char is #, diag message
// Diag message
if line.len() % 2 == 0 {
eprintln!("Discarding invalid diag msg '{}'", line);
} else {
println!("Read diag payload {:?} from Nag52 USB", line);
//println!("Read diag payload {:?} from Nag52 USB", line);
let contents: &str = &line[1..];
let can_id = u32::from_str_radix(&contents[0..4], 16).unwrap();
let payload: Vec<u8> = (4..contents.len())
@ -95,7 +98,7 @@ impl Nag52USB {
continue;
}
if !line.contains("(") || !line.contains(")") {
println!("{}", line);
println!("EEE {}", line);
continue;
}
let lvl = match line.chars().next().unwrap() {
@ -117,9 +120,12 @@ impl Nag52USB {
tag: split.0.split_once(")").unwrap().1.to_string(),
msg: split.1.to_string(),
};
println!("{:?}", msg);
read_tx_log.send(msg);
}
line.clear();
}
//std::thread::sleep(from_millis(10));
}
}
println!("Serial reader stop");
@ -145,20 +151,20 @@ impl Nag52USB {
ip: false,
},
},
rx_log: read_rx_log,
rx_diag: read_rx_diag,
rx_log: Some(read_rx_log),
tx_id: 0,
rx_id: 0,
})
}
pub fn read_log_msg(&self) -> Option<EspLogMessage> {
self.rx_log.try_recv().ok()
}
pub fn is_connected(&self) -> bool {
self.is_running.load(Ordering::Relaxed)
}
pub fn get_logger_receiver(&mut self) -> Option<mpsc::Receiver<EspLogMessage>> {
self.rx_log.take()
}
}
impl Drop for Nag52USB {
@ -244,14 +250,14 @@ impl PayloadChannel for Nag52USB {
) -> ecu_diagnostics::channel::ChannelResult<()> {
// Just write buffer
match self.port.as_mut() {
Some(mut p) => {
let mut buf = format!("#{:04X}", addr);
Some(p) => {
let mut buf = String::with_capacity(buffer.len()*2 + 6);
write!(buf, "#{:04X}", addr);
for x in buffer {
buf.push_str(&format!("{:02X}", x));
write!(buf, "{:02X}", x);
}
buf.push('\n');
let mut writer = BufWriter::new(&mut p);
writer.write_all(buf.as_bytes()).map_err(|e| ChannelError::IOError(e))?;
write!(buf, "\n");
p.write_all(buf.as_bytes()).map_err(|e| ChannelError::IOError(e))?;
Ok(())
}
None => Err(ChannelError::InterfaceNotOpen),