Carregar ficheiros para "/"
This commit is contained in:
492
binary_graph.py
Normal file
492
binary_graph.py
Normal file
@ -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('<Motion>', self.on_hover)
|
||||||
|
self.canvas.bind('<Leave>', 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('<Configure>', self.on_resize)
|
||||||
|
|
||||||
|
# Configurar scrolling com o mouse
|
||||||
|
self.canvas.bind('<MouseWheel>', 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')
|
||||||
33
car_brands.txt
Normal file
33
car_brands.txt
Normal file
@ -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
|
||||||
532
edc15_info_window.py
Normal file
532
edc15_info_window.py
Normal file
@ -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('<<ComboboxSelected>>', 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()
|
||||||
305
flash_window.py
Normal file
305
flash_window.py
Normal file
@ -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()
|
||||||
10
hexawork.log
Normal file
10
hexawork.log
Normal file
@ -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
|
||||||
Reference in New Issue
Block a user