Skip to content

Commit 901c358

Browse files
bug fixes
1 parent 2acb80f commit 901c358

1 file changed

Lines changed: 90 additions & 24 deletions

File tree

pica/lakeshore/T_Control_L350_Step_GUI.py

Lines changed: 90 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@
1010
import traceback
1111
import threading
1212
import queue
13-
import winsound
1413
from datetime import datetime
1514
from matplotlib.figure import Figure
1615
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
1716
import matplotlib as mpl
1817
import runpy
1918
from multiprocessing import Process
19+
import csv
20+
import platform
2021

2122
# --- Optional Packages ---
23+
try:
24+
import winsound
25+
except ImportError:
26+
pass
27+
2228
try:
2329
from PIL import Image, ImageTk
2430
PIL_AVAILABLE = True
@@ -87,29 +93,29 @@ def connect(self, visa_address):
8793
raise ConnectionError("PyVISA is not available.")
8894
self.lakeshore = self.rm.open_resource(visa_address)
8995
self.lakeshore.timeout = 10000
96+
self.lakeshore.write('*CLS') # clear status once; do NOT *RST mid-run
9097
idn = self.lakeshore.query('*IDN?').strip()
9198
print(f" Lakeshore Connected: {idn}")
9299
return idn
93100

94101
def configure_ramp(self, setpoint, rate, heater_range):
95-
self.lakeshore.write('*RST')
96-
time.sleep(0.5)
97-
self.lakeshore.write('*CLS')
98-
self.set_heater_range(1, heater_range)
99-
self.lakeshore.write(f'SETP 1,{setpoint}')
100-
self.lakeshore.write(f'RAMP 1,1,{rate}')
102+
self.set_heater_range(1, heater_range) # ensure heater on at desired range
103+
self.lakeshore.write(f'RAMP 1,1,{rate}') # enable ramp FIRST
104+
time.sleep(0.1)
105+
self.lakeshore.write(f'SETP 1,{setpoint}') # now the change is ramped
101106

102107
def set_heater_range(self, output, heater_range):
103-
range_map = {'off': 0, 'low': 2, 'medium': 4, 'high': 5}
108+
range_map = {'off': 0, 'low': 1, 'medium': 3, 'high': 5}
104109
range_code = range_map.get(heater_range.lower())
105110
if range_code is None:
106111
raise ValueError("Invalid heater range.")
107112
self.lakeshore.write(f'RANGE {output},{range_code}')
108113

109114
def get_status(self):
110-
temp = float(self.lakeshore.query('KRDG? A').strip())
111-
htr_output = float(self.lakeshore.query('HTR? 1').strip())
112-
return temp, htr_output
115+
temp = float(self.lakeshore.query('KRDG? A').strip()) # Kelvin, input A
116+
resistance = float(self.lakeshore.query('SRDG? A').strip()) # sensor units (ohms)
117+
htr_output = float(self.lakeshore.query('HTR? 1').strip()) # heater %, output 1
118+
return temp, resistance, htr_output
113119

114120
def stop_ramp(self):
115121
if self.lakeshore:
@@ -168,8 +174,9 @@ def __init__(self, root):
168174
self.logo_image = None
169175
self.backend = Lakeshore_Backend()
170176

171-
# Added 'target' to data storage for plotting expectations
172-
self.data_storage = {'time': [], 'temperature': [], 'target': [], 'heater': []}
177+
# Added 'resistance' to data storage for persistence
178+
self.data_storage = {'time': [], 'temperature': [], 'target': [],
179+
'resistance': [], 'heater': []}
173180

174181
self.setup_styles()
175182
self.create_widgets()
@@ -481,6 +488,30 @@ def _on_proceed(self):
481488
self._update_status_ui("INITIATING NEXT RAMP...", self.CLR_HEADER)
482489
self.proceed_event.set()
483490

491+
def _beep(self):
492+
def _ring():
493+
try:
494+
if platform.system() == 'Windows':
495+
import winsound
496+
winsound.Beep(1000, 500)
497+
else:
498+
self.root.bell()
499+
except Exception:
500+
pass
501+
threading.Thread(target=_ring, daemon=True).start()
502+
503+
def _close_data_file(self):
504+
f = getattr(self, 'data_file', None)
505+
if f:
506+
try:
507+
f.flush()
508+
f.close()
509+
self._put_gui_msg('log', text=f"Data file closed: {self.data_filepath}")
510+
except Exception:
511+
pass
512+
finally:
513+
self.data_file = None
514+
484515
# --- MAIN LOGIC ---
485516
def start_sequence(self):
486517
setpoints = list(self.listbox.get(0, tk.END))
@@ -508,6 +539,18 @@ def start_sequence(self):
508539
self.start_time = time.time()
509540
self.proceed_event.clear()
510541

542+
# --- Open persistent data file (flushed every point) ---
543+
os.makedirs("data", exist_ok=True)
544+
stamp = datetime.now().strftime("%Y%m%d_%H%M%S")
545+
self.data_filepath = os.path.join("data", f"TStep_{stamp}.csv")
546+
self.data_file = open(self.data_filepath, 'w', newline='')
547+
self.csv_writer = csv.writer(self.data_file)
548+
self.csv_writer.writerow(
549+
["Timestamp", "Elapsed_s", "Target_K", "Temperature_K",
550+
"Resistance_Ohm", "Heater_pct"])
551+
self.data_file.flush()
552+
self.log(f"Logging data to: {self.data_filepath}")
553+
511554
self.root.after(100, self._process_gui_queue)
512555

513556
self.measurement_thread = threading.Thread(target=self._hardware_worker_loop, daemon=True)
@@ -585,17 +628,22 @@ def _process_gui_queue(self):
585628
self._update_status_ui(msg['text'], msg['color'])
586629

587630
elif msg_type == 'plot':
588-
self.line_target.set_data(self.data_storage['time'], self.data_storage['target'])
589-
self.line_temp.set_data(self.data_storage['time'], self.data_storage['temperature'])
590-
self.line_heater.set_data(self.data_storage['time'], self.data_storage['heater'])
631+
n = min(len(self.data_storage['time']),
632+
len(self.data_storage['temperature']),
633+
len(self.data_storage['heater']),
634+
len(self.data_storage['target']))
635+
t = self.data_storage['time'][:n]
636+
self.line_target.set_data(t, self.data_storage['target'][:n])
637+
self.line_temp.set_data(t, self.data_storage['temperature'][:n])
638+
self.line_heater.set_data(t, self.data_storage['heater'][:n])
591639
for ax in [self.ax_temp, self.ax_heater]:
592640
ax.relim()
593641
ax.autoscale_view()
594642
self.canvas.draw_idle()
595643

596644
elif msg_type == 'handshake_ready':
597645
self.btn_proceed.config(state='normal')
598-
winsound.Beep(1000, 500)
646+
self._beep()
599647

600648
elif msg_type == 'sequence_complete':
601649
self.set_ui_state(running=False)
@@ -604,7 +652,7 @@ def _process_gui_queue(self):
604652
except queue.Empty:
605653
pass
606654

607-
if self.is_running:
655+
if self.is_running or not self.gui_queue.empty():
608656
self.root.after(100, self._process_gui_queue)
609657

610658
def _hardware_worker_loop(self):
@@ -623,11 +671,25 @@ def _hardware_worker_loop(self):
623671
stable_start_time = None
624672

625673
while self.is_running:
626-
temp, htr = self.backend.get_status()
627-
self.data_storage['time'].append(time.time() - self.start_time)
674+
temp, resistance, htr = self.backend.get_status()
675+
elapsed = time.time() - self.start_time
676+
now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
677+
678+
self.data_storage['time'].append(elapsed)
628679
self.data_storage['temperature'].append(temp)
629-
self.data_storage['target'].append(target) # Track target for graph
680+
self.data_storage['target'].append(target)
681+
self.data_storage['resistance'].append(resistance)
630682
self.data_storage['heater'].append(htr)
683+
684+
# Persist immediately so an abort/crash never loses a point
685+
try:
686+
self.csv_writer.writerow(
687+
[now_str, f"{elapsed:.2f}", f"{target:.4f}",
688+
f"{temp:.4f}", f"{resistance:.6g}", f"{htr:.2f}"])
689+
self.data_file.flush()
690+
os.fsync(self.data_file.fileno())
691+
except Exception as e:
692+
self._put_gui_msg('log', text=f"WARN: data write failed: {e}")
631693

632694
self._put_gui_msg('plot')
633695

@@ -658,22 +720,26 @@ def _hardware_worker_loop(self):
658720
self.proceed_event.wait()
659721

660722
if self.is_running:
661-
self.is_running = False
662723
self._put_gui_msg('log', text="Measurement Sequence Complete.")
663724
self._put_gui_msg('status', text="READY TO START", color=self.CLR_HEADER)
664725
self._put_gui_msg('sequence_complete')
665726
self.backend.stop_ramp()
727+
self.is_running = False # flip LAST, after messages are queued
666728

667729
except Exception as e:
668730
self._put_gui_msg('log', text=f"CRITICAL ERROR IN HARDWARE THREAD: {e}\n{traceback.format_exc()}")
669731
self.is_running = False
670-
self._put_gui_msg('sequence_complete')
732+
self._put_gui_msg('sequence_complete')
671733
self.backend.stop_ramp()
734+
finally:
735+
self._close_data_file()
672736

673737
def _on_closing(self):
674738
if self.is_running and messagebox.askyesno("Exit", "A sequence is active. Stop hardware and exit?"):
675739
self.stop_ramp()
676-
time.sleep(0.5)
740+
time.sleep(0.5)
741+
if self.measurement_thread and self.measurement_thread.is_alive():
742+
self.measurement_thread.join(timeout=2.0)
677743
self.root.destroy()
678744
elif not self.is_running:
679745
self.root.destroy()

0 commit comments

Comments
 (0)