From 10713c22cfbd409208b9e7b72effdd3685b30e6e Mon Sep 17 00:00:00 2001 From: godax84 Date: Thu, 19 Dec 2024 09:21:09 -0800 Subject: [PATCH] Carregar ficheiros para "src/core" --- src/core/file_comparison.py | 131 ++++++++++++++++++++ src/core/file_handler.py | 234 ++++++++++++++++++++++++++++++++++++ src/core/project_manager.py | 27 +++++ 3 files changed, 392 insertions(+) create mode 100644 src/core/file_comparison.py create mode 100644 src/core/file_handler.py create mode 100644 src/core/project_manager.py diff --git a/src/core/file_comparison.py b/src/core/file_comparison.py new file mode 100644 index 0000000..c456bb8 --- /dev/null +++ b/src/core/file_comparison.py @@ -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 diff --git a/src/core/file_handler.py b/src/core/file_handler.py new file mode 100644 index 0000000..e5c7c6a --- /dev/null +++ b/src/core/file_handler.py @@ -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 diff --git a/src/core/project_manager.py b/src/core/project_manager.py new file mode 100644 index 0000000..58927fc --- /dev/null +++ b/src/core/project_manager.py @@ -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