Skip to content

Commit cc0f7d3

Browse files
committed
opal-async: Add new node-type for running VILLASnode as an Asynchronous process in OPAL-RT's RT-LAB
Signed-off-by: Steffen Vogel <[email protected]>
1 parent 7e68858 commit cc0f7d3

File tree

14 files changed

+748
-3
lines changed

14 files changed

+748
-3
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ find_package(Lua)
7676
find_package(LibDataChannel)
7777
find_package(re)
7878
find_package(OpenDSSC)
79+
find_package(OpalAsyncApi)
7980

8081
# Check for tools
8182
find_program(PASTE NAMES paste)
@@ -192,6 +193,7 @@ cmake_dependent_option(WITH_NODE_MODBUS "Build with modbus node-type"
192193
cmake_dependent_option(WITH_NODE_MQTT "Build with mqtt node-type" "${WITH_DEFAULTS}" "MOSQUITTO_FOUND" OFF)
193194
cmake_dependent_option(WITH_NODE_NANOMSG "Build with nanomsg node-type" "${WITH_DEFAULTS}" "NANOMSG_FOUND" OFF)
194195
cmake_dependent_option(WITH_NODE_NGSI "Build with ngsi node-type" "${WITH_DEFAULTS}" "" OFF)
196+
cmake_dependent_option(WITH_NODE_OPAL_ASYNC "Build with opal.async node-type" "${WITH_DEFAULTS}" "OpalAsyncApi_FOUND" OFF)
195197
cmake_dependent_option(WITH_NODE_REDIS "Build with redis node-type" "${WITH_DEFAULTS}" "HIREDIS_FOUND; REDISPP_FOUND" OFF)
196198
cmake_dependent_option(WITH_NODE_RTP "Build with rtp node-type" "${WITH_DEFAULTS}" "re_FOUND" OFF)
197199
cmake_dependent_option(WITH_NODE_SHMEM "Build with shmem node-type" "${WITH_DEFAULTS}" "HAS_SEMAPHORE; HAS_MMAN" OFF)

cmake/FindOpalAsyncApi.cmake

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# CMakeLists.txt
2+
#
3+
# Author: Steffen Vogel <[email protected]>
4+
# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH
5+
# SPDX-License-Identifier: Apache-2.0
6+
7+
set(TARGET_RTLAB_ROOT "/usr/opalrt" CACHE STRING "RT-LAB Root directory")
8+
9+
if(EXISTS "${TARGET_RTLAB_ROOT}/common/bin/opalmodelmk")
10+
set(ENV{TARGET_RTLAB_ROOT} ${TARGET_RTLAB_ROOT})
11+
12+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/get_vars.mk"
13+
"all:\n\techo $(OPAL_LIBS) $(OPAL_LIBPATH)\n")
14+
15+
execute_process(
16+
COMMAND make -sf ${TARGET_RTLAB_ROOT}/common/bin/opalmodelmk
17+
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
18+
OUTPUT_VARIABLE OPAL_VARS
19+
)
20+
21+
string(STRIP ${OPAL_VARS} OPAL_VARS)
22+
string(REPLACE " " ";" OPAL_VARS ${OPAL_VARS})
23+
24+
set(OPAL_LIBRARIES -lOpalCore -lOpalUtils ${OPAL_VARS} -lirc -ldl -pthread -lrt)
25+
set(OPAL_INCLUDE_DIR ${TARGET_RTLAB_ROOT}/common/include_target)
26+
27+
add_library(OpalAsyncApi INTERFACE)
28+
target_include_directories(opal INTERFACE ${OPAL_INCLUDE_DIR})
29+
target_link_libraries(opal INTERFACE ${OPAL_LIBRARIES})
30+
endif()
31+
32+
find_package_handle_standard_args(OpalAsyncApi DEFAULT_MSG OPAL_LIBRARIES OPAL_INCLUDE_DIR)
33+
34+
mark_as_advanced(OPAL_LIBRARIES OPAL_INCLUDE_DIR)

doc/openapi/components/schemas/config/node_obj.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ discriminator:
3939
mqtt: nodes/_mqtt.yaml
4040
nanomsg: nodes/_nanomsg.yaml
4141
ngsi: nodes/_ngsi.yaml
42+
opal_async: nodes/_opal_async.yaml
4243
opendss: nodes/_opendss.yaml
4344
redis: nodes/_redis.yaml
4445
rtp: nodes/_rtp.yaml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# yaml-language-server: $schema=http://json-schema.org/draft-07/schema
2+
# SPDX-FileCopyrightText: 2025 Institute for Automation of Complex Power Systems, RWTH Aachen University
3+
# SPDX-License-Identifier: Apache-2.0
4+
---
5+
allOf:
6+
- $ref: ../node_obj.yaml
7+
- $ref: opal_async.yaml
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# yaml-language-server: $schema=http://json-schema.org/draft-07/schema
2+
# Author: Steffen Vogel <[email protected]>
3+
# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH
4+
# SPDX-License-Identifier: Apache-2.0
5+
---
6+
allOf:
7+
- type: object
8+
properties:
9+
id:
10+
description: The Send/Recv ID of the RT-Lab OpAsyncSend/Recv blocks.
11+
min: 1
12+
default: 1
13+
type: integer
14+
15+
in:
16+
type: object
17+
properties:
18+
reply:
19+
description: Send a confirmation to the Simulink model that signals have been received and processed.
20+
default: false
21+
type: boolean
22+
23+
shmem:
24+
description: Shared-memory parameters for communication with OpAsyncGenCtrl block of Simulink model.
25+
type: object
26+
required:
27+
- async_name
28+
- async_size
29+
- system_ctrl_name
30+
properties:
31+
async_name:
32+
description: Name of the shared memory region used for data exchange with the Simulink model.
33+
type: string
34+
async_size:
35+
description: Size of the shared memory region used for data exchange with the Simulink model.
36+
type: integer
37+
system_ctrl_name:
38+
description: Name of the shared memory region used for logging.
39+
type: string
40+
41+
- $ref: ../node_signals.yaml
42+
- $ref: ../node.yaml

etc/examples/nodes/opal_async.conf

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Author: Steffen Vogel <[email protected]>
2+
# SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
nodes = {
6+
opal_async_node1 = {
7+
type = "opal.async"
8+
9+
### The following settings are specific to the opal node-type!! ###
10+
11+
# The Send/Recv ID of the RT-Lab OpAsyncSend/Recv blocks.
12+
id = 1
13+
14+
in = {
15+
# Send a confirmation to the Simulink model that signals have been received and processed.
16+
reply = false
17+
18+
hooks = (
19+
"stats"
20+
)
21+
}
22+
}
23+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/* Node type: OPAL-RT Asynchronous Process (libOpalAsyncApi)
2+
*
3+
* Author: Steffen Vogel <[email protected]>
4+
* SPDX-FileCopyrightText: 2023-2025 OPAL-RT Germany GmbH
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#pragma once
9+
10+
#include <condition_variable>
11+
#include <mutex>
12+
13+
#include <spdlog/details/log_msg_buffer.h>
14+
#include <spdlog/details/null_mutex.h>
15+
#include <spdlog/sinks/base_sink.h>
16+
17+
// Define RTLAB before including OpalPrint.h for messages to be sent
18+
// to the OpalDisplay. Otherwise stdout will be used. */
19+
#define RTLAB
20+
21+
#include <AsyncApi.h>
22+
#include <OpalGenAsyncParamCtrl.h>
23+
#include <OpalPrint.h>
24+
25+
#include <villas/format.hpp>
26+
#include <villas/node.hpp>
27+
#include <villas/popen.hpp>
28+
#include <villas/sample.hpp>
29+
30+
namespace villas {
31+
namespace node {
32+
namespace opal {
33+
34+
class LogSink final : public spdlog::sinks::base_sink<std::mutex> {
35+
private:
36+
std::string shmemSystemCtrlName;
37+
38+
public:
39+
explicit LogSink(const std::string &shmemName);
40+
41+
~LogSink() override;
42+
43+
protected:
44+
void sink_it_(const spdlog::details::log_msg &msg) override;
45+
46+
void flush_() override {}
47+
};
48+
49+
} // namespace opal
50+
51+
// Forward declarations
52+
struct Sample;
53+
54+
class OpalAsyncNode : public Node {
55+
56+
protected:
57+
// RT-LAB -> VILLASnode
58+
// Corresponds to AsyncAPI's *Send* direction
59+
struct {
60+
unsigned id;
61+
bool present;
62+
unsigned length;
63+
64+
bool reply;
65+
int mode;
66+
67+
Opal_SendAsyncParam params;
68+
} in;
69+
70+
// VILLASnode -> RT-LAB
71+
// Corresponds to AsyncAPI's *Recv* direction
72+
struct {
73+
unsigned id;
74+
bool present;
75+
unsigned length;
76+
77+
Opal_RecvAsyncParam params;
78+
} out;
79+
80+
bool ready;
81+
std::mutex readyLock;
82+
std::condition_variable readyCv;
83+
84+
virtual int _read(struct Sample *smps[], unsigned cnt) override;
85+
86+
virtual int _write(struct Sample *smps[], unsigned cnt) override;
87+
88+
public:
89+
OpalAsyncNode(const uuid_t &id = {}, const std::string &name = "")
90+
: Node(id, name), ready(false) {
91+
in.id = 1;
92+
in.present = false;
93+
in.length = 0;
94+
in.reply = true;
95+
96+
out.id = 1;
97+
out.present = false;
98+
out.length = 0;
99+
}
100+
101+
virtual const std::string &getDetails() override;
102+
103+
virtual int start() override;
104+
virtual int stop() override;
105+
106+
virtual int parse(json_t *json) override;
107+
108+
void markReady();
109+
void waitReady();
110+
};
111+
112+
class OpalAsyncNodeFactory : public NodeFactory {
113+
114+
public:
115+
using NodeFactory::NodeFactory;
116+
117+
virtual Node *make(const uuid_t &id = {},
118+
const std::string &nme = "") override {
119+
auto *n = new OpalAsyncNode(id, nme);
120+
121+
init(n);
122+
123+
return n;
124+
}
125+
126+
virtual int getFlags() const override {
127+
return (int)NodeFactory::Flags::SUPPORTS_READ |
128+
(int)NodeFactory::Flags::SUPPORTS_WRITE |
129+
(int)NodeFactory::Flags::PROVIDES_SIGNALS;
130+
}
131+
132+
virtual std::string getName() const override { return "opal.async"; }
133+
134+
virtual std::string getDescription() const override {
135+
return "OPAL Asynchronous Process (libOpalAsyncApi)";
136+
}
137+
138+
virtual int getVectorize() const override { return 1; }
139+
140+
virtual int start(SuperNode *sn) override;
141+
142+
virtual int stop() override;
143+
};
144+
145+
} // namespace node
146+
} // namespace villas

lib/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,18 @@ if(WITH_NODE_INFINIBAND)
7676
list(APPEND LIB_SRC memory/ib.cpp)
7777
endif()
7878

79+
if(WITH_NODE_OPAL_ASYNC)
80+
add_library(OpalAsyncApi SHARED empty.cpp)
81+
target_link_libraries(OpalAsyncApi PRIVATE -Wl,--allow-multiple-definition -Wl,--whole-archive ${OPAL_LIBRARIES} -Wl,--no-whole-archive)
82+
target_include_directories(OpalAsyncApi PUBLIC ${OPAL_INCLUDE_DIR})
83+
84+
install(
85+
TARGETS OpalAsyncApi
86+
COMPONENT lib
87+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
88+
)
89+
endif()
90+
7991
add_subdirectory(nodes)
8092
list(APPEND WHOLE_ARCHIVES nodes)
8193

lib/empty.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** An empty file
2+
*
3+
* @author Steffen Vogel <[email protected]>
4+
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
5+
* @copyright OPAL-RT Germany GmbH
6+
* @license Apache 2.0
7+
*********************************************************************************/

lib/nodes/CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ if(WITH_NODE_IEC61850)
7171
list(APPEND LIBRARIES PkgConfig::LIBIEC61850)
7272
endif()
7373

74+
# Enable OPAL-RT Asynchronous Process support
75+
if(WITH_NODE_OPAL_ASYNC)
76+
list(APPEND NODE_SRC opal_async.cpp)
77+
list(APPEND LIBRARIES OpalAsyncApi)
78+
endif()
79+
7480
if(WITH_NODE_MODBUS)
7581
list(APPEND NODE_SRC modbus.cpp)
7682
list(APPEND LIBRARIES PkgConfig::MODBUS)
@@ -91,7 +97,7 @@ endif()
9197
# Enable NGSI support
9298
if(WITH_NODE_NGSI)
9399
list(APPEND NODE_SRC ngsi.cpp)
94-
list(APPEND INCLUDE_DIRECTORIES ${CURL_INCLUDE_DIRS})
100+
list(APPEND INCLUDE_DIRS ${CURL_INCLUDE_DIRS})
95101
list(APPEND LIBRARIES ${CURL_LIBRARIES})
96102
endif()
97103

0 commit comments

Comments
 (0)