From 6ca1a78bc2b195c0d9807271798dc3706acc29bf Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 00:09:08 +0200 Subject: [PATCH 01/20] adding hatch_build.py, and pyproject.py. Removing setup.py, plugin_info.toml, MANIFEST.in --- MANIFEST.in | 3 --- hatch_build.py | 10 ++++++++ plugin_info.toml | 24 ------------------ pyproject.toml | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 4 --- 5 files changed, 75 insertions(+), 31 deletions(-) delete mode 100644 MANIFEST.in create mode 100644 hatch_build.py delete mode 100644 plugin_info.toml create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 66a7ca5..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README.rst -include LICENSE -graft src \ No newline at end of file diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 0000000..f8302b7 --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,10 @@ +from pathlib import Path +from hatchling.metadata.plugin.interface import MetadataHookInterface +from pymodaq_utils.resources.hatch_build_plugins import update_metadata_from_toml + +here = Path(__file__).absolute().parent + + +class PluginInfoTomlHook(MetadataHookInterface): + def update(self, metadata: dict) -> None: + update_metadata_from_toml(metadata, here) \ No newline at end of file diff --git a/plugin_info.toml b/plugin_info.toml deleted file mode 100644 index 1841bdd..0000000 --- a/plugin_info.toml +++ /dev/null @@ -1,24 +0,0 @@ -## To modify by developer(s) of the plugin - -[plugin-info] -SHORT_PLUGIN_NAME = 'arduino' #to be modified, for instance daqmx then rename the module name: -# (pymodaq_plugins_template become pymodaq_plugins_daqmx for instance) - -package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_arduino' #to modify -description = 'Set of instrument plugins implemented using an Arduino Board' - -author = 'Sebastien J. Weber' -author-email = 'sebastien.weber@cemes.fr' -license = 'MIT' - -[plugin-install] -#packages required for your plugin: -packages-required = ['pymodaq>=4.3.0', 'telemetrix', 'pyvisa', 'pyvisa-py'] - -[features] # defines the plugin features contained into this plugin -instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) -extensions = true # true if plugins contains dashboard extensions -models = false # true if plugins contains pid models or other models (optimisation...) -h5exporters = false # true if plugin contains custom h5 file exporters -scanners = false # true if plugin contains custom scan layout (daq_scan extensions) - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..e1a25f4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,65 @@ + +[features] # defines the plugin features contained into this plugin +instruments = true # true if plugin contains instrument classes (else false, notice the lowercase for toml files) +extensions = true # true if plugins contains dashboard extensions +models = false # true if plugins contains pid models +h5exporters = false # true if plugin contains custom h5 file exporters +scanners = false # true if plugin contains custom scan layout (daq_scan extensions) + +[urls] +package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_arduino' + +[project] +name = "pymodaq_plugins_arduino" #todo modify template by your plugin short name +description = 'Set of instrument plugins implemented using an Arduino Board.' +dependencies = [ + "pymodaq>=4.3.0", 'telemetrix', 'pyvisa', 'pyvisa-py' + #todo: list here all dependencies your package may have +] + +authors = [ + {name = "Sebastien J. Weber", email = "sebastien.weber@cemes.fr"}, + {name = "Jérémie Margueritat", email = "jeremie.margueritat@univ-lyon1.fr"}, + #todo: list here all authors of your plugin +] +maintainers = [ + {name = "Sebastien J. Weber", email = "sebastien.weber@cemes.fr"}, + {name = "Jérémie Margueritat", email = "jeremie.margueritat@univ-lyon1.fr"}, + #todo: list here all maintainers of your plugin +] + +# nottodo: leave everything below as is! + +dynamic = ["version", "urls", "entry-points"] +readme = "README.rst" +license = { file="LICENSE" } +requires-python = ">=3.8" + +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Human Machine Interfaces", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Software Development :: User Interfaces", +] + +[build-system] +requires = [ + "hatchling>=1.9.0", + "hatch-vcs", "toml", + "pymodaq_utils>=0.0.6", +] +build-backend = "hatchling.build" + +[tool.hatch.metadata.hooks.custom] + +[tool.hatch.version] +source = "vcs" diff --git a/setup.py b/setup.py deleted file mode 100644 index c85f63a..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -from pymodaq.resources.setup_plugin import setup -from pathlib import Path - -setup(Path(__file__).parent) From d90455b72bdfdd7d87fc4c93e36e982e8a40ae17 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 00:16:34 +0200 Subject: [PATCH 02/20] creating daq_move_StepperMotor --- .../daq_move_plugins/daq_move_StepperMotor.py | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py new file mode 100644 index 0000000..4fd1ef0 --- /dev/null +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -0,0 +1,195 @@ + +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_arduino.hardware.arduino_telemetrix import Arduino # ACS controller wrapper + + +class DAQ_Move_StepperMotor(DAQ_Move_base): + """ Minimalistic plugin to control ACS motion stages with PyMoDAQ. + + 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. + + Use the ACSpy package wrapper to communicate with the ACS motion stages. + It may works with up to 8 axes depending the configuration. + It does not consider the daisy chain option: only one controller. + Only ETHERNET communication is implemented (see ACS manual for configuration). + (It has been tested with USB to ethernet adapter). + Tested with ACS SPiiPlusEC controller and one drive UDMnt(2 axes). + The stages were alio's translation stages AI-CM-6000-XY. + PyMoDAQ version during the test was PyMoDAQ==5.0.5. + The operating system used was Windows 11. + Installation instructions: ACS drivers must be installed from the manufacturer's. + They usually come with the controller and with a "buffer" file for coniguration. + + + 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 + # Configured for only two axiss, but can be changed to 8 axes. + _axis_names: Union[List[str], Dict[str, int]] = {'Axis0':0, 'Axis1':1} + # Here all axes are translations stages with the same unit if difefrent type of stages are used as for example one translation and one rotation the dict must be updated in consequences. + _controller_units: Union[str, List[str]] = {'Axis0': 'mm', 'Axis1': 'mm'} + # WARNING: Please refer to your specific stage to set a meaningful value. If you use different type of stages (ex: translation and rotation) it can be replaced by the appropriate epsilon value. + _epsilon: Union[float, List[float]] = 0.00001 + data_actuator_type = DataActuatorType.DataActuator + # # wether you use the new data style for actuator otherwise set this + # as DataActuatorType.float (or entirely remove the line) + # At this step I will not use the new data dtyle it might be implemented in the future + + params = [ {'title': 'Serial Number', 'name': 'serial_num', 'type': 'str', + 'value': '', 'readonly': True,}, + {'title': 'Buffer Number', 'name': 'buff_num', 'type': 'int', + 'value': 1, 'limits': [0, 9],} + ] + 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): + # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy + # autocompletion + self.controller: Controller = None + + #TODO 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.axes[self.axis_value].rpos, + unit=self.axis_unit) + 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): + """Terminate the communication protocol""" + self.controller.disable_all() + self.controller.disconnect() + + 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.get_actuator_value() # to update the current position of the axis + #self.axis_unit = 'mm' + # 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() + 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 = Controller(contype="ethernet", n_axes=2) + self.controller.connect() # any object that will control the stages + self.controller.enable_all() # enable all axes + self.settings['serial_num']=self.controller.serial_number() + self.controller.load_buffer(self.settings['buff_num']) # load the buffer file (it is needed to configure the controller) + + + info = "Controller connected and axis enabled" + initialized = True#self.controller.a_method_or_atttribute_to_check_if_init() # todo + 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.axes[self.axis_value].ptp(value.value()) # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + 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.axes[self.axis_value].ptpr(value.value()) + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + def move_home(self): + """Call the reference method of the controller""" + self.move_abs(DataActuator(data=0, unit=self.axis_unit)) + + + def stop_motion(self): + """Stop the actuator and emits move_done signal""" + + ## TODO for your custom plugin + raise NotImplemented # when writing your own plugin remove this line + self.controller.your_method_to_stop_positioning() # when writing your own plugin replace this line + self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + + +if __name__ == '__main__': + main(__file__) \ No newline at end of file From caaedf60c61b21cca5f761eece3018f53a9bbc7f Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 00:21:37 +0200 Subject: [PATCH 03/20] updating git tests --- .github/workflows/Test.yml | 2 +- .github/workflows/Testbase.yml | 8 +-- .github/workflows/compatibility.yml | 78 ++++++++++++++++++++++++++++ .github/workflows/python-publish.yml | 31 ++++++----- 4 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/compatibility.yml diff --git a/.github/workflows/Test.yml b/.github/workflows/Test.yml index 720d4db..ffd1522 100644 --- a/.github/workflows/Test.yml +++ b/.github/workflows/Test.yml @@ -6,5 +6,5 @@ jobs: call_workflow: uses: ./.github/workflows/Testbase.yml with: - python: '3.10' + python: '3.11' qt5: 'pyqt5' \ No newline at end of file diff --git a/.github/workflows/Testbase.yml b/.github/workflows/Testbase.yml index ed4517b..614121e 100644 --- a/.github/workflows/Testbase.yml +++ b/.github/workflows/Testbase.yml @@ -18,9 +18,9 @@ jobs: QT_DEBUG_PLUGINS: 1 steps: - name: Set up Python ${{ inputs.python }} - uses: actions/checkout@v3 + uses: actions/checkout@v4.2.2 - name: Install dependencies - uses: actions/setup-python@v4 + uses: actions/setup-python@v5.4.0 with: python-version: ${{ inputs.python }} - name: Install package @@ -29,7 +29,7 @@ jobs: python -m pip install --upgrade pip export QT_DEBUG_PLUGINS=1 pip install flake8 pytest pytest-cov pytest-qt pytest-xdist pytest-xvfb setuptools wheel numpy h5py ${{ inputs.qt5 }} toml - pip install pymodaq + pip install pymodaq pyqt5 pip install -e . - name: create local pymodaq folder and setting permissions run: | @@ -41,4 +41,4 @@ jobs: flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=src/pymodaq/resources/QtDesigner_Ressources,docs - name: Test with pytest run: | - pytest -n auto + pytest -n auto -k "not test_compatibility" \ No newline at end of file diff --git a/.github/workflows/compatibility.yml b/.github/workflows/compatibility.yml new file mode 100644 index 0000000..7c601f5 --- /dev/null +++ b/.github/workflows/compatibility.yml @@ -0,0 +1,78 @@ +name: Compatibility with pymodaq (latest release) + +on: + workflow_call: + + pull_request: + + push: + branches: + - '*' + +concurrency: + # github.workflow: name of the workflow + # github.event.pull_request.number || github.ref: pull request number or branch name if not a pull request + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # Cancel in-progress runs when a new workflow with the same group name is triggered + cancel-in-progress: true + +jobs: + tests: + continue-on-error: true + strategy: + fail-fast: false + matrix: + os: ["ubuntu-latest", "windows-latest"] + python-version: ["3.9", "3.10", "3.11", "3.12"] + qt-backend: ["pyqt5", "pyqt6", "pyside6"] + runs-on: ${{ matrix.os }} + env: + DISPLAY: ':99' + QT_DEBUG_PLUGINS: 1 + + steps: + - name: Set project name environment variable + run: | + echo "plugin_name=$(echo '${{ github.repository }}' | cut -d'/' -f2)" >> $GITHUB_ENV + + - name: Checkout the repo + uses: actions/checkout@v4.2.2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5.4.0 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest pytest-cov pytest-qt pytest-xvfb pytest-xdist setuptools wheel numpy h5py pymodaq ${{ matrix.qt-backend }} + pip install -e . + + # Create folder and set permissions on Ubuntu + - name: Create local pymodaq folder setup env (Linux) + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install -y libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-cursor0 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils libgl1 libegl1 + export QT_DEBUG_PLUGINS=1 + sudo mkdir -p /etc/.pymodaq + sudo chmod uo+rw /etc/.pymodaq + - name: Exporting debug variables (Windows) + if: runner.os == 'Windows' + run: | + set QT_DEBUG_PLUGINS=1 + + - name: Compatibility tests with ${{ matrix.os }} ${{ matrix.python-version }} ${{ matrix.qt-backend}} + run: | + pytest -vv -n 1 -k "test_compatibility" + + - name: Upload compatibility report + if: failure() + uses: actions/upload-artifact@v4.6.1 + with: + name: + path: 'import_report_tests_${{ env.plugin_name }}_None.txt' + if-no-files-found: error + + \ No newline at end of file diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 55326c5..78ca73f 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -13,25 +13,28 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v4.2.2 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5.4.0 with: - python-version: '3.11' + python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine toml "pymodaq>=4.1.0" pyqt5 - - - name: create local pymodaq folder and setting permissions + pip install hatch hatchling toml twine + - name: Get history and tags for SCM versioning to work run: | - sudo mkdir /etc/.pymodaq - sudo chmod uo+rw /etc/.pymodaq - - - name: Build and publish + git branch + git fetch --prune --unshallow + git fetch --depth=1 origin +refs/tags/*:refs/tags/* + hatch version + - name: Build + run: hatch build + - name: Check the build + run: twine check dist/* + - name: publish env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + HATCH_INDEX_USER: ${{ secrets.PYPI_USERNAME }} + HATCH_INDEX_AUTH: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel - twine upload dist/* + hatch publish \ No newline at end of file From 8bdce844f8f406fe59c0bcc231355676bafafc2c Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 21:53:40 +0200 Subject: [PATCH 04/20] Changing the parameter list and implementing move_done_callback and user_condition_to_reach_target --- .../daq_move_plugins/daq_move_StepperMotor.py | 48 +++++++------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 4fd1ef0..68f0af3 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -37,33 +37,23 @@ class DAQ_Move_StepperMotor(DAQ_Move_base): # TODO add your particular attributes here if any """ - is_multiaxes = False - # Configured for only two axiss, but can be changed to 8 axes. + is_multiaxes = True + # Configured for only two stepper motors with arduino Uno board. _axis_names: Union[List[str], Dict[str, int]] = {'Axis0':0, 'Axis1':1} - # Here all axes are translations stages with the same unit if difefrent type of stages are used as for example one translation and one rotation the dict must be updated in consequences. - _controller_units: Union[str, List[str]] = {'Axis0': 'mm', 'Axis1': 'mm'} - # WARNING: Please refer to your specific stage to set a meaningful value. If you use different type of stages (ex: translation and rotation) it can be replaced by the appropriate epsilon value. - _epsilon: Union[float, List[float]] = 0.00001 + # TODO changing the name using the config file is not implemented yet. + _controller_units: Union[str, List[str]] = '' + _epsilon: Union[float, List[float]] = 1 data_actuator_type = DataActuatorType.DataActuator - # # wether you use the new data style for actuator otherwise set this - # as DataActuatorType.float (or entirely remove the line) - # At this step I will not use the new data dtyle it might be implemented in the future - params = [ {'title': 'Serial Number', 'name': 'serial_num', 'type': 'str', - 'value': '', 'readonly': True,}, - {'title': 'Buffer Number', 'name': 'buff_num', 'type': 'int', - 'value': 1, 'limits': [0, 9],} + + params = [ {{'title': 'Ports:', 'name': 'com_port', 'type': 'list', + 'value': config('com_port'), 'limits': Arduino.COM_PORTS} ] + 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): - # TODO declare the type of the wrapper (and assign it to self.controller) you're going to use for easy - # autocompletion - self.controller: Controller = None - - #TODO declare here attributes you want/need to init with a default value - pass + self.controller: Optional[Arduino] = None def get_actuator_value(self): """Get the current value from the hardware with scaling conversion. @@ -79,19 +69,15 @@ def get_actuator_value(self): 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 + def move_done_callback(self, val: int): + """Will be triggered for each end of move; abs, rel or homing""" + self._move_done = True + self.stop_motion() + logger.debug('Callback called') - 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 user_condition_to_reach_target(self) -> bool: + """ Will be triggered for each end of move; abs, rel or homing""" + return self._move_done def close(self): """Terminate the communication protocol""" From 7e1e5cc8fb055fb1b21b9f92213a4ef7e65d1d6a Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 22:34:29 +0200 Subject: [PATCH 05/20] adding a stepper motor example for test purpose. --- .../hardware/stepper_absolute.py | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/pymodaq_plugins_arduino/hardware/stepper_absolute.py diff --git a/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py b/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py new file mode 100644 index 0000000..80ee586 --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py @@ -0,0 +1,132 @@ +""" + Copyright (c) 2022 Alan Yorinks All rights reserved. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + Version 3 as published by the Free Software Foundation; either + or (at your option) any later version. + This library is distributed in the hope that it will be useful,f + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE + along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +""" +import sys +import time + +from telemetrix import telemetrix + +""" +Run a motor to an absolute position. Server will send a callback notification +when motion is complete. + +Motor used to test is a NEMA-17 size - 200 steps/rev, 12V 350mA. +And the driver is a TB6600 4A 9-42V Nema 17 Stepper Motor Driver. + +The driver was connected as follows: +VCC 12 VDC +GND Power supply ground +ENA- Not connected +ENA+ Not connected +DIR- GND +DIR+ GPIO Pin 23 ESP32 +PUL- ESP32 GND +PUL+ GPIO Pin 22 ESP32 +A-, A+ Coil 1 stepper motor +B-, B+ Coil 2 stepper motor + +""" + +# GPIO Pins +PULSE_PIN = 8 +DIRECTION_PIN = 9 + +# flag to keep track of the number of times the callback +# was called. When == 2, exit program +exit_flag = 0 + + +def the_callback(data): + global exit_flag + date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(data[2])) + print(f'Motor {data[1]} absolute motion completed at: {date}.') + exit_flag += 1 + + +def running_callback(data): + if data[1]: + print('The motor is running.') + else: + print('The motor IS NOT running.') + + +def step_absolute(the_board): + + global exit_flag + # create an accelstepper instance for a TB6600 motor drive + # if you are using a micro stepper controller board: + # pin1 = pulse pin, pin2 = direction + motor = the_board.set_pin_mode_stepper(interface=1, pin1=PULSE_PIN, + pin2=DIRECTION_PIN) + + # if you are using a 28BYJ-48 Stepper Motor with ULN2003 + # comment out the line above and uncomment out the line below. + # motor = the_board.set_pin_mode_stepper(interface=4, pin1=5, pin2=4, pin3=14, + # pin4=12) + + # the_board.stepper_is_running(motor, callback=running_callback) + time.sleep(.5) + + # set the max speed and acceleration + the_board.stepper_set_current_position(0, 0) + the_board.stepper_set_max_speed(motor, 400) + the_board.stepper_set_acceleration(motor, 800) + + # set the absolute position in steps + the_board.stepper_move_to(motor, 2000) + + # run the motor + print('Starting motor...') + the_board.stepper_run(motor, completion_callback=the_callback) + time.sleep(.2) + #the_board.stepper_is_running(motor, callback=running_callback) + #time.sleep(.2) + while exit_flag == 0: + time.sleep(.2) + + the_board.stepper_set_current_position(0, 0) + the_board.stepper_set_max_speed(motor, 400) + the_board.stepper_set_acceleration(motor, 800) + # set the absolute position in steps + print('Running motor in opposite direction') + the_board.stepper_move_to(motor, -2000) + + the_board.stepper_run(motor, completion_callback=the_callback) + time.sleep(.2) + ##the_board.stepper_is_running(motor, callback=running_callback) + #time.sleep(.2) + + # keep application running + while exit_flag < 10: + try: + time.sleep(.2) + except KeyboardInterrupt: + the_board.shutdown() + sys.exit(0) + the_board.shutdown() + sys.exit(0) + + +# instantiate telemetrix +board = telemetrix.Telemetrix() +try: + # start the main function + step_absolute(board) + board.shutdown() +except KeyboardInterrupt: + board.shutdown() + sys.exit(0) \ No newline at end of file From 17f4f7b9eebaecbfbb5a3d3f19cd7b8489cfd429 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Thu, 24 Apr 2025 22:48:08 +0200 Subject: [PATCH 06/20] Adding enable capabilities to the example. --- src/pymodaq_plugins_arduino/hardware/stepper_absolute.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py b/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py index 80ee586..0c42979 100644 --- a/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py +++ b/src/pymodaq_plugins_arduino/hardware/stepper_absolute.py @@ -72,6 +72,8 @@ def step_absolute(the_board): # pin1 = pulse pin, pin2 = direction motor = the_board.set_pin_mode_stepper(interface=1, pin1=PULSE_PIN, pin2=DIRECTION_PIN) + the_board.set_pin_mode_digital_output(7) + the_board.digital_write(7, 1) # enable the motor driver # if you are using a 28BYJ-48 Stepper Motor with ULN2003 # comment out the line above and uncomment out the line below. @@ -79,8 +81,8 @@ def step_absolute(the_board): # pin4=12) # the_board.stepper_is_running(motor, callback=running_callback) - time.sleep(.5) - + time.sleep(5) + the_board.digital_write(7, 0) # set the max speed and acceleration the_board.stepper_set_current_position(0, 0) the_board.stepper_set_max_speed(motor, 400) From db85e8b1e906c8fe1a27bcc4e432f0397d54889b Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Fri, 25 Apr 2025 21:24:37 +0200 Subject: [PATCH 07/20] Adding a test program test_stepper_class for dev purpose. Implementation of new methods for the stepper motor in arduino_telemetrix. --- .../hardware/arduino_telemetrix.py | 52 ++++++++++++++-- .../hardware/test_stepper_class.py | 62 +++++++++++++++++++ 2 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/pymodaq_plugins_arduino/hardware/test_stepper_class.py diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py index 24fa194..a15d96d 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py @@ -1,6 +1,5 @@ import numbers -from threading import Lock - +from threading import Lock, Event from pyvisa import ResourceManager from telemetrix import telemetrix @@ -27,6 +26,8 @@ def __init__(self, *args, **kwargs): 3: 0, 4: 0, 5: 0} # Initialized dictionary for 6 analog channels + self.stepper_motor = None + self.completion_event = Event() @staticmethod def round_value(value): @@ -96,17 +97,56 @@ def servo_move_degree(self, pin: int, value: float): self.pin_values_output[pin] = value lock.release() + #Stepper Motor Methods + def initialize_stepper_motor(self, pulse_pin, direction_pin, enable_pin=7): + """ Initialize the stepper motor with the given pins """ + self.stepper_motor = self.set_pin_mode_stepper(interface=1, pin1=pulse_pin, pin2=direction_pin) + self.enable = enable_pin + self.set_pin_mode_digital_output(self.enable) # Set the enable pin as digital output + self.digital_write(self.enable , 1) # Disable the motor driver to avoid electrical consumption + self.stepper_set_current_position(self.stepper_motor, 0) # Set the current position to 0 + + def move_stepper_to_position(self, position, max_speed=200, acceleration=400): + """ Move the stepper motor to the specified position """ + if self.stepper_motor is None: + raise ValueError("Stepper motor not initialized. Call initialize_stepper_motor first.") + + # Set motor parameters + self.stepper_set_max_speed(self.stepper_motor, max_speed) + self.stepper_set_acceleration(self.stepper_motor, acceleration) + + # Set the target position + self.stepper_move_to(self.stepper_motor, position) + + # Run the motor and wait for completion + print(f'Starting motor to move to position {position}...') + self.completion_event.clear() + self.digital_write(self.enable, 0) # Enable the motor driver + self.stepper_run(self.stepper_motor, completion_callback=self._stepper_callback) + self.completion_event.wait() + self.digital_write(self.enable, 1) # Disable the motor driver + + def _stepper_callback(self, data): + """ Callback function to signal that the stepper motor has completed its movement """ + self.completion_event.set() # Signal that the motion is complete if __name__ == '__main__': import time - tele = Arduino('COM6') + tele = Arduino('COM10') + + # Test servo motor tele.set_pin_mode_servo(5, 100, 3000) time.sleep(.2) - tele.servo_write(5, 90) - time.sleep(1) - tele.servo_write(5, 00) + # Test stepper motor + tele.initialize_stepper_motor(pulse_pin=8, direction_pin=9, enable_pin=7) + tele.move_stepper_to_position(2000) # Move to position 2000 + time.sleep(2) + tele.move_stepper_to_position(-2000) # Move to position -2000 + time.sleep(2) + + tele.shutdown() \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/hardware/test_stepper_class.py b/src/pymodaq_plugins_arduino/hardware/test_stepper_class.py new file mode 100644 index 0000000..637cf7b --- /dev/null +++ b/src/pymodaq_plugins_arduino/hardware/test_stepper_class.py @@ -0,0 +1,62 @@ +import sys +import time +from telemetrix import telemetrix +from threading import Event + + +class StepperMotorController: + def __init__(self, pulse_pin, direction_pin, enable_pin=7): + self.pulse_pin = pulse_pin + self.direction_pin = direction_pin + self.enable_pin = enable_pin + self.board = telemetrix.Telemetrix() + self.motor = self.board.set_pin_mode_stepper(interface=1, pin1=self.pulse_pin, pin2=self.direction_pin) + self.completion_event = Event() + + # Initialize the enable pin + self.board.set_pin_mode_digital_output(self.enable_pin) + self.board.digital_write(self.enable_pin, 1) # Disbale the motor driver + + def the_callback(self, data): + self.completion_event.set() # Signal that the motion is complete + + def move_to_position(self, position, max_speed=400, acceleration=800): + # Set motor parameters + self.board.stepper_set_current_position(self.motor, 0) + self.board.stepper_set_max_speed(self.motor, max_speed) + self.board.stepper_set_acceleration(self.motor, acceleration) + + # Set the target position + self.board.stepper_move_to(self.motor, position) + + # Run the motor and wait for completion + print(f'Starting motor to move to position {position}...') + self.completion_event.clear() + self.board.digital_write(self.enable_pin, 0) # Enable the motor driver + self.board.stepper_run(self.motor, completion_callback=self.the_callback) + self.completion_event.wait() # Block until the motion is complete + self.board.digital_write(self.enable_pin, 1) + + def shutdown(self): + self.board.shutdown() + # Disable the motor driver + + +if __name__ == "__main__": + try: + # Create an instance of the StepperMotorController + stepper = StepperMotorController(pulse_pin=8, direction_pin=9) + + # Move to the first position + stepper.move_to_position(2000) + + # Move to the opposite position + print('Running motor in opposite direction...') + stepper.move_to_position(-2000) + + # Shutdown the board + stepper.shutdown() + + except KeyboardInterrupt: + stepper.shutdown() + sys.exit(0) \ No newline at end of file From efeecee8b73bca72dcb534b07ef143069c3138d8 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Fri, 25 Apr 2025 22:26:11 +0200 Subject: [PATCH 08/20] Adding a get_stepper_position method arduino_telemetrix. --- .../daq_move_plugins/daq_move_StepperMotor.py | 6 ++-- .../hardware/arduino_telemetrix.py | 31 +++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 68f0af3..58dee96 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -39,15 +39,15 @@ class DAQ_Move_StepperMotor(DAQ_Move_base): """ is_multiaxes = True # Configured for only two stepper motors with arduino Uno board. - _axis_names: Union[List[str], Dict[str, int]] = {'Axis0':0, 'Axis1':1} + _axis_names: Union[List[str], Dict[str, int]] = {'Motor0':0, 'Motor1':1} # TODO changing the name using the config file is not implemented yet. _controller_units: Union[str, List[str]] = '' _epsilon: Union[float, List[float]] = 1 data_actuator_type = DataActuatorType.DataActuator - params = [ {{'title': 'Ports:', 'name': 'com_port', 'type': 'list', - 'value': config('com_port'), 'limits': Arduino.COM_PORTS} + params = [ {'title': 'Ports:', 'name': 'com_port', 'type': 'list', + 'value': config('com_port'), 'limits': Arduino.COM_PORTS}, ] + 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 diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py index a15d96d..9181b18 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py @@ -28,7 +28,7 @@ def __init__(self, *args, **kwargs): 5: 0} # Initialized dictionary for 6 analog channels self.stepper_motor = None self.completion_event = Event() - + @staticmethod def round_value(value): return max(0, min(255, int(value))) @@ -129,6 +129,21 @@ def move_stepper_to_position(self, position, max_speed=200, acceleration=400): def _stepper_callback(self, data): """ Callback function to signal that the stepper motor has completed its movement """ self.completion_event.set() # Signal that the motion is complete + + + def get_stepper_position(self): + """ Retrieve the current position of the stepper motor """ + position_event = Event() + def position_callback(data): + """ Callback function to retrieve the current position of the stepper motor """ + self.position = data[2] + position_event.set() + + self.stepper_get_current_position(self.stepper_motor, + current_position_callback=position_callback) + position_event.wait() + print(f'move to {self.position}') + return self.position if __name__ == '__main__': import time @@ -143,10 +158,14 @@ def _stepper_callback(self, data): # Test stepper motor tele.initialize_stepper_motor(pulse_pin=8, direction_pin=9, enable_pin=7) - tele.move_stepper_to_position(2000) # Move to position 2000 - time.sleep(2) - tele.move_stepper_to_position(-2000) # Move to position -2000 - time.sleep(2) - + tele.get_stepper_position() + time.sleep(0.2) + tele.move_stepper_to_position(500) # Move to position 2000 + time.sleep(0.2) + tele.get_stepper_position() + time.sleep(0.2) + tele.move_stepper_to_position(-500) # Move to position -2000 + time.sleep(0.2) + tele.get_stepper_position() tele.shutdown() \ No newline at end of file From cc6bea5d76a96951a1e035cf20c7339cde5a76d9 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 12:39:18 +0200 Subject: [PATCH 09/20] Correcting errors Optional and Config Import --- .../daq_move_plugins/daq_move_StepperMotor.py | 43 ++++++------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 58dee96..4d7129a 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -1,47 +1,41 @@ -from typing import Union, List, Dict +from typing import Union, List, Dict, Optional 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_arduino.hardware.arduino_telemetrix import Arduino # ACS controller wrapper +from pymodaq_plugins_arduino.hardware.arduino_telemetrix import Arduino +from pymodaq_plugins_arduino.utils import Config +config = Config() class DAQ_Move_StepperMotor(DAQ_Move_base): - """ Minimalistic plugin to control ACS motion stages with PyMoDAQ. + """ Plugin to control stepper motor using Arduino controller and PyMoDAQ. 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. - Use the ACSpy package wrapper to communicate with the ACS motion stages. - It may works with up to 8 axes depending the configuration. + Use the arduino_telemetrix wrapper to communicate with the Arduino Board. + It may works with up to 4 axes depending the configuration. It does not consider the daisy chain option: only one controller. - Only ETHERNET communication is implemented (see ACS manual for configuration). - (It has been tested with USB to ethernet adapter). - Tested with ACS SPiiPlusEC controller and one drive UDMnt(2 axes). - The stages were alio's translation stages AI-CM-6000-XY. + Tested with Aarduino UNo and one motor NEMA17 (1 axe). PyMoDAQ version during the test was PyMoDAQ==5.0.5. The operating system used was Windows 11. - Installation instructions: ACS drivers must be installed from the manufacturer's. - They usually come with the controller and with a "buffer" file for coniguration. + Telemetrix4arduino has to be upload on the Arduino board. - 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 = True - # Configured for only two stepper motors with arduino Uno board. _axis_names: Union[List[str], Dict[str, int]] = {'Motor0':0, 'Motor1':1} # TODO changing the name using the config file is not implemented yet. - _controller_units: Union[str, List[str]] = '' + _controller_units: Union[str, List[str]] = '' #steps _epsilon: Union[float, List[float]] = 1 data_actuator_type = DataActuatorType.DataActuator @@ -49,9 +43,7 @@ class DAQ_Move_StepperMotor(DAQ_Move_base): params = [ {'title': 'Ports:', 'name': 'com_port', 'type': 'list', 'value': config('com_port'), 'limits': Arduino.COM_PORTS}, ] + 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): self.controller: Optional[Arduino] = None @@ -69,20 +61,13 @@ def get_actuator_value(self): pos = self.get_position_with_scaling(pos) return pos - def move_done_callback(self, val: int): - """Will be triggered for each end of move; abs, rel or homing""" - self._move_done = True - self.stop_motion() - logger.debug('Callback called') - def user_condition_to_reach_target(self) -> bool: """ Will be triggered for each end of move; abs, rel or homing""" return self._move_done def close(self): """Terminate the communication protocol""" - self.controller.disable_all() - self.controller.disconnect() + self.controller.shutdown() def commit_settings(self, param: Parameter): """Apply the consequences of a change of value in the detector settings @@ -123,7 +108,7 @@ def ini_stage(self, controller=None): 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 = Controller(contype="ethernet", n_axes=2) + self.controller = self.Controller(contype="ethernet", n_axes=2) self.controller.connect() # any object that will control the stages self.controller.enable_all() # enable all axes self.settings['serial_num']=self.controller.serial_number() From dc3e8d89ed15f717c99f82b6f667f921daebd4fd Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 12:47:27 +0200 Subject: [PATCH 10/20] adding stepper config in config_template.toml --- .../resources/config_template.toml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 13b207c..5547eff 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -17,4 +17,10 @@ cols = 16 rows = 2 [servo] -pin = 3 \ No newline at end of file +pin = 3 + +[stepper] +[stepper.pins] +ena_pin = 7 +pul_pin = 8 +dir_pin = 9 \ No newline at end of file From b753ea653f78e60d3bda2b360f12cbefb4cb319f Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 13:30:15 +0200 Subject: [PATCH 11/20] removing the Dataactuor type for instance, and modification to convert float target position into integer --- .../daq_move_plugins/daq_move_StepperMotor.py | 43 ++++++++----------- .../hardware/arduino_telemetrix.py | 6 +-- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 4d7129a..e7675a7 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -33,11 +33,10 @@ class DAQ_Move_StepperMotor(DAQ_Move_base): """ is_multiaxes = True - _axis_names: Union[List[str], Dict[str, int]] = {'Motor0':0, 'Motor1':1} - # TODO changing the name using the config file is not implemented yet. + _axis_names: Union[List[str], Dict[str, int]] = {'Stepper':0} _controller_units: Union[str, List[str]] = '' #steps _epsilon: Union[float, List[float]] = 1 - data_actuator_type = DataActuatorType.DataActuator + data_actuator_type = DataActuatorType.float params = [ {'title': 'Ports:', 'name': 'com_port', 'type': 'list', @@ -55,15 +54,14 @@ def get_actuator_value(self): float: The position obtained after scaling conversion. """ - pos = DataActuator( - data=self.controller.axes[self.axis_value].rpos, - unit=self.axis_unit) + pos = self.controller.get_stepper_position() pos = self.get_position_with_scaling(pos) return pos def user_condition_to_reach_target(self) -> bool: """ Will be triggered for each end of move; abs, rel or homing""" - return self._move_done + #åreturn self._move_done + pass def close(self): """Terminate the communication protocol""" @@ -78,17 +76,7 @@ def commit_settings(self, 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.get_actuator_value() # to update the current position of the axis - #self.axis_unit = 'mm' - # 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() - else: - pass + pass def ini_stage(self, controller=None): """Actuator communication initialization @@ -108,14 +96,17 @@ def ini_stage(self, controller=None): 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 = self.Controller(contype="ethernet", n_axes=2) - self.controller.connect() # any object that will control the stages - self.controller.enable_all() # enable all axes - self.settings['serial_num']=self.controller.serial_number() - self.controller.load_buffer(self.settings['buff_num']) # load the buffer file (it is needed to configure the controller) - + self.controller = Arduino( + com_port=self.settings['com_port'] + ) + self.controller.initialize_stepper_motor( + config('stepper','pins','pul_pin'), + config('stepper','pins','dir_pin'), + config('stepper','pins','ena_pin') + ) + - info = "Controller connected and axis enabled" + info = "Stepper motor connected with config file " initialized = True#self.controller.a_method_or_atttribute_to_check_if_init() # todo return info, initialized @@ -130,7 +121,7 @@ def move_abs(self, value: DataActuator): 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.axes[self.axis_value].ptp(value.value()) # when writing your own plugin replace this line + self.controller.move_stepper_to_position(value) # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) def move_rel(self, value: DataActuator): diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py index 9181b18..81e8805 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py @@ -106,7 +106,7 @@ def initialize_stepper_motor(self, pulse_pin, direction_pin, enable_pin=7): self.digital_write(self.enable , 1) # Disable the motor driver to avoid electrical consumption self.stepper_set_current_position(self.stepper_motor, 0) # Set the current position to 0 - def move_stepper_to_position(self, position, max_speed=200, acceleration=400): + def move_stepper_to_position(self, position: float, max_speed=200, acceleration=400): """ Move the stepper motor to the specified position """ if self.stepper_motor is None: raise ValueError("Stepper motor not initialized. Call initialize_stepper_motor first.") @@ -116,7 +116,7 @@ def move_stepper_to_position(self, position, max_speed=200, acceleration=400): self.stepper_set_acceleration(self.stepper_motor, acceleration) # Set the target position - self.stepper_move_to(self.stepper_motor, position) + self.stepper_move_to(self.stepper_motor, int(position)) # Run the motor and wait for completion print(f'Starting motor to move to position {position}...') @@ -142,7 +142,7 @@ def position_callback(data): self.stepper_get_current_position(self.stepper_motor, current_position_callback=position_callback) position_event.wait() - print(f'move to {self.position}') + print(f'actual position {self.position}') return self.position if __name__ == '__main__': From 61ce3a5778232164e25405185376644dd56814d7 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 14:03:36 +0200 Subject: [PATCH 12/20] implementing the user_condition_to_reach_target, it waits for the movement completion on the arduino side. The completion is definied in the arduino_telemetrix wrapper in the method move_stepper_to_position, where a callback is implemented. --- .../daq_move_plugins/daq_move_StepperMotor.py | 11 +++------- .../hardware/arduino_telemetrix.py | 21 +++++++++---------- 2 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index e7675a7..9c72567 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -60,8 +60,7 @@ def get_actuator_value(self): def user_condition_to_reach_target(self) -> bool: """ Will be triggered for each end of move; abs, rel or homing""" - #åreturn self._move_done - pass + return self._move_done def close(self): """Terminate the communication protocol""" @@ -99,11 +98,7 @@ def ini_stage(self, controller=None): self.controller = Arduino( com_port=self.settings['com_port'] ) - self.controller.initialize_stepper_motor( - config('stepper','pins','pul_pin'), - config('stepper','pins','dir_pin'), - config('stepper','pins','ena_pin') - ) + self.controller.initialize_stepper_motor(8, 9, 7) # pulse, direction, enable pins info = "Stepper motor connected with config file " @@ -121,7 +116,7 @@ def move_abs(self, value: DataActuator): 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.move_stepper_to_position(value) # when writing your own plugin replace this line + self._move_done = self.controller.move_stepper_to_position(value) # when writing your own plugin replace this line self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) def move_rel(self, value: DataActuator): diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py index 81e8805..532da88 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py @@ -27,8 +27,7 @@ def __init__(self, *args, **kwargs): 4: 0, 5: 0} # Initialized dictionary for 6 analog channels self.stepper_motor = None - self.completion_event = Event() - + @staticmethod def round_value(value): return max(0, min(255, int(value))) @@ -114,17 +113,17 @@ def move_stepper_to_position(self, position: float, max_speed=200, acceleration= # Set motor parameters self.stepper_set_max_speed(self.stepper_motor, max_speed) self.stepper_set_acceleration(self.stepper_motor, acceleration) - # Set the target position self.stepper_move_to(self.stepper_motor, int(position)) - - # Run the motor and wait for completion - print(f'Starting motor to move to position {position}...') - self.completion_event.clear() - self.digital_write(self.enable, 0) # Enable the motor driver - self.stepper_run(self.stepper_motor, completion_callback=self._stepper_callback) - self.completion_event.wait() - self.digital_write(self.enable, 1) # Disable the motor driver + completion_event = Event() + def completion_callback(data): + """ Callback function to signal that the stepper motor has completed its movement """ + completion_event.set() + self.digital_write(self.enable, 0) + self.stepper_run(self.stepper_motor, completion_callback=completion_callback) + completion_event.wait() + self.digital_write(self.enable, 1) + return True def _stepper_callback(self, data): """ Callback function to signal that the stepper motor has completed its movement """ From 1b39665097e0ee462dfc056d9db361303e9f3a6f Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:21:09 +0200 Subject: [PATCH 13/20] cleaning arduino_telemetrix.py --- .../daq_move_plugins/daq_move_StepperMotor.py | 6 +++++- src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py | 6 ------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 9c72567..87606ac 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -98,7 +98,11 @@ def ini_stage(self, controller=None): self.controller = Arduino( com_port=self.settings['com_port'] ) - self.controller.initialize_stepper_motor(8, 9, 7) # pulse, direction, enable pins + self.controller.initialize_stepper_motor( + config('stepper', 'pins', 'pul_pin'), + config('stepper', 'pins', 'dir_pin'), + config('stepper', 'pins', 'ena_pin'), + ) # pulse, direction, enable pins info = "Stepper motor connected with config file " diff --git a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py index 532da88..7d22b56 100644 --- a/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py +++ b/src/pymodaq_plugins_arduino/hardware/arduino_telemetrix.py @@ -125,11 +125,6 @@ def completion_callback(data): self.digital_write(self.enable, 1) return True - def _stepper_callback(self, data): - """ Callback function to signal that the stepper motor has completed its movement """ - self.completion_event.set() # Signal that the motion is complete - - def get_stepper_position(self): """ Retrieve the current position of the stepper motor """ position_event = Event() @@ -141,7 +136,6 @@ def position_callback(data): self.stepper_get_current_position(self.stepper_motor, current_position_callback=position_callback) position_event.wait() - print(f'actual position {self.position}') return self.position if __name__ == '__main__': From f93e3f675189015b1f1bb801c75c3a98278dfe3f Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:28:19 +0200 Subject: [PATCH 14/20] implementing the relative move --- .../daq_move_plugins/daq_move_StepperMotor.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 87606ac..0ec2953 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -116,12 +116,11 @@ def move_abs(self, value: DataActuator): ---------- 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._move_done = self.controller.move_stepper_to_position(value) # when writing your own plugin replace this line - self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + self._move_done = self.controller.move_stepper_to_position(value) + self.emit_status(ThreadCommand('Update_Status', ['absolute move done'])) def move_rel(self, value: DataActuator): """ Move the actuator to the relative target actuator value defined by value @@ -132,11 +131,9 @@ def move_rel(self, value: DataActuator): """ 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.axes[self.axis_value].ptpr(value.value()) - self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + value = self.set_position_relative_with_scaling(value) + self._move_done = self.controller.move_stepper_to_position(self.target_value) + self.emit_status(ThreadCommand('Update_Status', ['relative move done'])) def move_home(self): """Call the reference method of the controller""" From 9fe1b73a3565a82a009439bd4c13e5eba0bda607 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:30:12 +0200 Subject: [PATCH 15/20] some cleanings --- .../daq_move_plugins/daq_move_StepperMotor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 0ec2953..82d8151 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -53,7 +53,6 @@ def get_actuator_value(self): ------- float: The position obtained after scaling conversion. """ - pos = self.controller.get_stepper_position() pos = self.get_position_with_scaling(pos) return pos From 62db61825ffd7e3671346d27430a4c9a9c2af7db Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:37:33 +0200 Subject: [PATCH 16/20] more cleanings --- .../daq_move_plugins/daq_move_StepperMotor.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 82d8151..877d329 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -141,11 +141,7 @@ def move_home(self): def stop_motion(self): """Stop the actuator and emits move_done signal""" - - ## TODO for your custom plugin - raise NotImplemented # when writing your own plugin remove this line - self.controller.your_method_to_stop_positioning() # when writing your own plugin replace this line - self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) + pass if __name__ == '__main__': From 9cf2d9726b4b89726f60c6c64c9bb5d41f992a70 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:52:49 +0200 Subject: [PATCH 17/20] adding neam in the config_template.toml --- .../daq_move_plugins/daq_move_StepperMotor.py | 7 +++---- src/pymodaq_plugins_arduino/resources/config_template.toml | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 877d329..1c51345 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -33,7 +33,7 @@ class DAQ_Move_StepperMotor(DAQ_Move_base): """ is_multiaxes = True - _axis_names: Union[List[str], Dict[str, int]] = {'Stepper':0} + _axis_names: Union[List[str], Dict[str, int]] = {'Stepper':config('stepper')} _controller_units: Union[str, List[str]] = '' #steps _epsilon: Union[float, List[float]] = 1 data_actuator_type = DataActuatorType.float @@ -140,9 +140,8 @@ def move_home(self): def stop_motion(self): - """Stop the actuator and emits move_done signal""" - pass - + """Stop the actuator and emits move_done signal """ + pass if __name__ == '__main__': main(__file__) \ No newline at end of file diff --git a/src/pymodaq_plugins_arduino/resources/config_template.toml b/src/pymodaq_plugins_arduino/resources/config_template.toml index 5547eff..c66549b 100644 --- a/src/pymodaq_plugins_arduino/resources/config_template.toml +++ b/src/pymodaq_plugins_arduino/resources/config_template.toml @@ -20,6 +20,7 @@ rows = 2 pin = 3 [stepper] +name = 0 [stepper.pins] ena_pin = 7 pul_pin = 8 From 8b08fdc3c94cb64dde6381ac75e3824d91f795b4 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 21:55:08 +0200 Subject: [PATCH 18/20] correction of the homing --- .../daq_move_plugins/daq_move_StepperMotor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 1c51345..ec8cbd9 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -136,7 +136,7 @@ def move_rel(self, value: DataActuator): def move_home(self): """Call the reference method of the controller""" - self.move_abs(DataActuator(data=0, unit=self.axis_unit)) + self._move_done = self.controller.move_stepper_to_position(0) def stop_motion(self): From daffda9eded62c5b949622e1a079079e9e33da01 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 22:05:44 +0200 Subject: [PATCH 19/20] Updating move_home. Still an error the current value is not updated --- .../daq_move_plugins/daq_move_StepperMotor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index ec8cbd9..233c163 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -136,7 +136,8 @@ def move_rel(self, value: DataActuator): def move_home(self): """Call the reference method of the controller""" - self._move_done = self.controller.move_stepper_to_position(0) + self.move_abs(0) + self.emit_status(ThreadCommand('Update_Status', ['homing'])) def stop_motion(self): From 162f239d505b71300373612fcd685dbe365b9045 Mon Sep 17 00:00:00 2001 From: Jeremie Margueritat Date: Sun, 27 Apr 2025 22:25:21 +0200 Subject: [PATCH 20/20] Cleaning code. Still not satisfied with the move_home --- .../daq_move_plugins/daq_move_StepperMotor.py | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py index 233c163..43b8b67 100644 --- a/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py +++ b/src/pymodaq_plugins_arduino/daq_move_plugins/daq_move_StepperMotor.py @@ -1,148 +1,151 @@ - from typing import Union, List, Dict, Optional -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.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_arduino.hardware.arduino_telemetrix import Arduino +from pymodaq_plugins_arduino.hardware.arduino_telemetrix import Arduino from pymodaq_plugins_arduino.utils import Config config = Config() + class DAQ_Move_StepperMotor(DAQ_Move_base): - """ Plugin to control stepper motor using Arduino controller and PyMoDAQ. - + """Plugin to control stepper motor using Arduino controller and PyMoDAQ. + 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. - Use the arduino_telemetrix wrapper to communicate with the Arduino Board. - It may works with up to 4 axes depending the configuration. + Use the arduino_telemetrix wrapper to communicate with the Arduino Board. + It may work with up to 4 axes depending on the configuration. It does not consider the daisy chain option: only one controller. - Tested with Aarduino UNo and one motor NEMA17 (1 axe). + Tested with Arduino Uno and one motor NEMA17 (1 axis). PyMoDAQ version during the test was PyMoDAQ==5.0.5. The operating system used was Windows 11. - Telemetrix4arduino has to be upload on the Arduino board. - - Attributes: - ----------- + Telemetrix4arduino has to be uploaded on the Arduino board. + + Attributes + ---------- controller: object - The particular object that allow the communication with the hardware, in general a python wrapper around the - hardware library. - + The particular object that allows communication with the hardware, in general, a Python wrapper around the + hardware library. """ is_multiaxes = True - _axis_names: Union[List[str], Dict[str, int]] = {'Stepper':config('stepper')} - _controller_units: Union[str, List[str]] = '' #steps - _epsilon: Union[float, List[float]] = 1 - data_actuator_type = DataActuatorType.float - - - params = [ {'title': 'Ports:', 'name': 'com_port', 'type': 'list', - 'value': config('com_port'), 'limits': Arduino.COM_PORTS}, - ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) - + _axis_names: Union[List[str], Dict[str, int]] = {'Stepper': config('stepper')} + _controller_units: Union[str, List[str]] = '' # steps + _epsilon: Union[float, List[float]] = 1 + data_actuator_type = DataActuatorType.float + + params = [ + { + 'title': 'Ports:', + 'name': 'com_port', + 'type': 'list', + 'value': config('com_port'), + 'limits': Arduino.COM_PORTS, + } + ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) + def ini_attributes(self): self.controller: Optional[Arduino] = None - def get_actuator_value(self): + def get_actuator_value(self) -> float: """Get the current value from the hardware with scaling conversion. Returns ------- - float: The position obtained after scaling conversion. + float + The position obtained after scaling conversion. """ - pos = self.controller.get_stepper_position() + pos = self.controller.get_stepper_position() pos = self.get_position_with_scaling(pos) return pos def user_condition_to_reach_target(self) -> bool: - """ Will be triggered for each end of move; abs, rel or homing""" + """Will be triggered for each end of move; abs, rel, or homing.""" return self._move_done def close(self): - """Terminate the communication protocol""" + """Terminate the communication protocol.""" self.controller.shutdown() def commit_settings(self, param: Parameter): - """Apply the consequences of a change of value in the detector settings + """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 + A given parameter (within detector_settings) whose value has been changed by the user. """ - ## TODO for your custom plugin pass def ini_stage(self, controller=None): - """Actuator communication initialization + """Actuator communication initialization. Parameters ---------- - controller: (object) - custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case) + 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 + False if initialization failed otherwise True. """ - - self.ini_stage_init(slave_controller=controller) # will be useful when controller is slave + self.ini_stage_init(slave_controller=controller) # Useful when controller is slave - if self.is_master: # is needed when controller is master + if self.is_master: # Needed when controller is master self.controller = Arduino( com_port=self.settings['com_port'] - ) + ) self.controller.initialize_stepper_motor( config('stepper', 'pins', 'pul_pin'), config('stepper', 'pins', 'dir_pin'), config('stepper', 'pins', 'ena_pin'), - ) # pulse, direction, enable pins - - - info = "Stepper motor connected with config file " - initialized = True#self.controller.a_method_or_atttribute_to_check_if_init() # todo + ) # Pulse, direction, enable pins + + info = "Stepper motor connected with config file" + initialized = True # TODO: Replace with actual initialization check return info, initialized def move_abs(self, value: DataActuator): - """ Move the actuator to the absolute target defined by value + """Move the actuator to the absolute target defined by value. Parameters ---------- - value: (float) value of the absolute target positioning + value: float + Value of the absolute target positioning. """ - value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here + value = self.check_bound(value) # Apply bounds if user checked them self.target_value = value - value = self.set_position_with_scaling(value) # apply scaling if the user specified one - self._move_done = self.controller.move_stepper_to_position(value) + value = self.set_position_with_scaling(value) # Apply scaling if specified + self._move_done = self.controller.move_stepper_to_position(value) self.emit_status(ThreadCommand('Update_Status', ['absolute move done'])) def move_rel(self, value: DataActuator): - """ Move the actuator to the relative target actuator value defined by value + """Move the actuator to the relative target actuator value defined by value. Parameters ---------- - value: (float) value of the relative target positioning + 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) + value = self.set_position_relative_with_scaling(value) self._move_done = self.controller.move_stepper_to_position(self.target_value) self.emit_status(ThreadCommand('Update_Status', ['relative move done'])) def move_home(self): - """Call the reference method of the controller""" - self.move_abs(0) + """Call the reference method of the controller.""" + self.controller.move_stepper_to_position(0) # Move to home position (0) self.emit_status(ThreadCommand('Update_Status', ['homing'])) - def stop_motion(self): - """Stop the actuator and emits move_done signal """ - pass + """Stop the actuator and emit move_done signal.""" + pass + if __name__ == '__main__': main(__file__) \ No newline at end of file