#!/usr/bin/env python3 import pyvisa import time import threading import tkinter as tk from tkinter import ttk, messagebox import webbrowser import os import glob VERSION = "1.0 GUI" TOOL_NAME = "AM Level FE - HP8648B/C/D" TOOL_VERSION = f"{TOOL_NAME} v{VERSION}" AUTHOR = "Robert / YO4HFU, 2025" LOG_FILE = "AM_Level_FE_log.txt" DELAY = 0.05 DEFAULT_GPIB_NUMBER = 19 # ------------------ Utility ------------------ def clear_log_file(): with open(LOG_FILE, "w", encoding="utf-8") as f: f.write(f"{TOOL_NAME}\nVersion: {VERSION}\nAuthor: {AUTHOR}\n{'='*60}\n") def log_to_file(msg: str): with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(f"{msg}\n") # ------------------ Backend Instrument Control ------------------ class HP8648FEController: def __init__(self, log_callback=None, gpib_number=DEFAULT_GPIB_NUMBER): self.inst = None self.rm = None self.log_callback = log_callback self.gpib_number = gpib_number def log(self, msg): if self.log_callback: self.log_callback(msg) else: log_to_file(msg) def connect(self): gpib_address = f"GPIB0::{self.gpib_number}::INSTR" self.rm = pyvisa.ResourceManager() self.inst = self.rm.open_resource(gpib_address) self.inst.write_termination = '\n' self.inst.read_termination = '\n' self.inst.timeout = 5000 idn = self.query("*IDN?") self.log(f"Connected: {idn}") return idn def send(self, cmd: str): self.inst.write(cmd) self.log(f"-> {cmd}") time.sleep(DELAY) def query(self, cmd: str) -> str: self.log(f"-> {cmd}") reply = self.inst.query(cmd).strip() self.log(f"<- {reply}") time.sleep(DELAY) return reply def setup_instrument(self): self.send("SERV:PRODUCTION:PUP") time.sleep(10) self.send("FREQ 1200.0000000000 MHZ") self.send("POWER:ATT:AUTO 1") self.send("AM:STATE 0") self.send("FM:STATE 0") self.send("PM:STATE 0") self.send("OUTPUT 1") self.send("POWER:AMPL 0.0000000000") # Reset / Fext Level DAC / Atten 4GHz AM Mod self.send('DIAG:LATCH:SELECT "atten_4GHz_rpp_reset"') self.send("DIAG:LATCH:VAL #H00") self.send('DIAG:LATCH:SELECT "freq_ext_level_DAC"') self.send("DIAG:LATCH:VAL #H800") self.send('DIAG:LATCH:SELECT "atten_4GHz_am_mod_select"') self.send("DIAG:LATCH:VAL #H01") self.send('DIAG:LATCH:SELECT "atten_4GHz_rpp_reset"') self.send("DIAG:LATCH:VAL #H01") self.send('DIAG:LATCH:SELECT "fext_service_mux"') self.send("DIAG:LATCH:VAL #H03") # ------------------ GUI ------------------ class HP8648FEGUI: def __init__(self, root): self.root = root self.root.title(TOOL_VERSION) self.gpib_number = tk.IntVar(value=DEFAULT_GPIB_NUMBER) self.controller = HP8648FEController(log_callback=self.log, gpib_number=self.gpib_number.get()) clear_log_file() # clear log at startup self.build_ui() self.center_window() def center_window(self): self.root.update_idletasks() width = self.root.winfo_reqwidth() height = self.root.winfo_reqheight() screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() x = (screen_width // 2) - (width // 2) y = (screen_height // 2) - (height // 2) self.root.geometry(f"{width}x{height}+{x}+{y}") self.root.minsize(width, height) def build_ui(self): style = ttk.Style() style.configure("TNotebook.Tab", font=("Arial", 10, "bold"), padding=[8, 4]) style.configure("TButton", font=("Arial", 10, "bold"), padding=[5, 3], relief="raised", borderwidth=2) style.map("TButton", background=[("active", "#32CD32")], foreground=[("active", "#000000")]) notebook = ttk.Notebook(self.root) notebook.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) main_frame = tk.Frame(notebook, bg="SystemButtonFace", padx=10, pady=10) info_frame = tk.Frame(notebook, bg="#FFFFE0", padx=10, pady=10) notebook.add(main_frame, text="Main") notebook.add(info_frame, text="Info") main_frame.grid_columnconfigure(0, weight=1) main_frame.grid_columnconfigure(1, weight=1) main_frame.grid_rowconfigure(5, weight=1) info_text = tk.Text(info_frame, wrap="word", font=("Arial", 13), bg="#FFFFE0", fg="#333333", relief="flat") info_text.tag_configure("bold", font=("Arial", 12, "bold")) info_text.insert(tk.END, f"{TOOL_NAME}\n", "bold") info_text.insert(tk.END, f"Version: {VERSION}\n", "bold") info_text.insert(tk.END, f"Author: {AUTHOR}\n", "bold") info_text.insert(tk.END, "="*60 + "\n") info_text.insert(tk.END, """Procedure for instruments with AM Level FE potentiometer on A10 FEXT Module: - Locate AM Level FE R707 potentiometer on A10 FEXT Module, of the three potentiometers this is the closest to the back of the instrument. - Warm-up at least 45 minutes. - Only units with AM Level potentiometer. Not for serial prefixes >=3847A/3847U. - Remove J32 jumper on motherboard. - Apply -4.0000V +/-0.0025V to J31 pin 3. - Connect DVM to J31 pin 6. - Press SETUP INSTRUMENT to configure instrument for AM Level FE adjustment. - Press AM LEVEL FE ADJUST and follow instructions. - Adjust R707 potentiometer until DVM reads 0.000V +/-0.002V. - Power OFF and reinstall J32 jumper when done. """) info_text.config(state="disabled") info_text.pack(fill="both", expand=True) title_frame = tk.Frame(main_frame, bg="SystemButtonFace") title_frame.grid(row=0, column=0, columnspan=2, sticky="nsew") title_label = ttk.Label(title_frame, text=TOOL_NAME, font=("Helvetica", 12, "bold")) title_label.pack(side="top", pady=6) pdf_button = ttk.Button(title_frame, text="PDF DOC", width=10, command=self.open_pdf) pdf_button.pack(side="right", padx=5) ttk.Label(main_frame, text="GPIB Address #:", font=("Arial", 10, "bold")).grid(row=1, column=0, sticky="e", pady=4) ttk.Spinbox(main_frame, from_=0, to=30, textvariable=self.gpib_number, width=8, font=("Arial", 10, "bold")).grid(row=1, column=1, sticky="w", pady=4) ttk.Button(main_frame, text="CONNECT", command=self.connect).grid(row=2, column=0, sticky="ew", pady=3, padx=2) ttk.Button(main_frame, text="SETUP INSTRUMENT", command=self.setup_instr).grid(row=2, column=1, sticky="ew", pady=3, padx=2) ttk.Button(main_frame, text="AM LEVEL FE ADJUST", command=self.am_fe_adjust).grid(row=3, column=0, columnspan=2, sticky="ew", pady=3, padx=2) self.log_box = tk.Text(main_frame, width=70, height=20, bg="#F5F5F5", fg="#000000", font=("Arial", 10)) self.log_box.grid(row=5, column=0, columnspan=2, pady=8, sticky="nsew") # ------------------ Logging ------------------ def log(self, text): self.log_box.insert(tk.END, text + "\n") self.log_box.see(tk.END) log_to_file(text) # ------------------ Button Commands ------------------ def connect(self): try: self.controller.gpib_number = self.gpib_number.get() idn = self.controller.connect() messagebox.showinfo("Connected", f"Instrument Info:\n{idn}") except Exception as e: messagebox.showerror("Error", f"Cannot connect: {e}") def setup_instr(self): def worker(): self.log("Setting up instrument for AM Level FE adjustment...") try: self.controller.setup_instrument() self.log("Instrument setup completed.") messagebox.showinfo("Setup Complete", "Instrument configured for AM Level FE Adjustment.") except Exception as e: self.log(str(e)) messagebox.showerror("Error", str(e)) threading.Thread(target=worker).start() def am_fe_adjust(self): messagebox.showinfo("Remove Jumper", "Please REMOVE J32 jumper on motherboard before proceeding.") confirmed = messagebox.askyesno("Voltage Check", "Ensure external voltage -4.0000V +/-0.0025V applied to J31 pin 3.\n \n Press YES if correct.") if not confirmed: self.log("Aborted: Incorrect voltage confirmation.") messagebox.showwarning("Aborted", "Aborted due to voltage setup error.") return messagebox.showinfo("Adjust Potentiometer", "Locate AM Level FE potentiometer R707 on A10 FEXT Module, of the three potentiometers this is the closest to the back of the instrument. \n \n" "Connect DVM to J31 pin 6 and adjust potentiometer until DVM reads 0.000 V +/-0.002 V.\n \n" "Press OK once done.") self.log("Rebooting instrument for finalization...") try: self.controller.send("SERV:PRODUCTION:PUP") time.sleep(10) self.log("Instrument reboot completed.") messagebox.showinfo("AM Level FE Adjustment", "Adjustment completed successfully.\nPower OFF and reinstall J32 jumper.") except Exception as e: self.log(str(e)) messagebox.showerror("Error", str(e)) def open_pdf(self): pdf_files = glob.glob(os.path.join(os.getcwd(), "AM_Level_FE*.pdf")) if pdf_files: webbrowser.open_new(pdf_files[0]) else: messagebox.showerror("Error", "No AM_Level_FE PDF found in folder") # ------------------ Main ------------------ if __name__ == "__main__": root = tk.Tk() app = HP8648FEGUI(root) root.mainloop()