Updates for rust 1.62 and latest FW
This commit is contained in:
parent
bdd173c2d6
commit
ddcc4b76cb
|
@ -6,19 +6,23 @@ resolver = "2"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ecu_diagnostics="0.90.41"
|
||||
#egui="0.15.0"
|
||||
serde_json = { version = "1.0.79" }
|
||||
serde = { version = "1.0.137", features = ["derive"] }
|
||||
#ecu_diagnostics="0.90.41"
|
||||
ecu_diagnostics = {path="../../ecu_diagnostics"}
|
||||
image = "0.24.1"
|
||||
#serialport = {git = "https://github.com/rnd-ash/serialport-rs"} # For manual USB connection
|
||||
serial-rs = {git="https://github.com/rnd-ash/serial-rs"}
|
||||
nfd="0.0.4"
|
||||
egui_wgpu_backend = "0.17"
|
||||
egui_winit_platform = "0.14.0"
|
||||
pollster = "0.2.5"
|
||||
egui = "0.17.0"
|
||||
epi = "0.17.0"
|
||||
eframe = {git = "https://github.com/emilk/egui", features=["wgpu"] }
|
||||
wgpu = "0.12"
|
||||
winit = "0.26.1"
|
||||
egui-winit = "0.17.0"
|
||||
egui-wgpu = "0.18.0"
|
||||
egui-winit = "0.18.0"
|
||||
modular-bitfield = "0.11.2"
|
||||
static_assertions = "1.1.0"
|
||||
static_assertions = "1.1.0"
|
||||
env_logger="0.9.0"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
|
@ -2,11 +2,10 @@
|
|||
extern crate static_assertions;
|
||||
|
||||
use std::iter;
|
||||
use egui::FontDefinitions;
|
||||
use epi::App;
|
||||
use eframe::{NativeOptions, Renderer};
|
||||
use ui::launcher::Launcher;
|
||||
use window::MainWindow;
|
||||
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
|
||||
use egui_wgpu::renderer::{RenderPass, ScreenDescriptor};
|
||||
use egui_winit_platform::{Platform, PlatformDescriptor};
|
||||
use winit::event::Event::*;
|
||||
use winit::event_loop::ControlFlow;
|
||||
|
@ -19,20 +18,9 @@ mod window;
|
|||
#[cfg(all(target_arch = "x86_64", target_os = "windows"))]
|
||||
compile_error!("Windows can ONLY be built using the i686-pc-windows-msvc target!");
|
||||
|
||||
enum Event {
|
||||
RequestRedraw
|
||||
}
|
||||
|
||||
struct RepaintSignal(std::sync::Mutex<winit::event_loop::EventLoopProxy<Event>>);
|
||||
|
||||
impl epi::backend::RepaintSignal for RepaintSignal {
|
||||
fn request_repaint(&self) {
|
||||
self.0.lock().unwrap().send_event(Event::RequestRedraw).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
||||
env_logger::init();
|
||||
/*
|
||||
let event_loop = winit::event_loop::EventLoop::with_user_event();
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_decorations(true)
|
||||
|
@ -44,187 +32,15 @@ fn main() {
|
|||
})
|
||||
.build(&event_loop)
|
||||
.unwrap();
|
||||
|
||||
*/
|
||||
let mut app = window::MainWindow::new();
|
||||
app.add_new_page(Box::new(Launcher::new()));
|
||||
let mut native_options = NativeOptions::default();
|
||||
#[cfg(windows)]
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::DX12 | wgpu::Backends::DX11 | wgpu::Backends::GL);
|
||||
#[cfg(unix)]
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
|
||||
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::LowPower,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})).or_else(|| {
|
||||
pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::HighPerformance,
|
||||
compatible_surface: Some(&surface),
|
||||
force_fallback_adapter: false,
|
||||
})) }
|
||||
).unwrap();
|
||||
|
||||
let (device, queue) = pollster::block_on(adapter.request_device(
|
||||
&wgpu::DeviceDescriptor {
|
||||
features: wgpu::Features::default(),
|
||||
limits: wgpu::Limits::default(),
|
||||
label: None,
|
||||
},
|
||||
None,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let size = window.inner_size();
|
||||
let surface_format = surface.get_preferred_format(&adapter).unwrap();
|
||||
let mut surface_config = wgpu::SurfaceConfiguration {
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
format: surface_format,
|
||||
width: size.width as u32,
|
||||
height: size.height as u32,
|
||||
present_mode: wgpu::PresentMode::Fifo,
|
||||
};
|
||||
surface.configure(&device, &surface_config);
|
||||
|
||||
let repaint_signal = std::sync::Arc::new(RepaintSignal(std::sync::Mutex::new(
|
||||
event_loop.create_proxy(),
|
||||
)));
|
||||
|
||||
// We use the egui_winit_platform crate as the platform.
|
||||
let mut platform = Platform::new(PlatformDescriptor {
|
||||
physical_width: size.width as u32,
|
||||
physical_height: size.height as u32,
|
||||
scale_factor: window.scale_factor(),
|
||||
font_definitions: FontDefinitions::default(),
|
||||
style: Default::default(),
|
||||
});
|
||||
|
||||
let mut egui_rpass = RenderPass::new(&device, surface_format, 1);
|
||||
let mut state = egui_winit::State::new(4096, &window);
|
||||
let context = egui::Context::default();
|
||||
let mut app = MainWindow::new();
|
||||
app.add_new_page(Box::new(Launcher::new()));
|
||||
event_loop.run(move|event, _, control_flow| {
|
||||
match event {
|
||||
RedrawRequested(..) => {
|
||||
let output_frame = match surface.get_current_texture() {
|
||||
Ok(frame) => frame,
|
||||
Err(wgpu::SurfaceError::Outdated) => {
|
||||
// This error occurs when the app is minimized on Windows.
|
||||
// Silently return here to prevent spamming the console with:
|
||||
// "The underlying surface has changed, and therefore the swap chain must be updated"
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Dropped frame with error: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let output_view = output_frame
|
||||
.texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
// Begin to draw the UI frame.
|
||||
let input = state.take_egui_input(&window);
|
||||
context.begin_frame(input);
|
||||
let app_output = epi::backend::AppOutput::default();
|
||||
|
||||
let frame = epi::Frame::new(epi::backend::FrameData {
|
||||
info: epi::IntegrationInfo {
|
||||
name: "egui_example",
|
||||
web_info: None,
|
||||
cpu_usage: None,
|
||||
native_pixels_per_point: Some(window.scale_factor() as _),
|
||||
prefer_dark_mode: None,
|
||||
},
|
||||
output: app_output,
|
||||
repaint_signal: repaint_signal.clone(),
|
||||
});
|
||||
|
||||
// Draw the demo application.
|
||||
app.update(&context, &frame);
|
||||
|
||||
// End the UI frame. We could now handle the output and draw the UI with the backend.
|
||||
let output = context.end_frame();
|
||||
let paint_jobs = context.tessellate(output.shapes);
|
||||
|
||||
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||
label: Some("encoder"),
|
||||
});
|
||||
|
||||
// Upload all resources for the GPU.
|
||||
let screen_descriptor = ScreenDescriptor {
|
||||
physical_width: surface_config.width,
|
||||
physical_height: surface_config.height,
|
||||
scale_factor: window.scale_factor() as f32,
|
||||
};
|
||||
|
||||
egui_rpass.add_textures(&device, &queue, &output.textures_delta).unwrap();
|
||||
egui_rpass.remove_textures(output.textures_delta).unwrap();
|
||||
egui_rpass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor);
|
||||
|
||||
// Record all render passes.
|
||||
egui_rpass
|
||||
.execute(
|
||||
&mut encoder,
|
||||
&output_view,
|
||||
&paint_jobs,
|
||||
&screen_descriptor,
|
||||
Some(wgpu::Color::BLACK),
|
||||
)
|
||||
.unwrap();
|
||||
// Submit the commands.
|
||||
queue.submit(iter::once(encoder.finish()));
|
||||
|
||||
// Redraw egui
|
||||
output_frame.present();
|
||||
|
||||
// Suppport reactive on windows only, but not on linux.
|
||||
// if _output.needs_repaint {
|
||||
// *control_flow = ControlFlow::Poll;
|
||||
// } else {
|
||||
// *control_flow = ControlFlow::Wait;
|
||||
// }
|
||||
}
|
||||
MainEventsCleared | UserEvent(Event::RequestRedraw) => {
|
||||
window.request_redraw();
|
||||
}
|
||||
WindowEvent { event, .. } => match event {
|
||||
winit::event::WindowEvent::Resized(size) => {
|
||||
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
|
||||
// See: https://github.com/rust-windowing/winit/issues/208
|
||||
// This solves an issue where the app would panic when minimizing on Windows.
|
||||
if size.width > 0 && size.height > 0 {
|
||||
surface_config.width = size.width;
|
||||
surface_config.height = size.height;
|
||||
surface.configure(&device, &surface_config);
|
||||
}
|
||||
}
|
||||
winit::event::WindowEvent::CloseRequested => {
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
event => {
|
||||
// Pass the winit events to the platform integration.
|
||||
state.on_event(&context, &event);
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
let mut app = MainWindow::new();
|
||||
let mut native_options = eframe::NativeOptions::default();
|
||||
/*
|
||||
if let Ok(img) = image::load_from_memory_with_format(TRAY_ICON, ImageFormat::Png) {
|
||||
native_options.icon_data = Some(IconData {
|
||||
rgba: img.clone().into_bytes(),
|
||||
width: img.width(),
|
||||
height: img.height(),
|
||||
})
|
||||
{
|
||||
native_options.renderer = Renderer::Wgpu;
|
||||
}
|
||||
*/
|
||||
app.add_new_page(Box::new(Launcher::new()));
|
||||
native_options.initial_window_size = Some(Vec2::new(1280.0, 720.0));
|
||||
eframe::run_native(Box::new(app), native_options)
|
||||
*/
|
||||
eframe::run_native("Ultimate NAG52 config suite", native_options, Box::new(|cc| {
|
||||
Box::new(app)
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use std::{sync::{Arc, Mutex}, borrow::BorrowMut};
|
||||
|
||||
use ecu_diagnostics::{hardware::Hardware, kwp2000::{Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler, SessionType, ResetMode}, DiagnosticServer};
|
||||
use egui::{Ui, Label};
|
||||
use epi::Frame;
|
||||
use crate::{usb_hw::diag_usb::Nag52USB, window::PageAction};
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagnosticServer};
|
||||
use eframe::egui::Ui;
|
||||
use eframe::egui::{self, *};
|
||||
use crate::window::PageAction;
|
||||
|
||||
use self::cfg_structs::{TcmCoreConfig, DefaultProfile, EngineType};
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl ConfigPage {
|
|||
|
||||
|
||||
impl crate::window::InterfacePage for ConfigPage {
|
||||
fn make_ui(&mut self, ui: &mut Ui, frame: &Frame) -> PageAction {
|
||||
fn make_ui(&mut self, ui: &mut Ui, frame: &eframe::Frame) -> PageAction {
|
||||
ui.heading("TCM Configuration");
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{Read, Write},
|
||||
io::Write,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
}, num::Wrapping, ops::DerefMut,
|
||||
}, ops::DerefMut,
|
||||
};
|
||||
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagServerResult, DiagnosticServer, DiagError};
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
use nfd::Response;
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType}, DiagServerResult, DiagnosticServer, DiagError};
|
||||
use eframe::egui::{self, *};
|
||||
|
||||
use crate::{
|
||||
usb_hw::{diag_usb::Nag52USB, flasher::{self, bin::{Firmware, load_binary}}},
|
||||
window::{InterfacePage, PageAction},
|
||||
};
|
||||
|
||||
|
@ -91,7 +87,7 @@ impl InterfacePage for CrashAnalyzerUI {
|
|||
fn make_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &epi::Frame,
|
||||
frame: &eframe::Frame,
|
||||
) -> crate::window::PageAction {
|
||||
ui.heading("Crash Analyzer");
|
||||
ui.label(
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem::{size_of, transmute};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use ecu_diagnostics::hardware::Hardware;
|
||||
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler};
|
||||
use egui::plot::{Plot, Value, Line, Values, Legend};
|
||||
use egui::{Ui, RichText, Color32};
|
||||
use epi::Frame;
|
||||
use std::time::Instant;
|
||||
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
|
||||
use eframe::egui::plot::{Plot, Value, Line, Values, 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;
|
||||
use crate::usb_hw::diag_usb::Nag52USB;
|
||||
use ecu_diagnostics::{kwp2000::*, bcd_decode};
|
||||
use crate::ui::diagnostics::rli::{DataCanDump, DataGearboxSensors, DataSolenoids, LocalRecordData, RecordIdents};
|
||||
pub mod shift_reporter;
|
||||
use crate::ui::diagnostics::rli::{LocalRecordData, RecordIdents};
|
||||
|
||||
use self::rli::ChartData;
|
||||
|
||||
|
@ -58,7 +54,8 @@ impl DiagnosticsPage {
|
|||
|
||||
|
||||
impl crate::window::InterfacePage for DiagnosticsPage {
|
||||
fn make_ui(&mut self, ui: &mut Ui, frame: &Frame) -> PageAction {
|
||||
fn make_ui(&mut self, ui: &mut Ui, frame: &eframe::Frame) -> PageAction {
|
||||
let mut pending = false;
|
||||
ui.heading("This is experimental, use with MOST up-to-date firmware");
|
||||
|
||||
if ui.button("Query ECU Serial number").clicked() {
|
||||
|
@ -100,12 +97,25 @@ impl crate::window::InterfacePage for DiagnosticsPage {
|
|||
self.charting_data.clear();
|
||||
self.record_data = None;
|
||||
}
|
||||
if ui.button("Query solenoid pressures").clicked() {
|
||||
self.record_to_query = Some(RecordIdents::PressureStatus);
|
||||
self.chart_idx = 0;
|
||||
self.charting_data.clear();
|
||||
self.record_data = None;
|
||||
}
|
||||
if ui.button("Query can Rx data").clicked() {
|
||||
self.record_to_query = Some(RecordIdents::CanDataDump);
|
||||
self.chart_idx = 0;
|
||||
self.charting_data.clear();
|
||||
self.record_data = None;
|
||||
}
|
||||
if ui.button("Query i2S DMA data").clicked() {
|
||||
self.record_to_query = Some(RecordIdents::DmaDump);
|
||||
self.chart_idx = 0;
|
||||
self.charting_data.clear();
|
||||
self.record_data = None;
|
||||
pending = true;
|
||||
}
|
||||
|
||||
if ui.button("Query Performance metrics").clicked() {
|
||||
self.record_to_query = Some(RecordIdents::SysUsage);
|
||||
|
@ -124,64 +134,87 @@ impl crate::window::InterfacePage for DiagnosticsPage {
|
|||
},
|
||||
}
|
||||
|
||||
if self.query_loop && self.last_query_time.elapsed().as_millis() > 100 {
|
||||
if pending || (self.query_loop && self.last_query_time.elapsed().as_millis() > 100) {
|
||||
self.last_query_time = Instant::now();
|
||||
self.chart_idx += 100;
|
||||
if let Some(rid) = self.record_to_query {
|
||||
if let Ok(r) = rid.query_ecu(&mut self.server.lock().unwrap()) {
|
||||
self.record_data = Some(r)
|
||||
match rid.query_ecu(&mut self.server.lock().unwrap()) {
|
||||
Ok(r) => self.record_data = Some(r),
|
||||
Err(e) => {
|
||||
eprintln!("Could not query {}", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(data) = &self.record_data {
|
||||
data.to_table(ui);
|
||||
|
||||
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() > 500 {
|
||||
let _ = self.charting_data.pop_front();
|
||||
if let LocalRecordData::Dma(dma) = data {
|
||||
let mut points: Vec<Value> = Vec::new();
|
||||
for (idx, y) in dma.dma_buffer.clone().iter().enumerate() {
|
||||
points.push(Value::new(idx as f64, *y));
|
||||
}
|
||||
let avg = dma.adc_detect;
|
||||
Plot::new("I2S DMA")
|
||||
.allow_drag(false)
|
||||
.include_x(0)
|
||||
.include_x(1000)
|
||||
.include_y(3300)
|
||||
.show(ui, |plot_ui| {
|
||||
plot_ui.line(Line::new(Values::from_values(points)));
|
||||
plot_ui.line(Line::new(Values::from_values(vec![Value::new(0, avg), Value::new(1000, avg) ])).highlight(true))
|
||||
});
|
||||
|
||||
// Can guarantee everything in `self.charting_data` will have the SAME length
|
||||
// as `d`
|
||||
let mut lines = Vec::new();
|
||||
let mut legend = Legend::default();
|
||||
} else {
|
||||
|
||||
for (idx, (key, _, _)) in d.data.iter().enumerate() {
|
||||
let mut points: Vec<Value> = Vec::new();
|
||||
for (timestamp, point) in &self.charting_data {
|
||||
points.push(Value::new(*timestamp as f64, point.data[idx].1))
|
||||
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();
|
||||
}
|
||||
let mut key_hasher = DefaultHasher::default();
|
||||
key.hash(&mut key_hasher);
|
||||
let r = key_hasher.finish();
|
||||
lines.push(Line::new(Values::from_values(points)).name(key.clone()).color(Color32::from_rgb((r & 0xFF) as u8, ((r >> 8) & 0xFF) as u8, ((r >> 16) & 0xFF) as u8)))
|
||||
|
||||
// Can guarantee everything in `self.charting_data` will have the SAME length
|
||||
// as `d`
|
||||
let mut lines = Vec::new();
|
||||
let mut legend = Legend::default();
|
||||
|
||||
for (idx, (key, _, _)) in d.data.iter().enumerate() {
|
||||
let mut points: Vec<Value> = Vec::new();
|
||||
for (timestamp, point) in &self.charting_data {
|
||||
points.push(Value::new(*timestamp as f64, point.data[idx].1))
|
||||
}
|
||||
let mut key_hasher = DefaultHasher::default();
|
||||
key.hash(&mut key_hasher);
|
||||
let r = key_hasher.finish();
|
||||
lines.push(Line::new(Values::from_values(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)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
});
|
||||
}
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
|
||||
PageAction::None
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
//! Read data by local identifier data structures
|
||||
//! Based on diag_data.h in TCM source code
|
||||
//!
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use std::mem::size_of;
|
||||
use ecu_diagnostics::{DiagError, DiagServerResult};
|
||||
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer};
|
||||
use egui::{Color32, InnerResponse, RichText, Ui};
|
||||
use eframe::egui::{self, Color32, InnerResponse, RichText, Ui};
|
||||
use modular_bitfield::{bitfield, BitfieldSpecifier};
|
||||
|
||||
#[repr(u8)]
|
||||
|
@ -14,6 +15,8 @@ pub enum RecordIdents {
|
|||
SolenoidStatus = 0x21,
|
||||
CanDataDump = 0x22,
|
||||
SysUsage = 0x23,
|
||||
PressureStatus = 0x25,
|
||||
DmaDump = 0x26,
|
||||
}
|
||||
|
||||
impl RecordIdents {
|
||||
|
@ -24,6 +27,14 @@ impl RecordIdents {
|
|||
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::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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +44,9 @@ pub enum LocalRecordData {
|
|||
Sensors(DataGearboxSensors),
|
||||
Solenoids(DataSolenoids),
|
||||
Canbus(DataCanDump),
|
||||
SysUsage(DataSysUsage)
|
||||
SysUsage(DataSysUsage),
|
||||
Pressures(DataPressures),
|
||||
Dma(DataDmaDump)
|
||||
}
|
||||
|
||||
impl LocalRecordData {
|
||||
|
@ -42,7 +55,12 @@ impl LocalRecordData {
|
|||
LocalRecordData::Sensors(s) => s.to_table(ui),
|
||||
LocalRecordData::Solenoids(s) => s.to_table(ui),
|
||||
LocalRecordData::Canbus(s) => s.to_table(ui),
|
||||
LocalRecordData::SysUsage(s) => s.to_table(ui)
|
||||
LocalRecordData::SysUsage(s) => s.to_table(ui),
|
||||
LocalRecordData::Pressures(s) => s.to_table(ui),
|
||||
_ => {
|
||||
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,11 +70,56 @@ impl LocalRecordData {
|
|||
LocalRecordData::Solenoids(s) => s.to_chart_data(),
|
||||
LocalRecordData::Canbus(s) => s.to_chart_data(),
|
||||
LocalRecordData::SysUsage(s) => s.to_chart_data(),
|
||||
LocalRecordData::Pressures(s) => s.to_chart_data(),
|
||||
_ => vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct DataPressures {
|
||||
pub spc_pwm: u16,
|
||||
pub mpc_pwm: u16,
|
||||
pub tcc_pwm: u16,
|
||||
pub spc_pressure: u16,
|
||||
pub mpc_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.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.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.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))
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[bitfield]
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct DataGearboxSensors {
|
||||
|
@ -101,7 +164,7 @@ impl DataGearboxSensors {
|
|||
ui.end_row();
|
||||
|
||||
ui.label("ATF Oil temperature\n(Only when parking lock off)");
|
||||
ui.label(if self.atf_temp_c() as i32 == u16::MAX as i32 { 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");
|
||||
|
@ -139,6 +202,12 @@ impl ChartData {
|
|||
}
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
@ -151,6 +220,10 @@ pub struct DataSolenoids {
|
|||
pub spc_current: u16,
|
||||
pub mpc_current: u16,
|
||||
pub tcc_current: u16,
|
||||
pub targ_spc_current: u16,
|
||||
pub targ_mpc_current: u16,
|
||||
pub adjustment_spc: u16,
|
||||
pub adjustment_mpc: u16,
|
||||
pub y3_current: u16,
|
||||
pub y4_current: u16,
|
||||
pub y5_current: u16
|
||||
|
@ -160,11 +233,13 @@ 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", self.mpc_pwm(), self.mpc_current()));
|
||||
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", self.spc_pwm(), self.spc_current()));
|
||||
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");
|
||||
|
@ -185,12 +260,12 @@ impl DataSolenoids {
|
|||
|
||||
ui.label("Total current consumption");
|
||||
ui.label(format!("{} mA",
|
||||
self.y5_current() +
|
||||
self.y4_current() +
|
||||
self.y3_current() +
|
||||
self.mpc_current() +
|
||||
self.spc_current() +
|
||||
self.tcc_current()
|
||||
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();
|
||||
})
|
||||
|
@ -250,6 +325,10 @@ pub enum ShifterPosition {
|
|||
Drive,
|
||||
Plus,
|
||||
Minus,
|
||||
Four,
|
||||
Three,
|
||||
Two,
|
||||
One,
|
||||
SNV = 0xFF
|
||||
}
|
||||
|
||||
|
@ -339,14 +418,24 @@ impl DataCanDump {
|
|||
pub struct DataSysUsage {
|
||||
core1_usage: u16,
|
||||
core2_usage: u16,
|
||||
free_heap: u32,
|
||||
free_ram: u32,
|
||||
total_ram: u32,
|
||||
free_psram: u32,
|
||||
total_psram: u32,
|
||||
num_tasks: u32,
|
||||
}
|
||||
|
||||
impl DataSysUsage {
|
||||
pub fn to_table(&self, ui: &mut Ui) -> InnerResponse<()> {
|
||||
println!("{:#?}", self);
|
||||
let r_f = self.free_ram() as f32;
|
||||
let r_t = self.total_ram() as f32;
|
||||
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;
|
||||
|
||||
egui::Grid::new("DGS").striped(true).show(ui, |ui| {
|
||||
ui.label("Core 1 usage");
|
||||
ui.label(format!("{:.1} %", self.core1_usage() as f32 / 10.0));
|
||||
|
@ -356,12 +445,16 @@ impl DataSysUsage {
|
|||
ui.label(format!("{:.1} %", self.core2_usage() as f32 / 10.0));
|
||||
ui.end_row();
|
||||
|
||||
ui.label("Free Heap");
|
||||
ui.label(format!("{:.1} Kb", self.free_heap() as f32 / 1024.0));
|
||||
ui.label("Free internal RAM");
|
||||
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", self.free_psram() as f32 / 1024.0));
|
||||
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");
|
||||
ui.label(format!("{}", self.num_tasks()));
|
||||
ui.end_row();
|
||||
})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,460 @@
|
|||
use std::{sync::{Arc, atomic::{AtomicBool, Ordering}, RwLock, Mutex}, thread, time::Duration};
|
||||
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, KWP2000Error}, DiagnosticServer};
|
||||
use eframe::{egui::{plot::{Plot, Points, Value, Line, Values, LinkedAxisGroup, VLine, Text, LineStyle, PlotUi, Corner}, RichText}, epaint::{Stroke, Color32}};
|
||||
use modular_bitfield::bitfield;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
|
||||
use crate::ui::egui::ComboBox;
|
||||
|
||||
// Data structure for shift report
|
||||
pub const MAX_POINTS_PER_SR_ARRAY: usize = 6000/50;
|
||||
pub const REPORT_LEN: usize = std::mem::size_of::<ShiftReport>();
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)]
|
||||
struct ShiftPhase {
|
||||
ramp_time: u16,
|
||||
hold_time: u16,
|
||||
spc_pressure: u16,
|
||||
mpc_pressure: u16
|
||||
}
|
||||
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
|
||||
struct ShiftReport {
|
||||
atf_temp_c: i16,
|
||||
targ_curr: u8,
|
||||
profile: u8,
|
||||
requested_torque: u16,
|
||||
interval_points: u8,
|
||||
report_len: u16,
|
||||
engine_rpm: [u16; MAX_POINTS_PER_SR_ARRAY],
|
||||
input_rpm: [u16; MAX_POINTS_PER_SR_ARRAY],
|
||||
output_rpm: [u16; MAX_POINTS_PER_SR_ARRAY],
|
||||
engine_torque: [i16; MAX_POINTS_PER_SR_ARRAY],
|
||||
total_ms: u16,
|
||||
initial_mpc_pressure: u16,
|
||||
hold1_data: ShiftPhase,
|
||||
hold2_data: ShiftPhase,
|
||||
hold3_data: ShiftPhase,
|
||||
torque_data: ShiftPhase,
|
||||
overlap_data: ShiftPhase,
|
||||
max_pressure_data: ShiftPhase,
|
||||
transition_start: u16,
|
||||
transition_end: u16,
|
||||
flare_timestamp: u16,
|
||||
timeout: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
||||
struct SerializedShiftReport {
|
||||
atf_temp_c: i16,
|
||||
targ_curr: u8,
|
||||
profile: u8,
|
||||
requested_torque: u16,
|
||||
interval_points: u8,
|
||||
report_len: u16,
|
||||
engine_rpm: Vec<u16>,
|
||||
input_rpm: Vec<u16>,
|
||||
output_rpm: Vec<u16>,
|
||||
engine_torque: Vec<i16>,
|
||||
total_ms: u16,
|
||||
initial_mpc_pressure: u16,
|
||||
hold1_data: ShiftPhase,
|
||||
hold2_data: ShiftPhase,
|
||||
hold3_data: ShiftPhase,
|
||||
torque_data: ShiftPhase,
|
||||
overlap_data: ShiftPhase,
|
||||
max_pressure_data: ShiftPhase,
|
||||
transition_start: u16,
|
||||
transition_end: u16,
|
||||
flare_timestamp: u16,
|
||||
timeout: u8,
|
||||
}
|
||||
|
||||
impl SerializedShiftReport {
|
||||
fn from_raw(rpt: &ShiftReport) -> Self {
|
||||
let mut r = Self {
|
||||
atf_temp_c: rpt.atf_temp_c,
|
||||
targ_curr: rpt.targ_curr,
|
||||
profile: rpt.profile,
|
||||
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(),
|
||||
total_ms: rpt.total_ms,
|
||||
initial_mpc_pressure: rpt.initial_mpc_pressure,
|
||||
hold1_data: rpt.hold1_data,
|
||||
hold2_data: rpt.hold2_data,
|
||||
hold3_data: rpt.hold3_data,
|
||||
torque_data: rpt.torque_data,
|
||||
overlap_data: rpt.overlap_data,
|
||||
max_pressure_data: rpt.max_pressure_data,
|
||||
transition_start: rpt.transition_start,
|
||||
transition_end: rpt.transition_end,
|
||||
flare_timestamp: rpt.flare_timestamp,
|
||||
timeout: rpt.timeout,
|
||||
};
|
||||
r.engine_rpm.resize(r.report_len as usize, 0);
|
||||
r.input_rpm.resize(r.report_len as usize, 0);
|
||||
r.output_rpm.resize(r.report_len as usize, 0);
|
||||
r.engine_torque.resize(r.report_len as usize, 0);
|
||||
r
|
||||
}
|
||||
|
||||
fn to_raw(&self) -> ShiftReport {
|
||||
let mut r = ShiftReport {
|
||||
atf_temp_c: self.atf_temp_c,
|
||||
targ_curr: self.targ_curr,
|
||||
profile: self.targ_curr,
|
||||
requested_torque: self.requested_torque,
|
||||
interval_points: self.interval_points,
|
||||
report_len: self.report_len,
|
||||
engine_rpm: [0x00; MAX_POINTS_PER_SR_ARRAY],
|
||||
input_rpm: [0x00; MAX_POINTS_PER_SR_ARRAY],
|
||||
output_rpm: [0x00; MAX_POINTS_PER_SR_ARRAY],
|
||||
engine_torque: [0x00; MAX_POINTS_PER_SR_ARRAY],
|
||||
total_ms: self.total_ms,
|
||||
initial_mpc_pressure: self.initial_mpc_pressure,
|
||||
hold1_data: self.hold1_data,
|
||||
hold2_data: self.hold2_data,
|
||||
hold3_data: self.hold3_data,
|
||||
torque_data: self.torque_data,
|
||||
overlap_data: self.overlap_data,
|
||||
max_pressure_data: self.max_pressure_data,
|
||||
transition_start: self.transition_start,
|
||||
transition_end: self.transition_end,
|
||||
flare_timestamp: self.flare_timestamp,
|
||||
timeout: self.timeout,
|
||||
};
|
||||
unsafe {
|
||||
//r.engine_rpm
|
||||
//r.engine_rpm.copy_from_slice(&self.engine_rpm);
|
||||
//r.input_rpm.copy_from_slice(&self.input_rpm);
|
||||
//r.output_rpm.copy_from_slice(&self.output_rpm);
|
||||
//r.engine_torque.copy_from_slice(&self.engine_torque);
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
impl ShiftReportPage {
|
||||
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
|
||||
server.lock().unwrap().set_diagnostic_session_mode(SessionType::ExtendedDiagnostics);
|
||||
Self {
|
||||
bar,
|
||||
curr_report: None,
|
||||
report_list: Vec::new(),
|
||||
err: None,
|
||||
server,
|
||||
select_id: 0,
|
||||
axis_group: LinkedAxisGroup::x()
|
||||
}
|
||||
}
|
||||
pub fn parse_error(&mut self, e: ecu_diagnostics::DiagError) {
|
||||
match e {
|
||||
ecu_diagnostics::DiagError::ECUError { code, def: _ } => {
|
||||
// Conditions not correct actually implies in this mode that car is changing gear
|
||||
// so we don't have a lock on the resource
|
||||
if code == 0x22 {
|
||||
self.err = Some(format!("Car is currently changing gear, cannot query"))
|
||||
} else {
|
||||
self.err = Some(format!("Error ECU rejected the request: {}", 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 {
|
||||
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]);
|
||||
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
|
||||
}
|
||||
self.report_list.clear();
|
||||
for chunk in res.chunks(4) {
|
||||
let id = chunk[0];
|
||||
let targ = (chunk[1] & 0xF0) >> 4;
|
||||
let curr = chunk[1] & 0x0F;
|
||||
let atf: i16 = (chunk[2] as i16) << 8 | chunk[3] as i16;
|
||||
if targ != 0 && curr != 0 && atf != 0 {
|
||||
self.report_list.push((id, atf as i32, targ, curr))
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => self.parse_error(e)
|
||||
}
|
||||
}
|
||||
|
||||
if self.report_list.is_empty() {
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
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]);
|
||||
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
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let rpt_ptr: *const ShiftReport = res.as_ptr() as *const ShiftReport;
|
||||
let mut rpt = *rpt_ptr;
|
||||
rpt.transition_end;
|
||||
rpt.transition_start;
|
||||
self.curr_report = Some(rpt)
|
||||
}
|
||||
let x = self.curr_report.clone().unwrap();
|
||||
println!("{} {} {}", &{x.total_ms}, &{x.transition_start}, &{x.transition_end});
|
||||
},
|
||||
|
||||
Err(e) => self.parse_error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(e) = &self.err {
|
||||
ui.label(e);
|
||||
}
|
||||
|
||||
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!("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 - report.transition_start));
|
||||
}
|
||||
|
||||
|
||||
let time_axis: Vec<u16> = (0..=report.total_ms).step_by(report.interval_points as usize).collect();
|
||||
let mut time = 0;
|
||||
// Add pressure line (Static)
|
||||
let mut pressure_spc_points: Vec<Value> = Vec::new();
|
||||
let mut pressure_mpc_points: Vec<Value> = Vec::new();
|
||||
let mut engine_rpm_points: Vec<Value> = Vec::new();
|
||||
let mut input_rpm_points: Vec<Value> = Vec::new();
|
||||
let mut output_rpm_points: Vec<Value> = Vec::new();
|
||||
let mut torque_points: Vec<Value> = Vec::new();
|
||||
pressure_spc_points.push(Value::new(0, 0)); // Always
|
||||
pressure_mpc_points.push(Value::new(0, report.initial_mpc_pressure));
|
||||
|
||||
time += report.hold1_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold1_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold1_data.mpc_pressure));
|
||||
time += report.hold1_data.hold_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold1_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold1_data.mpc_pressure));
|
||||
|
||||
time += report.hold2_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold2_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold2_data.mpc_pressure));
|
||||
time += report.hold2_data.hold_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold2_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold2_data.mpc_pressure));
|
||||
|
||||
time += report.hold3_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold3_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold3_data.mpc_pressure));
|
||||
time += report.hold3_data.hold_time;
|
||||
pressure_spc_points.push(Value::new(time, report.hold3_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.hold3_data.mpc_pressure));
|
||||
|
||||
time += report.torque_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.torque_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.torque_data.mpc_pressure));
|
||||
time += report.torque_data.hold_time;
|
||||
pressure_spc_points.push(Value::new(time, report.torque_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.torque_data.mpc_pressure));
|
||||
|
||||
time += report.overlap_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.overlap_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.overlap_data.mpc_pressure));
|
||||
time += report.overlap_data.hold_time;
|
||||
pressure_spc_points.push(Value::new(time, report.overlap_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.overlap_data.mpc_pressure));
|
||||
|
||||
time += report.max_pressure_data.ramp_time;
|
||||
pressure_spc_points.push(Value::new(time, report.max_pressure_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.max_pressure_data.mpc_pressure));
|
||||
time = report.total_ms;
|
||||
pressure_spc_points.push(Value::new(time, report.max_pressure_data.spc_pressure));
|
||||
pressure_mpc_points.push(Value::new(time, report.max_pressure_data.mpc_pressure));
|
||||
|
||||
|
||||
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(Value::new(time_axis[x], report.engine_rpm[x]));
|
||||
input_rpm_points.push(Value::new(time_axis[x], report.input_rpm[x]));
|
||||
output_rpm_points.push(Value::new(time_axis[x], report.output_rpm[x]));
|
||||
torque_points.push(Value::new(time_axis[x], report.engine_torque[x]));
|
||||
}
|
||||
|
||||
// Add phase indication lines
|
||||
|
||||
|
||||
let spc_pressure_line = Line::new(Values::from_values(pressure_spc_points)).name("SPC Pressure (mBar)");
|
||||
let mpc_pressure_line = Line::new(Values::from_values(pressure_mpc_points)).name("MPC Pressure (mBar)");
|
||||
let engine_line = Line::new(Values::from_values(engine_rpm_points)).name("Engine (RPM)");
|
||||
let output_line = Line::new(Values::from_values(output_rpm_points)).name("Output shaft (RPM)");
|
||||
let input_line = Line::new(Values::from_values(input_rpm_points)).name("Input shaft (RPM)");
|
||||
let torque_line = Line::new(Values::from_values(torque_points)).name("Engine torque (Nm)");
|
||||
|
||||
time = 0;
|
||||
time += report.hold1_data.hold_time+report.hold1_data.ramp_time;
|
||||
let hold1_end_time = time;
|
||||
time += report.hold2_data.hold_time+report.hold2_data.ramp_time;
|
||||
let hold2_end_time = time;
|
||||
time += report.hold3_data.hold_time+report.hold3_data.ramp_time;
|
||||
let hold3_end_time = time;
|
||||
|
||||
time += report.torque_data.hold_time+report.torque_data.ramp_time;
|
||||
let torque_end_time = time;
|
||||
time += report.overlap_data.hold_time+report.overlap_data.ramp_time;
|
||||
let overlap_end_time = time;
|
||||
time += report.max_pressure_data.hold_time+report.max_pressure_data.ramp_time;
|
||||
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 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(hold1_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
|
||||
plot_ui.vline(VLine::new(hold2_end_time).style(LineStyle::dashed_loose()).color(phase_colour));
|
||||
plot_ui.vline(VLine::new(hold3_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")
|
||||
.legend(legand.clone())
|
||||
.height(height_per_chart)
|
||||
.allow_drag(false)
|
||||
.include_x(0)
|
||||
.include_x(report.total_ms as f32 * 1.2)
|
||||
.include_y(0)
|
||||
.include_y(8000)
|
||||
.link_axis(self.axis_group.clone())
|
||||
.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(Value::new((0+hold1_end_time)/2, 7700), "Bleed"));
|
||||
plot_ui.text(Text::new(Value::new((hold1_end_time+hold2_end_time)/2, 7700), "Fill"));
|
||||
plot_ui.text(Text::new(Value::new((hold2_end_time+hold3_end_time)/2, 7700), "Lock"));
|
||||
plot_ui.text(Text::new(Value::new((hold3_end_time+torque_end_time)/2, 7700), "Torque"));
|
||||
plot_ui.text(Text::new(Value::new((torque_end_time+overlap_end_time)/2, 7700), "Overlap"));
|
||||
plot_ui.text(Text::new(Value::new((overlap_end_time+max_p_end_time)/2, 7700), "Max P"));
|
||||
});
|
||||
|
||||
let mut plot_rpm = Plot::new("Input/Engine RPM")
|
||||
.legend(legand.clone())
|
||||
.height(height_per_chart)
|
||||
.allow_drag(false)
|
||||
.include_x(0)
|
||||
.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| {
|
||||
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(Value::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)
|
||||
.height(height_per_chart)
|
||||
.allow_drag(false)
|
||||
.include_x(0)
|
||||
.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| {
|
||||
plot_ui.line(torque_line);
|
||||
add_shift_regions(plot_ui);
|
||||
});
|
||||
}
|
||||
|
||||
PageAction::None
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"Shift report history"
|
||||
}
|
||||
|
||||
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
|
||||
Some(Box::new(self.bar.clone()))
|
||||
}
|
||||
}
|
|
@ -1,17 +1,21 @@
|
|||
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering}, RwLock}, thread, time::Duration, char::MAX};
|
||||
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64}, RwLock}, thread, time::{Duration, Instant}, char::MAX};
|
||||
|
||||
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, SessionType};
|
||||
use egui::plot::{Plot, Legend, Line, Values, Value};
|
||||
use eframe::egui::plot::{Plot, Legend, Line, Values, Value};
|
||||
|
||||
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
|
||||
|
||||
use super::rli::{DataSolenoids, RecordIdents, LocalRecordData};
|
||||
|
||||
const UPDATE_DELAY_MS: u64 = 100;
|
||||
|
||||
pub struct SolenoidPage{
|
||||
bar: MainStatusBar,
|
||||
query_ecu: Arc<AtomicBool>,
|
||||
curr_values: Arc<RwLock<Option<DataSolenoids>>>
|
||||
last_update_time: Arc<AtomicU64>,
|
||||
curr_values: Arc<RwLock<Option<DataSolenoids>>>,
|
||||
prev_values: Arc<RwLock<Option<DataSolenoids>>>,
|
||||
time_since_launch: Instant
|
||||
}
|
||||
|
||||
impl SolenoidPage {
|
||||
|
@ -22,22 +26,42 @@ impl SolenoidPage {
|
|||
let store = Arc::new(RwLock::new(None));
|
||||
let store_t = store.clone();
|
||||
|
||||
let store_old = Arc::new(RwLock::new(None));
|
||||
let store_old_t = store_old.clone();
|
||||
|
||||
let launch_time = Instant::now();
|
||||
let launch_time_t = launch_time.clone();
|
||||
|
||||
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);
|
||||
while run_t.load(Ordering::Relaxed) {
|
||||
let start = Instant::now();
|
||||
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);
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
let taken = start.elapsed().as_millis() as u64;
|
||||
if taken < UPDATE_DELAY_MS {
|
||||
std::thread::sleep(Duration::from_millis(UPDATE_DELAY_MS-taken));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
Self {
|
||||
bar,
|
||||
query_ecu: run,
|
||||
curr_values: store
|
||||
curr_values: store,
|
||||
last_update_time: last_update,
|
||||
prev_values: store_old,
|
||||
time_since_launch: launch_time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +72,7 @@ const MAX_DUTY: u16 = 0xFFF; // 12bit pwm (4096)
|
|||
const VOLTAGE_HIGH: u16 = 12;
|
||||
const VOLTAGE_LOW: u16 = 0;
|
||||
|
||||
fn make_line_duty_pwm(duty: u16, freq: u16, x_off: f64, y_off: f64) -> Values {
|
||||
fn make_line_duty_pwm(duty: f32, freq: u16, x_off: f64, y_off: f64) -> Values {
|
||||
let num_pulses = freq / GRAPH_TIME_MS as u16;
|
||||
let pulse_width = GRAPH_TIME_MS as f32 / num_pulses as f32;
|
||||
let pulse_on_width = (duty as f32/4096f32) * pulse_width;
|
||||
|
@ -58,10 +82,10 @@ fn make_line_duty_pwm(duty: u16, freq: u16, x_off: f64, y_off: f64) -> Values {
|
|||
let mut curr_x_pos = 0f32;
|
||||
|
||||
// Shortcut
|
||||
if duty == MAX_DUTY {
|
||||
if duty as u16 == MAX_DUTY {
|
||||
points.push(Value::new(0, VOLTAGE_LOW));
|
||||
points.push(Value::new(GRAPH_TIME_MS, VOLTAGE_LOW));
|
||||
} else if duty == 0 {
|
||||
} else if duty as u16 == 0 {
|
||||
points.push(Value::new(0, VOLTAGE_HIGH));
|
||||
points.push(Value::new(GRAPH_TIME_MS, VOLTAGE_HIGH));
|
||||
} else {
|
||||
|
@ -88,22 +112,35 @@ fn make_line_duty_pwm(duty: u16, freq: u16, x_off: f64, y_off: f64) -> Values {
|
|||
impl crate::window::InterfacePage for SolenoidPage {
|
||||
|
||||
|
||||
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &epi::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 curr = self.curr_values.read().unwrap().clone().unwrap_or_default();
|
||||
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 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 {
|
||||
proportion_prev = 0.5;
|
||||
proportion_curr = 0.5;
|
||||
} else if ms_since_update == UPDATE_DELAY_MS {
|
||||
proportion_prev = 0.5;
|
||||
proportion_curr = 0.5;
|
||||
}
|
||||
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()/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(), 1000, 0.0, 0.0)).name("MPC").width(2.0)));
|
||||
lines.push(("SPC", Line::new(make_line_duty_pwm(curr.spc_pwm(), 1000, 0.0, 0.0)).name("SPC").width(2.0)));
|
||||
lines.push(("TCC", Line::new(make_line_duty_pwm(curr.tcc_pwm(), 100, 0.0, 0.0)).name("TCC").width(2.0)));
|
||||
|
||||
lines.push(("Y3", Line::new(make_line_duty_pwm(curr.y3_pwm(), 1000, 0.0, 0.0)).name("Y3").width(2.0)));
|
||||
lines.push(("Y4", Line::new(make_line_duty_pwm(curr.y4_pwm(), 1000, 0.0, 0.0)).name("Y4").width(2.0)));
|
||||
lines.push(("Y5", Line::new(make_line_duty_pwm(curr.y5_pwm(), 1000, 0.0, 0.0)).name("Y5").width(2.0)));
|
||||
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))
|
||||
|
@ -119,9 +156,7 @@ impl crate::window::InterfacePage for SolenoidPage {
|
|||
plot_ui.line(line.1)
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
ui.ctx().request_repaint();
|
||||
PageAction::None
|
||||
}
|
||||
|
||||
|
@ -138,4 +173,4 @@ impl Drop for SolenoidPage {
|
|||
fn drop(&mut self) {
|
||||
self.query_ecu.store(false, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicU32, Ordering},
|
||||
Arc, Mutex, RwLock,
|
||||
}, num::Wrapping, ops::DerefMut, time::Instant,
|
||||
}, ops::DerefMut, time::Instant,
|
||||
};
|
||||
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType, ResetMode}, DiagServerResult, DiagnosticServer, DiagError};
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
use eframe::egui::*;
|
||||
use eframe::egui;
|
||||
use nfd::Response;
|
||||
|
||||
use crate::{
|
||||
usb_hw::{diag_usb::Nag52USB, flasher::{self, bin::{Firmware, load_binary}}},
|
||||
usb_hw::flasher::{bin::{Firmware, load_binary}},
|
||||
window::{InterfacePage, PageAction},
|
||||
};
|
||||
|
||||
|
@ -95,7 +92,7 @@ impl InterfacePage for FwUpdateUI {
|
|||
fn make_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &epi::Frame,
|
||||
frame: &eframe::Frame,
|
||||
) -> crate::window::PageAction {
|
||||
ui.heading("Firmware update");
|
||||
ui.label(
|
||||
|
@ -137,11 +134,16 @@ firmware.header.get_date()
|
|||
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|| {
|
||||
let mut lock = c.lock().unwrap();
|
||||
*state_c.write().unwrap() = FlashState::Prepare;
|
||||
old_timeouts = (lock.get_read_timeout(), lock.get_write_timeout());
|
||||
lock.set_rw_timeout(10000, 10000);
|
||||
|
||||
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))
|
||||
},
|
||||
Ok(size) => {
|
||||
|
@ -156,6 +158,7 @@ firmware.header.get_date()
|
|||
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));
|
||||
eprintln!("Writing failed! Error {}", e);
|
||||
lock.set_rw_timeout(old_timeouts.0, old_timeouts.1);
|
||||
failure = true;
|
||||
break;
|
||||
} else {
|
||||
|
@ -166,11 +169,13 @@ firmware.header.get_date()
|
|||
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))
|
||||
} else {
|
||||
*state_c.write().unwrap() = FlashState::Completed;
|
||||
}
|
||||
}
|
||||
lock.set_rw_timeout(old_timeouts.0, old_timeouts.1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64}, RwLock}, thread, time::{Duration, Instant}, char::MAX};
|
||||
|
||||
use ecu_diagnostics::kwp2000::{Kwp2000DiagnosticServer, SessionType};
|
||||
use eframe::egui::plot::{Plot, Legend, Line, Values, Value};
|
||||
|
||||
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
|
||||
|
||||
use rli::{DataSolenoids, RecordIdents, LocalRecordData};
|
||||
|
||||
use super::diagnostics::rli;
|
||||
|
||||
const UPDATE_DELAY_MS: u64 = 500;
|
||||
|
||||
pub struct IoManipulatorPage {
|
||||
bar: MainStatusBar,
|
||||
query_ecu: Arc<AtomicBool>,
|
||||
curr_solenoid_values: Arc<RwLock<Option<DataSolenoids>>>,
|
||||
time_since_launch: Instant,
|
||||
show_ui: bool
|
||||
}
|
||||
|
||||
impl IoManipulatorPage {
|
||||
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
|
||||
let run = Arc::new(AtomicBool::new(true));
|
||||
let run_t = run.clone();
|
||||
|
||||
let store = Arc::new(RwLock::new(None));
|
||||
let store_t = store.clone();
|
||||
|
||||
let store_old = Arc::new(RwLock::new(None));
|
||||
let store_old_t = store_old.clone();
|
||||
|
||||
let launch_time = Instant::now();
|
||||
let launch_time_t = launch_time.clone();
|
||||
|
||||
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);
|
||||
while run_t.load(Ordering::Relaxed) {
|
||||
let start = Instant::now();
|
||||
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);
|
||||
}
|
||||
}
|
||||
let taken = start.elapsed().as_millis() as u64;
|
||||
if taken < UPDATE_DELAY_MS {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const GRAPH_TIME_MS: u16 = 100;
|
||||
const MAX_DUTY: u16 = 0xFFF; // 12bit pwm (4096)
|
||||
|
||||
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 {
|
||||
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.
|
||||
|
||||
If the TCM detects it is inside a vehicle, it will reject any request.
|
||||
|
||||
Upon exiting this page, the TCM will reboot to reset to its default state.
|
||||
");
|
||||
|
||||
if !self.show_ui {
|
||||
let mut btn_action = None;
|
||||
ui.horizontal(|row| {
|
||||
if row.button("I understand").clicked() {
|
||||
|
||||
}
|
||||
if row.button("Take me to safety").clicked() {
|
||||
btn_action = Some(PageAction::Destroy);
|
||||
}
|
||||
});
|
||||
if let Some(req) = btn_action {
|
||||
return req;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
PageAction::None
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"IO Manipulator view"
|
||||
}
|
||||
|
||||
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
|
||||
Some(Box::new(self.bar.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IoManipulatorPage {
|
||||
fn drop(&mut self) {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
use std::{sync::{Arc, Mutex, mpsc}, ops::Deref};
|
||||
use std::{sync::{Arc, Mutex, mpsc}, ops::RangeInclusive};
|
||||
|
||||
use ecu_diagnostics::{hardware::{HardwareResult, HardwareScanner, passthru::{PassthruScanner, PassthruDevice}, Hardware, HardwareInfo}, channel::{PayloadChannel, IsoTPChannel}};
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
use ecu_diagnostics::{hardware::{HardwareResult, HardwareScanner, passthru::{PassthruScanner, PassthruDevice}, Hardware}, channel::IsoTPChannel};
|
||||
use eframe::egui::*;
|
||||
use eframe::egui;
|
||||
|
||||
#[cfg(unix)]
|
||||
use ecu_diagnostics::hardware::socketcan::{SocketCanScanner, SocketCanDevice};
|
||||
|
@ -13,6 +13,8 @@ use crate::{
|
|||
window::{InterfacePage, PageAction},
|
||||
};
|
||||
|
||||
use super::widgets::range_display::range_display;
|
||||
|
||||
type ScanResult = std::result::Result<Vec<String>, String>;
|
||||
|
||||
pub struct Launcher {
|
||||
|
@ -53,6 +55,7 @@ impl DynamicDevice {
|
|||
pub fn create_isotp_channel(&mut self) -> HardwareResult<Box<dyn IsoTPChannel>> {
|
||||
match self {
|
||||
DynamicDevice::Passthru(pt) => {
|
||||
PassthruDevice::toggle_sw_channel_raw(pt, true);
|
||||
Hardware::create_iso_tp_channel(pt.clone())
|
||||
},
|
||||
DynamicDevice::Usb(usb) => {
|
||||
|
@ -102,7 +105,7 @@ impl Launcher {
|
|||
}
|
||||
|
||||
impl InterfacePage for Launcher {
|
||||
fn make_ui(&mut self, ui: &mut Ui, frame: &epi::Frame) -> crate::window::PageAction {
|
||||
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");
|
||||
|
||||
|
@ -157,6 +160,9 @@ impl InterfacePage for Launcher {
|
|||
if let Some(e) = &self.launch_err {
|
||||
ui.label(RichText::new(format!("Error: {}", e)).color(Color32::from_rgb(255, 0, 0)));
|
||||
}
|
||||
|
||||
range_display(ui, 65.0, 50.0, 70.0, 0.0, 100.0);
|
||||
|
||||
crate::window::PageAction::None
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,17 @@
|
|||
use std::sync::{Arc, Mutex, mpsc};
|
||||
|
||||
use ecu_diagnostics::{
|
||||
hardware::Hardware,
|
||||
DiagnosticServer,
|
||||
kwp2000::{self, Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler}, channel::IsoTPChannel,
|
||||
kwp2000::{Kwp2000DiagnosticServer, Kwp2000ServerOptions, Kwp2000VoidHandler}, channel::IsoTPChannel,
|
||||
};
|
||||
use egui::*;
|
||||
use epi::Frame;
|
||||
use eframe::egui;
|
||||
use eframe::Frame;
|
||||
|
||||
use crate::{
|
||||
usb_hw::diag_usb::{Nag52USB, EspLogMessage},
|
||||
window::{InterfacePage, PageAction, StatusBar},
|
||||
usb_hw::diag_usb::{EspLogMessage},
|
||||
window::{InterfacePage, PageAction},
|
||||
};
|
||||
|
||||
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI, diagnostics::solenoids::SolenoidPage, };
|
||||
|
||||
use ecu_diagnostics::kwp2000::*;
|
||||
use super::{firmware_update::FwUpdateUI, status_bar::MainStatusBar, configuration::ConfigPage, crashanalyzer::CrashAnalyzerUI, diagnostics::{solenoids::SolenoidPage, shift_reporter::ShiftReportPage}, io_maipulator::IoManipulatorPage, routine_tests::RoutinePage, };
|
||||
use crate::ui::diagnostics::DiagnosticsPage;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
|
@ -37,7 +33,7 @@ impl MainPage {
|
|||
let channel_cfg = ecu_diagnostics::channel::IsoTPSettings {
|
||||
block_size: 0,
|
||||
st_min: 0,
|
||||
extended_addressing: false,
|
||||
extended_addresses: None,
|
||||
pad_frame: true,
|
||||
can_speed: 500_000,
|
||||
can_use_ext_addr: false,
|
||||
|
@ -45,8 +41,8 @@ impl MainPage {
|
|||
let server_settings = Kwp2000ServerOptions {
|
||||
send_id: 0x07E1,
|
||||
recv_id: 0x07E9,
|
||||
read_timeout_ms: 5000,
|
||||
write_timeout_ms: 5000,
|
||||
read_timeout_ms: if logger.is_some() { 1000 } else { 5000 }, // 250ms for USB, 5 seconds for CAN
|
||||
write_timeout_ms: if logger.is_some() { 1000 } else { 5000 }, // 250ms for USB, 5 seconds for CAN
|
||||
global_tp_id: 0,
|
||||
tester_present_interval_ms: 2000,
|
||||
tester_present_require_response: true,
|
||||
|
@ -72,7 +68,7 @@ impl InterfacePage for MainPage {
|
|||
fn make_ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
frame: &epi::Frame,
|
||||
frame: &Frame,
|
||||
) -> crate::window::PageAction {
|
||||
// UI context menu
|
||||
egui::menu::bar(ui, |bar_ui| {
|
||||
|
@ -111,6 +107,15 @@ impl InterfacePage for MainPage {
|
|||
if v.button("Solenoid live view").clicked() {
|
||||
create_page = Some(PageAction::Add(Box::new(SolenoidPage::new(self.diag_server.clone(), self.bar.clone()))));
|
||||
}
|
||||
if v.button("Shift report history viewer").clicked() {
|
||||
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()))));
|
||||
}
|
||||
if v.button("Diagnostic routine executor").clicked() {
|
||||
create_page = Some(PageAction::Add(Box::new(RoutinePage::new(self.diag_server.clone(), self.bar.clone()))));
|
||||
}
|
||||
if v.button("Map tuner").clicked() {}
|
||||
if v.button("Configure drive profiles").clicked() {}
|
||||
if v.button("Configure vehicle / gearbox").clicked() {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use egui::Color32;
|
||||
use eframe::egui::Color32;
|
||||
use eframe::egui;
|
||||
|
||||
use crate::window::InterfacePage;
|
||||
|
||||
|
@ -10,6 +11,9 @@ pub mod diagnostics;
|
|||
pub mod configuration;
|
||||
pub mod crashanalyzer;
|
||||
pub mod kwp_event;
|
||||
pub mod io_maipulator;
|
||||
pub mod routine_tests;
|
||||
pub mod widgets;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum StatusText {
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
use std::sync::{Mutex, Arc};
|
||||
|
||||
use ecu_diagnostics::kwp2000::Kwp2000DiagnosticServer;
|
||||
|
||||
use crate::window::PageAction;
|
||||
|
||||
use self::solenoid_test::SolenoidTestPage;
|
||||
|
||||
use super::status_bar::MainStatusBar;
|
||||
|
||||
pub mod solenoid_test;
|
||||
|
||||
|
||||
pub struct RoutinePage {
|
||||
bar: MainStatusBar,
|
||||
server: Arc<Mutex<Kwp2000DiagnosticServer>>,
|
||||
}
|
||||
|
||||
impl RoutinePage {
|
||||
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
|
||||
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 {
|
||||
ui.heading("Diagnostic routines");
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"Routine executor"
|
||||
}
|
||||
|
||||
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
|
||||
Some(Box::new(self.bar.clone()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
use std::{sync::{Arc, Mutex, atomic::{AtomicBool, Ordering, AtomicU64, AtomicU8}, RwLock}, thread, time::{Duration, Instant}, char::MAX, collections::btree_map::Range, ops::RangeInclusive};
|
||||
|
||||
use ecu_diagnostics::{kwp2000::{Kwp2000DiagnosticServer, SessionType}, DiagServerResult, DiagnosticServer, DiagError};
|
||||
use eframe::egui::{plot::{Plot, Legend, Line, Values, Value}, widgets, Color32, RichText, self};
|
||||
|
||||
use crate::{ui::status_bar::MainStatusBar, window::PageAction};
|
||||
|
||||
pub struct SolenoidTestPage {
|
||||
bar: MainStatusBar,
|
||||
test_state: Arc<AtomicU8>,
|
||||
test_result: Arc<RwLock<Option<TestResultsSolenoid>>>,
|
||||
test_status: Arc<RwLock<String>>,
|
||||
server: Arc<Mutex<Kwp2000DiagnosticServer>>
|
||||
}
|
||||
|
||||
|
||||
const TempCoefficient: f32 = 0.393; // Copper coils and wires
|
||||
|
||||
const ResistanceMeasureTemp: f32 = 25.0; // Mercedes tests resistance at 25C
|
||||
|
||||
// From Sonnax data
|
||||
const ResitanceMPC: std::ops::RangeInclusive<f32> = (4.0..=8.0); // 6
|
||||
const ResitanceSPC: std::ops::RangeInclusive<f32> = (4.0..=8.0); // 6
|
||||
const ResitanceTCC: std::ops::RangeInclusive<f32> = (2.0..=4.0); // 3
|
||||
|
||||
const ResitanceY3: std::ops::RangeInclusive<f32> = (2.5..=6.5); // 4.5
|
||||
const ResitanceY4: std::ops::RangeInclusive<f32> = (2.5..=6.5); // 4.5
|
||||
const ResitanceY5: std::ops::RangeInclusive<f32> = (2.5..=6.5); // 4.5
|
||||
|
||||
#[repr(packed)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TestResultsSolenoid {
|
||||
atf_temp: i16,
|
||||
mpc_off_current: u16,
|
||||
spc_off_current: u16,
|
||||
tcc_off_current: u16,
|
||||
y3_off_current: u16,
|
||||
y4_off_current: u16,
|
||||
y5_off_current: u16,
|
||||
|
||||
vbatt_mpc: u16,
|
||||
mpc_on_current: u16,
|
||||
|
||||
vbatt_spc: u16,
|
||||
spc_on_current: u16,
|
||||
|
||||
vbatt_tcc: u16,
|
||||
tcc_on_current: u16,
|
||||
|
||||
vbatt_y3: u16,
|
||||
y3_on_current: u16,
|
||||
|
||||
vbatt_y4: u16,
|
||||
y4_on_current: u16,
|
||||
|
||||
vbatt_y5: u16,
|
||||
y5_on_current: u16,
|
||||
}
|
||||
|
||||
impl SolenoidTestPage {
|
||||
pub fn new(server: Arc<Mutex<Kwp2000DiagnosticServer>>, bar: MainStatusBar) -> Self {
|
||||
Self {
|
||||
bar,
|
||||
server,
|
||||
test_state: Arc::new(AtomicU8::new(0)),
|
||||
test_result: Arc::new(RwLock::new(None)),
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
if (c_raw > 3200 && range != ResitanceTCC) {
|
||||
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());
|
||||
} else {
|
||||
c = Color32::RED;
|
||||
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 {
|
||||
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
|
||||
body.
|
||||
");
|
||||
|
||||
ui.separator();
|
||||
|
||||
|
||||
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
|
||||
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|| {
|
||||
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);
|
||||
state_ref.store(2, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
return;
|
||||
}
|
||||
if let Err(e) = guard.send_byte_array_with_response(&[0x31, 0xDE]) {
|
||||
let _ = guard.set_diagnostic_session_mode(SessionType::Normal);
|
||||
*str_ref.write().unwrap() = format!("ECU rejected the test: {}", e);
|
||||
state_ref.store(2, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
return;
|
||||
}
|
||||
|
||||
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: 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 failed {
|
||||
*str_ref.write().unwrap() = format!("Failed to get ECU test results: {}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
let _ = guard.set_diagnostic_session_mode(SessionType::Normal);
|
||||
state_ref.store(2, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
if state == 2 {
|
||||
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));
|
||||
|
||||
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.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.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.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.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.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.end_row();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
PageAction::None
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"IO Manipulator view"
|
||||
}
|
||||
|
||||
fn get_status_bar(&self) -> Option<Box<dyn crate::window::StatusBar>> {
|
||||
Some(Box::new(self.bar.clone()))
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use ecu_diagnostics::hardware::Hardware;
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
use eframe::egui::*;
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
sync::{Arc, Mutex, RwLock, mpsc}, borrow::BorrowMut,
|
||||
|
@ -10,6 +9,7 @@ use crate::{
|
|||
usb_hw::diag_usb::{EspLogMessage, Nag52USB},
|
||||
window::{InterfacePage, StatusBar},
|
||||
};
|
||||
use eframe::egui;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MainStatusBar {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod range_display;
|
|
@ -0,0 +1,51 @@
|
|||
use std::{ops::RangeInclusive, fmt::Display};
|
||||
|
||||
use eframe::{egui, epaint::{Rounding, Color32, FontId}, emath::{Rect, Pos2, Align2}};
|
||||
|
||||
|
||||
|
||||
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
|
||||
) -> 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())
|
||||
);
|
||||
|
||||
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())
|
||||
);
|
||||
|
||||
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())
|
||||
);
|
||||
|
||||
|
||||
// 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.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(value_region.right(), rect.bottom()+5.0), Align2::CENTER_BOTTOM, format!("{:.2}", curr_value), FontId::default(), visuals.text_color());
|
||||
|
||||
response
|
||||
}
|
|
@ -9,14 +9,13 @@ use std::{
|
|||
mpsc::{self},
|
||||
Arc,
|
||||
},
|
||||
time::{Instant, Duration},
|
||||
time::{Instant, Duration}, panic::catch_unwind,
|
||||
};
|
||||
use std::fmt::Write as SWrite;
|
||||
use ecu_diagnostics::{
|
||||
channel::{IsoTPChannel, PayloadChannel, ChannelError},
|
||||
hardware::{HardwareInfo, HardwareResult, HardwareError},
|
||||
};
|
||||
use egui::mutex::Mutex;
|
||||
use serial_rs::{SerialPort, PortInfo, SerialPortSettings, FlowControl};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -66,6 +65,8 @@ impl Nag52USB {
|
|||
|
||||
let is_running = Arc::new(AtomicBool::new(true));
|
||||
let is_running_r = is_running.clone();
|
||||
port.clear_input_buffer();
|
||||
port.clear_output_buffer();
|
||||
let mut port_clone = port.try_clone().unwrap();
|
||||
|
||||
// Create 2 threads, one to read the port, one to write to it
|
||||
|
@ -75,57 +76,36 @@ impl Nag52USB {
|
|||
let mut reader = BufReader::new(&mut port_clone);
|
||||
let mut line = String::new();
|
||||
loop {
|
||||
line.clear();
|
||||
if reader.read_line(&mut line).is_ok() {
|
||||
let _ = line.pop();
|
||||
if line.chars().next().unwrap() == '#' {
|
||||
line.pop();
|
||||
println!("LINE: {}", line);
|
||||
if line.is_empty() {continue;}
|
||||
if line.starts_with("#") || line.starts_with("07E9") {
|
||||
// First char is #, diag message
|
||||
// Diag message
|
||||
if line.len() % 2 == 0 {
|
||||
if line.starts_with("#") {
|
||||
line.remove(0);
|
||||
}
|
||||
if line.len() % 2 != 0 {
|
||||
eprintln!("Discarding invalid diag msg '{}'", line);
|
||||
} else {
|
||||
//println!("Read diag payload {:?} from Nag52 USB", line);
|
||||
let contents: &str = &line[1..];
|
||||
let can_id = u32::from_str_radix(&contents[0..4], 16).unwrap();
|
||||
let payload: Vec<u8> = (4..contents.len())
|
||||
.step_by(2)
|
||||
.map(|i| u8::from_str_radix(&contents[i..i + 2], 16).unwrap())
|
||||
.collect();
|
||||
read_tx_diag.send((can_id, payload));
|
||||
let can_id = u32::from_str_radix(&line[0..4], 16).unwrap();
|
||||
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())
|
||||
.collect();
|
||||
payload
|
||||
}) {
|
||||
read_tx_diag.send((can_id, p));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a log message
|
||||
if line.len() < 5 {
|
||||
continue;
|
||||
}
|
||||
if !line.contains("(") || !line.contains(")") {
|
||||
println!("EEE {}", line);
|
||||
continue;
|
||||
}
|
||||
let lvl = match line.chars().next().unwrap() {
|
||||
'I' => EspLogLevel::Info,
|
||||
'W' => EspLogLevel::Warn,
|
||||
'E' => EspLogLevel::Error,
|
||||
'D' => EspLogLevel::Debug,
|
||||
_ => continue,
|
||||
};
|
||||
let timestamp = u128::from_str_radix(
|
||||
line.split_once(")").unwrap().0.split_once("(").unwrap().1,
|
||||
10,
|
||||
)
|
||||
.unwrap();
|
||||
let split = line.split_once(": ").unwrap();
|
||||
let msg = EspLogMessage {
|
||||
lvl,
|
||||
timestamp,
|
||||
tag: split.0.split_once(")").unwrap().1.to_string(),
|
||||
msg: split.1.to_string(),
|
||||
};
|
||||
println!("{:?}", msg);
|
||||
read_tx_log.send(msg);
|
||||
//read_tx_log.send(msg);
|
||||
}
|
||||
line.clear();
|
||||
}
|
||||
//std::thread::sleep(from_millis(10));
|
||||
}
|
||||
}
|
||||
println!("Serial reader stop");
|
||||
|
@ -233,7 +213,7 @@ impl PayloadChannel for Nag52USB {
|
|||
fn read_bytes(&mut self, timeout_ms: u32) -> ecu_diagnostics::channel::ChannelResult<Vec<u8>> {
|
||||
let now = Instant::now();
|
||||
while now.elapsed().as_millis() < timeout_ms as u128 {
|
||||
if let Ok((id, data)) = self.rx_diag.recv() {
|
||||
if let Ok((id, data)) = self.rx_diag.try_recv() {
|
||||
if id == self.rx_id {
|
||||
return Ok(data);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
use std::{borrow::BorrowMut, collections::VecDeque};
|
||||
|
||||
use ecu_diagnostics::hardware::Hardware;
|
||||
use egui::*;
|
||||
use epi::*;
|
||||
|
||||
use crate::ui::status_bar::{self};
|
||||
use eframe::egui;
|
||||
|
||||
pub struct MainWindow {
|
||||
pages: VecDeque<Box<dyn InterfacePage>>,
|
||||
|
@ -37,14 +34,13 @@ impl MainWindow {
|
|||
}
|
||||
}
|
||||
|
||||
impl epi::App for MainWindow {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
|
||||
impl eframe::App for MainWindow {
|
||||
fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
|
||||
let stack_size = self.pages.len();
|
||||
if stack_size > 0 {
|
||||
let mut pop_page = false;
|
||||
if let Some(status_bar) = self.bar.borrow_mut() {
|
||||
egui::TopBottomPanel::bottom("NAV")
|
||||
.default_height(800.0)
|
||||
.show(ctx, |nav| {
|
||||
nav.horizontal(|row| {
|
||||
status_bar.draw(row, ctx);
|
||||
|
@ -78,16 +74,7 @@ impl epi::App for MainWindow {
|
|||
}
|
||||
//ctx.request_repaint(); // Continuous mode
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
if self.pages.len() > 0 {
|
||||
self.pages[0].get_title()
|
||||
} else {
|
||||
"Ultimate-Nag52 configuration utility"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PageAction {
|
||||
None,
|
||||
Destroy,
|
||||
|
@ -98,7 +85,7 @@ pub enum PageAction {
|
|||
}
|
||||
|
||||
pub trait InterfacePage {
|
||||
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &epi::Frame) -> PageAction;
|
||||
fn make_ui(&mut self, ui: &mut egui::Ui, frame: &eframe::Frame) -> PageAction;
|
||||
fn get_title(&self) -> &'static str;
|
||||
fn get_status_bar(&self) -> Option<Box<dyn StatusBar>>;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue