From 16f68bd726c28c35345ce8063d9c63de7d97c5cc Mon Sep 17 00:00:00 2001 From: godax84 Date: Thu, 19 Dec 2024 09:20:26 -0800 Subject: [PATCH] Carregar ficheiros para "src/core" --- src/core/__init__.py | 1 + src/core/command.py | 118 ++++++++ src/core/edc15_definitions.py | 160 +++++++++++ src/core/edc15_maps.py | 131 +++++++++ src/core/edc15p_maps.py | 504 ++++++++++++++++++++++++++++++++++ 5 files changed, 914 insertions(+) create mode 100644 src/core/__init__.py create mode 100644 src/core/command.py create mode 100644 src/core/edc15_definitions.py create mode 100644 src/core/edc15_maps.py create mode 100644 src/core/edc15p_maps.py diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/core/__init__.py @@ -0,0 +1 @@ + diff --git a/src/core/command.py b/src/core/command.py new file mode 100644 index 0000000..c2fd33a --- /dev/null +++ b/src/core/command.py @@ -0,0 +1,118 @@ +from abc import ABC, abstractmethod +from typing import List, Tuple, Any +import numpy as np +from .file_handler import MapData + +class Command(ABC): + @abstractmethod + def execute(self) -> bool: + pass + + @abstractmethod + def undo(self) -> bool: + pass + +class MapValueCommand(Command): + """Command for single value changes in a map""" + def __init__(self, map_data: MapData, x: int, y: int, new_value: float): + self.map_data = map_data + self.x = x + self.y = y + self.new_value = new_value + self.old_value = map_data.data[x, y] + + def execute(self) -> bool: + try: + self.map_data.data[self.x, self.y] = self.new_value + self.map_data.modified = True + return True + except Exception: + return False + + def undo(self) -> bool: + try: + self.map_data.data[self.x, self.y] = self.old_value + self.map_data.modified = True + return True + except Exception: + return False + +class RegionCommand(Command): + """Command for region modifications""" + def __init__(self, map_data: MapData, x1: int, y1: int, x2: int, y2: int, + new_values: np.ndarray): + self.map_data = map_data + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + self.new_values = new_values + self.old_values = map_data.data[x1:x2+1, y1:y2+1].copy() + + def execute(self) -> bool: + try: + self.map_data.data[self.x1:self.x2+1, self.y1:self.y2+1] = self.new_values + self.map_data.modified = True + return True + except Exception: + return False + + def undo(self) -> bool: + try: + self.map_data.data[self.x1:self.x2+1, self.y1:self.y2+1] = self.old_values + self.map_data.modified = True + return True + except Exception: + return False + +class CommandHistory: + def __init__(self, max_history: int = 50): + self.max_history = max_history + self.history: List[Command] = [] + self.current: int = -1 + + def execute(self, command: Command) -> bool: + """Execute a new command and add it to history""" + if command.execute(): + # Remove any undone commands + if self.current < len(self.history) - 1: + self.history = self.history[:self.current + 1] + + self.history.append(command) + self.current += 1 + + # Limit history size + if len(self.history) > self.max_history: + self.history = self.history[-self.max_history:] + self.current = len(self.history) - 1 + + return True + return False + + def undo(self) -> bool: + """Undo the last command""" + if self.can_undo(): + if self.history[self.current].undo(): + self.current -= 1 + return True + return False + + def redo(self) -> bool: + """Redo the last undone command""" + if self.can_redo(): + self.current += 1 + return self.history[self.current].execute() + return False + + def can_undo(self) -> bool: + """Check if undo is possible""" + return self.current >= 0 + + def can_redo(self) -> bool: + """Check if redo is possible""" + return self.current < len(self.history) - 1 + + def clear(self): + """Clear command history""" + self.history.clear() + self.current = -1 diff --git a/src/core/edc15_definitions.py b/src/core/edc15_definitions.py new file mode 100644 index 0000000..8362b9b --- /dev/null +++ b/src/core/edc15_definitions.py @@ -0,0 +1,160 @@ +from typing import Dict, List +from dataclasses import dataclass + +@dataclass +class MapAxis: + name: str + values: List[float] + units: str + +@dataclass +class EDC15MapInfo: + name: str + address: int + rows: int + cols: int + value_type: str + description: str + x_axis: MapAxis + y_axis: MapAxis + units: str + +# Common EDC15 map definitions +EDC15_MAPS = { + # Fuel Maps + "MAIN_FUEL_MAP": EDC15MapInfo( + name="Mapa Principal de Combustível", + address=0x4000, # Example address + rows=16, + cols=16, + value_type="float32", + description="Mapa principal de injeção de combustível", + x_axis=MapAxis("RPM", [800, 1000, 1500, 2000, 2500, 3000, 3500, 4000, + 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000], + "rpm"), + y_axis=MapAxis("Load", [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, + 110, 120, 130, 140, 150], + "%"), + units="mg/stroke" + ), + + "PILOT_INJECTION": EDC15MapInfo( + name="Mapa de Pré-Injeção", + address=0x4400, # Example address + rows=12, + cols=12, + value_type="float32", + description="Mapa de controle da pré-injeção", + x_axis=MapAxis("RPM", [800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, + 4000, 4400, 4800, 5200], + "rpm"), + y_axis=MapAxis("Load", [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165], + "%"), + units="mg/stroke" + ), + + # Boost Maps + "BOOST_CONTROL": EDC15MapInfo( + name="Mapa de Controle de Boost", + address=0x4800, # Example address + rows=16, + cols=12, + value_type="float32", + description="Mapa de controle de pressão do turbo", + x_axis=MapAxis("RPM", [1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, + 5000, 5500, 6000, 6500], + "rpm"), + y_axis=MapAxis("Load", [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, + 110, 120, 130, 140, 150], + "%"), + units="mbar" + ), + + # Timing Maps + "INJECTION_TIMING": EDC15MapInfo( + name="Mapa de Avanço de Injeção", + address=0x4C00, # Example address + rows=16, + cols=16, + value_type="float32", + description="Mapa de avanço do ponto de injeção", + x_axis=MapAxis("RPM", [800, 1000, 1500, 2000, 2500, 3000, 3500, 4000, + 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000], + "rpm"), + y_axis=MapAxis("Load", [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, + 110, 120, 130, 140, 150], + "%"), + units="degrees" + ), + + # EGR Maps + "EGR_CONTROL": EDC15MapInfo( + name="Mapa de Controle EGR", + address=0x5000, # Example address + rows=12, + cols=12, + value_type="float32", + description="Mapa de controle da válvula EGR", + x_axis=MapAxis("RPM", [800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, + 4000, 4400, 4800, 5200], + "rpm"), + y_axis=MapAxis("Load", [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165], + "%"), + units="%" + ), + + # Limit Maps + "TORQUE_LIMIT": EDC15MapInfo( + name="Mapa de Limite de Torque", + address=0x5400, # Example address + rows=12, + cols=16, + value_type="float32", + description="Mapa de limite de torque do motor", + x_axis=MapAxis("RPM", [800, 1000, 1500, 2000, 2500, 3000, 3500, 4000, + 4500, 5000, 5500, 6000, 6500, 7000, 7500, 8000], + "rpm"), + y_axis=MapAxis("Temperature", [0, 20, 40, 60, 70, 75, 80, 85, 90, 95, + 100, 105], + "°C"), + units="Nm" + ), +} + +# Checksum regions +CHECKSUM_REGIONS = [ + (0x0000, 0x1000), # Example region 1 + (0x4000, 0x6000), # Example region 2 +] + +# Known file signatures +EDC15_SIGNATURES = { + "EDC15P": bytes([0x55, 0xAA, 0x55, 0xAA]), + "EDC15V": bytes([0xAA, 0x55, 0xAA, 0x55]), +} + +def calculate_checksum(data: bytes, start: int, end: int) -> int: + """Calculate checksum for a region of data""" + checksum = 0 + for i in range(start, end): + if i < len(data): + checksum = (checksum + data[i]) & 0xFF + return checksum + +def validate_ecu_file(data: bytes) -> bool: + """Validate EDC15 file format and checksums""" + if len(data) < 0x8000: # Minimum size check + return False + + # Check for known signatures + for sig_name, signature in EDC15_SIGNATURES.items(): + if data.startswith(signature): + # Verify checksums for each region + for start, end in CHECKSUM_REGIONS: + stored_checksum = data[end] if end < len(data) else 0 + calculated = calculate_checksum(data, start, end) + if stored_checksum != calculated: + return False + return True + + return False diff --git a/src/core/edc15_maps.py b/src/core/edc15_maps.py new file mode 100644 index 0000000..1443eb4 --- /dev/null +++ b/src/core/edc15_maps.py @@ -0,0 +1,131 @@ +from typing import Dict, List, Tuple +import numpy as np +from .file_handler import MapData + +class EDC15MapDefinition: + def __init__(self, name: str, address: int, rows: int, cols: int, + value_type: str, description: str = ""): + self.name = name + self.address = address + self.rows = rows + self.cols = cols + self.value_type = value_type # 'uint8', 'uint16', 'float' + self.description = description + +class EDC15MapDetector: + # Common EDC15 map signatures + KNOWN_SIGNATURES = { + b'\x00\x00\x80\x3F': 'float32', # 1.0f in IEEE 754 + b'\x00\x00\x00\x40': 'float32', # 2.0f in IEEE 754 + b'\xCD\xCC\xCC\x3D': 'float32', # 0.1f in IEEE 754 + } + + # Common map sizes in EDC15 + COMMON_SIZES = [(8, 8), (16, 16), (12, 12), (16, 12), (12, 16)] + + def __init__(self, data: bytes): + self.data = data + self.size = len(data) + + def find_potential_maps(self) -> List[EDC15MapDefinition]: + """ + Find potential maps in the binary data based on known patterns + """ + potential_maps = [] + + # Search for known signatures + for signature, value_type in self.KNOWN_SIGNATURES.items(): + offset = 0 + while True: + offset = self.data.find(signature, offset) + if offset == -1: + break + + # Check for potential map structure around signature + for rows, cols in self.COMMON_SIZES: + if self._validate_map_structure(offset, rows, cols, value_type): + map_def = EDC15MapDefinition( + f"Map_0x{offset:X}", + offset, + rows, + cols, + value_type + ) + potential_maps.append(map_def) + + offset += len(signature) + + return potential_maps + + def _validate_map_structure(self, offset: int, rows: int, cols: int, + value_type: str) -> bool: + """ + Validate if a potential map structure exists at the given offset + """ + # Calculate required space for the map + bytes_per_value = 4 if value_type == 'float32' else 2 + required_space = rows * cols * bytes_per_value + + # Check if we have enough space in the file + if offset + required_space > self.size: + return False + + # Validate data consistency + try: + data = self._extract_map_data(offset, rows, cols, value_type) + + # Check for reasonable value ranges + if value_type == 'float32': + if not np.all(np.isfinite(data)): + return False + if np.any(np.abs(data) > 1000000): # Unreasonable values + return False + + # Check for patterns that suggest this is actually a map + # 1. Check if values are somewhat continuous + differences = np.abs(np.diff(data.ravel())) + if np.max(differences) > 1000: # Too large jumps between values + return False + + # 2. Check if there's some variation (not all same value) + if np.all(data == data[0, 0]): + return False + + return True + + except Exception: + return False + + def _extract_map_data(self, offset: int, rows: int, cols: int, + value_type: str) -> np.ndarray: + """ + Extract map data from binary file + """ + bytes_per_value = 4 if value_type == 'float32' else 2 + total_bytes = rows * cols * bytes_per_value + + raw_data = self.data[offset:offset + total_bytes] + + if value_type == 'float32': + data = np.frombuffer(raw_data, dtype=np.float32) + else: + data = np.frombuffer(raw_data, dtype=np.uint16) + + return data.reshape((rows, cols)) + + def extract_map(self, map_def: EDC15MapDefinition) -> MapData: + """ + Extract map data based on map definition + """ + data = self._extract_map_data( + map_def.address, + map_def.rows, + map_def.cols, + map_def.value_type + ) + + # Create axis data (placeholder - should be extracted from actual file) + x_axis = np.linspace(0, map_def.cols - 1, map_def.cols) + y_axis = np.linspace(0, map_def.rows - 1, map_def.rows) + + return MapData(map_def.name, data, x_axis, y_axis) diff --git a/src/core/edc15p_maps.py b/src/core/edc15p_maps.py new file mode 100644 index 0000000..2ca7056 --- /dev/null +++ b/src/core/edc15p_maps.py @@ -0,0 +1,504 @@ +from dataclasses import dataclass +from typing import Dict, List, Optional + +@dataclass +class MapDefinition: + name: str + address: str # Hex address in the binary + rows: int + cols: int + x_axis: List[float] + y_axis: List[float] + description: str + units: str + category: str + group: str + +# Map definitions for EDC15P +EDC15P_MAPS = { + # Drivers Wish [FUEL] + "drivers_wish_1": MapDefinition( + name="Drivers Wish Map 1", + address="04D206", + rows=12, + cols=8, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70], # Load % + description="Drivers Wish Map 1", + units="FUEL", + category="Fuel", + group="Drivers Wish [FUEL]" + ), + "drivers_wish_2": MapDefinition( + name="Drivers Wish Map 2", + address="06D206", + rows=12, + cols=8, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70], # Load % + description="Drivers Wish Map 2", + units="FUEL", + category="Fuel", + group="Drivers Wish [FUEL]" + ), + + # Torque [FUEL] + "torque_1": MapDefinition( + name="Torque Map 1", + address="04D8D2", + rows=3, + cols=20, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95], # Load % + description="Torque Control Map 1", + units="FUEL", + category="Torque", + group="Torque [FUEL]" + ), + "torque_2": MapDefinition( + name="Torque Map 2", + address="06D8D2", + rows=3, + cols=20, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95], # Load % + description="Torque Control Map 2", + units="FUEL", + category="Torque", + group="Torque [FUEL]" + ), + + # Smoke Limiter [FUEL] + "smoke_limiter_1": MapDefinition( + name="Smoke Limiter Map 1", + address="04DA04", + rows=16, + cols=13, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200, 5600], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Smoke Limiter Map 1", + units="FUEL", + category="Limiters", + group="Smoke Limiter [FUEL]" + ), + "smoke_limiter_2": MapDefinition( + name="Smoke Limiter Map 2", + address="04DBF6", + rows=16, + cols=13, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200, 5600], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Smoke Limiter Map 2", + units="FUEL", + category="Limiters", + group="Smoke Limiter [FUEL]" + ), + "smoke_limiter_3": MapDefinition( + name="Smoke Limiter Map 3", + address="06DA04", + rows=16, + cols=13, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200, 5600], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Smoke Limiter Map 3", + units="FUEL", + category="Limiters", + group="Smoke Limiter [FUEL]" + ), + "smoke_limiter_4": MapDefinition( + name="Smoke Limiter Map 4", + address="06DBF6", + rows=16, + cols=13, + x_axis=[800, 1200, 1600, 2000, 2400, 2800, 3200, 3600, 4000, 4400, 4800, 5200, 5600], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Smoke Limiter Map 4", + units="FUEL", + category="Limiters", + group="Smoke Limiter [FUEL]" + ), + + # Speed Limiter Via Temp [ENGINE LOAD] + "speed_temp_limiter_1": MapDefinition( + name="Speed Temperature Limiter 1", + address="04E732", + rows=4, + cols=4, + x_axis=[0, 30, 60, 90], # Temperature + y_axis=[0, 25, 50, 75], # Speed % + description="Speed Limiter via Temperature Map 1", + units="ENGINE LOAD", + category="Limiters", + group="Speed Limiter Via Temp [ENGINE LOAD]" + ), + "speed_temp_limiter_2": MapDefinition( + name="Speed Temperature Limiter 2", + address="06E732", + rows=4, + cols=4, + x_axis=[0, 30, 60, 90], # Temperature + y_axis=[0, 25, 50, 75], # Speed % + description="Speed Limiter via Temperature Map 2", + units="ENGINE LOAD", + category="Limiters", + group="Speed Limiter Via Temp [ENGINE LOAD]" + ), + + # Injection Limiter Via Temp [LOAD] + "injection_temp_limiter_1": MapDefinition( + name="Injection Temperature Limiter 1", + address="04F238", + rows=3, + cols=3, + x_axis=[0, 50, 100], # Temperature + y_axis=[0, 50, 100], # Load % + description="Injection Limiter via Temperature Map 1", + units="LOAD", + category="Limiters", + group="Injection Limiter Via Temp [LOAD]" + ), + "injection_temp_limiter_2": MapDefinition( + name="Injection Temperature Limiter 2", + address="06F238", + rows=3, + cols=3, + x_axis=[0, 50, 100], # Temperature + y_axis=[0, 50, 100], # Load % + description="Injection Limiter via Temperature Map 2", + units="LOAD", + category="Limiters", + group="Injection Limiter Via Temp [LOAD]" + ), + + # Boost Limiter Via Temperature [mBAR] + "boost_temp_limiter_1": MapDefinition( + name="Boost Temperature Limiter 1", + address="051FA0", + rows=16, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300], # Temperature + description="Boost Limiter via Temperature Map 1", + units="mBAR", + category="Limiters", + group="Boost Limiter Via Temperature [mBAR]" + ), + "boost_temp_limiter_2": MapDefinition( + name="Boost Temperature Limiter 2", + address="071FA0", + rows=16, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300], # Temperature + description="Boost Limiter via Temperature Map 2", + units="mBAR", + category="Limiters", + group="Boost Limiter Via Temperature [mBAR]" + ), + + # Spraying Time [DEG] + "spraying_time_1": MapDefinition( + name="Spraying Time Map 1", + address="054548", + rows=10, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90], # Load % + description="Spraying Time Map 1", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_2": MapDefinition( + name="Spraying Time Map 2", + address="05465C", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 2", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_3": MapDefinition( + name="Spraying Time Map 3", + address="0548E2", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 3", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_4": MapDefinition( + name="Spraying Time Map 4", + address="054B68", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 4", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_5": MapDefinition( + name="Spraying Time Map 5", + address="074548", + rows=10, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90], # Load % + description="Spraying Time Map 5", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_6": MapDefinition( + name="Spraying Time Map 6", + address="07465C", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 6", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_7": MapDefinition( + name="Spraying Time Map 7", + address="0748E2", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 7", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + "spraying_time_8": MapDefinition( + name="Spraying Time Map 8", + address="074B68", + rows=15, + cols=19, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000, 5500, 6000, 6500, 7000], # RPM + y_axis=[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90], # Load % + description="Spraying Time Map 8", + units="DEG", + category="Timing", + group="Spraying Time [DEG]" + ), + + # Air Mass Substitutive value [FUEL] + "air_mass_sub_1": MapDefinition( + name="Air Mass Substitutive Value 1", + address="055632", + rows=8, + cols=8, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70], # Load % + description="Air Mass Substitutive Value Map 1", + units="FUEL", + category="Air Mass", + group="Air Mass Substitutive value [FUEL]" + ), + "air_mass_sub_2": MapDefinition( + name="Air Mass Substitutive Value 2", + address="075632", + rows=8, + cols=8, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70], # Load % + description="Air Mass Substitutive Value Map 2", + units="FUEL", + category="Air Mass", + group="Air Mass Substitutive value [FUEL]" + ), + + # Boost Pressure [mBAR] + "boost_pressure_1": MapDefinition( + name="Boost Pressure Map 1", + address="056926", + rows=16, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Boost Pressure Control Map 1", + units="mBAR", + category="Boost", + group="Boost Pressure [mBAR]" + ), + "boost_pressure_2": MapDefinition( + name="Boost Pressure Map 2", + address="076926", + rows=16, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150], # Load % + description="Boost Pressure Control Map 2", + units="mBAR", + category="Boost", + group="Boost Pressure [mBAR]" + ), + + # Boost Limiter Via Patm [mBAR] + "boost_patm_limiter_1": MapDefinition( + name="Boost Patm Limiter 1", + address="056F1C", + rows=10, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[800, 850, 900, 950, 1000, 1050, 1100, 1150, 1200, 1250], # Atmospheric Pressure + description="Boost Limiter via Atmospheric Pressure Map 1", + units="mBAR", + category="Limiters", + group="Boost Limiter Via Patm [mBAR]" + ), + "boost_patm_limiter_2": MapDefinition( + name="Boost Patm Limiter 2", + address="076F1C", + rows=10, + cols=10, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500], # RPM + y_axis=[800, 850, 900, 950, 1000, 1050, 1100, 1150, 1200, 1250], # Atmospheric Pressure + description="Boost Limiter via Atmospheric Pressure Map 2", + units="mBAR", + category="Limiters", + group="Boost Limiter Via Patm [mBAR]" + ), + + # Advance Limiter Via Temp [DEG] + "advance_temp_limiter_1": MapDefinition( + name="Advance Temperature Limiter 1", + address="0581B6", + rows=14, + cols=11, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], # Temperature + description="Advance Limiter via Temperature Map 1", + units="DEG", + category="Limiters", + group="Advance Limiter Via Temp [DEG]" + ), + "advance_temp_limiter_2": MapDefinition( + name="Advance Temperature Limiter 2", + address="0781B6", + rows=14, + cols=11, + x_axis=[0, 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000], # RPM + y_axis=[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130], # Temperature + description="Advance Limiter via Temperature Map 2", + units="DEG", + category="Limiters", + group="Advance Limiter Via Temp [DEG]" + ), + + # Idle Speed [RPM] + "idle_speed_1": MapDefinition( + name="Idle Speed Control 1", + address="056FEC", + rows=1, + cols=2, + x_axis=[0], # No X axis (single column) + y_axis=[0, 1], # Two states + description="Idle Speed Control Map 1", + units="RPM", + category="Speed", + group="Idle Speed [RPM]" + ), + + # Maximum Boost Pressure [mBAR] + "max_boost_pressure_1": MapDefinition( + name="Maximum Boost Pressure 1", + address="051C84", + rows=1, + cols=1, + x_axis=[0], # Single value + y_axis=[0], # Single value + description="Maximum Boost Pressure Map 1", + units="mBAR", + category="Limiters", + group="Maximum Boost Pressure [mBAR]" + ), + "max_boost_pressure_2": MapDefinition( + name="Maximum Boost Pressure 2", + address="071C84", + rows=1, + cols=1, + x_axis=[0], # Single value + y_axis=[0], # Single value + description="Maximum Boost Pressure Map 2", + units="mBAR", + category="Limiters", + group="Maximum Boost Pressure [mBAR]" + ), + + # Maximum RPM Limiter [RPM] + "max_rpm_limiter_1": MapDefinition( + name="Maximum RPM Limiter 1", + address="0541A3", + rows=1, + cols=1, + x_axis=[0], # Single value + y_axis=[0], # Single value + description="Maximum RPM Limiter Map 1", + units="RPM", + category="Limiters", + group="Maximum RPM Limiter [RPM]" + ), + "max_rpm_limiter_2": MapDefinition( + name="Maximum RPM Limiter 2", + address="0741A3", + rows=1, + cols=1, + x_axis=[0], # Single value + y_axis=[0], # Single value + description="Maximum RPM Limiter Map 2", + units="RPM", + category="Limiters", + group="Maximum RPM Limiter [RPM]" + ) +} + +def get_map_definition(map_id: str) -> Optional[MapDefinition]: + """Get map definition by ID""" + return EDC15P_MAPS.get(map_id) + +def list_maps() -> List[str]: + """List all available map IDs""" + return list(EDC15P_MAPS.keys()) + +def list_maps_by_group() -> Dict[str, List[MapDefinition]]: + """List maps grouped by their groups""" + groups: Dict[str, List[MapDefinition]] = {} + + # First pass: collect all unique groups + for map_def in EDC15P_MAPS.values(): + if map_def.group not in groups: + groups[map_def.group] = [] + + # Second pass: add maps to their groups + for map_id, map_def in EDC15P_MAPS.items(): + groups[map_def.group].append(map_def) + + return groups + +def list_maps_by_category() -> Dict[str, List[MapDefinition]]: + """List maps grouped by category""" + categories: Dict[str, List[MapDefinition]] = {} + + # First pass: collect all unique categories + for map_def in EDC15P_MAPS.values(): + if map_def.category not in categories: + categories[map_def.category] = [] + + # Second pass: add maps to their categories + for map_id, map_def in EDC15P_MAPS.items(): + categories[map_def.category].append(map_def) + + return categories