Skip to content
Merged
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
34 changes: 34 additions & 0 deletions include/PyDataConsistencyGroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once

#include <pybind11/pybind11.h>
// pybind11.h must come first

#include <ChimeraTK/DataConsistencyGroup.h>

#include <pybind11/operators.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>

namespace py = pybind11;

namespace ChimeraTK {

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

class PyDataConsistencyGroup {
public:
static void bind(py::module& mod);
};

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

class PyMatchingMode {
public:
static void bind(py::module& mod);
};

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

} // namespace ChimeraTK
27 changes: 27 additions & 0 deletions include/PyTransferGroup.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once

#include <pybind11/pybind11.h>
// pybind11.h must come first

#include <ChimeraTK/TransferGroup.h>

#include <pybind11/operators.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>

namespace py = pybind11;

namespace ChimeraTK {

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

class PyTransferGroup {
public:
static void bind(py::module& mod);
};

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

} // namespace ChimeraTK
56 changes: 56 additions & 0 deletions src/PyDataConsistencyGroup.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "PyDataConsistencyGroup.h"

#include "PyTransferElement.h"

#include <pybind11/cast.h>
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>
namespace ctk = ChimeraTK;

namespace py = pybind11;
using namespace pybind11::literals;

namespace ChimeraTK {

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

void PyDataConsistencyGroup::bind(py::module& m) {
py::class_<ctk::DataConsistencyGroup>(m, "DataConsistencyGroup")
.def(py::init<ctk::DataConsistencyGroup::MatchingMode>())
.def(
"add", [](ctk::DataConsistencyGroup& self, PyTransferElementBase& element) { self.add(element.getTE()); },
R"(Add register to group.
The same TransferElement can be part of multiple DataConsistencyGroups. The register must be must be
readable, and it must have AccessMode::wait_for_new_data.)")
.def("update", &ctk::DataConsistencyGroup::update,
R"(This function must be called after an update was received from the ReadAnyGroup.
It returns true, if a consistent state is reached. It returns false if an TransferElementID was updated, that was not added to this group. For MatchingMode::historized, readAny will only let through consistent updates, so then update always returns true.)")
.def("getMatchingMode", &ctk::DataConsistencyGroup::getMatchingMode,
R"(Get the current MatchingMode of this DataConsistencyGroup.)")
.def("isConsistent", &ctk::DataConsistencyGroup::isConsistent,
R"(Returns true if a consistent state is reached )");
}

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

void PyMatchingMode::bind(py::module& m) {
py::enum_<ctk::DataConsistencyGroup::MatchingMode>(
m, "MatchingMode", "Enum describing the matching mode of a DataConsistencyGroup.")
.value("none", ctk::DataConsistencyGroup::MatchingMode::none,
"No matching, effectively disable the DataConsitencyGroup. update() will always return true. ")
.value("exact", ctk::DataConsistencyGroup::MatchingMode::exact,
"Require an exact match of the VersionNumber of all current values of the group's members. Require an "
"exact match of the VersionNumber of all current or historized values of the group's members ")
.value("historized", ctk::DataConsistencyGroup::MatchingMode::historized, "The data is not considered valid")
.export_values();
}

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

} // namespace ChimeraTK
4 changes: 0 additions & 4 deletions src/PyReadAnyGroup.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ namespace ChimeraTK {

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

// std::string PyReadAnyGroup::repr() const {}

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

void PyReadAnyGroup::bind(py::module& m) {
py::class_<ctk::ReadAnyGroup>(m, "ReadAnyGroup")
.def(py::init<>())
Expand Down
53 changes: 53 additions & 0 deletions src/PyTransferGroup.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "PyTransferGroup.h"

#include "PyTransferElement.h"

#include <pybind11/cast.h>
#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/pytypes.h>
#include <pybind11/stl.h>
namespace ctk = ChimeraTK;

namespace py = pybind11;
using namespace pybind11::literals;

namespace ChimeraTK {

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

void PyTransferGroup::bind(py::module& m) {
py::class_<ctk::TransferGroup>(m, "TransferGroup")
.def(py::init<>())
.def(
"addAccessor",
[](ctk::TransferGroup& self, PyTransferElementBase& element) { self.addAccessor(element.getTE()); },
R"(Add register to group. A register cannot be added to multiple groups. A TransferGroup can only be used with transfer elements that don't have AccessMode::wait_for_new_data.)")
.def("read", &ctk::TransferGroup::read, R"(Trigger read transfer for all accessors in the group.)")
.def(
"write",
[](ctk::TransferGroup& self, PyVersionNumber versionNumber) {
if(versionNumber == ChimeraTK::VersionNumber{nullptr}) {
self.write();
}
else {
self.write(versionNumber);
}
},
py::arg("versionNumber") = PyVersionNumber::getNullVersion(),
R"(Trigger write transfer for all accessors in the group.)")
.def("isReadOnly", &ctk::TransferGroup::isReadOnly,
R"(Returns true if all accessors in the group are read only.)")
.def("isReadable", &ctk::TransferGroup::isReadable,
R"(Returns true if all accessors in the group are readable.)")
.def("isWriteable", &ctk::TransferGroup::isWriteable,
R"(Returns true if all accessors in the group are writable.)");
}

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

} // namespace ChimeraTK
5 changes: 5 additions & 0 deletions src/deviceaccessPython.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
#include <pybind11/pybind11.h>
// pybind11.h must come first

#include "PyDataConsistencyGroup.h"
#include "PyDataType.h"
#include "PyDevice.h"
#include "PyOneDRegisterAccessor.h"
#include "PyReadAnyGroup.h"
#include "PyTransferGroup.h"
#include "PyTwoDRegisterAccessor.h"
#include "PyVersionNumber.h"
#include "RegisterCatalogue.h"
Expand Down Expand Up @@ -41,6 +43,9 @@ PYBIND11_MODULE(deviceaccess, m) {
DeviceAccessPython::DataDescriptor::bind(m);
ChimeraTK::PyReadAnyGroup::bind(m);
ChimeraTK::PyReadAnyGroupNotification::bind(m);
ChimeraTK::PyDataConsistencyGroup::bind(m);
ChimeraTK::PyMatchingMode::bind(m);
ChimeraTK::PyTransferGroup::bind(m);

m.def("setDMapFilePath", ChimeraTK::setDMapFilePath, py::arg("dmapFilePath"),
R"(Set the location of the dmap file.
Expand Down
20 changes: 17 additions & 3 deletions tests/deviceInformation/test_card.map
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@META_DATA_TEST 0.1.0-1-test

# name nr of elements address size bar width fracbits signed
# name nr of elements address size bar width fracbits signed
FLOAT32_TEST.SCALAR 0x00000001 0x00000000 0x00000004 0x00000001 32 IEEE754 1
FLOAT32_TEST.1DARRAY 0x00000004 0x00000004 0x00000010 0x00000001 32 IEEE754 1

Expand All @@ -27,9 +27,23 @@ INT32_TEST.SEQUENCE_2DARRAY_2 1 0x08 4 0x2 32
ASYNC.TEST_AREA_PUSH 0x0000000A 0x00000028 0x00000028 0x00000001 32 0 1 INTERRUPT2
ASYNC.DATA_READY 0x00000000 0x00000000 0x00000000 0x00000000 0 0 0 INTERRUPT2

ASYNC.SCALAR 0x00000001 0x000000CC 0x00000004 0x00000001 32 0 1
ASYNC.SCALAR 0x00000001 0x000000CC 0x00000004 0x00000001 32 0 1
ASYNC.SCALAR_PUSH 0x00000001 0x000000CC 0x00000004 0x00000001 32 0 1 INTERRUPT2

RW_TEST.SCALAR 0x00000001 0x00000012 0x00000004 0x00000001 32 0 1 RW
RO_TEST.SCALAR 0x00000001 0x00000012 0x00000004 0x00000001 32 0 1 RO
WO_TEST.SCALAR 0x00000001 0x00000012 0x00000004 0x00000001 32 0 1 WO
WO_TEST.SCALAR 0x00000001 0x00000012 0x00000004 0x00000001 32 0 1 WO


GROUP_TEST.SCALAR0 0x00000001 0x00000016 0x00000004 0x00000001 32 0 0 INTERRUPT0
GROUP_TEST.SCALAR1 0x00000001 0x00000020 0x00000004 0x00000001 32 0 0 INTERRUPT1
GROUP_TEST.SCALAR2 0x00000001 0x00000024 0x00000004 0x00000001 32 0 0 INTERRUPT2

GROUP_TEST.SCALAR3 0x00000001 0x00000028 0x00000004 0x00000001 32 0 0 INTERRUPT3
GROUP_TEST.SCALAR4 0x00000001 0x00000032 0x00000004 0x00000001 32 0 0 INTERRUPT3


TRANSFER_TEST.SCALAR1 0x00000001 0x00000036 0x00000004 0x00000001 32 0 0 RW
TRANSFER_TEST.SCALAR1_COPY 0x00000001 0x00000036 0x00000004 0x00000001 32 0 0 RW
TRANSFER_TEST.SCALAR2 0x00000001 0x00000040 0x00000004 0x00000001 32 0 0 RW
TRANSFER_TEST.SCALAR2_COPY 0x00000001 0x00000040 0x00000004 0x00000001 32 0 0 RW
115 changes: 115 additions & 0 deletions tests/testGroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,38 @@ def setUp(self):
np.int32, "GROUP_TEST.SCALAR1", 0, [da.AccessMode.wait_for_new_data])
self.scalar2: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR2", 0, [da.AccessMode.wait_for_new_data])
self.scalar3: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR3", 0, [da.AccessMode.wait_for_new_data])
self.scalar4: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR4", 0, [da.AccessMode.wait_for_new_data])

self.scalar0_writeable: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR0/DUMMY_WRITEABLE")
self.scalar1_writeable: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR1/DUMMY_WRITEABLE")
self.scalar2_writeable: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR2/DUMMY_WRITEABLE")
self.scalar3_writeable: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR3/DUMMY_WRITEABLE")
self.scalar4_writeable: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "GROUP_TEST.SCALAR4/DUMMY_WRITEABLE")

self.interrupt0: da.VoidRegisterAccessor = self.dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_0")
self.interrupt1: da.VoidRegisterAccessor = self.dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_1")
self.interrupt2: da.VoidRegisterAccessor = self.dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_2")
self.interrupt3: da.VoidRegisterAccessor = self.dev.getVoidRegisterAccessor("DUMMY_INTERRUPT_3")

self.push_rw: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "RW_TEST.SCALAR")
self.push_ro: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "RO_TEST.SCALAR")
self.push_ro2: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "RO_TEST.SCALAR")
self.push_wo: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "WO_TEST.SCALAR")

self.trans1: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "TRANSFER_TEST.SCALAR1")
self.trans1copy: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "TRANSFER_TEST.SCALAR1_COPY")
self.trans2: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(np.int32, "TRANSFER_TEST.SCALAR2")
self.trans2copy: da.ScalarRegisterAccessor = self.dev.getScalarRegisterAccessor(
np.int32, "TRANSFER_TEST.SCALAR2_COPY")

def tearDown(self) -> None:
self.dev.close()
Expand Down Expand Up @@ -156,6 +177,8 @@ def testReadAnyGroupsPollAndPushType(self):
self.pushScalar0.readLatest()
self.pushScalar1.readLatest()
self.scalar0.readLatest()
self.scalar0_writeable.setAndWrite(0)
self.pushScalar0.write()
self.pushScalar0_copy.readLatest() # not in the group
pollgroup.add(self.pushScalar0)
pollgroup.add(self.pushScalar1)
Expand All @@ -179,6 +202,98 @@ def testReadAnyGroupsPollAndPushType(self):
self.assertEqual(self.pushScalar0.get(), 412)
self.assertEqual(self.scalar0.get(), 0) # should not be updated, as it is not push type

def testDataConsistencyGroup(self):
registers = [self.scalar0, self.scalar1, self.scalar3, self.scalar4]
for acc in registers:
acc.readLatest()

# scalar0 and scalar1 have different interrupts, so they are never consistent
ragroup: da.ReadAnyGroup = da.ReadAnyGroup()
ragroup.add(self.scalar0)
ragroup.add(self.scalar1)
ragroup.finalise()

dcgroup: da.DataConsistencyGroup = da.DataConsistencyGroup(da.MatchingMode.exact)
dcgroup.add(self.scalar0)
dcgroup.add(self.scalar1)

self.scalar0_writeable.setAndWrite(123)
self.interrupt0.write()
self.scalar1_writeable.setAndWrite(456)
self.interrupt1.write()
id = ragroup.readAny()
self.assertEqual(id, self.scalar0.getId())
isConsistentFromUpdate = dcgroup.update(id)
self.assertFalse(isConsistentFromUpdate)
id = ragroup.readAny()
isConsistentFromUpdate = dcgroup.update(id)
self.assertFalse(isConsistentFromUpdate)
self.assertEqual(id, self.scalar1.getId())
self.assertFalse(dcgroup.isConsistent())

# scalar3 and scalar4 have the same interrupt, so they should be consistent
ragroup2: da.ReadAnyGroup = da.ReadAnyGroup()
ragroup2.add(self.scalar3)
ragroup2.add(self.scalar4)
ragroup2.finalise()

dcgroup2: da.DataConsistencyGroup = da.DataConsistencyGroup(da.MatchingMode.exact)
dcgroup2.add(self.scalar3)
dcgroup2.add(self.scalar4)

self.scalar3_writeable.setAndWrite(12233)
self.interrupt3.write()
self.scalar4_writeable.setAndWrite(45126)
self.interrupt3.write()
id = ragroup2.readAny()
self.assertEqual(id, self.scalar3.getId())
dcgroup2.update(id)
id = ragroup2.readAny()
dcgroup2.update(id)
self.assertEqual(id, self.scalar4.getId())
self.assertTrue(dcgroup2.isConsistent())

def testTransferGroup(self):
# test the properties of the group with different access modes
tg: da.TransferGroup = da.TransferGroup()
tg.addAccessor(self.push_rw)
self.assertTrue(tg.isReadable())
self.assertTrue(tg.isWriteable())
tg.addAccessor(self.push_wo)
self.assertFalse(tg.isReadable())
tg.addAccessor(self.push_ro)
self.assertFalse(tg.isWriteable())
self.assertFalse(tg.isReadOnly())
tg: da.TransferGroup = da.TransferGroup()
tg.addAccessor(self.push_ro2)
self.assertTrue(tg.isReadOnly())

# test read write behaviour
for acc in [self.trans1, self.trans2, self.trans1copy, self.trans2copy]:
acc.readLatest()
tg: da.TransferGroup = da.TransferGroup()
tg.addAccessor(self.trans1)
tg.addAccessor(self.trans2)

# no more indivdual reads or writes should be possible
self.assertRaises(RuntimeError, lambda: self.trans1.read())
self.assertRaises(RuntimeError, lambda: self.trans1.setAndWrite(42))

tg.read()
self.assertEqual(self.trans1.get(), 0)
self.assertEqual(self.trans2.get(), 0)
self.trans1copy.setAndWrite(123)
self.trans2copy.setAndWrite(456)
tg.read()
self.assertEqual(self.trans1.get(), 123)
self.assertEqual(self.trans2.get(), 456)

self.trans1.set(4242)
self.trans2.set(4546)
tg.write()
self.assertEqual(self.trans1copy.readAndGet(), 4242)
self.assertEqual(self.trans2copy.readAndGet(), 4546)


if __name__ == '__main__':
unittest.main()