nidaqmx 1.2.0.dev0__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/PKG-INFO +12 -7
  2. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/README.rst +4 -1
  3. nidaqmx-1.3.0/generated/nidaqmx/__init__.py +31 -0
  4. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/__main__.py +6 -3
  5. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_base_interpreter.py +108 -2
  6. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_bitfield_utils.py +8 -11
  7. nidaqmx-1.3.0/generated/nidaqmx/_dotenvpath.py +73 -0
  8. nidaqmx-1.3.0/generated/nidaqmx/_feature_toggles.py +162 -0
  9. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_grpc_interpreter.py +97 -2
  10. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_grpc_time.py +13 -10
  11. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_install_daqmx.py +23 -64
  12. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_installer_metadata.json +6 -6
  13. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_lib.py +72 -62
  14. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_lib_time.py +31 -19
  15. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_library_interpreter.py +781 -5
  16. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_linux_installation_commands.py +5 -6
  17. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/nidaqmx_pb2.py +1 -1
  18. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/nidaqmx_pb2.pyi +1 -1
  19. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_time.py +11 -9
  20. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/constants.py +11 -1
  21. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/errors.py +73 -71
  22. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/grpc_session_options.py +19 -14
  23. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/__init__.py +37 -0
  24. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_analog_multi_channel_reader.py +235 -0
  25. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_analog_single_channel_reader.py +193 -0
  26. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_analog_unscaled_reader.py +352 -0
  27. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_channel_reader_base.py +126 -0
  28. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_counter_reader.py +529 -0
  29. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_digital_multi_channel_reader.py +581 -0
  30. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_digital_single_channel_reader.py +472 -0
  31. nidaqmx-1.3.0/generated/nidaqmx/stream_readers/_power_readers.py +397 -0
  32. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/__init__.py +34 -0
  33. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_analog_multi_channel_writer.py +137 -0
  34. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_analog_single_channel_writer.py +119 -0
  35. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_analog_unscaled_writer.py +185 -0
  36. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_channel_writer_base.py +170 -0
  37. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_counter_writer.py +254 -0
  38. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_digital_multi_channel_writer.py +365 -0
  39. nidaqmx-1.3.0/generated/nidaqmx/stream_writers/_digital_single_channel_writer.py +322 -0
  40. nidaqmx-1.3.0/generated/nidaqmx/system/__init__.py +19 -0
  41. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/device_collection.py +27 -26
  42. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/persisted_channel_collection.py +36 -29
  43. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/persisted_scale_collection.py +30 -26
  44. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/persisted_task_collection.py +30 -25
  45. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/physical_channel_collection.py +86 -75
  46. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_watchdog_modules/expiration_states_collection.py +14 -12
  47. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/storage/__init__.py +3 -1
  48. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/storage/persisted_channel.py +27 -34
  49. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/storage/persisted_scale.py +28 -36
  50. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/storage/persisted_task.py +28 -36
  51. nidaqmx-1.3.0/generated/nidaqmx/task/__init__.py +15 -0
  52. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/_in_stream.py +18 -2
  53. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/_task.py +718 -331
  54. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/_timing.py +454 -39
  55. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/__init__.py +12 -2
  56. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/__init__.py +12 -2
  57. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_channel_collection.py +30 -34
  58. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/__init__.py +11 -2
  59. nidaqmx-1.3.0/generated/nidaqmx/types.py +71 -0
  60. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/utils.py +41 -39
  61. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/pyproject.toml +79 -37
  62. nidaqmx-1.2.0.dev0/generated/nidaqmx/__init__.py +0 -17
  63. nidaqmx-1.2.0.dev0/generated/nidaqmx/_stubs/session_pb2.py +0 -52
  64. nidaqmx-1.2.0.dev0/generated/nidaqmx/_stubs/session_pb2.pyi +0 -240
  65. nidaqmx-1.2.0.dev0/generated/nidaqmx/_stubs/session_pb2_grpc.py +0 -204
  66. nidaqmx-1.2.0.dev0/generated/nidaqmx/_stubs/session_pb2_grpc.pyi +0 -130
  67. nidaqmx-1.2.0.dev0/generated/nidaqmx/stream_readers.py +0 -2454
  68. nidaqmx-1.2.0.dev0/generated/nidaqmx/stream_writers.py +0 -1401
  69. nidaqmx-1.2.0.dev0/generated/nidaqmx/system/__init__.py +0 -9
  70. nidaqmx-1.2.0.dev0/generated/nidaqmx/task/__init__.py +0 -9
  71. nidaqmx-1.2.0.dev0/generated/nidaqmx/types.py +0 -69
  72. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/LICENSE +0 -0
  73. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/__init__.py +0 -0
  74. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/data_moniker_pb2.py +0 -0
  75. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/data_moniker_pb2.pyi +0 -0
  76. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/data_moniker_pb2_grpc.py +0 -0
  77. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/data_moniker_pb2_grpc.pyi +0 -0
  78. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/nidaqmx_pb2_grpc.py +0 -0
  79. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/_stubs/nidaqmx_pb2_grpc.pyi +0 -0
  80. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/error_codes.py +0 -0
  81. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/scale.py +0 -0
  82. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_collections/__init__.py +0 -0
  83. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_watchdog_modules/__init__.py +0 -0
  84. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/_watchdog_modules/expiration_state.py +0 -0
  85. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/device.py +0 -0
  86. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/physical_channel.py +0 -0
  87. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/system.py +0 -0
  88. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/system/watchdog.py +0 -0
  89. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/_export_signals.py +0 -0
  90. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/_out_stream.py +0 -0
  91. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_ai_channel.py +0 -0
  92. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_ao_channel.py +0 -0
  93. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_channel.py +0 -0
  94. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_ci_channel.py +0 -0
  95. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_co_channel.py +0 -0
  96. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_di_channel.py +0 -0
  97. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/channels/_do_channel.py +0 -0
  98. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_ai_channel_collection.py +0 -0
  99. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_ao_channel_collection.py +0 -0
  100. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_ci_channel_collection.py +0 -0
  101. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_co_channel_collection.py +0 -0
  102. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_di_channel_collection.py +0 -0
  103. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/collections/_do_channel_collection.py +0 -0
  104. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_arm_start_trigger.py +0 -0
  105. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_handshake_trigger.py +0 -0
  106. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_pause_trigger.py +0 -0
  107. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_reference_trigger.py +0 -0
  108. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_start_trigger.py +0 -0
  109. {nidaqmx-1.2.0.dev0 → nidaqmx-1.3.0}/generated/nidaqmx/task/triggering/_triggers.py +0 -0
@@ -1,8 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: nidaqmx
3
- Version: 1.2.0.dev0
3
+ Version: 1.3.0
4
4
  Summary: NI-DAQmx Python API
5
- Home-page: https://github.com/ni/nidaqmx-python
6
5
  License: MIT
7
6
  Keywords: nidaqmx,nidaq,daqmx,daq
8
7
  Author: NI
@@ -23,6 +22,7 @@ Classifier: Programming Language :: Python :: 3.10
23
22
  Classifier: Programming Language :: Python :: 3.11
24
23
  Classifier: Programming Language :: Python :: 3.12
25
24
  Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
26
  Classifier: Programming Language :: Python :: Implementation :: CPython
27
27
  Classifier: Programming Language :: Python :: Implementation :: PyPy
28
28
  Classifier: Topic :: System :: Hardware :: Hardware Drivers
@@ -31,13 +31,15 @@ Requires-Dist: click (>=8.0.0)
31
31
  Requires-Dist: deprecation (>=2.1)
32
32
  Requires-Dist: distro (>=1.9.0) ; sys_platform == "linux"
33
33
  Requires-Dist: grpcio (>=1.49.0,<2.0) ; extra == "grpc"
34
- Requires-Dist: hightime (>=0.2.2,<0.3.0)
35
- Requires-Dist: numpy (>=1.22) ; python_version >= "3.9" and python_version < "3.12"
36
- Requires-Dist: numpy (>=1.26) ; python_version >= "3.12" and python_version < "3.13"
34
+ Requires-Dist: hightime (>=0.2.2)
35
+ Requires-Dist: ni-grpcdevice-v1-proto (>=1.0.0) ; extra == "grpc"
36
+ Requires-Dist: nitypes (>=0.1.0dev10)
37
+ Requires-Dist: numpy (>=1.22) ; python_version >= "3.9" and python_version < "3.13"
37
38
  Requires-Dist: numpy (>=2.1) ; python_version >= "3.13" and python_version < "4.0"
38
39
  Requires-Dist: protobuf (>=4.21) ; extra == "grpc"
39
40
  Requires-Dist: python-decouple (>=3.8)
40
41
  Requires-Dist: requests (>=2.25.0)
42
+ Requires-Dist: typing_extensions (>=4.0.0)
41
43
  Requires-Dist: tzlocal (>=5.0,<6.0)
42
44
  Project-URL: Documentation, https://nidaqmx-python.readthedocs.io
43
45
  Project-URL: Repository, https://github.com/ni/nidaqmx-python
@@ -100,7 +102,10 @@ system.
100
102
  Python Version Support
101
103
  ----------------------
102
104
 
103
- **nidaqmx** supports CPython 3.9+ and PyPy3.
105
+ **nidaqmx** supports CPython 3.9+.
106
+
107
+ **nidaqmx** supports PyPy3 for non-gRPC use cases. For the status of PyPy support for gRPC,
108
+ see `PyPy support (grpc/grpc#4221) <https://github.com/grpc/grpc/issues/4221>`_.
104
109
 
105
110
  Installation
106
111
  ============
@@ -55,7 +55,10 @@ system.
55
55
  Python Version Support
56
56
  ----------------------
57
57
 
58
- **nidaqmx** supports CPython 3.9+ and PyPy3.
58
+ **nidaqmx** supports CPython 3.9+.
59
+
60
+ **nidaqmx** supports PyPy3 for non-gRPC use cases. For the status of PyPy support for gRPC,
61
+ see `PyPy support (grpc/grpc#4221) <https://github.com/grpc/grpc/issues/4221>`_.
59
62
 
60
63
  Installation
61
64
  ============
@@ -0,0 +1,31 @@
1
+ """The NI-DAQmx API for Python."""
2
+
3
+ from nidaqmx.errors import (
4
+ DaqError,
5
+ DaqReadError,
6
+ DaqResourceWarning,
7
+ DaqWarning,
8
+ DaqWriteError,
9
+ )
10
+ from nidaqmx.grpc_session_options import * # noqa: F403 - 'from nidaqmx.grpc_session_options import *' used; unable to detect undefined names (auto-generated noqa)
11
+ from nidaqmx.scale import Scale
12
+ from nidaqmx.task import Task
13
+ from nidaqmx.types import CtrFreq, CtrTick, CtrTime
14
+
15
+ try:
16
+ from importlib.metadata import version
17
+ except ImportError:
18
+ from importlib_metadata import version # type: ignore[no-redef]
19
+
20
+ __version__ = version(__name__)
21
+
22
+ __all__ = [ # noqa: F405 - 'errors' may be undefined, or defined from star imports: nidaqmx.grpc_session_options (auto-generated noqa)
23
+ "errors",
24
+ "scale",
25
+ "stream_readers",
26
+ "stream_writers",
27
+ "task",
28
+ ]
29
+
30
+ # Do not add a null logging handler. If the application has not configured logging, the
31
+ # default behavior is to log warnings and errors to stderr.
@@ -1,7 +1,8 @@
1
+ """'nidaqmx' command line utility."""
2
+
1
3
  from __future__ import annotations
2
4
 
3
5
  import logging
4
- from typing import Optional
5
6
 
6
7
  import click
7
8
 
@@ -16,12 +17,14 @@ from . import _install_daqmx
16
17
  count=True,
17
18
  help="Enable verbose logging. Repeat to increase verbosity.",
18
19
  )
19
- def main(verbosity: int) -> None:
20
+ def main( # noqa: D103 - Missing docstring in public function (auto-generated noqa)
21
+ verbosity: int,
22
+ ) -> None:
20
23
  _configure_logging(verbosity)
21
24
 
22
25
 
23
26
  @main.command()
24
- def installdriver():
27
+ def installdriver(): # noqa: D103 - Missing docstring in public function (auto-generated noqa)
25
28
  _install_daqmx.installdriver()
26
29
 
27
30
 
@@ -1,6 +1,11 @@
1
1
  # Do not edit this file; it was automatically generated.
2
+ from __future__ import annotations
3
+
2
4
  import abc
3
- from typing import Optional
5
+ import numpy
6
+ from nitypes.waveform import AnalogWaveform, DigitalWaveform
7
+ from typing import Any, Sequence
8
+ from nidaqmx.constants import WaveformAttributeMode
4
9
 
5
10
 
6
11
  class BaseEventHandler(abc.ABC):
@@ -1088,6 +1093,10 @@ class BaseInterpreter(abc.ABC):
1088
1093
  def get_timing_attribute_string(self, task, attribute):
1089
1094
  raise NotImplementedError
1090
1095
 
1096
+ @abc.abstractmethod
1097
+ def get_timing_attribute_timestamp(self, task, attribute):
1098
+ raise NotImplementedError
1099
+
1091
1100
  @abc.abstractmethod
1092
1101
  def get_timing_attribute_uint32(self, task, attribute):
1093
1102
  raise NotImplementedError
@@ -1845,4 +1854,101 @@ class BaseInterpreter(abc.ABC):
1845
1854
 
1846
1855
  @abc.abstractmethod
1847
1856
  def hash_task_handle(self, task_handle):
1848
- raise NotImplementedError
1857
+ raise NotImplementedError
1858
+
1859
+ @abc.abstractmethod
1860
+ def read_analog_waveform(
1861
+ self,
1862
+ task_handle: object,
1863
+ number_of_samples_per_channel: int,
1864
+ timeout: float,
1865
+ waveform: AnalogWaveform[numpy.float64],
1866
+ waveform_attribute_mode: WaveformAttributeMode
1867
+ ) -> int:
1868
+ raise NotImplementedError
1869
+
1870
+ @abc.abstractmethod
1871
+ def read_analog_waveforms(
1872
+ self,
1873
+ task_handle: object,
1874
+ number_of_samples_per_channel: int,
1875
+ timeout: float,
1876
+ waveforms: Sequence[AnalogWaveform[numpy.float64]],
1877
+ waveform_attribute_mode: WaveformAttributeMode
1878
+ ) -> int:
1879
+ raise NotImplementedError
1880
+
1881
+ @abc.abstractmethod
1882
+ def read_digital_waveform(
1883
+ self,
1884
+ task_handle: object,
1885
+ number_of_samples_per_channel: int,
1886
+ timeout: float,
1887
+ waveform: DigitalWaveform[Any],
1888
+ waveform_attribute_mode: WaveformAttributeMode
1889
+ ) -> int:
1890
+ raise NotImplementedError
1891
+
1892
+ @abc.abstractmethod
1893
+ def read_digital_waveforms(
1894
+ self,
1895
+ task_handle: object,
1896
+ channel_count: int,
1897
+ number_of_samples_per_channel: int,
1898
+ number_of_signals_per_sample: int,
1899
+ timeout: float,
1900
+ waveforms: Sequence[DigitalWaveform[Any]],
1901
+ waveform_attribute_mode: WaveformAttributeMode,
1902
+ ) -> int:
1903
+ raise NotImplementedError
1904
+
1905
+ @abc.abstractmethod
1906
+ def read_new_digital_waveforms(
1907
+ self,
1908
+ task_handle: object,
1909
+ channel_count: int,
1910
+ number_of_samples_per_channel: int,
1911
+ number_of_signals_per_sample: int,
1912
+ timeout: float,
1913
+ waveform_attribute_mode: WaveformAttributeMode,
1914
+ ) -> Sequence[DigitalWaveform[numpy.uint8]]:
1915
+ raise NotImplementedError
1916
+
1917
+ @abc.abstractmethod
1918
+ def write_analog_waveform(
1919
+ self,
1920
+ task_handle: object,
1921
+ waveform: AnalogWaveform[Any],
1922
+ auto_start: bool,
1923
+ timeout: float
1924
+ ) -> int:
1925
+ raise NotImplementedError
1926
+
1927
+ @abc.abstractmethod
1928
+ def write_analog_waveforms(
1929
+ self,
1930
+ task_handle: object,
1931
+ waveforms: Sequence[AnalogWaveform[Any]],
1932
+ auto_start: bool,
1933
+ timeout: float
1934
+ ) -> int:
1935
+ raise NotImplementedError
1936
+
1937
+ @abc.abstractmethod
1938
+ def write_digital_waveform(
1939
+ self,
1940
+ task_handle: object,
1941
+ waveform: DigitalWaveform[Any],
1942
+ auto_start: bool,
1943
+ timeout: float,
1944
+ ) -> int:
1945
+ raise NotImplementedError
1946
+
1947
+ def write_digital_waveforms(
1948
+ self,
1949
+ task_handle: object,
1950
+ waveform: Sequence[DigitalWaveform[Any]],
1951
+ auto_start: bool,
1952
+ timeout: float,
1953
+ ) -> int:
1954
+ raise NotImplementedError
@@ -1,41 +1,38 @@
1
- def enum_bitfield_to_list(bitfield_value, bitfield_enum_type,
2
- actual_enum_type):
3
- """
4
- Converts a bitfield value to a list of enums.
1
+ def enum_bitfield_to_list(bitfield_value, bitfield_enum_type, actual_enum_type):
2
+ """Converts a bitfield value to a list of enums.
5
3
 
6
4
  Args:
7
5
  bitfield_value (int): Specifies the value of the bitfield.
8
6
  bitfield_enum_type (enum.Enum): Specifies the bitfield enum type
9
7
  from which to mask and extract the enum values.
10
8
  actual_enum_type (enum.Enum): Specifies the actual enum type.
9
+
11
10
  Returns:
12
11
  List[enum.Enum]: Indicates the converted list of enums.
13
12
  """
14
13
  supported_values = []
15
14
  for bitfield_mask in bitfield_enum_type:
16
15
  if bitfield_value & bitfield_mask.value:
17
- enum_value = next(
18
- e for e in actual_enum_type if e.name == bitfield_mask.name)
16
+ enum_value = next(e for e in actual_enum_type if e.name == bitfield_mask.name)
19
17
  supported_values.append(enum_value)
20
18
 
21
19
  return supported_values
22
20
 
23
21
 
24
22
  def enum_list_to_bitfield(enum_list, bitfield_enum_type):
25
- """
26
- Converts a list of enums to a bitfield value.
23
+ """Converts a list of enums to a bitfield value.
27
24
 
28
25
  Args:
29
26
  enum_list (List[enum.Enum]): Specifies the list of enums.
30
27
  bitfield_enum_type (enum.Enum): Specifies the bitfield enum type
31
28
  from which to mask and extract the enum values.
29
+
32
30
  Returns:
33
31
  int: Indicates the value of the bitfield.
34
32
  """
35
33
  bitfield_value = 0
36
34
  for enum_value in enum_list:
37
- bitfield_mask = next(
38
- b for b in bitfield_enum_type if b.name == enum_value.name)
35
+ bitfield_mask = next(b for b in bitfield_enum_type if b.name == enum_value.name)
39
36
  bitfield_value |= bitfield_mask.value
40
37
 
41
- return bitfield_value
38
+ return bitfield_value
@@ -0,0 +1,73 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import sys
5
+ import traceback
6
+ from pathlib import Path
7
+
8
+
9
+ def get_dotenv_search_path() -> Path:
10
+ """Get the search path for loading the `.env` file."""
11
+ # Prefer to load the `.env` file from the current directory or its parents.
12
+ # If the current directory doesn't have a `.env` file, fall back to the
13
+ # script/EXE path or the TestStand code module path.
14
+ cwd = Path.cwd()
15
+ if not _has_dotenv_file(cwd):
16
+ if script_or_exe_path := _get_script_or_exe_path():
17
+ return script_or_exe_path.resolve().parent
18
+ if caller_path := _get_caller_path():
19
+ return caller_path.resolve().parent
20
+ return cwd
21
+
22
+
23
+ def _has_dotenv_file(dir: Path) -> bool:
24
+ """Check whether the dir or its parents contains a `.env` file."""
25
+ return (dir / ".env").exists() or any((p / ".env").exists() for p in dir.parents)
26
+
27
+
28
+ def _get_script_or_exe_path() -> Path | None:
29
+ """Get the path of the top-level script or PyInstaller EXE, if possible."""
30
+ if getattr(sys, "frozen", False):
31
+ return Path(sys.executable)
32
+
33
+ main_module = sys.modules.get("__main__")
34
+ if main_module:
35
+ script_path = getattr(main_module, "__file__", "")
36
+ if script_path:
37
+ return Path(script_path)
38
+
39
+ return None
40
+
41
+
42
+ def _get_caller_path() -> Path | None:
43
+ """Get the path of the module calling into this package, if possible."""
44
+ package_path = _get_package_path()
45
+ for frame, _ in traceback.walk_stack(inspect.currentframe()):
46
+ if frame.f_code.co_filename:
47
+ module_path = Path(frame.f_code.co_filename)
48
+ if _exists(module_path) and not module_path.is_relative_to(package_path):
49
+ return module_path
50
+
51
+ return None
52
+
53
+
54
+ # Path.exists() throws OSError when the path has invalid file characters.
55
+ # https://github.com/python/cpython/issues/79487
56
+ if sys.version_info >= (3, 10):
57
+
58
+ def _exists(path: Path) -> bool:
59
+ return path.exists()
60
+
61
+ else:
62
+
63
+ def _exists(path: Path) -> bool:
64
+ import os
65
+
66
+ return os.path.exists(path)
67
+
68
+
69
+ def _get_package_path() -> Path:
70
+ """Get the path of this package."""
71
+ module = sys.modules[__package__]
72
+ assert module.__file__ and module.__file__.endswith("__init__.py")
73
+ return Path(module.__file__).parent
@@ -0,0 +1,162 @@
1
+ """nidaqmx feature toggles."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ import sys
7
+ from enum import Enum
8
+ from typing import TYPE_CHECKING, Callable, TypeVar
9
+
10
+ from decouple import AutoConfig, Undefined, undefined
11
+
12
+ from nidaqmx._dotenvpath import get_dotenv_search_path
13
+ from nidaqmx.errors import FeatureNotSupportedError
14
+
15
+ if TYPE_CHECKING:
16
+ if sys.version_info >= (3, 10):
17
+ from typing import ParamSpec
18
+ else:
19
+ from typing_extensions import ParamSpec
20
+
21
+ if sys.version_info >= (3, 11):
22
+ from typing import Self
23
+ else:
24
+ from typing_extensions import Self
25
+
26
+ _P = ParamSpec("_P")
27
+ _T = TypeVar("_T")
28
+
29
+ _PREFIX = "NIDAQMX"
30
+
31
+ _config = AutoConfig(str(get_dotenv_search_path()))
32
+
33
+ if TYPE_CHECKING:
34
+ # Work around decouple's lack of type hints.
35
+ def _config(
36
+ option: str,
37
+ default: _T | Undefined = undefined,
38
+ cast: Callable[[str], _T] | Undefined = undefined,
39
+ ) -> _T: ...
40
+
41
+
42
+ # Based on the recipe at https://docs.python.org/3/howto/enum.html
43
+ class _OrderedEnum(Enum):
44
+ def __ge__(self, other: Self) -> bool:
45
+ if self.__class__ is other.__class__:
46
+ return self.value >= other.value
47
+ return NotImplemented
48
+
49
+ def __gt__(self, other: Self) -> bool:
50
+ if self.__class__ is other.__class__:
51
+ return self.value > other.value
52
+ return NotImplemented
53
+
54
+ def __le__(self, other: Self) -> bool:
55
+ if self.__class__ is other.__class__:
56
+ return self.value <= other.value
57
+ return NotImplemented
58
+
59
+ def __lt__(self, other: Self) -> bool:
60
+ if self.__class__ is other.__class__:
61
+ return self.value < other.value
62
+ return NotImplemented
63
+
64
+
65
+ class CodeReadiness(_OrderedEnum):
66
+ """Indicates whether code is ready to be supported."""
67
+
68
+ RELEASE = 0
69
+ NEXT_RELEASE = 1
70
+ INCOMPLETE = 2
71
+ PROTOTYPE = 3
72
+
73
+
74
+ def _init_code_readiness_level() -> CodeReadiness:
75
+ if _config(f"{_PREFIX}_ALLOW_INCOMPLETE", default=False, cast=bool):
76
+ return CodeReadiness.INCOMPLETE
77
+ elif _config(f"{_PREFIX}_ALLOW_NEXT_RELEASE", default=False, cast=bool):
78
+ return CodeReadiness.NEXT_RELEASE
79
+ else:
80
+ return CodeReadiness.RELEASE
81
+
82
+
83
+ # This is not public because `from _feature_toggles import CODE_READINESS_LEVEL`
84
+ # is incompatible with the patching performed by the use_code_readiness mark.
85
+ _CODE_READINESS_LEVEL = _init_code_readiness_level()
86
+
87
+
88
+ def get_code_readiness_level() -> CodeReadiness:
89
+ """Get the current code readiness level.
90
+
91
+ You can override this in tests by specifying the ``use_code_readiness``
92
+ mark.
93
+ """
94
+ return _CODE_READINESS_LEVEL
95
+
96
+
97
+ class FeatureToggle:
98
+ """A run-time feature toggle."""
99
+
100
+ name: str
101
+ """The name of the feature."""
102
+
103
+ readiness: CodeReadiness
104
+ """The code readiness at which this feature is enabled."""
105
+
106
+ def __init__(self, name: str, readiness: CodeReadiness) -> None:
107
+ """Initialize the feature toggle."""
108
+ assert name == name.upper()
109
+ self.name = name
110
+ self.readiness = readiness
111
+ self._is_enabled_override = None
112
+ # Only read the env var at initialization time.
113
+ if _config(f"{_PREFIX}_ENABLE_{name}", default=False, cast=bool):
114
+ self._is_enabled_override = True
115
+
116
+ @property
117
+ def is_enabled(self) -> bool:
118
+ """Indicates whether the feature is currently enabled.
119
+
120
+ You can enable/disable features in tests by specifying the
121
+ ``enable_feature_toggle`` or ``disable_feature_toggle`` marks.
122
+ """
123
+ if self._is_enabled_override is not None:
124
+ return self._is_enabled_override
125
+ return self.readiness <= get_code_readiness_level()
126
+
127
+ def raise_if_disabled(self) -> None:
128
+ """Raises an error if the feature is disabled."""
129
+ if self.is_enabled:
130
+ return
131
+
132
+ env_vars = f"{_PREFIX}_ENABLE_{self.name}"
133
+ if self.readiness in [CodeReadiness.NEXT_RELEASE, CodeReadiness.INCOMPLETE]:
134
+ env_vars += f" or {_PREFIX}_ALLOW_{self.readiness.name}"
135
+ message = (
136
+ f"The {self.name} feature is not supported at the current code readiness level. "
137
+ f" To enable it, set {env_vars}."
138
+ )
139
+ raise FeatureNotSupportedError(message)
140
+
141
+
142
+ def requires_feature(
143
+ feature_toggle: FeatureToggle,
144
+ ) -> Callable[[Callable[_P, _T]], Callable[_P, _T]]:
145
+ """Decorator specifying that the function requires the specified feature toggle."""
146
+
147
+ def decorator(func: Callable[_P, _T]) -> Callable[_P, _T]:
148
+ @functools.wraps(func)
149
+ def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _T:
150
+ feature_toggle.raise_if_disabled()
151
+ return func(*args, **kwargs)
152
+
153
+ return wrapper
154
+
155
+ return decorator
156
+
157
+
158
+ # --------------------------------------
159
+ # Define feature toggle constants here:
160
+ # --------------------------------------
161
+
162
+ WAVEFORM_SUPPORT = FeatureToggle("WAVEFORM_SUPPORT", CodeReadiness.INCOMPLETE)
@@ -5,7 +5,8 @@ import logging
5
5
  import threading
6
6
  import typing
7
7
  import warnings
8
- from typing import Callable, Generic, TypeVar
8
+ from nitypes.waveform import AnalogWaveform, DigitalWaveform
9
+ from typing import Any, Callable, Generic, Sequence, TypeVar
9
10
 
10
11
  import google.protobuf.message
11
12
  from google.protobuf.timestamp_pb2 import Timestamp as GrpcTimestamp
@@ -16,7 +17,7 @@ from . import errors as errors
16
17
  from nidaqmx._base_interpreter import BaseEventHandler, BaseInterpreter
17
18
  from nidaqmx._stubs import nidaqmx_pb2 as grpc_types
18
19
  from nidaqmx._stubs import nidaqmx_pb2_grpc as nidaqmx_grpc
19
- from nidaqmx._stubs import session_pb2 as session_grpc_types
20
+ from nidaqmx.constants import WaveformAttributeMode
20
21
  from nidaqmx.error_codes import DAQmxErrors
21
22
  from nidaqmx._grpc_time import convert_time_to_timestamp, convert_timestamp_to_time
22
23
 
@@ -2179,6 +2180,12 @@ class GrpcStubInterpreter(BaseInterpreter):
2179
2180
  grpc_types.GetTimingAttributeStringRequest(task=task, attribute_raw=attribute))
2180
2181
  return response.value
2181
2182
 
2183
+ def get_timing_attribute_timestamp(self, task, attribute):
2184
+ response = self._invoke(
2185
+ self._client.GetTimingAttributeTimestamp,
2186
+ grpc_types.GetTimingAttributeTimestampRequest(task=task, attribute_raw=attribute))
2187
+ return convert_timestamp_to_time(response.value)
2188
+
2182
2189
  def get_timing_attribute_uint32(self, task, attribute):
2183
2190
  response = self._invoke(
2184
2191
  self._client.GetTimingAttributeUInt32,
@@ -3600,6 +3607,94 @@ class GrpcStubInterpreter(BaseInterpreter):
3600
3607
  def internal_get_last_created_chan(self):
3601
3608
  raise NotImplementedError
3602
3609
 
3610
+ def read_analog_waveform(
3611
+ self,
3612
+ task_handle: object,
3613
+ number_of_samples_per_channel: int,
3614
+ timeout: float,
3615
+ waveform: AnalogWaveform[numpy.float64],
3616
+ waveform_attribute_mode: WaveformAttributeMode
3617
+ ) -> int:
3618
+ raise NotImplementedError
3619
+
3620
+ def read_analog_waveforms(
3621
+ self,
3622
+ task_handle: object,
3623
+ number_of_samples_per_channel: int,
3624
+ timeout: float,
3625
+ waveforms: Sequence[AnalogWaveform[numpy.float64]],
3626
+ waveform_attribute_mode: WaveformAttributeMode
3627
+ ) -> int:
3628
+ raise NotImplementedError
3629
+
3630
+ def read_digital_waveform(
3631
+ self,
3632
+ task_handle: object,
3633
+ number_of_samples_per_channel: int,
3634
+ timeout: float,
3635
+ waveform: DigitalWaveform[Any],
3636
+ waveform_attribute_mode: WaveformAttributeMode
3637
+ ) -> int:
3638
+ raise NotImplementedError
3639
+
3640
+ def read_digital_waveforms(
3641
+ self,
3642
+ task_handle: object,
3643
+ channel_count: int,
3644
+ number_of_samples_per_channel: int,
3645
+ number_of_signals_per_sample: int,
3646
+ timeout: float,
3647
+ waveforms: Sequence[DigitalWaveform[Any]],
3648
+ waveform_attribute_mode: WaveformAttributeMode,
3649
+ ) -> int:
3650
+ raise NotImplementedError
3651
+
3652
+ def read_new_digital_waveforms(
3653
+ self,
3654
+ task_handle: object,
3655
+ channel_count: int,
3656
+ number_of_samples_per_channel: int,
3657
+ number_of_signals_per_sample: int,
3658
+ timeout: float,
3659
+ waveform_attribute_mode: WaveformAttributeMode,
3660
+ ) -> Sequence[DigitalWaveform[numpy.uint8]]:
3661
+ raise NotImplementedError
3662
+
3663
+ def write_analog_waveform(
3664
+ self,
3665
+ task_handle: object,
3666
+ waveform: AnalogWaveform[typing.Any],
3667
+ auto_start: bool,
3668
+ timeout: float
3669
+ ) -> int:
3670
+ raise NotImplementedError
3671
+
3672
+ def write_analog_waveforms(
3673
+ self,
3674
+ task_handle: object,
3675
+ waveforms: Sequence[AnalogWaveform[typing.Any]],
3676
+ auto_start: bool,
3677
+ timeout: float
3678
+ ) -> int:
3679
+ raise NotImplementedError
3680
+
3681
+ def write_digital_waveform(
3682
+ self,
3683
+ task_handle: object,
3684
+ waveform: DigitalWaveform[Any],
3685
+ auto_start: bool,
3686
+ timeout: float,
3687
+ ) -> int:
3688
+ raise NotImplementedError
3689
+
3690
+ def write_digital_waveforms(
3691
+ self,
3692
+ task_handle: object,
3693
+ waveform: Sequence[DigitalWaveform[Any]],
3694
+ auto_start: bool,
3695
+ timeout: float,
3696
+ ) -> int:
3697
+ raise NotImplementedError
3603
3698
 
3604
3699
  def _assign_numpy_array(numpy_array, grpc_array):
3605
3700
  """