Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions include/RegisterCatalogue.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ namespace DeviceAccessPython {
static py::list items(ChimeraTK::RegisterCatalogue& self);

static py::list hiddenRegisters(ChimeraTK::RegisterCatalogue& self);

static void bind(py::module& m);
};

/*****************************************************************************************************************/
Expand All @@ -33,6 +35,9 @@ namespace DeviceAccessPython {

// convert return type form ChimeraTK::AccessModeFlags to Python list
static py::list getSupportedAccessModes(ChimeraTK::RegisterInfo& self);

static void bind(py::module& m);
static void bindBackendRegisterInfoBase(py::module& m);
};

/*****************************************************************************************************************/
Expand All @@ -41,6 +46,8 @@ namespace DeviceAccessPython {
public:
// Translate return type from RegisterPath to string
static ChimeraTK::DataDescriptor::FundamentalType fundamentalType(ChimeraTK::DataDescriptor& self);

static void bind(py::module& m);
};

/*****************************************************************************************************************/
Expand Down
34 changes: 27 additions & 7 deletions src/PyDataType.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,37 @@ namespace ChimeraTK {
/********************************************************************************************************************/

void ChimeraTK::PyDataType::bind(py::module& mod) {
py::class_<ChimeraTK::DataType> mDataType(mod, "DataType");
py::class_<ChimeraTK::DataType> mDataType(mod, "DataType",
R"(The actual enum representing the data type.
It is a plain enum so the data type class can be used like a class enum,
i.e. types are identified for instance as DataType::int32.)");
mDataType.def(py::init<ChimeraTK::DataType::TheType>())
.def("__str__", &ChimeraTK::DataType::getAsString)
.def("__repr__", [](const ChimeraTK::DataType& type) { return "DataType." + type.getAsString(); })
.def("isNumeric", &ChimeraTK::DataType::isNumeric)
.def("getAsString", &ChimeraTK::DataType::getAsString)
.def("isIntegral", &ChimeraTK::DataType::isIntegral)
.def("isSigned", &ChimeraTK::DataType::isSigned);
.def("isNumeric", &ChimeraTK::DataType::isNumeric,
R"(Returns whether the data type is numeric. Type 'none' returns false.

:return: True if the data type is numeric, false otherwise.
:rtype: bool)")
.def("getAsString", &ChimeraTK::DataType::getAsString,
R"(Get the data type as string.

:return: Data type as string.
:rtype: str)")
.def("isIntegral", &ChimeraTK::DataType::isIntegral,
R"(Return whether the raw data type is an integer. False is also returned for non-numerical types and 'none'.

:return: True if the data type is an integer, false otherwise.
:rtype: bool)")
.def("isSigned", &ChimeraTK::DataType::isSigned,
R"(Return whether the raw data type is signed. True for signed integers and floating point types (currently only signed implementations). False otherwise (also for non-numerical types and 'none').

:return: True if the data type is signed, false otherwise.
:rtype: bool)");

py::enum_<ChimeraTK::DataType::TheType>(mDataType, "TheType")
.value("none", ChimeraTK::DataType::none)
.value("none", ChimeraTK::DataType::none,
"The data type/concept does not exist, e.g. there is no raw transfer (do not confuse with Void)")
.value("int8", ChimeraTK::DataType::int8)
.value("uint8", ChimeraTK::DataType::uint8)
.value("int16", ChimeraTK::DataType::int16)
Expand All @@ -45,4 +65,4 @@ namespace ChimeraTK {

/********************************************************************************************************************/

} // namespace ChimeraTK
} // namespace ChimeraTK
226 changes: 205 additions & 21 deletions src/PyDevice.cc
Original file line number Diff line number Diff line change
Expand Up @@ -207,37 +207,222 @@ namespace ChimeraTK {
/*****************************************************************************************************************/

void PyDevice::bind(py::module& mod) {
py::class_<PyDevice> dev(mod, "Device");
dev.def(py::init<const std::string&>())
.def(py::init())
.def("open", py::overload_cast<const std::string&>(&PyDevice::open), py::arg("aliasName"))
.def("open", py::overload_cast<>(&PyDevice::open))
.def("close", &PyDevice::close)
py::class_<PyDevice> dev(mod, "Device",
R"(Class to access a ChimeraTK device.

The device can be opened and closed, and provides methods to obtain register accessors. Additionally,
convenience methods to read and write registers directly are provided.
The class also offers methods to check the device state, obtain the register catalogue, Metadata and to set exception conditions.)");

dev.def(py::init<const std::string&>(), py::arg("aliasName"),
R"(Initialize device and associate a backend.

Note:
The device is not opened after initialization.

:param aliasName: The ChimeraTK device descriptor for the device.
:type aliasName: str)")
.def(py::init(),
R"(Create device instance without associating a backend yet.

A backend has to be explicitly associated using open method which
has the alias or CDD as argument.)")
.def("open", py::overload_cast<const std::string&>(&PyDevice::open), py::arg("aliasName"),
R"(Open a device by the given alias name from the DMAP file.

:param aliasName: The device alias name from the DMAP file.
:type aliasName: str)")
.def("open", py::overload_cast<>(&PyDevice::open),
R"((Re-)Open the device.

Can only be called when the device was constructed with a given aliasName.)")
.def("close", &PyDevice::close,
R"(Close the device.

The connection with the alias name is kept so the device can be re-opened
using the open() function without argument.)")
.def("getVoidRegisterAccessor", &PyDevice::getVoidRegisterAccessor, py::arg("registerPathName"),
py::arg("accessModeFlags") = py::list(py::list()))
py::arg("accessModeFlags") = py::list(),
R"(Get a VoidRegisterAccessor object for the given register.

:param registerPathName: Full path name of the register.
:type registerPathName: str
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode]
:return: VoidRegisterAccessor for the specified register.
:rtype: VoidRegisterAccessor)")
.def("getScalarRegisterAccessor", &PyDevice::getScalarRegisterAccessor, py::arg("userType"),
py::arg("registerPathName"), py::arg("elementsOffset") = 0, py::arg("accessModeFlags") = py::list())
py::arg("registerPathName"), py::arg("elementsOffset") = 0, py::arg("accessModeFlags") = py::list(),
R"(Get a ScalarRegisterAccessor object for the given register.

The ScalarRegisterAccessor allows to read and write registers transparently
by using the accessor object like a variable of the type UserType.

:param userType: The data type for register access (numpy dtype).
:type userType: dtype
:param registerPathName: Full path name of the register.
:type registerPathName: str
:param elementsOffset: Word offset in register to access another but the first word.
:type elementsOffset: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:return: ScalarRegisterAccessor for the specified register.
:rtype: ScalarRegisterAccessor)")
.def("getOneDRegisterAccessor", &PyDevice::getOneDRegisterAccessor, py::arg("userType"),
py::arg("registerPathName"), py::arg("numberOfElements") = 0, py::arg("elementsOffset") = 0,
py::arg("accessModeFlags") = py::list())
py::arg("accessModeFlags") = py::list(),
R"(Get a OneDRegisterAccessor object for the given register.

The OneDRegisterAccessor allows to read and write registers transparently
by using the accessor object like a vector of the type UserType.

:param userType: The data type for register access (numpy dtype).
:type userType: dtype
:param registerPathName: Full path name of the register.
:type registerPathName: str
:param numberOfElements: Number of elements to access (0 for entire register).
:type numberOfElements: int, optional
:param elementsOffset: Word offset in register to skip initial elements.
:type elementsOffset: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:return: OneDRegisterAccessor for the specified register.
:rtype: OneDRegisterAccessor)")
.def("getTwoDRegisterAccessor", &PyDevice::getTwoDRegisterAccessor, py::arg("userType"),
py::arg("registerPathName"), py::arg("numberOfElements") = 0, py::arg("elementsOffset") = 0,
py::arg("accessModeFlags") = py::list())
.def("activateAsyncRead", &PyDevice::activateAsyncRead)
.def("getRegisterCatalogue", &PyDevice::getRegisterCatalogue)
py::arg("accessModeFlags") = py::list(),
R"(Get a TwoDRegisterAccessor object for the given register.

This allows to read and write transparently 2-dimensional registers.

:param userType: The data type for register access (numpy dtype).
:type userType: dtype
:param registerPathName: Full path name of the register.
:type registerPathName: str
:param numberOfElements: Number of elements per channel (0 for all).
:type numberOfElements: int, optional
:param elementsOffset: First element index for each channel to read.
:type elementsOffset: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:return: TwoDRegisterAccessor for the specified register.
:rtype: TwoDRegisterAccessor)")
.def("activateAsyncRead", &PyDevice::activateAsyncRead,
R"(Activate asynchronous read for all transfer elements with wait_for_new_data flag.

If called while the device is not opened or has an error, this call has no effect.
When this function returns, it is not guaranteed that all initial values have been
received already.)")
.def("getRegisterCatalogue", &PyDevice::getRegisterCatalogue,
R"(Return the register catalogue with detailed information on all registers.

:return: Register catalogue containing all register information.
:rtype: RegisterCatalogue)")
.def("read", &PyDevice::read, py::arg("registerPath"), py::arg("dtype") = py::dtype::of<double>(),
py::arg("numberOfWords") = 0, py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list())
py::arg("numberOfWords") = 0, py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(),
R"(Convenience function to read a register without obtaining an accessor.

Warning:
This function is inefficient as it creates and discards a register accessor
in each call. For better performance, use register accessors instead.

:param registerPath: Full path name of the register.
:type registerPath: str
:param dtype: Data type for the read operation (default: float64).
:type dtype: dtype, optional
:param numberOfWords: Number of elements to read (0 for scalar or entire register).
:type numberOfWords: int, optional
:param wordOffsetInRegister: Word offset in register to skip initial elements.
:type wordOffsetInRegister: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:return: Register value (scalar, 1D array, or 2D array depending on register type).
:rtype: scalar, ndarray, or list[list])")
.def("write", &PyDevice::write2D, py::arg("registerPath"), py::arg("dataToWrite"),
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none())
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(),
R"(Convenience function to write a 2D register without obtaining an accessor.

Warning:
This function is inefficient as it creates and discards a register accessor
in each call. For better performance, use register accessors instead.

:param registerPath: Full path name of the register.
:type registerPath: str
:param dataToWrite: 2D array data to write to the register.
:type dataToWrite: list[list]
:param wordOffsetInRegister: Word offset in register to skip initial elements.
:type wordOffsetInRegister: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:param dtype: Optional data type override (default: inferred from data).
:type dtype: dtype or None)")
.def("write", &PyDevice::write1D, py::arg("registerPath"), py::arg("dataToWrite"),
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none())
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(),
R"(Convenience function to write a 1D register without obtaining an accessor.

Warning:
This function is inefficient as it creates and discards a register accessor
in each call. For better performance, use register accessors instead.

:param registerPath: Full path name of the register.
:type registerPath: str
:param dataToWrite: 1D array data to write to the register.
:type dataToWrite: list or ndarray
:param wordOffsetInRegister: Word offset in register to skip initial elements.
:type wordOffsetInRegister: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:param dtype: Optional data type override (default: inferred from data).
:type dtype: dtype or None)")
.def("write", &PyDevice::writeScalar, py::arg("registerPath"), py::arg("dataToWrite"),
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none())
.def("isOpened", [](PyDevice& self) { return self._device.isOpened(); })
py::arg("wordOffsetInRegister") = 0, py::arg("accessModeFlags") = py::list(), py::arg("dtype") = py::none(),
R"(Convenience function to write a scalar register without obtaining an accessor.

Warning:
This function is inefficient as it creates and discards a register accessor
in each call. For better performance, use register accessors instead.

:param registerPath: Full path name of the register.
:type registerPath: str
:param dataToWrite: Scalar value to write to the register.
:type dataToWrite: int, float, or str
:param wordOffsetInRegister: Word offset in register (for multi-word registers).
:type wordOffsetInRegister: int, optional
:param accessModeFlags: Optional flags to control register access details.
:type accessModeFlags: list[AccessMode], optional
:param dtype: Optional data type override (default: inferred from data).
:type dtype: dtype or None)")
.def(
"isOpened", [](PyDevice& self) { return self._device.isOpened(); },
R"(Check if the device is currently opened.

:return: True if device is opened, false otherwise.
:rtype: bool)")
.def(
"setException", [](PyDevice& self, const std::string& msg) { return self._device.setException(msg); },
py::arg("message"))
.def("isFunctional", [](PyDevice& self) { return self._device.isFunctional(); })
py::arg("message"),
R"(Set the device into an exception state.

All asynchronous reads will be deactivated and all operations will see exceptions
until open() has successfully been called again.

:param message: Exception message describing the error condition.
:type message: str)")
.def(
"isFunctional", [](PyDevice& self) { return self._device.isFunctional(); },
R"(Check whether the device is working as intended.

Usually this means it is opened and does not have any errors.

:return: True if device is functional, false otherwise.
:rtype: bool)")
.def("getCatalogueMetadata", &PyDevice::getCatalogueMetadata, py::arg("metaTag"),
R"(Get metadata from the device catalogue.

:param metaTag: The metadata parameter name to retrieve.
:type metaTag: str
:return: The metadata value.
:rtype: str)")
.def("__enter__",
[](PyDevice& self) {
self.open();
Expand All @@ -248,8 +433,7 @@ namespace ChimeraTK {
[[maybe_unused]] py::object exc_traceback) {
self.close();
return false;
})
.def("getCatalogueMetadata", &PyDevice::getCatalogueMetadata, py::arg("metaTag"));
});
}

} // namespace ChimeraTK
Loading