New map editor + cargo fmt
This commit is contained in:
parent
c053774c69
commit
87654907e6
|
@ -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
|
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -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)),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -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)),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)) = ©.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)) = ©.curr_edit_cell {
|
||||
if let Ok(new_v) = i16::from_str_radix(¤t_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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 })
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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!(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue