From af57ad8578b7531fee23d243187d06b55d0e2a98 Mon Sep 17 00:00:00 2001 From: Francois Mallet Date: Wed, 3 Dec 2025 15:25:38 +0100 Subject: [PATCH 1/3] =?UTF-8?q?mise=20=C3=A0=20jour=20en=20mode=20signal?= =?UTF-8?q?=5Frecovery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- False | Bin 0 -> 363 bytes .../daq_move_plugins/daq_move_Lockin_SR830.py | 178 ++++++++++++ .../plugins_0D/daq_0Dviewer_LockInSR830.py | 255 ++++++++++-------- .../hardware/sr_830_thread_safe.py | 31 +++ .../hardware/utils.py | 16 ++ 5 files changed, 368 insertions(+), 112 deletions(-) create mode 100644 False create mode 100644 src/pymodaq_plugins_stanford_research_systems/daq_move_plugins/daq_move_Lockin_SR830.py create mode 100644 src/pymodaq_plugins_stanford_research_systems/hardware/sr_830_thread_safe.py create mode 100644 src/pymodaq_plugins_stanford_research_systems/hardware/utils.py diff --git a/False b/False new file mode 100644 index 0000000000000000000000000000000000000000..f24e50a598972f621ce16957a621131dbe54b5f8 GIT binary patch literal 363 zcmZo*otnVN00y;FGpW^O@F zs)A#3Noit9ei6iUpsfNf!2#w*W~K_osU; dict: + d = {} + for tc in time_constants: + d[f"{tc:.2e} {unit}"] = tc + return d + + +rm = pyvisa.ResourceManager() +VISA_RESOURCES = rm.list_resources() +ADAPTERS = dict(VISA=VISAAdapter, Prologix=PrologixAdapter) +SHIELD = {"Grounded": 0, "Floating": 1} +COUPLING = {"AC": 0, "DC": 1} +TIME_CONSTANTS = build_dict_from_float_list(SR830ThreadSafe.TIME_CONSTANTS, "s") +SENSITIVITIES = build_dict_from_float_list(SR830ThreadSafe.SENSITIVITIES, "V") +FILTER_SLOPES = build_dict_from_float_list(SR830ThreadSafe.FILTER_SLOPES, "") +#print(SR830ThreadSafe.INPUT_COUPLINGS) + +class DAQ_Move_Lockin_SR830(DAQ_Move_base): + """Plugin for the Signal Recovery DSP 7265 Instrument + + Does not currently support differential measurement. + """ + _controller_units = ['Hz','V'] + is_multiaxes = True + _axis_names = ['Freq','Amp'] + _epsilon = 0.01 + data_actuator_type = DataActuatorType.DataActuator + + params = [ + {'title': 'Adapter', 'name': 'adapter', 'type': 'list', + 'limits': list(ADAPTERS.keys())}, + {'title': 'VISA Address:', 'name': 'address', 'type': 'list', + 'limits': VISA_RESOURCES}, + {'title': 'Filter time constant', 'name': 'time_constant', + 'type': 'list', 'limits': list(TIME_CONSTANTS.keys())}, + {'title': 'Full-scale sensitivity', 'name': 'sensitivity', + 'type': 'list', 'limits': list(SENSITIVITIES.keys())}, + {'title': 'Filter slope', 'name': 'filter_slope', + 'type': 'list', 'limits': list(FILTER_SLOPES.keys())}, + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + + def ini_attributes(self) -> None: + self.controller: SR830ThreadSafe = None + + def get_actuator_value(self) -> DataActuator: + """Get the current value from the hardware with scaling conversion. + + Returns + ------- + float: The frequency obtained after scaling conversion. + """ + if self.axis_value == 'Amp': # Amp axis + val = DataActuator(data=self.controller.sine_voltage) + elif self.axis_value == 'Freq': # Frequency axis + val = DataActuator(data=self.controller.frequency) + val = self.get_position_with_scaling(val) + return val + + def close(self) -> None: + """Terminate the communication protocol""" + self.controller.shutdown() + + def commit_settings(self, param: Parameter) -> None: + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been + changed by the user + """ + if param.name() == "time_constant": + self.controller.time_constant = TIME_CONSTANTS[param.value()] + elif param.name() == "sensitivity": + self.controller.sensitivity= SENSITIVITIES[param.value()] + elif param.name() == "filter_slope": + self.controller.filter_slope= FILTER_SLOPES[param.value()] + else: + pass + + def ini_stage(self, controller: object = None) -> Tuple[str, bool]: + """Actuator communication initialization + + Parameters + ---------- + controller: (object) + custom object of a PyMoDAQ plugin (Slave case). None if only one + actuator by controller (Master case) + + Returns + ------- + info: str + Id of the lockin + initialized: bool + False if initialization failed otherwise True + controller: object + Controller of the daq_move + """ + self.ini_stage_init(slave_controller=controller) + + if self.is_master: + adapter = ADAPTERS[self.settings.child('adapter').value()]( + self.settings.child('address').value() + ) + self.controller = SR830ThreadSafe(adapter) + + try: + info = self.controller.id + initialized = True + except: + info = "" + initialized = False + + return info, initialized + + def move_abs(self, f: DataActuator) -> None: + """ Move the actuator to the absolute target defined by value + + Parameters + ---------- + value: (float) value of the absolute target value + """ + f = self.check_bound(f) + f = self.set_position_with_scaling(f) + if self.axis_value == 'Amp': # Amp axis + self.controller.sine_voltage = f.value() + elif self.axis_value == 'Freq': # Frequency axis + self.controller.frequency = f.value() + + self.target_value = f + self.current_value = self.target_value + + def move_rel(self, f: DataActuator) -> None: + """ Move the actuator to the relative target actuator value defined by + value + + Parameters + ---------- + value: (float) value of the relative target frequency + """ + f = (self.check_bound(self.current_value + f) + - self.current_value) + self.target_value = f + self.current_value + f = self.set_position_relative_with_scaling(f) + self.move_abs(self.target_value) + + def move_home(self): + """Call the reference method of the controller + + Set oscillator to 1kHz + """ + self.move_abs(DataActuator(1e3)) + + def stop_motion(self): + """Stop the actuator and emits move_done signal""" + pass + + if __name__ == '__main__': + main(__file__, init=False) \ No newline at end of file diff --git a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py index 340a3ea..038c8b5 100644 --- a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py +++ b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py @@ -1,22 +1,67 @@ -from typing import List -from time import perf_counter -from qtpy.QtCore import QThread +from typing import Tuple + +import pyvisa import numpy as np -from pymodaq.utils.daq_utils import ThreadCommand + +from pymeasure.adapters import VISAAdapter, PrologixAdapter + +from pymodaq.control_modules.viewer_utility_classes import ( + DAQ_Viewer_base, + comon_parameters, + main +) +from pymodaq.utils.parameter import Parameter, utils from pymodaq.utils.data import DataFromPlugins, DataToExport -from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main -from pymodaq.utils.parameter import Parameter -from pymeasure.instruments.srs.sr830 import SR830 -from pyvisa import ResourceManager +from pyqtgraph.parametertree.Parameter import registerParameterType +from pyqtgraph.parametertree.parameterTypes.basetypes import GroupParameter + +from pymodaq_plugins_stanford_research_systems.hardware.sr_830_thread_safe import SR830ThreadSafe + +#CHANNELS = ['x', 'y', 'mag', 'phase', 'adc1', 'adc2', 'adc3'] +CHANNELS = ['x', 'y'] +rm = pyvisa.ResourceManager() +VISA_RESOURCES = rm.list_resources() +ADAPTERS = dict(VISA=VISAAdapter, Prologix=PrologixAdapter) + +for channel in CHANNELS: + assert hasattr(SR830ThreadSafe, channel) + +class ChannelGroup(GroupParameter): + """Group Parameter listing the different output + """ + + def __init__(self, **opts) -> None: + opts['type'] = 'sr830channel' + opts['addText'] = "Add Channel" + super().__init__(**opts) + + def addNew(self) -> None: + """Add new channel to viewer + """ + name_prefix = 'SR380Ch' + + child_indexes = [int(par.name()[len(name_prefix) + 1:]) + for par in self.children()] + + if child_indexes == []: + newindex = 0 + else: + newindex = max(child_indexes) + 1 + + child = { + 'title': f'Measure {newindex:02.0f}', + 'name': f'{name_prefix}{newindex:02.0f}', + 'type': 'itemselect', + 'removable': True, + 'value': dict(all_items=CHANNELS, selected=CHANNELS[0]) + } + + self.addChild(child) + + +registerParameterType('sr830channel', ChannelGroup, override=True) -VISA_rm = ResourceManager() -devices = list(VISA_rm.list_resources()) -device = '' -for dev in devices: - if 'GPIB' in dev: - device = dev - break class DAQ_0DViewer_LockInSR830(DAQ_Viewer_base): @@ -32,31 +77,18 @@ class DAQ_0DViewer_LockInSR830(DAQ_Viewer_base): hardware library. """ - channels = ['X', 'Y', 'R', 'Theta', 'Aux In 1', - 'Aux In 2', 'Aux In 3', 'Aux In 4', 'Frequency', 'CH1', 'CH2'] - - params = comon_parameters + [ - {'title': 'VISA:', 'name': 'device', 'type': 'list', 'limits': devices, 'value': device}, - {'title': 'ID:', 'name': 'id', 'type': 'str', 'value': ""}, - {'title': 'Acquisition:', 'name': 'acq', 'type': 'group', 'children': [ - {'title': 'Use Trigger:', 'name': 'trigger', 'type': 'bool', 'value': False}, - {'title': 'Channels in separate viewer:', 'name': 'separate_viewers', 'type': 'bool', 'value': True}, - {'title': 'Channels:', 'name': 'channels', 'type': 'itemselect', - 'value': dict(all_items=channels, selected=['CH1', 'CH2'])}, - {'title': 'Sampling (Hz):', 'name': 'sampling_rate', 'type': 'list', 'limits': SR830.SAMPLE_FREQUENCIES}, - ]}, - {'title': 'Configuration:', 'name': 'config', 'type': 'group', 'children': [ - {'title': 'Reset:', 'name': 'reset', 'type': 'bool_push', 'value': False}, - {'title': 'Setup number:', 'name': 'setup_number', 'type': 'int', 'value': 1, 'min': 1, 'max': 9}, - {'title': 'Save setup:', 'name': 'save_setup', 'type': 'bool_push', 'value': False, - 'label': 'Save'}, - {'title': 'Load setup:', 'name': 'load_setup', 'type': 'bool_push', 'value': False, - 'label': 'Load'}, - ]}, - ] - - def ini_attributes(self): - self.controller: SR830 = None + + params = [ + {'title': 'Adapter', 'name': 'adapter', 'type': 'list', + 'limits': list(ADAPTERS.keys())}, + {'title': 'VISA Address:', 'name': 'address', 'type': 'list', + 'limits': VISA_RESOURCES}, + {'title': 'ID:', 'name': 'id', 'type': 'str'}, + {'title': 'Channels:', 'name': 'channels', 'type': 'sr830channel'} + ] + comon_parameters + + def ini_attributes(self) -> None: + self.controller: SR830ThreadSafe = None def commit_settings(self, param: Parameter): """Apply the consequences of a change of value in the detector settings @@ -66,30 +98,33 @@ def commit_settings(self, param: Parameter): param: Parameter A given parameter (within detector_settings) whose value has been changed by the user """ - if param.name() == 'load_setup': - self.controller.load_setup(self.settings['config', 'setup_number']) - param.setValue(False) - - elif param.name() == 'save_setup': - self.controller.save_setup(self.settings['config', 'setup_number']) - param.setValue(False) - - elif param.name() == 'channels': - selected_channels = param.value()['selected'] - data_list_array = [np.array([0.]) for _ in range(len(selected_channels))] - dwas = self.create_dwas(data_list_array) - self.dte_signal.emit(DataToExport(name='SR830', data=dwas)) - - elif param.name() == 'reset': - self.controller.reset() - param.setValue(False) - elif param.name() == 'trigger': - self.controller.aquireOnTrigger(param.value()) - - elif param.name() == 'sampling_rate': - self.controller.sample_frequency = param.value() - param.setValue(self.controller.sample_frequency) # check it + def commit_settings(self, param: Parameter) -> None: + """Apply the consequences of a change of value in the detector settings + + Parameters + ---------- + param: Parameter + A given parameter (within detector_settings) whose value has been + changed by the user + """ + if param.name() in utils.iter_children( + self.settings.child('channels'), []): + data = [] + for child in self.settings.child('channels').children(): + labels = child.value()['selected'] + data.append( + DataFromPlugins( + name=child.name(), + data=[np.array([0]) for _ in labels], + labels=labels, + dim='Data0D' + ) + ) + self.dte_signal_temp.emit(DataToExport( + name="lockinsr830", + data=data + )) def ini_detector(self, controller=None): """Detector communication initialization @@ -107,29 +142,40 @@ def ini_detector(self, controller=None): False if initialization failed otherwise True """ - if self.settings['controller_status'] == "Slave": - if controller is None: - raise Exception('no controller has been defined externally while this axe is a slave one') - else: - controller = controller - else: # Master stage - controller = SR830(self.settings['device']) - self.controller = controller - - self.controller.reset_buffer() - self.controller.start_buffer(False) - - self.settings.child('acq', 'sampling_rate').setValue(self.controller.sample_frequency) - info = self.controller.id - self.settings.child('id').setValue(info) - initialized = True + self.ini_detector_init(slave_controller=controller) + + if self.is_master: + adapter = ADAPTERS[self.settings.child('adapter').value()]( + self.settings.child('address').value() + ) + self.controller = SR830ThreadSafe(adapter) + + self.dte_signal_temp.emit( + DataToExport( + name="lockinsr830", + data=[ + DataFromPlugins( + name="lockinsr830", + data=[np.array([0]), np.array([0])], + dim="Data0D", + labels=["x", "y"] + ) + ] + ) + ) + + try: + info = self.controller.id + initialized = True + except: + info = "" + initialized = False + return info, initialized def close(self): """Terminate the communication protocol""" - if self.controller is not None: - self.controller.clear() - self.controller.shutdown() + pass def grab_data(self, Naverage=1, **kwargs): """Start a grab from the detector @@ -143,36 +189,21 @@ def grab_data(self, Naverage=1, **kwargs): others optionals arguments """ - selected_channels = self.settings['acq', 'channels']['selected'] - snapped_list = self.controller.snap(*selected_channels)[:len(selected_channels)] - data_list_array = [np.array([snapped]) for snapped in snapped_list] - dwas = self.create_dwas(data_list_array) - - self.dte_signal.emit(DataToExport(name='SR830', data=dwas)) - - def create_dwas(self, data_list_array: List[np.ndarray]) -> List[DataFromPlugins]: - """ Create a list of DataFromPlugins according to the selected channels - - Parameters - ---------- - data_list_array: List[np.ndarray] - The list of numpy ndarray to put into a list of DataFromPlugins - - Returns - ------- - List[DataFromPlugins] - """ - selected_channels = self.settings['acq', 'channels']['selected'] - if self.settings['acq', 'separate_viewers']: - - dwas = [DataFromPlugins(f'SR830:{selected_channels[ind]}', - data=[array], - labels=[selected_channels[ind]]) for ind, array in enumerate(data_list_array)] - else: - dwas = [DataFromPlugins('SR830', - data=data_list_array, - labels=selected_channels)] - return dwas + data = [] + for child in self.settings.child('channels').children(): + labels = child.value()['selected'][:] + subdata = [np.array([getattr(self.controller, label)]) + for label in labels] + data.append(DataFromPlugins( + name=child.name(), + data=subdata, + labels=labels, + dim='Data0D' + )) + self.dte_signal.emit(DataToExport( + name="lockinsr830", + data=data + )) def stop(self): """Stop the current grab hardware wise if necessary""" diff --git a/src/pymodaq_plugins_stanford_research_systems/hardware/sr_830_thread_safe.py b/src/pymodaq_plugins_stanford_research_systems/hardware/sr_830_thread_safe.py new file mode 100644 index 0000000..14400e5 --- /dev/null +++ b/src/pymodaq_plugins_stanford_research_systems/hardware/sr_830_thread_safe.py @@ -0,0 +1,31 @@ +from threading import Lock + +from pymeasure.instruments.srs.sr830 import SR830 + +from pymodaq.utils.logger import set_logger, get_module_name + +lock = Lock() +logger = set_logger(get_module_name(__file__), add_to_console=False) + +class SR830ThreadSafe(SR830): + + def read(self, **kwargs): + value = None + try: + lock.acquire() + value = super().read(**kwargs) + except Exception as e: + logger.debug(str(e)) + finally: + lock.release() + + return value + + def write(self, command, **kwargs): + try: + lock.acquire() + super().write(command, **kwargs) + except Exception as e: + logger.debug(str(e)) + finally: + lock.release() diff --git a/src/pymodaq_plugins_stanford_research_systems/hardware/utils.py b/src/pymodaq_plugins_stanford_research_systems/hardware/utils.py new file mode 100644 index 0000000..4c54050 --- /dev/null +++ b/src/pymodaq_plugins_stanford_research_systems/hardware/utils.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +""" +Created the 17/04/2023 + +@author: Sebastien Weber +""" +from typing import Tuple +from pyvisa import ResourceManager + + +def get_resources() -> Tuple[str]: + + rm = ResourceManager() + resources = rm.list_resources('?*') + rm.close() + return resources From 42e1b41f66d1e8fd816dcf427c9ac01604a53a3e Mon Sep 17 00:00:00 2001 From: Francois Mallet Date: Wed, 3 Dec 2025 15:56:13 +0100 Subject: [PATCH 2/3] ajout de mag et phase --- .../plugins_0D/daq_0Dviewer_LockInSR830.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py index 038c8b5..36322d6 100644 --- a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py +++ b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py @@ -17,9 +17,8 @@ from pyqtgraph.parametertree.parameterTypes.basetypes import GroupParameter from pymodaq_plugins_stanford_research_systems.hardware.sr_830_thread_safe import SR830ThreadSafe - -#CHANNELS = ['x', 'y', 'mag', 'phase', 'adc1', 'adc2', 'adc3'] -CHANNELS = ['x', 'y'] +] +CHANNELS = ['x', 'y','magnitude','theta'] rm = pyvisa.ResourceManager() VISA_RESOURCES = rm.list_resources() ADAPTERS = dict(VISA=VISAAdapter, Prologix=PrologixAdapter) @@ -39,7 +38,7 @@ def __init__(self, **opts) -> None: def addNew(self) -> None: """Add new channel to viewer """ - name_prefix = 'SR380Ch' + name_prefix = 'channel' child_indexes = [int(par.name()[len(name_prefix) + 1:]) for par in self.children()] From 8d013cb5f10048a5e4ea2c8b16ef83cd54f56857 Mon Sep 17 00:00:00 2001 From: Francois Mallet Date: Thu, 4 Dec 2025 13:53:45 +0100 Subject: [PATCH 3/3] SR830 daq viewer corrected --- .../plugins_0D/daq_0Dviewer_LockInSR830.py | 52 ++++++++----------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py index 36322d6..50ad8b3 100644 --- a/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py +++ b/src/pymodaq_plugins_stanford_research_systems/daq_viewer_plugins/plugins_0D/daq_0Dviewer_LockInSR830.py @@ -17,7 +17,7 @@ from pyqtgraph.parametertree.parameterTypes.basetypes import GroupParameter from pymodaq_plugins_stanford_research_systems.hardware.sr_830_thread_safe import SR830ThreadSafe -] + CHANNELS = ['x', 'y','magnitude','theta'] rm = pyvisa.ResourceManager() VISA_RESOURCES = rm.list_resources() @@ -89,41 +89,31 @@ class DAQ_0DViewer_LockInSR830(DAQ_Viewer_base): def ini_attributes(self) -> None: self.controller: SR830ThreadSafe = None - def commit_settings(self, param: Parameter): + def commit_settings(self, param: Parameter) -> None: """Apply the consequences of a change of value in the detector settings Parameters - ---------- - param: Parameter - A given parameter (within detector_settings) whose value has been changed by the user - """ - - def commit_settings(self, param: Parameter) -> None: - """Apply the consequences of a change of value in the detector settings - - Parameters ---------- - param: Parameter - A given parameter (within detector_settings) whose value has been - changed by the user + param: Parameter + A given parameter (within detector_settings) whose value has been + changed by the user """ - if param.name() in utils.iter_children( - self.settings.child('channels'), []): - data = [] - for child in self.settings.child('channels').children(): - labels = child.value()['selected'] - data.append( - DataFromPlugins( - name=child.name(), - data=[np.array([0]) for _ in labels], - labels=labels, - dim='Data0D' - ) - ) - self.dte_signal_temp.emit(DataToExport( - name="lockinsr830", - data=data - )) + if param.name() in utils.iter_children(self.settings.child('channels'), []): + data = [] + for child in self.settings.child('channels').children(): + labels = child.value()['selected'] + data.append( + DataFromPlugins( + name=child.name(), + data=[np.array([0]) for _ in labels], + labels=labels, + dim='Data0D' + ) + ) + self.dte_signal_temp.emit(DataToExport( + name="lockinsr830", + data=data + )) def ini_detector(self, controller=None): """Detector communication initialization