Skip to content

Commit 644008d

Browse files
committed
Add MINT test classes
1 parent 6a4f702 commit 644008d

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed

scripts/ModuleTestClasses.py

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
"""
2+
File to contain test classes for MINT hardware. Designed to interface with
3+
python sequencer in MIDAS
4+
"""
5+
6+
from time import sleep
7+
import numpy as np
8+
import paramiko
9+
10+
from perseus.client import ModuleClient
11+
from perseus.control import timestamp_dict
12+
13+
14+
class SSHAgent:
15+
def __init__(self, hostname, username, password):
16+
self.hostname = hostname
17+
self.username = username
18+
self.password = password
19+
self.client = None
20+
21+
def __del__(self):
22+
if self.client:
23+
self.client.close()
24+
25+
def connect_to_client(self):
26+
self.client = paramiko.SSHClient()
27+
self.client.load_system_host_keys()
28+
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
29+
try:
30+
self.client.connect(self.hostname, username=self.username, password=self.password)
31+
except Exception as e:
32+
raise Exception(f"Failed to connect to ssh client: {self.username}@{self.hostname} with error: {e}")
33+
34+
def close_client(self):
35+
if self.client:
36+
self.client.close()
37+
38+
class Mainboard:
39+
def __init__(self, module_name, module_ip):
40+
self.module = ModuleClient(module_ip)
41+
self.module_name = module_name
42+
43+
class InterposerTesting:
44+
"""
45+
Class for interposer test scripts. All functions should enable and configure
46+
hardware only as required for their functionality. All functions should
47+
return the interposer to a null state before returning.
48+
"""
49+
def __init__(self, module, interposer_id, ssh_client, sequencer):
50+
"""
51+
52+
in:
53+
- module: module instance from Mainboard class
54+
- interposer_id: XXX this is switching
55+
- ssh_client: ssh connection to pi or mainboard
56+
"""
57+
self.module = module
58+
self.interposer = module.interposer[interposer_id]
59+
self.ssh_client = ssh_client
60+
self.seq = sequencer
61+
62+
# Check to make sure this has loaded properly
63+
if hasattr(self.interposer, "load_error"):
64+
raise Exception(f"Interposer has load error: {self.interposer.load_error}")
65+
66+
def test_mist(self, threshold_acceptance, hv_acceptance):
67+
"""
68+
Checks threshold and MIST SiPM high voltage capability.
69+
70+
Stores acceptance within thresholds in
71+
output["threshold_acceptance_array"]
72+
output["hv_acceptance_array"]
73+
"""
74+
mist_voltages = np.array([30,20,10,5])
75+
threshold_voltages = np.array([0.8,0.5,0.1,0.05])
76+
77+
# Store multidimensional output in terms of channels and measurements
78+
output = {
79+
"readback_thresholds": np.zeros((4,len(threshold_voltage))),
80+
"measured_thresholds": np.zeros((4,len(threshold_voltages))),
81+
"readback_mist_hv": np.zeros(len(mist_voltages)),
82+
"measured_mist_hv": np.zeros(len(mist_voltages)),
83+
}
84+
85+
# measure thresholds
86+
for i, threshold_voltage in enumerate(threshold_voltages):
87+
# Set thresholds and get readback values
88+
for channel in range(4):
89+
output["readback_thresholds"][channel].append(self.interposer.set_mist_threshold(channel=channel, voltage=threshold_voltage))
90+
91+
# pause briefly for thresholds to set
92+
sleep(1)
93+
94+
# Read thresholds as measured by the onboard ADC
95+
output["measured_thresholds"][0,i] = self.interposer.read_adc_channel(2)
96+
output["measured_thresholds"][1,i] = self.interposer.read_adc_channel(3)
97+
output["measured_thresholds"][2,i] = self.interposer.read_adc_channel(1)
98+
output["measured_thresholds"][3,i] = self.interposer.read_adc_channel(0)
99+
100+
# Check acceptance. If any measurement minus the expected is greater
101+
# than the allowed acceptance, set acceptance to False
102+
output["threshold_acceptance_array"] = abs(output["measured_voltage"]-threshold_voltages[:,np.newaxis])<=threshold_acceptance
103+
104+
# Enable -3V3 voltage to MIST SiPMs.
105+
self.interposer.enable_muon_tracker_sipm_power(True)
106+
# Enable HV to MIST SiPMs. Should this be tested with the backup
107+
# generator as well?
108+
self.interposer.enable_flasher_hv(True)
109+
110+
# Get the current state, make sure these are set
111+
for i in range(5):
112+
sleep(1)
113+
state = self.interposer.get_state()
114+
if state["neg_3v3_state"] and state["mist_sipm_hv_state"]:
115+
break
116+
if i == 4:
117+
raise Exception("Could not enable MIST power")
118+
119+
# Set voltage, readback values, then measure
120+
for i, mist_voltage in enumerate(mist_voltages):
121+
# Set and get readback
122+
output["readback_mist_hv"][i] = self.interposer.set_muon_tracker_hv(True, mist_voltage)
123+
# pause and meditate
124+
sleep(2)
125+
# measure high voltage
126+
output["measured_mist_hv"][i] = self.interposer.read_adc_channel(4)
127+
128+
# Check if measurements are within acceptance of the set voltages
129+
output["hv_acceptance_array"] = abs(output["readback_mist_hv"]-mist_voltages)<=hv_acceptance
130+
131+
# Turn off all the things we turned on
132+
self.interposer.set_muon_tracker_hv(False, 0)
133+
self.interposer.enable_flasher_hv(False)
134+
self.interposer.enable_muon_tracker_sipm_power(False)
135+
136+
# check acceptance of HV
137+
return timestamp_dict(output)
138+
139+
def test_flasher_power(self, acceptance, use_backup=False):
140+
"""
141+
Function to test flasher HV and pulse widths
142+
143+
Stores acceptance within thresholds in
144+
output["hv_pre_filter_acceptance"]
145+
output["hv_post_filter_acceptance"]
146+
output["hv_f1_acceptance"]
147+
"""
148+
149+
pulse_widths_ns = np.array([5, 10])
150+
bias_voltages = np.array([35, 20, 10, 5])
151+
152+
output = {
153+
"measured_flasher_pre_filter_hv": np.zeros((len(bias_voltages),len(bias_voltages))),
154+
"measured_flasher_post_filter_hv": np.zeros((len(bias_voltages),len(bias_voltages))),
155+
"measured_flasher_hv_f1": np.zeros((len(bias_voltages),len(bias_voltages)))
156+
}
157+
158+
# Enable 5V, flasher HV
159+
self.interposer.enable_5v_power(True)
160+
self.interposer.enable_flasher_hv(True,use_backup)
161+
162+
# Check state, make sure 5V is set
163+
for i in range(5):
164+
sleep(1)
165+
state = self.interposer.get_state()
166+
if abs(state["5v_voltage"]-5) < 0.5:
167+
break
168+
if i == 4:
169+
raise Exception("Could not enable 5V power")
170+
171+
# Cycle through voltages and widths. (Do we need channels too?)
172+
# Use first channel for now, just measuring a global voltage.
173+
# Does the width actually change the main HV?
174+
for j, bias_voltage in enumerate(bias_voltages):
175+
for i, pulse_width in enumerate(pulse_widths_ns):
176+
# Set state
177+
self.interposer.set_flasher_state(0,True,bias_voltage,pulse_width)
178+
# Meditate some more
179+
sleep(2)
180+
# Measure
181+
output["measured_flasher_pre_filter_hv"][i,j] = self.interposer.read_adc_channel(6)
182+
output["measured_flasher_post_filter_hv"][i,j] = self.interposer.read_adc_channel(7)
183+
output["measured_flasher_hv_f1"][i,j] = self.interposer.read_adc_channel(8)
184+
185+
output["hv_pre_filter_acceptance"] = abs(output["measured_flasher_pre_filter_hv"]-bias_voltages[:,np.newaxis])<=acceptance
186+
output["hv_post_filter_acceptance"] = abs(output["measured_flasher_post_filter_hv"]-bias_voltages[:,np.newaxis])<=acceptance
187+
output["hv_f1_acceptance"] = abs(output["measured_flasher_hv_f1"]-bias_voltages[:,np.newaxis])<=acceptance
188+
189+
# Turn off flasher and voltages
190+
self.interposer.set_flasher_state(0,False,0,0)
191+
self.interposer.enable_flasher_hv(False,use_backup)
192+
self.interposer.enable_5v_power(False)
193+
194+
return timestamp_dict(output)
195+
196+
def test_microbase_presence(self, selected_microbases):
197+
"""
198+
Check each microbase, make sure that no load error is present and that
199+
we can read back the uid.
200+
201+
Stores acceptance in
202+
203+
"""
204+
# Deconvolve indices. Remove this when perseus is updated
205+
ubase_indices = [6,2,1,5,4,7,3,0]
206+
207+
output = {
208+
"ubase_uids": np.empty(8)
209+
}
210+
211+
# Go through microbases and check if we can read their IDs
212+
for microbase in selected_microbases:
213+
# Check if there's a load error
214+
if hasattr(self.interposer.ubase[ubase_indices[microbase]], "load_error"):
215+
output["ubase_uids"][microbase] = False
216+
continue
217+
# if not, store uid
218+
output["ubase_uids"][microbase] = self.interposer.ubase[ubase_indices[microbase]].uid
219+
220+
return timestamp_dict(output)
221+
222+
def flash_microbases(self, selected_microbases, script_path):
223+
"""
224+
Function to flash the selected microbases
225+
"""
226+
error_status = 1
227+
for microbase in selected_microbases:
228+
# Set up forwarding to the correct microbase
229+
self.interposer.select_ubase(microbase)
230+
# Power cycle the microbase to put it into flashing mode
231+
self.interposer.set_ubase_power(False)
232+
self.interposer.set_ubase_power(True)
233+
# Meditate
234+
sleep(2)
235+
# Use the ssh client to run the bash script that reflashes the microbases
236+
try:
237+
_, stdout, stderr = self.client.exec_command(f"bash {script_path}")
238+
self.seq.msg(f"Microbase {microbase} stdout: {stdout.read().decode()}, stderr: {stderr.read().decode()}")
239+
except Exception as e:
240+
self.seq.msg(f"Failed reflash on microbase {microbase} with exception: {e}, stdout: {stdout.read().decode()}, and stderr: {stderr.read().decode()}")
241+
error_status = -1
242+
243+
return error_status
244+
245+
def turn_on_hv(self, hv=False):
246+
"""
247+
Function to set the HV of a module either to the optimal value measured in the lab
248+
249+
"""
250+
251+

0 commit comments

Comments
 (0)