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:
parent
adb8c7a65e
commit
4cc26bc9c2
|
@ -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 => {
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue