Files
rslogger-merger/main.py
2025-10-09 08:48:40 -05:00

253 lines
9.5 KiB
Python

import re
import csv
import tkinter as tk
from tkinter import filedialog, messagebox
import subprocess
import os
from datetime import datetime
import shutil
import platform
try:
from openpyxl import load_workbook
XLSX_AVAILABLE = True
except ImportError:
XLSX_AVAILABLE = False
def parse_logs(log_text):
lines = log_text.splitlines()
if lines and '=~' in lines[0]:
lines = lines[1:]
pairs = []
i = 0
while i < len(lines):
line = lines[i].strip()
if not line:
i += 1
continue
# Updated regex to capture any units (lb, kg, g, etc.)
match = re.match(r'^GROSS\s+(\d+)\s*([a-zA-Z]+)$', line)
if match:
weight = int(match.group(1))
units = match.group(2)
i += 2 # Skip to timestamp line
if i < len(lines):
ts_line = lines[i].strip()
if re.match(r'^\d{2}:\d{2} (AM|PM) \d{2}/\d{2}/\d{2}$', ts_line):
# Convert timestamp to Excel-friendly 12-hour format
try:
dt = datetime.strptime(ts_line, '%I:%M %p %m/%d/%y')
excel_ts = dt.strftime('%m/%d/%Y %I:%M %p')
pairs.append((weight, units, excel_ts))
print((weight, units, excel_ts))
except ValueError:
i += 1
continue
i += 1 # Move to next weight entry
else:
i += 1
else:
i += 1
return pairs
def write_csv1(pairs, filename):
with open(filename, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['WEIGHT', 'UNITS', 'TIME'])
for weight, units, timestamp in pairs:
writer.writerow([weight, units, timestamp])
def write_csv2(pairs, filename):
with open(filename, 'w', newline='') as f:
writer = csv.writer(f)
writer.writerow(['GROSS_WT', 'TARE_WT', 'NET_WT', 'GROSS_T', 'TARE_T', 'GROSS_UNITS', 'TARE_UNITS', 'NET_UNITS'])
for j in range(0, len(pairs), 2):
if j + 1 < len(pairs):
gross_weight, gross_units, gross_time = pairs[j]
tare_weight, tare_units, tare_time = pairs[j + 1]
net = gross_weight - tare_weight
net_units = tare_units
if (tare_units != gross_units):
net_units = 'MISMATCH'
net = 'UNIT MISMATCH'
writer.writerow([gross_weight, tare_weight, net, gross_time, tare_time, gross_units, tare_units, net_units])
if (len(pairs) % 2): # if odd number of items
gross_weight, gross_units, gross_time = pairs[-1]
writer.writerow([gross_weight, '', '', gross_time, '', gross_units, '', ''])
def write_xlsx(pairs, output_filename, template_path='template.xlsx'):
"""Write sequential data to XLSX file using template"""
if not XLSX_AVAILABLE:
raise ImportError("openpyxl is required for XLSX export. Install with: pip install openpyxl")
if not os.path.exists(template_path):
raise FileNotFoundError(f"Template file not found: {template_path}")
# Load the template workbook (data_only=False preserves formulas)
wb = load_workbook(template_path, data_only=False)
# Check if SEQUENTIAL sheet exists
if 'SEQUENTIAL' not in wb.sheetnames:
raise ValueError("Template does not contain a 'SEQUENTIAL' sheet")
# Get the SEQUENTIAL sheet
ws = wb['SEQUENTIAL']
# Clear existing data (starting from row 2, assuming row 1 has headers)
# First, delete all rows below the header
if ws.max_row > 1:
ws.delete_rows(2, ws.max_row - 1)
# Write new data starting from row 2
for row_idx, (weight, units, timestamp) in enumerate(pairs, start=2):
ws.cell(row=row_idx, column=1, value=weight)
ws.cell(row=row_idx, column=2, value=units)
ws.cell(row=row_idx, column=3, value=timestamp)
# Set COMBINED as the active sheet (default sheet when opened)
if 'COMBINED' in wb.sheetnames:
wb.active = wb.sheetnames.index('COMBINED')
# Save to new filename (never overwrite template)
wb.save(output_filename)
def show_files_in_explorer(files):
"""Open file explorer and highlight the generated files"""
if not files:
return
# Get the directory of the first file
directory = os.path.dirname(os.path.abspath(files[0]))
system = platform.system()
if system == 'Windows':
# On Windows, use explorer with /select to highlight the file
# If multiple files, just select the first one
subprocess.run(['explorer', '/select,', os.path.abspath(files[0])])
elif system == 'Darwin': # macOS
# On Mac, use 'open' with -R to reveal in Finder
subprocess.run(['open', '-R', os.path.abspath(files[0])])
else: # Linux and others
# On Linux, just open the directory
subprocess.run(['xdg-open', directory])
def run_update_script():
try:
# Run update.sh script
result = subprocess.run(['sh', 'C:\\Program Files\\rslogger-merger\\update.sh'], capture_output=True, text=True, cwd=os.getcwd())
if result.returncode == 0:
messagebox.showinfo("Success", f"update.sh executed successfully!\n\nOutput:\n{result.stdout}")
else:
messagebox.showerror("Error", f"update.sh failed with return code {result.returncode}\n\nError:\n{result.stderr}")
except FileNotFoundError:
messagebox.showerror("Error", "update.sh not found in current directory")
except Exception as e:
messagebox.showerror("Error", f"Failed to run update.sh: {str(e)}")
def select_input_file():
filename = filedialog.askopenfilename(filetypes=[("Text files", "*.txt"), ("All files", "*.*")])
if filename:
input_file_var.set(filename)
def process_files():
input_file = input_file_var.get()
if not input_file:
messagebox.showerror("Error", "Please select an input file.")
return
generate_csv = csv_var.get()
generate_xlsx = xlsx_var.get()
if not generate_csv and not generate_xlsx:
messagebox.showerror("Error", "Please select at least one output format (CSV or XLSX).")
return
# Auto-generate output filenames
base = os.path.splitext(input_file)[0]
output_file1 = f"{base}.sequential.csv"
output_file2 = f"{base}.joined.csv"
output_xlsx = f"{base}.xlsx"
generated_files = []
error_messages = []
try:
with open(input_file, 'r') as f:
log_text = f.read()
pairs = parse_logs(log_text)
# Generate CSV files if checked
if generate_csv:
try:
write_csv1(pairs, output_file1)
generated_files.append(output_file1)
write_csv2(pairs, output_file2)
generated_files.append(output_file2)
except Exception as e:
error_messages.append(f"CSV export failed: {str(e)}")
# Generate XLSX file if checked
if generate_xlsx:
try:
write_xlsx(pairs, output_xlsx)
generated_files.append(output_xlsx)
except ImportError as e:
error_messages.append(f"XLSX export skipped: {str(e)}")
except Exception as e:
error_messages.append(f"XLSX export failed: {str(e)}")
# Build success message
if generated_files:
files_list = "\n".join(generated_files)
error_info = ""
if error_messages:
error_info = "\n\nWarnings:\n" + "\n".join(error_messages)
# Custom dialog with "Show the files" button
response = messagebox.askquestion("Success",
f"Files generated:\n{files_list}{error_info}\n\nShow the files?",
icon='info')
if response == 'yes':
show_files_in_explorer(generated_files)
else:
messagebox.showerror("Error", "No files were generated.\n\n" + "\n".join(error_messages))
except Exception as e:
messagebox.showerror("Error", f"An error occurred: {str(e)}")
root = tk.Tk()
root.title("Log to CSV Converter")
root.geometry("500x200")
input_file_var = tk.StringVar()
csv_var = tk.BooleanVar(value=False) # CSV unchecked by default
xlsx_var = tk.BooleanVar(value=True) # XLSX checked by default
# Input file selection
input_frame = tk.Frame(root)
input_frame.pack(pady=5, padx=10, fill='x')
tk.Label(input_frame, text="Select Input Log File:").pack(side='left')
tk.Entry(input_frame, textvariable=input_file_var, width=40).pack(side='left', padx=5)
tk.Button(input_frame, text="Browse Input", bg="#2196F3", command=select_input_file).pack(side='left')
# Output format checkboxes
format_frame = tk.Frame(root)
format_frame.pack(pady=5, padx=10, fill='x')
tk.Label(format_frame, text="Output Formats:").pack(side='left')
tk.Checkbutton(format_frame, text="CSV", variable=csv_var).pack(side='left', padx=10)
tk.Checkbutton(format_frame, text="XLSX", variable=xlsx_var).pack(side='left', padx=10)
# Convert button
tk.Button(root, text="CONVERT", command=process_files, font=("Arial", 14), relief="raised", bg="#4CAF50", fg="white").pack(pady=10, padx=10, fill='x')
# Update script button
tk.Button(root, text="Update this Application", command=run_update_script, font=("Arial", 12), relief="raised").pack(pady=5, padx=10, fill='x')
root.mainloop()