The user-code
plugin enables one to call a user-defined function when a specified event occur or certain data becomes available.
Important notes
- Make sure to compile your program with
Wl,--export-dynamic
or -rdynamic
flag (in CMake set ENABLE_EXPORTS
to TRUE
using set_target_properties
command) in order to generate necessary symbols.
- Make sure you use the proper access rights in your function in PDI_access (PDI_IN for reading, PDI_OUT for writing).
- Descriptor aliases enables one to use different descriptors without the need to recompile the code.
Dependencies between the code and the specification tree
To ensure proper work of the user-code
plugin, there are several conventions to follow in the application code and the specification tree.
First, each function name in specification tree must be a valid function name (watch out for name mangling).
Second, these functions can not take any arguments or return any value (i.e. their type must be void(void)
). Use descriptors for passing input/output variables instead.
Third, function's symbols must be exported to make them accessible by user-code
plugin. To do this, compile your program as described in Important notes.
Use examples
This section shows a simple examples with the use of a user-code
plugin.
Hello world!
First, we will call simple function without the use of descriptors to print "Hello world!" on "print" event.
hello_world.c:
#include <stdio.h>
#include <pdi.h>
void hello_world(void)
{
printf("Hello world!\n");
}
int main(int argc, char* argv[])
{
PC_tree_t conf = PC_parse_path("hello_world.yml");
return 0;
}
hello_world.yml:
plugins:
user_code:
on_event:
print:
hello_world: {}
output:
[PDI][User-code][13:40:55] *** info: Plugin loaded successfully
[PDI][13:40:55] *** info: Initialization successful
Hello world!
[PDI][13:40:55] *** info: Finalization
[PDI][User-code][13:40:55] *** info: Closing plugin
Handling input
Now we will pass some input data to the function.
print_number.c:
#include <stdio.h>
#include <pdi.h>
void print_number(void)
{
printf("I've got number %d.\n", *((int*)input));
}
int main(int argc, char* argv[])
{
PC_tree_t conf = PC_parse_path("print_number.yml");
int number = 42;
return 0;
}
print_number.yml:
data:
number: int
plugins:
user_code:
on_event:
print:
print_number: {input: $number}
output:
[PDI][User-code][13:49:30] *** info: Plugin loaded successfully
[PDI][13:49:30] *** info: Initialization successful
I've got number 42.
[PDI][13:49:30] *** info: Finalization
[PDI][User-code][13:49:30] *** info: Closing plugin
We can simplify this example by using on_data
to print value of number when it is shared to PDI.
print_number.c:
#include <stdio.h>
#include <pdi.h>
void print_number(void)
{
printf("I've got number %d.\n", *((int*)input));
}
int main(int argc, char* argv[])
{
PC_tree_t conf = PC_parse_path("print_number.yml");
int number = 42;
return 0;
}
print_number.yml:
data:
number: int
plugins:
user_code:
on_data:
number:
print_number: {input: $number}
output does not change:
[PDI][User-code][13:52:15] *** info: Plugin loaded successfully
[PDI][13:52:15] *** info: Initialization successful
I've got number 42.
[PDI][13:52:15] *** info: Finalization
[PDI][User-code][13:52:15] *** info: Closing plugin
Handling output
Output handling is very similar to the input handling, the only difference are the access rights. In this example we will call add_ten
function when number
is shared to PDI.
adding_to_number.c:
#include <stdio.h>
#include <pdi.h>
void add_ten(void)
{
*((int*)input) += 10;
}
int main(int argc, char* argv[])
{
PC_tree_t conf = PC_parse_path("adding_to_number.yml");
int number = 42;
printf("Before expose, number = %d.\n", number);
printf("After expose, number = %d.\n", number);
return 0;
}
adding_to_number.yml:
data:
number: int
plugins:
user_code:
on_data:
number:
add_ten: {input: $number}
output:
[PDI][User-code][13:53:51] *** info: Plugin loaded successfully
[PDI][13:53:51] *** info: Initialization successful
Before expose, number = 42.
After expose, number = 52.
[PDI][13:53:51] *** info: Finalization
[PDI][User-code][13:53:51] *** info: Closing plugin
Multiple input/output data
In this example we will use multiple data in function. We will add and multiply two given numbers and return the results on event "calculate".
calculate.c:
#include <stdio.h>
#include <pdi.h>
void sum_and_multiply(void)
{
*((int*)sum) = *((int*)number1) + *((int*)number2);
*((int*)product) = *((int*)number1) * *((int*)number2);
}
int main(int argc, char* argv[])
{
PC_tree_t conf = PC_parse_path("calculate.yml");
int foo = 4, bar = 5, res1 = 0, res2 = 0;
printf("Before calculation, foo = %d, bar = %d, res1 = %d, res2 = %d.\n", foo, bar, res1, res2);
printf("After calculation, foo = %d, bar = %d, res1 = %d, res2 = %d.\n", foo, bar, res1, res2);
return 0;
}
calculate.yml:
data:
foo: int
bar: int
res1: int
res2: int
plugins:
user_code:
on_event:
calculate:
sum_and_multiply: {number1: $foo, number2: $bar, sum: $res1, product: $res2}
output:
[PDI][User-code][13:58:20] *** info: Plugin loaded successfully
[PDI][13:58:20] *** info: Initialization successful
Before calculation, foo = 4, bar = 5, res1 = 0, res2 = 0.
After calculation, foo = 4, bar = 5, res1 = 9, res2 = 20.
[PDI][13:58:20] *** info: Finalization
[PDI][User-code][13:58:20] *** info: Closing plugin
Configuration grammar
The root of user-code
plugin is a dictionary that contains the following keys:
key | value |
"logging" (optional) | a logging |
"on_data" (optional) | a on_data |
"on_event" (optional) | a on_event |
".*" (optional) | anything |
- the
on_data
key specifies the list of descriptors that, when they become available, will cause the specified functions to be called,
- the
on_event
key specifies the list of events on which to call the specified functions,
- additional keys are ignored.
on_data
A on_data is a dictionary that contains the following keys:
- each key identifies the name of a descriptor, which will trigger specified functions when it becomes available.
on_event
A on_event is a dictionary that contains the following keys:
- each key identifies the name of an event, which will trigger specified functions when it occurs.
function_list
A function_list is a dictionary that contains the following keys:
- each key identifies the name of a function, which will be called on specified event or data,
- NOTE: these functions can not take any arguments or return any value (i.e. their type must be
void(void)
).
function_param_list
A function_param_list is a dictionary that contains the following keys:
key | value |
".*" (optional) | a $-expression referencing a data |
- each key identifies the name of a descriptor alias, which will be available during function execution.
Specification tree example
data:
desc1: int
desc2: float
desc3: double
plugins:
user_code:
on_data:
desc1:
fun1: {in: $desc2, out: $desc3}
desc2:
fun2: {}
fun3: {out: $desc2}
on_event:
event1:
fun2: {}
event2:
fun4: {param1: $desc2, param2: $desc1, param3: $desc3}