feat(parser): add Motorsport Electronics ME221/ME442 support#66
feat(parser): add Motorsport Electronics ME221/ME442 support#66SomethingNew71 merged 4 commits intomainfrom
Conversation
ME Tuner CSV exports start with "Time," which the RomRaider parser was also claiming via loose detection. RomRaider then treated Time as milliseconds and divided by 1000, so a 111-second ME221 log rendered as 0.111 seconds. Add a dedicated parser with strict detection (exact "Time" first column plus ME221 fingerprint columns like Sync status, Injector duty, WBO2 Curr. AFR, DBW Status) and dispatch it before RomRaider. Time is treated as seconds, the first timestamp is normalized to zero, units are inferred from channel names since ME Tuner does not embed them, and the trailing Dummy column is stripped. Reported by an MX-5 user running a turbo ME221.
There was a problem hiding this comment.
Code Review
This pull request adds support for Motorsport Electronics (ME221/ME442) ECU log files. The implementation includes a new parser that handles CSV exports from ME Tuner, featuring automatic unit inference for common channels and timestamp normalization. The parser is integrated into the application's detection logic with specific ordering to avoid conflicts with the RomRaider format. Comprehensive tests and example logs are also included. Feedback is provided regarding the efficiency of the parsing loop, suggesting the use of iterators to avoid unnecessary per-line vector allocations.
| let parts: Vec<&str> = line.split(',').collect(); | ||
| if parts.is_empty() { | ||
| continue; | ||
| } | ||
|
|
||
| let time_str = parts[0].trim(); | ||
| let Ok(time_val) = time_str.parse::<f64>() else { | ||
| continue; | ||
| }; | ||
|
|
||
| let relative_time = match first_time { | ||
| Some(first) => time_val - first, | ||
| None => { | ||
| first_time = Some(time_val); | ||
| 0.0 | ||
| } | ||
| }; | ||
| times.push(relative_time); | ||
|
|
||
| let mut row: Vec<Value> = Vec::with_capacity(channels.len()); | ||
| for part in parts.iter().skip(1).take(effective_count.saturating_sub(1)) { | ||
| let value = part.trim().parse::<f64>().unwrap_or(0.0); | ||
| row.push(Value::Float(value)); | ||
| } |
There was a problem hiding this comment.
Allocating a Vec for every line in the log file is inefficient, especially for large logs. You can use the iterator directly to avoid these allocations.
let mut parts = line.split(',');
let Some(time_str) = parts.next() else {
continue;
};
let Ok(time_val) = time_str.trim().parse::<f64>() else {
continue;
};
let relative_time = match first_time {
Some(first) => time_val - first,
None => {
first_time = Some(time_val);
0.0
}
};
times.push(relative_time);
let mut row: Vec<Value> = Vec::with_capacity(channels.len());
for part in parts.take(effective_count.saturating_sub(1)) {
let value = part.trim().parse::<f64>().unwrap_or(0.0);
row.push(Value::Float(value));
}Avoid allocating a Vec<&str> per line by using the split iterator directly. Addresses PR review feedback from gemini-code-assist.
Summary
Background
A user running a turbo ME221 in their MX-5 reported that UltraLog was importing ME221 CSVs with the wrong time scale (otherwise, all the data was there). ME Tuner exports are popular on turbo/supercharged MX-5, Ford Focus ST150, Honda S2000, Toyota Starlet, and Mitsubishi Evo builds.
Root cause
Both ME Tuner and RomRaider CSVs start with a
Time,header. RomRaider'sdetect()was permissive enough to claim ME221 files, and its parser divides the Time column by 1000 on the assumption it is milliseconds. ME Tuner's Time column is in seconds, so a 111-second log rendered as 0.111 seconds.Fix
MotorsportElectronicsparser with strict detection: first column must be exactlyTimeand at least 2 ME221 fingerprint columns must be present (Sync status,Lost sync count,Injector duty,MAP Raw,TPS Raw,Ign. Adv. Base Table Output,WBO2 Curr. AFR,DBW Status)app.rsso it wins the ambiguous detectionDummycolumn strippedTest plan
cargo test— all tests passcargo fmt --all -- --checkcargo clippy -- -D warningsME221_2025_12_22_13_13_40.csv,ME221_2026_04_12_11_59_52.csv) and assert time range is in seconds