New map editor + cargo fmt

This commit is contained in:
Ashcon Mohseninia 2022-11-14 17:23:07 +00:00
parent c053774c69
commit 87654907e6
28 changed files with 1733 additions and 907 deletions

View File

@ -24,6 +24,7 @@ modular-bitfield = "0.11.2"
static_assertions = "1.1.0"
env_logger="0.9.0"
egui-toast="0.3.0"
nom="7.1.1"
[profile.release]
debug = true

BIN
config_app/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,8 +1,8 @@
#[macro_use]
extern crate static_assertions;
use std::{iter, env};
use eframe::{NativeOptions, Renderer, IconData, epaint::Vec2};
use eframe::{epaint::Vec2, IconData, NativeOptions, Renderer};
use std::{env, iter};
use ui::launcher::Launcher;
use window::MainWindow;
@ -17,7 +17,9 @@ compile_error!("Windows can ONLY be built using the i686-pc-windows-msvc target!
fn main() {
env_logger::init();
let icon = image::load_from_memory(include_bytes!("../logo.png")).unwrap().to_rgba8();
let icon = image::load_from_memory(include_bytes!("../logo.png"))
.unwrap()
.to_rgba8();
let (icon_w, icon_h) = icon.dimensions();
#[cfg(unix)]
@ -26,7 +28,7 @@ fn main() {
let mut app = window::MainWindow::new();
app.add_new_page(Box::new(Launcher::new()));
let mut native_options = NativeOptions::default();
native_options.icon_data = Some(IconData{
native_options.icon_data = Some(IconData {
rgba: icon.into_raw(),
width: icon_w,
height: icon_h,
@ -36,7 +38,9 @@ fn main() {
{
native_options.renderer = Renderer::Wgpu;
}
eframe::run_native("Ultimate NAG52 config suite", native_options, Box::new(|cc| {
Box::new(app)
}));
eframe::run_native(
"Ultimate NAG52 config suite",
native_options,
Box::new(|cc| Box::new(app)),
);
}

View File

@ -1,6 +1,5 @@
use modular_bitfield::{bitfield, BitfieldSpecifier};
#[bitfield]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct TcmCoreConfig {
@ -13,25 +12,24 @@ pub struct TcmCoreConfig {
pub default_profile: DefaultProfile,
pub red_line_dieselrpm: u16,
pub red_line_petrolrpm: u16,
pub engine_type: EngineType
pub engine_type: EngineType,
}
#[derive(BitfieldSpecifier)]
#[bits=8]
#[bits = 8]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum DefaultProfile {
Standard = 0,
Comfort = 1,
Winter = 2,
Agility = 3,
Manual = 4
Manual = 4,
}
#[derive(BitfieldSpecifier)]
#[bits=8]
#[bits = 8]
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum EngineType {
Diesel,
Petrol
}
Petrol,
}

View File

@ -1,11 +1,17 @@
use std::{sync::{Arc, Mutex}, borrow::BorrowMut};
use std::{
borrow::BorrowMut,
sync::{Arc, Mutex},
};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagnosticServer};
use crate::window::PageAction;
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, ResetMode, SessionType},
DiagnosticServer,
};
use eframe::egui::Ui;
use eframe::egui::{self, *};
use crate::window::PageAction;
use self::cfg_structs::{TcmCoreConfig, DefaultProfile, EngineType};
use self::cfg_structs::{DefaultProfile, EngineType, TcmCoreConfig};
use super::{status_bar::MainStatusBar, StatusText};
@ -15,7 +21,7 @@ pub struct ConfigPage {
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
bar: MainStatusBar,
status: StatusText,
scn: Option<TcmCoreConfig>
scn: Option<TcmCoreConfig>,
}
impl ConfigPage {
@ -24,23 +30,26 @@ impl ConfigPage {
server,
bar,
status: StatusText::Ok("".into()),
scn: None
scn: None,
}
}
}
impl crate::window::InterfacePage for ConfigPage {
fn make_ui(&mut self, ui: &mut Ui, frame: &eframe::Frame) -> PageAction {
ui.heading("TCM Configuration");
if ui.button("Read Configuration").clicked() {
match self.server.lock().unwrap().read_custom_local_identifier(0xFE) {
match self
.server
.lock()
.unwrap()
.read_custom_local_identifier(0xFE)
{
Ok(res) => {
self.scn = Some(TcmCoreConfig::from_bytes(res.try_into().unwrap()));
self.status = StatusText::Ok(format!("Read OK!"));
},
}
Err(e) => {
self.status = StatusText::Err(format!("Error reading configuration: {}", e))
}
@ -48,7 +57,6 @@ impl crate::window::InterfacePage for ConfigPage {
}
if let Some(scn) = self.scn.borrow_mut() {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
let mut x = scn.is_large_nag() == 1;
ui.label("Using large 722.6");
@ -56,28 +64,30 @@ impl crate::window::InterfacePage for ConfigPage {
scn.set_is_large_nag(x as u8);
ui.end_row();
let mut curr_profile = scn.default_profile();
ui.label("Default drive profile");
egui::ComboBox::from_id_source("profile")
.width(100.0)
.selected_text(format!("{:?}", curr_profile))
.show_ui(ui, |cb_ui| {
let profiles = vec![
DefaultProfile::Standard,
DefaultProfile::Comfort,
DefaultProfile::Winter,
DefaultProfile::Agility,
DefaultProfile::Manual
];
for dev in profiles {
cb_ui.selectable_value(&mut curr_profile, dev.clone(), format!("{:?}", dev));
}
scn.set_default_profile(curr_profile)
});
.width(100.0)
.selected_text(format!("{:?}", curr_profile))
.show_ui(ui, |cb_ui| {
let profiles = vec![
DefaultProfile::Standard,
DefaultProfile::Comfort,
DefaultProfile::Winter,
DefaultProfile::Agility,
DefaultProfile::Manual,
];
for dev in profiles {
cb_ui.selectable_value(
&mut curr_profile,
dev.clone(),
format!("{:?}", dev),
);
}
scn.set_default_profile(curr_profile)
});
ui.end_row();
let mut buffer = format!("{:.2}", scn.diff_ratio() as f32 / 1000.0);
ui.label("Differential ratio");
ui.text_edit_singleline(&mut buffer);
@ -94,35 +104,30 @@ impl crate::window::InterfacePage for ConfigPage {
}
ui.end_row();
let mut engine = scn.engine_type();
ui.label("Engine type");
egui::ComboBox::from_id_source("engine_type")
.width(100.0)
.selected_text(format!("{:?}", engine))
.show_ui(ui, |cb_ui| {
let profiles = vec![
EngineType::Diesel,
EngineType::Petrol
];
for dev in profiles {
cb_ui.selectable_value(&mut engine, dev.clone(), format!("{:?}", dev));
}
scn.set_engine_type(engine)
});
.width(100.0)
.selected_text(format!("{:?}", engine))
.show_ui(ui, |cb_ui| {
let profiles = vec![EngineType::Diesel, EngineType::Petrol];
for dev in profiles {
cb_ui.selectable_value(&mut engine, dev.clone(), format!("{:?}", dev));
}
scn.set_engine_type(engine)
});
ui.end_row();
let mut buffer = match scn.engine_type() {
EngineType::Diesel => format!("{}", scn.red_line_dieselrpm()),
EngineType::Petrol => format!("{}", scn.red_line_petrolrpm())
EngineType::Petrol => format!("{}", scn.red_line_petrolrpm()),
};
ui.label("Engine redline RPM");
ui.text_edit_singleline(&mut buffer);
if let Ok(rpm) = buffer.parse::<u16>() {
match scn.engine_type() {
EngineType::Diesel => scn.set_red_line_dieselrpm(rpm),
EngineType::Petrol => scn.set_red_line_petrolrpm(rpm)
EngineType::Petrol => scn.set_red_line_petrolrpm(rpm),
}
}
ui.end_row();
@ -134,8 +139,8 @@ impl crate::window::InterfacePage for ConfigPage {
ui.end_row();
if scn.is_four_matic() == 1 {
let mut buffer = format!("{:.2}", scn.transfer_case_high_ratio() as f32 / 1000.0);
let mut buffer =
format!("{:.2}", scn.transfer_case_high_ratio() as f32 / 1000.0);
ui.label("Transfer case high ratio");
ui.text_edit_singleline(&mut buffer);
if let Ok(new_ratio) = buffer.parse::<f32>() {
@ -143,7 +148,8 @@ impl crate::window::InterfacePage for ConfigPage {
}
ui.end_row();
let mut buffer = format!("{:.2}", scn.transfer_case_low_ratio() as f32 / 1000.0);
let mut buffer =
format!("{:.2}", scn.transfer_case_low_ratio() as f32 / 1000.0);
ui.label("Transfer case low ratio");
ui.text_edit_singleline(&mut buffer);
if let Ok(new_ratio) = buffer.parse::<f32>() {
@ -151,21 +157,24 @@ impl crate::window::InterfacePage for ConfigPage {
}
ui.end_row();
}
});
if ui.button("Write configuration").clicked() {
let res = {
let mut x: Vec<u8> = vec![0x3B, 0xFE];
x.extend_from_slice(&scn.clone().into_bytes());
self.server.lock().unwrap().set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
self.server.lock().unwrap().send_byte_array_with_response(&x);
self.server.lock().unwrap().reset_ecu(ResetMode::PowerOnReset);
self.server
.lock()
.unwrap()
.set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
self.server
.lock()
.unwrap()
.send_byte_array_with_response(&x);
self.server
.lock()
.unwrap()
.reset_ecu(ResetMode::PowerOnReset);
};
}
}
@ -181,4 +190,4 @@ impl crate::window::InterfacePage for ConfigPage {
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
Some(Box::new(self.bar.clone()))
}
}
}

View File

@ -1,25 +1,29 @@
use std::{
fs::File,
io::Write,
sync::{
Arc, Mutex, RwLock,
}, ops::DerefMut,
ops::DerefMut,
sync::{Arc, Mutex, RwLock},
};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType}, DiagServerResult, DiagnosticServer, DiagError};
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, SessionType},
DiagError, DiagServerResult, DiagnosticServer,
};
use eframe::egui::{self, *};
use crate::{
window::{InterfacePage, PageAction},
};
use crate::window::{InterfacePage, PageAction};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum ReadState {
None,
Prepare,
ReadingBlock { id: u32, out_of: u32, bytes_written: u32 },
ReadingBlock {
id: u32,
out_of: u32,
bytes_written: u32,
},
Completed,
Aborted(String)
Aborted(String),
}
impl ReadState {
@ -27,7 +31,11 @@ impl ReadState {
match self {
ReadState::None => true,
ReadState::Prepare => false,
ReadState::ReadingBlock { id, out_of, bytes_written } => false,
ReadState::ReadingBlock {
id,
out_of,
bytes_written,
} => false,
ReadState::Completed => true,
ReadState::Aborted(_) => true,
}
@ -40,7 +48,7 @@ pub struct CrashAnalyzerUI {
}
impl CrashAnalyzerUI {
pub fn new(server:Arc<Mutex<Kwp2000DiagnosticServer>>) -> Self {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>) -> Self {
Self {
server,
read_state: Arc::new(RwLock::new(ReadState::None)),
@ -58,12 +66,12 @@ fn init_flash_mode(server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<(u3
// First request coredump info
let mut res = server.read_custom_local_identifier(0x24)?;
if res.len() != 8 {
return Err(DiagError::InvalidResponseLength)
return Err(DiagError::InvalidResponseLength);
}
let address = u32::from_le_bytes(res[0..4].try_into().unwrap());
let size = u32::from_le_bytes(res[4..8].try_into().unwrap());
if size == 0 {
return Ok((0,0,0));
return Ok((0, 0, 0));
}
let mut upload_req = vec![0x35, 0x31, 0x00, 0x00, 0x00];
upload_req.push((size >> 16) as u8);
@ -71,7 +79,7 @@ fn init_flash_mode(server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<(u3
upload_req.push((size >> 0) as u8);
res = server.send_byte_array_with_response(&upload_req)?;
if res.len() != 3 {
return Err(DiagError::InvalidResponseLength)
return Err(DiagError::InvalidResponseLength);
}
let bs: u32 = ((res[1] as u32) << 8) | res[2] as u32;
Ok((address, size, bs))
@ -84,11 +92,7 @@ fn on_flash_end(server: &mut Kwp2000DiagnosticServer, read: Vec<u8>) -> DiagServ
}
impl InterfacePage for CrashAnalyzerUI {
fn make_ui(
&mut self,
ui: &mut egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
ui.heading("Crash Analyzer");
ui.label(
RichText::new("Caution! Only use when car is off").color(Color32::from_rgb(255, 0, 0)),
@ -98,13 +102,16 @@ impl InterfacePage for CrashAnalyzerUI {
if ui.button("Read coredump ELF").clicked() {
let c = self.server.clone();
let state_c = self.read_state.clone();
std::thread::spawn(move|| {
std::thread::spawn(move || {
let mut lock = c.lock().unwrap();
*state_c.write().unwrap() = ReadState::Prepare;
match init_flash_mode(&mut lock.deref_mut()) {
Err(e) => {
*state_c.write().unwrap() = ReadState::Aborted(format!("ECU rejected flash programming mode: {}", e))
},
*state_c.write().unwrap() = ReadState::Aborted(format!(
"ECU rejected flash programming mode: {}",
e
))
}
Ok(size) => {
println!("OK {:?}", size);
if size.1 == 0x00 {
@ -116,14 +123,23 @@ impl InterfacePage for CrashAnalyzerUI {
let mut data: Vec<u8> = Vec::with_capacity(size.1 as usize);
let mut i = 0;
while (data.len() as u32) < size.1 {
match lock.send_byte_array_with_response(&[0x36, ((i+1) & 0xFF) as u8]) {
match lock.send_byte_array_with_response(&[
0x36,
((i + 1) & 0xFF) as u8,
]) {
Ok(p) => {
data.extend_from_slice(&p[2..]);
i+=1;
*state_c.write().unwrap() = ReadState::ReadingBlock { id: i+1, out_of: block_count, bytes_written: data.len() as u32 };
},
i += 1;
*state_c.write().unwrap() = ReadState::ReadingBlock {
id: i + 1,
out_of: block_count,
bytes_written: data.len() as u32,
};
}
Err(e) => {
*state_c.write().unwrap() = ReadState::Aborted(format!("ECU rejected transfer data: {}", e));
*state_c.write().unwrap() = ReadState::Aborted(
format!("ECU rejected transfer data: {}", e),
);
return;
}
}
@ -142,21 +158,39 @@ impl InterfacePage for CrashAnalyzerUI {
}
match &state {
ReadState::None => {},
ReadState::None => {}
ReadState::Prepare => {
egui::widgets::ProgressBar::new(0.0).show_percentage().animate(true).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().animate(true).desired_width(300.0).ui(ui);
}
ReadState::ReadingBlock {
id,
out_of,
bytes_written,
} => {
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 => {
ui.label(RichText::new("Coredump ELF saved as dump.elf!").color(Color32::from_rgb(0, 255, 0)));
},
ui.label(
RichText::new("Coredump ELF saved as dump.elf!")
.color(Color32::from_rgb(0, 255, 0)),
);
}
ReadState::Aborted(r) => {
ui.label(RichText::new(format!("Coredump read ABORTED! Reason: {}", r)).color(Color32::from_rgb(255, 0, 0)));
},
ui.label(
RichText::new(format!("Coredump read ABORTED! Reason: {}", r))
.color(Color32::from_rgb(255, 0, 0)),
);
}
}
return PageAction::SetBackButtonState(true);
}

View File

@ -0,0 +1 @@

View File

@ -1,28 +1,28 @@
use std::collections::VecDeque;
use crate::ui::status_bar::MainStatusBar;
use crate::window::{PageAction, StatusBar};
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
use eframe::egui::plot::{Legend, Line, Plot};
use eframe::egui::{Color32, RichText, Ui};
use std::collections::hash_map::DefaultHasher;
use std::collections::VecDeque;
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
use eframe::egui::plot::{Plot, Line, Legend};
use eframe::egui::{Ui, RichText, Color32};
use crate::ui::status_bar::MainStatusBar;
use crate::window::{PageAction, StatusBar};
pub mod rli;
pub mod data;
pub mod solenoids;
pub mod rli;
pub mod shift_reporter;
pub mod solenoids;
use crate::ui::diagnostics::rli::{LocalRecordData, RecordIdents};
use self::rli::ChartData;
pub enum CommandStatus {
Ok(String),
Err(String)
Err(String),
}
pub struct DiagnosticsPage{
pub struct DiagnosticsPage {
bar: MainStatusBar,
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
text: CommandStatus,
@ -31,7 +31,7 @@ pub struct DiagnosticsPage{
last_query_time: Instant,
query_loop: bool,
charting_data: VecDeque<(u128, ChartData)>,
chart_idx: u128
chart_idx: u128,
}
impl DiagnosticsPage {
@ -45,12 +45,11 @@ impl DiagnosticsPage {
last_query_time: Instant::now(),
query_loop: false,
charting_data: VecDeque::new(),
chart_idx: 0
chart_idx: 0,
}
}
}
impl crate::window::InterfacePage for DiagnosticsPage {
fn make_ui(&mut self, ui: &mut Ui, _frame: &eframe::Frame) -> PageAction {
let mut pending = false;
@ -59,9 +58,12 @@ impl crate::window::InterfacePage for DiagnosticsPage {
if ui.button("Query ECU Serial number").clicked() {
match self.server.lock().unwrap().read_ecu_serial_number() {
Ok(b) => {
self.text = CommandStatus::Ok(format!("ECU Serial: {}", String::from_utf8_lossy(&b).to_string()))
},
Err(e) => self.text = CommandStatus::Err(e.to_string())
self.text = CommandStatus::Ok(format!(
"ECU Serial: {}",
String::from_utf8_lossy(&b).to_string()
))
}
Err(e) => self.text = CommandStatus::Err(e.to_string()),
}
}
@ -78,8 +80,8 @@ impl crate::window::InterfacePage for DiagnosticsPage {
b.get_software_date_pretty(),
b.get_production_date_pretty()
))
},
Err(e) => self.text = CommandStatus::Err(e.to_string())
}
Err(e) => self.text = CommandStatus::Err(e.to_string()),
}
}
@ -125,10 +127,10 @@ impl crate::window::InterfacePage for DiagnosticsPage {
match &self.text {
CommandStatus::Ok(res) => {
ui.label(RichText::new(res).color(Color32::from_rgb(0, 255, 0)));
},
}
CommandStatus::Err(res) => {
ui.label(RichText::new(res).color(Color32::from_rgb(255, 0, 0)));
},
}
}
if pending || (self.query_loop && self.last_query_time.elapsed().as_millis() > 100) {
@ -140,76 +142,60 @@ impl crate::window::InterfacePage for DiagnosticsPage {
Err(e) => {
eprintln!("Could not query {}", e);
}
}
}
}
if let Some(data) = &self.record_data {
if let Some(data) = &self.record_data {
data.to_table(ui);
if let LocalRecordData::Dma(dma) = data {
let mut points: Vec<[f64; 2]> = Vec::new();
for (idx, y) in dma.dma_buffer.clone().iter().enumerate() {
points.push([idx as f64, *y as f64]);
let c = data.get_chart_data();
if !c.is_empty() {
let d = &c[0];
self.charting_data.push_back((self.chart_idx, d.clone()));
if self.charting_data.len() > (20000 / 100) {
// 20 seconds
let _ = self.charting_data.pop_front();
}
let avg = dma.adc_detect as f64;
Plot::new("I2S DMA")
// Can guarantee everything in `self.charting_data` will have the SAME length
// as `d`
let mut lines = Vec::new();
let legend = Legend::default();
for (idx, (key, _, _)) in d.data.iter().enumerate() {
let mut points: Vec<[f64; 2]> = Vec::new();
for (timestamp, point) in &self.charting_data {
points.push([*timestamp as f64, point.data[idx].1 as f64])
}
let mut key_hasher = DefaultHasher::default();
key.hash(&mut key_hasher);
let r = key_hasher.finish();
lines.push(Line::new(points).name(key.clone()).color(Color32::from_rgb(
(r & 0xFF) as u8,
((r >> 8) & 0xFF) as u8,
((r >> 16) & 0xFF) as u8,
)))
}
let mut plot = Plot::new(d.group_name.clone())
.allow_drag(false)
.include_x(0)
.include_x(1000)
.include_y(3300)
.show(ui, |plot_ui| {
plot_ui.line(Line::new(points));
plot_ui.line(Line::new(vec![[0.0, avg], [1000.0, avg]]).highlight(true))
});
} else {
let c = data.get_chart_data();
if !c.is_empty() {
let d = &c[0];
self.charting_data.push_back((
self.chart_idx,
d.clone()
));
if self.charting_data.len() > 1000 {
let _ = self.charting_data.pop_front();
.legend(legend);
if let Some((min, max)) = &d.bounds {
plot = plot.include_y(*min);
if *max > 0.1 {
// 0.0 check
plot = plot.include_y(*max);
}
// Can guarantee everything in `self.charting_data` will have the SAME length
// as `d`
let mut lines = Vec::new();
let legend = Legend::default();
for (idx, (key, _, _)) in d.data.iter().enumerate() {
let mut points: Vec<[f64; 2]> = Vec::new();
for (timestamp, point) in &self.charting_data {
points.push([*timestamp as f64, point.data[idx].1 as f64])
}
let mut key_hasher = DefaultHasher::default();
key.hash(&mut key_hasher);
let r = key_hasher.finish();
lines.push(Line::new(points).name(key.clone()).color(Color32::from_rgb((r & 0xFF) as u8, ((r >> 8) & 0xFF) as u8, ((r >> 16) & 0xFF) as u8)))
}
let mut plot = Plot::new(d.group_name.clone())
.allow_drag(false)
.legend(legend);
if let Some((min, max)) = &d.bounds {
plot = plot.include_y(*min);
if *max > 0.1 { // 0.0 check
plot = plot.include_y(*max);
}
}
plot.show(ui, |plot_ui| {
for x in lines {
plot_ui.line(x)
}
});
}
plot.show(ui, |plot_ui| {
for x in lines {
plot_ui.line(x)
}
});
}
ui.ctx().request_repaint();
}
@ -224,4 +210,4 @@ impl crate::window::InterfacePage for DiagnosticsPage {
fn get_status_bar(&self) -> Option<Box<dyn StatusBar>> {
Some(Box::new(self.bar.clone()))
}
}
}

View File

@ -1,10 +1,10 @@
//! Read data by local identifier data structures
//! Based on diag_data.h in TCM source code
//!
//!
use std::borrow::Borrow;
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
use ecu_diagnostics::{DiagError, DiagServerResult};
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer};
use eframe::egui::{self, Color32, InnerResponse, RichText, Ui};
use modular_bitfield::{bitfield, BitfieldSpecifier};
@ -16,27 +16,42 @@ pub enum RecordIdents {
CanDataDump = 0x22,
SysUsage = 0x23,
PressureStatus = 0x25,
DmaDump = 0x26,
SSData = 0x27,
}
impl RecordIdents {
pub fn query_ecu(&self, server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<LocalRecordData> {
pub fn query_ecu(
&self,
server: &mut Kwp2000DiagnosticServer,
) -> DiagServerResult<LocalRecordData> {
let resp = server.read_custom_local_identifier(*self as u8)?;
match self {
Self::GearboxSensors => Ok(LocalRecordData::Sensors(DataGearboxSensors::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::SolenoidStatus => Ok(LocalRecordData::Solenoids(DataSolenoids::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::CanDataDump => Ok(LocalRecordData::Canbus(DataCanDump::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::SysUsage => Ok(LocalRecordData::SysUsage(DataSysUsage::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::PressureStatus => Ok(LocalRecordData::Pressures(DataPressures::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::SSData => Ok(LocalRecordData::ShiftMonitorLive(DataShiftManager::from_bytes(resp.try_into().map_err(|_| DiagError::InvalidResponseLength )?))),
Self::DmaDump => {
let res = DataDmaDump {
adc_detect: u16::from_le_bytes([resp[0], resp[1]]),
dma_buffer: resp[2..].chunks_exact(2).into_iter().map(|x| u16::from_le_bytes([x[0], x[1]]) & 0x0FFF).collect(),
};
Ok(LocalRecordData::Dma(res))
}
Self::GearboxSensors => Ok(LocalRecordData::Sensors(DataGearboxSensors::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
))),
Self::SolenoidStatus => Ok(LocalRecordData::Solenoids(DataSolenoids::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
))),
Self::CanDataDump => Ok(LocalRecordData::Canbus(DataCanDump::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
))),
Self::SysUsage => Ok(LocalRecordData::SysUsage(DataSysUsage::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
))),
Self::PressureStatus => Ok(LocalRecordData::Pressures(DataPressures::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
))),
Self::SSData => Ok(LocalRecordData::ShiftMonitorLive(
DataShiftManager::from_bytes(
resp.try_into()
.map_err(|_| DiagError::InvalidResponseLength)?,
),
)),
}
}
}
@ -48,8 +63,7 @@ pub enum LocalRecordData {
Canbus(DataCanDump),
SysUsage(DataSysUsage),
Pressures(DataPressures),
Dma(DataDmaDump),
ShiftMonitorLive(DataShiftManager)
ShiftMonitorLive(DataShiftManager),
}
impl LocalRecordData {
@ -61,10 +75,7 @@ impl LocalRecordData {
LocalRecordData::SysUsage(s) => s.to_table(ui),
LocalRecordData::Pressures(s) => s.to_table(ui),
LocalRecordData::ShiftMonitorLive(s) => s.to_table(ui),
_ => {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
})
}
_ => egui::Grid::new("DGS").striped(true).show(ui, |ui| {}),
}
}
@ -76,7 +87,7 @@ impl LocalRecordData {
LocalRecordData::SysUsage(s) => s.to_chart_data(),
LocalRecordData::Pressures(s) => s.to_chart_data(),
LocalRecordData::ShiftMonitorLive(s) => s.to_chart_data(),
_ => vec![]
_ => vec![],
}
}
}
@ -89,42 +100,51 @@ pub struct DataPressures {
pub tcc_pwm: u16,
pub spc_pressure: u16,
pub mpc_pressure: u16,
pub tcc_pressure: u16
pub tcc_pressure: u16,
}
impl DataPressures {
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
ui.label("Shift pressure");
ui.label(if self.spc_pressure() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} mBar", self.spc_pressure()), false) });
ui.label(if self.spc_pressure() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} mBar", self.spc_pressure()), false)
});
ui.end_row();
ui.label("Modulating pressure");
ui.label(if self.mpc_pressure() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} mBar", self.mpc_pressure()), false) });
ui.label(if self.mpc_pressure() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} mBar", self.mpc_pressure()), false)
});
ui.end_row();
ui.label("Torque converter pressure");
ui.label(if self.tcc_pressure() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} mBar", self.tcc_pressure()), false) });
ui.label(if self.tcc_pressure() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} mBar", self.tcc_pressure()), false)
});
ui.end_row();
})
}
pub fn to_chart_data(&self) -> Vec<ChartData> {
vec![
ChartData::new(
"Requested pressures".into(),
vec![
("SPC pressure", self.spc_pressure() as f32, None),
("MPC pressure", self.mpc_pressure() as f32, None),
("TCC pressure", self.tcc_pressure() as f32, None),
],
Some((0.0, 0.0))
),
]
vec![ChartData::new(
"Requested pressures".into(),
vec![
("SPC pressure", self.spc_pressure() as f32, None),
("MPC pressure", self.mpc_pressure() as f32, None),
("TCC pressure", self.tcc_pressure() as f32, None),
],
Some((0.0, 0.0)),
)]
}
}
#[bitfield]
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct DataGearboxSensors {
@ -134,13 +154,13 @@ pub struct DataGearboxSensors {
pub calc_ratio: u16,
pub v_batt: u16,
pub atf_temp_c: u32,
pub parking_lock: u8
pub parking_lock: u8,
}
fn make_text<T: Into<String>>(t: T, e: bool) -> egui::RichText {
let mut s = RichText::new(t);
if e {
s = s.color(Color32::from_rgb(255,0,0))
s = s.color(Color32::from_rgb(255, 0, 0))
}
s
}
@ -148,48 +168,78 @@ fn make_text<T: Into<String>>(t: T, e: bool) -> egui::RichText {
impl DataGearboxSensors {
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
ui.label("N2 Pulse counter").on_hover_text("Raw counter value for PCNT for N2 hall effect RPM sensor");
ui.label(if self.n2_rpm() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} pulses/min", self.n2_rpm()), false) });
ui.label("N2 Pulse counter")
.on_hover_text("Raw counter value for PCNT for N2 hall effect RPM sensor");
ui.label(if self.n2_rpm() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} pulses/min", self.n2_rpm()), false)
});
ui.end_row();
ui.label("N3 Pulse counter").on_hover_text("Raw counter value for PCNT for N3 hall effect RPM sensor");
ui.label(if self.n3_rpm() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} pulses/min", self.n3_rpm()), false) });
ui.label("N3 Pulse counter")
.on_hover_text("Raw counter value for PCNT for N3 hall effect RPM sensor");
ui.label(if self.n3_rpm() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} pulses/min", self.n3_rpm()), false)
});
ui.end_row();
ui.label("Calculated input RPM").on_hover_text("Calculated input shaft RPM based on N2 and N3 raw values");
ui.label(if self.calculated_rpm() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{} RPM", self.calculated_rpm()), false) });
ui.label("Calculated input RPM")
.on_hover_text("Calculated input shaft RPM based on N2 and N3 raw values");
ui.label(if self.calculated_rpm() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{} RPM", self.calculated_rpm()), false)
});
ui.end_row();
ui.label("Calculated ratio").on_hover_text("Calculated gear ratio");
ui.label(if self.calculated_rpm() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{:.2}", self.calc_ratio() as f32 / 100.0), false) });
ui.label("Calculated ratio")
.on_hover_text("Calculated gear ratio");
ui.label(if self.calculated_rpm() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{:.2}", self.calc_ratio() as f32 / 100.0), false)
});
ui.end_row();
ui.label("Battery voltage");
ui.label(if self.v_batt() == u16::MAX { make_text("ERROR", true) } else { make_text(format!("{:.1} V", self.v_batt() as f32/1000.0), false) });
ui.label(if self.v_batt() == u16::MAX {
make_text("ERROR", true)
} else {
make_text(format!("{:.1} V", self.v_batt() as f32 / 1000.0), false)
});
ui.end_row();
ui.label("ATF Oil temperature\n(Only when parking lock off)");
ui.label(if self.parking_lock() != 0x00 { make_text("Cannot read\nParking lock engaged", true) } else { make_text(format!("{} *C", self.atf_temp_c() as i32), false) });
ui.label(if self.parking_lock() != 0x00 {
make_text("Cannot read\nParking lock engaged", true)
} else {
make_text(format!("{} *C", self.atf_temp_c() as i32), false)
});
ui.end_row();
ui.label("Parking lock");
ui.label(if self.parking_lock() == 0x00 { make_text("No", false) } else { make_text("Yes", false) });
ui.label(if self.parking_lock() == 0x00 {
make_text("No", false)
} else {
make_text("Yes", false)
});
ui.end_row();
})
}
pub fn to_chart_data(&self) -> Vec<ChartData> {
vec![
ChartData::new(
"RPM sensors".into(),
vec![
("N2 raw", self.n2_rpm() as f32, None),
("N3 raw", self.n3_rpm() as f32, None),
("Calculated RPM", self.calculated_rpm() as f32, None),
],
Some((0.0, 0.0))
),
]
vec![ChartData::new(
"RPM sensors".into(),
vec![
("N2 raw", self.n2_rpm() as f32, None),
("N3 raw", self.n3_rpm() as f32, None),
("Calculated RPM", self.calculated_rpm() as f32, None),
],
Some((0.0, 0.0)),
)]
}
}
@ -198,21 +248,26 @@ pub struct ChartData {
/// Min, Max
pub bounds: Option<(f32, f32)>,
pub group_name: String,
pub data: Vec<(String, f32, Option<String>)> // Data field name, data field value, data field unit
pub data: Vec<(String, f32, Option<String>)>, // Data field name, data field value, data field unit
}
impl ChartData {
pub fn new<T: Into<String>>(group_name: String, data: Vec<(T, f32, Option<T>)>, bounds: Option<(f32, f32)>) -> Self {
Self { bounds, group_name, data: data.into_iter().map(|(n, v, u)| (n.into(), v, u.map(|x| x.into()))).collect() }
pub fn new<T: Into<String>>(
group_name: String,
data: Vec<(T, f32, Option<T>)>,
bounds: Option<(f32, f32)>,
) -> Self {
Self {
bounds,
group_name,
data: data
.into_iter()
.map(|(n, v, u)| (n.into(), v, u.map(|x| x.into())))
.collect(),
}
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct DataDmaDump {
pub adc_detect: u16,
pub dma_buffer: Vec<u16>
}
#[bitfield]
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub struct DataSolenoids {
@ -231,46 +286,73 @@ pub struct DataSolenoids {
pub adjustment_mpc: u16,
pub y3_current: u16,
pub y4_current: u16,
pub y5_current: u16
pub y5_current: u16,
}
impl DataSolenoids {
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
ui.label("MPC Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA. Targ current {} mA. PWM Trim {:.2} %", self.mpc_pwm(), self.mpc_current(), self.targ_mpc_current(),
(self.adjustment_mpc() as f32 / 10.0) -100.0));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA. Targ current {} mA. PWM Trim {:.2} %",
self.mpc_pwm(),
self.mpc_current(),
self.targ_mpc_current(),
(self.adjustment_mpc() as f32 / 10.0) - 100.0
));
ui.end_row();
ui.label("SPC Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA. Targ current {} mA. PWM Trim {:.2} %", self.spc_pwm(), self.spc_current(), self.targ_spc_current(),
(self.adjustment_spc() as f32 / 10.0) -100.0));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA. Targ current {} mA. PWM Trim {:.2} %",
self.spc_pwm(),
self.spc_current(),
self.targ_spc_current(),
(self.adjustment_spc() as f32 / 10.0) - 100.0
));
ui.end_row();
ui.label("TCC Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA", self.tcc_pwm(), self.tcc_current()));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA",
self.tcc_pwm(),
self.tcc_current()
));
ui.end_row();
ui.label("Y3 shift Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA", self.y3_pwm(), self.y3_current()));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA",
self.y3_pwm(),
self.y3_current()
));
ui.end_row();
ui.label("Y4 shift Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA", self.y4_pwm(), self.y4_current()));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA",
self.y4_pwm(),
self.y4_current()
));
ui.end_row();
ui.label("Y5 shift Solenoid");
ui.label(format!("PWM {:>4}/4096, Est current {} mA", self.y5_pwm(), self.y5_current()));
ui.label(format!(
"PWM {:>4}/4096, Est current {} mA",
self.y5_pwm(),
self.y5_current()
));
ui.end_row();
ui.label("Total current consumption");
ui.label(format!("{} mA",
self.y5_current() as u32 +
self.y4_current() as u32 +
self.y3_current() as u32 +
self.mpc_current() as u32 +
self.spc_current() as u32 +
self.tcc_current() as u32
ui.label(format!(
"{} mA",
self.y5_current() as u32
+ self.y4_current() as u32
+ self.y3_current() as u32
+ self.mpc_current() as u32
+ self.spc_current() as u32
+ self.tcc_current() as u32
));
ui.end_row();
})
@ -288,7 +370,7 @@ impl DataSolenoids {
("Y4 Solenoid", self.y4_pwm() as f32, None),
("Y5 Solenoid", self.y5_pwm() as f32, None),
],
Some((0.0, 4096.0))
Some((0.0, 4096.0)),
),
ChartData::new(
"Solenoid Current".into(),
@ -300,25 +382,25 @@ impl DataSolenoids {
("Y4 Solenoid", self.y4_current() as f32, Some("mA")),
("Y5 Solenoid", self.y5_current() as f32, Some("mA")),
],
Some((0.0, 6600.0))
Some((0.0, 6600.0)),
),
]
}
}
#[derive(BitfieldSpecifier)]
#[bits=8]
#[bits = 8]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum PaddlePosition {
None,
Plus,
Minus,
PlusAndMinus,
SNV = 0xFF
SNV = 0xFF,
}
#[derive(BitfieldSpecifier)]
#[bits=8]
#[bits = 8]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
pub enum ShifterPosition {
Park,
@ -334,7 +416,7 @@ pub enum ShifterPosition {
Three,
Two,
One,
SNV = 0xFF
SNV = 0xFF,
}
#[bitfield]
@ -351,50 +433,111 @@ pub struct DataCanDump {
pub selector_position: ShifterPosition,
pub paddle_position: PaddlePosition,
pub engine_rpm: u16,
pub fuel_flow: u16
pub fuel_flow: u16,
}
impl DataCanDump {
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
ui.label("Accelerator pedal position");
ui.label(if self.pedal_position() == u8::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} %", self.pedal_position() as f32 / 250.0 * 100.0), false) });
ui.label(if self.pedal_position() == u8::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} %", self.pedal_position() as f32 / 250.0 * 100.0),
false,
)
});
ui.end_row();
ui.label("Engine RPM");
ui.label(if self.engine_rpm() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{} RPM", self.engine_rpm() as f32), false) });
ui.label(if self.engine_rpm() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(format!("{} RPM", self.engine_rpm() as f32), false)
});
ui.end_row();
ui.label("Engine minimum torque");
ui.label(if self.min_torque_ms() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} Nm", self.min_torque_ms() as f32 / 4.0 - 500.0), false) });
ui.label(if self.min_torque_ms() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} Nm", self.min_torque_ms() as f32 / 4.0 - 500.0),
false,
)
});
ui.end_row();
ui.label("Engine maximum torque");
ui.label(if self.max_torque_ms() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} Nm", self.max_torque_ms() as f32 / 4.0 - 500.0), false) });
ui.label(if self.max_torque_ms() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} Nm", self.max_torque_ms() as f32 / 4.0 - 500.0),
false,
)
});
ui.end_row();
ui.label("Engine static torque");
ui.label(if self.static_torque() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} Nm", self.static_torque() as f32 / 4.0 - 500.0), false) });
ui.label(if self.static_torque() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} Nm", self.static_torque() as f32 / 4.0 - 500.0),
false,
)
});
ui.end_row();
ui.label("Driver req torque");
ui.label(if self.driver_torque() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} Nm", self.driver_torque() as f32 / 4.0 - 500.0), false) });
ui.label(if self.driver_torque() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} Nm", self.driver_torque() as f32 / 4.0 - 500.0),
false,
)
});
ui.end_row();
ui.label("Rear right wheel speed");
ui.label(if self.right_rear_rpm() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} RPM", self.right_rear_rpm() as f32 / 2.0), false) });
ui.label(if self.right_rear_rpm() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} RPM", self.right_rear_rpm() as f32 / 2.0),
false,
)
});
ui.end_row();
ui.label("Rear left wheel speed");
ui.label(if self.left_rear_rpm() == u16::MAX { make_text("Signal not available", true) } else { make_text(format!("{:.1} RPM", self.left_rear_rpm() as f32 / 2.0), false) });
ui.label(if self.left_rear_rpm() == u16::MAX {
make_text("Signal not available", true)
} else {
make_text(
format!("{:.1} RPM", self.left_rear_rpm() as f32 / 2.0),
false,
)
});
ui.end_row();
ui.label("Gear selector position");
ui.label(if self.selector_position() == ShifterPosition::SNV { make_text("Signal not available", true) } else { make_text(format!("{:?}", self.selector_position()), false) });
ui.label(if self.selector_position() == ShifterPosition::SNV {
make_text("Signal not available", true)
} else {
make_text(format!("{:?}", self.selector_position()), false)
});
ui.end_row();
ui.label("Shift paddle position");
ui.label(if self.paddle_position() == PaddlePosition::SNV { make_text("Signal not available", true) } else { make_text(format!("{:?}", self.paddle_position()), false) });
ui.label(if self.paddle_position() == PaddlePosition::SNV {
make_text("Signal not available", true)
} else {
make_text(format!("{:?}", self.paddle_position()), false)
});
ui.end_row();
ui.label("Fuel flow");
@ -424,18 +567,16 @@ impl DataCanDump {
} else {
self.driver_torque() as f32 / 4.0 - 500.0
};
vec![
ChartData::new(
"Engine torque".into(),
vec![
("Max", max, None),
("Min", min, None),
("Static", sta, None),
("Driver", drv, None)
],
None
),
]
vec![ChartData::new(
"Engine torque".into(),
vec![
("Max", max, None),
("Min", min, None),
("Static", sta, None),
("Driver", drv, None),
],
None,
)]
}
}
@ -459,8 +600,8 @@ impl DataSysUsage {
let p_f = self.free_psram() as f32;
let p_t = self.total_psram() as f32;
let used_ram_perc = 100f32 * (r_t-r_f) / r_t;
let used_psram_perc = 100f32 * (p_t-p_f) / p_t;
let used_ram_perc = 100f32 * (r_t - r_f) / r_t;
let used_psram_perc = 100f32 * (p_t - p_f) / p_t;
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
ui.label("Core 1 usage");
@ -472,11 +613,19 @@ impl DataSysUsage {
ui.end_row();
ui.label("Free internal RAM");
ui.label(format!("{:.1} Kb ({:.1}% Used)", self.free_ram() as f32 / 1024.0, used_ram_perc));
ui.label(format!(
"{:.1} Kb ({:.1}% Used)",
self.free_ram() as f32 / 1024.0,
used_ram_perc
));
ui.end_row();
ui.label("Free PSRAM");
ui.label(format!("{:.1} Kb ({:.1}% Used)", self.free_psram() as f32 / 1024.0, used_psram_perc));
ui.label(format!(
"{:.1} Kb ({:.1}% Used)",
self.free_psram() as f32 / 1024.0,
used_psram_perc
));
ui.end_row();
ui.label("Num. OS Tasks");
@ -486,16 +635,14 @@ impl DataSysUsage {
}
pub fn to_chart_data(&self) -> Vec<ChartData> {
vec![
ChartData::new(
"CPU Usage".into(),
vec![
("Core 1", self.core1_usage() as f32 / 10.0, None),
("Core 2", self.core2_usage() as f32 / 10.0, None),
],
Some((0.0, 100.0))
),
]
vec![ChartData::new(
"CPU Usage".into(),
vec![
("Core 1", self.core1_usage() as f32 / 10.0, None),
("Core 2", self.core2_usage() as f32 / 10.0, None),
],
Some((0.0, 100.0)),
)]
}
}
@ -510,7 +657,7 @@ pub enum ShiftIdx {
FourThree = 6,
ThreeTwo = 7,
TwoOne = 8,
Unknown = 0xFF
Unknown = 0xFF,
}
#[bitfield]
@ -531,7 +678,6 @@ pub struct DataShiftManager {
impl DataShiftManager {
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
egui::Grid::new("SM").striped(true).show(ui, |ui| {
ui.label("SPC Pressure");
ui.label(format!("{} mBar", self.spc_pressure_mbar()));
@ -572,23 +718,20 @@ impl DataShiftManager {
6 => "4 -> 3",
7 => "3 -> 2",
8 => "2 -> 1",
_ => "UNKNOWN"
_ => "UNKNOWN",
});
ui.end_row();
})
}
pub fn to_chart_data(&self) -> Vec<ChartData> {
vec![
ChartData::new(
"RPMs".into(),
vec![
("Input", self.input_rpm() as f32, None),
("Engine", self.engine_rpm() as f32, None),
],
None
),
]
vec![ChartData::new(
"RPMs".into(),
vec![
("Input", self.input_rpm() as f32, None),
("Engine", self.engine_rpm() as f32, None),
],
None,
)]
}
}
}

View File

@ -1,14 +1,23 @@
use std::{sync::{Arc, Mutex}};
use std::sync::{Arc, Mutex};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType}, DiagnosticServer};
use eframe::{egui::{plot::{Plot, Line, LinkedAxisGroup, VLine, Text, LineStyle, PlotUi, Corner, PlotPoint}, RichText}, epaint::{Stroke, Color32}};
use serde::{Serialize, Deserialize};
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, SessionType},
DiagnosticServer,
};
use eframe::{
egui::{
plot::{Corner, Line, LineStyle, LinkedAxisGroup, Plot, PlotPoint, PlotUi, Text, VLine},
RichText,
},
epaint::{Color32, Stroke},
};
use serde::{Deserialize, Serialize};
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
use crate::ui::egui::ComboBox;
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
// Data structure for shift report
pub const MAX_POINTS_PER_SR_ARRAY: usize = 6000/50;
pub const MAX_POINTS_PER_SR_ARRAY: usize = 6000 / 50;
pub const REPORT_LEN: usize = std::mem::size_of::<ShiftReport>();
#[repr(packed)]
@ -17,10 +26,9 @@ struct ShiftPhase {
ramp_time: u16,
hold_time: u16,
spc_pressure: u16,
mpc_pressure: u16
mpc_pressure: u16,
}
#[repr(packed)]
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
struct ShiftReport {
@ -81,10 +89,10 @@ impl SerializedShiftReport {
requested_torque: rpt.requested_torque,
interval_points: rpt.interval_points,
report_len: rpt.report_len,
engine_rpm: (&{rpt.engine_rpm}).to_vec(),
input_rpm: (&{rpt.input_rpm}).to_vec(),
output_rpm: (&{rpt.output_rpm}).to_vec(),
engine_torque: (&{rpt.engine_torque}).to_vec(),
engine_rpm: (&{ rpt.engine_rpm }).to_vec(),
input_rpm: (&{ rpt.input_rpm }).to_vec(),
output_rpm: (&{ rpt.output_rpm }).to_vec(),
engine_torque: (&{ rpt.engine_torque }).to_vec(),
total_ms: rpt.total_ms,
initial_mpc_pressure: rpt.initial_mpc_pressure,
bleed_data: rpt.bleed_data,
@ -139,20 +147,22 @@ impl SerializedShiftReport {
}
}
pub struct ShiftReportPage{
pub struct ShiftReportPage {
bar: MainStatusBar,
curr_report: Option<ShiftReport>,
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
report_list: Vec<(u8, i32, u8, u8)>, // ID, ATF Temp, curr, target
err: Option<String>,
select_id: u32,
axis_group: LinkedAxisGroup
axis_group: LinkedAxisGroup,
}
impl ShiftReportPage {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
server.lock().unwrap().set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
server
.lock()
.unwrap()
.set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
Self {
bar,
curr_report: None,
@ -160,7 +170,7 @@ impl ShiftReportPage {
err: None,
server,
select_id: 0,
axis_group: LinkedAxisGroup::x()
axis_group: LinkedAxisGroup::x(),
}
}
pub fn parse_error(&mut self, e: ecu_diagnostics::DiagError) {
@ -173,24 +183,32 @@ impl ShiftReportPage {
} else {
self.err = Some(format!("Error ECU rejected the request: {}", e))
}
},
_ => self.err = Some(format!("Error querying ECU: {}", e))
}
_ => self.err = Some(format!("Error querying ECU: {}", e)),
}
}
}
impl crate::window::InterfacePage for ShiftReportPage {
fn make_ui(&mut self, ui: &mut eframe::egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
fn make_ui(
&mut self,
ui: &mut eframe::egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
ui.heading("Shift report history");
if ui.button("Query a list of available shifts").clicked() {
let rpt_query = self.server.lock().unwrap().send_byte_array_with_response(&[0x88, 0x00, 0x00]);
let rpt_query = self
.server
.lock()
.unwrap()
.send_byte_array_with_response(&[0x88, 0x00, 0x00]);
match rpt_query {
Ok(mut res) => {
res.drain(0..1);
if res.len() % 4 != 0 {
self.err = Some(format!("Incorrect report length!"));
return PageAction::None
return PageAction::None;
}
self.report_list.clear();
for chunk in res.chunks(4) {
@ -202,8 +220,8 @@ impl crate::window::InterfacePage for ShiftReportPage {
self.report_list.push((id, atf as i32, targ, curr))
}
}
},
Err(e) => self.parse_error(e)
}
Err(e) => self.parse_error(e),
}
}
@ -211,22 +229,34 @@ impl crate::window::InterfacePage for ShiftReportPage {
ui.label("No shift data available");
} else {
ComboBox::from_label("Select shift record")
.width(400.0)
.selected_text(format!("Shift ##{}", self.select_id))
.show_ui(ui, |cb_ui| {
for shift in &self.report_list {
cb_ui.selectable_value(&mut self.select_id, shift.0 as u32, format!("Shift ##{}", shift.0));
}
});
.width(400.0)
.selected_text(format!("Shift ##{}", self.select_id))
.show_ui(ui, |cb_ui| {
for shift in &self.report_list {
cb_ui.selectable_value(
&mut self.select_id,
shift.0 as u32,
format!("Shift ##{}", shift.0),
);
}
});
if ui.button("Query shift").clicked() {
let rpt_query = self.server.lock().unwrap().send_byte_array_with_response(&[0x88, 0x01, self.select_id as u8]);
let rpt_query = self.server.lock().unwrap().send_byte_array_with_response(&[
0x88,
0x01,
self.select_id as u8,
]);
match rpt_query {
Ok(mut res) => {
res.drain(0..1);
if res.len() != REPORT_LEN {
self.err = Some(format!("Incorrect report length. Want {} bytes, got {} bytes!", REPORT_LEN, res.len()));
return PageAction::None
self.err = Some(format!(
"Incorrect report length. Want {} bytes, got {} bytes!",
REPORT_LEN,
res.len()
));
return PageAction::None;
}
unsafe {
@ -237,10 +267,12 @@ impl crate::window::InterfacePage for ShiftReportPage {
self.curr_report = Some(rpt)
}
let x = self.curr_report.clone().unwrap();
println!("{} {} {}", &{x.total_ms}, &{x.transition_start}, &{x.transition_end});
},
println!("{} {} {}", &{ x.total_ms }, &{ x.transition_start }, &{
x.transition_end
});
}
Err(e) => self.parse_error(e)
Err(e) => self.parse_error(e),
}
}
}
@ -250,34 +282,45 @@ impl crate::window::InterfacePage for ShiftReportPage {
}
if let Some(report) = &self.curr_report {
ui.heading("Shift stats:");
ui.label(format!("Gear {} to gear {}", report.targ_curr & 0xF, (report.targ_curr & 0xF0) >> 4));
ui.label(format!("ATF Temp: {}°C", &{report.atf_temp_c}));
ui.label(format!(
"Gear {} to gear {}",
report.targ_curr & 0xF,
(report.targ_curr & 0xF0) >> 4
));
ui.label(format!("ATF Temp: {}°C", &{ report.atf_temp_c }));
ui.label(format!("Profile: {}", match report.profile { // Profiles.h
0 => "Standard",
1 => "Comfort",
2 => "Winter",
3 => "Agility",
4 => "Manual",
_ => "Unknown"
}));
ui.label(
if report.timeout == 0 {
RichText::new(format!("Shift completed after {} ms", &{report.total_ms}))
} else {
RichText::new(format!("Shift timed out after {} ms!", &{report.total_ms})).color(Color32::from_rgb(255, 0, 0))
ui.label(format!(
"Profile: {}",
match report.profile {
// Profiles.h
0 => "Standard",
1 => "Comfort",
2 => "Winter",
3 => "Agility",
4 => "Manual",
_ => "Unknown",
}
);
));
ui.label(if report.timeout == 0 {
RichText::new(format!("Shift completed after {} ms", &{ report.total_ms }))
} else {
RichText::new(format!("Shift timed out after {} ms!", &{
report.total_ms
}))
.color(Color32::from_rgb(255, 0, 0))
});
if report.timeout == 0 {
ui.label(format!("Gear transition period: {} ms", report.transition_end as i64 - report.transition_start as i64));
ui.label(format!(
"Gear transition period: {} ms",
report.transition_end as i64 - report.transition_start as i64
));
}
let time_axis: Vec<u16> = (0..=report.total_ms).step_by(report.interval_points as usize).collect();
let time_axis: Vec<u16> = (0..=report.total_ms)
.step_by(report.interval_points as usize)
.collect();
let mut time: f64 = 0.0;
// Add pressure line (Static)
let mut pressure_spc_points: Vec<[f64; 2]> = Vec::new();
@ -324,11 +367,15 @@ impl crate::window::InterfacePage for ShiftReportPage {
pressure_spc_points.push([time, report.max_pressure_data.spc_pressure as f64]);
pressure_mpc_points.push([time, report.max_pressure_data.mpc_pressure as f64]);
let mut rpm_max = *std::cmp::max(unsafe { report.engine_rpm }.iter().max().unwrap(), unsafe { report.input_rpm }.iter().max().unwrap()) as f64;
rpm_max = std::cmp::max(rpm_max as u16, *unsafe { report.output_rpm }.iter().max().unwrap()) as f64;
let trq_max = *unsafe { report.engine_torque}.iter().max().unwrap() as f64;
let mut rpm_max = *std::cmp::max(
unsafe { report.engine_rpm }.iter().max().unwrap(),
unsafe { report.input_rpm }.iter().max().unwrap(),
) as f64;
rpm_max = std::cmp::max(
rpm_max as u16,
*unsafe { report.output_rpm }.iter().max().unwrap(),
) as f64;
let trq_max = *unsafe { report.engine_torque }.iter().max().unwrap() as f64;
for x in 0..report.report_len as usize {
engine_rpm_points.push([time_axis[x] as f64, report.engine_rpm[x] as f64]);
@ -339,40 +386,63 @@ impl crate::window::InterfacePage for ShiftReportPage {
// Add phase indication lines
let spc_pressure_line = Line::new(pressure_spc_points).name("SPC Pressure (mBar)");
let mpc_pressure_line = Line::new(pressure_mpc_points).name("MPC Pressure (mBar)");
let engine_line = Line::new(engine_rpm_points).name("Engine (RPM)");
let output_line = Line::new(output_rpm_points).name("Output shaft (RPM)");
let input_line = Line::new(input_rpm_points).name("Input shaft (RPM)");
let torque_line = Line::new(torque_points).name("Engine torque (Nm)");
time = 0.0;
time += (report.bleed_data.hold_time+report.bleed_data.ramp_time) as f64;
time += (report.bleed_data.hold_time + report.bleed_data.ramp_time) as f64;
let bleed_end_time = time;
time += (report.fill_data.hold_time+report.fill_data.ramp_time) as f64;
time += (report.fill_data.hold_time + report.fill_data.ramp_time) as f64;
let fill_end_time = time;
time += (report.torque_data.hold_time+report.torque_data.ramp_time) as f64;
time += (report.torque_data.hold_time + report.torque_data.ramp_time) as f64;
let torque_end_time = time;
time += (report.overlap_data.hold_time+report.overlap_data.ramp_time) as f64;
time += (report.overlap_data.hold_time + report.overlap_data.ramp_time) as f64;
let overlap_end_time = time;
time += (report.max_pressure_data.hold_time+report.max_pressure_data.ramp_time) as f64;
time +=
(report.max_pressure_data.hold_time + report.max_pressure_data.ramp_time) as f64;
let max_p_end_time = time;
// #27ae60
let phase_colour = Color32::from_rgb(0x27, 0xae, 0x60);
let ok_colour = Color32::from_rgb(0, 255, 0);
let timeout_colour = Color32::from_rgb(255, 0, 0);
let height_per_chart = (ui.available_height()-50.0)/3.0;
let height_per_chart = (ui.available_height() - 50.0) / 3.0;
let legand = eframe::egui::plot::Legend::default().position(Corner::RightBottom);
let timeout = report.timeout != 0;
let add_shift_regions = |plot_ui: &mut PlotUi| {
plot_ui.vline(VLine::new(bleed_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
plot_ui.vline(VLine::new(fill_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
plot_ui.vline(VLine::new(torque_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
plot_ui.vline(VLine::new(overlap_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
plot_ui.vline(VLine::new(max_p_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
plot_ui.vline(VLine::new(report.total_ms).stroke(Stroke::new(2.0, if timeout {timeout_colour} else {ok_colour})));
plot_ui.vline(
VLine::new(bleed_end_time)
.style(LineStyle::dashed_loose())
.color(phase_colour),
);
plot_ui.vline(
VLine::new(fill_end_time)
.style(LineStyle::dashed_loose())
.color(phase_colour),
);
plot_ui.vline(
VLine::new(torque_end_time)
.style(LineStyle::dashed_loose())
.color(phase_colour),
);
plot_ui.vline(
VLine::new(overlap_end_time)
.style(LineStyle::dashed_loose())
.color(phase_colour),
);
plot_ui.vline(
VLine::new(max_p_end_time)
.style(LineStyle::dashed_loose())
.color(phase_colour),
);
plot_ui.vline(VLine::new(report.total_ms).stroke(Stroke::new(
2.0,
if timeout { timeout_colour } else { ok_colour },
)));
};
let mut plot_pressure = Plot::new("SPC pressure")
@ -384,15 +454,30 @@ impl crate::window::InterfacePage for ShiftReportPage {
.include_y(0)
.include_y(8000)
.link_axis(self.axis_group.clone())
.show(ui, |plot_ui| {
.show(ui, |plot_ui| {
plot_ui.line(spc_pressure_line);
plot_ui.line(mpc_pressure_line);
add_shift_regions(plot_ui);
plot_ui.text(Text::new(PlotPoint::new(bleed_end_time/2.0, 7700), "Bleed"));
plot_ui.text(Text::new(PlotPoint::new((bleed_end_time+fill_end_time)/2.0, 7700), "Fill"));
plot_ui.text(Text::new(PlotPoint::new((fill_end_time+torque_end_time)/2.0, 7700), "Torque"));
plot_ui.text(Text::new(PlotPoint::new((torque_end_time+overlap_end_time)/2.0, 7700), "Overlap"));
plot_ui.text(Text::new(PlotPoint::new((overlap_end_time+max_p_end_time)/2.0, 7700), "Max P"));
plot_ui.text(Text::new(
PlotPoint::new(bleed_end_time / 2.0, 7700),
"Bleed",
));
plot_ui.text(Text::new(
PlotPoint::new((bleed_end_time + fill_end_time) / 2.0, 7700),
"Fill",
));
plot_ui.text(Text::new(
PlotPoint::new((fill_end_time + torque_end_time) / 2.0, 7700),
"Torque",
));
plot_ui.text(Text::new(
PlotPoint::new((torque_end_time + overlap_end_time) / 2.0, 7700),
"Overlap",
));
plot_ui.text(Text::new(
PlotPoint::new((overlap_end_time + max_p_end_time) / 2.0, 7700),
"Max P",
));
});
let mut plot_rpm = Plot::new("Input/Engine RPM")
@ -403,19 +488,32 @@ impl crate::window::InterfacePage for ShiftReportPage {
.include_x(report.total_ms as f32 * 1.2)
.include_y(rpm_max * 1.2)
.link_axis(self.axis_group.clone())
.show(ui, |plot_ui| {
.show(ui, |plot_ui| {
plot_ui.line(engine_line);
plot_ui.line(input_line);
plot_ui.line(output_line);
// Show the User where the gear shift occurred
if report.timeout == 0 {
plot_ui.vline(VLine::new(report.transition_start).style(LineStyle::dashed_loose()).color(Color32::from_rgb(255, 192, 203)));
plot_ui.vline(VLine::new(report.transition_end).style(LineStyle::dashed_loose()).color(Color32::from_rgb(255, 192, 203)));
plot_ui.text(Text::new(PlotPoint::new((report.transition_start+report.transition_end)/2, rpm_max * 0.9), "SHIFT"));
plot_ui.vline(
VLine::new(report.transition_start)
.style(LineStyle::dashed_loose())
.color(Color32::from_rgb(255, 192, 203)),
);
plot_ui.vline(
VLine::new(report.transition_end)
.style(LineStyle::dashed_loose())
.color(Color32::from_rgb(255, 192, 203)),
);
plot_ui.text(Text::new(
PlotPoint::new(
(report.transition_start + report.transition_end) / 2,
rpm_max * 0.9,
),
"SHIFT",
));
}
add_shift_regions(plot_ui)
});
let mut plot_torque = Plot::new("Engine torque")
.legend(legand)
@ -425,7 +523,7 @@ impl crate::window::InterfacePage for ShiftReportPage {
.include_x(report.total_ms as f32 * 1.2)
.include_y(trq_max * 1.2)
.link_axis(self.axis_group.clone())
.show(ui, |plot_ui| {
.show(ui, |plot_ui| {
plot_ui.line(torque_line);
add_shift_regions(plot_ui);
});
@ -441,4 +539,4 @@ impl crate::window::InterfacePage for ShiftReportPage {
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
Some(Box::new(self.bar.clone()))
}
}
}

View File

@ -1,21 +1,28 @@
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64}, RwLock}, thread, time::{Duration, Instant}};
use std::{
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc, Mutex, RwLock,
},
thread,
time::{Duration, Instant},
};
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, SessionType};
use eframe::egui::plot::{Plot, Legend, Line, PlotPoints};
use eframe::egui::plot::{Legend, Line, Plot, PlotPoints};
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
use super::rli::{DataSolenoids, RecordIdents, LocalRecordData};
use super::rli::{DataSolenoids, LocalRecordData, RecordIdents};
const UPDATE_DELAY_MS: u64 = 100;
pub struct SolenoidPage{
pub struct SolenoidPage {
bar: MainStatusBar,
query_ecu: Arc<AtomicBool>,
last_update_time: Arc<AtomicU64>,
curr_values: Arc<RwLock<Option<DataSolenoids>>>,
prev_values: Arc<RwLock<Option<DataSolenoids>>>,
time_since_launch: Instant
time_since_launch: Instant,
}
impl SolenoidPage {
@ -34,34 +41,39 @@ impl SolenoidPage {
let last_update = Arc::new(AtomicU64::new(0));
let last_update_t = last_update.clone();
thread::spawn(move|| {
let _ = server.lock().unwrap().set_diagnostic_session_mode(SessionType::Normal);
thread::spawn(move || {
let _ = server
.lock()
.unwrap()
.set_diagnostic_session_mode(SessionType::Normal);
while run_t.load(Ordering::Relaxed) {
let start = Instant::now();
if let Ok(r) = RecordIdents::SolenoidStatus.query_ecu(&mut *server.lock().unwrap()) {
if let Ok(r) = RecordIdents::SolenoidStatus.query_ecu(&mut *server.lock().unwrap())
{
if let LocalRecordData::Solenoids(s) = r {
let curr = *store_t.read().unwrap();
*store_old_t.write().unwrap() = curr;
*store_t.write().unwrap() = Some(s);
last_update_t.store(launch_time_t.elapsed().as_millis() as u64, Ordering::Relaxed);
last_update_t.store(
launch_time_t.elapsed().as_millis() as u64,
Ordering::Relaxed,
);
}
}
let taken = start.elapsed().as_millis() as u64;
if taken < UPDATE_DELAY_MS {
std::thread::sleep(Duration::from_millis(UPDATE_DELAY_MS-taken));
std::thread::sleep(Duration::from_millis(UPDATE_DELAY_MS - taken));
}
}
});
Self {
bar,
query_ecu: run,
curr_values: store,
last_update_time: last_update,
prev_values: store_old,
time_since_launch: launch_time
time_since_launch: launch_time,
}
}
}
@ -69,13 +81,13 @@ impl SolenoidPage {
const GRAPH_TIME_MS: f64 = 100.0;
const MAX_DUTY: u16 = 0xFFF; // 12bit pwm (4096)
const VOLTAGE_HIGH: f64 = 12.0;
const VOLTAGE_HIGH: f64 = 12.0;
const VOLTAGE_LOW: f64 = 0.0;
fn make_line_duty_pwm(duty: f32, freq: u16, x_off: f64, y_off: f64) -> PlotPoints {
let num_pulses = freq / GRAPH_TIME_MS as u16;
let pulse_width = GRAPH_TIME_MS as f64 / num_pulses as f64;
let pulse_on_width = (duty as f64/4096.0) * pulse_width;
let pulse_on_width = (duty as f64 / 4096.0) * pulse_width;
let pulse_off_width = pulse_width - pulse_on_width;
let mut points: Vec<[f64; 2]> = Vec::new();
@ -94,7 +106,7 @@ fn make_line_duty_pwm(duty: f32, freq: u16, x_off: f64, y_off: f64) -> PlotPoint
points.push([curr_x_pos, VOLTAGE_HIGH]); // High, left
curr_x_pos += pulse_off_width;
points.push([curr_x_pos, VOLTAGE_HIGH]); // High, right
// Now vertical line
// Now vertical line
points.push([curr_x_pos, VOLTAGE_LOW]);
curr_x_pos += pulse_on_width;
points.push([curr_x_pos, VOLTAGE_LOW]);
@ -108,19 +120,24 @@ fn make_line_duty_pwm(duty: f32, freq: u16, x_off: f64, y_off: f64) -> PlotPoint
points.into()
}
impl crate::window::InterfacePage for SolenoidPage {
fn make_ui(&mut self, ui: &mut eframe::egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
fn make_ui(
&mut self,
ui: &mut eframe::egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
ui.heading("Solenoid live view");
let mut curr = self.curr_values.read().unwrap().clone().unwrap_or_default();
let mut prev = self.prev_values.read().unwrap().clone().unwrap_or_default();
let ms_since_update = std::cmp::min(UPDATE_DELAY_MS, self.time_since_launch.elapsed().as_millis() as u64 - self.last_update_time.load(Ordering::Relaxed));
let ms_since_update = std::cmp::min(
UPDATE_DELAY_MS,
self.time_since_launch.elapsed().as_millis() as u64
- self.last_update_time.load(Ordering::Relaxed),
);
let mut proportion_curr: f32 = (ms_since_update as f32)/UPDATE_DELAY_MS as f32; // Percentage of old value to use
let mut proportion_curr: f32 = (ms_since_update as f32) / UPDATE_DELAY_MS as f32; // Percentage of old value to use
let mut proportion_prev: f32 = 1.0 - proportion_curr; // Percentage of curr value to use
if ms_since_update == 0 {
@ -132,15 +149,78 @@ impl crate::window::InterfacePage for SolenoidPage {
}
let mut lines = Vec::new();
let mut legend = Legend::default();
let c_height = (ui.available_height()-50.0)/6.0;
let c_height = (ui.available_height() - 50.0) / 6.0;
lines.push(("MPC", Line::new(make_line_duty_pwm((curr.mpc_pwm() as f32 * proportion_curr) + (prev.mpc_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("MPC").width(2.0)));
lines.push(("SPC", Line::new(make_line_duty_pwm((curr.spc_pwm() as f32 * proportion_curr) + (prev.spc_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("SPC").width(2.0)));
lines.push(("TCC", Line::new(make_line_duty_pwm((curr.tcc_pwm() as f32 * proportion_curr) + (prev.tcc_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("TCC").width(2.0)));
lines.push((
"MPC",
Line::new(make_line_duty_pwm(
(curr.mpc_pwm() as f32 * proportion_curr)
+ (prev.mpc_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("MPC")
.width(2.0),
));
lines.push((
"SPC",
Line::new(make_line_duty_pwm(
(curr.spc_pwm() as f32 * proportion_curr)
+ (prev.spc_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("SPC")
.width(2.0),
));
lines.push((
"TCC",
Line::new(make_line_duty_pwm(
(curr.tcc_pwm() as f32 * proportion_curr)
+ (prev.tcc_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("TCC")
.width(2.0),
));
lines.push(("Y3", Line::new(make_line_duty_pwm((curr.y3_pwm() as f32 * proportion_curr) + (prev.y3_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("Y3").width(2.0)));
lines.push(("Y4", Line::new(make_line_duty_pwm((curr.y4_pwm() as f32 * proportion_curr) + (prev.y4_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("Y4").width(2.0)));
lines.push(("Y5", Line::new(make_line_duty_pwm((curr.y5_pwm() as f32 * proportion_curr) + (prev.y5_pwm() as f32 * proportion_prev), 1000, 0.0, 0.0)).name("Y5").width(2.0)));
lines.push((
"Y3",
Line::new(make_line_duty_pwm(
(curr.y3_pwm() as f32 * proportion_curr) + (prev.y3_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("Y3")
.width(2.0),
));
lines.push((
"Y4",
Line::new(make_line_duty_pwm(
(curr.y4_pwm() as f32 * proportion_curr) + (prev.y4_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("Y4")
.width(2.0),
));
lines.push((
"Y5",
Line::new(make_line_duty_pwm(
(curr.y5_pwm() as f32 * proportion_curr) + (prev.y5_pwm() as f32 * proportion_prev),
1000,
0.0,
0.0,
))
.name("Y5")
.width(2.0),
));
for line in lines {
let mut plot = Plot::new(format!("Solenoid {}", line.0))
@ -152,9 +232,7 @@ impl crate::window::InterfacePage for SolenoidPage {
plot = plot.include_x(0);
plot = plot.include_x(100);
plot.show(ui, |plot_ui| {
plot_ui.line(line.1)
});
plot.show(ui, |plot_ui| plot_ui.line(line.1));
}
ui.ctx().request_repaint();
PageAction::None

View File

@ -1,16 +1,19 @@
use std::{
sync::{
Arc, Mutex, RwLock,
}, ops::DerefMut, time::Instant,
ops::DerefMut,
sync::{Arc, Mutex, RwLock},
time::Instant,
};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagServerResult, DiagnosticServer, DiagError};
use eframe::egui::*;
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, ResetMode, SessionType},
DiagError, DiagServerResult, DiagnosticServer,
};
use eframe::egui;
use eframe::egui::*;
use nfd::Response;
use crate::{
usb_hw::flasher::{bin::{Firmware, load_binary, FirmwareHeader}},
usb_hw::flasher::bin::{load_binary, Firmware, FirmwareHeader},
window::{InterfacePage, PageAction},
};
@ -18,10 +21,14 @@ use crate::{
pub enum FlashState {
None,
Prepare,
WritingBlock { id: u32, out_of: u32, bytes_written: u32 },
WritingBlock {
id: u32,
out_of: u32,
bytes_written: u32,
},
Verify,
Completed,
Aborted(String)
Aborted(String),
}
impl FlashState {
@ -29,7 +36,11 @@ impl FlashState {
match self {
FlashState::None => true,
FlashState::Prepare => false,
FlashState::WritingBlock { id, out_of, bytes_written } => false,
FlashState::WritingBlock {
id,
out_of,
bytes_written,
} => false,
FlashState::Verify => false,
FlashState::Completed => true,
FlashState::Aborted(_) => true,
@ -46,13 +57,13 @@ pub struct FwUpdateUI {
flash_measure: Instant,
flash_speed: u32,
flash_eta: u32,
curr_fw: Option<FirmwareHeader>
curr_fw: Option<FirmwareHeader>,
}
pub struct FlasherMutate {}
impl FwUpdateUI {
pub fn new(server:Arc<Mutex<Kwp2000DiagnosticServer>>) -> Self {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>) -> Self {
Self {
server,
elf_path: None,
@ -62,7 +73,7 @@ impl FwUpdateUI {
flash_measure: Instant::now(),
flash_speed: 0,
flash_eta: 0,
curr_fw: None
curr_fw: None,
}
}
}
@ -71,7 +82,7 @@ fn init_flash_mode(server: &mut Kwp2000DiagnosticServer, flash_size: u32) -> Dia
server.set_diagnostic_session_mode(SessionType::Reprogramming)?;
let mut req: Vec<u8> = vec![0x34, 0x00, 0x00, 0x00, 0x00];
req.push((flash_size >> 16) as u8);
req.push((flash_size >> 8 ) as u8);
req.push((flash_size >> 8) as u8);
req.push((flash_size) as u8);
let resp = server.send_byte_array_with_response(&req)?;
let bs = (resp[1] as u16) << 8 | resp[2] as u16;
@ -83,19 +94,15 @@ fn on_flash_end(server: &mut Kwp2000DiagnosticServer) -> DiagServerResult<()> {
let status = server.send_byte_array_with_response(&[0x31, 0xE1])?;
if status[2] == 0x00 {
eprintln!("ECU Flash check OK! Rebooting");
return server.reset_ecu(ResetMode::PowerOnReset)
return server.reset_ecu(ResetMode::PowerOnReset);
} else {
eprintln!("ECU Flash check failed :(");
return Err(DiagError::NotSupported)
return Err(DiagError::NotSupported);
}
}
impl InterfacePage for FwUpdateUI {
fn make_ui(
&mut self,
ui: &mut egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
ui.heading("Firmware update");
ui.label(
RichText::new("Caution! Only use when car is off").color(Color32::from_rgb(255, 0, 0)),
@ -119,37 +126,35 @@ impl InterfacePage for FwUpdateUI {
if let Some(firmware) = &self.firmware {
ui.heading("New firmware");
ui.label(RichText::new(format!(
"Firmware size: {} bytes
"Firmware size: {} bytes
Firmware type: {}
Version: {}
IDF Version: {}
Compile time: {} on {}
",
firmware.raw.len(),
firmware.header.get_fw_name(),
firmware.header.get_version(),
firmware.header.get_idf_version(),
firmware.header.get_time(),
firmware.header.get_date()
firmware.raw.len(),
firmware.header.get_fw_name(),
firmware.header.get_version(),
firmware.header.get_idf_version(),
firmware.header.get_time(),
firmware.header.get_date()
)));
if let Some(c_fw) = self.curr_fw {
ui.heading("Current firmware");
ui.label(RichText::new(format!(
"
"
Firmware type: {}
Version: {}
IDF Version: {}
Compile time: {} on {}
",
c_fw.get_fw_name(),
c_fw.get_version(),
c_fw.get_idf_version(),
c_fw.get_time(),
c_fw.get_date()
c_fw.get_fw_name(),
c_fw.get_version(),
c_fw.get_idf_version(),
c_fw.get_time(),
c_fw.get_date()
)));
}
@ -157,29 +162,27 @@ c_fw.get_date()
if state.is_done() {
if ui.button("Query current firmware").clicked() {
let mut lock = self.server.lock().unwrap();
match lock.read_custom_local_identifier(0x28)
.and_then(|resp| {
if resp.len() != std::mem::size_of::<FirmwareHeader>() {
Err(DiagError::InvalidResponseLength)
} else {
Ok(unsafe { std::ptr::read::<FirmwareHeader>(resp.as_ptr() as *const _ ) })
}
} ) {
Ok(header) => {
self.curr_fw = Some(header)
},
Err(e) => {
// TODO
}
match lock.read_custom_local_identifier(0x28).and_then(|resp| {
if resp.len() != std::mem::size_of::<FirmwareHeader>() {
Err(DiagError::InvalidResponseLength)
} else {
Ok(unsafe {
std::ptr::read::<FirmwareHeader>(resp.as_ptr() as *const _)
})
}
}) {
Ok(header) => self.curr_fw = Some(header),
Err(e) => {
// TODO
}
}
}
if ui.button("Flash firmware").clicked() {
let c = self.server.clone();
let state_c = self.flash_state.clone();
let fw = firmware.clone();
let mut old_timeouts = (0, 0);
std::thread::spawn(move|| {
std::thread::spawn(move || {
let mut lock = c.lock().unwrap();
*state_c.write().unwrap() = FlashState::Prepare;
old_timeouts = (lock.get_read_timeout(), lock.get_write_timeout());
@ -188,8 +191,11 @@ c_fw.get_date()
match init_flash_mode(&mut lock.deref_mut(), fw.raw.len() as u32) {
Err(e) => {
lock.set_rw_timeout(old_timeouts.0, old_timeouts.1);
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU rejected flash programming mode: {}", e))
},
*state_c.write().unwrap() = FlashState::Aborted(format!(
"ECU rejected flash programming mode: {}",
e
))
}
Ok(size) => {
// Start the flasher!
let arr = fw.raw.chunks(size as usize);
@ -197,24 +203,34 @@ c_fw.get_date()
let mut failure = false;
let mut bytes_written = 0;
for (block_id, block) in arr.enumerate() {
let mut req = vec![0x36, ((block_id+1) & 0xFF) as u8]; // [Transfer data request, block counter]
let mut req = vec![0x36, ((block_id + 1) & 0xFF) as u8]; // [Transfer data request, block counter]
req.extend_from_slice(block); // Block to transfer
if let Err(e) = lock.send_byte_array_with_response(&req) {
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU failed to write data to flash: {}", e));
*state_c.write().unwrap() = FlashState::Aborted(format!(
"ECU failed to write data to flash: {}",
e
));
eprintln!("Writing failed! Error {}", e);
lock.set_rw_timeout(old_timeouts.0, old_timeouts.1);
failure = true;
break;
} else {
bytes_written += block.len() as u32;
*state_c.write().unwrap() = FlashState::WritingBlock { id: (block_id+1) as u32, out_of: total_blocks, bytes_written }
*state_c.write().unwrap() = FlashState::WritingBlock {
id: (block_id + 1) as u32,
out_of: total_blocks,
bytes_written,
}
}
}
if !failure {
*state_c.write().unwrap() = FlashState::Verify;
if let Err(e) = on_flash_end(&mut lock) {
lock.set_rw_timeout(old_timeouts.0, old_timeouts.1);
*state_c.write().unwrap() = FlashState::Aborted(format!("ECU failed to verify flash: {}", e))
*state_c.write().unwrap() = FlashState::Aborted(format!(
"ECU failed to verify flash: {}",
e
))
} else {
*state_c.write().unwrap() = FlashState::Completed;
}
@ -229,38 +245,64 @@ c_fw.get_date()
ui.label("DO NOT EXIT THE APP");
}
match &state {
FlashState::None => {},
FlashState::None => {}
FlashState::Prepare => {
egui::widgets::ProgressBar::new(0.0).show_percentage().animate(true).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);
let spd = (1000.0 * *bytes_written as f32 / self.flash_start.elapsed().as_millis() as f32) as u32;
}
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);
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!(
"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);
egui::widgets::ProgressBar::new(100.0)
.show_percentage()
.desired_width(300.0)
.ui(ui);
ui.label("Verifying written data...");
},
}
FlashState::Completed => {
ui.label(RichText::new("Flashing completed successfully!").color(Color32::from_rgb(0, 255, 0)));
},
ui.label(
RichText::new("Flashing completed successfully!")
.color(Color32::from_rgb(0, 255, 0)),
);
}
FlashState::Aborted(r) => {
ui.label(RichText::new(format!("Flashing was ABORTED! Reason: {}", r)).color(Color32::from_rgb(255, 0, 0)));
},
ui.label(
RichText::new(format!("Flashing was ABORTED! Reason: {}", r))
.color(Color32::from_rgb(255, 0, 0)),
);
}
}
return PageAction::SetBackButtonState(state.is_done());
}

View File

@ -1,11 +1,19 @@
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64}, RwLock}, thread, time::{Duration, Instant}, char::MAX};
use std::{
char::MAX,
sync::{
atomic::{AtomicBool, AtomicU64, Ordering},
Arc, Mutex, RwLock,
},
thread,
time::{Duration, Instant},
};
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, SessionType};
use eframe::egui::plot::{Plot, Legend, Line};
use eframe::egui::plot::{Legend, Line, Plot};
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
use rli::{DataSolenoids, RecordIdents, LocalRecordData};
use rli::{DataSolenoids, LocalRecordData, RecordIdents};
use super::diagnostics::rli;
@ -16,7 +24,7 @@ pub struct IoManipulatorPage {
query_ecu: Arc<AtomicBool>,
curr_solenoid_values: Arc<RwLock<Option<DataSolenoids>>>,
time_since_launch: Instant,
show_ui: bool
show_ui: bool,
}
impl IoManipulatorPage {
@ -35,33 +43,38 @@ impl IoManipulatorPage {
let last_update = Arc::new(AtomicU64::new(0));
let last_update_t = last_update.clone();
thread::spawn(move|| {
let _ = server.lock().unwrap().set_diagnostic_session_mode(SessionType::Normal);
thread::spawn(move || {
let _ = server
.lock()
.unwrap()
.set_diagnostic_session_mode(SessionType::Normal);
while run_t.load(Ordering::Relaxed) {
let start = Instant::now();
if let Ok(r) = RecordIdents::SolenoidStatus.query_ecu(&mut *server.lock().unwrap()) {
if let Ok(r) = RecordIdents::SolenoidStatus.query_ecu(&mut *server.lock().unwrap())
{
if let LocalRecordData::Solenoids(s) = r {
let curr = *store_t.read().unwrap();
*store_old_t.write().unwrap() = curr;
*store_t.write().unwrap() = Some(s);
last_update_t.store(launch_time_t.elapsed().as_millis() as u64, Ordering::Relaxed);
last_update_t.store(
launch_time_t.elapsed().as_millis() as u64,
Ordering::Relaxed,
);
}
}
let taken = start.elapsed().as_millis() as u64;
if taken < UPDATE_DELAY_MS {
std::thread::sleep(Duration::from_millis(UPDATE_DELAY_MS-taken));
std::thread::sleep(Duration::from_millis(UPDATE_DELAY_MS - taken));
}
}
});
Self {
bar,
query_ecu: run,
curr_solenoid_values: store,
time_since_launch: launch_time,
show_ui: false
show_ui: false,
}
}
}
@ -69,16 +82,17 @@ impl IoManipulatorPage {
const GRAPH_TIME_MS: u16 = 100;
const MAX_DUTY: u16 = 0xFFF; // 12bit pwm (4096)
const VOLTAGE_HIGH: u16 = 12;
const VOLTAGE_HIGH: u16 = 12;
const VOLTAGE_LOW: u16 = 0;
impl crate::window::InterfacePage for IoManipulatorPage {
fn make_ui(&mut self, ui: &mut eframe::egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
fn make_ui(
&mut self,
ui: &mut eframe::egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
ui.heading("IO Manipulator");
ui.label("
This UI is only intended for debugging the TCM on a test bench. It is to NEVER be used whilst the TCM is inside a vehicle.
@ -90,9 +104,7 @@ impl crate::window::InterfacePage for IoManipulatorPage {
if !self.show_ui {
let mut btn_action = None;
ui.horizontal(|row| {
if row.button("I understand").clicked() {
}
if row.button("I understand").clicked() {}
if row.button("Take me to safety").clicked() {
btn_action = Some(PageAction::Destroy);
}
@ -101,8 +113,6 @@ impl crate::window::InterfacePage for IoManipulatorPage {
return req;
}
}
PageAction::None
}
@ -117,7 +127,5 @@ impl crate::window::InterfacePage for IoManipulatorPage {
}
impl Drop for IoManipulatorPage {
fn drop(&mut self) {
}
fn drop(&mut self) {}
}

View File

@ -0,0 +1 @@

View File

@ -1,15 +1,27 @@
use std::{sync::{Arc, Mutex, mpsc}, ops::RangeInclusive};
use std::{
ops::RangeInclusive,
sync::{mpsc, Arc, Mutex},
};
use ecu_diagnostics::{hardware::{HardwareResult, HardwareScanner, passthru::{PassthruScanner, PassthruDevice}, Hardware}, channel::IsoTPChannel};
use eframe::egui::*;
use ecu_diagnostics::{
channel::IsoTPChannel,
hardware::{
passthru::{PassthruDevice, PassthruScanner},
Hardware, HardwareResult, HardwareScanner,
},
};
use eframe::egui;
use eframe::egui::*;
#[cfg(unix)]
use ecu_diagnostics::hardware::socketcan::{SocketCanScanner, SocketCanDevice};
use ecu_diagnostics::hardware::socketcan::{SocketCanDevice, SocketCanScanner};
use crate::{
ui::main::MainPage,
usb_hw::{diag_usb::{Nag52USB, EspLogMessage}, scanner::Nag52UsbScanner},
usb_hw::{
diag_usb::{EspLogMessage, Nag52USB},
scanner::Nag52UsbScanner,
},
window::{InterfacePage, PageAction},
};
@ -26,7 +38,7 @@ pub struct Launcher {
#[cfg(unix)]
scan_scanner: SocketCanScanner,
selected_device: String,
curr_api_type: DeviceType
curr_api_type: DeviceType,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
@ -34,21 +46,21 @@ pub enum DeviceType {
Passthru,
#[cfg(unix)]
SocketCan,
Usb
Usb,
}
pub enum DynamicDevice {
Passthru(Arc<Mutex<PassthruDevice>>),
Usb(Arc<Mutex<Nag52USB>>),
#[cfg(unix)]
SocketCAN(Arc<Mutex<SocketCanDevice>>)
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
_ => None,
}
}
@ -57,14 +69,10 @@ impl DynamicDevice {
DynamicDevice::Passthru(pt) => {
PassthruDevice::toggle_sw_channel_raw(pt, true);
Hardware::create_iso_tp_channel(pt.clone())
},
DynamicDevice::Usb(usb) => {
Hardware::create_iso_tp_channel(usb.clone())
},
}
DynamicDevice::Usb(usb) => Hardware::create_iso_tp_channel(usb.clone()),
#[cfg(unix)]
DynamicDevice::SocketCAN(s) => {
Hardware::create_iso_tp_channel(s.clone())
},
DynamicDevice::SocketCAN(s) => Hardware::create_iso_tp_channel(s.clone()),
}
}
}
@ -80,7 +88,7 @@ impl Launcher {
#[cfg(unix)]
scan_scanner: SocketCanScanner::new(),
selected_device: String::new(),
curr_api_type: DeviceType::Usb
curr_api_type: DeviceType::Usb,
}
}
}
@ -93,14 +101,21 @@ impl Launcher {
}
println!("Opening '{}'", tmp);
Ok(match self.curr_api_type {
DeviceType::Passthru => DynamicDevice::Passthru(self.pt_scanner.open_device_by_name(&tmp)?),
DeviceType::Passthru => {
DynamicDevice::Passthru(self.pt_scanner.open_device_by_name(&tmp)?)
}
#[cfg(unix)]
DeviceType::SocketCan => DynamicDevice::SocketCAN(self.scan_scanner.open_device_by_name(&tmp)?),
DeviceType::SocketCan => {
DynamicDevice::SocketCAN(self.scan_scanner.open_device_by_name(&tmp)?)
}
DeviceType::Usb => DynamicDevice::Usb(self.usb_scanner.open_device_by_name(&tmp)?),
})
}
pub fn get_device_list<T, X: Hardware>(scanner: &T) -> Vec<String> where T: HardwareScanner<X> {
pub fn get_device_list<T, X: Hardware>(scanner: &T) -> Vec<String>
where
T: HardwareScanner<X>,
{
return scanner
.list_devices()
.iter()
@ -118,13 +133,23 @@ impl Launcher {
impl InterfacePage for Launcher {
fn make_ui(&mut self, ui: &mut Ui, frame: &eframe::Frame) -> crate::window::PageAction {
ui.label("Ultimate-Nag52 configuration utility!");
ui.label("Please plug in your TCM via USB and select the correct port, or select another API");
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");
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.radio_value(
&mut self.curr_api_type,
DeviceType::SocketCan,
"SocketCAN device",
);
}
ui.heading("Devices");
@ -151,7 +176,11 @@ impl InterfacePage for Launcher {
match self.open_device(&self.selected_device) {
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())));
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)),

View File

@ -1,35 +1,49 @@
use std::sync::{Arc, Mutex, mpsc};
use std::sync::{mpsc, Arc, Mutex};
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler}, channel::IsoTPChannel,
channel::IsoTPChannel,
kwp2000::{Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler},
};
use eframe::egui;
use eframe::Frame;
use crate::{
usb_hw::{diag_usb::{EspLogMessage}, KwpEventLevel, KwpEventHandler},
usb_hw::{diag_usb::EspLogMessage, KwpEventHandler, KwpEventLevel},
window::{InterfacePage, PageAction},
};
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI, diagnostics::{solenoids::SolenoidPage, shift_reporter::ShiftReportPage}, io_maipulator::IoManipulatorPage, routine_tests::RoutinePage, map_editor::MapEditor, };
use super::{
configuration::ConfigPage,
crashanalyzer::CrashAnalyzerUI,
diagnostics::{shift_reporter::ShiftReportPage, solenoids::SolenoidPage},
firmware_update::FwUpdateUI,
io_maipulator::IoManipulatorPage,
map_editor::MapEditor,
routine_tests::RoutinePage,
status_bar::MainStatusBar,
};
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 fw_date: String,
}
pub struct MainPage {
bar: MainStatusBar,
show_about_ui: bool,
diag_server: Arc<Mutex<Kwp2000DiagnosticServer>>,
dev_info: DevInfo
dev_info: DevInfo,
}
impl MainPage {
pub fn new(mut channel: Box<dyn IsoTPChannel>, logger: Option<mpsc::Receiver<EspLogMessage>>, hw_name: String) -> Self {
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: 0,
st_min: 0,
@ -46,7 +60,7 @@ impl MainPage {
global_tp_id: 0,
tester_present_interval_ms: 2000,
tester_present_require_response: true,
global_session_control: false
global_session_control: false,
};
let (evt_sender, evt_receiver) = mpsc::channel::<(KwpEventLevel, String)>();
let kwp = Kwp2000DiagnosticServer::new_over_iso_tp(
@ -54,25 +68,24 @@ impl MainPage {
channel,
channel_cfg,
KwpEventHandler::new(evt_sender),
).unwrap();
)
.unwrap();
Self {
bar: MainStatusBar::new(evt_receiver, hw_name),
show_about_ui: false,
diag_server: Arc::new(Mutex::new(kwp)),
dev_info: DevInfo { compat_mode: "UNKNOWN".into(), fw_version: "UNKNOWN".into(), fw_date: "UNKNOWN".into() }
dev_info: DevInfo {
compat_mode: "UNKNOWN".into(),
fw_version: "UNKNOWN".into(),
fw_date: "UNKNOWN".into(),
},
}
}
}
impl InterfacePage for MainPage {
fn make_ui(
&mut self,
ui: &mut egui::Ui,
frame: &Frame,
) -> crate::window::PageAction {
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &Frame) -> crate::window::PageAction {
// UI context menu
egui::menu::bar(ui, |bar_ui| {
bar_ui.menu_button("File", |x| {
@ -81,14 +94,27 @@ impl InterfacePage for MainPage {
}
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()
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.dev_info = DevInfo {
compat_mode: "UNKNOWN".into(),
fw_version: "UNKNOWN".into(),
fw_date: "UNKNOWN".into(),
};
}
self.show_about_ui = true;
}
@ -98,33 +124,63 @@ impl InterfacePage for MainPage {
let mut create_page = None;
ui.vertical(|v| {
v.heading("Utilities");
if v.button("Firmware updater").on_disabled_hover_ui(|u| {u.label("Broken, will be added soon!");}).clicked() {
create_page = Some(PageAction::Add(Box::new(FwUpdateUI::new(self.diag_server.clone()))));
if v.button("Firmware updater")
.on_disabled_hover_ui(|u| {
u.label("Broken, will be added soon!");
})
.clicked()
{
create_page = Some(PageAction::Add(Box::new(FwUpdateUI::new(
self.diag_server.clone(),
))));
}
if v.button("Crash analyzer").clicked() {
create_page = Some(PageAction::Add(Box::new(CrashAnalyzerUI::new(self.diag_server.clone()))));
create_page = Some(PageAction::Add(Box::new(CrashAnalyzerUI::new(
self.diag_server.clone(),
))));
}
if v.button("Diagnostics").clicked() {
create_page = Some(PageAction::Add(Box::new(DiagnosticsPage::new(self.diag_server.clone(), self.bar.clone()))));
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()))));
create_page = Some(PageAction::Add(Box::new(SolenoidPage::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
if v.button("Shift report history viewer").clicked() {
create_page = Some(PageAction::Add(Box::new(ShiftReportPage::new(self.diag_server.clone(), self.bar.clone()))));
create_page = Some(PageAction::Add(Box::new(ShiftReportPage::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
if v.button("IO Manipulator").clicked() {
create_page = Some(PageAction::Add(Box::new(IoManipulatorPage::new(self.diag_server.clone(), self.bar.clone()))));
create_page = Some(PageAction::Add(Box::new(IoManipulatorPage::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
if v.button("Diagnostic routine executor").clicked() {
create_page = Some(PageAction::Add(Box::new(RoutinePage::new(self.diag_server.clone(), self.bar.clone()))));
create_page = Some(PageAction::Add(Box::new(RoutinePage::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
if v.button("Map tuner").clicked() {
create_page = Some(PageAction::Add(Box::new(MapEditor::new(self.diag_server.clone(), self.bar.clone()))));
create_page = Some(PageAction::Add(Box::new(MapEditor::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
if v.button("Configure drive profiles").clicked() {}
if v.button("Configure vehicle / gearbox").clicked() {
create_page = Some(PageAction::Add(Box::new(ConfigPage::new(self.diag_server.clone(), self.bar.clone()))));
create_page = Some(PageAction::Add(Box::new(ConfigPage::new(
self.diag_server.clone(),
self.bar.clone(),
))));
}
});
if let Some(page) = create_page {
@ -143,9 +199,15 @@ impl InterfacePage for MainPage {
"Configuration app version: {}",
env!("CARGO_PKG_VERSION")
));
about_cols.label(format!("TCM firmware version: {}", self.dev_info.fw_version));
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.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

@ -15,7 +15,7 @@ use eframe::{
egui::{
self,
plot::{Bar, BarChart, CoordinatesFormatter, HLine, Legend, Line, LineStyle, PlotPoints},
Layout, TextEdit, RichText,
Layout, TextEdit, RichText, Response,
},
epaint::{vec2, Color32, Stroke, FontId, TextShape},
};
@ -57,8 +57,10 @@ pub struct Map {
data_eeprom: Vec<i16>,
data_default: Vec<i16>,
data_modify: Vec<i16>,
data_loaded: Vec<i16>,
showing_default: bool,
ecu_ref: Arc<Mutex<Kwp2000DiagnosticServer>>,
curr_edit_cell: Option<(usize, String, Response)>,
}
fn read_i16(a: &[u8]) -> DiagServerResult<(&[u8], i16)> {
@ -170,14 +172,62 @@ impl Map {
y_values: y_elements,
eeprom_key: key,
data_eeprom: current.clone(),
data_loaded: current.clone(),
data_default: default,
data_modify: current,
meta,
showing_default: false,
ecu_ref: server,
curr_edit_cell: None
})
}
fn data_to_byte_array(&self, data: &Vec<i16>) -> Vec<u8> {
let mut ret = Vec::new();
ret.extend_from_slice(&((data.len()*2) as u16).to_le_bytes());
for point in data {
ret.extend_from_slice(&point.to_le_bytes());
}
ret
}
pub fn write_to_ram(&self) -> DiagServerResult<()> {
let mut payload: Vec<u8> = vec![
KWP2000Command::WriteDataByLocalIdentifier.into(),
0x19,
self.meta.id,
MapCmd::Write as u8,
];
payload.extend_from_slice(&self.data_to_byte_array(&self.data_modify));
self.ecu_ref.lock().unwrap().send_byte_array_with_response(&payload)?;
Ok(())
}
pub fn save_to_eeprom(&self) -> DiagServerResult<()> {
let payload: Vec<u8> = vec![
KWP2000Command::WriteDataByLocalIdentifier.into(),
0x19,
self.meta.id,
MapCmd::Burn as u8,
0x00, 0x00
];
self.ecu_ref.lock().unwrap().send_byte_array_with_response(&payload)?;
Ok(())
}
pub fn undo_changes(&self) -> DiagServerResult<()> {
let payload: Vec<u8> = vec![
KWP2000Command::WriteDataByLocalIdentifier.into(),
0x19,
self.meta.id,
MapCmd::Undo as u8,
0x00, 0x00
];
self.ecu_ref.lock().unwrap().send_byte_array_with_response(&payload)?;
Ok(())
}
fn get_x_label(&self, idx: usize) -> String {
if let Some(replace) = self.meta.x_replace {
format!("{}", replace.get(idx).unwrap_or(&"ERROR"))
@ -195,62 +245,94 @@ impl Map {
}
fn gen_edit_table(&mut self, raw_ui: &mut egui::Ui, showing_default: bool) {
let hash = if showing_default { &self.data_default } else { &self.data_eeprom };
let color = raw_ui.visuals().faint_bg_color;
let hash = if showing_default { &self.data_default } else { &self.data_loaded };
let header_color = raw_ui.visuals().warn_fg_color;
let cell_edit_color = raw_ui.visuals().error_fg_color;
if !self.meta.x_desc.is_empty() {
raw_ui.label(format!("X: {}", self.meta.x_desc));
}
if !self.meta.y_desc.is_empty() {
raw_ui.label(format!("Y: {}", self.meta.y_desc));
}
if !self.meta.v_desc.is_empty() {
raw_ui.label(format!("Values: {}", self.meta.v_desc));
}
let mut copy = self.clone();
let resp = raw_ui.push_id(&hash, |ui| {
let mut table_builder = egui_extras::TableBuilder::new(ui)
.striped(true)
.scroll(false)
.cell_layout(Layout::left_to_right(egui::Align::Center).with_cross_align(egui::Align::Center))
.column(Size::initial(60.0).at_least(60.0));
for _ in 0..self.x_values.len() {
table_builder = table_builder.column(Size::initial(60.0).at_least(70.0));
for _ in 0..copy.x_values.len() {
table_builder = table_builder.column(Size::initial(70.0).at_least(70.0));
}
table_builder.header(15.0, |mut header | {
header.col(|u| {}); // Nothing in corner cell
for v in &self.x_values {
header.col(|u| {
u.label(RichText::new(format!("{}", v)).color(color));
});
header.col(|_| {}); // Nothing in corner cell
if copy.x_values.len() == 1 {
header.col(|_|{});
} else {
for v in 0..copy.x_values.len() {
header.col(|u| {
u.label(RichText::new(format!("{}", copy.get_x_label(v))).color(header_color));
});
}
}
}).body(|body| {
body.rows(18.0, self.y_values.len(), |row_id, mut row| {
if let Some(replace) = self.meta.y_replace {
row.col(|x| { x.label(format!("{}", replace.get(row_id).unwrap_or(&"ERROR"))); });
} else {
row.col(|x| { x.label(format!("{}{}", self.y_values[row_id], self.meta.y_unit)); });
}
for x_pos in 0..self.x_values.len() {
body.rows(15.0, copy.y_values.len(), |row_id, mut row| {
// Header column
row.col(|c| { c.label(RichText::new(format!("{}", copy.get_y_label(row_id))).color(header_color));});
// Data columns
for x_pos in 0..copy.x_values.len() {
row.col(|cell| {
let value = match showing_default {
true => self.data_default.get((row_id*self.x_values.len())+x_pos).unwrap_or(&30000),
false => self.data_eeprom.get((row_id*self.x_values.len())+x_pos).unwrap_or(&30000)
};
// Add X values
cell.label(format!("{}", value));
match showing_default {
true => {
cell.label(format!("{}", copy.data_default[(row_id*copy.x_values.len())+x_pos]));
},
false => {
let map_idx = (row_id*copy.x_values.len())+x_pos;
let mut value = format!("{}", copy.data_modify[map_idx]);
if let Some((curr_edit_idx, current_edit_txt, resp)) = &copy.curr_edit_cell {
if *curr_edit_idx == map_idx {
println!("Editing current cell {}", current_edit_txt);
value = current_edit_txt.clone();
}
}
let changed_value = value != format!("{}", copy.data_eeprom[map_idx]);
let mut edit = TextEdit::singleline(&mut value);
if changed_value {
edit = edit.text_color(cell_edit_color);
}
let mut response = cell.add(edit);
if changed_value {
response = response.on_hover_text(format!("Original: {}", copy.data_loaded[map_idx]));
}
if response.lost_focus() || cell.ctx().input().key_pressed(egui::Key::Enter) {
println!("Cell ({},{}) lost focus, editing done", row_id, x_pos);
if let Ok(new_v) = i16::from_str_radix(&value, 10) {
copy.data_modify[map_idx] = new_v;
}
copy.curr_edit_cell = None;
} else if response.gained_focus() || response.has_focus() {
if let Some((curr_edit_idx, current_edit_txt, _resp)) = &copy.curr_edit_cell {
if let Ok(new_v) = i16::from_str_radix(&current_edit_txt, 10) {
copy.data_modify[*curr_edit_idx] = new_v;
}
}
copy.curr_edit_cell = Some((map_idx, value, response));
}
}
}
});
}
})
});
});
let line_visuals = raw_ui.visuals().text_color();
let size = resp.response.rect;
//raw_ui.painter().add(TextShape {
// pos: (size.left()-galley_y.size().x, size.bottom()).into(),
// galley: galley_y,
// underline: Stroke::none(),
// override_text_color: None,
// angle: -1.5708,
//});
*self = copy;
}
fn generate_window_ui(&mut self, raw_ui: &mut egui::Ui) {
fn generate_window_ui(&mut self, raw_ui: &mut egui::Ui) -> Option<PageAction> {
raw_ui.label(format!("EEPROM key: {}", self.eeprom_key));
raw_ui.label(format!(
"Map has {} elements",
@ -296,6 +378,7 @@ impl Map {
.allow_scroll(false)
.allow_zoom(false)
.height(150.0)
.width(raw_ui.available_width())
.show(raw_ui, |plot_ui| {
for l in lines {
plot_ui.line(l);
@ -304,14 +387,45 @@ impl Map {
}
raw_ui.checkbox(&mut self.showing_default, "Show flash defaults");
if self.data_modify != self.data_eeprom {
if raw_ui.button("Undo user changes").clicked() {}
if raw_ui.button("Write changes (To RAM)").clicked() {}
if raw_ui.button("Write changes (To EEPROM)").clicked() {}
if self.data_modify != self.data_loaded {
if raw_ui.button("Undo user changes").clicked() {
return match self.undo_changes() {
Ok(_) => {
self.data_modify = self.data_loaded.clone();
Some(PageAction::SendNotification { text: format!("Map {} undo OK!", self.eeprom_key), kind: ToastKind::Success })
},
Err(e) => Some(PageAction::SendNotification { text: format!("Map {} undo failed! {}", self.eeprom_key, e), kind: ToastKind::Error })
}
}
if raw_ui.button("Write changes (To RAM)").clicked() {
return match self.write_to_ram() {
Ok(_) => {
self.data_loaded = self.data_modify.clone();
Some(PageAction::SendNotification { text: format!("Map {} RAM write OK!", self.eeprom_key), kind: ToastKind::Success })
},
Err(e) => Some(PageAction::SendNotification { text: format!("Map {} RAM write failed! {}", self.eeprom_key, e), kind: ToastKind::Error })
}
}
}
if self.data_loaded != self.data_eeprom {
if raw_ui.button("Write changes (To EEPROM)").clicked() {
return match self.save_to_eeprom() {
Ok(_) => {
if let Ok(new_data) = Self::new(self.meta.id, self.ecu_ref.clone(), self.meta) {
*self = new_data;
}
Some(PageAction::SendNotification { text: format!("Map {} EEPROM save OK!", self.eeprom_key), kind: ToastKind::Success })
},
Err(e) => Some(PageAction::SendNotification { text: format!("Map {} EEPROM save failed! {}", self.eeprom_key, e), kind: ToastKind::Error })
}
}
}
if self.data_modify != self.data_default {
if raw_ui.button("Reset to flash defaults").clicked() {}
if raw_ui.button("Reset to flash defaults").clicked() {
self.data_modify = self.data_default.clone();
}
}
None
}
}
@ -323,6 +437,7 @@ pub struct MapData {
y_unit: &'static str,
x_desc: &'static str,
y_desc: &'static str,
v_desc: &'static str,
value_unit: &'static str,
x_replace: Option<&'static [&'static str]>,
y_replace: Option<&'static [&'static str]>,
@ -336,6 +451,7 @@ impl MapData {
y_unit: &'static str,
x_desc: &'static str,
y_desc: &'static str,
v_desc: &'static str,
value_unit: &'static str,
x_replace: Option<&'static [&'static str]>,
y_replace: Option<&'static [&'static str]>,
@ -347,6 +463,7 @@ impl MapData {
y_unit,
x_desc,
y_desc,
v_desc,
value_unit,
x_replace,
y_replace,
@ -362,6 +479,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Upshift RPM threshold",
"RPM",
None,
Some(&["1->2", "2->3", "3->4", "4->5"]),
@ -373,6 +491,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Upshift RPM threshold",
"RPM",
None,
Some(&["1->2", "2->3", "3->4", "4->5"]),
@ -384,6 +503,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Upshift RPM threshold",
"RPM",
None,
Some(&["1->2", "2->3", "3->4", "4->5"]),
@ -395,6 +515,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Downshift RPM threshold",
"RPM",
None,
Some(&["2->1", "3->2", "4->3", "5->4"]),
@ -406,6 +527,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Downshift RPM threshold",
"RPM",
None,
Some(&["2->1", "3->2", "4->3", "5->4"]),
@ -417,6 +539,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Pedal position (%)",
"Gear shift",
"Downshift RPM threshold",
"RPM",
None,
Some(&["2->1", "3->2", "4->3", "5->4"]),
@ -428,6 +551,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"Input torque (% of rated)",
"Gear",
"Downshift RPM threshold",
"mBar",
None,
Some(&["P/N", "R1/R2", "1", "2", "3", "4", "5"]),
@ -439,6 +563,7 @@ const MAP_ARRAY: [MapData; 11] = [
"C",
"Working pressure",
"ATF Temperature",
"Solenoid current (mA)",
"mA",
None,
None,
@ -450,6 +575,7 @@ const MAP_ARRAY: [MapData; 11] = [
"C",
"Converter pressure",
"ATF Temperature",
"Solenoid PWM duty (4096 = 100% on)",
"/4096",
None,
None,
@ -459,8 +585,9 @@ const MAP_ARRAY: [MapData; 11] = [
"Clutch filling time",
"C",
"",
"",
"ATF Temperature",
"Clutch",
"filling time in millseconds",
"ms",
None,
Some(&["K1", "K2", "K3", "B1", "B2"]),
@ -472,6 +599,7 @@ const MAP_ARRAY: [MapData; 11] = [
"",
"",
"Clutch",
"filling pressure in millibar",
"mBar",
None,
Some(&["K1", "K2", "K3", "B1", "B2"]),
@ -487,6 +615,7 @@ pub struct MapEditor {
impl MapEditor {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
server.lock().unwrap().set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
Self {
bar,
server,
@ -518,6 +647,7 @@ impl super::InterfacePage for MapEditor {
}
let mut remove_list: Vec<String> = Vec::new();
let mut action = None;
for (key, map) in self.loaded_maps.iter_mut() {
let mut open = true;
egui::Window::new(map.meta.name)
@ -527,7 +657,7 @@ impl super::InterfacePage for MapEditor {
.vscroll(false)
.default_size(vec2(800.0, 400.0))
.show(ui.ctx(), |window| {
map.generate_window_ui(window);
action = map.generate_window_ui(window);
});
if !open {
remove_list.push(key.clone())
@ -536,6 +666,9 @@ impl super::InterfacePage for MapEditor {
for key in remove_list {
self.loaded_maps.remove(&key);
}
if let Some(act) = action {
return act;
}
PageAction::None
}

View File

@ -1,32 +1,34 @@
use eframe::egui::Color32;
use eframe::egui;
use eframe::egui::Color32;
use crate::window::InterfacePage;
pub mod firmware_update;
pub mod launcher;
pub mod main;
pub mod status_bar;
pub mod diagnostics;
pub mod configuration;
pub mod crashanalyzer;
pub mod kwp_event;
pub mod diagnostics;
pub mod firmware_update;
pub mod io_maipulator;
pub mod routine_tests;
pub mod widgets;
pub mod kwp_event;
pub mod launcher;
pub mod main;
pub mod map_editor;
pub mod routine_tests;
pub mod status_bar;
pub mod widgets;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum StatusText {
Ok(String),
Err(String)
Err(String),
}
impl egui::Widget for StatusText {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
match self {
StatusText::Ok(t) => ui.add(egui::Label::new(egui::RichText::new(t))),
StatusText::Err(t) => ui.add(egui::Label::new(egui::RichText::new(t).color(Color32::from_rgb(255, 0, 0)))),
StatusText::Err(t) => ui.add(egui::Label::new(
egui::RichText::new(t).color(Color32::from_rgb(255, 0, 0)),
)),
}
}
}
}

View File

@ -1,4 +1,4 @@
use std::sync::{Mutex, Arc};
use std::sync::{Arc, Mutex};
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
@ -10,7 +10,6 @@ use super::status_bar::MainStatusBar;
pub mod solenoid_test;
pub struct RoutinePage {
bar: MainStatusBar,
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
@ -18,30 +17,32 @@ pub struct RoutinePage {
impl RoutinePage {
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
Self {
bar,
server
}
Self { bar, server }
}
}
impl crate::window::InterfacePage for RoutinePage {
fn make_ui(&mut self, ui: &mut eframe::egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
fn make_ui(
&mut self,
ui: &mut eframe::egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
ui.heading("Diagnostic routines");
ui.label("
ui.label(
"
Select test routine to run
");
",
);
let mut page_action = PageAction::None;
if ui.button("Solenoid test").clicked() {
page_action = PageAction::Add(Box::new(SolenoidTestPage::new(self.server.clone(), self.bar.clone())));
page_action = PageAction::Add(Box::new(SolenoidTestPage::new(
self.server.clone(),
self.bar.clone(),
)));
}
page_action
}
@ -53,4 +54,4 @@ impl crate::window::InterfacePage for RoutinePage {
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
Some(Box::new(self.bar.clone()))
}
}
}

View File

@ -1,7 +1,24 @@
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64, AtomicU8}, RwLock}, thread, time::{Duration, Instant}, char::MAX, collections::btree_map::Range, ops::RangeInclusive};
use std::{
char::MAX,
collections::btree_map::Range,
ops::RangeInclusive,
sync::{
atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering},
Arc, Mutex, RwLock,
},
thread,
time::{Duration, Instant},
};
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType}, DiagServerResult, DiagnosticServer, DiagError};
use eframe::egui::{plot::{Plot, Legend, Line}, widgets, Color32, RichText, self};
use ecu_diagnostics::{
kwp2000::{Kwp2000DiagnosticServer, SessionType},
DiagError, DiagServerResult, DiagnosticServer,
};
use eframe::egui::{
self,
plot::{Legend, Line, Plot},
widgets, Color32, RichText,
};
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
@ -10,10 +27,9 @@ pub struct SolenoidTestPage {
test_state: Arc<AtomicU8>,
test_result: Arc<RwLock<Option<TestResultsSolenoid>>>,
test_status: Arc<RwLock<String>>,
server: Arc<Mutex<Kwp2000DiagnosticServer>>
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
}
const TempCoefficient: f32 = 0.393; // Copper coils and wires
const ResistanceMeasureTemp: f32 = 25.0; // Mercedes tests resistance at 25C
@ -64,41 +80,56 @@ impl SolenoidTestPage {
server,
test_state: Arc::new(AtomicU8::new(0)),
test_result: Arc::new(RwLock::new(None)),
test_status: Arc::new(RwLock::new(String::new()))
test_status: Arc::new(RwLock::new(String::new())),
}
}
}
fn calc_resistance(current: u16, batt: u16, temp: i16) -> f32 {
let resistance_now = batt as f32 / current as f32;
return resistance_now + resistance_now*(((ResistanceMeasureTemp-temp as f32)*TempCoefficient)/100.0)
return resistance_now
+ resistance_now * (((ResistanceMeasureTemp - temp as f32) * TempCoefficient) / 100.0);
}
fn make_resistance_text(c_raw: u16, r: f32, range: RangeInclusive<f32>) -> egui::Label {
if c_raw == 0 {
return egui::Label::new(RichText::new("FAIL! Open circuit detected!").color(Color32::RED))
return egui::Label::new(RichText::new("FAIL! Open circuit detected!").color(Color32::RED));
}
if (c_raw > 3200 && range != ResitanceTCC) {
return egui::Label::new(RichText::new("FAIL! Short circuit detected!").color(Color32::RED))
return egui::Label::new(
RichText::new("FAIL! Short circuit detected!").color(Color32::RED),
);
}
let c: Color32;
let t: String;
if range.contains(&r) {
c = Color32::GREEN;
t = format!("OK! ({:.2}Ω). Acceptable range is {:.2}..{:.2}Ω", r, range.start(), range.end());
t = format!(
"OK! ({:.2}Ω). Acceptable range is {:.2}..{:.2}Ω",
r,
range.start(),
range.end()
);
} else {
c = Color32::RED;
t = format!("FAIL! ({:.2}Ω). Acceptable range is {:.2}..{:.2}Ω", r, range.start(), range.end());
t = format!(
"FAIL! ({:.2}Ω). Acceptable range is {:.2}..{:.2}Ω",
r,
range.start(),
range.end()
);
}
egui::Label::new(RichText::new(t).color(c))
}
impl crate::window::InterfacePage for SolenoidTestPage {
fn make_ui(&mut self, ui: &mut eframe::egui::Ui, frame: &eframe::Frame) -> crate::window::PageAction {
fn make_ui(
&mut self,
ui: &mut eframe::egui::Ui,
frame: &eframe::Frame,
) -> crate::window::PageAction {
ui.heading("Solenoid test");
ui.label("
This test will test each solenoid within the transmission, and ensure
that their resistance is within specification, and that there is no short circuit within the valve
@ -107,33 +138,37 @@ impl crate::window::InterfacePage for SolenoidTestPage {
ui.separator();
ui.label("
ui.label(
"
TEST REQUIRMENTS:
1. Shifter in D or R
2. Engine off
3. Not moving
4. Battery at least 11.5V
");
",
);
let state = self.test_state.load(Ordering::Relaxed);
if state == 1 { // Running
if state == 1 {
// Running
ui.label("Test running...");
} else {
if ui.button("Begin test").clicked() {
let ctx = ui.ctx().clone();
let s = self.server.clone();
let str_ref = self.test_status.clone();
let state_ref = self.test_state.clone();
let res_ref = self.test_result.clone();
std::thread::spawn(move|| {
std::thread::spawn(move || {
state_ref.store(1, Ordering::Relaxed);
let mut guard = s.lock().unwrap();
if let Err(e) = guard.set_diagnostic_session_mode(SessionType::ExtendedDiagnostics) {
*str_ref.write().unwrap() = format!("ECU failed to enter extended diagnostic mode: {}", e);
if let Err(e) =
guard.set_diagnostic_session_mode(SessionType::ExtendedDiagnostics)
{
*str_ref.write().unwrap() =
format!("ECU failed to enter extended diagnostic mode: {}", e);
state_ref.store(2, Ordering::Relaxed);
ctx.request_repaint();
return;
@ -147,21 +182,26 @@ impl crate::window::InterfacePage for SolenoidTestPage {
}
loop {
match guard.send_byte_array_with_response(&[0x33, 0xDE]) { // Request test results in a loop
match guard.send_byte_array_with_response(&[0x33, 0xDE]) {
// Request test results in a loop
Ok(res) => {
let routine_res_ptr: *const TestResultsSolenoid = res[2..].as_ptr() as * const TestResultsSolenoid;
let routine_res_ptr: *const TestResultsSolenoid =
res[2..].as_ptr() as *const TestResultsSolenoid;
let routine_res: TestResultsSolenoid = unsafe { *routine_res_ptr };
*res_ref.write().unwrap() = Some(routine_res);
*str_ref.write().unwrap() = format!("ECU Test Completed!");
break;
},
}
Err(e) => {
let mut failed = true;
if let DiagError::ECUError { code, def: _ } = e {
if code == 0x22 { failed = false; } // Just waiting for test to finish!
if code == 0x22 {
failed = false;
} // Just waiting for test to finish!
}
if failed {
*str_ref.write().unwrap() = format!("Failed to get ECU test results: {}", e);
*str_ref.write().unwrap() =
format!("Failed to get ECU test results: {}", e);
break;
}
}
@ -177,31 +217,83 @@ impl crate::window::InterfacePage for SolenoidTestPage {
ui.label(self.test_status.read().unwrap().as_str());
if let Some(results) = self.test_result.read().unwrap().clone() {
ui.separator();
ui.label(format!("ATF Temp was {} C. Showing results adjusted to {} C", &{results.atf_temp}, ResistanceMeasureTemp));
ui.label(format!(
"ATF Temp was {} C. Showing results adjusted to {} C",
&{ results.atf_temp },
ResistanceMeasureTemp
));
egui::Grid::new("S").striped(true).show(ui, |g_ui|{
egui::Grid::new("S").striped(true).show(ui, |g_ui| {
g_ui.label("MPC Solenoid");
g_ui.add(make_resistance_text(results.mpc_on_current, calc_resistance(results.mpc_on_current, results.vbatt_mpc, results.atf_temp), ResitanceMPC));
g_ui.add(make_resistance_text(
results.mpc_on_current,
calc_resistance(
results.mpc_on_current,
results.vbatt_mpc,
results.atf_temp,
),
ResitanceMPC,
));
g_ui.end_row();
g_ui.label("SPC Solenoid");
g_ui.add(make_resistance_text(results.spc_on_current, calc_resistance(results.spc_on_current, results.vbatt_spc, results.atf_temp), ResitanceSPC));
g_ui.add(make_resistance_text(
results.spc_on_current,
calc_resistance(
results.spc_on_current,
results.vbatt_spc,
results.atf_temp,
),
ResitanceSPC,
));
g_ui.end_row();
g_ui.label("TCC Solenoid");
g_ui.add(make_resistance_text(results.tcc_on_current, calc_resistance(results.tcc_on_current, results.vbatt_tcc, results.atf_temp), ResitanceTCC));
g_ui.add(make_resistance_text(
results.tcc_on_current,
calc_resistance(
results.tcc_on_current,
results.vbatt_tcc,
results.atf_temp,
),
ResitanceTCC,
));
g_ui.end_row();
g_ui.label("Y3 shift Solenoid");
g_ui.add(make_resistance_text(results.y3_on_current, calc_resistance(results.y3_on_current, results.vbatt_y3, results.atf_temp), ResitanceY3));
g_ui.add(make_resistance_text(
results.y3_on_current,
calc_resistance(
results.y3_on_current,
results.vbatt_y3,
results.atf_temp,
),
ResitanceY3,
));
g_ui.end_row();
g_ui.label("Y4 shift Solenoid");
g_ui.add(make_resistance_text(results.y4_on_current, calc_resistance(results.y4_on_current, results.vbatt_y4, results.atf_temp), ResitanceY4));
g_ui.add(make_resistance_text(
results.y4_on_current,
calc_resistance(
results.y4_on_current,
results.vbatt_y4,
results.atf_temp,
),
ResitanceY4,
));
g_ui.end_row();
g_ui.label("Y5 shift Solenoid");
g_ui.add(make_resistance_text(results.y5_on_current, calc_resistance(results.y5_on_current, results.vbatt_y5, results.atf_temp), ResitanceY5));
g_ui.add(make_resistance_text(
results.y5_on_current,
calc_resistance(
results.y5_on_current,
results.vbatt_y5,
results.atf_temp,
),
ResitanceY5,
));
g_ui.end_row();
});
}

View File

@ -1,12 +1,16 @@
use ecu_diagnostics::hardware::Hardware;
use eframe::egui::*;
use std::{
borrow::BorrowMut,
collections::VecDeque,
sync::{Arc, Mutex, RwLock, mpsc}, borrow::BorrowMut,
sync::{mpsc, Arc, Mutex, RwLock},
};
use crate::{
usb_hw::{diag_usb::{EspLogMessage, Nag52USB}, KwpEventLevel},
usb_hw::{
diag_usb::{EspLogMessage, Nag52USB},
KwpEventLevel,
},
window::{InterfacePage, StatusBar},
};
use eframe::egui;
@ -18,7 +22,7 @@ pub struct MainStatusBar {
receiver: Arc<mpsc::Receiver<(KwpEventLevel, String)>>,
hw_name: String,
auto_scroll: bool,
use_light_theme: bool
use_light_theme: bool,
}
impl MainStatusBar {
@ -29,7 +33,7 @@ impl MainStatusBar {
auto_scroll: true,
use_light_theme: false,
receiver: Arc::new(logger),
hw_name
hw_name,
}
}
}
@ -43,39 +47,29 @@ impl StatusBar for MainStatusBar {
egui::widgets::global_dark_light_mode_buttons(ui);
if self.show_log_view {
let ui_h = ui.available_height();
let ui_w = ui.available_width();
egui::containers::Window::new("Debug view")
.resizable(true)
.default_height(ui_h/4.0)
.default_width(ui_w/4.0)
.default_height(ui_h / 4.0)
.default_width(ui_w / 4.0)
.show(ui.ctx(), |log_view| {
log_view.vertical(|l_view| {
let max_height = l_view.available_height();
egui::containers::ScrollArea::vertical()
.auto_shrink([false, false])
.max_height(500.0)
.stick_to_bottom(true)
.auto_shrink([false, false])
.max_height(500.0)
.stick_to_bottom(true)
.show(l_view, |scroll| {
for (lvl, msg) in &self.msgs {
scroll.label(
RichText::new(format!(
"{}",
msg
))
.color(match lvl
{
KwpEventLevel::Warn => {
Color32::from_rgb(255, 215, 0)
}
KwpEventLevel::Err => {
Color32::from_rgb(255, 0, 0)
}
_ => ui.visuals().text_color()
}),
);
scroll.label(RichText::new(format!("{}", msg)).color(
match lvl {
KwpEventLevel::Warn => Color32::from_rgb(255, 215, 0),
KwpEventLevel::Err => Color32::from_rgb(255, 0, 0),
_ => ui.visuals().text_color(),
},
));
}
});
while let Ok(msg) = self.receiver.try_recv() {

View File

@ -1,51 +1,114 @@
use std::{ops::RangeInclusive, fmt::Display};
use std::{fmt::Display, ops::RangeInclusive};
use eframe::{egui, epaint::{Rounding, Color32, FontId}, emath::{Rect, Pos2, Align2}};
use eframe::{
egui,
emath::{Align2, Pos2, Rect},
epaint::{Color32, FontId, Rounding},
};
pub fn range_display<T: Into<f32> + Copy + Display>(ui: &mut egui::Ui,
pub fn range_display<T: Into<f32> + Copy + Display>(
ui: &mut egui::Ui,
curr_value: T,
min_ok: T,
max_ok: T,
min_display: T,
max_display: T
max_display: T,
) -> egui::Response {
let desired_size = egui::Vec2::new(ui.available_width(), 40.0);
let (rect, mut response) = ui.allocate_exact_size(desired_size, egui::Sense::click());
let bar_region = Rect::from_two_pos(
Pos2::new(rect.left(), rect.bottom()-10.0),
Pos2::new(rect.right(), rect.top())
Pos2::new(rect.left(), rect.bottom() - 10.0),
Pos2::new(rect.right(), rect.top()),
);
let total_size_per_range = desired_size.x / (max_display.into()-min_display.into());
let total_size_per_range = desired_size.x / (max_display.into() - min_display.into());
let ok_region_rect = Rect::from_two_pos(
Pos2::new(bar_region.left()+(total_size_per_range*(min_ok.into()-min_display.into())), bar_region.bottom()),
Pos2::new(bar_region.left()+(total_size_per_range*(min_ok.into()-min_display.into()))+total_size_per_range*(max_ok.into()-min_ok.into()), rect.top())
Pos2::new(
bar_region.left() + (total_size_per_range * (min_ok.into() - min_display.into())),
bar_region.bottom(),
),
Pos2::new(
bar_region.left()
+ (total_size_per_range * (min_ok.into() - min_display.into()))
+ total_size_per_range * (max_ok.into() - min_ok.into()),
rect.top(),
),
);
let value_region = Rect::from_two_pos(
Pos2::new(bar_region.left()+(total_size_per_range*(curr_value.into()-min_display.into())), bar_region.bottom()),
Pos2::new(bar_region.left()+(total_size_per_range*(curr_value.into()-min_display.into()))+2.0, rect.top())
Pos2::new(
bar_region.left() + (total_size_per_range * (curr_value.into() - min_display.into())),
bar_region.bottom(),
),
Pos2::new(
bar_region.left()
+ (total_size_per_range * (curr_value.into() - min_display.into()))
+ 2.0,
rect.top(),
),
);
// OK range
let visuals = ui.style().noninteractive().clone();
let painter = ui.painter();
painter.rect(bar_region, Rounding::from(4.0), Color32::RED, visuals.bg_stroke);
painter.rect(ok_region_rect, Rounding::none(), Color32::GREEN, visuals.fg_stroke);
painter.rect(value_region, Rounding::none(), Color32::BLACK, visuals.fg_stroke);
painter.rect(
bar_region,
Rounding::from(4.0),
Color32::RED,
visuals.bg_stroke,
);
painter.rect(
ok_region_rect,
Rounding::none(),
Color32::GREEN,
visuals.fg_stroke,
);
painter.rect(
value_region,
Rounding::none(),
Color32::BLACK,
visuals.fg_stroke,
);
painter.text(Pos2::new(rect.left(), rect.bottom()+5.0), Align2::LEFT_BOTTOM, format!("{:.2}", min_display), FontId::default(), visuals.text_color());
painter.text(Pos2::new(rect.right(), rect.bottom()+5.0), Align2::RIGHT_BOTTOM, format!("{:.2}", max_display), FontId::default(), visuals.text_color());
painter.text(
Pos2::new(rect.left(), rect.bottom() + 5.0),
Align2::LEFT_BOTTOM,
format!("{:.2}", min_display),
FontId::default(),
visuals.text_color(),
);
painter.text(
Pos2::new(rect.right(), rect.bottom() + 5.0),
Align2::RIGHT_BOTTOM,
format!("{:.2}", max_display),
FontId::default(),
visuals.text_color(),
);
painter.text(Pos2::new(ok_region_rect.left(), rect.bottom()+5.0), Align2::CENTER_BOTTOM, format!("{:.2}", min_ok), FontId::default(), visuals.text_color());
painter.text(Pos2::new(ok_region_rect.right(), rect.bottom()+5.0), Align2::CENTER_BOTTOM, format!("{:.2}", max_ok), FontId::default(), visuals.text_color());
painter.text(
Pos2::new(ok_region_rect.left(), rect.bottom() + 5.0),
Align2::CENTER_BOTTOM,
format!("{:.2}", min_ok),
FontId::default(),
visuals.text_color(),
);
painter.text(
Pos2::new(ok_region_rect.right(), rect.bottom() + 5.0),
Align2::CENTER_BOTTOM,
format!("{:.2}", max_ok),
FontId::default(),
visuals.text_color(),
);
painter.text(Pos2::new(value_region.right(), rect.bottom()+5.0), Align2::CENTER_BOTTOM, format!("{:.2}", curr_value), FontId::default(), visuals.text_color());
painter.text(
Pos2::new(value_region.right(), rect.bottom() + 5.0),
Align2::CENTER_BOTTOM,
format!("{:.2}", curr_value),
FontId::default(),
visuals.text_color(),
);
response
}
}

View File

@ -2,21 +2,22 @@
//! using the USB connection directly to the ESP32 chip
//! The USB endpoint ONLY supports sending fakes ISO-TP messages
use ecu_diagnostics::{
channel::{ChannelError, IsoTPChannel, PayloadChannel},
hardware::{HardwareError, HardwareInfo, HardwareResult},
};
use serial_rs::{FlowControl, PortInfo, SerialPort, SerialPortSettings};
use std::fmt::Write as SWrite;
use std::{
io::{BufRead, BufReader, BufWriter, Write},
panic::catch_unwind,
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self},
Arc,
},
time::{Instant, Duration}, panic::catch_unwind,
time::{Duration, Instant},
};
use std::fmt::Write as SWrite;
use ecu_diagnostics::{
channel::{IsoTPChannel, PayloadChannel, ChannelError},
hardware::{HardwareInfo, HardwareResult, HardwareError},
};
use serial_rs::{SerialPort, PortInfo, SerialPortSettings, FlowControl};
#[derive(Debug, Clone, Copy)]
pub enum EspLogLevel {
@ -43,7 +44,7 @@ pub struct Nag52USB {
is_running: Arc<AtomicBool>,
tx_id: u32,
rx_id: u32,
legacy_tx_mode: bool
legacy_tx_mode: bool,
}
unsafe impl Sync for Nag52USB {}
@ -51,15 +52,20 @@ unsafe impl Send for Nag52USB {}
impl Nag52USB {
pub fn new(path: &str, info: PortInfo, legacy_tx_mode: bool) -> HardwareResult<Self> {
let mut port = serial_rs::new_from_path(path, Some(SerialPortSettings::default()
.baud(921600)
.read_timeout(Some(100))
.write_timeout(Some(100))
.set_flow_control(FlowControl::None)))
.map_err(|e| HardwareError::APIError {
code: 99,
desc: e.to_string(),
})?;
let mut port = serial_rs::new_from_path(
path,
Some(
SerialPortSettings::default()
.baud(921600)
.read_timeout(Some(100))
.write_timeout(Some(100))
.set_flow_control(FlowControl::None),
),
)
.map_err(|e| HardwareError::APIError {
code: 99,
desc: e.to_string(),
})?;
let (read_tx_log, read_rx_log) = mpsc::channel::<EspLogMessage>();
let (read_tx_diag, read_rx_diag) = mpsc::channel::<(u32, Vec<u8>)>();
@ -78,9 +84,11 @@ impl Nag52USB {
let mut line = String::new();
loop {
line.clear();
if reader.read_line(&mut line).is_ok() {
if reader.read_line(&mut line).is_ok() {
line.pop();
if line.is_empty() {continue;}
if line.is_empty() {
continue;
}
//println!("LINE: {}", line);
if line.starts_with("#") || line.starts_with("07E9") {
// First char is #, diag message
@ -92,7 +100,7 @@ impl Nag52USB {
eprintln!("Discarding invalid diag msg '{}'", line);
} else {
let can_id = u32::from_str_radix(&line[0..4], 16).unwrap();
if let Ok(p) = catch_unwind(||{
if let Ok(p) = catch_unwind(|| {
let payload: Vec<u8> = (4..line.len())
.step_by(2)
.map(|i| u8::from_str_radix(&line[i..i + 2], 16).unwrap())
@ -103,7 +111,7 @@ impl Nag52USB {
}
}
} else {
println!("{}", line );
println!("{}", line);
//read_tx_log.send(msg);
}
line.clear();
@ -137,7 +145,7 @@ impl Nag52USB {
rx_log: Some(read_rx_log),
tx_id: 0,
rx_id: 0,
legacy_tx_mode
legacy_tx_mode,
})
}
@ -235,23 +243,25 @@ impl PayloadChannel for Nag52USB {
match self.port.as_mut() {
Some(p) => {
if self.legacy_tx_mode {
let mut buf = String::with_capacity(buffer.len()*2 + 6);
write!(buf, "#{:04X}", addr);
for x in buffer {
write!(buf, "{:02X}", x);
let mut buf = String::with_capacity(buffer.len() * 2 + 6);
write!(buf, "#{:04X}", addr);
for x in buffer {
write!(buf, "{:02X}", x);
}
write!(buf, "\n");
p.write_all(buf.as_bytes())
.map_err(|e| ChannelError::IOError(e))?;
} else {
let mut to_write = Vec::with_capacity(buffer.len() + 4);
let size: u16 = (buffer.len() + 2) as u16;
to_write.push((size >> 8) as u8);
to_write.push((size & 0xFF) as u8);
to_write.push((addr >> 8) as u8);
to_write.push((addr & 0xFF) as u8);
to_write.extend_from_slice(&buffer);
p.write_all(&to_write)
.map_err(|e| ChannelError::IOError(e))?;
}
write!(buf, "\n");
p.write_all(buf.as_bytes()).map_err(|e| ChannelError::IOError(e))?;
} else {
let mut to_write = Vec::with_capacity(buffer.len()+4);
let size: u16 = (buffer.len()+2) as u16;
to_write.push((size >> 8) as u8);
to_write.push((size & 0xFF) as u8);
to_write.push((addr >> 8) as u8);
to_write.push((addr & 0xFF) as u8);
to_write.extend_from_slice(&buffer);
p.write_all(&to_write).map_err(|e| ChannelError::IOError(e))?;
}
Ok(())
}
None => Err(ChannelError::InterfaceNotOpen),

View File

@ -1,7 +1,5 @@
use std::{fs::File, io::Read};
#[repr(C, packed)]
// Derrived from https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L97
#[derive(Debug, Clone, Copy)]
@ -15,7 +13,7 @@ pub struct FirmwareHeader {
date: [u8; 16],
idf_ver: [u8; 32],
app_elf_sha: [u8; 32],
reserv2: [u32; 20]
reserv2: [u32; 20],
}
impl FirmwareHeader {
@ -39,7 +37,7 @@ impl FirmwareHeader {
#[derive(Debug, Clone)]
pub struct Firmware {
pub raw: Vec<u8>,
pub header: FirmwareHeader
pub header: FirmwareHeader,
}
const HEADER_MAGIC: [u8; 4] = [0x32, 0x54, 0xCD, 0xAB];
@ -49,7 +47,7 @@ assert_eq_size!([u8; HEADER_SIZE], FirmwareHeader);
pub enum FirmwareLoadError {
InvalidHeader,
NotValid(String), // Reason
IoError(std::io::Error)
IoError(std::io::Error),
}
impl From<std::io::Error> for FirmwareLoadError {
@ -70,8 +68,6 @@ impl std::fmt::Display for FirmwareLoadError {
pub type FirwmareLoadResult<T> = std::result::Result<T, FirmwareLoadError>;
pub fn load_binary(path: String) -> FirwmareLoadResult<Firmware> {
let mut f = File::open(path)?;
let mut buf = Vec::new();
@ -81,23 +77,23 @@ pub fn load_binary(path: String) -> FirwmareLoadResult<Firmware> {
loop {
let tmp = &buf[header_start_idx..];
if tmp.len() < HEADER_MAGIC.len() || header_start_idx > 50 {
return Err(FirmwareLoadError::NotValid("Could not find header magic".into()))
return Err(FirmwareLoadError::NotValid(
"Could not find header magic".into(),
));
}
if tmp[..HEADER_MAGIC.len()] == HEADER_MAGIC {
break; // Found!
}
header_start_idx+=1;
header_start_idx += 1;
}
if buf[header_start_idx..].len() < HEADER_SIZE {
return Err(FirmwareLoadError::NotValid("Could not find header magic".into()))
return Err(FirmwareLoadError::NotValid(
"Could not find header magic".into(),
));
}
// Ok, read the header
let header: FirmwareHeader = unsafe { std::ptr::read(buf[header_start_idx..].as_ptr() as *const _ ) };
Ok(
Firmware {
raw: buf,
header,
}
)
}
let header: FirmwareHeader =
unsafe { std::ptr::read(buf[header_start_idx..].as_ptr() as *const _) };
Ok(Firmware { raw: buf, header })
}

View File

@ -1,6 +1,6 @@
//! ESP Flashing utility in order
//! to flash new firmware to the TCM
//!
//!
//! This modules content is based on https://github.com/espressif/esptool/blob/master/esptool.py
pub mod bin;
pub mod bin;

View File

@ -1,21 +1,21 @@
use std::sync::mpsc;
use ecu_diagnostics::{ServerEventHandler, kwp2000::SessionType, ServerEvent};
use ecu_diagnostics::{kwp2000::SessionType, ServerEvent, ServerEventHandler};
use eframe::epaint::Color32;
pub mod diag_usb;
pub mod scanner;
pub mod flasher;
pub mod scanner;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum KwpEventLevel {
Info,
Warn,
Err
Err,
}
pub struct KwpEventHandler {
evt_queue: mpsc::Sender<(KwpEventLevel, String)>
evt_queue: mpsc::Sender<(KwpEventLevel, String)>,
}
impl KwpEventHandler {
@ -24,8 +24,8 @@ impl KwpEventHandler {
}
}
unsafe impl Sync for KwpEventHandler{}
unsafe impl Send for KwpEventHandler{}
unsafe impl Sync for KwpEventHandler {}
unsafe impl Send for KwpEventHandler {}
impl ServerEventHandler<SessionType> for KwpEventHandler {
#[allow(unused_results)]
@ -35,22 +35,31 @@ impl ServerEventHandler<SessionType> for KwpEventHandler {
//ecu_diagnostics::ServerEvent::ServerStart => todo!(),
//ecu_diagnostics::ServerEvent::ServerExit => todo!(),
ecu_diagnostics::ServerEvent::DiagModeChange { old, new } => {
self.evt_queue.send((KwpEventLevel::Info, format!("Diag server change mode from '{:?}' to '{:?}'", old, new)));
},
self.evt_queue.send((
KwpEventLevel::Info,
format!("Diag server change mode from '{:?}' to '{:?}'", old, new),
));
}
ecu_diagnostics::ServerEvent::Request(req) => {
self.evt_queue.send((KwpEventLevel::Info, format!("OUT {:02X?}", req)));
},
self.evt_queue
.send((KwpEventLevel::Info, format!("OUT {:02X?}", req)));
}
ecu_diagnostics::ServerEvent::Response(res) => {
match res {
Ok(payload) => self.evt_queue.send((KwpEventLevel::Info, format!("IN {:02X?}", payload))),
Err(e) => self.evt_queue.send((KwpEventLevel::Err, format!("IN {}", e.to_string())))
Ok(payload) => self
.evt_queue
.send((KwpEventLevel::Info, format!("IN {:02X?}", payload))),
Err(e) => self
.evt_queue
.send((KwpEventLevel::Err, format!("IN {}", e.to_string()))),
};
},
}
ecu_diagnostics::ServerEvent::TesterPresentError(e) => {
self.evt_queue.send((KwpEventLevel::Warn, format!("TESTER PRESENT ERROR {}", e)));
},
self.evt_queue
.send((KwpEventLevel::Warn, format!("TESTER PRESENT ERROR {}", e)));
}
//ecu_diagnostics::ServerEvent::InterfaceCloseOnExitError(_) => todo!(),
_ => {}
}
}
}
}

View File

@ -1,8 +1,6 @@
use std::sync::{Arc, Mutex};
use ecu_diagnostics::{
hardware::{HardwareCapabilities, HardwareInfo, HardwareError},
};
use ecu_diagnostics::hardware::{HardwareCapabilities, HardwareError, HardwareInfo};
use serial_rs::PortInfo;
use super::diag_usb::Nag52USB;
@ -14,41 +12,50 @@ pub struct Nag52UsbScanner {
impl Nag52UsbScanner {
pub fn new() -> Self {
let mut tmp = match serial_rs::list_ports() {
Ok(ports) => ports
.iter()
.map(|i| (HardwareInfo {
name: i.get_port().to_string(),
vendor: Some(i.get_manufacturer().to_string()),
device_fw_version: None,
api_version: None,
library_version: None,
library_location: None,
capabilities: HardwareCapabilities {
iso_tp: true,
can: false,
kline: false,
kline_kwp: false,
sae_j1850: false,
sci: false,
ip: false,
Ok(ports) => ports
.iter()
.map(|i| {
(
HardwareInfo {
name: i.get_port().to_string(),
vendor: Some(i.get_manufacturer().to_string()),
device_fw_version: None,
api_version: None,
library_version: None,
library_location: None,
capabilities: HardwareCapabilities {
iso_tp: true,
can: false,
kline: false,
kline_kwp: false,
sae_j1850: false,
sci: false,
ip: false,
},
},
}, i.clone()))
.collect::<Vec<(HardwareInfo, PortInfo)>>(),
Err(_) => Vec::new(),
};
let l: Vec<(HardwareInfo, PortInfo)> = tmp.clone().iter_mut().map(|mut x| {
x.0.api_version = Some("Legacy".into());
x.clone()
}).collect();
i.clone(),
)
})
.collect::<Vec<(HardwareInfo, PortInfo)>>(),
Err(_) => Vec::new(),
};
let l: Vec<(HardwareInfo, PortInfo)> = tmp
.clone()
.iter_mut()
.map(|mut x| {
x.0.api_version = Some("Legacy".into());
x.clone()
})
.collect();
tmp.extend_from_slice(&l);
Self {ports: tmp}
Self { ports: tmp }
}
}
impl ecu_diagnostics::hardware::HardwareScanner<Nag52USB> for Nag52UsbScanner {
fn list_devices(&self) -> Vec<ecu_diagnostics::hardware::HardwareInfo> {
self.ports.iter().map(|(info, _ )| info.clone()).collect()
self.ports.iter().map(|(info, _)| info.clone()).collect()
}
fn open_device_by_index(
@ -56,7 +63,11 @@ impl ecu_diagnostics::hardware::HardwareScanner<Nag52USB> for Nag52UsbScanner {
idx: usize,
) -> ecu_diagnostics::hardware::HardwareResult<std::sync::Arc<std::sync::Mutex<Nag52USB>>> {
match self.ports.get(idx) {
Some((p, port)) => Ok(Arc::new(Mutex::new(Nag52USB::new(&p.name, port.clone(), false)?))),
Some((p, port)) => Ok(Arc::new(Mutex::new(Nag52USB::new(
&p.name,
port.clone(),
false,
)?))),
None => Err(HardwareError::DeviceNotFound),
}
}
@ -66,7 +77,11 @@ impl ecu_diagnostics::hardware::HardwareScanner<Nag52USB> for Nag52UsbScanner {
name: &str,
) -> ecu_diagnostics::hardware::HardwareResult<std::sync::Arc<std::sync::Mutex<Nag52USB>>> {
match self.ports.iter().find(|(i, p)| i.name == name) {
Some((p, port)) => Ok(Arc::new(Mutex::new(Nag52USB::new(&p.name, port.clone(), p.api_version.is_some())?))),
Some((p, port)) => Ok(Arc::new(Mutex::new(Nag52USB::new(
&p.name,
port.clone(),
p.api_version.is_some(),
)?))),
None => Err(HardwareError::DeviceNotFound),
}
}

View File

@ -1,8 +1,19 @@
use std::{borrow::BorrowMut, collections::VecDeque, time::{Instant, Duration}, ops::Add};
use std::{
borrow::BorrowMut,
collections::VecDeque,
ops::Add,
time::{Duration, Instant},
};
use crate::ui::{status_bar::{self}, main};
use eframe::{egui::{self, Direction}, epaint::Pos2};
use egui_toast::{Toasts, Toast, ToastOptions, ToastKind};
use crate::ui::{
main,
status_bar::{self},
};
use eframe::{
egui::{self, Direction},
epaint::Pos2,
};
use egui_toast::{Toast, ToastKind, ToastOptions, Toasts};
pub struct MainWindow {
pages: VecDeque<Box<dyn InterfacePage>>,
@ -17,7 +28,7 @@ impl MainWindow {
pages: VecDeque::new(),
curr_title: "OpenVehicleDiag".into(),
bar: None,
show_back: true
show_back: true,
}
}
pub fn add_new_page(&mut self, p: Box<dyn InterfacePage>) {
@ -42,25 +53,27 @@ impl eframe::App for MainWindow {
if stack_size > 0 {
let mut pop_page = false;
if let Some(status_bar) = self.bar.borrow_mut() {
egui::TopBottomPanel::bottom("NAV")
.show(ctx, |nav| {
nav.horizontal(|row| {
status_bar.draw(row, ctx);
if stack_size > 1 && self.show_back {
if row.button("Back").clicked() {
pop_page = true;
}
egui::TopBottomPanel::bottom("NAV").show(ctx, |nav| {
nav.horizontal(|row| {
status_bar.draw(row, ctx);
if stack_size > 1 && self.show_back {
if row.button("Back").clicked() {
pop_page = true;
}
});
s_bar_height = nav.available_height()
}
});
s_bar_height = nav.available_height()
});
}
if pop_page {
self.pop_page();
}
let mut toasts = Toasts::new(ctx)
.anchor(Pos2::new(5.0, ctx.available_rect().height()-s_bar_height - 5.0))
.anchor(Pos2::new(
5.0,
ctx.available_rect().height() - s_bar_height - 5.0,
))
.align_to_end(false)
.direction(Direction::BottomUp);
@ -76,13 +89,17 @@ impl eframe::App for MainWindow {
PageAction::RePaint => ctx.request_repaint(),
PageAction::SetBackButtonState(state) => {
self.show_back = state;
},
}
PageAction::SendNotification { text, kind } => {
println!("Pushing notification {}", text);
toasts.add(text, kind, ToastOptions {
show_icon: true,
expires_at: Some(Instant::now().add(Duration::from_secs(5))),
});
toasts.add(
text,
kind,
ToastOptions {
show_icon: true,
expires_at: Some(Instant::now().add(Duration::from_secs(5))),
},
);
}
}
});