Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
/target
colors.toml
settings.json

/logs
3 changes: 3 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub struct DAQApp {
pub can_bus_speed: connection::CanBusSpeed,
pub udp_port: u16,
pub can_messages: Vec<messages::MsgFromCan>,
pub log_folder: Option<std::path::PathBuf>,
}

impl DAQApp {
Expand All @@ -63,6 +64,7 @@ impl DAQApp {
udp_port: self.udp_port,
theme: self.theme_selection,
pixels_per_point: self.pixels_per_point,
log_folder: self.log_folder.clone(),
};
settings.save();
}
Expand Down Expand Up @@ -97,6 +99,7 @@ impl DAQApp {
can_bus_speed: settings.selected_speed,
udp_port: settings.udp_port,
can_messages: Vec::new(),
log_folder: settings.log_folder,
}
}

Expand Down
170 changes: 170 additions & 0 deletions src/can/daq_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use crate::daq_log_parse::consts::{BUS_ID_MASK, IS_EID_MASK};

use crate::daq_log_parse::parse::RawFrame;

use chrono::{Datelike, Timelike};
use std::fs::{File, create_dir_all};
use std::io::Write;
use std::path::PathBuf;
use std::time::Instant;

pub const LOG_FILE_ROTATE_MS: u128 = 60000;
pub const DEFAULT_FLUSH_MS: u128 = 1000;

pub fn byte_to_bcd_format(val: u8) -> u8 {
((val / 10) << 4) | (val % 10)
}

pub struct DaqLogger {
file: Option<File>,
folder_path: PathBuf,
buffer: Vec<RawFrame>,
file_created_at: Instant,
start_time: Instant,
last_flush: Instant,
buffer_capacity: usize,
}

impl DaqLogger {
pub fn new(folder_path: std::path::PathBuf) -> Self {
if let Err(e) = create_dir_all(&folder_path) {
log::error!(
"Failed to create directory for logs: {:?}: {}",
folder_path,
e
);
}

Self {
file: None,
folder_path: folder_path,
buffer: Vec::with_capacity(10000),
file_created_at: Instant::now(),
start_time: Instant::now(),
last_flush: Instant::now(),
buffer_capacity: 5000,
}
}

pub fn reset_start_time(&mut self) {
self.start_time = Instant::now();
}

pub fn update_folder(&mut self, new_folder: std::path::PathBuf) {
self.flush();
self.file = None;
self.folder_path = new_folder;
if let Err(e) = create_dir_all(&self.folder_path) {
log::error!(
"Failed to create directory for logs: {:?}: {}",
self.folder_path,
e
);
}
}

pub fn log_can2_frame(&mut self, frame: &slcan::Can2Frame, is_bus_1: bool) {
let (id, data) = match frame.id() {
slcan::Id::Standard(sid) => {
let id = sid.as_raw() as u32;
(id, frame.data().unwrap_or(&[]))
}
slcan::Id::Extended(eid) => {
let id = eid.as_raw() | IS_EID_MASK;
(id, frame.data().unwrap_or(&[]))
}
};

let frame_identity = if is_bus_1 { id | BUS_ID_MASK } else { id };

let mut data_array = [0u8; 8];
let len = data.len().min(8);
data_array[..len].copy_from_slice(&data[..len]);

let ticks_ms = self.start_time.elapsed().as_millis() as u32;

let raw_frame = RawFrame {
ticks_ms: ticks_ms,
identity: frame_identity,
data: data_array,
};

self.add_frame(raw_frame);
}

pub fn log_canfd_frame(&mut self, _frame: &slcan::CanFdFrame, _is_bus_1: bool) {
Comment thread
DanielProano marked this conversation as resolved.
Outdated
// RawFrame is fixed at 8 bytes (CAN 2.0 format); FD frames are not logged
}

fn add_frame(&mut self, frame: RawFrame) {
self.buffer.push(frame);

//Flush every 1 second
if self.buffer.len() >= self.buffer_capacity
|| self.last_flush.elapsed().as_millis() >= DEFAULT_FLUSH_MS
{
self.flush();
}
}

pub fn flush(&mut self) {
if self.buffer.is_empty() {
return;
}

// Create new file if time of creation has exceed threshold
if self.file.is_some() && self.file_created_at.elapsed().as_millis() >= LOG_FILE_ROTATE_MS {
self.file = None;
}

if self.file.is_none() {
let now = chrono::Local::now();
self.file_created_at = Instant::now();

let year_bcd = byte_to_bcd_format((now.year() % 100) as u8);
let month_bcd = byte_to_bcd_format(now.month() as u8);
let day_bcd = byte_to_bcd_format(now.day() as u8);
let hour_bcd = byte_to_bcd_format(now.hour() as u8);
let min_bcd = byte_to_bcd_format(now.minute() as u8);
let sec_bcd = byte_to_bcd_format(now.second() as u8);

let filename = format!(
"log-20{:02x}-{:02x}-{:02x}--{:02x}-{:02x}-{:02x}.log",
year_bcd, month_bcd, day_bcd, hour_bcd, min_bcd, sec_bcd
);

let file_path = self.folder_path.join(filename);
match File::create(&file_path) {
Ok(f) => self.file = Some(f),
Err(e) => {
Comment thread
DanielProano marked this conversation as resolved.
log::error!("Failed to create log file {:?}: {}", file_path, e);
self.buffer.clear();
self.last_flush = Instant::now();
return;
}
}
}

if let Some(ref mut file) = self.file {
Comment thread
DanielProano marked this conversation as resolved.
if let Err(e) = file.write_all(bytemuck::cast_slice(&self.buffer)) {
log::error!("Failed to write to log file: {}", e);
}

if let Err(e) = file.flush() {
log::error!("Failed to flush log file: {}", e);
}
}

self.buffer.clear();
self.last_flush = Instant::now();
}
}

impl Drop for DaqLogger {
fn drop(&mut self) {
self.flush();
if let Some(ref mut file) = self.file.take() {
let _ = file.sync_all();
}
}
}
1 change: 1 addition & 0 deletions src/can/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod bus_load;
pub mod daq_parser;
pub mod driver;
pub mod state;
pub mod thread;
18 changes: 16 additions & 2 deletions src/can/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const READ_RETRY_SLEEP_MS: u64 = 2;
const BUS_LOAD_UPDATE_MS: u128 = 200;

// Returns the number of payload data bytes in the CAN frame if it was a Can2 frame
fn process_can_frame(frame: slcan::CanFrame, state: &can::state::State) -> usize {
fn process_can_frame(frame: &slcan::CanFrame, state: &can::state::State) -> usize {
match frame {
slcan::CanFrame::Can2(frame2) => {
let decode_msg_id = util::can::slcan_to_u32_with_extid_flag(&frame2.id());
Expand Down Expand Up @@ -80,9 +80,11 @@ pub fn start_can_thread(
can_to_ui_tx: std::sync::mpsc::Sender<messages::MsgFromCan>,
ui_to_can_rx: std::sync::mpsc::Receiver<messages::MsgFromUi>,
selected_source: Option<connection::ConnectionSource>,
log_folder: std::path::PathBuf,
) -> std::thread::JoinHandle<()> {
std::thread::spawn(move || {
let mut state = can::state::State::new(can_to_ui_tx, ui_to_can_rx, selected_source);
let mut daq_logger = can::daq_parser::DaqLogger::new(log_folder);
Comment thread
LelsersLasers marked this conversation as resolved.

// MAIN LOOP
loop {
Expand Down Expand Up @@ -116,6 +118,9 @@ pub fn start_can_thread(
messages::MsgFromUi::DeleteSendMessage { msg_id } => {
state.delete_send_message(msg_id);
}
messages::MsgFromUi::UpdateLogFolder(path) => {
daq_logger.update_folder(path);
}
}
}
let msgs_to_send = state.send_this_tick();
Expand Down Expand Up @@ -198,6 +203,7 @@ pub fn start_can_thread(
.can_to_ui_tx
.send(messages::MsgFromCan::ConnectionSuccessful)
.expect("Failed to send connection successful message");
daq_logger.reset_start_time();
log::info!("Connected to {:?}", source);
}
Err(e) => {
Expand Down Expand Up @@ -229,9 +235,16 @@ pub fn start_can_thread(
match active_driver.read_frames() {
Ok(frames) => {
for frame in frames {
let data_bytes = process_can_frame(frame, &state);
let data_bytes = process_can_frame(&frame, &state);
state.bus_load_tracker.record_frame(data_bytes);

// Log each frame (buffered, not flushed yet)
match &frame {
slcan::CanFrame::Can2(f2) => daq_logger.log_can2_frame(f2, false),
slcan::CanFrame::CanFd(ffd) => daq_logger.log_canfd_frame(ffd, false),
}
}

// Send bus load updates periodically
if state.last_bus_load_update.elapsed().as_millis() >= BUS_LOAD_UPDATE_MS {
state.bus_load_tracker.cleanup();
Expand Down Expand Up @@ -288,6 +301,7 @@ pub fn start_can_thread(
}
}
}

unreachable!("CAN thread should never exit on its own");
})
}
8 changes: 4 additions & 4 deletions src/daq_log_parse/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub struct ParsedMessage {
#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone)]
// based on definition of timestamped_frame_t in timestamped_frame.h in firmware repo
struct RawFrame {
ticks_ms: u32,
identity: u32,
data: [u8; 8],
pub struct RawFrame {
pub ticks_ms: u32,
pub identity: u32,
pub data: [u8; 8],
}

pub fn parse_log_files(
Expand Down
12 changes: 10 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,16 @@ fn main() -> eframe::Result<()> {
.expect("Failed to send connect message to CAN thread");
}

let _can_thread =
can::thread::start_can_thread(can_to_ui_tx, ui_to_can_rx, settings.selected_source.clone());
let log_folder = settings
.log_folder
.clone()
.unwrap_or_else(|| std::path::PathBuf::from(settings::DEFAULT_LOG_FOLDER));
let _can_thread = can::thread::start_can_thread(
can_to_ui_tx,
ui_to_can_rx,
settings.selected_source.clone(),
log_folder,
);

let per_img = eframe::icon_data::from_png_bytes(assets::PER_LOGO_BYTES)
.expect("Failed to load logo image");
Expand Down
1 change: 1 addition & 0 deletions src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub enum MsgFromUi {
Connect(connection::ConnectionSource),
AddSendMessage(AddSendMessage),
DeleteSendMessage { msg_id: u32 },
UpdateLogFolder(std::path::PathBuf),
}

pub enum MsgFromCan {
Expand Down
4 changes: 4 additions & 0 deletions src/settings.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{connection, theme};

pub const SETTINGS_PATH: &str = "settings.json";
pub const DEFAULT_LOG_FOLDER: &str = "logs";
const DEFAULT_UDP_PORT: u16 = 5005;
const DEFAULT_CAN_SPEED: connection::CanBusSpeed = connection::CanBusSpeed::Kbps500;

Expand All @@ -12,6 +13,8 @@ pub struct Settings {
pub udp_port: u16,
pub theme: theme::ThemeSelection,
pub pixels_per_point: Option<f32>,
#[serde(default)]
pub log_folder: Option<std::path::PathBuf>,
}

impl Default for Settings {
Expand All @@ -23,6 +26,7 @@ impl Default for Settings {
udp_port: DEFAULT_UDP_PORT,
theme: theme::ThemeSelection::Default,
pixels_per_point: None,
log_folder: None,
}
}
}
Expand Down
23 changes: 22 additions & 1 deletion src/ui/sidebar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{action, app, assets, connection, formatter, messages, util, widget_constructor};
use crate::{
action, app, assets, connection, formatter, messages, settings, util, widget_constructor,
};
use eframe::egui;

pub fn select_dbc(
Expand Down Expand Up @@ -273,6 +275,25 @@ pub fn show(app: &mut app::DAQApp, ctx: &egui::Context) {
}
});

ui.horizontal(|ui| {
if ui.button("Select Log Folder").clicked()
&& let Some(path) = rfd::FileDialog::new().pick_folder()
{
app.ui_to_can_tx
.send(messages::MsgFromUi::UpdateLogFolder(path.clone()))
.expect("Failed to send log folder update");
app.log_folder = Some(path);
app.save_settings();
}

let log_display = app
.log_folder
.clone()
.unwrap_or_else(|| std::path::PathBuf::from(settings::DEFAULT_LOG_FOLDER));

ui.label(log_display.display().to_string());
});

ui.separator();

if ui.button("Reload formatter").clicked() {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl ThemeSelection {
ThemeSelection::OneDark => Some(ONEDARK_THEME_PATH),
ThemeSelection::Default => None,
};
path.and_then(|p| ThemeColors::load_from_file(p))
path.and_then(ThemeColors::load_from_file)
.unwrap_or_default()
}

Expand Down
Loading