Skip to content

Commit cf7e08e

Browse files
Updates version 1.5.0 fixes issue #11
Signed-off-by: Cole Gentry <peapod2007@gmail.com>
1 parent c88e536 commit cf7e08e

File tree

6 files changed

+112
-55
lines changed

6 files changed

+112
-55
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ultralog"
3-
version = "1.4.3"
3+
version = "1.5.0"
44
edition = "2021"
55
description = "A high-performance ECU log viewer written in Rust"
66
authors = ["Cole Gentry"]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A high-performance, cross-platform ECU log viewer written in Rust.
66

77
![CI](https://github.com/SomethingNew71/UltraLog/actions/workflows/ci.yml/badge.svg)
88
![License](https://img.shields.io/badge/license-AGPL--3.0-blue.svg)
9-
![Version](https://img.shields.io/badge/version-1.4.3-green.svg)
9+
![Version](https://img.shields.io/badge/version-1.5.0-green.svg)
1010

1111
---
1212

docs/index.html

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
<title>UltraLog - Unlock Your Performance Data</title>
88
<meta name="description"
99
content="A high-performance, cross-platform ECU log viewer. Analyze your Haltech, ECUMaster, RomRaider, Speeduino, and more.">
10-
<meta name="keywords" content="ECU, datalog, log viewer, Haltech, ECUMaster, RomRaider, Subaru, Speeduino, rusEFI, automotive, tuning">
10+
<meta name="keywords"
11+
content="ECU, datalog, log viewer, Haltech, ECUMaster, RomRaider, Subaru, Speeduino, rusEFI, automotive, tuning">
1112

1213
<!-- Open Graph -->
1314
<meta property="og:title" content="UltraLog - ECU Log Viewer">
@@ -710,7 +711,7 @@
710711
<strong>Free and open source</strong> — no subscriptions, no licenses, just download and go.
711712
</p>
712713
<div class="hero-badges">
713-
<span class="version-badge">v1.4.3</span>
714+
<span class="version-badge">v1.5.0</span>
714715
<a href="https://github.com/SomethingNew71/UltraLog" class="opensource-badge" target="_blank">
715716
<i class="fa-brands fa-github"></i> Open Source
716717
</a>
@@ -907,7 +908,7 @@ <h2>Supported ECUs</h2>
907908
.catch(() => { });
908909

909910
// Screenshot Carousel
910-
(function() {
911+
(function () {
911912
const track = document.querySelector('.carousel-track');
912913
const slides = document.querySelectorAll('.carousel-slide');
913914
const dots = document.querySelectorAll('.carousel-dot');

src/parsers/aim.rs

Lines changed: 90 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,29 @@ impl Aim {
4848
}
4949

5050
/// Parse AIM XRK/DRK file from a file path
51-
#[cfg(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64"))]
51+
#[cfg(all(
52+
any(target_os = "windows", target_os = "linux"),
53+
target_arch = "x86_64"
54+
))]
5255
pub fn parse_file(path: &Path) -> Result<Log, Box<dyn Error>> {
5356
Self::parse_file_xdrk(path)
5457
}
5558

5659
/// Parse AIM XRK/DRK file from a file path (pure Rust implementation for unsupported platforms)
57-
#[cfg(not(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64")))]
60+
#[cfg(not(all(
61+
any(target_os = "windows", target_os = "linux"),
62+
target_arch = "x86_64"
63+
)))]
5864
pub fn parse_file(path: &Path) -> Result<Log, Box<dyn Error>> {
5965
let data = std::fs::read(path)?;
6066
Self::parse_binary(&data)
6167
}
6268

6369
/// Parse using xdrk library (Windows/Linux x86_64 only)
64-
#[cfg(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64"))]
70+
#[cfg(all(
71+
any(target_os = "windows", target_os = "linux"),
72+
target_arch = "x86_64"
73+
))]
6574
fn parse_file_xdrk(path: &Path) -> Result<Log, Box<dyn Error>> {
6675
// Load the XRK file using xdrk
6776
let run = xdrk::Run::load(path)?;
@@ -89,7 +98,9 @@ impl Aim {
8998
let mut channels = Vec::with_capacity(channel_count);
9099

91100
for i in 0..channel_count {
92-
let name = run.channel_name(i).unwrap_or_else(|_| format!("Channel_{}", i));
101+
let name = run
102+
.channel_name(i)
103+
.unwrap_or_else(|_| format!("Channel_{}", i));
93104
let unit = run.channel_unit(i).unwrap_or_default();
94105
channels.push(AimChannel { name, unit });
95106
}
@@ -116,7 +127,10 @@ impl Aim {
116127
tracing::warn!("No samples found in AIM log file");
117128
return Ok(Log {
118129
meta: Meta::Aim(meta),
119-
channels: channels.into_iter().map(super::types::Channel::Aim).collect(),
130+
channels: channels
131+
.into_iter()
132+
.map(super::types::Channel::Aim)
133+
.collect(),
120134
times,
121135
data,
122136
});
@@ -161,21 +175,30 @@ impl Aim {
161175

162176
Ok(Log {
163177
meta: Meta::Aim(meta),
164-
channels: channels.into_iter().map(super::types::Channel::Aim).collect(),
178+
channels: channels
179+
.into_iter()
180+
.map(super::types::Channel::Aim)
181+
.collect(),
165182
times,
166183
data,
167184
})
168185
}
169186

170187
/// Parse XRK binary data using pure Rust implementation
171188
/// This is used on platforms where xdrk is not available (macOS, ARM, etc.)
172-
#[cfg(not(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64")))]
189+
#[cfg(not(all(
190+
any(target_os = "windows", target_os = "linux"),
191+
target_arch = "x86_64"
192+
)))]
173193
fn parse_binary(data: &[u8]) -> Result<Log, Box<dyn Error>> {
174194
if !Self::detect(data) {
175195
return Err("Not a valid AIM XRK file".into());
176196
}
177197

178-
tracing::info!("Parsing AIM XRK file using pure Rust implementation ({} bytes)", data.len());
198+
tracing::info!(
199+
"Parsing AIM XRK file using pure Rust implementation ({} bytes)",
200+
data.len()
201+
);
179202

180203
// Parse channels from the XRK binary format
181204
let channels = Self::parse_channels(data)?;
@@ -191,14 +214,20 @@ impl Aim {
191214

192215
Ok(Log {
193216
meta: Meta::Aim(meta),
194-
channels: channels.into_iter().map(super::types::Channel::Aim).collect(),
217+
channels: channels
218+
.into_iter()
219+
.map(super::types::Channel::Aim)
220+
.collect(),
195221
times,
196222
data: channel_data,
197223
})
198224
}
199225

200226
/// Parse channel definitions from XRK data
201-
#[cfg(not(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64")))]
227+
#[cfg(not(all(
228+
any(target_os = "windows", target_os = "linux"),
229+
target_arch = "x86_64"
230+
)))]
202231
fn parse_channels(data: &[u8]) -> Result<Vec<AimChannel>, Box<dyn Error>> {
203232
let mut channels = Vec::new();
204233

@@ -279,7 +308,10 @@ impl Aim {
279308
}
280309

281310
/// Parse metadata from the XRK file footer
282-
#[cfg(not(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64")))]
311+
#[cfg(not(all(
312+
any(target_os = "windows", target_os = "linux"),
313+
target_arch = "x86_64"
314+
)))]
283315
fn parse_metadata(data: &[u8]) -> Result<AimMeta, Box<dyn Error>> {
284316
let mut meta = AimMeta::default();
285317

@@ -289,7 +321,8 @@ impl Aim {
289321
if start + 50 < data.len() {
290322
// Skip length bytes and read vehicle name
291323
if let Some(end) = Self::find_pattern(&data[start..], b"<", 0) {
292-
meta.vehicle = Self::read_null_terminated_string(&data[start + 4..], end.min(50));
324+
meta.vehicle =
325+
Self::read_null_terminated_string(&data[start + 4..], end.min(50));
293326
}
294327
}
295328
}
@@ -299,7 +332,8 @@ impl Aim {
299332
let start = pos + 5;
300333
if start + 100 < data.len() {
301334
if let Some(end) = Self::find_pattern(&data[start..], b"<", 0) {
302-
meta.championship = Self::read_null_terminated_string(&data[start + 4..], end.min(100));
335+
meta.championship =
336+
Self::read_null_terminated_string(&data[start + 4..], end.min(100));
303337
}
304338
}
305339
}
@@ -309,7 +343,8 @@ impl Aim {
309343
let start = pos + 5;
310344
if start + 50 < data.len() {
311345
if let Some(end) = Self::find_pattern(&data[start..], b"<", 0) {
312-
meta.venue_type = Self::read_null_terminated_string(&data[start + 4..], end.min(50));
346+
meta.venue_type =
347+
Self::read_null_terminated_string(&data[start + 4..], end.min(50));
313348
}
314349
}
315350
}
@@ -318,8 +353,14 @@ impl Aim {
318353
}
319354

320355
/// Parse channel data samples from )(G records
321-
#[cfg(not(all(any(target_os = "windows", target_os = "linux"), target_arch = "x86_64")))]
322-
fn parse_channel_data(data: &[u8], channel_count: usize) -> Result<(Vec<f64>, Vec<Vec<Value>>), Box<dyn Error>> {
356+
#[cfg(not(all(
357+
any(target_os = "windows", target_os = "linux"),
358+
target_arch = "x86_64"
359+
)))]
360+
fn parse_channel_data(
361+
data: &[u8],
362+
channel_count: usize,
363+
) -> Result<(Vec<f64>, Vec<Vec<Value>>), Box<dyn Error>> {
323364
let mut times = Vec::new();
324365
let mut all_data: Vec<Vec<Value>> = Vec::new();
325366

@@ -340,8 +381,7 @@ impl Aim {
340381
while offset + 20 < data.len() {
341382
if let Some(pos) = Self::find_pattern(data, marker, offset) {
342383
// Find the next marker to determine record size
343-
let next_pos = Self::find_pattern(data, b")(", pos + 3)
344-
.unwrap_or(data.len());
384+
let next_pos = Self::find_pattern(data, b")(", pos + 3).unwrap_or(data.len());
345385
let record_size = next_pos - pos;
346386

347387
// Process records in the typical telemetry size range (100-200 bytes)
@@ -441,14 +481,9 @@ impl Aim {
441481
/// Read a null-terminated string from a byte slice, up to max_len bytes
442482
fn read_null_terminated_string(data: &[u8], max_len: usize) -> String {
443483
let max = max_len.min(data.len());
444-
let end = data[..max]
445-
.iter()
446-
.position(|&b| b == 0)
447-
.unwrap_or(max);
448-
449-
String::from_utf8_lossy(&data[..end])
450-
.trim()
451-
.to_string()
484+
let end = data[..max].iter().position(|&b| b == 0).unwrap_or(max);
485+
486+
String::from_utf8_lossy(&data[..end]).trim().to_string()
452487
}
453488
}
454489

@@ -543,7 +578,11 @@ mod tests {
543578

544579
// Verify detection
545580
let data = std::fs::read(&path).expect("Failed to read file");
546-
assert!(Aim::detect(&data), "Should detect {} as XRK format", path.display());
581+
assert!(
582+
Aim::detect(&data),
583+
"Should detect {} as XRK format",
584+
path.display()
585+
);
547586

548587
// Parse the file
549588
match Aim::parse_file(&path) {
@@ -552,9 +591,21 @@ mod tests {
552591
eprintln!(" Data records: {}", log.data.len());
553592

554593
// Verify we got actual data
555-
assert!(!log.channels.is_empty(), "Should have channels for {}", path.display());
556-
assert!(!log.data.is_empty(), "Should have data records for {}", path.display());
557-
assert!(!log.times.is_empty(), "Should have timestamps for {}", path.display());
594+
assert!(
595+
!log.channels.is_empty(),
596+
"Should have channels for {}",
597+
path.display()
598+
);
599+
assert!(
600+
!log.data.is_empty(),
601+
"Should have data records for {}",
602+
path.display()
603+
);
604+
assert!(
605+
!log.times.is_empty(),
606+
"Should have timestamps for {}",
607+
path.display()
608+
);
558609

559610
if !log.times.is_empty() {
560611
eprintln!(
@@ -565,10 +616,15 @@ mod tests {
565616
}
566617

567618
// Verify data has actual values
568-
let has_non_zero = log.data.iter().any(|row| {
569-
row.iter().any(|v| v.as_f64().abs() > 0.0001)
570-
});
571-
assert!(has_non_zero, "Should have non-zero values for {}", path.display());
619+
let has_non_zero = log
620+
.data
621+
.iter()
622+
.any(|row| row.iter().any(|v| v.as_f64().abs() > 0.0001));
623+
assert!(
624+
has_non_zero,
625+
"Should have non-zero values for {}",
626+
path.display()
627+
);
572628

573629
parsed_count += 1;
574630
}

wiki/Home.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ UltraLog is a high-performance, cross-platform desktop application for viewing a
44

55
## Quick Links
66

7-
| Getting Started | Reference | Support |
8-
|-----------------|-----------|---------|
9-
| [[Installation]] | [[Supported-ECU-Formats]] | [[Troubleshooting]] |
10-
| [[Getting-Started]] | [[Unit-Conversion]] | [[FAQ]] |
11-
| [[User-Guide]] | [[Field-Normalization]] | [[Development]] |
7+
| Getting Started | Reference | Support |
8+
| ------------------- | ------------------------- | ------------------- |
9+
| [[Installation]] | [[Supported-ECU-Formats]] | [[Troubleshooting]] |
10+
| [[Getting-Started]] | [[Unit-Conversion]] | [[FAQ]] |
11+
| [[User-Guide]] | [[Field-Normalization]] | [[Development]] |
1212

1313
## Key Features
1414

@@ -21,15 +21,15 @@ UltraLog is a high-performance, cross-platform desktop application for viewing a
2121

2222
## Supported ECU Systems
2323

24-
| ECU System | Status | File Format |
25-
|------------|--------|-------------|
26-
| Haltech | Full Support | CSV from NSP |
27-
| ECUMaster EMU Pro | Full Support | CSV export |
28-
| RomRaider | Full Support | CSV from Logger |
29-
| Speeduino / rusEFI | Full Support | MLG binary |
30-
| MegaSquirt | Coming Soon | - |
31-
| AEM | Coming Soon | - |
32-
| MoTeC | Coming Soon | - |
24+
| ECU System | Status | File Format |
25+
| ------------------ | ------------ | --------------- |
26+
| Haltech | Full Support | CSV from NSP |
27+
| ECUMaster EMU Pro | Full Support | CSV export |
28+
| RomRaider | Full Support | CSV from Logger |
29+
| Speeduino / rusEFI | Full Support | MLG binary |
30+
| MegaSquirt | Coming Soon | - |
31+
| AEM | Coming Soon | - |
32+
| MoTeC | Coming Soon | - |
3333

3434
## System Requirements
3535

@@ -53,7 +53,7 @@ UltraLog is a high-performance, cross-platform desktop application for viewing a
5353

5454
## Version Information
5555

56-
- **Current Version:** 1.4.3
56+
- **Current Version:** 1.5.0
5757
- **License:** AGPL-3.0
5858
- **Author:** Cole Gentry
5959

0 commit comments

Comments
 (0)