diff --git a/README.rst b/README.rst index d2ae413..b4f3222 100644 --- a/README.rst +++ b/README.rst @@ -26,6 +26,7 @@ Contributors * Amelie Jarnac * Aurore Finco +* Sébastien Guerrero (sebastien.guerrero@insa-lyon.fr) Instruments =========== @@ -40,6 +41,6 @@ Viewer0D ++++++++ * **DAQmx_PLcounter**: Single photon counting - +* **NIDAQmx**: For now (01/2025) Only Analog Input tested and working. (current-voltage-temperature measurements on cDAQ & DAQ-USB) diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 0000000..a0208ee --- /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) diff --git a/plugin_info.toml b/plugin_info.toml deleted file mode 100644 index d5abec0..0000000 --- a/plugin_info.toml +++ /dev/null @@ -1,23 +0,0 @@ -## To modify by developper(s) of the plugin - -[plugin-info] -SHORT_PLUGIN_NAME = 'daqmx' #for instance daqmx -package-url = 'https://github.com/PyMoDAQ/pymodaq_plugins_daqmx' #to modify -description = 'Hardware plugins for PyMoDAQ using the NiDAQmx framework (pydaqmx wrapper)' - -author = 'Sébastien Weber' -author-email = 'sebastien.weber@cemes.fr' -license = 'MIT' - -[plugin-install] -#packages required for your plugin: -packages-required = ['pydaqmx', 'pymodaq>4.0.1'] -## - -[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 = false # true if plugins contains dashboard extensions -pid_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) - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..3726a60 --- /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 = false # 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_daqmx' + +[project] +name = "pymodaq_plugins_daqmx" +description = 'Hardware plugins for PyMoDAQ using the NiDAQmx framework (nidaq wrapper)' +dependencies = [ + "pymodaq>=4.4", + "pydaqmx", + "nidaqmx", + "aenum" +] + +authors = [ + {name = 'Sébastien Weber', email = 'sebastien.weber@cemes.fr'}, + {name = 'Sébastien Guerrero', email = 'sebastien.guerrero@insa-lyon.fr'}, +] +maintainers = [ + {name = 'Sébastien Weber', email = 'sebastien.weber@cemes.fr'}, + {name = 'Sébastien Guerrero', email = 'sebastien.guerrero@insa-lyon.fr'}, +] + +# 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/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_MultipleScannerControl.py b/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_MultipleScannerControl.py index 3e8bc0c..e95e90e 100644 --- a/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_MultipleScannerControl.py +++ b/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_MultipleScannerControl.py @@ -11,6 +11,7 @@ import PyDAQmx + class DAQ_Move_DAQmx_MultipleScannerControl(DAQ_Move_base): """Plugin to control a piezo scanners with a NI card. This modules requires a clock channel to handle the timing of the movement and display the position, and this clock channel is shared between the master and diff --git a/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_ScannerControl.py b/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_ScannerControl.py index 24ca8cf..1a576e6 100644 --- a/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_ScannerControl.py +++ b/src/pymodaq_plugins_daqmx/daq_move_plugins/daq_move_DAQmx_ScannerControl.py @@ -11,6 +11,7 @@ import PyDAQmx + class DAQ_Move_DAQmx_ScannerControl(DAQ_Move_base): """Plugin to control a piezo scanner with a NI card. This modules requires a clock channel to handle the timing of the movement and display the position. Avoid using several scanners (ie several analog outputs) diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx.py deleted file mode 100644 index 2c8d623..0000000 --- a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx.py +++ /dev/null @@ -1,25 +0,0 @@ -from pymodaq_plugins_daqmx.hardware.national_instruments.daq_NIDAQmx import DAQ_NIDAQmx_Viewer -from pymodaq.control_modules.viewer_utility_classes import main - - -class DAQ_0DViewer_DAQmx(DAQ_NIDAQmx_Viewer): - """ - ==================== ======================== - **Attributes** **Type** - *data_grabed_signal* instance of Signal - *params* dictionnary list - *task* - ==================== ======================== - - See Also - -------- - refresh_hardware - """ - control_type = "0D" # could be "0D", "1D" - - def __init__(self, *args, **kwargs): - super().__init__(*args, control_type=self.control_type, **kwargs) - - -if __name__ == '__main__': - main(__file__, False) diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxAI.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxAI.py deleted file mode 100644 index 35383e2..0000000 --- a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxAI.py +++ /dev/null @@ -1,168 +0,0 @@ -import numpy as np -from easydict import EasyDict as edict -from pymodaq.utils.daq_utils import ThreadCommand, getLineInfo -from pymodaq.utils.data import DataFromPlugins, Axis -from pymodaq.utils.logger import set_logger, get_module_name -from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main - -from pymodaq_plugins_daqmx.hardware.national_instruments.daqmx import DAQmx, DAQ_analog_types, DAQ_thermocouples,\ - DAQ_termination, Edge, DAQ_NIDAQ_source, \ - ClockSettings, AIChannel, Counter, AIThermoChannel, AOChannel, TriggerSettings, DOChannel, DIChannel - -logger = set_logger(get_module_name(__file__)) - - -class DAQ_0DViewer_DAQmxAI(DAQ_Viewer_base): - """Specific DAQmx plugin for getting analog input data as 0D or 1D data - - """ - params = comon_parameters+[ - {'title': 'Display type:', 'name': 'display', 'type': 'list', 'limits': ['0D', '1D']}, - {'title': 'Frequency Acq.:', 'name': 'frequency', 'type': 'int', 'value': 1000, 'min': 1}, - {'title': 'Nsamples:', 'name': 'Nsamples', 'type': 'int', 'value': 100, 'default': 100, 'min': 1}, - {'title': 'AI:', 'name': 'ai_channel', 'type': 'list', - 'limits': DAQmx.get_NIDAQ_channels(source_type='Analog_Input'), - 'value': DAQmx.get_NIDAQ_channels(source_type='Analog_Input')[0]}, - ] - hardware_averaging = True - live_mode_available = True - - def __init__(self, parent=None, params_state=None): - super().__init__(parent, params_state) - - self.channels_ai = None - self.clock_settings = None - self.data_tot = None - self.live = False - self.Naverage = 1 - self.ind_average = 0 - - def commit_settings(self, param): - """ - """ - - self.update_tasks() - - def ini_detector(self, controller=None): - """Detector communication initialization - - Parameters - ---------- - controller: (object) custom object of a PyMoDAQ plugin (Slave case). None if only one detector by controller (Master case) - - Returns - ------- - self.status (edict): with initialization status: three fields: - * info (str) - * controller (object) initialized controller - *initialized: (bool): False if initialization failed otherwise True - """ - self.controller = self.ini_detector_init(controller, dict(ai=DAQmx())) - - self.update_tasks() - - info = "Current measurement ready" - initialized = True - - return info, initialized - - def update_tasks(self): - - self.channels_ai = [AIChannel(name=self.settings.child('ai_channel').value(), - source='Analog_Input', analog_type='Voltage', - value_min=-10., value_max=10., termination='Diff', ), - ] - - self.clock_settings_ai = ClockSettings(frequency=self.settings.child('frequency').value(), - Nsamples=self.settings.child('Nsamples').value(), repetition=self.live) - - self.controller['ai'].update_task(self.channels_ai, self.clock_settings_ai) - - def close(self): - """ - Terminate the communication protocol - """ - pass - ## - - def grab_data(self, Naverage=1, **kwargs): - """ - - Parameters - ---------- - Naverage: (int) Number of hardware averaging - kwargs: (dict) of others optionals arguments - """ - - - update = False - - if 'live' in kwargs: - if kwargs['live'] != self.live: - update = True - self.live = kwargs['live'] - - if Naverage != self.Naverage: - self.Naverage = Naverage - update = True - - if update: - self.update_tasks() - - self.ind_average = 0 - self.data_tot = np.zeros((len(self.channels_ai), self.clock_settings_ai.Nsamples)) - - while not self.controller['ai'].isTaskDone(): - self.stop() - if self.controller['ai'].c_callback is None: - self.controller['ai'].register_callback(self.read_data, 'Nsamples', self.clock_settings_ai.Nsamples) - self.controller['ai'].task.StartTask() - #self.read_data(None, None, None, None) - - def read_data(self, taskhandle, status, samples=0, callbackdata=None): - #print(f'going to read {self.clock_settings_ai.Nsamples} samples, callbakc {samples}') - data = self.controller['ai'].readAnalog(len(self.channels_ai), self.clock_settings_ai) - if not self.live: - self.stop() - self.ind_average += 1 - self.data_tot += 1 / self.Naverage * data - - if self.ind_average == self.Naverage: - self.emit_data(self.data_tot) - self.ind_average = 0 - self.data_tot = np.zeros((len(self.channels_ai), self.clock_settings_ai.Nsamples)) - - return 0 #mandatory for the PyDAQmx callback - - def emit_data(self, data): - channels_name = [ch.name for ch in self.channels_ai] - - if self.settings.child('display').value() == '0D': - data = np.mean(data, 1) - - #data = np.squeeze(data) - # print(f'shape is {data.shape}') - # print(data) - if len(self.channels_ai) == 1 and data.size == 1: - data_export = [np.array([data[0]])] - else: - data_export = [np.squeeze(data[ind, :]) for ind in range(len(self.channels_ai))] - - # print(f'data len is {len(data_export)} and shape is {data_export[0].shape}') - self.data_grabed_signal.emit([DataFromPlugins( - name='NI AI', - data=data_export, - dim=f'Data{self.settings.child("display").value()}', labels=channels_name)]) - - def stop(self): - try: - self.controller['ai'].task.StopTask() - except: - pass - ############################## - - return '' - - -if __name__ == '__main__': - main(__file__) diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxDualAI.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxDualAI.py deleted file mode 100644 index d66792c..0000000 --- a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmxDualAI.py +++ /dev/null @@ -1,201 +0,0 @@ -import numpy as np -from qtpy.QtCore import QThread -from easydict import EasyDict as edict -from pymodaq.utils.daq_utils import ThreadCommand, getLineInfo -from pymodaq.utils.data import DataFromPlugins, Axis -from pymodaq.utils.logger import set_logger, get_module_name -from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main - -from pymodaq_plugins_daqmx.hardware.national_instruments.daqmx import DAQmx, DAQ_analog_types, DAQ_thermocouples,\ - DAQ_termination, Edge, DAQ_NIDAQ_source, \ - ClockSettings, AIChannel, Counter, AIThermoChannel, AOChannel, TriggerSettings, DOChannel, DIChannel - -logger = set_logger(get_module_name(__file__)) - - -class DAQ_0DViewer_DAQmxDualAI(DAQ_Viewer_base): - """ - """ - params = comon_parameters+[ - {'title': 'Display type:', 'name': 'display', 'type': 'list', 'limits': ['0D', '1D']}, - {'title': 'Frequency Acq.:', 'name': 'frequency', 'type': 'int', 'value': 1000, 'min': 1}, - {'title': 'Nsamples:', 'name': 'Nsamples', 'type': 'int', 'value': 100, 'default': 100, 'min': 1}, - {'title': 'AI0:', 'name': 'ai_channel0', 'type': 'list', - 'limits': DAQmx.get_NIDAQ_channels(source_type='Analog_Input'), - 'value': DAQmx.get_NIDAQ_channels(source_type='Analog_Input')[2]}, - {'title': 'AI1:', 'name': 'ai_channel1', 'type': 'list', - 'limits': DAQmx.get_NIDAQ_channels(source_type='Analog_Input'), - 'value': DAQmx.get_NIDAQ_channels(source_type='Analog_Input')[3]}, - ] - hardware_averaging = True - live_mode_available = True - - def __init__(self, parent=None, params_state=None): - super().__init__(parent, params_state) - - self.channels_ai = None - self.clock_settings = None - self.data_tot = None - self.live = False - self.Naverage = 1 - self.ind_average = 0 - - def commit_settings(self, param): - """ - """ - - self.update_tasks() - - def ini_detector(self, controller=None): - """Detector communication initialization - - Parameters - ---------- - controller: (object) custom object of a PyMoDAQ plugin (Slave case). None if only one detector by controller (Master case) - - Returns - ------- - self.status (edict): with initialization status: three fields: - * info (str) - * controller (object) initialized controller - *initialized: (bool): False if initialization failed otherwise True - """ - - try: - self.status.update(edict(initialized=False,info="", x_axis=None,y_axis=None,controller=None)) - if self.settings.child(('controller_status')).value() == "Slave": - if controller is None: - raise Exception('no controller has been defined externally while this detector is a slave one') - else: - self.controller = controller - else: - - self.controller = dict(ai=DAQmx()) - ##################################### - - self.update_tasks() - - - self.status.info = "Current measurement ready" - self.status.initialized = True - self.status.controller = self.controller - return self.status - - except Exception as e: - self.emit_status(ThreadCommand('Update_Status', [getLineInfo() + str(e), 'log'])) - self.status.info = getLineInfo() + str(e) - self.status.initialized = False - return self.status - - def update_tasks(self): - - self.channels_ai = [AIChannel(name=self.settings.child('ai_channel0').value(), - source='Analog_Input', analog_type='Voltage', - value_min=-10., value_max=10., termination='Diff', ), - AIChannel(name=self.settings.child('ai_channel1').value(), - source='Analog_Input', analog_type='Voltage', - value_min=-10., value_max=10., termination='Diff', ), - ] - - self.clock_settings_ai = ClockSettings(frequency=self.settings.child('frequency').value(), - Nsamples=self.settings.child('Nsamples').value(), repetition=self.live) - - self.controller['ai'].update_task(self.channels_ai, self.clock_settings_ai) - - - - def close(self): - """ - Terminate the communication protocol - """ - pass - ## - - def grab_data(self, Naverage=1, **kwargs): - """ - - Parameters - ---------- - Naverage: (int) Number of hardware averaging - kwargs: (dict) of others optionals arguments - """ - - - update = False - - if 'live' in kwargs: - if kwargs['live'] != self.live: - update = True - self.live = kwargs['live'] - - if Naverage != self.Naverage: - self.Naverage = Naverage - update = True - - if update: - self.update_tasks() - - self.ind_average = 0 - self.data_tot = np.zeros((len(self.channels_ai), self.clock_settings_ai.Nsamples)) - - while not self.controller['ai'].isTaskDone(): - self.stop() - if self.controller['ai'].c_callback is None: - self.controller['ai'].register_callback(self.read_data, 'Nsamples', self.clock_settings_ai.Nsamples) - self.controller['ai'].task.StartTask() - #QThread.msleep(500) - #self.read_data(None, 0) - - def read_data(self, taskhandle, status, samples=0, callbackdata=None): - #print(f'going to read {self.clock_settings_ai.Nsamples} samples, callbakc {samples}') - data = self.controller['ai'].readAnalog(len(self.channels_ai), self.clock_settings_ai) - if not self.live: - self.stop() - self.ind_average += 1 - - self.data_tot += 1 / self.Naverage * data.reshape(len(self.channels_ai), self.clock_settings_ai.Nsamples) - - if self.ind_average == self.Naverage: - self.emit_data(self.data_tot) - self.ind_average = 0 - self.data_tot = np.zeros((len(self.channels_ai), self.clock_settings_ai.Nsamples)) - - return 0 #mandatory for the PyDAQmx callback - - def emit_data(self, data): - channels_name = [ch.name for ch in self.channels_ai] - - if self.settings.child('display').value() == '0D': - data = np.mean(data, 1) - - #data = np.squeeze(data) - # print(f'shape is {data.shape}') - # print(data) - if len(self.channels_ai) == 1 and data.size == 1: - data_export = [np.array([data[0]])] - else: - data_export = [np.squeeze(data[ind]) for ind in range(len(self.channels_ai))] - - if self.settings.child('display').value() == '0D': - datatosend = [np.array([data_export[0]]), np.array([data_export[1]])] - else: - datatosend = [d for d in data_export] - - # print(f'data len is {len(data_export)} and shape is {data_export[0].shape}') - self.data_grabed_signal.emit([DataFromPlugins( - name='NI AI', - data=datatosend, - dim=f'Data{self.settings.child("display").value()}', labels=channels_name)]) - - def stop(self): - try: - self.controller['ai'].task.StopTask() - except: - pass - ############################## - - return '' - - -if __name__ == '__main__': - main(__file__) diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx_PLcounter.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx_PLcounter.py index b1afc2b..154df24 100755 --- a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx_PLcounter.py +++ b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_DAQmx_PLcounter.py @@ -8,10 +8,6 @@ Edge, ClockSettings, Counter, ClockCounter, TriggerSettings from PyDAQmx import DAQmx_Val_ContSamps -# DAQmx_Val_DoNotInvertPolarity, DAQmxConnectTerms, -# DAQmx_Val_FiniteSamps, DAQmx_Val_CurrReadPos, \ -# DAQmx_Val_DoNotOverwriteUnreadSamps - class DAQ_0DViewer_DAQmx_PLcounter(DAQ_Viewer_base): """ diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_NIDAQmx.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_NIDAQmx.py new file mode 100644 index 0000000..0c3895b --- /dev/null +++ b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_0D/daq_0Dviewer_NIDAQmx.py @@ -0,0 +1,160 @@ +from pymodaq.control_modules.viewer_utility_classes import main +from pymodaq.control_modules.viewer_utility_classes import comon_parameters as viewer_params +from pymodaq_plugins_daqmx import config +from pymodaq_plugins_daqmx.hardware.national_instruments.daqmxni import AIChannel, AIThermoChannel, NIDAQmx, niTask, \ + niDevice, TemperatureUnits, CJCSource, VoltageUnits, AcquisitionType +from pymodaq_plugins_daqmx.hardware.national_instruments.daqmxni import UsageTypeAI, ThermocoupleType, \ + TerminalConfiguration, Edge +from pymodaq_plugins_daqmx.hardware.national_instruments.NIDAQmx_base import DAQ_NIDAQmx_base +from pymodaq_plugins_daqmx.hardware.national_instruments.NIDAQmx_Viewer import DAQ_NIDAQmx_Viewer +from pymodaq.utils.logger import set_logger, get_module_name +logger = set_logger(get_module_name(__file__)) + + +class DAQ_0DViewer_NIDAQmx(DAQ_NIDAQmx_Viewer): + """ + Plugin for a 0D data visualization & acquisition with various NI modules plugged in a NI cDAQ. + """ + + config_channels: list + channels_ai: list + config: config # todo review Useful/Unused + controller: NIDAQmx + config_devices: list + config_modules: list + current_device: niDevice + live: bool + Naverage: int + + param_devices = NIDAQmx.get_NIDAQ_devices().device_names + params = viewer_params + [ + {'title': 'Display type:', 'name': 'display', 'type': 'list', 'limits': ['0D', '1D']}, + {'title': 'Devices :', 'name': 'devices', 'type': 'list', 'limits': param_devices, + 'value': param_devices[0] + }, + ] + DAQ_NIDAQmx_base.params + + def __init__(self, parent=None, params_state=None): + super().__init__(parent, params_state) + + def ini_attributes(self): + super().ini_attributes() + self.channels_ai = [] + self.config = config + self.config_channels = [] + self.config_devices = [] + self.config_modules = [] + self.live = False + self.Naverage = 1 + + def close(self): + self.live = False + self.controller.stop() + self.controller.close() + pass + + +if __name__ == '__main__': + """Main section used during development tests""" + main_file = False + if main_file: + main(__file__) + else: + try: + print("In main") + from pymodaq_plugins_daqmx.hardware.national_instruments.daqmxni import niSystem + + # EXPLORE DEVICES + devices = niSystem.local().devices + print("devices {}".format(devices)) + print("devices names {}".format(devices.device_names)) + print("devices types {}".format([dev.product_type for dev in devices])) + cdaq = devices[0] + mod1 = cdaq.chassis_module_devices[0] # Equivalent devices[1] + mod2 = devices[2] + mod3 = devices[3] + try: + usb1 = devices[4] + except Exception as e: + pass + print("cDAQ modules: {}".format(mod.compact_daq_chassis_device.product_type for mod in [mod1, mod2, mod3])) + + # TEST RESOURCES + try: + for device in devices: + device.self_test_device() + except Exception as e: + print("Resources test failed: {}" .format(e)) + + # CREATE CHANNELS + channels_th = [AIThermoChannel(name="cDAQ1Mod1/ai0", + source='Analog_Input', + analog_type='Thermocouple', + value_min=-100, + value_max=1000, + thermo_type=ThermocoupleType.K), + ] + channels_voltage = [AIChannel(name="cDAQ1Mod3/ai0", + source='Analog_Input', + analog_type='voltage', + value_min=-80.0e-3, + value_max=80.0e-3, + termination=TerminalConfiguration.DEFAULT, + ), + AIChannel(name="cDAQ1Mod3/ai1", + source='Analog_Input', + analog_type='voltage', + value_min=-80.0e-3, + value_max=80.0e-3, + termination=TerminalConfiguration.DEFAULT, + ), + ] + # CREATE TASK + task_9211 = niTask() + task_9205 = niTask() + + def callback_9211(task_handle, every_n_samples_event_type, number_of_samples, callback_data): + data9211 = task_9211.read(5) + print(data9211) + + def callback_9205(task_handle, every_n_samples_event_type, number_of_samples, callback_data): + data9205 = task_9205.read(5) + print(data9205) + + for channel in channels_th: + task_9211.ai_channels.add_ai_thrmcpl_chan(channel.name, + "", + channel.value_min, + channel.value_max, + TemperatureUnits.DEG_C, + channel.thermo_type, + CJCSource.BUILT_IN, + 0., + "") + for channel in channels_voltage: + task_9205.ai_channels.add_ai_voltage_chan(channel.name, + "", + channel.termination, + channel.value_min, + channel.value_max, + VoltageUnits.VOLTS, + "") + task_9211.timing.cfg_samp_clk_timing(5.0, None, Edge.RISING, + AcquisitionType.CONTINUOUS, 5) + task_9211.register_every_n_samples_acquired_into_buffer_event(10, callback_9211) + + task_9205.timing.cfg_samp_clk_timing(10, None, Edge.RISING, + AcquisitionType.CONTINUOUS, 10) + task_9205.register_every_n_samples_acquired_into_buffer_event(2, callback_9205) + + task_9211.start() + task_9205.start() + + print("Acquisition in progress... Press enter to stop") + input() + + task_9211.close() + task_9205.close() + + except Exception as e: + print("Exception ({}): {}".format(type(e), str(e))) diff --git a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_1D/daq_1Dviewer_DAQmx.py b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_1D/daq_1Dviewer_DAQmx.py index fbf9020..ebe032f 100644 --- a/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_1D/daq_1Dviewer_DAQmx.py +++ b/src/pymodaq_plugins_daqmx/daq_viewer_plugins/plugins_1D/daq_1Dviewer_DAQmx.py @@ -1,4 +1,4 @@ -from pymodaq_plugins_daqmx.hardware.national_instruments.daq_NIDAQmx import DAQ_NIDAQmx_Viewer +from pymodaq_plugins_daqmx.hardware.national_instruments.NIDAQmx_Viewer import DAQ_NIDAQmx_Viewer from pymodaq.control_modules.viewer_utility_classes import main diff --git a/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_Viewer.py b/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_Viewer.py new file mode 100644 index 0000000..ba15f27 --- /dev/null +++ b/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_Viewer.py @@ -0,0 +1,218 @@ +import nidaqmx +import numpy as np +import traceback +from qtpy import QtCore +from .daqmxni import NIDAQmx +from pymodaq_plugins_daqmx.hardware.national_instruments.NIDAQmx_base import DAQ_NIDAQmx_base, TerminalConfiguration, \ + UsageTypeAI, ChannelType +from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters as viewer_params +from pymodaq.utils.daq_utils import ThreadCommand +from pymodaq.utils.data import DataFromPlugins, DataToExport +from pymodaq.utils.logger import set_logger, get_module_name +logger = set_logger(get_module_name(__file__)) + + +class DAQ_NIDAQmx_Viewer(DAQ_Viewer_base, DAQ_NIDAQmx_base): + """ + ==================== ======================== + **Attributes** **Type** + *data_grabed_signal* instance of Signal + *params* dictionary list + *task* + ==================== ======================== + + See Also + -------- + refresh_hardware + """ + + live_mode_available = True + params = viewer_params + DAQ_NIDAQmx_base.params + + def __init__(self, parent=None, params_state=None, control_type="0D"): + DAQ_Viewer_base.__init__(self, parent, params_state) # defines settings attribute and various other methods + DAQ_NIDAQmx_base.__init__(self) + + self.current_device = None + self.Naverage = None + self.live = False + self.control_type = control_type # could be "0D", "1D" or "Actuator" + if self.control_type == "0D": + self.settings.child('NIDAQ_type').setLimits([ChannelType.ANALOG_INPUT.name, + ChannelType.COUNTER_INPUT.name, + ChannelType.DIGITAL_INPUT.name]) # analog & digital input + counter + elif self.control_type == "1D": + self.settings.child('NIDAQ_type').setLimits(ChannelType.ANALOG_INPUT.name) + elif self.control_type == "Actuator": + self.settings.child('NIDAQ_type').setLimits(ChannelType.ANALOG_OUTPUT.name, ChannelType.COUNTER_OUTPUT.name) + + self.settings.child('ao_settings').hide() + self.settings.child('ao_channels').hide() + + # timer used for the counter + self.timer = QtCore.QTimer() + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.counter_done) + + def stop(self): + """Stop the current grab hardware wise if necessary""" + try: + self.controller.stop() + self.live = False + logger.info("Acquisition stopped.") + except Exception: + pass + self.emit_status(ThreadCommand('Update_Status', ['Acquisition stopped.'])) + return '' + + def commit_settings(self, param): + """ + Activate the parameters changes in the hardware. + + =============== ================================ =========================== + **Parameters** **Type** **Description** + *param* instance of pyqtgraph.parameter the parameter to activate + =============== ================================ =========================== + + See Also + -------- + update_NIDAQ_channels, update_task, refresh_hardware + """ + + if param.parent() is not None: + if param.parent().name() == 'ai_channels': + device = param.opts['title'].split('/')[0] + self.settings.child('clock_settings', 'frequency').setOpts(max=self.controller.getAIMaxRate(device)) + + ranges = self.controller.getAIVoltageRange(device) + param.child('voltage_settings', 'volt_min').setOpts(limits=[r[0] for r in ranges]) + param.child('voltage_settings', 'volt_max').setOpts(limits=[r[1] for r in ranges]) + + DAQ_NIDAQmx_base.commit_settings(self, param) + + def ini_detector(self, controller=None): + """ + Initialisation procedure of the detector. + + See Also + -------- + daq_utils.ThreadCommand + """ + try: + self.current_device = nidaqmx.system.Device(self.settings["devices"]) + self.controller = self.ini_detector_init(controller, NIDAQmx()) + self.controller.configuration_sequence(self, self.current_device) + + # actions to perform in order to set properly the settings tree options + self.commit_settings(self.settings.child('NIDAQ_type')) + for ch in self.config_channels: + try: + ch.analog_type = ch.analog_type + ch.termination = ch.termination + except: + pass + if self.settings.child("devices").value() in ch.name: + self.settings.child('ai_channels').addNew(ch.name) + param = [a for a in self.settings.child('ai_channels').childs if a.opts['title'] == ch.name][0] + self.settings.child("ai_channels", param.opts['name'], "ai_type").setValue(ch.analog_type.name) + param.child("voltage_settings").show(ch.analog_type == UsageTypeAI.VOLTAGE) + param.child("current_settings").show(ch.analog_type == UsageTypeAI.CURRENT) + param.child("thermoc_settings").show(ch.analog_type == UsageTypeAI.TEMPERATURE_THERMOCOUPLE) + if ch.analog_type == UsageTypeAI.VOLTAGE: + self.settings.child("ai_channels", param.opts['name'], "voltage_settings", "volt_min").setValue( + ch.value_min) + self.settings.child("ai_channels", param.opts['name'], "voltage_settings", "volt_max").setValue( + ch.value_max) + self.settings.child("ai_channels", param.opts['name'], "termination").setValue( + ch.termination.name) + elif ch.analog_type == UsageTypeAI.CURRENT: + self.settings.child("ai_channels", param.opts['name'], "current_settings", "curr_min").setValue( + ch.value_min) + self.settings.child("ai_channels", param.opts['name'], "current_settings", "curr_max").setValue( + ch.value_max) + self.settings.child("ai_channels", param.opts['name'], "termination").setValue( + ch.termination.name) + elif ch.analog_type == UsageTypeAI.TEMPERATURE_THERMOCOUPLE: + self.settings.child("ai_channels", + param.opts['name'], + "thermoc_settings", + "thermoc_type").setValue(ch.thermo_type.name) + self.settings.child("ai_channels", + param.opts['name'], + "thermoc_settings", + "T_min").setValue(ch.value_min) + self.settings.child("ai_channels", + param.opts['name'], + "thermoc_settings", + "T_max").setValue(ch.value_max) + self.settings.child("ai_channels", param.opts['name'], "termination").setValue( + TerminalConfiguration.DEFAULT.name) + info = "Plugin Initialized" + initialized = True + logger.info("Detector 0D initialized") + return info, initialized + + except Exception as e: + logger.info(traceback.format_exc()) + self.emit_status(ThreadCommand('Update_Status', [str(e), 'log'])) + info = str(e) + initialized = False + return info, initialized + + def grab_data(self, Naverage=1, **kwargs): + """ + | grab the current values with NIDAQ profile procedure. + | + | Send the data_grabed_signal once done. + + =============== ======== =============================================== + **Parameters** **Type** **Description** + *Naverage* int Number of values to average + =============== ======== =============================================== + """ + update = False + + if 'live' in kwargs: + if kwargs['live'] != self.live: + update = True + self.live = kwargs['live'] + + if Naverage != self.Naverage: + self.Naverage = Naverage + update = True + if update: + self.update_task() + + if self.controller.task is None: + self.update_task() + + self.controller.register_callback(self.emit_data, "Nsamples", self.clock_settings.Nsamples) + self.controller.start() + + def emit_data(self, task_handle, every_n_samples_event_type, number_of_samples, callback_data): + channels_names = [ch.name for ch in self.channels] + # channels_ai_names = [ch.name for ch in self.channels if ch.source == 'Analog_Input'] + data_from_task = self.controller.task.read(self.settings['nsamplestoread'], timeout=20.0) + if self.control_type == "0D": + if not len(self.controller.task.channels.channel_names) != 1: + data_dfp = [np.array(data_from_task)] + else: + data_dfp = list(map(np.array, data_from_task)) + dte = DataToExport(name='NIDAQmx', + data=[DataFromPlugins(name='NI Analog Input', + data=data_dfp, + dim=f'Data{self.settings.child("display").value()}', + labels=channels_names + ), + ]) + self.dte_signal.emit(dte) + return 0 # mandatory for the NIDAQmx callback + + def counter_done(self): + channels_name = [ch.name for ch in self.channels] + data_counter = self.readCounter(len(self.channels), + self.settings['counter_settings', 'counting_time'] * 1e-3) + self.data_grabed_signal.emit([DataFromPlugins(name='NI Counter', data=[data_counter / 1e-3], dim='Data0D', + labels=channels_name, )]) + # y_axis=Axis(label='Count Number', units='1/s'))]) + self.task.StopTask() diff --git a/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_base.py b/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_base.py new file mode 100644 index 0000000..7218073 --- /dev/null +++ b/src/pymodaq_plugins_daqmx/hardware/national_instruments/NIDAQmx_base.py @@ -0,0 +1,490 @@ +from qtpy import QtWidgets +from qtpy.QtCore import Signal +from pymodaq.utils.logger import set_logger, get_module_name +from pymodaq.utils.parameter import Parameter +from pymodaq.utils.parameter.pymodaq_ptypes import registerParameterType, GroupParameter +from pymodaq_plugins_daqmx.hardware.national_instruments.daqmxni import NIDAQmx, Edge, ChannelType, ClockSettings, \ + AIChannel, AIThermoChannel, AOChannel, CIChannel, COChannel, DOChannel, DIChannel, UsageTypeAI, UsageTypeAO, \ + ThermocoupleType, TerminalConfiguration, TriggerSettings + + +logger = set_logger(get_module_name(__file__)) + + +class ScalableGroupAI(GroupParameter): + + """ + | + + ================ ============= + **Attributes** **Type** + *opts* dictionnary + ================ ============= + + See Also + -------- + hardware.DAQ_Move_Stage_type + """ + + params = [{'title': 'AI type:', 'name': 'ai_type', 'type': 'list', 'limits': [Uai.name for Uai in UsageTypeAI]}, + {'title': 'Voltage:', 'name': 'voltage_settings', 'type': 'group', 'children': [ + {'title': 'Voltage Min:', 'name': 'volt_min', 'type': 'float', 'value': -10.}, + {'title': 'Voltage Max:', 'name': 'volt_max', 'type': 'float', 'value': 10.}, + ]}, + {'title': 'Current:', 'name': 'current_settings', 'type': 'group', 'visible': False, 'children': [ + {'title': 'Current Min:', 'name': 'curr_min', 'type': 'float', 'value': -1, 'suffix': 'A'}, + {'title': 'Current Max:', 'name': 'curr_max', 'type': 'float', 'value': 1, 'suffix': 'A'}, + ]}, + {'title': 'Thermocouple:', 'name': 'thermoc_settings', 'type': 'group', 'visible': False, 'children': [ + {'title': 'Thc. type:', 'name': 'thermoc_type', 'type': 'list', + 'limits': [Th.name for Th in ThermocoupleType], 'value': 'K'}, + {'title': 'Temp. Min (°C):', 'name': 'T_min', 'type': 'float', 'value': 0, 'suffix': '°C'}, + {'title': 'Temp. Max (°C):', 'name': 'T_max', 'type': 'float', 'value': 50, 'suffix': '°C'}, + ]}, + {'title': 'Termination:', 'name': 'termination', 'type': 'list', + 'limits': [Te.name for Te in TerminalConfiguration]}, + ] + + def __init__(self, **opts): + opts['type'] = 'groupai' + opts['addText'] = "Add" + opts['addList'] = opts['limits'] + GroupParameter.__init__(self, **opts) + + def addNew(self, typ=None): + """ + Add a child. + + =============== =========== + **Parameters** **Type** + *typ* string + =============== =========== + """ + childnames = [par.name() for par in self.children()] + if childnames == []: + newindex = 0 + else: + newindex = len(childnames) + + child = {'title': typ, 'name': 'ai{:02.0f}'.format(newindex), 'type': 'group', 'children': self.params, + 'removable': True, 'renamable': False} + + self.addChild(child) + + +registerParameterType('groupai', ScalableGroupAI, override=True) + + +class ScalableGroupAO(GroupParameter): + """ + | + + ================ ============= + **Attributes** **Type** + *opts* dictionnary + ================ ============= + + See Also + -------- + hardware.DAQ_Move_Stage_type + """ + + params = [{'title': 'AO type:', 'name': 'ao_type', 'type': 'list', 'limits': [Uao.name for Uao in UsageTypeAO]}, + {'title': 'Voltages:', 'name': 'voltage_settings', 'type': 'group', 'children': [ + {'title': 'Voltage Min:', 'name': 'volt_min', 'type': 'list', 'value': -10., }, + {'title': 'Voltage Max:', 'name': 'volt_max', 'type': 'list', 'value': 10., }, + ]}, + {'title': 'Current:', 'name': 'current_settings', 'type': 'group', 'visible': False, 'children': [ + {'title': 'Current Min:', 'name': 'curr_min', 'type': 'float', 'value': -1, 'suffix': 'A'}, + {'title': 'Current Max:', 'name': 'curr_max', 'type': 'float', 'value': 1, 'suffix': 'A'}, + ]}, + ] + + def __init__(self, **opts): + opts['type'] = 'groupao' + opts['addText'] = "Add" + opts['addList'] = opts['limits'] + GroupParameter.__init__(self, **opts) + + def addNew(self, typ=None): + """ + Add a child. + + =============== =========== + **Parameters** **Type** + *typ* string + =============== =========== + """ + childnames = [par.name() for par in self.children()] + if childnames == []: + newindex = 0 + else: + newindex = len(childnames) + + child = {'title': typ, 'name': 'ao{:02.0f}'.format(newindex), 'type': 'group', 'children': self.params, + 'removable': True, 'renamable': False} + + self.addChild(child) + + +registerParameterType('groupao', ScalableGroupAO, override=True) + + +class ScalableGroupCounter(GroupParameter): + """ + | + + ================ ============= + **Attributes** **Type** + *opts* dictionnary + ================ ============= + + See Also + -------- + hardware.DAQ_Move_Stage_type + """ + + params = [{'title': 'Edge type:', 'name': 'edge', 'type': 'list', 'limits': [e.name for e in Edge]}, ] + + def __init__(self, **opts): + opts['type'] = 'groupcounter' + opts['addText'] = "Add" + opts['addList'] = opts['limits'] + GroupParameter.__init__(self, **opts) + + def addNew(self, typ=None): + """ + Add a child. + + =============== =========== + **Parameters** **Type** + *typ* string + =============== =========== + """ + childnames = [par.name() for par in self.children()] + if childnames == []: + newindex = 0 + else: + newindex = len(childnames) + + child = {'title': typ, 'name': 'counter{:02.0f}'.format(newindex), 'type': 'group', 'children': self.params, + 'removable': True, 'renamable': False} + + self.addChild(child) + + +registerParameterType('groupcounter', ScalableGroupCounter, override=True) + + +class ScalableGroupDI(GroupParameter): + """ + """ + + params = [] + + def __init__(self, **opts): + opts['type'] = 'groupdi' + opts['addText'] = "Add" + opts['addList'] = opts['limits'] + GroupParameter.__init__(self, **opts) + + def addNew(self, typ=None): + """ + Add a child. + + =============== =========== + **Parameters** **Type** + *typ* string + =============== =========== + """ + childnames = [par.name() for par in self.children()] + if childnames == []: + newindex = 0 + else: + newindex = len(childnames) + + child = {'title': typ, 'name': 'di{:02.0f}'.format(newindex), 'type': 'group', 'children': self.params, + 'removable': True, 'renamable': False} + self.addChild(child) + + +registerParameterType('groupdi', ScalableGroupDI, override=True) + + +class ScalableGroupDO(GroupParameter): + """ + """ + + params = [] + + def __init__(self, **opts): + opts['type'] = 'groupdo' + opts['addText'] = "Add" + opts['addList'] = opts['limits'] + GroupParameter.__init__(self, **opts) + + def addNew(self, typ=None): + """ + Add a child. + + =============== =========== + **Parameters** **Type** + *typ* string + =============== =========== + """ + childnames = [par.name() for par in self.children()] + if childnames == []: + newindex = 0 + else: + newindex = len(childnames) + + child = {'title': typ, 'name': 'counter{:02.0f}'.format(newindex), 'type': 'group', 'children': self.params, + 'removable': True, 'renamable': False} + self.addChild(child) + + +registerParameterType('groupdo', ScalableGroupDO, override=True) + + +class DAQ_NIDAQmx_base: + """ + Base NIDAQmx class for using DAQmx objects from daqmxni.py in the DAQ_NIDAQmx_Move & DAQ_NIDAQmx_Viewer + """ + data_grabed_signal = Signal(list) + param_instance = NIDAQmx() + params = [{'title': 'Refresh hardware:', 'name': 'refresh_hardware', 'type': 'bool', 'value': False}, + {'title': 'Signal type:', 'name': 'NIDAQ_type', 'type': 'list', + 'limits': [Ds.name for Ds in ChannelType]}, + {'title': 'NSamples To Read', 'name': 'nsamplestoread', 'type': 'int', 'value': 1000, 'default': 1000, + 'min': 1}, + {'title': 'AO Settings:', 'name': 'ao_settings', 'type': 'group', 'children': [ + {'title': 'Waveform:', 'name': 'waveform', 'type': 'list', 'value': 'DC', + 'limits': ['DC', 'Sinus', 'Ramp']}, + {'title': 'Controlled param:', 'name': 'cont_param', 'type': 'list', 'value': 'offset', + 'limits': ['offset', 'amplitude', 'frequency']}, + {'title': 'Waveform Settings:', 'name': 'waveform_settings', 'type': 'group', 'visible': False, + 'children': [ + {'title': 'Offset:', 'name': 'offset', 'type': 'float', 'value': 0., }, + {'title': 'Amplitude:', 'name': 'amplitude', 'type': 'float', 'value': 1., }, + {'title': 'Frequency:', 'name': 'frequency', 'type': 'float', 'value': 10., }, + ]}, + ]}, + {'title': 'Clock Settings:', 'name': 'clock_settings', 'type': 'group', 'children': [ + {'title': 'Nsamples:', 'name': 'Nsamples', 'type': 'int', 'value': 1000, 'default': 1000, 'min': 1}, + {'title': 'Frequency:', 'name': 'frequency', 'type': 'float', 'value': 1000., 'default': 1000., + 'min': 0., 'suffix': 'Hz'}, + {'title': 'Repetition?:', 'name': 'repetition', 'type': 'bool', 'value': False, }, + ] + }, + {'title': 'AI Channels:', 'name': 'ai_channels', 'type': 'groupai', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.ANALOG_INPUT)}, + {'title': 'AO Channels:', 'name': 'ao_channels', 'type': 'groupao', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.ANALOG_OUTPUT)}, + {'title': 'DO Channels:', 'name': 'do_channels', 'type': 'groupdo', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.DIGITAL_OUTPUT)}, + {'title': 'DI Channels:', 'name': 'di_channels', 'type': 'groupdi', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.DIGITAL_INPUT)}, + {'title': 'Counter Settings:', 'name': 'counter_settings', 'type': 'group', 'visible': True, 'children': [ + {'title': 'Counting time (ms):', 'name': 'counting_time', 'type': 'float', 'value': 100., + 'default': 100., 'min': 0.}, + {'title': 'CI Channels:', 'name': 'ci_channels', 'type': 'groupcounter', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.COUNTER_INPUT)}, + {'title': 'CO Channels:', 'name': 'co_channels', 'type': 'groupcounter', + 'limits': NIDAQmx.get_NIDAQ_channels(source_type=ChannelType.COUNTER_OUTPUT)}, + ]}, + {'title': 'Trigger Settings:', 'name': 'trigger_settings', 'type': 'group', 'visible': True, 'children': [ + {'title': 'Enable?:', 'name': 'enable', 'type': 'bool', 'value': False, }, + {'title': 'Trigger Source:', 'name': 'trigger_channel', 'type': 'list', + 'limits': NIDAQmx.getTriggeringSources()}, + {'title': 'Edge type:', 'name': 'edge', 'type': 'list', 'limits': [e.name for e in Edge], + 'visible': False}, + {'title': 'Level:', 'name': 'level', 'type': 'float', 'value': 1., 'visible': False} + ]} + ] + + def __init__(self): + super().__init__() + + self.timer = None + self.channels = None + self.clock_settings = None + self.trigger_settings = None + self.live = False + + def commit_settings(self, param: Parameter): + """ + Activate the parameters changes in the hardware. + + =============== ================================ =========================== + **Parameters** **Type** **Description** + *param* instance of pyqtgraph.parameter the parameter to activate + =============== ================================ =========================== + + See Also + -------- + update_NIDAQ_channels, update_task, DAQ_NIDAQ_source, refresh_hardware + """ + if param.name() == 'NIDAQ_devices': + self.controller.update_NIDAQ_channels() + + if param.name() == 'NIDAQ_type': + self.controller.update_NIDAQ_channels(param.value()) + if param.value() == ChannelType.ANALOG_INPUT.name: # analog input + self.settings.child('clock_settings').show() + self.settings.child('ai_channels').show() + self.settings.child('ao_channels').hide() + self.settings.child('ao_settings').hide() + self.settings.child('counter_settings').hide() + self.settings.child('do_channels').hide() + self.settings.child('di_channels').hide() + + elif param.value() == ChannelType.ANALOG_OUTPUT.name: # analog output + self.settings.child('clock_settings').show() + self.settings.child('ai_channels').hide() + self.settings.child('ao_channels').show() + self.settings.child('ao_settings').show() + self.settings.child('counter_settings').hide() + self.settings.child('do_channels').hide() + self.settings.child('di_channels').hide() + + elif param.value() == ChannelType.COUNTER_INPUT.name: # counter input + self.settings.child('clock_settings').hide() + self.settings.child('ai_channels').hide() + self.settings.child('ao_channels').hide() + self.settings.child('ao_settings').hide() + self.settings.child('counter_settings', 'ci_channels').show() + self.settings.child('do_channels').hide() + self.settings.child('di_channels').hide() + + elif param.value() == ChannelType.COUNTER_OUTPUT.name: # counter output + self.settings.child('clock_settings').hide() + self.settings.child('ai_channels').hide() + self.settings.child('ao_channels').hide() + self.settings.child('ao_settings').hide() + self.settings.child('counter_settings', 'co_channels').show() + self.settings.child('do_channels').hide() + self.settings.child('di_channels').hide() + + elif param.value() == ChannelType.DIGITAL_INPUT.name: # Digital_Input + self.settings.child('clock_settings').show() + self.settings.child('ai_channels').hide() + self.settings.child('ao_channels').show() + self.settings.child('ao_settings').show() + self.settings.child('counter_settings').hide() + self.settings.child('do_channels').hide() + self.settings.child('di_channels').show() + + elif param.value() == ChannelType.DIGITAL_OUTPUT.name: # digital output + self.settings.child('clock_settings').show() + self.settings.child('ai_channels').hide() + self.settings.child('ao_channels').hide() + self.settings.child('ao_settings').hide() + self.settings.child('counter_settings').hide() + self.settings.child('do_channels').show() + self.settings.child('di_channels').hide() + + elif param.name() == 'refresh_hardware': + if param.value(): + self.controller.refresh_hardware() + QtWidgets.QApplication.processEvents() + self.settings.child('refresh_hardware').setValue(False) + + elif param.name() == 'ai_type': + param.parent().child('voltage_settings').show(param.value() == UsageTypeAI.VOLTAGE.name) + param.parent().child('current_settings').show(param.value() == UsageTypeAI.CURRENT.name) + param.parent().child('thermoc_settings').show(param.value() == UsageTypeAI.TEMPERATURE_THERMOCOUPLE.name) + + elif param.name() == 'ao_type': + param.parent().child('voltage_settings').show(param.value() == UsageTypeAI.VOLTAGE.name) + param.parent().child('current_settings').show(param.value() == UsageTypeAI.CURRENT.name) + + elif param.name() == 'trigger_channel': + param.parent().child('level').show('PF' not in param.opts['title']) + + def update_task(self): + self.channels = self.get_channels_from_settings() + self.clock_settings = ClockSettings(frequency=self.settings['clock_settings', 'frequency'], + Nsamples=self.settings['clock_settings', 'Nsamples'], + edge=Edge.RISING, + repetition=self.live, ) + self.trigger_settings = \ + TriggerSettings(trig_source=self.settings['trigger_settings', 'trigger_channel'], + enable=self.settings['trigger_settings', 'enable'], + edge=Edge[self.settings['trigger_settings', 'edge']], + level=self.settings['trigger_settings', 'level'], ) + if self.channels: + self.controller.update_task(self.channels, self.clock_settings, trigger_settings=self.trigger_settings) + + def get_channels_from_settings(self): + channels = [] + if self.settings['NIDAQ_type'] == ChannelType.ANALOG_INPUT.name: # analog input + source = ChannelType.ANALOG_INPUT + for channel in self.settings.child('ai_channels').children(): + analog_type = UsageTypeAI[channel['ai_type']] + if analog_type == UsageTypeAI.VOLTAGE: + channels.append(AIChannel(name=channel.opts['title'], + source=source, analog_type=analog_type, + value_min=channel['voltage_settings', 'volt_min'], + value_max=channel['voltage_settings', 'volt_max'], + termination=TerminalConfiguration[channel['termination']], )) + elif analog_type == UsageTypeAI.CURRENT: + channels.append(AIChannel(name=channel.opts['title'], + source=source, analog_type=analog_type, + value_min=channel['current_settings', 'curr_min'], + value_max=channel['current_settings', 'curr_max'], + termination=TerminalConfiguration[channel['termination']], )) + elif analog_type == UsageTypeAI.TEMPERATURE_THERMOCOUPLE: + channels.append(AIThermoChannel(name=channel.opts['title'], + source=source, analog_type=analog_type, + value_min=channel['thermoc_settings', 'T_min'], + value_max=channel['thermoc_settings', 'T_max'], + thermo_type=ThermocoupleType[ + channel['thermoc_settings', 'thermoc_type']], )) + + elif self.settings['NIDAQ_type'] == ChannelType.ANALOG_OUTPUT.name: # analog output + source = ChannelType.ANALOG_OUTPUT + for channel in self.settings.child('ao_channels').children(): + analog_type = UsageTypeAO[channel['ao_type']] + channels.append(AOChannel(name=channel.opts['title'], + source=source, analog_type=analog_type, + value_min=channel['voltage_settings', 'volt_min'], + value_max=channel['voltage_settings', 'volt_max'], + )) + + elif self.settings['NIDAQ_type'] == ChannelType.COUNTER_INPUT.name: # counter input + source = ChannelType.COUNTER_INPUT + for channel in self.settings.child('counter_settings', 'ci_channels').children(): + channels.append(CIChannel(name=channel.opts['title'], + source=source, edge=Edge[channel['edge']])) + + elif self.settings['NIDAQ_type'] == ChannelType.COUNTER_OUTPUT.name: # counter output + source = ChannelType.COUNTER_OUTPUT + for channel in self.settings.child('counter_settings', 'co_channels').children(): + channels.append(COChannel(name=channel.opts['title'], + source=source, edge=Edge[channel['edge']])) + + elif self.settings['NIDAQ_type'] == ChannelType.DIGITAL_INPUT.name: # digital input + source = ChannelType.DIGITAL_INPUT + for channel in self.settings.child('di_channels').children(): + channels.append(DIChannel(name=channel.opts['title'], + source=source)) + + elif self.settings['NIDAQ_type'] == ChannelType.DIGITAL_OUTPUT.name: # Digital output + source = ChannelType.DIGITAL_OUTPUT + for channel in self.settings.child('do_channels').children(): + channels.append(DOChannel(name=channel.opts['title'], + source=source)) + + channels = [ch for ch in channels if self.settings.child("devices").value() in ch.name] + return channels + + def stop(self): + """ + """ + if not not self.timer: + self.timer.stop() + QtWidgets.QApplication.processEvents() + self.controller.stop() + + + diff --git a/src/pymodaq_plugins_daqmx/hardware/national_instruments/daq_NIDAQmx.py b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daq_NIDAQmx.py index f0f28b8..003479e 100644 --- a/src/pymodaq_plugins_daqmx/hardware/national_instruments/daq_NIDAQmx.py +++ b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daq_NIDAQmx.py @@ -859,5 +859,4 @@ def stop_motion(self): self.stop() self.emit_status(ThreadCommand('Update_Status', ['Some info you want to log'])) self.move_done() # to let the interface know the actuator stopped - ############################## - + ############################## \ No newline at end of file diff --git a/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmx.py b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmx.py index 1493b8f..3e1f3f1 100644 --- a/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmx.py +++ b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmx.py @@ -105,8 +105,9 @@ def names(cls): class ClockMode(IntEnum): """ """ - Finite = PyDAQmx.DAQmx_Val_Rising - Continuous = PyDAQmx.DAQmx_Val_Falling + Finite = PyDAQmx.DAQmx_Val_FiniteSamps + Continuous = PyDAQmx.DAQmx_Val_ContSamps + @classmethod def names(cls): diff --git a/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmxni.py b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmxni.py new file mode 100644 index 0000000..cf7ebe8 --- /dev/null +++ b/src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmxni.py @@ -0,0 +1,616 @@ +import time +import traceback +from enum import Enum +import numpy as np +from pymodaq.utils.logger import set_logger, get_module_name + +from nidaqmx.constants import AcquisitionType, VoltageUnits, CurrentUnits, CurrentShuntResistorLocation, \ + TemperatureUnits, CJCSource, CountDirection, Level, FrequencyUnits, TimeUnits, \ + LineGrouping, UsageTypeAI, UsageTypeAO, UsageTypeCI, UsageTypeCO, Edge, \ + TerminalConfiguration, ThermocoupleType, ChannelType + +from nidaqmx.system import System as niSystem +from nidaqmx.system.device import Device as niDevice +from nidaqmx import Task as niTask +from nidaqmx.errors import DaqError, DAQmxErrors +from pymodaq_plugins_daqmx import config + + +logger = set_logger(get_module_name(__file__)) + + +class ClockSettingsBase: + def __init__(self, Nsamples=1000, repetition=False): + + self.Nsamples = Nsamples + self.repetition = repetition + + +class ClockSettings(ClockSettingsBase): + def __init__(self, source=None, frequency=1000, Nsamples=1000, edge=Edge.RISING, repetition=False): + super().__init__(Nsamples, repetition) + self.source = source + assert edge in Edge + self.frequency = frequency + self.edge = edge + + +class ChangeDetectionSettings(ClockSettingsBase): + def __init__(self, Nsamples=1000, rising_channel='', falling_channel='', + repetition=False): + super().__init__(Nsamples, repetition) + self.rising_channel = rising_channel + self.falling_channel = falling_channel + + +class TriggerSettings: + def __init__(self, trig_source='', enable=False, edge=Edge.RISING, level=0.1): + assert edge in Edge + self.trig_source = trig_source + self.enable = enable + self.edge = edge + self.level = level + + +class Channel: + def __init__(self, name='', source=ChannelType.ANALOG_INPUT): + """ + Parameters + ---------- + + """ + self.name = name + assert source in ChannelType + self.source = source + + +class AChannel(Channel): + def __init__(self, analog_type=UsageTypeAI.VOLTAGE, value_min=-10., value_max=+10., **kwargs): + """ + Parameters + ---------- + min: (float) minimum value for the configured input channel (could be voltage, amps, temperature...) + max: (float) maximum value for the configured input channel + """ + super().__init__(**kwargs) + self.value_min = value_min + self.value_max = value_max + assert analog_type in UsageTypeAI + self.analog_type = analog_type + + +class AIChannel(AChannel): + def __init__(self, termination=TerminalConfiguration.DEFAULT, **kwargs): + super().__init__(**kwargs) + assert termination in TerminalConfiguration + self.termination = termination + + +class AIThermoChannel(AIChannel): + def __init__(self, thermo_type=ThermocoupleType.K, **kwargs): + super().__init__(**kwargs) + assert thermo_type in ThermocoupleType + self.thermo_type = thermo_type + + +class AOChannel(AChannel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class CIChannel(Channel): + def __init__(self, edge=Edge.RISING, counter_type=UsageTypeCI.COUNT_EDGES, **kwargs): + super().__init__(**kwargs) + assert edge in Edge + assert counter_type in UsageTypeCI + self.edge = edge + self.counter_type = counter_type + + +class COChannel(Channel): + def __init__(self, edge=Edge.RISING, counter_type=UsageTypeCO.PULSE_FREQUENCY, **kwargs): + super().__init__(**kwargs) + assert edge in Edge + assert counter_type in UsageTypeCO + self.edge = edge + self.counter_type = counter_type + + +class ClockCounter(COChannel): + def __init__(self, clock_frequency=100, counter_type=UsageTypeCO.PULSE_FREQUENCY, **kwargs): + super().counter_type = counter_type + super().__init__(**kwargs) + self.clock_frequency = clock_frequency + self.counter_type = counter_type + + +class DigitalChannel(Channel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class DOChannel(DigitalChannel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class DIChannel(DigitalChannel): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + +class NIDAQmx: + """Wrapper around the NIDAQmx package giving an easy-to-use object to instantiate channels and tasks""" + def __init__(self): + self.devices = [] + self.channels = [] + self._device = None + self._task = None + self.update_NIDAQ_devices() + self.update_NIDAQ_channels() + self.c_callback = None # A qui servent ces callback ?? + self.callback_data = None + self.is_scalar = True + self.write_buffer = np.array([0.]) # ou est utilisé ce buffer?? + + @property + def task(self): + return self._task + + @property + def device(self): + return self._device + + @device.setter + def device(self, device): + if device not in self.devices.device_names: + raise IOError(f'your device: {device} is not known or connected') + self._device = device + + def update_NIDAQ_devices(self): + self.devices = self.get_NIDAQ_devices() + + @classmethod + def get_NIDAQ_devices(cls): + """Get list of NI connected devices + + Returns + ------- + list + list of devices as strings to be used in subsequent commands + """ + try: + devices = niSystem.local().devices + if devices == ['']: + devices = [] + return devices + except DaqError as e: + return e.error_code + + def update_NIDAQ_channels(self, source_type=None): + self.channels = self.get_NIDAQ_channels(self.devices.device_names, source_type=source_type) + + @classmethod + def get_NIDAQ_channels(cls, devices=None, source_type=None): + """Get the list of available channels for all NiDAq connected devices + + Parameters + ---------- + devices: list + list of strings, each one being a connected device + source_type: str + Channels possible types + + Returns + ------- + List of str containing device and channel names + + """ + if devices is None: + devices = cls.get_NIDAQ_devices().device_names + + if source_type is None: + source_type = ChannelType + if not isinstance(source_type, list): + source_type = [source_type] + channels_tot = [] + channels = [] + if not not devices: + for device in devices: + for source in source_type: + if source == ChannelType.ANALOG_INPUT: # analog input + channels = niDevice(device).ai_physical_chans.channel_names + elif source == ChannelType.ANALOG_OUTPUT: # analog output + channels = niDevice(device).ao_physical_chans.channel_names + elif source == ChannelType.COUNTER_INPUT: # counter input + channels = niDevice(device).ci_physical_chans.channel_names + elif source == ChannelType.COUNTER_OUTPUT: # counter output + channels = niDevice(device).co_physical_chans.channel_names + elif source == ChannelType.DIGITAL_INPUT: # digital input + channels = niDevice(device).di_lines.channel_names + elif source == ChannelType.DIGITAL_OUTPUT: # digital output + channels = niDevice(device).do_lines.channel_names + if channels != ['']: + channels_tot.extend(channels) + + return channels_tot + + def configuration_sequence(self, viewer, current_device): + """Configure each / modules / channels as giver by the user in the configuration file + + Read the .toml file to get the desired hardware configuration, + and send the nidaqmx a sequence which set up each channel. + """ + logger.info("********** CONFIGURATION SEQUENCE INITIALIZED **********") + devices_info = [dev.name + ': ' + dev.product_type for dev in self.devices] + logger.info("Detected devices: {}".format(devices_info)) + try: + viewer.config_devices = [config["NIDAQ_Devices", dev].get('name') for dev in viewer.config["NIDAQ_Devices"] + if "Mod" not in config["NIDAQ_Devices", dev].get('name')] + for dev in config["NIDAQ_Devices"]: + if not isinstance(config["NIDAQ_Devices", dev], dict): + continue + try: + device_name = config["NIDAQ_Devices", dev].get('name') + if not device_name == current_device.name: + continue + device_product = config["NIDAQ_Devices", dev].get('product') + device = niDevice(device_name) + assert device in self.devices and device.product_type == device_product, device.name + except AssertionError as err: + logger.error("Device {} not detected: {}".format(device_name, err)) + continue + for mod in config["NIDAQ_Devices", dev]: + if not isinstance(config["NIDAQ_Devices", dev, mod], dict): + continue + try: + module_name = config["NIDAQ_Devices", dev, mod].get('name') + module_product = config["NIDAQ_Devices", dev, mod].get('product') + module = niDevice(module_name) + assert module in self.devices and module.product_type == module_product, module.name + viewer.config_modules.append(config["NIDAQ_Devices", dev, mod].get('name')) + except AssertionError as err: + logger.error("Module {} not detected: {}".format(module_name, err)) + continue + for src in config["NIDAQ_Devices", dev, mod]: + if not isinstance(config["NIDAQ_Devices", dev, mod, src], dict): + continue + if src == "ai": + ai = config["NIDAQ_Devices", dev, mod, src] + for ch in ai.keys(): + name = module_name + "/" + str(ch) + source = ChannelType[ai[ch].get("source")] + analog_type = UsageTypeAI[ai[ch].get("analog_type")] + if analog_type == UsageTypeAI.VOLTAGE: + term = TerminalConfiguration[ai[ch].get("termination")] + viewer.config_channels.append(AIChannel + (name=name, + source=source, + analog_type=analog_type, + value_min=float(ai[ch].get("value_min")), + value_max=float(ai[ch].get("value_max")), + termination=term, + )) + elif analog_type == UsageTypeAI.CURRENT: + term = TerminalConfiguration[ai[ch].get("termination")] + viewer.config_channels.append(AIChannel + (name=name, + source=source, + analog_type=analog_type, + value_min=float(ai[ch].get("value_min")), + value_max=float(ai[ch].get("value_max")), + termination=term, + )) + elif analog_type == UsageTypeAI.TEMPERATURE_THERMOCOUPLE: + th = ThermocoupleType[ai[ch].get("thermo_type")] + viewer.config_channels.append(AIThermoChannel + (name=name, + source=source, + analog_type=analog_type, + value_min=float(ai[ch].get("value_min")), + value_max=float(ai[ch].get("value_max")), + thermo_type=th, + )) + logger.info("Devices from config: {}".format(viewer.config_devices)) + logger.info("Modules from config: {}".format(viewer.config_modules)) + logger.info("Channels from config: {}".format([ch.name for ch in viewer.config_channels])) + except AssertionError as err: + logger.error("Configuration entries <{}> does not match the hardware ".format(err)) + except Exception as err: + logger.info("Configuration sequence error, verify if your config matches the hardware: {}".format(err)) + pass + logger.info(" ********** CONFIGURATION SEQUENCE SUCCESSFULLY ENDED **********") + + @classmethod + def getAOMaxRate(cls, device): + return niDevice(device).ao_max_rate + + @classmethod + def getAIMaxRate(cls, device): + return niDevice(device).ai_max_single_chan_rate + + @classmethod + def isAnalogTriggeringSupported(cls, device): + return niDevice(device).anlg_trig_supported + + @classmethod + def isDigitalTriggeringSupported(cls, device): + return niDevice(device).dig_trig_supported + + @classmethod + def getTriggeringSources(cls, devices=None): + sources = [] + if devices is None: + devices = cls.get_NIDAQ_devices().device_names + + for device in devices: + if cls.isDigitalTriggeringSupported(device): + string = niDevice(device).terminals + channels = [chan for chan in string if 'PFI' in chan] + if channels != ['']: + sources.extend(channels) + if cls.isAnalogTriggeringSupported(device): + channels = niDevice(device).ai_physical_chans.channel_names + if channels != ['']: + sources.extend(channels) + return sources + + def update_task(self, channels=[], clock_settings=ClockSettings(), trigger_settings=TriggerSettings()): + + try: + if self._task is not None: + if isinstance(self._task, niTask): + self._task.close() + + self._task = None + self.c_callback = None + + self._task = niTask() + logger.info("TASK: {}".format(self._task)) + err_code = None + + # create all channels one task for one type of channels + for channel in channels: + if channel.source == ChannelType.ANALOG_INPUT: # analog input + try: + if channel.analog_type == UsageTypeAI.VOLTAGE: + self._task.ai_channels.add_ai_voltage_chan(channel.name, + "", + channel.termination, + channel.value_min, + channel.value_max, + VoltageUnits.VOLTS, + "") + + elif channel.analog_type == UsageTypeAI.CURRENT: + self._task.ai_channels.add_ai_current_chan(channel.name, + "", + channel.termination, + channel.value_min, + channel.value_max, + CurrentUnits.AMPS, + CurrentShuntResistorLocation.INTERNAL, + 0., + "") + + elif channel.analog_type == UsageTypeAI.TEMPERATURE_THERMOCOUPLE: + self._task.ai_channels.add_ai_thrmcpl_chan(channel.name, + "", + channel.value_min, + channel.value_max, + TemperatureUnits.DEG_C, + channel.thermo_type, + CJCSource.BUILT_IN, + 0., + "") + except DaqError as e: + err_code = e.error_code + if err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + elif channel.source == ChannelType.ANALOG_OUTPUT: # Analog_Output + try: + if channel.analog_type == UsageTypeAO.VOLTAGE: + self._task.ao_channels.add_ao_voltage_chan(channel.name, "", + channel.value_min, + channel.value_max, + VoltageUnits.VOLTS, None) + + elif channel.analog_type == UsageTypeAO.CURRENT: + self._task.ao_channels.add_ao_current_chan(channel.name, "", + channel.value_min, + channel.value_max, + VoltageUnits.VOLTS, None) + except DaqError as e: + err_code = e.error_code + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + elif channel.source == ChannelType.COUNTER_INPUT: # counter input + try: + if channel.counter_type == UsageTypeCI.COUNT_EDGES: + self._task.ci_channels.add_ci_count_edges_chan(channel.name, "", + channel.edge, 0, + CountDirection.COUNT_UP) + elif channel.counter_type == UsageTypeCI.PULSE_WIDTH_DIGITAL_SEMI_PERIOD: + self._task.ci_channels.add_ci_semi_period_chan(channel.name, "counter task", + 0, # expected min + channel.value_max, # expected max + TimeUnits.TICKS, "") + + except DaqError as e: + err_code = e.error_code + + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + elif channel.source == ChannelType.COUNTER_OUTPUT: # counter output + try: + if channel.counter_type == UsageTypeCO.PULSE_FREQUENCY: + self._task.co_channels.add_co_pulse_chan_freq(channel.name, "clock task", + FrequencyUnits.HZ, + Level.LOW, + 0, + channel.clock_frequency, + 0.5) + + except DaqError as e: + err_code = e.error_code + + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + elif channel.source == ChannelType.DIGITAL_OUTPUT: + try: + self._task.do_channels.add_do_chan(channel.name, "", + LineGrouping.CHAN_PER_LINE) + except DaqError as e: + err_code = e.error_code + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + elif channel.source == ChannelType.DIGITAL_INPUT: # Digital_Input + try: + self._task.di_channels.add_di_chan(channel.name, "", + LineGrouping.CHAN_PER_LINE) + except DaqError as e: + err_code = e.error_code + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + raise IOError(status) + + # configure the timing + if clock_settings.repetition: + mode = AcquisitionType.CONTINUOUS + else: + mode = AcquisitionType.FINITE + if clock_settings.Nsamples > 1 and isinstance(err_code, type(None)): + try: + if isinstance(clock_settings, ClockSettings): + self._task.timing.cfg_samp_clk_timing(clock_settings.frequency, + clock_settings.source, + clock_settings.edge, + mode, + clock_settings.Nsamples) + elif isinstance(clock_settings, ChangeDetectionSettings): + self._task.timing.cfg_change_detection_timing(clock_settings.rising_channel, + clock_settings.falling_channel, + mode, + clock_settings.Nsamples) + except DaqError as e: + err_code = e.error_code + if not not err_code: + status = self.DAQmxGetErrorString(err_code) + logger.error(traceback.format_exc()) + raise IOError(status) + + for channel in channels: + if not trigger_settings.enable: + if channel.source == ChannelType.COUNTER_INPUT: + pass # Maybe here adding the configuration fastCTr0 with Ctr1 etc...? + else: + pass + # err = self._task.triggers.start_trigger.disable_start_trig() + # if err != 0: + # raise IOError(self.DAQmxGetErrorString(err)) + else: + if 'PF' in trigger_settings.trig_source: + self._task.triggers.start_trigger.disable_start_trig() + elif 'ai' in trigger_settings.trig_source: + self._task.triggers.start_trigger.cfg_anlg_edge_start_trig( + trigger_settings.trig_source, + Edge[trigger_settings.edge], + trigger_settings.level) + else: + raise IOError('Unsupported Trigger source') + logger.info("Task's channels{}".format(self._task.ai_channels.channel_names)) + except Exception as e: + logger.error("Exception caught: {}".format(e)) + logger.error(traceback.format_exc()) + + def register_callback(self, callback, event='done', nsamples=1): + + if event == 'done': + self._task.register_done_event(callback) + elif event == 'sample': + self._task.register_every_n_samples_acquired_into_buffer_event(1, callback) + elif event == 'Nsamples': + self._task.register_every_n_samples_acquired_into_buffer_event(nsamples, callback) + + def readCounter(self): + # return 25 + + time.sleep(1) + self.value = self._task.read() + + # t = Timer(counting_time,self.read) + # self._task.start() + # t.start() + # time.sleep(2*counting_time) + # print(self.value) + return self.value + + @classmethod + def getAIVoltageRange(cls, device='Dev1'): + ret = niSystem.local().devices[device].ai_voltage_rngs # todo self.devices[device].ai_voltage_rngs + return [tuple(ret[6:8])] + + @classmethod + def getAOVoltageRange(cls, device='Dev1'): + ret = niSystem.local().devices[device].ao_voltage_rngs # todo self.devices[device].ao_voltage_rngs + return [tuple(ret)] # [(-10., 10.)] Why this format is needed?? + + def stop(self): + if self._task is not None: + self._task.stop() + + def start(self): + if self._task is not None: + self._task.start() + + def close(self): + """ + close the current task. + """ + if self._task is not None: + self._task.stop() + self._task.close() + self._task = None + + @classmethod + def DAQmxGetErrorString(cls, error_code): + if error_code is None: + return '' + else: + return DAQmxErrors(error_code).name + + def isTaskDone(self): + return self._task.is_task_done() + + def waitTaskDone(self, timeout=10.): + ret = self._task.wait_until_done(timeout) + if ret != 0: + logger.info(self.DAQmxGetErrorString(ret)) + + def refresh_hardware(self): + """ + Refresh the NIDAQ hardware from settings values. + + See Also + -------- + update_NIDAQ_devices, update_NIDAQ_channels + """ + self.update_NIDAQ_devices() + self.update_NIDAQ_channels() + + +if __name__ == '__main__': + pass diff --git a/src/pymodaq_plugins_daqmx/resources/config_template.toml b/src/pymodaq_plugins_daqmx/resources/config_template.toml index cf493ee..4a84012 100644 --- a/src/pymodaq_plugins_daqmx/resources/config_template.toml +++ b/src/pymodaq_plugins_daqmx/resources/config_template.toml @@ -1,2 +1,28 @@ title = 'Configuration file of the DAQmx plugin' +[NIDAQ_Devices] + +[NIDAQ_Devices.DEVICE01] +title = "Configuration entry for a NIDAQmx device" +name = "" +product = "" + +[NIDAQ_Devices.DEVICE01.MODULE01] +title = "A module plugged in the NIDAQmx device" +name = "" +product = "" + +[NIDAQ_Devices.DEVICE01.MODULE02] +title = "A module plugged in a NIDAQmx device" +name = "" +product = "" + +[NIDAQ_Devices.DEVICE01.MODULE03] +title = "A module plugged in a NIDAQmx device" +name = "" +product = "" + +[NIDAQ_Devices.DEVICE01.MODULE01.ai.ai0] +current_fields_example = "source, analog_type, value_min, value_max, termination" +voltage_fields_example = "source, analog_type, value_min, value_max, termination" +thrmcpl_fields_example = "source, analog_type, value_min, value_max, thermo_type" \ No newline at end of file diff --git a/src/pymodaq_plugins_daqmx/utils.py b/src/pymodaq_plugins_daqmx/utils.py index 1f225ab..17af231 100644 --- a/src/pymodaq_plugins_daqmx/utils.py +++ b/src/pymodaq_plugins_daqmx/utils.py @@ -12,4 +12,4 @@ class Config(BaseConfig): """Main class to deal with configuration values for this plugin""" config_template_path = Path(__file__).parent.joinpath('resources/config_template.toml') - config_name = f"config_{__package__.split('pymodaq_plugins_')[1]}" + config_name = f"config_{__package__.split('pymodaq_plugins_')[1]}" \ No newline at end of file