Carregar ficheiros para "src/core"
This commit is contained in:
131
src/core/file_comparison.py
Normal file
131
src/core/file_comparison.py
Normal file
@ -0,0 +1,131 @@
|
||||
from typing import List, Tuple, Dict
|
||||
import numpy as np
|
||||
import os
|
||||
from .file_handler import ECUFile, MapData
|
||||
from .edc15_maps import EDC15MapDetector
|
||||
|
||||
class FileComparison:
|
||||
def __init__(self, file1: ECUFile, file2: ECUFile):
|
||||
self.file1 = file1
|
||||
self.file2 = file2
|
||||
self.differences = []
|
||||
self.map_differences = {}
|
||||
|
||||
def compare_files(self) -> bool:
|
||||
"""
|
||||
Compare two ECU files and find differences
|
||||
Returns True if differences were found
|
||||
"""
|
||||
if not self.file1.data or not self.file2.data:
|
||||
return False
|
||||
|
||||
# Compare file sizes
|
||||
if len(self.file1.data) != len(self.file2.data):
|
||||
self.differences.append(("File Size",
|
||||
len(self.file1.data),
|
||||
len(self.file2.data)))
|
||||
return True
|
||||
|
||||
# Compare byte by byte
|
||||
diff_positions = []
|
||||
for i, (b1, b2) in enumerate(zip(self.file1.data, self.file2.data)):
|
||||
if b1 != b2:
|
||||
diff_positions.append((i, b1, b2))
|
||||
|
||||
if diff_positions:
|
||||
self.differences.extend(diff_positions)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def compare_maps(self) -> Dict[str, np.ndarray]:
|
||||
"""
|
||||
Compare maps between two files
|
||||
Returns dictionary of map differences
|
||||
"""
|
||||
# Detect maps in both files
|
||||
detector1 = EDC15MapDetector(self.file1.data)
|
||||
detector2 = EDC15MapDetector(self.file2.data)
|
||||
|
||||
maps1 = detector1.find_potential_maps()
|
||||
maps2 = detector2.find_potential_maps()
|
||||
|
||||
# Compare maps at same addresses
|
||||
for map1 in maps1:
|
||||
for map2 in maps2:
|
||||
if map1.address == map2.address:
|
||||
map_data1 = detector1.extract_map(map1)
|
||||
map_data2 = detector2.extract_map(map2)
|
||||
|
||||
if not np.array_equal(map_data1.data, map_data2.data):
|
||||
diff = map_data2.data - map_data1.data
|
||||
self.map_differences[map1.name] = {
|
||||
'difference': diff,
|
||||
'original': map_data1,
|
||||
'modified': map_data2
|
||||
}
|
||||
|
||||
return self.map_differences
|
||||
|
||||
def get_difference_summary(self) -> str:
|
||||
"""
|
||||
Generate a human-readable summary of differences
|
||||
"""
|
||||
summary = []
|
||||
|
||||
if not self.differences and not self.map_differences:
|
||||
return "No differences found between the files."
|
||||
|
||||
if self.differences:
|
||||
summary.append(f"Found {len(self.differences)} byte differences:")
|
||||
for i, (pos, val1, val2) in enumerate(self.differences[:10]):
|
||||
summary.append(f" Offset 0x{pos:X}: {val1:02X} -> {val2:02X}")
|
||||
if len(self.differences) > 10:
|
||||
summary.append(f" ... and {len(self.differences)-10} more differences")
|
||||
|
||||
if self.map_differences:
|
||||
summary.append(f"\nFound {len(self.map_differences)} modified maps:")
|
||||
for map_name, diff_data in self.map_differences.items():
|
||||
diff = diff_data['difference']
|
||||
changes = np.count_nonzero(diff)
|
||||
max_change = np.max(np.abs(diff))
|
||||
summary.append(f" {map_name}: {changes} changed values, "
|
||||
f"max difference: {max_change:.2f}")
|
||||
|
||||
return "\n".join(summary)
|
||||
|
||||
def export_report(self, filepath: str) -> bool:
|
||||
"""Export comparison report to file"""
|
||||
try:
|
||||
with open(filepath, 'w') as f:
|
||||
# Write header
|
||||
f.write("ECU File Comparison Report\n")
|
||||
f.write("=" * 30 + "\n\n")
|
||||
|
||||
# Write file information
|
||||
f.write("File 1: " + os.path.basename(self.file1.file_path) + "\n")
|
||||
f.write("File 2: " + os.path.basename(self.file2.file_path) + "\n\n")
|
||||
|
||||
# Write differences
|
||||
f.write("Map Differences\n")
|
||||
f.write("-" * 20 + "\n\n")
|
||||
|
||||
for map_name, diff_data in self.map_differences.items():
|
||||
f.write(f"Map: {map_name}\n")
|
||||
f.write(f"Total differences: {np.count_nonzero(diff_data['difference'])}\n")
|
||||
f.write("Changes:\n")
|
||||
|
||||
diff = diff_data['difference']
|
||||
rows, cols = diff.shape
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
if diff[i, j] != 0:
|
||||
orig = diff_data['original'].data[i, j]
|
||||
mod = diff_data['modified'].data[i, j]
|
||||
f.write(f" Position ({i},{j}): {orig:.2f} -> {mod:.2f}\n")
|
||||
f.write("\n")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
234
src/core/file_handler.py
Normal file
234
src/core/file_handler.py
Normal file
@ -0,0 +1,234 @@
|
||||
import os
|
||||
import numpy as np
|
||||
from typing import Dict, Optional, Tuple
|
||||
from dataclasses import dataclass
|
||||
from .edc15p_maps import MapDefinition, get_map_definition, list_maps_by_group, EDC15P_MAPS
|
||||
import shutil
|
||||
|
||||
@dataclass
|
||||
class MapData:
|
||||
"""Map data structure"""
|
||||
name: str
|
||||
map_id: str
|
||||
data: np.ndarray
|
||||
rows: int
|
||||
cols: int
|
||||
x_axis: np.ndarray
|
||||
y_axis: np.ndarray
|
||||
address: str
|
||||
units: str = ""
|
||||
description: str = ""
|
||||
group: str = ""
|
||||
|
||||
# Additional properties for map manager
|
||||
data_format: str = "16-bit Hi-Lo"
|
||||
display_mode: str = "Original" # Original, Difference, or Percent
|
||||
is_signed: bool = False
|
||||
swap_axis: bool = False
|
||||
display_hex: bool = False
|
||||
is_reciprocal: bool = False
|
||||
value_min: float = 0.0
|
||||
value_max: float = 0.0
|
||||
factor: float = 1.0
|
||||
offset: float = 0.0
|
||||
precision: int = 0
|
||||
formula: str = ""
|
||||
|
||||
# X-axis properties
|
||||
x_name: str = "RPM"
|
||||
x_address: str = ""
|
||||
x_data_source: str = "EPROM"
|
||||
x_data_format: str = "16-bit Hi-Lo"
|
||||
x_step: int = 0
|
||||
x_factor: float = 1.0
|
||||
x_offset: float = 0.0
|
||||
x_precision: int = 0
|
||||
x_is_signed: bool = False
|
||||
x_is_inverted: bool = False
|
||||
x_display_hex: bool = False
|
||||
x_is_reciprocal: bool = False
|
||||
x_formula: str = ""
|
||||
|
||||
# Y-axis properties
|
||||
y_name: str = "FUEL"
|
||||
y_address: str = ""
|
||||
y_data_source: str = "EPROM"
|
||||
y_data_format: str = "16-bit Hi-Lo"
|
||||
y_step: int = 0
|
||||
y_factor: float = 1.0
|
||||
y_offset: float = 0.0
|
||||
y_precision: int = 0
|
||||
y_is_signed: bool = False
|
||||
y_is_inverted: bool = False
|
||||
y_display_hex: bool = False
|
||||
y_is_reciprocal: bool = False
|
||||
y_formula: str = ""
|
||||
|
||||
def get_value(self, row: int, col: int) -> float:
|
||||
"""Get value at specified coordinates"""
|
||||
return float(self.data[row, col])
|
||||
|
||||
def set_value(self, row: int, col: int, value: float):
|
||||
"""Set value at specified coordinates"""
|
||||
self.data[row, col] = value
|
||||
|
||||
class ECUFile:
|
||||
"""Class to handle ECU file operations"""
|
||||
|
||||
def __init__(self, filepath: str):
|
||||
self.filepath = filepath
|
||||
self.backup_path = f"{filepath}.backup"
|
||||
self.content = None
|
||||
self.maps: Dict[str, MapData] = {} # Key is map_id
|
||||
self.modified = False
|
||||
|
||||
def read_file(self) -> bool:
|
||||
"""Read and parse the ECU file"""
|
||||
try:
|
||||
with open(self.filepath, 'rb') as f:
|
||||
self.content = f.read()
|
||||
|
||||
# Create backup if it doesn't exist
|
||||
if not os.path.exists(self.backup_path):
|
||||
shutil.copy2(self.filepath, self.backup_path)
|
||||
|
||||
# Extract maps
|
||||
self._extract_map_data()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
def get_raw_content(self) -> Optional[bytes]:
|
||||
"""Get raw binary content of the file"""
|
||||
return self.content
|
||||
|
||||
def read_map_data(self, map_id: str, map_def: MapDefinition) -> Tuple[np.ndarray, bool]:
|
||||
"""Read map data from binary file using map definition"""
|
||||
try:
|
||||
# Convert hex address to integer
|
||||
address = int(map_def.address, 16)
|
||||
|
||||
# Calculate total bytes needed (2 bytes per value - 16 bit)
|
||||
total_bytes = map_def.rows * map_def.cols * 2
|
||||
|
||||
# Ensure we have enough data
|
||||
if address + total_bytes > len(self.content):
|
||||
return np.zeros((map_def.rows, map_def.cols)), False
|
||||
|
||||
# Read raw bytes from file
|
||||
raw_data = self.content[address:address + total_bytes]
|
||||
|
||||
# Convert bytes to 16-bit integers (little-endian)
|
||||
data = np.frombuffer(raw_data, dtype=np.uint16, count=map_def.rows * map_def.cols)
|
||||
data = data.reshape(map_def.rows, map_def.cols)
|
||||
|
||||
# Verify data shape
|
||||
if data.shape != (map_def.rows, map_def.cols):
|
||||
return np.zeros((map_def.rows, map_def.cols)), False
|
||||
|
||||
return data, True
|
||||
|
||||
except Exception as e:
|
||||
return np.zeros((map_def.rows, map_def.cols)), False
|
||||
|
||||
def _extract_map_data(self):
|
||||
"""Extract real map data from ECU file"""
|
||||
if not self.content:
|
||||
return
|
||||
|
||||
# Get all map definitions by group
|
||||
groups = list_maps_by_group()
|
||||
for group_name, maps in groups.items():
|
||||
for map_def in maps:
|
||||
try:
|
||||
# Find map ID
|
||||
map_id = next((k for k, v in EDC15P_MAPS.items() if v == map_def), None)
|
||||
if not map_id:
|
||||
continue
|
||||
|
||||
# Read map data
|
||||
data, success = self.read_map_data(map_id, map_def)
|
||||
if not success:
|
||||
continue
|
||||
|
||||
# Create MapData object with all necessary information
|
||||
self.maps[map_id] = MapData(
|
||||
name=map_def.name,
|
||||
map_id=map_id,
|
||||
data=data,
|
||||
rows=map_def.rows,
|
||||
cols=map_def.cols,
|
||||
x_axis=np.array(map_def.x_axis),
|
||||
y_axis=np.array(map_def.y_axis),
|
||||
address=map_def.address,
|
||||
units=map_def.units,
|
||||
description=map_def.description,
|
||||
group=map_def.group
|
||||
)
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
def _update_file_content(self):
|
||||
"""Update file content with modified maps"""
|
||||
if not self.content:
|
||||
return
|
||||
|
||||
# Get all map definitions
|
||||
for map_id, map_data in self.maps.items():
|
||||
try:
|
||||
# Get address
|
||||
address = int(map_data.address, 16)
|
||||
|
||||
# Convert data to bytes (16-bit little-endian)
|
||||
raw_data = map_data.data.astype(np.uint16).tobytes()
|
||||
|
||||
# Update content
|
||||
self.content = (
|
||||
self.content[:address] +
|
||||
raw_data +
|
||||
self.content[address + len(raw_data):]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
def save_file(self, filepath: Optional[str] = None) -> bool:
|
||||
"""Save the file content"""
|
||||
try:
|
||||
# Update content with modified maps
|
||||
self._update_file_content()
|
||||
|
||||
# Use provided filepath or default to original
|
||||
save_path = filepath or self.filepath
|
||||
|
||||
with open(save_path, 'wb') as f:
|
||||
f.write(self.content)
|
||||
|
||||
self.modified = False
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
def restore_backup(self) -> bool:
|
||||
"""Restore from backup file"""
|
||||
try:
|
||||
if not os.path.exists(self.backup_path):
|
||||
return False
|
||||
|
||||
# Read backup content
|
||||
with open(self.backup_path, 'rb') as f:
|
||||
self.content = f.read()
|
||||
|
||||
# Re-extract maps
|
||||
self.maps.clear()
|
||||
self._extract_map_data()
|
||||
|
||||
# Save restored content
|
||||
return self.save_file()
|
||||
|
||||
except Exception as e:
|
||||
return False
|
||||
27
src/core/project_manager.py
Normal file
27
src/core/project_manager.py
Normal file
@ -0,0 +1,27 @@
|
||||
class ProjectManager:
|
||||
def __init__(self):
|
||||
self.current_project = None
|
||||
|
||||
def create_project(self, name, path):
|
||||
"""
|
||||
Create a new ECU project
|
||||
"""
|
||||
pass
|
||||
|
||||
def open_project(self, path):
|
||||
"""
|
||||
Open an existing ECU project
|
||||
"""
|
||||
pass
|
||||
|
||||
def save_project(self):
|
||||
"""
|
||||
Save current project
|
||||
"""
|
||||
pass
|
||||
|
||||
def validate_file(self, file_path):
|
||||
"""
|
||||
Validate ECU file integrity
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user