import subprocess
import tkinter as tk
from tkinter import messagebox, ttk
import logging
from gui.module_test_data import ModuleTestData
import datetime
import threading
import os
import time
import signal
[docs]
class TestInterface(tk.Frame):
""" Interface (or base class) from which all the sets of tests inherit.
This defines the basic behaviour for a pre-defined set of tests including making a set of buttons, running the associated scripts.
"""
test_name = "Base Test"
# Dictionary of estimated time of running for the named tests. Any tests executed not listed below will assume to take 1 minute.
_TEST_TIMES = {
"IV-MEASURE" : 10,
"ADC-CALIBRATION" : 2,
"ANALOG-READBACK" : 30,
"SLDO" : 10,
"VCAL-CALIBRATION" : 3,
"LP-MODE" : 2,
"std_thresholdscan_hr" : 10,
"std_tune_pixelthreshold -t 1500" : 2,
"std_thresholdscan_hd" : 5,
"std_discbumpscan" : 3,
"std_thresholdscan_zerobias" : 4,
"selftrigger_source" : 30
}
PWD = os.getcwd()
_QC_TESTS = ['IV-MEASURE', 'ADC-CALIBRATION', 'ANALOG-READBACK', 'SLDO', 'VCAL-CALIBRATION', 'INJECTION-CAPACITANCE', 'LP-MODE', 'DATA-TRANSMISSION']
def __init__(self, parent, controller, mod_data):
super().__init__(parent) #this calls tk.Frame.__init__(self, parent)
tk.Label(self, text=self.test_name).grid(row=0, columnspan=2) # Title of set of test
self.test_list = self.get_test_list(mod_data)
self.make_buttons(controller, self.test_list, mod_data)
[docs]
def get_test_list():
# to be overrided by child classes.
return []
[docs]
def check_mod_data_loaded(self, mod_data : ModuleTestData):
"""Tests whether all the module testing properties have been saved into the ModuleTestData file. NB: Only checks for existence, not validity - we assume that the imported data is all reasonable.
Args:
mod_data: Module test data
Returns:
mod_data.loc_id, mod_data.mod_sn, mod_data.temp, mod_data.version | None, None, None, None: Throws an exception and returns None if the attributes don't exist.
"""
try:
return mod_data.loc_id, mod_data.mod_sn,mod_data.temp, mod_data.version
except AttributeError:
messagebox.showerror("showerror", "Module info has not been loaded correctly, try load again.")
return None, None, None, None
[docs]
def run_test(self, master, button : tk.Button, test : str, mod_data : ModuleTestData):
""" Locates and runs the script requested, autofilling module information.
Args:
master: Window containing the frame
button: tkinter button triggering the script
test: name of script
mod_data: ModuleTestData object with module data (SN, local ID etc) loaded.
"""
loc_id, mod_sn, temp, version = self.check_mod_data_loaded(mod_data)
home_path = mod_data.home_path
dry_run = mod_data.dry_run
if loc_id is not None:
template = self.gen_cmd(mod_data)
echo = ""
if dry_run:
template += " ; sleep 2" # simulates the script taking some time
echo = "echo "
template += " ; {echo}cd {pwd}" # returns to original GUI directory after executing script. Use this instead of os changedir to emulate the dry run
logging.info(f"Running {test}")
cmd = template.format(echo=echo, home_path=home_path, loc_id=loc_id, mod_sn=mod_sn, temp=temp, test=test.split(' ')[0], test_flags = ' '.join(test.split(' ')[1:]), version=version, pwd=self.PWD) # fills module information
logging.debug(f"********** \n CMD: {cmd} \n ***********")
if "zerobias" in test: # TODO: implement HV ramping for zerobias test + (temp, LV, HV) checks for other tests
messagebox.showinfo("show info", "HV source to 0V")
time.sleep(1)
self.open_popup(master, test, cmd)
messagebox.showinfo("show info", "HV source to -120V")
else:
self.open_popup(master, test, cmd)
button.configure(bg="green")
[docs]
def gen_cmd() -> str:
""""Returns the command template for the relevant test scripts, changing the config depending on the chip version and temperature; to be overridden by child classes. """
return ""
[docs]
def run_cmd(self, cmd : str, on_done):
""""Threaded subprocess run (shell command).
Args:
cmd (str): shell command to be executed, including prepended cd to script dir
on_done (function): function to define behaviour after command has been executed (e.g., stop progress bar and close popup).
"""
# TODO: implement failure protocol.
def task():
self.proc = subprocess.Popen(cmd, shell=True, preexec_fn=os.setsid) # This blocks the main thread
self.proc.wait()
on_done("Finished!") # callback in main thread
threading.Thread(target=task, daemon=True).start()
[docs]
def kill_proc(self):
if hasattr(self, 'proc') and self.proc.poll() is None:
os.killpg(os.getpgid(self.proc.pid), signal.SIGTERM)