From 607b8e75b2348a4f726d4d8351ac266055bf2680 Mon Sep 17 00:00:00 2001 From: Tobias Ahrens Date: Tue, 28 Jan 2025 10:24:39 +0100 Subject: [PATCH] Make blank package placeholder This commit removes all sources from the python package. The reason is that the package has been deprecated and we want a clean pypi version --- .github/workflows/code_quality.yml | 25 - .github/workflows/tests.yml | 41 - CHANGELOG.md | 78 - CONTRIBUTING.md | 35 - README.md | 38 +- pyproject.toml | 16 +- src/labone/__init__.py | 20 +- src/labone/core/__init__.py | 41 - src/labone/core/errors.py | 172 - src/labone/core/helper.py | 135 - src/labone/core/hpk_schema.py | 1707 ------ src/labone/core/kernel_session.py | 152 - src/labone/core/session.py | 993 ---- src/labone/core/shf_vector_data.py | 416 -- src/labone/core/subscription.py | 538 -- src/labone/core/value.py | 320 -- src/labone/dataserver.py | 245 - src/labone/errors.py | 5 - src/labone/instrument.py | 181 - src/labone/mock/__init__.py | 20 - src/labone/mock/automatic_server.py | 354 -- src/labone/mock/errors.py | 7 - src/labone/mock/session.py | 464 -- src/labone/node_info.py | 207 - src/labone/nodetree/__init__.py | 27 - src/labone/nodetree/entry_point.py | 59 - src/labone/nodetree/enum.py | 174 - src/labone/nodetree/errors.py | 23 - src/labone/nodetree/helper.py | 236 - src/labone/nodetree/node.py | 1533 ------ src/labone/server/__init__.py | 19 - tests/__init__.py | 1 - tests/core/__init__.py | 1 - tests/core/resources/error.capnp | 30 - tests/core/resources/hello_msg.capnp | 30 - tests/core/resources/orchestrator.capnp | 30 - tests/core/resources/path.capnp | 11 - tests/core/resources/reflection.capnp | 13 - tests/core/resources/result.capnp | 14 - tests/core/resources/session_protocol.capnp | 159 - tests/core/resources/testfile.capnp | 5 - tests/core/resources/uuid.capnp | 18 - tests/core/resources/value.capnp | 82 - tests/core/test_annotated_value_from_capnp.py | 279 - tests/core/test_annotated_value_to_capnp.py | 261 - tests/core/test_shf_vector_data.py | 221 - tests/core/test_subscription.py | 383 -- tests/mock/__init__.py | 1 - .../ab_hpk_automatic_functionality_test.py | 241 - tests/mock/module_test.py | 145 - .../mock/test_automatic_mock_functionality.py | 282 - tests/mock/test_session_mock_template.py | 274 - tests/mock_server_for_testing.py | 54 - tests/nodetree/__init__.py | 1 - .../nodetree/resources/device_nodes_info.json | 4791 ----------------- tests/nodetree/resources/zi_nodes_info.json | 147 - tests/nodetree/test_node_functional.py | 322 -- tests/nodetree/test_node_info.py | 187 - .../test_node_to_session_interface.py | 144 - tests/nodetree/test_not_raises.py | 46 - tests/nodetree/test_result_node.py | 183 - tests/nodetree/test_split_join_path.py | 56 - tests/nodetree/test_user_experience.py | 221 - tests/test_dataserver.py | 113 - tests/test_instrument.py | 61 - tests/test_version.py | 7 - 66 files changed, 16 insertions(+), 17079 deletions(-) delete mode 100644 .github/workflows/code_quality.yml delete mode 100644 .github/workflows/tests.yml delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.md delete mode 100644 src/labone/core/__init__.py delete mode 100644 src/labone/core/errors.py delete mode 100644 src/labone/core/helper.py delete mode 100644 src/labone/core/hpk_schema.py delete mode 100644 src/labone/core/kernel_session.py delete mode 100644 src/labone/core/session.py delete mode 100644 src/labone/core/shf_vector_data.py delete mode 100644 src/labone/core/subscription.py delete mode 100644 src/labone/core/value.py delete mode 100644 src/labone/dataserver.py delete mode 100644 src/labone/errors.py delete mode 100644 src/labone/instrument.py delete mode 100644 src/labone/mock/__init__.py delete mode 100644 src/labone/mock/automatic_server.py delete mode 100644 src/labone/mock/errors.py delete mode 100644 src/labone/mock/session.py delete mode 100644 src/labone/node_info.py delete mode 100644 src/labone/nodetree/__init__.py delete mode 100644 src/labone/nodetree/entry_point.py delete mode 100644 src/labone/nodetree/enum.py delete mode 100644 src/labone/nodetree/errors.py delete mode 100644 src/labone/nodetree/helper.py delete mode 100644 src/labone/nodetree/node.py delete mode 100644 src/labone/server/__init__.py delete mode 100644 tests/__init__.py delete mode 100644 tests/core/__init__.py delete mode 100644 tests/core/resources/error.capnp delete mode 100644 tests/core/resources/hello_msg.capnp delete mode 100644 tests/core/resources/orchestrator.capnp delete mode 100644 tests/core/resources/path.capnp delete mode 100644 tests/core/resources/reflection.capnp delete mode 100644 tests/core/resources/result.capnp delete mode 100644 tests/core/resources/session_protocol.capnp delete mode 100644 tests/core/resources/testfile.capnp delete mode 100644 tests/core/resources/uuid.capnp delete mode 100644 tests/core/resources/value.capnp delete mode 100644 tests/core/test_annotated_value_from_capnp.py delete mode 100644 tests/core/test_annotated_value_to_capnp.py delete mode 100644 tests/core/test_shf_vector_data.py delete mode 100644 tests/core/test_subscription.py delete mode 100644 tests/mock/__init__.py delete mode 100644 tests/mock/ab_hpk_automatic_functionality_test.py delete mode 100644 tests/mock/module_test.py delete mode 100644 tests/mock/test_automatic_mock_functionality.py delete mode 100644 tests/mock/test_session_mock_template.py delete mode 100644 tests/mock_server_for_testing.py delete mode 100644 tests/nodetree/__init__.py delete mode 100644 tests/nodetree/resources/device_nodes_info.json delete mode 100644 tests/nodetree/resources/zi_nodes_info.json delete mode 100644 tests/nodetree/test_node_functional.py delete mode 100644 tests/nodetree/test_node_info.py delete mode 100644 tests/nodetree/test_node_to_session_interface.py delete mode 100644 tests/nodetree/test_not_raises.py delete mode 100644 tests/nodetree/test_result_node.py delete mode 100644 tests/nodetree/test_split_join_path.py delete mode 100644 tests/nodetree/test_user_experience.py delete mode 100644 tests/test_dataserver.py delete mode 100644 tests/test_instrument.py delete mode 100644 tests/test_version.py diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml deleted file mode 100644 index 51da736..0000000 --- a/.github/workflows/code_quality.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Code quality check - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - code-quality: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] - steps: - - uses: actions/checkout@v4 - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install -r requirements.txt - - name: Run style check - run: hatch run +py=${{ matrix.python-version }} lint:all diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index b8302a4..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Tests - -on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - -jobs: - tests: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - include: - - name: py39 - python-version: 3.9 - - name: py310 - python-version: "3.10" - - name: py311 - python-version: "3.11" - coverage: true - - name: py312 - python-version: "3.12" - - name: py313 - python-version: "3.13" - steps: - - uses: actions/checkout@v4 - - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: pip install -r requirements.txt - - name: Run tests - run: hatch run +py=${{ matrix.python-version }} test:cov - - name: Upload coverage reports to Codecov - if: matrix.coverage - uses: codecov/codecov-action@v4 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 184a3a5..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,78 +0,0 @@ -# Labone Python API Changelog - -## Version 3.2.1 -* Fix bug that caused subscriptions to potentially miss value updates after the subscription was registered but before the subscribe functions returned. - -## Version 3.2.0 -* `subscribe` accepts keyword arguments, which are forwarded to the data-server. - This allows to configure the subscription to the data-server. - Note that as of LabOne 24.10, no node supports yet subscription configuration. -* Fix error message in data server log if a subscription is cancelled gracefully. -* Adapt mock data server to hand unsubscribe events correctly. - -## Version 3.1.2 -* Fix bug which caused streaming errors to cancel the subscriptions -* Raise severity of errors during subscriptions to `FAILED` to cause a data server - log entry. - -## Version 3.1.1 -* Add support for Python 3.13 - -## Version 3.1.0 -* Expose timeout from underlying package when creating a new connection. - This allows specifying custom timeouts, e.g. when dealing with slow networks - -## Version 3.0.0 - -* Enable server side parsing of the shf vectors. This is a breaking change since -the structure of the shf vector structs are now unified with the capnp schema. -* Replace the following `labone.core.Value` types with their capnp equivalent: - `ShfDemodulatorVectorData`,`ShfResultLoggerVectorData`,`ShfScopeVectorData`, - `ShfPidVectorData`,`CntSample`,`TriggerSample` -* Move the `extra_header` from the annotated value into the value field. This only affects -shf vectors -* Adapt the `session.set` and `session.set_with_expression` functions to take either -an `AnnotatedValue` or `Value` and a path. This prevents unnecessary copies. -* Add support for `zhinst.comms` 3.0 -* Update the `hpk_schema` to the latest version. This included stubs for all structs -defined in the capnp schema. - -## Version 2.3.2 -* Pump version of `zhinst.comms` to 2.1 - -## Version 2.3.1 -* Add missing dependency on setuptools - -## Version 2.3.0 -* Pump version of `zhinst.comms` to 2.0.0 -* Deprecate `labone.server` since the logic has been moved to `zhinst.comms.server` - -## Version 2.2.0 -* Adapt the server module to support closing a running server and run a server - forever. - -## Version 2.1.0 -* Pump version of `zhinst.comms` to 1.1.0 - -## Version 2.0.0 - -* Switch to the custom capnp backend `zhinst.comms`. This fixes the stability issues. - -## Version 1.1.0 - -* Introduce `labone.server` which allows spawning capnp servers based on the - reflection schema. -* Adapt `labone` to latest capnp schema improvements. -* Fix bug in `labone.nodetree` that caused the node tree never to be destroyed. - The result was that when creating and deleting sessions frequently pycapnp - would crash because to many sessions where active at the same time. - -## Version 1.0.0 - -* Initial release of the `labone` API. -* Full LabOne Session functionality. - * get Value(s) - * set Values(s) - * list nodes - * subscribe -* Async node tree implementation. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d8d5d02..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,35 +0,0 @@ -# Contributing - -We welcome any contribution. Since the project is currently not intended for direct -use we do not offer support or external documentation. - -## Development - -This project uses [Hatch](https://hatch.pypa.io/latest/) for development management. - -### Install initial requirements - -```bash -pip install -r requirements.txt -``` - -### Running tests - -### Package tests - -```bash -hatch run test:test -``` - -### Code style tests - -```bash -hatch run lint:fmt -hatch run lint:typing -``` - -### Building the package - -```bash -hatch build -``` diff --git a/README.md b/README.md index fb2141c..5c20737 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,6 @@ -| | | -| --- | --- | -| Package | [![PyPI version](https://badge.fury.io/py/labone.svg)](https://badge.fury.io/py/labone) | -| Meta | [![Hatch project](https://img.shields.io/badge/%F0%9F%A5%9A-Hatch-4051b5.svg)](https://github.com/pypa/hatch) [![linting - Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json)](https://github.com/charliermarsh/ruff) [![code style - Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![types - Mypy](https://img.shields.io/badge/types-Mypy-blue.svg)](https://github.com/python/mypy) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)| -| CI | ![](https://github.com/zhinst/labone-python/actions/workflows/github-code-scanning/codeql/badge.svg) ![](https://codecov.io/gh/zhinst/labone-python/branch/main/graph/badge.svg?token=VUDDFQE20M) ![](https://github.com/zhinst/labone-python/actions/workflows/code_quality.yml/badge.svg) ![](https://github.com/zhinst/labone-python/actions/workflows/tests.yml/badge.svg) | ------ - # LabOne Python API -The `labone` package provides a plain asynchronous Python API for [LabOne](https://www.zhinst.com/labone), the control software of Zurich Instruments. - -> [!CAUTION] -> This API package is solely being developed to support [LabOne Q](https://www.zhinst.com/quantum-computing-systems/labone-q), the software framework for quantum computing. -> -> For direct access to the instruments without LabOne Q, the standard Python API for all Zurich Instruments' devices is provided through the `zhinst` package and can be obtained from [PyPI](https://pypi.org/project/zhinst/) by `pip install zhinst`. -> - -> [!NOTE] -> Since `labone` is not intended for direct usage, we do not offer any support -> or external documentation. Please contact [Zurich Instruments](mailto:info@zhinst.com) if you have any questions. - -## LabOne compatibility -The `labone` package is broadly compatible with any LabOne version greater or equal than 23.06. When used with older versions of LabOne, some features may not be available or only partially working. - -| `labone` version | Minimum LabOne version | Best with LabOne | -| ---------------- | ---------------------- | ---------------- | -| up to 2.3 | 23.06 | 23.06 or later | -| up to 3.1.2 | 23.06 | 24.10 or later | -| from 3.2.0 | 23.06 | 25.01 or later | - -## Internal Documentation - -The internal documentation can be found [here](http://docs.pages.zhinst.com/internal-documentation-hub/async_labone/index.html). -Due to the early stage there is not public documentation. - -## Contributing +The `labone` package has been removed for the time being. +Please refer to [zhinst-toolkit](https://pypi.org/project/zhinst-toolkit/) +for the latest LabOne Python API. -See [Contributing](CONTRIBUTING.md) diff --git a/pyproject.toml b/pyproject.toml index 18ce6a4..945b385 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "labone" dynamic = ["version"] -description = "Python API for Zurich Instruments LabOne software." +description = "Zurich Instruments." readme = "README.md" license = { text = "Apache 2.0" } requires-python = ">=3.9" @@ -14,7 +14,7 @@ authors = [ ] keywords = ["zhinst"] classifiers = [ - "Development Status :: 3 - Alpha", + "Development Status :: 7 - Inactive", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", @@ -26,17 +26,11 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering", ] -dependencies = [ - "numpy>=1.20", - "packaging", - "typing_extensions>=4.8.0", - "zhinst-comms~=3.0", -] +dependencies = [] [project.urls] homepage = "https://zhinst.com" -repository = "https://github.com/zhinst/labone-python" -changelog = "https://github.com/zhinst/labone-python/blob/main/CHANGELOG.md" +repository = "https://github.com/zhinst/toolkit" [tool.hatch.version] @@ -83,8 +77,6 @@ dependencies = [ "black>=24.8.0", "mypy>=1.11.2", "ruff>=0.6.4", - "numpy>=1.20", - "zhinst-comms~=3.0", ] [tool.hatch.envs.lint.scripts] diff --git a/src/labone/__init__.py b/src/labone/__init__.py index 0b8d74b..d0a9ba1 100644 --- a/src/labone/__init__.py +++ b/src/labone/__init__.py @@ -1,13 +1,11 @@ -"""Official package for the Zurich Instruments LabOne software.""" +"""Deprecated package for the Zurich Instruments LabOne software. -from labone._version import __version__ # type: ignore[import] -from labone.core import ListNodesFlags -from labone.dataserver import DataServer -from labone.instrument import Instrument +The `labone` package has been removed for the time being. +Please refer to [zhinst-toolkit](https://pypi.org/project/zhinst-toolkit/) +for the latest LabOne Python API. +""" -__all__ = [ - "DataServer", - "Instrument", - "ListNodesFlags", - "__version__", -] +msg = """The `labone` package has been removed for the time being. +Please refer to [zhinst-toolkit](https://pypi.org/project/zhinst-toolkit/) +for the latest LabOne Python API.""" +raise ImportError(msg) diff --git a/src/labone/core/__init__.py b/src/labone/core/__init__.py deleted file mode 100644 index a0ff4e5..0000000 --- a/src/labone/core/__init__.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Subpackage for the core functionality of the LabOne API. - -This subpackage manages the communication with the LabOne data server. -It encapsulates the low level logic of the underlying protocol and provides -a python only interface to the rest of the API. -""" - -from labone.core.helper import ZIContext -from labone.core.kernel_session import ( - KernelInfo, - KernelSession, - ServerInfo, -) -from labone.core.session import ( - ListNodesFlags, - ListNodesInfoFlags, - Session, -) -from labone.core.shf_vector_data import ShfGeneratorWaveformVectorData -from labone.core.subscription import ( - CircularDataQueue, - DataQueue, - DistinctConsecutiveDataQueue, -) -from labone.core.value import AnnotatedValue, Value - -__all__ = [ - "AnnotatedValue", - "CircularDataQueue", - "DataQueue", - "DistinctConsecutiveDataQueue", - "KernelInfo", - "KernelSession", - "ListNodesFlags", - "ListNodesInfoFlags", - "ServerInfo", - "Session", - "ShfGeneratorWaveformVectorData", - "Value", - "ZIContext", -] diff --git a/src/labone/core/errors.py b/src/labone/core/errors.py deleted file mode 100644 index b182e2a..0000000 --- a/src/labone/core/errors.py +++ /dev/null @@ -1,172 +0,0 @@ -"""LabOne Error classes for the core component of the API.""" - -from __future__ import annotations - -import typing as t -from asyncio import QueueEmpty - -import zhinst.comms - -from labone.core import hpk_schema -from labone.errors import LabOneError - - -class LabOneCoreError(LabOneError): - """Base class for all LabOne core errors.""" - - def __init__(self, message: str, code: int = 0x8000, category: str = ""): - super().__init__(message) - self.code = code - self.category = category - - -class CancelledError(LabOneCoreError): - """Raised when a Value or Node can not be found.""" - - -class NotFoundError(LabOneCoreError): - """Raised when a Value or Node can not be found.""" - - -class OverwhelmedError(LabOneCoreError): - """Raised when the server is overwhelmed.""" - - -class BadRequestError(LabOneCoreError): - """Raised when the request cannot be interpreted.""" - - -class UnimplementedError(LabOneCoreError): - """Raised when the request cannot be interpreted.""" - - -class InternalError(LabOneCoreError): - """Raised when an internal error occurs.""" - - -class LabOneTimeoutError(LabOneCoreError, TimeoutError): - """Raised when a timeout occurs.""" - - -class UnavailableError(LabOneCoreError): - """Raised when the kernel is unavailable.""" - - -class SHFHeaderVersionNotSupportedError(LabOneCoreError): - """Raised when the SHF header version is not supported.""" - - def __init__(self, version: tuple[int, int]): - msg = ( - f"The SHF extra header version {version[0]}.{version[1]} " - "is not supported in this context." - ) - super().__init__(msg, code=0, category="SHFHeaderVersionNotSupported") - - -T = t.TypeVar("T") - - -def translate_comms_error( - func: t.Callable[..., t.Awaitable[T]], -) -> t.Callable[..., t.Awaitable[T]]: - """Translate zhinst.comms exceptions to labone exceptions. - - A decorator to catch all exceptions from zhinst.comms and re-raise - them as LabOneCoreError exceptions. - """ - - def wrapper(*args, **kwargs) -> t.Awaitable[T]: - try: - return func(*args, **kwargs) - except zhinst.comms.errors.CancelledError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.NotFoundError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.OverwhelmedError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.BadRequestError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.UnimplementedError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.UnavailableError as e: - raise UnavailableError(str(e)) from e - except zhinst.comms.errors.TimeoutError as e: - raise LabOneTimeoutError(str(e)) from e - except zhinst.comms.errors.BaseError as e: - raise LabOneCoreError(str(e)) from e - - return wrapper - - -def async_translate_comms_error( - func: t.Callable[..., t.Awaitable[T]], -) -> t.Callable[..., t.Awaitable[T]]: - """Translate zhinst.comms exceptions to labone exceptions. - - A decorator to catch all exceptions from zhinst.comms and re-raise - them as LabOneCoreError exceptions. - """ - - async def wrapper(*args, **kwargs) -> T: - try: - return await func(*args, **kwargs) - except zhinst.comms.errors.CancelledError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.NotFoundError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.OverwhelmedError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.BadRequestError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.UnimplementedError as e: - raise LabOneCoreError(str(e)) from e - except zhinst.comms.errors.UnavailableError as e: - raise UnavailableError(str(e)) from e - except zhinst.comms.errors.TimeoutError as e: - raise LabOneTimeoutError(str(e)) from e - except zhinst.comms.errors.BaseError as e: - raise LabOneCoreError(str(e)) from e - - return wrapper - - -################################################################## -## Streaming Errors ## -################################################################## - - -class StreamingError(LabOneCoreError): - """Base class for all LabOne streaming errors.""" - - -class EmptyDisconnectedDataQueueError(StreamingError, QueueEmpty): - """Raised when the data queue is empty and disconnected.""" - - -_ZI_ERROR_MAP = { - hpk_schema.ErrorKind.cancelled: CancelledError, - hpk_schema.ErrorKind.unknown: LabOneCoreError, - hpk_schema.ErrorKind.notFound: NotFoundError, - hpk_schema.ErrorKind.overwhelmed: OverwhelmedError, - hpk_schema.ErrorKind.badRequest: BadRequestError, - hpk_schema.ErrorKind.unimplemented: UnimplementedError, - hpk_schema.ErrorKind.internal: InternalError, - hpk_schema.ErrorKind.unavailable: UnavailableError, - hpk_schema.ErrorKind.timeout: LabOneTimeoutError, -} - - -def raise_streaming_error(err: hpk_schema.Error) -> None: - """Raise labone error from a labone error struct. - - Args: - err: The streaming error to be converted. - - Raises: - LabOneCoreError: The converted error. - """ - raise _ZI_ERROR_MAP.get(err.kind, LabOneCoreError)( # type: ignore[call-overload] - err.message, - code=err.code, - category=err.category, - ) diff --git a/src/labone/core/helper.py b/src/labone/core/helper.py deleted file mode 100644 index 468c5a1..0000000 --- a/src/labone/core/helper.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Module for stuff that is shared between different modules. - -This module bypasses the circular dependency between the modules -within the core. -""" - -from __future__ import annotations - -import logging -from enum import IntEnum - -import numpy as np -import zhinst.comms -from typing_extensions import TypeAlias - -logger = logging.getLogger(__name__) - -LabOneNodePath: TypeAlias = str - - -ZIContext: TypeAlias = zhinst.comms.CapnpContext -_DEFAULT_CONTEXT: ZIContext | None = None - - -def get_default_context() -> ZIContext: - """Get the default context. - - The default context is a global context that is used by default if no - context is provided. It is typically the desired behavior to have a single - context and there are only rare cases where multiple contexts are needed. - - Returns: - The default context. - """ - global _DEFAULT_CONTEXT # noqa: PLW0603 - if _DEFAULT_CONTEXT is None: - _DEFAULT_CONTEXT = zhinst.comms.CapnpContext() - return _DEFAULT_CONTEXT - - -class VectorValueType(IntEnum): - """Mapping of the vector value type. - - VectorValueType specifies the type of the vector. It uses (a subset) of - values from `ZIValueType_enum` from the C++ client. The most commonly used - types are "VECTOR_DATA" and "BYTE_ARRAY". Some vectors use a different - format, e.g. for SHF devices. - """ - - BYTE_ARRAY = 7 - VECTOR_DATA = 67 - SHF_GENERATOR_WAVEFORM_VECTOR_DATA = 69 - SHF_RESULT_LOGGER_VECTOR_DATA = 70 - SHF_SCOPE_VECTOR_DATA = 71 - SHF_DEMODULATOR_VECTOR_DATA = 72 - - -class VectorElementType(IntEnum): - """Type of the elements in a vector supported by the labone interface. - - Since the vector data is transmitted as a byte array the type of the - elements in the vector must be specified. This enum contains all supported - types by the labone interface. - """ - - UINT8 = 0 - UINT16 = 1 - UINT32 = 2 - UINT64 = 3 - FLOAT = 4 - DOUBLE = 5 - STRING = 6 - COMPLEX_FLOAT = 7 - COMPLEX_DOUBLE = 8 - - @classmethod - def from_numpy_type( - cls, - numpy_type: np.dtype, - ) -> VectorElementType: - """Construct a VectorElementType from a numpy type. - - Args: - numpy_type: The numpy type to be converted. - - Returns: - The VectorElementType corresponding to the numpy type. - - Raises: - ValueError: If the numpy type has no corresponding - VectorElementType. - """ - if np.issubdtype(numpy_type, np.uint8): - return cls.UINT8 - if np.issubdtype(numpy_type, np.uint16): - return cls.UINT16 - if np.issubdtype(numpy_type, np.uint32): - return cls.UINT32 - if np.issubdtype(numpy_type, np.uint64): - return cls.UINT64 - if np.issubdtype(numpy_type, np.single): - return cls.FLOAT - if np.issubdtype(numpy_type, np.double): - return cls.DOUBLE - if np.issubdtype(numpy_type, np.csingle): - return cls.COMPLEX_FLOAT - if np.issubdtype(numpy_type, np.cdouble): - return cls.COMPLEX_DOUBLE - msg = f"Invalid vector element type: {numpy_type}." - raise ValueError(msg) - - def to_numpy_type(self) -> np.dtype: - """Convert to numpy type. - - This should always work since all relevant types are supported by - numpy. - - Returns: - The numpy type corresponding to the VectorElementType. - """ - return _CAPNP_TO_NUMPY_TYPE[self] # type: ignore[return-value] - - -# Static Mapping from VectorElementType to numpy type. -_CAPNP_TO_NUMPY_TYPE = { - VectorElementType.UINT8: np.uint8, - VectorElementType.UINT16: np.uint16, - VectorElementType.UINT32: np.uint32, - VectorElementType.UINT64: np.uint64, - VectorElementType.FLOAT: np.single, - VectorElementType.DOUBLE: np.double, - VectorElementType.STRING: str, - VectorElementType.COMPLEX_FLOAT: np.csingle, - VectorElementType.COMPLEX_DOUBLE: np.cdouble, -} diff --git a/src/labone/core/hpk_schema.py b/src/labone/core/hpk_schema.py deleted file mode 100644 index d8334b9..0000000 --- a/src/labone/core/hpk_schema.py +++ /dev/null @@ -1,1707 +0,0 @@ -# ruff: noqa: E501, N815, D101, D102, N802 -"""Python schema for hpk.capnp. - -This file is generated and contains a Cap'n Proto schema in packed binary format; -specifically a serialized reflection_capnp.CapSchema message with the typeId field -not set. - -The schema has been compiled from the file hpk.capnp. - -This file contains the following schema nodes: -# - capnp/c++.capnp:allowCancellation @0xac7096ff8cfc9dce -# - capnp/c++.capnp:namespace @0xb9c6f99ebf805f2c -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection @0xf9a52e68104bc776 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session @0xb9d445582da4a55c -# - hpk.capnp:Hpk @0xa621130a90860008 -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Params @0x86a77be57bfa08f7 -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:CapSchema @0xcb31ef7a76eb85cf -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getTheSchema$Results @0xc9db2193e1883dd1 -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Params @0xe3e32fd4d93c1199 -# - zhinst/io/protocol/capnp/reflection/reflection.capnp:Reflection.getReflectionVersion$Results @0xfb31a67fd905411a -# - capnp/schema.capnp:Node @0xe682ab4cf923a417 -# - capnp/schema.capnp:ElementSize @0xd1958f7dba521926 -# - capnp/schema.capnp:Field @0x9aad50a41f4af45f -# - capnp/schema.capnp:Node.struct @0x9ea0b19b37fb4435 -# - capnp/schema.capnp:Enumerant @0x978a7cebdc549a4d -# - capnp/schema.capnp:Node.enum @0xb54ab3364333f598 -# - capnp/schema.capnp:Method @0x9500cce23b334d80 -# - capnp/schema.capnp:Superclass @0xa9962a9ed0a4d7f8 -# - capnp/schema.capnp:Node.interface @0xe82753cff0c2218f -# - capnp/schema.capnp:Type @0xd07378ede1f9cc60 -# - capnp/schema.capnp:Value @0xce23dcd2d7b00c9b -# - capnp/schema.capnp:Node.const @0xb18aa5ac7a0d9420 -# - capnp/schema.capnp:Node.annotation @0xec1619d4400a0290 -# - capnp/schema.capnp:Node.NestedNode @0xdebf55bbfa0fc242 -# - capnp/schema.capnp:Annotation @0xf1c8950dab257542 -# - capnp/schema.capnp:Node.Parameter @0xb9521bccf10fa3b1 -# - capnp/schema.capnp:Brand @0x903455f06065422b -# - capnp/schema.capnp:Brand.Scope @0xabd73485a9636bc9 -# - capnp/schema.capnp:Brand.Binding @0xc863cd16969ee7fc -# - capnp/schema.capnp:Type.list @0x87e739250a60ea97 -# - capnp/schema.capnp:Type.enum @0x9e0e78711a7f87a9 -# - capnp/schema.capnp:Type.struct @0xac3a6f60ef4cc6d3 -# - capnp/schema.capnp:Type.interface @0xed8bca69f7fb0cbf -# - capnp/schema.capnp:Type.anyPointer.unconstrained @0x8e3b5f79fe593656 -# - capnp/schema.capnp:Type.anyPointer.parameter @0x9dd1f724f4614a85 -# - capnp/schema.capnp:Type.anyPointer.implicitMethodParameter @0xbaefc9120c56e274 -# - capnp/schema.capnp:Type.anyPointer @0xc2573fe8a23e49f1 -# - capnp/schema.capnp:Field.slot @0xc42305476bb4746f -# - capnp/schema.capnp:Field.group @0xcafccddb68db1d11 -# - capnp/schema.capnp:Field.ordinal @0xbb90d5c287870be6 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Params @0x92035429255ef5a8 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodes$Results @0xebd34bdb74c961b0 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Params @0xcd242e1bb990dfe7 -# - zhinst/io/protocol/capnp/common/result.capnp:Result @0xbab0f33e1934323d -# - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue @0xf408ee376e837cdc -# - zhinst/io/protocol/capnp/common/error.capnp:Error @0xc4e34e4c517d11d9 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedGetValues$Results @0xe948518c129575bb -# - zhinst/io/protocol/capnp/common/value.capnp:Value @0xb1838b4771be75ac -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:ReturnFromSetWhen @0xdd2da53aac55edf9 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Params @0xa8f6fded40f38065 -# - zhinst/io/protocol/capnp/common/value.capnp:VoidStruct @0xdf7fe8e981437816 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue$Results @0x8aef0146a9595df9 -# - zhinst/io/protocol/capnp/streaming/streaming.capnp:Subscription @0xedac21a53de1b1d4 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Params @0xb9b18be9afb1b3e4 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.subscribe$Results @0xa0ca6692df48c93f -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.disconnectDevice$Params @0xe682e60b2cd90782 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Params @0xcd837fe86531f6c1 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.listNodesJson$Results @0xa8151642645bfa32 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.unsubscribe$Params @0xb16a1640db91e61c -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Params @0xd77d14cf14b3405a -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getSessionVersion$Results @0xcc68434da57cf6b3 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Params @0xdae05cd32680aba1 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.deprecatedSetValue2$Results @0xfd47648e56c0e689 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:LookupMode @0xda5049b5e072f425 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Params @0x9fe29a10493e27c7 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.setValue$Results @0x9474d85f26f63900 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Params @0xc5e872236cc4917b -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.getValue$Results @0xce98719dbad0a401 -# - zhinst/io/protocol/capnp/session/session_protocol.capnp:Session.cancel$Params @0xb77c3d30c74311fc -# - zhinst/io/protocol/capnp/common/value.capnp:AnnotatedValue.Metadata @0xad53d8ca57af3018 -# - zhinst/io/protocol/capnp/common/complex.capnp:Complex @0xaaf1afaf97b4b157 -# - zhinst/io/protocol/capnp/common/value.capnp:VectorData @0x994c65b80df38978 -# - zhinst/io/protocol/capnp/common/value.capnp:CntSample @0xe9370bd8287d6065 -# - zhinst/io/protocol/capnp/common/value.capnp:TriggerSample @0xdeb72097c27d0d95 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfDemodulatorVectorData @0x9b03e3e3e6006582 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData @0xbba061f579761ddd -# - zhinst/io/protocol/capnp/common/value.capnp:LargeVectorData @0xd948f7b09959b00a -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfScopeVectorData @0xa9b07f1f82a93bc9 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfGeneratorWaveformVectorData @0xedcfe06e81c4f3d0 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfPidVectorData @0xff6ee171549a2870 -# - zhinst/io/protocol/capnp/common/error.capnp:ErrorKind @0xb7e671e24a9802bd -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfDemodulatorVectorData.properties @0xd475786ca25c300d -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData.properties @0xddb7b421ea0eec5e -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfResultLoggerVectorData.vector @0x980b68b5449bdf12 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfScopeVectorData.properties @0xd707b876f9608924 -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfScopeVectorData.vector @0xd10aab878c8e14bc -# - zhinst/io/protocol/capnp/common/shf_vectors.capnp:ShfPidVectorData.properties @0x9be09d0677995544 -# - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle @0xf51e28a7f5b41574 -# - zhinst/io/protocol/capnp/common/kwargs.capnp:Kwargs @0x8f25ce07a664cee0 -# - zhinst/io/protocol/capnp/streaming/streaming.capnp:StreamingHandle.sendValues$Params @0xa30bad0f967c47b6 -# - capnp/stream.capnp:StreamResult @0x995f9a3377c0b16e -# - zhinst/io/protocol/capnp/common/kwargs.capnp:Kwargs.Entry @0xa2644e6fd3dc2553 - -""" - -from __future__ import annotations - -import typing as t -import warnings - -import zhinst.comms -from packaging.version import Version - -_GENERATED_WITH = Version("3.0.1") - - -def _check_version_compatibility() -> None: - runtime_version = Version(zhinst.comms.__version__) - if runtime_version.major != _GENERATED_WITH.major: - warnings.warn( - f"You are using zhinst.comms version {runtime_version}, but the schema in '{__name__}' was " - f"generated using version {_GENERATED_WITH}. It is recommended to regenerate " - "this file using the 'compile' command.", - stacklevel=3, - ) - - -_check_version_compatibility() - -_CAPNP_BINARY_SCHEMA = b"IQMEM/8D+QcDGQxQAQEAADEBpx9TcAEFBv/OnfyM/5ZwrADREAUBA/+BTjC4e334vQAAATO5DxIBE8kPBwAAEQIB\ -AAH/LF+Av575xrkAURAFAf+BTjC4e334vQAAAREqAROhDwcAABFSAQAB/3bHSxBoLqX5ABE1A/8OrTlskcJhhAAA\ -ARF6ARHCAQAAEfoBE20PBwAA/1ylpC1YRdS5ABE4A/9Hc0PViRLCtgAAARPCAQETCgIBAAATQgIBE5IGAQAA/QiG\ -kAoTIaYRCgP/Ps6GC7EbtOUAAAETogYBE7oGAQAAE8oGARPaBgEAAP/3CPp75XunhgARQAEAAAQHAAATCgcBAAT/\ -z4XrdnrvMcsAUTUBAf8OrTlskcJhhAAFAQcAABNqBwETsgcBAAATwgcBAAH/0T2I4ZMh28kAEUABAAAFAQcAABPq\ -CAEAARNKCQEAAf+ZETzZ1C/j4wARQAEAAAQHAAAT2gkBAAT/GkEF2X+mMfsAEUABAAAFAQcAABNCCgEAAROqCgEA\ -Af8XpCP5TKuC5gBREwEF/9lyTGIJxT+pAEUGBwYBBhMyCwETUgsBAAATwgsBAAH/JhlSun2PldEAERMC/9lyTGIJ\ -xT+pAAABE/IRARMaEgEAABMqEgEAAf9f9EofpFCtmgBREwED/9lyTGIJxT+pAEUEBwIBBBNaEwETghMBAAATshMB\ -AAH/NUT7N5uxoJ4AURgBBf8XpCP5TKuC5gAVBgcBAAATohYBAAETyhYBAAH/TZpU3Ot8ipcAURMBAf/ZckxiCcU/\ -qQAFAgcAABOCGgETqhoBAAATuhoBAAH/mPUzQzazSrUAURgBBf8XpCP5TKuC5gAVBgcBAAATYhwBAAETihwBAAG/\ -gE0zO+LMlVETAQP/2XJMYgnFP6kABQUHAAATOh0BE2IdAQAAE3IdAQAB//jXpNCeKpapAFETAQH/2XJMYgnFP6kA\ -BQEHAAATUgICE3oCAgAAE4oCAgAB/48hwvDPUyfoAFEYAQX/F6Qj+UyrguYAFQYHAQAAE4oDAgABE7oDAgAB/2DM\ -+eHteHPQAFETAQP/2XJMYgnFP6kARQEHEwAAEwIFAhMiBQIAABMyBQIAAf+bDLDX0twjzgBREwEC/9lyTGIJxT+p\ -AEUBBxMAABMiDQITSg0CAAATWg0CAAH/IJQNeqylirEAURgBBf8XpCP5TKuC5gAVBgcBAAATYhYCAAETihYCAAH/\ -kAIKQNQZFuwAURgBBf8XpCP5TKuC5gAVBgcBAAATihcCAAETuhcCAAH/QsIP+rtVv94AURgBAf8XpCP5TKuC5gAF\ -AQcAABNaHgITih4CAAATmh4CAAH/QnUlqw2VyPEAURMBAf/ZckxiCcU/qQAFAgcAABOaHwITwh8CAAAT0h8CAAH/\ -saMP8cwbUrkAERgB/xekI/lMq4LmAAUBBwAAE0ohAhN6IQIAABOKIQIAAf8rQmVg8FU0kAAREwH/2XJMYgnFP6kA\ -BQEHAAATEiICEzoiAgAAE3oiAgAB/8lrY6mFNNerAFEZAQL/K0JlYPBVNJAARQEHAgEEEyIjAhNKIwIAABNaIwIA\ -Af/8556WFs1jyABRGQEB/ytCZWDwVTSQAEUBBwIAABPyJAITIiUCAAATMiUCAAH/l+pgCiU554cAURgBA/9gzPnh\ -7Xhz0AAVAQcBAAATMiYCAAETWiYCAAH/qYd/GnF4Dp4AURgBA/9gzPnh7Xhz0AAVAQcBAAAT6iYCAAETEicCAAH/\ -08ZM72BvOqwAURgBA/9gzPnh7Xhz0AAVAQcBAAATEigCAAETOigCAAH/vwz792nKi+0AURgBA/9gzPnh7Xhz0AAV\ -AQcBAAATOikCAAETaikCAAH/VjZZ/nlfO44AUSMBA//xST6i6D9XwgBVAQcBBAEFE2oqAgABE6oqAgAB/4VKYfQk\ -99GdAFEjAQP/8Uk+oug/V8IAFQEHAQAAE6IsAgABE9osAgAB/3TiVgwSye+6AFEjAQP/8Uk+oug/V8IAFQEHAQAA\ -E+ItAgABEyouAgAB//FJPqLoP1fCAFEYAQP/YMz54e14c9AAVQEHAQMBBBO6LgIAARPqLgIAAf9vdLRrRwUjxABR\ -GQED/1/0Sh+kUK2aABUEBwEAABPaLwIAARMCMAIAAf8RHdto2838ygBRGQED/1/0Sh+kUK2aABUEBwEAABMKMgIA\ -ARMyMgIAAf/mC4eHwtWQuwBRGQED/1/0Sh+kUK2aAFUEBwECAQUTujICAAET6jICAAH/qPVeJSlUA5IAUUABAQAA\ -BQIHAAAT+jMCAAETWjQCAAH/sGHJdNtL0+sAEUABAAAFAQcAABPaNQIAARM6NgIAAf/n35C5Gy4kzQARQAEAAAUC\ -BwAAE+I2AgABE0o3AgAB/z0yNBk+87C6AFEtAQH/1auk3B/j6PcARQEHAhABE2o4AhOqOAIAABO6OAIAABO6OQL/\ -3HyDbjfuCPQAESwB/8e8JTSyy6X8AAUCBwAAE+o5AhMyOgIAABNiOgIAAf/ZEX1RTE7jxABRLAEB/+ubuSzWYnC5\ -AAUDBwAAE2o7AhOqOwIAABO6OwIAAf+7dZUSjFFI6QARQAEAAAUBBwAAEyo+AgABE5I+AgAB/6x1vnFHi4OxAFEs\ -AQL/x7wlNLLLpfwARQEHEgEEEQIDEUIDAAARUgMAAf/57VWsOqUt3QAROAL/R3ND1YkSwrYAAAETmgkDE/IJAwAA\ -EwIKAwAB/2WA80Dt/faoAFFAAQEAAAUCBwAAE7IKAwABExoLAwAB/xZ4Q4Hp6H/fABEsAf/HvCU0ssul/AAEBwAA\ -E5oMAxPaDAMAA//5XVmpRgHvigARQAEAAAUBBwAAE+oMAwABE1INAwAB/9Sx4T2lIaztABEzAf/IdJxrBfSTzAAF\ -BAcAABNqDgMTsg4DAAATwg4DAAH/5LOxr+mLsbkAEUABAAAFAQcAABPCEAMAARMiEQMAAf8/yUjfkmbKoAARQAEA\ -AAUBBwAAE7IRAwABExISAwAB/4IH2SwL5oLmABFAAQAABAcAABMqEwMABP/B9jFl6H+DzQBRQAEBAAAFAgcAABOK\ -EwMAARPqEwMAAf8y+ltkQhYVqAARQAEAAAUBBwAAE2oVAwABE8oVAwAB/xzmkdtAFmqxABFAAQAABQIHAAATWhYD\ -AAETuhYDAAH/WkCzFM8UfdcAEUABAAAEBwAAE+IXAwAE/7P2fKVNQ2jMABFAAQAABQEHAAATShgDAAETshgDAAH/\ -oauAJtNc4NoAUUABAQAABQMHAAATOhkDAAETohkDAAH/iebAVo5kR/0AEUABAAAFAQcAABOaGwMAARMCHAMAAf8l\ -9HLgtUlQ2gAROAL/R3ND1YkSwrYAAAETGh0DE2odAwAAE3odAwAB/8cnPkkQmuKfAFFAAQEAAAUDBwAAE9odAwAB\ -EzIeAwAB/jn2Jl/YdJQRQAEAAAUBBwAAE7IgAwABExIhAwAB/3uRxGwjcujFAFFAAQEAAAUCBwAAE0oiAwABE6Ii\ -AwAB/wGk0LqdcZjOABFAAQAABQEHAAAToiQDAAETAiUDAAH//BFDxzA9fLcAEUABAAAFAQcAABM6JgMAAROSJgMA\ -Af8YMK9XythTrQBROwEB/9x8g2437gj0AAUBBwAAExonAxNqJwMAABN6JwMAAf9XsbSXr6/xqgBRLgEC/+lQjv4W\ -FGPRAAQHAAATgigDE8IoAwAAE9IoAwAB/3iJ8w24ZUyZAFEsAQH/x7wlNLLLpfwABQEHAAAT0ikDExIqAwAAEyIq\ -AwAB/2VgfSjYCzfpAFEsAQL/x7wlNLLLpfwABAcAABMyLAMTciwDAAATgiwDAAH/lQ19wpcgt94AUSwBBf/HvCU0\ -ssul/AAEBwAAEwIuAxNKLgMAABNaLgMAAfuCZebj4wObUTIBCP+UTWR/GIOdtAAFAgcAABPaMQMTMjIDAAATQjID\ -AAH/3R12efVhoLsAUTIBCf+UTWR/GIOdtAAFAQcAABPKMwMTIjQDAAATMjQDAAH/CrBZmbD3SNkAUSwBAf/HvCU0\ -ssul/AAFAQcAABPKNAMTEjUDAAATIjUDAAH/yTupgh9/sKkAUTIBCf+UTWR/GIOdtAAFAQcAABNaNwMTqjcDAAAT\ -ujcDAAH/0PPEgW7gz+0AETIB/5RNZH8Yg520AAUBBwAAE1I4AxOyOAMAABPCOAMAAf9wKJpUceFu/wBRMgEI/5RN\ -ZH8Yg520AAUCBwAAE2o5AxO6OQMAABPKOQMAAf+9AphK4nHmtwARLAL/65u5LNZicLkAAAETUjsDE5I7AwAAE6I7\ -AwAB/w0wXKJseHXUAFFLAQj7gmXm4+MDmxUCBwEAABMqPQMAAROKPQMAAf9e7A7qIbS33QBRTAEJ/90ddnn1YaC7\ -ABUBBwEAABMaRAMAARN6RAMAAf8S35tEtWgLmABRTAEJ/90ddnn1YaC7AFUBBwECAR0ToksDAAETAkwDAAH/JIlg\ -+Xa4B9cAUUUBCf/JO6mCH3+wqQAVAQcBAAATQk0DAAETmk0DAAH/vBSOjIerCtEAUUUBCf/JO6mCH3+wqQBVAQcB\ -AgEgE7pTAwABExJUAwAB/0RVmXcGneCbAFFDAQj/cCiaVHHhbv8AFQIHAQAAE1JVAwABE6pVAwAB/3QVtPWnKB71\ -ABEzA//IdJxrBfSTzAAAARMyXAMTglwDAAATklwDE/pcAwAA/+DOZKYHziWPABEtAf/EzBMjqgRAzAAFAQcAABMK\ -XQMTSl0DAAATcl0DAAH/tkd8lg+tC6MAEUMBAAAFAQcAABMaXgMAARN6XgMAAf9uscB3M5pfmQAREwH/+POTE6lm\ -w4YABAcAABMiXwMTSl8DAAP/UyXc029OZKIAETQB/+DOZKYHziWPAAUCBwAAE1pfAxOiXwMAABOyXwMAAf9jYXBu\ -cC9jKwMrLmNhcG5wOmFsbG93Q2FuY2VsbGF0aW8BblABAVABAVABAVADAQADEQHS/2NhcG5wL2MrAisuY2FwbnA6\ -bmFtZXNwYWMBZVADAQEMAAIxAQIC/3poaW5zdC9pB28vcHJvdG9jb2wvY2FwbnAvcmVmbGVjdGlvbi9yZWZsZWN0\ -aW9uLmNhcG5wOlJlZmxlY3Rpb24AEQEXUQQBAf9a/Q8GdeXLjAARAZL/Y2FwYWJpbGkBdHlWZXJzaW8BbhEBh1EI\ -AwUBAf/3CPp75XunhgHRPYjhkyHbyRExagACESkHAAD/mRE82dQv4+MBGkEF2X+mMfsRHaoAAhEZB/9nZXRUaGVT\ -YwAPaGVtYUAB/2dldFJlZmxlAWN0aW9uVmVyD3Npb25AATEBAgL/emhpbnN0L2kHby9wcm90b2NvbC9jYXBucC9z\ -ZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lvbgARARdRBAEB/9ACL5YcrlC3ABEBkv9jYXBhYmls\ -aQF0eVZlcnNpbwFuMQEHA1EwAwUBAf+o9V4lKVQDkgGwYcl020vT6xNxAVIAAhNpAQcBC//n35C5Gy4kzQG7dZUS\ -jFFI6RNdAaIAAhNZAQcBCf9lgPNA7f32qAH5XVmpRgHvihNNAZoAAhNJAQcBBf/ks7Gv6YuxuQE/yUjfkmbKoBM9\ -AVIAAhM1AQcBB/+CB9ksC+aC5gEWeEOB6eh/3xMpAYoAAhMlAQcBCP/B9jFl6H+DzQEy+ltkQhYVqBMZAXIAAhMR\ -AQcBBv8c5pHbQBZqsQEWeEOB6eh/3xMFAWIAAhH9BwAA/1pAsxTPFH3XAbP2fKVNQ2jMEfGSAAIR7QcBCv+hq4Am\ -01zg2gGJ5sBWjmRH/RHhogACEd0HAQP/xyc+SRCa4p8BADn2Jl/YdJQR0UoAAhHJBwEC/3uRxGwjcujFAQGk0Lqd\ -cZjOEb1KAAIRtQcBBP/8EUPHMD18twEWeEOB6eh/3xGpOgACEZ0H/2xpc3ROb2RlAAFzQAH/ZGVwcmVjYXQBZWRH\ -ZXRWYWwHdWVzQAH/ZGVwcmVjYXQBZWRTZXRWYWwDdWVAAf9zdWJzY3JpYgABZUAB/2Rpc2Nvbm5lAWN0RGV2aWNl\ -AABAAf9saXN0Tm9kZQAfc0pzb25AAf91bnN1YnNjcgAHaWJlQAH/Z2V0U2Vzc2kBb25WZXJzaW8BbkAB/2RlcHJl\ -Y2F0AWVkU2V0VmFsB3VlMkAB/3NldFZhbHVlAAAAQAH/Z2V0VmFsdWUAAABAAT9jYW5jZWxAAREBB1ABAREBcv9o\ -cGsuY2FwbgAfcDpIcGsRAQdQAQERAQdQAwURASdRCAEB/3bHSxBoLqX5AAAA/1ylpC1YRdS5AAAAMQGiAv96aGlu\ -c3QvaQlvL3Byb3RvY29sL2NhcG5wL3JlZmxlY3Rpb24vcmVmbGVjdGlvbi5jYXBucDpSZWZsZWN0aW9uLmdldFRo\ -ZVNjaGVtYSRQYXIHYW1zMQH6Af96aGluc3QvaQZvL3Byb3RvY29sL2NhcG5wL3JlZmxlY3Rpb24vcmVmbGVjdGlv\ -bi5jYXBucDpDYXA/U2NoZW1hEQEHUAEBEQF3UQgDBAAABAEAABEpOgAAUSQDAVEwAgEBARQBAQAAES1SAABRLAMB\ -UUgCAT90eXBlSWQBCQACAQkAAf90aGVTY2hlbQABYQEOAAFQAwEBEP8XpCP5TKuC5gAAAQEOAAExAaoC/3poaW5z\ -dC9pCW8vcHJvdG9jb2wvY2FwbnAvcmVmbGVjdGlvbi9yZWZsZWN0aW9uLmNhcG5wOlJlZmxlY3Rpb24uZ2V0VGhl\ -U2NoZW1hJFJlcw91bHRzEQE/UQQDBAAABAEAABENUgAAUQwDAVEYAgH/dGhlU2NoZW0AAWEBEP/Phet2eu8xywAA\ -AQEQAAExAeIC/3poaW5zdC9pCm8vcHJvdG9jb2wvY2FwbnAvcmVmbGVjdGlvbi9yZWZsZWN0aW9uLmNhcG5wOlJl\ -ZmxlY3Rpb24uZ2V0UmVmbGVjdGlvblZlcnNpb24kUGFyB2FtczEB6gL/emhpbnN0L2kKby9wcm90b2NvbC9jYXBu\ -cC9yZWZsZWN0aW9uL3JlZmxlY3Rpb24uY2FwbnA6UmVmbGVjdGlvbi5nZXRSZWZsZWN0aW9uVmVyc2lvbiRSZXMP\ -dWx0cxEBP1EEAwQAAAQBAAARDUIAAFEIAwFRFAIBf3ZlcnNpb24BDAACAQwAAREBwv9jYXBucC9zYwJoZW1hLmNh\ -cG5wOk5vZGUAEQE3UQwBAf+xow/xzBtSuQAREVL/QsIP+rtVv94AERFa/65XEwTjHY7zABERWv9QYXJhbWV0ZQAB\ -cv9OZXN0ZWRObwADZGX/U291cmNlSW4AA2ZvMQEXA1E4AwQAAAQBAAATeQEaAABTdAEDAVOAAQIBAQEUAQEAABN9\ -AWIAAFN8AQMBU4gBAgERAgIUAQIAABOFAcIAAFOIAQMBU5QBAgERAwIUAQMAABORAUIAAFOMAQMBU5gBAgERBgEU\ -AQQAABOVAWIAAFOUAQMBU7ABAgERBwIUAQUAABOtAWIAAFOsAQMBU8gBAgENCP//FAEGAAATxQEqAABTwAEDAVPM\ -AQIBDQn+/wEB/zVE+zebsaCeABPJAToAAg0K/f8BAf+Y9TNDNrNKtQATsQEqAAINC/z/AQH/jyHC8M9TJ+gAE5kB\ -UgACDQz7/wEB/yCUDXqspYqxABOFATIAAg0N+v8BAf+QAgpA1BkW7AATbQFaAAIRBAUUASAAABNZAVoAAFNYAQMB\ -U3QBAgExBSABFAEhAAATcQFSAABTcAEDAVN8AQIBA2lkAQkAAgEJAAH/ZGlzcGxheU4AB2FtZQEMAAIBDAAB/2Rp\ -c3BsYXlOAmFtZVByZWZpeExlbmd0aAABCAACAQgAAX9zY29wZUlkAQkAAgEJAAH/bmVzdGVkTm8AB2RlcwEOAAFQ\ -AwEBEP9Cwg/6u1W/3gAAAQEOAAH/YW5ub3RhdGkAB29ucwEOAAFQAwEBEP9CdSWrDZXI8QAAAQEOAAEPZmlsZQAG\ -P3N0cnVjdA9lbnVt/2ludGVyZmFjAAFlH2NvbnN0/2Fubm90YXRpAANvbv9wYXJhbWV0ZQADcnMBDgABUAMBARD/\ -saMP8cwbUrkAAAEBDgAB/2lzR2VuZXJpAAFjAQEAAgEBAAERAfr/Y2FwbnAvc2MCaGVtYS5jYXBucDpFbGVtZT9u\ -dFNpemURAQdQAQERAcdRIAECAAARWTIAAAEBEVEiAAABAhFJKgAAAQMRQUoAAAEEET1SAAABBRE5WgAAAQYRNUIA\ -AAEHES2CAAAfZW1wdHkHYml0D2J5dGX/dHdvQnl0ZXMAAAD/Zm91ckJ5dGUAAXP/ZWlnaHRCeXQAA2Vzf3BvaW50\ -ZXL/aW5saW5lQ28BbXBvc2l0ZQARAcr/Y2FwbnAvc2MCaGVtYS5jYXBucDpGaWVsZAAAEQEXUQQBAf8Sx/58vkyx\ -lwARAXr/bm9EaXNjcmkAP21pbmFudDEBjwFRHAMEAAAEAQAAEbUqAABRsAMBUbwCAQEBFAEBAAARuVIAAFG4AwFR\ -xAIBEQIBFAECAAARwWIAAFHAAwFR3AIBEQMBFAEDAQER2ZIAAFHcAwFR6AIBDQT//wEB/290tGtHBSPEABHlKgAC\ -DQX+/wEB/xEd22jbzfzKABHNMgACAQYBAf/mC4eHwtWQuwARtUIAAg9uYW1lAQwAAgEMAAH/Y29kZU9yZGUAAXIB\ -BwACAQcAAf9hbm5vdGF0aQAHb25zAQ4AAVADAQEQ/0J1JasNlcjxAAABAQ4AAf9kaXNjcmltaQFuYW50VmFsdQFl\ -AQcAAg0H//8AAQ9zbG90H2dyb3Vwf29yZGluYWwRAfr/Y2FwbnAvc2MCaGVtYS5jYXBucDpOb2RlLj9zdHJ1Y3Qx\ -AY8BURwDBBAHFAEHAAARtXIAAFG0AwFRwAIBEQEMFAEIAAARvWoAAFG8AwFRyAIBEQINFAEJAAARxbIAAFHIAwFR\ -1AIBEQPgFAEKAAAR0UIAAFHMAwFR2AIBEQQPFAELAAAR1ZIAAFHYAwFR5AIBEQUIFAEMAAAR4ZoAAFHkAwFR8AIB\ -EQYDFAENAAAR7ToAAFHoAwFTBAECAf9kYXRhV29yZAAfQ291bnQBBwACAQcAAf9wb2ludGVyQwAPb3VudAEHAAIB\ -BwAB/3ByZWZlcnJlAWRMaXN0RW5jH29kaW5nAQ//JhlSun2PldEAAAEBDwABf2lzR3JvdXABAQACAQEAAf9kaXNj\ -cmltaQFuYW50Q291bgF0AQcAAgEHAAH/ZGlzY3JpbWkBbmFudE9mZnMDZXQBCAACAQgAAT9maWVsZHMBDgABUAMB\ -ARD/X/RKH6RQrZoAAAEBDgABEQHq/2NhcG5wL3NjAmhlbWEuY2FwbnA6RW51bWUPcmFudBEBB1ABAREBr1EMAwQA\ -AAQBAAARRSoAAFFAAwFRTAIBAQEUAQEAABFJUgAAUUgDAVFUAgERAgEUAQIAABFRYgAAUVADAVFsAgEPbmFtZQEM\ -AAIBDAAB/2NvZGVPcmRlAAFyAQcAAgEHAAH/YW5ub3RhdGkAB29ucwEOAAFQAwEBEP9CdSWrDZXI8QAAAQEOAAER\ -Aer/Y2FwbnAvc2MCaGVtYS5jYXBucDpOb2RlLg9lbnVtEQE/UQQDBBADFAEOAAARDVoAAFEMAwFRKAIB/2VudW1l\ -cmFuAAN0cwEOAAFQAwEBEP9NmlTc63yKlwAAAQEOAAERAdL/Y2FwbnAvc2MCaGVtYS5jYXBucDpNZXRobwFkEQEH\ -UAEBMQHHAVEgAwQAAAQBAAAR0SoAAFHMAwFR2AIBAQEUAQEAABHVUgAAUdQDAVHgAgERAwEUAQIAABHdggAAUdwD\ -ARECAhEFAhQBAwAAESICAAARQgIRagIRBwEUAQQAABGKAgAAEaICEeoCEQQCFAEFAAATCgECAAATIgECE0oBAhEG\ -AxQBBgAAE2oBAgAAE4IBAhOqAQIRAgQUAQcAABPKAQIAABPqAQITMgICD25hbWUBDAACAQwAAf9jb2RlT3JkZQAB\ -cgEHAAIBBwAB/3BhcmFtU3RyAXVjdFR5cGUAAQkAAlACAQEJAAERAYr/cmVzdWx0U3QBcnVjdFR5cGUAAFADAQEJ\ -AAJQAgEBCQABEQFi/2Fubm90YXRpAAdvbnNQAwEBDgABUAMBARD/QnUlqw2VyPEAAAFQAgEBDgABEQFa/3BhcmFt\ -QnJhAANuZFADAQEQ/ytCZWDwVTSQAAABUAIBARAAAREBYv9yZXN1bHRCcgAHYW5kUAMBARD/K0JlYPBVNJAAAAFQ\ -AgEBEAABEQGa/2ltcGxpY2l0AVBhcmFtZXRlA3JzUAMBAQ4AAVADAQEQ/7GjD/HMG1K5AAABUAIBAQ4AAREB8v9j\ -YXBucC9zYwJoZW1hLmNhcG5wOlN1cGVyH2NsYXNzEQEHUAEBEQF3UQgDBAAABAEAABEpGgAAUSQDAVEwAgEBARQB\ -AQAAES0yAABRKAMBUTQCAQNpZAEJAAIBCQABH2JyYW5kARD/K0JlYPBVNJAAAAEBEAABMQESAf9jYXBucC9zYwNo\ -ZW1hLmNhcG5wOk5vZGUuaW50ZXJmYWMBZREBd1EIAwQQAxQBDwAAESlCAABRJAMBUUACAREBBBQBHwAAET1qAABR\ -PAMBUVgCAX9tZXRob2RzAQ4AAVADAQEQv4BNMzvizJUAAQEOAAH/c3VwZXJjbGEAD3NzZXMBDgABUAMBARD/+Nek\ -0J4qlqkAAAEBDgABEQHC/2NhcG5wL3NjAmhlbWEuY2FwbnA6VHlwZQARAQdQAQExAS8EUUwDBAz//wQBAAATBQIq\ -AABSAgMBUwwCAgENAf7/FAEBAAATCQIqAABTBAIDAVMQAgIBDQL9/xQBAgAAEw0CKgAAUwgCAwFTFAICAQ0D/P8U\ -AQMAABMRAjIAAFMMAgMBUxgCAgENBPv/FAEEAAATFQIyAABTEAIDAVMcAgIBDQX6/xQBBQAAExkCMgAAUxQCAwFT\ -IAICAQ0G+f8UAQYAABMdAjIAAFMYAgMBUyQCAgENB/j/FAEHAAATIQI6AABTHAIDAVMoAgIBDQj3/xQBCAAAEyUC\ -OgAAUyACAwFTLAICAQ0J9v8UAQkAABMpAjoAAFMkAgMBUzACAgENCvX/FAEKAAATLQJCAABTKAIDAVM0AgIBDQv0\ -/xQBCwAAEzECQgAAUywCAwFTOAICAQ0M8/8UAQwAABM1AioAAFMwAgMBUzwCAgENDfL/FAENAAATOQIqAABTNAID\ -AVNAAgIBDQ7x/wEB/5fqYAolOeeHABM9AioAAg0P8P8BAf+ph38acXgOngATJQIqAAINEO//AQH/08ZM72BvOqwA\ -Ew0COgACDRHu/wEB/78M+/dpyovtABP1AVIAAg0S7f8BAf/xST6i6D9XwgAT4QFaAAIPdm9pZAAGD2Jvb2wABg9p\ -bnQ4AAYfaW50MTYABh9pbnQzMgAGH2ludDY0AAYfdWludDgABj91aW50MTYABj91aW50MzIABj91aW50NjQABn9m\ -bG9hdDMyAAZ/ZmxvYXQ2NAAGD3RleHQABg9kYXRhAAYPbGlzdA9lbnVtP3N0cnVjdP9pbnRlcmZhYwABZf9hbnlQ\ -b2ludAADZXIRAcr/Y2FwbnAvc2MCaGVtYS5jYXBucDpWYWx1ZQAAEQEHUAEBMQEvBFFMAwQM//8EAQAAEwUCKgAA\ -UgIDAVMMAgIBHQH+/xAUAQEAABMJAioAAFMEAgMBUxACAgEdAv3/AhQBAgAAEw0CKgAAUwgCAwFTFAICAR0D/P8B\ -FAEDAAATEQIyAABTDAIDAVMYAgIBHQT7/wEUAQQAABMVAjIAAFMQAgMBUxwCAgEdBfr/ARQBBQAAExkCMgAAUxQC\ -AwFTIAICAR0G+f8CFAEGAAATHQIyAABTGAIDAVMkAgIBHQf4/wEUAQcAABMhAjoAAFMcAgMBUygCAgEdCPf/ARQB\ -CAAAEyUCOgAAUyACAwFTLAICAR0J9v8BFAEJAAATKQI6AABTJAIDAVMwAgIBHQr1/wEUAQoAABMtAkIAAFMoAgMB\ -UzQCAgEdC/T/ARQBCwAAEzECQgAAUywCAwFTOAICAQ0M8/8UAQwAABM1AioAAFMwAgMBUzwCAgENDfL/FAENAAAT\ -OQIqAABTNAIDAVNAAgIBDQ7x/xQBDgAAEz0CKgAAUzgCAwFTRAICAR0P8P8BFAEPAAATQQIqAABTPAIDAVNIAgIB\ -DRDv/xQBEAAAE0UCOgAAU0ACAwFTTAICAQ0R7v8UAREAABNJAlIAAFNIAgMBU1QCAgENEu3/FAESAAATUQJaAABT\ -UAIDAVNcAgIBD3ZvaWQABg9ib29sAQEAAgEBAAEPaW50OAECAAIBAgABH2ludDE2AQMAAgEDAAEfaW50MzIBBAAC\ -AQQAAR9pbnQ2NAEFAAIBBQABH3VpbnQ4AQYAAgEGAAE/dWludDE2AQcAAgEHAAE/dWludDMyAQgAAgEIAAE/dWlu\ -dDY0AQkAAgEJAAF/ZmxvYXQzMgEKAAIBCgABf2Zsb2F0NjQBCwACAQsAAQ90ZXh0AQwAAgEMAAEPZGF0YQENAAIB\ -DQABD2xpc3QBEgACARIAAQ9lbnVtAQcAAgEHAAE/c3RydWN0ARIAAgESAAH/aW50ZXJmYWMAAWUABv9hbnlQb2lu\ -dAADZXIBEgACARIAAREB8v9jYXBucC9zYwJoZW1hLmNhcG5wOk5vZGUuH2NvbnN0EQF3UQgDBBADFAEQAAARKSoA\ -AFEkAwFRMAIBEQEEFAERAAARLTIAAFEoAwFRNAIBD3R5cGUBEP9gzPnh7Xhz0AAAAQEQAAEfdmFsdWUBEP+bDLDX\ -0twjzgAAAQEQAAExARoB/2NhcG5wL3NjA2hlbWEuY2FwbnA6Tm9kZS5hbm5vdGF0aQNvbjEB3wJRNAMEEAMUARIA\ -ABNdASoAAFNYAQMBU2QBAgERAXAUARMAABNhAWIAAFNgAQMBU2wBAgERAnEUARQAABNpAWoAAFNoAQMBU3QBAgER\ -A3IUARUAABNxAWIAAFNwAQMBU3wBAgERBHMUARYAABN5AYoAAFN8AQMBU4gBAgERBXQUARcAABOFAXIAAFOEAQMB\ -U5ABAgERBnUUARgAABONAWoAAFOMAQMBU5gBAgERB3YUARkAABOVAWoAAFOUAQMBU6ABAgERCHcUARoAABOdAWoA\ -AFOcAQMBU6gBAgERCXgUARsAABOlAYoAAFOoAQMBU7QBAgERCnkUARwAABOxAXIAAFOwAQMBU7wBAgERC3oUAR0A\ -ABO5AWoAAFO4AQMBU8QBAgERDHsUAR4AABPBAZIAAFPEAQMBU9ABAgEPdHlwZQEQ/2DM+eHteHPQAAABARAAAf90\ -YXJnZXRzRgAHaWxlAQEAAgEBAAH/dGFyZ2V0c0MAD29uc3QBAQACAQEAAf90YXJnZXRzRQAHbnVtAQEAAgEBAAH/\ -dGFyZ2V0c0UBbnVtZXJhbnQAAAEBAAIBAQAB/3RhcmdldHNTAB90cnVjdAEBAAIBAQAB/3RhcmdldHNGAA9pZWxk\ -AQEAAgEBAAH/dGFyZ2V0c1UAD25pb24BAQACAQEAAf90YXJnZXRzRwAPcm91cAEBAAIBAQAB/3RhcmdldHNJAW50\ -ZXJmYWNlAAABAQACAQEAAf90YXJnZXRzTQAfZXRob2QBAQACAQEAAf90YXJnZXRzUAAPYXJhbQEBAAIBAQAB/3Rh\ -cmdldHNBAW5ub3RhdGlvAW4BAQACAQEAATEBGgH/Y2FwbnAvc2MDaGVtYS5jYXBucDpOb2RlLk5lc3RlZE5vA2Rl\ -EQEHUAEBEQF3UQgDBAAABAEAABEpKgAAUSQDAVEwAgEBARQBAQAAES0aAABRKAMBUTQCAQ9uYW1lAQwAAgEMAAED\ -aWQBCQACAQkAAREB8v9jYXBucC9zYwJoZW1hLmNhcG5wOkFubm90H2F0aW9uEQEHUAEBEQGvUQwDBAAABAEAABFF\ -GgAAUUADAVFMAgEBAhQBAQAAEUkyAABRRAMBUVACAREBARQBAgAAEU0yAABRSAMBUVQCAQNpZAEJAAIBCQABH3Zh\ -bHVlARD/mwyw19LcI84AAAEBEAABH2JyYW5kARD/K0JlYPBVNJAAAAEBEAABMQESAf9jYXBucC9zYwNoZW1hLmNh\ -cG5wOk5vZGUuUGFyYW1ldGUBchEBB1ABAREBP1EEAwQAAAQBAAARDSoAAFEIAwFRFAIBD25hbWUBDAACAQwAAREB\ -yv9jYXBucC9zYwJoZW1hLmNhcG5wOkJyYW5kAAARASdRCAEB/8lrY6mFNNerABEJMv/8556WFs1jyAARBUIfU2Nv\ -cGV/QmluZGluZxEBP1EEAwQAAAQBAAARDToAAFEIAwFRJAIBP3Njb3BlcwEOAAFQAwEBEP/Ja2OphTTXqwAAAQEO\ -AAERAfr/Y2FwbnAvc2MCaGVtYS5jYXBucDpCcmFuZD8uU2NvcGURAQdQAQERAa9RDAMEAAAEAQAAEUVCAABRQAMB\ -UUwCAQ0B//8UAQEAABFJKgAAUUQDAVFgAgENAv7/FAECAAARXUIAAFFYAwFRZAIBf3Njb3BlSWQBCQACAQkAAQ9i\ -aW5kAQ4AAVADAQEQ//znnpYWzWPIAAABAQ4AAX9pbmhlcml0AAYxAQoB/2NhcG5wL3NjA2hlbWEuY2FwbnA6QnJh\ -bmQuQmluZGluZwAAEQEHUAEBEQF3UQgDBAz//wQBAAARKUIAAFEkAwFRMAIBDQH+/xQBAQAAES0qAABRKAMBUTQC\ -AX91bmJvdW5kAAYPdHlwZQEQ/2DM+eHteHPQAAABARAAAREB6v9jYXBucC9zYwJoZW1hLmNhcG5wOlR5cGUuD2xp\ -c3QRAT9RBAMEAAAUAQ4AABENYgAAUQwDAVEYAgH/ZWxlbWVudFQAB3lwZQEQ/2DM+eHteHPQAAABARAAAREB6v9j\ -YXBucC9zYwJoZW1hLmNhcG5wOlR5cGUuD2VudW0RAXdRCAMEEAEUAQ8AABEpOgAAUSQDAVEwAgEBARQBFQAAES0y\ -AABRKAMBUTQCAT90eXBlSWQBCQACAQkAAR9icmFuZAEQ/ytCZWDwVTSQAAABARAAAREB+v9jYXBucC9zYwJoZW1h\ -LmNhcG5wOlR5cGUuP3N0cnVjdBEBd1EIAwQQARQBEAAAESk6AABRJAMBUTACAQEBFAEWAAARLTIAAFEoAwFRNAIB\ -P3R5cGVJZAEJAAIBCQABH2JyYW5kARD/K0JlYPBVNJAAAAEBEAABMQESAf9jYXBucC9zYwNoZW1hLmNhcG5wOlR5\ -cGUuaW50ZXJmYWMBZREBd1EIAwQQARQBEQAAESk6AABRJAMBUTACAQEBFAEXAAARLTIAAFEoAwFRNAIBP3R5cGVJ\ -ZAEJAAIBCQABH2JyYW5kARD/K0JlYPBVNJAAAAEBEAABMQGKAf9jYXBucC9zYwVoZW1hLmNhcG5wOlR5cGUuYW55\ -UG9pbnRlci51bmNvbnN0cmFpbmVkAAARAedREAMEDP//FAESAAARYUIAAFFcAwFRaAIBDQH+/xQBGQAAEWU6AABR\ -YAMBUWwCAQ0C/f8UARoAABFpKgAAUWQDAVFwAgENA/z/FAEbAAARbVoAAFFsAwFReAIBf2FueUtpbmQABj9zdHJ1\ -Y3QABg9saXN0AAb/Y2FwYWJpbGkAA3R5AAYxAWoB/2NhcG5wL3NjBGhlbWEuY2FwbnA6VHlwZS5hbnlQb2ludGVy\ -LnBhcmFtD2V0ZXIRAXdRCAMEEAIUARMAABEpQgAAUSQDAVEwAgERAQUUARQAABEtegAAUSwDAVE4AgF/c2NvcGVJ\ -ZAEJAAIBCQAB/3BhcmFtZXRlAD9ySW5kZXgBBwACAQcAATEB2gH/Y2FwbnAvc2MGaGVtYS5jYXBucDpUeXBlLmFu\ -eVBvaW50ZXIuaW1wbGljaXRNZXRob2RQYXJhbWV0A2VyEQE/UQQDBBAFFAEYAAARDXoAAFEMAwFRGAIB/3BhcmFt\ -ZXRlAD9ySW5kZXgBBwACAQcAATEBGgH/Y2FwbnAvc2MDaGVtYS5jYXBucDpUeXBlLmFueVBvaW50A2VyEQGvUQwD\ -BAz//wEB/1Y2Wf55XzuOABFFcgACDQH+/wEB/4VKYfQk99GdABExUgACDQL9/wEB/3TiVgwSye+6ABEdwgAC/3Vu\ -Y29uc3RyAB9haW5lZP9wYXJhbWV0ZQABcv9pbXBsaWNpdAJNZXRob2RQYXJhbWV0ZXIAEQHy/2NhcG5wL3NjAmhl\ -bWEuY2FwbnA6RmllbGQfLnNsb3QRAedREAMEEAEUAQQAABFhOgAAUVwDAVFoAgERAQIUAQUAABFlKgAAUWADAVFs\ -AgERAgMUAQYAABFpagAAUWgDAVF0AgERA4AUAQoAABFxmgAAUXQDAVGAAgE/b2Zmc2V0AQgAAgEIAAEPdHlwZQEQ\ -/2DM+eHteHPQAAABARAAAf9kZWZhdWx0VgAPYWx1ZQEQ/5sMsNfS3CPOAAABARAAAf9oYWRFeHBsaQFjaXREZWZh\ -dQNsdAEBAAIBAQABEQH6/2NhcG5wL3NjAmhlbWEuY2FwbnA6RmllbGQ/Lmdyb3VwEQE/UQQDBBACFAEHAAARDToA\ -AFEIAwFRFAIBP3R5cGVJZAEJAAIBCQABMQEKAf9jYXBucC9zYwNoZW1hLmNhcG5wOkZpZWxkLm9yZGluYWwAABEB\ -d1EIAwQM//8UAQgAABEpSgAAUSgDAVE0AgEdAf7/BhQBCQAAETFKAABRMAMBUTwCAf9pbXBsaWNpdAAAB/9leHBs\ -aWNpdAAAAAEHAAIBBwABMQGKAv96aGluc3QvaQlvL3Byb3RvY29sL2NhcG5wL3Nlc3Npb24vc2Vzc2lvbl9wcm90\ -b2NvbC5jYXBucDpTZXNzaW9uLmxpc3ROb2RlcyRQYXJhbXMAABEBr1EMAwQAAAQBAAARRXoAAFFEAwFRUAIBAQEU\ -AQEAABFNMgAAUUgDAVFUAgERAgEUAQIAABFROgAAUUwDAVFYAgH/cGF0aEV4cHIAP2Vzc2lvbgEMAAIBDAABH2Zs\ -YWdzAQgAAgEIAAE/Y2xpZW50AQ0AAgENAAExAZIC/3poaW5zdC9pCW8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lvbi9z\ -ZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24ubGlzdE5vZGVzJFJlc3VsdAFzEQE/UQQDBAAABAEAABENMgAA\ -UQgDAVEkAgEfcGF0aHMBDgABUAMBAQwAAgEOAAExAdoC/3poaW5zdC9pCm8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lv\ -bi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24uZGVwcmVjYXRlZEdldFZhbHVlcyRQYXJhA21zEQF3UQgD\ -BAAABAEAABEpMgAAUSQDAVFAAgERAQEUAQEBARE9OgAAUTgDAVFEAgEfcGF0aHMBDgABUAMBAQwAAgEOAAE/Y2xp\ -ZW50AQ0AAgENAAARAQIxAaIB/3poaW5zdC9pBW8vcHJvdG9jb2wvY2FwbnAvY29tbW9uL3Jlc3VsdC5jYXBucDpS\ -ZXMHdWx0EQEHUAEBEQF3UQgDBAz//wQBAAARKRoAAFEkAwFRMAIBDQH+/xQBAQAAES0iAABRKAMBUTQCAQNvawES\ -AQH/PTI0GT7zsLoAAAABEgABB2VycgESBQEB/z0yNBk+87C6AAAAARIAAREBF0EIAREFKhEFMg9UeXBlH0Vycm9y\ -MQHaAf96aGluc3QvaQZvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi92YWx1ZS5jYXBucDpBbm5vdGF0ZWRWYWwDdWUR\ -ARdRBAEB/xgwr1fK2FOtABEBSv9NZXRhZGF0YQAAABEBd1EIAwQAAAQBAAARKUoAAFEoAwFRNAIBEQEBFAEBAAAR\ -MTIAAFEsAwFROAIB/21ldGFkYXRhAAAAARD/GDCvV8rYU60AAAEBEAABH3ZhbHVlARD/rHW+cUeLg7EAAAEBEAAB\ -MQGSAf96aGluc3QvaQVvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9lcnJvci5jYXBucDpFcnJvAXIRAQdQAQExAR8B\ -URQDBAAABAEAABF9KgAAUXgDAVGEAgEBARQBAQAAEYFCAABRfAMBUYgCARECARQBAgAAEYVKAABRhAMBUZACARED\ -AhQBAwEBEY0qAABRiAMBUZQCAREEAhQBBAAAEZE6AABRjAMBUZgCAQ9jb2RlAQgAAgEIAAF/bWVzc2FnZQEMAAIB\ -DAAB/2NhdGVnb3J5AAAAAQwAAgEMAAEPa2luZAEP/70CmEricea3AAABBQ8CAAE/c291cmNlAQwAAgEMAAExAeIC\ -/3poaW5zdC9pCm8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lvbi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24u\ -ZGVwcmVjYXRlZEdldFZhbHVlcyRSZXN1B2x0cxEBP1EEAwQAAAQBAAARDToAAFEIAwFRbAIBP3Jlc3VsdAEOAAFQ\ -AwEBEP89MjQZPvOwugAAAEABEQEfUQQCAf89MjQZPvOwugAAABEBJ1EIAQEBAVEIAwEBAVEQAwEBEP/cfINuN+4I\ -9AAAAQEQ/9kRfVFMTuPEAAABAQ4AATEBkgH/emhpbnN0L2kFby9wcm90b2NvbC9jYXBucC9jb21tb24vdmFsdWUu\ -Y2FwbnA6VmFsdQFlEQEHUAEBMQH3A1FIAwQM//8EAQAAE+kBMgAAU+QBAwFT8AECAQ0B/v8UAQEAABPtAToAAFPo\ -AQMBU/QBAgENAv3/FAECAAAT8QFCAABT7AEDAVP4AQIBDQP8/xQBAwAAE/UBOgAAU/ABAwFT/AECAQ0E+/8UAQQA\ -ABP5AVoAAFP4AQMBUwQCAgENBfr/FAEFAAATAQJSAABSAgMBUwwCAgENBvn/FAEGAAATCQJyAABTCAIDAVMUAgIB\ -DQf4/xQBBwAAExECKgAAUwwCAwFTGAICAQ0I9/8UAQgAABMVAnoAAFMUAgMBUyACAgENCfb/FAEJAAATHQJqAABT\ -HAIDAVMoAgIBDQr1/xQBCgAAEyUCogAAUygCAwFTNAICAQ0L9P8UAQsAABMxAooAAFM0AgMBU1ACAgENDPP/FAEM\ -AAATTQKqAABTUAIDAVNsAgIBDQ3y/xQBDQAAE2kCggAAU2gCAwFTdAICAQ0O8f8UAQ4AABNxAioAAFNsAgMBU3gC\ -AgEND/D/FAEPAAATdQJqAABTdAIDAVOAAgIBDRDv/xQBEAAAE30CygAAU4QCAwFTkAICAQ0R7v8UAREAABONAloA\ -AFOMAgMBU5gCAgEfaW50NjQBBQACAQUAAT9kb3VibGUBCwACAQsAAX9jb21wbGV4ARD/V7G0l6+v8aoAAAEBEAAB\ -P3N0cmluZwEMAAIBDAAB/3ZlY3RvckRhAAN0YQEQ/3iJ8w24ZUyZAAABARAAAf9jbnRTYW1wbAABZQEQ/2VgfSjY\ -CzfpAAABARAAAf90cmlnZ2VyUwAfYW1wbGUBEP+VDX3ClyC33gAAAQEQAAEPbm9uZQEQ/xZ4Q4Hp6H/fAAABARAA\ -Af9zdHJlYW1pbgA/Z0Vycm9yARD/2RF9UUxO48QAAAEBEAAB/3NoZkRlbW9kAA9EYXRhARD7gmXm4+MDmwABARAA\ -Af9zaGZSZXN1bAF0TG9nZ2VyRAdhdGEBEP/dHXZ59WGguwAAAQEQAAH/dmVjdG9yQ24BdFNhbXBsZXMAAAEOAAFQ\ -AwEBEP9lYH0o2As36QAAAQEOAAH/dmVjdG9yVHIBaWdnZXJTYW0PcGxlcwEOAAFQAwEBEP+VDX3ClyC33gAAAQEO\ -AAH/bGFyZ2VWZWMBdG9yRGF0YQABEP8KsFmZsPdI2QAAAQEQAAEPYm9vbAEBAAIBAQAB/3NoZlNjb3BlAA9EYXRh\ -ARD/yTupgh9/sKkAAAEBEAAB/3NoZkdlbmVyAmF0b3JXYXZlZm9ybURhdGEAAAEQ/9DzxIFu4M/tAAABARAAAf9z\ -aGZQaWREYQADdGEBEP9wKJpUceFu/wAAAQEQAAExAVIC/3poaW5zdC9pCG8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lv\ -bi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlJldHVybkZyb21TZXRXaGUBbhEBB1ABAREBZ1EQAQIAABEpKgAAAQER\ -IVIAAAECER1iAAABAxEZogAAD2FzYXD/ZGV2aWNlQWMAAWv/dW51c2VkQXMAB3luY/91bnVzZWRUcgFhbnNhY3Rp\ -bwduYWwxAdIC/3poaW5zdC9pCm8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lvbi9zZXNzaW9uX3Byb3RvY29sLmNhcG5w\ -OlNlc3Npb24uZGVwcmVjYXRlZFNldFZhbHVlJFBhcmFtAXMRAa9RDAMEAAAEAQAAEUUqAABRQAMBUUwCAREBARQB\ -AQAAEUkyAABRRAMBUVACAQECFAECAQERTWoAAFFMAwFRWAIBD3BhdGgBDAACAQwAAR92YWx1ZQEQ/6x1vnFHi4Ox\ -AAABARAAAf9jb21wbGV0ZQAPV2hlbgEP//ntVaw6pS3dAAABBQ8BAAExAboB/3poaW5zdC9pBW8vcHJvdG9jb2wv\ -Y2FwbnAvY29tbW9uL3ZhbHVlLmNhcG5wOlZvaWQ/U3RydWN0EQEHUAEBMQHaAv96aGluc3QvaQpvL3Byb3RvY29s\ -L2NhcG5wL3Nlc3Npb24vc2Vzc2lvbl9wcm90b2NvbC5jYXBucDpTZXNzaW9uLmRlcHJlY2F0ZWRTZXRWYWx1ZSRS\ -ZXN1bAN0cxEBP1EEAwQAAAQBAAARDToAAFEIAwFRXAIBP3Jlc3VsdAEQ/z0yNBk+87C6AAAAQAERAR9RBAIB/z0y\ -NBk+87C6AAAAEQEnUQgBAQEBUQgDAQEBURADAQEQ/xZ4Q4Hp6H/fAAABARD/2RF9UUxO48QAAAEBEAABMQECAv96\ -aGluc3QvaQdvL3Byb3RvY29sL2NhcG5wL3N0cmVhbWluZy9zdHJlYW1pbmcuY2FwbnA6U3Vic2NyaXB0aW9uABEB\ -B1ABAREB51EQAwQAAAQBAAARYSoAAFFcAwFRaAIBEQEBFAEBAAARZYIAAFFkAwFRcAIBEQICFAECAAARbWoAAFFs\ -AwFReAIBEQMDFAEDAAARdToAAFFwAwFRfAIBD3BhdGgBDAACAQwAAf9zdHJlYW1pbgFnSGFuZGxlAAER/3QVtPWn\ -KB71AAABAREAAf9zdWJzY3JpYgAPZXJJZAENAAIBDQABP2t3YXJncwEQ/+DOZKYHziWPAAABARAAATEBigL/emhp\ -bnN0L2kJby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lvbi5zdWJz\ -Y3JpYmUkUGFyYW1zAAARAT9RBAMEAAAEAQAAEQ1qAABRDAMBURgCAf9zdWJzY3JpcAAPdGlvbgEQ/9Sx4T2lIazt\ -AAABARAAATEBkgL/emhpbnN0L2kJby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2Fw\ -bnA6U2Vzc2lvbi5zdWJzY3JpYmUkUmVzdWx0AXMRAT9RBAMEAAAEAQAAEQ06AABRCAMBUVwCAT9yZXN1bHQBEP89\ -MjQZPvOwugAAAEABEQEfUQQCAf89MjQZPvOwugAAABEBJ1EIAQEBAVEIAwEBAVEQAwEBEP8WeEOB6eh/3wAAAQEQ\ -/9kRfVFMTuPEAAABARAAATEBwgL/emhpbnN0L2kKby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJv\ -dG9jb2wuY2FwbnA6U2Vzc2lvbi5kaXNjb25uZWN0RGV2aWNlJFBhcmFtcwAxAaoC/3poaW5zdC9pCW8vcHJvdG9j\ -b2wvY2FwbnAvc2Vzc2lvbi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24ubGlzdE5vZGVzSnNvbiRQYQ9y\ -YW1zEQGvUQwDBAAABAEAABFFegAAUUQDAVFQAgEBARQBAQAAEU0yAABRSAMBUVQCARECARQBAgAAEVE6AABRTAMB\ -UVgCAf9wYXRoRXhwcgA/ZXNzaW9uAQwAAgEMAAEfZmxhZ3MBCAACAQgAAT9jbGllbnQBDQACAQ0AATEBsgL/emhp\ -bnN0L2kJby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lvbi5saXN0\ -Tm9kZXNKc29uJFJlH3N1bHRzEQE/UQQDBAAABAEAABENUgAAUQwDAVEYAgH/bm9kZVByb3AAAXMBDAACAQwAATEB\ -mgL/emhpbnN0L2kJby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lv\ -bi51bnN1YnNjcmliZSRQYXJhA21zEQF3UQgDBAAABAEAABEpagAAUSgDAVE0AgERAQEUAQEAABExMgAAUSwDAVFI\ -AgH/c3Vic2NyaWIAD2VySWQBDQACAQ0AAR9wYXRocwEOAAFQAwEBDAACAQ4AATEBygL/emhpbnN0L2kKby9wcm90\ -b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lvbi5nZXRTZXNzaW9uVmVyc2lv\ -biRQYXJhbXMAADEB0gL/emhpbnN0L2kKby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wu\ -Y2FwbnA6U2Vzc2lvbi5nZXRTZXNzaW9uVmVyc2lvbiRSZXN1bHQBcxEBP1EEAwQAAAQBAAARDUIAAFEIAwFRFAIB\ -f3ZlcnNpb24BDAACAQwAATEB2gL/emhpbnN0L2kKby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJv\ -dG9jb2wuY2FwbnA6U2Vzc2lvbi5kZXByZWNhdGVkU2V0VmFsdWUyJFBhcmEDbXMRAedREAMEAAAEAQAAEWEqAABR\ -XAMBUWgCAREBARQBAQAAEWUyAABRYAMBUWwCAQECFAECAQERaWoAAFFoAwFRdAIBEQMCFAEDAQERcToAAFFsAwFR\ -eAIBD3BhdGgBDAACAQwAAR92YWx1ZQEQ/6x1vnFHi4OxAAABARAAAf9jb21wbGV0ZQAPV2hlbgEP//ntVaw6pS3d\ -AAABBQ8BAAE/Y2xpZW50AQ0AAgENAAARAQIxAeIC/3poaW5zdC9pCm8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lvbi9z\ -ZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24uZGVwcmVjYXRlZFNldFZhbHVlMiRSZXN1B2x0cxEBP1EEAwQA\ -AAQBAAARDToAAFEIAwFRXAIBP3Jlc3VsdAEQ/z0yNBk+87C6AAAAQAERAR9RBAIB/z0yNBk+87C6AAAAEQEnUQgB\ -AQEBUQgDAQEBURADAQEQ/9x8g2437gj0AAABARD/2RF9UUxO48QAAAEBEAABMQEaAv96aGluc3QvaQdvL3Byb3Rv\ -Y29sL2NhcG5wL3Nlc3Npb24vc2Vzc2lvbl9wcm90b2NvbC5jYXBucDpMb29rdXBNbwNkZREBB1ABAREBN1EIAQIA\ -ABERagAAAQERDXIAAP9kaXJlY3RMbwAPb2t1cP93aXRoRXhwYQAfbnNpb24xAYIC/3poaW5zdC9pCW8vcHJvdG9j\ -b2wvY2FwbnAvc2Vzc2lvbi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24uc2V0VmFsdWUkUGFyYW1zADEB\ -HwFRFAMEAAAEAQAAEX16AABRfAMBUYgCAREBARQBAQAAEYUyAABRgAMBUYwCAQECFAECAQERiVoAAFGIAwFRlAIB\ -EQMBFAEDAQERkWoAAFGQAwFRnAIBEQQCFAEEAQERmToAAFGUAwFRoAIB/3BhdGhFeHByAD9lc3Npb24BDAACAQwA\ -AR92YWx1ZQEQ/6x1vnFHi4OxAAABARAAAf9sb29rdXBNbwADZGUBD/8l9HLgtUlQ2gAAAQEPAAH/Y29tcGxldGUA\ -D1doZW4BD//57VWsOqUt3QAAAQUPAQABP2NsaWVudAENAAIBDQAAEQECMQGKAv96aGluc3QvaQlvL3Byb3RvY29s\ -L2NhcG5wL3Nlc3Npb24vc2Vzc2lvbl9wcm90b2NvbC5jYXBucDpTZXNzaW9uLnNldFZhbHVlJFJlc3VsdHMAABEB\ -P1EEAwQAAAQBAAARDToAAFEIAwFRbAIBP3Jlc3VsdAEOAAFQAwEBEP89MjQZPvOwugAAAEABEQEfUQQCAf89MjQZ\ -PvOwugAAABEBJ1EIAQEBAVEIAwEBAVEQAwEBEP/cfINuN+4I9AAAAQEQ/9kRfVFMTuPEAAABAQ4AATEBggL/emhp\ -bnN0L2kJby9wcm90b2NvbC9jYXBucC9zZXNzaW9uL3Nlc3Npb25fcHJvdG9jb2wuY2FwbnA6U2Vzc2lvbi5nZXRW\ -YWx1ZSRQYXJhbXMAEQHnURADBAAABAEAABFhegAAUWADAVFsAgEBARQBAQEBEWlaAABRaAMBUXQCARECARQBAgEB\ -EXEyAABRbAMBUXgCAREDARQBAwEBEXU6AABRcAMBUXwCAf9wYXRoRXhwcgA/ZXNzaW9uAQwAAgEMAAH/bG9va3Vw\ -TW8AA2RlAQ//JfRy4LVJUNoAAAEBDwABH2ZsYWdzAQgAAgEIAAE/Y2xpZW50AQ0AAgENAAARAQIxAYoC/3poaW5z\ -dC9pCW8vcHJvdG9jb2wvY2FwbnAvc2Vzc2lvbi9zZXNzaW9uX3Byb3RvY29sLmNhcG5wOlNlc3Npb24uZ2V0VmFs\ -dWUkUmVzdWx0cwAAEQE/UQQDBAAABAEAABENOgAAUQgDAVFsAgE/cmVzdWx0AQ4AAVADAQEQ/z0yNBk+87C6AAAA\ -QAERAR9RBAIB/z0yNBk+87C6AAAAEQEnUQgBAQEBUQgDAQEBURADAQEQ/9x8g2437gj0AAABARD/2RF9UUxO48QA\ -AAEBDgABMQFyAv96aGluc3QvaQhvL3Byb3RvY29sL2NhcG5wL3Nlc3Npb24vc2Vzc2lvbl9wcm90b2NvbC5jYXBu\ -cDpTZXNzaW9uLmNhbmNlbCRQH2FyYW1zEQE/UQQDBAAABAEAABENOgAAUQgDAVEUAgE/Y2xpZW50AQ0AAgENAAEx\ -ASIC/3poaW5zdC9pB28vcHJvdG9jb2wvY2FwbnAvY29tbW9uL3ZhbHVlLmNhcG5wOkFubm90YXRlZFZhbHVlLk1l\ -dGFkB2F0YREBB1ABAREBd1EIAwQAAAQBAAARKVIAAFEoAwFRNAIBAQEUAQEAABExKgAAUSwDAVE4AgH/dGltZXN0\ -YW0AAXABCQACAQkAAQ9wYXRoAQwAAgEMAAExAbIB/3poaW5zdC9pBW8vcHJvdG9jb2wvY2FwbnAvY29tbW9uL2Nv\ -bXBsZXguY2FwbnA6Q28fbXBsZXgRAQdQAQERAXdRCAMEAAAEAQAAESkqAABRJAMBUTACAREBARQBAQAAES0qAABR\ -KAMBUTQCAQ9yZWFsAQsAAgELAAEPaW1hZwELAAIBCwABMQG6Af96aGluc3QvaQVvL3Byb3RvY29sL2NhcG5wL2Nv\ -bW1vbi92YWx1ZS5jYXBucDpWZWN0P29yRGF0YREBB1ABAREB51EQAwQAAAQBAAARYVIAAFFgAwFRbAIBEQECFAEB\ -AAARaZIAAFFsAwFReAIBEQIBFAECAAARdYIAAFF0AwFRgAIBAQMUAQMAABF9KgAAUXgDAVGEAgH/dmFsdWVUeXAA\ -AWUBBwACAQcAAf92ZWN0b3JFbAFlbWVudFR5cAFlAQYAAgEGAAH/ZXh0cmFIZWEBZGVySW5mbwABCAACAQgAAQ9k\ -YXRhAQ0AAgENAAExAbIB/3poaW5zdC9pBW8vcHJvdG9jb2wvY2FwbnAvY29tbW9uL3ZhbHVlLmNhcG5wOkNudFMf\ -YW1wbGURAQdQAQERAa9RDAMEAAAEAQAAEUVSAABRRAMBUVACAREBAhQBAQAAEU1CAABRSAMBUVQCARECAxQBAgAA\ -EVFCAABRTAMBUVgCAf90aW1lc3RhbQABcAEJAAIBCQABf2NvdW50ZXIBBAACAQQAAX90cmlnZ2VyAQgAAgEIAAEx\ -AdIB/3poaW5zdC9pBm8vcHJvdG9jb2wvY2FwbnAvY29tbW9uL3ZhbHVlLmNhcG5wOlRyaWdnZXJTYW1wbAFlEQEH\ -UAEBMQGPAVEcAwQAAAQBAAARtVIAAFG0AwFRwAIBEQEBFAEBAAARvVoAAFG8AwFRyAIBEQIEFAECAAARxUIAAFHA\ -AwFRzAIBEQMFFAEDAAARyXoAAFHIAwFR1AIBEQQGFAEEAAAR0VoAAFHQAwFR3AIBEQUHFAEFAAAR2SIAAFHUAwFR\ -4AIBEQYIFAEGAAAR3XIAAFHcAwFR6AIB/3RpbWVzdGFtAAFwAQkAAgEJAAH/c2FtcGxlVGkAA2NrAQkAAgEJAAF/\ -dHJpZ2dlcgEIAAIBCAAB/21pc3NlZFRyAD9pZ2dlcnMBCAACAQgAAf9hd2dUcmlnZwADZXIBCAACAQgAAQdkaW8B\ -CAACAQgAAf9zZXF1ZW5jZQAfSW5kZXgBCAACAQgAATEBWgL/emhpbnN0L2kIby9wcm90b2NvbC9jYXBucC9jb21t\ -b24vc2hmX3ZlY3RvcnMuY2FwbnA6U2hmRGVtb2R1bGF0b3JWZWN0b3JEYQN0YREBB1ABAREBr1EMAwQAAAEB/w0w\ -XKJseHXUABFFWgACAQEUAQ0AABExEgAAUSwDAVFIAgERAgEUAQ4AABFFEgAAUUADAVFcAgH/cHJvcGVydGkAA2Vz\ -AXgBDgABUAMBAQsAAgEOAAEBeQEOAAFQAwEBCwACAQ4AATEBYgL/emhpbnN0L2kIby9wcm90b2NvbC9jYXBucC9j\ -b21tb24vc2hmX3ZlY3RvcnMuY2FwbnA6U2hmUmVzdWx0TG9nZ2VyVmVjdG9yRAdhdGERAQdQAQERAXdRCAMEAAAB\ -Af9e7A7qIbS33QARKVoAAgEBAQH/Et+bRLVoC5gAERU6AAL/cHJvcGVydGkAA2VzP3ZlY3RvcjEB4gH/emhpbnN0\ -L2kGby9wcm90b2NvbC9jYXBucC9jb21tb24vdmFsdWUuY2FwbnA6TGFyZ2VWZWN0b3JEB2F0YREBB1ABAREB51EQ\ -AwQAAAQBAAARYVIAAFFgAwFRbAIBEQECFAEBAAARaZIAAFFsAwFReAIBEQIBFAECAAARdYIAAFF0AwFRgAIBAQMU\ -AQMAABF9agAAUXwDAVGYAgH/dmFsdWVUeXAAAWUBBwACAQcAAf92ZWN0b3JFbAFlbWVudFR5cAFlAQYAAgEGAAH/\ -ZXh0cmFIZWEBZGVySW5mbwABCAACAQgAAf9kYXRhU2VnbQAPZW50cwEOAAFQAwEBDQACAQ4AATEBKgL/emhpbnN0\ -L2kHby9wcm90b2NvbC9jYXBucC9jb21tb24vc2hmX3ZlY3RvcnMuY2FwbnA6U2hmU2NvcGVWZWN0b3IPRGF0YREB\ -B1ABAREBd1EIAwQAAAEB/ySJYPl2uAfXABEpWgACAQEBAf+8FI6Mh6sK0QARFToAAv9wcm9wZXJ0aQADZXM/dmVj\ -dG9yMQGKAv96aGluc3QvaQlvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9zaGZfdmVjdG9ycy5jYXBucDpTaGZHZW5l\ -cmF0b3JXYXZlZm9ybVZlY3RvckRhdGEAABEBB1ABAREBP1EEAwQAAAQBAAARDUIAAFEIAwFRJAIBf2NvbXBsZXgB\ -DgABUAMBARD/V7G0l6+v8aoAAAEBDgABMQEaAv96aGluc3QvaQdvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9zaGZf\ -dmVjdG9ycy5jYXBucDpTaGZQaWRWZWN0b3JEYQN0YREBB1ABAREBr1EMAwQAAAEB/0RVmXcGneCbABFFWgACAQEU\ -AQ0AABExMgAAUSwDAVFIAgERAgEUAQ4AABFFMgAAUUADAVFcAgH/cHJvcGVydGkAA2VzH3ZhbHVlAQ4AAVADAQEL\ -AAIBDgABH2Vycm9yAQ4AAVADAQELAAIBDgABMQGyAf96aGluc3QvaQVvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9l\ -cnJvci5jYXBucDpFcnJvH3JLaW5kEQEHUAEBEQH3USgBAgAAEXEaAAABARFpUgAAAQIRZUIAAAEDEV1KAAABBBFZ\ -YgAAAQURVVoAAAEGEVFyAAABBxFNSgAAAQgRSWIAAAEJEUVCAAADb2v/Y2FuY2VsbGUAAWR/dW5rbm93bv9ub3RG\ -b3VuZAAAAP9vdmVyd2hlbAAHbWVk/2JhZFJlcXVlAANzdP91bmltcGxlbQAfZW50ZWT/aW50ZXJuYWwAAAD/dW5h\ -dmFpbGEAB2JsZX90aW1lb3V0MQGyAv96aGluc3QvaQlvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9zaGZfdmVjdG9y\ -cy5jYXBucDpTaGZEZW1vZHVsYXRvclZlY3RvckRhdGEucHJvcGUfcnRpZXMxAd8CUTQDBAAABAEAABNdAVIAAFNc\ -AQMBU2gBAgERAQEUAQEAABNlARoAAFNgAQMBU2wBAgERAgQUAQIAABNpAWIAAFNoAQMBU3QBAgERAwUUAQMAABNx\ -AWIAAFNwAQMBU3wBAgERBAYUAQQAABN5AWoAAFN4AQMBU4QBAgERBQQUAQUAABOBAYoAAFOEAQMBU5ABAgERBgUU\ -AQYAABONAVoAAFOMAQMBU5gBAgERB+AUAQcAABOVAToAAFOQAQMBU5wBAgERCA8UAQgAABOZAYoAAFOcAQMBU6gB\ -AgERCRgUAQkAABOlAUoAAFOkAQMBU7ABAgERCh0UAQoAABOtAXIAAFOsAQMBU7gBAgERCxkUAQsAABO1AWoAAFO0\ -AQMBU8ABAgERDAcUAQwAABO9AXoAAFO8AQMBU8gBAgH/dGltZXN0YW0AAXABCQACAQkAAQNkdAEJAAIBCQAB/2J1\ -cnN0TGVuAAdndGgBCAACAQgAAf9idXJzdE9mZgAHc2V0AQgAAgEIAAH/dHJpZ2dlckkAD25kZXgBCAACAQgAAf90\ -cmlnZ2VyVAFpbWVzdGFtcAAAAQkAAgEJAAH/Y2VudGVyRnIAA2VxAQsAAgELAAE/cmZQYXRoAQEAAgEBAAH/b3Nj\ -aWxsYXQBb3JTb3VyY2UAAAEHAAIBBwAB/2hhcm1vbmljAAAAAQcAAgEHAAH/dHJpZ2dlclMAH291cmNlAQYAAgEG\ -AAH/c2lnbmFsU28AD3VyY2UBBwACAQcAAf9vc2NpbGxhdAA/b3JGcmVxAQsAAgELAAExAboC/3poaW5zdC9pCW8v\ -cHJvdG9jb2wvY2FwbnAvY29tbW9uL3NoZl92ZWN0b3JzLmNhcG5wOlNoZlJlc3VsdExvZ2dlclZlY3RvckRhdGEu\ -cHJvcD9lcnRpZXMxARcDUTgDBAAABAEAABN5AVIAAFN4AQMBU4QBAgERAQIUAQEAABOBATIAAFN8AQMBU4gBAgER\ -AgMUAQIAABOFAWoAAFOEAQMBU5ABAgERAwIUAQMAABONAUIAAFOIAQMBU5QBAgERBAMUAQQAABORAYIAAFOQAQMB\ -U5wBAgERBQgUAQUAABOZAVoAAFOYAQMBU6QBAgERBgkUAQYAABOhAVoAAFOgAQMBU6wBAgERBwoUAQcAABOpAYoA\ -AFOsAQMBU7gBAgERCAsUAQgAABO1AWIAAFO0AQMBU8ABAgERCQwUAQkAABO9AWIAAFO8AQMBU8gBAgERChoUAQoA\ -ABPFAaIAAFPIAQMBU9QBAgERCxsUAQsAABPRAaoAAFPUAQMBU+ABAgERDBwUAQwAABPdAaIAAFPgAQMBU+wBAgER\ -DQgUAQ0AABPpAaoAAFPsAQMBU/gBAgH/dGltZXN0YW0AAXABCQACAQkAAR9qb2JJZAEIAAIBCAAB/3JlcGV0aXRp\ -AA9vbklkAQgAAgEIAAF/c2NhbGluZwELAAIBCwAB/2NlbnRlckZyAWVxdWVuY3kAAQsAAgELAAH/ZGF0YVNvdXIA\ -A2NlAQgAAgEIAAH/bnVtU2FtcGwAA2VzAQgAAgEIAAH/bnVtU3BlY3QBclNhbXBsZXMAAAEIAAIBCAAB/251bUF2\ -ZXJhAAdnZXMBCAACAQgAAf9udW1BY3F1aQAHcmVkAQgAAgEIAAH/aG9sZG9mZkUBcnJvcnNSZXMHbG9nAQcAAgEH\ -AAH/aG9sZG9mZkUBcnJvcnNSZWEPZG91dAEHAAIBBwAB/2hvbGRvZmZFAXJyb3JzU3BlB2N0cgEHAAIBBwAB/2Zp\ -cnN0U2FtAXBsZVRpbWVzD3RhbXABCQACAQkAATEBmgL/emhpbnN0L2kJby9wcm90b2NvbC9jYXBucC9jb21tb24v\ -c2hmX3ZlY3RvcnMuY2FwbnA6U2hmUmVzdWx0TG9nZ2VyVmVjdG9yRGF0YS52ZWN0A29yEQF3UQgDBAz//xQBDgAA\ -ESkqAABRJAMBUUACAQ0B/v8UAQ8AABE9QgAAUTgDAVFUAgEPcmVhbAEOAAFQAwEBCwACAQ4AAX9jb21wbGV4AQ4A\ -AVADAQEQ/1extJevr/GqAAABAQ4AATEBggL/emhpbnN0L2kJby9wcm90b2NvbC9jYXBucC9jb21tb24vc2hmX3Zl\ -Y3RvcnMuY2FwbnA6U2hmU2NvcGVWZWN0b3JEYXRhLnByb3BlcnRpZXMAMQGnAlEwAwQAAAQBAAATQQFSAABTQAED\ -AVNMAQIBEQECFAEBAAATSQFyAABTSAEDAVNUAQIBEQIDFAECAAATUQEyAABTTAEDAVNYAQIBEQMCFAEDAAATVQFC\ -AABTUAEDAVNcAQIBEQQDFAEEAAATWQGCAABTWAEDAVNkAQIBEQUEFAEFAAATYQGKAABTZAEDAVNwAQIBEQYKFAEG\ -AAATbQFiAABTbAEDAVN4AQIBEQcLFAEHAAATdQFqAABTdAEDAVOAAQIBEQgMFAEIAAATfQFiAABTfAEDAVOIAQIB\ -EQkNFAEJAAAThQGKAABTiAEDAVOUAQIBEQoOFAEKAAATkQGSAABTlAEDAVOgAQIBEQsPFAELAAATnQGSAABToAED\ -AVOsAQIB/3RpbWVzdGFtAAFwAQkAAgEJAAH/dGltZXN0YW0AH3BEaWZmAQgAAgEIAAEfZmxhZ3MBCAACAQgAAX9z\ -Y2FsaW5nAQsAAgELAAH/Y2VudGVyRnIBZXF1ZW5jeQABCwACAQsAAf90cmlnZ2VyVAFpbWVzdGFtcAAAAQkAAgEJ\ -AAH/aW5wdXRTZWwAB2VjdAEIAAIBCAAB/2F2ZXJhZ2VDAA9vdW50AQgAAgEIAAH/bnVtU2VnbWUAB250cwEIAAIB\ -CAAB/251bVRvdGFsAVNlZ21lbnRzAAABCAACAQgAAf9maXJzdFNlZwFtZW50SW5kZQF4AQgAAgEIAAH/bnVtTWlz\ -c2UBZFRyaWdnZXIBcwEIAAIBCAABMQFiAv96aGluc3QvaQhvL3Byb3RvY29sL2NhcG5wL2NvbW1vbi9zaGZfdmVj\ -dG9ycy5jYXBucDpTaGZTY29wZVZlY3RvckRhdGEudmVjB3RvchEBd1EIAwQM//8UAQwAABEpKgAAUSQDAVFAAgEN\ -Af7/FAENAAARPUIAAFE4AwFRVAIBD3JlYWwBDgABUAMBAQsAAgEOAAF/Y29tcGxleAEOAAFQAwEBEP9XsbSXr6/x\ -qgAAAQEOAAExAXIC/3poaW5zdC9pCG8vcHJvdG9jb2wvY2FwbnAvY29tbW9uL3NoZl92ZWN0b3JzLmNhcG5wOlNo\ -ZlBpZFZlY3RvckRhdGEucHJvcGUfcnRpZXMxAd8CUTQDBAAABAEAABNdAVIAAFNcAQMBU2gBAgERAQEUAQEAABNl\ -AXIAAFNkAQMBU3ABAgERAgQUAQIAABNtAWIAAFNsAQMBU3gBAgERAwUUAQMAABN1AWIAAFN0AQMBU4ABAgERBAYU\ -AQQAABN9AWoAAFN8AQMBU4gBAgERBQQUAQUAABOFAYoAAFOIAQMBU5QBAgERBgUUAQYAABORAWIAAFOQAQMBU5wB\ -AgERBxwUAQcAABOZAWoAAFOYAQMBU6QBAgERCB0UAQgAABOhATIAAFOcAQMBU6gBAgERCR4UAQkAABOlAXIAAFOk\ -AQMBU7ABAgERCh8UAQoAABOtAToAAFOoAQMBU7QBAgERCzAUAQsAABOxAVoAAFOwAQMBU7wBAgERDAcUAQwAABO5\ -AUoAAFO4AQMBU8QBAgH/dGltZXN0YW0AAXABCQACAQkAAf90aW1lc3RhbQAfcERpZmYBCQACAQkAAf9idXJzdExl\ -bgAHZ3RoAQgAAgEIAAH/YnVyc3RPZmYAB3NldAEIAAIBCAAB/3RyaWdnZXJJAA9uZGV4AQgAAgEIAAH/dHJpZ2dl\ -clQBaW1lc3RhbXAAAAEJAAIBCQAB/2NlbnRlclBvAAdpbnQBCwACAQsAAf9pbnB1dENoYQAPbm5lbAEGAAIBBgAB\ -H2lucHV0AQYAAgEGAAH/b3V0cHV0Q2gAH2FubmVsAQYAAgEGAAE/b3V0cHV0AQYAAgEGAAH/dHJpZ2dlclMAA3Jj\ -AQYAAgEGAAH/c2V0UG9pbnQAAAABCwACAQsAATEBGgL/emhpbnN0L2kHby9wcm90b2NvbC9jYXBucC9zdHJlYW1p\ -bmcvc3RyZWFtaW5nLmNhcG5wOlN0cmVhbWluZ0hhbmQDbGURAQdQAQERAUdRBAMFAAD/tkd8lg+tC6MBbrHAdzOa\ -X5kREVoAAhEJB/9zZW5kVmFsdQADZXNAAREBB1ABATEBogH/emhpbnN0L2kFby9wcm90b2NvbC9jYXBucC9jb21t\ -b24va3dhcmdzLmNhcG5wOkt3YQdyZ3MRARdRBAEB/1Ml3NNvTmSiABEBMh9FbnRyeREBP1EEAwQAAAQBAAARDUIA\ -AFEIAwFRJAIBf2VudHJpZXMBDgABUAMBARD/UyXc029OZKIAAAEBDgABMQGqAv96aGluc3QvaQlvL3Byb3RvY29s\ -L2NhcG5wL3N0cmVhbWluZy9zdHJlYW1pbmcuY2FwbnA6U3RyZWFtaW5nSGFuZGxlLnNlbmRWYWx1ZXMkUGEPcmFt\ -cxEBP1EEAwQAAAQBAAARDToAAFEIAwFRJAIBP3ZhbHVlcwEOAAFQAwEBEP/cfINuN+4I9AAAAQEOAAExAQIB/2Nh\ -cG5wL3N0A3JlYW0uY2FwbnA6U3RyZWFtUmVzdWx0ABEBB1ABATEB0gH/emhpbnN0L2kGby9wcm90b2NvbC9jYXBu\ -cC9jb21tb24va3dhcmdzLmNhcG5wOkt3YXJncy5FbnRyAXkRAQdQAQERAXdRCAMEAAAEAQAAESkiAABRJAMBUTAC\ -AREBARQBAQEBES0yAABRKAMBUTQCAQdrZXkBDAACAQwAAR92YWx1ZQEQ/6x1vnFHi4OxAAABARAAAFACAQAAAQcP\ -/P///w==" - -_SCHEMA_LOADER = None - - -def get_schema_loader() -> zhinst.comms.SchemaLoader: - """Get the schema loader for the HPK schema. - - The schema is cached since it is expensive to load and there is no need to - load it multiple times. - - Returns: - The schema loader for the HPK schema. - """ - global _SCHEMA_LOADER # noqa: PLW0603 - if _SCHEMA_LOADER is None: - _SCHEMA_LOADER = zhinst.comms.SchemaLoader( - _CAPNP_BINARY_SCHEMA, # type: ignore[arg-type] - version=str(_GENERATED_WITH), - ) - return _SCHEMA_LOADER - - -# The following structs are generated for all structs and enums in the schema above. -# They all derive from zhinst.comms.DynamicStruct and have no additional logic on their -# own. They mainly serve two use cases: -# -# 1. Provide a typed interface to the schema and detect breaking changes more easily. -# Since zhinst.comms is schema agnostic the return types are always DynamicStructs. -# As a user one can cast the return values to the generated structs to get a typed -# interface. -# Example: -# `result = t.cast(schema.GetValueResults, await client.getValues(...))` -# -# 2. Provide a typed interface for creating new messages. -# A DynamicStruct can be created by providing a schema loader and the capnp id. -# The generated structs provide a more convenient way to create new messages. -# Example: -# `msg = schema.GetValueParams()` -# Note that the additional kwargs are passed to the constructor of the DynamicStruct -# and can be used to set initial values. -# -# IMPORTANT: There is no additional logic in the generated structs. They are just a -# typed interface to the schema. All logic is implemented in the DynamicStruct class. -# -# IMPORTANT: There are quite some differences between how python and capnp handle things. -# One of them is that capnp struct can have unions. The generator does not support unions -# and will generate a struct for each union branch. This is not ideal but should be fine. -# -# For more information see the documentation of zhinst.comms.DynamicStruct. - - -class ReflectionGetTheSchemaParams(zhinst.comms.DynamicStruct): - capnp_id = 0x86A77BE57BFA08F7 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CapSchema(zhinst.comms.DynamicStruct): - capnp_id = 0xCB31EF7A76EB85CF - typeId: int # uint64 - theSchema: list[CapnpNode] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_theSchema(self, size: int) -> list[CapnpNode]: # type: ignore[valid-type] - return super().init_list("theSchema", size) # type: ignore[return-value] - - -class ReflectionGetTheSchemaResults(zhinst.comms.DynamicStruct): - capnp_id = 0xC9DB2193E1883DD1 - theSchema: CapSchema - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_theSchema(self, **kwarg) -> CapSchema: # type: ignore[valid-type] - return super().init_struct("theSchema", **kwarg) # type: ignore[return-value] - - -class ReflectionGetReflectionVersionParams(zhinst.comms.DynamicStruct): - capnp_id = 0xE3E32FD4D93C1199 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class ReflectionGetReflectionVersionResults(zhinst.comms.DynamicStruct): - capnp_id = 0xFB31A67FD905411A - version: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CapnpNode(zhinst.comms.DynamicStruct): - capnp_id = 0xE682AB4CF923A417 - id: int # uint64 - displayName: str - displayNamePrefixLength: int # uint32 - scopeId: int # uint64 - nestedNodes: list[CapnpNodeNestedNode] - annotations: list[CapnpAnnotation] - file: t.Any - struct: CapnpNodeStruct - enum: CapnpNodeEnum - interface: CapnpNodeInterface - const: CapnpNodeConst - annotation: CapnpNodeAnnotation - parameters: list[CapnpNodeParameter] - isGeneric: bool - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_nestedNodes(self, size: int) -> list[CapnpNodeNestedNode]: # type: ignore[valid-type] - return super().init_list("nestedNodes", size) # type: ignore[return-value] - - def init_annotations(self, size: int) -> list[CapnpAnnotation]: # type: ignore[valid-type] - return super().init_list("annotations", size) # type: ignore[return-value] - - def init_enum(self, **kwarg) -> CapnpNodeEnum: # type: ignore[valid-type] - return super().init_struct("enum", **kwarg) # type: ignore[return-value] - - def init_interface(self, **kwarg) -> CapnpNodeInterface: # type: ignore[valid-type] - return super().init_struct("interface", **kwarg) # type: ignore[return-value] - - def init_const(self, **kwarg) -> CapnpNodeConst: # type: ignore[valid-type] - return super().init_struct("const", **kwarg) # type: ignore[return-value] - - def init_annotation(self, **kwarg) -> CapnpNodeAnnotation: # type: ignore[valid-type] - return super().init_struct("annotation", **kwarg) # type: ignore[return-value] - - def init_parameters(self, size: int) -> list[CapnpNodeParameter]: # type: ignore[valid-type] - return super().init_list("parameters", size) # type: ignore[return-value] - - -class CapnpElementSize(zhinst.comms.DynamicEnum): - capnp_id = 0xD1958F7DBA521926 - empty = 0 - bit = 1 - byte = 2 - twoBytes = 3 - fourBytes = 4 - eightBytes = 5 - pointer = 6 - inlineComposite = 7 - - -class CapnpField(zhinst.comms.DynamicStruct): - capnp_id = 0x9AAD50A41F4AF45F - name: str - codeOrder: int # uint16 - annotations: list[CapnpAnnotation] - discriminantValue: int # uint16 - slot: CapnpFieldSlot - group: CapnpFieldGroup - ordinal: CapnpFieldOrdinal - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_annotations(self, size: int) -> list[CapnpAnnotation]: # type: ignore[valid-type] - return super().init_list("annotations", size) # type: ignore[return-value] - - def init_slot(self, **kwarg) -> CapnpFieldSlot: # type: ignore[valid-type] - return super().init_struct("slot", **kwarg) # type: ignore[return-value] - - def init_group(self, **kwarg) -> CapnpFieldGroup: # type: ignore[valid-type] - return super().init_struct("group", **kwarg) # type: ignore[return-value] - - def init_ordinal(self, **kwarg) -> CapnpFieldOrdinal: # type: ignore[valid-type] - return super().init_struct("ordinal", **kwarg) # type: ignore[return-value] - - -class CapnpNodeStruct(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x9EA0B19B37FB4435 - dataWordCount: int # uint16 - pointerCount: int # uint16 - preferredListEncoding: CapnpElementSize - isGroup: bool - discriminantCount: int # uint16 - discriminantOffset: int # uint32 - fields: list[CapnpField] - - def init_fields(self, size: int) -> list[CapnpField]: # type: ignore[valid-type] - return super().init_list("fields", size) # type: ignore[return-value] - - -class CapnpEnumerant(zhinst.comms.DynamicStruct): - capnp_id = 0x978A7CEBDC549A4D - name: str - codeOrder: int # uint16 - annotations: list[CapnpAnnotation] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_annotations(self, size: int) -> list[CapnpAnnotation]: # type: ignore[valid-type] - return super().init_list("annotations", size) # type: ignore[return-value] - - -class CapnpNodeEnum(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xB54AB3364333F598 - enumerants: list[CapnpEnumerant] - - def init_enumerants(self, size: int) -> list[CapnpEnumerant]: # type: ignore[valid-type] - return super().init_list("enumerants", size) # type: ignore[return-value] - - -class CapnpMethod(zhinst.comms.DynamicStruct): - capnp_id = 0x9500CCE23B334D80 - name: str - codeOrder: int # uint16 - paramStructType: int # uint64 - resultStructType: int # uint64 - annotations: list[CapnpAnnotation] - paramBrand: CapnpBrand - resultBrand: CapnpBrand - implicitParameters: list[CapnpNodeParameter] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_annotations(self, size: int) -> list[CapnpAnnotation]: # type: ignore[valid-type] - return super().init_list("annotations", size) # type: ignore[return-value] - - def init_paramBrand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("paramBrand", **kwarg) # type: ignore[return-value] - - def init_resultBrand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("resultBrand", **kwarg) # type: ignore[return-value] - - def init_implicitParameters(self, size: int) -> list[CapnpNodeParameter]: # type: ignore[valid-type] - return super().init_list("implicitParameters", size) # type: ignore[return-value] - - -class CapnpSuperclass(zhinst.comms.DynamicStruct): - capnp_id = 0xA9962A9ED0A4D7F8 - id: int # uint64 - brand: CapnpBrand - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_brand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("brand", **kwarg) # type: ignore[return-value] - - -class CapnpNodeInterface(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xE82753CFF0C2218F - methods: list[CapnpMethod] - superclasses: list[CapnpSuperclass] - - def init_methods(self, size: int) -> list[CapnpMethod]: # type: ignore[valid-type] - return super().init_list("methods", size) # type: ignore[return-value] - - def init_superclasses(self, size: int) -> list[CapnpSuperclass]: # type: ignore[valid-type] - return super().init_list("superclasses", size) # type: ignore[return-value] - - -class CapnpType(zhinst.comms.DynamicStruct): - capnp_id = 0xD07378EDE1F9CC60 - void: t.Any - bool: t.Any - int8: t.Any - int16: t.Any - int32: t.Any - int64: t.Any - uint8: t.Any - uint16: t.Any - uint32: t.Any - uint64: t.Any - float32: t.Any - float64: t.Any - text: t.Any - data: t.Any - list: CapnpTypeList - enum: CapnpTypeEnum - struct: CapnpTypeStruct - interface: CapnpTypeInterface - anyPointer: CapnpTypeAnyPointer - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_interface(self, **kwarg) -> CapnpTypeInterface: # type: ignore[valid-type] - return super().init_struct("interface", **kwarg) # type: ignore[return-value] - - def init_anyPointer(self, **kwarg) -> CapnpTypeAnyPointer: # type: ignore[valid-type] - return super().init_struct("anyPointer", **kwarg) # type: ignore[return-value] - - -class CapnpValue(zhinst.comms.DynamicStruct): - capnp_id = 0xCE23DCD2D7B00C9B - void: t.Any - bool: bool - int8: int # uint8 - int16: int # uint16 - int32: int # uint32 - int64: int # uint64 - uint8: int # uint8 - uint16: int # uint16 - uint32: int # uint32 - uint64: int # uint64 - float32: float # float32 - float64: float # float64 - text: str - data: bytes - list: t.Any - enum: int # uint16 - struct: t.Any - interface: t.Any - anyPointer: t.Any - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CapnpNodeConst(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xB18AA5AC7A0D9420 - type: CapnpType - value: CapnpValue - - def init_type(self, **kwarg) -> CapnpType: # type: ignore[valid-type] - return super().init_struct("type", **kwarg) # type: ignore[return-value] - - def init_value(self, **kwarg) -> CapnpValue: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - -class CapnpNodeAnnotation(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xEC1619D4400A0290 - type: CapnpType - targetsFile: bool - targetsConst: bool - targetsEnum: bool - targetsEnumerant: bool - targetsStruct: bool - targetsField: bool - targetsUnion: bool - targetsGroup: bool - targetsInterface: bool - targetsMethod: bool - targetsParam: bool - targetsAnnotation: bool - - def init_type(self, **kwarg) -> CapnpType: # type: ignore[valid-type] - return super().init_struct("type", **kwarg) # type: ignore[return-value] - - -class CapnpNodeNestedNode(zhinst.comms.DynamicStruct): - capnp_id = 0xDEBF55BBFA0FC242 - name: str - id: int # uint64 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CapnpAnnotation(zhinst.comms.DynamicStruct): - capnp_id = 0xF1C8950DAB257542 - id: int # uint64 - value: CapnpValue - brand: CapnpBrand - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_value(self, **kwarg) -> CapnpValue: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - def init_brand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("brand", **kwarg) # type: ignore[return-value] - - -class CapnpNodeParameter(zhinst.comms.DynamicStruct): - capnp_id = 0xB9521BCCF10FA3B1 - name: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CapnpBrand(zhinst.comms.DynamicStruct): - capnp_id = 0x903455F06065422B - scopes: list[CapnpBrandScope] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_scopes(self, size: int) -> list[CapnpBrandScope]: # type: ignore[valid-type] - return super().init_list("scopes", size) # type: ignore[return-value] - - -class CapnpBrandScope(zhinst.comms.DynamicStruct): - capnp_id = 0xABD73485A9636BC9 - scopeId: int # uint64 - bind: list[CapnpBrandBinding] - inherit: t.Any - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_bind(self, size: int) -> list[CapnpBrandBinding]: # type: ignore[valid-type] - return super().init_list("bind", size) # type: ignore[return-value] - - -class CapnpBrandBinding(zhinst.comms.DynamicStruct): - capnp_id = 0xC863CD16969EE7FC - unbound: t.Any - type: CapnpType - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_type(self, **kwarg) -> CapnpType: # type: ignore[valid-type] - return super().init_struct("type", **kwarg) # type: ignore[return-value] - - -class CapnpTypeList(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x87E739250A60EA97 - elementType: CapnpType - - def init_elementType(self, **kwarg) -> CapnpType: # type: ignore[valid-type] - return super().init_struct("elementType", **kwarg) # type: ignore[return-value] - - -class CapnpTypeEnum(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x9E0E78711A7F87A9 - typeId: int # uint64 - brand: CapnpBrand - - def init_brand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("brand", **kwarg) # type: ignore[return-value] - - -class CapnpTypeStruct(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xAC3A6F60EF4CC6D3 - typeId: int # uint64 - brand: CapnpBrand - - def init_brand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("brand", **kwarg) # type: ignore[return-value] - - -class CapnpTypeInterface(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xED8BCA69F7FB0CBF - typeId: int # uint64 - brand: CapnpBrand - - def init_brand(self, **kwarg) -> CapnpBrand: # type: ignore[valid-type] - return super().init_struct("brand", **kwarg) # type: ignore[return-value] - - -class CapnpTypeAnyPointerUnconstrained(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x8E3B5F79FE593656 - anyKind: t.Any - struct: t.Any - list: t.Any - capability: t.Any - - -class CapnpTypeAnyPointerParameter(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x9DD1F724F4614A85 - scopeId: int # uint64 - parameterIndex: int # uint16 - - -class CapnpTypeAnyPointerImplicitMethodParameter(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xBAEFC9120C56E274 - parameterIndex: int # uint16 - - -class CapnpTypeAnyPointer(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xC2573FE8A23E49F1 - unconstrained: CapnpTypeAnyPointerUnconstrained - parameter: CapnpTypeAnyPointerParameter - implicitMethodParameter: CapnpTypeAnyPointerImplicitMethodParameter - - def init_unconstrained(self, **kwarg) -> CapnpTypeAnyPointerUnconstrained: # type: ignore[valid-type] - return super().init_struct("unconstrained", **kwarg) # type: ignore[return-value] - - def init_parameter(self, **kwarg) -> CapnpTypeAnyPointerParameter: # type: ignore[valid-type] - return super().init_struct("parameter", **kwarg) # type: ignore[return-value] - - def init_implicitMethodParameter( - self, - **kwarg, - ) -> CapnpTypeAnyPointerImplicitMethodParameter: # type: ignore[valid-type] - return super().init_struct("implicitMethodParameter", **kwarg) # type: ignore[return-value] - - -class CapnpFieldSlot(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xC42305476BB4746F - offset: int # uint32 - type: CapnpType - defaultValue: CapnpValue - hadExplicitDefault: bool - - def init_type(self, **kwarg) -> CapnpType: # type: ignore[valid-type] - return super().init_struct("type", **kwarg) # type: ignore[return-value] - - def init_defaultValue(self, **kwarg) -> CapnpValue: # type: ignore[valid-type] - return super().init_struct("defaultValue", **kwarg) # type: ignore[return-value] - - -class CapnpFieldGroup(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xCAFCCDDB68DB1D11 - typeId: int # uint64 - - -class CapnpFieldOrdinal(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xBB90D5C287870BE6 - implicit: t.Any - explicit: int # uint16 - - -class SessionListNodesParams(zhinst.comms.DynamicStruct): - capnp_id = 0x92035429255EF5A8 - pathExpression: str - flags: int # uint32 - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionListNodesResults(zhinst.comms.DynamicStruct): - capnp_id = 0xEBD34BDB74C961B0 - paths: list[str] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_paths(self, size: int) -> list[str]: # type: ignore[valid-type] - return super().init_list("paths", size) # type: ignore[return-value] - - -class SessionDeprecatedGetValuesParams(zhinst.comms.DynamicStruct): - capnp_id = 0xCD242E1BB990DFE7 - paths: list[str] - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_paths(self, size: int) -> list[str]: # type: ignore[valid-type] - return super().init_list("paths", size) # type: ignore[return-value] - - -class Result(zhinst.comms.DynamicStruct): - capnp_id = 0xBAB0F33E1934323D - ok: t.Any - err: t.Any - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class AnnotatedValue(zhinst.comms.DynamicStruct): - capnp_id = 0xF408EE376E837CDC - metadata: AnnotatedValueMetadata - value: Value - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_metadata(self, **kwarg) -> AnnotatedValueMetadata: # type: ignore[valid-type] - return super().init_struct("metadata", **kwarg) # type: ignore[return-value] - - def init_value(self, **kwarg) -> Value: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - -class Error(zhinst.comms.DynamicStruct): - capnp_id = 0xC4E34E4C517D11D9 - code: int # uint32 - message: str - category: str - kind: ErrorKind - source: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionDeprecatedGetValuesResults(zhinst.comms.DynamicStruct): - capnp_id = 0xE948518C129575BB - result: list[Result] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, size: int) -> list[Result]: # type: ignore[valid-type] - return super().init_list("result", size) # type: ignore[return-value] - - -class Value(zhinst.comms.DynamicStruct): - capnp_id = 0xB1838B4771BE75AC - int64: int # uint64 - double: float # float64 - complex: complex - string: str - vectorData: VectorData - cntSample: CntSample - triggerSample: TriggerSample - none: VoidStruct - streamingError: Error - shfDemodData: ShfDemodulatorVectorData - shfResultLoggerData: ShfResultLoggerVectorData - vectorCntSamples: list[CntSample] - vectorTriggerSamples: list[TriggerSample] - largeVectorData: LargeVectorData - bool: bool - shfScopeData: ShfScopeVectorData - shfGeneratorWaveformData: ShfGeneratorWaveformVectorData - shfPidData: ShfPidVectorData - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_complex(self, **kwarg) -> complex: # type: ignore[valid-type] - return super().init_struct("complex", **kwarg) # type: ignore[return-value] - - def init_vectorData(self, **kwarg) -> VectorData: # type: ignore[valid-type] - return super().init_struct("vectorData", **kwarg) # type: ignore[return-value] - - def init_cntSample(self, **kwarg) -> CntSample: # type: ignore[valid-type] - return super().init_struct("cntSample", **kwarg) # type: ignore[return-value] - - def init_triggerSample(self, **kwarg) -> TriggerSample: # type: ignore[valid-type] - return super().init_struct("triggerSample", **kwarg) # type: ignore[return-value] - - def init_none(self, **kwarg) -> VoidStruct: # type: ignore[valid-type] - return super().init_struct("none", **kwarg) # type: ignore[return-value] - - def init_streamingError(self, **kwarg) -> Error: # type: ignore[valid-type] - return super().init_struct("streamingError", **kwarg) # type: ignore[return-value] - - def init_shfDemodData(self, **kwarg) -> ShfDemodulatorVectorData: # type: ignore[valid-type] - return super().init_struct("shfDemodData", **kwarg) # type: ignore[return-value] - - def init_shfResultLoggerData(self, **kwarg) -> ShfResultLoggerVectorData: # type: ignore[valid-type] - return super().init_struct("shfResultLoggerData", **kwarg) # type: ignore[return-value] - - def init_vectorCntSamples(self, size: int) -> list[CntSample]: # type: ignore[valid-type] - return super().init_list("vectorCntSamples", size) # type: ignore[return-value] - - def init_vectorTriggerSamples(self, size: int) -> list[TriggerSample]: # type: ignore[valid-type] - return super().init_list("vectorTriggerSamples", size) # type: ignore[return-value] - - def init_largeVectorData(self, **kwarg) -> LargeVectorData: # type: ignore[valid-type] - return super().init_struct("largeVectorData", **kwarg) # type: ignore[return-value] - - def init_shfScopeData(self, **kwarg) -> ShfScopeVectorData: # type: ignore[valid-type] - return super().init_struct("shfScopeData", **kwarg) # type: ignore[return-value] - - def init_shfGeneratorWaveformData(self, **kwarg) -> ShfGeneratorWaveformVectorData: # type: ignore[valid-type] - return super().init_struct("shfGeneratorWaveformData", **kwarg) # type: ignore[return-value] - - def init_shfPidData(self, **kwarg) -> ShfPidVectorData: # type: ignore[valid-type] - return super().init_struct("shfPidData", **kwarg) # type: ignore[return-value] - - -class ReturnFromSetWhen(zhinst.comms.DynamicEnum): - capnp_id = 0xDD2DA53AAC55EDF9 - asap = 0 - deviceAck = 1 - unusedAsync = 2 - unusedTransactional = 3 - - -class SessionDeprecatedSetValueParams(zhinst.comms.DynamicStruct): - capnp_id = 0xA8F6FDED40F38065 - path: str - value: Value - completeWhen: ReturnFromSetWhen - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_value(self, **kwarg) -> Value: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - -class VoidStruct(zhinst.comms.DynamicStruct): - capnp_id = 0xDF7FE8E981437816 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionDeprecatedSetValueResults(zhinst.comms.DynamicStruct): - capnp_id = 0x8AEF0146A9595DF9 - result: Result - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, **kwarg) -> Result: # type: ignore[valid-type] - return super().init_struct("result", **kwarg) # type: ignore[return-value] - - -class Subscription(zhinst.comms.DynamicStruct): - capnp_id = 0xEDAC21A53DE1B1D4 - path: str - streamingHandle: t.Any - subscriberId: bytes - kwargs: Kwargs - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_kwargs(self, **kwarg) -> Kwargs: # type: ignore[valid-type] - return super().init_struct("kwargs", **kwarg) # type: ignore[return-value] - - -class SessionSubscribeParams(zhinst.comms.DynamicStruct): - capnp_id = 0xB9B18BE9AFB1B3E4 - subscription: Subscription - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_subscription(self, **kwarg) -> Subscription: # type: ignore[valid-type] - return super().init_struct("subscription", **kwarg) # type: ignore[return-value] - - -class SessionSubscribeResults(zhinst.comms.DynamicStruct): - capnp_id = 0xA0CA6692DF48C93F - result: Result - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, **kwarg) -> Result: # type: ignore[valid-type] - return super().init_struct("result", **kwarg) # type: ignore[return-value] - - -class SessionDisconnectDeviceParams(zhinst.comms.DynamicStruct): - capnp_id = 0xE682E60B2CD90782 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionListNodesJsonParams(zhinst.comms.DynamicStruct): - capnp_id = 0xCD837FE86531F6C1 - pathExpression: str - flags: int # uint32 - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionListNodesJsonResults(zhinst.comms.DynamicStruct): - capnp_id = 0xA8151642645BFA32 - nodeProps: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionUnsubscribeParams(zhinst.comms.DynamicStruct): - capnp_id = 0xB16A1640DB91E61C - subscriberId: bytes - paths: list[str] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_paths(self, size: int) -> list[str]: # type: ignore[valid-type] - return super().init_list("paths", size) # type: ignore[return-value] - - -class SessionGetSessionVersionParams(zhinst.comms.DynamicStruct): - capnp_id = 0xD77D14CF14B3405A - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionGetSessionVersionResults(zhinst.comms.DynamicStruct): - capnp_id = 0xCC68434DA57CF6B3 - version: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionDeprecatedSetValue2Params(zhinst.comms.DynamicStruct): - capnp_id = 0xDAE05CD32680ABA1 - path: str - value: Value - completeWhen: ReturnFromSetWhen - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_value(self, **kwarg) -> Value: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - -class SessionDeprecatedSetValue2Results(zhinst.comms.DynamicStruct): - capnp_id = 0xFD47648E56C0E689 - result: Result - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, **kwarg) -> Result: # type: ignore[valid-type] - return super().init_struct("result", **kwarg) # type: ignore[return-value] - - -class LookupMode(zhinst.comms.DynamicEnum): - capnp_id = 0xDA5049B5E072F425 - directLookup = 0 - withExpansion = 1 - - -class SessionSetValueParams(zhinst.comms.DynamicStruct): - capnp_id = 0x9FE29A10493E27C7 - pathExpression: str - value: Value - lookupMode: LookupMode - completeWhen: ReturnFromSetWhen - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_value(self, **kwarg) -> Value: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] - - -class SessionSetValueResults(zhinst.comms.DynamicStruct): - capnp_id = 0x9474D85F26F63900 - result: list[Result] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, size: int) -> list[Result]: # type: ignore[valid-type] - return super().init_list("result", size) # type: ignore[return-value] - - -class SessionGetValueParams(zhinst.comms.DynamicStruct): - capnp_id = 0xC5E872236CC4917B - pathExpression: str - lookupMode: LookupMode - flags: int # uint32 - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class SessionGetValueResults(zhinst.comms.DynamicStruct): - capnp_id = 0xCE98719DBAD0A401 - result: list[Result] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_result(self, size: int) -> list[Result]: # type: ignore[valid-type] - return super().init_list("result", size) # type: ignore[return-value] - - -class SessionCancelParams(zhinst.comms.DynamicStruct): - capnp_id = 0xB77C3D30C74311FC - client: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class AnnotatedValueMetadata(zhinst.comms.DynamicStruct): - capnp_id = 0xAD53D8CA57AF3018 - timestamp: int # uint64 - path: str - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class Complex(zhinst.comms.DynamicStruct): - capnp_id = 0xAAF1AFAF97B4B157 - real: float # float64 - imag: float # float64 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class VectorData(zhinst.comms.DynamicStruct): - capnp_id = 0x994C65B80DF38978 - valueType: int # uint16 - vectorElementType: int # uint8 - extraHeaderInfo: int # uint32 - data: bytes - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class CntSample(zhinst.comms.DynamicStruct): - capnp_id = 0xE9370BD8287D6065 - timestamp: int # uint64 - counter: int # uint32 - trigger: int # uint32 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class TriggerSample(zhinst.comms.DynamicStruct): - capnp_id = 0xDEB72097C27D0D95 - timestamp: int # uint64 - sampleTick: int # uint64 - trigger: int # uint32 - missedTriggers: int # uint32 - awgTrigger: int # uint32 - dio: int # uint32 - sequenceIndex: int # uint32 - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class ShfDemodulatorVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0x9B03E3E3E6006582 - properties: ShfDemodulatorVectorDataProperties - x: list[float] # list[float64] - y: list[float] # list[float64] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_properties(self, **kwarg) -> ShfDemodulatorVectorDataProperties: # type: ignore[valid-type] - return super().init_struct("properties", **kwarg) # type: ignore[return-value] - - def init_x(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("x", size) # type: ignore[return-value] - - def init_y(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("y", size) # type: ignore[return-value] - - -class ShfResultLoggerVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0xBBA061F579761DDD - properties: ShfResultLoggerVectorDataProperties - vector: ShfResultLoggerVectorDataVector - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_properties(self, **kwarg) -> ShfResultLoggerVectorDataProperties: # type: ignore[valid-type] - return super().init_struct("properties", **kwarg) # type: ignore[return-value] - - def init_vector(self, **kwarg) -> ShfResultLoggerVectorDataVector: # type: ignore[valid-type] - return super().init_struct("vector", **kwarg) # type: ignore[return-value] - - -class LargeVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0xD948F7B09959B00A - valueType: int # uint16 - vectorElementType: int # uint8 - extraHeaderInfo: int # uint32 - dataSegments: list[bytes] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_dataSegments(self, size: int) -> list[bytes]: # type: ignore[valid-type] - return super().init_list("dataSegments", size) # type: ignore[return-value] - - -class ShfScopeVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0xA9B07F1F82A93BC9 - properties: ShfScopeVectorDataProperties - vector: ShfScopeVectorDataVector - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_properties(self, **kwarg) -> ShfScopeVectorDataProperties: # type: ignore[valid-type] - return super().init_struct("properties", **kwarg) # type: ignore[return-value] - - def init_vector(self, **kwarg) -> ShfScopeVectorDataVector: # type: ignore[valid-type] - return super().init_struct("vector", **kwarg) # type: ignore[return-value] - - -class ShfGeneratorWaveformVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0xEDCFE06E81C4F3D0 - complex: list[complex] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_complex(self, size: int) -> list[complex]: # type: ignore[valid-type] - return super().init_list("complex", size) # type: ignore[return-value] - - -class ShfPidVectorData(zhinst.comms.DynamicStruct): - capnp_id = 0xFF6EE171549A2870 - properties: ShfPidVectorDataProperties - value: list[float] # list[float64] - error: list[float] # list[float64] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_properties(self, **kwarg) -> ShfPidVectorDataProperties: # type: ignore[valid-type] - return super().init_struct("properties", **kwarg) # type: ignore[return-value] - - def init_value(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("value", size) # type: ignore[return-value] - - def init_error(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("error", size) # type: ignore[return-value] - - -class ErrorKind(zhinst.comms.DynamicEnum): - capnp_id = 0xB7E671E24A9802BD - ok = 0 - cancelled = 1 - unknown = 2 - notFound = 3 - overwhelmed = 4 - badRequest = 5 - unimplemented = 6 - internal = 7 - unavailable = 8 - timeout = 9 - - -class ShfDemodulatorVectorDataProperties(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xD475786CA25C300D - timestamp: int # uint64 - dt: int # uint64 - burstLength: int # uint32 - burstOffset: int # uint32 - triggerIndex: int # uint32 - triggerTimestamp: int # uint64 - centerFreq: float # float64 - rfPath: bool - oscillatorSource: int # uint16 - harmonic: int # uint16 - triggerSource: int # uint8 - signalSource: int # uint16 - oscillatorFreq: float # float64 - scaling: float # float64 - - -class ShfResultLoggerVectorDataProperties(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xDDB7B421EA0EEC5E - timestamp: int # uint64 - jobId: int # uint32 - repetitionId: int # uint32 - scaling: float # float64 - centerFrequency: float # float64 - dataSource: int # uint32 - numSamples: int # uint32 - numSpectrSamples: int # uint32 - numAverages: int # uint32 - numAcquired: int # uint32 - holdoffErrorsReslog: int # uint16 - holdoffErrorsReadout: int # uint16 - holdoffErrorsSpectr: int # uint16 - firstSampleTimestamp: int # uint64 - - -class ShfResultLoggerVectorDataVector(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x980B68B5449BDF12 - real: list[float] # list[float64] - complex: list[complex] - - def init_real(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("real", size) # type: ignore[return-value] - - def init_complex(self, size: int) -> list[complex]: # type: ignore[valid-type] - return super().init_list("complex", size) # type: ignore[return-value] - - -class ShfScopeVectorDataProperties(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xD707B876F9608924 - timestamp: int # uint64 - timestampDiff: int # uint32 - flags: int # uint32 - scaling: float # float64 - centerFrequency: float # float64 - triggerTimestamp: int # uint64 - inputSelect: int # uint32 - averageCount: int # uint32 - numSegments: int # uint32 - numTotalSegments: int # uint32 - firstSegmentIndex: int # uint32 - numMissedTriggers: int # uint32 - - -class ShfScopeVectorDataVector(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0xD10AAB878C8E14BC - real: list[float] # list[float64] - complex: list[complex] - - def init_real(self, size: int) -> list[float]: # type: ignore[valid-type] - return super().init_list("real", size) # type: ignore[return-value] - - def init_complex(self, size: int) -> list[complex]: # type: ignore[valid-type] - return super().init_list("complex", size) # type: ignore[return-value] - - -class ShfPidVectorDataProperties(zhinst.comms.DynamicStruct): - # Group struct. This struct is not instantiable directly. - capnp_id = 0x9BE09D0677995544 - timestamp: int # uint64 - timestampDiff: int # uint64 - burstLength: int # uint32 - burstOffset: int # uint32 - triggerIndex: int # uint32 - triggerTimestamp: int # uint64 - centerPoint: float # float64 - inputChannel: int # uint8 - input: int # uint8 - outputChannel: int # uint8 - output: int # uint8 - triggerSrc: int # uint8 - setPoint: float # float64 - - -class Kwargs(zhinst.comms.DynamicStruct): - capnp_id = 0x8F25CE07A664CEE0 - entries: list[KwargsEntry] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_entries(self, size: int) -> list[KwargsEntry]: # type: ignore[valid-type] - return super().init_list("entries", size) # type: ignore[return-value] - - -class StreamingHandleSendValuesParams(zhinst.comms.DynamicStruct): - capnp_id = 0xA30BAD0F967C47B6 - values: list[AnnotatedValue] - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_values(self, size: int) -> list[AnnotatedValue]: # type: ignore[valid-type] - return super().init_list("values", size) # type: ignore[return-value] - - -class CapnpStreamResult(zhinst.comms.DynamicStruct): - capnp_id = 0x995F9A3377C0B16E - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - -class KwargsEntry(zhinst.comms.DynamicStruct): - capnp_id = 0xA2644E6FD3DC2553 - key: str - value: Value - - def __init__(self, **kwargs): - super().__init__(get_schema_loader(), self.capnp_id, **kwargs) - - def init_value(self, **kwarg) -> Value: # type: ignore[valid-type] - return super().init_struct("value", **kwarg) # type: ignore[return-value] diff --git a/src/labone/core/kernel_session.py b/src/labone/core/kernel_session.py deleted file mode 100644 index c0fb774..0000000 --- a/src/labone/core/kernel_session.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Module for a session to a LabOne Kernel. - -A Kernel is a remote server that provides access to a defined set of nodes. -It can be a device kernel that provides access to the device nodes but it -can also be a kernel that provides additional functionality, e.g. the -Data Server (ZI) kernel. - -Every Kernel provides the same interface and can therefore be handled -in the same way. The only difference is the set of nodes that are available -on the kernel. - -The number of sessions to a kernel is not limited. However, due to the -asynchronous interface, it is often not necessary to have multiple sessions -to the same kernel. -""" - -from __future__ import annotations - -from dataclasses import dataclass - -import zhinst.comms -from packaging import version -from typing_extensions import TypeAlias - -from labone.core import hpk_schema -from labone.core.errors import async_translate_comms_error -from labone.core.helper import ZIContext, get_default_context -from labone.core.session import Session - -KernelInfo: TypeAlias = zhinst.comms.DestinationParams -HPK_SCHEMA_ID = 0xA621130A90860008 - - -@dataclass(frozen=True) -class ServerInfo: - """Information about a server.""" - - host: str - port: int - - -class KernelSession(Session): - """Session to a LabOne kernel. - - Representation of a single session to a LabOne kernel. This class - encapsulates the labone interaction and exposes a Python native API. - All functions are exposed as they are implemented in the kernel - interface and are directly forwarded to the kernel. - - Each function implements the required error handling both for the - socket communication and the server errors. This means unless an Exception - is raised the call was successful. - - The KernelSession class is instantiated through the staticmethod - `create()`. - This is due to the fact that the instantiation is done asynchronously. - To call the constructor directly an already existing connection - must be provided. - - !!! note - - Due to the asynchronous interface, one needs to use the static method - `create` instead of the `__init__` method. - - ```python - kernel_info = ZIContext.zi_connection() - server_info = ServerInfo(host="localhost", port=8004) - kernel_session = await KernelSession( - kernel_info = kernel_info, - server_info = server_info, - ) - ``` - - Args: - core_session: The underlying zhinst.comms session. - context: The context in which the session is running. - server_info: Information about the target data server. - capability_version: The capability version the server reported. - """ - - def __init__( - self, - core_session: zhinst.comms.DynamicClient, - *, - context: ZIContext, - server_info: ServerInfo, - capability_version: version.Version, - ) -> None: - super().__init__( - core_session, - context=context, - capability_version=capability_version, - ) - self._server_info = server_info - - @staticmethod - @async_translate_comms_error - async def create( - *, - kernel_info: KernelInfo, - server_info: ServerInfo, - context: ZIContext | None = None, - timeout: int = 5000, - ) -> KernelSession: - """Create a new session to a LabOne kernel. - - Since the creation of a new session happens asynchronously, this method - is required, instead of a simple constructor (since a constructor can - not be asynchronous). - - Args: - kernel_info: Information about the target kernel. - server_info: Information about the target data server. - context: Context in which the session should run. If not provided - the default context will be used which is in most cases the - desired behavior. - timeout: Timeout in milliseconds for the connection setup. - - Returns: - A new session to the specified kernel. - - Raises: - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming - request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If another error happens during the session creation. - """ - if context is None: - context = get_default_context() - core_session = await context.connect_labone( - server_info.host, - server_info.port, - kernel_info, - schema=hpk_schema.get_schema_loader().get_interface_schema(HPK_SCHEMA_ID), - timeout=timeout, - ) - compatibility_version = version.Version( - (await core_session.getSessionVersion()).version, - ) - return KernelSession( - core_session, - context=context, - server_info=server_info, - capability_version=compatibility_version, - ) - - @property - def server_info(self) -> ServerInfo: - """Information about the server.""" - return self._server_info diff --git a/src/labone/core/session.py b/src/labone/core/session.py deleted file mode 100644 index df8f5e9..0000000 --- a/src/labone/core/session.py +++ /dev/null @@ -1,993 +0,0 @@ -"""Module for the Zurich Instruments Session Capability. - -The Session capability provides access to the basic interactions with nodes -and or properties of a server. Its used in multiple places with the software -stack of Zurich Instruments. For example the Data Server kernel for a device -provides a Session capability to access the nodes of the device. - -Every Session capability provides the same interface and can therefore be -handled in the same way. The only difference is the set of nodes/properties that -are available. - -The number of sessions to a capability is not limited. However, due to the -asynchronous interface, it is often not necessary to have multiple sessions -to the same capability. -""" - -from __future__ import annotations - -import asyncio -import json -import typing as t -import uuid -from contextlib import asynccontextmanager -from enum import IntFlag -from typing import Literal - -from packaging import version -from typing_extensions import NotRequired, TypeAlias, TypedDict -from zhinst.comms import unwrap - -from labone.core import errors, hpk_schema -from labone.core.errors import async_translate_comms_error, translate_comms_error -from labone.core.subscription import DataQueue, QueueProtocol, StreamingHandle -from labone.core.value import AnnotatedValue, Value, value_from_python_types - -if t.TYPE_CHECKING: - import zhinst.comms - - from labone.core.helper import ( - LabOneNodePath, - ZIContext, - ) - -T = t.TypeVar("T") - -# Control node for transactions. This node is only available for UHF and MF devices. -_TRANSACTION_NODE_PATH = "/ctrl/transaction/state" - -NodeType: TypeAlias = Literal[ - "Integer (64 bit)", - "Double", - "Complex Double", - "String", - "ZIScopeWave", - "ZIImpedanceSample", - "ZICntSample", - "ZITrigSample", - "ZIVectorData", - "ZIDemodSample", - "ZIPWAWave", - "ZIAuxInSample", - "ZIDIOSample", -] - - -class NodeInfo(TypedDict): - """Node information structure. - - - Node: Node absolute path. - - Description: Node description. - - Properties: Comma-separated list of node properties. - A node can have one or multiple of the following properties: - - "Read", "Write", "Stream", "Setting", "Pipelined" - - - Type: Node type. - - Unit: Node unit. - - Options: Possible values for the node. - The key exists only if the node `Type` is `Integer (enumerated)`. - """ - - Node: LabOneNodePath - Description: str - Properties: str - Type: NodeType - Unit: str - Options: NotRequired[dict[str, str]] - - -class ListNodesInfoFlags(IntFlag): - """Options for specifying on how `Session.list_nodes_info()` returns the nodes. - - Multiple flags can be combined by using bitwise operations: - - `ListNodesInfoFlags.SETTINGS_ONLY | ListNodesInfoFlags.EXCLUDE_VECTORS` - - - ALL: Return all matching nodes. - - SETTINGS_ONLY: Return only setting nodes. - - STREAMING_ONLY: Return only streaming nodes. - - BASE_CHANNEL_ONLY: Return only one instance of a channel - in case of multiple channels. - - GET_ONLY: Return only nodes which can be used with the get command. - - EXCLUDE_STREAMING: Exclude streaming nodes. - - EXCLUDE_VECTORS: Exclude vector nodes. - """ - - ALL = 0 - SETTINGS_ONLY = 1 << 3 - STREAMING_ONLY = 1 << 4 - BASE_CHANNEL_ONLY = 1 << 6 - GET_ONLY = 1 << 7 - EXCLUDE_STREAMING = 1 << 20 - EXCLUDE_VECTORS = 1 << 24 - - -class ListNodesFlags(IntFlag): - """Options for specifying on how `Session.list_nodes()` returns the nodes. - - Multiple flags can be combined by using bitwise operations: - - `ListNodesFlags.ABSOLUTE | ListNodesFlags.RECURSIVE` - - - ALL: Return all matching nodes. - - RECURSIVE: Return nodes recursively. - - ABSOLUTE: Absolute node paths. - - LEAVES_ONLY: Return only leave nodes, which means they - are at the outermost level of the three. - - SETTINGS_ONLY: Return only setting nodes. - - STREAMING_ONLY: Return only streaming nodes. - - BASE_CHANNEL_ONLY: Return only one instance of a channel - in case of multiple channels. - - GET_ONLY: Return only nodes which can be used with the get command. - - EXCLUDE_STREAMING: Exclude streaming nodes. - - EXCLUDE_VECTORS: Exclude vector nodes. - """ - - ALL = 0 - RECURSIVE = 1 - ABSOLUTE = 1 << 1 - LEAVES_ONLY = 1 << 2 - SETTINGS_ONLY = 1 << 3 - STREAMING_ONLY = 1 << 4 - BASE_CHANNEL_ONLY = 1 << 6 - GET_ONLY = 1 << 7 - EXCLUDE_STREAMING = 1 << 20 - EXCLUDE_VECTORS = 1 << 24 - - -class Session: - """Generic Capnp session client. - - Representation of a single Session capability. This class - encapsulates the labone interaction an exposes a python native api. - All function are exposed as they are implemented in the interface - of the labone server and are directly forwarded. - - Each function implements the required error handling both for the - socket communication and the server errors. This means unless an Exception - is raised the call was successful. - - The Session already requires an existing connection this is due to the - fact that the instantiation is done asynchronously. - - Args: - session: Active labone session. - context: Context the session runs in. - """ - - # The minimum capability version that is required by the labone api. - MIN_CAPABILITY_VERSION = version.Version("1.7.0") - # The capability version hardcoded in the hpk schema. - CAPABILITY_VERSION = version.Version("1.15.0") - - def __init__( - self, - session: zhinst.comms.DynamicClient, - *, - context: ZIContext, - capability_version: version.Version, - ): - self._context = context - self._session = session - - # The client_id is required by most messages to identify the client - # on the server side. It is unique per session. - self._client_id = uuid.uuid4() - self._has_transaction_support: bool | None = None - self._capability_version = capability_version - - def close(self) -> None: - """Close the session. - - Release the underlying network resources and close the session. Since - python does not follow the RAII pattern, it is not guaranteed that the - destructor is called. This may causes a session to stay open even if the - object is not used anymore. - If needed, the close method should be called explicitly. - """ - self._session.close() - - def ensure_compatibility(self) -> None: - """Ensure the compatibility with the connected server. - - Ensures that all function call will work as expected and all required - features are implemented within the server. - - Warning: - Only the compatibility with the server is checked. The compatibility - with the device is not checked. - - Info: - This function is already called within the create method and does - not need to be called again. - - Raises: - UnavailableError: If the kernel is not compatible. - """ - if self._capability_version < Session.MIN_CAPABILITY_VERSION: - msg = ( - f"The data server version is not supported by the LabOne API. " - "Please update the LabOne software to the latest version. " - f"({self._capability_version}<{Session.MIN_CAPABILITY_VERSION})" - ) - raise errors.UnavailableError(msg) - if self._capability_version.major > Session.CAPABILITY_VERSION.major: - msg = ( - "The data server version is incompatible with this LabOne API " - "version. Please install the latest python package and retry. " - f"({self._capability_version}> {Session.CAPABILITY_VERSION})" - ) - raise errors.UnavailableError(msg) - - async def _list_nodes_postprocessing( - self, - future: t.Awaitable[hpk_schema.SessionListNodesResults], - ) -> list[LabOneNodePath]: - """Postprocessing for the list nodes function. - - Convert the response from the server to a list of node paths. - - Args: - future: Future for a list nodes call. - - Returns: - List of node paths. - """ - response = await future - return list(response.paths) - - @translate_comms_error - def list_nodes( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, - ) -> t.Awaitable[list[LabOneNodePath]]: - """List the nodes found at a given path. - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - path: A string representing the path where the nodes are to be listed. - Value is case insensitive. - - Supports * wildcards, except in the place of node path forward - slashes. (default="") - flags: The flags for modifying the returned nodes. - - Returns: - A list of strings representing the nodes. - - Returns an empty list when `path` does not match any nodes or the nodes - matching `path` does not fit into given `flags` criteria. - - Raises: - TypeError: If `path` is not a string or `flags` is not an integer. - ValueError: If `flags` value is out-of-bounds. - OverwhelmedError: If the kernel is overwhelmed. - UnimplementedError: If the list nodes request is not - supported by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - - - Examples: - Getting all the nodes: - - >>> await session.list_nodes("*") - ["/zi/config", "/zi/about", "/zi/debug", "/zi/clockbase", \ - "/zi/devices", "/zi/mds"] - - Getting the nodes with a specific path: - - >>> await session.list_nodes("zi/devices") - ["/zi/visible", "/zi/connected"] - - Using wildcards in the place of forward slashes will result - to an empty list: - - >>> await session.list_nodes("zi*devices") - [] - - Using flags: - - >>> await session.list_nodes( - ... "zi/devices", - ... flags=ListNodesFlags.RECURSIVE | ListNodesFlags.EXCLUDE_VECTORS - ... ) - """ - future = self._session.listNodes( - pathExpression=path, - flags=int(flags), - client=self._client_id.bytes, - ) - return self._list_nodes_postprocessing(future) # type: ignore[arg-type] - - async def _list_nodes_info_postprocessing( - self, - future: t.Awaitable[hpk_schema.SessionListNodesJsonResults], - ) -> dict[LabOneNodePath, NodeInfo]: - """Postprocessing for the list nodes info function. - - Convert the response from the server to a json dict. - - Args: - future: Future for a list nodes call. - - Returns: - A python dictionary of the list nodes info response. - """ - response = await future - try: - return json.loads(response.nodeProps) - except RuntimeError as e: - msg = "Error while listing nodes info." - raise errors.LabOneCoreError(msg) from e - - @translate_comms_error - def list_nodes_info( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, - ) -> t.Awaitable[dict[LabOneNodePath, NodeInfo]]: - """List the nodes and their information found at a given path. - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - path: A string representing the path where the nodes are to be listed. - Value is case insensitive. - - Supports * wildcards, except in the place of node path - forward slashes. (default="") - - flags: The flags for modifying the returned nodes. - - Returns: - A python dictionary where absolute node paths are keys and their - information are values. - - An empty dictionary when `path` does not match any nodes or the nodes - matching `path` does not fit into given `flags` criteria. - - Raises: - TypeError: If `path` is not a string or `flags` is not an integer. - ValueError: If `flags` value is out-of-bounds. - OverwhelmedError: If the kernel is overwhelmed. - UnimplementedError: If the list nodes info request is not - supported by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - - Examples: - Using a wildcard in the node path that matches multiple nodes: - - >>> await session.list_nodes_info("/zi/devices/*") - { - '/zi/devices/visible': { - 'Node': '/ZI/DEVICES/VISIBLE', - 'Description': 'Contains a list of devices in the network' \ - 'visible to the LabOne Data Server.', - 'Properties': 'Read', - 'Type': 'String', - 'Unit': 'None' - }, - '/zi/devices/connected': { - 'Node': '/ZI/DEVICES/CONNECTED', - 'Description': 'Contains a list of devices connected to the' \ - 'LabOne Data Server.', - 'Properties': 'Read', - 'Type': 'String', - 'Unit': 'None' - } - } - - With nodes of type 'Integer (enumerated)', the returned - value has an additional 'Options" key: - - >>> await session.list_nodes_info("/zi/config/open") - { - '/zi/config/open': { - 'Node': '/ZI/CONFIG/OPEN', - 'Description': 'Enable communication with the LabOne Data Server' \ - 'from other computers in the network.', - 'Properties': 'Read, Write, Setting', - 'Type': 'Integer (enumerated)', - 'Unit': 'None', - 'Options': { - '0': '"local": Communication only possible with ' \ - 'the local machine.', - '1': '"network": Communication possible with other' \ - 'machines in the network.' - } - } - } - """ - future = self._session.listNodesJson( - pathExpression=path, - flags=int(flags), - client=self._client_id.bytes, - ) - return self._list_nodes_info_postprocessing(future) # type: ignore[arg-type] - - async def _single_value_postprocessing( - self, - future: t.Awaitable[ - hpk_schema.SessionSetValueResults | hpk_schema.SessionGetValueResults - ], - path: LabOneNodePath, - ) -> AnnotatedValue: - """Postprocessing for the get/set with expression functions. - - Convert a single value response from the server to an annotated value. - - Args: - future: Future for a get/set call. - path: LabOne node path. - - Returns: - Annotated value of the node. - """ - response = (await future).result - try: - return AnnotatedValue.from_capnp( - t.cast(hpk_schema.AnnotatedValue, unwrap(response[0])), - ) - except IndexError as e: - msg = f"No value returned for path {path}." - raise errors.LabOneCoreError(msg) from e - except RuntimeError as e: - msg = f"Error while processing value from {path}. {e}" - raise errors.LabOneCoreError(msg) from e - - async def _multi_value_postprocessing( - self, - future: t.Awaitable[ - hpk_schema.SessionSetValueResults | hpk_schema.SessionGetValueResults - ], - path: LabOneNodePath, - ) -> list[AnnotatedValue]: - """Postprocessing for the get/set with expression functions. - - Convert a multiple value response from the server to a list of - annotated values. - - Args: - future: Future for a get/set call. - path: LabOne node path. - - Returns: - List of annotated value of the node. - """ - response = (await future).result - try: - return [ - AnnotatedValue.from_capnp( - t.cast(hpk_schema.AnnotatedValue, unwrap(raw_result)), - ) - for raw_result in response - ] - except RuntimeError as e: - msg = f"Error while setting {path}." - raise errors.LabOneCoreError(msg) from e - - @t.overload - def set(self, value: AnnotatedValue) -> t.Awaitable[AnnotatedValue]: ... - - @t.overload - def set( - self, - value: Value, - path: LabOneNodePath, - ) -> t.Awaitable[AnnotatedValue]: ... - - @translate_comms_error - def set( - self, - value: AnnotatedValue | Value, - path: LabOneNodePath | None = None, - ) -> t.Awaitable[AnnotatedValue]: - """Set the value of a node. - - ```python - await session.set(AnnotatedValue(path="/zi/debug/level", value=2) - ``` - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - value: Value to be set. - path: LabOne node path. The path can be relative or absolute. - - Returns: - Acknowledged value from the device. - - Raises: - TypeError: If the node path is of wrong type. - NotFoundError: If the node path does not exist. - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not settable. - UnimplementedError: If the set request is not - supported by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - if isinstance(value, AnnotatedValue): - future = self._session.setValue( - pathExpression=value.path, - value=value_from_python_types( - value.value, - capability_version=self._capability_version, - ), - lookupMode="directLookup", - client=self._client_id.bytes, - ) - return self._single_value_postprocessing(future, value.path) # type: ignore[arg-type] - future = self._session.setValue( - pathExpression=path, - value=value_from_python_types( - value, - capability_version=self._capability_version, - ), - lookupMode="directLookup", - client=self._client_id.bytes, - ) - return self._single_value_postprocessing(future, path) # type: ignore[arg-type] - - @t.overload - def set_with_expression( - self, - value: AnnotatedValue, - ) -> t.Awaitable[list[AnnotatedValue]]: ... - - @t.overload - def set_with_expression( - self, - value: Value, - path: LabOneNodePath, - ) -> t.Awaitable[list[AnnotatedValue]]: ... - - @translate_comms_error - def set_with_expression( - self, - value: AnnotatedValue | Value, - path: LabOneNodePath | None = None, - ) -> t.Awaitable[list[AnnotatedValue]]: - """Set the value of all nodes matching the path expression. - - A path expression is a labone node path. The difference to a normal - node path is that it can contain wildcards and must not be a leaf node. - In short it is a path that can match multiple nodes. For more information - on path expressions see the `list_nodes()` function. - - If an error occurs while fetching the values no value is returned but - the first first exception instead. - - ```python - ack_values = await session.set_with_expression( - AnnotatedValue(path="/zi/*/level", value=2) - ) - print(ack_values[0]) - ``` - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - value: Value to be set. - path: LabOne node path. - - Returns: - Acknowledged value from the device. - - Raises: - TypeError: If the node path is of wrong type. - NotFoundError: If the node path does not exist. - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not settable. - UnimplementedError: If the set with expression request is not - supported by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - if isinstance(value, AnnotatedValue): - future = self._session.setValue( - pathExpression=value.path, - value=value_from_python_types( - value.value, - capability_version=self._capability_version, - ), - lookupMode="withExpansion", - client=self._client_id.bytes, - ) - return self._multi_value_postprocessing(future, value.path) # type: ignore[arg-type] - future = self._session.setValue( - pathExpression=path, - value=value_from_python_types( - value, - capability_version=self._capability_version, - ), - lookupMode="withExpansion", - client=self._client_id.bytes, - ) - return self._multi_value_postprocessing(future, path) # type: ignore[arg-type] - - @translate_comms_error - def get( - self, - path: LabOneNodePath, - ) -> t.Awaitable[AnnotatedValue]: - """Get the value of a node. - - The node can either be passed as an absolute path, starting with a leading - slash and the device id (e.g. "/dev123/demods/0/enable") or as relative - path (e.g. "demods/0/enable"). In the latter case the device id is - automatically added to the path by the server. Note that the - orchestrator/ZI kernel always requires absolute paths (/zi/about/version). - - ```python - await session.get('/zi/devices/visible') - ``` - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - path: LabOne node path (relative or absolute). - - Returns: - Annotated value of the node. - - Raises: - TypeError: If `path` is not a string. - NotFoundError: If the path does not exist. - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not readable. - UnimplementedError: If the get request is not supported - by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - future = self._session.getValue( - pathExpression=path, - lookupMode="directLookup", - client=self._client_id.bytes, - ) - return self._single_value_postprocessing(future, path) # type: ignore[arg-type] - - @translate_comms_error - def get_with_expression( - self, - path_expression: LabOneNodePath, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE - | ListNodesFlags.RECURSIVE - | ListNodesFlags.LEAVES_ONLY - | ListNodesFlags.EXCLUDE_STREAMING - | ListNodesFlags.GET_ONLY, - ) -> t.Awaitable[list[AnnotatedValue]]: - """Get the value of all nodes matching the path expression. - - A path expression is a labone node path. The difference to a normal - node path is that it can contain wildcards and must not be a leaf node. - In short it is a path that can match multiple nodes. For more information - on path expressions see the `list_nodes()` function. - - If an error occurs while fetching the values no value is returned but - the first first exception instead. - - ```python - values = await session.get_with_expression("/zi/*/level") - print(values[0]) - ``` - - Note: - The function is not async but returns an awaitable object. - This makes this function eagerly evaluated instead of the python - default lazy evaluation. This means that the request is sent to the - server immediately even if the result is not awaited. - - Args: - path_expression: LabOne path expression. - flags: The flags used by the server (list_nodes()) to filter the - nodes. - - Returns: - Annotated values from the nodes matching the path expression. - - Raises: - TypeError: If the node path is of wrong type. - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not readable. - UnimplementedError: If the get with expression request is not - supported by the server. - InternalError: If an unexpected internal error occurs - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - future = self._session.getValue( - pathExpression=path_expression, - lookupMode="withExpansion", - flags=int(flags), - client=self._client_id.bytes, - ) - return self._multi_value_postprocessing(future, path_expression) # type: ignore[arg-type] - - @t.overload - async def subscribe( - self, - path: LabOneNodePath, - *, - queue_type: None = None, - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - get_initial_value: bool = False, - **kwargs, - ) -> DataQueue: ... - - @t.overload - async def subscribe( - self, - path: LabOneNodePath, - *, - queue_type: type[QueueProtocol], - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol: ... - - @async_translate_comms_error - async def subscribe( - self, - path: LabOneNodePath, - *, - queue_type: type[QueueProtocol] | None = None, - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol | DataQueue: - """Register a new subscription to a node. - - Registers a new subscription to a node on the kernel/server. All - updates to the node will be pushed to the returned data queue. - - Note: - An update is triggered by the device itself and does not - exclusively mean a change in the value of the node. For example - a set request from any client will also trigger an update event. - - It is safe to have multiple subscriptions to the same path. However - in most cases it is more efficient to fork (DataQueue.fork) an - existing DataQueue rather then registering a new subscription at the - server. This is because the kernel/server will send the update events - to every registered subscription independently, causing additional - network overhead. - - ```python - data_sink = await session.subscribe("/zi/devices/visible") - newly_detected_device = await data_sink.get() - ``` - - Args: - path: String representing the path of the node to be streamed. - Currently does not support wildcards in the path. - parser_callback: Function to bring values obtained from - data-queue into desired format. This may involve parsing - them or putting them into an enum. - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - get_initial_value: If True, the initial value of the node is - is placed in the queue. (default=False) - kwargs: extra keyword arguments which are passed to the data-server - to further configure the subscription. - - Returns: - An instance of the DataQueue class. This async queue will receive - all update events for the subscribed node. - - Raises: - TypeError: If `path` is not a string - NotFoundError: If the path does not exist. - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path can not be subscribed. - UnimplementedError: If the subscribe request is not supported - by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - streaming_handle = StreamingHandle(parser_callback=parser_callback) - - subscription = { - "path": path, - "streamingHandle": self._context.register_callback( - streaming_handle.capnp_callback, - ), - "subscriberId": self._client_id.bytes, - "kwargs": { - "entries": [ - { - "key": k, - "value": value_from_python_types( - v, - capability_version=self._capability_version, - ), - } - for k, v in kwargs.items() - ], - }, - } - if get_initial_value: - new_queue_type = queue_type or DataQueue - queue = new_queue_type( - path=path, - handle=streaming_handle, - ) - _, initial_value = await asyncio.gather( - self._session.subscribe(subscription=subscription), - self.get(path), - ) - # If the queue already has received a update event we do not - # need to put the initial value in the queue. As it may break the - # order. - if queue.empty(): - queue.put_nowait(initial_value) - return queue - new_queue_type = queue_type or DataQueue - queue = new_queue_type( - path=path, - handle=streaming_handle, - ) - await self._session.subscribe(subscription=subscription) - return queue - - async def wait_for_state_change( - self, - path: LabOneNodePath, - value: int, - *, - invert: bool = False, - ) -> None: - """Waits until the node has the expected state/value. - - Warning: - Only supports integer and keyword nodes. (The value can either be the value - or its corresponding enum value as string) - - Args: - path: LabOne node path. - value: Expected value of the node. - invert: Instead of waiting for the value, the function will wait for - any value except the passed value. (default = False) - Useful when waiting for value to change from existing one. - """ - # order important so that no update can happen unseen by the queue after - # reading the current state - queue = await self.subscribe(path, get_initial_value=True) - - # block until value is correct - node_value = await queue.get() - while (value != node_value.value) ^ invert: # pragma: no cover - node_value = await queue.get() - - async def _supports_transaction(self) -> bool: - """Check if the session supports transactions. - - Transactions are only supported by MF and UHF devices. Transactions mitigate - the problem of the data server being blocking. Newer devices use a different - data server implementation which is non-blocking. Therefore transactions are - not required for these devices. - - Returns: - True if the session supports transactions, False otherwise. - """ - if self._has_transaction_support is None: - self._has_transaction_support = ( - len(await self.get_with_expression(_TRANSACTION_NODE_PATH)) == 1 - ) - return self._has_transaction_support - - @asynccontextmanager - async def set_transaction(self) -> t.AsyncGenerator[list[t.Awaitable], None]: - """Context manager for a transactional set. - - Once the context manager is entered, every set request that is added to the - `requests` list is executed in a single transaction. The transaction is - committed when the context manager is exited. Note that the transaction is - handled by the data server and there is no special handling required by - the client. The client can use the `set` function as usual. To ensure the - right order of execution every promise that contains a set request must be - added to the `requests` list. This ensures the requests are part of the - transaction and are executed in the right order. - - By design a transaction does not return any values. This also means that - there is no error handling for the requests. If a request fails, the other - requests are still executed and no error is raised. - - Important: - This function is only helpful for UHF and MF devices, since the - underlying data server only support blocking calls. Although it can be - used for all device types there is no benefit in this case, since it will - be equivalent to a simple asyncio.gather. - - UHF and MF devices are not natively supported by the capnp interface of the - LabOne data server. Therefore a wrapper is used to emulate the capnp interface. - This wrapper uses the old LabOne Client API to communicate with the device. - The old Client is not asynchronous and therefore the communication is blocking. - When setting multiple nodes in a row, the blocking communication can lead to - a significant performance drop. To avoid this, the transactional set is used. - It allows to set multiple nodes in a single request. - - ```python - async with session.set_transaction() as requests: - requests.append(session.set(value1)) - requests.append(session.set(value2)) - requests.append(custom_async_function_that_sets_nodes(...)) - ``` - - Yields: - List to which the set requests must be appended. - """ - requests: list[t.Awaitable] = [] - if await self._supports_transaction(): - begin_request = self.set( - AnnotatedValue(path=_TRANSACTION_NODE_PATH, value=1), - ) - yield requests - requests = [begin_request, *requests] - requests.append( - self.set(AnnotatedValue(path=_TRANSACTION_NODE_PATH, value=0)), - ) - else: - yield requests - await asyncio.gather(*requests) - - @property - def context(self) -> ZIContext: - """Get the context instance.""" - return self._context # pragma: no cover - - @property - def raw_session(self) -> zhinst.comms.DynamicClient: - """Get the underlying session.""" - return self._session - - @property - def client_id(self) -> uuid.UUID: - """Get the underlying session.""" - return self._client_id diff --git a/src/labone/core/shf_vector_data.py b/src/labone/core/shf_vector_data.py deleted file mode 100644 index af1bfb7..0000000 --- a/src/labone/core/shf_vector_data.py +++ /dev/null @@ -1,416 +0,0 @@ -"""Module for parsing shf vector data. - -This module duplicates the functionality of the C++ client. It is planed to -move this logic into the kernel/server. This module should be removed once -the kernel/server is updated. - -The reason why shf vector types need special handling is because the device -send for simplicity a single byte array for both the data and the extra header. -The extra header is a struct with a variable length. The length of the extra -header is encoded in the 16 least significant bits of the extraHeaderInfo -field of the vector data. The extra header is then followed by the data. -""" - -from __future__ import annotations - -import logging -import struct -from dataclasses import dataclass - -import numpy as np - -from labone.core import hpk_schema -from labone.core.errors import LabOneCoreError, SHFHeaderVersionNotSupportedError -from labone.core.helper import VectorElementType, VectorValueType - -logger = logging.getLogger(__name__) - -_SHF_WAVEFORM_UNSIGNED_ENCODING_BITS = 18 -_SHF_WAVEFORM_SIGNED_ENCODING_BITS = _SHF_WAVEFORM_UNSIGNED_ENCODING_BITS - 1 -_SHF_WAVEFORM_SCALING = (1 << _SHF_WAVEFORM_SIGNED_ENCODING_BITS) - 1 -_SUPPORTED_SHF_VECTOR_DATA_TYPES = [ - VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA, - VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA, - VectorValueType.SHF_SCOPE_VECTOR_DATA, -] - - -@dataclass(frozen=True) -class _HeaderVersion: - """Class for the version of the extra header.""" - - major: int - minor: int - - def as_tuple(self) -> tuple[int, int]: - """Return the version as tuple.""" - return self.major, self.minor - - -@dataclass -class ShfResultLoggerVectorData: - """SHF result logger sample data.""" - - vector: np.ndarray - properties: hpk_schema.ShfResultLoggerVectorDataProperties - - -def shf_result_logger_properties_from_binary( - binary: bytes, - *, - version: _HeaderVersion, -) -> hpk_schema.ShfResultLoggerVectorDataProperties: - """Parse the extra header of result logger vectors. - - Args: - binary: The binary string representing the extra header. - version: The version of the extra header. - - Returns: - The parsed extra header. - - Raises: - SHFHeaderVersionNotSupportedError: If the version is not supported. - """ - if (version.major == 0) and (version.minor >= 1): - result = hpk_schema.ShfResultLoggerVectorData() - prop = result.init_properties() - prop.timestamp = struct.unpack("q", binary[0:8])[0] - prop.jobId = struct.unpack("I", binary[8:12])[0] - prop.repetitionId = struct.unpack("I", binary[12:16])[0] - prop.scaling = struct.unpack("d", binary[16:24])[0] - prop.centerFrequency = struct.unpack("d", binary[24:32])[0] - prop.dataSource = struct.unpack("I", binary[32:36])[0] - prop.numSamples = struct.unpack("I", binary[36:40])[0] - prop.numSpectrSamples = struct.unpack("I", binary[40:44])[0] - prop.numAverages = struct.unpack("I", binary[44:48])[0] - prop.numAcquired = struct.unpack("I", binary[48:52])[0] - prop.holdoffErrorsReslog = struct.unpack("H", binary[52:54])[0] - prop.holdoffErrorsReadout = struct.unpack("H", binary[54:56])[0] - prop.holdoffErrorsSpectr = struct.unpack("H", binary[56:58])[0] - prop.firstSampleTimestamp = struct.unpack("Q", binary[58:66])[0] - return result.properties - raise SHFHeaderVersionNotSupportedError(version=version.as_tuple()) - - -@dataclass -class ShfScopeVectorData: - """SHF scope sample data.""" - - vector: np.ndarray - properties: hpk_schema.ShfScopeVectorDataProperties - - -def shf_scope_properties_from_binary( - binary: bytes, - *, - version: _HeaderVersion, -) -> hpk_schema.ShfScopeVectorDataProperties: - """Parse the extra header of scope vectors. - - Args: - binary: The binary string representing the extra header. - version: The version of the extra header. - - Returns: - The parsed extra header. - - Raises: - SHFHeaderVersionNotSupportedError: If the version is not supported. - """ - if (version.major == 0) and (version.minor >= 2): # noqa: PLR2004 - result = hpk_schema.ShfScopeVectorData() - prop = result.init_properties() - prop.timestamp = struct.unpack("q", binary[0:8])[0] - prop.timestampDiff = struct.unpack("I", binary[8:12])[0] - prop.flags = struct.unpack("I", binary[12:16])[0] - prop.scaling = struct.unpack("d", binary[16:24])[0] - prop.centerFrequency = struct.unpack("d", binary[24:32])[0] - prop.triggerTimestamp = struct.unpack("q", binary[32:40])[0] - prop.inputSelect = struct.unpack("I", binary[40:44])[0] - prop.averageCount = struct.unpack("I", binary[44:48])[0] - prop.numSegments = struct.unpack("I", binary[48:52])[0] - prop.numTotalSegments = struct.unpack("I", binary[52:56])[0] - prop.firstSegmentIndex = struct.unpack("I", binary[56:60])[0] - prop.numMissedTriggers = struct.unpack("I", binary[60:64])[0] - return result.properties - if (version.major == 0) and (version.minor >= 1): - result = hpk_schema.ShfScopeVectorData() - prop = result.init_properties() - prop.timestamp = struct.unpack("q", binary[0:8])[0] - prop.timestampDiff = struct.unpack("I", binary[8:12])[0] - prop.flags = struct.unpack("I", binary[12:16])[0] - prop.scaling = struct.unpack("d", binary[16:24])[0] - prop.centerFrequency = struct.unpack("d", binary[24:32])[0] - prop.triggerTimestamp = struct.unpack("q", binary[32:40])[0] - prop.inputSelect = struct.unpack("I", binary[40:44])[0] - prop.averageCount = struct.unpack("I", binary[44:48])[0] - return result.properties - raise SHFHeaderVersionNotSupportedError(version=version.as_tuple()) - - -@dataclass -class ShfDemodulatorVectorData: - """SHF demodulator sample data.""" - - x: np.ndarray - y: np.ndarray - properties: hpk_schema.ShfDemodulatorVectorDataProperties - - -def _parse_extra_header_version(extra_header_info: int) -> _HeaderVersion: - """Extract the header version from the extra header info. - - Args: - extra_header_info: The extra header info. - - Returns: - The header version. - """ - if extra_header_info == 0: - msg = "Vector data does not contain extra header." - raise ValueError(msg) - version = extra_header_info >> 16 - return _HeaderVersion(major=(version & 0xE0) >> 5, minor=version & 0x1F) - - -def _deserialize_shf_result_logger_vector( - *, - raw_data: bytes, - extra_header_info: int, - header_length: int, - element_type: VectorElementType, -) -> ShfResultLoggerVectorData: - """Deserialize the vector data for result logger vector. - - Args: - raw_data: The binary data representing the vector. - extra_header_info: The extra header info for the vector. - header_length: The length of the extra header of the vector. - element_type: Type of the elements in the vector. - - Returns: - The deserialized vector and the extra header - - Raises: - SHFHeaderVersionNotSupportedError: If the version is not supported. - LabOneCoreError: If the version cannot be parsed. - """ - # Parse header - raw_extra_header = raw_data[:header_length] - try: - version = _parse_extra_header_version(extra_header_info) - except ValueError as e: - if len(raw_data) == 0: - return ShfResultLoggerVectorData( - vector=np.array([], dtype=np.int32), - properties=hpk_schema.ShfResultLoggerVectorData().properties, - ) - msg = ( - "Unable to parse the version of the shf result vector." # pragma: no cover - ) - raise LabOneCoreError(msg) from e # pragma: no cover - extra_header = shf_result_logger_properties_from_binary( - raw_extra_header, - version=version, - ) - - # Parse raw data - data = np.frombuffer( - raw_data[header_length:], - dtype=element_type.to_numpy_type(), - ) - return ShfResultLoggerVectorData(vector=data, properties=extra_header) - - -def _deserialize_shf_scope_vector( - *, - raw_data: bytes, - extra_header_info: int, - header_length: int, -) -> ShfScopeVectorData: - """Deserialize the vector data for waveform vectors. - - Args: - raw_data: The binary data representing the vector. - extra_header_info: The extra header info for the vector. - header_length: The length of the extra header of the vector. - - Returns: - The deserialized vector and the extra header - - Raises: - SHFHeaderVersionNotSupportedError: If the version is not supported. - LabOneCoreError: If the version cannot be parsed. - """ - # Parse header - raw_extra_header = raw_data[:header_length] - try: - version = _parse_extra_header_version(extra_header_info) - except ValueError as e: - if len(raw_data) == 0: - return ShfScopeVectorData( - vector=np.array([], dtype=np.int32), - properties=hpk_schema.ShfScopeVectorData().properties, - ) - msg = "Unable to parse the version of the shf scope vector." # pragma: no cover - raise LabOneCoreError(msg) from e # pragma: no cover - - extra_header = shf_scope_properties_from_binary( - raw_extra_header, - version=version, - ) - - # Parse raw data - data = ( - np.frombuffer(raw_data[header_length:], dtype=np.int32) * extra_header.scaling - ) - data_real = data[::2] - data_imag = data[1::2] - return ShfScopeVectorData( - vector=data_real + 1j * data_imag, - properties=extra_header, - ) - - -@dataclass -class ShfGeneratorWaveformVectorData: - """SHF generator waveform sample data.""" - - complex: np.ndarray - - -def _deserialize_shf_waveform_vector( - raw_data: bytes, -) -> ShfGeneratorWaveformVectorData: - """Deserialize the vector data for waveform vectors. - - Args: - raw_data: The binary data representing the vector. - - Returns: - The deserialized vector and the extra header (None for waveforms). - """ - shf_wavforms_signed_encoding_bits = 17 - scaling = 1 / float((1 << shf_wavforms_signed_encoding_bits) - 1) - - data = np.frombuffer(raw_data, dtype=np.int32) * scaling - data_real = data[::2] - data_imag = data[1::2] - return ShfGeneratorWaveformVectorData(complex=data_real + 1j * data_imag) - - -@dataclass -class ShfPidVectorData: - """SHF pid sample data.""" - - value: np.ndarray - error: np.ndarray - properties: hpk_schema.ShfPidVectorDataProperties - - -def get_header_length(vector_data: hpk_schema.VectorData) -> int: - """Get the length of the extra header. - - The 16 least significant bits of extra_header_info contain the length of - the header, expressed in 32-bits words. Take the 16 lsb with & 0x0000FFFF, - then multiply by 4 with << 2 to express the length in bytes. - - Args: - vector_data: The vector data struct. - - Returns: - The length of the extra header in bytes. - """ - return (vector_data.extraHeaderInfo & 0x0000FFFF) << 2 - - -def supports_shf_vector_parsing_from_vector_data(value_type: int) -> bool: - """Check if the value type is supported for SHF vector parsing. - - Not all value types are supported for SHF vector parsing. Since the latest - version of LabOne supports parsing the SHF vectors on the server side, this - is a deprecated function. - - Args: - value_type: The value type to check. - - Returns: - True if the value type is supported, False otherwise. - """ - return value_type in _SUPPORTED_SHF_VECTOR_DATA_TYPES - - -def parse_shf_vector_from_vector_data( - vector_data: hpk_schema.VectorData, -) -> ShfScopeVectorData | ShfResultLoggerVectorData | ShfGeneratorWaveformVectorData: - """Parse the SHF vector data struct. - - An SHF vector consists of an extra header and a the data vector. - Since the latest version of LabOne supports parsing the SHF vectors on the - server side, this is a deprecated function. - - Args: - vector_data: The vector data struct. - - Returns: - The deserialized vector and the extra header - - Raises: - ValueError: If the vector value type is not supported. - SHFHeaderVersionNotSupportedError: If the version is not supported. - LabOneCoreError: If the version cannot be parsed. - """ - raw_data = vector_data.data - extra_header_info: int = vector_data.extraHeaderInfo - header_length = get_header_length(vector_data) - - value_type = vector_data.valueType - if value_type == VectorValueType.SHF_SCOPE_VECTOR_DATA: - return _deserialize_shf_scope_vector( - raw_data=raw_data, - extra_header_info=extra_header_info, - header_length=header_length, - ) - if value_type == VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA: - return _deserialize_shf_result_logger_vector( - raw_data=raw_data, - extra_header_info=extra_header_info, - header_length=header_length, - element_type=VectorElementType(vector_data.vectorElementType), - ) - if value_type == VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA: - return _deserialize_shf_waveform_vector(raw_data) - msg = f"Unsupported vector value type: {value_type}" - raise ValueError(msg) - - -def preprocess_complex_shf_waveform_vector( - data: np.ndarray, -) -> dict: - """Preprocess complex waveform vector data. - - Complex waveform vectors are transmitted as two uint32 interleaved vectors. - This function converts the complex waveform vector data into the - corresponding uint32 vector. - - Args: - data: The complex waveform vector data. - - Returns: - The uint32 vector data. - """ - real_scaled = np.round(np.real(data) * _SHF_WAVEFORM_SCALING).astype(np.int32) - imag_scaled = np.round(np.imag(data) * _SHF_WAVEFORM_SCALING).astype(np.int32) - decoded_data = np.empty((2 * data.size,), dtype=np.int32) - decoded_data[::2] = real_scaled - decoded_data[1::2] = imag_scaled - - return { - "vectorData": { - "valueType": VectorValueType.VECTOR_DATA.value, - "extraHeaderInfo": 0, - "vectorElementType": VectorElementType.UINT32.value, - "data": decoded_data.tobytes(), - }, - } diff --git a/src/labone/core/subscription.py b/src/labone/core/subscription.py deleted file mode 100644 index 484d4f3..0000000 --- a/src/labone/core/subscription.py +++ /dev/null @@ -1,538 +0,0 @@ -"""This module contains the logic for the subscription mechanism. - -Subscriptions are implemented through the capnp stream mechanism. It handles -all the communication stuff, e.g. back pressure and flow control. The only thing -the client needs to provide is a `SessionClient.StreamingHandle.Server` -implementation. The `sendValues()` method of this class will be called by the -kernel through RPC whenever an update event for the subscribed node is -received. To make this as simple as possible the user only interacts with -a DataQueue object. A async queue with the addition of a connection guard. - -It is possible to create a fork of a data queue. A fork will receive the -values from the same underlying subscription as the original data queue. -However, the connection state of the fork is independent of the original. - -It is always recommended to disconnect the data queue when it is not needed -anymore. This will free up resources on the server side and prevent the server -from sending unnecessary data. -""" - -from __future__ import annotations - -import asyncio -import logging -import typing as t -import weakref - -import zhinst.comms - -from labone.core import errors, hpk_schema -from labone.core.value import AnnotatedValue - -if t.TYPE_CHECKING: - from labone.core.helper import LabOneNodePath - -logger = logging.getLogger(__name__) - - -class _ConnectionState: - """Connection state guard. - - Helper class that represents the connection state. The sole purpose of this - class is to have an expressive way of showing that a disconnect on a data - queue is final and can not be reverted. - """ - - def __init__(self) -> None: - self.__connected = True - - @property - def connected(self) -> bool: - """Connection state.""" - return self.__connected - - def disconnect(self) -> None: - """Disconnect the data queue. - - This operation is final and can not be reverted. - """ - self.__connected = False - - def __bool__(self) -> bool: - """All.""" - return self.connected - - -class DataQueue(asyncio.Queue): - """Queue for a single node subscription. - - The Queue holds all values updates received for the subscribed node. This - interface is identical to the asyncio.Queue interface, with a additional - connection guard. If the data queue is disconnected, the subscription will - eventually be canceled on the kernel side. In any case a disconnected data - queue will not receive any new values. - - Warning: - The disconnect will only be recognized by the kernel/server during the - next update event. Until that the server will not be aware of the - disconnect. (e.g. asking the kernel which nodes are subscribed might not - reflect the reality). - - Args: - path: Path of the subscribed node. - """ - - def __init__( - self, - *, - path: LabOneNodePath, - handle: StreamingHandle, - ) -> None: - super().__init__() - self._path = path - self._connection_state = _ConnectionState() - self._handle = handle - - handle.register_data_queue(weakref.ref(self)) - - def __repr__(self) -> str: - return str( - f"{self.__class__.__name__}(path={self._path!r}, " - f"maxsize={self.maxsize}, qsize={self.qsize()}, " - f"connected={self.connected})", - ) - - @t.overload - def fork(self, queue_type: None) -> DataQueue: ... - - @t.overload - def fork( - self, - queue_type: type[QueueProtocol], - ) -> QueueProtocol: ... - - def fork( - self, - queue_type: type[QueueProtocol] | None = None, - ) -> DataQueue | QueueProtocol: - """Create a fork of the subscription. - - The forked subscription will receive all updates that the original - subscription receives. Its connection state is independent of the original - subscription, meaning even if the original subscription is disconnected, - the forked subscription will still receive updates. - - Warning: - The forked subscription will not contain any values before the fork. - - Args: - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - - Returns: - A new data queue to the same underlying subscription. - """ - if not self._connection_state: - msg = str( - "The data queue has been disconnected. A fork does not make " - "sense as it would never receive data.", - ) - raise errors.StreamingError(msg) - new_queue_type = queue_type or DataQueue - return new_queue_type( - path=self._path, - handle=self._handle, - ) - - def disconnect(self) -> None: - """Disconnect the data queue. - - This operation is final and can not be reverted. A disconnected queue - will not receive any new values. - - Important: - It is always recommended to disconnect the data queue when it is not - needed anymore. This will free up resources on the server side and - prevent the server from sending unnecessary data. - """ - self._connection_state.disconnect() - - def put_nowait(self, item: AnnotatedValue) -> None: - """Put an item into the queue without blocking. - - Args: - item: The item to the put in the queue. - - Raises: - StreamingError: If the data queue has been disconnected. - """ - if not self._connection_state: - msg = "The data queue has been disconnected." - raise errors.StreamingError(msg) - return super().put_nowait(item) - - async def get(self) -> AnnotatedValue: - """Remove and return an item from the queue. - - Returns: - The first item in the queue. If the queue is empty, wait until an - item is available. - - Raises: - EmptyDisconnectedDataQueueError: If the data queue if empty AND - disconnected. - """ - if self.empty() and not self._connection_state: - msg = str( - "The data queue is empty and it has been disconnected, " - "therefore it will not receive data anymore.", - ) - raise errors.EmptyDisconnectedDataQueueError( - msg, - ) - return await super().get() - - @property - def connected(self) -> bool: - """Connection state.""" - return bool(self._connection_state) - - @property - def path(self) -> LabOneNodePath: - """Path of the subscribed node.""" - return self._path - - @property - def maxsize(self) -> int: - """Number of items allowed in the queue.""" - return self._maxsize - - @maxsize.setter - def maxsize(self, maxsize: int) -> None: - """Number of items allowed in the queue.""" - if not self._connection_state: - msg = str( - "Has been disconnected, therefore it will not receive data anymore." - "Changing the maxsize will not have any effect.", - ) - raise errors.StreamingError(msg) - if self.qsize() > maxsize: - msg = str( - "The new maxsize is smaller than the current qsize. " - "This results in data loss and is forbidden.", - ) - raise errors.StreamingError(msg) - self._maxsize = maxsize - - -QueueProtocol = t.TypeVar("QueueProtocol", bound=DataQueue) - - -class CircularDataQueue(DataQueue): - """Circular data queue. - - This data queue is identical to the DataQueue, with the exception that it - will remove the oldest item from the queue if the queue is full and a new - item is added. - """ - - async def put(self, item: AnnotatedValue) -> None: - """Put an item into the queue. - - If the queue is full the oldest item will be removed and the new item - will be added to the end of the queue. - - Args: - item: The item to the put in the queue. - - Raises: - StreamingError: If the data queue has been disconnected. - """ - if self.full(): - self.get_nowait() - await super().put(item) - - def put_nowait(self, item: AnnotatedValue) -> None: - """Put an item into the queue without blocking. - - If the queue is full the oldest item will be removed and the new item - will be added to the end of the queue. - - Args: - item: The item to the put in the queue. - - Raises: - StreamingError: If the data queue has been disconnected. - """ - if self.full(): - self.get_nowait() - super().put_nowait(item) - - @t.overload - def fork(self, queue_type: None) -> CircularDataQueue: ... - - @t.overload - def fork( - self, - queue_type: type[QueueProtocol], - ) -> QueueProtocol: ... - - def fork( - self, - queue_type: type[QueueProtocol] | None = None, - ) -> CircularDataQueue | QueueProtocol: - """Create a fork of the subscription. - - The forked subscription will receive all updates that the original - subscription receives. Its connection state is independent of the original - subscription, meaning even if the original subscription is disconnected, - the forked subscription will still receive updates. - - Warning: - The forked subscription will not contain any values before the fork. - - Args: - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - - Returns: - A new data queue to the same underlying subscription. - """ - return DataQueue.fork( # type: ignore[return-value] - self, - queue_type=queue_type if queue_type is not None else CircularDataQueue, - ) - - -class DistinctConsecutiveDataQueue(DataQueue): - """Data queue that only accepts values which have changed. - - This data queue is identical to the DataQueue, with the exception that it - will accept new values that have a different value than the last value. - """ - - def __init__(self, *, path: LabOneNodePath, handle: StreamingHandle) -> None: - DataQueue.__init__( - self, - path=path, - handle=handle, - ) - self._last_value = AnnotatedValue(value=None, path="unkown") - - def put_nowait(self, item: AnnotatedValue) -> None: - """Put an item into the queue without blocking. - - If the queue is full the oldest item will be removed and the new item - will be added to the end of the queue. - - Args: - item: The item to the put in the queue. - - Raises: - StreamingError: If the data queue has been disconnected. - """ - if item.value != self._last_value.value: - DataQueue.put_nowait(self, item) - self._last_value = item - - @t.overload - def fork(self, queue_type: None) -> CircularDataQueue: ... - - @t.overload - def fork( - self, - queue_type: type[QueueProtocol], - ) -> QueueProtocol: ... - - def fork( - self, - queue_type: type[QueueProtocol] | None = None, - ) -> CircularDataQueue | QueueProtocol: - """Create a fork of the subscription. - - The forked subscription will receive all updates that the original - subscription receives. Its connection state is independent of the original - subscription, meaning even if the original subscription is disconnected, - the forked subscription will still receive updates. - - Warning: - The forked subscription will not contain any values before the fork. - - Args: - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - - Returns: - A new data queue to the same underlying subscription. - """ - return DataQueue.fork( # type: ignore[return-value] - self, - queue_type=( - queue_type if queue_type is not None else DistinctConsecutiveDataQueue - ), - ) - - -class StreamingHandle: - """Streaming Handle server implementation. - - Args: - data_queue: Weak reference to the data queue to which the values - will be added. - """ - - def __init__( - self, - *, - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - ) -> None: - self._data_queues = [] # type: ignore[var-annotated] - - if parser_callback is None: - - def parser_callback(x: AnnotatedValue) -> AnnotatedValue: - return x - - self._parser_callback = parser_callback - - def register_data_queue( - self, - data_queue: weakref.ReferenceType[QueueProtocol], - ) -> None: - """Register a new data queue. - - Args: - data_queue: Weak reference to the data queue to which the values - will be added. - """ - self._data_queues.append(data_queue) - - def _add_to_data_queue( - self, - data_queue: QueueProtocol | None, - value: AnnotatedValue, - ) -> bool: - """Add a value to the data queue. - - The value is added to the queue non blocking, meaning that if the queue - is full, an error is raised. - - Args: - data_queue: The data queue to which the value will be added. - value: The value to add to the data queue. - - Returns: - True if the value was added to the data queue, False otherwise. - - Raises: - StreamingError: If the data queue is full or disconnected. - AttributeError: If the data queue has been garbage collected. - """ - if data_queue is None: - # The server holds only a weak reference to the data queue. - # If the data queue has been garbage collected, the weak reference - # will be None. - return False - try: - data_queue.put_nowait(value) - except errors.StreamingError: - logger.debug( - "Data queue %s has disconnected. Removing from list of queues.", - hex(id(data_queue)), - ) - return False - except asyncio.QueueFull: - logger.warning( - "Data queue %s is full. No more data will be pushed to the queue.", - hex(id(data_queue)), - ) - data_queue.disconnect() # type: ignore[union-attr] # supposed to throw - return False - else: - return True - - def distribute_to_data_queues(self, value: AnnotatedValue) -> None: - """Add a value to all data queues. - - Distribute to all data queues and remove the ones that are not - connected anymore. - - Args: - value: The value to add to the data queue. - """ - self._data_queues = [ - data_queue - for data_queue in self._data_queues - if self._add_to_data_queue(data_queue(), value) - ] - - def _distribute_to_data_queues( - self, - value: hpk_schema.AnnotatedValue, - ) -> None: - """Add a value to all data queues. - - The value is added to the queue non blocking, meaning that if the queue - is full, an error is raised. - - Args: - value: The value to add to the data queue. - - Raises: - ValueError: If the value could not be parsed. - """ - try: - parsed_value = self._parser_callback(AnnotatedValue.from_capnp(value)) - except errors.LabOneCoreError as err: # pragma: no cover - # A streaming Error was received. - # Followup Commit: This needs to be distributed to all data queues. But the - # error should not be raised here since this would disconnect the - # subscription. - logger.exception(err.args[0]) - return - except ValueError as err: # pragma: no cover - self._data_queues = [ - data_queue().disconnect() # type: ignore[union-attr] # supposed to throw - for data_queue in self._data_queues - if data_queue() is not None - ] - logger.error( # noqa: TRY400 - "Disconnecting subscription. Could not parse value. Error: %s", - err.args[0], - ) - raise - self.distribute_to_data_queues(parsed_value) - - async def capnp_callback( - self, - interface: int, # noqa: ARG002 - method_index: int, # noqa: ARG002 - call_input: hpk_schema.StreamingHandleSendValuesParams, - fulfiller: zhinst.comms.Fulfiller, - ) -> None: - """Capnp Interface callback. - - This function is called by the kernel (through RPC) when an update - event for the subscribed node is received. - - Args: - interface: The interface of the capnp schema. - method_index: The method index of the capnp schema. - call_input: The input data of the capnp schema. - fulfiller: The fulfiller to fulfill the promise. - """ - try: - list(map(self._distribute_to_data_queues, call_input.values)) - if len(self._data_queues) == 0: - msg = "No queues registered anymore" - fulfiller.reject(zhinst.comms.Fulfiller.DISCONNECTED, msg) - return - fulfiller.fulfill() - except Exception as err: # noqa: BLE001 - fulfiller.reject(zhinst.comms.Fulfiller.FAILED, err.args[0]) diff --git a/src/labone/core/value.py b/src/labone/core/value.py deleted file mode 100644 index 53145f2..0000000 --- a/src/labone/core/value.py +++ /dev/null @@ -1,320 +0,0 @@ -"""Module to handle the creation and conversion of values between capnp and python. - -The relevant class is the dataclass `AnnotatedValue`. It is used as the main -data container for all values send to and received by the kernel/server. -It has both a function to convert a capnp message to a python object and vice -versa. -""" - -from __future__ import annotations - -import logging -import typing as t -from dataclasses import dataclass - -import numpy as np -from packaging import version -from typing_extensions import TypeAlias - -from labone.core import hpk_schema -from labone.core.errors import LabOneCoreError, raise_streaming_error -from labone.core.helper import ( - LabOneNodePath, - VectorElementType, - VectorValueType, -) -from labone.core.shf_vector_data import ( - ShfDemodulatorVectorData, - ShfGeneratorWaveformVectorData, - ShfPidVectorData, - ShfResultLoggerVectorData, - ShfScopeVectorData, - parse_shf_vector_from_vector_data, - preprocess_complex_shf_waveform_vector, - supports_shf_vector_parsing_from_vector_data, -) - -logger = logging.getLogger(__name__) - - -CapnpInput: TypeAlias = dict[str, t.Any] - - -@dataclass -class AnnotatedValue: - """Python representation of a node value. - - This class is used both for parsing received values from the server - and for packing values to be send to the server. - - Note that in order to send data to the server only the `value` and `path` - attributes are relevant. The other attributes are only used for parsing - received data and will be ignored by the kernel/server. - - Args: - value: Node Value. - path: Absolute node path. - timestamp: Timestamp at which the device sent the value. - (Only relevant for received values.) - """ - - value: Value - path: LabOneNodePath - timestamp: int | None = None - - def __repr__(self) -> str: - return ( - f"AnnotatedValue(value={self.value}, path={self.path}, " - f"timestamp={self.timestamp})" - ) - - @staticmethod - def from_capnp(raw: hpk_schema.AnnotatedValue) -> AnnotatedValue: - """Convert a capnp AnnotatedValue to a python AnnotatedValue. - - Args: - raw: The capnp AnnotatedValue to convert - - Returns: - The converted AnnotatedValue. - """ - try: - value = _capnp_value_to_python_value(raw.value) - except AttributeError: - value = None - try: - timestamp = raw.metadata.timestamp - except AttributeError: - timestamp = None - try: - path = raw.metadata.path - except AttributeError: - path = "" - return AnnotatedValue( - value=value, - timestamp=timestamp, - path=path, - ) - - -# All possible types of values that can be stored in a node. -Value = t.Union[ - bool, - int, - float, - str, - complex, - np.ndarray, - ShfDemodulatorVectorData, - ShfResultLoggerVectorData, - ShfScopeVectorData, - ShfPidVectorData, - ShfGeneratorWaveformVectorData, - hpk_schema.CntSample, - hpk_schema.TriggerSample, - list[hpk_schema.CntSample], - list[hpk_schema.TriggerSample], - dict, - None, - hpk_schema.VectorData, -] - - -def _parse_vector_data( - value: hpk_schema.VectorData, -) -> ( - ShfScopeVectorData - | ShfResultLoggerVectorData - | ShfGeneratorWaveformVectorData - | np.ndarray - | str -): - """Parse a capnp vector data message. - - Args: - value: The capnp vector data message to parse. - - Returns: - The parsed vector data. - """ - element_type = VectorElementType(value.vectorElementType) - if element_type == VectorElementType.STRING: - # Special case for strings which are send as byte arrays - return value.data.decode() - if supports_shf_vector_parsing_from_vector_data(value.valueType): - return parse_shf_vector_from_vector_data(value) - return np.frombuffer(value.data, dtype=element_type.to_numpy_type()) - - -_TO_PYTHON_PARSER = { - "none": lambda _: None, - "vectorData": _parse_vector_data, - "largeVectorData": lambda value: np.frombuffer( - value.dataSegments, - dtype=VectorElementType(value.vectorElementType).to_numpy_type(), - ), - "streamingError": lambda value: raise_streaming_error( - t.cast(hpk_schema.Error, value), - ), - "shfDemodData": lambda value: ShfDemodulatorVectorData( - x=np.array(value.x, copy=False), - y=np.array(value.y, copy=False), - properties=value.properties, - ), - "shfResultLoggerData": lambda value: ShfResultLoggerVectorData( - vector=( - np.array(value.vector.real, copy=False) - if hasattr(value.vector, "real") - else np.array(value.vector.complex, copy=False) - ), - properties=value.properties, - ), - "shfScopeData": lambda value: ShfScopeVectorData( - vector=( - np.array(value.vector.real, copy=False) - if hasattr(value.vector, "real") - else np.array(value.vector.complex, copy=False) - ), - properties=value.properties, - ), - "shfGeneratorWaveformData": lambda value: ShfGeneratorWaveformVectorData( - complex=np.array(value.complex, copy=False), - ), - "shfPidData": lambda value: ShfPidVectorData( - value=np.array(value.value, copy=False), - error=np.array(value.error, copy=False), - properties=value.properties, - ), -} - - -def _capnp_value_to_python_value( - capnp_value: hpk_schema.Value, -) -> Value: - """Convert a capnp value to a python value. - - Args: - capnp_value: The value to convert. - - Returns: - The converted value. - - Raises: - ValueError: If the capnp value can not be parsed. - LabOneCoreError: If the capnp value is a streaming error. - """ - union_element_name = capnp_value.get_union_element_name() - return _TO_PYTHON_PARSER.get(union_element_name, lambda x: x)( - getattr(capnp_value, union_element_name), - ) - - -def _numpy_vector_to_capnp_vector( - np_vector: np.ndarray, -) -> CapnpInput: - """Convert a numpy vector to a capnp vector. - - Args: - np_vector: The numpy vector to convert. - path: The path of the node the vector belongs to. - reflection: The reflection server used for the conversion. - - Returns: - The converted capnp vector. - - LabOneCoreError: If the numpy type has no corresponding - VectorElementType. - """ - request_value: dict[str, t.Any] = {} - request_value["extraHeaderInfo"] = 0 - request_value["valueType"] = VectorValueType.VECTOR_DATA.value - - np_data = np_vector - np_vector_type = np_vector.dtype - request_value["data"] = np_data.tobytes() - try: - request_value["vectorElementType"] = VectorElementType.from_numpy_type( - np_vector_type, - ).value - except ValueError as e: - msg = f"Unsupported numpy type: {np_vector_type}" - raise ValueError(msg) from e - return {"vectorData": request_value} - - -_FROM_PYTHON_PARSER = { - dict: lambda x, _: x, - bool: lambda x, compat_version: ( - {"bool": x} - if compat_version >= version.Version("1.13.0") - else {"int64": int(x)} - ), - int: lambda x, _: {"int64": x}, - float: lambda x, _: {"double": x}, - complex: lambda x, _: {"complex": x}, - str: lambda x, _: {"string": x}, - bytes: lambda x, _: { - "vectorData": { - "valueType": VectorValueType.BYTE_ARRAY.value, - "extraHeaderInfo": 0, - "vectorElementType": VectorElementType.UINT8.value, - "data": x, - }, - }, - np.ndarray: lambda x, _: _numpy_vector_to_capnp_vector(x), - ShfDemodulatorVectorData: lambda x, _: { - "shfDemodData": {"properties": x.properties, "x": x.x, "y": x.y}, - }, - ShfResultLoggerVectorData: lambda x, _: { - "shfResultLoggerData": { - "properties": x.properties, - "vector": {"real" if x.vector.dtype.kind == "f" else "complex": x.vector}, - }, - }, - ShfScopeVectorData: lambda x, _: { - "shfScopeData": { - "properties": x.properties, - "vector": {"real" if x.vector.dtype.kind == "f" else "complex": x.vector}, - }, - }, - ShfGeneratorWaveformVectorData: lambda x, compat_version: ( - {"shfGeneratorWaveformData": {"complex": x.complex}} - if compat_version >= version.Version("1.15.0") - else preprocess_complex_shf_waveform_vector(x.complex) - ), - ShfPidVectorData: lambda x, _: { - "shfPidData": { - "properties": x.properties, - "value": x.value, - "error": x.error, - }, - }, - hpk_schema.CntSample: lambda x, _: {"cntSample": x}, - hpk_schema.TriggerSample: lambda x, _: {"triggerSample": x}, - list[hpk_schema.CntSample]: lambda x, _: {"cntSample": x}, - list[hpk_schema.TriggerSample]: lambda x, _: {"triggerSample": x}, -} - - -def value_from_python_types( - value: Value, - *, - capability_version: version.Version, -) -> CapnpInput: - """Create `Value` builder from Python types. - - Args: - value: The value to be converted. - capability_version: The version of the capability to be used. - - Returns: - A new message builder for `capnp:Value`. - - Raises: - LabOneCoreError: If the data type of the value to be set is not supported. - """ - try: - return _FROM_PYTHON_PARSER[type(value)](value, capability_version) - except KeyError as e: - msg = f"Unsupported data type: {type(value)}" - raise LabOneCoreError(msg) from e diff --git a/src/labone/dataserver.py b/src/labone/dataserver.py deleted file mode 100644 index c06fc34..0000000 --- a/src/labone/dataserver.py +++ /dev/null @@ -1,245 +0,0 @@ -"""High-level functionality for connecting to devices and zi-nodes.""" - -from __future__ import annotations - -import json -import typing as t - -from labone.core import ( - AnnotatedValue, - KernelInfo, - KernelSession, - ServerInfo, - Session, - ZIContext, -) -from labone.errors import LabOneError -from labone.nodetree import construct_nodetree -from labone.nodetree.node import Node, PartialNode - -if t.TYPE_CHECKING: - from labone.core.errors import ( # noqa: F401 - BadRequestError, - InternalError, - LabOneCoreError, - UnavailableError, - ) - - -class DataServer(PartialNode): - """Connection to a LabOne Data Server. - - This class gives access to the LabOne Data Server configuration. It is not - tied to a specific device but exposes the nodes used to control the - DataServer. This is done through the so called zi-nodes. - - !!! note - - Due to the asynchronous interface, one needs to use the static method - `create` instead of the `__init__` method. - - ```python - from labone import DataServer - data_server = await DataServer.create("127.0.0.1") - ``` - - Args: - host: host address of the DataServer. - port: Port of the DataServer. - model_node: Example node that serves as a model for setting the inherited - node attributes. - """ - - def __init__( - self, - host: str, - port: int = 8004, - *, - model_node: Node, - ): - self._host = host - self._port = port - - super().__init__( - tree_manager=model_node.tree_manager, - path_segments=model_node.path_segments, - subtree_paths=model_node.subtree_paths, - ) - - @staticmethod - async def create_from_session( - *, - session: Session, - host: str = "localhost", - port: int = 8004, - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - hide_zi_prefix: bool = True, - ) -> DataServer: - """Create a new Session to a LabOne Data Server. - - Args: - session: Session to use for the connection. - host: host address of the DataServer (default = "localhost"). - port: Port of the DataServer (default = 8004). - hide_zi_prefix: Hides to common prefix `zi` from the node names. - E.g. `data_server.debug.info` can be used instead of - `data_server.zi.debug.info`. - custom_parser: A function that takes an annotated value and returns an - annotated value. This function is applied to all values coming from - the server. It is applied after the default enum parser, if - applicable. - - Returns: - The connected DataServer. - - Raises: - LabOneError: If an error appeared in the connection to the device. - """ - try: - model_node = await construct_nodetree( - session, - hide_kernel_prefix=hide_zi_prefix, - custom_parser=custom_parser, - ) - except LabOneError as e: - msg = f"While connecting to DataServer at {host}:{port} an error occurred." - raise LabOneError(msg) from e - - return DataServer(host, port, model_node=model_node) # type: ignore[arg-type] - # previous type ignore is due to the implicit assumption that a device root - # will always be a partial node - - @staticmethod - async def create( - host: str, - port: int = 8004, - *, - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - hide_zi_prefix: bool = True, - context: ZIContext | None = None, - timeout: int = 5000, - ) -> DataServer: - """Create a new Session to a LabOne Data Server. - - Args: - host: host address of the DataServer. - port: Port of the DataServer (default = 8004). - hide_zi_prefix: Hides to common prefix `zi` from the node names. - E.g. `data_server.debug.info` can be used instead of - `data_server.zi.debug.info`. - custom_parser: A function that takes an annotated value and returns an - annotated value. This function is applied to all values coming from - the server. It is applied after the default enum parser, if - applicable. - context: Context in which the session should run. If not provided - the default context will be used which is in most cases the - desired behavior. - timeout: Timeout in milliseconds for the connection setup. - - Returns: - The connected DataServer. - - Raises: - UnavailableError: If the data server was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming - request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If another error happens during the session creation. - LabOneError: If an error appeared in the connection to the device. - """ - session = await KernelSession.create( - kernel_info=KernelInfo.zi_connection(), - server_info=ServerInfo(host=host, port=port), - context=context, - timeout=timeout, - ) - - return await DataServer.create_from_session( - session=session, - host=host, - port=port, - custom_parser=custom_parser, - hide_zi_prefix=hide_zi_prefix, - ) - - async def check_firmware_compatibility( - self, - devices: list[str] | None = None, - ) -> None: - """Check if the firmware matches the LabOne version. - - Args: - devices: List of devices to check. If `None`, all devices connected - to the data server are checked. - - Raises: - ConnectionError: If the device is currently updating - LabOneError: If the firmware revision does not match to the - version of the connected LabOne DataServer. - """ - raw_discovery_info = await self.tree_manager.session.get("/zi/devices") - discovery_info: dict[str, dict] = json.loads( - raw_discovery_info.value, # type: ignore[arg-type] - ) - - devices_currently_updating = [] - devices_update_firmware = [] - devices_update_labone = [] - devices_to_test = devices if devices is not None else discovery_info.keys() - for device_id, device_info in discovery_info.items(): - if device_id not in devices_to_test: - continue - status_flag = device_info["STATUSFLAGS"] - - if status_flag & 1 << 8: - devices_currently_updating.append(device_id) - - if status_flag & 1 << 4 or status_flag & 1 << 5: - devices_update_firmware.append(device_id) - - if status_flag & 1 << 6 or status_flag & 1 << 7: - devices_update_labone.append(device_id) - - messages = [] - if devices_currently_updating: - messages.append( - f"The device(s) {', '.join(devices_currently_updating)} " - f"is/are currently updating. " - "Please try again after the update process is complete.", - ) - - if devices_update_firmware: - messages.append( - f"The Firmware of the device(s) {', '.join(devices_update_firmware)} " - f"do/does not match the LabOne version. " - "Please update the firmware (e.g. in the LabOne UI)", - ) - - if devices_update_labone: - messages.append( - f"The Firmware of the device(s) {', '.join(devices_update_labone)} " - f"do/does not match the LabOne version. " - "Please update LabOne to the latest version from " - "https://www.zhinst.com/support/download-center.", - ) - - if messages: - raise LabOneError( - "Found these compatibility issues:\n" + "\n".join(messages), - ) - - @property - def host(self) -> str: - """Host of the Data Server.""" - return self._host # pragma: no cover - - @property - def port(self) -> int: - """Port of the Data Server.""" - return self._port # pragma: no cover - - @property - def kernel_session(self) -> KernelSession: - """Kernel session used by the instrument.""" - return self._tree_manager.session # type: ignore[return-value] diff --git a/src/labone/errors.py b/src/labone/errors.py deleted file mode 100644 index 577b92e..0000000 --- a/src/labone/errors.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Common custom errors used in entire LabOne package.""" - - -class LabOneError(RuntimeError): - """Base class for all errors raised by the LabOne.""" diff --git a/src/labone/instrument.py b/src/labone/instrument.py deleted file mode 100644 index 69d561a..0000000 --- a/src/labone/instrument.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Base Instrument Driver. - -Natively works with all device types. -""" - -from __future__ import annotations - -import typing as t - -from labone.core import ( - AnnotatedValue, - KernelInfo, - KernelSession, - ServerInfo, - Session, - ZIContext, -) -from labone.errors import LabOneError -from labone.nodetree import construct_nodetree -from labone.nodetree.node import Node, PartialNode - -if t.TYPE_CHECKING: - from labone.core.errors import ( # noqa: F401 - BadRequestError, - InternalError, - LabOneCoreError, - UnavailableError, - ) - - -class Instrument(PartialNode): - """Generic driver for a Zurich Instrument device. - - This class serves as the main entry point for any connection to a - Zurich Instrument device. - - At Zurich Instruments a server-based connectivity methodology is used. - Server-based means that all communication between the user and the - instrument takes place via a computer program called a server, the data - server. (For more information on the architecture please refer to the - [user manual](http://docs.zhinst.com/labone_programming_manual/introduction.html)) - - !!! note - - Due to the asynchronous interface, one needs to use the static method - `create` instead of the `__init__` method. - - ```python - from labone import Instrument - instrument = await Instrument.create("dev2345", host="127.0.0.1") - ``` - - Args: - serial: Serial number of the device, e.g. 'dev2345'. - The serial number can be found on the back panel of the instrument. - model_node: Example node which serves as a model for setting the inherited - node attributes. - """ - - def __init__( - self, - *, - serial: str, - model_node: Node, - ): - self._serial = serial - super().__init__( - tree_manager=model_node.tree_manager, - path_segments=model_node.path_segments, - subtree_paths=model_node.subtree_paths, - ) - - @staticmethod - async def create_from_session( - serial: str, - *, - session: Session, - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - ) -> Instrument: - """Create an Instrument from an existing session. - - Args: - serial: Serial number of the device, e.g. `dev2345`. - The serial number can be found on the back panel of the instrument. - session: Session to use for the instrument. - custom_parser: A function that takes an annotated value and returns an - annotated value. This function is applied to all values coming from - the server. It is applied after the default enum parser, if - applicable. - - Returns: - The connected device. - - Raises: - LabOneError: If an error appeared in the connection to the device. - """ - try: - model_node = await construct_nodetree( - session, - custom_parser=custom_parser, - ) - except LabOneError as e: - msg = f"While connecting to device {serial} an error occurred." - raise LabOneError(msg) from e - - return Instrument( - serial=serial, - model_node=model_node, - ) - - @staticmethod - async def create( - serial: str, - *, - host: str, - port: int = 8004, - interface: str = "", - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - context: ZIContext | None = None, - timeout: int = 5000, - ) -> Instrument: - """Connect to a device. - - Args: - serial: Serial number of the device, e.g. `dev2345`. - The serial number can be found on the back panel of the instrument. - host: host address of the DataServer. - port: Port of the DataServer. - interface: The interface that should be used to connect to the device. - It is only needed if the device is accessible through multiple - interfaces, and a specific interface should be enforced. If no value is - provided, the data server will automatically choose an available - interface. (default = "") - custom_parser: A function that takes an annotated value and returns an - annotated value. This function is applied to all values coming from - the server. It is applied after the default enum parser, if - applicable. - context: Context in which the session should run. If not provided - the default context will be used which is in most cases the - desired behavior. - timeout: Timeout in milliseconds for the connection setup. - - Returns: - The connected device. - - Raises: - UnavailableError: If the kernel was not found or unable to connect. - BadRequestError: If there is a generic problem interpreting the incoming - request - InternalError: If the kernel could not be launched or another internal - error occurred. - LabOneCoreError: If another error happens during the session creation. - LabOneError: If an error appeared in the connection to the device. - """ - session = await KernelSession.create( - kernel_info=KernelInfo.device_connection( - device_id=serial, - interface=interface, - ), - server_info=ServerInfo(host=host, port=port), - context=context, - timeout=timeout, - ) - return await Instrument.create_from_session( - serial, - session=session, - custom_parser=custom_parser, - ) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self.serial})" - - @property - def serial(self) -> str: - """Instrument specific serial.""" - return self._serial - - @property - def kernel_session(self) -> KernelSession: - """Kernel session used by the instrument.""" - return self._tree_manager.session # type: ignore[return-value] diff --git a/src/labone/mock/__init__.py b/src/labone/mock/__init__.py deleted file mode 100644 index 3e55689..0000000 --- a/src/labone/mock/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Mock Server. - -A capnp server is provided, which will run locally. An interface is provided for -defining the behavior of the server. Subclassing this interface allows for -custom mock server definition. An example implementation is provided defining -typical desired behavior. This way a custom implementation does not need to start -from scratch. - -Example: - >>> mock_session = await AutomaticLabOneServer.start_pipe(paths_to_info) - >>> queue = await session.subscribe("/a/b/c") - >>> print(await session.set(AnnotatedValue(path="/a/b/c", value=123, timestamp=0))) - >>> print(await session.get("/a/b/t")) -""" - -from labone.mock.automatic_server import AutomaticLabOneServer - -__all__ = [ - "AutomaticLabOneServer", -] diff --git a/src/labone/mock/automatic_server.py b/src/labone/mock/automatic_server.py deleted file mode 100644 index a69f89f..0000000 --- a/src/labone/mock/automatic_server.py +++ /dev/null @@ -1,354 +0,0 @@ -"""Partially predefined behavior for HPK mock. - -This class provides basic Hpk mock functionality by taking over some usually -desired tasks. With that in place, the user may inherit from this class -in order to further specify behavior, without having to start from scratch. -Even if some of the predefined behavior is not desired, the implementation -can give some reference on how an individual mock server can be implemented. - - -Already predefined behavior: - - * Simulating state for get/set: - A dictionary is used to store the state of the mock server. - Get and set will access this dictionary. - * Answering list_nodes(_info) via knowledge of the tree structure: - Given a dictionary of paths to node info passed in the constructor, - the list_nodes(_info) methods will be able to answer accordingly. - * Reducing get_with_expression/set_with_expression to multiple get/set: - As the tree structure is known, the get_with_expression/set_with_expression - methods can be implemented by calling the get/set methods multiple times. - * Managing subscriptions and passing all changes into the queues: - The subscriptions are stored and on every change, the new value is passed - into the queues. - * Adding chronological timestamps to responses: - The server answers need timestamps to the responses in any case. - By using the monotonic clock, the timestamps are added automatically. - -""" - -from __future__ import annotations - -import asyncio -import fnmatch -import time -import typing as t -from dataclasses import dataclass - -from labone.core import ListNodesFlags, ListNodesInfoFlags -from labone.core.errors import LabOneCoreError -from labone.core.value import ( - AnnotatedValue, - Value, -) -from labone.mock.errors import LabOneMockError -from labone.mock.session import LabOneServerBase, Subscription -from labone.node_info import NodeInfo - -if t.TYPE_CHECKING: - from labone.core.helper import LabOneNodePath - from labone.core.session import NodeInfo as NodeInfoType - - -@dataclass -class PathData: - """Data stored for each path in the mock server.""" - - value: Value - info: NodeInfo - streaming_handles: list[Subscription] - - -class AutomaticLabOneServer(LabOneServerBase): - """Predefined behaviour for HPK mock. - - Args: - paths_to_info: Dictionary of paths to node info. (tree structure) - """ - - def __init__( - self, - paths_to_info: dict[LabOneNodePath, NodeInfoType], - ) -> None: - super().__init__() - # storing state and tree structure, info and subscriptions - # set all existing paths to 0. - common_prefix_raw = ( - next(iter(paths_to_info.keys())).split("/") if paths_to_info else [] - ) - self._common_prefix: str | None = ( - f"/{common_prefix_raw[1]}" - if len(common_prefix_raw) > 1 and common_prefix_raw[1] != "" - else None - ) - self.memory: dict[LabOneNodePath, PathData] = {} - for path, given_info in paths_to_info.items(): - info = NodeInfo.plain_default_info(path=path) - info.update({"Type": "Integer (64 bit)"}) # for mock, int nodes are default - info.update(given_info) - self.memory[path] = PathData( - value=0, - info=NodeInfo(info), - streaming_handles=[], - ) - if self._common_prefix and not path.startswith(self._common_prefix): - self._common_prefix = None - - def get_timestamp(self) -> int: - """Create a realistic timestamp. - - Call this function to obtain a timestamp for some response. - As a internal clock is used, subsequent calls will return - increasing timestamps. - - Returns: - Timestamp in nanoseconds. - """ - return time.monotonic_ns() - - def _sanitize_path(self, path: LabOneNodePath) -> LabOneNodePath: - """Sanitize the path. - - Removes trailing slashes and replaces empty path with root path. - - Args: - path: Path to sanitize. - - Returns: - Sanitized path. - """ - if self._common_prefix and not path.startswith("/"): - return f"{self._common_prefix}/{path}" - return path - - async def list_nodes_info( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, # noqa: ARG002 - ) -> dict[LabOneNodePath, NodeInfoType]: - """Predefined behavior for list_nodes_info. - - Uses knowledge of the tree structure to answer. - - Warning: - Flags will be ignored in this implementation. (TODO) - For now, the behavior is equivalent to - ListNodesFlags.RECURSIVE | ListNodesFlags.ABSOLUTE - - Args: - path: Path to narrow down which nodes should be listed. Omitting - the path will list all nodes by default. - flags: Flags to control the behavior of the list_nodes_info method. - - Returns: - Dictionary of paths to node info. - """ - return { - p: self.memory[p].info.as_dict for p in await self.list_nodes(path=path) - } - - async def list_nodes( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, # noqa: ARG002 - ) -> list[LabOneNodePath]: - """Predefined behavior for list_nodes. - - Uses knowledge of the tree structure to answer. - - Warning: - Flags will be ignored in this implementation. (TODO) - For now, the behavior is equivalent to - ListNodesFlags.RECURSIVE | ListNodesFlags.ABSOLUTE - - Args: - path: Path to narrow down which nodes should be listed. Omitting - the path will list all nodes by default. - flags: Flags to control the behavior of the list_nodes method. - - Returns: - List of paths. - """ - if path in [""]: - return [] - return [ - p - for p in self.memory - if fnmatch.fnmatch(p, path) - or fnmatch.fnmatch(p, path + "*") - or fnmatch.fnmatch(p, path + "/*") - or p == path - ] - - async def get(self, path: LabOneNodePath) -> AnnotatedValue: - """Predefined behavior for get. - - Look up the path in the internal dictionary. - - Args: - path: Path of the node to get. - - Returns: - Corresponding value. - """ - path = self._sanitize_path(path) - try: - value = self.memory[path].value - except KeyError as e: - msg = f"Path {path} not found in mock server. Cannot get it." - raise LabOneMockError(msg) from e - response = AnnotatedValue(path=path, value=value) - response.timestamp = self.get_timestamp() - return response - - async def get_with_expression( - self, - path_expression: LabOneNodePath, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE # noqa: ARG002 - | ListNodesFlags.RECURSIVE - | ListNodesFlags.LEAVES_ONLY - | ListNodesFlags.EXCLUDE_STREAMING - | ListNodesFlags.GET_ONLY, - ) -> list[AnnotatedValue]: - """Predefined behavior for get_with_expression. - - Find all nodes associated with the path expression - and call get for each of them. - - Args: - path_expression: Path expression to get. - flags: Flags to control the behavior of the get_with_expression method. - - Returns: - List of values, corresponding to nodes of the path expression. - """ - return [await self.get(p) for p in await self.list_nodes(path=path_expression)] - - async def _update_subscriptions(self, value: AnnotatedValue) -> None: - """Update all subscriptions with the new value. - - Args: - value: New value. - """ - if self.memory[value.path].streaming_handles: - # sending updated value to subscriptions - result = await asyncio.gather( - *[ - handle.send_value(value) - for handle in self.memory[value.path].streaming_handles - ], - ) - # Remove all disconnected subscriptions - self.memory[value.path].streaming_handles = [ - handle - for handle, success in zip( - self.memory[value.path].streaming_handles, - result, - ) - if success - ] - - @t.overload - async def set(self, value: AnnotatedValue) -> AnnotatedValue: ... - - @t.overload - async def set( - self, - value: Value, - path: LabOneNodePath, - ) -> AnnotatedValue: ... - - async def set( - self, - value: AnnotatedValue | Value, - path: str = "", - ) -> AnnotatedValue: - """Predefined behavior for set. - - Updates the internal dictionary. A set command is considered - as an update and will be distributed to all registered subscription handlers. - - Args: - value: Value to set. - path: LabOne node path. The path can be relative or absolute. - - Returns: - Acknowledged value. - """ - if isinstance(value, AnnotatedValue): - path = value.path - value = value.value - path = self._sanitize_path(path) - if path not in self.memory: - msg = f"Path {path} not found in mock server. Cannot set it." - raise LabOneCoreError(msg) - self.memory[path].value = value - - if not self.memory[path].info.writable: - msg = f"Path {path} is not writeable." - raise LabOneCoreError(msg) - - response = AnnotatedValue( - value=value, - path=path, - timestamp=self.get_timestamp(), - ) - await self._update_subscriptions(value=response) - return response - - @t.overload - async def set_with_expression( - self, - value: AnnotatedValue, - ) -> list[AnnotatedValue]: ... - - @t.overload - async def set_with_expression( - self, - value: Value, - path: LabOneNodePath, - ) -> list[AnnotatedValue]: ... - - async def set_with_expression( - self, - value: AnnotatedValue | Value, - path: LabOneNodePath | None = None, - ) -> list[AnnotatedValue]: - """Predefined behavior for set_with_expression. - - Finds all nodes associated with the path expression - and call set for each of them. - - Args: - value: Value to set. - path:LabOne node path. - - Returns: - List of acknowledged values, corresponding to nodes of the path expression. - """ - if isinstance(value, AnnotatedValue): - path = value.path - value = value.value - result = [ - await self.set(value=value, path=p) for p in await self.list_nodes(path) # type: ignore[arg-type] - ] - if not result: - msg = f"No node found matching path '{path}'." - raise LabOneCoreError(msg) - return result - - async def subscribe(self, subscription: Subscription) -> None: - """Predefined behavior for subscribe. - - Stores the subscription. Whenever an update event happens - they are distributed to all registered handles, - - Args: - subscription: Subscription object containing information on - where to distribute updates to. - """ - self.memory[self._sanitize_path(subscription.path)].streaming_handles.append( - subscription, - ) diff --git a/src/labone/mock/errors.py b/src/labone/mock/errors.py deleted file mode 100644 index 4afe7bf..0000000 --- a/src/labone/mock/errors.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Mock specific errors.""" - -from labone.errors import LabOneError - - -class LabOneMockError(LabOneError): - """Base class for all LabOne mock errors.""" diff --git a/src/labone/mock/session.py b/src/labone/mock/session.py deleted file mode 100644 index 3f2b7e2..0000000 --- a/src/labone/mock/session.py +++ /dev/null @@ -1,464 +0,0 @@ -"""Capnp Server method definitions. - -This module contains the method definitions for the Capnp Server, -including setting and getting values, listing nodes, and subscribing to -nodes. This specific capnp server methods define the specific -Hpk behavior. -The logic of the capnp methods is delegated to the SessionFunctionality class, -which offers a blueprint meant to be overridden by the user. -""" - -from __future__ import annotations - -import json -from abc import ABC, abstractmethod -from typing import TYPE_CHECKING - -import zhinst.comms -from zhinst.comms.server import CapnpResult, CapnpServer, capnp_method - -from labone.core import ListNodesFlags, ListNodesInfoFlags, hpk_schema -from labone.core.helper import get_default_context -from labone.core.session import Session -from labone.core.value import ( - AnnotatedValue, - _capnp_value_to_python_value, - value_from_python_types, -) - -HPK_SCHEMA_ID = 0xA621130A90860008 -SESSION_SCHEMA_ID = 0xB9D445582DA4A55C -SERVER_ERROR = "SERVER_ERROR" - - -if TYPE_CHECKING: - from labone.core.helper import LabOneNodePath - from labone.core.session import NodeInfo - - -class Subscription: - """Subscription abstraction class. - - This class hides the capnp specific subscription object and the logic to send - and AnnotatedValue to the subscriber. - - Args: - path: Path to the node to subscribe to. - streaming_handle: Capnp specific object to send updates to. - subscriber_id: Capnp specific id of the subscriber. - """ - - def __init__( - self, - path: LabOneNodePath, - streaming_handle: zhinst.comms.DynamicClient, - subscriber_id: bytes, - ): - self._path = path - self._streaming_handle = streaming_handle - self.subscriber_id = subscriber_id - - async def send_value(self, value: AnnotatedValue) -> bool: - """Send value to the subscriber. - - Args: - value: Value to send. - - Returns: - Flag indicating if the subscription is active - """ - try: - await self._streaming_handle.sendValues( - values=[ - { - "value": value_from_python_types( - value.value, - capability_version=Session.CAPABILITY_VERSION, - ), - "metadata": { - "path": value.path, - "timestamp": value.timestamp, - }, - }, - ], - ) - except zhinst.comms.errors.DisconnectError: - return False - return True - - @property - def path(self) -> LabOneNodePath: - """Node path of the subscription.""" - return self._path - - -class MockSession(Session): - """Regular Session holding a mock server. - - This class is designed for holding the mock server. - This is needed, because otherwise, - there would be no reference to the capnp objects, which would go out of scope. - This way, the correct lifetime of the capnp objects is ensured, by attaching it to - its client. - - Args: - mock_server: Mock server. - capnp_session: Capnp session. - reflection: Reflection server. - """ - - def __init__( - self, - server: LabOneServerBase, - client: zhinst.comms.DynamicClient, - *, - context: zhinst.comms.CapnpContext, - ): - super().__init__( - client, - context=context, - capability_version=Session.CAPABILITY_VERSION, - ) - self._mock_server = server - - @property - def mock_server(self) -> LabOneServerBase: - """Mock server.""" - return self._mock_server - - -def build_capnp_error(error: Exception) -> CapnpResult: - """Helper function to build a capnp error message. - - Args: - error: Caught python exception to be converted. - - Returns: - Capnp Type dictionary for Result(Error). - """ - return { - "err": { - "code": 2, - "message": f"{error}", - "category": SERVER_ERROR, - "source": __name__, - }, - } - - -class LabOneServerBase(ABC, CapnpServer): - """Blueprint for defining the session behavior. - - The SessionFunctionality class offers a interface between - capnp server logic and the user. The user can override the methods - to define an individual server. The signature of the methods - is mostly identical to the session-interface on the caller side. - Thereby it feels as if the session-interface is overwritten directly, - hiding the capnp server logic from the user. - - Two possible ways to use this class arise: - * Call methods indirectly (via capnp server), by having a session - to a server. - * Call methods directly. This can be used to manipulate the state - internally. Limitations of what can be set to a server are - bypassed. E.g. can be useful when setting SHF vector nodes. - - Both approaches can be combined. - """ - - def __init__(self): - CapnpServer.__init__( - self, - schema=hpk_schema.get_schema_loader().get_interface_schema(HPK_SCHEMA_ID), - ) - - @abstractmethod - async def get(self, path: LabOneNodePath) -> AnnotatedValue: - """Override this method for defining get behavior. - - Args: - path: Path to a single the node. - - Returns: - Retrieved value. - """ - ... - - @abstractmethod - async def get_with_expression( - self, - path_expression: LabOneNodePath, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE - | ListNodesFlags.RECURSIVE - | ListNodesFlags.LEAVES_ONLY - | ListNodesFlags.EXCLUDE_STREAMING - | ListNodesFlags.GET_ONLY, - ) -> list[AnnotatedValue]: - """Override this method for defining get_with_expression behavior. - - Args: - path_expression: Path expression to get. - flags: Flags to control the behavior of the get_with_expression method. - - Returns: - List of values, corresponding to nodes of the path expression. - """ - ... - - @abstractmethod - async def set(self, value: AnnotatedValue) -> AnnotatedValue: - """Override this method for defining set behavior. - - Args: - value: Value to set. Note that this is in the form of an AnnotatedValue. - Therefore, value, path and timestamp are encapsulated within. - - Returns: - Acknowledged value (also in annotated form). - """ - ... - - @abstractmethod - async def set_with_expression(self, value: AnnotatedValue) -> list[AnnotatedValue]: - """Override this method for defining set_with_expression behavior. - - Args: - value: Value to set. Note that this is in the form of an AnnotatedValue. - Therefore, value, wildcard-path and timestamp are encapsulated within. - All nodes matching the wildcard-path will be set to the value. - - Returns: - Acknowledged values (also in annotated form). - """ - ... - - @abstractmethod - async def list_nodes( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesFlags, - ) -> list[LabOneNodePath]: - """Override this method for defining list_nodes behavior. - - Args: - path: Path to narrow down which nodes should be listed. - Omitting the path will list all nodes by default. - flags: Flags to control the behavior of the list_nodes method. - - Returns: - List of nodes, corresponding to the path and flags. - """ - ... - - @abstractmethod - async def list_nodes_info( - self, - path: LabOneNodePath, - *, - flags: ListNodesInfoFlags, - ) -> dict[LabOneNodePath, NodeInfo]: - """Override this method for defining list_nodes_info behavior. - - Args: - path: Path to narrow down which nodes should be listed. - Omitting the path will list all nodes by default. - flags: Flags to control the behavior of the list_nodes_info method. - - Returns: - Dictionary of paths to node info. - """ - ... - - @abstractmethod - async def subscribe(self, subscription: Subscription) -> None: - """Override this method for defining subscription behavior. - - Args: - subscription: Subscription object containing information on - where to distribute updates to. - """ - ... - - @capnp_method(SESSION_SCHEMA_ID, 7) - async def _get_session_version_interface( - self, - _: hpk_schema.SessionGetSessionVersionParams, - ) -> CapnpResult: - """Capnp server method to get session version. - - Returns: - Capnp result. - """ - return {"version": str(Session.CAPABILITY_VERSION)} - - @capnp_method(SESSION_SCHEMA_ID, 0) - async def _list_nodes_interface( - self, - call_input: hpk_schema.SessionListNodesParams, - ) -> CapnpResult: - """Capnp server method to list nodes. - - Args: - call_input: Arguments the server has been called with - - Returns: - Capnp result. - """ - return { - "paths": await self.list_nodes( - call_input.pathExpression, - flags=ListNodesFlags(call_input.flags), - ), - } - - @capnp_method(SESSION_SCHEMA_ID, 5) - async def _list_nodes_json_interface( - self, - call_input: hpk_schema.SessionListNodesJsonParams, - ) -> CapnpResult: - """Capnp server method to list nodes json. - - Args: - call_input: Arguments the server has been called with - - Returns: - Capnp result. - """ - return { - "nodeProps": json.dumps( - await self.list_nodes_info( - call_input.pathExpression, - flags=ListNodesInfoFlags(call_input.flags), - ), - ), - } - - @capnp_method(SESSION_SCHEMA_ID, 10) - async def _get_value_interface( - self, - call_input: hpk_schema.SessionGetValueParams, - ) -> CapnpResult: - """Capnp server method to get values. - - Args: - call_input: Arguments the server has been called with - - Returns: - Capnp result. - """ - lookup_mode = call_input.lookupMode - try: - if lookup_mode == 0: # direct lookup - responses = [await self.get(call_input.pathExpression)] - else: - responses = await self.get_with_expression( - call_input.pathExpression, - flags=call_input.flags, - ) - except Exception as e: # noqa: BLE001 - return {"result": [build_capnp_error(e)]} - - result = [ - { - "ok": { - "value": value_from_python_types( - response.value, - capability_version=Session.CAPABILITY_VERSION, - ), - "metadata": { - "path": response.path, - "timestamp": response.timestamp, - }, - }, - } - for response in responses - ] - return {"result": result} - - @capnp_method(SESSION_SCHEMA_ID, 9) - async def _set_value_interface( - self, - call_input: hpk_schema.SessionSetValueParams, - ) -> CapnpResult: - """Capnp server method to set values. - - Args: - call_input: Arguments the server has been called with - - Returns: - Capnp result. - """ - annotated_value = AnnotatedValue( - value=_capnp_value_to_python_value(call_input.value), - path=call_input.pathExpression, - ) - - try: - if call_input.lookupMode == 0: # direct lookup - responses = [ - await self.set(annotated_value), - ] - else: - responses = await self.set_with_expression( - annotated_value, - ) - except Exception as e: # noqa: BLE001 - return {"result": [build_capnp_error(e)]} - - result = [ - { - "ok": { - "value": value_from_python_types( - response.value, - capability_version=Session.CAPABILITY_VERSION, - ), - "metadata": { - "path": response.path, - "timestamp": response.timestamp, - }, - }, - } - for response in responses - ] - return {"result": result} - - @capnp_method(SESSION_SCHEMA_ID, 3) - async def _subscribe_interface( - self, - call_input: hpk_schema.SessionSubscribeParams, - ) -> CapnpResult: - """Capnp server method to subscribe to nodes. - - Args: - call_input: Arguments the server has been called with - - Returns: - Capnp result. - """ - try: - subscription = Subscription( - path=call_input.subscription.path, - streaming_handle=call_input.subscription.streamingHandle, - subscriber_id=call_input.subscription.subscriberId, - ) - await self.subscribe(subscription) - except Exception as e: # noqa: BLE001 - return {"result": [build_capnp_error(e)]} - return {"result": {"ok": {}}} - - async def start_pipe( # type: ignore[override] - self, - context: zhinst.comms.CapnpContext | None = None, - ) -> MockSession: - """Create a local pipe to the server. - - A pipe is a local single connection to the server. - - Args: - context: context to use. - """ - if context is None: - context = get_default_context() - client = await super().start_pipe(context) - return MockSession(self, client, context=context) diff --git a/src/labone/node_info.py b/src/labone/node_info.py deleted file mode 100644 index 6ac4eaa..0000000 --- a/src/labone/node_info.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Brings node info into a more usable format.""" - -from __future__ import annotations - -import re -import typing as t -from functools import cached_property - -if t.TYPE_CHECKING: - from labone.core.helper import LabOneNodePath - from labone.core.session import NodeInfo as NodeInfoType - from labone.core.session import NodeType - - -def _parse_option_keywords_description(option_string: str) -> tuple[list[str], str]: - r"""Parse the option string into keywords and description. - - Infos for enumerated nodes come with a string for each option. - This function parses this string into relevant information. - - There are two valid formats for the option string: - 1. Having a single keyword: - e.g. "Alive" - -> keyword: ["Alive"], description: "" - - 2. Having one or multiple keywords in parenthesis, - optionally followed by a colon and a description: - e.g. "\"sigin0\", \"signal_input0\": Sig In 1" - -> keyword: ["sigin0", "signal_input0"], description: "Sig In 1" - - Args: - option_string: String, which should be parsed. - - Returns: - List of keywords and the description. - """ - # find all keywords in parenthesis - matches = list(re.finditer(r'"(?P[a-zA-Z0-9-_"]+)"', option_string)) - options = [option_string] if not matches else [m.group("keyword") for m in matches] - - # take everythin after ": " as the description if present - description_match = re.search(r": (.*)", option_string) - description = description_match.group(1) if description_match else "" - - return options, description - - -class OptionInfo(t.NamedTuple): - """Representing structure of options in NodeInfo.""" - - enum: str - description: t.Any - - -class NodeInfo: - """Encapsulating information about a leaf node. - - This class contains all information about a node provided by the server. - This includes: - - * LabOne node path - * Description of the node - * Properties of the node (e.g. Read, Write, Setting) - * Type of the node (e.g. Double, Integer, String) - * Unit of the node, if applicable (e.g. V, Hz, dB) - * Options of the node, if applicable (e.g. 0: "Off", 1: "On") - - Args: - info: Raw information about the node (JSON formatted), as provided by - the server. - """ - - def __init__(self, info: NodeInfoType): - self._info: NodeInfoType = info - - @classmethod - def plain_default_info(cls, *, path: str) -> NodeInfoType: - """Get default info for a node. - - Returns: - Default info for a node. - """ - return { - "Description": "", - "Properties": "Read, Write", - "Unit": "None", - "Node": path, - } # type: ignore[typeddict-item] - - def _checked_dict_access( - self, - item: str, - ) -> LabOneNodePath | str | NodeType | dict[str, str]: - """Indirect access to underlying dictionary. - - The underlying dictionary may not be complete in some cases. - This function raises an appropriate error if an required key is missing. - - Args: - item: Key to access. - - Returns: - Value of the key. - - Raises: - KeyError: If the key is valid but not present in the dictionary. - - """ - if item not in self._info and item in [ - "Description", - "Node", - "Properties", - "Type", - "Unit", - "Options", - ]: - msg = f"NodeInfo is incomplete. As '{item}'\ - is missing, not all behavior is available." - raise KeyError(msg) - return self._info[item] # type: ignore[literal-required] - - def __getattr__( - self, - item: str, - ) -> LabOneNodePath | str | NodeType | dict[str, str] | None: - return self._checked_dict_access(item.capitalize()) - - def __dir__(self) -> list[str]: - return [k.lower() for k in self._info] + [ - var - for var, value in vars(self.__class__).items() - if isinstance(value, property) and not var.startswith("_") - ] - - def __repr__(self) -> str: - return f'NodeInfo({self._info.get("Node", "unknown path")})' - - def __str__(self) -> str: - string = self._info.get("Node", "unknown path") - string += "\n" + self._info.get("Description", "(no description available)") - for key, value in self._info.items(): - if key == "Options": - string += f"\n{key}:" - for option, description in value.items(): # type: ignore[attr-defined] - string += f"\n {option}: {description}" - elif key not in ["Description", "Node", "SetParser", "GetParser"]: - string += f"\n{key}: {value}" - return string - - @property - def readable(self) -> bool: - """Flag if the node is readable.""" - return "Read" in self._checked_dict_access("Properties") # type: ignore[return-value] - - @property - def writable(self) -> bool: - """Flag if the node is writable.""" - return "Write" in self._checked_dict_access("Properties") # type: ignore[return-value] - - @property - def is_setting(self) -> bool: - """Flag if the node is a setting.""" - return "Setting" in self._checked_dict_access("Properties") # type: ignore[return-value] - - @property - def is_vector(self) -> bool: - """Flag if the value of the node a vector.""" - return "Vector" in self._checked_dict_access("Type") # type: ignore[return-value] - - @property - def path(self) -> str: - """LabOne path of the node.""" - return self._checked_dict_access("Node").lower() # type: ignore[return-value, union-attr] - - @property - def description(self) -> str: - """Description of the node.""" - return self._checked_dict_access("Description") # type: ignore[return-value] - - @property - def type(self) -> str: - """Type of the node.""" - return self._checked_dict_access("Type") # type: ignore[return-value] - - @property - def unit(self) -> str: - """Unit of the node.""" - return self._checked_dict_access("Unit") # type: ignore[return-value] - - @cached_property - def options(self) -> dict[int, OptionInfo]: - """Option mapping of the node.""" - options_mapping = {} - for key, option_string in self._info.get("Options", {}).items(): - options, description = _parse_option_keywords_description(option_string) - - # Only use the first keyword as the enum value - options_mapping[int(key)] = OptionInfo( - enum=options[0], - description=description, - ) - return options_mapping - - @property - def as_dict(self) -> NodeInfoType: - """Underlying dictionary.""" - return self._info diff --git a/src/labone/nodetree/__init__.py b/src/labone/nodetree/__init__.py deleted file mode 100644 index 704747b..0000000 --- a/src/labone/nodetree/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Subpackage providing the pythonic node-tree. - -This subpackage delivers tree-like classes to navigate node-paths efficiently. -These nodes allow communication to server to get or set values accordingly. - -This module enables accessing LabOne nodes with the dot-operator: - ->>> zi.debug.level - -Internally LabOne used a path-based representation of the nodes -(e.g. /zi/debug/level). This module provides a mapping between -the path-based representation and the object-based representation. - -A Node object will be created on the fly and no initial generation is done upfront. -This allows an fast and intuitive access to the nodes. - -To get or set one or multiple values the call operator can be used on any node -object. Calling a node with no arguments will result in getting the values, -while passing a value will result in setting it. - ->>> await zi.debug.level() ->>> await zi.debug.level(6) -""" - -from labone.nodetree.entry_point import construct_nodetree - -__all__ = ["construct_nodetree"] diff --git a/src/labone/nodetree/entry_point.py b/src/labone/nodetree/entry_point.py deleted file mode 100644 index 6ff6bcd..0000000 --- a/src/labone/nodetree/entry_point.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Main entry point for the node tree package. - -This module provides a function to construct a node tree from a session. -""" - -from __future__ import annotations - -import typing as t - -from labone.nodetree.enum import get_default_enum_parser -from labone.nodetree.node import NodeTreeManager - -if t.TYPE_CHECKING: - from labone.core import AnnotatedValue - from labone.nodetree.helper import Session - from labone.nodetree.node import Node - - -async def construct_nodetree( - session: Session, - *, - hide_kernel_prefix: bool = True, - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, -) -> Node: - """Create a nodetree structure from a LabOne session. - - Issues a single requests to the server to retrieve the structural - information about the tree. - - Args: - session: Connection to data-server. - hide_kernel_prefix: Enter a trivial first path-segment automatically. - E.g. having the result of this function in a variable `tree` - `tree.debug.info` can be used instead of `tree.device1234.debug.info`. - Setting this option makes working with the tree easier. - custom_parser: A function that takes an annotated value and returns an - annotated value. This function is applied to all values coming from - the server. It is applied after the default enum parser, if - applicable. - - Returns: - Root-node of the tree. - """ - path_to_info = await session.list_nodes_info("*") - - parser = get_default_enum_parser(path_to_info) - - if custom_parser is not None: - first_parser = parser # this assignment prevents infinite recursion - parser = lambda x: custom_parser(first_parser(x)) # type: ignore[misc] # noqa: E731 - - nodetree_manager = NodeTreeManager( - session=session, - parser=parser, - path_to_info=path_to_info, - hide_kernel_prefix=hide_kernel_prefix, - ) - - return nodetree_manager.root diff --git a/src/labone/nodetree/enum.py b/src/labone/nodetree/enum.py deleted file mode 100644 index e80ebbd..0000000 --- a/src/labone/nodetree/enum.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Module providing a parser for enumerated integer values. - -Some integer values coming from the server are enumerated. This means that -only a limited set of integer values are valid and each integer value is -associated to a keyword. This module provides a parser that converts the -integer value to an Enum object. - -The Enum object is created dynamically based on the node information. This -is not ideal since it makes it hard to use the Enum as a user. Nevertheless -its beneficial to apply the parser to the values coming from the server since -it makes the values more readable and easier to use. Since we return a IntEnum -object the values can be treated as integers and used as such. -""" - -from __future__ import annotations - -import logging -import typing as t -from enum import Enum, IntEnum -from functools import lru_cache - -from labone.node_info import _parse_option_keywords_description - -if t.TYPE_CHECKING: - from labone.core import AnnotatedValue - from labone.core.helper import LabOneNodePath - from labone.core.session import NodeInfo as NodeInfoType - -logger = logging.getLogger(__name__) - -T = t.TypeVar("T") - - -class NodeEnumMeta: - """Custom Metaclass for NodeEnum. - - Note: Required to enable pickling of a NodeEnum value. - - It simply servers the purpose to recreate a NodeEnum for a given enum - value. Since the NodeEnums are created dynamically there is no way recreate - a NodeEnum value since python can not find the definition. This class - bypasses this problem by providing the functionality to recreate the - Enum on the fly. - - Warning: - Although the class of the resulting enum object looks and feels - the same as the original one it is not. Therefore comparing the `type` - will fail. This is however the only limitation. - (type(value_old) != type(value_new) but value_old == value_new) - - Args: - value: Value of the NodeEnum object that should be created. - class_name: Name of the NodeEnum class. - names: Mapping of the enum names to their corresponding integer value. - module: Should be set to the module this class is being created in. - """ - - def __new__( # type: ignore[misc] # noqa: D102 - cls, - value: int, - class_name: str, - names: dict[str, int], - module: str, - ) -> Enum: - new_enum = NodeEnum(class_name, names, module=module) - return new_enum(value) # type: ignore[func-returns-value] - - -class NodeEnum(IntEnum): - """Custom dynamically picklable IntEnum class. - - The Enum values for a device are created dynamically in toolkit based on - the node information. Since they are not predefined but rather created - dynamically, the are not picklable. This custom child class of IntEnum - overwrites the reduce function that returns all information required to - recreate the Enum class in `NodeEnumMeta`. - - For more information on the reduce functionality and how it is used within - the pickle package see - [pep307](https://peps.python.org/pep-0307/#extended-reduce-api). - """ - - # Required for typing - def __init__(self, *args, **kwargs) -> None: ... - - # Required for typing - def __call__(self, *args, **kwargs) -> None: # noqa: D102 - ... - - def __reduce_ex__( - self, - _: object, - ) -> tuple[type[NodeEnumMeta], tuple[int, str, dict, str]]: - return NodeEnumMeta, ( - self._value_, - self.__class__.__name__, - {key: int(value) for key, value in self.__class__._member_map_.items()}, # type: ignore[call-overload] - self.__class__.__module__, - ) - - -def _get_enum(*, info: NodeInfoType, path: LabOneNodePath) -> NodeEnum | None: - """Enum of the node options.""" - if "Options" not in info: - return None - - keyword_to_option = {} - for key, option_string in info["Options"].items(): - keywords, _ = _parse_option_keywords_description(option_string) - for keywork in keywords: - keyword_to_option[keywork] = int(key) - - return NodeEnum(path, keyword_to_option, module=__name__) - - -def get_default_enum_parser( - path_to_info: dict[LabOneNodePath, NodeInfoType], -) -> t.Callable[[AnnotatedValue], AnnotatedValue]: - """Get a generic parser for enumerated integer values. - - The returned parser can be called with an annotated value. If the value - is enumerated and the corresponding Enum can be found, the value will be - converted to the Enum. - - The lookup is cached to speed up the process. - - Args: - path_to_info: Mapping of node paths to their corresponding NodeInfo. - - Returns: - Function that parses the value of a node to an Enum if possible. - """ - - @lru_cache - def get_enum_cached(path: LabOneNodePath) -> NodeEnum | None: - """Cache based on path.""" - return _get_enum(info=path_to_info[path], path=path) - - def default_enum_parser(annotated_value: AnnotatedValue) -> AnnotatedValue: - """Default Enum Parser. - - Args: - annotated_value: Value to be parsed. - - Returns: - Parsed value. - """ - try: - enum = get_enum_cached(annotated_value.path) - except KeyError: # pragma: no cover - # There is no sane scenario where this should happen. But the - # parser should not raise an exception. Therefore we return the - # original value. - logger.warning( # pragma: no cover - "Failed to parse the result for %s, its not part of the node tree.", - annotated_value.path, - ) - return annotated_value # pragma: no cover - if enum is not None and annotated_value.value is not None: - try: - annotated_value.value = enum(annotated_value.value) - except ValueError: # pragma: no cover - # The value is not part of the enum. This is a critical error - # of the server. But the parser should not raise an exception. - # Therefore we return the original value. - logger.warning( # pragma: no cover - "Failed to parse the %s for %s, the value is not part of the enum.", - annotated_value.value, - annotated_value.path, - ) - return annotated_value # pragma: no cover - return annotated_value - - return default_enum_parser diff --git a/src/labone/nodetree/errors.py b/src/labone/nodetree/errors.py deleted file mode 100644 index eae49fb..0000000 --- a/src/labone/nodetree/errors.py +++ /dev/null @@ -1,23 +0,0 @@ -"""LabOne Error classes associated with the nodetree.""" - -from labone.errors import LabOneError - - -class LabOneNodetreeError(LabOneError): - """Base class for all LabOne nodetree errors.""" - - -class LabOneInvalidPathError(LabOneNodetreeError): - """Raised when the path is invalid.""" - - -class LabOneInappropriateNodeTypeError(LabOneNodetreeError): - """Raised when a node is not of the expected type.""" - - -class LabOneNoEnumError(LabOneNodetreeError): - """Raised when it is tried to get a enum for a node which is not an enum.""" - - -class LabOneNotImplementedError(LabOneNodetreeError, NotImplementedError): - """Raised when a method is not implemented.""" diff --git a/src/labone/nodetree/helper.py b/src/labone/nodetree/helper.py deleted file mode 100644 index 963e48a..0000000 --- a/src/labone/nodetree/helper.py +++ /dev/null @@ -1,236 +0,0 @@ -"""Internal helper functions for node-tree. - -Managing path-representation and structure of path-collections. -""" - -from __future__ import annotations - -import keyword -import typing as t - -from labone.core import AnnotatedValue, ListNodesFlags, ListNodesInfoFlags -from labone.core.helper import LabOneNodePath - -if t.TYPE_CHECKING: - from typing_extensions import TypeAlias - - from labone.core.session import NodeInfo - from labone.core.subscription import QueueProtocol - - -NormalizedPathSegment: TypeAlias = str -PATH_SEPERATOR = "/" -WILDCARD = "*" - -T = t.TypeVar("T") - - -TreeProp = dict[LabOneNodePath, T] - - -class NestedDict(t.Protocol[T]): # type: ignore[misc] - """Protocol representing a nested dictionary structure.""" - - def __getitem__(self, key: str) -> T | NestedDict[T]: ... - - # retyping dict method, because inheriting from non-protocal is prohibited - def __setitem__(self, key: str, item: T | NestedDict) -> None: ... - - def keys(self) -> t.KeysView[str]: - """...""" - - def items(self) -> t.ItemsView[str, T | NestedDict[T]]: - """...""" - - def __iter__(self) -> t.Iterator[str]: - """...""" - - -FlatPathDict: TypeAlias = dict[ - NormalizedPathSegment, - list[list[NormalizedPathSegment]], -] - - -class Session(t.Protocol): - """Interface for communication with a data-server.""" - - def list_nodes( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE, - ) -> t.Awaitable[list[LabOneNodePath]]: - """List the nodes found at a given path.""" - ... - - def list_nodes_info( - self, - path: LabOneNodePath = "", - *, - flags: ListNodesInfoFlags | int = ListNodesInfoFlags.ALL, - ) -> t.Awaitable[dict[LabOneNodePath, NodeInfo]]: - """List the nodes and their information found at a given path.""" - ... - - def set(self, value: AnnotatedValue) -> t.Awaitable[AnnotatedValue]: - """Set the value of a node.""" - ... - - def set_with_expression( - self, - value: AnnotatedValue, - ) -> t.Awaitable[list[AnnotatedValue]]: - """Set the value of all nodes matching the path expression.""" - ... - - def get( - self, - path: LabOneNodePath, - ) -> t.Awaitable[AnnotatedValue]: - """Get the value of a node.""" - ... - - def get_with_expression( - self, - path_expression: LabOneNodePath, - flags: ListNodesFlags | int = ListNodesFlags.ABSOLUTE - | ListNodesFlags.RECURSIVE - | ListNodesFlags.LEAVES_ONLY - | ListNodesFlags.EXCLUDE_STREAMING - | ListNodesFlags.GET_ONLY, - ) -> t.Awaitable[list[AnnotatedValue]]: - """Get the value of all nodes matching the path expression.""" - ... - - async def subscribe( - self, - path: LabOneNodePath, - *, - parser_callback: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, - queue_type: type[QueueProtocol], - get_initial_value: bool, - ) -> QueueProtocol: - """Register a new subscription to a node.""" - ... - - async def wait_for_state_change( - self, - path: LabOneNodePath, - value: int, - *, - invert: bool = False, - ) -> None: - """Waits until the node has the expected state/value.""" - ... - - -class UndefinedStructure(dict): - """Representing the state that the substructure of a node is not known.""" - - -def join_path(path_segments: t.Iterable[NormalizedPathSegment]) -> LabOneNodePath: - """Join path in a well-defined manner. - - Node that a leading seperator is always added. - - Args: - path_segments: Segments to join. - - Returns: - One joint path. - """ - return PATH_SEPERATOR + PATH_SEPERATOR.join(path_segments) - - -def split_path(path: LabOneNodePath) -> list[NormalizedPathSegment]: - """Split path in a well-defined manner. - - A leading seperator is ignored. - - Args: - path: Path to be split. - - Returns: - Segments of which the path consists. - """ - if path == "/": - return [] - path_segments = path.split(PATH_SEPERATOR) - first_item_index = 0 - if path_segments[0] == "": - # this happens if the path started with '/' - # ignore leading '/' - first_item_index = 1 - return path_segments[first_item_index:] - - -def normalize_path_segment(path_segment: str | int) -> NormalizedPathSegment: - """Bring segment into a standard form. - - - no integers, but only strings - - '_' in reserved names ignored - - Args: - path_segment: Segment of a path to be normalized. - - Returns: - The segment, following the described formatting standards. - """ - return str(path_segment).lower().rstrip("_") - - -def pythonify_path_segment(path_segment: NormalizedPathSegment) -> str: - """Try to bring segment into a form, which can be used as an attribute for a node. - - - add '_' at end of reserved names - - make clear that numbers should be used with indexing e.g. [0] instead of node.0 - - Args: - path_segment: Segment in the usual representation. - - Returns: - Path segment in pythonic representation. - """ - if keyword.iskeyword(path_segment): - return path_segment + "_" - - return path_segment - - -def build_prefix_dict( - suffix_list: list[list[NormalizedPathSegment]], -) -> FlatPathDict: - """Builds a dictionary prefix-to-remainder from list of paths. - - Build a dictionary of first-segment to list of path-suffixes - like {prefix1: [suffix1, suffix2], prefix2: [], prefix3: [suffix3]} - where all suffixes are guaranteed to be non-empty. - - Note: - This function assumes following preconditions: - - No empty lists in main list, checkable with - >>> for l in suffix_list: - >>> assert l - - No two paths are identical, so each member of suffix_list is unique. - - After this function, for each value of the resulting dictionary, - the same criteria apply as postconditions. - - Args: - suffix_list: list of paths (is split form) - - Returns: - Dictionary of first-segment -> list of path-suffixes starting with it - """ - result: FlatPathDict = {} - for path in suffix_list: - first_segment = path[0] - path_suffix = path[1:] - - if first_segment not in result: - result[first_segment] = [] - if path_suffix: - result[first_segment].append(path_suffix) - - return result diff --git a/src/labone/nodetree/node.py b/src/labone/nodetree/node.py deleted file mode 100644 index 739f5f2..0000000 --- a/src/labone/nodetree/node.py +++ /dev/null @@ -1,1533 +0,0 @@ -"""LabOne object-based node-tree implementation. - -This module contains the core functionality of the node-tree. It provides -the classes for the different types of nodes, the node info and the -NodeTreeManager, which is the interface to the server. -""" - -from __future__ import annotations - -import asyncio -import typing as t -import warnings -import weakref -from abc import ABC, abstractmethod -from functools import cached_property - -from labone.core.subscription import DataQueue -from labone.core.value import AnnotatedValue, Value -from labone.node_info import NodeInfo -from labone.nodetree.errors import ( - LabOneInappropriateNodeTypeError, - LabOneInvalidPathError, - LabOneNotImplementedError, -) -from labone.nodetree.helper import ( - WILDCARD, - NestedDict, - NormalizedPathSegment, - Session, - TreeProp, - UndefinedStructure, - build_prefix_dict, - join_path, - normalize_path_segment, - pythonify_path_segment, - split_path, -) - -if t.TYPE_CHECKING: - from labone.core.helper import LabOneNodePath - from labone.core.session import NodeInfo as NodeInfoType - from labone.core.subscription import QueueProtocol - from labone.nodetree.enum import NodeEnum - -T = t.TypeVar("T") - - -class NodeTreeManager: - """Managing relation of a node tree and its underlying session. - - Acts as a factory for nodes and holds the reference to the underlying - session. I holds a nested dictionary representing the structure of the - node tree. - - Args: - session: Session from which the node tree data is retrieved. - path_to_info: Result of former server-call, representing structure of - tree plus information about each node. - parser: Function, which is used to parse incoming values. It may do this - in a path-specific manner. - hide_kernel_prefix: Enter a trivial first path-segment automatically. - E.g. having the result of this function in a variable `tree` - `tree.debug.info` can be used instead of `tree.device1234.debug.info`. - Setting this option makes working with the tree easier. - """ - - def __init__( - self, - *, - session: Session, - path_to_info: dict[LabOneNodePath, NodeInfoType], - parser: t.Callable[[AnnotatedValue], AnnotatedValue], - hide_kernel_prefix: bool = True, - ): - self._session = session - self.path_to_info = path_to_info - self._parser = parser - self._hide_kernel_prefix = hide_kernel_prefix - - self._cache_path_segments_to_node: weakref.WeakValueDictionary[ - tuple[NormalizedPathSegment, ...], - Node, - ] = weakref.WeakValueDictionary() - self._cache_find_substructure: (dict)[ - tuple[NormalizedPathSegment, ...], - NestedDict[list[list[NormalizedPathSegment]] | dict], - ] = {} - - self._root_prefix: tuple[str, ...] = () - self._paths_as_segments: list[list[NormalizedPathSegment]] = [] - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - self.add_nodes_with_info(path_to_info) - - def add_nodes_with_info( - self, - path_to_info: dict[LabOneNodePath, NodeInfoType], - ) -> None: - """Add new nodes to the tree. - - This function will not check that the added nodes are valid, - so it should be used with care. Normally, it is not necessary to - add nodes manually, because they will be acquired automatically - on construction. - - Warning: - The root prefix may change, if the prefix was hidden before but is now not - unique any more. The access to the nodes in the tree may change. - This will trigger a warning. - - Args: - path_to_info: Describing the new paths and the associated - information. - """ - # dict prevents duplicates - self.path_to_info.update(path_to_info) - - self._paths_as_segments = [split_path(path) for path in self.path_to_info] - - # already explored structure is forgotten and will be re-explored on demand. - # this is necessary, because the new nodes might be in the middle of the tree - self._cache_find_substructure = {} - - # type casting to allow assignment of more general type into the dict - self._partially_explored_structure: NestedDict[ - list[list[NormalizedPathSegment]] | dict - ] = build_prefix_dict( - self._paths_as_segments, - ) # type: ignore[assignment] - - # root prefix may change, if the prefix was hidden before but is now not - # unique any more - old_root_prefix = self._root_prefix - has_common_prefix = len(self._partially_explored_structure.keys()) == 1 - - if not self._hide_kernel_prefix or not has_common_prefix: - self._root_prefix = () - else: - common_prefix = next(iter(self._partially_explored_structure.keys())) - self._root_prefix = (common_prefix,) - - if self._root_prefix != old_root_prefix: - msg = ( - f"Root prefix changed from '{join_path(old_root_prefix)}'" - f" to '{join_path(self._root_prefix)}'. " - f"This means in order to index the same node as before, " - f"use {'.'.join(['root',*old_root_prefix,'(...)'])} " - f"instead of {'.'.join(['root',*self._root_prefix,'(...)'])}." - ) - warnings.warn(msg, Warning, stacklevel=1) - - def add_nodes(self, paths: list[LabOneNodePath]) -> None: - """Add new nodes to the tree. - - This function will not check that the added nodes are valid, - so it should be used with care. Normally, it is not necessary to - add nodes manually, because they will be acquired automatically - on construction. - - Warning: - The root prefix may change, if the prefix was hidden before but is now not - unique any more. The access to the nodes in the tree may change. - This will trigger a warning. - - Args: - paths: Paths of the new nodes. - """ - self.add_nodes_with_info( - {p: NodeInfo.plain_default_info(path=p) for p in paths}, - ) - - def find_substructure( - self, - path_segments: tuple[NormalizedPathSegment, ...], - ) -> NestedDict[list[list[NormalizedPathSegment]] | dict]: - """Find children and explore the node structure lazily as needed. - - This function must be used by all nodes to find their children. - This ensures efficient caching of the structure and avoids - unnecessary lookups. - - Args: - path_segments: Segments describing the path, for which the children - should be found. - - Returns: - Nested dictionary, representing the structure of the children. - - Raises: - LabOneInvalidPathError: If the path segments are invalid. - """ - if path_segments in self._cache_find_substructure: - return self._cache_find_substructure[path_segments] - - # base case - if not path_segments: - # this is not worth adding to the cache, so just return it - return self._partially_explored_structure - - # solving recursively, taking advantage of caching - # makes usual indexing of lower nodes O(1) - sub_solution = self.find_substructure(path_segments[:-1]) - segment = path_segments[-1] - - try: - sub_solution[segment] - except KeyError as e: - if segment == WILDCARD: - msg = ( - f"Cannot find structure for a path containing a wildcard," - f"however, `find_structure` was called with " - f"{join_path(path_segments)}" - ) - raise LabOneInvalidPathError(msg) from e - - msg = ( - f"Path '{join_path(path_segments)}' is illegal, because '{segment}' " - f"is not a viable extension of '{join_path(path_segments[:-1])}'. " - f"It does not correspond to any existing node." - f"\nViable extensions would be {list(sub_solution.keys())}" - ) - raise LabOneInvalidPathError(msg) from e - - # explore structure deeper on demand - # the path not being cached implies this is the first time - # this substructure is explored. - # So we know it is a list of paths, which we now build into a prefix dict. - sub_solution[segment] = build_prefix_dict( - sub_solution[segment], # type: ignore[arg-type] - ) - - result: NestedDict[list[list[NormalizedPathSegment]] | dict] = sub_solution[ - segment - ] # type: ignore[assignment] - self._cache_find_substructure[path_segments] = result - return result - - def raw_path_to_node( - self, - path: LabOneNodePath, - ) -> Node: - """Convert a LabOne node path into a node object. - - Caches node-objects and enforces thereby a singleton pattern. - Only one node-object per path. - - Args: - path: Path, for which a node object should be provided. - - Returns: - Node-object corresponding to the path. - - Raises: - LabOneInvalidPathError: - In no subtree_paths are given and the path is invalid. - """ - return self.path_segments_to_node(tuple(split_path(path))) - - def path_segments_to_node( - self, - path_segments: tuple[NormalizedPathSegment, ...], - ) -> Node: - """Convert a tuple of path-segments into a node object. - - Caches node-objects and enforces thereby a singleton pattern. - Only one node-object per path. - - Args: - path_segments: Segments describing the path, for which a node object - should be provided. - - Returns: - Node-object corresponding to the path. - - Raises: - LabOneInvalidPathError: In no subtree_paths are given and the path - is invalid. - """ - if path_segments in self._cache_path_segments_to_node: - return self._cache_path_segments_to_node[path_segments] - - result = Node.build(tree_manager=self, path_segments=path_segments) - self._cache_path_segments_to_node[path_segments] = result - return result - - def __hash__(self) -> int: - return id(self) - - @property - def parser(self) -> t.Callable[[AnnotatedValue], AnnotatedValue]: - """Parser for values received from the server.""" - return self._parser - - @property - def session(self) -> Session: - """Underlying Session to the server.""" - return self._session - - @property - def root(self) -> Node: - """Create the root-node of the tree. - - Depending on the hide_kelnel_prefix-setting of the - NodeTreeManager, the root will either be '/' or - the directly entered device, like '/dev1234' - - Returns: - Root-node of the tree. - """ - return self.path_segments_to_node(self._root_prefix) - - -class MetaNode(ABC): - """Basic functionality of all nodes. - - This class provides common behavior for all node classes, both normal nodes - and result nodes. This includes the traversal of the tree and the generation - of sub-nodes. - - Args: - tree_manager: Interface managing the node-tree and the corresponding - session. - path_segments: A tuple describing the path. - subtree_paths: Structure, defining which sub-nodes exist. - May contain a Nested dictionary or a list of paths. If a list is - passed, a prefix-to-suffix-dictionary will be created out of it. - """ - - def __init__( - self, - *, - tree_manager: NodeTreeManager, - path_segments: tuple[NormalizedPathSegment, ...], - subtree_paths: ( - NestedDict[list[list[NormalizedPathSegment]] | dict] | UndefinedStructure - ), - ): - self._tree_manager = tree_manager - self._path_segments = path_segments - self._subtree_paths = subtree_paths - - @abstractmethod - def __getattr__(self, next_segment: str) -> MetaNode | AnnotatedValue: - """Access sub-node or value. - - ```python - node.demods # where 'demods' is the next segment to enter - ``` - - Args: - next_segment: Segment, with which the current path should be extended - - Returns: - New node-object, representing the extended path, or plain value, - if came to a leaf-node of a result. - """ - ... - - @abstractmethod - def __getitem__(self, path_extension: str | int) -> MetaNode | AnnotatedValue: - """Go one or multiple levels deeper into the tree structure. - - The primary purpose of this operator is to allow accessing subnodes that - are numbers (e.g. /0, /1, ...). The attribute access operator (dot) does - not allow this, because numbers are not valid identifiers in Python. - - However, this operator can deal with a number of different scenarios: - - - simple path extensions: - - ```python - node['deeper'] - ``` - - - path indexing: - - ```python - node['deeper/path'] - ``` - - - numeric indexing: - - ```python - node[0] - ``` - - - wildcards (placeholder for multiple path-extensions): - - ```python - node['*'] - ``` - - - combinations of all that: - - ```python - node['deeper/*/path/0'] - ``` - - - All these implementations are equivalent: - - ```python - node['mds/groups/0'] - node['mds']['groups'][0] - node.mds.groups[0] - ``` - - Args: - path_extension: path, number or wildcard. - - Returns: New node-object, representing the extended path, or plain value, - if came to a leaf-node. - """ - ... - - def __iter__(self) -> t.Iterator[MetaNode | AnnotatedValue]: - """Iterating through direct sub-nodes. - - The paths are traversed in a sorted manner, providing a clear order. - This is particularly useful when iterating through numbered child nodes, - such as /0, /1, ... or alphabetically sorted child nodes. - - Returns: - Sub-nodes iterator. - """ - for segment in sorted(self._subtree_paths.keys()): - yield self[segment] - - def __len__(self) -> int: - """Number of direct sub-nodes.""" - return len(self._subtree_paths.keys()) - - def __repr__(self) -> str: - return f"{self.__class__.__name__}({self!s})" - - def __str__(self) -> str: - return self.path - - def __lt__(self, other: object) -> bool: - if not isinstance(other, MetaNode): # pragma: no cover - msg = ( - f"'<' not supported between instances of " - f"'{type(self)}' and '{type(other)}'" - ) # pragma: no cover - raise TypeError(msg) # pragma: no cover - return self.path < other.path - - def is_child_node( - self, - child_node: MetaNode | t.Sequence[NormalizedPathSegment], - ) -> bool: - """Checks if a node is a direct child node of this node. - - Children of children (etc.) will not be counted as direct children. - The node itself is also not counted as its child. - - Args: - child_node: Potential child node. - - Returns: - Boolean if passed node is a child node. - """ - path_segments = ( - child_node.path_segments - if isinstance(child_node, MetaNode) - else tuple(child_node) - ) - - return ( - len(path_segments) == len(self.path_segments) + 1 - and path_segments[: len(self.path_segments)] == self.path_segments - and path_segments[-1] in self._subtree_paths - ) - - @property - def tree_manager(self) -> NodeTreeManager: - """Get interface managing the node-tree and the corresponding session.""" - return self._tree_manager - - @property - def path(self) -> LabOneNodePath: - """The LabOne node path, this node corresponds to.""" - return join_path(self._path_segments) - - @property - def path_segments(self) -> tuple[NormalizedPathSegment, ...]: - """The underlying segments of the path, this node corresponds to.""" - return self._path_segments - - @property - def raw_tree(self) -> tuple[NormalizedPathSegment, ...]: - """The underlying segments of the path, this node corresponds to. - - Deprecated: use 'path_segments' instead. - """ - return self.path_segments - - @property - def subtree_paths( - self, - ) -> NestedDict[list[list[NormalizedPathSegment]] | dict] | UndefinedStructure: - """Structure defining which sub-nodes exist.""" - return self._subtree_paths - - @property - def children(self) -> list[str]: - """List of direct sub-node names.""" - return [pythonify_path_segment(p) for p in self._subtree_paths] - - -class ResultNode(MetaNode): - """Representing values of a get-request in form of a tree. - - When issuing a get-request on a partial or wildcard node, the server will - return a list of AnnotatedValues for every leaf-node in the subtree. - This class adds the same object oriented interface as the normal nodes, when - hitting a leaf node the result is returned. - - This allows to work with the results of a get-request in the same way as - with the normal nodes. If needed one can still iterate through all the - results. `results` will return an iterator over all results, not only the - direct children. - - Args: - tree_manager: - Interface managing the node-tree and the corresponding session. - path_segments: A tuple describing the path. - subtree_paths: - Structure, defining which sub-nodes exist. - value_structure: - Storage of the values at the leaf-nodes. They will be - returned once the tree is traversed. - timestamp: - The time the results where created. - """ - - def __init__( - self, - tree_manager: NodeTreeManager, - path_segments: tuple[NormalizedPathSegment, ...], - subtree_paths: NestedDict[list[list[NormalizedPathSegment]] | dict], - value_structure: TreeProp, - timestamp: int, - ): - super().__init__( - tree_manager=tree_manager, - path_segments=path_segments, - subtree_paths=subtree_paths, - ) - self._value_structure = value_structure - self._timestamp = timestamp - - def __getattr__(self, next_segment: str) -> ResultNode | AnnotatedValue: - """Access sub-node or value. - - Args: - next_segment: Segment, with which the current path should be extended. - - Returns: - New node-object, representing the extended path, or plain value, - if came to a leaf-node. - """ - return self.try_generate_subnode(normalize_path_segment(next_segment)) - - def __getitem__(self, path_extension: str | int) -> ResultNode | AnnotatedValue: - """Go one or multiple levels deeper into the tree structure. - - The primary purpose of this operator is to allow accessing subnodes that - are numbers (e.g. /0, /1, ...). The attribute access operator (dot) does - not allow this, because numbers are not valid identifiers in Python. - - However, this operator can deal with a number of different scenarios: - - - simple path extensions: - >>> node['deeper'] - - path indexing: - >>> node['deeper/path'] - - numeric indexing: - >>> node[0] - - wildcards (placeholder for multiple path-extensions): - >>> node['*'] - - combinations of all that: - >>> node['deeper/*/path/0'] - - Args: - path_extension: path, number or wildcard. - - Returns: New node-object, representing the extended path, or plain value, - if came to a leaf-node. - - Raises: - LabOneInvalidPathError: If path is invalid. - - """ - relative_path_segments = split_path(str(path_extension)) - current_node = self - try: - for path_segment in relative_path_segments: - current_node = current_node.try_generate_subnode( - normalize_path_segment(path_segment), - ) # type: ignore[assignment] - - except AttributeError as e: - msg = ( - f"Path {join_path((*self.path_segments,*relative_path_segments))} " - f"is invalid, because {current_node.path} " - f"is already a leaf-node." - ) - raise LabOneInvalidPathError(msg) from e - - return current_node - - def __dir__(self) -> t.Iterable[str]: - """Show valid subtree-extensions in hints. - - Returns: - Iterator of valid dot-access identifier. - - """ - return self.children + list(super().__dir__()) - - def __contains__(self, item: str | int | ResultNode | AnnotatedValue) -> bool: - """Checks if a path-segment or node is an immediate sub-node of this one.""" - if isinstance(item, ResultNode): - return self.is_child_node(item) - if isinstance(item, AnnotatedValue): - return self.is_child_node(split_path(item.path)) - return normalize_path_segment(item) in self._subtree_paths - - def __call__(self, *_, **__) -> None: - """Not supported on the Result node. - - Showing an error to express result-nodes can't be get/set. - - Raises: - LabOneInappropriateNodeTypeError: Always. - """ - msg = ( - "Trying to get/set a result node. This is not possible, because " - "result nodes represents values of a former get-request. " - "To interact with a device, make sure to operate on normal nodes." - ) - raise LabOneInappropriateNodeTypeError(msg) - - def __str__(self) -> str: - value_dict = { - path: self._value_structure[path].value - for path in self._value_structure - if path.startswith(self.path) - } - return ( - f"{self.__class__.__name__}('{self.path}', time: #{self._timestamp}, " - f"data: {value_dict})" - ) - - def __repr__(self) -> str: - return f"{self!s} -> {[str(k) for k in self._subtree_paths]}" - - def results(self) -> t.Iterator[AnnotatedValue]: - """Iterating through all results. - - The difference to the normal iterator is that this iterator will iterate - through all results, not only the direct children. This is useful when - iterating through results of a wildcard or partial node. - - Returns: - Results iterator. - """ - for path, value in self._value_structure.items(): - if path.startswith(self.path): - yield value - - def try_generate_subnode( - self, - next_path_segment: NormalizedPathSegment, - ) -> ResultNode | AnnotatedValue: - """Provides nodes for the extended path or the original values for leafs. - - Will fail if the resulting Path is ill-formed. - - Args: - next_path_segment: Segment, with which the current path should be - extended. - - Returns: - New node-object, representing the extended path, or plain value, - if came to a leaf-node. - - Raises: - LabOneInvalidPathError: If the extension leads to an invalid path or if it - is tried to use wildcards in ResultNodes. - """ - extended_path = (*self._path_segments, next_path_segment) - try: - next_subtree = self.subtree_paths[next_path_segment] - except KeyError as e: - if next_path_segment == WILDCARD: - msg = ( - f"Wildcards '*' are not allowed in a tree representing " - f"measurement results. However, it was tried to extend {self.path} " - f"with a wildcard." - ) - raise LabOneInvalidPathError(msg) from e - - # this call checks whether the path is in the nodetree itself - # if this is not the case, this line will raise the appropriate error - # otherwise, the path is in the tree, but not captured in this particular - # result node. - self.tree_manager.find_substructure(extended_path) - - msg = ( - f"Path '{join_path(extended_path)}' is not captured in this " - "result node. It corresponds to an existing node, but the " - "request producing this result collection was make such that " - "this result is not included. Change either the request or " - "access a different node." - ) - raise LabOneInvalidPathError(msg) from e - - # exploring deeper tree stucture if it is not aleady known - if isinstance(next_subtree, list): # pragma: no cover - next_subtree = build_prefix_dict(next_subtree) - - deeper_node = ResultNode( - tree_manager=self.tree_manager, - path_segments=extended_path, - subtree_paths=next_subtree, # type: ignore[arg-type] - value_structure=self._value_structure, - timestamp=self._timestamp, - ) - - if not next_subtree: - # give value instead of subnode if already at a leaf - # generate hypothetical node in order to apply normal behavior - return self._value_structure[deeper_node.path] - return deeper_node - - -class Node(MetaNode, ABC): - """Single node in the object-based node tree. - - The child nodes of each node can be accessed either by attribute or by item. - - The core functionality of each node is the overloaded call operator. - Making a call gets the value(s) for that node. Passing a value to the call - operator will set that value to the node on the device. Calling a node that - is not a leaf (wildcard or partial node) will return/set the value on every - node that matches it. - - Warning: - Setting a value to a non-leaf node will try to set the value of all - nodes that matches that node. It should therefore be used with great care - to avoid unintentional changes. - - In addition to the call operator every node has a `wait_for_state_change` - function that can be used to wait for a node to change state. - - Leaf nodes also have a `subscribe` function that can be used to subscribe to - changes in the node. For more information on the subscription functionality - see the documentation of the `subscribe` function or the `DataQueue` class. - """ - - def __getattr__(self, next_segment: str) -> Node: - """Access sub-node. - - ```python - node.demods # where 'demods' is the next segment to enter - ``` - - Args: - next_segment: Segment, with which the current path should be extended - - Returns: - New node-object, representing the extended path, or plain value, - if came to a leaf-node. - """ - return self.try_generate_subnode(normalize_path_segment(next_segment)) - - def __getitem__(self, path_extension: str | int) -> Node: - """Go one or multiple levels deeper into the tree structure. - - The primary purpose of this operator is to allow accessing subnodes that - are numbers (e.g. /0, /1, ...). The attribute access operator (dot) does - not allow this, because numbers are not valid identifiers in Python. - - However, this operator can deal with a number of different scenarios: - - - simple path extensions: - - ```python - node['deeper'] - ``` - - - path indexing: - - ```python - node['deeper/path'] - ``` - - - numeric indexing: - - ```python - node[0] - ``` - - - wildcards (placeholder for multiple path-extensions): - - ```python - node['*'] - ``` - - - combinations of all that: - - ```python - node['deeper/*/path/0'] - ``` - - - All these implementations are equivalent: - - ```python - node['mds/groups/0'] - node['mds']['groups'][0] - node.mds.groups[0] - ``` - - Args: - path_extension: path, number or wildcard. - - Returns: New node-object, representing the extended path - """ - relative_path_segments = split_path(str(path_extension)) - current_node = self - - for path_segment in relative_path_segments: - current_node = current_node.try_generate_subnode( - normalize_path_segment(path_segment), - ) - - return current_node - - def __eq__(self, other: object) -> bool: - return ( - other.__class__ == self.__class__ - and self.path_segments == other.path_segments # type:ignore[attr-defined] - and self.tree_manager == other.tree_manager # type:ignore[attr-defined] - ) - - def __hash__(self) -> int: - return hash((self.path, hash(self.__class__), hash(self._tree_manager))) - - def __dir__(self) -> t.Iterable[str]: - """Show valid subtree-extensions in hints. - - Returns: - Iterator of valid dot-access identifier. - - """ - return self.children + list(super().__dir__()) - - def __contains__(self, item: str | int | Node) -> bool: - """Checks if a path-segment or node is an immediate sub-node of this one. - - Args: - item: To be checked this is among the child-nodes. Can be called with - either a node, or a plain identifier/number, which would be used - to identify the child. - - Returns: - If item describes/is a valid subnode. - - Example: - >>> if "debug" in node: # implicit call to __contains__ - >>> print(node["debug"]) # if contained, indexing is valid - >>> print(node.debug) # ... as well as dot-access - - Nodes can also be used as arguments. In this example, it is asserted - that all subnodes are "contained" in the node. - >>> for subnode in node: # implicit call to __iter__ - >>> assert subnode in node # implicit call to __contains__ - """ - if isinstance(item, Node): - return self.is_child_node(item) - return normalize_path_segment(item) in self._subtree_paths - - def __call__( - self, - value: Value | None = None, - ) -> t.Awaitable[AnnotatedValue | ResultNode]: - """Call with or without a value for setting/getting the node. - - Args: - value: optional value, which is set to the node. If it is omitted, - a get request is triggered instead. - - Returns: - The current value of the node is returned either way. If a set-request - is triggered, the new value will be given back. In case of non-leaf - nodes, a node-structure representing the results of all sub-paths is - returned. - - Raises: - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not writeable or readable. - UnimplementedError: If the get or set request is not supported - by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - if value is None: - return self._get() - - return self._set(value) - - @abstractmethod - def _get( - self, - ) -> t.Awaitable[AnnotatedValue | ResultNode]: ... - - @abstractmethod - def _set( - self, - value: Value, - ) -> t.Awaitable[AnnotatedValue | ResultNode]: ... - - @classmethod - def build( - cls, - *, - tree_manager: NodeTreeManager, - path_segments: tuple[NormalizedPathSegment, ...], - ) -> Node: - """Construct a matching subnode. - - Useful for creating a node, not necessarily knowing what kind of node it - should be. This factory method will choose the correct one. - - Args: - tree_manager: Interface managing the node-tree and the corresponding - session. - path_segments: A tuple describing the path. - - Returns: - A node of the appropriate type. - """ - contains_wildcards = any(segment == WILDCARD for segment in path_segments) - if contains_wildcards: - subtree_paths: ( - NestedDict[list[list[NormalizedPathSegment]] | dict] - | UndefinedStructure - ) = UndefinedStructure() - else: - subtree_paths = tree_manager.find_substructure(path_segments) - - is_leaf = not bool(subtree_paths) - - if contains_wildcards: - return WildcardNode( - tree_manager=tree_manager, - path_segments=path_segments, - subtree_paths=subtree_paths, - ) - - if is_leaf: - return LeafNode( - tree_manager=tree_manager, - path_segments=path_segments, - subtree_paths=subtree_paths, - ) - - return PartialNode( - tree_manager=tree_manager, - path_segments=path_segments, - subtree_paths=subtree_paths, - ) - - @abstractmethod - def try_generate_subnode( - self, - next_path_segment: NormalizedPathSegment, - ) -> Node: - """Provides nodes for the extended path or the original values for leafs. - - Args: - next_path_segment: Segment, with which the current path should be extended. - - Returns: - New node-object, representing the extended path. - - """ - ... - - @abstractmethod - async def wait_for_state_change( - self, - value: int | NodeEnum, - *, - invert: bool = False, - ) -> None: - """Waits until the node has the expected state/value. - - Warning: - Only supports integer and keyword nodes. (The value can either be the value - or its corresponding enum value as string) - - Args: - value: Expected value of the node. - invert: Instead of waiting for the value, the function will wait for - any value except the passed value. (default = False) - Useful when waiting for value to change from existing one. - """ - ... - - @t.overload - async def subscribe( - self, - *, - get_initial_value: bool = False, - **kwargs, - ) -> DataQueue: ... - - @t.overload - async def subscribe( - self, - queue_type: type[QueueProtocol], - *, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol: ... - - @abstractmethod - async def subscribe( - self, - queue_type: type[QueueProtocol] | None = None, - *, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol | DataQueue: - """Subscribe to a node. - - Subscribing to a node will cause the server to send updates that happen - to the node to the client. The updates are sent to the client automatically - without any further interaction needed. Every update will be put into the - queue, which can be used to receive the updates. - - Warning: - Currently one can only subscribe to nodes that are leaf-nodes. - - Note: - A node can be subscribed multiple times. Each subscription will - create a new queue. The queues are independent of each other. It is - however recommended to only subscribe to a node once and then fork - the queue into multiple independent queues if needed. This will - prevent unnecessary network traffic. - - Note: - There is no explicit unsubscribe function. The subscription will - automatically be cancelled when the queue is closed. This will - happen when the queue is garbage collected or when the queue is - closed manually. - - Args: - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - get_initial_value: If True, the initial value of the node is - is placed in the queue. (default=False) - kwargs: extra keyword arguments which are passed to the data-server - to further configure the subscription. - - Returns: - A DataQueue, which can be used to receive any changes to the node in a - flexible manner. - """ - - @property - def root(self) -> Node: - """Providing root node. - - Depending on the hide_kelnel_prefix-setting of the - NodeTreeManager, the root will either be '/' or - the directly entered device, like '/dev1234' - - Returns: - Root of the tree structure, this node is part of. - """ - return self.tree_manager.root - - -class LeafNode(Node): - """Node corresponding to a leaf in the path-structure.""" - - async def _value_postprocessing( - self, - result: t.Awaitable[AnnotatedValue], - ) -> AnnotatedValue: - return self._tree_manager.parser(await result) - - def _get(self) -> t.Awaitable[AnnotatedValue]: - """Get the value of the node. - - Returns: - The current value of the node. - - Raises: - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not readable. - UnimplementedError: If the get request is not supported - by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - return self._value_postprocessing(self._tree_manager.session.get(self.path)) - - def _set( - self, - value: Value, - ) -> t.Awaitable[AnnotatedValue]: - """Set the value of the node. - - Args: - value: Value, which should be set to the node. - - Returns: - The new value of the node. - - Raises: - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not settable. - UnimplementedError: If the set request is not - supported by the server. - InternalError: If an unexpected internal error occurs. - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - return self._value_postprocessing( - self._tree_manager.session.set(AnnotatedValue(value=value, path=self.path)), - ) - - def try_generate_subnode( - self, - next_path_segment: NormalizedPathSegment, - ) -> Node: - """Provides nodes for the extended path or the original values for leafs. - - Args: - next_path_segment: Segment, with which the current path should be - extended. - - Returns: - New node-object, representing the extended path. - - Raises: - LabOneInvalidPathError: Always, because extending leaf-paths is illegal. - - """ - msg = ( - f"Node '{self.path}' cannot be extended with " - f"'/{next_path_segment}' because it is a leaf node." - ) - raise LabOneInvalidPathError(msg) - - @t.overload - async def subscribe( - self, - *, - get_initial_value: bool = False, - **kwargs, - ) -> DataQueue: ... - - @t.overload - async def subscribe( - self, - queue_type: type[QueueProtocol], - *, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol: ... - - async def subscribe( - self, - queue_type: type[QueueProtocol] | None = None, - *, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol | DataQueue: - """Subscribe to a node. - - Subscribing to a node will cause the server to send updates that happen - to the node to the client. The updates are sent to the client automatically - without any further interaction needed. Every update will be put into the - queue, which can be used to receive the updates. - - Note: - A node can be subscribed multiple times. Each subscription will - create a new queue. The queues are independent of each other. It is - however recommended to only subscribe to a node once and then fork - the queue into multiple independent queues if needed. This will - prevent unnecessary network traffic. - - Note: - There is no explicit unsubscribe function. The subscription will - automatically be cancelled when the queue is closed. This will - happen when the queue is garbage collected or when the queue is - closed manually. - - Args: - queue_type: The type of the queue to be returned. This can be - any class matching the DataQueue interface. Only needed if the - default DataQueue class is not sufficient. If None is passed - the default DataQueue class is used. (default=None) - - get_initial_value: If True, the initial value of the node is - is placed in the queue. (default=False) - - kwargs: extra keyword arguments which are passed to the data-server - to further configure the subscription. - - Returns: - A DataQueue, which can be used to receive any changes to the node in a - flexible manner. - """ - return await self._tree_manager.session.subscribe( - self.path, - parser_callback=self._tree_manager.parser, - queue_type=queue_type or DataQueue, - get_initial_value=get_initial_value, - **kwargs, - ) - - async def wait_for_state_change( - self, - value: int | NodeEnum, - *, - invert: bool = False, - ) -> None: - """Waits until the node has the expected state/value. - - Warning: - Only supports integer and keyword nodes. (The value can either be the value - or its corresponding enum value as string) - - Args: - value: Expected value of the node. - invert: Instead of waiting for the value, the function will wait for - any value except the passed value. (default = False) - Useful when waiting for value to change from existing one. - """ - await self._tree_manager.session.wait_for_state_change( - self.path, - value, - invert=invert, - ) - - @cached_property - def node_info(self) -> NodeInfo: - """Additional information about the node.""" - return NodeInfo(self.tree_manager.path_to_info[self.path]) - - -class WildcardOrPartialNode(Node, ABC): - """Common functionality for wildcard and partial nodes.""" - - def _get( - self, - ) -> t.Awaitable[ResultNode]: - """Get the value of the node. - - Raises: - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not readable. - UnimplementedError: If the get with expression request is not - supported by the server. - InternalError: If an unexpected internal error occurs - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - return self._package_response( - self._tree_manager.session.get_with_expression(self.path), - ) - - def _set( - self, - value: Value, - ) -> t.Awaitable[ResultNode]: - """Set the value of the node. - - Args: - value: Value, which should be set to the node. - - Raises: - OverwhelmedError: If the kernel is overwhelmed. - BadRequestError: If the path is not readable. - UnimplementedError: If the get with expression request is not - supported by the server. - InternalError: If an unexpected internal error occurs - LabOneCoreError: If something else went wrong that can not be - mapped to one of the other errors. - """ - return self._package_response( - self._tree_manager.session.set_with_expression( - AnnotatedValue(value=value, path=self.path), - ), - ) - - async def _package_response( - self, - result: t.Awaitable[list[AnnotatedValue]], - ) -> ResultNode: - """Package server-response of wildcard or partial get-request. - - The result node will start to index from the root of the tree: - - >>> result_node = device.demods["*"].sample["*"].x() - >>> result_node.demods[0].sample[0].x - - Of course, only the paths matching the wildcard/partial path - will be available in the result node. - - Args: - result: server-response to get (or set) request - - Returns: - Node-structure, representing the results. - """ - raw_response = await result - timestamp = raw_response[0].timestamp if raw_response else None - path_segments = ( - self.tree_manager.root.path_segments - ) # same starting point as root - - # replace values by enum values and parse if applicable - raw_response = [self._tree_manager.parser(r) for r in raw_response] - - # package into dict - response_dict = { - annotated_value.path: annotated_value for annotated_value in raw_response - } - - subtree_paths = build_prefix_dict( - [ - split_path(ann.path)[len(path_segments) :] # suffix after root path - for ann in raw_response - ], - ) - - return ResultNode( - tree_manager=self.tree_manager, - path_segments=path_segments, - subtree_paths=subtree_paths, # type: ignore[arg-type] - value_structure=response_dict, - timestamp=timestamp if timestamp else 0, - ) - - @t.overload - async def subscribe( - self, - *, - get_initial_value: bool = False, - **kwargs, - ) -> DataQueue: ... - - @t.overload - async def subscribe( - self, - queue_type: type[QueueProtocol], - *, - get_initial_value: bool = False, - **kwargs, - ) -> QueueProtocol: ... - - async def subscribe( - self, - queue_type: type[QueueProtocol] | None = None, # noqa: ARG002 - *, - get_initial_value: bool = False, # noqa: ARG002 - **kwargs, # noqa: ARG002 - ) -> QueueProtocol | DataQueue: - """Subscribe to a node. - - Currently not supported for wildcard and partial nodes. - - Raises: - NotImplementedError: Always. - """ - msg = ( - "Subscribing to paths with wildcards " - "or non-leaf paths is not supported. " - "Subscribe to a leaf node instead " - "and make sure to not use wildcards in the path." - ) - raise LabOneNotImplementedError(msg) - - -class WildcardNode(WildcardOrPartialNode): - """Node corresponding to a path containing one or more wildcards.""" - - def __contains__(self, item: str | int | Node) -> bool: - msg = ( - "Checking if a wildcard-node contains a subnode is not supported." - "For checking if a path is contained in a node, make sure to not use" - "wildcards in the path." - ) - raise LabOneInappropriateNodeTypeError(msg) - - def __iter__(self) -> t.Iterator[Node]: - msg = ( - "Iterating through a wildcard-node is not supported. " - "For iterating through child nodes, make sure to not " - "use wildcards in the path." - ) - raise LabOneInappropriateNodeTypeError(msg) - - def __len__(self) -> int: - msg = ( - "Getting the length of a wildcard-node is not supported." - "For getting the length of a node, make sure to not " - "use wildcards in the path." - ) - raise LabOneInappropriateNodeTypeError(msg) - - def try_generate_subnode( - self, - next_path_segment: NormalizedPathSegment, - ) -> Node: - """Provides nodes for the extended path or the original values for leafs. - - Will never fail, because wildcard-paths are not checked to have valid matchings. - - Args: - next_path_segment: Segment, with which the current path should be - extended. - - Returns: - New node-object, representing the extended path. - - """ - extended_path = (*self._path_segments, next_path_segment) - return self._tree_manager.path_segments_to_node(extended_path) - - async def wait_for_state_change( - self, - value: int | NodeEnum, - *, - invert: bool = False, - ) -> None: - """Waits until all wildcard-associated nodes have the expected state/value. - - Warning: - Only supports integer and keyword nodes. (The value can either be the value - or its corresponding enum value as string) - - Args: - value: Expected value of the node. - invert: Instead of waiting for the value, the function will wait for - any value except the passed value. (default = False) - Useful when waiting for value to change from existing one. - """ - # find paths corresponding to this wildcard-path and put them into nodes - resolved_nodes = [ - self.tree_manager.raw_path_to_node(path) - for path in await self.tree_manager.session.list_nodes(self.path) - ] - await asyncio.gather( - *[ - node.wait_for_state_change(value, invert=invert) - for node in resolved_nodes - ], - ) - - -class PartialNode(WildcardOrPartialNode): - """Node corresponding to a path, which has not reached a leaf yet.""" - - def try_generate_subnode( - self, - next_path_segment: NormalizedPathSegment, - ) -> Node: - """Provides nodes for the extended path or the original values for leafs. - - Will fail if the resulting Path is ill-formed. - - Args: - next_path_segment: Segment, with which the current path should be - extended. - - Returns: - New node-object, representing the extended path, or plain value, - if came to a leaf-node. - - Raises: - LabOneInvalidPathError: If the extension leads to an invalid path. - - """ - extended_path = (*self._path_segments, next_path_segment) - - # first try to extend the path. Will fail if the resulting path is invalid - try: - self.tree_manager.find_substructure(extended_path) - except LabOneInvalidPathError: - # wildcards are always legal - if next_path_segment == WILDCARD: - return self._tree_manager.path_segments_to_node(extended_path) - raise - - return self._tree_manager.path_segments_to_node(extended_path) - - async def wait_for_state_change( - self, - value: int | NodeEnum, # noqa: ARG002 - *, - invert: bool = False, # noqa: ARG002 - ) -> None: - """Not applicable for partial-nodes. - - Args: - value: Expected value of the node. - invert: Instead of waiting for the value, the function will wait for - any value except the passed value. (default = False) - Useful when waiting for value to change from existing one. - - Raises: - LabOneInappropriateNodeTypeError: Always, because partial nodes cannot be - waited for. - """ - msg = ( - "Cannot wait for a partial node to change its value. Consider waiting " - "for a change of one or more leaf-nodes instead." - ) - raise LabOneInappropriateNodeTypeError(msg) diff --git a/src/labone/server/__init__.py b/src/labone/server/__init__.py deleted file mode 100644 index b281d1f..0000000 --- a/src/labone/server/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Subpackage for the server functionality of the LabOne API. - -Warning this module is deprecated and will be removed in the future. Use the -`zhinst-coms.server` and `labone.mock` module instead. -""" - -from zhinst.comms.server import CapnpServer - -from labone.mock.session import LabOneServerBase, MockSession, Subscription - -__all__ = ["CapnpServer", "LabOneServerBase", "MockSession", "Subscription"] - -import warnings - -_deprecation_warning = ( - "The `labone.server` module is deprecated and will be removed in the future." - " Use the `zhinst-coms.server` and `labone.mock` module instead." -) -warnings.warn(DeprecationWarning(_deprecation_warning), stacklevel=2) diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index ad1d8c7..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Test Module""" diff --git a/tests/core/__init__.py b/tests/core/__init__.py deleted file mode 100644 index 3ab3860..0000000 --- a/tests/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Test Module for the labone.core subpackage.""" diff --git a/tests/core/resources/error.capnp b/tests/core/resources/error.capnp deleted file mode 100644 index f0d8044..0000000 --- a/tests/core/resources/error.capnp +++ /dev/null @@ -1,30 +0,0 @@ -@0xb97062d62cb99beb; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -# The error kind advises the client on how to handle the error. -enum ErrorKind @0xb7e671e24a9802bd { - # approximate HTTP status codes: - ok @0; # HTTP 200 - cancelled @1; - unknown @2; - notFound @3; # HTTP 404 - overwhelmed @4; # HTTP 429 - badRequest @5; # HTTP 400 - unimplemented @6; # HTTP 501 - internal @7; # HTTP 500 - unavailable @8; # HTTP 593 - timeout @9; # HTTP 504 -} - -struct Error @0xc4e34e4c517d11d9 { - code @0 :UInt32; - message @1 :Text; - category @2 :Text; - kind @3 :ErrorKind = unknown; - source @4 :Text; -} - diff --git a/tests/core/resources/hello_msg.capnp b/tests/core/resources/hello_msg.capnp deleted file mode 100644 index ff2e62e..0000000 --- a/tests/core/resources/hello_msg.capnp +++ /dev/null @@ -1,30 +0,0 @@ -@0xef99a05432b7c2c9; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -using Version = Text; - -struct HelloMsg @0xd62994dbff882318 { - const fixedLength :UInt16 = 256; - - kind @0 :Kind; - protocol @1 :Protocol = capnp; - schema @2 :Version; - l1Ver @3 :Version; - - enum Kind @0x8dc0ce66c2de04e2 { - unknown @0; - orchestrator @1; - hpk @2; - client @3; - } - enum Protocol @0xe737d5f7b51820cf { - unknown @0; - capnp @1; - http @2; - } -} - diff --git a/tests/core/resources/orchestrator.capnp b/tests/core/resources/orchestrator.capnp deleted file mode 100644 index 8673497..0000000 --- a/tests/core/resources/orchestrator.capnp +++ /dev/null @@ -1,30 +0,0 @@ -@0x9c2e0a9bb9b48f32; - -using Version = Text; - -interface Orchestrator @0xc4f8eb57ff6a6601 { - const capabilityVersion :Version = "1.6.0"; - - enum ErrorCode @0xfbae13420614145a { - ok @0; - unknown @1; - kernelNotFound @2; - illegalDeviceIdentifier @3; - deviceNotFound @4; - kernelLaunchFailure @5; - firmwareUpdateRequired @6; - interfaceMismatch @7; - # The device is visible, but cannot be connected through the requested - # interface. - differentInterfaceInUse@8; - deviceInUse @9; - unsupportedApiLevel @10; - # Generic problem interpreting the incoming request - badRequest @11; - } - - struct Error @0x8e3d8f0587488365 { - code @0 :ErrorCode; - message @1 :Text; - } -} \ No newline at end of file diff --git a/tests/core/resources/path.capnp b/tests/core/resources/path.capnp deleted file mode 100644 index 518245b..0000000 --- a/tests/core/resources/path.capnp +++ /dev/null @@ -1,11 +0,0 @@ -@0xc5aec659eb26a09e; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -using Path = Text; -using Paths = List(Path); -using PathExpression = Text; - diff --git a/tests/core/resources/reflection.capnp b/tests/core/resources/reflection.capnp deleted file mode 100644 index be8453e..0000000 --- a/tests/core/resources/reflection.capnp +++ /dev/null @@ -1,13 +0,0 @@ -@0x8461c2916c39ad0e; - -using Schema = import "/capnp/schema.capnp"; - -struct CapSchema @0xcb31ef7a76eb85cf { - typeId @0 :UInt64; - theSchema @1 :List(Schema.Node); -} - -interface Reflection @0xf9a52e68104bc776 { - getTheSchema @0 () -> (theSchema :CapSchema); -} - diff --git a/tests/core/resources/result.capnp b/tests/core/resources/result.capnp deleted file mode 100644 index 8fd2735..0000000 --- a/tests/core/resources/result.capnp +++ /dev/null @@ -1,14 +0,0 @@ -@0xf7e8e31fdca4abd5; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -struct Result @0xbab0f33e1934323d (Type, Error) { - union { - ok @0 :Type; - err @1 :Error; - } -} - diff --git a/tests/core/resources/session_protocol.capnp b/tests/core/resources/session_protocol.capnp deleted file mode 100644 index 437dae4..0000000 --- a/tests/core/resources/session_protocol.capnp +++ /dev/null @@ -1,159 +0,0 @@ -@0xb6c21289d5437347; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -using import "error.capnp".Error; -using import "path.capnp".Path; -using import "path.capnp".Paths; -using import "path.capnp".PathExpression; -using import "result.capnp".Result; -using import "uuid.capnp".Uuid; -using import "value.capnp".AnnotatedValue; -using import "value.capnp".Value; -using import "value.capnp".Void; - -using Version = Text; -# A unique identifier for a client. Note that the HPK makes no assumption about -# what a client is. In particular, the HPK does not assume there's any relationship -# between sockets/ip and client id. The same connection can be used by different clients, -# and the same client can use different connections -using ClientId = Uuid; -const defaultClientId :ClientId = ""; - -struct Subscription @0xedac21a53de1b1d4 { - path @0 :Path; - streamingHandle @1 :StreamingHandle; - subscriberId @2 :ClientId; -} - -# Unless otherwise specified, all Paths accepted by the Session interface are -# case insensitive and can be either absolute (starts with a '/') or relative. -# If a path is relative, it is assumed to be relative to the device id. For -# example path "raw/int" will be expanded to "/devXXX/raw/int". -interface Session @0xb9d445582da4a55c { - # 1.0.0: initial version. - # 1.1.0: Added version to hello message. - # 1.2.0: Existing setValue was deprecated in favour of a new version that returns the acknowledged value. - # Added None type to Value union. - # 1.3.0: Added ClientId to setValue and getValue. This allows reordering of requests with different clientIds. - # 1.4.0: Added "http" as protocol in the hello message - # 1.5.0: Added setValue and new getValue function that accept PathExpressions as input. - const capabilityVersion :Version = "1.5.0"; - - getCapabilityVersion @7 () -> (version :Version); - - listNodes @0 (pathExpression :PathExpression, flags :UInt32, client :ClientId) -> (paths :Paths); - # The "client" field is needed only when subscribedonly is used. - - getValue @10 (pathExpression :PathExpression, - lookupMode :LookupMode = directLookup, - flags :UInt32 = 0, - client :ClientId = .defaultClientId) - -> (result :List(Result(AnnotatedValue, Error))); - # Note 1: A pathExpressions can be anything that listNodes can resolve. - # This means that, e.g., wildcards are allowed. Passing multiple - # comma-separated paths as a single pathExpression is also possible. - # Note 2: The lookupMode controls if and how pathExpressions are resolved. - # By default the pathExpression is expected to be a node path pointing - # to an existing leaf node. If lookupMode is set to withExpansion, - # the server tries to resolve the pathExpression with listNodes internally - # and returns the value for all matching nodes. - - setValue @9 (pathExpression :PathExpression, - value :Value, - lookupMode :LookupMode = directLookup, - completeWhen :ReturnFromSetWhen = deviceAck, - client :ClientId = .defaultClientId) - -> (result :List(Result(AnnotatedValue, Error))); - # Note 1: in certain cases, the HPK allows setting nodes with a type different - # from that of the value passed in. For example, it is allowed to set - # a double from an integer. The returned value is always of the type - # of the node. So it can happen that setValue returns a value of a type - # different from the one that was passed in. - # Note 2: The AnnotatedValue is only returned if `completeWhen` is set to `deviceAck`. - # Note 3: The lookupMode controls if and how pathExpressions are resolved. - # By default the pathExpression is expected to be a node path pointing - # to an existing leaf node. If lookupMode is set to withExpansion, - # the server tries to resolve the pathExpression with listNodes internally - # and sets the value to all matching nodes. - - - subscribe @3 (subscription :Subscription) -> (result :Result(Void, Error)); - # Note 1: this function currently does not support wildcards in the path - # Note 2: it is safe to have multiple subscriptions with the same subscriberId - # and path. This is convenient as it allows rapid resubscription. - # You can unsubscribe, then subscribe again even before the unsubscribe - # has returned. - - unsubscribe @6 (subscriberId :ClientId, paths :Paths) -> Void; - # Cancel the given subscriptions. Wildcards in the paths are not expanded. - # - # It is not an error to pass a non-existing or a non-subscribed path to unsubscribe. - # - # Calling "unsubscribe" is not strictly required. It is perfectly safe to - # destroy the client side StreamingHandle without calling unsubscribing first. - # However, it may take a while for the HPK to notice that the client side of the - # subscription has been dropped. This can cause surprising results when using - # the subscribedonly flag in listNodes. - # - # The HPK does not forbid the existence of multiple subscriptions with the same - # subscriberId and path. If multiple subscriptions exist with the same - # subscriberId and path, they are all deleted. It's not possible at the moment - # to selectively delete a single subscription. - - disconnectDevice @4 () -> Void; - # disconnectDevice effectively causes the HPK to be torn down. By design, - # disconnectDevice will never return a response, and requests will always - # raise a DISCONNECTED exception. - - listNodesJson @5 (pathExpression :PathExpression, flags :UInt32, client :ClientId) -> (nodeProps :Text); - # The "client" field is needed only when subscribedonly is used. - - # DEPRECATED - # SetValue returns the acknowledged value as part of 1.2.0 - deprecatedSetValue @2 (path :Path, - value :Value, - completeWhen :ReturnFromSetWhen = deviceAck) - -> (result :Result(Void, Error)); - # DEPRECATED - # Server side path expansion was added in 1.5.0 - deprecatedSetValue2 @8 (path :Path, - value :Value, - completeWhen :ReturnFromSetWhen = deviceAck, - client :ClientId = .defaultClientId) - -> (result :Result(AnnotatedValue, Error)); - deprecatedGetValues @1 (paths :Paths, client :ClientId = .defaultClientId) -> (result :List(Result(AnnotatedValue, Error))); -} - -interface StreamingHandle @0xf51e28a7f5b41574 { - sendValues @0 (values :List(AnnotatedValue)) -> stream; - # Note: Capnp documentation reccommends that, when using streaming a "done()" - # function is provided as well. This is necessary for the sending side to - # ensure that the streaming was entirely successful. In the Session interface - # however a done() call would not make sense, because the streaming goes on - # "forever" and is fire and forget. -} - -enum ReturnFromSetWhen @0xdd2da53aac55edf9 { - # Return an answer as soon as the request has been forwarded to the device, - # but before it has been acknowledged. This mode exists to mimick the MDK - # default "set" behavior. Eventually, it should be deprecated. - asap @0; - - # Wait until the device has responded to the set request before returning the - # answer to the client. This is the default for the HPK - deviceAck @1; - - unusedAsync @2; - unusedTransactional @3; -} - -enum LookupMode @0xda5049b5e072f425 { - # The path expected to be a single node path. - directLookup @0; - # The server tries to resolve the path with listNodes internally. - withExpansion @1; -} diff --git a/tests/core/resources/testfile.capnp b/tests/core/resources/testfile.capnp deleted file mode 100644 index b581f3e..0000000 --- a/tests/core/resources/testfile.capnp +++ /dev/null @@ -1,5 +0,0 @@ -@0xb6c21289d5437345; - -interface TestInterface @0xb9d445582da4a55b { - testMethod @0 (testUint32Field :UInt32, testTextField :Text); -} diff --git a/tests/core/resources/uuid.capnp b/tests/core/resources/uuid.capnp deleted file mode 100644 index f3104f2..0000000 --- a/tests/core/resources/uuid.capnp +++ /dev/null @@ -1,18 +0,0 @@ -@0x8440bbc31dc1ee7e; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -# Uuid is expected to always be 16 bytes (128 bits) long. -# We can't enforce this unfortunately because Capnp doesn't have a way to declare -# fixed size arrays. -using Uuid = Data; - -# At the moment, this is used only for testing. In the future we may rename it -# and reuse it, if we see that using a raw buffer as Uuid is too inconvenient. -struct TestUuid @0xe6dd15a768032fd6 { - uuid @0 :Uuid; -} - diff --git a/tests/core/resources/value.capnp b/tests/core/resources/value.capnp deleted file mode 100644 index b26e8b6..0000000 --- a/tests/core/resources/value.capnp +++ /dev/null @@ -1,82 +0,0 @@ -@0xfca5cbb23425bcc7; - -using Cxx = import "/capnp/c++.capnp"; -# zhinst::capnp would be nicer than zhinst_capnp, but there's the chance of namespace -# collisions with the "capnp" namespace -$Cxx.namespace("zhinst_capnp"); - -using import "path.capnp".Path; -using import "error.capnp".Error; - -# StreamingErrors are used to signal errors within a continuous stream of data. -# Outside of the streaming context, the must not occur but rather be signaled -# using the result error mechanism. -using StreamingError = Error; - -struct Void { } - -struct VectorData @0x994c65b80df38978 { - # "valueType" specifies the type of the vector. It uses (a subset) of values from `ZIValueType_enum`. - # The most commonly used type is "ZI_VALUE_TYPE_VECTOR_DATA". Some vectors use a different format, - # e.g., for SHF devices. - valueType @0 :UInt16; - # "vectorElementType" uses the values from `ZIVectorElementType_enum` to specify the - # data type of each element in the vector. - vectorElementType @1 :UInt8; - # ExtraHeader: [31:16] type and version information, [15:0] length in 32-bit words - extraHeaderInfo @2 :UInt32; - # "data" maps to the `VectorData` struct. - data @3 :Data; -} - -struct Complex @0xaaf1afaf97b4b157 { - real @0 :Float64; - imag @1 :Float64; -} - -# The CntSample data type is application specific. We generally avoid -# application specific data types, but these are needed to support the HDAWG -# in the HPK. -struct CntSample @0xe9370bd8287d6065 { - timestamp @0 :UInt64; - counter @1 :Int32; - trigger @2 :UInt32; -} - -# The TriggerSample data type is application specific. We generally avoid -# application specific data types, but these are needed to support the HDAWG -# in the HPK. -struct TriggerSample @0xdeb72097c27d0d95 { - timestamp @0 :UInt64; - sampleTick @1 :UInt64; - trigger @2 :UInt32; - missedTriggers @3 :UInt32; - awgTrigger @4 :UInt32; - dio @5 :UInt32; - sequenceIndex @6 :UInt32; -} - -struct Value @0xb1838b4771be75ac { - union { - int64 @0 :Int64; - double @1 :Float64; - complex @2 :Complex; - string @3 :Text; - vectorData @4 :VectorData; - cntSample @5 :CntSample; - triggerSample @6 :TriggerSample; - none @7 :Void; - streamingError @8 :StreamingError; - } -} - -struct AnnotatedValue @0xf408ee376e837cdc { - struct Metadata @0xad53d8ca57af3018 { - timestamp @0 :UInt64; - path @1 :Path; - } - - metadata @0 :Metadata; - value @1 :Value; -} - diff --git a/tests/core/test_annotated_value_from_capnp.py b/tests/core/test_annotated_value_from_capnp.py deleted file mode 100644 index bdd2276..0000000 --- a/tests/core/test_annotated_value_from_capnp.py +++ /dev/null @@ -1,279 +0,0 @@ -"""Tests the conversion from capnp to AnnotatedValue""" - -from unittest.mock import patch - -import numpy as np -import pytest - -import labone.core.value as value_module -from labone.core import errors, hpk_schema - - -def test_void(): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_none() - - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value is None - - -def test_trigger_sample(): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - trigger_sample = msg.init_value().init_triggerSample() - trigger_sample.timestamp = 1 - trigger_sample.sampleTick = 2 - trigger_sample.trigger = 3 - trigger_sample.missedTriggers = 4 - trigger_sample.awgTrigger = 5 - trigger_sample.dio = 6 - trigger_sample.sequenceIndex = 7 - - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value.timestamp == trigger_sample.timestamp - assert parsed_value.value.sampleTick == trigger_sample.sampleTick - assert parsed_value.value.trigger == trigger_sample.trigger - assert parsed_value.value.missedTriggers == trigger_sample.missedTriggers - assert parsed_value.value.awgTrigger == trigger_sample.awgTrigger - assert parsed_value.value.dio == trigger_sample.dio - assert parsed_value.value.sequenceIndex == trigger_sample.sequenceIndex - - -def test_cnt_sample(): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - cnt_sample = msg.init_value().init_cntSample() - cnt_sample.timestamp = 1 - cnt_sample.counter = 2 - cnt_sample.trigger = 3 - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value.timestamp == cnt_sample.timestamp - assert parsed_value.value.counter == cnt_sample.counter - assert parsed_value.value.trigger == cnt_sample.trigger - - -@pytest.mark.parametrize( - ("kind", "error_class"), - [ - ("ok", errors.LabOneCoreError), - ("cancelled", errors.CancelledError), - ("notFound", errors.NotFoundError), - ("unknown", errors.LabOneCoreError), - ("overwhelmed", errors.OverwhelmedError), - ("badRequest", errors.BadRequestError), - ("unimplemented", errors.UnimplementedError), - ("internal", errors.InternalError), - ("unavailable", errors.UnavailableError), - ("timeout", errors.LabOneTimeoutError), - ], -) -def test_streaming_error(kind, error_class): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_streamingError() - msg.value.streamingError.code = 1 - msg.value.streamingError.message = "Test message" - msg.value.streamingError.category = "zi:test" - msg.value.streamingError.kind = kind - msg.value.streamingError.source = "" - with pytest.raises(error_class): - value_module.AnnotatedValue.from_capnp(msg) - - -@pytest.mark.parametrize( - ("type_name", "input_val", "output_val"), - [ - ("int64", 42, 42), - ("double", 42.0, 42.0), - ("complex", {"real": 42, "imag": 66}, 42 + 66j), - ("complex", 42 + 66j, 42 + 66j), - ("string", "42", "42"), - ], -) -def test_generic_types(type_name, input_val, output_val): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - setattr(msg.init_value(), type_name, input_val) - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value == output_val - - -def test_string_vector(): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_vectorData() - msg.value.vectorData.valueType = 7 - msg.value.vectorData.vectorElementType = 6 - msg.value.vectorData.extraHeaderInfo = 0 - msg.value.vectorData.data = b"Hello World" - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value == "Hello World" - - -def test_generic_vector(): - input_array = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.uint32) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_vectorData() - msg.value.vectorData.valueType = 67 - msg.value.vectorData.vectorElementType = 2 - msg.value.vectorData.extraHeaderInfo = 0 - msg.value.vectorData.data = input_array.tobytes() - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert np.array_equal(parsed_value.value, input_array) - - -@patch.object( - value_module, - "parse_shf_vector_from_vector_data", - autospec=True, -) -def test_shf_vector(mock_method): - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_vectorData() - msg.value.vectorData.valueType = 69 - msg.value.vectorData.vectorElementType = 2 - msg.value.vectorData.extraHeaderInfo = 0 - msg.value.vectorData.data = b"" - mock_method.return_value = "array", "extra_header" - parsed_value = value_module.AnnotatedValue.from_capnp(msg) - mock_method.assert_called_once() - assert mock_method.call_args[0][0] == msg.value.vectorData - assert parsed_value.timestamp == msg.metadata.timestamp - assert parsed_value.path == msg.metadata.path - assert parsed_value.value == ("array", "extra_header") - - -@patch.object( - value_module, - "parse_shf_vector_from_vector_data", - autospec=True, -) -def test_unknown_shf_vector(mock_method): - input_array = np.linspace(0, 1, 200, dtype=np.uint32) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_vectorData() - msg.value.vectorData.valueType = 69 - msg.value.vectorData.vectorElementType = 2 - msg.value.vectorData.extraHeaderInfo = 32 - msg.value.vectorData.data = input_array.tobytes() - mock_method.side_effect = ValueError("Unknown SHF vector type") - with pytest.raises(ValueError): - value_module.AnnotatedValue.from_capnp(msg) - mock_method.assert_called_once() - assert mock_method.call_args[0][0] == msg.value.vectorData - - -def test_shf_demodulator_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.float64) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfDemodData() - msg.value.shfDemodData.x = input_array - msg.value.shfDemodData.y = input_array - msg.value.shfDemodData.properties.timestamp = 42 - msg.value.shfDemodData.properties.centerFreq = 1e6 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.x, input_array) - assert np.allclose(result.value.y, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.centerFreq == 1e6 - - -def test_shf_result_logger_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.float64) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfResultLoggerData() - msg.value.shfResultLoggerData.vector.real = input_array - msg.value.shfResultLoggerData.properties.timestamp = 42 - msg.value.shfResultLoggerData.properties.centerFrequency = 1e6 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.vector, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.centerFrequency == 1e6 - - -def test_complex_shf_result_logger_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.complex128) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfResultLoggerData() - msg.value.shfResultLoggerData.vector.complex = input_array - msg.value.shfResultLoggerData.properties.timestamp = 42 - msg.value.shfResultLoggerData.properties.centerFrequency = 1e6 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.vector, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.centerFrequency == 1e6 - - -def test_shf_scope_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.float64) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfScopeData() - msg.value.shfScopeData.vector.real = input_array - msg.value.shfScopeData.properties.timestamp = 42 - msg.value.shfScopeData.properties.centerFrequency = 1e6 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.vector, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.centerFrequency == 1e6 - - -def test_complex_shf_scope_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.complex128) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfScopeData() - msg.value.shfScopeData.vector.complex = input_array - msg.value.shfScopeData.properties.timestamp = 42 - msg.value.shfScopeData.properties.centerFrequency = 1e6 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.vector, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.centerFrequency == 1e6 - - -def test_shf_pid_vector_data(): - input_array = np.linspace(0, 1, 200, dtype=np.float64) - msg = hpk_schema.AnnotatedValue() - msg.init_metadata(timestamp=42, path="/non/of/your/business") - msg.init_value().init_shfPidData() - msg.value.shfPidData.value = input_array - msg.value.shfPidData.error = input_array - msg.value.shfPidData.properties.timestamp = 42 - msg.value.shfPidData.properties.triggerTimestamp = 54 - result = value_module.AnnotatedValue.from_capnp(msg) - assert result.timestamp == msg.metadata.timestamp - assert result.path == msg.metadata.path - assert np.allclose(result.value.value, input_array) - assert np.allclose(result.value.error, input_array) - assert result.value.properties.timestamp == 42 - assert result.value.properties.triggerTimestamp == 54 diff --git a/tests/core/test_annotated_value_to_capnp.py b/tests/core/test_annotated_value_to_capnp.py deleted file mode 100644 index 24948df..0000000 --- a/tests/core/test_annotated_value_to_capnp.py +++ /dev/null @@ -1,261 +0,0 @@ -"""Tests for `AnnotatedValue` and its' parts. - -NOTE: `capnp` builder instances cannot be asserter for equality. - -TODO: Tests for invalid Python value cases. -""" - -import numpy as np -import pytest -from hypothesis import HealthCheck, given, settings -from hypothesis import strategies as st -from hypothesis.extra.numpy import arrays - -from labone.core.errors import LabOneCoreError -from labone.core.helper import VectorElementType -from labone.core.session import Session -from labone.core.shf_vector_data import ( - ShfGeneratorWaveformVectorData, - VectorValueType, - preprocess_complex_shf_waveform_vector, -) -from labone.core.value import value_from_python_types - - -@given(st.integers(min_value=-np.int64(), max_value=np.int64())) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_int64(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value["int64"] == inp - - -@pytest.mark.parametrize("inp", [False, True]) -def test_value_from_python_types_bool(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value["bool"] == inp - - -@given(st.floats(allow_nan=False)) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_double(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value["double"] == inp - - -def test_value_from_python_types_np_nan(): - value1 = value_from_python_types( - np.nan, - capability_version=Session.CAPABILITY_VERSION, - ) - assert np.isnan(value1["double"]) - - inp = complex(real=0.0, imag=np.nan.imag) - value2 = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value2["complex"].real == inp.real - assert value2["complex"].imag == inp.imag - - -@given(st.complex_numbers(allow_nan=False)) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_complex(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value["complex"].real == inp.real - assert value["complex"].imag == inp.imag - - -@given(st.text()) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_string(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - assert value["string"] == inp - - -@given(st.binary()) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_bytes(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.BYTE_ARRAY.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT8.value - assert vec_data["data"] == inp - - -@given(arrays(dtype=np.uint8, shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint8(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT8.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=np.uint16, shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint16(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT16.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=np.uint32, shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint32(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT32.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=(np.uint64, int), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_uint64(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT64.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=(float, np.double), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_double(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.DOUBLE.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=(np.single), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_float(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.FLOAT.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=(np.csingle), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_complex_float(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.COMPLEX_FLOAT.value - assert vec_data["data"] == inp.tobytes() - - -def test_value_from_python_types_vector_data_complex_waveform(): - inp = np.array([1 + 2j, 3 + 4j], dtype=np.complex128) - value = value_from_python_types( - ShfGeneratorWaveformVectorData(complex=inp), - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["shfGeneratorWaveformData"] - assert np.allclose(vec_data["complex"], inp) - - -def test_value_from_python_types_vector_data_complex_waveform_manual(): - inp = np.array([1 + 2j, 3 + 4j], dtype=np.complex128) - value = value_from_python_types( - ShfGeneratorWaveformVectorData(complex=inp), - capability_version=Session.MIN_CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.UINT32.value - assert ( - vec_data["data"] - == preprocess_complex_shf_waveform_vector(inp)["vectorData"]["data"] - ) - - -@given(arrays(dtype=(np.cdouble), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_complex_double(inp): - value = value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - vec_data = value["vectorData"] - assert vec_data["valueType"] == VectorValueType.VECTOR_DATA.value - assert vec_data["extraHeaderInfo"] == 0 - assert vec_data["vectorElementType"] == VectorElementType.COMPLEX_DOUBLE.value - assert vec_data["data"] == inp.tobytes() - - -@given(arrays(dtype=(np.bytes_), shape=(1, 2))) -@settings(suppress_health_check=(HealthCheck.function_scoped_fixture,)) -def test_value_from_python_types_vector_data_invalid(inp): - with pytest.raises(ValueError): - value_from_python_types( - inp, - capability_version=Session.CAPABILITY_VERSION, - ) - - -def test_value_from_python_types_invalid(): - class FakeObject: - pass - - with pytest.raises(LabOneCoreError): - value_from_python_types( - FakeObject, - capability_version=Session.CAPABILITY_VERSION, - ) diff --git a/tests/core/test_shf_vector_data.py b/tests/core/test_shf_vector_data.py deleted file mode 100644 index 09fd60b..0000000 --- a/tests/core/test_shf_vector_data.py +++ /dev/null @@ -1,221 +0,0 @@ -import struct - -import numpy as np -import pytest - -from labone.core import hpk_schema -from labone.core.helper import VectorElementType -from labone.core.shf_vector_data import ( - # SHFDemodSample, - # ShfDemodulatorVectorExtraHeader, - # ShfResultLoggerVectorExtraHeader, - # ShfScopeVectorExtraHeader, - ShfGeneratorWaveformVectorData, - ShfResultLoggerVectorData, - ShfScopeVectorData, - VectorValueType, - # encode_shf_vector_data_struct, - get_header_length, - parse_shf_vector_from_vector_data, - # preprocess_complex_shf_waveform_vector, -) - - -class FakeShfVectorDataStruct: - def __init__(self, extra_header_info=None): - self.extraHeaderInfo = extra_header_info - - -@pytest.mark.parametrize( - ("header_bytes", "expected_length"), - [ - (b"\x00\x00\x00\x00", 0), - (b"\x01\x00\x00\x00", 0), - (b"\x02\x02\x00\x00", 0), - (b"\x02\x02\x02\x00", 2048), - (b"\x00\x00\x00\x01", 4), - (b"\x02\x02\x02\x00\x02\x02\x02\x00", 2048), - (b"\x02\x02\x02\x02\x02\x02\x02\x02", 2056), - (b"\x00\x00\x00\x00\x00\x00\x00\x01", 4), - (b"\x00\x00\x00\x00\x00\x00\x00\x05", 20), - ], -) -def test_get_header_length(header_bytes, expected_length): - assert ( - get_header_length( - FakeShfVectorDataStruct( - extra_header_info=int.from_bytes(bytes=header_bytes, byteorder="big"), - ), - ) - == expected_length - ) - - -def _to_vector_data( - value_type: int, - data: bytes = b"", - extra_header_info: int = 0, - vector_element_type: VectorElementType = VectorElementType.UINT8, -): - msg = hpk_schema.VectorData() - msg.valueType = value_type - msg.vectorElementType = vector_element_type - msg.extraHeaderInfo = extra_header_info - msg.data = data - return msg - - -@pytest.mark.parametrize( - "value_type", - [ - VectorValueType.SHF_SCOPE_VECTOR_DATA, - VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA, - VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA, - ], -) -def test_missing_extra_header(value_type): - input_vector = _to_vector_data(value_type=value_type.value) - parse_shf_vector_from_vector_data(input_vector) - - -def test_unsupported_vector_type(): - input_vector = _to_vector_data(value_type=80) - with pytest.raises(ValueError): - parse_shf_vector_from_vector_data(input_vector) - - -def _construct_extra_header_value(header_length, major_version, minor_version): - return int(header_length / 4) | major_version << 21 | minor_version << 16 - - -@pytest.mark.parametrize( - "value_type", - [ - VectorValueType.SHF_SCOPE_VECTOR_DATA, - VectorValueType.SHF_DEMODULATOR_VECTOR_DATA, - VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA, - ], -) -def test_invalid_header_version( - value_type, -): - input_vector = _to_vector_data( - value_type=value_type.value, - extra_header_info=_construct_extra_header_value( - header_length=8, - major_version=0, - minor_version=0, - ), - data=b"\x00" * 16, - ) - with pytest.raises(Exception): # noqa: B017 - parse_shf_vector_from_vector_data(input_vector) - - -@pytest.mark.parametrize("vector_length", range(0, 200, 32)) -@pytest.mark.parametrize("scaling", [x * 0.25 for x in range(5)]) -@pytest.mark.parametrize("header_version", [1, 2]) -@pytest.mark.parametrize(("x", "y"), [(0, 0), (1, 1), (32, 743)]) -def test_shf_scope_vector( - vector_length, - scaling, - header_version, - x, - y, -): - header_length = 64 - # Manually set the scaling Factor - header = b"\x00" * 16 + struct.pack("d", scaling) + b"\x00" - header = header + b"\x00" * (header_length - len(header)) - # The data are interleave complex integers - data = (struct.pack("I", x) + struct.pack("I", y)) * vector_length - - input_vector = _to_vector_data( - value_type=VectorValueType.SHF_SCOPE_VECTOR_DATA.value, - extra_header_info=_construct_extra_header_value( - header_length=header_length, - major_version=0, - minor_version=header_version, - ), - data=header + data, - ) - output_vector = parse_shf_vector_from_vector_data(input_vector) - assert isinstance(output_vector, ShfScopeVectorData) - assert np.array_equal( - output_vector.vector, - x * scaling + 1j * y * scaling * np.ones(vector_length, dtype=np.complex128), - ) - - assert output_vector.properties.averageCount == 0 - assert output_vector.properties.centerFrequency == 0.0 - assert output_vector.properties.inputSelect == 0 - assert output_vector.properties.flags == 0 - assert output_vector.properties.scaling == scaling - assert output_vector.properties.timestamp == 0 - assert output_vector.properties.timestampDiff == 0 - assert output_vector.properties.triggerTimestamp == 0 - assert output_vector.properties.numMissedTriggers == 0 - assert output_vector.properties.numSegments == 0 - assert output_vector.properties.numTotalSegments == 0 - assert output_vector.properties.firstSegmentIndex == 0 - - -@pytest.mark.parametrize("vector_length", range(0, 200, 32)) -@pytest.mark.parametrize("header_version", [1]) -@pytest.mark.parametrize("x", range(0, 30, 7)) -def test_shf_result_logger_vector(vector_length, header_version, x): - header_length = 72 - header = b"\x00" * header_length - data = struct.pack("I", x) * vector_length - input_vector = _to_vector_data( - value_type=VectorValueType.SHF_RESULT_LOGGER_VECTOR_DATA.value, - vector_element_type=2, - extra_header_info=_construct_extra_header_value( - header_length=header_length, - major_version=0, - minor_version=header_version, - ), - data=header + data, - ) - - output_vector = parse_shf_vector_from_vector_data(input_vector) - - assert isinstance(output_vector, ShfResultLoggerVectorData) - assert np.array_equal( - output_vector.vector, - x * np.ones(vector_length, dtype=np.uint32), - ) - - assert output_vector.properties.timestamp == 0 - assert output_vector.properties.timestamp == 0 - assert output_vector.properties.jobId == 0 - assert output_vector.properties.repetitionId == 0 - assert output_vector.properties.scaling == 0.0 - assert output_vector.properties.centerFrequency == 0.0 - assert output_vector.properties.dataSource == 0 - assert output_vector.properties.numSamples == 0 - assert output_vector.properties.numSpectrSamples == 0 - assert output_vector.properties.numAverages == 0 - assert output_vector.properties.numAcquired == 0 - assert output_vector.properties.holdoffErrorsReslog == 0 - assert output_vector.properties.holdoffErrorsReadout == 0 - assert output_vector.properties.holdoffErrorsSpectr == 0 - - -@pytest.mark.parametrize("vector_length", range(0, 200, 32)) -@pytest.mark.parametrize(("x", "y"), [(0, 0), (1, 1), (32, 743), (3785687, 1285732)]) -def test_shf_waveform_logger_vector(vector_length, x, y): - input_vector = _to_vector_data( - value_type=VectorValueType.SHF_GENERATOR_WAVEFORM_VECTOR_DATA.value, - vector_element_type=2, # uint32 - extra_header_info=0, - data=(struct.pack("I", x) + struct.pack("I", y)) * vector_length, - ) - output_vector = parse_shf_vector_from_vector_data(input_vector) - assert isinstance(output_vector, ShfGeneratorWaveformVectorData) - const_scaling = 1 / 131071.0 # constant scaling factor based on the encoding bits - assert np.array_equal( - output_vector.complex, - x * const_scaling - + 1j * const_scaling * y * np.ones(vector_length, dtype=np.complex128), - ) diff --git a/tests/core/test_subscription.py b/tests/core/test_subscription.py deleted file mode 100644 index 81442e6..0000000 --- a/tests/core/test_subscription.py +++ /dev/null @@ -1,383 +0,0 @@ -"""Tests for the `labone.core.subscription` module.""" - -import asyncio -import logging -from unittest.mock import MagicMock - -import pytest - -from labone.core import errors, hpk_schema -from labone.core.subscription import ( - CircularDataQueue, - DataQueue, - DistinctConsecutiveDataQueue, - StreamingHandle, -) -from labone.core.value import AnnotatedValue - - -class FakeSubscription: - def __init__(self): - self.data_queues = [] - - def register_data_queue(self, data_queue) -> None: - self.data_queues.append(data_queue) - - -def test_data_queue_path(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert queue.path == "dummy" - - -def test_data_queue_maxsize(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert queue.maxsize == 0 - - -def test_data_queue_maxsize_to_low(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.put_nowait("test") - queue.put_nowait("test") - queue.maxsize = 2 - with pytest.raises(errors.StreamingError): - queue.maxsize = 1 - - -def test_data_queue_maxsize_disconnected(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.disconnect() - with pytest.raises(errors.StreamingError): - queue.maxsize = 42 - - -def test_data_queue_repr_idle(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert repr(queue) == "DataQueue(path='dummy', maxsize=0, qsize=0, connected=True)" - - -def test_data_queue_repr(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.maxsize = 42 - queue.put_nowait("test") - queue.put_nowait("test") - queue.disconnect() - assert ( - repr(queue) == "DataQueue(path='dummy', maxsize=42, qsize=2, connected=False)" - ) - - -def test_data_queue_disconnect(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert queue.connected - queue.disconnect() - assert not queue.connected - - -def test_data_queue_fork(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert len(subscription.data_queues) == 1 - forked_queue = queue.fork() - assert len(subscription.data_queues) == 2 - assert forked_queue.path == queue.path - assert forked_queue.connected - - -def test_data_queue_fork_disconnected(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.disconnect() - with pytest.raises(errors.StreamingError): - queue.fork() - - -def test_data_queue_put_nowait(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - assert queue.qsize() == 0 - queue.put_nowait("test") - assert queue.qsize() == 1 - assert queue.get_nowait() == "test" - assert queue.qsize() == 0 - - -def test_data_queue_put_nowait_disconnected(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.disconnect() - with pytest.raises(errors.StreamingError): - queue.put_nowait("test") - - -@pytest.mark.asyncio -async def test_data_queue_get(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.put_nowait("test") - assert await queue.get() == "test" - - -@pytest.mark.asyncio -async def test_data_queue_get_timeout(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - with pytest.raises(asyncio.TimeoutError): - await asyncio.wait_for(queue.get(), 0.01) - - -@pytest.mark.asyncio -async def test_data_queue_get_disconnected_ok(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.put_nowait("test") - queue.disconnect() - assert await queue.get() == "test" - - -@pytest.mark.asyncio -async def test_data_queue_get_disconnected_empty(): - subscription = FakeSubscription() - queue = DataQueue(path="dummy", handle=subscription) - queue.disconnect() - with pytest.raises(errors.EmptyDisconnectedDataQueueError): - await queue.get() - - -@pytest.mark.asyncio -async def test_circular_data_queue_put_enough_space(): - subscription = FakeSubscription() - queue = CircularDataQueue( - path="dummy", - handle=subscription, - ) - queue.maxsize = 2 - await asyncio.wait_for(queue.put("test"), timeout=0.01) - assert queue.qsize() == 1 - assert queue.get_nowait() == "test" - - -@pytest.mark.asyncio -async def test_circular_data_queue_put_full(): - subscription = FakeSubscription() - queue = CircularDataQueue( - path="dummy", - handle=subscription, - ) - queue.maxsize = 2 - await asyncio.wait_for(queue.put("test1"), timeout=0.01) - await asyncio.wait_for(queue.put("test2"), timeout=0.01) - await asyncio.wait_for(queue.put("test3"), timeout=0.01) - assert queue.qsize() == 2 - assert queue.get_nowait() == "test2" - assert queue.get_nowait() == "test3" - - -@pytest.mark.asyncio -async def test_circular_data_queue_put_no_wait_enough_space(): - subscription = FakeSubscription() - queue = CircularDataQueue( - path="dummy", - handle=subscription, - ) - queue.maxsize = 2 - queue.put_nowait("test") - assert queue.qsize() == 1 - assert queue.get_nowait() == "test" - - -@pytest.mark.asyncio -async def test_circular_data_queue_put_no_wait_full(): - subscription = FakeSubscription() - queue = CircularDataQueue( - path="dummy", - handle=subscription, - ) - queue.maxsize = 2 - queue.put_nowait("test1") - queue.put_nowait("test2") - queue.put_nowait("test3") - assert queue.qsize() == 2 - assert queue.get_nowait() == "test2" - assert queue.get_nowait() == "test3" - - -def test_circular_data_queue_fork(): - subscription = FakeSubscription() - queue = CircularDataQueue( - path="dummy", - handle=subscription, - ) - assert len(subscription.data_queues) == 1 - forked_queue = queue.fork() - assert isinstance(forked_queue, CircularDataQueue) - assert len(subscription.data_queues) == 2 - assert forked_queue.path == queue.path - assert forked_queue.connected - - -def test_streaming_handle_register(): - streaming_handle = StreamingHandle() - DataQueue(path="dummy", handle=streaming_handle) - assert len(streaming_handle._data_queues) == 1 - - -@pytest.mark.parametrize("num_values", range(0, 20, 4)) -@pytest.mark.parametrize("num_queues", [1, 2, 6]) -@pytest.mark.asyncio -async def test_streaming_handle_update_event(num_values, num_queues): - streaming_handle = StreamingHandle() - queues = [] - for _ in range(num_queues): - queue = DataQueue( - path="dummy", - handle=streaming_handle, - ) - queues.append(queue) - for i in range(num_values): - value = AnnotatedValue(value=i, path="dummy", timestamp=0) - streaming_handle.distribute_to_data_queues(value) - for queue in queues: - assert queue.qsize() == num_values - for i in range(num_values): - assert queue.get_nowait() == AnnotatedValue( - value=i, - path="dummy", - timestamp=0, - ) - - -def test_streaming_handle_with_parser_callback(): - StreamingHandle( - parser_callback=lambda a: AnnotatedValue(path=a.path, value=a.value * 2), - ) - - -@pytest.mark.asyncio -async def test_capnp_callback(caplog): - streaming_handle = StreamingHandle() - queue = DataQueue( - path="dummy", - handle=streaming_handle, - ) - call_param = hpk_schema.StreamingHandleSendValuesParams() - values = call_param.init_values(2) - - values[0].init_metadata(timestamp=0, path="dummy") - values[0].init_value(int64=42) - - values[1].init_metadata(timestamp=1, path="dummy") - values[1].init_value(double=22.0) - - fulfiller = MagicMock() - with caplog.at_level(logging.ERROR): - await streaming_handle.capnp_callback(0, 0, call_param, fulfiller) - assert "" in caplog.text - assert queue.qsize() == 2 - assert queue.get_nowait() == AnnotatedValue(value=42, path="dummy", timestamp=0) - assert queue.get_nowait() == AnnotatedValue(value=22.0, path="dummy", timestamp=1) - fulfiller.fulfill.assert_called_once() - - -@pytest.mark.asyncio -async def test_streaming_error(caplog): - streaming_handle = StreamingHandle() - queue = DataQueue( - path="dummy", - handle=streaming_handle, - ) - call_param = hpk_schema.StreamingHandleSendValuesParams() - values = call_param.init_values(1) - values[0].init_metadata(timestamp=0, path="dummy") - values[0].init_value().init_streamingError( - code=1, - message="test error", - category="unknown", - ) - fulfiller = MagicMock() - with caplog.at_level(logging.ERROR): - await streaming_handle.capnp_callback(0, 0, call_param, fulfiller) - assert "test error" in caplog.text - assert queue.qsize() == 0 - fulfiller.fulfill.assert_called_once() - - -@pytest.mark.asyncio -async def test_streaming_error_with_value(caplog): - streaming_handle = StreamingHandle() - queue = DataQueue( - path="dummy", - handle=streaming_handle, - ) - call_param = hpk_schema.StreamingHandleSendValuesParams() - values = call_param.init_values(2) - - # Fist value is a streaming error - values[0].init_metadata(timestamp=0, path="dummy") - values[0].init_value().init_streamingError( - code=1, - message="test error", - category="unknown", - ) - - # Second value is a normal value - values[1].init_metadata(timestamp=0, path="dummy") - values[1].init_value(int64=42) - - fulfiller = MagicMock() - with caplog.at_level(logging.ERROR): - await streaming_handle.capnp_callback(0, 0, call_param, fulfiller) - assert "test error" in caplog.text - assert queue.qsize() == 1 - assert queue.get_nowait() == AnnotatedValue(value=42, path="dummy", timestamp=0) - fulfiller.fulfill.assert_called_once() - - -@pytest.mark.asyncio -async def test_distinct_data_queue_put_no_wait_new_value(): - subscription = FakeSubscription() - queue = DistinctConsecutiveDataQueue( - path="dummy", - handle=subscription, - ) - value1 = AnnotatedValue(value=1, path="dummy") - value2 = AnnotatedValue(value=2, path="dummy") - queue.put_nowait(value1) - queue.put_nowait(value2) - assert queue.qsize() == 2 - assert queue.get_nowait() == value1 - assert queue.get_nowait() == value2 - - -@pytest.mark.asyncio -async def test_distinct_data_queue_put_no_wait_same_value(): - subscription = FakeSubscription() - queue = DistinctConsecutiveDataQueue( - path="dummy", - handle=subscription, - ) - value = AnnotatedValue(value=1, path="dummy") - queue.put_nowait(value) - queue.put_nowait(value) - assert queue.qsize() == 1 - assert queue.get_nowait() == value - - -def test_distinct_data_queue_fork(): - subscription = FakeSubscription() - queue = DistinctConsecutiveDataQueue( - path="dummy", - handle=subscription, - ) - assert len(subscription.data_queues) == 1 - forked_queue = queue.fork() - assert isinstance(forked_queue, DistinctConsecutiveDataQueue) - assert len(subscription.data_queues) == 2 - assert forked_queue.path == queue.path - assert forked_queue.connected diff --git a/tests/mock/__init__.py b/tests/mock/__init__.py deleted file mode 100644 index b330fe8..0000000 --- a/tests/mock/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Testing module for mock server.""" diff --git a/tests/mock/ab_hpk_automatic_functionality_test.py b/tests/mock/ab_hpk_automatic_functionality_test.py deleted file mode 100644 index 3665cc6..0000000 --- a/tests/mock/ab_hpk_automatic_functionality_test.py +++ /dev/null @@ -1,241 +0,0 @@ -"""AB test to ensure that the mock behaves like the real hpk in required aspects. - -These tests run the same commands on a real hpk and the mock server. If the -behavior is the same, the tests pass. If the behavior is different, the test -fails. This is to ensure that the mock server behaves like the real hpk in -required aspects. - -This is especially important if the hpk evolves with time. Testing against real -functionality makes sure that the mock server is up to date. Depending tests -and code still has a meaning. - -To run these tests: - * start a real hpk server on localhost:8004 - * run pytest -k mock_compatibility - -By selecting the server to run on localhost:8004, it can be chosen with -which version of the hpk the mock server is compared. -""" - -import io -from contextlib import redirect_stdout - -import pytest - -from labone.core import AnnotatedValue, KernelInfo, KernelSession, ServerInfo -from labone.core.session import ListNodesFlags, Session -from labone.mock import AutomaticLabOneServer -from labone.mock.session import MockSession - - -async def get_session(): - return await KernelSession.create( - kernel_info=KernelInfo.zi_connection(), - server_info=ServerInfo(host="localhost", port=8004), - ) - - -async def get_mock_session() -> MockSession: - # this makes sure to work on same node tree as real session - session = await get_session() - paths_to_info = await session.list_nodes_info("*") - - return await AutomaticLabOneServer(paths_to_info).start_pipe() - - -def same_prints_and_exceptions_for_real_and_mock(test_function): - """ - Calls the decorated function with both the real hpk and - the mock version. Compares the behavior by comparing the - printed output. Use print statements in the decorated function - to compare the behavior! - If exceptions are raised, it is compared that they are raised - in both cases and are of the same kind. However, the message - is not compared. - """ - - async def new_test_function(*args, **kwargs): - session = await get_session() - mock_session = await get_mock_session() - string_output = io.StringIO() - string_output_mock = io.StringIO() - exception = None - exception_mock = None - try: - with redirect_stdout(string_output, *args, **kwargs): - await test_function(session) - except Exception as e: # noqa: BLE001 - exception = e - try: - with redirect_stdout(string_output_mock, *args, **kwargs): - await test_function(mock_session) - except Exception as e: # noqa: BLE001 - exception_mock = e - assert (exception is None) == (exception_mock is None) - if exception is not None: - assert type(exception) is type(exception_mock) - assert string_output.getvalue() == string_output_mock.getvalue() - - return new_test_function - - -@pytest.mark.mock_compatibility -@pytest.mark.parametrize( - "path", - [ - "", - "*", - "/", - "/*", - "/zi", - "/zi/", - "/zi/*", - "/zi/debug", - "/zi/debug/level", - "/zi/debug/*", - "/zi/debug/level/*", - "/zi/debug/level/*/*", - "/a/b", # test invalid node - ], -) -@pytest.mark.asyncio -async def test_list_nodes_compatible(path): - async def procedure(session): - nodes = await session.list_nodes( - path, - flags=ListNodesFlags.RECURSIVE - | ListNodesFlags.ABSOLUTE - | ListNodesFlags.LEAVES_ONLY, - ) - print(sorted(nodes)) # noqa: T201 - - await same_prints_and_exceptions_for_real_and_mock(procedure)() - - -@pytest.mark.mock_compatibility -@pytest.mark.parametrize( - "path", - [ - "", - "*", - "/", - "/*", - "/zi", - "/zi/", - "/zi/*", - "/zi/debug", - "/zi/debug/level", - "/zi/debug/*", - "/zi/debug/level/*", - "/zi/debug/level/*/*", - "/a/b", # test invalid node - ], -) -@pytest.mark.asyncio -async def test_list_nodes_info_compatible(path): - async def procedure(session): - nodes = await session.list_nodes_info( - path, - flags=ListNodesFlags.RECURSIVE - | ListNodesFlags.ABSOLUTE - | ListNodesFlags.LEAVES_ONLY, - ) - print(sorted(nodes)) # noqa: T201 - - await same_prints_and_exceptions_for_real_and_mock(procedure)() - - -@pytest.mark.mock_compatibility -@pytest.mark.asyncio -@pytest.mark.parametrize( - "path", - [ - "/zi/debug/level", - "/zi/debug/log", - "/zi*", - "/a/b", # test invalid node - ], -) -async def test_get_compatible(path): - async def procedure(session): - result = await session.get(path) - print(result.path) # noqa: T201 - - await same_prints_and_exceptions_for_real_and_mock(procedure)() - - -@pytest.mark.mock_compatibility -@pytest.mark.asyncio -@pytest.mark.parametrize( - "path", - [ - "/zi/debug/level", - "/zi/debug/log", - "/a/b", # test invalid node - ], -) -@pytest.mark.parametrize("value", [1, 24, 0, -1]) -async def test_state_keeping_compatible(path, value): - async def procedure(session): - await session.set(AnnotatedValue(path=path, value=value)) - result = await session.get(path) - print(result.path, result.value, result.extra_header) # noqa: T201 - - await same_prints_and_exceptions_for_real_and_mock(procedure)() - - -@pytest.mark.mock_compatibility -@pytest.mark.parametrize( - "expression", - [ - "", - "*", - "/", - "/*", - "/zi", - "/zi/", - "/zi/*", - "/zi/debug", - "/zi/debug/level", - "/zi/debug/*", - "/zi/debug/level/*", - "/zi/debug/level/*/*", - ], -) -@pytest.mark.asyncio -async def test_get_with_expression_compatible(expression): - async def procedure(session): - result = await session.get_with_expression(expression) - print(sorted([r.path for r in result])) # noqa: T201 - - await same_prints_and_exceptions_for_real_and_mock(procedure)() - - -@pytest.mark.mock_compatibility -@pytest.mark.parametrize( - "expression", - [ - "", - "*", - "/", - "/*", - "/zi", - "/zi/", - "/zi/*", - "/zi/debug", - "/zi/debug/level", - "/zi/debug/*", - "/zi/debug/level/*", - "/zi/debug/level/*/*", - ], -) -@pytest.mark.asyncio -async def test_set_with_expression_compatible(expression): - @same_prints_and_exceptions_for_real_and_mock - async def procedure(session: Session): - result = await session.set_with_expression( - AnnotatedValue(path=expression, value=17), - ) - print(sorted([r.path for r in result])) # noqa: T201 - - await procedure() diff --git a/tests/mock/module_test.py b/tests/mock/module_test.py deleted file mode 100644 index 462dde5..0000000 --- a/tests/mock/module_test.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Scope: session, capnp (without localhost, but within python), - server, concrete server, functionality - -Subscription behavior can only be tested with a client, holding a queue and -a mock server on the other side of capnp. Aiming to test the -AutomaticLabOneServer, -we still need to use a larger scope in order to test meaningful behavior. - -""" - -import numpy as np -import pytest - -from labone.core import hpk_schema -from labone.core.shf_vector_data import ( - ShfDemodulatorVectorData, - ShfResultLoggerVectorData, - ShfScopeVectorData, -) -from labone.mock import AutomaticLabOneServer - - -@pytest.mark.asyncio -async def test_useable_via_entry_point(): - """If this crashes, the module is not useable in the desired manner. - Tests that a session can be established and used. - Only looks that no error is raised. - """ - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - await session.set(value=7, path="/a/b") - - -@pytest.mark.asyncio -async def test_subscription(): - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.set(path="/a/b", value=7) - assert (await queue.get()).value == 7 - assert queue.empty() - - -@pytest.mark.asyncio -async def test_unsubscribe(): - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - queue.disconnect() - await session.set(path="/a/b", value=7) - assert queue.empty() - - -@pytest.mark.asyncio -async def test_subscription_multiple_changes(): - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.set(path="/a/b", value=7) - await session.set(path="/a/b", value=3) - await session.set(path="/a/b", value=5) - assert (await queue.get()).value == 7 - assert (await queue.get()).value == 3 - assert (await queue.get()).value == 5 - assert queue.empty() - - -@pytest.mark.asyncio -async def test_subscription_seperate_for_each_path(): - session = await AutomaticLabOneServer({"/a/b": {}, "/a/c": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - queue2 = await session.subscribe("/a/c") - await session.set(path="/a/b", value=7) - await session.set(path="/a/c", value=5) - assert (await queue.get()).value == 7 - assert (await queue2.get()).value == 5 - assert queue.empty() - assert queue2.empty() - - -@pytest.mark.asyncio -async def test_subscription_updated_by_set_with_expression(): - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.set_with_expression(path="/a", value=7) - assert (await queue.get()).value == 7 - assert queue.empty() - - -@pytest.mark.asyncio -async def test_shf_scope_vector_handled_correctly_through_set_and_subscription(): - value = ShfScopeVectorData( - vector=np.array([6 + 6j, 3 + 3j], dtype=np.complex128), - properties=hpk_schema.ShfScopeVectorData().properties, - ) - value.properties.scaling = 3.0 - value.properties.centerFrequency = 7 - - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.mock_server.set(path="/a/b", value=value) - result = await queue.get() - assert np.allclose(result.value.vector, value.vector) - - -@pytest.mark.asyncio -async def test_shf_result_logger_vector_handled_correctly_in_set_and_subscribe(): - value = ShfResultLoggerVectorData( - vector=np.array([50 + 100j, 100 + 150j], dtype=np.complex128), - properties=hpk_schema.ShfResultLoggerVectorData().properties, - ) - value.properties.scaling = 3.0 - value.properties.centerFrequency = 7 - - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.mock_server.set(path="/a/b", value=value) - result = await queue.get() - assert np.allclose(result.value.vector, value.vector) - - -@pytest.mark.asyncio -async def test_shf_demodulator_vector_handled_correctly_through_set_and_subscription(): - value = ShfDemodulatorVectorData( - x=np.array([6, 3], dtype=np.int64), - y=np.array([7, 2], dtype=np.int64), - properties=hpk_schema.ShfDemodulatorVectorData().properties, - ) - session = await AutomaticLabOneServer({"/a/b": {}}).start_pipe() - - queue = await session.subscribe("/a/b") - await session.mock_server.set(path="/a/b", value=value) - subscription_value = await queue.get() - assert np.allclose(subscription_value.value.x, value.x) - assert np.allclose(subscription_value.value.y, value.y) - - -@pytest.mark.asyncio -async def test_ensure_compatibility(): - session = await AutomaticLabOneServer({}).start_pipe() - session.ensure_compatibility() diff --git a/tests/mock/test_automatic_mock_functionality.py b/tests/mock/test_automatic_mock_functionality.py deleted file mode 100644 index 4d78a75..0000000 --- a/tests/mock/test_automatic_mock_functionality.py +++ /dev/null @@ -1,282 +0,0 @@ -"""Unit Tests for the AutomaticLabOneServer.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING - -import numpy as np -import pytest - -from labone.core.errors import LabOneCoreError - -if TYPE_CHECKING: - from labone.core.helper import LabOneNodePath -from labone.core.value import AnnotatedValue, Value -from labone.mock import AutomaticLabOneServer - - -async def get_functionality_with_state(state: dict[LabOneNodePath, Value]): - functionality = AutomaticLabOneServer({path: {} for path in state}) - for path, value in state.items(): - await functionality.set(AnnotatedValue(value=value, path=path)) - return functionality - - -@pytest.mark.asyncio -async def test_node_info_default_readable(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.get("/a/b") - - -@pytest.mark.asyncio -async def test_node_info_default_writable(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.set(AnnotatedValue(path="/a/b", value=1)) - - -async def check_state_agrees_with( - functionality: AutomaticLabOneServer, - state: dict[LabOneNodePath, Value], -) -> bool: - for path, value in state.items(): - if (await functionality.get(path)).value != value: - return False - return True - - -@pytest.mark.asyncio -async def test_remembers_state(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.set(AnnotatedValue(value=123, path="/a/b")) - assert (await functionality.get("/a/b")).value == 123 - - -@pytest.mark.asyncio -async def test_relavtive_path(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.set(AnnotatedValue(value=123, path="b")) - assert (await functionality.get("b")).value == 123 - - -@pytest.mark.asyncio -async def test_state_overwritable(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.set(AnnotatedValue(value=123, path="/a/b")) - await functionality.set(AnnotatedValue(value=456, path="/a/b")) - assert (await functionality.get("/a/b")).value == 456 - - -@pytest.mark.asyncio -async def test_seperate_state_per_path(): - functionality = AutomaticLabOneServer({"/a/b": {}, "/a/c": {}}) - await functionality.set(AnnotatedValue(value=123, path="/a/b")) - await functionality.set(AnnotatedValue(value=456, path="/a/c")) - assert (await functionality.get("/a/b")).value == 123 - assert (await functionality.get("/a/c")).value == 456 - - -@pytest.mark.asyncio -async def test_cannot_get_outside_of_tree_structure(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - with pytest.raises(Exception): # noqa: B017 - await functionality.get("/a/c") - - -@pytest.mark.asyncio -async def test_cannot_set_outside_of_tree_structure(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - with pytest.raises(Exception): # noqa: B017 - await functionality.set(AnnotatedValue(value=123, path="/a/c")) - - -@pytest.mark.asyncio -async def test_list_nodes_answered_by_tree_structure(): - functionality = AutomaticLabOneServer( - {"/x": {}, "/x/y": {}, "/v/w/q/a": {}}, - ) - assert set(await functionality.list_nodes("*")) == {"/x", "/x/y", "/v/w/q/a"} - - -@pytest.mark.parametrize( - ("path_to_info", "path", "expected"), - [ - # test option to get all paths with * - ({}, "*", {}), - ({"/a/b": {}}, "*", {"/a/b": {}}), - ({"/a": {}, "/b": {}, "/c/d/e": {}}, "*", {"/a": {}, "/b": {}, "/c/d/e": {}}), - # if specific path, not necessarily all paths are returned - ({}, "/a", {}), - ({"/a/b": {}}, "/c", {}), - ( - {"/x/y": {}, "/x/z/n": {"Description": "_"}, "/x/z/q/a": {}}, - "/x/z", - {"/x/z/n": {"Description": "_"}, "/x/z/q/a": {}}, - ), - ({"/a/b": {}, "/a/c": {}}, "/a", {"/a/b": {}, "/a/c": {}}), - # a path matches itself - ({"/a/b": {}}, "/a/b", {"/a/b": {}}), - # a path does not match itself plus wildcard - ({"/a/b": {}}, "/a/b/*", {}), - # test wildcard constillations - ({"/a/b": {}, "/a/c": {}}, "/*/b", {"/a/b": {}}), - ({"/a/b": {}, "/a/c": {}}, "/*", {"/a/b": {}, "/a/c": {}}), - ], -) -@pytest.mark.asyncio -async def test_list_nodes_info(path_to_info, path, expected): - functionality = AutomaticLabOneServer(path_to_info) - assert (await functionality.list_nodes_info(path)).keys() == expected.keys() - - -@pytest.mark.parametrize( - "path_to_info", - [ - {}, - {"/a/b": {}}, - {"/a": {}, "/b": {}, "/c/d/e": {}}, - {"/x/y/1": {}, "/x/y/2": {}, "/x/z/n": {}, "/x/z/q/a": {}}, - ], -) -@pytest.mark.parametrize( - "path", - [ - "", - "/a/*", - "/x/y/*", - "/x/z/*", - "/x/*", - "/*", - ], -) -@pytest.mark.asyncio -async def test_consistency_list_nodes_vs_list_nodes_info(path_to_info, path): - functionality = AutomaticLabOneServer(path_to_info) - - assert set((await functionality.list_nodes_info(path)).keys()) == set( - await functionality.list_nodes(path), - ) - - -@pytest.mark.parametrize( - ("expression", "expected"), - [ - ("/*", {1, 2, 3, 4, 5}), - ("*", {1, 2, 3, 4, 5}), - ("/a/b/c", {1}), - ("/a/b", {1, 2}), - ("/a", {1, 2, 3, 4}), - ("/a/x", {3, 4}), - ], -) -@pytest.mark.asyncio -async def test_get_with_expression(expression, expected): - functionality = await get_functionality_with_state( - { - "/a/b/c": 1, - "/a/b/d": 2, - "/a/x": 3, - "/a/x/y": 4, - "/b": 5, - }, - ) - assert { - ann.value for ann in (await functionality.get_with_expression(expression)) - } == expected - - -@pytest.mark.parametrize( - ("expression", "value", "expected_new_state"), - [ - ("*", 7, {"/a/b/c": 7, "/a/b/d": 7, "/a/x": 7, "/a/x/y": 7, "/b": 7}), - ("/a/b/c", 7, {"/a/b/c": 7, "/a/b/d": 2, "/a/x": 3, "/a/x/y": 4, "/b": 5}), - ("/a/b", 7, {"/a/b/c": 7, "/a/b/d": 7, "/a/x": 3, "/a/x/y": 4, "/b": 5}), - ( - "/a", - 7, - { - "/a/b/c": 7, - "/a/b/d": 7, - "/a/x": 7, - "/a/x/y": 7, - "/b": 5, - }, - ), - ], -) -@pytest.mark.asyncio -async def test_set_with_expression(expression, value, expected_new_state): - functionality = await get_functionality_with_state( - { - "/a/b/c": 1, - "/a/b/d": 2, - "/a/x": 3, - "/a/x/y": 4, - "/b": 5, - }, - ) - - await functionality.set_with_expression( - AnnotatedValue(value=value, path=expression), - ) - - assert await check_state_agrees_with(functionality, expected_new_state) - - -@pytest.mark.parametrize( - "value", - [ - 5, - 6.3, - "hello", - b"hello", - 2 + 3j, - ], -) -@pytest.mark.asyncio -async def test_handling_of_multiple_data_types(value: Value): - functionality = AutomaticLabOneServer({"/a/b": {}}) - await functionality.set(AnnotatedValue(value=value, path="/a/b")) - assert (await functionality.get("/a/b")).value == value - - -@pytest.mark.asyncio -async def test_handling_of_numpy_array(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - value = np.array([1, 2, 3]) - await functionality.set(AnnotatedValue(value=value, path="/a/b")) - assert np.all((await functionality.get("/a/b")).value == value) - - -@pytest.mark.asyncio -async def test_timestamps_are_increasing(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - - # calling set 10 times to ensure higher probability for wrong order, - # if timestamps are not increasing - responses = [ - await functionality.set(AnnotatedValue(value=1, path="/a/b")) for _ in range(10) - ] - - # calling all functions with timestamp once - responses.append(await functionality.get("/a/b")) - responses += await functionality.set_with_expression( - AnnotatedValue(value=2, path="/a/*"), - ) - responses += await functionality.get_with_expression("/a/*") - - sorted_by_timestamp = sorted(responses, key=lambda x: x.timestamp) - assert sorted_by_timestamp == responses - - -@pytest.mark.asyncio -async def test_cannot_set_readonly_node(): - functionality = AutomaticLabOneServer({"/a/b": {"Properties": "Read"}}) - with pytest.raises(LabOneCoreError): - await functionality.set(AnnotatedValue(value=1, path="/a/b")) - - -@pytest.mark.asyncio -async def test_error_when_set_with_expression_no_matches(): - functionality = AutomaticLabOneServer({"/a/b": {}}) - with pytest.raises(LabOneCoreError): - await functionality.set_with_expression(AnnotatedValue(value=1, path="/b/*")) diff --git a/tests/mock/test_session_mock_template.py b/tests/mock/test_session_mock_template.py deleted file mode 100644 index c25b266..0000000 --- a/tests/mock/test_session_mock_template.py +++ /dev/null @@ -1,274 +0,0 @@ -""" -Scope: session, capnp, mock server, concrete (hpk) mock server - -The desired properties of the session mock template are that -a call to a session will result in a corresponding call to the functionality. - -As the functionality is seperated via an interface, we test that this -interface will be called correctly. Thus we show, that session, capnp and -server mock are compatible and work as a functional unit. -""" - -from __future__ import annotations - -from unittest.mock import ANY, AsyncMock - -import pytest - -from labone.core.session import ListNodesFlags -from labone.core.value import AnnotatedValue -from labone.mock.session import LabOneServerBase - - -class MockLabOneServer(LabOneServerBase): - async def set(self, value: AnnotatedValue) -> AnnotatedValue: ... - - async def get(self, path: str) -> AnnotatedValue: ... - - async def get_with_expression(self, path: str) -> list[AnnotatedValue]: ... - - async def set_with_expression( - self, - value: AnnotatedValue, - ) -> list[AnnotatedValue]: ... - - async def subscribe(self) -> None: ... - - async def list_nodes(self, path: str, flags: ListNodesFlags) -> list[str]: ... - - async def list_nodes_info( - self, - path: str, - flags: ListNodesFlags, - ) -> dict[str, dict]: ... - - -@pytest.mark.asyncio -async def test_set_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.set = AsyncMock() - functionality.set.return_value = AnnotatedValue( - path="/mock/path", - value=1, - timestamp=2, - ) - - session = await functionality.start_pipe() - - response = await session.set(AnnotatedValue(path="/a/b", value=7)) - - # propogates to functionality - functionality.set.assert_called_once_with(AnnotatedValue(path="/a/b", value=7)) - - # returns functionality response - assert response == AnnotatedValue(path="/mock/path", value=1, timestamp=2) - - -@pytest.mark.asyncio -async def test_get_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.get = AsyncMock() - functionality.get.return_value = AnnotatedValue( - path="/mock/path", - value=1, - timestamp=2, - ) - - session = await functionality.start_pipe() - - response = await session.get("/a/b") - - # propogates to functionality - functionality.get.assert_called_once_with("/a/b") - - # returns functionality response - assert response == AnnotatedValue(path="/mock/path", value=1, timestamp=2) - - -@pytest.mark.asyncio -async def test_get_with_expression_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.get_with_expression = AsyncMock() - functionality.get_with_expression.return_value = [ - AnnotatedValue(path="/mock/path", value=1, timestamp=2), - AnnotatedValue(path="/mock/path/2", value=3, timestamp=4), - ] - - session = await functionality.start_pipe() - - response = await session.get_with_expression("/a/b") - - # propogates to functionality. Note: no guarantees for flags tested! - functionality.get_with_expression.assert_called_once_with("/a/b", flags=ANY) - - # returns functionality response - assert response == [ - AnnotatedValue(path="/mock/path", value=1, timestamp=2), - AnnotatedValue(path="/mock/path/2", value=3, timestamp=4), - ] - - -@pytest.mark.asyncio -async def test_set_with_expression_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.set_with_expression = AsyncMock() - functionality.set_with_expression.return_value = [ - AnnotatedValue(path="/mock/path", value=1, timestamp=2), - AnnotatedValue(path="/mock/path/2", value=1, timestamp=4), - ] - - session = await functionality.start_pipe() - - response = await session.set_with_expression(AnnotatedValue(path="/a/b", value=7)) - - # propogates to functionality - functionality.set_with_expression.assert_called_once_with( - AnnotatedValue(path="/a/b", value=7), - ) - - # returns functionality response - assert response == [ - AnnotatedValue(path="/mock/path", value=1, timestamp=2), - AnnotatedValue(path="/mock/path/2", value=1, timestamp=4), - ] - - -@pytest.mark.asyncio -async def test_subscribe_propagates_to_functionality(): - functionality = MockLabOneServer() - functionality.subscribe = AsyncMock() - functionality.subscribe.return_value = None - - session = await functionality.start_pipe() - - await session.subscribe("/a/b") - - # propogates to functionality - functionality.subscribe.assert_called_once() - - -@pytest.mark.asyncio -async def test_list_nodes_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.list_nodes = AsyncMock() - functionality.list_nodes.return_value = ["/a/b", "/a/c"] - - session = await functionality.start_pipe() - - response = await session.list_nodes("/a/b", flags=ListNodesFlags.ABSOLUTE) - - # propogates to functionality - functionality.list_nodes.assert_called_once_with( - "/a/b", - flags=ListNodesFlags.ABSOLUTE, - ) - - # returns functionality response - assert response == ["/a/b", "/a/c"] - - -@pytest.mark.asyncio -async def test_list_nodes_info_propagates_to_functionality_and_back(): - functionality = MockLabOneServer() - functionality.list_nodes_info = AsyncMock() - functionality.list_nodes_info.return_value = { - "/a/b": {"value": 1}, - "/a/c": {"value": 2}, - } - - session = await functionality.start_pipe() - - response = await session.list_nodes_info(path="/a/b", flags=ListNodesFlags.ABSOLUTE) - - # propogates to functionality - functionality.list_nodes_info.assert_called_once() - - # returns functionality response - assert response == { - "/a/b": {"value": 1}, - "/a/c": {"value": 2}, - } - - -@pytest.mark.asyncio -async def test_errors_in_set_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.set = AsyncMock() - functionality.set.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.set(AnnotatedValue(path="/a/b", value=7)) - - -@pytest.mark.asyncio -async def test_errors_in_get_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.get = AsyncMock() - functionality.get.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.get("/a/b") - - -@pytest.mark.asyncio -async def test_errors_in_get_with_expression_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.get_with_expression = AsyncMock() - functionality.get_with_expression.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.get_with_expression("/a/b") - - -@pytest.mark.asyncio -async def test_errors_in_set_with_expression_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.set_with_expression = AsyncMock() - functionality.set_with_expression.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.set_with_expression(AnnotatedValue(path="/a/b", value=7)) - - -@pytest.mark.asyncio -async def test_errors_in_subscribe_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.subscribe = AsyncMock() - functionality.subscribe.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.subscribe("/a/b") - - -@pytest.mark.asyncio -async def test_errors_in_list_nodes_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.list_nodes = AsyncMock() - functionality.list_nodes.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.list_nodes("/a/b") - - -@pytest.mark.asyncio -async def test_errors_in_list_nodes_info_functionality_are_transmitted_back(): - functionality = MockLabOneServer() - functionality.list_nodes_info = AsyncMock() - functionality.list_nodes_info.side_effect = Exception("Some error") - - session = await functionality.start_pipe() - - with pytest.raises(Exception): # noqa: B017 - await session.list_nodes_info(path="/a/b") diff --git a/tests/mock_server_for_testing.py b/tests/mock_server_for_testing.py deleted file mode 100644 index 1e26b46..0000000 --- a/tests/mock_server_for_testing.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Helper for using mock server for high level testing.""" - -from __future__ import annotations - -import typing as t -from unittest.mock import AsyncMock, Mock - -from labone.core.session import NodeInfo, Session -from labone.mock import AutomaticLabOneServer -from labone.nodetree.entry_point import construct_nodetree - -if t.TYPE_CHECKING: - from labone.core.value import AnnotatedValue - from labone.nodetree.node import Node - - -async def get_mocked_node( - nodes_to_info: dict[str, NodeInfo], - *, - hide_kernel_prefix: bool = False, - custom_parser: t.Callable[[AnnotatedValue], AnnotatedValue] | None = None, -) -> Node: - """Uses custom mock implementation. - - Use when testing calls to the server. - """ - session_mock = await AutomaticLabOneServer(nodes_to_info).start_pipe() - return ( - await construct_nodetree( - session_mock, - hide_kernel_prefix=hide_kernel_prefix, - custom_parser=custom_parser, - ) - ).root - - -async def get_unittest_mocked_node( - nodes_to_info: dict[str, NodeInfo], - *, - hide_kernel_prefix: bool = False, -) -> Node: - """Minimal unittest mock. - - Use when no calls to the server are tested. - This way, the tests do not depend on the `labone` - mock server. - """ - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value=nodes_to_info) - session_mock.get_with_expression = AsyncMock() - - return ( - await construct_nodetree(session_mock, hide_kernel_prefix=hide_kernel_prefix) - ).root diff --git a/tests/nodetree/__init__.py b/tests/nodetree/__init__.py deleted file mode 100644 index 5ee5098..0000000 --- a/tests/nodetree/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Test Module for the labone.node subpackage.""" diff --git a/tests/nodetree/resources/device_nodes_info.json b/tests/nodetree/resources/device_nodes_info.json deleted file mode 100644 index cb68ac7..0000000 --- a/tests/nodetree/resources/device_nodes_info.json +++ /dev/null @@ -1,4791 +0,0 @@ -{ - "/dev1234/system/kerneltype": { - "Node": "/dev1234/SYSTEM/KERNELTYPE", - "Description": "Returns the type of the data server kernel (mdk or hpk).", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/update": { - "Node": "/dev1234/SYSTEM/UPDATE", - "Description": "Requests update of the device firmware and bitstream from the dataserver.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/clockbase": { - "Node": "/dev1234/CLOCKBASE", - "Description": "Returns the internal clock frequency of the device.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/system/activeinterface": { - "Node": "/dev1234/SYSTEM/ACTIVEINTERFACE", - "Description": "Currently active interface of the device.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/fwrevision": { - "Node": "/dev1234/SYSTEM/FWREVISION", - "Description": "Revision of the device-internal controller software.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/fpgarevision": { - "Node": "/dev1234/SYSTEM/FPGAREVISION", - "Description": "HDL firmware revision.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/features/serial": { - "Node": "/dev1234/FEATURES/SERIAL", - "Description": "Device serial number.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/features/devtype": { - "Node": "/dev1234/FEATURES/DEVTYPE", - "Description": "Returns the device type.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/stats/physical/power/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/0", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/1", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/2", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/3", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/4", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/5", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/6", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/7", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/8", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/9", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/10", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/11": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/11", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/12": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/12", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/13": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/13", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/14": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/14", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/15": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/15", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/16": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/16", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/17": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/17", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/18": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/18", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/19": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/19", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/20": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/20", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/21": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/21", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/22": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/22", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/23": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/23", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/24": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/24", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/25": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/25", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/26": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/26", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/voltages/27": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/VOLTAGES/27", - "Description": "Voltages of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/power/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/0", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/1", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/2": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/2", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/3": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/3", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/4": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/4", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/5": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/5", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/6": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/6", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/7": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/7", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/8": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/8", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/9": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/9", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/10": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/10", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/11": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/11", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/12": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/12", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/13": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/13", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/14": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/14", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/15": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/15", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/16": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/16", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/17": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/17", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/18": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/18", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/19": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/19", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/20": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/20", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/21": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/21", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/22": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/22", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/23": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/23", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/24": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/24", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/25": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/25", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/26": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/26", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/currents/27": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/CURRENTS/27", - "Description": "Currents of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/power/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/0", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/1", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/2", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/3", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/4", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/5", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/6": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/6", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/7": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/7", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/8": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/8", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/9": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/9", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/10": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/10", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/11": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/11", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/12": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/12", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/13": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/13", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/14": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/14", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/15": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/15", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/16": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/16", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/17": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/17", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/18": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/18", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/19": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/19", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/20": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/20", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/21": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/21", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/22": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/22", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/23": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/23", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/24": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/24", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/25": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/25", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/26": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/26", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/power/temperatures/27": { - "Node": "/dev1234/STATS/PHYSICAL/POWER/TEMPERATURES/27", - "Description": "Temperatures of the main power supply.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/VOLTAGES/0", - "Description": "Internal voltage measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/VOLTAGES/1", - "Description": "Internal voltage measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/VOLTAGES/2", - "Description": "Internal voltage measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/VOLTAGES/3", - "Description": "Internal voltage measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/VOLTAGES/4", - "Description": "Internal voltage measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/CURRENTS/0", - "Description": "Internal current measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "mA" - }, - "/dev1234/stats/physical/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/CURRENTS/1", - "Description": "Internal current measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "mA" - }, - "/dev1234/stats/physical/currents/2": { - "Node": "/dev1234/STATS/PHYSICAL/CURRENTS/2", - "Description": "Internal current measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "mA" - }, - "/dev1234/stats/physical/currents/3": { - "Node": "/dev1234/STATS/PHYSICAL/CURRENTS/3", - "Description": "Internal current measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "mA" - }, - "/dev1234/stats/physical/currents/4": { - "Node": "/dev1234/STATS/PHYSICAL/CURRENTS/4", - "Description": "Internal current measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "mA" - }, - "/dev1234/stats/physical/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/0", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/1", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/2", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/3", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/4", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/5", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/6": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/6", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/temperatures/7": { - "Node": "/dev1234/STATS/PHYSICAL/TEMPERATURES/7", - "Description": "Internal temperature measurements.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/fanspeeds/0": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/0", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/stats/physical/fanspeeds/1": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/1", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/stats/physical/fanspeeds/2": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/2", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/stats/physical/fanspeeds/3": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/3", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/stats/physical/fanspeeds/4": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/4", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/stats/physical/fanspeeds/5": { - "Node": "/dev1234/STATS/PHYSICAL/FANSPEEDS/5", - "Description": "Speed of the internal cooling fans for monitoring.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "RPM" - }, - "/dev1234/status/flags/binary": { - "Node": "/dev1234/STATUS/FLAGS/BINARY", - "Description": "A set of binary flags giving an indication of the state of various parts of the device. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/status/adc0min": { - "Node": "/dev1234/STATUS/ADC0MIN", - "Description": "The minimum value on Signal Input 1 (ADC0) during 100 ms", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/status/adc0max": { - "Node": "/dev1234/STATUS/ADC0MAX", - "Description": "The maximum value on Signal Input 1 (ADC0) during 100 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/status/adc1min": { - "Node": "/dev1234/STATUS/ADC1MIN", - "Description": "The minimum value on Signal Input 2 (ADC1) during 100 ms", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/status/adc1max": { - "Node": "/dev1234/STATUS/ADC1MAX", - "Description": "The maximum value on Signal Input 2 (ADC1) during 100 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/status/time": { - "Node": "/dev1234/STATUS/TIME", - "Description": "The current timestamp.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/stats/physical/fpga/temp": { - "Node": "/dev1234/STATS/PHYSICAL/FPGA/TEMP", - "Description": "Internal temperature of the FPGA.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/fpga/core": { - "Node": "/dev1234/STATS/PHYSICAL/FPGA/CORE", - "Description": "Core voltage of the FPGA.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/fpga/aux": { - "Node": "/dev1234/STATS/PHYSICAL/FPGA/AUX", - "Description": "Supply voltage of the FPGA.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/fpga/pstemp": { - "Node": "/dev1234/STATS/PHYSICAL/FPGA/PSTEMP", - "Description": "Internal temperature of the FPGA's processor system.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/overtemperature": { - "Node": "/dev1234/STATS/PHYSICAL/OVERTEMPERATURE", - "Description": "This flag is set to 1 if the temperature of the FPGA exceeds 85\u00b0C. It will be reset to 0 after a restart of the device.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/nics/0/saveip": { - "Node": "/dev1234/SYSTEM/NICS/0/SAVEIP", - "Description": "If written, this action will program the defined static IP address to the device.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/nics/0/defaultip4": { - "Node": "/dev1234/SYSTEM/NICS/0/DEFAULTIP4", - "Description": "IPv4 address of the device to use if static IP is enabled.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/defaultmask": { - "Node": "/dev1234/SYSTEM/NICS/0/DEFAULTMASK", - "Description": "IPv4 mask in case of static IP.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/defaultgateway": { - "Node": "/dev1234/SYSTEM/NICS/0/DEFAULTGATEWAY", - "Description": "Default gateway configuration for the network connection.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/static": { - "Node": "/dev1234/SYSTEM/NICS/0/STATIC", - "Description": "Enable this flag if the device is used in a network with fixed IP assignment without a DHCP server.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/nics/0/mac": { - "Node": "/dev1234/SYSTEM/NICS/0/MAC", - "Description": "Current MAC address of the device network interface.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/ip4": { - "Node": "/dev1234/SYSTEM/NICS/0/IP4", - "Description": "Current IPv4 of the device.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/mask": { - "Node": "/dev1234/SYSTEM/NICS/0/MASK", - "Description": "Current network mask.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/0/gateway": { - "Node": "/dev1234/SYSTEM/NICS/0/GATEWAY", - "Description": "Current network gateway.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/saveip": { - "Node": "/dev1234/SYSTEM/NICS/1/SAVEIP", - "Description": "If written, this action will program the defined static IP address to the device.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/nics/1/defaultip4": { - "Node": "/dev1234/SYSTEM/NICS/1/DEFAULTIP4", - "Description": "IPv4 address of the device to use if static IP is enabled.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/defaultmask": { - "Node": "/dev1234/SYSTEM/NICS/1/DEFAULTMASK", - "Description": "IPv4 mask in case of static IP.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/defaultgateway": { - "Node": "/dev1234/SYSTEM/NICS/1/DEFAULTGATEWAY", - "Description": "Default gateway configuration for the network connection.", - "Properties": "Read, Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/static": { - "Node": "/dev1234/SYSTEM/NICS/1/STATIC", - "Description": "Enable this flag if the device is used in a network with fixed IP assignment without a DHCP server.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/nics/1/mac": { - "Node": "/dev1234/SYSTEM/NICS/1/MAC", - "Description": "Current MAC address of the device network interface.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/ip4": { - "Node": "/dev1234/SYSTEM/NICS/1/IP4", - "Description": "Current IPv4 of the device.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/mask": { - "Node": "/dev1234/SYSTEM/NICS/1/MASK", - "Description": "Current network mask.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/nics/1/gateway": { - "Node": "/dev1234/SYSTEM/NICS/1/GATEWAY", - "Description": "Current network gateway.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/stall": { - "Node": "/dev1234/SYSTEM/STALL", - "Description": "Indicates if the network connection is stalled.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/identify": { - "Node": "/dev1234/SYSTEM/IDENTIFY", - "Description": "Setting this node to 1 will cause the device to blink the power led for a few seconds.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/features/options": { - "Node": "/dev1234/FEATURES/OPTIONS", - "Description": "Returns enabled options.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/features/code": { - "Node": "/dev1234/FEATURES/CODE", - "Description": "Node providing a mechanism to enter feature codes into the instrument.", - "Properties": "Write", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/properties/timebase": { - "Node": "/dev1234/SYSTEM/PROPERTIES/TIMEBASE", - "Description": "Minimal time difference between two timestamps. The value is equal to 1/(maximum sampling rate).", - "Properties": "Read", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/system/properties/freqresolution": { - "Node": "/dev1234/SYSTEM/PROPERTIES/FREQRESOLUTION", - "Description": "The number of bits used to represent a frequency.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/properties/negativefreq": { - "Node": "/dev1234/SYSTEM/PROPERTIES/NEGATIVEFREQ", - "Description": "Indicates whether negative frequencies are supported.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/properties/minfreq": { - "Node": "/dev1234/SYSTEM/PROPERTIES/MINFREQ", - "Description": "The minimum oscillator frequency that can be set.", - "Properties": "Read", - "Type": "Double", - "Unit": "None" - }, - "/dev1234/system/properties/maxfreq": { - "Node": "/dev1234/SYSTEM/PROPERTIES/MAXFREQ", - "Description": "The maximum oscillator frequency that can be set.", - "Properties": "Read", - "Type": "Double", - "Unit": "None" - }, - "/dev1234/system/properties/mintimeconstant": { - "Node": "/dev1234/SYSTEM/PROPERTIES/MINTIMECONSTANT", - "Description": "The minimum demodulator time constant that can be set. Only relevant for lock-in amplifiers.", - "Properties": "Read", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/system/properties/maxtimeconstant": { - "Node": "/dev1234/SYSTEM/PROPERTIES/MAXTIMECONSTANT", - "Description": "The maximum demodulator time constant that can be set. Only relevant for lock-in amplifiers.", - "Properties": "Read", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/system/properties/freqscaling": { - "Node": "/dev1234/SYSTEM/PROPERTIES/FREQSCALING", - "Description": "The scale factor to use to convert a frequency represented as a freqresolution-bit integer to a floating point value.", - "Properties": "Read", - "Type": "Double", - "Unit": "None" - }, - "/dev1234/system/properties/maxdemodrate": { - "Node": "/dev1234/SYSTEM/PROPERTIES/MAXDEMODRATE", - "Description": "The maximum demodulator rate that can be set. Only relevant for lock-in amplifiers.", - "Properties": "Read", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/system/swtriggers/0/single": { - "Node": "/dev1234/SYSTEM/SWTRIGGERS/0/SINGLE", - "Description": "Issues a single software trigger event.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/fwlogenable": { - "Node": "/dev1234/SYSTEM/FWLOGENABLE", - "Description": "Enables logging to the fwlog node.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/boardrevisions/0": { - "Node": "/dev1234/SYSTEM/BOARDREVISIONS/0", - "Description": "Hardware revision of the FPGA base board", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/boardrevisions/1": { - "Node": "/dev1234/SYSTEM/BOARDREVISIONS/1", - "Description": "Hardware revision of the analog board", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/system/shutdown": { - "Node": "/dev1234/SYSTEM/SHUTDOWN", - "Description": "Sending a '1' to this node initiates a shutdown of the operating system on the device. It is recommended to trigger this shutdown before switching the device off with the hardware switch at the back side of the device.", - "Properties": "Read, Write", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/clocks/referenceclock/in/source": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/IN/SOURCE", - "Description": "The intended reference clock source. When the source is changed, all the instruments connected with ZSync links will be disconnected. The connection should be re-established manually.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"internal\": The internal clock is intended to be used as the frequency and time base reference.", - "1": "\"external\": An external clock is intended to be used as the frequency and time base reference. Provide a clean and stable 10 MHz or 100 MHz reference to the appropriate back panel connector." - } - }, - "/dev1234/system/clocks/referenceclock/in/sourceactual": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/IN/SOURCEACTUAL", - "Description": "The actual reference clock source.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"internal\": The internal clock is used as the frequency and time base reference.", - "1": "\"external\": An external clock is used as the frequency and time base reference." - } - }, - "/dev1234/system/clocks/referenceclock/in/status": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/IN/STATUS", - "Description": "Status of the reference clock.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"locked\": Reference clock has been locked on.", - "1": "\"error\": There was an error locking onto the reference clock signal.", - "2": "\"busy\": The device is busy trying to lock onto the reference clock signal." - } - }, - "/dev1234/system/clocks/referenceclock/in/freq": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/IN/FREQ", - "Description": "Indicates the frequency of the reference clock.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/system/clocks/referenceclock/out/enable": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/OUT/ENABLE", - "Description": "Enable clock signal on the reference clock output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/system/clocks/referenceclock/out/freq": { - "Node": "/dev1234/SYSTEM/CLOCKS/REFERENCECLOCK/OUT/FREQ", - "Description": "Select the frequency of the output reference clock. Only 10 MHz and 100 MHz are allowed.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/system/fwlog": { - "Node": "/dev1234/SYSTEM/FWLOG", - "Description": "Returns log output of the firmware.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/dev1234/stats/physical/sigouts/0/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/0", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/1", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/2", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/3", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/4", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/5", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/6", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/7", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/8", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/9", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/10", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/voltages/11": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/VOLTAGES/11", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/0/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/CURRENTS/0", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/0/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/CURRENTS/1", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/0/currents/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/CURRENTS/2", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/0/currents/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/CURRENTS/3", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/0/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/0/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/0/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/0/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/0/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/1/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/0", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/1", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/2", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/3", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/4", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/5", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/6", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/7", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/8", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/9", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/10", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/voltages/11": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/VOLTAGES/11", - "Description": "Provides internal voltage readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigouts/1/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/CURRENTS/0", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/1/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/CURRENTS/1", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/1/currents/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/CURRENTS/2", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/1/currents/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/CURRENTS/3", - "Description": "Provides internal current readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigouts/1/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/1/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/1/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigouts/1/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGOUTS/1/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Signal Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/0/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/10", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/11": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/11", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/12": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/12", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/voltages/13": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/VOLTAGES/13", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/0/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/CURRENTS/0", - "Description": "Provides internal current readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigins/0/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/CURRENTS/1", - "Description": "Provides internal current readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigins/0/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/0/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/0/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/0/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/0/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/1/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/10", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/11": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/11", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/12": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/12", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/voltages/13": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/VOLTAGES/13", - "Description": "Provides internal voltage measurement on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/sigins/1/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/CURRENTS/0", - "Description": "Provides internal current readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigins/1/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/CURRENTS/1", - "Description": "Provides internal current readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/sigins/1/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/1/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/1/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/sigins/1/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/SIGINS/1/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Signal Input board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/4", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/0/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/0/TEMPERATURES/5", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/4", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/1/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/1/TEMPERATURES/5", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/4", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/2/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/2/TEMPERATURES/5", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/0", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/1", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/2", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/3", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/4", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/5", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/6", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/7", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/8", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/VOLTAGES/9", - "Description": "Provides internal voltage measurement on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/4", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/auxouts/3/temperatures/5": { - "Node": "/dev1234/STATS/PHYSICAL/AUXOUTS/3/TEMPERATURES/5", - "Description": "Provides internal temperature readings on the Auxiliary Output board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/synthesizer/voltages/0": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/0", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/1": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/1", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/2": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/2", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/3": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/3", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/4": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/4", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/5": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/5", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/6": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/6", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/7": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/7", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/8": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/8", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/9": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/9", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/voltages/10": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/VOLTAGES/10", - "Description": "Provides internal voltage readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/stats/physical/synthesizer/currents/0": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/CURRENTS/0", - "Description": "Provides internal current readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/synthesizer/currents/1": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/CURRENTS/1", - "Description": "Provides internal current readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/synthesizer/currents/2": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/CURRENTS/2", - "Description": "Provides internal current readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/synthesizer/currents/3": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/CURRENTS/3", - "Description": "Provides internal current readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/synthesizer/currents/4": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/CURRENTS/4", - "Description": "Provides internal current readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "A" - }, - "/dev1234/stats/physical/synthesizer/temperatures/0": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/TEMPERATURES/0", - "Description": "Provides internal temperature readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/synthesizer/temperatures/1": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/TEMPERATURES/1", - "Description": "Provides internal temperature readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/synthesizer/temperatures/2": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/TEMPERATURES/2", - "Description": "Provides internal temperature readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/synthesizer/temperatures/3": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/TEMPERATURES/3", - "Description": "Provides internal temperature readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/stats/physical/synthesizer/temperatures/4": { - "Node": "/dev1234/STATS/PHYSICAL/SYNTHESIZER/TEMPERATURES/4", - "Description": "Provides internal temperature readings on the Synthesizer board for monitoring.", - "Properties": "Read", - "Type": "Double", - "Unit": "\u00b0C" - }, - "/dev1234/scopes/0/enable": { - "Node": "/dev1234/SCOPES/0/ENABLE", - "Description": "Enables the acquisition of scope shots.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/length": { - "Node": "/dev1234/SCOPES/0/LENGTH", - "Description": "Defines the length of the recorded Scope shot in number of samples.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/time": { - "Node": "/dev1234/SCOPES/0/TIME", - "Description": "Defines the time base of the scope from the divider exponent of the instrument's clock base. The resulting sampling time is 2^n/clockbase.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/single": { - "Node": "/dev1234/SCOPES/0/SINGLE", - "Description": "Puts the Scope into single shot mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/trigger/channel": { - "Node": "/dev1234/SCOPES/0/TRIGGER/CHANNEL", - "Description": "Selects the trigger source signal.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger Input 1.", - "1": "\"trigin2\": Trigger Input 2.", - "2": "\"trigin3\": Trigger Input 3.", - "3": "\"trigin4\": Trigger Input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/scopes/0/trigger/delay": { - "Node": "/dev1234/SCOPES/0/TRIGGER/DELAY", - "Description": "The delay of a Scope measurement. A negative delay results in data being acquired before the trigger point. The resolution is 2 ns.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/scopes/0/trigger/enable": { - "Node": "/dev1234/SCOPES/0/TRIGGER/ENABLE", - "Description": "When triggering is enabled scope data are acquired every time the defined trigger condition is met.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: Continuous scope shot acquisition", - "1": "\"on\": ON: Trigger based scope shot acquisition" - } - }, - "/dev1234/scopes/0/averaging/count": { - "Node": "/dev1234/SCOPES/0/AVERAGING/COUNT", - "Description": "Configures the number of Scope measurements to average.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/averaging/enable": { - "Node": "/dev1234/SCOPES/0/AVERAGING/ENABLE", - "Description": "Enables averaging of Scope measurements.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/segments/count": { - "Node": "/dev1234/SCOPES/0/SEGMENTS/COUNT", - "Description": "Specifies the number of segments to be recorded in device memory. The maximum scope shot size is given by the available memory divided by the number of segments. This functionality requires the DIG option.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/segments/enable": { - "Node": "/dev1234/SCOPES/0/SEGMENTS/ENABLE", - "Description": "Enable segmented scope recording. This allows for full bandwidth recording of scope shots with a minimum dead time between individual shots. This functionality requires the DIG option.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/scopes/0/channels/0/enable": { - "Node": "/dev1234/SCOPES/0/CHANNELS/0/ENABLE", - "Description": "Enables recording for this Scope channel.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "Dependent" - }, - "/dev1234/scopes/0/channels/0/inputselect": { - "Node": "/dev1234/SCOPES/0/CHANNELS/0/INPUTSELECT", - "Description": "Selects the scope input signal.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Signal Input Channel 1", - "1": "\"sigin1\", \"signal_input1\": Signal Input Channel 2", - "8": "Aux Input Channel 1", - "9": "Aux Input Channel 2", - "16": "\"auxin0\", \"auxiliary_input0\": Aux Input Channel 1", - "17": "\"auxin1\", \"auxiliary_input1\": Aux Input Channel 2" - } - }, - "/dev1234/scopes/0/channels/1/enable": { - "Node": "/dev1234/SCOPES/0/CHANNELS/1/ENABLE", - "Description": "Enables recording for this Scope channel.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "Dependent" - }, - "/dev1234/scopes/0/channels/1/inputselect": { - "Node": "/dev1234/SCOPES/0/CHANNELS/1/INPUTSELECT", - "Description": "Selects the scope input signal.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Signal Input Channel 1", - "1": "\"sigin1\", \"signal_input1\": Signal Input Channel 2", - "8": "Aux Input Channel 1", - "9": "Aux Input Channel 2", - "16": "\"auxin0\", \"auxiliary_input0\": Aux Input Channel 1", - "17": "\"auxin1\", \"auxiliary_input1\": Aux Input Channel 2" - } - }, - "/dev1234/scopes/0/channels/0/wave": { - "Node": "/dev1234/SCOPES/0/CHANNELS/0/WAVE", - "Description": "Contains the acquired Scope measurement data.", - "Properties": "Read", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/scopes/0/channels/1/wave": { - "Node": "/dev1234/SCOPES/0/CHANNELS/1/WAVE", - "Description": "Contains the acquired Scope measurement data.", - "Properties": "Read", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/0/order": { - "Node": "/dev1234/DEMODS/0/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/0/harmonic": { - "Node": "/dev1234/DEMODS/0/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/0/bypass": { - "Node": "/dev1234/DEMODS/0/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/0/timeconstant": { - "Node": "/dev1234/DEMODS/0/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/0/adcselect": { - "Node": "/dev1234/DEMODS/0/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/0/oscselect": { - "Node": "/dev1234/DEMODS/0/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/0/enable": { - "Node": "/dev1234/DEMODS/0/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/0/rate": { - "Node": "/dev1234/DEMODS/0/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/0/burstlen": { - "Node": "/dev1234/DEMODS/0/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/0/missedtrigfull": { - "Node": "/dev1234/DEMODS/0/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/0/missedtrigbusy": { - "Node": "/dev1234/DEMODS/0/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/0/freq": { - "Node": "/dev1234/DEMODS/0/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/0/phaseshift": { - "Node": "/dev1234/DEMODS/0/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/0/trigger/source": { - "Node": "/dev1234/DEMODS/0/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/0/trigger/mode": { - "Node": "/dev1234/DEMODS/0/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/0/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/0/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/1/order": { - "Node": "/dev1234/DEMODS/1/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/1/harmonic": { - "Node": "/dev1234/DEMODS/1/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/1/bypass": { - "Node": "/dev1234/DEMODS/1/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/1/timeconstant": { - "Node": "/dev1234/DEMODS/1/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/1/adcselect": { - "Node": "/dev1234/DEMODS/1/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/1/oscselect": { - "Node": "/dev1234/DEMODS/1/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/1/enable": { - "Node": "/dev1234/DEMODS/1/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/1/rate": { - "Node": "/dev1234/DEMODS/1/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/1/burstlen": { - "Node": "/dev1234/DEMODS/1/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/1/missedtrigfull": { - "Node": "/dev1234/DEMODS/1/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/1/missedtrigbusy": { - "Node": "/dev1234/DEMODS/1/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/1/freq": { - "Node": "/dev1234/DEMODS/1/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/1/phaseshift": { - "Node": "/dev1234/DEMODS/1/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/1/trigger/source": { - "Node": "/dev1234/DEMODS/1/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/1/trigger/mode": { - "Node": "/dev1234/DEMODS/1/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/1/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/1/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/2/order": { - "Node": "/dev1234/DEMODS/2/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/2/harmonic": { - "Node": "/dev1234/DEMODS/2/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/2/bypass": { - "Node": "/dev1234/DEMODS/2/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/2/timeconstant": { - "Node": "/dev1234/DEMODS/2/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/2/adcselect": { - "Node": "/dev1234/DEMODS/2/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/2/oscselect": { - "Node": "/dev1234/DEMODS/2/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/2/enable": { - "Node": "/dev1234/DEMODS/2/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/2/rate": { - "Node": "/dev1234/DEMODS/2/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/2/burstlen": { - "Node": "/dev1234/DEMODS/2/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/2/missedtrigfull": { - "Node": "/dev1234/DEMODS/2/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/2/missedtrigbusy": { - "Node": "/dev1234/DEMODS/2/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/2/freq": { - "Node": "/dev1234/DEMODS/2/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/2/phaseshift": { - "Node": "/dev1234/DEMODS/2/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/2/trigger/source": { - "Node": "/dev1234/DEMODS/2/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/2/trigger/mode": { - "Node": "/dev1234/DEMODS/2/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/2/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/2/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/3/order": { - "Node": "/dev1234/DEMODS/3/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/3/harmonic": { - "Node": "/dev1234/DEMODS/3/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/3/bypass": { - "Node": "/dev1234/DEMODS/3/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/3/timeconstant": { - "Node": "/dev1234/DEMODS/3/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/3/adcselect": { - "Node": "/dev1234/DEMODS/3/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/3/oscselect": { - "Node": "/dev1234/DEMODS/3/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/3/enable": { - "Node": "/dev1234/DEMODS/3/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/3/rate": { - "Node": "/dev1234/DEMODS/3/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/3/burstlen": { - "Node": "/dev1234/DEMODS/3/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/3/missedtrigfull": { - "Node": "/dev1234/DEMODS/3/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/3/missedtrigbusy": { - "Node": "/dev1234/DEMODS/3/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/3/freq": { - "Node": "/dev1234/DEMODS/3/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/3/phaseshift": { - "Node": "/dev1234/DEMODS/3/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/3/trigger/source": { - "Node": "/dev1234/DEMODS/3/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/3/trigger/mode": { - "Node": "/dev1234/DEMODS/3/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/3/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/3/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/4/order": { - "Node": "/dev1234/DEMODS/4/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/4/harmonic": { - "Node": "/dev1234/DEMODS/4/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/4/bypass": { - "Node": "/dev1234/DEMODS/4/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/4/timeconstant": { - "Node": "/dev1234/DEMODS/4/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/4/adcselect": { - "Node": "/dev1234/DEMODS/4/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/4/oscselect": { - "Node": "/dev1234/DEMODS/4/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/4/enable": { - "Node": "/dev1234/DEMODS/4/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/4/rate": { - "Node": "/dev1234/DEMODS/4/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/4/burstlen": { - "Node": "/dev1234/DEMODS/4/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/4/missedtrigfull": { - "Node": "/dev1234/DEMODS/4/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/4/missedtrigbusy": { - "Node": "/dev1234/DEMODS/4/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/4/freq": { - "Node": "/dev1234/DEMODS/4/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/4/phaseshift": { - "Node": "/dev1234/DEMODS/4/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/4/trigger/source": { - "Node": "/dev1234/DEMODS/4/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/4/trigger/mode": { - "Node": "/dev1234/DEMODS/4/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/4/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/4/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/5/order": { - "Node": "/dev1234/DEMODS/5/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/5/harmonic": { - "Node": "/dev1234/DEMODS/5/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/5/bypass": { - "Node": "/dev1234/DEMODS/5/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/5/timeconstant": { - "Node": "/dev1234/DEMODS/5/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/5/adcselect": { - "Node": "/dev1234/DEMODS/5/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/5/oscselect": { - "Node": "/dev1234/DEMODS/5/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/5/enable": { - "Node": "/dev1234/DEMODS/5/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/5/rate": { - "Node": "/dev1234/DEMODS/5/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/5/burstlen": { - "Node": "/dev1234/DEMODS/5/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/5/missedtrigfull": { - "Node": "/dev1234/DEMODS/5/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/5/missedtrigbusy": { - "Node": "/dev1234/DEMODS/5/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/5/freq": { - "Node": "/dev1234/DEMODS/5/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/5/phaseshift": { - "Node": "/dev1234/DEMODS/5/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/5/trigger/source": { - "Node": "/dev1234/DEMODS/5/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/5/trigger/mode": { - "Node": "/dev1234/DEMODS/5/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/5/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/5/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/6/order": { - "Node": "/dev1234/DEMODS/6/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/6/harmonic": { - "Node": "/dev1234/DEMODS/6/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/6/bypass": { - "Node": "/dev1234/DEMODS/6/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/6/timeconstant": { - "Node": "/dev1234/DEMODS/6/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/6/adcselect": { - "Node": "/dev1234/DEMODS/6/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/6/oscselect": { - "Node": "/dev1234/DEMODS/6/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/6/enable": { - "Node": "/dev1234/DEMODS/6/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/6/rate": { - "Node": "/dev1234/DEMODS/6/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/6/burstlen": { - "Node": "/dev1234/DEMODS/6/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/6/missedtrigfull": { - "Node": "/dev1234/DEMODS/6/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/6/missedtrigbusy": { - "Node": "/dev1234/DEMODS/6/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/6/freq": { - "Node": "/dev1234/DEMODS/6/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/6/phaseshift": { - "Node": "/dev1234/DEMODS/6/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/6/trigger/source": { - "Node": "/dev1234/DEMODS/6/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/6/trigger/mode": { - "Node": "/dev1234/DEMODS/6/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/6/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/6/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/7/order": { - "Node": "/dev1234/DEMODS/7/ORDER", - "Description": "Selects the filter roll off between 6 dB/oct and 24 dB/oct.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "1st order filter 6 dB/oct", - "2": "2nd order filter 12 dB/oct", - "3": "3rd order filter 18 dB/oct", - "4": "4th order filter 24 dB/oct" - } - }, - "/dev1234/demods/7/harmonic": { - "Node": "/dev1234/DEMODS/7/HARMONIC", - "Description": "Multiplies the demodulator's reference frequency by an integer factor. If the demodulator is used as a phase detector in external reference mode (PLL), the effect is that the internal oscillator locks to the external frequency divided by the integer factor.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/7/bypass": { - "Node": "/dev1234/DEMODS/7/BYPASS", - "Description": "Allows to bypass the demodulator low-pass filter, thus increasing the bandwidth.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": disabled", - "1": "\"enabled\": enabled" - } - }, - "/dev1234/demods/7/timeconstant": { - "Node": "/dev1234/DEMODS/7/TIMECONSTANT", - "Description": "Sets the integration time constant or in other words, the cutoff frequency of the demodulator low pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "s" - }, - "/dev1234/demods/7/adcselect": { - "Node": "/dev1234/DEMODS/7/ADCSELECT", - "Description": "Selects the input signal for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Sig In 1", - "1": "\"sigin1\", \"signal_input1\": Sig In 2" - } - }, - "/dev1234/demods/7/oscselect": { - "Node": "/dev1234/DEMODS/7/OSCSELECT", - "Description": "Connects the demodulator with the supplied oscillator. Number of available oscillators depends on the installed options.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "Oscillator 1", - "1": "Oscillator 2", - "2": "Oscillator 3", - "3": "Oscillator 4", - "4": "Oscillator 5", - "5": "Oscillator 6", - "6": "Oscillator 7", - "7": "Oscillator 8" - } - }, - "/dev1234/demods/7/enable": { - "Node": "/dev1234/DEMODS/7/ENABLE", - "Description": "Enables the data acquisition for the corresponding demodulator. Note: increasing number of active demodulators increases load on the physical connection to the host computer.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"off\": OFF: demodulator inactive", - "1": "\"on\": ON: demodulator active" - } - }, - "/dev1234/demods/7/rate": { - "Node": "/dev1234/DEMODS/7/RATE", - "Description": "Defines the demodulator sampling rate, the number of samples that are sent to the host computer per second. A rate of about 7-10 higher as compared to the filter bandwidth usually provides sufficient aliasing suppression. This is also the rate of data received by LabOne Data Server and saved to the computer hard disk. This setting has no impact on the sample rate on the auxiliary outputs connectors. Note: the value inserted by the user may be approximated to the nearest value supported by the instrument.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "1/s" - }, - "/dev1234/demods/7/burstlen": { - "Node": "/dev1234/DEMODS/7/BURSTLEN", - "Description": "Defines how many (complex) samples should be acquired with each trigger. Reserved for future use.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/7/missedtrigfull": { - "Node": "/dev1234/DEMODS/7/MISSEDTRIGFULL", - "Description": "Indicates the number of times the memory is full and trigger is omitted.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/7/missedtrigbusy": { - "Node": "/dev1234/DEMODS/7/MISSEDTRIGBUSY", - "Description": "Indicates the number of times the acquisition unit is busy (still recording previous burst) and trigger is omitted. Reserved for future use.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/demods/7/freq": { - "Node": "/dev1234/DEMODS/7/FREQ", - "Description": "Indicates the frequency used for demodulation and for output generation. The demodulation frequency is calculated with oscillator frequency times the harmonic factor. When the MOD option is used linear combinations of oscillator frequencies including the harmonic factors define the demodulation frequencies.", - "Properties": "Read", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/demods/7/phaseshift": { - "Node": "/dev1234/DEMODS/7/PHASESHIFT", - "Description": "Phase shift applied to the reference input of the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "deg" - }, - "/dev1234/demods/7/trigger/source": { - "Node": "/dev1234/DEMODS/7/TRIGGER/SOURCE", - "Description": "Selects the trigger input for the demodulator.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trigin1\": Trigger input 1.", - "1": "\"trigin2\": Trigger input 2.", - "2": "\"trigin3\": Trigger input 3.", - "3": "\"trigin4\": Trigger input 4.", - "1024": "\"swtrig0\", \"software_trigger0\": Software Trigger 1." - } - }, - "/dev1234/demods/7/trigger/mode": { - "Node": "/dev1234/DEMODS/7/TRIGGER/MODE", - "Description": "Selects the trigger mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "1": "\"rising_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s rising edge.", - "2": "\"falling_edge\": Demodulator data is streamed to the host computer on the trigger\u2019s falling edge.", - "3": "\"both_edge\": Demodulator data is streamed to the host computer on both trigger\u2019s edges." - } - }, - "/dev1234/demods/7/trigger/triggeracq": { - "Node": "/dev1234/DEMODS/7/TRIGGER/TRIGGERACQ", - "Description": "Enables the triggered acquisition.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"continuous\": Continuous Demodulator data acquisition (triggering is disabled).", - "1": "\"triggered\": Triggered Demodulator data acquisition." - } - }, - "/dev1234/demods/0/sample": { - "Node": "/dev1234/DEMODS/0/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/1/sample": { - "Node": "/dev1234/DEMODS/1/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/2/sample": { - "Node": "/dev1234/DEMODS/2/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/3/sample": { - "Node": "/dev1234/DEMODS/3/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/4/sample": { - "Node": "/dev1234/DEMODS/4/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/5/sample": { - "Node": "/dev1234/DEMODS/5/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/6/sample": { - "Node": "/dev1234/DEMODS/6/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/demods/7/sample": { - "Node": "/dev1234/DEMODS/7/SAMPLE", - "Description": "Contains streamed demodulator samples with sample interval defined by the demodulator data rate.", - "Properties": "Read, Stream", - "Type": "ZIVectorData", - "Unit": "Dependent" - }, - "/dev1234/oscs/0/freq": { - "Node": "/dev1234/OSCS/0/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/1/freq": { - "Node": "/dev1234/OSCS/1/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/2/freq": { - "Node": "/dev1234/OSCS/2/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/3/freq": { - "Node": "/dev1234/OSCS/3/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/4/freq": { - "Node": "/dev1234/OSCS/4/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/5/freq": { - "Node": "/dev1234/OSCS/5/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/6/freq": { - "Node": "/dev1234/OSCS/6/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/oscs/7/freq": { - "Node": "/dev1234/OSCS/7/FREQ", - "Description": "Frequency control for each oscillator.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/synthesizers/0/centerfreq": { - "Node": "/dev1234/SYNTHESIZERS/0/CENTERFREQ", - "Description": "The Center Frequency of the detection band at the input/output of the instrument", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/sigins/0/on": { - "Node": "/dev1234/SIGINS/0/ON", - "Description": "Enables the signal input.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigins/0/range": { - "Node": "/dev1234/SIGINS/0/RANGE", - "Description": "Sets the maximal Range of the Signal Input power. The instrument selects the closest available Range with a resolution of 5 dBm.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "dBm" - }, - "/dev1234/sigins/0/overrangecount": { - "Node": "/dev1234/SIGINS/0/OVERRANGECOUNT", - "Description": "Indicates the number of times the Signal Input was in an overrange condition within the last 200 ms. It is checked for an overrange condition every 10 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/on": { - "Node": "/dev1234/SIGOUTS/0/ON", - "Description": "Enabling/Disabling the Signal Output. Corresponds to the blue LED indicator on the instrument front panel.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/range": { - "Node": "/dev1234/SIGOUTS/0/RANGE", - "Description": "Sets the maximal Range of the Signal Output power. The instrument selects the closest available Range with a resolution of 5 dBm.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "dBm" - }, - "/dev1234/sigouts/0/overrangecount": { - "Node": "/dev1234/SIGOUTS/0/OVERRANGECOUNT", - "Description": "Indicates the number of times the Signal Output was in an overrange condition within the last 200 ms. It is checked for an overrange condition every 10 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/filter": { - "Node": "/dev1234/SIGOUTS/0/FILTER", - "Description": "Reads the selected analog filter before the Signal Output.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"lowpass_1500\": Low-pass filter of 1.5 GHz.", - "1": "\"lowpass_3000\": Low-pass filter of 3 GHz.", - "2": "\"bandpass_3000_6000\": Band-pass filter between 3 GHz - 6 GHz", - "3": "\"bandpass_6000_10000\": Band-pass filter between 6 GHz - 10 GHz" - } - }, - "/dev1234/sigouts/0/rfpath": { - "Node": "/dev1234/SIGOUTS/0/RFPATH", - "Description": "Switches between radio frequency and baseband signal paths.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"BB\": Baseband path.", - "1": "\"RF\": Radio frequency path." - } - }, - "/dev1234/sigouts/0/rfinterlock": { - "Node": "/dev1234/SIGOUTS/0/RFINTERLOCK", - "Description": "Enables (1) or disables (0) the RF interlock between input and output of the same channel. If enabled, the output is always configured according to the input.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": RF interlock disabled.", - "1": "\"enabled\": RF interlock enabled." - } - }, - "/dev1234/sigins/0/rfpath": { - "Node": "/dev1234/SIGINS/0/RFPATH", - "Description": "Switches between radio frequency and baseband signal paths.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"BB\": Baseband path", - "1": "\"RF\": Radio frequency path" - } - }, - "/dev1234/sigins/0/ac": { - "Node": "/dev1234/SIGINS/0/AC", - "Description": "Defines the input coupling for the Signal Inputs. AC coupling inserts a high-pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"dc\": OFF: DC coupling", - "1": "\"ac\": ON: AC coupling" - } - }, - "/dev1234/synthesizers/1/centerfreq": { - "Node": "/dev1234/SYNTHESIZERS/1/CENTERFREQ", - "Description": "The Center Frequency of the detection band at the input/output of the instrument", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "Hz" - }, - "/dev1234/sigins/1/on": { - "Node": "/dev1234/SIGINS/1/ON", - "Description": "Enables the signal input.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigins/1/range": { - "Node": "/dev1234/SIGINS/1/RANGE", - "Description": "Sets the maximal Range of the Signal Input power. The instrument selects the closest available Range with a resolution of 5 dBm.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "dBm" - }, - "/dev1234/sigins/1/overrangecount": { - "Node": "/dev1234/SIGINS/1/OVERRANGECOUNT", - "Description": "Indicates the number of times the Signal Input was in an overrange condition within the last 200 ms. It is checked for an overrange condition every 10 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/on": { - "Node": "/dev1234/SIGOUTS/1/ON", - "Description": "Enabling/Disabling the Signal Output. Corresponds to the blue LED indicator on the instrument front panel.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/range": { - "Node": "/dev1234/SIGOUTS/1/RANGE", - "Description": "Sets the maximal Range of the Signal Output power. The instrument selects the closest available Range with a resolution of 5 dBm.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "dBm" - }, - "/dev1234/sigouts/1/overrangecount": { - "Node": "/dev1234/SIGOUTS/1/OVERRANGECOUNT", - "Description": "Indicates the number of times the Signal Output was in an overrange condition within the last 200 ms. It is checked for an overrange condition every 10 ms.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/filter": { - "Node": "/dev1234/SIGOUTS/1/FILTER", - "Description": "Reads the selected analog filter before the Signal Output.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"lowpass_1500\": Low-pass filter of 1.5 GHz.", - "1": "\"lowpass_3000\": Low-pass filter of 3 GHz.", - "2": "\"bandpass_3000_6000\": Band-pass filter between 3 GHz - 6 GHz", - "3": "\"bandpass_6000_10000\": Band-pass filter between 6 GHz - 10 GHz" - } - }, - "/dev1234/sigouts/1/rfpath": { - "Node": "/dev1234/SIGOUTS/1/RFPATH", - "Description": "Switches between radio frequency and baseband signal paths.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"BB\": Baseband path.", - "1": "\"RF\": Radio frequency path." - } - }, - "/dev1234/sigouts/1/rfinterlock": { - "Node": "/dev1234/SIGOUTS/1/RFINTERLOCK", - "Description": "Enables (1) or disables (0) the RF interlock between input and output of the same channel. If enabled, the output is always configured according to the input.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": RF interlock disabled.", - "1": "\"enabled\": RF interlock enabled." - } - }, - "/dev1234/sigins/1/rfpath": { - "Node": "/dev1234/SIGINS/1/RFPATH", - "Description": "Switches between radio frequency and baseband signal paths.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"BB\": Baseband path", - "1": "\"RF\": Radio frequency path" - } - }, - "/dev1234/sigins/1/ac": { - "Node": "/dev1234/SIGINS/1/AC", - "Description": "Defines the input coupling for the Signal Inputs. AC coupling inserts a high-pass filter.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"dc\": OFF: DC coupling", - "1": "\"ac\": ON: AC coupling" - } - }, - "/dev1234/sigouts/0/generators/0/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/0/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/0/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/0/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/1/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/1/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/1/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/1/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/2/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/2/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/2/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/2/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/3/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/3/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/3/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/3/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/4/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/4/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/4/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/4/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/5/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/5/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/5/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/5/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/6/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/6/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/6/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/6/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/0/generators/7/enable": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/7/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/0/generators/7/amplitude": { - "Node": "/dev1234/SIGOUTS/0/GENERATORS/7/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/0/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/0/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/0/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/0/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/1/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/1/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/1/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/1/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/2/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/2/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/2/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/2/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/3/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/3/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/3/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/3/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/4/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/4/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/4/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/4/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/5/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/5/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/5/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/5/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/6/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/6/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/6/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/6/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/sigouts/1/generators/7/enable": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/7/ENABLE", - "Description": "Enables (1) or disables (0) the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/sigouts/1/generators/7/amplitude": { - "Node": "/dev1234/SIGOUTS/1/GENERATORS/7/AMPLITUDE", - "Description": "Sets the amplitude of the generator output.", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/extrefs/0/enable": { - "Node": "/dev1234/EXTREFS/0/ENABLE", - "Description": "Enables the external reference.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/0/adcselect": { - "Node": "/dev1234/EXTREFS/0/ADCSELECT", - "Description": "Indicates the input signal selection for the selected demodulator.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Signal Input 1 is connected to the corresponding demodulator.", - "1": "\"sigin1\", \"signal_input1\": Signal Input 2 is connected to the corresponding demodulator.", - "8": "\"auxin0\", \"auxiliary_input0\": Auxiliary Input 1 is connected to the corresponding demodulator." - } - }, - "/dev1234/extrefs/0/automode": { - "Node": "/dev1234/EXTREFS/0/AUTOMODE", - "Description": "This defines the type of automatic adaptation of parameters in the PID used for Ext Ref.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "2": "\"low_bandwidth\", \"pid_coeffs_filter_low_bw\": The PID coefficients, the filter bandwidth and the output limits are automatically set using a low bandwidth.", - "3": "\"high_bandwidth\", \"pid_coeffs_filter_high_bw\": The PID coefficients, the filter bandwidth and the output limits are automatically set using a high bandwidth.", - "4": "\"all\", \"pid_coeffs_filter_auto_bw\": The PID coefficient, the filter bandwidth and the output limits are dynamically adapted." - } - }, - "/dev1234/extrefs/0/demodselect": { - "Node": "/dev1234/EXTREFS/0/DEMODSELECT", - "Description": "Indicates the demodulator connected to the extref channel.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/0/oscselect": { - "Node": "/dev1234/EXTREFS/0/OSCSELECT", - "Description": "Indicates which oscillator is being locked to the external reference.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/0/locked": { - "Node": "/dev1234/EXTREFS/0/LOCKED", - "Description": "Indicates whether the external reference is locked.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/1/enable": { - "Node": "/dev1234/EXTREFS/1/ENABLE", - "Description": "Enables the external reference.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/1/adcselect": { - "Node": "/dev1234/EXTREFS/1/ADCSELECT", - "Description": "Indicates the input signal selection for the selected demodulator.", - "Properties": "Read", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"sigin0\", \"signal_input0\": Signal Input 1 is connected to the corresponding demodulator.", - "1": "\"sigin1\", \"signal_input1\": Signal Input 2 is connected to the corresponding demodulator.", - "8": "\"auxin0\", \"auxiliary_input0\": Auxiliary Input 1 is connected to the corresponding demodulator." - } - }, - "/dev1234/extrefs/1/automode": { - "Node": "/dev1234/EXTREFS/1/AUTOMODE", - "Description": "This defines the type of automatic adaptation of parameters in the PID used for Ext Ref.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "2": "\"low_bandwidth\", \"pid_coeffs_filter_low_bw\": The PID coefficients, the filter bandwidth and the output limits are automatically set using a low bandwidth.", - "3": "\"high_bandwidth\", \"pid_coeffs_filter_high_bw\": The PID coefficients, the filter bandwidth and the output limits are automatically set using a high bandwidth.", - "4": "\"all\", \"pid_coeffs_filter_auto_bw\": The PID coefficient, the filter bandwidth and the output limits are dynamically adapted." - } - }, - "/dev1234/extrefs/1/demodselect": { - "Node": "/dev1234/EXTREFS/1/DEMODSELECT", - "Description": "Indicates the demodulator connected to the extref channel.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/1/oscselect": { - "Node": "/dev1234/EXTREFS/1/OSCSELECT", - "Description": "Indicates which oscillator is being locked to the external reference.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/extrefs/1/locked": { - "Node": "/dev1234/EXTREFS/1/LOCKED", - "Description": "Indicates whether the external reference is locked.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/0/highspeed/outputselect": { - "Node": "/dev1234/AUXOUTS/0/HIGHSPEED/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/0/highspeed/outputchannel": { - "Node": "/dev1234/AUXOUTS/0/HIGHSPEED/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/0/highspeed/preoffset": { - "Node": "/dev1234/AUXOUTS/0/HIGHSPEED/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/0/highspeed/scale": { - "Node": "/dev1234/AUXOUTS/0/HIGHSPEED/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/0/highspeed/offset": { - "Node": "/dev1234/AUXOUTS/0/HIGHSPEED/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/0/highprecision/outputselect": { - "Node": "/dev1234/AUXOUTS/0/HIGHPRECISION/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/0/highprecision/outputchannel": { - "Node": "/dev1234/AUXOUTS/0/HIGHPRECISION/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/0/highprecision/preoffset": { - "Node": "/dev1234/AUXOUTS/0/HIGHPRECISION/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/0/highprecision/scale": { - "Node": "/dev1234/AUXOUTS/0/HIGHPRECISION/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/0/highprecision/offset": { - "Node": "/dev1234/AUXOUTS/0/HIGHPRECISION/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/1/highspeed/outputselect": { - "Node": "/dev1234/AUXOUTS/1/HIGHSPEED/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/1/highspeed/outputchannel": { - "Node": "/dev1234/AUXOUTS/1/HIGHSPEED/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/1/highspeed/preoffset": { - "Node": "/dev1234/AUXOUTS/1/HIGHSPEED/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/1/highspeed/scale": { - "Node": "/dev1234/AUXOUTS/1/HIGHSPEED/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/1/highspeed/offset": { - "Node": "/dev1234/AUXOUTS/1/HIGHSPEED/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/1/highprecision/outputselect": { - "Node": "/dev1234/AUXOUTS/1/HIGHPRECISION/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/1/highprecision/outputchannel": { - "Node": "/dev1234/AUXOUTS/1/HIGHPRECISION/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/1/highprecision/preoffset": { - "Node": "/dev1234/AUXOUTS/1/HIGHPRECISION/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/1/highprecision/scale": { - "Node": "/dev1234/AUXOUTS/1/HIGHPRECISION/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/1/highprecision/offset": { - "Node": "/dev1234/AUXOUTS/1/HIGHPRECISION/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/2/highspeed/outputselect": { - "Node": "/dev1234/AUXOUTS/2/HIGHSPEED/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/2/highspeed/outputchannel": { - "Node": "/dev1234/AUXOUTS/2/HIGHSPEED/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/2/highspeed/preoffset": { - "Node": "/dev1234/AUXOUTS/2/HIGHSPEED/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/2/highspeed/scale": { - "Node": "/dev1234/AUXOUTS/2/HIGHSPEED/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/2/highspeed/offset": { - "Node": "/dev1234/AUXOUTS/2/HIGHSPEED/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/2/highprecision/outputselect": { - "Node": "/dev1234/AUXOUTS/2/HIGHPRECISION/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/2/highprecision/outputchannel": { - "Node": "/dev1234/AUXOUTS/2/HIGHPRECISION/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/2/highprecision/preoffset": { - "Node": "/dev1234/AUXOUTS/2/HIGHPRECISION/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/2/highprecision/scale": { - "Node": "/dev1234/AUXOUTS/2/HIGHPRECISION/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/2/highprecision/offset": { - "Node": "/dev1234/AUXOUTS/2/HIGHPRECISION/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/3/highspeed/outputselect": { - "Node": "/dev1234/AUXOUTS/3/HIGHSPEED/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/3/highspeed/outputchannel": { - "Node": "/dev1234/AUXOUTS/3/HIGHSPEED/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/3/highspeed/preoffset": { - "Node": "/dev1234/AUXOUTS/3/HIGHSPEED/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/3/highspeed/scale": { - "Node": "/dev1234/AUXOUTS/3/HIGHSPEED/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/3/highspeed/offset": { - "Node": "/dev1234/AUXOUTS/3/HIGHSPEED/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/auxouts/3/highprecision/outputselect": { - "Node": "/dev1234/AUXOUTS/3/HIGHPRECISION/OUTPUTSELECT", - "Description": "Selects the signal source for the Auxiliary Output.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "\"manual\": Selects Manual as the output option.", - "0": "\"demod_x\": Selects Demod X as the output option.", - "1": "\"demod_y\": Selects Demod Y as the output option.", - "2": "\"demod_r\": Select Demod R as the output option.", - "3": "\"demod_theta\": Select Demod Theta as the output option." - } - }, - "/dev1234/auxouts/3/highprecision/outputchannel": { - "Node": "/dev1234/AUXOUTS/3/HIGHPRECISION/OUTPUTCHANNEL", - "Description": "Selects the channel of the selected signal source.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/auxouts/3/highprecision/preoffset": { - "Node": "/dev1234/AUXOUTS/3/HIGHPRECISION/PREOFFSET", - "Description": "Adds a pre-offset to the signal before scaling is applied. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "unit of signal source" - }, - "/dev1234/auxouts/3/highprecision/scale": { - "Node": "/dev1234/AUXOUTS/3/HIGHPRECISION/SCALE", - "Description": "Multiplication factor to scale the signal. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V / [unit of signal source]" - }, - "/dev1234/auxouts/3/highprecision/offset": { - "Node": "/dev1234/AUXOUTS/3/HIGHPRECISION/OFFSET", - "Description": "Adds the specified offset voltage to the signal after scaling. Auxiliary Output Value = (Signal+Preoffset)*Scale + Offset", - "Properties": "Read, Write, Setting", - "Type": "Double", - "Unit": "V" - }, - "/dev1234/system/digitalmixer/phasesyncenable": { - "Node": "/dev1234/SYSTEM/DIGITALMIXER/PHASESYNCENABLE", - "Description": "Configures the NCO reset mode.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"disabled\": If disabled, the instrument does not automatically reset NCOs when switching a channel between BB and RF modes.", - "1": "\"enabled\": If enabled, the instrument automatically resets the NCOs of all channels whenever a channel is switched between BB and RF, in order to restore alignment." - } - }, - "/dev1234/dios/0/drive": { - "Node": "/dev1234/DIOS/0/DRIVE", - "Description": "When on (1), the corresponding 8-bit bus is in output mode. When off (0), it is in input mode. Bit 0 corresponds to the least significant byte. For example, the value 1 drives the least significant byte, the value 8 drives the most significant byte.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/dios/0/output": { - "Node": "/dev1234/DIOS/0/OUTPUT", - "Description": "Sets the value of the DIO output for those bytes where 'drive' is enabled.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/dios/0/mode": { - "Node": "/dev1234/DIOS/0/MODE", - "Description": "Select DIO mode", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"manual\": Enables manual control of the DIO output bits." - } - }, - "/dev1234/dios/0/input": { - "Node": "/dev1234/DIOS/0/INPUT", - "Description": "Gives the value of the DIO input for those bytes where drive is disabled.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/dev1234/dios/0/interface": { - "Node": "/dev1234/DIOS/0/INTERFACE", - "Description": "Selects the interface standard to use on the 32-bit DIO interface. A value of 0 means that a 3.3 V CMOS interface is used. A value of 1 means that an LVDS compatible interface is used.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - } -} \ No newline at end of file diff --git a/tests/nodetree/resources/zi_nodes_info.json b/tests/nodetree/resources/zi_nodes_info.json deleted file mode 100644 index 9d471ef..0000000 --- a/tests/nodetree/resources/zi_nodes_info.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "/zi/config/open": { - "Node": "/ZI/CONFIG/OPEN", - "Description": "Enable communication with the LabOne Data Server from other computers in the network.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"local\": Communication only possible with the local machine.", - "1": "\"network\": Communication possible with other machines in the network." - } - }, - "/zi/config/port": { - "Node": "/ZI/CONFIG/PORT", - "Description": "The IP port on which the LabOne Data Server listens.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/zi/about/revision": { - "Node": "/ZI/ABOUT/REVISION", - "Description": "Contains the revision number of the Zurich Instruments Data Server.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/zi/about/version": { - "Node": "/ZI/ABOUT/VERSION", - "Description": "Contains the version of the LabOne software.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/about/commit": { - "Node": "/ZI/ABOUT/COMMIT", - "Description": "Contains the commit hash of the source code used to build this version of the LabOne software.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/about/copyright": { - "Node": "/ZI/ABOUT/COPYRIGHT", - "Description": "Holds the copyright notice.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/about/dataserver": { - "Node": "/ZI/ABOUT/DATASERVER", - "Description": "Contains information about the Zurich Instruments Data Server.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/about/fwrevision": { - "Node": "/ZI/ABOUT/FWREVISION", - "Description": "Contains the revision of the device firmware.", - "Properties": "Read", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/zi/debug/logpath": { - "Node": "/ZI/DEBUG/LOGPATH", - "Description": "Returns the path of the log directory.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/debug/level": { - "Node": "/ZI/DEBUG/LEVEL", - "Description": "Set the logging level (amount of detail) of the LabOne Data Server.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "0": "\"trace\": Trace. Messages designated as traces are logged.", - "1": "\"debug\": Debug. Messages designated as debugging info are logged.", - "2": "\"info\": Info. Messages designated as informational are logged.", - "3": "\"status\": Status. Messages designated as status info are logged.", - "4": "\"warning\": Warning. Messages designated as warnings are logged.", - "5": "\"error\": Error. Messages designated as errors are logged.", - "6": "\"fatal\": Fatal. Messages designated as fatal errors are logged." - } - }, - "/zi/debug/log": { - "Node": "/ZI/DEBUG/LOG", - "Description": "Returns the logfile text of the LabOne Data Server.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/clockbase": { - "Node": "/ZI/CLOCKBASE", - "Description": "A fallback clock frequency that can be used by clients for calculating time bases when no other is available.", - "Properties": "Read", - "Type": "Double", - "Unit": "None" - }, - "/zi/devices/visible": { - "Node": "/ZI/DEVICES/VISIBLE", - "Description": "Contains a list of devices in the network visible to the LabOne Data Server.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/devices/connected": { - "Node": "/ZI/DEVICES/CONNECTED", - "Description": "Contains a list of devices connected to the LabOne Data Server.", - "Properties": "Read", - "Type": "String", - "Unit": "None" - }, - "/zi/mds/groups/0/devices": { - "Node": "/ZI/MDS/GROUPS/0/DEVICES", - "Description": "Contains a list of devices in this synchronization group.", - "Properties": "Read, Write, Setting", - "Type": "String", - "Unit": "None" - }, - "/zi/mds/groups/0/status": { - "Node": "/ZI/MDS/GROUPS/0/STATUS", - "Description": "Indicates the status the synchronization group.", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "None", - "Options": { - "-1": "Error. An error occurred in the synchronization process.", - "0": "New", - "1": "Sync", - "2": "Alive" - } - }, - "/zi/mds/groups/0/locked": { - "Node": "/ZI/MDS/GROUPS/0/LOCKED", - "Description": "Indicates whether the device group is locked by a MDS module.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - }, - "/zi/mds/groups/0/keepalive": { - "Node": "/ZI/MDS/GROUPS/0/KEEPALIVE", - "Description": "Set by the MDS module to indicate control over this synchronization group.", - "Properties": "Read, Write, Setting", - "Type": "Integer (64 bit)", - "Unit": "None" - } -} \ No newline at end of file diff --git a/tests/nodetree/test_node_functional.py b/tests/nodetree/test_node_functional.py deleted file mode 100644 index 02e127f..0000000 --- a/tests/nodetree/test_node_functional.py +++ /dev/null @@ -1,322 +0,0 @@ -from __future__ import annotations - -import pickle -import typing as t -import warnings -from enum import Enum -from io import BytesIO - -import pytest - -if t.TYPE_CHECKING: - from labone.core.value import AnnotatedValue -from labone.nodetree.enum import _get_enum -from labone.nodetree.errors import LabOneInvalidPathError -from tests.mock_server_for_testing import get_mocked_node, get_unittest_mocked_node - - -@pytest.mark.asyncio -async def test_node_dot_subpathing(): - node = await get_unittest_mocked_node({"/a/b/c/d": {}}) - - assert node.a.b.c.d.path == "/a/b/c/d" - - -@pytest.mark.asyncio -async def test_node_bracket_subpathing(): - node = await get_unittest_mocked_node({"/a/b/c/d": {}}) - - assert node["a"]["b"]["c"]["d"].path == "/a/b/c/d" - - -@pytest.mark.asyncio -async def test_node_bracket_path_subpathing(): - node = await get_unittest_mocked_node({"/a/b/c/d": {}}) - - assert node["a/b/c/d"].path == "/a/b/c/d" - - -@pytest.mark.asyncio -async def test_node_bracket_number_subpathing(): - node = await get_unittest_mocked_node({"/0": {}}) - assert node[0].path == "/0" - - -@pytest.mark.asyncio -async def test_node_bracket_wildcard_subpathing(): - node = await get_unittest_mocked_node({"/a/b": {}}) - assert node["*"].b.path == "/*/b" - - -@pytest.mark.asyncio -async def test_subpathing_invalid_path_raises(): - node = await get_unittest_mocked_node({"/a": {}}) - with pytest.raises(LabOneInvalidPathError): - node.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_subpathing_too_deep_path_raises(): - node = await get_unittest_mocked_node({"/a": {}}) - with pytest.raises(LabOneInvalidPathError): - node.a.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_subpathing_too_deep_path_long_brackets_raises(): - node = await get_unittest_mocked_node({"/a": {}}) - with pytest.raises(LabOneInvalidPathError): - node["a/b"] - - -@pytest.mark.asyncio -async def test_access_same_node_repeatedly(): - node = await get_unittest_mocked_node({"/a/b": {}}) - for _ in range(10): - node.a.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_hide_kernel_prefix(): - node = await get_unittest_mocked_node({"/a/b": {}}, hide_kernel_prefix=True) - assert node.path == "/a" - - -@pytest.mark.asyncio -async def test_dont_hide_kernel_prefix(): - node = await get_unittest_mocked_node({"/a/b": {}}, hide_kernel_prefix=False) - assert node.path == "/" - - -@pytest.mark.asyncio -async def test_root_property_plain(): - node = await get_unittest_mocked_node({"/a/b": {}}, hide_kernel_prefix=False) - assert node.a.b.root == node - - -@pytest.mark.asyncio -async def test_root_property_hide_kernel_prefix(): - node = await get_unittest_mocked_node({"/a/b": {}}, hide_kernel_prefix=True) - assert node.b.root == node - - -@pytest.mark.parametrize( - "paths", - [ - set(), - {"/a"}, - {"/a", "/c", "/b", "/d"}, - ], -) -@pytest.mark.asyncio -async def test_iterating_over_node(paths): - node = await get_unittest_mocked_node({path: {} for path in paths}) - assert {subnode.path for subnode in node} == paths - - -@pytest.mark.asyncio -async def test_iterating_over_node_sorted(): - paths = {"/a", "/c", "/b", "/d"} - node = await get_unittest_mocked_node({path: {} for path in paths}) - assert [subnode.path for subnode in node] == sorted(paths) - - -@pytest.mark.asyncio -async def test_length_of_node(): - node = await get_unittest_mocked_node({"/a/b": {}, "/a/c": {}}) - assert len(node) == 1 - assert len(node.a) == 2 - - -@pytest.mark.asyncio -async def test_contains_next_segment(): - node = await get_unittest_mocked_node({"/a/b": {}, "/a/c": {}}) - assert "a" in node - assert "b" in node.a - assert "c" in node.a - assert "d" not in node.a - - -@pytest.mark.asyncio -async def test_contains_subnode(): - node = await get_unittest_mocked_node({"/a": {}, "/c/d": {}}) - assert node.a in node - assert node.c in node - - -@pytest.mark.asyncio -async def test_node_does_not_contain_itself(): - node = await get_unittest_mocked_node({"/a/b": {}}) - assert node not in node # noqa: PLR0124 - - -@pytest.mark.asyncio -async def test_node_does_not_contain_deeper_child(): - node = await get_unittest_mocked_node({"/a/b": {}}) - assert node.a.b not in node - - -@pytest.mark.asyncio -async def test_node_does_not_contain_other_path(): - node = await get_unittest_mocked_node({"/a/b": {}, "/c/d": {}}) - assert node.c.d not in node.a - - -@pytest.mark.asyncio -async def test_enum_parsing(): - node = await get_mocked_node( - {"/a": {"Options": {"0": "off"}, "Type": "Integer (enumerated)"}}, - ) - await node.a(0) - value = (await node.a()).value - - assert isinstance(value, Enum) - assert value == 0 - assert value.name == "off" - - -@pytest.mark.asyncio -async def test_no_enum_parsing_non_enum_nodes(): - node = await get_mocked_node({"/a": {}}) - await node.a(0) - value = (await node.a()).value - - assert not isinstance(value, Enum) - assert value == 0 - - -@pytest.mark.asyncio -async def test_enum_parsing_in_subscriptions(): - node = await get_mocked_node( - { - "/a": { - "Options": {"0": "off"}, - "Type": "Integer (enumerated)", - }, - }, - ) - queue = await node.a.subscribe() - await node.a(0) - value = (await queue.get()).value - - assert isinstance(value, Enum) - assert value == 0 - assert value.name == "off" - - -@pytest.mark.asyncio -async def test_custom_parser(): - def custom_parser(value: AnnotatedValue) -> AnnotatedValue: - value.value = value.value * 100 - return value - - node = await get_mocked_node({"/a": {}}, custom_parser=custom_parser) - await node.a(5) - assert (await node.a()).value == 500 - - -@pytest.mark.asyncio -async def test_custom_parser_in_subscriptions(): - def custom_parser(value: AnnotatedValue) -> AnnotatedValue: - value.value = value.value * 100 - return value - - node = await get_mocked_node({"/a": {}}, custom_parser=custom_parser) - - queue = await node.a.subscribe() - await node.a(5) - assert (await queue.get()).value == 500 - - -@pytest.mark.asyncio -async def test_comparable(): - paths = {"/a/d", "/a/e", "/a/b", "/a/c", "/a/f"} - node = await get_unittest_mocked_node({path: {} for path in paths}) - - # same comparison behavior as corresponding paths - assert sorted(paths) == sorted([node[p].path for p in paths]) - - -@pytest.mark.asyncio -async def test_hashing(): - node = await get_unittest_mocked_node({"/a/b": {}, "/c/d": {}, "/e/f": {}}) - nodes = [node.a.b, node.c.d, node.e.f] - - # lossless hashing - assert sorted(set(nodes)) == sorted(nodes) - - -def test_pickle_enum(): - enum_value = _get_enum( - path="/a", - info={ - "Options": {"0": "off"}, - "Type": "Integer (enumerated)", - }, - )(0) - buffer = BytesIO() - pickle.dump(enum_value, buffer) - - buffer.seek(0) - unpickled_obj = pickle.load(buffer) # noqa: S301 - - assert unpickled_obj == enum_value - - -@pytest.mark.asyncio -async def test_keyword_paths(): - node = await get_mocked_node({"/with/in/try": {}}) - assert node.with_.in_.try_.path == "/with/in/try" - - -@pytest.mark.asyncio -async def test_adding_nodes_manually_with_info(): - node = await get_unittest_mocked_node({"/a/b": {}}) - node.tree_manager.add_nodes_with_info({"/a/c": {}}) - assert node.a.c.path == "/a/c" # accessable - - -@pytest.mark.asyncio -async def test_adding_multiple_nodes_manually_with_info(): - node = await get_unittest_mocked_node({"/a/b": {}}) - node.tree_manager.add_nodes_with_info({"/a/c": {}, "/a/d": {}}) - assert node.a.c.path == "/a/c" - assert node.a.d.path == "/a/d" # accessable - - -@pytest.mark.asyncio -async def test_adding_nodes_manually(): - node = await get_unittest_mocked_node({"/a/b": {}}) - node.tree_manager.add_nodes(["/a/c"]) - assert node.a.c.path == "/a/c" # accessable - - -@pytest.mark.asyncio -async def test_adding_nodes_manually_hidden_prefix_change(): - node = await get_mocked_node({"/common_prefix/b": {}}, hide_kernel_prefix=True) - node = node.root - subnode_via_hidden_prefix = node.b - - # once no commen first prefix exists, the access via hidden prefix - # is not possible anymore - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - node.tree_manager.add_nodes(["/other_prefix/c"]) - node = node.root - subnode_via_shown_prefix = node.common_prefix.b - - assert subnode_via_hidden_prefix == subnode_via_shown_prefix - - -@pytest.mark.asyncio -async def test_adding_nodes_manually_hidden_prefix_does_only_change_if_required(): - node = await get_unittest_mocked_node( - {"/common_prefix/b": {}}, - hide_kernel_prefix=True, - ) - subnode_via_hidden_prefix = node.b - - # common first prefix still exists, so the access via hidden prefix - # is still possible - node.tree_manager.add_nodes(["/common_prefix/c"]) - assert subnode_via_hidden_prefix == node.b diff --git a/tests/nodetree/test_node_info.py b/tests/nodetree/test_node_info.py deleted file mode 100644 index f9b7307..0000000 --- a/tests/nodetree/test_node_info.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -In the current implementation, the node info is not used for -any functionality. Nethertheless, it may be useful for -customers. -""" - -from __future__ import annotations - -import pytest - -from labone.node_info import NodeInfo, OptionInfo -from tests.mock_server_for_testing import get_unittest_mocked_node - - -@pytest.mark.asyncio -async def test_node_info_accessable(): - node = await get_unittest_mocked_node( - { - "/a/b": { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": {"1": "Sync", "2": "Alive"}, - }, - }, - ) - node.a.b.node_info # noqa: B018 # no error - - -@pytest.mark.asyncio -async def test_node_info_attribute_redirects_to_node_info(): - plain_info = { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": {"1": "Sync", "2": "Alive"}, - } - node = await get_unittest_mocked_node({"/a/b": plain_info}) - assert node.a.b.node_info.as_dict == NodeInfo(plain_info).as_dict - - -@pytest.mark.parametrize( - ("attribute", "expected"), - [ - ("path", "/a/b"), - ("description", "abcde"), - ("properties", "Read, Write, Setting"), - ("type", "Integer (enumerated)"), - ("unit", "V"), - ], -) -@pytest.mark.asyncio -async def test_node_info_attributes(attribute, expected): - info = NodeInfo( - { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": {"1": "Sync", "2": "Alive"}, - }, - ) - assert getattr(info, attribute) == expected - - -@pytest.mark.parametrize( - ("plain_options", "parsed_options"), - [ - ( - {"1": "Sync", "2": "Alive"}, - { - 1: OptionInfo(enum="Sync", description=""), - 2: OptionInfo(enum="Alive", description=""), - }, - ), - ( - { - "1": '"Sync": Currently Synchronizing', - "2": '"Alive": Device is reachable.', - }, - ( - { - 1: OptionInfo(enum="Sync", description="Currently Synchronizing"), - 2: OptionInfo(enum="Alive", description="Device is reachable."), - } - ), - ), - ], -) -@pytest.mark.asyncio -async def test_options(plain_options, parsed_options): - info = NodeInfo( - { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": plain_options, - }, - ) - assert info.options == parsed_options - - -@pytest.mark.parametrize( - ("attribute", "expected"), - [ - ("readable", True), - ("writable", True), - ("is_setting", True), - ("is_vector", False), - ("path", "/a/b"), - ], -) -@pytest.mark.asyncio -async def test_node_info_emergent_attributes(attribute, expected): - info = NodeInfo( - { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": {"1": "Sync", "2": "Alive"}, - }, - ) - assert getattr(info, attribute) == expected - - -@pytest.mark.parametrize( - ("attribute", "expected"), - [ - ("readable", False), - ("writable", False), - ("is_setting", False), - ("is_vector", True), - ("path", "/a/b"), - ], -) -@pytest.mark.asyncio -async def test_node_info_emergent_attributes_case2(attribute, expected): - info = NodeInfo( - { - "Node": "/a/b", - "Description": "abcde", - "Properties": "", - "Type": "ZIVectorData", - "Unit": "V", - }, - ) - assert getattr(info, attribute) == expected - - -@pytest.mark.asyncio -async def test_empty_node_info_representable(): - info = NodeInfo({}) - repr(info) - - -@pytest.mark.asyncio -async def test_empty_node_info_stringifyable(): - info = NodeInfo({}) - str(info) - - -@pytest.mark.asyncio -async def test_partial_infos_partially_useable(): - info = NodeInfo({"Type": "ZIVectorData"}) - assert info.type == "ZIVectorData" # no error - - -@pytest.mark.asyncio -async def test_incomplete_info_raises(): - with pytest.raises(KeyError): - NodeInfo({}).type # noqa: B018 - - -@pytest.mark.asyncio -async def test_default_info_readable_writeable(): - info = NodeInfo(NodeInfo.plain_default_info(path="/a/b")) - assert info.readable is True - assert info.writable is True diff --git a/tests/nodetree/test_node_to_session_interface.py b/tests/nodetree/test_node_to_session_interface.py deleted file mode 100644 index c3f9866..0000000 --- a/tests/nodetree/test_node_to_session_interface.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Testing that nodetree calles the correct methods of the session. - -The calls are verified via mocking of the lower level interface. -""" - -from __future__ import annotations - -from unittest.mock import ANY, AsyncMock, Mock - -import pytest - -from labone.core.session import Session -from labone.core.value import AnnotatedValue -from labone.nodetree.entry_point import construct_nodetree - - -@pytest.mark.asyncio -async def test_get_translates_to_session(): - value = AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4) - - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.get = AsyncMock(return_value=value) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - assert value == await node.a.b.c.d() - session_mock.get.assert_called_once_with("/a/b/c/d") - - -@pytest.mark.asyncio -async def test_set_translates_to_session(): - value = AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4) - - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.set = AsyncMock(return_value=value) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - assert await node.a.b.c.d(42) == value - session_mock.set.assert_called_once_with( - AnnotatedValue(path="/a/b/c/d", value=42, timestamp=ANY), - ) - - -@pytest.mark.asyncio -async def test_partial_get_translates_to_session(): - value = (AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4),) - - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.get_with_expression = AsyncMock(return_value=value) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a.b.c() - session_mock.get_with_expression.assert_called_once_with("/a/b/c") - - -@pytest.mark.asyncio -async def test_partial_set_translates_to_session(): - value = AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4) - - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.set_with_expression = AsyncMock(return_value=[value]) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a.b.c(32) - session_mock.set_with_expression.assert_called_once_with( - AnnotatedValue(path="/a/b/c", value=32), - ) - - -@pytest.mark.asyncio -async def test_wildcard_get_translates_to_session(): - value = (AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4),) - - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.get_with_expression = AsyncMock(return_value=value) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a["*"].c.d() - session_mock.get_with_expression.assert_called_once_with("/a/*/c/d") - - -@pytest.mark.asyncio -async def test_wildcard_set_translates_to_session(): - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.set_with_expression = AsyncMock( - return_value=[ - AnnotatedValue(path="/a/b/c/d", value=42, timestamp=4), - ], - ) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a["*"].c.d(35) - session_mock.set_with_expression.assert_called_once_with( - AnnotatedValue(path="/a/*/c/d", value=35), - ) - - -@pytest.mark.asyncio -async def test_partial_subscribe_translates_to_session(): - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - session_mock.subscribe = AsyncMock(return_value=Mock()) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a.b.c.d.subscribe() - session_mock.subscribe.assert_called_once_with( - "/a/b/c/d", - parser_callback=ANY, - queue_type=ANY, - get_initial_value=ANY, - ) - - -@pytest.mark.asyncio -async def test_wait_for_state_change_translates_to_session(): - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b/c/d": {}}) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node.a.b.c.d.wait_for_state_change(value=5, invert=True) - - session_mock.wait_for_state_change.assert_called_once_with( - "/a/b/c/d", - 5, - invert=True, - ) - - -@pytest.mark.asyncio -async def test_wait_for_state_change_wildcard_translates_to_session(): - session_mock = Mock(spec=Session) - session_mock.list_nodes_info = AsyncMock(return_value={"/a/b": {}, "/a/c": {}}) - session_mock.list_nodes = AsyncMock(return_value=["/a/b", "/a/c"]) - node = (await construct_nodetree(session_mock, hide_kernel_prefix=False)).root - - await node["*"].wait_for_state_change(value=5, invert=True) - - session_mock.wait_for_state_change.assert_any_call("/a/b", 5, invert=True) - session_mock.wait_for_state_change.assert_any_call("/a/c", 5, invert=True) diff --git a/tests/nodetree/test_not_raises.py b/tests/nodetree/test_not_raises.py deleted file mode 100644 index 6382aa1..0000000 --- a/tests/nodetree/test_not_raises.py +++ /dev/null @@ -1,46 +0,0 @@ -from __future__ import annotations - -import pytest - -from tests.mock_server_for_testing import get_mocked_node, get_unittest_mocked_node - - -@pytest.mark.parametrize("method", ["__str__", "__repr__"]) -@pytest.mark.asyncio -async def test_metanode_not_raises(method): - node = await get_unittest_mocked_node({"/a/b/c/d": {}}) - getattr(node, method)() - - -@pytest.mark.parametrize("method", ["__str__", "__repr__", "__dir__"]) -@pytest.mark.asyncio -async def test_result_node_not_raises(method): - node = await get_mocked_node({"/a/b": {}}) - result = await node() - getattr(result, method)() - - -@pytest.mark.parametrize("method", ["__str__", "__repr__", "__dir__"]) -@pytest.mark.asyncio -async def test_node_not_raises(method): - node = await get_unittest_mocked_node({"/a/in/c/d": {}}) - getattr(node, method)() - - -@pytest.mark.parametrize("method", ["__str__", "__repr__", "__dir__"]) -@pytest.mark.asyncio -async def test_nodeinfo_not_raises(method): - node = await get_unittest_mocked_node( - { - "/a": { - "Node": "/a/b", - "Description": "abcde", - "Properties": "Read, Write, Setting", - "Type": "Integer (enumerated)", - "Unit": "V", - "Options": {"1": "Sync", "2": "Alive"}, - }, - }, - ) - info = node.a.node_info - getattr(info, method)() diff --git a/tests/nodetree/test_result_node.py b/tests/nodetree/test_result_node.py deleted file mode 100644 index 111a5d2..0000000 --- a/tests/nodetree/test_result_node.py +++ /dev/null @@ -1,183 +0,0 @@ -import pytest - -from labone.core.value import AnnotatedValue -from labone.nodetree.errors import LabOneInvalidPathError -from tests.mock_server_for_testing import get_mocked_node - - -@pytest.mark.asyncio -async def test_partial_get_result_node(): - node = await get_mocked_node( - {"/a/b/c/d": {}}, - ) - response = await node.a.b() - assert response.path_segments == () - response.a.b.c.d # noqa: B018 # path valid - - -@pytest.mark.asyncio -async def test_partial_get_result_node_multiple_subpaths(): - node = await get_mocked_node( - { - "/a/c": {}, - "/a/d": {}, - }, - ) - - response = await node.a() - assert response.path_segments == () - response.a.c # noqa: B018 # path valid - response.a.d # noqa: B018 # path valid - - -@pytest.mark.asyncio -async def test_partial_get_result_node_long_bracket_subpathing(): - node = await get_mocked_node( - {"/a/b/c": {}}, - ) - response = await node() - response["a/b/c"] # path valid - - -@pytest.mark.asyncio -async def test_partial_get_result_node_bracket_subpathing(): - node = await get_mocked_node( - {"/a/b/c": {}}, - ) - response = await node() - response["a"]["b"]["c"] # path valid - - -@pytest.mark.asyncio -async def test_partial_get_result_node_bracket_integer_subpathing(): - node = await get_mocked_node({"/0": {}}) - response = await node() - response[0] # path valid - - -@pytest.mark.asyncio -async def test_partial_get_result_node_wrong_path_raises(): - node = await get_mocked_node({"/a": {}}) - response = await node() - with pytest.raises(LabOneInvalidPathError): - response.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_partial_get_result_node_too_long_path_raises(): - node = await get_mocked_node({"/a": {}}) - response = await node() - with pytest.raises(AttributeError): # will be a AnnotatedValue Attribute error - response.a.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_partial_get_result_node_too_long_bracket_path_raises(): - node = await get_mocked_node({"/a": {}}) - response = await node() - with pytest.raises(LabOneInvalidPathError): - response["a/b"] - - -@pytest.mark.asyncio -async def test_partial_get_result_node_iterate_through_leaves(): - paths = {"/a/b/c", "/a/b/d", "/a/b/e"} - node = await get_mocked_node( - {p: {} for p in paths}, - ) - response = await node.a() - assert paths == {leaf.path for leaf in response.results()} - - -@pytest.mark.asyncio -async def test_partial_get_result_node_iterate_through_leaves_partial_scope(): - paths = {"/a/b/c", "/a/b/d", "/x/y"} - node = await get_mocked_node( - {p: {} for p in paths}, - ) - response = await node() - - # results shall only give results agreing to the current path prefix - assert {"/x/y"} == {leaf.path for leaf in response.x.results()} - - -@pytest.mark.asyncio -async def test_partial_get_result_node_values_as_leafs(): - node = await get_mocked_node({"/a/b": {}}) - result = await node() - assert isinstance(result.a.b, AnnotatedValue) - - -@pytest.mark.asyncio -async def test_partial_get_result_node_contains_next_segment(): - node = await get_mocked_node({"/a/b": {}, "/a/c": {}}) - result = await node() - assert "a" in result - assert "b" in result.a - assert "c" in result.a - assert "d" not in result.a - - -@pytest.mark.asyncio -async def test_partial_get_result_node_contains_subnode(): - node = await get_mocked_node({"/a/b": {}}) - result = await node() - assert result.a in result - - -@pytest.mark.asyncio -async def test_partial_get_result_node_contains_value_at_leaf(): - node = await get_mocked_node({"/a": {}}) - result = await node() - assert result.a in result - - -@pytest.mark.asyncio -async def test_partial_get_result_node_only_matches_accessable(): - node = await get_mocked_node({"/a/b/c": {}, "/a/x/c": {}}) - response = await node.a.b() # different pattern - with pytest.raises(LabOneInvalidPathError): - response.a.x # noqa: B018 - - -@pytest.mark.asyncio -async def test_partial_get_result_node_only_access_same_node_repeatedly(): - node = await get_mocked_node({"/a/b": {}}) - response = await node() - for _ in range(10): - response.a.b # noqa: B018 - - -@pytest.mark.asyncio -async def test_wildcard_get_result_node_basic_behavior(): - node = await get_mocked_node({"/a/b/c": {}}) - response = await node.a["*"].c() - assert response.path_segments == () - response.a.b.c # noqa: B018 - - -@pytest.mark.asyncio -async def test_wildcard_get_result_node_hide_prefix(): - node = await get_mocked_node( - {"/a/b/c": {}}, - hide_kernel_prefix=True, - ) - response = await node["*"].c() - assert response.path_segments == ("a",) - response.b.c # noqa: B018 - - -@pytest.mark.asyncio -async def test_wildcard_get_result_node_multiple_matches(): - node = await get_mocked_node({"/a/b/c": {}, "/a/x/c": {}}) - response = await node.a["*"].c() - response.a.b.c # noqa: B018 - response.a.x.c # noqa: B018 - - -@pytest.mark.asyncio -async def test_wildcard_get_result_node_only_matches_accessable(): - node = await get_mocked_node({"/a/b/c": {}}) - response = await node.a["*"].d() # different pattern - with pytest.raises(LabOneInvalidPathError): - response.a.b.c # noqa: B018 diff --git a/tests/nodetree/test_split_join_path.py b/tests/nodetree/test_split_join_path.py deleted file mode 100644 index eb39dda..0000000 --- a/tests/nodetree/test_split_join_path.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest - -from labone.nodetree.helper import join_path, split_path - - -class TestJoinSplitPath: - @pytest.mark.parametrize( - ("path_segments", "path"), - [ - ([], "/"), - (["a"], "/a"), - (["a", "b", "c"], "/a/b/c"), - (["a", "aa", "a", "aaa"], "/a/aa/a/aaa"), - ], - ) - def test_join_path(self, path_segments, path): - assert join_path(path_segments) == path - - @pytest.mark.parametrize( - ("path_segments", "path"), - [ - ([], "/"), - (["a"], "/a"), - (["a", "b", "c"], "/a/b/c"), - (["a", "aa", "a", "aaa"], "/a/aa/a/aaa"), - ], - ) - def test_split_path(self, path_segments, path): - assert split_path(path) == path_segments - - @pytest.mark.parametrize( - ("path_segments", "path"), - [ - ([], "/"), - (["a"], "/a"), - (["a", "b", "c"], "/a/b/c"), - (["a", "aa", "a", "aaa"], "/a/aa/a/aaa"), - ], - ) - def test_annihilation_split_join(self, path_segments, path): - assert join_path(split_path(path)) == path - assert split_path(join_path(path_segments)) == path_segments - - @pytest.mark.parametrize( - ("path_snippet1", "path_snippet2"), - [ - ("/a/b/c", "/d/e/f"), - ("/a/b", "/c/d/e/f"), - ("/", "a/b/c"), - ], - ) - def test_split_associativity(self, path_snippet1, path_snippet2): - assert ( - join_path(split_path(path_snippet1) + split_path(path_snippet2)) - == path_snippet1 + path_snippet2 - ) diff --git a/tests/nodetree/test_user_experience.py b/tests/nodetree/test_user_experience.py deleted file mode 100644 index 176ca6e..0000000 --- a/tests/nodetree/test_user_experience.py +++ /dev/null @@ -1,221 +0,0 @@ -""" -These tests do not test required behavior of the module. -Thus, they may fail even if everything is working correctly. -They are in place to make sure the user experience is as good as possible. -If new features are implemented or context changes, -the tests may not be relevant any more. -That is why for every test, there is a description to explain its purpose. -By consulting the description, it will the possible to determine -if the test is still relevant. -""" - -from __future__ import annotations - -import pytest - -from tests.mock_server_for_testing import get_mocked_node, get_unittest_mocked_node - - -def check_message_useful(message: str, keywords: list[str]) -> bool: - """Checks that an (error) message is suitable for a given situation. - - This function is a bare trial to assess the usefulness of - an error message to the user. It is a crude heuristic. - Human judgement is still the source of truth. - - If this function misclassifies an error message as not useful, - some keywords can be adjusted to make it succeed. - """ - return sum([k in message.lower() for k in keywords]) / len(keywords) >= 0.5 - - -class UsefulErrorMessage: - def __init__(self, keywords: list[str]): - self.keywords = keywords - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - if exc_type is not None: - if not check_message_useful(str(exc_value), self.keywords): - msg = f"Error message does not seem to be useful: {exc_value}" - raise AssertionError(msg) - return True - - msg = "No error was raised - even tough it was inticipated." - raise AssertionError(msg) - - -class TestOnlyLeafNodesCanBeSubscribedTo: - """Only leaf nodes can be subscribed to, - but it is not always obvious which nodes are leaf nodes. - Therefore, calling subscribe on a non-leaf node can happen - accidentely. - """ - - @pytest.mark.asyncio - async def test_subscribe_to_partial_node_raises(self): - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["partial", "cannot", "subscri", "leaf"]): - await node.a.subscribe() - - @pytest.mark.asyncio - async def test_subscribe_to_wildcard_node_raises(self): - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["wildcard", "cannot", "subscri", "leaf"]): - await node["*"].b.subscribe() - - -class TestWildcardNodeDoesNotKnowTreeStructure: - """Once wildcards are used in paths, it becomes harder - to determine which subnodes exist. Computing this is - not done for performance reasons. For this reason, - structure related operations are not supported on - wildcard nodes. Instead of giving confusing results, - an error message is shown. - """ - - @pytest.mark.asyncio - async def test_wildcard_node_contains_raises(self): - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["wildcard", "cannot", "contain"]): - "/a/b" in node["*"] # noqa: B015 - - @pytest.mark.asyncio - async def test_wildcard_node_iter_raises(self): - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["wildcard", "cannot", "iter"]): - for _ in node["*"]: - pass - - @pytest.mark.asyncio - async def test_wildcard_node_len_raises(self): - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["wildcard", "cannot", "len"]): - len(node["*"]) - - -@pytest.mark.asyncio -async def test_calling_result_node_raises(): - """ - Result nodes are already call-results. - Therefore there is no semantic in calling them. - May happen accidentely though. - """ - node = await get_unittest_mocked_node({"/a": {}}) - result = await node() - - with UsefulErrorMessage(["result", "cannot", "get", "set"]): - await result() - - -@pytest.mark.asyncio -async def test_using_wildcards_in_result_node_raises(): - """ - Wildcards are useful for specifying which paths to get/set. - In result nodes, there is no point in using them and it is - not supported. However, this is not obvious. - """ - node = await get_unittest_mocked_node({"/a": {}}) - result = await node() - - with UsefulErrorMessage(["result", "cannot", "wildcard"]): - await result["*"] - - -@pytest.mark.asyncio -async def test_wait_for_state_change_on_partial_node_raises(): - """ - Waiting for a change is done via providing a value. - Waiting for multiple nodes only makes sense if they - are all of the same type. This is most often not - the case for partial nodes. May be tried accidentely though. - """ - node = await get_unittest_mocked_node({"/a/b": {}}) - - with UsefulErrorMessage(["partial", "cannot", "state"]): - await node.a.wait_for_state_change(5) - - -@pytest.mark.asyncio -async def test_dir_shows_subpaths(): - """ - In order to make working on the nodetree easy, it is - important to know what path extensions are possible. - So the __dir__ method is supposed to show the subpaths. - """ - node = await get_unittest_mocked_node({"/a/b": {}, "/a/c": {}}) - - assert "b" in dir(node.a) - assert "c" in dir(node.a) - - -@pytest.mark.asyncio -async def test_dir_shows_subpaths_keywords_with_underscore(): - """ - Some path segments collide with python keywords. - Therefor, it wont be possible to type node.in.debug - Thats why the __dir__ method is supposed to show keywords - with an underscore, which is the intended way to use this in python. - """ - node = await get_unittest_mocked_node({"/in": {}}) - assert "in_" in dir(node) - - -class TestResultNodesNotGotPaths: - """ - Result Nodes can be used like the root node to - navigate to specific nodes. However, the get request of - a partial or wildcard node will only deliver a incomplete - snapshot. If the user now navigates to a node that was - not obtained, a useful error should be thrown - """ - - @pytest.mark.asyncio - async def test_result_nodes_not_got_paths_partial(self): - node = await get_unittest_mocked_node({"/a/b": {}, "/a/c/d": {}}) - result = await node.a.c() - - with UsefulErrorMessage(["not", "result", "captured"]): - result.a.b # noqa: B018 - - @pytest.mark.asyncio - async def test_result_nodes_not_got_paths_wildcard(self): - node = await get_unittest_mocked_node({"/a/b/c": {}, "/a/b/d": {}}) - result = await node.a["*"].c() # pattern ends with c - - with UsefulErrorMessage(["not", "result", "captured"]): - result.a.b.d # noqa: B018 - - -class TestResultNodeSubindexingFiltersShownPaths: - """ - On result node subindexing, the paths gets extended, but the dictionary - of all results (also the onces not matching the more precise path) - is just passed. This is expected, but may confuse the user. Therefore, - in representations, only the matching paths should be shown. - """ - - @pytest.mark.parametrize("method", [repr, str]) - @pytest.mark.asyncio - async def test_partial_nodes(self, method): - node = await get_mocked_node({"/a/b/c": {}, "/a/x/y": {}}) - result = await node.a() - representation = method(result.a.b) - assert "/a/b/c" in representation - assert "/a/x/y" not in representation - - @pytest.mark.parametrize("method", [repr, str]) - @pytest.mark.asyncio - async def test_wildcard_nodes(self, method): - node = await get_mocked_node({"/a/b/c": {}, "/a/x/c": {}}) - result = await node.a["*"].c() - representation = method(result.a.b) - assert "/a/b/c" in representation - assert "/a/x/c" not in representation diff --git a/tests/test_dataserver.py b/tests/test_dataserver.py deleted file mode 100644 index 5a426b4..0000000 --- a/tests/test_dataserver.py +++ /dev/null @@ -1,113 +0,0 @@ -from unittest.mock import AsyncMock, MagicMock, patch - -import pytest - -from labone.core import ( - KernelSession, -) -from labone.dataserver import DataServer -from labone.errors import LabOneError -from labone.mock import AutomaticLabOneServer - - -@pytest.mark.asyncio -async def test_create_ok(): - session = await AutomaticLabOneServer({}).start_pipe() - dataserver = await DataServer.create_from_session( - session=session, - host="host", - port=8004, - ) - assert dataserver.kernel_session == session - - -@pytest.mark.asyncio -async def test_create_ok_new_session(): - session = await AutomaticLabOneServer({}).start_pipe() - with patch.object( - KernelSession, - "create", - autospec=True, - return_value="session", - ) as create_mock: - create_mock.return_value = session - dataserver = await DataServer.create(host="host", port=8004) - assert dataserver.kernel_session == session - assert create_mock.call_count == 1 - - -@pytest.mark.asyncio -async def test_create_raises(): - session = MagicMock() - session.list_nodes_info = AsyncMock(side_effect=LabOneError()) - with pytest.raises(LabOneError): - await DataServer.create_from_session(session=session, host="host", port=8004) - - -@pytest.mark.parametrize( - ("status_nr"), - [0, 1 << 1, 1 << 2, 1 << 3, (1 << 2) + (1 << 3)], -) -@pytest.mark.asyncio -async def test_check_firmware_compatibility(status_nr): - session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() - dataserver = await DataServer.create_from_session( - session=session, - host="host", - port=8004, - ) - await dataserver.devices('{"DEV90021":{"STATUSFLAGS":' + str(status_nr) + "}}") - - await DataServer.check_firmware_compatibility(dataserver) - - -@pytest.mark.asyncio -async def test_check_firmware_compatibility_single_instrument(): - session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() - dataserver = await DataServer.create_from_session( - session=session, - host="host", - port=8004, - ) - await dataserver.devices( - '{"DEV90021":{"STATUSFLAGS": 0 },"DEV90022":{"STATUSFLAGS": 16 }}', - ) - await DataServer.check_firmware_compatibility(dataserver, devices=["DEV90021"]) - - -@pytest.mark.parametrize( - ("id_and_codes", "contained_in_error"), - [ - ([("DEV1000", 1 << 8)], ["updating", "DEV1000"]), - ([("DEV1000", 1 << 4)], ["update the firmware", "DEV1000"]), - ([("DEV1000", 1 << 5)], ["update the firmware", "DEV1000"]), - ([("DEV1000", 1 << 6)], ["update LabOne", "DEV1000"]), - ([("DEV1000", 1 << 7)], ["update LabOne", "DEV1000"]), - ( - [("DEV1000", ((1 << 8) + (1 << 7)))], - ["updating", "update LabOne", "DEV1000"], - ), - ( - [("DEV1000", 1 << 7), ("DEV2000", 1 << 5)], - ["update LabOne", "update the firmware", "DEV1000", "DEV2000"], - ), - ], -) -@pytest.mark.asyncio -async def test_check_firmware_compatibility_raises(id_and_codes, contained_in_error): - val = "{" - for id_, code in id_and_codes: - val += '"' + id_ + '":{"STATUSFLAGS":' + str(code) + "}," - val = val[:-1] + "}" - session = await AutomaticLabOneServer({"/zi/devices": {}}).start_pipe() - dataserver = await DataServer.create_from_session( - session=session, - host="host", - port=8004, - ) - await dataserver.devices(val) - - with pytest.raises(LabOneError) as e_info: - await DataServer.check_firmware_compatibility(dataserver) - for s in contained_in_error: - assert s in str(e_info.value) diff --git a/tests/test_instrument.py b/tests/test_instrument.py deleted file mode 100644 index dc29e40..0000000 --- a/tests/test_instrument.py +++ /dev/null @@ -1,61 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest - -from labone.errors import LabOneError -from labone.instrument import Instrument -from labone.mock import AutomaticLabOneServer -from tests.mock_server_for_testing import get_mocked_node - - -@pytest.mark.asyncio -@patch("labone.instrument.KernelSession", autospec=True) -async def test_connect_device(kernel_session): - session = await AutomaticLabOneServer({}).start_pipe() - kernel_session.create.return_value = session - instrument = await Instrument.create("dev1234", host="testee") - assert instrument.kernel_session == session - assert instrument.serial == "dev1234" - kernel_session.create.assert_awaited_once() - assert kernel_session.create.call_args.kwargs["server_info"].host == "testee" - assert kernel_session.create.call_args.kwargs["server_info"].port == 8004 - - -@pytest.mark.asyncio -@patch("labone.instrument.KernelSession", autospec=True) -async def test_connect_device_custom_default_args(kernel_session): - session = await AutomaticLabOneServer({}).start_pipe() - kernel_session.create.return_value = session - instrument = await Instrument.create( - "dev1234", - host="testee", - port=8888, - interface="wifi", - ) - assert instrument.kernel_session == session - assert instrument.serial == "dev1234" - kernel_session.create.assert_awaited_once() - assert kernel_session.create.call_args.kwargs["server_info"].host == "testee" - assert kernel_session.create.call_args.kwargs["server_info"].port == 8888 - - -@pytest.mark.asyncio -@patch("labone.instrument.KernelSession", autospec=True) -async def test_connect_device_error(kernel_session): - session = MagicMock() - kernel_session.create.return_value = session - session.list_nodes_info.side_effect = LabOneError() - with pytest.raises(LabOneError): - await Instrument.create("dev1234", host="testee") - - -@pytest.mark.asyncio -async def test_underlying_server(): - instrument = Instrument(serial="dev1234", model_node=await get_mocked_node({})) - assert instrument.kernel_session == instrument._tree_manager.session - - -@pytest.mark.asyncio -async def test_repr(): - instrument = Instrument(serial="dev1234", model_node=await get_mocked_node({})) - assert instrument.serial in repr(instrument) diff --git a/tests/test_version.py b/tests/test_version.py deleted file mode 100644 index 5726f49..0000000 --- a/tests/test_version.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Test module for the package version.""" - -import labone - - -def test_pkg_version(): - assert labone.__version__ == labone._version.__version__