From 60c2c4eb597513569f06c4a329bbfc026353780d Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Tue, 27 Jan 2026 09:09:18 +0100 Subject: [PATCH 1/5] feat: improve docs --- doc/ChimeraTK_Favicon.ico | Bin 0 -> 15406 bytes doc/api_reference.rst | 60 ++ doc/conf.py.in | 41 +- doc/deviceaccess.rst | 3 +- doc/examples.rst | 263 +++++++++ doc/faq.rst | 373 ++++++++++++ doc/getting_started.rst | 174 ++++++ doc/index.rst | 106 +++- doc/mtca4u.rst | 2 +- doc/overview.rst | 90 +++ doc/troubleshooting.rst | 532 ++++++++++++++++++ doc/user_guide.rst | 441 +++++++++++++++ pyproject.toml | 8 +- src/PyDataType.cc | 16 +- src/PyDevice.cc | 266 ++++----- src/PyOneDRegisterAccessor.cc | 126 +++-- src/PyScalarRegisterAccessor.cc | 144 ++--- src/PyTwoDRegisterAccessor.cc | 124 ++-- src/PyVersionNumber.cc | 12 +- src/PyVoidRegisterAccessor.cc | 87 +-- src/RegisterCatalogue.cc | 158 +++--- src/deviceaccessPython.cc | 40 +- tests/documentationExamples/someCrate.dmap | 1 + .../documentationExamples/someDummyModule.map | 5 + tests/testDocExamples.py | 51 ++ 25 files changed, 2640 insertions(+), 483 deletions(-) create mode 100644 doc/ChimeraTK_Favicon.ico create mode 100644 doc/api_reference.rst create mode 100644 doc/examples.rst create mode 100644 doc/faq.rst create mode 100644 doc/getting_started.rst create mode 100644 doc/overview.rst create mode 100644 doc/troubleshooting.rst create mode 100644 doc/user_guide.rst create mode 100644 tests/documentationExamples/someCrate.dmap create mode 100644 tests/documentationExamples/someDummyModule.map create mode 100644 tests/testDocExamples.py diff --git a/doc/ChimeraTK_Favicon.ico b/doc/ChimeraTK_Favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..140b69f0e7881c0faab3608c791a3f390f600053 GIT binary patch literal 15406 zcmeHN33y&rnf}>C_N5@xMOzS=f}_LCD2@myZWJgZE!IkDrIfNMf-`IiI*dBZhzfN< zq%BK3f^0)ex3uY&(l$wxW^0plNtgUdnl@>c|Nrmy-~IT$-_6a|Buz@u=TYwS+}uCs z-gC}(zVj{b_kAakI3{si;>c#cWjeS9KuSt5}bGbZ|f>{*G#i)g#_(vJ5RB@zYa zCK6-u4Z5I5)Q-oHFIAQJ0Pmr6%$DCj#%nKl%GY=8^sW7lZ#6o;UbE9{+wgR@{H`$r zjpfw8GBb4Ab=B<(!%+EFg9<%E1$L{!Z~7X8?RNdEf!ma;Lf`Hj!EIin+=J_so239GRp46?}f^3TBXdYM-BFg z>F`=M&TyG7m05nT^4bcY zj{Ac@HG!K^cKs_Vv-Z;}y?l&HFB`5>OU^>SGgLbFV`v*Kbft4fU>z-reb~PyCvjr7 z_63KuWp_WL%q`zj*_x+RcJEx$sm#iUm0kCLcs`=+eQzme{}N?Y{Sa;Q9QKX+&vn>t zR(w}sEamK50GgjyZ|th!KH2E27yO&j zHhdNQz5#ufD(#)C(C;?{;4*HK_fAC@!8?9|WR_PVztJI=Xm7RPEbNxhV z>prK#FzDRR_=ne$KZ|{Nr7G?{KXBTOAbSAwJS%jDepZ=VAHshB4Exuh0;etR3jVbj z+3j8X$1$9SKi&~!n*y#mTQf^|sBOFkyuVOo@;;;7mUp85z8&ye_UV1G-8c3aVPHcB zv)H3uB17a2FVfgw-*_z2<+bI{B7Y+NIRO3HRYM;2raA7@-!tCd{@x#rcG{USxW9m# z_UW(bQg0-VYtB3O)b>?poxxk-H{0IJH6F-4?-a9a%Gd0@&&_q4mX~<#CGB3S%$B=# zQ_5>vR$}dbX0BN@X+rZG=bn1BXlZ$S*lBjv4<7JZHq`sno&` zDEiFMca*Vt8f^Y`%55t$4!0K}*~5pE$^2G( z=L+%V{NFcgRqE{%WnOs?8uY>m%7mYGn{uiKl1Z!bk%ziujcW~s|6ZzGrI(zoQVSAF zTmJoM(Ir8jWcndjj{hh@C(;MUZfUA zv}Axoe4`fo^zG)J8FkM-d--q8mNoa_A%=o%TlTR9f|`x0>3M>^h} z2bsBAg>JfYaG9%{zo1Nd?L}$I4rP^i$*TU5$P}?o7IIIl%6+6Ry3MPBVIIKyGLdt; z;g8s>pA-8DmNd)m2HyEK@bNy`6Uq?xfqP3F=p9^_?yd*)Velu}%$k3Oo;L!Iv_?AH zyb}BPb>XGc@CIzb4KgorBk`MAaj(3m{8P`pwoO8-zUe05;=2Ur=!G}Ry0v$%R0d>1 za96q_%Q@pc=rKRAICVhZ2pNQ3F}FU5vF;Y^nOTcHrcJ^)dcoI}+xWK7NepY2--Gs> zfNy6D|B2WA%r>Dp3|yf%oqLh;GS$lOHNWsbRg!iZUdMRjJMiiapntd6a$-)(H^-q& z5UY|#>X!Tu8$_E=Y!`i_K`)z?UNjl=ejYaQY0&p=?BQBq?8jgW3p3sMAIV>?$RGEL zdcc?@yX)7YgJ#8jvX9g+%0L#nkNpyvrkq4_0eiXqzeR4OZ5w0-xS#f+_l0<*Jn!)Y zqQkDsPR=Jpt}pvbDy+)K9){2Cbh6|rW!>EJ?}+~{lQ@pCCokH3+H~5)(9@(%WT(S#NJFnb z^letgfd;EWE&e^BCO)Iuhu6c1T#wzZC<*+4^?8Nhuy0VHx7&mwfyU@^7Wm9yJ+)hYb?mJ#ICS|cI{l{)WhEG_+PQdsiniPFCP|L zLAz^1_NarsFOF$#y#C5~tphy~)6Ya~D1%V1X$K@;PtF(nO24KTPr>>TgCp+N@~@5J zaM&la@Y?9S zkhH6R_z?T{9`8v1r+uK^AWotGcHyt-M|ivKC28nQ88Gk*W-;s-zCF@;2GfuJg8qYk zlQ)0A*YW3I=z9N~p>u$F5~OGo({cZ`xl_pg39ppawDtQs-QSbom%)+J|;G?$z- z5_&vBTRi-n+PuUE`|0)H}pIWLKKE3B>e&N9npe%&s+<&xTkqt8^2CyxDd6R)6B2%5R@)t{yd#y!dmhgE?BO z;QCvf-+UABWno|) z@F5^Wr(I-7_)gvnE+#gPFAfRp?7Q`^l$xbeCaRnZPdplHpj=w{*G%)8*EA|Wi{xL| zoblX)c`|25Y-uOwq2>du-LyhtjUM=tYk-b$E_2h3cT&&Z%^Ju9tLTPX5tpVQtHAHj z!6Trbak<;P3NgqEtl=%G^Du5@?aXT{?i`cfAr~hx48F}|i*CB(NcRDJKh`XrdXw9{ zvKIBgkhwb48-;JsTi6oRwFbEs#?P76mr5>&vJ)cz3t5-gS4ZuP^(yK=a|6uJNlpPV zfY-dbk#c(^`jFl5sS8kFEDM}W;5076{GXJ#E`^v%L+o#$_Q!k^^Be=N18Zcw#{4t$ zhf%%@@dx5$#?Yw+$07IesKjQl3CLqTCpA2&p&>8o*1uY+ueti;Ly@EK^-(8TTfa9a z$Ts^h*oZ%WgIwE}kgu8{Ig!-TbCEYh4sg-w$WMJs!S+Y7VITWJd1IZH`8?)mVtEgJ zQ}SPo^E1dFNp6xDe|X7!Kesl~$wWm`pG3$FWy*H)m#vd(-zWgu|U z64x>B#N2sm!LgESWsVcR2>ag>$uejTeJi^0oADrXl+44$7h`zTGSL4x-dg4gXrCuE1ZMjGNv+C2;P-Hp-yBkvWh;dt2#${B0IM%f*ba|~m8 z)0Oytj`U&eLNEG8N1h1skGiwu2ssaK>Xcq|v(Qf&VtzKT>UFF9-l>ldfR|VcwD-)O z4|x%}r_Eu_2f2ONYSf<~6U@=D79%<9rRPAdM@SA{WQp>Gya{!k`kG$xQOOI_<};Vb zoC5PcteJ=oy^UNuay`t4F~=i&2Y&G^0P`QL1F$A8^aH23$roNyz>EFuLAG%681LZ9 z-Pm8S*;ABr=5|R7YYfKbTah=t8uAL=MJ``*Z#?V3GXR_qc^7GC?q6=^2eChkaV}!f zY2v?FqoKY;>&1KI$!K%f#@sdY+R->CVB8DkJOuTDc*#4sW|v)d+a>+cZ)_nj7s^!XZR}4>3%->+E!V}|BxdM{*A=5x-8pWL90$LH|tID+jd) z;v&{x=#N;3mAVwyX@}Y ziTtz9#@ZbBlRTkbc$q5r6vQvo575e5pp1k18}i6F|Av}0=q1i$96R*llzr4GtkqgC|-{JsxZ0@#Jv2WJtq{Bh8)1;P`tP0TOgU37QE zIm80e7X2T6$8WlSi#i74dzqFs( z9Y0+b+s}Aj+F#;IFSR9#p};rlkp-K<+ArfO2YcW^w_WG}^__S+fX$%2VS9A10LCm7 zJ*WJ2wc|bdGnOC*rO(6yyIIYypQH_S;??2niIaNE5C1nyZ~HkgayS1K^(D5-?tBuq zV}i&kWtcWYY&Oqc()ZAhK~AY3tjn^Fr5B7x8_zgIx8NMej)9sj?Ry6GMb<$jwkVw; zc!g&Q`=g(})!Opi-}V<5+hvolg^UV*=vp<7Mu7M9`8)^8HHfdJFD2&TQ!vHpf-8t$ zSd;U$ZNL&^d-!1Lf!H+obDw8Ip%ZcwUkXme`J{e$PdV_?m8M;B&mlF^crUEdiKDTP zHN-I^;!AXf75F(X8hi{2HI>FIL$__nAnc8P8&+xBWE$XJ{3RD zb1TAs=m*bx&>lx?!`UO8p`b4#PY!pH1TR&r8wJ;X>u|h}*TNcvvGu-ZnUCz`6UHH5 zp&Mcw1Nc_48RCl~esUeekn|VC3&i|94@DmY+b2Aa@i)(uQ&$GOFehoinbGG-Q%v7r zp4r^X&d2$uBIdZch5)w1-t(N~NqB~Y=d;O2o~a_XM{UOT+U{#DLNIJGc1IrQqzh-SBdez3!@hZBBn{c>}dvt7OU~zQjH-k@AjkGUS^JtRg)eusXb^Sli5ZHzy8{>ZL>4Kj4-nFua~vgNhp z?YBy%O#(JJqI|k4Nt~b;e07R*U}-9rRmvmxo!HE*0FK1jSmtuLA9AJ(7>4Jg#J4d& zftZ*0nK2dZ7x5-_KfC)^hxQn(@vey3%)!BI42p) zk`Etm?RY{kORSfaG1^awcYssnJQwnYjA0qWNleAFVFO(lgE@NcxsiY4jDd1JgP}eC zhCJXtQWmLCg6){w>aZz2Na8v=ap(ZD-J=s#8@-TkbsJtUvx=rns(oqnQLR;CAC65y zr>!mDdDd&+gafHP{X;KuhV(DwC4`OCa87n^dd=k@=y&Li8e5>Ny)7ln}_%O$Ow{#7)Kk*WCrdIyM(MIv)Yt54B zPvT73GN*R#9{7lqyYEenH{zTW@*aC}=61PRGUG|RX!3aGDv1~W5_Au=PR4Qj7MwPW d@icQ9y!ktWHohCE@B8oG=fL|M_}k*Zp8!^luYmvn literal 0 HcmV?d00001 diff --git a/doc/api_reference.rst b/doc/api_reference.rst new file mode 100644 index 0000000..fb7f234 --- /dev/null +++ b/doc/api_reference.rst @@ -0,0 +1,60 @@ +API Reference +============= + +Complete API documentation for the ChimeraTK DeviceAccess Python bindings. + + +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..9a3cfe9 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,19 @@ autodoc_mock_imports = ["_da_python_bindings"] # ones. extensions = [ 'sphinx.ext.autodoc', + '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 +126,30 @@ 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 + +# -- 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' + # -- 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 @@ -150,7 +182,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..0a5b847 --- /dev/null +++ b/doc/examples.rst @@ -0,0 +1,263 @@ +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 + DUMMY_DEVICE (dummy_name_prefix:?) + MODBUS_DEVICE (modbus://192.168.1.100?address_list=device.xml) + SERIAL_DEVICE (serial:///dev/ttyUSB0?speed=115200) + + +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("DUMMY_DEVICE") + modbus = deviceaccess.Device("MODBUS_DEVICE") + serial = deviceaccess.Device("SERIAL_DEVICE") + + # All use the same accessor interface + for device in [dummy, modbus, serial]: + value = device.getScalarRegisterAccessor("MEASUREMENT") + value.read() + print(f"Value: {float(value)}") + + +.. _transfer_groups_python: + +Synchronized Access with Transfer Groups +----------------------------------------- + +Use transfer groups to read/write multiple registers atomically: + +.. code-block:: python + + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + # Create a transfer group + group = device.getTransferGroup() + + # Add registers to the group + voltage = device.getScalarRegisterAccessor("VOLTAGE") + current = device.getScalarRegisterAccessor("CURRENT") + power = device.getScalarRegisterAccessor("POWER") + + group.addAccessor(voltage) + group.addAccessor(current) + group.addAccessor(power) + + # Read all at once + group.read() + + # All values are from the same hardware snapshot + print(f"V={float(voltage)}, I={float(current)}, P={float(power)}") + + # Write all at once + voltage.write(230.0) + current.write(10.0) + power.write(2300.0) + + group.write() + + +.. _data_consistency_python: + +Data Consistency Groups +----------------------- + +For reading coherent data across multiple samples: + +.. code-block:: python + + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + # Create a data consistency group + consistency_group = device.getDataConsistencyGroup() + + # Add accessors for channels + channels = [] + for i in range(4): + channel = device.getScalarRegisterAccessor(f"CHANNEL_{i}") + channels.append(channel) + consistency_group.addAccessor(channel) + + # Read all channels with guaranteed consistency + consistency_group.read() + + # Process the consistent data + values = [float(ch) for ch in channels] + print(f"Channel values: {values}") + + +Error Handling +-------------- + +Robust code includes proper error handling: + +.. code-block:: python + + import deviceaccess + + try: + device = deviceaccess.Device("MY_DEVICE") + except deviceaccess.DoocsException as e: + print(f"Failed to open device: {e}") + exit(1) + + try: + register = device.getScalarRegisterAccessor("MEASUREMENT") + register.read() + value = float(register) + print(f"Read value: {value}") + except deviceaccess.DoocsException as e: + print(f"Read operation failed: {e}") + except ValueError as e: + print(f"Type conversion failed: {e}") + + +Monitoring Values Over Time +---------------------------- + +Read values periodically: + +.. code-block:: python + + import deviceaccess + import time + + device = deviceaccess.Device("MY_DEVICE") + temperature = device.getScalarRegisterAccessor("TEMPERATURE") + + readings = [] + for i in range(10): + temperature.read() + value = float(temperature) + readings.append(value) + print(f"Reading {i+1}: {value} °C") + + if i < 9: + time.sleep(1.0) + + print(f"Average: {sum(readings) / len(readings)} °C") + + +Batch Operations +---------------- + +Efficiently perform multiple operations: + +.. code-block:: python + + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + # Get multiple accessors at once + registers = { + "voltage": device.getScalarRegisterAccessor("VOLTAGE"), + "current": device.getScalarRegisterAccessor("CURRENT"), + "frequency": device.getScalarRegisterAccessor("FREQUENCY"), + } + + # Read all + for accessor in registers.values(): + accessor.read() + + # Process results + status = {name: float(accessor) for name, accessor in registers.items()} + print(f"Device status: {status}") + + +Integration with NumPy and Pandas +--------------------------------- + +Working with scientific Python libraries: + +.. code-block:: python + + import deviceaccess + import pandas as pd + import numpy as np + + device = deviceaccess.Device("MY_DEVICE") + + # Collect time-series data + data = [] + for i in range(100): + waveform = device.getArrayRegisterAccessor("WAVEFORM") + waveform.read() + + data.append({ + 'timestamp': i, + 'mean': np.mean(waveform[:]), + 'std': np.std(waveform[:]), + 'min': np.min(waveform[:]), + 'max': np.max(waveform[:]), + }) + + # Create DataFrame for analysis + df = pd.DataFrame(data) + print(df.describe()) + +.. _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..8da2e80 --- /dev/null +++ b/doc/faq.rst @@ -0,0 +1,373 @@ +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: ``pip install chimeratk-deviceaccess`` or build from source with CMake. + + +**Q: What Python versions are supported?** + +A: Python 3.6 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("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.write(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.write(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("VALUE") + scalar.write(42.0) + scalar.write() + + # Array + array = device.getArrayRegisterAccessor("DATA") + 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 access the value: + + .. code-block:: python + + register.read() + + float_val = float(register) # Converts to float + int_val = int(register) # Converts to int + str_val = str(register) # Converts to string + + +**Q: Can I specify a type when getting an accessor?** + +A: The accessor is obtained with the hardware type information already known. + Type conversion happens at access time: + + .. code-block:: python + + # Get accessor for this hardware register + register = device.getScalarRegisterAccessor("TEMPERATURE") + register.read() + + # Convert to the type you want + celsius = float(register) + fahrenheit = celsius * 9 / 5 + 32 + + +**Q: What happens if I try to convert to an incompatible type?** + +A: You'll get a ``ValueError`` or similar exception. Always handle conversion errors: + + .. code-block:: python + + try: + value = int(register) + except (ValueError, TypeError) as e: + print(f"Cannot convert: {e}") + + +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: Where should I put my device map file?** + +A: Typically in your project's configuration directory. You then tell the application + where to find it, usually through an environment variable or configuration file. + + ``export DEVICE_MAP_FILE=/path/to/devices.dmap`` + + +**Q: How do I debug device map issues?** + +A: - Check that the file exists and is readable + - Verify the syntax is correct (see the user guide) + - Try opening a device and catch exceptions for error messages + - Set debug environment variables (see troubleshooting) + + +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 + + .. code-block:: python + + # All read together + group = device.getTransferGroup() + group.addAccessor(voltage) + group.addAccessor(current) + group.read() # One operation + + +**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 semantics across multiple accesses + + Use transfer groups for most cases. Data consistency groups are for advanced scenarios. + + +**Q: Do transfer groups improve performance?** + +A: Yes, typically. Instead of multiple hardware operations (one per register), + a transfer group uses a single operation for all registers. + + +Error Handling +-------------- + +**Q: What exceptions can the library raise?** + +A: The main ones are: + + - ``DoocsException`` - DOOCS backend errors + - ``DeviceException`` - Device access errors + - ``TimeoutException`` - Operation timeout + - ``NotImplemented`` - Feature not supported + + See the API reference for a complete list. + + +**Q: How should I handle read/write errors?** + +A: Always wrap hardware operations in try-except: + + .. code-block:: python + + import deviceaccess + + try: + register.read() + except deviceaccess.TimeoutException: + print("Read timed out") + except deviceaccess.DoocsException as e: + print(f"Device error: {e}") + + +**Q: The device opens but register access fails. What's wrong?** + +A: Several possibilities: + + - Register name is wrong or doesn't exist + - Device connection was lost + - Hardware is offline or not responding + - Permission issues with the device + + Enable debug logging and check the error message. See :doc:`troubleshooting`. + + +Performance and Optimization +----------------------------- + +**Q: How can I improve performance for many reads?** + +A: - Use transfer groups for multiple related registers + - Reuse accessors instead of creating new ones each time + - Minimize how often you call read/write + - Consider caching values if they change infrequently + + See :doc:`user_guide` for optimization strategies. + + +**Q: Is there an asynchronous API?** + +A: The synchronous API is the standard. For asynchronous access: + + - Use Python threading to run read/write in background threads + - Consider thread pools for high-volume operations + - Look into the ``threading`` or ``asyncio`` modules + + +**Q: How much memory do accessors use?** + +A: Memory usage is proportional to the data size. Array accessors for large arrays + will use more memory. Generally not a concern unless working with many large accessors. + + +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: + + - Dummy (for testing) + - DOOCS (common in accelerators) + - Modbus TCP/RTU + - Others depending on your build + + Check your device map documentation or ChimeraTK docs for available backends. + + +**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.getArrayRegisterAccessor("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.getArrayRegisterAccessor("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! NumPy is very useful for array operations: + + .. code-block:: python + + import numpy as np + + array = device.getArrayRegisterAccessor("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/getting_started.rst b/doc/getting_started.rst new file mode 100644 index 0000000..87d5c12 --- /dev/null +++ b/doc/getting_started.rst @@ -0,0 +1,174 @@ +Getting Started +=============== + +Installation +------------ + +Prerequisites +~~~~~~~~~~~~~ + +* Python 3.6 or higher +* CMake 3.16 or higher (for building from source) +* ChimeraTK DeviceAccess library installed + +Using Package Manager +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + # On Debian/Ubuntu systems + sudo apt-get install python3-chimeratk-deviceaccess + +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'll need a device map file (``devices.dmap``) that describes your device: + +.. code-block:: text + + (DEVICE_LABEL) (URI) + MY_DEVICE (dummy_name_prefix:?) + + +Basic Example +~~~~~~~~~~~~~ + +.. code-block:: python + + import deviceaccess + + # Open the device using its name from the device map + device = deviceaccess.Device("MY_DEVICE") + + # Get an accessor for a scalar register + temperature = device.getScalarRegisterAccessor("TEMPERATURE") + + # Read the value from hardware + temperature.read() + + # Access the value (accessor acts like the data type) + print(f"Temperature: {float(temperature)}") + + +Step-by-Step Explanation +~~~~~~~~~~~~~~~~~~~~~~~~~ + +1. **Import**: The ``deviceaccess`` module contains all necessary classes +2. **Device Creation**: ``Device()`` opens a connection to the hardware +3. **Get Accessor**: Accessors are type-safe handles to registers +4. **Read/Write**: ``read()`` and ``write()`` transfer data to/from hardware +5. **Data Access**: Accessors behave like the data they represent + +.. note:: + + All read and write operations are **synchronous** - they block until the operation completes. + Check the :doc:`user_guide` for asynchronous patterns and advanced usage. + + +Working with Device Maps +------------------------ + +The device map file is crucial for telling the library about your devices. + +Basic Format +~~~~~~~~~~~~ + +.. code-block:: text + + # Comments start with # + # Format: DEVICE_NAME BACKEND_SPECIFICATION + + MY_DEVICE (dummy_name_prefix:?) + REAL_DEVICE (modbus://192.168.1.100?address_list=device.xml) + + +Finding Device Map Files +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device map files are typically: + +* Located in your project's configuration directory +* Named with a ``.dmap`` extension +* Pointed to via environment variables or hardcoded paths +* Documented in your project's setup guide + + +Accessor Types +-------------- + +The library provides different accessor types for different data patterns: + +ScalarRegisterAccessor +~~~~~~~~~~~~~~~~~~~~~~ + +For single values: + +.. code-block:: python + + # Floating-point value + voltage = device.getScalarRegisterAccessor("VOLTAGE") + voltage.read() + print(float(voltage)) + + # Integer value + count = device.getScalarRegisterAccessor("COUNTER") + count.read() + print(int(count)) + + +ArrayRegisterAccessor +~~~~~~~~~~~~~~~~~~~~~ + +For arrays of values: + +.. code-block:: python + + # Get an array accessor + spectrum = device.getArrayRegisterAccessor("SPECTRUM") + spectrum.read() + + # Access as list + data = spectrum[:] + print(f"Read {len(data)} values") + + +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 + + +Common Issues +~~~~~~~~~~~~~ + +* **Device not found**: Check that your device map file is accessible and correctly configured +* **Import errors**: Ensure the Python bindings are properly installed in your Python path +* **Permission denied**: You may need elevated privileges for certain hardware backends + +See :doc:`troubleshooting` for more help. diff --git a/doc/index.rst b/doc/index.rst index cbfa82c..589c2fb 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. + + +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 +* Error handling utilities +* Device map configuration + + +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..74a9cd7 100644 --- a/doc/mtca4u.rst +++ b/doc/mtca4u.rst @@ -1,4 +1,4 @@ -mtca4u module +mtca4u module (legacy) ============= .. automodule:: mtca4u diff --git a/doc/overview.rst b/doc/overview.rst new file mode 100644 index 0000000..ffe4458 --- /dev/null +++ b/doc/overview.rst @@ -0,0 +1,90 @@ +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. + + +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, arrays, and complex data structures +* **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 backend plugins +* **Synchronous Operations**: All I/O operations are blocking and straightforward + +.. note:: + + The library is actively maintained and widely used in accelerator control systems and scientific instruments. + + +How It Works +------------ + +The basic workflow is: + +1. **Open a device** using a device map file that describes your hardware +2. **Get accessors** for the registers you want to work with +3. **Read/Write data** using the accessor interface +4. **Handle errors** with meaningful exceptions + +.. code-block:: python + + # Simple workflow example + device = deviceaccess.Device("MY_DEVICE") + + # Get an accessor for a register + temperature = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") + + # Read data from hardware + temperature.read() + + # Access the data + current_temp = float(temperature) + + # Write new data + device.getScalarRegisterAccessor("SETPOINT").write(42.0) + + +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 +* Web-based interfaces and dashboards +* 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..6becb0a --- /dev/null +++ b/doc/troubleshooting.rst @@ -0,0 +1,532 @@ +Troubleshooting +=============== + +This guide helps you diagnose and solve common issues with the DeviceAccess Python bindings. + + +Installation Problems +--------------------- + +Module Import Fails +~~~~~~~~~~~~~~~~~~~~ + +**Problem:** ``ModuleNotFoundError: No module named 'deviceaccess'`` + +**Diagnosis:** + +.. code-block:: bash + + # Check if the module is installed + python -c "import deviceaccess; print(deviceaccess.__file__)" + + # Check Python path + python -c "import sys; print(sys.path)" + + # Verify installation + pip show chimeratk-deviceaccess + +**Solutions:** + +1. **Package not installed**: Reinstall it + + .. code-block:: bash + + pip install --upgrade chimeratk-deviceaccess + # or from source: + mkdir build && cd build && cmake .. && make && sudo make install + +2. **Wrong Python environment**: Ensure you're using the correct Python + + .. code-block:: bash + + which python + which python3 + # Activate the correct virtual environment if using one + +3. **Installation path issue**: Try installing with user flag + + .. code-block:: bash + + pip install --user chimeratk-deviceaccess + + +Missing Dependencies +~~~~~~~~~~~~~~~~~~~~ + +**Problem:** ``ImportError: libDOOCSapi.so: cannot open shared object file`` + +**Solution:** Install the ChimeraTK DeviceAccess library dependency + +.. code-block:: bash + + # On Debian/Ubuntu + sudo apt-get install libchimeratk-deviceaccess + + # Or build and install from source + git clone https://github.com/ChimeraTK/ChimeraTK-DeviceAccess.git + cd ChimeraTK-DeviceAccess + mkdir build && cd build && cmake .. && make && sudo make install + + +Device Connection Issues +------------------------ + +Cannot Open Device +~~~~~~~~~~~~~~~~~~~ + +**Problem:** ``DoocsException: Cannot open device 'MY_DEVICE'`` + +**Diagnosis Steps:** + +1. Check the device map file exists and is accessible: + + .. code-block:: bash + + test -f $DEVICE_MAP_FILE && echo "Map file found" || echo "Map file not found" + +2. Verify the device map file syntax: + + .. code-block:: bash + + cat $DEVICE_MAP_FILE + # Look for proper format: DEVICE_NAME (backend_spec) + +3. Test device name is correct: + + .. code-block:: python + + import deviceaccess + try: + device = deviceaccess.Device("MY_DEVICE") + print("Device opened successfully") + except Exception as e: + print(f"Error: {e}") + +4. Check environment variables: + + .. code-block:: bash + + env | grep -i device + env | grep -i map + +**Solutions:** + +1. **Device map file not set**: Ensure the environment variable is set + + .. code-block:: bash + + export DEVICE_MAP_FILE=/path/to/devices.dmap + python your_script.py + +2. **Wrong device name**: Verify the name matches exactly (case-sensitive) + + .. code-block:: python + + # In device map: MY_DEVICE + # In code: must also be MY_DEVICE (not my_device) + device = deviceaccess.Device("MY_DEVICE") + +3. **Device map syntax error**: Check format is correct + + .. code-block:: text + + # Good: DEVICE_NAME (backend://spec) + # Bad: DEVICE_NAME (backend://spec # Missing space + # Bad: DEVICE_NAME backend://spec # Missing parentheses + TEST (dummy_name_prefix:?) + +4. **Backend not available**: The specified backend might not be built + + .. code-block:: bash + + # Check available backends + apt search chimeratk-device | grep backend + # or check build logs + + +Connection Timeout +~~~~~~~~~~~~~~~~~~ + +**Problem:** ``TimeoutException: Read timeout after waiting X seconds`` + +**Possible Causes:** + +- Device is offline or unreachable +- Network issues (for remote devices) +- Device is busy or locked +- Firewall blocking communication + +**Solutions:** + +1. **Verify device is online**: + + .. code-block:: bash + + # For network devices, try ping + ping 192.168.1.100 + + # For local devices, check if they're visible + lsusb # for USB devices + dmesg # for device messages + +2. **Check network connectivity**: + + .. code-block:: bash + + # Check firewall rules + sudo iptables -L -n + + # Try connecting to device port + telnet 192.168.1.100 502 # For Modbus TCP + +3. **Increase timeout** (if supported): + + .. code-block:: python + + # This depends on the backend implementation + # See backend documentation for timeout options + +4. **Enable debug logging** to see what's happening: + + .. code-block:: bash + + export DEVICEACCESS_DEBUG=1 + python your_script.py + + +Permission Denied +~~~~~~~~~~~~~~~~~ + +**Problem:** ``DoocsException: Permission denied`` or ``OSError: Permission denied`` + +**Solutions:** + +1. **Insufficient user privileges**: Run with appropriate permissions + + .. code-block:: bash + + # For USB devices, you might need to be in the right group + sudo usermod -a -G plugdev $USER + + # Or use sudo if necessary + sudo python your_script.py + +2. **Serial port access**: For serial devices + + .. code-block:: bash + + # Add user to dialout group + sudo usermod -a -G dialout $USER + + # Check device permissions + ls -l /dev/ttyUSB0 + +3. **Device file permissions**: If using a socket or special device + + .. code-block:: bash + + # Check who owns the device + ls -l /path/to/device + + # Give your user access if needed + sudo chown $USER:$USER /path/to/device + + +Register Access Issues +---------------------- + +Register Not Found +~~~~~~~~~~~~~~~~~~~ + +**Problem:** ``DoocsException: Register 'REGISTER_NAME' not found`` + +**Diagnosis:** + +1. Check the register name is spelled correctly: + + .. code-block:: python + + # In device config: TEMPERATURE_SENSOR + # In code: must match exactly + register = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") + +2. Verify the register exists on the device: + + .. code-block:: bash + + # Check device documentation + # Try with a known register to verify device connection works + +**Solutions:** + +1. **Spelling error**: Register names are case-sensitive + + .. code-block:: python + + # Wrong: TEMPERATURE (device has TEMPERATURE_SENSOR) + # Right: + register = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") + +2. **Wrong device**: Accessing from incorrect device + + .. code-block:: python + + # Check you're using the right device + print(f"Device opened, looking for: REGISTER_NAME") + register = device.getScalarRegisterAccessor("REGISTER_NAME") + +3. **Backend doesn't expose register**: Some backends filter registers + + .. code-block:: bash + + # Verify register is available via backend + # Check backend configuration files or device documentation + + +Type Conversion Errors +~~~~~~~~~~~~~~~~~~~~~~ + +**Problem:** ``ValueError: Cannot convert to type X`` + +**Example:** + +.. code-block:: python + + register.read() + value = int(register) # Fails if register contains text + + +**Solutions:** + +1. **Use correct type**: Match the hardware type + + .. code-block:: python + + # If register is a string + value = str(register) + + # If register is float + value = float(register) + +2. **Check hardware documentation**: Verify what type the register actually is + +3. **Explicit type handling**: + + .. code-block:: python + + import deviceaccess + + try: + register.read() + value = float(register) + except ValueError: + # Fallback to string + value = str(register) + + +Data Inconsistency +~~~~~~~~~~~~~~~~~~ + +**Problem:** Multiple register values don't seem to match expectations + +**Solution:** Use transfer groups for consistency + +.. code-block:: python + + # Wrong: Values might be inconsistent + voltage = device.getScalarRegisterAccessor("VOLTAGE") + current = device.getScalarRegisterAccessor("CURRENT") + + voltage.read() + current.read() # Voltage might have changed between reads + + # Right: Consistent read + group = device.getTransferGroup() + group.addAccessor(voltage) + group.addAccessor(current) + group.read() # Both read at same time + + +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("VALUE") # Slow! + register.read() + + # Good: Reuse accessor + register = device.getScalarRegisterAccessor("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 + +3. **Using individual reads instead of transfer groups?** + + .. code-block:: python + + # Bad: N hardware operations + for reg in registers: + reg.read() + + # Good: One hardware operation + group = device.getTransferGroup() + for reg in registers: + group.addAccessor(reg) + group.read() + +**Solutions:** + +- Reuse accessors +- Minimize hardware access operations +- Use transfer groups +- Cache data between reads if appropriate + + +Memory Issues +~~~~~~~~~~~~~ + +**Problem:** High memory usage with large arrays + +**Solutions:** + +1. **Process arrays in chunks**: + + .. code-block:: python + + import numpy as np + + array = device.getArrayRegisterAccessor("LARGE_ARRAY") + array.read() + + # Process in chunks + chunk_size = 1000 + for i in range(0, len(array), chunk_size): + chunk = np.array(array[i:i+chunk_size]) + # Process chunk... + +2. **Use iterators instead of copying**: + + .. code-block:: python + + array = device.getArrayRegisterAccessor("DATA") + array.read() + + # Iterator doesn't copy all data + for value in array[:]: + process(value) + + +Debugging Tips +-------------- + +Enable Debug Output +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + export DEVICEACCESS_DEBUG=1 + export DEVICE_MAP_DEBUG=1 + python your_script.py + + +Print Diagnostic Information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import deviceaccess + import sys + + print("Python:", sys.version) + print("DeviceAccess version:", deviceaccess.__version__) + + # Try opening a device + try: + device = deviceaccess.Device("TEST") + print("Device opened successfully") + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() + + +Check System Resources +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: bash + + # Check available memory + free -h + + # Check file descriptors + ulimit -n + + # Monitor processes + ps aux | grep python + + +Use Python Debugger +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import pdb + import deviceaccess + + pdb.set_trace() # Execution stops here + + # Commands: c (continue), n (next), s (step), p var (print variable) + device = deviceaccess.Device("MY_DEVICE") + + +Getting Help +------------ + +When reporting issues, include: + +1. Python version: ``python --version`` +2. Library version: ``pip show chimeratk-deviceaccess`` +3. OS and platform: ``uname -a`` +4. Complete error message and traceback +5. Device map file (sanitized) +6. Minimal reproducible example +7. Debug output (with ``DEVICEACCESS_DEBUG=1``) + +**Report to:** + +- GitHub Issues: https://github.com/ChimeraTK/ChimeraTK-DeviceAccess-PythonBindings/issues +- ChimeraTK Wiki: https://chimeratk.github.io/ +- Your local project documentation + + +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..674069c --- /dev/null +++ b/doc/user_guide.rst @@ -0,0 +1,441 @@ +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 + + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + # Obtain: Get accessor + value = device.getScalarRegisterAccessor("VALUE") + + # Transfer: Read from hardware + value.read() + + # Access: Work with local buffer (no hardware communication) + current = float(value) + doubled = current * 2 + + # Modify and transfer back + value.write(doubled) + + +Accessor Types +~~~~~~~~~~~~~~ + +**ScalarRegisterAccessor**: Single values + +.. code-block:: python + + scalar = device.getScalarRegisterAccessor("VOLTAGE") + scalar.read() + print(float(scalar)) # Behaves like a float + scalar.write(42.0) + + +**ArrayRegisterAccessor**: Array of values + +.. code-block:: python + + array = device.getArrayRegisterAccessor("SPECTRUM") + array.read() + print(len(array)) # Length of array + print(array[0]) # First element + for val in array[:]: # Iterate over all values + print(val) + + +**NDRegisterAccessor**: Multi-dimensional arrays + +.. code-block:: python + + # For 2D or higher dimensional data + matrix = device.getNDRegisterAccessor("IMAGE") + matrix.read() + # Access like a nested array + + +Type Conversion +--------------- + +Automatic Type Conversion +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The bindings automatically convert between hardware and Python types: + +.. code-block:: python + + # Hardware might store as raw integers, Python sees floats + sensor = device.getScalarRegisterAccessor("TEMPERATURE") + sensor.read() + + # These all work: + temp_float = float(sensor) + temp_int = int(sensor) + temp_str = str(sensor) + + +Explicit Type Specification +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When getting an accessor, specify the Python type you want: + +.. code-block:: python + + # Read as double + double_val = device.getScalarRegisterAccessor("VALUE") + + # Read as integer + int_val = device.getScalarRegisterAccessor("COUNTER") + + +Handling Arrays +~~~~~~~~~~~~~~~ + +.. code-block:: python + + import numpy as np + + array = device.getArrayRegisterAccessor("DATA") + array.read() + + # Convert to NumPy for analysis + data = np.array(array[:]) + + # Modify and write back + modified = data * 2 + for i, val in enumerate(modified): + array[i] = val + array.write() + + +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 + + # Problem: Values might change between reads + voltage = device.getScalarRegisterAccessor("VOLTAGE") + voltage.read() + + current = device.getScalarRegisterAccessor("CURRENT") + current.read() + # Current was just updated, but voltage is old! + + +Solution: Transfer Groups +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Solution: Read both at once + group = device.getTransferGroup() + + voltage = device.getScalarRegisterAccessor("VOLTAGE") + current = device.getScalarRegisterAccessor("CURRENT") + + group.addAccessor(voltage) + group.addAccessor(current) + + group.read() # Both updated atomically + + +Data Consistency Groups +----------------------- + +For advanced scenarios with multiple samples or high-frequency updates: + +.. code-block:: python + + consistency_group = device.getDataConsistencyGroup() + + # Add accessors you want consistent snapshots of + register1 = device.getScalarRegisterAccessor("REGISTER_1") + register2 = device.getScalarRegisterAccessor("REGISTER_2") + + consistency_group.addAccessor(register1) + consistency_group.addAccessor(register2) + + # Read guarantees consistency across all accessors + consistency_group.read() + + +Error Handling +-------------- + +Understanding Exceptions +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The library raises exceptions for error conditions: + +.. code-block:: python + + import deviceaccess + + try: + device = deviceaccess.Device("MY_DEVICE") + except deviceaccess.Exception as e: + print(f"Error: {e}") + + try: + register = device.getScalarRegisterAccessor("MISSING") + register.read() + except deviceaccess.DoocsException as e: + print(f"Device error: {e}") + + +Common Exceptions +~~~~~~~~~~~~~~~~~ + +* ``DeviceException``: Device not found or unavailable +* ``DoocsException``: DOOCS backend specific errors +* ``NotImplemented``: Feature not supported by backend +* ``TimeoutException``: Operation took too long + + +Best Practices for Error Handling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import deviceaccess + import time + + def safe_read(accessor, max_retries=3): + """Read with retry logic""" + for attempt in range(max_retries): + try: + accessor.read() + return float(accessor) + except deviceaccess.TimeoutException: + if attempt < max_retries - 1: + time.sleep(0.1) # Brief delay before retry + else: + raise + + +Device Maps +----------- + +Device maps define your hardware configuration. + +Basic Syntax +~~~~~~~~~~~~ + +.. code-block:: text + + # Device map format + # LABEL BACKEND_SPECIFICATION + + # Dummy device for testing + TEST_DEVICE (dummy_name_prefix:?) + + # Real DOOCS device + ACCELERATOR (doocs://192.168.1.100) + + # Modbus TCP device + SENSOR_ARRAY (modbus://192.168.1.50?address_list=sensors.xml) + + +Best Practices +~~~~~~~~~~~~~~ + +* Use meaningful device labels +* Organize by system or subsystem +* Document non-obvious backend specifications +* Keep separate maps for testing and production +* Version control your device maps + + +Performance Considerations +-------------------------- + +Minimize Hardware Communication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Inefficient: Multiple reads + for i in range(1000): + register.read() + print(float(register)) + + # Efficient: Read once, work with value + register.read() + value = float(register) + for i in range(1000): + print(value) + + +Use Transfer Groups +~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Multiple individual reads + for r in registers: + r.read() # Each is a separate hardware operation + + # Transfer group: Single operation + group = device.getTransferGroup() + for r in registers: + group.addAccessor(r) + group.read() # One hardware operation for all + + +Batch Operations +~~~~~~~~~~~~~~~~ + +.. code-block:: python + + # Instead of: + for value in values: + register.write(value) + + # Consider: Buffer updates and write together + register.write(final_value) + + +Thread Safety +------------- + +The library provides synchronous, thread-safe operations, but there are considerations: + +Thread Safety Guarantees +~~~~~~~~~~~~~~~~~~~~~~~~ + +* Device objects are thread-safe for read/write operations +* Accessors from the same device can be used concurrently +* Each read/write operation is atomic + +.. code-block:: python + + import threading + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + def read_thread(): + register = device.getScalarRegisterAccessor("VALUE") + while True: + register.read() + print(f"Read: {float(register)}") + + def write_thread(): + register = device.getScalarRegisterAccessor("VALUE") + register.write(42.0) + + # Both threads can safely access the device + t1 = threading.Thread(target=read_thread) + t2 = threading.Thread(target=write_thread) + t1.start() + t2.start() + + +Debugging +--------- + +Enabling Debug Output +~~~~~~~~~~~~~~~~~~~~~ + +Set environment variables to enable debug logging: + +.. code-block:: bash + + export DEVICEACCESS_DEBUG=1 + python your_script.py + + +Checking Device Availability +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import deviceaccess + + try: + device = deviceaccess.Device("MY_DEVICE") + print("Device opened successfully") + except Exception as e: + print(f"Cannot open device: {e}") + + +Testing Connections +~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: python + + import deviceaccess + + device = deviceaccess.Device("MY_DEVICE") + + # Try a simple read + try: + test_register = device.getScalarRegisterAccessor("TEST_REGISTER") + test_register.read() + print(f"Connection OK, value: {float(test_register)}") + except Exception as e: + print(f"Connection failed: {e}") + + +Memory Management +----------------- + +Accessors hold references to registers, so be mindful of: + +* Creating many accessors for large arrays +* Long-lived accessor objects +* Memory usage in monitoring loops + +.. code-block:: python + + # Good: Reuse accessors + register = device.getScalarRegisterAccessor("VALUE") + for i in range(1000): + register.read() + # Process... + + # Avoid: Creating accessors in loops + for i in range(1000): + register = device.getScalarRegisterAccessor("VALUE") + register.read() + + +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..86e61fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,8 @@ [tool.autopep8] -max_line_length = 120 \ No newline at end of file +max_line_length = 120 + +[project.optional-dependencies] +docs = [ + "sphinx >= 6.0", + "sphinx-rtd-theme >= 3.0", +] \ No newline at end of file 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..01cd731 100644 --- a/src/PyDevice.cc +++ b/src/PyDevice.cc @@ -218,10 +218,10 @@ namespace ChimeraTK { R"(Initialize device and associate a backend. Note: - The device is not opened after initialization. + The device is not opened after initialization. - :param aliasName: The ChimeraTK device descriptor for the device. - :type aliasName: str)") + Args: + aliasName: The ChimeraTK device descriptor for the device.)") .def(py::init(), R"(Create device instance without associating a backend yet. @@ -230,8 +230,8 @@ namespace ChimeraTK { .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. @@ -245,49 +245,64 @@ namespace ChimeraTK { 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)") + 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 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)") + 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 entire register). + elementsOffset (int): Word offset in 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(), @@ -295,18 +310,22 @@ namespace ChimeraTK { 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)") + 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. @@ -316,113 +335,108 @@ namespace ChimeraTK { .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])") + 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 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. + 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. - - :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)") + 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 scalar value to single-element register. + - 1D array (ndarray): Write 1D array data to 1D register. + - 2D array (ndarray): Write 2D array data to 2D register. + + wordOffsetInRegister (int): Word offset in 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 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. - :return: True if device is functional, false otherwise. - :rtype: bool)") + Returns: + bool: True if 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(); diff --git a/src/PyOneDRegisterAccessor.cc b/src/PyOneDRegisterAccessor.cc index f86aa16..1f70124 100644 --- a/src/PyOneDRegisterAccessor.cc +++ b/src/PyOneDRegisterAccessor.cc @@ -187,7 +187,12 @@ 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: + :meth:`readNonBlocking`: Read without blocking if no data available. + :meth:`readLatest`: Read latest value, discarding intermediate updates. + :meth:`readAndGet`: Convenience method combining read() and get().)") .def("readNonBlocking", &PyOneDRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -199,26 +204,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 (:class:`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: + :meth:`setAndWrite`: Convenience method combining set() and write(). + :meth:`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,10 +238,12 @@ 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 (:class:`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. @@ -238,51 +251,51 @@ namespace ChimeraTK { .def("getName", &PyOneDRegisterAccessor::getName, R"(Returns 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. 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. - :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. 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. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + :class:`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,75 +304,74 @@ 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: + :class:`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: + :class:`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.)") .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 (:class:`VersionNumber`): Optional version number for the write operation.)") .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.)") .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 (:class:`DataValidity`): The data validity state to set.)") .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[:class:`AccessMode`]: List of access mode flags.)") .def("readAndGet", &PyOneDRegisterAccessor::readAndGet, R"(Convenience function to read and return the register data. diff --git a/src/PyScalarRegisterAccessor.cc b/src/PyScalarRegisterAccessor.cc index 84027e4..b709414 100644 --- a/src/PyScalarRegisterAccessor.cc +++ b/src/PyScalarRegisterAccessor.cc @@ -249,7 +249,12 @@ 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: + :meth:`readNonBlocking`: Read without blocking if no data available. + :meth:`readLatest`: Read latest value, discarding intermediate updates. + :meth:`readAndGet`: Convenience method combining read() and get().)") .def("readNonBlocking", &PyScalarRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -261,24 +266,30 @@ 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 (:class:`VersionNumber`): Version number to use for this write operation. If not specified, + a new version number is generated. + + See Also: + :meth:`setAndWrite`: Convenience method combining set() and write(). + :meth:`writeIfDifferent`: Only write if value has changed. + :meth:`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 +298,57 @@ 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 (:class:`VersionNumber`): Version number to use for this write operation. If not specified, + a new version number is generated.)") .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: + :class:`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 +357,44 @@ 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: + :class:`TransferElementID`: The unique transfer element 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: + :class:`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.)") .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.)") .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.)") .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,34 +402,32 @@ 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 (:class:`VersionNumber`): Optional version number for the write operation.)") .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 (:class:`VersionNumber`): Optional version number for the write operation.)") .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.)") .def("interrupt", &PyScalarRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. @@ -427,25 +437,25 @@ namespace ChimeraTK { 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[:class:`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 (:class:`DataValidity`): The data validity state to set.)") .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 +467,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 +483,9 @@ 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.)") .def("__repr__", &PyScalarRegisterAccessor::repr); for(const auto& fn : PyTransferElementBase::specialFunctionsToEmulateNumeric) { scalaracc.def(fn.c_str(), [fn](PyScalarRegisterAccessor& acc, PyScalarRegisterAccessor& other) { diff --git a/src/PyTwoDRegisterAccessor.cc b/src/PyTwoDRegisterAccessor.cc index 560032d..cb19d86 100644 --- a/src/PyTwoDRegisterAccessor.cc +++ b/src/PyTwoDRegisterAccessor.cc @@ -312,7 +312,11 @@ 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: + :meth:`readNonBlocking`: Read without blocking if no data available. + :meth:`readLatest`: Read latest value, discarding intermediate updates.)") .def("readNonBlocking", &PyTwoDRegisterAccessor::readNonBlocking, R"(Read the next value, if available in the input buffer. @@ -324,25 +328,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. - :param versionNumber: Version number to use for this write operation. If not specified, a new version - number is generated. - :type versionNumber: VersionNumber)", + Args: + versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new + version number is generated. + + See Also: + :meth:`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,9 +359,9 @@ 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 (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new + version number is generated.)", py::arg("versionNumber") = PyVersionNumber::getNullVersion()) .def("interrupt", &PyTwoDRegisterAccessor::interrupt, R"(Interrupt a blocking read operation. @@ -363,51 +370,51 @@ namespace ChimeraTK { .def("getName", &PyTwoDRegisterAccessor::getName, R"(Returns 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. 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. - :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. 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. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + :class:`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,72 +423,70 @@ 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: + :class:`TransferElementID`: The unique transfer element 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: + :class:`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.)") .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.)", py::arg("channel"), py::arg("element"), py::arg("value")) .def("setDataValidity", &PyTwoDRegisterAccessor::setDataValidity, R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (:class:`DataValidity`): The data validity state to set.)") .def("getAccessModeFlags", &PyTwoDRegisterAccessor::getAccessModeFlags, R"(Return the access mode flags that were used to create this accessor. @@ -547,10 +552,11 @@ namespace ChimeraTK { .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)") + Args: + index (int): Channel index. + + Returns: + ndarray | list: View of the selected channel as a 1D array-like object.)") .def("__getattr__", &PyTwoDRegisterAccessor::getattr); for(const auto& fn : PyTransferElementBase::specialFunctionsToEmulateNumeric) { diff --git a/src/PyVersionNumber.cc b/src/PyVersionNumber.cc index a931d10..9781a86 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,16 +62,16 @@ 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.)") .def("__lt__", &ChimeraTK::VersionNumber::operator<, R"(Compare two version numbers (self < other).)") diff --git a/src/PyVoidRegisterAccessor.cc b/src/PyVoidRegisterAccessor.cc index 8536f56..a7c49cc 100644 --- a/src/PyVoidRegisterAccessor.cc +++ b/src/PyVoidRegisterAccessor.cc @@ -71,7 +71,11 @@ 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: + :meth:`readNonBlocking`: Read without blocking if no data available. + :meth:`readLatest`: Read latest value, discarding intermediate updates.)", py::call_guard()) .def("readNonBlocking", &ChimeraTK::VoidRegisterAccessor::readNonBlocking, R"(Read the next event, if available. @@ -84,25 +88,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 (:class:`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: + :meth:`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,9 +120,12 @@ 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 (:class:`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. @@ -121,61 +134,61 @@ namespace ChimeraTK { .def("getName", &ChimeraTK::VoidRegisterAccessor::getName, R"(Returns 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. 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. - :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. This can be used to determine the type at runtime. - :return: Type information object. - :rtype: type)") + Returns: + type: Type information object.)") .def("setDataValidity", &PyVoidRegisterAccessor::setDataValidity, R"(Set the data validity of the accessor. - :param validity: The data validity state to set. - :type validity: DataValidity)") + Args: + validity (:class:`DataValidity`): The data validity state to set.)") .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. This refers to the last read or write operation. - :return: The version number of the last transfer. - :rtype: VersionNumber)") + Returns: + :class:`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 +196,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: + :class:`TransferElementID`: The unique transfer element 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[:class:`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: + :class:`DataValidity`: The current data validity state.)"); } /********************************************************************************************************************/ diff --git a/src/RegisterCatalogue.cc b/src/RegisterCatalogue.cc index aa08fd4..2804b60 100644 --- a/src/RegisterCatalogue.cc +++ b/src/RegisterCatalogue.cc @@ -42,30 +42,32 @@ namespace DeviceAccessPython { .def("hiddenRegisters", DeviceAccessPython::RegisterCatalogue::hiddenRegisters, R"(Returns list of all hidden registers in the catalogue - :return: a list of hidden :class:`deviceaccess.RegisterInfo` objects. - :rtype: list[deviceaccess.RegisterInfo])") + Returns: + list[deviceaccess.RegisterInfo]: A list of hidden RegisterInfo objects.)") .def("hasRegister", &ChimeraTK::RegisterCatalogue::hasRegister, 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. - :return: Register information. - :rtype: RegisterInfo - :raises ChimeraTK::logic_error: if register does not exist in the catalogue.)"); + Returns: + RegisterInfo: Register information. + + Raises: + ChimeraTK::logic_error: If register does not exist in the catalogue.)"); } /*******************************************************************************************************************/ @@ -97,61 +99,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 +162,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.)"); } /*******************************************************************************************************************/ @@ -236,8 +238,8 @@ namespace DeviceAccessPython { 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 +247,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 +273,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 +284,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 +295,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 +319,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..b69b077 100644 --- a/src/deviceaccessPython.cc +++ b/src/deviceaccessPython.cc @@ -50,14 +50,14 @@ 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).)"); m.def("getDMapFilePath", ChimeraTK::getDMapFilePath, R"(Returns 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. @@ -107,42 +107,42 @@ PYBIND11_MODULE(deviceaccess, m) { .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.)") .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)") + Returns: + RegisterPath: Modified RegisterPath object.)") .def("__lt__", &ChimeraTK::RegisterPath::operator<) .def("length", &ChimeraTK::RegisterPath::length, R"(Get the length of the path (including leading slash). - :return: Length of the register path. - :rtype: int)") + Returns: + int: Length of the register path.)") .def("startsWith", &ChimeraTK::RegisterPath::startsWith) .def("endsWith", &ChimeraTK::RegisterPath::endsWith) .def("getComponents", &ChimeraTK::RegisterPath::getComponents, R"(Split path into components. - :return: list of path components. - :rtype: list[str])") + Returns: + list[str]: List of path components.)") .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; }) diff --git a/tests/documentationExamples/someCrate.dmap b/tests/documentationExamples/someCrate.dmap new file mode 100644 index 0000000..be435d8 --- /dev/null +++ b/tests/documentationExamples/someCrate.dmap @@ -0,0 +1 @@ +someDummyDevice (dummy?map=./someDummyModule.map) diff --git a/tests/documentationExamples/someDummyModule.map b/tests/documentationExamples/someDummyModule.map new file mode 100644 index 0000000..56baeb4 --- /dev/null +++ b/tests/documentationExamples/someDummyModule.map @@ -0,0 +1,5 @@ +# name nr of elements address size bar width fracbits signed +SENSORS.TEMPERATURE 0x01 0x00000000 0x00000004 0x0 32 4 1 +SENSORS.SET_POINT 0x01 0x00000004 0x00000004 0x0 32 4 1 + +SENSORS.WAVEFORM 0x08 0x00000008 0x00000020 0x0 32 0 1 diff --git a/tests/testDocExamples.py b/tests/testDocExamples.py new file mode 100644 index 0000000..c13b489 --- /dev/null +++ b/tests/testDocExamples.py @@ -0,0 +1,51 @@ +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() + + # 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 + + def testExamples(self): + self.simpleScalarAccess() + self.simpleOneDAccess() From 51fd8984e5b7a15e6d25072ccfca0f98afb81edb Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Fri, 27 Feb 2026 10:04:06 +0100 Subject: [PATCH 2/5] fixup! feat: improve docs --- doc/examples.rst | 17 ++- doc/getting_started.rst | 143 +++++------------- doc/index.rst | 6 +- doc/overview.rst | 36 +---- tests/documentationExamples/someCrate.dmap | 2 + .../documentationExamples/someDummyModule.map | 8 +- tests/testDocExamples.py | 21 ++- 7 files changed, 76 insertions(+), 157 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 0a5b847..3c28ed1 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -40,10 +40,10 @@ The ChimeraTK library supports multiple backends. Configure them in your device .. code-block:: text # Device map example - DUMMY_DEVICE (dummy_name_prefix:?) - MODBUS_DEVICE (modbus://192.168.1.100?address_list=device.xml) - SERIAL_DEVICE (serial:///dev/ttyUSB0?speed=115200) - + 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: @@ -52,12 +52,13 @@ Then use them the same way in your Python code: import deviceaccess # Open different backend devices with same API - dummy = deviceaccess.Device("DUMMY_DEVICE") - modbus = deviceaccess.Device("MODBUS_DEVICE") - serial = deviceaccess.Device("SERIAL_DEVICE") + 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, modbus, serial]: + for device in [dummy, pcie, scpi, opcua]: value = device.getScalarRegisterAccessor("MEASUREMENT") value.read() print(f"Value: {float(value)}") diff --git a/doc/getting_started.rst b/doc/getting_started.rst index 87d5c12..2591a5b 100644 --- a/doc/getting_started.rst +++ b/doc/getting_started.rst @@ -7,17 +7,25 @@ Installation Prerequisites ~~~~~~~~~~~~~ -* Python 3.6 or higher +* 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 -Using Package Manager +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 - # On Debian/Ubuntu systems - sudo apt-get install python3-chimeratk-deviceaccess + sudo apt update && sudo apt-get install python3-mtca4upy From Source ~~~~~~~~~~~ @@ -42,117 +50,50 @@ 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: -You'll need a device map file (``devices.dmap``) that describes your device: +.. literalinclude:: ../tests/documentationExamples/someCrate.dmap + :language: text -.. code-block:: text - (DEVICE_LABEL) (URI) - MY_DEVICE (dummy_name_prefix:?) +.. 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. -Basic Example -~~~~~~~~~~~~~ - -.. code-block:: python - - import deviceaccess +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: - # Open the device using its name from the device map - device = deviceaccess.Device("MY_DEVICE") +.. literalinclude:: ../tests/documentationExamples/someDummyModule.map + :language: text - # Get an accessor for a scalar register - temperature = device.getScalarRegisterAccessor("TEMPERATURE") - # Read the value from hardware - temperature.read() +Basic Example +~~~~~~~~~~~~~ - # Access the value (accessor acts like the data type) - print(f"Temperature: {float(temperature)}") +.. literalinclude:: ../tests/testDocExamples.py + :pyobject: TestDocExamples.simpleScalarAccess + :lines: 2- + :dedent: 8 Step-by-Step Explanation ~~~~~~~~~~~~~~~~~~~~~~~~~ -1. **Import**: The ``deviceaccess`` module contains all necessary classes -2. **Device Creation**: ``Device()`` opens a connection to the hardware -3. **Get Accessor**: Accessors are type-safe handles to registers -4. **Read/Write**: ``read()`` and ``write()`` transfer data to/from hardware -5. **Data Access**: Accessors behave like the data they represent +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:: - All read and write operations are **synchronous** - they block until the operation completes. + 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. - -Working with Device Maps ------------------------- - -The device map file is crucial for telling the library about your devices. - -Basic Format -~~~~~~~~~~~~ - -.. code-block:: text - - # Comments start with # - # Format: DEVICE_NAME BACKEND_SPECIFICATION - - MY_DEVICE (dummy_name_prefix:?) - REAL_DEVICE (modbus://192.168.1.100?address_list=device.xml) - - -Finding Device Map Files -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Device map files are typically: - -* Located in your project's configuration directory -* Named with a ``.dmap`` extension -* Pointed to via environment variables or hardcoded paths -* Documented in your project's setup guide - - -Accessor Types --------------- - -The library provides different accessor types for different data patterns: - -ScalarRegisterAccessor -~~~~~~~~~~~~~~~~~~~~~~ - -For single values: - -.. code-block:: python - - # Floating-point value - voltage = device.getScalarRegisterAccessor("VOLTAGE") - voltage.read() - print(float(voltage)) - - # Integer value - count = device.getScalarRegisterAccessor("COUNTER") - count.read() - print(int(count)) - - -ArrayRegisterAccessor -~~~~~~~~~~~~~~~~~~~~~ - -For arrays of values: - -.. code-block:: python - - # Get an array accessor - spectrum = device.getArrayRegisterAccessor("SPECTRUM") - spectrum.read() - - # Access as list - data = spectrum[:] - print(f"Read {len(data)} values") - - Next Steps ---------- @@ -162,13 +103,3 @@ Now that you have the basics: * Read the :doc:`user_guide` for deeper concepts * Check the :doc:`api_reference` for complete API details * Browse :doc:`faq` for common questions - - -Common Issues -~~~~~~~~~~~~~ - -* **Device not found**: Check that your device map file is accessible and correctly configured -* **Import errors**: Ensure the Python bindings are properly installed in your Python path -* **Permission denied**: You may need elevated privileges for certain hardware backends - -See :doc:`troubleshooting` for more help. diff --git a/doc/index.rst b/doc/index.rst index 589c2fb..2b7088c 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -30,7 +30,7 @@ The bindings enable Python developers to: * 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. +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 @@ -76,8 +76,8 @@ The main module is :doc:`deviceaccess` which provides: * Device class for opening and managing connections * Various accessor types for different data structures -* Error handling utilities -* Device map configuration +* Register information retrieval +* Transfer group and data consistency group management Questions and Troubleshooting diff --git a/doc/overview.rst b/doc/overview.rst index ffe4458..5ec01a5 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -11,6 +11,8 @@ allowing you to focus on your application logic rather than low-level hardware d 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 devision's beam controls group. It is designed to be robust, efficient, and easy to use in a variety of applications. + Key Features ~~~~~~~~~~~~ @@ -21,39 +23,6 @@ Key Features * **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 backend plugins -* **Synchronous Operations**: All I/O operations are blocking and straightforward - -.. note:: - - The library is actively maintained and widely used in accelerator control systems and scientific instruments. - - -How It Works ------------- - -The basic workflow is: - -1. **Open a device** using a device map file that describes your hardware -2. **Get accessors** for the registers you want to work with -3. **Read/Write data** using the accessor interface -4. **Handle errors** with meaningful exceptions - -.. code-block:: python - - # Simple workflow example - device = deviceaccess.Device("MY_DEVICE") - - # Get an accessor for a register - temperature = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") - - # Read data from hardware - temperature.read() - - # Access the data - current_temp = float(temperature) - - # Write new data - device.getScalarRegisterAccessor("SETPOINT").write(42.0) Common Use Cases @@ -74,7 +43,6 @@ 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 -* Web-based interfaces and dashboards * Machine learning and data analysis workflows The C++ backend ensures high performance while Python's flexibility enables rapid development. diff --git a/tests/documentationExamples/someCrate.dmap b/tests/documentationExamples/someCrate.dmap index be435d8..94970d2 100644 --- a/tests/documentationExamples/someCrate.dmap +++ b/tests/documentationExamples/someCrate.dmap @@ -1 +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 index 56baeb4..0ead20f 100644 --- a/tests/documentationExamples/someDummyModule.map +++ b/tests/documentationExamples/someDummyModule.map @@ -1,5 +1,5 @@ -# name nr of elements address size bar width fracbits signed -SENSORS.TEMPERATURE 0x01 0x00000000 0x00000004 0x0 32 4 1 -SENSORS.SET_POINT 0x01 0x00000004 0x00000004 0x0 32 4 1 +# 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 +SENSORS.WAVEFORM 0x08 0x00000008 0x00000020 0x0 32 0 1 RW diff --git a/tests/testDocExamples.py b/tests/testDocExamples.py index c13b489..396986f 100644 --- a/tests/testDocExamples.py +++ b/tests/testDocExamples.py @@ -1,4 +1,5 @@ import unittest +import inspect class TestDocExamples(unittest.TestCase): @@ -36,6 +37,9 @@ def simpleOneDAccess(self): 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 @@ -46,6 +50,19 @@ def simpleOneDAccess(self): waveform.read() print("Modified waveform data:", waveform) # accessor can be used as a numpy array or python list + # slicing also works + print("First 5 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): - self.simpleScalarAccess() - self.simpleOneDAccess() + # 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() From 6956a8e1eaa6dccff0213bcbd1661a1e1a8f2e2d Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Fri, 24 Apr 2026 12:37:33 +0200 Subject: [PATCH 3/5] doc: redo doc strings for google style to better render html files Co-authored-by: Copilot --- doc/api_reference.rst | 29 +++ doc/conf.py.in | 10 +- doc/generated/deviceaccess.AccessMode.rst | 31 +++ .../deviceaccess.DataConsistencyGroup.rst | 26 ++ doc/generated/deviceaccess.DataDescriptor.rst | 31 +++ doc/generated/deviceaccess.DataType.rst | 45 ++++ doc/generated/deviceaccess.DataValidity.rst | 31 +++ doc/generated/deviceaccess.Device.rst | 36 +++ .../deviceaccess.OneDRegisterAccessor.rst | 48 ++++ doc/generated/deviceaccess.ReadAnyGroup.rst | 32 +++ .../deviceaccess.RegisterCatalogue.rst | 26 ++ doc/generated/deviceaccess.RegisterInfo.rst | 33 +++ .../deviceaccess.ScalarRegisterAccessor.rst | 54 ++++ doc/generated/deviceaccess.TransferGroup.rst | 28 +++ .../deviceaccess.TwoDRegisterAccessor.rst | 47 ++++ doc/generated/deviceaccess.VersionNumber.rst | 25 ++ .../deviceaccess.VoidRegisterAccessor.rst | 41 ++++ .../deviceaccess.getDMapFilePath.rst | 6 + .../deviceaccess.setDMapFilePath.rst | 6 + pyproject.toml | 3 +- src/PyDataConsistencyGroup.cc | 52 +++- src/PyDevice.cc | 232 +++++++++--------- src/PyOneDRegisterAccessor.cc | 121 +++++---- src/PyReadAnyGroup.cc | 107 ++++++-- src/PyScalarRegisterAccessor.cc | 80 ++++-- src/PyTransferGroup.cc | 39 ++- src/PyTwoDRegisterAccessor.cc | 120 +++++---- src/PyVersionNumber.cc | 77 +++++- src/PyVoidRegisterAccessor.cc | 49 ++-- src/RegisterCatalogue.cc | 18 +- src/deviceaccessPython.cc | 152 ++++++++++-- 31 files changed, 1304 insertions(+), 331 deletions(-) create mode 100644 doc/generated/deviceaccess.AccessMode.rst create mode 100644 doc/generated/deviceaccess.DataConsistencyGroup.rst create mode 100644 doc/generated/deviceaccess.DataDescriptor.rst create mode 100644 doc/generated/deviceaccess.DataType.rst create mode 100644 doc/generated/deviceaccess.DataValidity.rst create mode 100644 doc/generated/deviceaccess.Device.rst create mode 100644 doc/generated/deviceaccess.OneDRegisterAccessor.rst create mode 100644 doc/generated/deviceaccess.ReadAnyGroup.rst create mode 100644 doc/generated/deviceaccess.RegisterCatalogue.rst create mode 100644 doc/generated/deviceaccess.RegisterInfo.rst create mode 100644 doc/generated/deviceaccess.ScalarRegisterAccessor.rst create mode 100644 doc/generated/deviceaccess.TransferGroup.rst create mode 100644 doc/generated/deviceaccess.TwoDRegisterAccessor.rst create mode 100644 doc/generated/deviceaccess.VersionNumber.rst create mode 100644 doc/generated/deviceaccess.VoidRegisterAccessor.rst create mode 100644 doc/generated/deviceaccess.getDMapFilePath.rst create mode 100644 doc/generated/deviceaccess.setDMapFilePath.rst diff --git a/doc/api_reference.rst b/doc/api_reference.rst index fb7f234..d9613fe 100644 --- a/doc/api_reference.rst +++ b/doc/api_reference.rst @@ -4,6 +4,35 @@ 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 ~~~~~~~~~~~~ diff --git a/doc/conf.py.in b/doc/conf.py.in index 9a3cfe9..02c8565 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -41,6 +41,7 @@ except Exception: # ones. extensions = [ 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', 'sphinx_autodoc_typehints', 'sphinx.ext.todo', 'sphinx.ext.viewcode', @@ -132,6 +133,7 @@ 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 = { @@ -143,6 +145,8 @@ autodoc_default_options = { } autodoc_typehints = 'short' autodoc_typehints_format = 'short' +autosummary_generate = True +autosummary_imported_members = False # -- Options for HTML output ---------------------------------------------- @@ -158,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. 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/pyproject.toml b/pyproject.toml index 86e61fb..2ad36f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,5 +4,6 @@ max_line_length = 120 [project.optional-dependencies] docs = [ "sphinx >= 6.0", + "sphinx-autodoc-typehints", "sphinx-rtd-theme >= 3.0", -] \ No newline at end of file +] 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/PyDevice.cc b/src/PyDevice.cc index 01cd731..a40bbd5 100644 --- a/src/PyDevice.cc +++ b/src/PyDevice.cc @@ -208,51 +208,51 @@ 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: - The device is not opened after initialization. + Note: + The device is not opened after initialization. - Args: - aliasName: The ChimeraTK device descriptor for the device.)") + 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. - Args: - aliasName (str): The device alias name from the DMAP file.)") + 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. - Args: - registerPathName (str): Full path name of the register. - accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + Args: + registerPathName (str): Full path name of the register. + accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. - Returns: + Returns: :class:`VoidRegisterAccessor`: VoidRegisterAccessor for the specified register. - See Also: + See Also: :meth:`getScalarRegisterAccessor`: For scalar value registers. :meth:`getOneDRegisterAccessor`: For 1D array registers. :meth:`getTwoDRegisterAccessor`: For 2D array registers. @@ -261,19 +261,19 @@ namespace ChimeraTK { 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 specified data type. + 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 register to access other than the first word. - accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + 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: + Returns: :class:`ScalarRegisterAccessor`: ScalarRegisterAccessor for the specified register. - See Also: + See Also: :meth:`getOneDRegisterAccessor`: For 1D array registers. :meth:`getTwoDRegisterAccessor`: For 2D array registers. :meth:`getVoidRegisterAccessor`: For trigger-only registers. @@ -284,20 +284,20 @@ namespace ChimeraTK { 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 specified data type. + 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 entire register). - elementsOffset (int): Word offset in register to skip initial elements. - accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + 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: + Returns: :class:`OneDRegisterAccessor`: OneDRegisterAccessor for the specified register. - See Also: + See Also: :meth:`getScalarRegisterAccessor`: For single-value registers. :meth:`getTwoDRegisterAccessor`: For 2D array registers. :meth:`getVoidRegisterAccessor`: For trigger-only registers. @@ -308,54 +308,53 @@ namespace ChimeraTK { py::arg("accessModeFlags") = py::list(), R"(Get a TwoDRegisterAccessor object for the given register. - This allows to read and write transparently 2-dimensional registers. + 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. + 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: + Returns: :class:`TwoDRegisterAccessor`: TwoDRegisterAccessor for the specified register. - See Also: + 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. - Returns: - :class:`RegisterCatalogue`: RegisterCatalogue containing all register information.)") + 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. + 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 register to skip initial elements. - accessModeFlags (list[:class:`AccessMode`]): Optional flags to control register access details. + 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: + Returns: scalar | ndarray: Register value (scalar, 1D array, or 2D array depending on register type). - See Also: + 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. @@ -368,83 +367,84 @@ namespace ChimeraTK { py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::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 scalar value to single-element register. - - 1D array (ndarray): Write 1D array data to 1D register. - - 2D array (ndarray): Write 2D array data to 2D register. - - wordOffsetInRegister (int): Word offset in 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.)") + 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. Returns: - bool: True if device is opened, False otherwise.)") + 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. - Args: - message (str): Exception message describing the error condition.)") + 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. - Returns: - bool: True if device is functional, False otherwise.)") + Returns: + bool: `True` if the device is functional, `False` otherwise.)") .def("getCatalogueMetadata", &PyDevice::getCatalogueMetadata, py::arg("metaTag"), R"(Get metadata from the device catalogue. - Args: - metaTag (str): The metadata parameter name to retrieve. + Args: + metaTag (str): The metadata parameter name to retrieve. - Returns: - str: The metadata value.)") + 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 1f70124..d0a7e42 100644 --- a/src/PyOneDRegisterAccessor.cc +++ b/src/PyOneDRegisterAccessor.cc @@ -190,9 +190,12 @@ namespace ChimeraTK { Otherwise it still might block for a short time until the data transfer was complete. See Also: - :meth:`readNonBlocking`: Read without blocking if no data available. - :meth:`readLatest`: Read latest value, discarding intermediate updates. - :meth:`readAndGet`: Convenience method combining read() and get().)") + 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. @@ -221,15 +224,15 @@ namespace ChimeraTK { In case of an unbuffered write transfer, the return value will always be false. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, + 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: - :meth:`setAndWrite`: Convenience method combining set() and write(). - :meth:`writeDestructively`: Optimized write that may destroy buffer.)") + 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. @@ -239,7 +242,7 @@ namespace ChimeraTK { undefined data after calling this function. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new version number is generated. Returns: @@ -247,38 +250,41 @@ namespace ChimeraTK { .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. 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.'. 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. 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. 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. Returns: - :class:`VersionNumber`: The version number of the last transfer.)") + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyOneDRegisterAccessor::isReadOnly, R"(Check if accessor is read only. @@ -305,14 +311,14 @@ namespace ChimeraTK { the very same register. Returns: - :class:`TransferElementID`: The unique accessor ID.)") + 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. Returns: - :class:`DataValidity`: The current data validity state.)") + DataValidity: The current data validity state.)") .def("getNElements", &PyOneDRegisterAccessor::getNElements, R"(Return number of elements/samples in the register. @@ -327,7 +333,10 @@ namespace ChimeraTK { R"(Set the values of the array. Args: - newValue (list | ndarray): New values to set in the buffer.)") + 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. @@ -336,7 +345,10 @@ namespace ChimeraTK { Args: newValue (list | ndarray): New values to set and write. - versionNumber (:class:`VersionNumber`): Optional version number for the write operation.)") + 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). @@ -354,7 +366,10 @@ namespace ChimeraTK { Args: element (int): Element index to write. - value (float): The cooked value to set.)") + 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. @@ -364,38 +379,43 @@ namespace ChimeraTK { R"(Set the data validity of the accessor. Args: - validity (:class:`DataValidity`): The data validity state to set.)") + 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. Returns: - list[:class:`AccessMode`]: List of access mode flags.)") + 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) { @@ -404,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) { @@ -416,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) { @@ -428,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 b709414..70da11c 100644 --- a/src/PyScalarRegisterAccessor.cc +++ b/src/PyScalarRegisterAccessor.cc @@ -252,9 +252,12 @@ namespace ChimeraTK { Otherwise it still might block for a short time until the data transfer was complete. See Also: - :meth:`readNonBlocking`: Read without blocking if no data available. - :meth:`readLatest`: Read latest value, discarding intermediate updates. - :meth:`readAndGet`: Convenience method combining read() and get().)") + 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. @@ -283,13 +286,16 @@ namespace ChimeraTK { In case of an unbuffered write transfer, the return value will always be false. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, + 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: - :meth:`setAndWrite`: Convenience method combining set() and write(). - :meth:`writeIfDifferent`: Only write if value has changed. - :meth:`writeDestructively`: Optimized write that may destroy buffer.)") + 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. @@ -299,8 +305,11 @@ namespace ChimeraTK { undefined data after calling this function. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, - a new version number is generated.)") + 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. @@ -331,7 +340,7 @@ namespace ChimeraTK { This refers to the last read or write operation. Returns: - :class:`VersionNumber`: The version number of the last transfer.)") + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyScalarRegisterAccessor::isReadOnly, R"(Check if accessor is read only. @@ -358,14 +367,14 @@ namespace ChimeraTK { the very same register. Returns: - :class:`TransferElementID`: The unique transfer element ID.)") + 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. Returns: - :class:`DataValidity`: The current data validity state.)") + DataValidity: The current data validity state.)") .def("get", &PyScalarRegisterAccessor::get, R"(Return the scalar value (without a previous read). @@ -382,19 +391,28 @@ namespace ChimeraTK { R"(Set the scalar value. Args: - val (int | float | bool | str): New value to set in the buffer.)") + 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. Args: - val (list): List containing a single value to set.)") + 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. Args: - val (ndarray): Array containing a single value to set.)") + 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. @@ -404,7 +422,10 @@ namespace ChimeraTK { Args: newValue (int | float | bool | str): New value to compare and potentially write. - versionNumber (:class:`VersionNumber`): Optional version number for the write operation.)") + 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. @@ -413,7 +434,10 @@ namespace ChimeraTK { Args: newValue (int | float | bool | str): New value to set and write. - versionNumber (:class:`VersionNumber`): Optional version number for the write operation.)") + 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). @@ -427,18 +451,24 @@ namespace ChimeraTK { This converts to raw and writes the data to the user buffer. It does not do any read or write transfers. Args: - value (float): The cooked value to set.)") + 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. Returns: - list[:class:`AccessMode`]: List of access mode flags.)") + list[AccessMode]: List of access mode flags.)") .def("isInitialised", &PyScalarRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. @@ -448,7 +478,10 @@ namespace ChimeraTK { R"(Set the data validity of the accessor. Args: - validity (:class:`DataValidity`): The data validity state to set.)") + 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. @@ -485,7 +518,10 @@ namespace ChimeraTK { Args: index (int): Must be 0 for scalar accessors. - value (int | float | bool | str): New value to set.)") + 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 cb19d86..d634845 100644 --- a/src/PyTwoDRegisterAccessor.cc +++ b/src/PyTwoDRegisterAccessor.cc @@ -315,8 +315,11 @@ namespace ChimeraTK { still might block for a short time until the data transfer was complete. See Also: - :meth:`readNonBlocking`: Read without blocking if no data available. - :meth:`readLatest`: Read latest value, discarding intermediate updates.)") + 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. @@ -345,11 +348,14 @@ namespace ChimeraTK { In case of an unbuffered write transfer, the return value will always be false. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new + 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. + See Also: - :meth:`writeDestructively`: Optimized write that may destroy buffer.)", + 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 @@ -360,44 +366,50 @@ namespace ChimeraTK { this function. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new - version number is generated.)", + 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. 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.'. 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. 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. 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. Returns: - :class:`VersionNumber`: The version number of the last transfer.)") + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &PyTwoDRegisterAccessor::isReadOnly, R"(Check if accessor is read only. @@ -424,14 +436,14 @@ namespace ChimeraTK { register. Returns: - :class:`TransferElementID`: The unique transfer element ID.)") + 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. Returns: - :class:`DataValidity`: The current data validity state.)") + DataValidity: The current data validity state.)") .def("isInitialised", &PyTwoDRegisterAccessor::isInitialised, R"(Check if the accessor is initialised. @@ -459,7 +471,10 @@ namespace ChimeraTK { R"(Set the values of the 2D array buffer. Args: - newValue (list[list[UserType]] | ndarray): New values to set, shaped as [channels][elements] or a 2D numpy array.)") + 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). @@ -480,39 +495,47 @@ namespace ChimeraTK { Args: channel (int): Channel index. element (int): Element index within the channel. - value (float): The cooked value to set.)", + 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. Args: - validity (:class:`DataValidity`): The data validity state to set.)") + 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) { @@ -521,10 +544,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) { @@ -533,10 +558,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) { @@ -545,10 +572,12 @@ 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. @@ -557,7 +586,14 @@ namespace ChimeraTK { Returns: ndarray | list: View of the selected channel as a 1D array-like object.)") - .def("__getattr__", &PyTwoDRegisterAccessor::getattr); + .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 9781a86..47756f3 100644 --- a/src/PyVersionNumber.cc +++ b/src/PyVersionNumber.cc @@ -72,14 +72,77 @@ namespace ChimeraTK { 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 a7c49cc..b81c55a 100644 --- a/src/PyVoidRegisterAccessor.cc +++ b/src/PyVoidRegisterAccessor.cc @@ -71,11 +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: - :meth:`readNonBlocking`: Read without blocking if no data available. - :meth:`readLatest`: Read latest value, discarding intermediate updates.)", + 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. @@ -105,14 +108,14 @@ namespace ChimeraTK { case of an unbuffered write transfer, the return value will always be false. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new + 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: - :meth:`writeDestructively`: Optimized write that may destroy buffer.)", + 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. @@ -121,7 +124,7 @@ namespace ChimeraTK { case, the application must expect internal buffers to contain undefined data after calling this function. Args: - versionNumber (:class:`VersionNumber`): Version number to use for this write operation. If not specified, a new + versionNumber (VersionNumber): Version number to use for this write operation. If not specified, a new version number is generated. Returns: @@ -130,48 +133,54 @@ namespace ChimeraTK { .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. 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.'. 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. 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. Returns: type: Type information object.)") - .def("setDataValidity", &PyVoidRegisterAccessor::setDataValidity, + .def("setDataValidity", &PyVoidRegisterAccessor::setDataValidity, py::arg("validity"), R"(Set the data validity of the accessor. Args: - validity (:class:`DataValidity`): The data validity state to set.)") + 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. 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. Returns: - :class:`VersionNumber`: The version number of the last transfer.)") + VersionNumber: The version number of the last transfer.)") .def("isReadOnly", &ChimeraTK::VoidRegisterAccessor::isReadOnly, R"(Check if accessor is read only. @@ -197,21 +206,21 @@ namespace ChimeraTK { accessing the very same register. Returns: - :class:`TransferElementID`: The unique transfer element ID.)") + 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. Returns: - list[:class:`AccessMode`]: List of access mode flags.)") + 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. Returns: - :class:`DataValidity`: The current data validity state.)"); + DataValidity: The current data validity state.)"); } /********************************************************************************************************************/ diff --git a/src/RegisterCatalogue.cc b/src/RegisterCatalogue.cc index 2804b60..101a4d1 100644 --- a/src/RegisterCatalogue.cc +++ b/src/RegisterCatalogue.cc @@ -37,14 +37,22 @@ 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. Returns: list[deviceaccess.RegisterInfo]: A list of hidden RegisterInfo objects.)") - .def("hasRegister", &ChimeraTK::RegisterCatalogue::hasRegister, + .def("hasRegister", &ChimeraTK::RegisterCatalogue::hasRegister, py::arg("registerPathName"), R"(Check if register with the given path name exists. Args: @@ -233,7 +241,7 @@ 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. diff --git a/src/deviceaccessPython.cc b/src/deviceaccessPython.cc index b69b077..e635452 100644 --- a/src/deviceaccessPython.cc +++ b/src/deviceaccessPython.cc @@ -51,10 +51,13 @@ PYBIND11_MODULE(deviceaccess, m) { R"(Set the location of the dmap file. Args: - dmapFilePath (str): Relative or absolute path of the dmap file (directory and file name).)"); + 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. Returns: str: Path of the dmap file (directory and file name).)"); @@ -62,8 +65,8 @@ PYBIND11_MODULE(deviceaccess, m) { 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,27 +91,69 @@ 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(), 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. Args: - altSeparator (str): Alternative separator character to use instead of "/". Use an empty string to reset to default.)") + 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. @@ -130,25 +175,82 @@ PYBIND11_MODULE(deviceaccess, m) { Returns: RegisterPath: Modified RegisterPath object.)") - .def("__lt__", &ChimeraTK::RegisterPath::operator<) + .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). Returns: int: Length of the register path.)") - .def("startsWith", &ChimeraTK::RegisterPath::startsWith) - .def("endsWith", &ChimeraTK::RegisterPath::endsWith) + .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. Returns: list[str]: List of path components.)") - .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; }); + .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(); From 95e4d4d98cd77290ad753f567e674f1cbdc95227 Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Fri, 8 May 2026 09:41:12 +0200 Subject: [PATCH 4/5] fix: remove fluff for review --- doc/examples.rst | 181 ---------------- doc/faq.rst | 147 ++----------- doc/overview.rst | 4 +- doc/troubleshooting.rst | 465 +--------------------------------------- doc/user_guide.rst | 336 ++--------------------------- 5 files changed, 38 insertions(+), 1095 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index 3c28ed1..a60d176 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -63,187 +63,6 @@ Then use them the same way in your Python code: value.read() print(f"Value: {float(value)}") - -.. _transfer_groups_python: - -Synchronized Access with Transfer Groups ------------------------------------------ - -Use transfer groups to read/write multiple registers atomically: - -.. code-block:: python - - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - # Create a transfer group - group = device.getTransferGroup() - - # Add registers to the group - voltage = device.getScalarRegisterAccessor("VOLTAGE") - current = device.getScalarRegisterAccessor("CURRENT") - power = device.getScalarRegisterAccessor("POWER") - - group.addAccessor(voltage) - group.addAccessor(current) - group.addAccessor(power) - - # Read all at once - group.read() - - # All values are from the same hardware snapshot - print(f"V={float(voltage)}, I={float(current)}, P={float(power)}") - - # Write all at once - voltage.write(230.0) - current.write(10.0) - power.write(2300.0) - - group.write() - - -.. _data_consistency_python: - -Data Consistency Groups ------------------------ - -For reading coherent data across multiple samples: - -.. code-block:: python - - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - # Create a data consistency group - consistency_group = device.getDataConsistencyGroup() - - # Add accessors for channels - channels = [] - for i in range(4): - channel = device.getScalarRegisterAccessor(f"CHANNEL_{i}") - channels.append(channel) - consistency_group.addAccessor(channel) - - # Read all channels with guaranteed consistency - consistency_group.read() - - # Process the consistent data - values = [float(ch) for ch in channels] - print(f"Channel values: {values}") - - -Error Handling --------------- - -Robust code includes proper error handling: - -.. code-block:: python - - import deviceaccess - - try: - device = deviceaccess.Device("MY_DEVICE") - except deviceaccess.DoocsException as e: - print(f"Failed to open device: {e}") - exit(1) - - try: - register = device.getScalarRegisterAccessor("MEASUREMENT") - register.read() - value = float(register) - print(f"Read value: {value}") - except deviceaccess.DoocsException as e: - print(f"Read operation failed: {e}") - except ValueError as e: - print(f"Type conversion failed: {e}") - - -Monitoring Values Over Time ----------------------------- - -Read values periodically: - -.. code-block:: python - - import deviceaccess - import time - - device = deviceaccess.Device("MY_DEVICE") - temperature = device.getScalarRegisterAccessor("TEMPERATURE") - - readings = [] - for i in range(10): - temperature.read() - value = float(temperature) - readings.append(value) - print(f"Reading {i+1}: {value} °C") - - if i < 9: - time.sleep(1.0) - - print(f"Average: {sum(readings) / len(readings)} °C") - - -Batch Operations ----------------- - -Efficiently perform multiple operations: - -.. code-block:: python - - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - # Get multiple accessors at once - registers = { - "voltage": device.getScalarRegisterAccessor("VOLTAGE"), - "current": device.getScalarRegisterAccessor("CURRENT"), - "frequency": device.getScalarRegisterAccessor("FREQUENCY"), - } - - # Read all - for accessor in registers.values(): - accessor.read() - - # Process results - status = {name: float(accessor) for name, accessor in registers.items()} - print(f"Device status: {status}") - - -Integration with NumPy and Pandas ---------------------------------- - -Working with scientific Python libraries: - -.. code-block:: python - - import deviceaccess - import pandas as pd - import numpy as np - - device = deviceaccess.Device("MY_DEVICE") - - # Collect time-series data - data = [] - for i in range(100): - waveform = device.getArrayRegisterAccessor("WAVEFORM") - waveform.read() - - data.append({ - 'timestamp': i, - 'mean': np.mean(waveform[:]), - 'std': np.std(waveform[:]), - 'min': np.min(waveform[:]), - 'max': np.max(waveform[:]), - }) - - # Create DataFrame for analysis - df = pd.DataFrame(data) - print(df.describe()) - .. _used_map_files_section: Used Map Files diff --git a/doc/faq.rst b/doc/faq.rst index 8da2e80..51df7b0 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -54,7 +54,7 @@ A: - Call ``read()`` to transfer data **from hardware to the local buffer** my_value = float(accessor) # Modify local copy - accessor.write(my_value * 2) # Or use accessor.write() + accessor.set(my_value * 2) # Or use accessor.write() # Write back to hardware accessor.write() @@ -68,7 +68,7 @@ A: - ``read()`` / ``write()`` - Transfer data with hardware .. code-block:: python # These don't talk to hardware: - accessor.write(42.0) # Modifies local buffer + accessor.set(42.0) # Modifies local buffer value = float(accessor) # Reads local buffer # This transfers data: @@ -84,11 +84,12 @@ A: Yes, but methods vary by accessor type: # Scalar scalar = device.getScalarRegisterAccessor("VALUE") - scalar.write(42.0) + scalar.set(42.0) scalar.write() # Array array = device.getArrayRegisterAccessor("DATA") + # Can be treated like a python list with numpy methods, incl. slices array[0] = 1.5 array[1] = 2.5 array.write() @@ -99,43 +100,12 @@ Accessors and Type Conversion **Q: How does the library handle type conversion?** -A: Automatic conversion happens when you access the value: +A: Automatic conversion happens when you request the register: .. code-block:: python - register.read() - - float_val = float(register) # Converts to float - int_val = int(register) # Converts to int - str_val = str(register) # Converts to string - - -**Q: Can I specify a type when getting an accessor?** - -A: The accessor is obtained with the hardware type information already known. - Type conversion happens at access time: - - .. code-block:: python - - # Get accessor for this hardware register - register = device.getScalarRegisterAccessor("TEMPERATURE") - register.read() - - # Convert to the type you want - celsius = float(register) - fahrenheit = celsius * 9 / 5 + 32 - - -**Q: What happens if I try to convert to an incompatible type?** - -A: You'll get a ``ValueError`` or similar exception. Always handle conversion errors: - - .. code-block:: python - - try: - value = int(register) - except (ValueError, TypeError) as e: - print(f"Cannot convert: {e}") + aFloatValue = dev.getScalarRegisterAccessor(float, "SENSORS.TEMPERATURE") + anIntList = device.getOneDRegisterAccessor(int, "SENSORS.WAVEFORM") Device Maps @@ -149,20 +119,11 @@ A: A device map (`.dmap`) file describes the devices your application can access See :ref:`Device Maps ` in the user guide for details. -**Q: Where should I put my device map file?** - -A: Typically in your project's configuration directory. You then tell the application - where to find it, usually through an environment variable or configuration file. - - ``export DEVICE_MAP_FILE=/path/to/devices.dmap`` - - **Q: How do I debug device map issues?** A: - Check that the file exists and is readable - - Verify the syntax is correct (see the user guide) - - Try opening a device and catch exceptions for error messages - - Set debug environment variables (see troubleshooting) + - Verify the syntax is correct + - Try opening the device with QtHardMon or Chai. Transfer Groups @@ -176,70 +137,17 @@ A: Use transfer groups when you need to: - Write multiple registers atomically - Reduce hardware communication overhead - .. code-block:: python - - # All read together - group = device.getTransferGroup() - group.addAccessor(voltage) - group.addAccessor(current) - group.read() # One operation **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 semantics across multiple accesses - - Use transfer groups for most cases. Data consistency groups are for advanced scenarios. + - **Data Consistency Group**: Provides consistency via VersionNumbers **Q: Do transfer groups improve performance?** -A: Yes, typically. Instead of multiple hardware operations (one per register), - a transfer group uses a single operation for all registers. - - -Error Handling --------------- - -**Q: What exceptions can the library raise?** - -A: The main ones are: - - - ``DoocsException`` - DOOCS backend errors - - ``DeviceException`` - Device access errors - - ``TimeoutException`` - Operation timeout - - ``NotImplemented`` - Feature not supported - - See the API reference for a complete list. - - -**Q: How should I handle read/write errors?** - -A: Always wrap hardware operations in try-except: - - .. code-block:: python - - import deviceaccess - - try: - register.read() - except deviceaccess.TimeoutException: - print("Read timed out") - except deviceaccess.DoocsException as e: - print(f"Device error: {e}") - - -**Q: The device opens but register access fails. What's wrong?** - -A: Several possibilities: - - - Register name is wrong or doesn't exist - - Device connection was lost - - Hardware is offline or not responding - - Permission issues with the device - - Enable debug logging and check the error message. See :doc:`troubleshooting`. +A: Yes, if the backend supports it. Performance and Optimization @@ -247,27 +155,19 @@ Performance and Optimization **Q: How can I improve performance for many reads?** -A: - Use transfer groups for multiple related registers - - Reuse accessors instead of creating new ones each time +A: - Reuse accessors instead of creating new ones each time - Minimize how often you call read/write - - Consider caching values if they change infrequently See :doc:`user_guide` for optimization strategies. **Q: Is there an asynchronous API?** -A: The synchronous API is the standard. For asynchronous access: +A: The synchronous API is the standard. For asynchronous access you need to set up the device: - - Use Python threading to run read/write in background threads - - Consider thread pools for high-volume operations - - Look into the ``threading`` or ``asyncio`` modules - - -**Q: How much memory do accessors use?** + .. code-block:: python -A: Memory usage is proportional to the data size. Array accessors for large arrays - will use more memory. Generally not a concern unless working with many large accessors. + dev.activateAsyncRead() Compatibility and Versions @@ -281,14 +181,7 @@ A: Yes! The Python bindings wrap the C++ library, providing the same functionali **Q: What backend devices are supported?** -A: Supported backends depend on your ChimeraTK installation: - - - Dummy (for testing) - - DOOCS (common in accelerators) - - Modbus TCP/RTU - - Others depending on your build - - Check your device map documentation or ChimeraTK docs for available backends. +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?** @@ -306,7 +199,7 @@ A: Use ``ArrayRegisterAccessor``: .. code-block:: python - array = device.getArrayRegisterAccessor("DATA") + array = device.getArrayRegisterAccessor(int, "DATA") array.read() # Access elements @@ -329,7 +222,7 @@ A: Yes: .. code-block:: python - array = device.getArrayRegisterAccessor("DATA") + array = device.getArrayRegisterAccessor(float, "DATA") array.read() # Modify @@ -342,13 +235,13 @@ A: Yes: **Q: Can I use NumPy with the bindings?** -A: Yes! NumPy is very useful for array operations: +A: Yes! The bindings were written with NumPy as a use-case. .. code-block:: python import numpy as np - array = device.getArrayRegisterAccessor("DATA") + array = device.getArrayRegisterAccessor(int, "DATA") array.read() # Convert to NumPy diff --git a/doc/overview.rst b/doc/overview.rst index 5ec01a5..6bfd812 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -19,10 +19,10 @@ 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, arrays, and complex data structures +* **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 backend plugins +* **Multiple Backends**: Support for various communication protocols through production ready backends Common Use Cases diff --git a/doc/troubleshooting.rst b/doc/troubleshooting.rst index 6becb0a..09fd7d0 100644 --- a/doc/troubleshooting.rst +++ b/doc/troubleshooting.rst @@ -3,350 +3,6 @@ Troubleshooting This guide helps you diagnose and solve common issues with the DeviceAccess Python bindings. - -Installation Problems ---------------------- - -Module Import Fails -~~~~~~~~~~~~~~~~~~~~ - -**Problem:** ``ModuleNotFoundError: No module named 'deviceaccess'`` - -**Diagnosis:** - -.. code-block:: bash - - # Check if the module is installed - python -c "import deviceaccess; print(deviceaccess.__file__)" - - # Check Python path - python -c "import sys; print(sys.path)" - - # Verify installation - pip show chimeratk-deviceaccess - -**Solutions:** - -1. **Package not installed**: Reinstall it - - .. code-block:: bash - - pip install --upgrade chimeratk-deviceaccess - # or from source: - mkdir build && cd build && cmake .. && make && sudo make install - -2. **Wrong Python environment**: Ensure you're using the correct Python - - .. code-block:: bash - - which python - which python3 - # Activate the correct virtual environment if using one - -3. **Installation path issue**: Try installing with user flag - - .. code-block:: bash - - pip install --user chimeratk-deviceaccess - - -Missing Dependencies -~~~~~~~~~~~~~~~~~~~~ - -**Problem:** ``ImportError: libDOOCSapi.so: cannot open shared object file`` - -**Solution:** Install the ChimeraTK DeviceAccess library dependency - -.. code-block:: bash - - # On Debian/Ubuntu - sudo apt-get install libchimeratk-deviceaccess - - # Or build and install from source - git clone https://github.com/ChimeraTK/ChimeraTK-DeviceAccess.git - cd ChimeraTK-DeviceAccess - mkdir build && cd build && cmake .. && make && sudo make install - - -Device Connection Issues ------------------------- - -Cannot Open Device -~~~~~~~~~~~~~~~~~~~ - -**Problem:** ``DoocsException: Cannot open device 'MY_DEVICE'`` - -**Diagnosis Steps:** - -1. Check the device map file exists and is accessible: - - .. code-block:: bash - - test -f $DEVICE_MAP_FILE && echo "Map file found" || echo "Map file not found" - -2. Verify the device map file syntax: - - .. code-block:: bash - - cat $DEVICE_MAP_FILE - # Look for proper format: DEVICE_NAME (backend_spec) - -3. Test device name is correct: - - .. code-block:: python - - import deviceaccess - try: - device = deviceaccess.Device("MY_DEVICE") - print("Device opened successfully") - except Exception as e: - print(f"Error: {e}") - -4. Check environment variables: - - .. code-block:: bash - - env | grep -i device - env | grep -i map - -**Solutions:** - -1. **Device map file not set**: Ensure the environment variable is set - - .. code-block:: bash - - export DEVICE_MAP_FILE=/path/to/devices.dmap - python your_script.py - -2. **Wrong device name**: Verify the name matches exactly (case-sensitive) - - .. code-block:: python - - # In device map: MY_DEVICE - # In code: must also be MY_DEVICE (not my_device) - device = deviceaccess.Device("MY_DEVICE") - -3. **Device map syntax error**: Check format is correct - - .. code-block:: text - - # Good: DEVICE_NAME (backend://spec) - # Bad: DEVICE_NAME (backend://spec # Missing space - # Bad: DEVICE_NAME backend://spec # Missing parentheses - TEST (dummy_name_prefix:?) - -4. **Backend not available**: The specified backend might not be built - - .. code-block:: bash - - # Check available backends - apt search chimeratk-device | grep backend - # or check build logs - - -Connection Timeout -~~~~~~~~~~~~~~~~~~ - -**Problem:** ``TimeoutException: Read timeout after waiting X seconds`` - -**Possible Causes:** - -- Device is offline or unreachable -- Network issues (for remote devices) -- Device is busy or locked -- Firewall blocking communication - -**Solutions:** - -1. **Verify device is online**: - - .. code-block:: bash - - # For network devices, try ping - ping 192.168.1.100 - - # For local devices, check if they're visible - lsusb # for USB devices - dmesg # for device messages - -2. **Check network connectivity**: - - .. code-block:: bash - - # Check firewall rules - sudo iptables -L -n - - # Try connecting to device port - telnet 192.168.1.100 502 # For Modbus TCP - -3. **Increase timeout** (if supported): - - .. code-block:: python - - # This depends on the backend implementation - # See backend documentation for timeout options - -4. **Enable debug logging** to see what's happening: - - .. code-block:: bash - - export DEVICEACCESS_DEBUG=1 - python your_script.py - - -Permission Denied -~~~~~~~~~~~~~~~~~ - -**Problem:** ``DoocsException: Permission denied`` or ``OSError: Permission denied`` - -**Solutions:** - -1. **Insufficient user privileges**: Run with appropriate permissions - - .. code-block:: bash - - # For USB devices, you might need to be in the right group - sudo usermod -a -G plugdev $USER - - # Or use sudo if necessary - sudo python your_script.py - -2. **Serial port access**: For serial devices - - .. code-block:: bash - - # Add user to dialout group - sudo usermod -a -G dialout $USER - - # Check device permissions - ls -l /dev/ttyUSB0 - -3. **Device file permissions**: If using a socket or special device - - .. code-block:: bash - - # Check who owns the device - ls -l /path/to/device - - # Give your user access if needed - sudo chown $USER:$USER /path/to/device - - -Register Access Issues ----------------------- - -Register Not Found -~~~~~~~~~~~~~~~~~~~ - -**Problem:** ``DoocsException: Register 'REGISTER_NAME' not found`` - -**Diagnosis:** - -1. Check the register name is spelled correctly: - - .. code-block:: python - - # In device config: TEMPERATURE_SENSOR - # In code: must match exactly - register = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") - -2. Verify the register exists on the device: - - .. code-block:: bash - - # Check device documentation - # Try with a known register to verify device connection works - -**Solutions:** - -1. **Spelling error**: Register names are case-sensitive - - .. code-block:: python - - # Wrong: TEMPERATURE (device has TEMPERATURE_SENSOR) - # Right: - register = device.getScalarRegisterAccessor("TEMPERATURE_SENSOR") - -2. **Wrong device**: Accessing from incorrect device - - .. code-block:: python - - # Check you're using the right device - print(f"Device opened, looking for: REGISTER_NAME") - register = device.getScalarRegisterAccessor("REGISTER_NAME") - -3. **Backend doesn't expose register**: Some backends filter registers - - .. code-block:: bash - - # Verify register is available via backend - # Check backend configuration files or device documentation - - -Type Conversion Errors -~~~~~~~~~~~~~~~~~~~~~~ - -**Problem:** ``ValueError: Cannot convert to type X`` - -**Example:** - -.. code-block:: python - - register.read() - value = int(register) # Fails if register contains text - - -**Solutions:** - -1. **Use correct type**: Match the hardware type - - .. code-block:: python - - # If register is a string - value = str(register) - - # If register is float - value = float(register) - -2. **Check hardware documentation**: Verify what type the register actually is - -3. **Explicit type handling**: - - .. code-block:: python - - import deviceaccess - - try: - register.read() - value = float(register) - except ValueError: - # Fallback to string - value = str(register) - - -Data Inconsistency -~~~~~~~~~~~~~~~~~~ - -**Problem:** Multiple register values don't seem to match expectations - -**Solution:** Use transfer groups for consistency - -.. code-block:: python - - # Wrong: Values might be inconsistent - voltage = device.getScalarRegisterAccessor("VOLTAGE") - current = device.getScalarRegisterAccessor("CURRENT") - - voltage.read() - current.read() # Voltage might have changed between reads - - # Right: Consistent read - group = device.getTransferGroup() - group.addAccessor(voltage) - group.addAccessor(current) - group.read() # Both read at same time - - Performance Issues ------------------ @@ -361,11 +17,11 @@ Read/Write Operations Slow # Bad: Create accessor in loop for i in range(1000): - register = device.getScalarRegisterAccessor("VALUE") # Slow! + register = device.getScalarRegisterAccessor(int, "VALUE") # Slow! register.read() # Good: Reuse accessor - register = device.getScalarRegisterAccessor("VALUE") + register = device.getScalarRegisterAccessor(int,"VALUE") for i in range(1000): register.read() @@ -384,143 +40,30 @@ Read/Write Operations Slow for i in range(100): # Use value, don't read again -3. **Using individual reads instead of transfer groups?** - - .. code-block:: python - - # Bad: N hardware operations - for reg in registers: - reg.read() - - # Good: One hardware operation - group = device.getTransferGroup() - for reg in registers: - group.addAccessor(reg) - group.read() **Solutions:** - Reuse accessors - Minimize hardware access operations -- Use transfer groups - Cache data between reads if appropriate -Memory Issues -~~~~~~~~~~~~~ - -**Problem:** High memory usage with large arrays - -**Solutions:** - -1. **Process arrays in chunks**: - - .. code-block:: python - - import numpy as np - - array = device.getArrayRegisterAccessor("LARGE_ARRAY") - array.read() - - # Process in chunks - chunk_size = 1000 - for i in range(0, len(array), chunk_size): - chunk = np.array(array[i:i+chunk_size]) - # Process chunk... - -2. **Use iterators instead of copying**: - - .. code-block:: python - - array = device.getArrayRegisterAccessor("DATA") - array.read() - - # Iterator doesn't copy all data - for value in array[:]: - process(value) - - -Debugging Tips --------------- - -Enable Debug Output -~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - export DEVICEACCESS_DEBUG=1 - export DEVICE_MAP_DEBUG=1 - python your_script.py - - -Print Diagnostic Information -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import deviceaccess - import sys - - print("Python:", sys.version) - print("DeviceAccess version:", deviceaccess.__version__) - - # Try opening a device - try: - device = deviceaccess.Device("TEST") - print("Device opened successfully") - except Exception as e: - print(f"Error: {e}") - import traceback - traceback.print_exc() - - -Check System Resources -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: bash - - # Check available memory - free -h - - # Check file descriptors - ulimit -n - - # Monitor processes - ps aux | grep python - - -Use Python Debugger -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import pdb - import deviceaccess - - pdb.set_trace() # Execution stops here - - # Commands: c (continue), n (next), s (step), p var (print variable) - device = deviceaccess.Device("MY_DEVICE") - - Getting Help ------------ When reporting issues, include: 1. Python version: ``python --version`` -2. Library version: ``pip show chimeratk-deviceaccess`` +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 -7. Debug output (with ``DEVICEACCESS_DEBUG=1``) **Report to:** +- Inside DESY: Redmine - GitHub Issues: https://github.com/ChimeraTK/ChimeraTK-DeviceAccess-PythonBindings/issues -- ChimeraTK Wiki: https://chimeratk.github.io/ -- Your local project documentation See Also diff --git a/doc/user_guide.rst b/doc/user_guide.rst index 674069c..cb58ea5 100644 --- a/doc/user_guide.rst +++ b/doc/user_guide.rst @@ -25,22 +25,7 @@ Accessor Lifecycle .. code-block:: python - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - # Obtain: Get accessor - value = device.getScalarRegisterAccessor("VALUE") - - # Transfer: Read from hardware - value.read() - - # Access: Work with local buffer (no hardware communication) - current = float(value) - doubled = current * 2 - - # Modify and transfer back - value.write(doubled) + # TODO Accessor Types @@ -50,33 +35,21 @@ Accessor Types .. code-block:: python - scalar = device.getScalarRegisterAccessor("VOLTAGE") - scalar.read() - print(float(scalar)) # Behaves like a float - scalar.write(42.0) + # TODO -**ArrayRegisterAccessor**: Array of values +**ArrayRegisterAccessor**: List of values .. code-block:: python - array = device.getArrayRegisterAccessor("SPECTRUM") - array.read() - print(len(array)) # Length of array - print(array[0]) # First element - for val in array[:]: # Iterate over all values - print(val) + # TODO -**NDRegisterAccessor**: Multi-dimensional arrays +**TwoDRegisterAccessor**: Two-dimensional arrays .. code-block:: python - # For 2D or higher dimensional data - matrix = device.getNDRegisterAccessor("IMAGE") - matrix.read() - # Access like a nested array - + # TODO Type Conversion --------------- @@ -84,53 +57,11 @@ Type Conversion Automatic Type Conversion ~~~~~~~~~~~~~~~~~~~~~~~~~~ -The bindings automatically convert between hardware and Python types: - -.. code-block:: python - - # Hardware might store as raw integers, Python sees floats - sensor = device.getScalarRegisterAccessor("TEMPERATURE") - sensor.read() - - # These all work: - temp_float = float(sensor) - temp_int = int(sensor) - temp_str = str(sensor) - - -Explicit Type Specification -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When getting an accessor, specify the Python type you want: - -.. code-block:: python - - # Read as double - double_val = device.getScalarRegisterAccessor("VALUE") - - # Read as integer - int_val = device.getScalarRegisterAccessor("COUNTER") - - -Handling Arrays -~~~~~~~~~~~~~~~ +The bindings automatically convert between hardware and Python types as set on accessor creation: .. code-block:: python - import numpy as np - - array = device.getArrayRegisterAccessor("DATA") - array.read() - - # Convert to NumPy for analysis - data = np.array(array[:]) - - # Modify and write back - modified = data * 2 - for i, val in enumerate(modified): - array[i] = val - array.write() - + # TODO Transfer Groups --------------- @@ -145,13 +76,7 @@ if a register changes between reads. .. code-block:: python - # Problem: Values might change between reads - voltage = device.getScalarRegisterAccessor("VOLTAGE") - voltage.read() - - current = device.getScalarRegisterAccessor("CURRENT") - current.read() - # Current was just updated, but voltage is old! + # TODO Example of changes between reads Solution: Transfer Groups @@ -159,16 +84,7 @@ Solution: Transfer Groups .. code-block:: python - # Solution: Read both at once - group = device.getTransferGroup() - - voltage = device.getScalarRegisterAccessor("VOLTAGE") - current = device.getScalarRegisterAccessor("CURRENT") - - group.addAccessor(voltage) - group.addAccessor(current) - - group.read() # Both updated atomically + # TODO Data Consistency Groups @@ -178,72 +94,7 @@ For advanced scenarios with multiple samples or high-frequency updates: .. code-block:: python - consistency_group = device.getDataConsistencyGroup() - - # Add accessors you want consistent snapshots of - register1 = device.getScalarRegisterAccessor("REGISTER_1") - register2 = device.getScalarRegisterAccessor("REGISTER_2") - - consistency_group.addAccessor(register1) - consistency_group.addAccessor(register2) - - # Read guarantees consistency across all accessors - consistency_group.read() - - -Error Handling --------------- - -Understanding Exceptions -~~~~~~~~~~~~~~~~~~~~~~~~~ - -The library raises exceptions for error conditions: - -.. code-block:: python - - import deviceaccess - - try: - device = deviceaccess.Device("MY_DEVICE") - except deviceaccess.Exception as e: - print(f"Error: {e}") - - try: - register = device.getScalarRegisterAccessor("MISSING") - register.read() - except deviceaccess.DoocsException as e: - print(f"Device error: {e}") - - -Common Exceptions -~~~~~~~~~~~~~~~~~ - -* ``DeviceException``: Device not found or unavailable -* ``DoocsException``: DOOCS backend specific errors -* ``NotImplemented``: Feature not supported by backend -* ``TimeoutException``: Operation took too long - - -Best Practices for Error Handling -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import deviceaccess - import time - - def safe_read(accessor, max_retries=3): - """Read with retry logic""" - for attempt in range(max_retries): - try: - accessor.read() - return float(accessor) - except deviceaccess.TimeoutException: - if attempt < max_retries - 1: - time.sleep(0.1) # Brief delay before retry - else: - raise - + # TODO Device Maps ----------- @@ -258,14 +109,7 @@ Basic Syntax # Device map format # LABEL BACKEND_SPECIFICATION - # Dummy device for testing - TEST_DEVICE (dummy_name_prefix:?) - - # Real DOOCS device - ACCELERATOR (doocs://192.168.1.100) - - # Modbus TCP device - SENSOR_ARRAY (modbus://192.168.1.50?address_list=sensors.xml) + # TODO: Give examples Best Practices @@ -273,165 +117,9 @@ Best Practices * Use meaningful device labels * Organize by system or subsystem -* Document non-obvious backend specifications -* Keep separate maps for testing and production * Version control your device maps -Performance Considerations --------------------------- - -Minimize Hardware Communication -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Inefficient: Multiple reads - for i in range(1000): - register.read() - print(float(register)) - - # Efficient: Read once, work with value - register.read() - value = float(register) - for i in range(1000): - print(value) - - -Use Transfer Groups -~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Multiple individual reads - for r in registers: - r.read() # Each is a separate hardware operation - - # Transfer group: Single operation - group = device.getTransferGroup() - for r in registers: - group.addAccessor(r) - group.read() # One hardware operation for all - - -Batch Operations -~~~~~~~~~~~~~~~~ - -.. code-block:: python - - # Instead of: - for value in values: - register.write(value) - - # Consider: Buffer updates and write together - register.write(final_value) - - -Thread Safety -------------- - -The library provides synchronous, thread-safe operations, but there are considerations: - -Thread Safety Guarantees -~~~~~~~~~~~~~~~~~~~~~~~~ - -* Device objects are thread-safe for read/write operations -* Accessors from the same device can be used concurrently -* Each read/write operation is atomic - -.. code-block:: python - - import threading - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - def read_thread(): - register = device.getScalarRegisterAccessor("VALUE") - while True: - register.read() - print(f"Read: {float(register)}") - - def write_thread(): - register = device.getScalarRegisterAccessor("VALUE") - register.write(42.0) - - # Both threads can safely access the device - t1 = threading.Thread(target=read_thread) - t2 = threading.Thread(target=write_thread) - t1.start() - t2.start() - - -Debugging ---------- - -Enabling Debug Output -~~~~~~~~~~~~~~~~~~~~~ - -Set environment variables to enable debug logging: - -.. code-block:: bash - - export DEVICEACCESS_DEBUG=1 - python your_script.py - - -Checking Device Availability -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import deviceaccess - - try: - device = deviceaccess.Device("MY_DEVICE") - print("Device opened successfully") - except Exception as e: - print(f"Cannot open device: {e}") - - -Testing Connections -~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - import deviceaccess - - device = deviceaccess.Device("MY_DEVICE") - - # Try a simple read - try: - test_register = device.getScalarRegisterAccessor("TEST_REGISTER") - test_register.read() - print(f"Connection OK, value: {float(test_register)}") - except Exception as e: - print(f"Connection failed: {e}") - - -Memory Management ------------------ - -Accessors hold references to registers, so be mindful of: - -* Creating many accessors for large arrays -* Long-lived accessor objects -* Memory usage in monitoring loops - -.. code-block:: python - - # Good: Reuse accessors - register = device.getScalarRegisterAccessor("VALUE") - for i in range(1000): - register.read() - # Process... - - # Avoid: Creating accessors in loops - for i in range(1000): - register = device.getScalarRegisterAccessor("VALUE") - register.read() - - See Also -------- From 2149e3dbb37d7e03f67ac75c7755f28b2bde5cca Mon Sep 17 00:00:00 2001 From: Christian Willner <34183939+vaeng@users.noreply.github.com> Date: Fri, 8 May 2026 10:15:21 +0200 Subject: [PATCH 5/5] fix: apply copilot suggestions Co-authored-by: Copilot --- doc/examples.rst | 4 ++-- doc/faq.rst | 16 ++++++++-------- doc/index.rst | 2 +- doc/mtca4u.rst | 2 +- doc/overview.rst | 2 +- pyproject.toml | 2 +- src/PyTwoDRegisterAccessor.cc | 3 --- src/deviceaccessPython.cc | 7 ++++++- tests/testDocExamples.py | 3 +-- 9 files changed, 21 insertions(+), 20 deletions(-) diff --git a/doc/examples.rst b/doc/examples.rst index a60d176..9e01eb9 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -20,7 +20,7 @@ Reading and writing a single register value: .. _array_example_python: Working with 1D Accessors -------------------- +------------------------- Reading and processing array data: @@ -59,7 +59,7 @@ Then use them the same way in your Python code: # All use the same accessor interface for device in [dummy, pcie, scpi, opcua]: - value = device.getScalarRegisterAccessor("MEASUREMENT") + value = device.getScalarRegisterAccessor(int, "MEASUREMENT") value.read() print(f"Value: {float(value)}") diff --git a/doc/faq.rst b/doc/faq.rst index 51df7b0..4740222 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -8,12 +8,12 @@ Installation and Setup **Q: How do I install the Python bindings?** A: See the :doc:`getting_started` guide for detailed installation instructions. - Quick version: ``pip install chimeratk-deviceaccess`` or build from source with CMake. + Quick version: install the distribution packages as described there, or build from source with CMake. **Q: What Python versions are supported?** -A: Python 3.6 and higher are supported. Check the project's CI/CD configuration +A: Python 3.12 and higher are supported. Check the project's CI/CD configuration for the currently tested versions. @@ -34,7 +34,7 @@ A: Create an accessor and call ``read()``: .. code-block:: python - accessor = device.getScalarRegisterAccessor("REGISTER_NAME") + accessor = device.getScalarRegisterAccessor(float, "REGISTER_NAME") accessor.read() value = float(accessor) @@ -83,12 +83,12 @@ A: Yes, but methods vary by accessor type: .. code-block:: python # Scalar - scalar = device.getScalarRegisterAccessor("VALUE") + scalar = device.getScalarRegisterAccessor(float, "VALUE") scalar.set(42.0) scalar.write() # Array - array = device.getArrayRegisterAccessor("DATA") + 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 @@ -199,7 +199,7 @@ A: Use ``ArrayRegisterAccessor``: .. code-block:: python - array = device.getArrayRegisterAccessor(int, "DATA") + array = device.getOneDRegisterAccessor(int, "DATA") array.read() # Access elements @@ -222,7 +222,7 @@ A: Yes: .. code-block:: python - array = device.getArrayRegisterAccessor(float, "DATA") + array = device.getOneDRegisterAccessor(float, "DATA") array.read() # Modify @@ -241,7 +241,7 @@ A: Yes! The bindings were written with NumPy as a use-case. import numpy as np - array = device.getArrayRegisterAccessor(int, "DATA") + array = device.getOneDRegisterAccessor(int, "DATA") array.read() # Convert to NumPy diff --git a/doc/index.rst b/doc/index.rst index 2b7088c..2ad5364 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -17,7 +17,7 @@ ChimeraTK DeviceAccess Python Bindings Overview -------- -ChimeraTK DeviceAccess Python Bindings provide Pythonic access to the `ChimeraTK DeviceAccess library`__, +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 diff --git a/doc/mtca4u.rst b/doc/mtca4u.rst index 74a9cd7..82c6882 100644 --- a/doc/mtca4u.rst +++ b/doc/mtca4u.rst @@ -1,5 +1,5 @@ mtca4u module (legacy) -============= +====================== .. automodule:: mtca4u :members: diff --git a/doc/overview.rst b/doc/overview.rst index 6bfd812..1ceadec 100644 --- a/doc/overview.rst +++ b/doc/overview.rst @@ -11,7 +11,7 @@ allowing you to focus on your application logic rather than low-level hardware d 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 devision's beam controls group. It is designed to be robust, efficient, and easy to use in a variety of applications. +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 diff --git a/pyproject.toml b/pyproject.toml index 2ad36f8..c356b7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,6 @@ max_line_length = 120 [project.optional-dependencies] docs = [ "sphinx >= 6.0", - "sphinx-autodoc-typehints", + "sphinx-autodoc-typehints >= 1.22", "sphinx-rtd-theme >= 3.0", ] diff --git a/src/PyTwoDRegisterAccessor.cc b/src/PyTwoDRegisterAccessor.cc index d634845..7c5312b 100644 --- a/src/PyTwoDRegisterAccessor.cc +++ b/src/PyTwoDRegisterAccessor.cc @@ -344,9 +344,6 @@ namespace ChimeraTK { .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. diff --git a/src/deviceaccessPython.cc b/src/deviceaccessPython.cc index e635452..e8db511 100644 --- a/src/deviceaccessPython.cc +++ b/src/deviceaccessPython.cc @@ -100,7 +100,7 @@ PYBIND11_MODULE(deviceaccess, m) { R"(Check whether the ID is valid. Returns: - bool: `True if the ID is valid, `False` otherwise.)") + bool: `True` if the ID is valid, `False` otherwise.)") .def("__ne__", &ChimeraTK::TransferElementID::operator!=, py::arg("other"), R"(Compare two TransferElement IDs for inequality. @@ -131,6 +131,11 @@ PYBIND11_MODULE(deviceaccess, m) { 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. diff --git a/tests/testDocExamples.py b/tests/testDocExamples.py index 396986f..07685e9 100644 --- a/tests/testDocExamples.py +++ b/tests/testDocExamples.py @@ -1,5 +1,4 @@ import unittest -import inspect class TestDocExamples(unittest.TestCase): @@ -51,7 +50,7 @@ def simpleOneDAccess(self): print("Modified waveform data:", waveform) # accessor can be used as a numpy array or python list # slicing also works - print("First 5 elements:", waveform[:3]) + 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])