From 3506aa344e2d7eb0b54885de70f36b6f12cab11d Mon Sep 17 00:00:00 2001 From: godax84 Date: Fri, 6 Dec 2024 09:36:38 -0800 Subject: [PATCH] Carregar ficheiros para "/" --- binary_graph.py | 492 +++++++++++++++++++++++++++++++++++++++ car_brands.txt | 33 +++ edc15_info_window.py | 532 +++++++++++++++++++++++++++++++++++++++++++ flash_window.py | 305 +++++++++++++++++++++++++ hexawork.log | 10 + 5 files changed, 1372 insertions(+) create mode 100644 binary_graph.py create mode 100644 car_brands.txt create mode 100644 edc15_info_window.py create mode 100644 flash_window.py create mode 100644 hexawork.log diff --git a/binary_graph.py b/binary_graph.py new file mode 100644 index 0000000..9a55940 --- /dev/null +++ b/binary_graph.py @@ -0,0 +1,492 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import numpy as np +from logger import logger + +class BinaryGraphWindow: + def __init__(self, parent, binary_data): + try: + logger.info("Initializing Binary Graph Window") + + # Validate input + if not isinstance(binary_data, (list, np.ndarray)) or len(binary_data) == 0: + raise ValueError("Invalid binary data: must be a non-empty list or numpy array") + + # Store data as instance attribute + self.binary_data = list(binary_data) + self.parent = parent + + # Criar janela + self.window = tk.Toplevel(parent) + self.window.title("2D Binary Graph") + self.window.geometry("1000x600") + + # Frame principal + self.main_frame = ttk.Frame(self.window) + self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # Frame para os botões de bits e configurações + self.top_frame = ttk.Frame(self.main_frame) + self.top_frame.pack(fill=tk.X, padx=5, pady=5) + + # Frame para os botões de bits + self.bits_frame = ttk.Frame(self.top_frame) + self.bits_frame.pack(side=tk.LEFT, padx=(0, 10)) + + # Frame para seleção de pontos + self.points_frame = ttk.Frame(self.top_frame) + self.points_frame.pack(side=tk.LEFT) + + # Variável para controlar o modo de bits + self.bits_mode = tk.StringVar(value="8b") + + # Variável para controlar o número de pontos + self.points_var = tk.IntVar(value=512) + + # Estilo para botões pequenos + style = ttk.Style() + style.configure("Small.TButton", padding=1) + + # Botões para seleção de bits + self.btn_8b = ttk.Button(self.bits_frame, text="8b", width=3, + style="Small.TButton", + command=lambda: self.change_bits_mode("8b")) + self.btn_8b.pack(side=tk.LEFT, padx=2) + + self.btn_16b = ttk.Button(self.bits_frame, text="16b", width=3, + style="Small.TButton", + command=lambda: self.change_bits_mode("16b")) + self.btn_16b.pack(side=tk.LEFT, padx=2) + + self.btn_32b = ttk.Button(self.bits_frame, text="32b", width=3, + style="Small.TButton", + command=lambda: self.change_bits_mode("32b")) + self.btn_32b.pack(side=tk.LEFT, padx=2) + + # Label e Spinbox para seleção de pontos + ttk.Label(self.points_frame, text="Points:").pack(side=tk.LEFT, padx=(0, 5)) + self.points_spinbox = ttk.Spinbox( + self.points_frame, + from_=5, + to=1500, + textvariable=self.points_var, + width=5, + command=self.update_points_display + ) + self.points_spinbox.pack(side=tk.LEFT, padx=(0, 5)) + + # Frame para canvas e scrollbar + self.graph_frame = ttk.Frame(self.main_frame) + self.graph_frame.pack(fill=tk.BOTH, expand=True) + + # Criar label para hover + self.hover_label = tk.Label(self.window, text="", bg='lightyellow', relief=tk.SOLID, borderwidth=1) + + # Canvas para o gráfico + self.canvas = tk.Canvas(self.graph_frame, bg='white') + self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) + + # Scrollbar horizontal + self.scrollbar = ttk.Scrollbar(self.graph_frame, + orient=tk.HORIZONTAL, + command=self.on_scroll) + self.scrollbar.pack(side=tk.BOTTOM, fill=tk.X) + + # Configurar canvas para usar scrollbar + self.canvas.configure(xscrollcommand=self.scrollbar.set) + + # Configurar eventos de mouse + self.canvas.bind('', self.on_hover) + self.canvas.bind('', self.hide_hover_label) + + # Configurações iniciais + self.max_display = 512 # Definir 512 pontos como padrão + self.current_start = 0 + self.points_var.set(512) # Definir valor inicial do spinbox + + # Processar dados iniciais + self.process_data() + + # Plotar dados iniciais + self.plot_data() + + # Destacar botão 8b inicialmente + self.update_bits_buttons() + + # Vincular evento de redimensionamento + self.canvas.bind('', self.on_resize) + + # Configurar scrolling com o mouse + self.canvas.bind('', self.on_mousewheel) + + logger.info(f"Binary Graph initialized with {len(binary_data)} data points") + + except Exception as e: + logger.error(f"Error initializing Binary Graph: {e}", exc_info=True) + messagebox.showerror("Initialization Error", str(e)) + if hasattr(self, 'window'): + self.window.destroy() + + def on_scroll(self, *args): + """Manipula a rolagem do scrollbar""" + try: + # Obter a posição atual do scrollbar + if len(args) > 1: # Scroll através da scrollbar + fraction = float(args[1]) + total_points = len(self.processed_data) + + # Calcular novo índice inicial baseado na fração da scrollbar + max_start = max(0, total_points - self.max_display) + self.current_start = int(fraction * max_start) + + # Garantir que current_start não ultrapasse o limite + self.current_start = max(0, min(self.current_start, max_start)) + + # Replottar com a nova posição + self.plot_data() + + logger.info(f"Scrolled to position {self.current_start}") + + except Exception as e: + logger.error(f"Error in scroll handling: {e}", exc_info=True) + + def update_scrollbar(self): + """Atualiza a posição e o tamanho do scrollbar""" + try: + total_points = len(self.processed_data) + + if total_points <= self.max_display: + # Se todos os pontos estão visíveis, desabilitar scrollbar + self.scrollbar.set(0, 1) + return + + # Calcular a fração visível + visible_fraction = self.max_display / total_points + + # Calcular a posição inicial como fração + max_start = total_points - self.max_display + start_fraction = self.current_start / max_start if max_start > 0 else 0 + + # Definir posição e tamanho do scrollbar + self.scrollbar.set(start_fraction, start_fraction + visible_fraction) + + logger.info(f"Updated scrollbar: position {start_fraction:.2f}, size {visible_fraction:.2f}") + + except Exception as e: + logger.error(f"Error updating scrollbar: {e}", exc_info=True) + + def change_bits_mode(self, mode): + # Definir o modo de bits + self.bits_mode.set(mode) + + # Atualizar estado dos botões + self.update_bits_buttons() + + # Processar dados com o novo modo de bits + self.process_data() + + # Resetar para 512 pontos ao mudar o modo de bits + self.points_var.set(512) + self.max_display = 512 + self.current_start = 0 + + # Replottar dados + self.plot_data() + logger.info(f"Changed graph mode to {mode}") + + def update_bits_buttons(self): + # Atualizar aparência dos botões + current_mode = self.bits_mode.get() + for btn, mode in [(self.btn_8b, "8b"), + (self.btn_16b, "16b"), + (self.btn_32b, "32b")]: + if mode == current_mode: + btn.state(['pressed']) + else: + btn.state(['!pressed']) + + def process_data(self): + # Processar dados de acordo com o modo selecionado + mode = self.bits_mode.get() + raw_data = self.binary_data + + if mode == "8b": + self.processed_data = raw_data + elif mode == "16b": + # Converter para 16 bits (combinar bytes adjacentes) + self.processed_data = [] + for i in range(0, len(raw_data)-1, 2): + value = (raw_data[i+1] << 8) | raw_data[i] + self.processed_data.append(value) + elif mode == "32b": + # Converter para 32 bits (combinar 4 bytes adjacentes) + self.processed_data = [] + for i in range(0, len(raw_data)-3, 4): + value = (raw_data[i+3] << 24) | (raw_data[i+2] << 16) | \ + (raw_data[i+1] << 8) | raw_data[i] + self.processed_data.append(value) + + def get_max_value_for_mode(self): + """Retorna o valor máximo para o modo de bits atual""" + mode = self.bits_mode.get() + if mode == "8b": + return 255 + elif mode == "16b": + return 65535 + else: # 32b + return 4294967295 + + def format_hex(self, value): + """Formata um número para hexadecimal sem o prefixo 0x""" + return format(value, 'X') + + def plot_data(self): + """Plota os dados no canvas""" + try: + # Limpar canvas + self.canvas.delete("all") + + # Obter dimensões do canvas + plot_width = self.canvas.winfo_width() - 100 # Margem para eixos + plot_height = self.canvas.winfo_height() - 100 # Margem para eixos + margin_left = 50 + margin_bottom = 50 + + if not self.processed_data or len(self.processed_data) == 0: + return + + # Calcular índices de início e fim para exibição + end_idx = min(self.current_start + self.max_display, len(self.processed_data)) + visible_data = self.processed_data[self.current_start:end_idx] + + if not visible_data: + return + + # Usar valor máximo fixo baseado no modo de bits + max_val = self.get_max_value_for_mode() + min_val = 0 # Sempre começar do zero + + # Calcular escalas + x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width + y_scale = plot_height / max_val if max_val != 0 else 1 + + # Preparar pontos para plotagem + points = [] + for i, value in enumerate(visible_data): + x = margin_left + (i * x_scale) + y = self.canvas.winfo_height() - margin_bottom - (value * y_scale) + points.extend([x, y]) + + # Plotar linha + if len(points) >= 4: + self.canvas.create_line(points, fill='blue', width=1) + + # Desenhar eixo X + self.canvas.create_line( + margin_left, self.canvas.winfo_height() - margin_bottom, + margin_left + plot_width, self.canvas.winfo_height() - margin_bottom, + fill='black' + ) + + # Desenhar eixo Y + self.canvas.create_line( + margin_left, self.canvas.winfo_height() - margin_bottom, + margin_left, margin_bottom, + fill='black' + ) + + # Desenhar marcações no eixo X + num_x_ticks = 10 + for i in range(num_x_ticks + 1): + x_pos = margin_left + (i * plot_width / num_x_ticks) + self.canvas.create_line( + x_pos, self.canvas.winfo_height() - margin_bottom, + x_pos, self.canvas.winfo_height() - margin_bottom + 5, + fill='black' + ) + x_val = self.current_start + int((i / num_x_ticks) * (end_idx - self.current_start)) + + # Multiplicar por 2 e converter para hex apenas no modo 16 bits + if self.bits_mode.get() == "16b": + display_val = x_val * 2 + hex_x_val = self.format_hex(display_val) + else: + hex_x_val = self.format_hex(x_val) + + self.canvas.create_text( + x_pos, self.canvas.winfo_height() - margin_bottom + 20, + text=hex_x_val + ) + + # Desenhar marcações no eixo Y + num_y_ticks = 5 + for i in range(num_y_ticks + 1): + y_val = int((i / num_y_ticks) * max_val) + y_pos = self.canvas.winfo_height() - margin_bottom - (y_val * y_scale) + self.canvas.create_line( + margin_left - 5, y_pos, + margin_left, y_pos, + fill='black' + ) + # Manter valor do eixo Y em decimal + self.canvas.create_text( + margin_left - 25, y_pos, + text=str(y_val) + ) + + # Atualizar scrollbar + self.update_scrollbar() + + # Atualizar título do gráfico + title = f"{self.bits_mode.get()}-bit Binary Data" + self.canvas.create_text( + self.canvas.winfo_width() // 2, + 20, + text=title, + font=('Arial', 12, 'bold') + ) + + logger.info(f"Plotted {len(visible_data)} points from index {self.current_start} to {end_idx}") + + except Exception as e: + logger.error(f"Error plotting data: {e}", exc_info=True) + messagebox.showerror("Plotting Error", str(e)) + + def on_resize(self, event): + # Replottar quando o canvas for redimensionado + self.plot_data() + + def update_points_display(self): + """Atualiza o número de pontos exibidos no gráfico""" + try: + # Obter o novo número de pontos do spinbox + new_points = self.points_var.get() + + # Validar entrada + if new_points < 5 or new_points > 1500: + messagebox.showerror("Invalid Input", "Points must be between 5 and 1500") + # Restaurar o valor anterior + self.points_var.set(self.max_display) + return + + # Limpar canvas antes de replottar + self.canvas.delete("all") + + # Atualizar número máximo de pontos + self.max_display = new_points + + # Garantir que o início atual não ultrapasse o total de pontos + total_points = len(self.processed_data) + self.current_start = min(self.current_start, max(0, total_points - new_points)) + + # Replottar dados + self.plot_data() + + # Atualizar scrollbar + self.update_scrollbar() + + logger.info(f"Updated graph display to {new_points} points") + + except Exception as e: + logger.error(f"Error updating points display: {e}", exc_info=True) + messagebox.showerror("Update Error", str(e)) + + def on_mousewheel(self, event): + # Manipula a rolagem com o mouse + try: + # Obter a posição atual do scrollbar + total_points = len(self.processed_data) + + # Calcular novo índice inicial baseado na fração da scrollbar + max_start = max(0, total_points - self.max_display) + if event.delta > 0: + self.current_start = max(0, self.current_start - 10) + else: + self.current_start = min(max_start, self.current_start + 10) + + # Replottar com a nova posição + self.plot_data() + + logger.info(f"Scrolled to position {self.current_start}") + + except Exception as e: + logger.error(f"Error in mousewheel handling: {e}", exc_info=True) + + def on_hover(self, event): + try: + # Parâmetros de plotagem + width = self.canvas.winfo_width() + height = self.canvas.winfo_height() + margin_left = 50 + margin_bottom = 50 + plot_width = width - 100 + plot_height = height - 100 + + # Remover linhas de hover anteriores + self.canvas.delete('hover_line', 'hover_marker') + + # Verificar se o mouse está dentro da área do gráfico + if (event.x < margin_left or event.x > width - 50 or + event.y < 50 or event.y > height - margin_bottom): + self.hide_hover_label(event) + return + + # Dados processados + data = self.processed_data + end_idx = min(self.current_start + self.max_display, len(data)) + visible_data = data[self.current_start:end_idx] + + if not visible_data: + return + + # Calcular índice X baseado na posição relativa do mouse + mouse_x_rel = event.x - margin_left + x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width + x_index = int(mouse_x_rel / x_scale) + + if x_index < 0 or x_index >= len(visible_data): + return + + # Calcular valor Y + y_value = visible_data[x_index] + x_pos = self.current_start + x_index + + # Calcular coordenadas para linha vertical + line_x = margin_left + (x_index * x_scale) + + # Desenhar linha vertical + self.canvas.create_line( + line_x, 50, + line_x, height - margin_bottom, + fill='red', + dash=(4, 4), + tags='hover_line' + ) + + # Formatar valores + hex_x = format(x_pos * 2, 'X') if self.bits_mode.get() == "16b" else format(x_pos, 'X') + hex_y = format(y_value, 'X') + + # Atualizar label de hover + hover_text = ( + f"X: 0x{hex_x}\n" + f"Y: {y_value} (0x{hex_y})" + ) + + self.hover_label.config(text=hover_text) + + # Posicionar label próximo ao cursor + self.hover_label.place( + x=event.x_root - self.window.winfo_rootx() + 10, + y=event.y_root - self.window.winfo_rooty() + 10 + ) + self.hover_label.lift() + + except Exception as e: + logger.error(f"Erro no hover: {e}") + + def hide_hover_label(self, event): + # Esconder label quando mouse sai do gráfico + self.hover_label.place_forget() + # Remover linhas de hover + self.canvas.delete('hover_line', 'hover_marker') diff --git a/car_brands.txt b/car_brands.txt new file mode 100644 index 0000000..0fc7964 --- /dev/null +++ b/car_brands.txt @@ -0,0 +1,33 @@ +Alfa Romeo +Audi +BMW +Chrysler +Citroen +Dacia +Dodge +Fiat +Ford +Honda +Hyundai +Isuzu +Jeep +Kia +Lancia +Mercedes-Benz +Mercury +Mitsubishi +Nissan +Opel +Peugeot +Pontiac +Renault +Saab +Saturn +Seat +Skoda +Smart +Suzuki +Toyota +Vauxhall +Volkswagen +Volvo diff --git a/edc15_info_window.py b/edc15_info_window.py new file mode 100644 index 0000000..1e9352d --- /dev/null +++ b/edc15_info_window.py @@ -0,0 +1,532 @@ +import tkinter as tk +from tkinter import ttk, messagebox +import psycopg2 +from datetime import datetime +import logging +import re +import os + +class EDC15InfoWindow(tk.Toplevel): + def __init__(self, parent, filename, binary_data=None): + logging.info("Initializing EDC15InfoWindow") + try: + super().__init__(parent) + self.title("EDC15 File Information") + self.filename = filename + self.binary_data = binary_data + + # Flag to track if project is created + self.project_created = False + + # Flag to track if window was closed by user + self.window_closed = False + + # Add protocol for window close + self.protocol("WM_DELETE_WINDOW", self.on_closing) + + # Load ECU types and car brands from files + self.ecu_types = self.load_ecu_types() + self.car_brands = self.load_car_brands() + + # Load readout methods + self.readout_methods = self.load_readout_methods() + + # Hardware number prefixes + self.hw_prefixes = ['0281', '0265', '0255', '0275', '0235', '0285'] + + # Software number prefix + self.sw_prefix = '103' + + # Lists to store all found numbers + self.all_hw_numbers = [] + self.all_sw_numbers = [] + + # Configure window + self.geometry("800x600") + logging.info("EDC15InfoWindow geometry configured") + + # Create main frame + main_frame = ttk.Frame(self, padding="10") + main_frame.pack(fill="both", expand=True) + logging.info("EDC15InfoWindow main frame created") + + # Customer Section + customer_frame = ttk.LabelFrame(main_frame, text="Customer", padding="5") + customer_frame.pack(fill="x", padx=5, pady=5) + + ttk.Label(customer_frame, text="Name:").grid(row=0, column=0, sticky=tk.W) + self.name_var = tk.StringVar() + self.name_entry = ttk.Combobox(customer_frame, textvariable=self.name_var) + self.name_entry.grid(row=0, column=1, sticky=(tk.W, tk.E)) + + ttk.Label(customer_frame, text="Phone:").grid(row=1, column=0, sticky=tk.W) + self.phone_var = tk.StringVar() + ttk.Entry(customer_frame, textvariable=self.phone_var).grid(row=1, column=1, sticky=(tk.W, tk.E)) + + ttk.Label(customer_frame, text="License Plate:").grid(row=2, column=0, sticky=tk.W) + self.license_var = tk.StringVar() + ttk.Entry(customer_frame, textvariable=self.license_var).grid(row=2, column=1, sticky=(tk.W, tk.E)) + + # Car Section with Comments + car_frame = ttk.LabelFrame(main_frame, text="Car", padding="5") + car_frame.pack(fill="x", padx=5, pady=5) + + # Left side - Car details + car_left_frame = ttk.Frame(car_frame) + car_left_frame.pack(side="left", fill="x", expand=True, padx=5) + + # Brand + ttk.Label(car_left_frame, text="Brand:").grid(row=0, column=0, sticky="w", padx=5, pady=2) + self.brand_var = tk.StringVar() + self.brand_combo = ttk.Combobox(car_left_frame, textvariable=self.brand_var, values=self.car_brands) + self.brand_combo.grid(row=0, column=1, sticky="ew", padx=5, pady=2) + self.brand_combo.bind('<>', self.on_brand_selected) + + # Model + ttk.Label(car_left_frame, text="Model:").grid(row=1, column=0, sticky="w", padx=5, pady=2) + self.model_var = tk.StringVar() + self.model_combo = ttk.Combobox(car_left_frame, textvariable=self.model_var) + self.model_combo.grid(row=1, column=1, sticky="ew", padx=5, pady=2) + + # Engine + ttk.Label(car_left_frame, text="Engine:").grid(row=2, column=0, sticky="w", padx=5, pady=2) + self.engine_var = tk.StringVar() + ttk.Entry(car_left_frame, textvariable=self.engine_var).grid(row=2, column=1, sticky="ew", padx=5, pady=2) + + # HP + ttk.Label(car_left_frame, text="HP:").grid(row=3, column=0, sticky="w", padx=5, pady=2) + self.hp_var = tk.StringVar() + ttk.Entry(car_left_frame, textvariable=self.hp_var).grid(row=3, column=1, sticky="ew", padx=5, pady=2) + + # KW + ttk.Label(car_left_frame, text="KW:").grid(row=4, column=0, sticky="w", padx=5, pady=2) + self.kw_var = tk.StringVar() + ttk.Entry(car_left_frame, textvariable=self.kw_var).grid(row=4, column=1, sticky="ew", padx=5, pady=2) + + # Year + year_label = tk.Label(car_left_frame, text="Year:") + year_label.grid(row=5, column=0, sticky='w', padx=5, pady=2) + self.year_var = tk.StringVar() + self.year_combo = ttk.Combobox(car_left_frame, textvariable=self.year_var, state="readonly", width=20) + self.year_combo['values'] = list(map(str, range(1993, 2009))) # Years from 1993 to 2008 + self.year_combo.grid(row=5, column=1, sticky='w', padx=5, pady=2) + self.year_combo.set('') # Default empty selection + + # VIN + ttk.Label(car_left_frame, text="VIN:").grid(row=6, column=0, sticky="w", padx=5, pady=2) + self.vin_var = tk.StringVar() + ttk.Entry(car_left_frame, textvariable=self.vin_var).grid(row=6, column=1, sticky="ew", padx=5, pady=2) + + # Transmission + transmission_label = tk.Label(car_left_frame, text="Transmission:") + transmission_label.grid(row=7, column=0, sticky='w', padx=5, pady=2) + self.transmission_var = tk.StringVar() + self.transmission_combo = ttk.Combobox(car_left_frame, textvariable=self.transmission_var, state="readonly", width=20) + self.transmission_combo['values'] = ['Manual', 'Automatic'] + self.transmission_combo.grid(row=7, column=1, sticky='w', padx=5, pady=2) + self.transmission_combo.set('') # Default empty selection + + # Right side - Comments + car_right_frame = ttk.LabelFrame(car_frame, text="Comments", padding="5") + car_right_frame.pack(side="right", fill="both", expand=True, padx=5) + + self.comment_text = tk.Text(car_right_frame, height=10, width=30, wrap=tk.WORD) + comment_scrollbar = ttk.Scrollbar(car_right_frame, orient="vertical", command=self.comment_text.yview) + self.comment_text.configure(yscrollcommand=comment_scrollbar.set) + + self.comment_text.pack(side="left", fill="both", expand=True) + comment_scrollbar.pack(side="right", fill="y") + + # ECU and Project Section + ecu_project_frame = ttk.LabelFrame(main_frame, text="ECU and Project", padding="5") + ecu_project_frame.pack(fill="x", padx=5, pady=5) + + # Create a frame to hold ECU and Project side by side + ecu_project_inner_frame = ttk.Frame(ecu_project_frame) + ecu_project_inner_frame.pack(fill="x", expand=True) + + # Left side - ECU details + ecu_left_frame = ttk.Frame(ecu_project_inner_frame) + ecu_left_frame.pack(side="left", fill="x", expand=True, padx=5) + + ttk.Label(ecu_left_frame, text="ECU Type:").grid(row=0, column=0, sticky="w", padx=5, pady=2) + self.ecu_type_var = tk.StringVar() + self.ecu_type_combo = ttk.Combobox(ecu_left_frame, textvariable=self.ecu_type_var, values=self.ecu_types) + self.ecu_type_combo.grid(row=0, column=1, sticky="ew", padx=5, pady=2) + + ttk.Label(ecu_left_frame, text="Hardware:").grid(row=1, column=0, sticky="w", padx=5, pady=2) + self.hardware_var = tk.StringVar() + ttk.Entry(ecu_left_frame, textvariable=self.hardware_var).grid(row=1, column=1, sticky="ew", padx=5, pady=2) + + ttk.Label(ecu_left_frame, text="Software:").grid(row=2, column=0, sticky="w", padx=5, pady=2) + self.software_var = tk.StringVar() + ttk.Entry(ecu_left_frame, textvariable=self.software_var).grid(row=2, column=1, sticky="ew", padx=5, pady=2) + + # Right side - Project details + project_right_frame = ttk.Frame(ecu_project_inner_frame) + project_right_frame.pack(side="right", fill="x", expand=True, padx=5) + + ttk.Label(project_right_frame, text="Type:").grid(row=0, column=0, sticky="w", padx=5, pady=2) + self.project_type_var = tk.StringVar() + self.project_type_combo = ttk.Combobox(project_right_frame, textvariable=self.project_type_var) + self.project_type_combo['values'] = [ + 'BOOST CALIBRATION', + 'COLD START', + 'DPF REMOVAL', + 'DTC REMOVAL', + 'EGR REMOVAL', + 'EXHAUST FLAP', + 'FLAPS REMOVAL', + 'HOT START', + 'IMMO REMOVAL', + 'LAMBDA', + 'LAUNCH CONTROL', + 'MAF REMOVAL', + 'SPEED LIMITER REMOVAL', + 'TUNING', + 'TVA REMOVAL' + ] + self.project_type_combo.grid(row=0, column=1, sticky="ew", padx=5, pady=2) + self.project_type_combo.config(state="readonly") + + ttk.Label(project_right_frame, text="State:").grid(row=1, column=0, sticky="w", padx=5, pady=2) + self.state_var = tk.StringVar(value="NEW") + self.state_combo = ttk.Combobox(project_right_frame, textvariable=self.state_var) + self.state_combo['values'] = [ + 'NEW', + 'DEVELOPING', + 'TESTING', + 'FINISH' + ] + self.state_combo.grid(row=1, column=1, sticky="ew", padx=5, pady=2) + self.state_combo.config(state="readonly") + + ttk.Label(project_right_frame, text="Readout by:").grid(row=2, column=0, sticky="w", padx=5, pady=2) + self.readout_var = tk.StringVar() + self.readout_combo = ttk.Combobox(project_right_frame, textvariable=self.readout_var, values=self.readout_methods) + self.readout_combo.grid(row=2, column=1, sticky="ew", padx=5, pady=2) + self.readout_combo.config(state="readonly") + + # Buttons at the bottom + button_frame = ttk.Frame(main_frame) + button_frame.pack(fill="x", padx=5, pady=10) + + ttk.Button(button_frame, text="Create Project", command=self.create_project).pack(side=tk.LEFT, padx=5) + ttk.Button(button_frame, text="Cancel", command=self.destroy).pack(side=tk.RIGHT, padx=5) + + # Find hardware and software numbers if binary data is available + if self.binary_data: + self.find_all_numbers() + self.update_comment_with_extra_numbers() + + except Exception as e: + logging.error(f"Error initializing EDC15InfoWindow: {str(e)}") + + def load_ecu_types(self): + """Load ECU types from list_EDC15.txt file""" + try: + with open('list_EDC15.txt', 'r') as file: + return [line.strip() for line in file if line.strip()] + except Exception as e: + logging.error(f"Error loading ECU types: {str(e)}") + return [] + + def load_car_brands(self): + """Load car brands from car_brands.txt""" + try: + brands_file = os.path.join(os.path.dirname(__file__), 'car_brands.txt') + if os.path.exists(brands_file): + with open(brands_file, 'r') as f: + brands = [line.strip() for line in f if line.strip()] + logging.info(f"Loaded {len(brands)} car brands") + return sorted(brands) + else: + logging.warning("car_brands.txt not found") + return [] + except Exception as e: + logging.error(f"Error loading car brands: {str(e)}") + return [] + + def load_readout_methods(self): + """Load readout methods from readout.txt file""" + try: + with open(os.path.join(os.path.dirname(__file__), 'readout.txt'), 'r') as f: + return [line.strip() for line in f if line.strip()] + except FileNotFoundError: + logging.warning("readout.txt file not found") + return [] + except Exception as e: + logging.error(f"Error reading readout.txt: {str(e)}") + return [] + + def find_all_numbers(self): + """Find all hardware and software numbers in binary data""" + try: + if not self.binary_data: + return + + # Convert binary data to ASCII string + ascii_data = self.binary_data.decode('ascii', errors='ignore') + + # Find hardware numbers + hw_pattern = '(' + '|'.join(self.hw_prefixes) + ')[0-9]{5}' + hw_matches = re.finditer(hw_pattern, ascii_data) + + # Store all hardware numbers found + for match in hw_matches: + number = match.group(0) + if len(number) == 9 and number.isdigit(): + self.all_hw_numbers.append(number) + + # Set first hardware number found to the hardware field + if self.all_hw_numbers: + self.hardware_var.set(self.all_hw_numbers[0]) + logging.info(f"Found primary hardware number: {self.all_hw_numbers[0]}") + + # Find software numbers + sw_pattern = f'{self.sw_prefix}[0-9]{{6}}' + sw_matches = re.finditer(sw_pattern, ascii_data) + + # Store all software numbers found + for match in sw_matches: + number = match.group(0) + if len(number) == 9 and number.isdigit(): + self.all_sw_numbers.append(number) + + # Set first software number found to the software field + if self.all_sw_numbers: + self.software_var.set(self.all_sw_numbers[0]) + logging.info(f"Found primary software number: {self.all_sw_numbers[0]}") + + except Exception as e: + logging.error(f"Error finding numbers: {str(e)}") + + def update_comment_with_extra_numbers(self): + """Update comment field with additional numbers found, excluding the ones already shown in hardware/software fields""" + try: + # Get the current hardware and software numbers from the fields + current_hw = self.hardware_var.get() + current_sw = self.software_var.get() + + # Filter out numbers that are already shown in the fields + extra_hw_numbers = [num for num in self.all_hw_numbers if num != current_hw] + extra_sw_numbers = [num for num in self.all_sw_numbers if num != current_sw] + + if extra_hw_numbers or extra_sw_numbers: + comment = "Additional numbers found:\n" + + if extra_hw_numbers: + comment += "\nHardware numbers:\n" + comment += "\n".join(extra_hw_numbers) + + if extra_sw_numbers: + comment += "\nSoftware numbers:\n" + comment += "\n".join(extra_sw_numbers) + + self.comment_text.delete('1.0', tk.END) + self.comment_text.insert('1.0', comment) + logging.info("Updated comment with extra numbers") + + except Exception as e: + logging.error(f"Error updating comment with extra numbers: {str(e)}") + + def on_brand_selected(self, event=None): + """Update model list when brand is selected""" + try: + selected_brand = self.brand_var.get() + if selected_brand: + # Convert brand name to filename format + filename_brand = selected_brand + if selected_brand == "Citroen": + filename_brand = "Citroen" + elif selected_brand == "Mercedes-Benz": + filename_brand = "Mercedes" + + # Construct path to model file + model_file = os.path.join(os.path.dirname(__file__), 'Model_car', f'{filename_brand}.txt') + + if os.path.exists(model_file): + # Load models from file + with open(model_file, 'r') as f: + models = [line.strip() for line in f if line.strip()] + + # Update model combobox + self.model_combo['values'] = sorted(models) + logging.info(f"Loaded {len(models)} models for {selected_brand}") + else: + self.model_combo['values'] = [] + logging.warning(f"No model file found for {selected_brand}") + + # Clear current model selection + self.model_var.set('') + + except Exception as e: + logging.error(f"Error loading models for {selected_brand}: {str(e)}") + self.model_combo['values'] = [] + + def save_data(self): + try: + conn = psycopg2.connect( + dbname="hexaw", + user="postgres", + password="postgres", + host="localhost" + ) + cur = conn.cursor() + + # Create table if it doesn't exist (removed producer field) + cur.execute(""" + CREATE TABLE IF NOT EXISTS edc15_files ( + id SERIAL PRIMARY KEY, + filename TEXT, + customer_name TEXT, + customer_phone TEXT, + license_plate TEXT, + car_brand TEXT, + car_model TEXT, + car_engine TEXT, + car_hp TEXT, + car_kw TEXT, + car_year TEXT, + car_vin TEXT, + car_transmission TEXT, + ecu_type TEXT, + ecu_part TEXT, + ecu_software TEXT, + ecu_hardware TEXT, + project_type TEXT, + project_state TEXT, + readout_by TEXT, + comment TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + + # Insert data (removed producer field) + cur.execute(""" + INSERT INTO edc15_files ( + filename, customer_name, customer_phone, license_plate, + car_brand, car_model, car_engine, car_hp, car_kw, + car_year, car_vin, car_transmission, + ecu_type, ecu_part, ecu_software, ecu_hardware, + project_type, project_state, readout_by, + comment + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, + %s + ) + """, ( + self.filename, + self.name_var.get(), + self.phone_var.get(), + self.license_var.get(), + self.brand_var.get(), + self.model_var.get(), + self.engine_var.get(), + self.hp_var.get(), + self.kw_var.get(), + self.year_combo.get(), + self.vin_var.get(), + self.transmission_combo.get(), + self.ecu_type_var.get(), + '', + self.software_var.get(), + self.hardware_var.get(), + self.project_type_var.get(), + self.state_var.get(), + self.readout_var.get(), + self.comment_text.get("1.0", tk.END).strip() + )) + + conn.commit() + messagebox.showinfo("Success", "Data saved successfully!") + self.destroy() + + except Exception as e: + messagebox.showerror("Error", f"Failed to save data: {str(e)}") + logging.error(f"Database error: {str(e)}") + finally: + if 'cur' in locals(): + cur.close() + if 'conn' in locals(): + conn.close() + + def create_project(self): + try: + # Validate required fields before creating project + if not all([ + self.name_var.get().strip(), + self.brand_combo.get(), + self.model_combo.get(), + self.project_type_combo.get(), + self.state_combo.get(), + self.readout_combo.get() + ]): + messagebox.showwarning("Incomplete Information", "Please fill in all required fields.") + return + + # Existing project creation logic + conn = psycopg2.connect( + dbname="hexawork", + user="postgres", + password="postgres", + host="localhost" + ) + cur = conn.cursor() + + cur.execute(""" + INSERT INTO edc15_files ( + filename, customer_name, customer_phone, license_plate, + car_brand, car_model, car_engine, car_hp, car_kw, + car_year, car_vin, car_transmission, + ecu_type, ecu_part, ecu_software, ecu_hardware, + project_type, project_state, readout_by, + comment + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, + %s, %s, %s, %s, %s, %s, %s, %s + ) + """, ( + self.filename, + self.name_var.get(), + self.phone_var.get(), + self.license_var.get(), + self.brand_combo.get(), + self.model_combo.get(), + self.engine_var.get(), + self.hp_var.get(), + self.kw_var.get(), + self.year_combo.get(), + self.vin_var.get(), + self.transmission_combo.get(), + self.ecu_type_var.get(), + '', + self.software_var.get(), + self.hardware_var.get(), + self.project_type_var.get(), + self.state_var.get(), + self.readout_var.get(), + self.comment_text.get("1.0", tk.END).strip() + )) + + conn.commit() + + # Set project created flag + self.project_created = True + + messagebox.showinfo("Success", "Project created successfully!") + self.destroy() + + except Exception as e: + logging.error(f"Error creating project: {str(e)}") + messagebox.showerror("Error", f"Failed to create project: {str(e)}") + finally: + if 'conn' in locals(): + cur.close() + conn.close() + + def on_closing(self): + """Handle window closing event""" + self.window_closed = True + self.destroy() diff --git a/flash_window.py b/flash_window.py new file mode 100644 index 0000000..798127b --- /dev/null +++ b/flash_window.py @@ -0,0 +1,305 @@ +import tkinter as tk +from tkinter import filedialog, messagebox, ttk, scrolledtext +from view_parameters import ViewParametersDialog +from edc15_info_window import EDC15InfoWindow +import struct +import os +import logging +from logger import logger # Use the centralized logger + +# Create a logger +# logger = logging.getLogger(__name__) + +class FlashWindow: + def __init__(self, master, main_window): + try: + logger.info("Initializing FlashWindow") + + # Validate input + if master is None: + raise ValueError("Master window cannot be None") + + # Store references + self.parent = master + self.main_window = main_window # Referência à janela principal + + # Create window + self.window = tk.Toplevel(master) + self.window.title("Flash File - EDC15") + self.window.geometry("800x600") + + # Minimizar janela principal quando abrir Flash + self.main_window.iconify() + + # Configurar para restaurar janela principal quando fechar + self.window.protocol("WM_DELETE_WINDOW", self.on_close) + + # Add a frame to the new window + self.flash_frame = ttk.Frame(self.window) + self.flash_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True) + + # Create buttons frame + self.buttons_frame = ttk.Frame(self.flash_frame) + self.buttons_frame.pack(fill=tk.X, pady=(0, 10)) + + # Add Open and Save buttons + ttk.Button(self.buttons_frame, text="New Project", command=self.open_file).pack(side=tk.LEFT, padx=5) + ttk.Button(self.buttons_frame, text="Save File", command=self.save_file).pack(side=tk.LEFT, padx=5) + ttk.Button(self.buttons_frame, text="Parâmetros", command=self.show_parameters).pack(side=tk.LEFT, padx=5) + + # Botão para alternar entre Hexa e Decimal + self.btn_view_mode = ttk.Button(self.buttons_frame, text="Hexa", command=self.toggle_view_mode) + self.btn_view_mode.pack(side=tk.LEFT, padx=5) + + # Botão 2D + self.btn_2d_view = ttk.Button(self.buttons_frame, text="2D", command=self.open_2d_graph) + self.btn_2d_view.pack(side=tk.LEFT, padx=5) + + # File path label + self.file_path = tk.StringVar() + self.file_path_label = ttk.Label(self.flash_frame, textvariable=self.file_path, wraplength=780) + self.file_path_label.pack(fill=tk.X, pady=5) + + # Create text area for binary content + self.content_frame = ttk.LabelFrame(self.flash_frame, text="Binary Content") + self.content_frame.pack(fill=tk.BOTH, expand=True, pady=5) + + self.text_area = scrolledtext.ScrolledText(self.content_frame, wrap=tk.WORD, width=80, height=20) + self.text_area.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + self.text_area.config(font=('Courier', 10)) + + # Store binary data and view parameters + self.binary_data = None + self.view_params = { + 'first_cell': '0000F0', + 'offset': 0, + 'columns': 16, + 'hex_view': False, + 'show_diff': True, + 'value_type': '8bit', + 'address_type': '8bit' + } + + logger.info("FlashWindow initialized successfully") + + except Exception as e: + logger.error(f"Error initializing FlashWindow: {e}", exc_info=True) + messagebox.showerror("Initialization Error", str(e)) + if hasattr(self, 'window'): + self.window.destroy() + + def format_value(self, data, start_idx, value_type, hex_view): + try: + # Primeiro, garantir que temos bytes suficientes + if value_type == '8bit' and len(data) < 1: + return "?" * (2 if hex_view else 3) + elif value_type == '16bit' and len(data) < 2: + return "?" * (4 if hex_view else 5) + elif (value_type == '32bit' or value_type == 'float') and len(data) < 4: + return "?" * (8 if hex_view else 10) + + # Agora processar o valor + if value_type == '8bit': + value = data[0] + if hex_view: + return f"{value:02X}" + return f"{value:03d}" + + elif value_type == '16bit': + value = struct.unpack('>H', data[:2])[0] # big-endian + if hex_view: + return f"{value:04X}" + return f"{value:05d}" + + elif value_type == '32bit': + value = struct.unpack('>I', data[:4])[0] # big-endian + if hex_view: + return f"{value:08X}" + return f"{value:010d}" + + elif value_type == 'float': + value = struct.unpack('>f', data[:4])[0] # big-endian + return f"{value:10.4f}" + + except (IndexError, struct.error) as e: + return "?" * (2 if hex_view else 3) + + def get_value_size(self, value_type): + if value_type == '8bit': + return 1 + elif value_type == '16bit': + return 2 + else: # 32bit ou float + return 4 + + def show_parameters(self): + dialog = ViewParametersDialog(self.window, self.view_params) + result = dialog.show() + if result: + self.view_params = result + self.display_binary_data() + + def open_file(self): + """Open and display a binary file""" + try: + file_path = filedialog.askopenfilename( + filetypes=[("Binary files", "*.bin"), ("All files", "*.*")] + ) + + if file_path: + logger.info(f"Opening file in Flash window: {file_path}") + + # Read file data + with open(file_path, 'rb') as file: + self.binary_data = file.read() + + # Show EDC15 info window with binary data + info_window = EDC15InfoWindow(self.window, os.path.basename(file_path), self.binary_data) + info_window.transient(self.window) + info_window.grab_set() + self.window.wait_window(info_window) + + # Check if project was created or window was closed + if not info_window.project_created or info_window.window_closed: + # Reset binary data and file path if project not created + self.binary_data = None + self.file_path.set("") + self.text_area.delete('1.0', tk.END) + return + + # Display file content + self.file_path.set(f"Selected file: {file_path}") + self.display_binary_data() + + except Exception as e: + logger.error(f"Error opening file: {e}", exc_info=True) + messagebox.showerror("Error", f"Error opening file: {e}") + + def display_binary_data(self): + if self.binary_data: + self.text_area.delete('1.0', tk.END) + + # Get parameters + bytes_per_row = self.view_params['columns'] + start_offset = self.view_params['offset'] + hex_view = self.view_params['hex_view'] + value_type = self.view_params['value_type'] + + # Update frame title + mode_text = "Hexadecimal" if hex_view else "Decimal" + value_text = f"{value_type} values" + if value_type == 'float': + value_text = "Floating point values" + self.content_frame.configure(text=f"Binary Content - {mode_text} ({value_text})") + + # Calculate display range + display_data = self.binary_data[start_offset:] + + # Get the size of each value based on type + value_size = self.get_value_size(value_type) + values_per_row = bytes_per_row // value_size + + for i in range(0, len(display_data), bytes_per_row): + # Format offset (from the first_cell value) + base_offset = int(self.view_params['first_cell'], 16) + current_offset = base_offset + i + offset = f"{current_offset:08X}" + + # Get row data + row_data = display_data[i:i + bytes_per_row] + + # Format values + values = [] + ascii_values = [] + + # Process each value in the row + for j in range(0, len(row_data), value_size): + value_data = row_data[j:j + value_size] + if len(value_data) == value_size: + # Format the value according to type + value_str = self.format_value(value_data, j, value_type, hex_view) + values.append(value_str) + + # ASCII representation (only for 8-bit values) + if value_type == '8bit': + b = value_data[0] + ascii_values.append(chr(b) if 32 <= b <= 126 else '.') + else: + ascii_values.extend(['.' for _ in range(value_size)]) + + # Join values and ASCII + values_part = " ".join(values) + ascii_part = "".join(ascii_values) + + # Add the line to display + line = f"{offset}: {values_part} | {ascii_part}\n" + self.text_area.insert(tk.END, line) + + self.text_area.see('1.0') # Scroll to top + + def save_file(self): + if not self.binary_data: + messagebox.showwarning("Warning", "No data to save. Please open a file first.") + return + + file_path = filedialog.asksaveasfilename( + title="Save Binary File", + defaultextension=".bin", + filetypes=[ + ("Binary files", "*.bin"), + ("All files", "*.*") + ] + ) + if file_path: + try: + with open(file_path, 'wb') as f: + f.write(self.binary_data) + self.file_path.set(f"File saved to: {file_path}") + except Exception as e: + messagebox.showerror("Error", f"Error saving file: {str(e)}") + + def open_2d_graph(self): + try: + # Ensure binary data is available + if not hasattr(self, 'binary_data') or self.binary_data is None: + logger.warning("No binary data available to plot graph") + messagebox.showwarning("Graph Error", "No binary data available to plot.") + return + + # Import BinaryGraphWindow dynamically to avoid circular imports + from binary_graph import BinaryGraphWindow + + # Convert binary data to byte values if not already done + # Ensure we're passing a list of integers + byte_values = [int(byte) for byte in self.binary_data] + + # Open binary graph window + graph_window = BinaryGraphWindow(self.window, byte_values) + + logger.info("Binary graph window opened successfully") + + except ImportError as e: + logger.error(f"Failed to import BinaryGraphWindow: {e}") + messagebox.showerror("Import Error", "Could not load graph module.") + + except Exception as e: + logger.error(f"Error opening binary graph: {e}", exc_info=True) + messagebox.showerror("Graph Error", f"Could not open graph: {e}") + + def toggle_view_mode(self): + # Alternar entre hexadecimal e decimal + current_mode = self.view_params.get('hex_view', False) + self.view_params['hex_view'] = not current_mode + + # Atualizar texto do botão + self.btn_view_mode.config(text="Decimal" if current_mode else "Hexa") + + # Redesenhar dados + self.display_binary_data() + + def on_close(self): + # Restaurar janela principal + self.main_window.deiconify() + + # Fechar janela Flash + self.window.destroy() diff --git a/hexawork.log b/hexawork.log new file mode 100644 index 0000000..ba06621 --- /dev/null +++ b/hexawork.log @@ -0,0 +1,10 @@ +2024-12-06 17:32:00,125 - hexaw_logger - INFO - Initializing FlashWindow +2024-12-06 17:32:00,140 - hexaw_logger - INFO - FlashWindow initialized successfully +2024-12-06 17:32:03,784 - hexaw_logger - INFO - Opening file in Flash window: C:/Users/paulo/Py_project/HexaW/Files/AUDI58B0.BIN +2024-12-06 17:32:03,784 - root - INFO - Initializing EDC15InfoWindow +2024-12-06 17:32:03,785 - root - INFO - Loaded 33 car brands +2024-12-06 17:32:03,785 - root - INFO - EDC15InfoWindow geometry configured +2024-12-06 17:32:03,787 - root - INFO - EDC15InfoWindow main frame created +2024-12-06 17:32:03,803 - root - INFO - Found primary hardware number: 028101046 +2024-12-06 17:32:03,803 - root - INFO - Found primary software number: 103736369 +2024-12-06 17:32:13,579 - hexaw_logger - WARNING - No binary data available to plot graph