From 640616acc9df248dc9aefa38c9f8ffb98c22e0df Mon Sep 17 00:00:00 2001 From: Lou Barreau Date: Tue, 24 Jun 2025 09:45:43 +0200 Subject: [PATCH] Create daq_move_Monochromator.py --- .../daq_move_Monochromator.py | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py diff --git a/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py new file mode 100644 index 0000000..a069691 --- /dev/null +++ b/src/pymodaq_plugins_teaching/daq_move_plugins/daq_move_Monochromator.py @@ -0,0 +1,179 @@ + +from typing import Union, List, Dict +from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, + main, DataActuatorType, DataActuator) + +from pymodaq_utils.utils import ThreadCommand # object used to send info back to the main thread +from pymodaq_gui.parameter import Parameter + + +from pymodaq_plugins_teaching.hardware.spectrometer import Spectrometer + + +class DAQ_Move_Monochromator(DAQ_Move_base): + """ Instrument plugin class for an actuator. + + This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Move module through inheritance via + DAQ_Move_base. It makes a bridge between the DAQ_Move module and the Python wrapper of a particular instrument. + + TODO (in the end) Complete the docstring of your plugin with: + * The set of controllers and actuators that should be compatible with this instrument plugin. + * With which instrument and controller it has been tested. + * The version of PyMoDAQ during the test. + * The version of the operating system. + * Installation instructions: what manufacturer’s drivers should be installed to make it run? + + Attributes: + ----------- + controller: object + The particular object that allow the communication with the hardware, in general a python wrapper around the + hardware library. + + # TODO add your particular attributes here if any + + """ + is_multiaxes = False # set to True if this plugin is controlled for a multiaxis controller + _axis_names: Union[List[str], Dict[str, int]] = [''] # complete the list, put correct number of axes + _controller_units: Union[str, List[str]] = 'nm' # the correct unit here, it could be a single str (the same one is applied to all axes) or a list of str (as much as the number of axes) + _epsilon: Union[float, List[float]] = 0.1 # a value that is correct depending on your controller it could be a single float of a list of float (as much as the number of axes) + data_actuator_type = DataActuatorType.DataActuator # whether you use the new data style for actuator otherwise set this + # as DataActuatorType.float (or entirely remove the line) + + params = [{'title': 'info:', 'name': 'info', 'type': 'str', 'value': '' }, + {'title': 'grating:', 'name': 'grating', 'type': 'list', 'value': Spectrometer.gratings[0] }] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached + # the target value. It is the developer responsibility to put here a meaningful value + + def ini_attributes(self): + # declare the type of the wrapper (and assign it to self.controller) you're going to use for easy + # autocompletion + self.controller: Spectrometer = None + + # declare here attributes you want/need to init with a default value + pass + + def get_actuator_value(self): + """Get the current value from the hardware with scaling conversion. + + Returns + ------- + float: The position obtained after scaling conversion. + """ + + pos = DataActuator(data=self.controller.get_wavelength(), units='nm') # when writing your own plugin replace this line + pos = self.get_position_with_scaling(pos) + return pos + + def user_condition_to_reach_target(self) -> bool: + """ Implement a condition for exiting the polling mechanism and specifying that the + target value has been reached + + Returns + ------- + bool: if True, PyMoDAQ considers the target value has been reached + """ + # TODO either delete this method if the usual polling is fine with you, but if need you can + # add here some other condition to be fullfilled either a completely new one or + # using or/and operations between the epsilon_bool and some other custom booleans + # for a usage example see DAQ_Move_brushlessMotor from the Thorlabs plugin + return True + + def close(self): + + self.controller.close_communication() # when writing your own plugin replace this line + + def commit_settings(self, param: Parameter): + """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 + """ + ## TODO for your custom plugin + if param.name() == 'axis': + self.axis_unit = self.controller.your_method_to_get_correct_axis_unit() + # do this only if you can and if the units are not known beforehand, for instance + # if the motors connected to the controller are of different type (mm, µm, nm, , etc...) + # see BrushlessDCMotor from the thorlabs plugin for an exemple + + elif param.name() == "a_parameter_you've_added_in_self.params": + self.controller.your_method_to_apply_this_param_change() + + elif param.name() == "grating": + self.controller.your_method_to_apply_this_param_change() + + else: + pass + + def ini_stage(self, controller=None): + """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 + initialized: bool + False if initialization failed otherwise True + """ + + self.ini_stage_init(slave_controller=controller) # will be useful when controller is slave + + if self.is_master: # is needed when controller is master + self.controller = Spectrometer() # arguments for instantiation!) + # enter here whatever is needed for your controller initialization and eventual + # opening of the communication channel + + info = "Monochromator initialized" + initialized = self.controller.open_communication() # + return info, initialized + + def move_abs(self, value: DataActuator): + """ Move the actuator to the absolute target defined by value + + Parameters + ---------- + value: (float) value of the absolute target positioning + """ + + value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here + self.target_value = value + value = self.set_position_with_scaling(value) # apply scaling if the user specified one + self.controller.set_wavelength(value.value('nm'),set_type='abs') # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Abs move done'])) + + def move_rel(self, value: DataActuator): + """ Move the actuator to the relative target actuator value defined by value + + Parameters + ---------- + value: (float) value of the relative target positioning + """ + value = self.check_bound(self.current_position + value) - self.current_position + self.target_value = value + self.current_position + value = self.set_position_relative_with_scaling(value) + + self.controller.set_wavelength(value.value('nm'),set_type='rel') # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Rel move done'])) + + def move_home(self): + """Call the reference method of the controller""" + + ## TODO for your custom plugin + raise NotImplemented # when writing your own plugin remove this line + self.controller.your_method_to_get_to_a_known_reference() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + def stop_motion(self): + """Stop the actuator and emits move_done signal""" + + self.controller.stop() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Motion stopped'])) + + +if __name__ == '__main__': + main(__file__)