Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions Gui/QtGUIutils/TessieCoolingApp.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class TessieMonitorWorker(QObject):

temp_update = pyqtSignal(list)
env_update = pyqtSignal(float, float)
set_update = pyqtSignal(list)
status_msg = pyqtSignal(str)

def __init__(self, instruments=None, interval_ms: int = 2000):
Expand Down Expand Up @@ -56,6 +57,14 @@ def poll(self):
except Exception as e:
logger.debug(f"Worker temp read issue: {e}")

# Temperature setpoints
try:
setpoints = coldbox.read("TEMPERATURE_SET")
if isinstance(setpoints, list) and len(setpoints) == 8:
self.set_update.emit(setpoints)
except Exception as e:
logger.debug(f"Worker setpoint read issue: {e}")

# Environment
rh = None
dp = None
Expand Down Expand Up @@ -94,20 +103,30 @@ class TessieCoolingApp(QWidget):
temp_update_signal = pyqtSignal(list)
# (rh in %, dew point in C)
env_update_signal = pyqtSignal(float, float)
set_update_signal = pyqtSignal(list)

def __init__(self, master=None):
super(TessieCoolingApp, self).__init__()
self.master = master
self.instruments = None
self._thread = None
self._worker = None
# Latest cached values
self._latest_temperatures = None # type: list
self._last_temp_update_ts = 0.0
self._latest_rh = None # type: float
self._latest_dp = None # type: float
self._last_env_update_ts = 0.0
self._latest_setpoints = None # type: list
self._last_set_update_ts = 0.0

# Initialize UI
self.initUI()

# Connect signals (widget-owned signals kept for compatibility)
self.temp_update_signal.connect(self.update_temperature_display)
self.env_update_signal.connect(self.update_environment_display)
self.set_update_signal.connect(self.update_setpoints_cache)

# Start temperature monitoring if instruments are available
if self.master and hasattr(self.master, 'instruments'):
Expand Down Expand Up @@ -231,6 +250,7 @@ def start_temperature_monitoring(self):
# Bridge worker signals to existing slots
self._worker.temp_update.connect(self.update_temperature_display)
self._worker.env_update.connect(self.update_environment_display)
self._worker.set_update.connect(self.update_setpoints_cache)

# Clean-up when thread finishes
self._thread.finished.connect(self._worker.deleteLater)
Expand All @@ -254,6 +274,10 @@ def stop_temperature_monitoring(self):
def update_temperature_display(self, temperatures):
"""Update the temperature display with new values."""
try:
# cache latest temperatures and timestamp
if isinstance(temperatures, list) and len(temperatures) == 8:
self._latest_temperatures = [float(t) if isinstance(t, (int, float)) else t for t in temperatures]
self._last_temp_update_ts = time.time()
for i, temp in enumerate(temperatures):
channel = i + 1
if channel in self.temp_labels:
Expand Down Expand Up @@ -282,6 +306,13 @@ def update_temperature_display(self, temperatures):
def update_environment_display(self, rh_value: float, dew_point: float):
"""Update the environment display with RH (%) and Dew Point (°C)."""
try:
# cache latest env values and timestamp
if isinstance(rh_value, (int, float)):
self._latest_rh = float(rh_value)
self._last_env_update_ts = time.time()
if isinstance(dew_point, (int, float)):
self._latest_dp = float(dew_point)
self._last_env_update_ts = time.time()
# Relative Humidity formatting and color coding
if isinstance(rh_value, (int, float)):
rh_str = f"{rh_value:.1f} %"
Expand All @@ -307,6 +338,38 @@ def update_environment_display(self, rh_value: float, dew_point: float):
self.dp_value_label.setStyleSheet("QLabel { color: gray; }")
except Exception as e:
logger.error(f"Error updating environment display: {e}")

def update_setpoints_cache(self, setpoints):
"""Cache the latest temperature setpoints list (length 8)."""
try:
if isinstance(setpoints, list) and len(setpoints) == 8:
self._latest_setpoints = [float(s) if isinstance(s, (int, float)) else s for s in setpoints]
self._last_set_update_ts = time.time()
except Exception as e:
logger.debug(f"Error caching setpoints: {e}")

# --- Public getters for other components (e.g., TestHandler) ---
def get_latest_temperatures(self, with_timestamp: bool = False):
"""Return a copy of the latest temperatures [8] or None. If with_timestamp, also return the unix ts."""
temps = list(self._latest_temperatures) if isinstance(self._latest_temperatures, list) else None
if with_timestamp:
return temps, self._last_temp_update_ts
return temps

def get_latest_env(self, with_timestamp: bool = False):
"""Return (rh, dew_point) or (rh, dew_point, ts) if with_timestamp."""
rh = self._latest_rh
dp = self._latest_dp
if with_timestamp:
return rh, dp, self._last_env_update_ts
return rh, dp

def get_latest_setpoints(self, with_timestamp: bool = False):
"""Return list of 8 temperature setpoints or None. If with_timestamp, also return ts."""
s = list(self._latest_setpoints) if isinstance(self._latest_setpoints, list) else None
if with_timestamp:
return s, self._last_set_update_ts
return s

def refresh_temperatures(self):
"""Manually refresh temperature readings."""
Expand Down
121 changes: 121 additions & 0 deletions Gui/python/TestHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@


from Gui.QtGUIutils.QtMatplotlibUtils import ScanCanvas
from Gui.QtGUIutils.TessieCoolingApp import TessieCoolingApp

from Gui.python.TestValidator import ResultGrader
from Gui.python.ANSIColoringParser import parseANSI
Expand Down Expand Up @@ -670,6 +671,12 @@ def runSingleTest(self, testName, nextTest=None):

self.updateOptimizedXMLValues()
self.configTest()
if site_settings.cooler == "Tessie":
# Log Tessie readings (temps/env) from widget cache at the start of each test
try:
self._log_tessie_from_widget("start")
except Exception as e:
logger.debug(f"Could not log Tessie readings from widget: {e}")

# Make sure that the GUI is not trying to write to the root directory
try:
Expand Down Expand Up @@ -1065,12 +1072,126 @@ def urgentStop(self):
self.haltSignal.emit(self.halt)
self.starttime = None

def _find_tessie_widget(self):
"""Try to find a TessieCoolingApp widget in the UI tree."""
candidates = []
try:
candidates.append(getattr(self.master, 'window', None))
except Exception:
pass
candidates.extend([self.master if hasattr(self, 'master') else None, self.runwindow])
for parent in filter(None, candidates):
try:
w = parent.findChild(TessieCoolingApp)
if w is not None:
return w
except Exception:
continue
# Fallback to a direct attribute if provided by the app
w = getattr(self.master, 'tessie_widget', None)
return w

def _log_tessie_from_widget(self, phase: str = "start"):
"""Fetch cached Tessie readings from the TessieCoolingApp widget and persist them.
Uses widget cache only, no direct instrument queries.

phase: 'start' or 'end' to control filename and console label.
- start -> writes tessie_start_temps.json (kept for backward compatibility)
- end -> writes tessie_end_values.json
"""
widget = self._find_tessie_widget()
temps = None
rh = None
dp = None
setpoints = None
if widget:
try:
if hasattr(widget, 'get_latest_temperatures'):
temps = widget.get_latest_temperatures()
except Exception:
temps = None
try:
if hasattr(widget, 'get_latest_env'):
env = widget.get_latest_env()
if isinstance(env, tuple):
rh, dp = env
except Exception:
rh, dp = None, None
try:
if hasattr(widget, 'get_latest_setpoints'):
setpoints = widget.get_latest_setpoints()
except Exception:
setpoints = None

ts = datetime.now().strftime("%H:%M:%S")
# Build console message
label = "start-of-test" if str(phase).lower().startswith("s") else "end-of-test"
parts = []
if isinstance(temps, list) and len(temps) == 8:
parts.append("temps=" + ", ".join(f"{t:.2f}°C" for t in temps))
if isinstance(rh, (int, float)):
parts.append(f"RH={rh:.1f}%")
if isinstance(dp, (int, float)):
parts.append(f"DP={dp:.2f}°C")
# Include setpoints if available
try:
if isinstance(setpoints, list) and len(setpoints) == 8:
uniq = {round(float(x), 2) for x in setpoints if isinstance(x, (int, float))}
if len(uniq) == 1:
sval = next(iter(uniq))
parts.append(f"set={sval:.2f}°C")
else:
parts.append("set=[" + ", ".join(
f"{float(x):.2f}°C" if isinstance(x, (int, float)) else str(x)
for x in setpoints
) + "]")
except Exception:
pass

msg = f"[{ts}] Tessie {label} (widget): " + ("; ".join(parts) if parts else "unavailable")

try:
for console in getattr(self.runwindow, 'ConsoleViews', []):
self.outputString.emit(msg, console)
except Exception:
pass
logger.info(msg)

# Persist snapshot to output dir
try:
if getattr(self, 'output_dir', None):
import os, json
# Keep filenames as before for compatibility
if str(phase).lower().startswith("s"):
out_path = os.path.join(self.output_dir, "tessie_start_temps.json")
else:
out_path = os.path.join(self.output_dir, "tessie_end_values.json")
payload = {
"source": "widget",
"timestamp": ts,
"temperatures": temps,
"relative_humidity": rh,
"dew_point": dp,
"phase": "start" if str(phase).lower().startswith("s") else "end",
"temperature_setpoints": setpoints,
}
with open(out_path, "w") as f:
json.dump(payload, f, indent=2)
except Exception:
pass

def validateTest(self):
for process in self.run_processes:
if process.state() == QProcess.Running:
return

self.finished_tests.append(self.currentTest)
if site_settings.cooler == "Tessie":
# Snapshot end-of-test Tessie values (from widget cache only)
try:
self._log_tessie_from_widget("end")
except Exception:
pass
try:
passed = []
results = []
Expand Down
Loading