From 5bc63f7524821e1a3cdc8f390a14c5c8430cc5dc Mon Sep 17 00:00:00 2001 From: KeVteL Date: Mon, 7 Oct 2024 11:44:40 +0200 Subject: [PATCH] Python-Wrapper added Necessary changes to integrate the python wrapper into the existing build system. Added the python wrapper Code. Testing the build and functionality still has to be done. Signed-off-by: KeVteL Signed-off-by: Kevin Vu te Laar --- CMakeLists.txt | 2 +- clients/CMakeLists.txt | 1 + clients/python-wrapper/CMakeLists.txt | 16 ++ .../python-wrapper/villas-python-wrapper.cpp | 216 ++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 clients/python-wrapper/CMakeLists.txt create mode 100644 clients/python-wrapper/villas-python-wrapper.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f2871bea6..914b38c26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project(villas-node VERSION 0.0.0 diff --git a/clients/CMakeLists.txt b/clients/CMakeLists.txt index 18483662a..689299ae1 100644 --- a/clients/CMakeLists.txt +++ b/clients/CMakeLists.txt @@ -4,5 +4,6 @@ # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 +add_subdirectory(python-wrapper) add_subdirectory(opal) add_subdirectory(shmem) diff --git a/clients/python-wrapper/CMakeLists.txt b/clients/python-wrapper/CMakeLists.txt new file mode 100644 index 000000000..a4945e041 --- /dev/null +++ b/clients/python-wrapper/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.15...3.29) +project(villas-python-wrapper LANGUAGES CXX) + +set(PYBIND11_FINDPYTHON ON) +find_package(pybind11 CONFIG REQUIRED) +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + +pybind11_add_module(villas-python-wrapper villas-python-wrapper.cpp) +target_link_libraries(villas-python-wrapper PUBLIC villas) + +install( + TARGETS villas-python-wrapper + COMPONENT bin + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/clients/python-wrapper/villas-python-wrapper.cpp b/clients/python-wrapper/villas-python-wrapper.cpp new file mode 100644 index 000000000..b00fd6c00 --- /dev/null +++ b/clients/python-wrapper/villas-python-wrapper.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; + +//pybind11 can not deal with (void **) as function input parameters, +//therefore we have to cast a simple (void *) pointer to the corresponding type +// +//thus: +//convenience functions for dealing with: +// +//(vnode *) +villas::node::Node *node_cast(void *p){ + vnode *n = static_cast(p); + auto *nc = reinterpret_cast(n); + return nc; +} +// +//(vsample *) +villas::node::Sample *sample_cast(void *p){ + vsample *smp = static_cast(p); + auto *smpc = reinterpret_cast(smp); + return smpc; +} +// +//(vsample **) +villas::node::Sample **samples_cast(void *pp){ + vsample **smps = static_cast(pp); + auto **smpsc = reinterpret_cast(smps); + return smpsc; +} + +//wrapper bindings, sorted alphabetically +// @param villas_node Name of the module to be bound +// @param m Access variable for modifying the module code +// +PYBIND11_MODULE(villas-python-wrapper, m) { + m.def("memory_init", [](int hugepages) -> int { + return villas::node::memory::init(hugepages); + }); + + m.def("node_check", [](void *p) -> int { + return node_cast(p)->check(); + }); + + m.def("node_destroy", [](void *p) -> int { + node_cast(p)->~Node(); + return 0; + }); + + m.def("node_details", [](void *p) -> const char * { + return node_cast(p)->getDetails().c_str(); + }, + py::return_value_policy::copy); + + m.def("node_input_signals_max_cnt", [](void *p) -> unsigned { + return node_cast(p)->getInputSignalsMaxCount(); + }); + + m.def("node_is_enabled", [](void *p) -> int { + return node_cast(p)->isEnabled(); + }); + + m.def("node_is_valid_name", [](const char *name) -> bool { + return villas::node::Node::isValidName(name); + }); + + m.def("node_name", [](void *p) -> const char * { + return node_cast(p)->getName().c_str(); + }, + py::return_value_policy::copy); + + m.def("node_name_full", [](void *p) -> const char * { + return node_cast(p)->getNameFull().c_str(); + }, + py::return_value_policy::copy); + + m.def("node_name_short", [](void *p) -> const char * { + return node_cast(p)->getNameShort().c_str(); + }, + py::return_value_policy::copy); + + m.def("node_netem_fds", [](void *p, int fds[]) -> int { + villas::node::Node *nc = node_cast(p); + auto l = nc->getNetemFDs(); + + for (unsigned i = 0; i < l.size() && i < 16; i++){ + fds[i] = l[i]; + } + + return l.size(); + }); + + m.def("node_new", [](const char *id_str, const char *json_str) -> vnode * { + json_error_t err; + uuid_t id; + uuid_parse(id_str, id); + auto *json = json_loads(json_str, 0, &err); + return (vnode *)villas::node::NodeFactory::make(json, id); + }, + py::return_value_policy::reference); + + m.def("node_output_signals_max_cnt", [](void *p) -> unsigned { + return node_cast(p)->getOutputSignalsMaxCount(); + }); + + m.def("node_pause", [](void *p) -> int { + return node_cast(p)->pause(); + }); + + m.def("node_poll_fds", [](void *p, int fds[]) -> int { + villas::node::Node *nc = node_cast(p); + auto l = nc->getPollFDs(); + + for (unsigned i = 0; i < l.size() && i < 16; i++){ + fds[i] = l[i]; + } + + return l.size(); + }); + + m.def("node_prepare", [](void *p) -> int { + return node_cast(p)->prepare(); + }); + + m.def("node_read", [](void *p, void *pp, unsigned cnt) -> int { + return node_cast(p)->read(samples_cast(pp), cnt); + }); + + m.def("node_restart", [](void *p) -> int { + return node_cast(p)->restart(); + }); + + m.def("node_resume", [](void *p) -> int { + return node_cast(p)->resume(); + }); + + m.def("node_reverse", [](void *p) -> int { + return node_cast(p)->reverse(); + }); + + m.def("node_start", [](void *p) -> int { + return node_cast(p)->start(); + }); + + m.def("node_stop", [](void *p) -> int { + return node_cast(p)->stop(); + }); + + m.def("node_to_json", [](void *p) -> json_t * { + return node_cast(p)->toJson(); + }, + py::return_value_policy::copy); + + m.def("node_to_json_str", [](void *p) -> const char * { + auto json = node_cast(p)->toJson(); + return json_dumps(json, 0); + }, + py::return_value_policy::copy); + + m.def("node_write", [](void *p, void *pp, unsigned cnt) -> int { + return node_cast(p)->write(samples_cast(pp), cnt); + }); + + m.def("sample_alloc", [](unsigned len) -> vsample * { + return (vsample *)villas::node::sample_alloc_mem(len); + }, py::return_value_policy::reference); + + // Decrease reference count and release memory if last reference was held. + m.def("sample_decref", [](void *p) -> void { + villas::node::sample_decref(sample_cast(p)); + }); + + m.def("sample_length", [](void *p) -> unsigned { + return sample_cast(p)->length; + }); + + m.def("sample_pack", [](unsigned seq, struct timespec *ts_origin, + struct timespec *ts_received, unsigned len, + double *values) -> vsample * { + auto *smp = villas::node::sample_alloc_mem(len); + + smp->sequence = seq; + smp->ts.origin = *ts_origin; + smp->ts.received = *ts_received; + smp->length = len; + smp->flags = (int)villas::node::SampleFlags::HAS_SEQUENCE | (int)villas::node::SampleFlags::HAS_DATA | + (int)villas::node::SampleFlags::HAS_TS_ORIGIN; + + memcpy((double *)smp->data, values, sizeof(double) * len); + + return (vsample *)smp; + }, py::return_value_policy::reference); + + m.def("sample_unpack", [](void *p, unsigned *seq, struct timespec *ts_origin, + struct timespec *ts_received, int *flags, unsigned *len, + double *values) -> void { + auto *smp = sample_cast(p); + + *seq = smp->sequence; + + *ts_origin = smp->ts.origin; + *ts_received = smp->ts.received; + + *flags = smp->flags; + *len = smp->length; + + memcpy(values, (double *)smp->data, sizeof(double) * *len); + }, + py::return_value_policy::reference); +} +