diff --git a/doc/ChimeraTK_Favicon.ico b/doc/ChimeraTK_Favicon.ico new file mode 100644 index 0000000..140b69f Binary files /dev/null and b/doc/ChimeraTK_Favicon.ico differ diff --git a/doc/api_reference.rst b/doc/api_reference.rst new file mode 100644 index 0000000..d9613fe --- /dev/null +++ b/doc/api_reference.rst @@ -0,0 +1,89 @@ +API Reference +============= + +Complete API documentation for the ChimeraTK DeviceAccess Python bindings. + + +API Index +~~~~~~~~~ + +The entries below provide direct links to the most important module-level functions, classes, and enums. + +.. currentmodule:: deviceaccess + +.. autosummary:: + :toctree: generated + + setDMapFilePath + getDMapFilePath + Device + ScalarRegisterAccessor + OneDRegisterAccessor + TwoDRegisterAccessor + VoidRegisterAccessor + TransferGroup + ReadAnyGroup + DataConsistencyGroup + RegisterCatalogue + RegisterInfo + DataDescriptor + DataType + VersionNumber + AccessMode + DataValidity + + +Core Classes +~~~~~~~~~~~~ + +**Device** + Main class for opening and managing connections to devices. + +**ScalarRegisterAccessor** + Accessor for single-valued registers. + +**OneDRegisterAccessor** + Accessor for array-valued registers. + +**TwoDRegisterAccessor** + Accessor for 2D array registers. + + +Type Mapping +~~~~~~~~~~~~ + +Python types are automatically mapped to hardware types, Numpy types are also supported: + +* ``int`` ↔ int32 +* ``float`` ↔ float32 +* ``str`` ↔ String registers +* ``list`` / ``array`` / ``numpy.ndarray`` ↔ Array registers + + +Main Module: deviceaccess +-------------------------- + +.. automodule:: deviceaccess + :members: + :show-inheritance: + + +Legacy Module: mtca4u +--------------------- + +.. note:: + + The ``mtca4u`` module is a legacy interface. New code should use the ``deviceaccess`` module instead. + +.. automodule:: mtca4u + :members: + :undoc-members: + :show-inheritance: + +See Also +-------- + +* :doc:`user_guide` for usage patterns and best practices +* :doc:`examples` for practical code samples +* :doc:`getting_started` for installation and basic usage +* :doc:`faq` for common questions diff --git a/doc/conf.py.in b/doc/conf.py.in index 34fc08d..02c8565 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -22,8 +22,14 @@ import shlex sys.path.insert(0, os.path.abspath('${CMAKE_BINARY_DIR}')) # -# Remove dependency of doc generation dependency from compilation. -autodoc_mock_imports = ["_da_python_bindings"] +# Mocking of the core C-extension breaks introspection (no members to document). +# Only mock when the extension cannot be imported (e.g. on readthedocs), +# otherwise use the built binary from CMAKE_BINARY_DIR so autodoc can see members. +try: + import deviceaccess # noqa: F401 + autodoc_mock_imports = [] +except Exception: + autodoc_mock_imports = ["deviceaccess"] # -- General configuration ------------------------------------------------ @@ -35,11 +41,20 @@ autodoc_mock_imports = ["_da_python_bindings"] # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx_autodoc_typehints', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', + 'sphinx.ext.intersphinx', ] +# Intersphinx mapping to link to external documentation +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable/', None), +} + # Add any paths that contain templates here, relative to this directory. # templates_path = ['_templates'] @@ -112,12 +127,33 @@ pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True +# -- Napoleon extension configuration (parse docstrings) ------------------ +napoleon_google_docstring = True +napoleon_numpy_docstring = False +napoleon_include_init_doc = True +napoleon_include_private_with_doc = False +napoleon_attr_annotations = True +napoleon_preprocess_types = True + +# -- Autodoc configuration ------------------------------------------------- +autodoc_default_options = { + 'members': True, + 'member-order': 'bysource', + 'special-members': '__init__', + 'undoc-members': False, + 'show-inheritance': True, +} +autodoc_typehints = 'short' +autodoc_typehints_format = 'short' +autosummary_generate = True +autosummary_imported_members = False + # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -126,7 +162,11 @@ html_theme_options = { 'logo_name': 'true', 'code_font_size': '.688em', 'show_powered_by': 'false', - 'github_button': 'false'} + 'github_button': 'false', + 'collapse_navigation': False, + 'navigation_depth': 4, + 'sticky_navigation': True, + 'titles_only': False} # 'code_font_family': 'Deja Vu Sans Mono',} # Add any paths that contain custom themes here, relative to this directory. @@ -150,7 +190,8 @@ html_sidebars = { # The name of an image file (relative to this directory) to place at the top # of the sidebar. -html_logo = '${CMAKE_SOURCE_DIR}/doc/DESY_logo.png' +html_logo = '${CMAKE_SOURCE_DIR}/doc/ChimeraTK_Logo_whitebg.png' +html_favicon = '${CMAKE_SOURCE_DIR}/doc/ChimeraTK_Favicon.ico' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 diff --git a/doc/deviceaccess.rst b/doc/deviceaccess.rst index 217e064..a09a072 100644 --- a/doc/deviceaccess.rst +++ b/doc/deviceaccess.rst @@ -3,4 +3,5 @@ deviceaccess module .. automodule:: deviceaccess :members: - :show-inheritance: \ No newline at end of file + :undoc-members: + :show-inheritance: diff --git a/doc/examples.rst b/doc/examples.rst new file mode 100644 index 0000000..9e01eb9 --- /dev/null +++ b/doc/examples.rst @@ -0,0 +1,83 @@ +Examples +======== + +This page contains practical examples demonstrating common usage patterns with the DeviceAccess Python bindings. +The actual example code, and the `map` and `dmap` files used in this example can be found in the `tests` folder of the source distribution. The content is also listed in the :ref:`Used Map Files section ` section below. + +.. _basic_example_python: + +Basic Scalar Register Access +----------------------------- + +Reading and writing a single register value: + +.. literalinclude:: ../tests/testDocExamples.py + :pyobject: TestDocExamples.simpleScalarAccess + :lines: 2- + :dedent: 4 + + +.. _array_example_python: + +Working with 1D Accessors +------------------------- + +Reading and processing array data: + +.. literalinclude:: ../tests/testDocExamples.py + :pyobject: TestDocExamples.simpleOneDAccess + :lines: 2- + :dedent: 4 + + +.. _device_map_example_python: + +Using Different Device Backends +-------------------------------- + +The ChimeraTK library supports multiple backends. Configure them in your device map file: + +.. code-block:: text + + # Device map example + AMC_PCIe (xdma:xdma/slot6?map=device.map) + SCPI_Dev (CommandBasedTCP:lab_dev?map=hw_prep.json&port=50000) + OPCUADev (opcua:192.168.1.101?port=16664) + DummyDev (dummy?map=device.map) + +Then use them the same way in your Python code: + +.. code-block:: python + + import deviceaccess + + # Open different backend devices with same API + dummy = deviceaccess.Device("DummyDev") + pcie = deviceaccess.Device("AMC_PCIe") + scpi = deviceaccess.Device("SCPI_Dev") + opcua = deviceaccess.Device("OPCUADev") + + # All use the same accessor interface + for device in [dummy, pcie, scpi, opcua]: + value = device.getScalarRegisterAccessor(int, "MEASUREMENT") + value.read() + print(f"Value: {float(value)}") + +.. _used_map_files_section: + +Used Map Files +-------------- + +.. literalinclude:: ../tests/documentationExamples/someCrate.dmap + :caption: Example Crate dMap File + +.. literalinclude:: ../tests/documentationExamples/someDummyModule.map + :caption: Example Module Map File + + +See Also +-------- + +* :doc:`user_guide` for in-depth explanations +* :doc:`api_reference` for complete API documentation +* :doc:`faq` for common questions diff --git a/doc/faq.rst b/doc/faq.rst new file mode 100644 index 0000000..4740222 --- /dev/null +++ b/doc/faq.rst @@ -0,0 +1,266 @@ +Frequently Asked Questions +========================== + + +Installation and Setup +---------------------- + +**Q: How do I install the Python bindings?** + +A: See the :doc:`getting_started` guide for detailed installation instructions. + Quick version: install the distribution packages as described there, or build from source with CMake. + + +**Q: What Python versions are supported?** + +A: Python 3.12 and higher are supported. Check the project's CI/CD configuration + for the currently tested versions. + + +**Q: I'm getting ImportError when trying to import deviceaccess** + +A: This usually means the package isn't installed or not in your Python path. + Try: ``python -c "import deviceaccess; print(deviceaccess.__file__)"`` + + If that fails, reinstall the package. See :doc:`troubleshooting` for more help. + + +Basic Usage +----------- + +**Q: How do I read a value from a register?** + +A: Create an accessor and call ``read()``: + + .. code-block:: python + + accessor = device.getScalarRegisterAccessor(float, "REGISTER_NAME") + accessor.read() + value = float(accessor) + + +**Q: When do I need to call read() or write()?** + +A: - Call ``read()`` to transfer data **from hardware to the local buffer** + - Call ``write()`` to transfer data **from the local buffer to hardware** + - Between calls, you're working with the local buffer (no hardware communication) + + .. code-block:: python + + # Read from hardware + accessor.read() + + # Work with local copy (fast, no hardware access) + my_value = float(accessor) + + # Modify local copy + accessor.set(my_value * 2) # Or use accessor.write() + + # Write back to hardware + accessor.write() + + +**Q: What's the difference between read/write and the accessor value?** + +A: - ``read()`` / ``write()`` - Transfer data with hardware + - Accessing the accessor value - Work with local buffer only + + .. code-block:: python + + # These don't talk to hardware: + accessor.set(42.0) # Modifies local buffer + value = float(accessor) # Reads local buffer + + # This transfers data: + accessor.read() # Get latest from hardware + accessor.write() # Send to hardware + + +**Q: Can I modify an accessor and then write it?** + +A: Yes, but methods vary by accessor type: + + .. code-block:: python + + # Scalar + scalar = device.getScalarRegisterAccessor(float, "VALUE") + scalar.set(42.0) + scalar.write() + + # Array + array = device.getOneDRegisterAccessor(float, "DATA") + # Can be treated like a python list with numpy methods, incl. slices + array[0] = 1.5 + array[1] = 2.5 + array.write() + + +Accessors and Type Conversion +------------------------------ + +**Q: How does the library handle type conversion?** + +A: Automatic conversion happens when you request the register: + + .. code-block:: python + + aFloatValue = dev.getScalarRegisterAccessor(float, "SENSORS.TEMPERATURE") + anIntList = device.getOneDRegisterAccessor(int, "SENSORS.WAVEFORM") + + +Device Maps +----------- + +**Q: What is a device map file?** + +A: A device map (`.dmap`) file describes the devices your application can access. + It maps logical device names to hardware locations and backend specifications. + + See :ref:`Device Maps ` in the user guide for details. + + +**Q: How do I debug device map issues?** + +A: - Check that the file exists and is readable + - Verify the syntax is correct + - Try opening the device with QtHardMon or Chai. + + +Transfer Groups +--------------- + +**Q: When should I use transfer groups?** + +A: Use transfer groups when you need to: + + - Read multiple registers with guaranteed consistency + - Write multiple registers atomically + - Reduce hardware communication overhead + + + +**Q: What's the difference between transfer groups and data consistency groups?** + +A: - **Transfer Group**: Synchronizes multiple registers in a single read/write + - **Data Consistency Group**: Provides consistency via VersionNumbers + + +**Q: Do transfer groups improve performance?** + +A: Yes, if the backend supports it. + + +Performance and Optimization +----------------------------- + +**Q: How can I improve performance for many reads?** + +A: - Reuse accessors instead of creating new ones each time + - Minimize how often you call read/write + + See :doc:`user_guide` for optimization strategies. + + +**Q: Is there an asynchronous API?** + +A: The synchronous API is the standard. For asynchronous access you need to set up the device: + + .. code-block:: python + + dev.activateAsyncRead() + + +Compatibility and Versions +--------------------------- + +**Q: Is this compatible with my existing C++ code?** + +A: Yes! The Python bindings wrap the C++ library, providing the same functionality + through a Pythonic interface. The underlying hardware access is identical. + + +**Q: What backend devices are supported?** + +A: Supported backends depend on your ChimeraTK installation. The Python bindings offer the same support as the C++ version of DeviceAccess. + + +**Q: Can I use old code written for the mtca4u module?** + +A: Yes, the ``mtca4u`` module is still supported for compatibility. + However, new code should use the ``deviceaccess`` module instead. + + +Data Handling +------------- + +**Q: How do I work with array data?** + +A: Use ``ArrayRegisterAccessor``: + + .. code-block:: python + + array = device.getOneDRegisterAccessor(int, "DATA") + array.read() + + # Access elements + first_val = array[0] + last_val = array[-1] + + # Iterate + for val in array[:]: + print(val) + + # Convert to list or numpy array + data = list(array[:]) + import numpy as np + data = np.array(array[:]) + + +**Q: Can I modify array data and write it back?** + +A: Yes: + + .. code-block:: python + + array = device.getOneDRegisterAccessor(float, "DATA") + array.read() + + # Modify + for i in range(len(array)): + array[i] = array[i] * 2 + + # Write back + array.write() + + +**Q: Can I use NumPy with the bindings?** + +A: Yes! The bindings were written with NumPy as a use-case. + + .. code-block:: python + + import numpy as np + + array = device.getOneDRegisterAccessor(int, "DATA") + array.read() + + # Convert to NumPy + data = np.array(array[:]) + + # Analyze and modify + processed = np.fft.fft(data) + + # Write back + for i, val in enumerate(processed): + array[i] = val + array.write() + + +Still Have Questions? +--------------------- + +- Check the :doc:`examples` for practical code samples +- Review the :doc:`user_guide` for in-depth explanations +- See :doc:`troubleshooting` for problem-solving +- Check the API reference for class and method documentation +- Look at the `ChimeraTK documentation `_ diff --git a/doc/generated/deviceaccess.AccessMode.rst b/doc/generated/deviceaccess.AccessMode.rst new file mode 100644 index 0000000..93c4642 --- /dev/null +++ b/doc/generated/deviceaccess.AccessMode.rst @@ -0,0 +1,31 @@ +deviceaccess.AccessMode +======================= + +.. currentmodule:: deviceaccess + +.. autoclass:: AccessMode + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~AccessMode.__init__ + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~AccessMode.name + ~AccessMode.raw + ~AccessMode.value + ~AccessMode.wait_for_new_data + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.DataConsistencyGroup.rst b/doc/generated/deviceaccess.DataConsistencyGroup.rst new file mode 100644 index 0000000..db645ec --- /dev/null +++ b/doc/generated/deviceaccess.DataConsistencyGroup.rst @@ -0,0 +1,26 @@ +deviceaccess.DataConsistencyGroup +================================= + +.. currentmodule:: deviceaccess + +.. autoclass:: DataConsistencyGroup + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~DataConsistencyGroup.__init__ + ~DataConsistencyGroup.add + ~DataConsistencyGroup.getMatchingMode + ~DataConsistencyGroup.isConsistent + ~DataConsistencyGroup.update + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.DataDescriptor.rst b/doc/generated/deviceaccess.DataDescriptor.rst new file mode 100644 index 0000000..6e3fa24 --- /dev/null +++ b/doc/generated/deviceaccess.DataDescriptor.rst @@ -0,0 +1,31 @@ +deviceaccess.DataDescriptor +=========================== + +.. currentmodule:: deviceaccess + +.. autoclass:: DataDescriptor + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~DataDescriptor.__init__ + ~DataDescriptor.fundamentalType + ~DataDescriptor.isIntegral + ~DataDescriptor.isSigned + ~DataDescriptor.minimumDataType + ~DataDescriptor.nDigits + ~DataDescriptor.nFractionalDigits + ~DataDescriptor.rawDataType + ~DataDescriptor.setRawDataType + ~DataDescriptor.transportLayerDataType + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.DataType.rst b/doc/generated/deviceaccess.DataType.rst new file mode 100644 index 0000000..9765dd2 --- /dev/null +++ b/doc/generated/deviceaccess.DataType.rst @@ -0,0 +1,45 @@ +deviceaccess.DataType +===================== + +.. currentmodule:: deviceaccess + +.. autoclass:: DataType + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~DataType.__init__ + ~DataType.getAsString + ~DataType.isIntegral + ~DataType.isNumeric + ~DataType.isSigned + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~DataType.Boolean + ~DataType.Void + ~DataType.float32 + ~DataType.float64 + ~DataType.int16 + ~DataType.int32 + ~DataType.int64 + ~DataType.int8 + ~DataType.none + ~DataType.string + ~DataType.uint16 + ~DataType.uint32 + ~DataType.uint64 + ~DataType.uint8 + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.DataValidity.rst b/doc/generated/deviceaccess.DataValidity.rst new file mode 100644 index 0000000..af05699 --- /dev/null +++ b/doc/generated/deviceaccess.DataValidity.rst @@ -0,0 +1,31 @@ +deviceaccess.DataValidity +========================= + +.. currentmodule:: deviceaccess + +.. autoclass:: DataValidity + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~DataValidity.__init__ + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~DataValidity.faulty + ~DataValidity.name + ~DataValidity.ok + ~DataValidity.value + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.Device.rst b/doc/generated/deviceaccess.Device.rst new file mode 100644 index 0000000..5c62378 --- /dev/null +++ b/doc/generated/deviceaccess.Device.rst @@ -0,0 +1,36 @@ +deviceaccess.Device +=================== + +.. currentmodule:: deviceaccess + +.. autoclass:: Device + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~Device.__init__ + ~Device.activateAsyncRead + ~Device.close + ~Device.getCatalogueMetadata + ~Device.getOneDRegisterAccessor + ~Device.getRegisterCatalogue + ~Device.getScalarRegisterAccessor + ~Device.getTwoDRegisterAccessor + ~Device.getVoidRegisterAccessor + ~Device.isFunctional + ~Device.isOpened + ~Device.open + ~Device.read + ~Device.setException + ~Device.write + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.OneDRegisterAccessor.rst b/doc/generated/deviceaccess.OneDRegisterAccessor.rst new file mode 100644 index 0000000..d705d75 --- /dev/null +++ b/doc/generated/deviceaccess.OneDRegisterAccessor.rst @@ -0,0 +1,48 @@ +deviceaccess.OneDRegisterAccessor +================================= + +.. currentmodule:: deviceaccess + +.. autoclass:: OneDRegisterAccessor + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~OneDRegisterAccessor.__init__ + ~OneDRegisterAccessor.dataValidity + ~OneDRegisterAccessor.get + ~OneDRegisterAccessor.getAccessModeFlags + ~OneDRegisterAccessor.getAsCooked + ~OneDRegisterAccessor.getDescription + ~OneDRegisterAccessor.getId + ~OneDRegisterAccessor.getNElements + ~OneDRegisterAccessor.getName + ~OneDRegisterAccessor.getUnit + ~OneDRegisterAccessor.getValueType + ~OneDRegisterAccessor.getVersionNumber + ~OneDRegisterAccessor.interrupt + ~OneDRegisterAccessor.isInitialised + ~OneDRegisterAccessor.isReadOnly + ~OneDRegisterAccessor.isReadable + ~OneDRegisterAccessor.isWriteable + ~OneDRegisterAccessor.read + ~OneDRegisterAccessor.readAndGet + ~OneDRegisterAccessor.readLatest + ~OneDRegisterAccessor.readNonBlocking + ~OneDRegisterAccessor.set + ~OneDRegisterAccessor.setAndWrite + ~OneDRegisterAccessor.setAsCooked + ~OneDRegisterAccessor.setDataValidity + ~OneDRegisterAccessor.write + ~OneDRegisterAccessor.writeDestructively + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.ReadAnyGroup.rst b/doc/generated/deviceaccess.ReadAnyGroup.rst new file mode 100644 index 0000000..12cf1cd --- /dev/null +++ b/doc/generated/deviceaccess.ReadAnyGroup.rst @@ -0,0 +1,32 @@ +deviceaccess.ReadAnyGroup +========================= + +.. currentmodule:: deviceaccess + +.. autoclass:: ReadAnyGroup + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~ReadAnyGroup.__init__ + ~ReadAnyGroup.add + ~ReadAnyGroup.finalise + ~ReadAnyGroup.interrupt + ~ReadAnyGroup.processPolled + ~ReadAnyGroup.readAny + ~ReadAnyGroup.readAnyNonBlocking + ~ReadAnyGroup.readUntil + ~ReadAnyGroup.readUntilAll + ~ReadAnyGroup.waitAny + ~ReadAnyGroup.waitAnyNonBlocking + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.RegisterCatalogue.rst b/doc/generated/deviceaccess.RegisterCatalogue.rst new file mode 100644 index 0000000..97b2c3b --- /dev/null +++ b/doc/generated/deviceaccess.RegisterCatalogue.rst @@ -0,0 +1,26 @@ +deviceaccess.RegisterCatalogue +============================== + +.. currentmodule:: deviceaccess + +.. autoclass:: RegisterCatalogue + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~RegisterCatalogue.__init__ + ~RegisterCatalogue.getNumberOfRegisters + ~RegisterCatalogue.getRegister + ~RegisterCatalogue.hasRegister + ~RegisterCatalogue.hiddenRegisters + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.RegisterInfo.rst b/doc/generated/deviceaccess.RegisterInfo.rst new file mode 100644 index 0000000..1a3410e --- /dev/null +++ b/doc/generated/deviceaccess.RegisterInfo.rst @@ -0,0 +1,33 @@ +deviceaccess.RegisterInfo +========================= + +.. currentmodule:: deviceaccess + +.. autoclass:: RegisterInfo + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~RegisterInfo.__init__ + ~RegisterInfo.getDataDescriptor + ~RegisterInfo.getNumberOfChannels + ~RegisterInfo.getNumberOfDimensions + ~RegisterInfo.getNumberOfElements + ~RegisterInfo.getQualifiedAsyncId + ~RegisterInfo.getRegisterName + ~RegisterInfo.getSupportedAccessModes + ~RegisterInfo.getTags + ~RegisterInfo.isReadable + ~RegisterInfo.isValid + ~RegisterInfo.isWriteable + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.ScalarRegisterAccessor.rst b/doc/generated/deviceaccess.ScalarRegisterAccessor.rst new file mode 100644 index 0000000..c61151d --- /dev/null +++ b/doc/generated/deviceaccess.ScalarRegisterAccessor.rst @@ -0,0 +1,54 @@ +deviceaccess.ScalarRegisterAccessor +=================================== + +.. currentmodule:: deviceaccess + +.. autoclass:: ScalarRegisterAccessor + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~ScalarRegisterAccessor.__init__ + ~ScalarRegisterAccessor.dataValidity + ~ScalarRegisterAccessor.get + ~ScalarRegisterAccessor.getAccessModeFlags + ~ScalarRegisterAccessor.getAsCooked + ~ScalarRegisterAccessor.getDescription + ~ScalarRegisterAccessor.getId + ~ScalarRegisterAccessor.getName + ~ScalarRegisterAccessor.getUnit + ~ScalarRegisterAccessor.getValueType + ~ScalarRegisterAccessor.getVersionNumber + ~ScalarRegisterAccessor.interrupt + ~ScalarRegisterAccessor.isInitialised + ~ScalarRegisterAccessor.isReadOnly + ~ScalarRegisterAccessor.isReadable + ~ScalarRegisterAccessor.isWriteable + ~ScalarRegisterAccessor.read + ~ScalarRegisterAccessor.readAndGet + ~ScalarRegisterAccessor.readLatest + ~ScalarRegisterAccessor.readNonBlocking + ~ScalarRegisterAccessor.set + ~ScalarRegisterAccessor.setAndWrite + ~ScalarRegisterAccessor.setAsCooked + ~ScalarRegisterAccessor.setDataValidity + ~ScalarRegisterAccessor.write + ~ScalarRegisterAccessor.writeDestructively + ~ScalarRegisterAccessor.writeIfDifferent + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~ScalarRegisterAccessor.dtype + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.TransferGroup.rst b/doc/generated/deviceaccess.TransferGroup.rst new file mode 100644 index 0000000..32d42e4 --- /dev/null +++ b/doc/generated/deviceaccess.TransferGroup.rst @@ -0,0 +1,28 @@ +deviceaccess.TransferGroup +========================== + +.. currentmodule:: deviceaccess + +.. autoclass:: TransferGroup + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TransferGroup.__init__ + ~TransferGroup.addAccessor + ~TransferGroup.isReadOnly + ~TransferGroup.isReadable + ~TransferGroup.isWriteable + ~TransferGroup.read + ~TransferGroup.write + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.TwoDRegisterAccessor.rst b/doc/generated/deviceaccess.TwoDRegisterAccessor.rst new file mode 100644 index 0000000..8f66146 --- /dev/null +++ b/doc/generated/deviceaccess.TwoDRegisterAccessor.rst @@ -0,0 +1,47 @@ +deviceaccess.TwoDRegisterAccessor +================================= + +.. currentmodule:: deviceaccess + +.. autoclass:: TwoDRegisterAccessor + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TwoDRegisterAccessor.__init__ + ~TwoDRegisterAccessor.dataValidity + ~TwoDRegisterAccessor.get + ~TwoDRegisterAccessor.getAccessModeFlags + ~TwoDRegisterAccessor.getAsCooked + ~TwoDRegisterAccessor.getDescription + ~TwoDRegisterAccessor.getId + ~TwoDRegisterAccessor.getNChannels + ~TwoDRegisterAccessor.getNElementsPerChannel + ~TwoDRegisterAccessor.getName + ~TwoDRegisterAccessor.getUnit + ~TwoDRegisterAccessor.getValueType + ~TwoDRegisterAccessor.getVersionNumber + ~TwoDRegisterAccessor.interrupt + ~TwoDRegisterAccessor.isInitialised + ~TwoDRegisterAccessor.isReadOnly + ~TwoDRegisterAccessor.isReadable + ~TwoDRegisterAccessor.isWriteable + ~TwoDRegisterAccessor.read + ~TwoDRegisterAccessor.readLatest + ~TwoDRegisterAccessor.readNonBlocking + ~TwoDRegisterAccessor.set + ~TwoDRegisterAccessor.setAsCooked + ~TwoDRegisterAccessor.setDataValidity + ~TwoDRegisterAccessor.write + ~TwoDRegisterAccessor.writeDestructively + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.VersionNumber.rst b/doc/generated/deviceaccess.VersionNumber.rst new file mode 100644 index 0000000..a719dd0 --- /dev/null +++ b/doc/generated/deviceaccess.VersionNumber.rst @@ -0,0 +1,25 @@ +deviceaccess.VersionNumber +========================== + +.. currentmodule:: deviceaccess + +.. autoclass:: VersionNumber + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~VersionNumber.__init__ + ~VersionNumber.getNullVersion + ~VersionNumber.getTime + ~VersionNumber.getVersionNumberAsString + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.VoidRegisterAccessor.rst b/doc/generated/deviceaccess.VoidRegisterAccessor.rst new file mode 100644 index 0000000..d85a8c7 --- /dev/null +++ b/doc/generated/deviceaccess.VoidRegisterAccessor.rst @@ -0,0 +1,41 @@ +deviceaccess.VoidRegisterAccessor +================================= + +.. currentmodule:: deviceaccess + +.. autoclass:: VoidRegisterAccessor + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~VoidRegisterAccessor.__init__ + ~VoidRegisterAccessor.dataValidity + ~VoidRegisterAccessor.getAccessModeFlags + ~VoidRegisterAccessor.getDescription + ~VoidRegisterAccessor.getId + ~VoidRegisterAccessor.getName + ~VoidRegisterAccessor.getUnit + ~VoidRegisterAccessor.getValueType + ~VoidRegisterAccessor.getVersionNumber + ~VoidRegisterAccessor.interrupt + ~VoidRegisterAccessor.isInitialised + ~VoidRegisterAccessor.isReadOnly + ~VoidRegisterAccessor.isReadable + ~VoidRegisterAccessor.isWriteable + ~VoidRegisterAccessor.read + ~VoidRegisterAccessor.readLatest + ~VoidRegisterAccessor.readNonBlocking + ~VoidRegisterAccessor.setDataValidity + ~VoidRegisterAccessor.write + ~VoidRegisterAccessor.writeDestructively + + + + + + \ No newline at end of file diff --git a/doc/generated/deviceaccess.getDMapFilePath.rst b/doc/generated/deviceaccess.getDMapFilePath.rst new file mode 100644 index 0000000..01edc3c --- /dev/null +++ b/doc/generated/deviceaccess.getDMapFilePath.rst @@ -0,0 +1,6 @@ +deviceaccess.getDMapFilePath +============================ + +.. currentmodule:: deviceaccess + +.. autofunction:: getDMapFilePath \ No newline at end of file diff --git a/doc/generated/deviceaccess.setDMapFilePath.rst b/doc/generated/deviceaccess.setDMapFilePath.rst new file mode 100644 index 0000000..ff93627 --- /dev/null +++ b/doc/generated/deviceaccess.setDMapFilePath.rst @@ -0,0 +1,6 @@ +deviceaccess.setDMapFilePath +============================ + +.. currentmodule:: deviceaccess + +.. autofunction:: setDMapFilePath \ No newline at end of file diff --git a/doc/getting_started.rst b/doc/getting_started.rst new file mode 100644 index 0000000..2591a5b --- /dev/null +++ b/doc/getting_started.rst @@ -0,0 +1,105 @@ +Getting Started +=============== + +Installation +------------ + +Prerequisites +~~~~~~~~~~~~~ + +* Python 3.12 or higher, might work with older versions but not tested +* CMake 3.16 or higher (for building from source) +* ChimeraTK DeviceAccess library installed + +Repository-Based Installation on Debian/Ubuntu systems +~~~~~~~~~~~~~~~~~~~~~ + +If you haven't already, add the public DOOCS Package Repository to your system, receive the DESY DOOCS key and add the DOOCS repository. + +.. code-block:: bash + + wget -O - https://doocs-web.desy.de/pub/doocs/DOOCS-key.gpg.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/doocs-keyring.gpg + sudo sh -c 'echo "deb https://doocs-web.desy.de/pub/doocs $(lsb_release -cs) main" > /etc/apt/sources.list.d/doocs.list' + +Installation of the actual Python bindings package can then be done via apt, the package is named ``python3-mtca4upy``: + +.. code-block:: bash + + sudo apt update && sudo apt-get install python3-mtca4upy + +From Source +~~~~~~~~~~~ + +.. code-block:: bash + + # Clone the repository + git clone https://github.com/ChimeraTK/ChimeraTK-DeviceAccess-PythonBindings.git + cd ChimeraTK-DeviceAccess-PythonBindings + + # Build and install + mkdir build && cd build + cmake .. + make + sudo make install + + +Your First Program +------------------ + +Let's create a simple program to read a value from a device. + +Prerequisites +~~~~~~~~~~~~~ +You will need a device with a map file, that can be referenced in a dmap with the respective backend. For testing purposes, you can use the dummy backend with a dummy device map entry like this: + +.. literalinclude:: ../tests/documentationExamples/someCrate.dmap + :language: text + + + +.. note:: When testing application code, it is often beneficial not to rely on real hardware. + ChimeraTK-DeviceAccess provides two backends for this purpose, the ChimeraTK::DummyBackend and the ChimeraTK::SharedDummyBackend. + The DummyBackend emulates a devices' register space in application memory. + The SharedDummyBackend allocates the registers in shared memory, so it can be access from multiple processes. + E.g., QtHardMon or Chai can be used to stimulate and monitor a running application. + Hence, these backends provide a generic way to test input-/output- operations on the application. + +The following snippet gives a map file with a 32-bit scalar register and an 8-bit array register, that can be used with the dummy backends: + +.. literalinclude:: ../tests/documentationExamples/someDummyModule.map + :language: text + + +Basic Example +~~~~~~~~~~~~~ + +.. literalinclude:: ../tests/testDocExamples.py + :pyobject: TestDocExamples.simpleScalarAccess + :lines: 2- + :dedent: 8 + + +Step-by-Step Explanation +~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. **Import**: The ``deviceaccess`` module contains all necessary classes and methods +2. **Initial Setup**: Set up dmap file name according to your configuration +3. **Device Creation**: ``Device()`` opens a connection to the hardware with the handle defined in the dmap file +4. **Get Accessor**: Accessors are type-safe handles to registers, regardless of the underlying hardware +5. **Read/Write**: ``read()`` and ``write()`` transfer data to/from hardware +6. **Data Access**: Accessors behave like the data they represent and can be used like Python types (e.g., float, int, list, even numpy arrays) after reading + +.. note:: + + By default, all read and write operations are **synchronous** - they block until the operation completes. + Check the :doc:`user_guide` for asynchronous patterns and advanced usage. + +Next Steps +---------- + +Now that you have the basics: + +* See :doc:`examples` for more real-world patterns +* Read the :doc:`user_guide` for deeper concepts +* Check the :doc:`api_reference` for complete API details +* Browse :doc:`faq` for common questions diff --git a/doc/index.rst b/doc/index.rst index cbfa82c..2ad5364 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -1,25 +1,95 @@ -.. pyBindingsTrunk documentation master file, created by - sphinx-quickstart on Tue May 26 14:00:37 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. +ChimeraTK DeviceAccess Python Bindings +====================================== -mtca4u Python Bindings Doucmentation -==================================== +.. toctree:: + :hidden: + :maxdepth: 3 -Contents: + overview + getting_started + user_guide + examples + api_reference + faq + troubleshooting -.. toctree:: - :maxdepth: 4 - mtca4u - deviceaccess - matlab - +Overview +-------- + +ChimeraTK DeviceAccess Python Bindings provide Pythonic access to the `ChimeraTK DeviceAccess library`_, +a C++ device access library for register-based devices. + +.. _ChimeraTK DeviceAccess library: https://chimeratk.github.io/ChimeraTK-DeviceAccess/tag/html/index.html + +The bindings enable Python developers to: + +* Access hardware registers by name through an intuitive accessor interface +* Read and write device data with automatic type conversion +* Work with scalar, array, and structured data types +* Utilize transfer groups for synchronized access to multiple registers +* Leverage data consistency groups for coherent data reading + +All read and write operations are synchronous and blocking until the data transfer is complete, asynchronous operations are available for push-based data updates. + + +Quick Start +----------- + +To get started with the Python bindings, see the :doc:`getting_started` guide for: + +* Installation instructions +* Your first device access example +* Basic accessor usage patterns + + +Tutorials and Examples +---------------------- + +Learn by example with our comprehensive tutorials: + +* :ref:`basic_example_python` - Access a single register value +* :ref:`array_example_python` - Work with array registers +* :ref:`device_map_example_python` - Using device map files +* :ref:`transfer_groups_python` - Synchronized multi-register access +* :ref:`data_consistency_python` - Reading coherent data + + +User Guide +---------- + +For more detailed information, refer to the :doc:`user_guide` which covers: + +* Understanding accessors +* Data type conversion +* Error handling and exceptions +* Best practices +* Advanced features + + +API Reference +------------- + +Complete API documentation is available in the :doc:`api_reference` section. + +The main module is :doc:`deviceaccess` which provides: + +* Device class for opening and managing connections +* Various accessor types for different data structures +* Register information retrieval +* Transfer group and data consistency group management + + +Questions and Troubleshooting +----------------------------- + +* See :doc:`faq` for common questions and answers +* Check :doc:`troubleshooting` for solutions to common issues -Indices and tables -================== -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +Indices and Tables +------------------ +* :ref:`genindex` - Index of all classes and functions +* :ref:`modindex` - All modules and submodules +* :ref:`search` - Search this documentation diff --git a/doc/mtca4u.rst b/doc/mtca4u.rst index e7eac17..82c6882 100644 --- a/doc/mtca4u.rst +++ b/doc/mtca4u.rst @@ -1,5 +1,5 @@ -mtca4u module -============= +mtca4u module (legacy) +====================== .. automodule:: mtca4u :members: diff --git a/doc/overview.rst b/doc/overview.rst new file mode 100644 index 0000000..1ceadec --- /dev/null +++ b/doc/overview.rst @@ -0,0 +1,58 @@ +Overview +======== + +What is ChimeraTK DeviceAccess? +------------------------------- + +ChimeraTK DeviceAccess is a library designed for reading and writing data from register-based devices. +It provides a unified interface that abstracts the underlying hardware communication protocols, +allowing you to focus on your application logic rather than low-level hardware details. + +The Python bindings bring this powerful library to Python developers, enabling seamless integration +with Python-based control systems, data acquisition applications, and scientific computing workflows. + +The library is actively maintained and widely used in accelerator control systems and scientific instruments by DESY's accelerator division'ss beam controls group. It is designed to be robust, efficient, and easy to use in a variety of applications. + + +Key Features +~~~~~~~~~~~~ + +* **Named Register Access**: Access registers by meaningful names rather than numerical addresses +* **Automatic Type Conversion**: Seamless conversion between hardware data types and Python types +* **Flexible Accessor Types**: Support for scalar values, 1D and 2D arrays. +* **Synchronized Access**: Transfer groups for atomic read/write operations across multiple registers +* **Data Consistency**: Data consistency groups ensure coherent snapshots of multiple registers +* **Multiple Backends**: Support for various communication protocols through production ready backends + + +Common Use Cases +---------------- + +* **Experimental Control Systems**: Control particle accelerators, beamlines, and experiments +* **Sensor Data Acquisition**: Read sensor values and log measurement data +* **Hardware Testing**: Automated testing of hardware devices and components +* **Scientific Instrumentation**: Integration with measurement and analysis frameworks +* **Real-time Data Processing**: Access hardware data for real-time processing pipelines + + +Why Python Bindings? +-------------------- + +Python's rich ecosystem and ease of use make it ideal for: + +* Rapid prototyping and experimentation +* Integration with scientific computing stacks (NumPy, SciPy, Matplotlib) +* Building automation and monitoring scripts +* Machine learning and data analysis workflows + +The C++ backend ensures high performance while Python's flexibility enables rapid development. + + +Getting Help +~~~~~~~~~~~~ + +* Check the :doc:`getting_started` guide for basic usage +* Browse the :doc:`examples` for common patterns +* See :doc:`faq` for frequently asked questions +* Consult :doc:`troubleshooting` for common issues +* Review the :doc:`api_reference` for detailed API documentation diff --git a/doc/troubleshooting.rst b/doc/troubleshooting.rst new file mode 100644 index 0000000..09fd7d0 --- /dev/null +++ b/doc/troubleshooting.rst @@ -0,0 +1,75 @@ +Troubleshooting +=============== + +This guide helps you diagnose and solve common issues with the DeviceAccess Python bindings. + +Performance Issues +------------------ + +Read/Write Operations Slow +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Diagnosis:** + +1. **Are you creating accessors repeatedly?** + + .. code-block:: python + + # Bad: Create accessor in loop + for i in range(1000): + register = device.getScalarRegisterAccessor(int, "VALUE") # Slow! + register.read() + + # Good: Reuse accessor + register = device.getScalarRegisterAccessor(int,"VALUE") + for i in range(1000): + register.read() + +2. **Are you making unnecessary hardware calls?** + + .. code-block:: python + + # Bad: Multiple reads for same data + register.read() + for i in range(100): + register.read() # Unnecessary! + + # Good: Read once + register.read() + value = float(register) + for i in range(100): + # Use value, don't read again + + +**Solutions:** + +- Reuse accessors +- Minimize hardware access operations +- Cache data between reads if appropriate + + +Getting Help +------------ + +When reporting issues, include: + +1. Python version: ``python --version`` +2. Library version: ``apt show python3-mtca4upy`` +3. OS and platform: ``uname -a`` +4. Complete error message and traceback +5. Device map file (sanitized) +6. Minimal reproducible example + +**Report to:** + +- Inside DESY: Redmine +- GitHub Issues: https://github.com/ChimeraTK/ChimeraTK-DeviceAccess-PythonBindings/issues + + +See Also +-------- + +- :doc:`faq` for common questions +- :doc:`getting_started` for setup help +- :doc:`user_guide` for usage guidance +- API reference for documentation diff --git a/doc/user_guide.rst b/doc/user_guide.rst new file mode 100644 index 0000000..cb58ea5 --- /dev/null +++ b/doc/user_guide.rst @@ -0,0 +1,129 @@ +User Guide +========== + +This guide covers important concepts and best practices for using the ChimeraTK DeviceAccess Python bindings. + + +Understanding Accessors +------------------------ + +Accessors are the primary interface for reading and writing device registers. They encapsulate: + +* A reference to a specific register +* A local buffer holding the current value +* Type information and conversion logic +* Synchronization with hardware + + +Accessor Lifecycle +~~~~~~~~~~~~~~~~~~~ + +1. **Obtain**: Get an accessor from a device for a specific register +2. **Transfer**: Use ``read()`` to get data from hardware or ``write()`` to send data +3. **Access**: Read/write the local buffer without hardware communication +4. **Repeat**: Transfer more data as needed + +.. code-block:: python + + # TODO + + +Accessor Types +~~~~~~~~~~~~~~ + +**ScalarRegisterAccessor**: Single values + +.. code-block:: python + + # TODO + + +**ArrayRegisterAccessor**: List of values + +.. code-block:: python + + # TODO + + +**TwoDRegisterAccessor**: Two-dimensional arrays + +.. code-block:: python + + # TODO + +Type Conversion +--------------- + +Automatic Type Conversion +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The bindings automatically convert between hardware and Python types as set on accessor creation: + +.. code-block:: python + + # TODO + +Transfer Groups +--------------- + +Transfer groups enable atomic operations on multiple registers: + +Motivation +~~~~~~~~~~ + +Without transfer groups, reading multiple registers could result in inconsistent data +if a register changes between reads. + +.. code-block:: python + + # TODO Example of changes between reads + + +Solution: Transfer Groups +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # TODO + + +Data Consistency Groups +----------------------- + +For advanced scenarios with multiple samples or high-frequency updates: + +.. code-block:: python + + # TODO + +Device Maps +----------- + +Device maps define your hardware configuration. + +Basic Syntax +~~~~~~~~~~~~ + +.. code-block:: text + + # Device map format + # LABEL BACKEND_SPECIFICATION + + # TODO: Give examples + + +Best Practices +~~~~~~~~~~~~~~ + +* Use meaningful device labels +* Organize by system or subsystem +* Version control your device maps + + +See Also +-------- + +* :doc:`examples` for practical patterns +* :doc:`api_reference` for complete API +* :doc:`faq` for common questions +* :doc:`troubleshooting` for problem solving diff --git a/pyproject.toml b/pyproject.toml index 6dffe81..c356b7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,9 @@ [tool.autopep8] -max_line_length = 120 \ No newline at end of file +max_line_length = 120 + +[project.optional-dependencies] +docs = [ + "sphinx >= 6.0", + "sphinx-autodoc-typehints >= 1.22", + "sphinx-rtd-theme >= 3.0", +] diff --git a/src/PyDataConsistencyGroup.cc b/src/PyDataConsistencyGroup.cc index e042398..8850095 100644 --- a/src/PyDataConsistencyGroup.cc +++ b/src/PyDataConsistencyGroup.cc @@ -22,19 +22,45 @@ namespace ChimeraTK { void PyDataConsistencyGroup::bind(py::module& m) { py::class_(m, "DataConsistencyGroup") - .def(py::init()) + .def(py::init(), py::arg("matchingMode"), + R"(Create a data consistency group. + + Args: + matchingMode (MatchingMode): Matching strategy used to determine whether group members are consistent.)") .def( "add", [](ctk::DataConsistencyGroup& self, PyTransferElementBase& element) { self.add(element.getTE()); }, - R"(Add register to group. - The same TransferElement can be part of multiple DataConsistencyGroups. The register must be must be - readable, and it must have AccessMode::wait_for_new_data.)") - .def("update", &ctk::DataConsistencyGroup::update, - R"(This function must be called after an update was received from the ReadAnyGroup. - It returns true, if a consistent state is reached. It returns false if an TransferElementID was updated, that was not added to this group. For MatchingMode::historized, readAny will only let through consistent updates, so then update always returns true.)") + py::arg("element"), + R"(Add a TransferElement to the group. + + The same TransferElement can be part of multiple DataConsistencyGroup instances. + + Args: + element (TransferElementBase): Element to add. It must be readable and use AccessMode.wait_for_new_data.)") + .def("update", &ctk::DataConsistencyGroup::update, py::arg("updatedId"), + R"(Process an update notification for one TransferElement. + + Call this after an update was received from ReadAnyGroup. + + Args: + updatedId (TransferElementID): ID of the element that received an update. + + Returns: + bool: True if the group is in a consistent state after processing the update. False if the updated ID + was not added to this group. + + Note: + For MatchingMode.historized, ReadAnyGroup only forwards consistent updates, so this function normally + returns true.)") .def("getMatchingMode", &ctk::DataConsistencyGroup::getMatchingMode, - R"(Get the current MatchingMode of this DataConsistencyGroup.)") + R"(Get the current MatchingMode of this DataConsistencyGroup. + + Returns: + MatchingMode: The matching mode used by this group.)") .def("isConsistent", &ctk::DataConsistencyGroup::isConsistent, - R"(Returns true if a consistent state is reached )"); + R"(Check whether the group is currently in a consistent state. + + Returns: + bool: `True` if a consistent state is reached, `False` otherwise.)"); } /*******************************************************************************************************************/ @@ -43,11 +69,11 @@ namespace ChimeraTK { py::enum_( m, "MatchingMode", "Enum describing the matching mode of a DataConsistencyGroup.") .value("none", ctk::DataConsistencyGroup::MatchingMode::none, - "No matching, effectively disable the DataConsitencyGroup. update() will always return true. ") + "No consistency matching. Effectively disables consistency checks for the group.") .value("exact", ctk::DataConsistencyGroup::MatchingMode::exact, - "Require an exact match of the VersionNumber of all current values of the group's members. Require an " - "exact match of the VersionNumber of all current or historized values of the group's members ") - .value("historized", ctk::DataConsistencyGroup::MatchingMode::historized, "The data is not considered valid") + "Require an exact VersionNumber match across all current values of the group's members.") + .value("historized", ctk::DataConsistencyGroup::MatchingMode::historized, + "Allow matching against historized values to find a consistent state across group members.") .export_values(); } diff --git a/src/PyDataType.cc b/src/PyDataType.cc index 56bfb04..9f120b3 100644 --- a/src/PyDataType.cc +++ b/src/PyDataType.cc @@ -22,23 +22,23 @@ namespace ChimeraTK { .def("isNumeric", &ChimeraTK::DataType::isNumeric, R"(Returns whether the data type is numeric. Type 'none' returns false. - :return: True if the data type is numeric, false otherwise. - :rtype: bool)") + Returns: + bool: True if the data type is numeric, false otherwise.)") .def("getAsString", &ChimeraTK::DataType::getAsString, R"(Get the data type as string. - :return: Data type as string. - :rtype: str)") + Returns: + str: Data type as string.)") .def("isIntegral", &ChimeraTK::DataType::isIntegral, R"(Return whether the raw data type is an integer. False is also returned for non-numerical types and 'none'. - :return: True if the data type is an integer, false otherwise. - :rtype: bool)") + Returns: + bool: True if the data type is an integer, false otherwise.)") .def("isSigned", &ChimeraTK::DataType::isSigned, R"(Return whether the raw data type is signed. True for signed integers and floating point types (currently only signed implementations). False otherwise (also for non-numerical types and 'none'). - :return: True if the data type is signed, false otherwise. - :rtype: bool)"); + Returns: + bool: True if the data type is signed, false otherwise.)"); py::enum_(mDataType, "TheType") .value("none", ChimeraTK::DataType::none, diff --git a/src/PyDevice.cc b/src/PyDevice.cc index ca0416c..a40bbd5 100644 --- a/src/PyDevice.cc +++ b/src/PyDevice.cc @@ -208,229 +208,243 @@ namespace ChimeraTK { void PyDevice::bind(py::module& mod) { py::class_ dev(mod, "Device", - R"(Class to access a ChimeraTK device. + R"(Class for accessing a ChimeraTK device. - The device can be opened and closed, and provides methods to obtain register accessors. Additionally, - convenience methods to read and write registers directly are provided. - The class also offers methods to check the device state, obtain the register catalogue, Metadata and to set exception conditions.)"); + The device can be opened and closed, and provides methods to obtain register accessors. In addition, + convenience methods to read and write registers directly are available. + The class also provides methods to inspect the device state, obtain the register catalogue and metadata, + and set exception conditions.)"); dev.def(py::init(), py::arg("aliasName"), R"(Initialize device and associate a backend. - Note: + Note: The device is not opened after initialization. - :param aliasName: The ChimeraTK device descriptor for the device. - :type aliasName: str)") + Args: + aliasName (str): The ChimeraTK device descriptor for the device.)") .def(py::init(), R"(Create device instance without associating a backend yet. - A backend has to be explicitly associated using open method which - has the alias or CDD as argument.)") + A backend has to be explicitly associated using the open() method, which takes the alias or CDD as an + argument.)") .def("open", py::overload_cast(&PyDevice::open), py::arg("aliasName"), R"(Open a device by the given alias name from the DMAP file. - :param aliasName: The device alias name from the DMAP file. - :type aliasName: str)") + Args: + aliasName (str): The device alias name from the DMAP file.)") .def("open", py::overload_cast<>(&PyDevice::open), R"((Re-)Open the device. - Can only be called when the device was constructed with a given aliasName.)") + Can only be called when the device was constructed with a given aliasName.)") .def("close", &PyDevice::close, R"(Close the device. - The connection with the alias name is kept so the device can be re-opened - using the open() function without argument.)") + The connection with the alias name is kept so the device can be reopened using open() without arguments.)") .def("getVoidRegisterAccessor", &PyDevice::getVoidRegisterAccessor, py::arg("registerPathName"), py::arg("accessModeFlags") = py::list(), R"(Get a VoidRegisterAccessor object for the given register. - :param registerPathName: Full path name of the register. - :type registerPathName: str - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode] - :return: VoidRegisterAccessor for the specified register. - :rtype: VoidRegisterAccessor)") + Args: + registerPathName (str): Full path name of the register. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + + Returns: + :class:`VoidRegisterAccessor`: VoidRegisterAccessor for the specified register. + + See Also: + :meth:`getScalarRegisterAccessor`: For scalar value registers. + :meth:`getOneDRegisterAccessor`: For 1D array registers. + :meth:`getTwoDRegisterAccessor`: For 2D array registers. + :meth:`getRegisterCatalogue`: Browse all available registers.)") .def("getScalarRegisterAccessor", &PyDevice::getScalarRegisterAccessor, py::arg("userType"), py::arg("registerPathName"), py::arg("elementsOffset") = 0, py::arg("accessModeFlags") = py::list(), R"(Get a ScalarRegisterAccessor object for the given register. - The ScalarRegisterAccessor allows to read and write registers transparently - by using the accessor object like a variable of the type UserType. - - :param userType: The data type for register access (numpy dtype). - :type userType: dtype - :param registerPathName: Full path name of the register. - :type registerPathName: str - :param elementsOffset: Word offset in register to access another but the first word. - :type elementsOffset: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :return: ScalarRegisterAccessor for the specified register. - :rtype: ScalarRegisterAccessor)") + The ScalarRegisterAccessor allows reading and writing registers transparently by using the accessor object + like a variable of the specified data type. + + Args: + userType (numpy.dtype): The data type for register access (numpy dtype). + registerPathName (str): Full path name of the register. + elementsOffset (int): Word offset in the register to access other than the first word. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + + Returns: + :class:`ScalarRegisterAccessor`: ScalarRegisterAccessor for the specified register. + + See Also: + :meth:`getOneDRegisterAccessor`: For 1D array registers. + :meth:`getTwoDRegisterAccessor`: For 2D array registers. + :meth:`getVoidRegisterAccessor`: For trigger-only registers. + :meth:`read`: Convenience function for one-time reads. + :meth:`write`: Convenience function for one-time writes.)") .def("getOneDRegisterAccessor", &PyDevice::getOneDRegisterAccessor, py::arg("userType"), py::arg("registerPathName"), py::arg("numberOfElements") = 0, py::arg("elementsOffset") = 0, py::arg("accessModeFlags") = py::list(), R"(Get a OneDRegisterAccessor object for the given register. - The OneDRegisterAccessor allows to read and write registers transparently - by using the accessor object like a vector of the type UserType. - - :param userType: The data type for register access (numpy dtype). - :type userType: dtype - :param registerPathName: Full path name of the register. - :type registerPathName: str - :param numberOfElements: Number of elements to access (0 for entire register). - :type numberOfElements: int, optional - :param elementsOffset: Word offset in register to skip initial elements. - :type elementsOffset: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :return: OneDRegisterAccessor for the specified register. - :rtype: OneDRegisterAccessor)") + The OneDRegisterAccessor allows reading and writing registers transparently by using the accessor object + like a vector of the specified data type. + + Args: + userType (numpy.dtype): The data type for register access (numpy dtype). + registerPathName (str): Full path name of the register. + numberOfElements (int): Number of elements to access (0 for the entire register). + elementsOffset (int): Word offset in the register to skip initial elements. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + + Returns: + :class:`OneDRegisterAccessor`: OneDRegisterAccessor for the specified register. + + See Also: + :meth:`getScalarRegisterAccessor`: For single-value registers. + :meth:`getTwoDRegisterAccessor`: For 2D array registers. + :meth:`getVoidRegisterAccessor`: For trigger-only registers. + :meth:`read`: Convenience function for one-time reads. + :meth:`write`: Convenience function for one-time writes.)") .def("getTwoDRegisterAccessor", &PyDevice::getTwoDRegisterAccessor, py::arg("userType"), py::arg("registerPathName"), py::arg("numberOfElements") = 0, py::arg("elementsOffset") = 0, py::arg("accessModeFlags") = py::list(), R"(Get a TwoDRegisterAccessor object for the given register. - This allows to read and write transparently 2-dimensional registers. - - :param userType: The data type for register access (numpy dtype). - :type userType: dtype - :param registerPathName: Full path name of the register. - :type registerPathName: str - :param numberOfElements: Number of elements per channel (0 for all). - :type numberOfElements: int, optional - :param elementsOffset: First element index for each channel to read. - :type elementsOffset: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :return: TwoDRegisterAccessor for the specified register. - :rtype: TwoDRegisterAccessor)") + This allows reading and writing 2-dimensional registers transparently. + + Args: + userType (numpy.dtype): The data type for register access (numpy dtype). + registerPathName (str): Full path name of the register. + numberOfElements (int): Number of elements per channel (0 for all). + elementsOffset (int): First element index for each channel to read. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + + Returns: + :class:`TwoDRegisterAccessor`: TwoDRegisterAccessor for the specified register. + + See Also: + :meth:`getOneDRegisterAccessor`: For 1D array registers. + :meth:`getScalarRegisterAccessor`: For single-value registers. + :meth:`getVoidRegisterAccessor`: For trigger-only registers. + :meth:`read`: Convenience function for one-time reads. + :meth:`write`: Convenience function for one-time writes.)") .def("activateAsyncRead", &PyDevice::activateAsyncRead, - R"(Activate asynchronous read for all transfer elements with wait_for_new_data flag. + R"(Activate asynchronous read for all TransferElements with wait_for_new_data flag. - If called while the device is not opened or has an error, this call has no effect. - When this function returns, it is not guaranteed that all initial values have been - received already.)") + If called while the device is not opened or has an error, this call has no effect. + When this function returns, it is not guaranteed that all initial values have been received already.)") .def("getRegisterCatalogue", &PyDevice::getRegisterCatalogue, R"(Return the register catalogue with detailed information on all registers. - :return: Register catalogue containing all register information. - :rtype: RegisterCatalogue)") + Returns: + :class:`RegisterCatalogue`: RegisterCatalogue containing all register information.)") .def("read", &PyDevice::read, py::arg("registerPath"), py::arg("dtype") = py::dtype::of(), py::arg("numberOfWords") = 0, py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), R"(Convenience function to read a register without obtaining an accessor. - Warning: - This function is inefficient as it creates and discards a register accessor - in each call. For better performance, use register accessors instead. - - :param registerPath: Full path name of the register. - :type registerPath: str - :param dtype: Data type for the read operation (default: float64). - :type dtype: dtype, optional - :param numberOfWords: Number of elements to read (0 for scalar or entire register). - :type numberOfWords: int, optional - :param wordOffsetInRegister: Word offset in register to skip initial elements. - :type wordOffsetInRegister: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :return: Register value (scalar, 1D array, or 2D array depending on register type). - :rtype: scalar, ndarray, or list[list])") + Warning: + This function is inefficient as it creates and discards a register accessor in each call. For better + performance, use register accessors instead. + + Args: + registerPath (str): Full path name of the register. + dtype (numpy.dtype): Data type for the read operation (default: float64). + numberOfWords (int): Number of elements to read (0 for scalar or entire register). + wordOffsetInRegister (int): Word offset in the register to skip initial elements. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + + Returns: + scalar | ndarray: Register value (scalar, 1D array, or 2D array depending on register type). + + See Also: + :meth:`getScalarRegisterAccessor`: For efficient repeated scalar access. + :meth:`getOneDRegisterAccessor`: For efficient repeated 1D array access. + :meth:`getTwoDRegisterAccessor`: For efficient repeated 2D array access. + :meth:`write`: Convenience function for one-time writes.)") .def("write", &PyDevice::write2D, py::arg("registerPath"), py::arg("dataToWrite"), - py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(), - R"(Convenience function to write a 2D register without obtaining an accessor. - - Warning: - This function is inefficient as it creates and discards a register accessor - in each call. For better performance, use register accessors instead. - - :param registerPath: Full path name of the register. - :type registerPath: str - :param dataToWrite: 2D array data to write to the register. - :type dataToWrite: list[list] - :param wordOffsetInRegister: Word offset in register to skip initial elements. - :type wordOffsetInRegister: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :param dtype: Optional data type override (default: inferred from data). - :type dtype: dtype or None)") + py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none()) .def("write", &PyDevice::write1D, py::arg("registerPath"), py::arg("dataToWrite"), - py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(), - R"(Convenience function to write a 1D register without obtaining an accessor. - - Warning: - This function is inefficient as it creates and discards a register accessor - in each call. For better performance, use register accessors instead. - - :param registerPath: Full path name of the register. - :type registerPath: str - :param dataToWrite: 1D array data to write to the register. - :type dataToWrite: list or ndarray - :param wordOffsetInRegister: Word offset in register to skip initial elements. - :type wordOffsetInRegister: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :param dtype: Optional data type override (default: inferred from data). - :type dtype: dtype or None)") + py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none()) .def("write", &PyDevice::writeScalar, py::arg("registerPath"), py::arg("dataToWrite"), py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(), - R"(Convenience function to write a scalar register without obtaining an accessor. - - Warning: - This function is inefficient as it creates and discards a register accessor - in each call. For better performance, use register accessors instead. - - :param registerPath: Full path name of the register. - :type registerPath: str - :param dataToWrite: Scalar value to write to the register. - :type dataToWrite: int, float, or str - :param wordOffsetInRegister: Word offset in register (for multi-word registers). - :type wordOffsetInRegister: int, optional - :param accessModeFlags: Optional flags to control register access details. - :type accessModeFlags: list[AccessMode], optional - :param dtype: Optional data type override (default: inferred from data). - :type dtype: dtype or None)") + R"(Convenience function to write a register without obtaining an accessor. + + This method is overloaded to handle different data types. The appropriate overload is selected based on the + type of dataToWrite. + + Warning: + This function is inefficient as it creates and discards a register accessor in each call. For better + performance, use register accessors instead: + :meth:`getScalarRegisterAccessor`, :meth:`getOneDRegisterAccessor`, :meth:`getTwoDRegisterAccessor`. + + Args: + registerPath (str): Full path name of the register. + dataToWrite (int | float | bool | str | ndarray): Data to write. Type determines operation: + + - Scalar (int, float, bool, str): Write a scalar value to a single-element register. + - 1D array (ndarray): Write 1D array data to a 1D register. + - 2D array (ndarray): Write 2D array data to a 2D register. + + wordOffsetInRegister (int): Word offset in the register to skip initial elements (default: 0). + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access (default: []). + dtype (numpy.dtype | None): Optional data type override. If None, type is inferred from data (default: + None). + + Examples: + >>> import ChimeraTK.DeviceAccess as da + >>> import numpy as np + >>> da.setDMapFilePath('testCrate.dmap') + >>> device = da.Device('TEST_CARD') + >>> device.open() + >>> # Write scalar value + >>> device.write('MYSCALAR', 42.5) + >>> # Write 1D array + >>> device.write('MYARRAY', np.array([1.0, 2.0, 3.0])) + >>> # Write 2D array + >>> device.write('MY2DARRAY', np.array([[1, 2], [3, 4]])) + See Also: + :meth:`getScalarRegisterAccessor`: For efficient repeated scalar access. + :meth:`getOneDRegisterAccessor`: For efficient repeated 1D array access. + :meth:`getTwoDRegisterAccessor`: For efficient repeated 2D array access. + :meth:`read`: Convenience function for one-time reads.)") .def( "isOpened", [](PyDevice& self) { return self._device.isOpened(); }, R"(Check if the device is currently opened. - :return: True if device is opened, false otherwise. - :rtype: bool)") + Returns: + bool: `True` if the device is opened, `False` otherwise.)") .def( "setException", [](PyDevice& self, const std::string& msg) { return self._device.setException(msg); }, py::arg("message"), R"(Set the device into an exception state. - All asynchronous reads will be deactivated and all operations will see exceptions - until open() has successfully been called again. + All asynchronous reads will be deactivated and all operations will see exceptions until open() has + successfully been called again. - :param message: Exception message describing the error condition. - :type message: str)") + Args: + message (str): Exception message describing the error condition.)") .def( "isFunctional", [](PyDevice& self) { return self._device.isFunctional(); }, R"(Check whether the device is working as intended. - Usually this means it is opened and does not have any errors. + Usually this means it is opened and does not have any errors. - :return: True if device is functional, false otherwise. - :rtype: bool)") + Returns: + bool: `True` if the device is functional, `False` otherwise.)") .def("getCatalogueMetadata", &PyDevice::getCatalogueMetadata, py::arg("metaTag"), R"(Get metadata from the device catalogue. - :param metaTag: The metadata parameter name to retrieve. - :type metaTag: str - :return: The metadata value. - :rtype: str)") + Args: + metaTag (str): The metadata parameter name to retrieve. + + Returns: + str: The metadata value.)") .def("__enter__", [](PyDevice& self) { self.open(); return &self; }) .def("__exit__", - [](PyDevice& self, [[maybe_unused]] py::object exc_type, [[maybe_unused]] py::object exc_val, - [[maybe_unused]] py::object exc_traceback) { + [](PyDevice& self, [[maybe_unused]] const py::object& exc_type, [[maybe_unused]] const py::object& exc_val, + [[maybe_unused]] const py::object& exc_traceback) { self.close(); return false; }); diff --git a/src/PyOneDRegisterAccessor.cc b/src/PyOneDRegisterAccessor.cc index f86aa16..d0a7e42 100644 --- a/src/PyOneDRegisterAccessor.cc +++ b/src/PyOneDRegisterAccessor.cc @@ -187,7 +187,15 @@ namespace ChimeraTK { R"(Read the data from the device. If AccessMode.wait_for_new_data was set, this function will block until new data has arrived. - Otherwise it still might block for a short time until the data transfer was complete.)") + Otherwise it still might block for a short time until the data transfer was complete. + + See Also: + readNonBlocking: Read without blocking if no data is available. + readLatest: Read latest value while discarding intermediate updates. + readAndGet: Convenience method combining read() and get(). + + Returns: + None: This function does not return a value.)") .def("readNonBlocking", &PyOneDRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -199,26 +207,32 @@ namespace ChimeraTK { transfer data to obtain the current value before returning. Also this function is not guaranteed to be lock free. The return value will be always true in this mode. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("readLatest", &PyOneDRegisterAccessor::readLatest, R"(Read the latest value, discarding any other update since the last read if present. Otherwise this function is identical to readNonBlocking(), i.e. it will never wait for new values and it will return whether a new value was available if AccessMode.wait_for_new_data is set. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("write", &PyOneDRegisterAccessor::write, py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Write the data to device. The return value is true if old data was lost on the write transfer (e.g. due to a buffer overflow). In case of an unbuffered write transfer, the return value will always be false. - :param versionNumber: Version number to use for this write operation. If not specified, a new version number is generated. - :type versionNumber: VersionNumber - :return: True if data was lost, false otherwise. - :rtype: bool)") + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, + a new version number is generated. + + Returns: + bool: True if data was lost, false otherwise. + + See Also: + setAndWrite: Convenience method combining set() and write(). + writeDestructively: Optimized write that may destroy buffer.)") .def("writeDestructively", &PyOneDRegisterAccessor::writeDestructively, py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Just like write(), but allows the implementation to destroy the content of the user buffer in the process. @@ -227,62 +241,67 @@ namespace ChimeraTK { write(). In any case, the application must expect the user buffer of the accessor to contain undefined data after calling this function. - :param versionNumber: Version number to use for this write operation. If not specified, a new version number is generated. - :type versionNumber: VersionNumber - :return: True if data was lost, false otherwise. - :rtype: bool)") + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, + a new version number is generated. + + Returns: + bool: True if data was lost, false otherwise.)") .def("interrupt", &PyOneDRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. - This will cause a blocking read to return immediately and throw an InterruptedException.)") + This will cause a blocking read to return immediately and throw an InterruptedException. + + Returns: + None: This function does not return a value.)") .def("getName", &PyOneDRegisterAccessor::getName, - R"(Returns the name that identifies the process variable. + R"(Return the name that identifies the process variable. - :return: The register name. - :rtype: str)") + Returns: + str: The register name.)") .def("getUnit", &PyOneDRegisterAccessor::getUnit, - R"(Returns the engineering unit. + R"(Return the engineering unit. If none was specified, it will default to 'n./a.'. - :return: The engineering unit string. - :rtype: str)") + Returns: + str: The engineering unit string.)") .def("getDescription", &PyOneDRegisterAccessor::getDescription, - R"(Returns the description of this variable/register. + R"(Return the description of this variable/register. - :return: The description string. - :rtype: str)") + Returns: + str: The description string.)") .def("getValueType", &PyOneDRegisterAccessor::getValueType, - R"(Returns the numpy dtype for the value type of this accessor. + R"(Return the numpy dtype for the value type of this accessor. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: numpy.dtype)") + Returns: + numpy.dtype: Type information object.)") .def("getVersionNumber", &PyOneDRegisterAccessor::getVersionNumber, - R"(Returns the version number that is associated with the last transfer. + R"(Return the version number that is associated with the last transfer. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyOneDRegisterAccessor::isReadOnly, R"(Check if accessor is read only. This means it is readable but not writeable. - :return: True if read only, false otherwise. - :rtype: bool)") + Returns: + bool: True if read only, false otherwise.)") .def("isReadable", &PyOneDRegisterAccessor::isReadable, R"(Check if accessor is readable. - :return: True if readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if readable, false otherwise.)") .def("isWriteable", &PyOneDRegisterAccessor::isWriteable, R"(Check if accessor is writeable. - :return: True if writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if writeable, false otherwise.)") .def("getId", &PyOneDRegisterAccessor::getId, R"(Obtain unique ID for the actual implementation of this accessor. @@ -291,99 +310,112 @@ namespace ChimeraTK { different calls to Device.getOneDRegisterAccessor() will have a different ID even when accessing the very same register. - :return: The unique accessor ID. - :rtype: TransferElementID)") + Returns: + TransferElementID: The unique accessor ID.)") .def("dataValidity", &PyOneDRegisterAccessor::dataValidity, R"(Return current validity of the data. Will always return DataValidity.ok if the backend does not support it. - :return: The current data validity state. - :rtype: DataValidity)") + Returns: + DataValidity: The current data validity state.)") .def("getNElements", &PyOneDRegisterAccessor::getNElements, R"(Return number of elements/samples in the register. - :return: Number of elements in the register. - :rtype: int)") + Returns: + int: Number of elements in the register.)") .def("get", &PyOneDRegisterAccessor::get, R"(Return the register data as an array (without a previous read). - :return: Array containing the register data. - :rtype: ndarray)") + Returns: + ndarray: Array containing the register data.)") .def("set", &PyOneDRegisterAccessor::set, py::arg("newValue"), R"(Set the values of the array. - :param newValue: New values to set in the buffer. - :type newValue: list or ndarray)") + Args: + newValue (list | ndarray): New values to set in the buffer. + + Returns: + None: This function does not return a value.)") .def("setAndWrite", &PyOneDRegisterAccessor::setAndWrite, py::arg("newValue"), py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Convenience function to set and write new value. If versionNumber is not specified, a new version number is generated. - :param newValue: New values to set and write. - :type newValue: list or ndarray - :param versionNumber: Optional version number for the write operation. - :type versionNumber: VersionNumber)") + Args: + newValue (list | ndarray): New values to set and write. + versionNumber (VersionNumber): Optional version number for the write operation. + + Returns: + None: This function does not return a value.)") .def("getAsCooked", &PyOneDRegisterAccessor::getAsCooked, py::arg("element"), R"(Get the cooked values in case the accessor is a raw accessor (which does not do data conversion). This returns the converted data from the user buffer. It does not do any read or write transfer. - :param element: Element index to read. - :type element: int - :return: The cooked value at the specified element. - :rtype: int)") + Args: + element (int): Element index to read. + + Returns: + int: The cooked value at the specified element.)") .def("setAsCooked", &PyOneDRegisterAccessor::setAsCooked, py::arg("element"), py::arg("value"), R"(Set the cooked values in case the accessor is a raw accessor (which does not do data conversion). This converts to raw and writes the data to the user buffer. It does not do any read or write transfer. - :param element: Element index to write. - :type element: int - :param value: The cooked value to set. - :type value: float)") + Args: + element (int): Element index to write. + value (float): The cooked value to set. + + Returns: + None: This function does not return a value.)") .def("isInitialised", &PyOneDRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. - :return: True if initialised, false otherwise. - :rtype: bool)") + Returns: + bool: True if initialised, false otherwise.)") .def("setDataValidity", &PyOneDRegisterAccessor::setDataValidity, py::arg("validity"), R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (DataValidity): The data validity state to set. + + Returns: + None: This function does not return a value.)") .def("getAccessModeFlags", &PyOneDRegisterAccessor::getAccessModeFlags, R"(Return the access mode flags that were used to create this accessor. This can be used to determine the setting of the raw and the wait_for_new_data flags. - :return: List of access mode flags. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: List of access mode flags.)") .def("readAndGet", &PyOneDRegisterAccessor::readAndGet, R"(Convenience function to read and return the register data. - :return: Array containing the register data after reading. - :rtype: ndarray)") + Returns: + ndarray: Array containing the register data after reading.)") .def( "__getitem__", [](PyOneDRegisterAccessor& acc, size_t index) { return acc.getitem(index); }, py::arg("index"), R"(Get an element from the array by index. - :param index: The element index. - :type index: int - :return: The value at the specified index. - :rtype: scalar)") + Args: + index (int): The element index. + + Returns: + scalar: The value at the specified index.)") .def( "__getitem__", [](PyOneDRegisterAccessor& acc, const py::object& slice) { return acc.get().attr("__getitem__")(slice); }, py::arg("slice"), R"(Get an element from the array by index. - :param slice: A slice object. - :type slice: slice - :return: The value(s) at the specified slice. - :rtype: np.ndarray)") + Args: + slice (slice): A slice object. + + Returns: + np.ndarray: The value(s) at the specified slice.)") .def( "__setitem__", [](PyOneDRegisterAccessor& acc, size_t index, const UserTypeVariantNoVoid& value) { @@ -392,10 +424,12 @@ namespace ChimeraTK { py::arg("index"), py::arg("value"), R"(Set an element in the array by index. - :param index: The element index. - :type index: int - :param value: The value to set at the specified index. - :type value: user type)") + Args: + index (int): The element index. + value (user type): The value to set at the specified index. + + Returns: + None: This function does not return a value.)") .def( "__setitem__", [](PyOneDRegisterAccessor& acc, const py::object& slice, const UserTypeVariantNoVoid& value) { @@ -404,10 +438,12 @@ namespace ChimeraTK { py::arg("slice"), py::arg("value"), R"(Set an element in the array by slice. - :param slice: The element slice. - :type slice: slice - :param value: The value to set at the specified slice. - :type value: user type)") + Args: + slice (slice): The element slice. + value (user type): The value to set at the specified slice. + + Returns: + None: This function does not return a value.)") .def( "__setitem__", [](PyOneDRegisterAccessor& acc, const py::object& slice, const py::object& array) { @@ -416,11 +452,20 @@ namespace ChimeraTK { py::arg("slice"), py::arg("array"), R"(Set an element in the array by slice. - :param slice: The element slice. - :type slice: slice - :param array: The value to set at the specified slice. - :type array: list or ndarray)") - .def("__getattr__", &PyOneDRegisterAccessor::getattr); + Args: + slice (slice): The element slice. + array (list or ndarray): The value to set at the specified slice. + + Returns: + None: This function does not return a value.)") + .def("__getattr__", &PyOneDRegisterAccessor::getattr, py::arg("name"), + R"(Forward unknown attribute access to the underlying array-like object. + + Args: + name (str): Name of the attribute. + + Returns: + object: Attribute value or callable attribute proxy.)"); for(const auto& fn : PyTransferElementBase::specialFunctionsToEmulateNumeric) { arrayacc.def(fn.c_str(), [fn](PyOneDRegisterAccessor& acc, PyOneDRegisterAccessor& other) { diff --git a/src/PyReadAnyGroup.cc b/src/PyReadAnyGroup.cc index cc42732..188a510 100644 --- a/src/PyReadAnyGroup.cc +++ b/src/PyReadAnyGroup.cc @@ -21,38 +21,65 @@ namespace ChimeraTK { /********************************************************************************************************************/ void PyReadAnyGroup::bind(py::module& m) { - py::class_(m, "ReadAnyGroup") - .def(py::init<>()) + py::class_(m, "ReadAnyGroup", + R"(Group for waiting on updates from multiple TransferElements with wait_for_new_data enabled.)") + .def(py::init<>(), R"(Create an empty ReadAnyGroup.)") .def("finalise", &ctk::ReadAnyGroup::finalise, - R"(Finalise the group. After this, add() may no longer be called and read methods may be used.)") - .def("interrupt", &ctk::ReadAnyGroup::interrupt, R"(Interrupt the group.)") + R"(Finalise the group. + + After this, add() may no longer be called and read/wait methods may be used. + + Returns: + None: This function does not return a value.)") + .def("interrupt", &ctk::ReadAnyGroup::interrupt, R"(Interrupt blocking operations running on this group.)") .def( "add", [](ctk::ReadAnyGroup& self, PyTransferElementBase& element) { self.add(element.getTE()); }, - R"(Add register to group. Note that calling this function is only allowed before finalise() has been called. The given register may not yet be part of a ReadAnyGroup or a TransferGroup, otherwise an exception is thrown. The register must be must be readable.)", - py::arg("element")) + py::arg("element"), + R"(Add a TransferElement to the group. + + This is only allowed before finalise() has been called. The given element may not already be part of a + ReadAnyGroup or TransferGroup, otherwise an exception is thrown. The element must be readable. + + Returns: + None: This function does not return a value.)") .def("readAny", &ctk::ReadAnyGroup::readAny, - R"(Wait until one of the elements in this group has received an update.)") + R"(Wait until one of the elements in this group has received an update. + + Returns: + TransferElementID: ID of the element whose update has been processed.)") .def("readAnyNonBlocking", &ctk::ReadAnyGroup::readAnyNonBlocking, - R"(Wait until one of the elements in this group has received an update.)") + R"(Return immediately and process an update if one is available. + + Returns: + TransferElementID: ID of the processed element, or an invalid ID if no update is available.)") .def( "readUntil", [](ctk::ReadAnyGroup& self, const ctk::TransferElementID& id) { self.readUntil(id); }, - R"(Wait until the given TransferElementID has received an update and store it to its user buffer. )", - py::arg("id")) + py::arg("id"), + R"(Wait until the given TransferElementID has received an update and store it in its user buffer. + + Returns: + None: This function does not return a value.)") .def( "readUntil", [](ctk::ReadAnyGroup& self, const PyTransferElementBase& element) { self.readUntil(element.getTE()); }, - R"(Wait until the given TransferElement has received an update and store it to its user buffer.)", - py::arg("element")) + py::arg("element"), + R"(Wait until the given TransferElement has received an update and store it in its user buffer. + + Returns: + None: This function does not return a value.)") .def( "readUntilAll", [](ctk::ReadAnyGroup& self, const std::vector& ids) { self.readUntilAll(ids); }, - R"(Wait until all of the given TransferElementID has received an update and store it to its user buffer.)", - py::arg("ids")) + py::arg("ids"), + R"(Wait until all given TransferElementID values have received updates and store them in their user buffers. + + Returns: + None: This function does not return a value.)") .def( "readUntilAll", [](ctk::ReadAnyGroup& self, const py::list& elements) { // implementation from deviceaccess/src/ReadAnyGroup.cpp, adapted to work with PyTransferElementBase - // wihtout to template every possible usertype + // without templating every possible user type auto locals = py::dict("self"_a = self, "elements"_a = elements); py::exec(R"( found = {} @@ -72,35 +99,61 @@ namespace ChimeraTK { )", py::globals(), locals); }, - R"(Wait until all of the given TransferElement has received an update and store it to its user buffer.)", - py::arg("element")) + py::arg("elements"), + R"(Wait until all given TransferElements have received updates and store them in their user buffers. + + Returns: + None: This function does not return a value.)") .def("waitAny", &ctk::ReadAnyGroup::waitAny, - R"(Wait until any of the elements in this group has received an update, but do not process the update.)") + R"(Wait until any element in this group has received an update, but do not process the update. + + Returns: + Notification: Notification object for the pending update.)") .def("waitAnyNonBlocking", &ctk::ReadAnyGroup::waitAnyNonBlocking, - R"(Check if an update is available in the group, but do not block if no update is available.)") + R"(Return immediately with a notification if an update is available. + + Returns: + Notification: Notification object for a pending update, or an invalid notification if none is available.)") .def("processPolled", &ctk::ReadAnyGroup::processPolled, - R"(Process polled transfer elements (update them if new values are available.)"); + R"(Process polled TransferElements and update them if new values are available. + + Returns: + None: This function does not return a value.)"); } /*******************************************************************************************************************/ void PyReadAnyGroupNotification::bind(py::module& m) { - py::class_(m, "Notification") - .def(py::init<>()) - .def("accept", &ctk::ReadAnyGroup::Notification::accept, R"(Accept the notification.)") + py::class_( + m, "Notification", R"(Notification returned by ReadAnyGroup wait methods.)") + .def(py::init<>(), R"(Create an invalid notification.)") + .def("accept", &ctk::ReadAnyGroup::Notification::accept, + R"(Accept the notification and process the associated update. + + Returns: + None: This function does not return a value.)") .def("getId", &ctk::ReadAnyGroup::Notification::getId, - R"(Return the ID of the transfer element for which this notification has been generated.)") + R"(Return the ID of the TransferElement for which this notification was generated. + + Returns: + TransferElementID: ID of the associated TransferElement.)") .def( "getTransferElement", [](ctk::ReadAnyGroup::Notification&) { - // Implementation would need a different architecture, as we cannot just return ther cpp transfer element. + // Implementation would need a different architecture, as we cannot just return the C++ TransferElement. // We would need to change the ReadAnyGroup to keep references to the added accessors and return those here. throw std::runtime_error( "getTransferElement() is not implemented yet, please contact the developers if you need this."); }, - R"(Return the transfer element for which this notification has been generated.)") + R"(Return the TransferElement for which this notification was generated. + + Raises: + RuntimeError: Always raised because this method is not implemented.)") .def("isReady", &ctk::ReadAnyGroup::Notification::isReady, - R"(Tell whether this notification is valid and has not been accepted yet.)"); + R"(Tell whether this notification is valid and has not been accepted yet. + + Returns: + bool: True if this notification is ready to be accepted, false otherwise.)"); } /********************************************************************************************************************/ diff --git a/src/PyScalarRegisterAccessor.cc b/src/PyScalarRegisterAccessor.cc index 84027e4..70da11c 100644 --- a/src/PyScalarRegisterAccessor.cc +++ b/src/PyScalarRegisterAccessor.cc @@ -249,7 +249,15 @@ namespace ChimeraTK { R"(Read the data from the device. If AccessMode.wait_for_new_data was set, this function will block until new data has arrived. - Otherwise it still might block for a short time until the data transfer was complete.)") + Otherwise it still might block for a short time until the data transfer was complete. + + See Also: + readNonBlocking: Read without blocking if no data is available. + readLatest: Read latest value while discarding intermediate updates. + readAndGet: Convenience method combining read() and get(). + + Returns: + None: This function does not return a value.)") .def("readNonBlocking", &PyScalarRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -261,24 +269,33 @@ namespace ChimeraTK { transfer data to obtain the current value before returning. Also this function is not guaranteed to be lock free. The return value will be always true in this mode. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("readLatest", &PyScalarRegisterAccessor::readLatest, R"(Read the latest value, discarding any other update since the last read if present. Otherwise this function is identical to readNonBlocking(), i.e. it will never wait for new values and it will return whether a new value was available if AccessMode.wait_for_new_data is set. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("write", &PyScalarRegisterAccessor::write, py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Write the data to device. The return value is true if old data was lost on the write transfer (e.g. due to a buffer overflow). In case of an unbuffered write transfer, the return value will always be false. - :param versionNumber: Version number to use for this write operation. If not specified, a new version number is generated. - :type versionNumber: VersionNumber)") + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, + a new version number is generated. + + Returns: + bool: True if data was lost, false otherwise. + + See Also: + setAndWrite: Convenience method combining set() and write(). + writeIfDifferent: Only write if value has changed. + writeDestructively: Optimized write that may destroy buffer.)") .def("writeDestructively", &PyScalarRegisterAccessor::writeDestructively, py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Just like write(), but allows the implementation to destroy the content of the user buffer in the process. @@ -287,56 +304,60 @@ namespace ChimeraTK { write(). In any case, the application must expect the user buffer of the accessor to contain undefined data after calling this function. - :param versionNumber: Version number to use for this write operation. If not specified, a new version number is generated. - :type versionNumber: VersionNumberl)") + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, + a new version number is generated. + + Returns: + bool: True if data was lost, false otherwise.)") .def("getName", &PyScalarRegisterAccessor::getName, R"(Returns the name that identifies the process variable. - :return: The register name. - :rtype: str)") + Returns: + str: The register name.)") .def("getUnit", &PyScalarRegisterAccessor::getUnit, R"(Returns the engineering unit. If none was specified, it will default to 'n./a.'. - :return: The engineering unit string. - :rtype: str)") + Returns: + str: The engineering unit string.)") .def("getDescription", &PyScalarRegisterAccessor::getDescription, R"(Returns the description of this variable/register. - :return: The description string. - :rtype: str)") + Returns: + str: The description string.)") .def("getValueType", &PyScalarRegisterAccessor::getValueType, R"(Returns the type_info for the value type of this accessor. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: type)") + Returns: + type: Type information object.)") .def("getVersionNumber", &PyScalarRegisterAccessor::getVersionNumber, R"(Returns the version number that is associated with the last transfer. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyScalarRegisterAccessor::isReadOnly, R"(Check if accessor is read only. This means it is readable but not writeable. - :return: True if read only, false otherwise. - :rtype: bool)") + Returns: + bool: True if read only, false otherwise.)") .def("isReadable", &PyScalarRegisterAccessor::isReadable, R"(Check if accessor is readable. - :return: True if readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if readable, false otherwise.)") .def("isWriteable", &PyScalarRegisterAccessor::isWriteable, R"(Check if accessor is writeable. - :return: True if writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if writeable, false otherwise.)") .def("getId", &PyScalarRegisterAccessor::getId, R"(Obtain unique ID for the actual implementation of this accessor. @@ -345,44 +366,53 @@ namespace ChimeraTK { different calls to Device.getScalarRegisterAccessor() will have a different ID even when accessing the very same register. - :return: The unique transfer element ID. - :rtype: TransferElementID)") + Returns: + TransferElementID: The unique TransferElement ID.)") .def("dataValidity", &PyScalarRegisterAccessor::dataValidity, R"(Return current validity of the data. Will always return DataValidity.ok if the backend does not support it. - :return: The current data validity state. - :rtype: DataValidity)") + Returns: + DataValidity: The current data validity state.)") .def("get", &PyScalarRegisterAccessor::get, R"(Return the scalar value (without a previous read). - :return: The current value in the buffer. - :rtype: scalar)") + Returns: + scalar: The current value in the buffer.)") .def("readAndGet", &PyScalarRegisterAccessor::readAndGet, R"(Convenience function to read and return the scalar value. - :return: The value after reading from device. - :rtype: scalar)") + Returns: + scalar: The value after reading from device.)") .def( "set", [](PyScalarRegisterAccessor& self, const UserTypeVariantNoVoid& val) { self.set(val); }, py::arg("val"), R"(Set the scalar value. - :param val: New value to set in the buffer. - :type val: int, float, bool, or str)") + Args: + val (int | float | bool | str): New value to set in the buffer. + + Returns: + None: This function does not return a value.)") .def( "set", [](PyScalarRegisterAccessor& self, const py::list& val) { self.setList(val); }, py::arg("val"), R"(Set the scalar value from a list. - :param val: List containing a single value to set. - :type val: list)") + Args: + val (list): List containing a single value to set. + + Returns: + None: This function does not return a value.)") .def( "set", [](PyScalarRegisterAccessor& self, const py::array& val) { self.setArray(val); }, py::arg("val"), R"(Set the scalar value from a numpy array. - :param val: Array containing a single value to set. - :type val: ndarray)") + Args: + val (ndarray): Array containing a single value to set. + + Returns: + None: This function does not return a value.)") .def("writeIfDifferent", &PyScalarRegisterAccessor::writeIfDifferent, py::arg("newValue"), py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Convenience function to set and write new value if it differs from the current value. @@ -390,62 +420,75 @@ namespace ChimeraTK { The given version number is only used in case the value differs. If versionNumber is not specified, a new version number is generated only if the write actually takes place. - :param newValue: New value to compare and potentially write. - :type newValue: int, float, bool, or str - :param versionNumber: Optional version number for the write operation. - :type versionNumber: VersionNumber)") + Args: + newValue (int | float | bool | str): New value to compare and potentially write. + versionNumber (VersionNumber): Optional version number for the write operation. + + Returns: + None: This function does not return a value.)") .def("setAndWrite", &PyScalarRegisterAccessor::setAndWrite, py::arg("newValue"), py::arg("versionNumber") = PyVersionNumber::getNullVersion(), R"(Convenience function to set and write new value. If versionNumber is not specified, a new version number is generated. - :param newValue: New value to set and write. - :type newValue: int, float, bool, or str - :param versionNumber: Optional version number for the write operation. - :type versionNumber: VersionNumber)") + Args: + newValue (int | float | bool | str): New value to set and write. + versionNumber (VersionNumber): Optional version number for the write operation. + + Returns: + None: This function does not return a value.)") .def("getAsCooked", &PyScalarRegisterAccessor::getAsCooked, R"(Get the cooked values in case the accessor is a raw accessor (which does not do data conversion). This returns the converted data from the user buffer. It does not do any read or write transfers. - :return: The cooked value. - :rtype: float)") + Returns: + float: The cooked value.)") .def("setAsCooked", &PyScalarRegisterAccessor::setAsCooked, py::arg("value"), R"(Set the cooked values in case the accessor is a raw accessor (which does not do data conversion). This converts to raw and writes the data to the user buffer. It does not do any read or write transfers. - :param value: The cooked value to set. - :type value: float)") + Args: + value (float): The cooked value to set. + + Returns: + None: This function does not return a value.)") .def("interrupt", &PyScalarRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. - This will cause a blocking read to return immediately and throw an InterruptedException.)") + This will cause a blocking read to return immediately and throw an InterruptedException. + + Returns: + None: This function does not return a value.)") .def("getAccessModeFlags", &PyScalarRegisterAccessor::getAccessModeFlags, R"(Return the access mode flags that were used to create this accessor. This can be used to determine the setting of the raw and the wait_for_new_data flags. - :return: List of access mode flags. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: List of access mode flags.)") .def("isInitialised", &PyScalarRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. - :return: True if initialised, false otherwise. - :rtype: bool)") + Returns: + bool: True if initialised, false otherwise.)") .def("setDataValidity", &PyScalarRegisterAccessor::setDataValidity, py::arg("validity"), R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (DataValidity): The data validity state to set. + + Returns: + None: This function does not return a value.)") .def_property_readonly("dtype", &PyScalarRegisterAccessor::getValueType, R"(Return the dtype of the value type of this accessor. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: dtype)") + Returns: + dtype: Type information object.)") .def( "__getitem__", [](PyScalarRegisterAccessor& self, const size_t& index) { @@ -457,10 +500,11 @@ namespace ChimeraTK { py::arg("index"), R"(Get the value of the scalar register accessor (same as get()). - :param index: Must be 0 for scalar accessors. - :type index: int - :return: The current value. - :rtype: scalar)") + Args: + index (int): Must be 0 for scalar accessors. + + Returns: + scalar: The current value.)") .def( "__setitem__", [](PyScalarRegisterAccessor& self, const size_t& index, const UserTypeVariantNoVoid& value) { @@ -472,10 +516,12 @@ namespace ChimeraTK { py::arg("index"), py::arg("value"), R"(Set the value of the scalar register accessor (same as set()). - :param index: Must be 0 for scalar accessors. - :type index: int - :param value: New value to set. - :type value: int, float, bool, or str)") + Args: + index (int): Must be 0 for scalar accessors. + value (int | float | bool | str): New value to set. + + Returns: + None: This function does not return a value.)") .def("__repr__", &PyScalarRegisterAccessor::repr); for(const auto& fn : PyTransferElementBase::specialFunctionsToEmulateNumeric) { scalaracc.def(fn.c_str(), [fn](PyScalarRegisterAccessor& acc, PyScalarRegisterAccessor& other) { diff --git a/src/PyTransferGroup.cc b/src/PyTransferGroup.cc index ec4637e..1e47e57 100644 --- a/src/PyTransferGroup.cc +++ b/src/PyTransferGroup.cc @@ -21,13 +21,23 @@ namespace ChimeraTK { /********************************************************************************************************************/ void PyTransferGroup::bind(py::module& m) { - py::class_(m, "TransferGroup") - .def(py::init<>()) + py::class_(m, "TransferGroup", + R"(Group of TransferElements for coordinated read and write operations. + + A TransferGroup allows triggering one read or write call for all added accessors.)") + .def(py::init<>(), R"(Create an empty TransferGroup.)") .def( "addAccessor", [](ctk::TransferGroup& self, PyTransferElementBase& element) { self.addAccessor(element.getTE()); }, - R"(Add register to group. A register cannot be added to multiple groups. A TransferGroup can only be used with transfer elements that don't have AccessMode::wait_for_new_data.)") - .def("read", &ctk::TransferGroup::read, R"(Trigger read transfer for all accessors in the group.)") + py::arg("element"), + R"(Add an accessor to the group. + + A TransferElement can only be part of one TransferGroup. TransferGroup can only be used with transfer + elements that do not have AccessMode.wait_for_new_data. + + Args: + element (TransferElementBase): Accessor to add to the group.)") + .def("read", &ctk::TransferGroup::read, R"(Trigger a read transfer for all accessors in the group.)") .def( "write", [](ctk::TransferGroup& self, PyVersionNumber versionNumber) { @@ -39,13 +49,26 @@ namespace ChimeraTK { } }, py::arg("versionNumber") = PyVersionNumber::getNullVersion(), - R"(Trigger write transfer for all accessors in the group.)") + R"(Trigger a write transfer for all accessors in the group. + + Args: + versionNumber (VersionNumber): Optional version number used for the write operation. If not set, a new + version number is generated.)") .def("isReadOnly", &ctk::TransferGroup::isReadOnly, - R"(Returns true if all accessors in the group are read only.)") + R"(Check whether all accessors in the group are read only. + + Returns: + bool: True if all accessors are read only, false otherwise.)") .def("isReadable", &ctk::TransferGroup::isReadable, - R"(Returns true if all accessors in the group are readable.)") + R"(Check whether all accessors in the group are readable. + + Returns: + bool: True if all accessors are readable, false otherwise.)") .def("isWriteable", &ctk::TransferGroup::isWriteable, - R"(Returns true if all accessors in the group are writable.)"); + R"(Check whether all accessors in the group are writable. + + Returns: + bool: True if all accessors are writable, false otherwise.)"); } /********************************************************************************************************************/ diff --git a/src/PyTwoDRegisterAccessor.cc b/src/PyTwoDRegisterAccessor.cc index 560032d..7c5312b 100644 --- a/src/PyTwoDRegisterAccessor.cc +++ b/src/PyTwoDRegisterAccessor.cc @@ -312,7 +312,14 @@ namespace ChimeraTK { R"(Read the data from the device. If AccessMode.wait_for_new_data was set, this function will block until new data has arrived. Otherwise it - still might block for a short time until the data transfer was complete.)") + still might block for a short time until the data transfer was complete. + + See Also: + readNonBlocking: Read without blocking if no data is available. + readLatest: Read latest value while discarding intermediate updates. + + Returns: + None: This function does not return a value.)") .def("readNonBlocking", &PyTwoDRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -324,25 +331,28 @@ namespace ChimeraTK { the current value before returning. Also this function is not guaranteed to be lock free. The return value will be always true in this mode. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("readLatest", &PyTwoDRegisterAccessor::readLatest, R"(Read the latest value, discarding any other update since the last read if present. Otherwise this function is identical to readNonBlocking(), i.e. it will never wait for new values and it will return whether a new value was available if AccessMode.wait_for_new_data is set. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("write", &PyTwoDRegisterAccessor::write, R"(Write the buffered data to the device. - The return value is true if old data was lost on the write transfer (e.g. due to a buffer overflow). - In case of an unbuffered write transfer, the return value will always be false. + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new + version number is generated. + + Returns: + None: This function does not return a value. - :param versionNumber: Version number to use for this write operation. If not specified, a new version - number is generated. - :type versionNumber: VersionNumber)", + See Also: + writeDestructively: Optimized write that may destroy buffer.)", py::arg("versionNumber") = PyVersionNumber::getNullVersion()) .def("writeDestructively", &PyTwoDRegisterAccessor::writeDestructively, R"(Just like write(), but allows the implementation to destroy the content of the user buffer in the @@ -352,62 +362,68 @@ namespace ChimeraTK { case, the application must expect the user buffer of the accessor to contain undefined data after calling this function. - :param versionNumber: Version number to use for this write operation. If not specified, a new version - number is generated. - :type versionNumber: VersionNumber)", + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new + version number is generated. + + Returns: + None: This function does not return a value.)", py::arg("versionNumber") = PyVersionNumber::getNullVersion()) .def("interrupt", &PyTwoDRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. - This will cause a blocking read to return immediately and throw an InterruptedException.)") + This will cause a blocking read to return immediately and throw an InterruptedException. + + Returns: + None: This function does not return a value.)") .def("getName", &PyTwoDRegisterAccessor::getName, - R"(Returns the name that identifies the process variable. + R"(Return the name that identifies the process variable. - :return: The register name. - :rtype: str)") + Returns: + str: The register name.)") .def("getUnit", &PyTwoDRegisterAccessor::getUnit, - R"(Returns the engineering unit. + R"(Return the engineering unit. If none was specified, it will default to 'n./a.'. - :return: The engineering unit string. - :rtype: str)") + Returns: + str: The engineering unit string.)") .def("getDescription", &PyTwoDRegisterAccessor::getDescription, - R"(Returns the description of this variable/register. + R"(Return the description of this variable/register. - :return: The description string. - :rtype: str)") + Returns: + str: The description string.)") .def("getValueType", &PyTwoDRegisterAccessor::getValueType, - R"(Returns the type_info for the value type of this accessor. + R"(Return the type_info for the value type of this accessor. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: type)") + Returns: + type: Type information object.)") .def("getVersionNumber", &PyTwoDRegisterAccessor::getVersionNumber, - R"(Returns the version number that is associated with the last transfer. + R"(Return the version number that is associated with the last transfer. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyTwoDRegisterAccessor::isReadOnly, R"(Check if accessor is read only. This means it is readable but not writeable. - :return: True if read only, false otherwise. - :rtype: bool)") + Returns: + bool: True if read only, false otherwise.)") .def("isReadable", &PyTwoDRegisterAccessor::isReadable, R"(Check if accessor is readable. - :return: True if readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if readable, false otherwise.)") .def("isWriteable", &PyTwoDRegisterAccessor::isWriteable, R"(Check if accessor is writeable. - :return: True if writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if writeable, false otherwise.)") .def("getId", &PyTwoDRegisterAccessor::getId, R"(Obtain unique ID for the actual implementation of this accessor. @@ -416,98 +432,107 @@ namespace ChimeraTK { calls to Device.getTwoDRegisterAccessor() will have a different ID even when accessing the very same register. - :return: The unique transfer element ID. - :rtype: TransferElementID)") + Returns: + TransferElementID: The unique TransferElement ID.)") .def("dataValidity", &PyTwoDRegisterAccessor::dataValidity, R"(Return current validity of the data. Will always return DataValidity.ok if the backend does not support it. - :return: The current data validity state. - :rtype: DataValidity)") + Returns: + DataValidity: The current data validity state.)") .def("isInitialised", &PyTwoDRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. - :return: True if initialised, false otherwise. - :rtype: bool)") + Returns: + bool: True if initialised, false otherwise.)") .def("getNElementsPerChannel", &PyTwoDRegisterAccessor::getNElementsPerChannel, R"(Return number of elements/samples per channel in the register. - :return: Number of elements per channel. - :rtype: int)") + Returns: + int: Number of elements per channel.)") .def("getNChannels", &PyTwoDRegisterAccessor::getNChannels, R"(Return number of channels in the register. - :return: Number of channels. - :rtype: int)") + Returns: + int: Number of channels.)") .def("get", &PyTwoDRegisterAccessor::get, R"(Return a 2D array of UserType from the internal buffer (without a previous read). The returned object is typically a numpy ndarray with shape (channels, elements_per_channel). For string registers, a list of lists is returned instead. - :return: Current buffer content as a 2D array-like object. - :rtype: ndarray or list)") + Returns: + ndarray | list: Current buffer content as a 2D array-like object.)") .def("set", &PyTwoDRegisterAccessor::set, py::arg("newValue"), R"(Set the values of the 2D array buffer. - :param newValue: New values to set, shaped as [channels][elements] or a 2D numpy array. - :type newValue: list[list[UserType]] or ndarray)") + Args: + newValue (list[list[UserType]] | ndarray): New values to set, shaped as [channels][elements] or a 2D numpy array. + + Returns: + None: This function does not return a value.)") .def("getAsCooked", &PyTwoDRegisterAccessor::getAsCooked, R"(Get a cooked value for a specific channel and element when the accessor is raw (no data conversion). This returns the converted data from the user buffer. It does not do any read or write transfer. - :param channel: Channel index. - :type channel: int - :param element: Element index within the channel. - :type element: int - :return: The cooked value. - :rtype: float)", + Args: + channel (int): Channel index. + element (int): Element index within the channel. + + Returns: + float: The cooked value.)", py::arg("channel"), py::arg("element")) .def("setAsCooked", &PyTwoDRegisterAccessor::setAsCooked, R"(Set a cooked value for a specific channel and element when the accessor is raw (no data conversion). This converts to raw and writes the data to the user buffer. It does not do any read or write transfer. - :param channel: Channel index. - :type channel: int - :param element: Element index within the channel. - :type element: int - :param value: The cooked value to set. - :type value: float)", + Args: + channel (int): Channel index. + element (int): Element index within the channel. + value (float): The cooked value to set. + + Returns: + None: This function does not return a value.)", py::arg("channel"), py::arg("element"), py::arg("value")) - .def("setDataValidity", &PyTwoDRegisterAccessor::setDataValidity, + .def("setDataValidity", &PyTwoDRegisterAccessor::setDataValidity, py::arg("validity"), R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (DataValidity): The data validity state to set. + + Returns: + None: This function does not return a value.)") .def("getAccessModeFlags", &PyTwoDRegisterAccessor::getAccessModeFlags, R"(Return the access mode flags that were used to create this accessor. This can be used to determine the setting of the raw and the wait_for_new_data flags. - :return: List of access mode flags. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: List of access mode flags.)") .def( "__getitem__", [](PyTwoDRegisterAccessor& acc, size_t index) { return acc.getitem(index); }, py::arg("index"), R"(Get an element from the array by index. - :param index: The element index. - :type index: int - :return: The value at the specified index. - :rtype: scalar)") + Args: + index (int): The element index. + + Returns: + scalar: The value at the specified index.)") .def( "__getitem__", [](PyTwoDRegisterAccessor& acc, const py::object& slice) { return acc.get().attr("__getitem__")(slice); }, py::arg("slice"), R"(Get an element from the array by index. - :param slice: A slice object. - :type slice: slice - :return: The value(s) at the specified slice. - :rtype: np.ndarray)") + Args: + slice (slice): A slice object. + + Returns: + np.ndarray: The value(s) at the specified slice.)") .def( "__setitem__", [](PyTwoDRegisterAccessor& acc, size_t index, const UserTypeVariantNoVoid& value) { @@ -516,10 +541,12 @@ namespace ChimeraTK { py::arg("index"), py::arg("value"), R"(Set an element in the array by index. - :param index: The element index. - :type index: int - :param value: The value to set at the specified index. - :type value: user type)") + Args: + index (int): The element index. + value (user type): The value to set at the specified index. + + Returns: + None: This function does not return a value.)") .def( "__setitem__", [](PyTwoDRegisterAccessor& acc, const py::object& slice, const UserTypeVariantNoVoid& value) { @@ -528,10 +555,12 @@ namespace ChimeraTK { py::arg("slice"), py::arg("value"), R"(Set an element in the array by slice. - :param slice: The element slice. - :type slice: slice - :param value: The value to set at the specified slice. - :type value: user type)") + Args: + slice (slice): The element slice. + value (user type): The value to set at the specified slice. + + Returns: + None: This function does not return a value.)") .def( "__setitem__", [](PyTwoDRegisterAccessor& acc, const py::object& slice, const py::object& array) { @@ -540,18 +569,28 @@ namespace ChimeraTK { py::arg("slice"), py::arg("array"), R"(Set an element in the array by slice. - :param slice: The element slice. - :type slice: slice - :param array: The value to set at the specified slice. - :type array: list or ndarray)") + Args: + slice (slice): The element slice. + array (list or ndarray): The value to set at the specified slice. + + Returns: + None: This function does not return a value.)") .def("__getitem__", &PyTwoDRegisterAccessor::getitem, py::arg("index"), R"(Get a single channel by index. - :param index: Channel index. - :type index: int - :return: View of the selected channel as a 1D array-like object. - :rtype: ndarray or list)") - .def("__getattr__", &PyTwoDRegisterAccessor::getattr); + Args: + index (int): Channel index. + + Returns: + ndarray | list: View of the selected channel as a 1D array-like object.)") + .def("__getattr__", &PyTwoDRegisterAccessor::getattr, py::arg("name"), + R"(Forward unknown attribute access to the underlying array-like object. + + Args: + name (str): Name of the attribute. + + Returns: + object: Attribute value or callable attribute proxy.)"); for(const auto& fn : PyTransferElementBase::specialFunctionsToEmulateNumeric) { arrayacc.def(fn.c_str(), [fn](PyTwoDRegisterAccessor& acc, PyTwoDRegisterAccessor& other) { diff --git a/src/PyVersionNumber.cc b/src/PyVersionNumber.cc index a931d10..47756f3 100644 --- a/src/PyVersionNumber.cc +++ b/src/PyVersionNumber.cc @@ -44,8 +44,8 @@ namespace ChimeraTK { }, R"(Return the human readable string representation of the version number. - :return: Version number as string. - :rtype: str)"); + Returns: + str: Version number as string.)"); py::class_(m, "VersionNumber", R"(Class for generating and holding version numbers without exposing a numeric representation. @@ -62,24 +62,87 @@ namespace ChimeraTK { The null version is guaranteed to be smaller than all version numbers generated with the default constructor and can be used to initialise version numbers that are not yet used for data transfers. - :return: Null version number instance. - :rtype: VersionNumber)") + Returns: + VersionNumber: Null version number instance.)") .def("getTime", &PyVersionNumber::getTime, R"(Get the time stamp associated with this version number. Note: This Python binding currently returns a fixed placeholder time (1990-01-01 00:00:00). - :return: Time stamp of the version number. - :rtype: datetime)") + Returns: + datetime: Time stamp of the version number.)") + .def("__repr__", &PyVersionNumber::repr, + R"(Return a debug representation including the version number string. + + Returns: + str: Debug representation of the version number.)") + .def( + "__lt__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self < other; }, + py::arg("other"), + R"(Compare whether this version number is smaller than another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self is smaller than other, otherwise False.)") + .def( + "__le__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self <= other; }, + py::arg("other"), + R"(Compare whether this version number is smaller than or equal to another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self is smaller than or equal to other, otherwise False.)") + .def( + "__gt__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self > other; }, + py::arg("other"), + R"(Compare whether this version number is greater than another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self is greater than other, otherwise False.)") .def( - "__repr__", &PyVersionNumber::repr, R"(Return a debug representation including the version number string.)") - .def("__lt__", &ChimeraTK::VersionNumber::operator<, R"(Compare two version numbers (self < other).)") - .def("__le__", &ChimeraTK::VersionNumber::operator<=, R"(Compare two version numbers (self <= other).)") - .def("__gt__", &ChimeraTK::VersionNumber::operator>, R"(Compare two version numbers (self > other).)") - .def("__ge__", &ChimeraTK::VersionNumber::operator>=, R"(Compare two version numbers (self >= other).)") - .def("__ne__", &ChimeraTK::VersionNumber::operator!=, R"(Compare two version numbers (self != other).)") - .def("__eq__", &ChimeraTK::VersionNumber::operator==, R"(Compare two version numbers (self == other).)"); + "__ge__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self >= other; }, + py::arg("other"), + R"(Compare whether this version number is greater than or equal to another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self is greater than or equal to other, otherwise False.)") + .def( + "__ne__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self != other; }, + py::arg("other"), + R"(Compare whether this version number is different from another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self differs from other, otherwise False.)") + .def( + "__eq__", + [](const ChimeraTK::VersionNumber& self, const ChimeraTK::VersionNumber& other) { return self == other; }, + py::arg("other"), + R"(Compare whether this version number is equal to another one. + + Args: + other (VersionNumber): Version number to compare against. + + Returns: + bool: True if self equals other, otherwise False.)"); } /********************************************************************************************************************/ diff --git a/src/PyVoidRegisterAccessor.cc b/src/PyVoidRegisterAccessor.cc index 8536f56..b81c55a 100644 --- a/src/PyVoidRegisterAccessor.cc +++ b/src/PyVoidRegisterAccessor.cc @@ -71,7 +71,14 @@ namespace ChimeraTK { R"(Read from the device to synchronise with the latest event. If AccessMode.wait_for_new_data was set, this function will block until new data has arrived. Otherwise it - still might block for a short time until the data transfer was complete.)", + still might block for a short time until the data transfer was complete. + + See Also: + readNonBlocking: Read without blocking if no data is available. + readLatest: Read latest value while discarding intermediate updates. + + Returns: + None: This function does not return a value.)", py::call_guard()) .def("readNonBlocking", &ChimeraTK::VoidRegisterAccessor::readNonBlocking, R"(Read the next event, if available. @@ -84,25 +91,31 @@ namespace ChimeraTK { the current state before returning. Also this function is not guaranteed to be lock free. The return value will be always true in this mode. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("readLatest", &ChimeraTK::VoidRegisterAccessor::readLatest, R"(Read the latest event, discarding intermediate updates since the last read if present. Otherwise this function is identical to readNonBlocking(), i.e. it will never wait for new values and it will return whether a new value was available if AccessMode.wait_for_new_data is set. - :return: True if new data was available, false otherwise. - :rtype: bool)") + Returns: + bool: True if new data was available, false otherwise.)") .def("write", &PyVoidRegisterAccessor::write, R"(Trigger a write to the device (no payload). The return value is true if old data was lost on the write transfer (e.g. due to a buffer overflow). In case of an unbuffered write transfer, the return value will always be false. - :param versionNumber: Version number to use for this write operation. If not specified, a new version - number is generated. - :type versionNumber: VersionNumber)", + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new + version number is generated. + + Returns: + bool: True if old data was lost on the write transfer. + + See Also: + writeDestructively: Optimized write that may destroy buffer.)", py::arg("versionNumber") = PyVersionNumber::getNullVersion()) .def("writeDestructively", &PyVoidRegisterAccessor::writeDestructively, R"(Like write(), but allows the implementation to destroy the content of internal buffers in the process. @@ -110,72 +123,81 @@ namespace ChimeraTK { This is an optional optimisation, hence there is a default implementation which just calls write(). In any case, the application must expect internal buffers to contain undefined data after calling this function. - :param versionNumber: Version number to use for this write operation. If not specified, a new version - number is generated. - :type versionNumber: VersionNumber)", + Args: + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new + version number is generated. + + Returns: + bool: True if old data was lost on the write transfer.)", py::arg("versionNumber") = PyVersionNumber::getNullVersion()) .def("interrupt", &ChimeraTK::VoidRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. - This will cause a blocking read to return immediately and throw an InterruptedException.)") + This will cause a blocking read to return immediately and throw an InterruptedException. + + Returns: + None: This function does not return a value.)") .def("getName", &ChimeraTK::VoidRegisterAccessor::getName, - R"(Returns the name that identifies the process variable. + R"(Return the name that identifies the process variable. - :return: The register name. - :rtype: str)") + Returns: + str: The register name.)") .def("getUnit", &ChimeraTK::VoidRegisterAccessor::getUnit, - R"(Returns the engineering unit. + R"(Return the engineering unit. If none was specified, it will default to 'n./a.'. - :return: The engineering unit string. - :rtype: str)") + Returns: + str: The engineering unit string.)") .def("getDescription", &ChimeraTK::VoidRegisterAccessor::getDescription, - R"(Returns the description of this variable/register. + R"(Return the description of this variable/register. - :return: The description string. - :rtype: str)") + Returns: + str: The description string.)") .def("getValueType", &ChimeraTK::VoidRegisterAccessor::getValueType, - R"(Returns the type_info for the value type of this accessor. + R"(Return the type_info for the value type of this accessor. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: type)") - .def("setDataValidity", &PyVoidRegisterAccessor::setDataValidity, + Returns: + type: Type information object.)") + .def("setDataValidity", &PyVoidRegisterAccessor::setDataValidity, py::arg("validity"), R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (DataValidity): The data validity state to set. + + Returns: + None: This function does not return a value.)") .def("isInitialised", &ChimeraTK::VoidRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. - :return: True if initialised, false otherwise. - :rtype: bool)") + Returns: + bool: True if initialised, false otherwise.)") .def("getVersionNumber", &ChimeraTK::VoidRegisterAccessor::getVersionNumber, - R"(Returns the version number that is associated with the last transfer. + R"(Return the version number that is associated with the last transfer. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &ChimeraTK::VoidRegisterAccessor::isReadOnly, R"(Check if accessor is read only. This means it is readable but not writeable. - :return: True if read only, false otherwise. - :rtype: bool)") + Returns: + bool: True if read only, false otherwise.)") .def("isReadable", &ChimeraTK::VoidRegisterAccessor::isReadable, R"(Check if accessor is readable. - :return: True if readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if readable, false otherwise.)") .def("isWriteable", &ChimeraTK::VoidRegisterAccessor::isWriteable, R"(Check if accessor is writeable. - :return: True if writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if writeable, false otherwise.)") .def("getId", &ChimeraTK::VoidRegisterAccessor::getId, R"(Obtain unique ID for the actual implementation of this accessor. @@ -183,22 +205,22 @@ namespace ChimeraTK { the same ID, while two instances obtained by two different calls will have a different ID even when accessing the very same register. - :return: The unique transfer element ID. - :rtype: TransferElementID)") + Returns: + TransferElementID: The unique TransferElement ID.)") .def("getAccessModeFlags", &PyVoidRegisterAccessor::getAccessModeFlagsAsList, R"(Return the access mode flags that were used to create this accessor. This can be used to determine the setting of the raw and the wait_for_new_data flags. - :return: List of access mode flags. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: List of access mode flags.)") .def("dataValidity", &ChimeraTK::VoidRegisterAccessor::dataValidity, R"(Return current validity of the data. Will always return DataValidity.ok if the backend does not support it. - :return: The current data validity state. - :rtype: DataValidity)"); + Returns: + DataValidity: The current data validity state.)"); } /********************************************************************************************************************/ diff --git a/src/RegisterCatalogue.cc b/src/RegisterCatalogue.cc index aa08fd4..101a4d1 100644 --- a/src/RegisterCatalogue.cc +++ b/src/RegisterCatalogue.cc @@ -37,35 +37,45 @@ namespace DeviceAccessPython { .def(py::init(), "Catalogue of register information.") .def( "__iter__", [](const ChimeraTK::RegisterCatalogue& s) { return py::make_iterator(s.begin(), s.end()); }, - py::keep_alive<0, 1>()) - .def("_items", DeviceAccessPython::RegisterCatalogue::items) + py::keep_alive<0, 1>(), + R"(Return an iterator over visible registers in the catalogue. + + Returns: + iterator: Iterator yielding RegisterInfo objects.)") + .def("_items", DeviceAccessPython::RegisterCatalogue::items, + R"(Return all visible registers in the catalogue as a list. + + Returns: + list[deviceaccess.RegisterInfo]: List of visible RegisterInfo objects.)") .def("hiddenRegisters", DeviceAccessPython::RegisterCatalogue::hiddenRegisters, - R"(Returns list of all hidden registers in the catalogue + R"(Return list of all hidden registers in the catalogue. - :return: a list of hidden :class:`deviceaccess.RegisterInfo` objects. - :rtype: list[deviceaccess.RegisterInfo])") - .def("hasRegister", &ChimeraTK::RegisterCatalogue::hasRegister, + Returns: + list[deviceaccess.RegisterInfo]: A list of hidden RegisterInfo objects.)") + .def("hasRegister", &ChimeraTK::RegisterCatalogue::hasRegister, py::arg("registerPathName"), R"(Check if register with the given path name exists. - :param registerPathName: Full path name of the register. - :type registerPathName: str + Args: + registerPathName (str): Full path name of the register. - :return: True if register exists in the catalogue, false otherwise. - :rtype: bool)") + Returns: + bool: True if register exists in the catalogue, false otherwise.)") .def("getNumberOfRegisters", &ChimeraTK::RegisterCatalogue::getNumberOfRegisters, R"(Get number of registers in the catalogue. - :return: Number of registers in the catalogue. - :rtype: int)") + Returns: + int: Number of registers in the catalogue.)") .def("getRegister", &ChimeraTK::RegisterCatalogue::getRegister, py::arg("registerPathName"), R"(Get register information for a given full path name. - :param registerPathName: Full path name of the register. - :type registerPathName: str + Args: + registerPathName (str): Full path name of the register. + + Returns: + RegisterInfo: Register information. - :return: Register information. - :rtype: RegisterInfo - :raises ChimeraTK::logic_error: if register does not exist in the catalogue.)"); + Raises: + ChimeraTK::logic_error: If register does not exist in the catalogue.)"); } /*******************************************************************************************************************/ @@ -97,61 +107,61 @@ namespace DeviceAccessPython { .def("getDataDescriptor", DeviceAccessPython::RegisterInfo::getDataDescriptor, R"(Return description of the actual payload data for this register. - :return: :class:`deviceaccess.DataDescriptor` object containing information about the data format. - :rtype: DataDescriptor)") + Returns: + DataDescriptor: Object containing information about the data format.)") .def("isReadable", &ChimeraTK::RegisterInfo::isReadable, R"(Check whether the register is readable. - :return: True if the register is readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if the register is readable, false otherwise.)") .def("isValid", &ChimeraTK::RegisterInfo::isValid, R"(Check whether the RegisterInfo object is valid. - :return: True if the object contains a valid implementation, false otherwise. - :rtype: bool)") + Returns: + bool: True if the object contains a valid implementation, false otherwise.)") .def("isWriteable", &ChimeraTK::RegisterInfo::isWriteable, R"(Check whether the register is writeable. - :return: True if the register is writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if the register is writeable, false otherwise.)") .def("getRegisterName", DeviceAccessPython::RegisterInfo::getRegisterName, R"(Return the full path name of the register. - :return: Full path name of the register (including modules). - :rtype: RegisterPath)") + Returns: + RegisterPath: Full path name of the register (including modules).)") .def("getSupportedAccessModes", DeviceAccessPython::RegisterInfo::getSupportedAccessModes, R"(Return all supported AccessModes for this register. - :return: Flags indicating supported access modes. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: Flags indicating supported access modes.)") .def("getNumberOfElements", &ChimeraTK::RegisterInfo::getNumberOfElements, R"(Return the number of elements per channel. - :return: Number of elements per channel. - :rtype: int)") + Returns: + int: Number of elements per channel.)") .def("getNumberOfDimensions", &ChimeraTK::RegisterInfo::getNumberOfDimensions, R"(Return the number of dimensions of this register. - :return: Number of dimensions (0=scalar, 1=1D array, 2=2D array). - :rtype: int)") + Returns: + int: Number of dimensions (0=scalar, 1=1D array, 2=2D array).)") .def("getNumberOfChannels", &ChimeraTK::RegisterInfo::getNumberOfChannels, R"(Return the number of channels in the register. - :return: Number of channels. - :rtype: int)") + Returns: + int: Number of channels.)") .def("getQualifiedAsyncId", &ChimeraTK::RegisterInfo::getQualifiedAsyncId, R"(Get the fully qualified async::SubDomain ID. If the register does not support wait_for_new_data it will be empty. Note: At the moment using async::Domain and async::SubDomain is not mandatory yet, so the ID might be empty even if the register supports wait_for_new_data. - :return: List of IDs forming the fully qualified async::SubDomain ID. - :rtype: list[int])") + Returns: + list[int]: List of IDs forming the fully qualified async::SubDomain ID.)") .def("getTags", &ChimeraTK::RegisterInfo::getTags, R"(Get the list of tags that are associated with this register. - :return: Set of tags associated with this register. - :rtype: set[str])"); + Returns: + set[str]: Set of tags associated with this register.)"); } /*******************************************************************************************************************/ @@ -160,64 +170,64 @@ namespace DeviceAccessPython { .def("getDataDescriptor", &ChimeraTK::BackendRegisterInfoBase::getDataDescriptor, R"(Return description of the actual payload data for this register. - :return: :class:`deviceaccess.DataDescriptor` object containing information about the data format. - :rtype: DataDescriptor)") + Returns: + DataDescriptor: Object containing information about the data format.)") .def("isReadable", &ChimeraTK::BackendRegisterInfoBase::isReadable, R"(Return whether the register is readable. - :return: True if the register is readable, false otherwise. - :rtype: bool)") + Returns: + bool: True if the register is readable, false otherwise.)") .def("isWriteable", &ChimeraTK::BackendRegisterInfoBase::isWriteable, R"(Return whether the register is writeable. - :return: True if the register is writeable, false otherwise. - :rtype: bool)") + Returns: + bool: True if the register is writeable, false otherwise.)") .def("getRegisterName", &ChimeraTK::BackendRegisterInfoBase::getRegisterName, R"(Return full path name of the register. - :return: Full path name of the register (including modules). - :rtype: RegisterPath)") + Returns: + RegisterPath: Full path name of the register (including modules).)") .def("getSupportedAccessModes", &ChimeraTK::BackendRegisterInfoBase::getSupportedAccessModes, R"(Return all supported AccessModes for this register. - :return: Flags indicating supported access modes. - :rtype: list[AccessMode])") + Returns: + list[AccessMode]: Flags indicating supported access modes.)") .def("getNumberOfElements", &ChimeraTK::BackendRegisterInfoBase::getNumberOfElements, R"(Return number of elements per channel. - :return: Number of elements per channel. - :rtype: int)") + Returns: + int: Number of elements per channel.)") .def("getNumberOfDimensions", &ChimeraTK::BackendRegisterInfoBase::getNumberOfDimensions, R"(Return number of dimensions of this register. - :return: Number of dimensions (0=scalar, 1=1D array, 2=2D array). - :rtype: int)") + Returns: + int: Number of dimensions (0=scalar, 1=1D array, 2=2D array).)") .def("getNumberOfChannels", &ChimeraTK::BackendRegisterInfoBase::getNumberOfChannels, R"(Return number of channels in register. - :return: Number of channels. - :rtype: int)") + Returns: + int: Number of channels.)") .def("getQualifiedAsyncId", &ChimeraTK::BackendRegisterInfoBase::getQualifiedAsyncId, R"(Return the fully qualified async::SubDomain ID. The default implementation returns an empty vector. - :return: List of IDs forming the fully qualified async::SubDomain ID. - :rtype: list[int])") + Returns: + list[int]: List of IDs forming the fully qualified async::SubDomain ID.)") .def("getTags", &ChimeraTK::BackendRegisterInfoBase::getTags, R"(Get the list of tags associated with this register. The default implementation returns an empty set. - :return: Set of tags associated with this register. - :rtype: set[str])") + Returns: + set[str]: Set of tags associated with this register.)") .def("isHidden", &ChimeraTK::BackendRegisterInfoBase::isHidden, R"(Return whether the register is "hidden". Hidden registers won't be listed when iterating the catalogue, but can be explicitly iterated. - :return: True if the register is hidden, false otherwise. - :rtype: bool)"); + Returns: + bool: True if the register is hidden, false otherwise.)"); } /*******************************************************************************************************************/ @@ -231,13 +241,13 @@ namespace DeviceAccessPython { void DataDescriptor::bind(py::module& m) { py::class_(m, "DataDescriptor") .def(py::init()) - .def(py::init(), + .def(py::init(), py::arg("type"), R"(Construct a DataDescriptor from a DataType object. The DataDescriptor will describe the passed DataType with no raw type. - :param type: The data type to describe. - :type type: DataType)") + Args: + type (DataType): The data type to describe.)") .def(py::init<>(), R"(Default constructor. @@ -245,22 +255,22 @@ namespace DeviceAccessPython { .def("fundamentalType", DeviceAccessPython::DataDescriptor::fundamentalType, R"(Get the fundamental data type. - :return: The fundamental data type. - :rtype: FundamentalType)") + Returns: + FundamentalType: The fundamental data type.)") .def("isSigned", &ChimeraTK::DataDescriptor::isSigned, R"(Return whether the data is signed or not. Only valid for numeric data types. - :return: True if the data is signed, false otherwise. - :rtype: bool)") + Returns: + bool: True if the data is signed, false otherwise.)") .def("isIntegral", &ChimeraTK::DataDescriptor::isIntegral, R"(Return whether the data is integral or not. - May only be called for numeric data types. Examples: int or. float. + May only be called for numeric data types. Examples: int or float. - :return: True if the data is integral, false otherwise. - :rtype: bool)") + Returns: + bool: True if the data is integral, false otherwise.)") .def("nDigits", &ChimeraTK::DataDescriptor::nDigits, R"(Return the approximate maximum number of digits needed to represent the value. @@ -271,8 +281,8 @@ namespace DeviceAccessPython { this might be a large number (e.g. 300), which indicates that a different representation than plain decimal numbers should be chosen. - :return: Approximate maximum number of digits (base 10). - :rtype: int)") + Returns: + int: Approximate maximum number of digits (base 10).)") .def("nFractionalDigits", &ChimeraTK::DataDescriptor::nFractionalDigits, R"(Return the approximate maximum number of digits after the decimal dot. @@ -282,8 +292,8 @@ namespace DeviceAccessPython { Note: This number should only be used for displaying purposes. There is no guarantee that the full precision can be displayed with the given number of digits. - :return: Approximate maximum number of fractional digits (base 10). - :rtype: int)") + Returns: + int: Approximate maximum number of fractional digits (base 10).)") .def("rawDataType", &ChimeraTK::DataDescriptor::rawDataType, R"(Get the raw data type. @@ -293,16 +303,16 @@ namespace DeviceAccessPython { Most backends will have type 'none' (no raw data conversion available). - :return: The raw data type. - :rtype: DataType)") + Returns: + DataType: The raw data type.)") .def("setRawDataType", &ChimeraTK::DataDescriptor::setRawDataType, py::arg("rawDataType"), R"(Set the raw data type. This is useful e.g. when a decorated register should no longer allow raw access, in which case you should set DataType.none. - :param rawDataType: The raw data type to set. - :type rawDataType: DataType)") + Args: + rawDataType (DataType): The raw data type to set.)") .def("transportLayerDataType", &ChimeraTK::DataDescriptor::transportLayerDataType, R"(Get the data type on the transport layer. @@ -317,15 +327,15 @@ namespace DeviceAccessPython { Note: Currently all implementations return 'none'. There is no public API to access the transport layer data yet. - :return: The transport layer data type. - :rtype: DataType)") + Returns: + DataType: The transport layer data type.)") .def("minimumDataType", &ChimeraTK::DataDescriptor::minimumDataType, R"(Get the minimum data type required to represent the described data type. This is the minimum data type needed in the host CPU to represent the value. - :return: The minimum required data type. - :rtype: DataType)"); + Returns: + DataType: The minimum required data type.)"); } /*******************************************************************************************************************/ diff --git a/src/deviceaccessPython.cc b/src/deviceaccessPython.cc index b2d44d3..e8db511 100644 --- a/src/deviceaccessPython.cc +++ b/src/deviceaccessPython.cc @@ -50,20 +50,23 @@ PYBIND11_MODULE(deviceaccess, m) { m.def("setDMapFilePath", ChimeraTK::setDMapFilePath, py::arg("dmapFilePath"), R"(Set the location of the dmap file. - :param dmapFilePath: Relative or absolute path of the dmap file (directory and file name). - :type dmapFilePath: str)"); + Args: + dmapFilePath (str): Relative or absolute path of the dmap file (directory and file name). + + Returns: + None: This function does not return a value.)"); m.def("getDMapFilePath", ChimeraTK::getDMapFilePath, - R"(Returns the dmap file name which the library currently uses for looking up device(alias) names. + R"(Return the dmap file name which the library currently uses for looking up device(alias) names. - :return: Path of the dmap file (directory and file name). - :rtype: str)"); + Returns: + str: Path of the dmap file (directory and file name).)"); py::enum_(m, "AccessMode", R"(Access mode flags for register access. - Note: - Using the raw flag will make your code intrinsically dependent on the backend type, since the actual raw data type must be known.)") + Note: + Using the raw flag makes code dependent on the backend type, since the actual raw data type must be known.)") .value("raw", ChimeraTK::AccessMode::raw, R"(This access mode disables any possible conversion from the original hardware data type into the given UserType. Obtaining the accessor with a UserType unequal to the actual raw data type will fail and throw an exception.)") .value("wait_for_new_data", ChimeraTK::AccessMode::wait_for_new_data, @@ -88,67 +91,171 @@ PYBIND11_MODULE(deviceaccess, m) { communication errors with a device, rather to signalize the consumer after such an error that the data is currently not trustable, because we are performing calculations with the last known valid data, for example.)") - .value("ok", ChimeraTK::DataValidity::ok, "The data is considered valid") - .value("faulty", ChimeraTK::DataValidity::faulty, "The data is not considered valid") + .value("ok", ChimeraTK::DataValidity::ok, "The data is considered valid.") + .value("faulty", ChimeraTK::DataValidity::faulty, "The data is not considered valid.") .export_values(); py::class_(m, "TransferElementID") - .def("isValid", &ChimeraTK::TransferElementID::isValid, "Check whether the ID is valid.") - .def("__ne__", &ChimeraTK::TransferElementID::operator!=) - .def("__hash__", - [](const ChimeraTK::TransferElementID& self) { return std::hash{}(self); }) - .def("__eq__", &ChimeraTK::TransferElementID::operator==); + .def("isValid", &ChimeraTK::TransferElementID::isValid, + R"(Check whether the ID is valid. + + Returns: + bool: `True` if the ID is valid, `False` otherwise.)") + .def("__ne__", &ChimeraTK::TransferElementID::operator!=, py::arg("other"), + R"(Compare two TransferElement IDs for inequality. + + Args: + other (TransferElementID): ID to compare with. + + Returns: + bool: True if the IDs are different, false otherwise.)") + .def( + "__hash__", + [](const ChimeraTK::TransferElementID& self) { return std::hash{}(self); }, + R"(Return the hash value of this TransferElement ID. + + Returns: + int: Hash value.)") + .def("__eq__", &ChimeraTK::TransferElementID::operator==, py::arg("other"), + R"(Compare two TransferElement IDs for equality. + + Args: + other (TransferElementID): ID to compare with. + + Returns: + bool: True if the IDs are equal, false otherwise.)"); py::class_(m, "RegisterPath", - R"a(Class to store a register path name. Elements of the path are separated by a "/" character, but an separation character (e.g. ".") can optionally be specified as well. Different equivalent notations will be converted into a standardised notation automatically.)a") - .def(py::init()) - .def(py::init(), py::arg("path")) - .def("__str__", &ChimeraTK::RegisterPath::operator std::string) + R"(Class to store a register path name. + + Elements of the path are separated by a "/" character, but an alternative separator character such as "." + can optionally be specified as well. Different equivalent notations are converted into a standardised notation + automatically.)") + .def(py::init<>(), + R"(Create an empty RegisterPath. + + Returns: + RegisterPath: Empty register path.)") + .def(py::init(), py::arg("other"), + R"(Create a RegisterPath by copying another RegisterPath. + + Args: + other (RegisterPath): Path to copy.)") + .def(py::init(), py::arg("path"), + R"(Create a RegisterPath from a path string. + + Args: + path (str): Register path string.)") + .def("__str__", &ChimeraTK::RegisterPath::operator std::string, + R"(Return the path as a string. + + Returns: + str: Register path string.)") .def("setAltSeparator", &ChimeraTK::RegisterPath::setAltSeparator, py::arg("altSeparator"), R"(Set alternative separator. - :param altSeparator: Alternative separator character to use instead of "/". Use an empty string to reset to default. - :type altSeparator: str)") + Args: + altSeparator (str): Alternative separator character to use instead of "/". Use an empty string to reset to default. + + Returns: + None: This function does not return a value.)") .def("getWithAltSeparator", &ChimeraTK::RegisterPath::getWithAltSeparator, R"(Obtain path with alternative separator character instead of "/". The leading separator will be omitted. - :return: Register path with alternative separator. - :rtype: str)") + Returns: + str: Register path with alternative separator.)") .def("__itruediv__", &ChimeraTK::RegisterPath::operator/=, py::arg("rightHandSide"), R"(Modify this object by adding a new element to this path. - :param rightHandSide: New element to add to the path. - :type rightHandSide: str + Args: + rightHandSide (str): New element to add to the path. - :return: Modified RegisterPath object. - :rtype: RegisterPath)") + Returns: + RegisterPath: Modified RegisterPath object.)") .def("__iadd__", &ChimeraTK::RegisterPath::operator+=, py::arg("rightHandSide"), R"(Modify this object by concatenating the given string to the path. - :param rightHandSide: String to concatenate to the path. - :type rightHandSide: str + Args: + rightHandSide (str): String to concatenate to the path. - :return: Modified RegisterPath object. - :rtype: RegisterPath)") - .def("__lt__", &ChimeraTK::RegisterPath::operator<) + Returns: + RegisterPath: Modified RegisterPath object.)") + .def("__lt__", &ChimeraTK::RegisterPath::operator<, py::arg("other"), + R"(Compare two paths lexicographically. + + Args: + other (RegisterPath): Path to compare with. + + Returns: + bool: True if this path is lexicographically smaller, false otherwise.)") .def("length", &ChimeraTK::RegisterPath::length, R"(Get the length of the path (including leading slash). - :return: Length of the register path. - :rtype: int)") - .def("startsWith", &ChimeraTK::RegisterPath::startsWith) - .def("endsWith", &ChimeraTK::RegisterPath::endsWith) + Returns: + int: Length of the register path.)") + .def("startsWith", &ChimeraTK::RegisterPath::startsWith, py::arg("prefix"), + R"(Check whether the path starts with the given prefix. + + Args: + prefix (RegisterPath): Prefix to check. + + Returns: + bool: True if this path starts with prefix, false otherwise.)") + .def("endsWith", &ChimeraTK::RegisterPath::endsWith, py::arg("suffix"), + R"(Check whether the path ends with the given suffix. + + Args: + suffix (RegisterPath): Suffix to check. + + Returns: + bool: True if this path ends with suffix, false otherwise.)") .def("getComponents", &ChimeraTK::RegisterPath::getComponents, R"(Split path into components. - :return: list of path components. - :rtype: list[str])") - .def("__ne__", - [](const ChimeraTK::RegisterPath& self, const ChimeraTK::RegisterPath& other) { return self != other; }) - .def("__ne__", [](const ChimeraTK::RegisterPath& self, const std::string& other) { return self != other; }) - .def("__eq__", - [](const ChimeraTK::RegisterPath& self, const ChimeraTK::RegisterPath& other) { return self == other; }) - .def("__eq__", [](const ChimeraTK::RegisterPath& self, const std::string& other) { return self == other; }); + Returns: + list[str]: List of path components.)") + .def( + "__ne__", + [](const ChimeraTK::RegisterPath& self, const ChimeraTK::RegisterPath& other) { return self != other; }, + py::arg("other"), + R"(Compare two RegisterPath objects for inequality. + + Args: + other (RegisterPath): Path to compare with. + + Returns: + bool: True if the paths are different, false otherwise.)") + .def( + "__ne__", [](const ChimeraTK::RegisterPath& self, const std::string& other) { return self != other; }, + py::arg("other"), + R"(Compare RegisterPath and string for inequality. + + Args: + other (str): Path string to compare with. + + Returns: + bool: True if the paths are different, false otherwise.)") + .def( + "__eq__", + [](const ChimeraTK::RegisterPath& self, const ChimeraTK::RegisterPath& other) { return self == other; }, + py::arg("other"), + R"(Compare two RegisterPath objects for equality. + + Args: + other (RegisterPath): Path to compare with. + + Returns: + bool: True if the paths are equal, false otherwise.)") + .def( + "__eq__", [](const ChimeraTK::RegisterPath& self, const std::string& other) { return self == other; }, + py::arg("other"), + R"(Compare RegisterPath and string for equality. + + Args: + other (str): Path string to compare with. + + Returns: + bool: True if the paths are equal, false otherwise.)"); py::implicitly_convertible(); diff --git a/tests/documentationExamples/someCrate.dmap b/tests/documentationExamples/someCrate.dmap new file mode 100644 index 0000000..94970d2 --- /dev/null +++ b/tests/documentationExamples/someCrate.dmap @@ -0,0 +1,3 @@ +# DEVICE_LABEL backend_specification +someDummyDevice (dummy?map=./someDummyModule.map) +someSharedDummyDevice (sharedMemoryDummy?map=someDummyModule.map) diff --git a/tests/documentationExamples/someDummyModule.map b/tests/documentationExamples/someDummyModule.map new file mode 100644 index 0000000..0ead20f --- /dev/null +++ b/tests/documentationExamples/someDummyModule.map @@ -0,0 +1,5 @@ +# name nr of elements address size bar width fracbits signed access +SENSORS.TEMPERATURE 0x01 0x00000000 0x00000004 0x0 32 4 1 RW +SENSORS.SET_POINT 0x01 0x00000004 0x00000004 0x0 32 4 1 RW + +SENSORS.WAVEFORM 0x08 0x00000008 0x00000020 0x0 32 0 1 RW diff --git a/tests/testDocExamples.py b/tests/testDocExamples.py new file mode 100644 index 0000000..07685e9 --- /dev/null +++ b/tests/testDocExamples.py @@ -0,0 +1,67 @@ +import unittest + + +class TestDocExamples(unittest.TestCase): + + def simpleScalarAccess(self): + import deviceaccess as da + + da.setDMapFilePath("documentationExamples/someCrate.dmap") + device = da.Device("someDummyDevice") + device.open() + + # Read a value + temperature = device.getScalarRegisterAccessor(float, "SENSORS.TEMPERATURE") + temperature.read() + print(f"Current temperature: {float(temperature)} °C") + + # Write a value + setpoint = device.getScalarRegisterAccessor(float, "SENSORS.SET_POINT") + setpoint.set(25.0) + setpoint.write() + + # Verify the write + setpoint.read() + print(f"Setpoint is now: {float(setpoint)} °C") + + def simpleOneDAccess(self): + import deviceaccess as da + import numpy as np + + da.setDMapFilePath("documentationExamples/someCrate.dmap") + device = da.Device("someDummyDevice") + device.open() + + # Get 1D accessor + waveform = device.getOneDRegisterAccessor(int, "SENSORS.WAVEFORM") + waveform.read() + + # Access like a list: + [print(f"Waveform element {i}: {waveform[i]}") for i in range(len(waveform))] + + # Scale all values by a factor of 2 + waveform *= 2 + + # Write back the modified waveform + waveform.write() + + # Read back to verify + waveform.read() + print("Modified waveform data:", waveform) # accessor can be used as a numpy array or python list + + # slicing also works + print("First 3 elements:", waveform[:3]) + print("Last 2 elements:", waveform[-2:]) + print("Elements 2 to 4:", waveform[1:4]) + print("Every other element:", waveform[::2]) + + def testExamples(self): + # Get all methods defined directly in this class (not inherited) + test_methods = [ + getattr(self, name) for name in vars(self.__class__) + if callable(getattr(self.__class__, name)) and name != 'testExamples' and not name.startswith('_') + ] + + # Call all discovered methods + for method in test_methods: + method()