Skip to content

Commit

Permalink
Added auto-restarting functionality for the laser. It will now try to…
Browse files Browse the repository at this point in the history
… pick up where it left off if it's been too long since the last time it locked successfully. Also fixed a bug where the laser hardware module would report that a scan had finished before it had begun.
  • Loading branch information
lange50 committed Mar 27, 2025
1 parent 0950fb1 commit dbbf58d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 4 deletions.
1 change: 1 addition & 0 deletions cfg/terascan.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ logic:
daq: nidaq # Note that this logic assumes there is exactly one (digital) input to the DAQ.
options:
record_length_ms: 1 # Length of data (in ms) to take at each wavelength. Only integers.
laser_timeout_s: 10 # time in seconds before we automatically restart the laser scan


# daq_reader_logic:
Expand Down
20 changes: 19 additions & 1 deletion src/qudi/hardware/laser/solstis_laser.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing import List

from PySide2 import QtCore
from time import time

from qudi.core.configoption import ConfigOption
from qudi.core.statusvariable import StatusVar
Expand Down Expand Up @@ -79,6 +80,8 @@ def on_activate(self):
if (self._scan_type == -1):
self._scan_type = self.get_default_scan_type()
self._scan_rate = self.get_default_scan_rate()

self._scan_started = time()

def on_deactivate(self):
""" Deactivate module.
Expand Down Expand Up @@ -237,6 +240,7 @@ def start_scan(self) -> bool:
pause=True)
solstis.scan_stitch_op(self.socket, self._scan_type, "start")
self.sigScanStarted.emit()
self._scan_started = time()
self.module_state.lock()
return True

Expand All @@ -257,6 +261,19 @@ def stop_scan(self) -> bool:
except solstis.SolstisError as e:
self.log.exception(f'Scan stop failure: {e.message}')
return False

@QtCore.Slot(float)
def restart_scan(self, wavelength: float) -> bool:
"""Restart a scan from a specified wavelength"""

if self.module_state() == 'locked':
self.module_state.unlock()
self._start_wavelength = wavelength
self._scan_started = time()
return self.start_scan()
else:
self.log.warning('Restart scan called when not running')
return False

def pause_scan(self):
"""Pause a running scan (unimplemented)"""
Expand Down Expand Up @@ -358,7 +375,8 @@ def set_scan_rate(self, scan_rate: int):


def __status_update(self):
if self.module_state() == 'locked':
if self.module_state() == 'locked' \
and self._scan_started + 3 < time(): # if we check for status too soon, we might get a false positive
status = self.get_laser_state()
if status['in_progress'] == False:
self.sigScanFinished.emit()
Expand Down
35 changes: 32 additions & 3 deletions src/qudi/logic/terascan_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class TerascanLogic(LogicBase):
daq: nidaq # Note that this logic assumes there is exactly one (digital) input to the DAQ.
options:
record_length_ms: 1 # Length of data (in ms) to take at each wavelength. Only integers.
laser_timeout_s: 10 # Time in seconds to wait for the laser to lock before automatically restarting the scan
"""

# declare connectors
Expand All @@ -53,6 +53,8 @@ class TerascanLogic(LogicBase):
_record_length_ms = ConfigOption(name='record_length_ms',
default=1,
missing='info')

_laser_timeout_s = ConfigOption(name='laser_timeout_s', default=10)

# status variables:
_start_wavelength = StatusVar('start_wavelength', default=0.75)
Expand All @@ -65,6 +67,7 @@ class TerascanLogic(LogicBase):
_laser_locked = StatusVar('laser_locked', default=False)
_current_data = StatusVar('current_data', default=[]) # list of TerascanData

_last_locked: float = 0

# Update signals, e.g. for GUI module
sigWavelengthUpdated = QtCore.Signal(float)
Expand All @@ -76,6 +79,7 @@ class TerascanLogic(LogicBase):
sigSetLaserWavelengths = QtCore.Signal(float, float)
sigSetLaserScanRate = QtCore.Signal(int)
sigSetLaserScanType = QtCore.Signal(int)
sigRestartLaser = QtCore.Signal(float) # used when we need to restart the laser, as everything else is already running

sigStartScan = QtCore.Signal()
sigStopScan = QtCore.Signal()
Expand All @@ -87,7 +91,7 @@ class TerascanLogic(LogicBase):

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.__timer = None
self._thread_lock = RecursiveMutex()

def on_activate(self):
Expand All @@ -101,6 +105,7 @@ def on_activate(self):
self.sigSetLaserWavelengths.connect(laser.set_wavelengths, QtCore.Qt.QueuedConnection)
self.sigSetLaserScanRate.connect(laser.set_scan_rate, QtCore.Qt.QueuedConnection)
self.sigSetLaserScanType.connect(laser.set_scan_type, QtCore.Qt.DirectConnection) # Is direct on purpose
self.sigRestartLaser.connect(laser.restart_scan, QtCore.Qt.QueuedConnection)

self.sigStartScan.connect(counter.start_measure, QtCore.Qt.QueuedConnection)
self.sigStartScan.connect(laser.start_scan, QtCore.Qt.QueuedConnection)
Expand All @@ -124,9 +129,17 @@ def on_activate(self):
self._record_length_s = self._record_length_ms * 1e-3
self._bin_width_s = self._record_length_s
self.sigConfigureCounter.emit(self._bin_width_s, self._record_length_s)

# Add watchdog timer
self.__timer = QtCore.QTimer()
self.__timer.setSingleShot(False)
self.__timer.timeout.connect(self.__watchdog)
self.__timer.start(500)

def on_deactivate(self):
pass
self.__timer.stop()
self.__timer.timeout.disconnect()
self.__timer = None

@property
def locked(self) -> bool:
Expand All @@ -146,6 +159,8 @@ def start_scan(self):
if self.module_state() == 'idle':
self.module_state.lock()
self._current_data = []
self._last_locked = time.time()
self.sigSetLaserWavelengths.emit(self._start_wavelength, self._end_wavelength)
self.sigStartScan.emit()

@QtCore.Slot()
Expand Down Expand Up @@ -243,3 +258,17 @@ def _new_daq_data(self, data: List[ReaderVal]):
# self.sigStopCounting.emit()

self.sigLaserLocked.emit(self._laser_locked)


#### Watchdog Timer ####
def __watchdog(self):
with self._thread_lock:
if self.module_state() == 'locked':
if self._laser_locked:
self._last_locked = time.time()
elif time.time() - self._last_locked > self._laser_timeout_s:
self.log.info('Laser did not lock in time. Restarting scan.')
new_start = self._current_wavelength*1e-3
self._last_locked = time.time()
self.sigRestartLaser.emit(new_start)

0 comments on commit dbbf58d

Please sign in to comment.