Skip to content

Commit b3a6ede

Browse files
possible stop button issue tried fixing
1 parent 3636a8b commit b3a6ede

1 file changed

Lines changed: 86 additions & 29 deletions

File tree

pica/keysight/Temprature_Scan_E4980A_GUI.py

Lines changed: 86 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Measurement (Keysight E4980A + Lakeshore 350).
55
Original Authors: Prathamesh Deshmukh (template programs)
66
Integrated by: AI-assisted merge per design specification
7-
Version: V: 1.0
7+
Version: V: 1.1
88
"""
99

1010
# ===============================================================================
@@ -305,6 +305,7 @@ def perform_measurement(self, freq, delay):
305305
self.instrument.write(f":FREQ {freq}")
306306
time.sleep(delay)
307307
self.instrument.write(":TRIG:IMM")
308+
self.instrument.query("*OPC?") # added for robustness on LAN
308309
vals = self.instrument.query_ascii_values(":FETC?")
309310
R, X = vals[0], vals[1]
310311
status = int(vals[2]) if len(vals) > 2 else 0
@@ -345,6 +346,7 @@ def __init__(self):
345346
self.lakeshore = None
346347
self.lcr = LCR_Backend()
347348
self.params = {}
349+
self.SAFETY_KILL_TEMP_K = 350.0 # Hard safety ceiling
348350

349351
def initialize_instruments(self, parameters):
350352
self.params = parameters
@@ -357,7 +359,20 @@ def initialize_instruments(self, parameters):
357359
# corr_enabled, cable_len
358360
self.lcr.initialize_instrument(parameters)
359361

360-
def measure_frequency_sweep(self, frequencies, delay):
362+
def check_safety_kill(self, temperature_k):
363+
if temperature_k >= self.SAFETY_KILL_TEMP_K:
364+
for attempt in range(3):
365+
try:
366+
self.lakeshore.set_heater_range(1, 'off')
367+
break
368+
except Exception as e:
369+
print(f"Kill attempt {attempt+1} failed: {e}")
370+
time.sleep(0.5)
371+
print(f"!!! SAFETY KILL: T={temperature_k:.3f} K. Heater OFF.")
372+
return True
373+
return False
374+
375+
def measure_frequency_sweep(self, frequencies, delay, stop_event=None):
361376
"""
362377
Appendix A.1 — CRITICAL:
363378
Reads Lakeshore temperature INSIDE the frequency loop so every
@@ -366,11 +381,21 @@ def measure_frequency_sweep(self, frequencies, delay):
366381
"""
367382
htr = self.lakeshore.get_heater_output(1)
368383
points = []
384+
killed = False
369385
for f in frequencies:
386+
if stop_event is not None and stop_event.is_set():
387+
break # abort mid-sweep, cleanly
370388
temp = self.lakeshore.get_temperature('A') # T for THIS point
389+
390+
# Issue #2: per-point safety kill check
391+
if temp >= self.SAFETY_KILL_TEMP_K:
392+
killed = self.check_safety_kill(temp)
393+
points.append((temp, f, float('nan'), float('nan'), -1))
394+
break
395+
371396
R, X, status = self.lcr.perform_measurement(f, delay)
372397
points.append((temp, f, R, X, status))
373-
return {'heater': htr, 'points': points}
398+
return {'heater': htr, 'points': points, 'killed': killed}
374399

375400
def close_instruments(self):
376401
print("\n--- [Backend] Closing all instrument connections. ---")
@@ -392,7 +417,7 @@ class Integrated_CT_GUI:
392417
LCR measurement.
393418
"""
394419

395-
PROGRAM_VERSION = "1.0"
420+
PROGRAM_VERSION = "1.1"
396421
LOGO_SIZE = 110
397422

398423
# --- Default frequency list (Section 4 of original instructions) ---
@@ -454,6 +479,9 @@ def __init__(self, root):
454479
self._last_draw_time = 0.0
455480
self._redraw_interval = 0.25 # seconds; redraw at most ~4×/sec
456481

482+
# --- Stop Event (Issue #1 Fix) ---
483+
self.stop_event = threading.Event()
484+
457485
# --- Backend ---
458486
self.backend = Combined_Backend()
459487
atexit.register(self.backend.close_instruments)
@@ -977,7 +1005,7 @@ def calculate_impedance_parameters(self, f, R, X):
9771005

9781006
# Complex capacitance C* = C' - jC''
9791007
Cp_double_prime = G / omega_safe
980-
Cs_double_prime = Cp_double_prime
1008+
Cs_double_prime = D * Cs # Fix Issue #7: Physically correct series loss
9811009

9821010
return [
9831011
Q, # 0
@@ -1096,6 +1124,7 @@ def start_measurement(self):
10961124

10971125
# --- Reset state ---
10981126
self.is_stabilizing, self.is_running = True, False
1127+
self.stop_event.clear() # Issue #1: Clear stop event
10991128
self.start_button.config(state='disabled')
11001129
self.stop_button.config(state='normal')
11011130
self.scan_button.config(state='disabled')
@@ -1197,18 +1226,29 @@ def _on_plot_freq_change(self, event=None):
11971226

11981227
# ------------------------------------------------------------------
11991228
def stop_measurement(self, from_user=True):
1200-
if self.is_running or self.is_stabilizing:
1201-
self.is_running, self.is_stabilizing = False, False
1202-
if from_user:
1203-
self.log("Measurement stopped by user.")
1204-
self.start_button.config(state='normal')
1205-
self.stop_button.config(state='disabled')
1206-
self.scan_button.config(state='normal')
1207-
self.backend.close_instruments()
1208-
if from_user:
1209-
messagebox.showinfo(
1210-
"Info",
1211-
"Measurement stopped and instruments disconnected.")
1229+
# Issue #1 Fix: Wait for worker to finish before touching VISA
1230+
if not (self.is_running or self.is_stabilizing):
1231+
return
1232+
self.is_running, self.is_stabilizing = False, False
1233+
self.stop_event.set()
1234+
if from_user:
1235+
self.log("Stop requested; waiting for worker to finish...")
1236+
1237+
# Wait for the worker to exit BEFORE touching the VISA sessions.
1238+
if self.measurement_thread and self.measurement_thread.is_alive():
1239+
self.measurement_thread.join(timeout=15.0)
1240+
if self.measurement_thread.is_alive():
1241+
self.log("WARNING: worker did not exit in 15 s; "
1242+
"closing sessions anyway.")
1243+
1244+
self.start_button.config(state='normal')
1245+
self.stop_button.config(state='disabled')
1246+
self.scan_button.config(state='normal')
1247+
self.backend.close_instruments()
1248+
if from_user:
1249+
self.log("Measurement stopped and instruments disconnected.")
1250+
messagebox.showinfo(
1251+
"Info", "Measurement stopped and instruments disconnected.")
12121252

12131253
# ==================================================================
12141254
# WORKER THREAD (Section 7, amended by Appendix A.1)
@@ -1217,7 +1257,7 @@ def _measurement_worker(self):
12171257
params = self.backend.params
12181258
try:
12191259
# --- Stabilization Phase (verbatim from R-T template) ---
1220-
while self.is_stabilizing:
1260+
while self.is_stabilizing and not self.stop_event.is_set():
12211261
current_temp = self.backend.lakeshore.get_temperature('A')
12221262
self.data_queue.put(
12231263
f"LOG:Stabilizing... Current: {current_temp:.4f} K "
@@ -1234,14 +1274,14 @@ def _measurement_worker(self):
12341274
self.data_queue.put(
12351275
f"LOG:Stabilized at {current_temp:.4f} K. "
12361276
f"Waiting 5s before ramp...")
1237-
time.sleep(5)
1277+
self.stop_event.wait(5)
12381278
self.is_stabilizing = False
12391279
self.is_running = True
12401280
break
1241-
time.sleep(2)
1281+
self.stop_event.wait(2)
12421282

12431283
# --- Ramp Phase ---
1244-
if self.is_running:
1284+
if self.is_running and not self.stop_event.is_set():
12451285
self.backend.lakeshore.set_setpoint(1, params['end_temp'])
12461286
self.backend.lakeshore.setup_ramp(1, params['rate'])
12471287
self.backend.lakeshore.set_heater_range(1, 'high')
@@ -1253,12 +1293,21 @@ def _measurement_worker(self):
12531293
# --- Measurement Loop ---
12541294
# Appendix A.1: measure_frequency_sweep reads T inside the
12551295
# frequency loop so every data point is bound to its own T.
1256-
while self.is_running:
1296+
while self.is_running and not self.stop_event.is_set():
12571297
cycle = self.backend.measure_frequency_sweep(
1258-
self.frequencies, params['delay'])
1298+
self.frequencies, params['delay'], self.stop_event)
1299+
1300+
if not cycle['points']:
1301+
break
1302+
12591303
elapsed = time.time() - self.start_time
12601304
self.data_queue.put(('CYCLE', cycle, elapsed))
12611305

1306+
# Check if the safety kill was triggered
1307+
if cycle.get('killed', False):
1308+
self.data_queue.put("CUTOFF")
1309+
break
1310+
12621311
# Termination check uses the temperature of the LAST
12631312
# point measured in this cycle.
12641313
last_temp = cycle['points'][-1][0]
@@ -1276,27 +1325,35 @@ def _measurement_worker(self):
12761325
# QUEUE PROCESSING (main thread)
12771326
# ==================================================================
12781327
def _process_data_queue(self):
1328+
# Issue #4 Fix: Fully drain the queue before acting on terminal events
1329+
terminal = None
12791330
try:
12801331
while not self.data_queue.empty():
12811332
data = self.data_queue.get_nowait()
12821333

12831334
if isinstance(data, str) and data.startswith("LOG:"):
12841335
self._handle_log_message(data[4:])
12851336
elif isinstance(data, str) and data == "CUTOFF":
1286-
self._handle_cutoff_event()
1287-
return
1337+
terminal = "CUTOFF" # defer; keep draining
12881338
elif isinstance(data, str) and data == "COMPLETE":
1289-
self._handle_complete_event()
1290-
return
1339+
terminal = "COMPLETE" # defer; keep draining
12911340
elif isinstance(data, Exception):
1292-
self._handle_runtime_error(data)
1293-
return
1341+
terminal = data
12941342
elif isinstance(data, tuple) and data[0] == 'CYCLE':
12951343
_, cycle, elapsed = data
12961344
self._process_cycle(cycle, elapsed)
12971345
except queue.Empty:
12981346
pass
12991347

1348+
if terminal == "CUTOFF":
1349+
self._handle_cutoff_event()
1350+
return
1351+
if terminal == "COMPLETE":
1352+
self._handle_complete_event()
1353+
return
1354+
if isinstance(terminal, Exception):
1355+
self._handle_runtime_error(terminal)
1356+
return
13001357
if self.is_running or self.is_stabilizing:
13011358
self.root.after(200, self._process_data_queue)
13021359

0 commit comments

Comments
 (0)