Skip to content
Open
Show file tree
Hide file tree
Changes from all 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) {
// 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) => {
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 {
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);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read this PR a little quickly but I think you may still have the same problem of the log folder never being updated. When the user selects a new folder, that info never propagates to the CAN thread/daq logger


// 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