opc source code

This commit is contained in:
flower_linux
2026-06-09 17:27:24 +08:00
parent 70f675a48b
commit f0da5cb3c3
2002 changed files with 269812 additions and 7 deletions

View File

@@ -0,0 +1,78 @@
# open62541 Realtime OPC UA PubSub Publisher
This example is a self-contained PubSub publisher over raw Ethernet. It
showcases the realtime-capabilities of OPC UA PubSub. The core idea is that the
publisher callback can be triggered from a time-triggered system interrupt and
sends out the PubSub message within the interrupt.
The publisher retrieves its configuration and the payload data from the
information model of an OPC UA server. (It could also be run in a standalone
mode without an OPC UA server.) Since the publisher interrupt preempts the
execution of the normal OPC UA server, the information model needs to be
consistent at every time (reentrant). The specific techniques used to make the
OPC UA server reentrant are described in this publication:
```
@inproceedings{pfrommer2018open,
title={Open source OPC UA PubSub over TSN for realtime industrial communication},
author={Pfrommer, Julius and Ebner, Andreas and Ravikumar, Siddharth and Karunakaran, Bhagath},
booktitle={2018 IEEE 23rd International Conference on Emerging Technologies and Factory Automation (ETFA)},
pages={1087--1090},
year={2018},
organization={IEEE}
}
```
Please cite if you use this work.
OPC UA PubSub for open62541 is funded by an industry consortium in the context
of an OSADL project (Open Source Automation Development Lab). Technical
development is conducted by Fraunhofer IOSB and Kalycito Infotech.
https://www.osadl.org/OPC-UA-TSN.opcua-tsn.0.html
## Realtime communication with Time-Sensitive Networking (TSN)
OPC UA PubSub can be used together with TSN for hard-realtime Ethernet-based
communication. Vendor-specific APIs are commonly used for TSN capabilities. This
example only uses the standard Linux API for raw Ethernet. Vendor-specific
examples may be added at a later time.
## Building the RT Publisher
The main open62541 library needs to be built with the following build options
enabled for the realtime PubSub example. Note that some of the other examples
supplied with open62541 will not link against the library with these build
options. For good timings, ensure that the `CMAKE_BUILD_TYPE` is set to
`Release`.
- UA_ENABLE_PUBSUB
- UA_BUILD_EXAMPLES
- UA_ENABLE_MALLOC_SINGLETON
- UA_ENABLE_PUBSUB_BUFMALLOC
- UA_ENABLE_IMMUTABLE_NODES
The publisher contains some hard-coded values that need to be adjusted to
specific systems. Please check the top definitions in
`pubsub_interrupt_publish.c` and `start_rt_publish.sh`.
## Running the RT Publisher
The publisher must be run as root for direct access to the Ethernet interface.
`# ./rt_publisher`
The example contains a script to be used with RT-Preempt Linux kernel. The
following command starts the publisher, locks the process to a specific CPU, and
sets the scheduling policy.
`# start_rt_publish.sh ./rt_publisher`
The measurements are written to a file (publisher_measurement.csv) with the
following fields for every publish callback:
- Counter
- Publication Interval
- Nominal time for the current publish
- Start delay from the nominal time
- Duration of the publish callback

View File

@@ -0,0 +1,188 @@
Tested Environment:
Apollo Lake processor-1.60GHz with Intel i210 Ethernet Controller
OS - Debian/Lubuntu
Kernel - 4.19.37-rt19, 5.4.59-rt36
[Note: The pubsub TSN applications have two functionalities - ETF and XDP.
ETF will work in all RT kernels above 4.19. XDP is tested only on 5.4.59-rt36 kernel and it may work on kernels above 5.4.
If user wishes to run these pubsub TSN applications in the kernel below 4.19, there might be minimal changes required in the applications]
PRE-REQUISITES:
We recommend at least two Intel x86-based nodes with 4-cores and Intel i210 Ethernet Controllers connected in peer-peer fashion
RT enabled kernel in nodes (say node1 and node2)
Ensure the nodes are ptp synchronized
PTP SYNCHRONIZATION:
Clone and install linuxptp version 2.0
git clone https://github.com/richardcochran/linuxptp.git
cd linuxptp/
git checkout -f 059269d0cc50f8543b00c3c1f52f33a6c1aa5912
make
make install
cp configs/gPTP.cfg /etc/linuxptp/
Create VLAN with VLAN ID of 8 in peer to peer connected interface, copy paste the following lines in "/etc/network/interfaces" file and setup suitable IP address
auto <i210 interface>.8
iface <i210 interface>.8 inet static
address <suitable IP address>
netmask 255.255.255.0
To make node as ptp master run the following command (say in node1):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
To make node as ptp slave run the following command (say in node2):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/ptp4l.log -e /var/log/ptp4l.err.log /usr/bin/taskset -c 1 chrt 90 /usr/local/sbin/ptp4l -i <I210 interface> -2 -mq -s -f /etc/linuxptp/gPTP.cfg --step_threshold=1 --fault_reset_interval=0 --announceReceiptTimeout=10 --transportSpecific=1
To ensure phc2sys synchronization (in both nodes, node1 and node2):
sudo daemonize -E BUILD_ID=dontKillMe -o /var/log/phc2sys.log -e /var/log/phc2sys.err.log /usr/bin/taskset -c 1 chrt 89 /usr/local/sbin/phc2sys -s <I210 interface> -c CLOCK_REALTIME --step_threshold=1 --transportSpecific=1 -w -m
Check if ptp4l and phc2sys are running
ptp4l log in /var/log/ptp4l.log
phc2sys log in /var/log/phc2sys.log
Check if /etc/modules has entry for 8021q
cat /etc/modules | grep '8021q'
For ETF Transmit: (In both nodes)
#Configure ETF
sudo tc qdisc add dev <I210 interface> parent root mqprio num_tc 3 map 2 2 1 0 2 2 2 2 2 2 2 2 2 2 2 2 queues 1@0 1@1 2@2 hw 0
MQPRIO_NUM=`sudo tc qdisc show | grep mqprio | cut -d ':' -f1 | cut -d ' ' -f3`
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:1 etf offload clockid CLOCK_TAI delta 150000
sudo tc qdisc add dev <I210 interface> parent $MQPRIO_NUM:2 etf offload clockid CLOCK_TAI delta 150000
In both nodes:
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan egress $j:$j ; done
for j in `seq 0 7`;do sudo ip link set <I210 interface>.8 type vlan ingress $j:$j ; done
TO RUN ETF APPLICATIONS:
To run ETF applications over Ethernet in two nodes connected in peer-to-peer network
mkdir build
cd build
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make
The generated binaries are generated in build/bin/ folder
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> - Run in node 1
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> - Run in node 2
Eg: ./bin/examples/pubsub_TSN_publisher -interface enp2s0
./bin/examples/pubsub_TSN_loopback -interface enp2s0
NOTE: It is always recommended to run pubsub_TSN_loopback application first to avoid missing the initial data published by pubsub_TSN_publisher
To know more usage
./bin/examples/pubsub_TSN_publisher -help
./bin/examples/pubsub_TSN_loopback -help
NOTE: To know more about running the OPC UA PubSub application and to evaluate performance, refer the Quick Start Guide - https://www.kalycito.com/how-to-run-opc-ua-pubsub-tsn/
============================================================================================================
You can also subscribe using XDP (Express Data Path) for faster processing the data, follow the below steps:
Pre-requisties for XDP:
Minimum kernel version of 5.4 (Tested in RT enabled kernel - 5.4.59-rt36 with Debian OS)
Install llvm and clang
apt-get install llvm
apt-get install clang
Clone libbpf using the following steps:
git clone https://github.com/libbpf/libbpf.git
cd libbpf/
git checkout ab067ed3710550c6d1b127aac6437f96f8f99447
cd src/
OBJDIR=/usr/lib make install
Check if libbpf.so is available in /usr/lib folder, and bpf/ folder is available in /usr/include folder in both nodes (node1 and node2)
NOTE: Make sure the header file if_xdp.h available in /usr/include/linux/ is the right header file as in kernel version 5.4.59, if not update the if_xdp.h to newer version. (Verify your if_xdp.h file with https://elixir.bootlin.com/linux/v5.4.36/source/include/uapi/linux/if_xdp.h)
As per the sample application, XDP listens to Rx_Queue 2; to direct the incoming ethernet packets to the second Rx_Queue, the follow the given steps
In both nodes:
INGRESS POLICY steps:
#Configure equal weightage to queue 0 and 1
ethtool -X <I210 interface> equal 2
#Disable VLAN offload
ethtool -K <I210 interface> rxvlan off
ethtool -K <I210 interface> ntuple on
ethtool --config-ntuple <I210 interface> delete 15
ethtool --config-ntuple <I210 interface> delete 14
#Below command forwards the VLAN tagged packets to RX_Queue 2
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:01 loc 15 action 2
ethtool --config-ntuple <I210 interface> flow-type ether proto 0x8100 dst 01:00:5E:7F:00:02 loc 14 action 2
To run ETF in Publisher and XDP in Subscriber in two nodes connected in peer-to-peer network
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make
By default XDP will be disabled in the Subscriber, use -enableXdpSubscribe to use XDP in RX.
./bin/examples/pubsub_TSN_publisher -interface <I210 interface> -enableXdpSubscribe - Run in node 1
./bin/examples/pubsub_TSN_loopback -interface <I210 interface> -enableXdpSubscribe - Run in node 2
Optional steps:
By default, XDP listens to RX_Queue 2 with SKB and COPY mode.
To make XDP listen to other RX_Queue, provide -xdpQueue <num> as arguments when executing applications and modify the action value (INGRESS POLICY) to the desired queue number.
To enable ZEROCOPY mode, provide -xdpBindFlagZeroCopy as an additional argument to the applications
To enable XDP Driver (DRV) mode, provide -xdpFlagDrvMode as an additional argument to the applications
NOTE: To enable XDP socket, pass its configuration parameters through connectionProperties (KeyValuePair) in connectionConfig as mentioned below. XDP socket has support
for both Publisher and Subscriber connection but it is recommended to use XDP only in Subscriber connection.
UA_KeyValuePair connectionOptions[4]; // KeyValuePair for XDP configuration
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "enableXdpSocket"); // Key for enabling the XDP socket
UA_Boolean enableXdp = UA_TRUE; // Boolean value for enabling and disabling XDP socket
UA_Variant_setScalar(&connectionOptions[0].value, &enableXdp, &UA_TYPES[UA_TYPES_BOOLEAN]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "xdpflag"); // Key for the XDP flags
UA_UInt32 flags = xdpFlag; // Value to determine whether XDP works in SKB mode or DRV mode
UA_Variant_setScalar(&connectionOptions[1].value, &flags, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[2].key = UA_QUALIFIEDNAME(0, "hwreceivequeue"); // Key for the hardware queue
UA_UInt32 rxqueue = xdpQueue; // Value of the hardware queue to receive the packets
UA_Variant_setScalar(&connectionOptions[2].value, &rxqueue, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[3].key = UA_QUALIFIEDNAME(0, "xdpbindflag"); // Key for the XDP bind flags
UA_UInt32 bindflags = xdpBindFlag; // Value to determine whether XDP works in COPY or ZEROCOPY mode
UA_Variant_setScalar(&connectionOptions[3].value, &bindflags, &UA_TYPES[UA_TYPES_UINT16]);
connectionConfig.connectionProperties = connectionOptions; // Provide all the KeyValuePairs to properties of connectionConfig
connectionConfig.connectionPropertiesSize = 4;
To know more usage
./bin/examples/pubsub_TSN_publisher -help
./bin/examples/pubsub_TSN_loopback -help
===================================================================================================================================================================
NOTE:
If VLAN tag is used:
Ensure the MAC address is given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
If VLAN tag is not used:
Ensure the MAC address is not given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01"
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher.c and pubsub_TSN_loopback.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
=====================================================================================================================================================================
TO RUN THE UNIT TEST CASES FOR ETHERNET FUNCTIONALITY:
Change the ethernet interface in #define ETHERNET_INTERFACE macro in
check_pubsub_connection_ethernet.c
check_pubsub_publish_ethernet.c
check_pubsub_connection_ethernet_etf.c
check_pubsub_publish_ethernet_etf.c
1. To test ethernet connection creation and ethernet publish unit tests(without realtime)
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binaries are generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_ethernet - To check ethernet connection creation
./bin/tests/check_pubsub_publish_ethernet - To check ethernet send functionality
2. To test ethernet connection creation and ethernet publish unit tests(with realtime)
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binaries are generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_ethernet_etf - To check ethernet connection creation with etf
./bin/tests/check_pubsub_publish_ethernet_etf - To check ethernet send functionality with etf
TO RUN THE UNIT TEST CASES FOR XDP FUNCTIONALITY:
1. To test connection creation using XDP transport layer
cd open62541/build
make clean
cmake -DUA_ENABLE_PUBSUB=ON -DUA_BUILD_UNIT_TESTS=ON ..
make
The following binary is generated in build/bin/tests folder
./bin/tests/check_pubsub_connection_xdp <I210 interface> - To check connection creation with XDP

View File

@@ -0,0 +1,195 @@
Tested Environment:
TTTech IP (2.3.0) - Arm architecture
Kernel - 5.4.40 (non-RT)
============================================================================================================
PRE-REQUISITES:
1) We recommend at least two TTTech TSN IP nodes with 2-cores
2) Ensure the nodes are ptp synchronized
deptp_tool --get-current-dataset
3) Add and aditional clock domain /etc/deptp/ptp_config.xml, reboot the board after applying this change (this is a one time step)
<Clock>
<clock_class>187</clock_class>
<clock_accuracy>1us</clock_accuracy>
<clock_priority1>247</clock_priority1>
<clock_priority2>248</clock_priority2>
<domain>100</domain>
<time_source>internal oscillator</time_source>
</Clock>
4) Create VLAN with VLAN ID of 8 in peer to peer connected interface using the below command
ip link add link <interfaceName> name myvlan type vlan id 8 ingress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7 egress-qos-map 0:7 1:7 2:7 3:7 4:7 5:7 6:7 7:7
ip addr add <VlanIpAddress> dev myvlan
ip link set dev myvlan up
bridge vlan add vid 8 dev <portName>
bridge vlan add vid 8 dev <portName2> (optional e.g. for monitoring)
bridge vlan add vid 8 dev <InternalPortName>
ip route add 224.0.0.0/24 dev myvlan
NOTE: <portName> refers to the external physical port that has ethernet cable connected to it in each node
5) Create the Qbv configuration i.e setting up the gating configuration:
1)Create a config file on both the nodes using the below command
touch qbv.cfg
2)At node 1 copy the below gating configuration and paste it in the qbv.cfg
sgs 5000 0x80
sgs 490000 0x7F
3)At node 2 copy the below gating configuration and paste it in the qbv.cfg
sgs 5000 0x80
sgs 490000 0x7F
4)To schedule the gating configuration run the below command on both the nodes
tsntool st wrcl <portName> qbv.cfg
tsntool st rdacl <portName> /dev/stdout
tsntool st configure 5.0 1/2000 10000 <portName>
tsntool st show <portName>
6) After these steps the following files shoud exist and have values:
cat /run/ptp_wc_mon_offset
cat /sys/class/net/<portName>/ieee8021ST/OperBaseTime
============================================================================================================
Cross Compiler options to compile:
1) To compile the binaries create the folder using the below command:
mkdir build
2) Traverse to the folder using the command
cd build
3) Install the arm cross compiler - arm-linux-gnueabihf
4) Cross compilation for ARM architecture
CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabihf-gcc
CMAKE_C_COMPILER_AR /usr/bin/arm-linux-gnueabihf-gcc-ar-7
CMAKE_C_COMPILER_RANLIB /usr/bin/arm-linux-gnueabihf-gcc-ranlib-7
CMAKE_C_FLAGS -Wno-error
CMAKE_C_FLAGS_DEBUG -g -Wno-error
CMAKE_C_LINKER /usr/bin/arm-linux-gnueabihf-ld
CMAKE_NM /usr/bin/arm-linux-gnueabihf-nm
CMAKE_OBJCOPY /usr/bin/arm-linux-gnueabihf-objcopy
CMAKE_OBJDUMP /usr/bin/arm-linux-gnueabihf-objdump
CMAKE_RANLIB /usr/bin/arm-linux-gnueabihf-ranlib
CMAKE_STRIP /usr/bin/arm-linux-gnueabihf-strip
Note: The above mentioned options are changes for arm compiler.Ignore them if you
are compiling for x86. The options will be available in tools/cmake/Toolchain-ARM.cmake
for cross compilation
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make -j4 pubsub_TSN_publisher_multiple_thread
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
cmake -DCMAKE_TOOLCHAIN_FILE=../tools/cmake/Toolchain-ARM.cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
make -j4 pubsub_TSN_loopback_single_thread
5) Compilation for x86 architecture
CMake options for Publisher application(pubsub_TSN_publisher_multiple_thread):
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON ..
make -j4 pubsub_TSN_publisher_multiple_thread
CMake options for loopback application(pubsub_TSN_loopback_single_thread):
cmake -DUA_BUILD_EXAMPLES=ON -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_BUFMALLOC=ON -DUA_ENABLE_MALLOC_SINGLETON=ON ..
make -j4 pubsub_TSN_loopback_single_thread
============================================================================================================
To RUN the APPLICATIONS:
The generated binaries are generated in build/bin/ folder
============================================================================================================
TWO WAY COMMUNICATION
============================================================================================================
For Ethernet(Without logs) For long run:
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
For Ethernet:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
Note: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
For UDP(Without logs) For long run:
For UDP only one change need to be done before running that is need to provide the interface ip address as an input for -interface option
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -pubSubAppPriority 99 - Run in node 2
For UDP:(With logs - Provide the counterdata and its time which helps to identify the roundtrip time): For Short run
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -subAppPriority 99 -pubAppPriority 98 - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
NOTE: Application will close after sending 100000 packets and you will get the logs in .csv files. To change the number of packets to capture for short run
change the parameter #MAX_MEASUREMENTS define value in pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
============================================================================================================
ONE WAY COMMUNICATION: For one way communication disable(comment) the macro TWO_WAY_COMMUNICATION in the the pubsub_TSN_publisher_multiple_thread and pubsub_TSN_loopback_single_thread
============================================================================================================
Run steps for Ethernet:
./pubsub_TSN_publisher_multiple_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <interfaceName> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
Run steps for UDP:
./pubsub_TSN_publisher_multiple_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubAppPriority - Run in node 1
./pubsub_TSN_loopback_single_thread -interface <VlanIpAddress> -disableSoTxtime -operBaseTime /sys/class/net/<portName>/ieee8021ST/OperBaseTime -monotonicOffset /run/ptp_wc_mon_offset -cycleTimeInMsec 0.5 -enableCsvLog -pubSubAppPriority 99 - Run in node 2
NOTE: As we have mentioned previously for long run remove the option -enableCsvLog
============================================================================================================
NOTE: It is always recommended to run pubsub_TSN_loopback_single_thread application first to avoid missing the initial data published by pubsub_TSN_publisher_multiple_thread
To know more usage
./bin/examples/pubsub_TSN_publisher_multiple_thread -help
./bin/examples/pubsub_TSN_loopback_single_thread -help
============================================================================================================
NOTE:
For Ethernet
If VLAN tag is used:
Ensure the MAC address is given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01:8.3" where 8 is the VLAN ID and 3 is the PCP
If VLAN tag is not used:
Ensure the MAC address is not given along with VLAN ID and PCP
Eg: "opc.eth://01-00-5E-00-00-01"
If MAC address is changed, follow INGRESS POLICY steps with dst address to be the same as SUBSCRIBING_MAC_ADDRESS for both nodes
To increase the payload size, change the REPEATED_NODECOUNTS macro in pubsub_TSN_publisher_multiple_thread.c and pubsub_TSN_loopback_single_thread.c applications (for 1 REPEATED_NODECOUNTS, 9 bytes of data will increase in payload)
============================================================================================================
Output Logs: If application runs with option enableCsvLog
/**
* Trace point setup
*
* +--------------+ +----------------+
* T1 | OPCUA PubSub | T8 T5 | OPCUA loopback | T4
* | | Application | ^ | | Application | ^
* | +--------------+ | | +----------------+ |
* User | | | | | | | |
* Space | | | | | | | |
* | | | | | | | |
* ----------|--------------|------------------------|----------------|-------
* | | Node 1 | | | | Node 2 | |
* Kernel| | | | | | | |
* Space | | | | | | | |
* | | | | | | | |
* v +--------------+ | v +----------------+ |
* T2 | TX tcpdump | T7<----------------T6 | RX tcpdump | T3
* | +--------------+ +----------------+ ^
* | |
* ----------------------------------------------------------------
*/
For publisher application (pubsub_TSN_publisher_multiple_thread): publisher_T1.csv, subscriber_T8.csv
For loopback application: publisher_T5.csv, subscriber_T4.csv
To Compute the round trip time: Subtract T8-T1 (subtract the time in the .csv files of subscriber_T8.csv - publisher_T1.csv)
============================================================================================================
To close the running application press ctrl+c during the application exit the packet loss count of the entire run will be displayed in the console print.
============================================================================================================
For the TTTech TSN IP version 2.3.0 the following are applicable:
<interfaceName> sw0ep
<portName> available external physical ports: sw0p2, sw0p3, sw0p5, sw0p3
<InternalPortName> sw0p1

View File

@@ -0,0 +1,48 @@
####################
# Nodeset Examples PubSub#
####################
###################
# Custom XML #
###################
set(FILE_CSV_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
set(FILE_BSD_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
set(FILE_NS_DIRPREFIX ${PROJECT_SOURCE_DIR}/pubsub_realtime/nodeset)
set(PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR})
# generate namespace from XML file
ua_generate_nodeset_and_datatypes(
NAME "example_publisher"
FILE_NS "${FILE_NS_DIRPREFIX}/pubDataModel.xml"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
)
ua_generate_nodeset_and_datatypes(
NAME "example_subscriber"
FILE_NS "${FILE_NS_DIRPREFIX}/subDataModel.xml"
DEPENDS "${CMAKE_CURRENT_LIST_DIR}/../../../tools/schema/Opc.Ua.NodeSet2.Reduced.xml"
)
# The .csv file can be created from within UaModeler or manually
ua_generate_nodeid_header(
NAME "example_nodeids_publisher"
ID_PREFIX "EXAMPLE_NS_PUBLISHER"
TARGET_SUFFIX "ids_example_publisher"
FILE_CSV "${FILE_CSV_DIRPREFIX}/pubDataModel.csv"
)
ua_generate_nodeid_header(
NAME "example_nodeids_subscriber"
ID_PREFIX "EXAMPLE_NS_SUBSCRIBER"
TARGET_SUFFIX "ids_example_subscriber"
FILE_CSV "${FILE_CSV_DIRPREFIX}/subDataModel.csv"
)
add_example(pubsub_nodeset_rt_publisher pubsub_nodeset_rt_publisher.c
${UA_NODESET_EXAMPLE_PUBLISHER_SOURCES}
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_publisher.h)
add_example(pubsub_nodeset_rt_subscriber pubsub_nodeset_rt_subscriber.c
${UA_NODESET_EXAMPLE_SUBSCRIBER_SOURCES}
${PROJECT_BINARY_DIR}/src_generated/open62541/example_nodeids_subscriber.h)
target_link_libraries(pubsub_nodeset_rt_publisher rt pthread)
target_link_libraries(pubsub_nodeset_rt_subscriber rt pthread)

View File

@@ -0,0 +1,4 @@
PublisherInfo,2001,ObjectType
Publisher,2004,Object
PublisherCounterValue,2005,Variable
Pressure,2006,Variable
1 PublisherInfo 2001 ObjectType
2 Publisher 2004 Object
3 PublisherCounterValue 2005 Variable
4 Pressure 2006 Variable

View File

@@ -0,0 +1,80 @@
<?xml version='1.0' encoding='utf-8'?>
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamespaceUris>
<Uri>http://yourorganisation.org/test/</Uri>
</NamespaceUris>
<Aliases>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="UInt64">i=9</Alias>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="Organizes">i=35</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasComponent">i=47</Alias>
</Aliases>
<UAObjectType BrowseName="1:PublisherInfo" NodeId="ns=1;i=2001">
<DisplayName>PublisherInfo</DisplayName>
<Description>PublisherInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
</References>
</UAObjectType>
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
<DisplayName>PublisherCounterVariable</DisplayName>
<Description>PublisherCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
<UAObject BrowseName="1:Publisher" NodeId="ns=1;i=2004" ParentNodeId="i=85">
<DisplayName>Publisher</DisplayName>
<Description>PublisherInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
</References>
</UAObject>
<UAVariable BrowseName="1:PublisherCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
<DisplayName>PublisherCounterVariable</DisplayName>
<Description>PublisherCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
</UANodeSet>

View File

@@ -0,0 +1,736 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
/**
* .. _pubsub-nodeset-tutorial:
*
* Publisher Realtime example using custom nodes
* ---------------------------------------------
*
* The purpose of this example file is to use the custom nodes of the XML
* file(pubDataModel.xml) for publisher. This Publisher example uses the two
* custom nodes (PublisherCounterVariable and Pressure) created using the XML
* file(pubDataModel.xml) for publishing the packet. The pubDataModel.csv will
* contain the nodeids of custom nodes(object and variables) and the nodeids of
* the custom nodes are harcoded inside the addDataSetField API. This example
* uses two threads namely the Publisher and UserApplication. The Publisher
* thread is used to publish data at every cycle. The UserApplication thread
* serves the functionality of the Control loop, which increments the
* counterdata to be published by the Publisher and also writes the published
* data in a csv along with transmission timestamp.
*
* Run steps of the Publisher application as mentioned below:
*
* ``./bin/examples/pubsub_nodeset_rt_publisher -i <iface>``
*
* For more information run ``./bin/examples/pubsub_nodeset_rt_publisher -h``. */
#define _GNU_SOURCE
/* For thread operations */
#include <pthread.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/types_generated.h>
#include "ua_pubsub.h"
#include "open62541/namespace_example_publisher_generated.h"
/* to find load of each thread
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_publisher */
/* Configurable Parameters */
/* Cycle time in milliseconds */
#define DEFAULT_CYCLE_TIME 0.25
/* Qbv offset */
#define QBV_OFFSET 25 * 1000
#define DEFAULT_SOCKET_PRIORITY 3
#define PUBLISHER_ID 2234
#define WRITER_GROUP_ID 101
#define DATA_SET_WRITER_ID 62541
#define PUBLISHING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
#define PORT_NUMBER 62541
/* Non-Configurable Parameters */
/* Milli sec and sec conversion to nano sec */
#define MILLI_SECONDS 1000 * 1000
#define SECONDS 1000 * 1000 * 1000
#define SECONDS_SLEEP 5
#define DEFAULT_PUB_SCHED_PRIORITY 78
#define DEFAULT_PUBSUB_CORE 2
#define DEFAULT_USER_APP_CORE 3
#define MAX_MEASUREMENTS 30000000
#define SECONDS_INCREMENT 1
#define CLOCKID CLOCK_TAI
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
/* Below mentioned parameters can be provided as input using command line arguments
* If user did not provide the below mentioned parameters as input through command line
* argument then default value will be used */
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
static UA_Boolean consolePrint = UA_FALSE;
static UA_Int32 socketPriority = DEFAULT_SOCKET_PRIORITY;
static UA_Int32 pubPriority = DEFAULT_PUB_SCHED_PRIORITY;
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
static UA_Boolean useSoTxtime = UA_TRUE;
/* User application Pub will wakeup at the 30% of cycle time and handles the */
/* user data write in Information model */
/* First 30% is left for subscriber for future use*/
static UA_Double userAppWakeupPercentage = 0.3;
/* Publisher will sleep for 60% of cycle time and then prepares the */
/* transmission packet within 40% */
/* after some prototyping and analyzing it */
static UA_Double pubWakeupPercentage = 0.6;
static UA_Boolean fileWrite = UA_FALSE;
/* If the Hardcoded publisher MAC addresses need to be changed,
* change PUBLISHING_MAC_ADDRESS
*/
/* Set server running as true */
UA_Boolean running = UA_TRUE;
UA_UInt16 nsIdx = 0;
/* Variables corresponding to PubSub connection creation,
* published data set and writer group */
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
/* Variables for counter data handling in address space */
UA_UInt64 *pubCounterData;
UA_DataValue *pubDataValueRT;
/* Variables for counter data handling in address space */
UA_Double *pressureData;
UA_DataValue *pressureValueRT;
/* File to store the data and timestamps for different traffic */
FILE *fpPublisher;
char *fileName = "publisher_T1.csv";
/* Array to store published counter data */
UA_UInt64 publishCounterValue[MAX_MEASUREMENTS];
UA_Double pressureValues[MAX_MEASUREMENTS];
size_t measurementsPublisher = 0;
/* Array to store timestamp */
struct timespec publishTimestamp[MAX_MEASUREMENTS];
/* Thread for publisher */
pthread_t pubthreadID;
struct timespec dataModificationTime;
/* Thread for user application*/
pthread_t userApplicationThreadID;
typedef struct {
UA_Server* ServerRun;
} serverConfigStruct;
/* Structure to define thread parameters */
typedef struct {
UA_Server* server;
void* data;
UA_ServerCallback callback;
UA_Duration interval_ms;
UA_UInt64* callbackId;
} threadArg;
/* Publisher thread routine for ETF */
void *publisherETF(void *arg);
/* User application thread routine */
void *userApplicationPub(void *arg);
/* To create multi-threads */
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
char *applicationName, void *serverConfig);
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_FALSE;
}
/**
* **Nanosecond field handling**
*
* Nanosecond field in timespec is checked for overflowing and one second
* is added to seconds field and nanosecond field is set to zero
*/
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
/* Check if ns field is greater than '1 ns less than 1sec' */
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
/* Move to next second and remove it from ns field */
timeSpecValue->tv_sec += SECONDS_INCREMENT;
timeSpecValue->tv_nsec -= SECONDS;
}
}
/**
* **Custom callback handling**
*
* Custom callback thread handling overwrites the default timer based
* callback function with the custom (user-specified) callback interval. */
/* Add a callback for cyclic repetition */
static UA_StatusCode
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
/* Initialize arguments required for the thread to run */
threadArg *threadArguments = (threadArg *) UA_malloc(sizeof(threadArg));
/* Pass the value required for the threads */
threadArguments->server = server;
threadArguments->data = data;
threadArguments->callback = callback;
threadArguments->interval_ms = interval_ms;
threadArguments->callbackId = callbackId;
/* Create the publisher thread with the required priority and core affinity */
char threadNamePub[10] = "Publisher";
pubthreadID = threadCreation(pubPriority, pubSubCore, publisherETF, threadNamePub, threadArguments);
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
/* Callback interval need not be modified as it is thread based implementation.
* The thread uses nanosleep for calculating cycle time and modification in
* nanosleep value changes cycle time */
return UA_STATUSCODE_GOOD;
}
/* Remove the callback added for cyclic repetition */
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId){
if(callbackId && (pthread_join((pthread_t)callbackId, NULL) != 0))
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Pthread Join Failed thread: %lu\n", (long unsigned)callbackId);
}
/**
* **External data source handling**
*
* If the external data source is written over the information model, the
* externalDataWriteCallback will be triggered. The user has to take care and assure
* that the write leads not to synchronization issues and race conditions. */
static UA_StatusCode
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeid,
void *nodeContext, const UA_NumericRange *range){
//allow read without any preparation
return UA_STATUSCODE_GOOD;
}
/**
* **PubSub connection handling**
*
* Create a new ConnectionConfig. The addPubSubConnection function takes the
* config and creates a new connection. The Connection identifier is
* copied to the NodeId parameter.
*/
static void
addPubSubConnection(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlPub){
/* Details about the connection configuration and handling are located
* in the pubsub connection tutorial */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("Publisher Connection");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrl = *networkAddressUrlPub;
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = PUBLISHER_ID;
/* Connection options are given as Key/Value Pairs - Sockprio and Txtime */
UA_KeyValuePair connectionOptions[2];
connectionOptions[0].key = UA_QUALIFIEDNAME(0, "sockpriority");
UA_UInt32 sockPriority = (UA_UInt32)socketPriority;
UA_Variant_setScalar(&connectionOptions[0].value, &sockPriority, &UA_TYPES[UA_TYPES_UINT32]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, "enablesotxtime");
UA_Boolean enableTxTime = UA_TRUE;
UA_Variant_setScalar(&connectionOptions[1].value, &enableTxTime, &UA_TYPES[UA_TYPES_BOOLEAN]);
connectionConfig.connectionProperties.map = connectionOptions;
connectionConfig.connectionProperties.mapSize = 2;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
}
/**
* **PublishedDataSet handling**
*
* Details about the connection configuration and handling are located
* in the pubsub connection tutorial
*/
static void
addPublishedDataSet(UA_Server *server) {
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
}
/**
* **DataSetField handling**
*
* The DataSetField (DSF) is part of the PDS and describes exactly one
* published field.
*/
/* This example only uses two addDataSetField which uses the custom nodes of the XML file
* (pubDataModel.xml) */
static void
_addDataSetField(UA_Server *server) {
UA_NodeId dataSetFieldIdent;
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
pubCounterData = UA_UInt64_new();
*pubCounterData = 0;
pubDataValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pubDataValueRT->value, pubCounterData, &UA_TYPES[UA_TYPES_UINT64]);
pubDataValueRT->hasValue = UA_TRUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &pubDataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* If user need to change the nodeid of the custom nodes in the application then it must be
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
/* The nodeid of the Custom node PublisherCounterVariable is 2005 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
/* setup RT DataSetField config */
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2005);
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
UA_NodeId dataSetFieldIdent1;
UA_DataSetFieldConfig dsfConfig1;
memset(&dsfConfig1, 0, sizeof(UA_DataSetFieldConfig));
pressureData = UA_Double_new();
*pressureData = 17.07;
pressureValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
pressureValueRT->hasValue = UA_TRUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend1;
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend1.backend.external.value = &pressureValueRT;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* The nodeid of the Custom node Pressure is 2006 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
/* setup RT DataSetField config */
dsfConfig1.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig1.field.variable.publishParameters.publishedVariable = UA_NODEID_NUMERIC(nsIdx, 2006);
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig1, &dataSetFieldIdent1);
}
/**
* **WriterGroup handling**
*
* The WriterGroup (WG) is part of the connection and contains the primary
* configuration parameters for the message creation.
*/
static void
addWriterGroup(UA_Server *server) {
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = cycleTimeMsec;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.writerGroupId = WRITER_GROUP_ID;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
writerGroupConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
writerGroupConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE];
/* The configuration flags for the messages are encapsulated inside the
* message- and transport settings extension objects. These extension
* objects are defined by the standard. e.g.
* UadpWriterGroupMessageDataType */
UA_UadpWriterGroupMessageDataType *writerGroupMessage = UA_UadpWriterGroupMessageDataType_new();
/* Change message settings of writerGroup to send PublisherId,
* WriterGroupId in GroupHeader and DataSetWriterId in PayloadHeader
* of NetworkMessage */
writerGroupMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
writerGroupConfig.messageSettings.content.decoded.data = writerGroupMessage;
UA_Server_addWriterGroup(server, connectionIdent, &writerGroupConfig, &writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
UA_UadpWriterGroupMessageDataType_delete(writerGroupMessage);
}
/**
* **DataSetWriter handling**
*
* A DataSetWriter (DSW) is the glue between the WG and the PDS. The DSW is
* linked to exactly one PDS and contains additional information for the
* message generation.
*/
static void
addDataSetWriter(UA_Server *server) {
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
}
/**
* **Published data handling**
*
* The published data is updated in the array using this function
*/
static void
updateMeasurementsPublisher(struct timespec start_time,
UA_UInt64 counterValue, UA_Double pressureValue) {
publishTimestamp[measurementsPublisher] = start_time;
publishCounterValue[measurementsPublisher] = counterValue;
pressureValues[measurementsPublisher] = pressureValue;
measurementsPublisher++;
}
/**
* **Publisher thread routine**
*
* The Publisher thread sleeps for 60% of the cycletime (250us) and prepares the tranmission packet within 40% of
* cycletime. The data published by this thread in one cycle is subscribed by the subscriber thread of pubsub_nodeset_rt_subscriber in the
* next cycle (two cycle timing model).
*
* The publisherETF function is the routine used by the publisher thread.
*/
void *publisherETF(void *arg) {
struct timespec nextnanosleeptime;
UA_ServerCallback pubCallback;
UA_Server* server;
UA_WriterGroup* currentWriterGroup;
UA_UInt64 interval_ns;
UA_UInt64 transmission_time;
/* Initialise value for nextnanosleeptime timespec */
nextnanosleeptime.tv_nsec = 0;
threadArg *threadArgumentsPublisher = (threadArg *)arg;
server = threadArgumentsPublisher->server;
pubCallback = threadArgumentsPublisher->callback;
currentWriterGroup = (UA_WriterGroup *)threadArgumentsPublisher->data;
interval_ns = (UA_UInt64)(threadArgumentsPublisher->interval_ms * MILLI_SECONDS);
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptime);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptime.tv_sec += SECONDS_SLEEP;
nextnanosleeptime.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptime);
/* Define Ethernet ETF transport settings */
UA_EthernetWriterGroupTransportDataType ethernettransportSettings;
memset(&ethernettransportSettings, 0, sizeof(UA_EthernetWriterGroupTransportDataType));
ethernettransportSettings.transmission_time = 0;
/* Encapsulate ETF config in transportSettings */
UA_ExtensionObject transportSettings;
memset(&transportSettings, 0, sizeof(UA_ExtensionObject));
/* TODO: transportSettings encoding and type to be defined */
transportSettings.content.decoded.data = &ethernettransportSettings;
currentWriterGroup->config.transportSettings = transportSettings;
UA_UInt64 roundOffCycleTime = (UA_UInt64)((cycleTimeMsec * MILLI_SECONDS) - (cycleTimeMsec * pubWakeupPercentage * MILLI_SECONDS));
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptime, NULL);
transmission_time = ((UA_UInt64)nextnanosleeptime.tv_sec * SECONDS + (UA_UInt64)nextnanosleeptime.tv_nsec) + roundOffCycleTime + QBV_OFFSET;
ethernettransportSettings.transmission_time = transmission_time;
pubCallback(server, currentWriterGroup);
nextnanosleeptime.tv_nsec += (__syscall_slong_t)interval_ns;
nanoSecondFieldConversion(&nextnanosleeptime);
}
UA_free(threadArgumentsPublisher);
return (void*)NULL;
}
/**
* **UserApplication thread routine**
*
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
* This thread is used to increment the counterdata that will be published by the Publisher thread and also writes the published data in a csv.
*/
void *userApplicationPub(void *arg) {
struct timespec nextnanosleeptimeUserApplication;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
*pubCounterData = 0;
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
*pubCounterData = *pubCounterData + 1;
*pressureData = *pressureData + 1;
clock_gettime(CLOCKID, &dataModificationTime);
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE))
updateMeasurementsPublisher(dataModificationTime, *pubCounterData, *pressureData);
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
}
return (void*)NULL;
}
/**
* **Thread creation**
*
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
* created thread.
*/
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), char *applicationName, void *serverConfig){
/* Core affinity set */
cpu_set_t cpuset;
pthread_t threadID;
struct sched_param schedParam;
UA_Int32 returnValue = 0;
UA_Int32 errorSetAffinity = 0;
/* Return the ID for thread */
threadID = pthread_self();
schedParam.sched_priority = threadPriority;
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
exit(1);
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
"\npthread_setschedparam:%s Thread priority is %d \n", \
applicationName, schedParam.sched_priority);
CPU_ZERO(&cpuset);
CPU_SET((size_t)coreAffinity, &cpuset);
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
if (errorSetAffinity) {
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
exit(1);
}
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
}
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
}
return threadID;
}
/**
* **Usage function**
*
* The usage function gives the list of options that can be configured in the application.
*
* ./bin/examples/pubsub_nodeset_rt_publisher -h gives the list of options for running the application.
*/
static void usage(char *appname)
{
fprintf(stderr,
"\n"
"usage: %s [options]\n"
"\n"
" -i [name] use network interface 'name'\n"
" -C [num] cycle time in milli seconds (default %lf)\n"
" -p Do you need to print the data in console output\n"
" -s [num] set SO_PRIORITY to 'num' (default %d)\n"
" -P [num] Publisher priority value (default %d)\n"
" -U [num] User application priority value (default %d)\n"
" -c [num] run on CPU for publisher'num'(default %d)\n"
" -u [num] run on CPU for userApplication'num'(default %d)\n"
" -t do not use SO_TXTIME\n"
" -m [mac_addr] ToDO:dst MAC address\n"
" -h prints this message and exits\n"
"\n",
appname, DEFAULT_CYCLE_TIME, DEFAULT_SOCKET_PRIORITY, DEFAULT_PUB_SCHED_PRIORITY, \
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
}
/**
* **Main Server code**
*
* The main function contains publisher threads running
*/
int main(int argc, char **argv) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Int32 returnValue = 0;
char *interface = NULL;
char *progname;
UA_Int32 argInputs = -1;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
pthread_t userThreadID;
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
/* Files namespace_example_publisher_generated.h and namespace_example_publisher_generated.c are created from
* pubDataModel.xml in the /src_generated directory by CMake */
/* Loading the user created variables into the information model from the generated .c and .h files */
if(namespace_example_publisher_generated(server) != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
"Check previous output for any error.");
}
else
{
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
}
UA_NetworkAddressUrlDataType networkAddressUrlPub;
/* Process the command line arguments */
/* For more information run ./bin/examples/pubsub_nodeset_rt_publisher -h */
progname = strrchr(argv[0], '/');
progname = progname ? 1 + progname : argv[0];
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
switch (argInputs) {
case 'i':
interface = optarg;
break;
case 'C':
cycleTimeMsec = atof(optarg);
break;
case 'f':
fileName = optarg;
fileWrite = UA_TRUE;
fpPublisher = fopen(fileName, "w");
break;
case 'p':
consolePrint = UA_TRUE;
break;
case 's':
socketPriority = atoi(optarg);
break;
case 'P':
pubPriority = atoi(optarg);
break;
case 'U':
userAppPriority = atoi(optarg);
break;
case 'c':
pubSubCore = atoi(optarg);
break;
case 'u':
userAppCore = atoi(optarg);
break;
case 't':
useSoTxtime = UA_FALSE;
break;
case 'm':
/*ToDo:Need to handle for mac address*/
break;
case 'h':
usage(progname);
return -1;
case '?':
usage(progname);
return -1;
}
}
if (cycleTimeMsec < 0.125) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
usage(progname);
return -1;
}
if (!interface) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
usage(progname);
UA_Server_delete(server);
return 0;
}
networkAddressUrlPub.networkInterface = UA_STRING(interface);
networkAddressUrlPub.url = UA_STRING(PUBLISHING_MAC_ADDRESS);
addPubSubConnection(server, &networkAddressUrlPub);
addPublishedDataSet(server);
_addDataSetField(server);
addWriterGroup(server);
addDataSetWriter(server);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
serverConfigStruct *serverConfig;
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
serverConfig->ServerRun = server;
char threadNameUserApplication[22] = "UserApplicationPub";
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationPub, threadNameUserApplication, serverConfig);
retval |= UA_Server_run(server, &running);
returnValue = pthread_join(pubthreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for publisher thread:%d\n", returnValue);
}
returnValue = pthread_join(userThreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue);
}
if (fileWrite == UA_TRUE) {
/* Write the published data in a file */
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
fprintf(fpPublisher, "%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);
}
fclose(fpPublisher);
}
if (consolePrint == UA_TRUE) {
size_t pubLoopVariable = 0;
for (pubLoopVariable = 0; pubLoopVariable < measurementsPublisher;
pubLoopVariable++) {
printf("%lu,%ld.%09ld,%lf\n",
(long unsigned)publishCounterValue[pubLoopVariable],
publishTimestamp[pubLoopVariable].tv_sec,
publishTimestamp[pubLoopVariable].tv_nsec,
pressureValues[pubLoopVariable]);
}
}
UA_Server_delete(server);
UA_free(serverConfig);
UA_free(pubCounterData);
/* Free external data source */
UA_free(pubDataValueRT);
UA_free(pressureData);
/* Free external data source */
UA_free(pressureValueRT);
return (int)retval;
}

View File

@@ -0,0 +1,656 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
/**
* .. _pubsub-nodeset-subscriber-tutorial:
*
* Subscriber Realtime example using custom nodes
* ----------------------------------------------
*
* The purpose of this example file is to use the custom nodes of the XML
* file(subDataModel.xml) for subscriber. This Subscriber example uses the two
* custom nodes (SubscriberCounterVariable and Pressure) created using the XML
* file(subDataModel.xml) for subscribing the packet. The subDataModel.csv will
* contain the nodeids of custom nodes(object and variables) and the nodeids of
* the custom nodes are harcoded inside the addSubscribedVariables API
*
* This example uses two threads namely the Subscriber and UserApplication. The
* Subscriber thread is used to subscribe to data at every cycle. The
* UserApplication thread serves the functionality of the Control loop, which
* reads the Information Model of the Subscriber and the new counterdata will be
* written in the csv along with received timestamp.
*
* Run steps of the Subscriber application as mentioned below:
*
* ``./bin/examples/pubsub_nodeset_rt_subscriber -i <iface>``
*
* For more information run ``./bin/examples/pubsub_nodeset_rt_subscriber -h``. */
#define _GNU_SOURCE
/* For thread operations */
#include <pthread.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/types_generated.h>
#include "ua_pubsub.h"
#include "open62541/namespace_example_subscriber_generated.h"
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig;
/* to find load of each thread
* ps -L -o pid,pri,%cpu -C pubsub_nodeset_rt_subscriber*/
/* Configurable Parameters */
/* Cycle time in milliseconds */
#define DEFAULT_CYCLE_TIME 0.25
/* Qbv offset */
#define QBV_OFFSET 25 * 1000
#define DEFAULT_SOCKET_PRIORITY 3
#define PUBLISHER_ID_SUB 2234
#define WRITER_GROUP_ID_SUB 101
#define DATA_SET_WRITER_ID_SUB 62541
#define SUBSCRIBING_MAC_ADDRESS "opc.eth://01-00-5E-7F-00-01:8.3"
#define PORT_NUMBER 62541
/* Non-Configurable Parameters */
/* Milli sec and sec conversion to nano sec */
#define MILLI_SECONDS 1000 * 1000
#define SECONDS 1000 * 1000 * 1000
#define SECONDS_SLEEP 5
#define DEFAULT_SUB_SCHED_PRIORITY 81
#define MAX_MEASUREMENTS 30000000
#define DEFAULT_PUBSUB_CORE 2
#define DEFAULT_USER_APP_CORE 3
#define SECONDS_INCREMENT 1
#define CLOCKID CLOCK_TAI
#define ETH_TRANSPORT_PROFILE "http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp"
#define DEFAULT_USERAPPLICATION_SCHED_PRIORITY 75
/* Below mentioned parameters can be provided as input using command line arguments
* If user did not provide the below mentioned parameters as input through command line
* argument then default value will be used */
static UA_Double cycleTimeMsec = DEFAULT_CYCLE_TIME;
static UA_Boolean consolePrint = UA_FALSE;
static UA_Int32 subPriority = DEFAULT_SUB_SCHED_PRIORITY;
static UA_Int32 userAppPriority = DEFAULT_USERAPPLICATION_SCHED_PRIORITY;
static UA_Int32 pubSubCore = DEFAULT_PUBSUB_CORE;
static UA_Int32 userAppCore = DEFAULT_USER_APP_CORE;
/* User application Pub will wakeup at the 30% of cycle time and handles the */
/* user data write in Information model */
/* After 60% is left for publisher */
static UA_Double userAppWakeupPercentage = 0.3;
/* Subscriber will wake up at the start of cycle time and then receives the packet */
static UA_Double subWakeupPercentage = 0;
static UA_Boolean fileWrite = UA_FALSE;
/* Set server running as true */
UA_Boolean running = UA_TRUE;
UA_UInt16 nsIdx = 0;
UA_UInt64 *subCounterData;
UA_DataValue *subDataValueRT;
UA_Double *pressureData;
UA_DataValue *pressureValueRT;
/* File to store the data and timestamps for different traffic */
FILE *fpSubscriber;
char *fileName = "subscriber_T4.csv";
/* Array to store subscribed counter data */
UA_UInt64 subscribeCounterValue[MAX_MEASUREMENTS];
UA_Double pressureValues[MAX_MEASUREMENTS];
size_t measurementsSubscriber = 0;
/* Array to store timestamp */
struct timespec subscribeTimestamp[MAX_MEASUREMENTS];
/* Thread for subscriber */
pthread_t subthreadID;
/* Variable for PubSub connection creation */
UA_NodeId connectionIdentSubscriber;
struct timespec dataReceiveTime;
/* Thread for user application*/
pthread_t userApplicationThreadID;
typedef struct {
UA_Server* ServerRun;
} serverConfigStruct;
/* Structure to define thread parameters */
typedef struct {
UA_Server* server;
void* data;
UA_ServerCallback callback;
UA_Duration interval_ms;
UA_UInt64* callbackId;
} threadArg;
/* Subscriber thread routine */
void *subscriber(void *arg);
/* User application thread routine */
void *userApplicationSub(void *arg);
/* To create multi-threads */
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *),
char *applicationName, void *serverConfig);
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_FALSE;
}
/**
* **Nanosecond field handling**
*
* Nanosecond field in timespec is checked for overflowing and one second
* is added to seconds field and nanosecond field is set to zero
*/
static void nanoSecondFieldConversion(struct timespec *timeSpecValue) {
/* Check if ns field is greater than '1 ns less than 1sec' */
while (timeSpecValue->tv_nsec > (SECONDS -1)) {
/* Move to next second and remove it from ns field */
timeSpecValue->tv_sec += SECONDS_INCREMENT;
timeSpecValue->tv_nsec -= SECONDS;
}
}
/**
* **External data source handling**
*
* If the external data source is written over the information model, the
* externalDataWriteCallback will be triggered. The user has to take care and assure
* that the write leads not to synchronization issues and race conditions. */
static UA_StatusCode
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeid,
void *nodeContext, const UA_NumericRange *range){
//allow read without any preparation
return UA_STATUSCODE_GOOD;
}
/**
* **Subscriber Connection Creation**
*
* Create Subscriber connection for the Subscriber thread
*/
static void
addPubSubConnectionSubscriber(UA_Server *server, UA_NetworkAddressUrlDataType *networkAddressUrlSubscriber){
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Details about the connection configuration and handling are located
* in the pubsub connection tutorial */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("Subscriber Connection");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrlsubscribe = *networkAddressUrlSubscriber;
connectionConfig.transportProfileUri = UA_STRING(ETH_TRANSPORT_PROFILE);
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrlsubscribe, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
retval |= UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentSubscriber);
if (retval == UA_STATUSCODE_GOOD)
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,"The PubSub Connection was created successfully!");
}
/**
* **ReaderGroup**
*
* ReaderGroup is used to group a list of DataSetReaders. All ReaderGroups are
* created within a PubSubConnection and automatically deleted if the connection
* is removed. */
/* Add ReaderGroup to the created connection */
static void
addReaderGroup(UA_Server *server) {
if (server == NULL) {
return;
}
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
UA_Server_addReaderGroup(server, connectionIdentSubscriber, &readerGroupConfig,
&readerGroupIdentifier);
}
/**
* **SubscribedDataSet**
*
* Set SubscribedDataSet type to TargetVariables data type
* Add SubscriberCounter variable to the DataSetReader */
static void addSubscribedVariables (UA_Server *server) {
if (server == NULL) {
return;
}
UA_FieldTargetVariable *targetVars = (UA_FieldTargetVariable*)
UA_calloc(2, sizeof(UA_FieldTargetVariable));
subCounterData = UA_UInt64_new();
*subCounterData = 0;
subDataValueRT = UA_DataValue_new();
UA_Variant_setScalar(&subDataValueRT->value, subCounterData, &UA_TYPES[UA_TYPES_UINT64]);
subDataValueRT->hasValue = UA_TRUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &subDataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* If user need to change the nodeid of the custom nodes in the application then it must be
* changed inside the xml and .csv file inside examples\pubsub_realtime\nodeset\*/
/* The nodeid of the Custom node SubscriberCounterVariable is 2005 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2005), valueBackend);
UA_FieldTargetDataType_init(&targetVars[0].targetVariable);
targetVars[0].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[0].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2005);
pressureData = UA_Double_new();
*pressureData = 0;
pressureValueRT = UA_DataValue_new();
UA_Variant_setScalar(&pressureValueRT->value, pressureData, &UA_TYPES[UA_TYPES_DOUBLE]);
pressureValueRT->hasValue = UA_TRUE;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend1;
valueBackend1.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend1.backend.external.value = &pressureValueRT;
valueBackend1.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend1.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
/* The nodeid of the Custom node Pressure is 2006 which is used below */
UA_Server_setVariableNode_valueBackend(server, UA_NODEID_NUMERIC(nsIdx, 2006), valueBackend1);
UA_FieldTargetDataType_init(&targetVars[1].targetVariable);
targetVars[1].targetVariable.attributeId = UA_ATTRIBUTEID_VALUE;
targetVars[1].targetVariable.targetNodeId = UA_NODEID_NUMERIC(nsIdx, 2006);
/* Set the subscribed data to TargetVariable type */
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables = targetVars;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize = 2;
}
/**
* **DataSetReader**
*
* DataSetReader can receive NetworkMessages with the DataSetMessage
* of interest sent by the Publisher. DataSetReader provides
* the configuration necessary to receive and process DataSetMessages
* on the Subscriber side. DataSetReader must be linked with a
* SubscribedDataSet and be contained within a ReaderGroup. */
/* Add DataSetReader to the ReaderGroup */
static void
addDataSetReader(UA_Server *server) {
if (server == NULL) {
return;
}
memset (&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
readerConfig.name = UA_STRING("DataSet Reader 1");
UA_UInt16 publisherIdentifier = PUBLISHER_ID_SUB;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = WRITER_GROUP_ID_SUB;
readerConfig.dataSetWriterId = DATA_SET_WRITER_ID_SUB;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
(UA_UadpNetworkMessageContentMask)UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
/* Setting up Meta data configuration in DataSetReader */
UA_DataSetMetaDataType *pMetaData = &readerConfig.dataSetMetaData;
/* FilltestMetadata function in subscriber implementation */
UA_DataSetMetaDataType_init(pMetaData);
pMetaData->name = UA_STRING ("DataSet Test");
/* Static definition of number of fields size to 1 to create one
targetVariable */
pMetaData->fieldsSize = 2;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
/* Unsigned Integer DataType */
UA_FieldMetaData_init (&pMetaData->fields[0]);
UA_NodeId_copy (&UA_TYPES[UA_TYPES_UINT64].typeId,
&pMetaData->fields[0].dataType);
pMetaData->fields[0].builtInType = UA_NS0ID_UINT64;
pMetaData->fields[0].valueRank = -1; /* scalar */
/* Double DataType */
UA_FieldMetaData_init (&pMetaData->fields[1]);
UA_NodeId_copy (&UA_TYPES[UA_TYPES_DOUBLE].typeId,
&pMetaData->fields[1].dataType);
pMetaData->fields[1].builtInType = UA_NS0ID_DOUBLE;
pMetaData->fields[1].valueRank = -1; /* scalar */
/* Setup Target Variables in DSR config */
addSubscribedVariables(server);
/* Setting up Meta data configuration in DataSetReader */
UA_Server_addDataSetReader(server, readerGroupIdentifier, &readerConfig,
&readerIdentifier);
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
}
/**
* **Subscribed data handling**
*
* The subscribed data is updated in the array using this function Subscribed data handling**
*/
static void
updateMeasurementsSubscriber(struct timespec receive_time, UA_UInt64 counterValue, UA_Double pressureValue) {
subscribeTimestamp[measurementsSubscriber] = receive_time;
subscribeCounterValue[measurementsSubscriber] = counterValue;
pressureValues[measurementsSubscriber] = pressureValue;
measurementsSubscriber++;
}
/**
* **Subscriber thread routine**
*
* Subscriber thread will wakeup during the start of cycle at 250us interval and check if the packets are received.
* The subscriber function is the routine used by the subscriber thread.
*/
void *subscriber(void *arg) {
UA_Server* server;
UA_ReaderGroup* currentReaderGroup;
UA_ServerCallback subCallback;
struct timespec nextnanosleeptimeSub;
threadArg *threadArgumentsSubscriber = (threadArg *)arg;
server = threadArgumentsSubscriber->server;
subCallback = threadArgumentsSubscriber->callback;
currentReaderGroup = (UA_ReaderGroup *)threadArgumentsSubscriber->data;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeSub);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeSub.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeSub.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * subWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeSub);
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeSub, NULL);
/* Read subscribed data from the SubscriberCounter variable */
subCallback(server, currentReaderGroup);
nextnanosleeptimeSub.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeSub);
}
UA_free(threadArgumentsSubscriber);
return (void*)NULL;
}
/**
* **UserApplication thread routine**
*
* The userapplication thread will wakeup at 30% of cycle time and handles the userdata in the Information Model.
* This thread is used to write the counterdata that is subscribed by the Subscriber thread in a csv.
*/
void *userApplicationSub(void *arg) {
struct timespec nextnanosleeptimeUserApplication;
/* Get current time and compute the next nanosleeptime */
clock_gettime(CLOCKID, &nextnanosleeptimeUserApplication);
/* Variable to nano Sleep until 1ms before a 1 second boundary */
nextnanosleeptimeUserApplication.tv_sec += SECONDS_SLEEP;
nextnanosleeptimeUserApplication.tv_nsec = (__syscall_slong_t)(cycleTimeMsec * userAppWakeupPercentage * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
while (running) {
clock_nanosleep(CLOCKID, TIMER_ABSTIME, &nextnanosleeptimeUserApplication, NULL);
clock_gettime(CLOCKID, &dataReceiveTime);
if ((fileWrite == UA_TRUE) || (consolePrint == UA_TRUE)) {
if (*subCounterData > 0)
updateMeasurementsSubscriber(dataReceiveTime, *subCounterData, *pressureData);
}
nextnanosleeptimeUserApplication.tv_nsec += (__syscall_slong_t)(cycleTimeMsec * MILLI_SECONDS);
nanoSecondFieldConversion(&nextnanosleeptimeUserApplication);
}
return (void*)NULL;
}
/**
* **Thread creation**
*
* The threadcreation functionality creates thread with given threadpriority, coreaffinity. The function returns the threadID of the newly
* created thread.
*/
static pthread_t threadCreation(UA_Int32 threadPriority, UA_Int32 coreAffinity, void *(*thread) (void *), \
char *applicationName, void *serverConfig){
/* Core affinity set */
cpu_set_t cpuset;
pthread_t threadID;
struct sched_param schedParam;
UA_Int32 returnValue = 0;
UA_Int32 errorSetAffinity = 0;
/* Return the ID for thread */
threadID = pthread_self();
schedParam.sched_priority = threadPriority;
returnValue = pthread_setschedparam(threadID, SCHED_FIFO, &schedParam);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"pthread_setschedparam: failed\n");
exit(1);
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,\
"\npthread_setschedparam:%s Thread priority is %d \n", \
applicationName, schedParam.sched_priority);
CPU_ZERO(&cpuset);
CPU_SET((size_t)coreAffinity, &cpuset);
errorSetAffinity = pthread_setaffinity_np(threadID, sizeof(cpu_set_t), &cpuset);
if (errorSetAffinity) {
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(errorSetAffinity));
exit(1);
}
returnValue = pthread_create(&threadID, NULL, thread, serverConfig);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,":%s Cannot create thread\n", applicationName);
}
if (CPU_ISSET((size_t)coreAffinity, &cpuset)) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%s CPU CORE: %d\n", applicationName, coreAffinity);
}
return threadID;
}
/**
* **Usage function**
*
* The usage function gives the list of options that can be configured in the application.
*
* ./bin/examples/pubsub_nodeset_rt_subscriber -h gives the list of options for running the application.
*/
static void usage(char *appname)
{
fprintf(stderr,
"\n"
"usage: %s [options]\n"
"\n"
" -i [name] use network interface 'name'\n"
" -C [num] cycle time in milli seconds (default %lf)\n"
" -p Do you need to print the data in console output\n"
" -P [num] Publisher priority value (default %d)\n"
" -U [num] User application priority value (default %d)\n"
" -c [num] run on CPU for publisher'num'(default %d)\n"
" -u [num] run on CPU for userApplication'num'(default %d)\n"
" -m [mac_addr] ToDO:dst MAC address\n"
" -h prints this message and exits\n"
"\n",
appname, DEFAULT_CYCLE_TIME, DEFAULT_SUB_SCHED_PRIORITY, \
DEFAULT_USERAPPLICATION_SCHED_PRIORITY, DEFAULT_PUBSUB_CORE, DEFAULT_USER_APP_CORE);
}
/**
* **Main Server code**
*
* The main function contains subscriber threads running
*/
int main(int argc, char **argv) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Int32 returnValue = 0;
char *interface = NULL;
char *progname;
UA_Int32 argInputs = -1;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
pthread_t userThreadID;
UA_ServerConfig_setMinimal(config, PORT_NUMBER, NULL);
/* Files namespace_example_subscriber_generated.h and namespace_example_subscriber_generated.c are created from
* subDataModel.xml in the /src_generated directory by CMake */
/* Loading the user created variables into the information model from the generated .c and .h files */
if(namespace_example_subscriber_generated(server) != UA_STATUSCODE_GOOD) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Could not add the example nodeset. "
"Check previous output for any error.");
}
else
{
nsIdx = UA_Server_addNamespace(server, "http://yourorganisation.org/test/");
}
UA_NetworkAddressUrlDataType networkAddressUrlSub;
/* For more information run ./bin/examples/pubsub_nodeset_rt_subscriber -h */
/* Process the command line arguments */
progname = strrchr(argv[0], '/');
progname = progname ? 1 + progname : argv[0];
while (EOF != (argInputs = getopt(argc, argv, "i:C:f:ps:P:U:c:u:tm:h:"))) {
switch (argInputs) {
case 'i':
interface = optarg;
break;
case 'C':
cycleTimeMsec = atof(optarg);
break;
case 'f':
fileName = optarg;
fileWrite = UA_TRUE;
fpSubscriber = fopen(fileName, "w");
break;
case 'p':
consolePrint = UA_TRUE;
break;
case 'P':
subPriority = atoi(optarg);
break;
case 'U':
userAppPriority = atoi(optarg);
break;
case 'c':
pubSubCore = atoi(optarg);
break;
case 'u':
userAppCore = atoi(optarg);
break;
case 'm':
/*ToDo:Need to handle for mac address*/
break;
case 'h':
usage(progname);
return -1;
case '?':
usage(progname);
return -1;
}
}
if (cycleTimeMsec < 0.125) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "%f Bad cycle time", cycleTimeMsec);
usage(progname);
return -1;
}
if (!interface) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Need a network interface to run");
usage(progname);
UA_Server_delete(server);
return 0;
}
networkAddressUrlSub.networkInterface = UA_STRING(interface);
networkAddressUrlSub.url = UA_STRING(SUBSCRIBING_MAC_ADDRESS);
addPubSubConnectionSubscriber(server, &networkAddressUrlSub);
addReaderGroup(server);
addDataSetReader(server);
UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier);
UA_Server_setReaderGroupOperational(server, readerGroupIdentifier);
serverConfigStruct *serverConfig;
serverConfig = (serverConfigStruct*)UA_malloc(sizeof(serverConfigStruct));
serverConfig->ServerRun = server;
char threadNameUserApplication[22] = "UserApplicationSub";
userThreadID = threadCreation(userAppPriority, userAppCore, userApplicationSub, threadNameUserApplication, serverConfig);
retval |= UA_Server_run(server, &running);
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
returnValue = pthread_join(subthreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for subscriber thread:%d\n", returnValue);
}
returnValue = pthread_join(userThreadID, NULL);
if (returnValue != 0) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"\nPthread Join Failed for User thread:%d\n", returnValue);
}
if (fileWrite == UA_TRUE) {
/* Write the subscribed data in the file */
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);
}
fclose(fpSubscriber);
}
if (consolePrint == UA_TRUE) {
size_t subLoopVariable = 0;
for (subLoopVariable = 0; subLoopVariable < measurementsSubscriber;
subLoopVariable++) {
fprintf(fpSubscriber, "%lu,%ld.%09ld,%lf\n",
(long unsigned)subscribeCounterValue[subLoopVariable],
subscribeTimestamp[subLoopVariable].tv_sec,
subscribeTimestamp[subLoopVariable].tv_nsec,
pressureValues[subLoopVariable]);
}
}
UA_Server_delete(server);
UA_free(serverConfig);
UA_free(subCounterData);
/* Free external data source */
UA_free(subDataValueRT);
UA_free(pressureData);
/* Free external data source */
UA_free(pressureValueRT);
return (int)retval;
}

View File

@@ -0,0 +1,4 @@
SubscriberInfo,2001,ObjectType
Subscriber,2004,Object
PublisherCounterValue,2005,Variable
Pressure,2006,Variable
1 SubscriberInfo 2001 ObjectType
2 Subscriber 2004 Object
3 PublisherCounterValue 2005 Variable
4 Pressure 2006 Variable

View File

@@ -0,0 +1,80 @@
<?xml version='1.0' encoding='utf-8'?>
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<NamespaceUris>
<Uri>http://yourorganisation.org/test/</Uri>
</NamespaceUris>
<Aliases>
<Alias Alias="HasModellingRule">i=37</Alias>
<Alias Alias="UInt64">i=9</Alias>
<Alias Alias="Double">i=11</Alias>
<Alias Alias="Organizes">i=35</Alias>
<Alias Alias="HasTypeDefinition">i=40</Alias>
<Alias Alias="HasSubtype">i=45</Alias>
<Alias Alias="HasComponent">i=47</Alias>
</Aliases>
<UAObjectType BrowseName="1:SubscriberInfo" NodeId="ns=1;i=2001">
<DisplayName>SubscriberInfo</DisplayName>
<Description>SubscriberInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2002</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2003</Reference>
</References>
</UAObjectType>
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2002" ParentNodeId="ns=1;i=2001">
<DisplayName>SubscriberCounterVariable</DisplayName>
<Description>SubscriberCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2003" ParentNodeId="ns=1;i=2001">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2001</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
<Reference ReferenceType="HasModellingRule">i=78</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
<UAObject BrowseName="1:Subscriber" NodeId="ns=1;i=2004" ParentNodeId="i=85">
<DisplayName>Subscriber</DisplayName>
<Description>SubscriberInfo</Description>
<References>
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference>
<Reference ReferenceType="HasTypeDefinition">ns=1;i=2001</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2005</Reference>
<Reference ReferenceType="HasComponent">ns=1;i=2006</Reference>
</References>
</UAObject>
<UAVariable BrowseName="1:SubscriberCounterVariable" DataType="UInt64" NodeId="ns=1;i=2005" ParentNodeId="ns=1;i=2004">
<DisplayName>SubscriberCounterVariable</DisplayName>
<Description>SubscriberCounterVariable</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:UInt64>0</uax:UInt64>
</Value>
</UAVariable>
<UAVariable BrowseName="1:Pressure" DataType="Double" NodeId="ns=1;i=2006" ParentNodeId="ns=1;i=2004">
<DisplayName>Pressure</DisplayName>
<Description>Pressure</Description>
<References>
<Reference IsForward="false" ReferenceType="HasComponent">ns=1;i=2004</Reference>
<Reference ReferenceType="HasTypeDefinition">i=63</Reference>
</References>
<Value>
<uax:Double>0.0</uax:Double>
</Value>
</UAVariable>
</UANodeSet>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,380 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information.
*
* Copyright 2018-2019 (c) Kalycito Infotech
* Copyright 2019 (c) Fraunhofer IOSB (Author: Andreas Ebner)
* Copyright 2019 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
* Copyright (c) 2020 Wind River Systems, Inc.
*/
#if __STDC_VERSION__ >= 199901L
#define _XOPEN_SOURCE 600
#else
#define _XOPEN_SOURCE 500
#endif /* __STDC_VERSION__ */
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <open62541/server.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server_pubsub.h>
#define ETH_PUBLISH_ADDRESS "opc.eth://0a-00-27-00-00-08"
#define ETH_INTERFACE "enp0s8"
#define MAX_MEASUREMENTS 10000
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
#define CLOCKID CLOCK_MONOTONIC_RAW
#define SIG SIGUSR1
#define PUB_INTERVAL 0.25 /* Publish interval in milliseconds */
#define DATA_SET_WRITER_ID 62541
#define MEASUREMENT_OUTPUT "publisher_measurement.csv"
/* The RT level of the publisher */
/* possible options: PUBSUB_CONFIG_FASTPATH_NONE, PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS, PUBSUB_CONFIG_FASTPATH_STATIC_VALUES */
#define PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS
UA_NodeId counterNodePublisher = {1, UA_NODEIDTYPE_NUMERIC, {1234}};
UA_Int64 pubIntervalNs;
UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
UA_Server *pubServer;
UA_Boolean running = true;
void *pubData;
timer_t pubEventTimer;
struct sigevent pubEvent;
struct sigaction signalAction;
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
UA_DataValue *staticValueSource = NULL;
#endif
/* Arrays to store measurement data */
UA_Int32 currentPublishCycleTime[MAX_MEASUREMENTS+1];
struct timespec calculatedCycleStartTime[MAX_MEASUREMENTS+1];
struct timespec cycleStartDelay[MAX_MEASUREMENTS+1];
struct timespec cycleDuration[MAX_MEASUREMENTS+1];
size_t publisherMeasurementsCounter = 0;
/* The value to published */
static UA_UInt64 publishValue = 62541;
static UA_StatusCode
readPublishValue(UA_Server *server,
const UA_NodeId *sessionId, void *sessionContext,
const UA_NodeId *nodeId, void *nodeContext,
UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
UA_DataValue *dataValue) {
UA_Variant_setScalarCopy(&dataValue->value, &publishValue,
&UA_TYPES[UA_TYPES_UINT64]);
dataValue->hasValue = true;
return UA_STATUSCODE_GOOD;
}
static void
timespec_diff(struct timespec *start, struct timespec *stop,
struct timespec *result) {
if((stop->tv_nsec - start->tv_nsec) < 0) {
result->tv_sec = stop->tv_sec - start->tv_sec - 1;
result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
} else {
result->tv_sec = stop->tv_sec - start->tv_sec;
result->tv_nsec = stop->tv_nsec - start->tv_nsec;
}
}
/* Used to adjust the nanosecond > 1s field value */
static void
nanoSecondFieldConversion(struct timespec *timeSpecValue) {
while(timeSpecValue->tv_nsec > (SECONDS_AS_NANO_SECONDS - 1)) {
timeSpecValue->tv_sec += 1;
timeSpecValue->tv_nsec -= SECONDS_AS_NANO_SECONDS;
}
}
/* Signal handler */
static void
publishInterrupt(int sig, siginfo_t* si, void* uc) {
if(si->si_value.sival_ptr != &pubEventTimer) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "stray signal");
return;
}
/* Execute the publish callback in the interrupt */
struct timespec begin, end;
clock_gettime(CLOCKID, &begin);
pubCallback(pubServer, pubData);
clock_gettime(CLOCKID, &end);
if(publisherMeasurementsCounter >= MAX_MEASUREMENTS)
return;
/* Save current configured publish interval */
currentPublishCycleTime[publisherMeasurementsCounter] = (UA_Int32)pubIntervalNs;
/* Save the difference to the calculated time */
timespec_diff(&calculatedCycleStartTime[publisherMeasurementsCounter],
&begin, &cycleStartDelay[publisherMeasurementsCounter]);
/* Save the duration of the publish callback */
timespec_diff(&begin, &end, &cycleDuration[publisherMeasurementsCounter]);
publisherMeasurementsCounter++;
/* Save the calculated starting time for the next cycle */
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec =
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_nsec + pubIntervalNs;
calculatedCycleStartTime[publisherMeasurementsCounter].tv_sec =
calculatedCycleStartTime[publisherMeasurementsCounter - 1].tv_sec;
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
/* Write the pubsub measurement data */
if(publisherMeasurementsCounter == MAX_MEASUREMENTS) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Logging the measurements to %s", MEASUREMENT_OUTPUT);
FILE *fpPublisher = fopen(MEASUREMENT_OUTPUT, "w");
for(UA_UInt32 i = 0; i < publisherMeasurementsCounter; i++) {
fprintf(fpPublisher, "%u, %u, %ld.%09ld, %ld.%09ld, %ld.%09ld\n",
i,
currentPublishCycleTime[i],
calculatedCycleStartTime[i].tv_sec,
calculatedCycleStartTime[i].tv_nsec,
cycleStartDelay[i].tv_sec,
cycleStartDelay[i].tv_nsec,
cycleDuration[i].tv_sec,
cycleDuration[i].tv_nsec);
}
fclose(fpPublisher);
}
}
/* The following three methods are originally defined in
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
* use system interrupts instead if time-triggered callbacks in the OPC UA
* server control flow. */
static UA_StatusCode
addPubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy,
UA_UInt64 *callbackId) {
if(pubCallback) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"At most one publisher can be registered for interrupt callbacks");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
/* Set global values for the publish callback */
int resultTimerCreate = 0;
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
/* Handle the signal */
memset(&signalAction, 0, sizeof(signalAction));
signalAction.sa_flags = SA_SIGINFO;
signalAction.sa_sigaction = publishInterrupt;
sigemptyset(&signalAction.sa_mask);
sigaction(SIG, &signalAction, NULL);
/* Create the timer */
memset(&pubEventTimer, 0, sizeof(pubEventTimer));
pubEvent.sigev_notify = SIGEV_SIGNAL;
pubEvent.sigev_signo = SIG;
pubEvent.sigev_value.sival_ptr = &pubEventTimer;
resultTimerCreate = timer_create(CLOCKID, &pubEvent, &pubEventTimer);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to create a system event with code %s",
strerror(errno));
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Arm the timer */
struct itimerspec timerspec;
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to arm the system timer with code %i", resultTimerCreate);
timer_delete(pubEventTimer);
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Start taking measurements */
publisherMeasurementsCounter = 0;
clock_gettime(CLOCKID, &calculatedCycleStartTime[0]);
calculatedCycleStartTime[0].tv_nsec += pubIntervalNs;
nanoSecondFieldConversion(&calculatedCycleStartTime[0]);
/* Set the callback -- used as a sentinel to detect an operational publisher */
pubServer = server;
pubCallback = callback;
pubData = data;
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId, UA_Double interval_ms,
UA_DateTime *baseTime, UA_TimerPolicy timerPolicy) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Switching the publisher cycle to %lf milliseconds", interval_ms);
struct itimerspec timerspec;
int resultTimerCreate = 0;
pubIntervalNs = (UA_Int64) (interval_ms * MILLI_AS_NANO_SECONDS);
timerspec.it_interval.tv_sec = (long int) (pubIntervalNs / SECONDS_AS_NANO_SECONDS);
timerspec.it_interval.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
timerspec.it_value.tv_sec = (long int) (pubIntervalNs / (SECONDS_AS_NANO_SECONDS));
timerspec.it_value.tv_nsec = (long int) (pubIntervalNs % SECONDS_AS_NANO_SECONDS);
resultTimerCreate = timer_settime(pubEventTimer, 0, &timerspec, NULL);
if(resultTimerCreate != 0) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Failed to arm the system timer");
timer_delete(pubEventTimer);
return UA_STATUSCODE_BADINTERNALERROR;
}
clock_gettime(CLOCKID, &calculatedCycleStartTime[publisherMeasurementsCounter]);
calculatedCycleStartTime[publisherMeasurementsCounter].tv_nsec += pubIntervalNs;
nanoSecondFieldConversion(&calculatedCycleStartTime[publisherMeasurementsCounter]);
return UA_STATUSCODE_GOOD;
}
static void
removePubSubApplicationCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(!pubCallback)
return;
timer_delete(pubEventTimer);
pubCallback = NULL; /* So that a new callback can be registered */
}
static void
addPubSubConfiguration(UA_Server* server) {
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
connectionConfig.enabled = true;
UA_NetworkAddressUrlDataType networkAddressUrl =
{UA_STRING(ETH_INTERFACE), UA_STRING(ETH_PUBLISH_ADDRESS)};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
&publishedDataSetIdent);
UA_NodeId dataSetFieldIdentCounter;
UA_DataSetFieldConfig counterValue;
memset(&counterValue, 0, sizeof(UA_DataSetFieldConfig));
counterValue.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
counterValue.field.variable.fieldNameAlias = UA_STRING ("Counter Variable 1");
counterValue.field.variable.promotedField = UA_FALSE;
counterValue.field.variable.publishParameters.publishedVariable = counterNodePublisher;
counterValue.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
staticValueSource = UA_DataValue_new();
UA_Variant_setScalar(&staticValueSource->value, &publishValue, &UA_TYPES[UA_TYPES_UINT64]);
counterValue.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
counterValue.field.variable.rtValueSource.staticValueSource = &staticValueSource;
#endif
UA_Server_addDataSetField(server, publishedDataSetIdent, &counterValue,
&dataSetFieldIdentCounter);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = PUB_INTERVAL;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addPubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changePubSubApplicationCallback;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removePubSubApplicationCallback;
UA_Server_addWriterGroup(server, connectionIdent,
&writerGroupConfig, &writerGroupIdent);
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
}
static void
addServerNodes(UA_Server* server) {
UA_UInt64 value = 0;
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
UA_Variant_setScalar(&publisherAttr.value, &value, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher Counter");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_DataSource dataSource;
dataSource.read = readPublishValue;
dataSource.write = NULL;
UA_Server_addDataSourceVariableNode(server, counterNodePublisher,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Publisher Counter"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, dataSource, NULL, NULL);
}
/* Stop signal */
static void stopHandler(int sign) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
running = UA_FALSE;
}
int main(void) {
signal(SIGINT, stopHandler);
signal(SIGTERM, stopHandler);
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
addServerNodes(server);
addPubSubConfiguration(server);
/* Run the server */
UA_StatusCode retval = UA_Server_run(server, &running);
#if defined(PUBSUB_CONFIG_FASTPATH_FIXED_OFFSETS) || (PUBSUB_CONFIG_FASTPATH_STATIC_VALUES)
if(staticValueSource != NULL) {
UA_DataValue_init(staticValueSource);
UA_DataValue_delete(staticValueSource);
}
#endif
UA_Server_delete(server);
return (int)retval;
}

View File

@@ -0,0 +1,51 @@
#!/bin/bash
#program path or install dir
IF=enp0s8
CPU_NR=3
PRIO=90
OLDDIR=$(pwd)
reset() {
#standard on most systems. ondemand -> Dynamic CPU-Freq.
echo ondemand >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
for i in *
do
#~disable sleep state
echo 0 >$i/disable
done
phy=$IF #get interface name
#get pid's from interface irq
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
do
#retrive or set a process's CPU affinity
taskset -pc 0-$CPU_NR $i >/dev/null
#manipulate the real-time attributes of a process -p priority -f scheduling policy to SCHED_FIFO
chrt -pf 50 $i
done
#distribute hardware interrupts across processsors on a muliprocessor system
systemctl start irqbalance
}
trap reset ERR
systemctl stop irqbalance
phy=$IF
for i in `ps ax | grep -v grep | grep $phy | sed "s/^ //" | cut -d" " -f1`
do
taskset -pc $CPU_NR $i >/dev/null
chrt -pf $PRIO $i
done
cd /sys/devices/system/cpu/cpu$CPU_NR/cpuidle
for i in `ls -1r`
do
echo 1 >$i/disable
done
echo performance >/sys/devices/system/cpu/cpu$CPU_NR/cpufreq/scaling_governor
cd $OLDDIR
taskset -c $CPU_NR chrt -f $PRIO $1

View File

@@ -0,0 +1,103 @@
# Open62541 pubsub VxWorks TSN publisher
This TSN publisher is a complement to [pubsub_interrupt_publish.c](../pubsub_interrupt_publish.c)(For more information, please refer to its [README](../README.md)). It is a VxWorks specific implementation with VxWorks-native TSN features like TSN Clock, TSN Stream, etc, which can be applied to hard realtime scenarios.
In this publisher, TSN cycle is triggered by TSN Clock -- A high-precision hardware interrupt generated by a 1588 device. The ISR will wake up a task to publish OPC-UA packets, one packet per cycle.
Considering in a typical VxWorks user scenario -- an embedded device with limited compute resources and probably without local storage, the performance will not be measured directly by this publisher. Instead, the publisher will collect all the necessary data, pack the data into an OPC-UA packet and publish it.
The measurement data includes:
1. The trigger time(1588 time) of each cycle.
2. Each wake-up time(1588 time) of the publisher task.
3. Each end time(1588 time) of the publisher task.
## User interfaces
This publisher provides 2 APIs to users:
1. `open62541PubTSNStart`
```C
/**
* Create a publisher with/without TSN.
* eName: Ethernet interface name like "gei", "gem", etc
* eNameSize: The length of eName including "\0"
* unit: Unit NO of the ethernet interface
* stkIdx: Network stack index
* withTsn: true: Enable TSN; false: Disable TSN
*
* @return OK on success, otherwise ERROR
*/
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn);
```
In order to get the best performance, this API supports to associate a dedicated CPU core with TSN related interrupt and tasks. The stkIdx argument is used for this purpose. Its value is the CPU core number: 0 means Core 0, 1 means Core 1, etc.
2. `open62541PubTSNStop`. This API is used to stop the publisher.
```C
void open62541PubTSNStop();
```
## Building the VxWorks TSN Publisher
This publisher should be built under VxWorks' building environment. For specific building instructions, please refer to VxWorks' user manual.
## Running the VxWorks TSN Publisher
1. Before starting the publisher, you should make sure that PTP is well synchronized. For the usage of PTP, please refer to VxWorks' user manual.
2. Do TSN configuration by tsnConfig as below:
```sh
-> tsnConfig("gei", 4, 1, 0, "/romfs/62541.json")
```
`62541.json` is a TSN configuration file. Below is one example configuration using 250 usecs cycle time. Adjust cycle_time and tx_time according to your system performance.
```json
{
"schedule": {
"cycle_time": 250000,
"start_sec": 0,
"start_nsec": 0
},
"stream_objects": [
{
"stream": {
"name": "flow1",
"dst_mac": "01:00:5E:00:00:01",
"vid": 3000,
"pcp": 7,
"tclass": 7,
"tx_time": {
"offset": 200000
}
}
}
]
}
```
3. Then run `open62541PubTSNStart`. Below is an example.
```sh
-> open62541PubTSNStart("gei", 4, 1, 3)
value = 0 = 0x0
-> [1970-01-01 00:47:33.100 (UTC+0000)] warn/server Username/Password configured, but no encrypting SecurityPolicy. This can leak credentials on the ne.
[1970-01-01 00:47:33.100 (UTC+0000)] warn/userland AcceptAll Certificate Verification. Any remote certificate will be accepted.
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland PubSub channel requested
[1970-01-01 00:47:33.100 (UTC+0000)] info/server Open PubSub ethernet connection.
[1970-01-01 00:47:33.100 (UTC+0000)] info/userland Adding a publisher with a cycle time of 0.250000 milliseconds
[1970-01-01 00:47:33.200 (UTC+0000)] info/network TCP network layer listening on opc.tcp://vxWorks:4840/
```
4. Run `open62541PubTSNStop` to stop the publisher.
```sh
-> open62541PubTSNStop
[1970-01-01 00:48:16.500 (UTC+0000)] info/server Stop TSN publisher
[1970-01-01 00:48:16.516 (UTC+0000)] info/network Shutting down the TCP network layer
[1970-01-01 00:48:16.516 (UTC+0000)] info/server PubSub cleanup was called.
value = 0 = 0x0
```

View File

@@ -0,0 +1,628 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright (c) 2020, 2022 Wind River Systems, Inc.
*/
#include <vxWorks.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <endCommon.h>
#include <tsnClkLib.h>
#include <endian.h>
#include <semLib.h>
#include <taskLib.h>
#include <tsnConfigLib.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/plugin/log_stdout.h>
#define ETH_PUBLISH_ADDRESS "opc.eth://01-00-5E-00-00-01"
#define MILLI_AS_NANO_SECONDS (1000 * 1000)
#define SECONDS_AS_NANO_SECONDS (1000 * 1000 * 1000)
#define DATA_SET_WRITER_ID 62541
#define TSN_TIMER_NO 0
#define NS_PER_SEC 1000000000 /* nanoseconds per one second */
#define NS_PER_MS 1000000 /* nanoseconds per 1 millisecond */
#define TSN_TASK_PRIO 30
#define TSN_TASK_STACKSZ 8192
#define KEY_STREAM_NAME "streamName"
#define KEY_STACK_IDX "stackIdx"
static UA_NodeId seqNumNodeId;
static UA_NodeId cycleTriggerTimeNodeId;
static UA_NodeId taskBeginTimeNodeId;
static UA_NodeId taskEndTimeNodeId;
static UA_ServerCallback pubCallback = NULL; /* Sentinel if a timer is active */
static UA_Server *pubServer;
static UA_Boolean running = true;
static void *pubData;
static TASK_ID tsnTask = TASK_ID_NULL;
static TASK_ID serverTask = TASK_ID_NULL;
static TSN_STREAM_CFG *streamCfg = NULL;
/* The value to published */
static UA_UInt32 sequenceNumber = 0;
static UA_UInt64 cycleTriggerTime = 0;
static UA_UInt64 lastCycleTriggerTime = 0;
static UA_UInt64 lastTaskBeginTime = 0;
static UA_UInt64 lastTaskEndTime = 0;
static UA_DataValue *staticValueSeqNum = NULL;
static UA_DataValue *staticValueCycTrig = NULL;
static UA_DataValue *staticValueCycBegin = NULL;
static UA_DataValue *staticValueCycEnd = NULL;
static clockid_t tsnClockId = 0; /* TSN clock ID */
static char *ethName = NULL;
static int ethUnit = 0;
static char ethInterface[END_NAME_MAX];
static SEM_ID msgSendSem = SEM_ID_NULL;
static UA_String streamName = {0, NULL};
static uint32_t stackIndex = 0;
static UA_Double pubInterval = 0;
static bool withTSN = false;
static UA_UInt64 ieee1588TimeGet() {
struct timespec ts;
(void)tsnClockTimeGet(tsnClockId, &ts);
return ((UA_UInt64)ts.tv_sec * NS_PER_SEC + (UA_UInt64)ts.tv_nsec);
}
/* Signal handler */
static void
publishInterrupt(_Vx_usr_arg_t arg) {
cycleTriggerTime = ieee1588TimeGet();
if(running) {
(void)semGive(msgSendSem);
}
}
/**
* **initTsnTimer**
*
* This function initializes a TSN timer. It connects a user defined routine
* to the interrupt handler and sets timer expiration rate.
*
* period is the period of the timer in nanoseconds.
* RETURNS: Clock Id or 0 if anything fails*/
static clockid_t
initTsnTimer(uint32_t period) {
clockid_t cid;
uint32_t tickRate = 0;
cid = tsnClockIdGet(ethName, ethUnit, TSN_TIMER_NO);
if(cid != 0) {
tickRate = NS_PER_SEC / period;
if(tsnTimerAllocate(cid) == ERROR) {
return 0;
}
if((tsnClockConnect(cid, (FUNCPTR)publishInterrupt, NULL) == ERROR) ||
(tsnClockRateSet(cid, tickRate) == ERROR)) {
(void)tsnTimerRelease(cid);
return 0;
}
}
/* Reroute TSN timer interrupt to a specific CPU core */
if(tsnClockIntReroute(ethName, ethUnit, stackIndex) != OK) {
cid = 0;
}
return cid;
}
/* The following three methods are originally defined in
* /src/pubsub/ua_pubsub_manager.c. We provide a custom implementation here to
* use system interrupts instead of time-triggered callbacks in the OPC UA
* server control flow. */
static UA_StatusCode
addApplicationCallback(UA_Server *server, UA_NodeId identifier,
UA_ServerCallback callback,
void *data,
UA_Double interval_ms,
UA_UInt64 *callbackId) {
if(pubCallback) {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"At most one publisher can be registered for interrupt callbacks");
return UA_STATUSCODE_BADINTERNALERROR;
}
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Adding a publisher with a cycle time of %lf milliseconds", interval_ms);
/* Convert a double float value milliseconds into an integer value in nanoseconds */
uint32_t interval = (uint32_t)(interval_ms * NS_PER_MS);
tsnClockId = initTsnTimer(interval);
if(tsnClockId == 0) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a TSN timer");
return UA_STATUSCODE_BADINTERNALERROR;
}
/* Set the callback -- used as a sentinel to detect an operational publisher */
pubServer = server;
pubCallback = callback;
pubData = data;
if(tsnClockEnable (tsnClockId, NULL) == ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot enable a TSN timer");
(void)tsnTimerRelease(tsnClockId);
tsnClockId = 0;
return UA_STATUSCODE_BADINTERNALERROR;
}
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
changeApplicationCallbackInterval(UA_Server *server, UA_NodeId identifier,
UA_UInt64 callbackId,
UA_Double interval_ms) {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
"Switching the publisher cycle to %lf milliseconds", interval_ms);
/* We are not going to change the timer interval for this case */
return UA_STATUSCODE_GOOD;
}
static void
removeApplicationPubSubCallback(UA_Server *server, UA_NodeId identifier, UA_UInt64 callbackId) {
if(!pubCallback) {
return;
}
/* Before release timer resource, wait for TSN task stopping first. */
(void) semGive(msgSendSem);
if(tsnTask != TASK_ID_NULL) {
(void) taskWait(tsnTask, WAIT_FOREVER);
tsnTask = TASK_ID_NULL;
}
/* It is safe to disable and release the timer first, then clear callback */
if(tsnClockId != 0) {
(void)tsnClockDisable(tsnClockId);
(void)tsnTimerRelease(tsnClockId);
tsnClockId = 0;
}
pubCallback = NULL;
pubServer = NULL;
pubData = NULL;
}
static void
addPubSubConfiguration(UA_Server* server) {
UA_NodeId connectionIdent;
UA_NodeId publishedDataSetIdent;
UA_NodeId writerGroupIdent;
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
connectionConfig.enabled = true;
UA_NetworkAddressUrlDataType networkAddressUrl =
{UA_STRING(ethInterface), UA_STRING(ETH_PUBLISH_ADDRESS)};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherId.numeric = UA_UInt32_random();
UA_KeyValuePair connectionOptions[2];
connectionOptions[0].key = UA_QUALIFIEDNAME(0, KEY_STREAM_NAME);
UA_Variant_setScalar(&connectionOptions[0].value, &streamName, &UA_TYPES[UA_TYPES_STRING]);
connectionOptions[1].key = UA_QUALIFIEDNAME(0, KEY_STACK_IDX);
UA_Variant_setScalar(&connectionOptions[1].value, &stackIndex, &UA_TYPES[UA_TYPES_UINT32]);
connectionConfig.connectionPropertiesSize = 2;
connectionConfig.connectionProperties = connectionOptions;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdent);
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig,
&publishedDataSetIdent);
UA_DataSetFieldConfig dataSetFieldCfg;
UA_NodeId f4;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Sequence Number");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = seqNumNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueSeqNum = UA_DataValue_new();
UA_Variant_setScalar(&staticValueSeqNum->value, &sequenceNumber, &UA_TYPES[UA_TYPES_UINT32]);
staticValueSeqNum->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueSeqNum;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f4);
UA_NodeId f3;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Cycle Trigger Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = cycleTriggerTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycTrig = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycTrig->value, &lastCycleTriggerTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycTrig->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycTrig;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f3);
UA_NodeId f2;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task Begin Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskBeginTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycBegin = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycBegin->value, &lastTaskBeginTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycBegin->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycBegin;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f2);
UA_NodeId f1;
memset(&dataSetFieldCfg, 0, sizeof(UA_DataSetFieldConfig));
dataSetFieldCfg.dataSetFieldType = UA_PUBSUB_DATASETFIELD_VARIABLE;
dataSetFieldCfg.field.variable.fieldNameAlias = UA_STRING ("Task End Time");
dataSetFieldCfg.field.variable.promotedField = UA_FALSE;
dataSetFieldCfg.field.variable.publishParameters.publishedVariable = taskEndTimeNodeId;
dataSetFieldCfg.field.variable.publishParameters.attributeId = UA_ATTRIBUTEID_VALUE;
dataSetFieldCfg.field.variable.rtValueSource.rtFieldSourceEnabled = UA_TRUE;
staticValueCycEnd = UA_DataValue_new();
UA_Variant_setScalar(&staticValueCycEnd->value, &lastTaskEndTime, &UA_TYPES[UA_TYPES_UINT64]);
staticValueCycEnd->value.storageType = UA_VARIANT_DATA_NODELETE;
dataSetFieldCfg.field.variable.rtValueSource.staticValueSource = &staticValueCycEnd;
UA_Server_addDataSetField(server, publishedDataSetIdent, &dataSetFieldCfg, &f1);
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = pubInterval;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
writerGroupConfig.pubsubManagerCallback.addCustomCallback = addApplicationCallback;
writerGroupConfig.pubsubManagerCallback.changeCustomCallback = changeApplicationCallbackInterval;
writerGroupConfig.pubsubManagerCallback.removeCustomCallback = removeApplicationPubSubCallback;
UA_Server_addWriterGroup(server, connectionIdent,
&writerGroupConfig, &writerGroupIdent);
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = DATA_SET_WRITER_ID;
dataSetWriterConfig.keyFrameCount = 10;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
}
static void
addServerNodes(UA_Server* server) {
UA_UInt64 initVal64 = 0;
UA_UInt32 initVal32 = 0;
UA_NodeId folderId;
UA_NodeId_init(&folderId);
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
oAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Publisher TSN");
UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
UA_QUALIFIEDNAME(1, "Publisher TSN"), UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
oAttr, NULL, &folderId);
UA_NodeId_init(&seqNumNodeId);
seqNumNodeId = UA_NODEID_STRING(1, "sequence.number");
UA_VariableAttributes publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal32, &UA_TYPES[UA_TYPES_UINT32]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Sequence Number");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, seqNumNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Sequence Number"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &staticValueSeqNum;
UA_Server_setVariableNode_valueBackend(server, seqNumNodeId, valueBackend);
UA_NodeId_init(&cycleTriggerTimeNodeId);
cycleTriggerTimeNodeId = UA_NODEID_STRING(1, "cycle.trigger.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cycle Trigger Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, cycleTriggerTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Cycle Trigger Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycTrig;
UA_Server_setVariableNode_valueBackend(server, cycleTriggerTimeNodeId, valueBackend);
UA_NodeId_init(&taskBeginTimeNodeId);
taskBeginTimeNodeId = UA_NODEID_STRING(1, "task.begin.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task Begin Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, taskBeginTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Task Begin Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycBegin;
UA_Server_setVariableNode_valueBackend(server, taskBeginTimeNodeId, valueBackend);
UA_NodeId_init(&taskEndTimeNodeId);
taskEndTimeNodeId = UA_NODEID_STRING(1, "task.end.time");
publisherAttr = UA_VariableAttributes_default;
publisherAttr.dataType = UA_TYPES[UA_TYPES_UINT64].typeId;
UA_Variant_setScalar(&publisherAttr.value, &initVal64, &UA_TYPES[UA_TYPES_UINT64]);
publisherAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Task End Time");
publisherAttr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
UA_Server_addVariableNode(server, taskEndTimeNodeId,
folderId,
UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Task End Time"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
publisherAttr, NULL, NULL);
valueBackend.backend.external.value = &staticValueCycEnd;
UA_Server_setVariableNode_valueBackend(server, taskEndTimeNodeId, valueBackend);
}
static void open62541EthTSNTask(void) {
uint64_t t = 0;
while(running) {
(void) semTake(msgSendSem, WAIT_FOREVER);
if(!running) {
break;
}
t = ieee1588TimeGet();
/*
* Because we cannot get the task end time of one packet before it is
* sent, we let one packet take its previous packet's cycleTriggerTime,
* taskBeginTime and taskEndTime.
*/
if(sequenceNumber == 0) {
lastCycleTriggerTime = 0;
lastTaskBeginTime = 0;
lastTaskEndTime = 0;
}
pubCallback(pubServer, pubData);
sequenceNumber++;
lastCycleTriggerTime = cycleTriggerTime;
lastTaskBeginTime = t;
lastTaskEndTime = ieee1588TimeGet();
}
}
static void open62541ServerTask(void) {
UA_Server *server = UA_Server_new();
if(server == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot allocate a server object");
goto serverCleanup;
}
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setDefault(config);
addServerNodes(server);
addPubSubConfiguration(server);
/* Run the server */
(void) UA_Server_run(server, &running);
serverCleanup:
if(server != NULL) {
UA_Server_delete(server);
server = NULL;
}
}
static bool initTSNStream(char *eName, size_t eNameSize, int unit) {
streamCfg = tsnConfigFind (eName, eNameSize, unit);
if(streamCfg == NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find TSN configuration for %s%d", eName, unit);
return false;
}
if(streamCfg->streamCount == 0) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot find stream defined for %s%d", eName, unit);
return false;
}
else {
if(withTSN) {
streamName = UA_STRING(streamCfg->streamObjs[0].stream.name);
} else {
UA_LOG_WARNING(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "No TSN stream associated.");
streamName = UA_STRING_NULL;
}
}
pubInterval = (UA_Double)((streamCfg->schedule.cycleTime * 1.0) / NS_PER_MS);
return true;
}
static bool initTSNTask() {
tsnTask = taskSpawn ((char *)"tTsnPub", TSN_TASK_PRIO, 0, TSN_TASK_STACKSZ, (FUNCPTR)open62541EthTSNTask,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if(tsnTask == TASK_ID_ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a TSN task");
return false;
}
/* Reroute TSN task to a specific CPU core */
#ifdef _WRS_CONFIG_SMP
unsigned int ncpus = vxCpuConfiguredGet ();
cpuset_t cpus;
if(stackIndex < ncpus) {
CPUSET_ZERO (cpus);
CPUSET_SET (cpus, stackIndex);
if(taskCpuAffinitySet (tsnTask, cpus) != OK) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
return false;
}
}
#endif
return true;
}
static bool initServerTask() {
serverTask = taskSpawn((char *)"tPubServer", TSN_TASK_PRIO + 5, 0, TSN_TASK_STACKSZ*2, (FUNCPTR)open62541ServerTask,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
if(serverTask == TASK_ID_ERROR) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot spawn a server task");
return false;
}
/* Reroute TSN task to a specific CPU core */
#ifdef _WRS_CONFIG_SMP
unsigned int ncpus = vxCpuConfiguredGet();
cpuset_t cpus;
if(stackIndex < ncpus) {
CPUSET_ZERO(cpus);
CPUSET_SET(cpus, stackIndex);
if(taskCpuAffinitySet(serverTask, cpus) != OK) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot move TSN task to core %d", stackIndex);
return false;
}
}
#endif
return true;
}
void open62541PubTSNStop() {
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Stop TSN publisher");
running = UA_FALSE;
if(serverTask != TASK_ID_NULL) {
(void) taskWait(serverTask, WAIT_FOREVER);
serverTask = TASK_ID_NULL;
}
(void) semGive(msgSendSem);
if(tsnTask != TASK_ID_NULL) {
(void) taskWait(tsnTask, WAIT_FOREVER);
tsnTask = TASK_ID_NULL;
}
if(msgSendSem != NULL) {
(void)semDelete(msgSendSem);
msgSendSem = SEM_ID_NULL;
}
ethName = NULL;
ethUnit = 0;
streamName = UA_STRING_NULL;
stackIndex = 0;
withTSN = false;
pubInterval = 0;
streamCfg = NULL;
if(staticValueSeqNum != NULL) {
UA_DataValue_delete(staticValueSeqNum);
staticValueSeqNum = NULL;
}
if(staticValueCycTrig != NULL) {
UA_DataValue_delete(staticValueCycTrig);
staticValueCycTrig = NULL;
}
if(staticValueCycBegin != NULL) {
UA_DataValue_delete(staticValueCycBegin);
staticValueCycBegin = NULL;
}
if(staticValueCycEnd != NULL) {
UA_DataValue_delete(staticValueCycEnd);
staticValueCycEnd = NULL;
}
sequenceNumber = 0;
cycleTriggerTime = 0;
lastCycleTriggerTime = 0;
lastTaskBeginTime = 0;
lastTaskEndTime = 0;
}
/**
* Create a publisher with/without TSN.
* eName: Ethernet interface name like "gei", "gem", etc
* eNameSize: The length of eName including "\0"
* unit: Unit NO of the ethernet interface
* stkIdx: Network stack index
* withTsn: true: Enable TSN; false: Disable TSN
*
* @return OK on success, otherwise ERROR
*/
STATUS open62541PubTSNStart(char *eName, size_t eNameSize, int unit, uint32_t stkIdx, bool withTsn) {
if((eName == NULL) || (eNameSize == 0)) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Ethernet interface name is invalid");
return ERROR;
}
ethName = eName;
ethUnit = unit;
stackIndex = stkIdx;
withTSN = withTsn;
snprintf(ethInterface, sizeof(ethInterface), "%s%d", eName, unit);
if(!initTSNStream(eName, eNameSize, unit)) {
goto startCleanup;
}
/* Create a binary semaphore which is used by the TSN timer to wake up the sender task */
msgSendSem = semBCreate(SEM_Q_FIFO, SEM_EMPTY);
if(msgSendSem == SEM_ID_NULL) {
UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Cannot create a semaphore");
goto startCleanup;
}
running = true;
if(!initTSNTask()) {
goto startCleanup;
}
if(!initServerTask()) {
goto startCleanup;
}
return OK;
startCleanup:
open62541PubTSNStop();
return ERROR;
}

Binary file not shown.

View File

@@ -0,0 +1,183 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/plugin/eventloop.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/types.h>
#include <signal.h>
#define PUBSUB_CONFIG_PUBLISH_CYCLE_MS 100
#define PUBSUB_CONFIG_FIELD_COUNT 10
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
/**
* For realtime publishing the following is configured:
*
* - Direct-access data source with double-pointers to a DataValue.
* That allows atomic updates by switching the pointer to a new DataValue.
* - The WriterGroup has UA_PUBSUB_RT_FIXED_SIZE configured. So
* the message is pre-computed and updated only at fixed offsets.
* - A dedicated EventLoop is instantiated for PubSub.
* So the client/server operations do not interfere with the publisher.
*
* Note that for true realtime the following additions are needed (at least):
*
* - An EventLoop + PubSubConnection that supports TSN.
* - The EventLoop uses a fixed TX buffer instead of a malloc each.
* - Preparing the message with a time-offset before sending.
*/
/* Values in static locations. We cycle the dvPointers double-pointer to the
* next with atomic operations. */
UA_UInt32 valueStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue dvStore[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue *dvPointers[PUBSUB_CONFIG_FIELD_COUNT];
static void
valueUpdateCallback(UA_Server *server, void *data) {
for(int i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; ++i) {
if(dvPointers[i] < &dvStore[PUBSUB_CONFIG_FIELD_COUNT - 1])
UA_atomic_xchg((void**)&dvPointers[i], dvPointers[i]+1);
else
UA_atomic_xchg((void**)&dvPointers[i], &dvStore[0]);
}
}
/* Dedicated EventLoop for PubSub */
volatile UA_Boolean pubSubELRunning = true;
UA_EventLoop *pubSubEL;
static void *
runPubSubEL(void *_) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
pthread_sigmask(SIG_BLOCK, &set, NULL);
while(pubSubELRunning)
pubSubEL->run(pubSubEL, 100);
return NULL;
}
int main(void) {
UA_Server *server = UA_Server_new();
/* Instantiate the custom EventLoop.
* Will be attached to the PubSubConnection and gets used for everything "above".
* This should be bound to a dedicated core for RT. */
pubSubEL = UA_EventLoop_new_POSIX(UA_Log_Stdout);
UA_ConnectionManager *udpCM =
UA_ConnectionManager_new_POSIX_UDP(UA_STRING("udp connection manager"));
pubSubEL->registerEventSource(pubSubEL, (UA_EventSource *)udpCM);
pubSubEL->start(pubSubEL);
pthread_t pubSubELThread;
pthread_create(&pubSubELThread, NULL, runPubSubEL, NULL);
/* Prepare the values */
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
valueStore[i] = (UA_UInt32) i + 1;
UA_Variant_setScalar(&dvStore[i].value, &valueStore[i], &UA_TYPES[UA_TYPES_UINT32]);
dvStore[i].hasValue = true;
dvPointers[i] = &dvStore[i];
}
/* Add a PubSubConnection */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
connectionConfig.enabled = true;
connectionConfig.eventLoop = pubSubEL;
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl, &UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
/* Add a PublishedDataSet */
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
/* Add DataSetFields with static value source to PDS */
UA_DataSetFieldConfig dsfConfig;
for(size_t i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
/* TODO: Point to a variable in the information model */
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.rtValueSource.rtFieldSourceEnabled = true;
dsfConfig.field.variable.rtValueSource.staticValueSource = &dvPointers[i];
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dataSetFieldIdent);
}
/* Add a WriterGroup */
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = PUBSUB_CONFIG_PUBLISH_CYCLE_MS;
writerGroupConfig.enabled = true;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
/* Change message settings of writerGroup to send PublisherId, WriterGroupId
* in GroupHeader and DataSetWriterId in PayloadHeader of NetworkMessage */
UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
/* Add a DataSetWriter to the WriterGroup */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
UA_UadpDataSetWriterMessageDataType uadpDataSetWriterMessageDataType;
UA_UadpDataSetWriterMessageDataType_init(&uadpDataSetWriterMessageDataType);
uadpDataSetWriterMessageDataType.dataSetMessageContentMask =
UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
UA_ExtensionObject_setValue(&dataSetWriterConfig.messageSettings,
&uadpDataSetWriterMessageDataType,
&UA_TYPES[UA_TYPES_UADPDATASETWRITERMESSAGEDATATYPE]);
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
/* Freeze the PubSub configuration and enable */
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
/* Add a callback that updates the value */
UA_UInt64 callbackId;
UA_Server_addRepeatedCallback(server, valueUpdateCallback, NULL,
PUBSUB_CONFIG_PUBLISH_CYCLE_MS, &callbackId);
UA_StatusCode retval = UA_Server_runUntilInterrupt(server);
pubSubELRunning = false;
pthread_join(pubSubELThread, NULL);
pubSubEL->stop(pubSubEL);
while(pubSubEL->state != UA_EVENTLOOPSTATE_STOPPED)
pubSubEL->run(pubSubEL, 0);
pubSubEL->free(pubSubEL);
UA_Server_delete(server);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -0,0 +1,197 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/plugin/log.h>
#include <open62541/plugin/log_stdout.h>
#include <open62541/server_config_default.h>
#include <stdlib.h>
UA_NodeId publishedDataSetIdent, dataSetFieldIdent, writerGroupIdent, connectionIdentifier;
UA_UInt32 *integerRTValue, *integerRTValue2;
UA_NodeId rtNodeId1, rtNodeId2;
/* Info: It is still possible to create a RT-PubSub configuration without an
* information model node. Just set the DSF flags to 'rtInformationModelNode' ->
* true and 'rtInformationModelNode' -> false and provide the PTR to your self
* managed value source. */
static UA_NodeId
addVariable(UA_Server *server, char *name) {
/* Define the attribute of the myInteger variable node */
UA_VariableAttributes attr = UA_VariableAttributes_default;
UA_UInt32 myInteger = 42;
UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_UINT32]);
attr.description = UA_LOCALIZEDTEXT("en-US", name);
attr.displayName = UA_LOCALIZEDTEXT("en-US", name);
attr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
/* Add the variable node to the information model */
UA_NodeId outNodeId;
UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, name);
UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId, parentReferenceNodeId,
myIntegerName, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
attr, NULL, &outNodeId);
return outNodeId;
}
/* If the external data source is written over the information model, the
* externalDataWriteCallback will be triggered. The user has to take care and assure
* that the write leads not to synchronization issues and race conditions. */
static UA_StatusCode
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data) {
/* It's possible to create a new DataValue here and use an atomic ptr switch
* to update the value without the need for locks e.g. UA_atomic_cmpxchg(); */
if(UA_NodeId_equal(nodeId, &rtNodeId1)){
memcpy(integerRTValue, data->value.data, sizeof(UA_UInt32));
} else if(UA_NodeId_equal(nodeId, &rtNodeId2)){
memcpy(integerRTValue2, data->value.data, sizeof(UA_UInt32));
}
return UA_STATUSCODE_GOOD;
}
static void
cyclicValueUpdateCallback_UpdateToMemory(UA_Server *server, void *data) {
*integerRTValue = (*integerRTValue)+1;
*integerRTValue2 = (*integerRTValue2)+1;
}
static void
cyclicValueUpdateCallback_UpdateToStack(UA_Server *server, void *data) {
UA_Variant valueToWrite;
UA_UInt32 newValue = (*integerRTValue)+10;
UA_Variant_setScalar(&valueToWrite, &newValue, &UA_TYPES[UA_TYPES_UINT32]);
UA_Server_writeValue(server, rtNodeId1, valueToWrite);
UA_Variant valueToWrite2;
UA_UInt32 newValue2 = (*integerRTValue2)+10;
UA_Variant_setScalar(&valueToWrite2, &newValue2, &UA_TYPES[UA_TYPES_UINT32]);
UA_Server_writeValue(server, rtNodeId2, valueToWrite2);
}
int main(void){
UA_Server *server = UA_Server_new();
/* Add one PubSubConnection */
UA_PubSubConnectionConfig connectionConfig;
memset(&connectionConfig, 0, sizeof(connectionConfig));
connectionConfig.name = UA_STRING("UDP-UADP Connection 1");
connectionConfig.transportProfileUri =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
connectionConfig.enabled = UA_TRUE;
UA_NetworkAddressUrlDataType networkAddressUrl =
{UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
UA_Variant_setScalar(&connectionConfig.address, &networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT16;
connectionConfig.publisherId.uint16 = 2234;
UA_Server_addPubSubConnection(server, &connectionConfig, &connectionIdentifier);
/* Add one PublishedDataSet */
UA_PublishedDataSetConfig publishedDataSetConfig;
memset(&publishedDataSetConfig, 0, sizeof(UA_PublishedDataSetConfig));
publishedDataSetConfig.publishedDataSetType = UA_PUBSUB_DATASET_PUBLISHEDITEMS;
publishedDataSetConfig.name = UA_STRING("Demo PDS");
/* Add one DataSetField to the PDS */
UA_Server_addPublishedDataSet(server, &publishedDataSetConfig, &publishedDataSetIdent);
/* Add RT configuration */
UA_WriterGroupConfig writerGroupConfig;
memset(&writerGroupConfig, 0, sizeof(UA_WriterGroupConfig));
writerGroupConfig.name = UA_STRING("Demo WriterGroup");
writerGroupConfig.publishingInterval = 1000;
writerGroupConfig.enabled = UA_FALSE;
writerGroupConfig.writerGroupId = 100;
writerGroupConfig.encodingMimeType = UA_PUBSUB_ENCODING_UADP;
UA_UadpWriterGroupMessageDataType writerGroupMessage;
UA_UadpWriterGroupMessageDataType_init(&writerGroupMessage);
writerGroupMessage.networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID | UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
UA_ExtensionObject_setValue(&writerGroupConfig.messageSettings, &writerGroupMessage,
&UA_TYPES[UA_TYPES_UADPWRITERGROUPMESSAGEDATATYPE]);
writerGroupConfig.rtLevel = UA_PUBSUB_RT_FIXED_SIZE;
UA_Server_addWriterGroup(server, connectionIdentifier, &writerGroupConfig, &writerGroupIdent);
/* Add one DataSetWriter */
UA_NodeId dataSetWriterIdent;
UA_DataSetWriterConfig dataSetWriterConfig;
memset(&dataSetWriterConfig, 0, sizeof(UA_DataSetWriterConfig));
dataSetWriterConfig.name = UA_STRING("Demo DataSetWriter");
dataSetWriterConfig.dataSetWriterId = 62541;
dataSetWriterConfig.keyFrameCount = 10;
/* Encode fields as RAW-Encoded */
dataSetWriterConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
UA_Server_addDataSetWriter(server, writerGroupIdent, publishedDataSetIdent,
&dataSetWriterConfig, &dataSetWriterIdent);
/* Add new nodes */
rtNodeId1 = addVariable(server, "RT value source 1");
rtNodeId2 = addVariable(server, "RT value source 2");
/* Set the value backend to 'external value source' */
integerRTValue = UA_UInt32_new();
UA_DataValue *dataValueRT = UA_DataValue_new();
dataValueRT->hasValue = UA_TRUE;
UA_Variant_setScalar(&dataValueRT->value, integerRTValue, &UA_TYPES[UA_TYPES_UINT32]);
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &dataValueRT;
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
UA_Server_setVariableNode_valueBackend(server, rtNodeId1, valueBackend);
/* Setup RT DataSetField config */
UA_NodeId dsfNodeId;
UA_DataSetFieldConfig dsfConfig;
memset(&dsfConfig, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig.field.variable.publishParameters.publishedVariable = rtNodeId1;
dsfConfig.field.variable.fieldNameAlias = UA_STRING("Field 1");
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig, &dsfNodeId);
/* Set the value backend of the above create node to 'external value source' */
integerRTValue2 = UA_UInt32_new();
*integerRTValue2 = 1000;
UA_DataValue *dataValue2RT = UA_DataValue_new();
dataValue2RT->hasValue = true;
UA_Variant_setScalar(&dataValue2RT->value, integerRTValue2, &UA_TYPES[UA_TYPES_UINT32]);
UA_ValueBackend valueBackend2;
valueBackend2.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend2.backend.external.value = &dataValue2RT;
valueBackend2.backend.external.callback.userWrite = externalDataWriteCallback;
UA_Server_setVariableNode_valueBackend(server, rtNodeId2, valueBackend2);
/* Setup second DataSetField config */
UA_DataSetFieldConfig dsfConfig2;
memset(&dsfConfig2, 0, sizeof(UA_DataSetFieldConfig));
dsfConfig2.field.variable.rtValueSource.rtInformationModelNode = UA_TRUE;
dsfConfig2.field.variable.publishParameters.publishedVariable = rtNodeId2;
dsfConfig2.field.variable.fieldNameAlias = UA_STRING("Field 2");
UA_Server_addDataSetField(server, publishedDataSetIdent, &dsfConfig2, NULL);
/* Freeze the PubSub configuration (and start implicitly the publish callback) */
UA_Server_freezeWriterGroupConfiguration(server, writerGroupIdent);
UA_Server_setWriterGroupOperational(server, writerGroupIdent);
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToMemory,
NULL, 1000, NULL);
UA_Server_addRepeatedCallback(server, cyclicValueUpdateCallback_UpdateToStack,
NULL, 5000, NULL);
UA_StatusCode retval = UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
UA_DataValue_delete(dataValueRT);
UA_DataValue_delete(dataValue2RT);
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -0,0 +1,381 @@
/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
* See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
#include <open62541/server_pubsub.h>
#include <open62541/server_config_default.h>
#include <open62541/types.h>
#include <open62541/types_generated.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define PUBSUB_CONFIG_FIELD_COUNT 10
/**
* The main target of this example is to reduce the time spread and effort
* during the subscribe cycle. This RT level example is based on buffered
* DataSetMessages and NetworkMessages. Since changes in the
* PubSub-configuration will invalidate the buffered frames, the PubSub
* configuration must be frozen after the configuration phase.
*
* After the PubSub-configuration freeze, the NetworkMessages and
* DataSetMessages will be calculated and buffered. During the subscribe cycle,
* decoding will happen only to the necessary offsets and the buffered
* NetworkMessage will only be updated.
*/
UA_NodeId connectionIdentifier;
UA_NodeId readerGroupIdentifier;
UA_NodeId readerIdentifier;
UA_DataSetReaderConfig readerConfig;
/* Simulate a custom data sink (e.g. shared memory) */
UA_UInt32 repeatedFieldValues[PUBSUB_CONFIG_FIELD_COUNT];
UA_DataValue *repeatedDataValueRT[PUBSUB_CONFIG_FIELD_COUNT];
/* If the external data source is written over the information model, the
* externalDataWriteCallback will be triggered. The user has to take care and assure
* that the write leads not to synchronization issues and race conditions. */
static UA_StatusCode
externalDataWriteCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeId,
void *nodeContext, const UA_NumericRange *range,
const UA_DataValue *data){
//node values are updated by using variables in the memory
//UA_Server_write is not used for updating node values.
return UA_STATUSCODE_GOOD;
}
static UA_StatusCode
externalDataReadNotificationCallback(UA_Server *server, const UA_NodeId *sessionId,
void *sessionContext, const UA_NodeId *nodeid,
void *nodeContext, const UA_NumericRange *range){
//allow read without any preparation
return UA_STATUSCODE_GOOD;
}
static void
subscribeAfterWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
const UA_NodeId *readerGroupId,
const UA_NodeId *targetVariableId,
void *targetVariableContext,
UA_DataValue **externalDataValue) {
(void) server;
(void) dataSetReaderId;
(void) readerGroupId;
(void) targetVariableContext;
assert(targetVariableId != 0);
assert(externalDataValue != 0);
UA_String strId;
UA_String_init(&strId);
UA_NodeId_print(targetVariableId, &strId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "subscribeAfterWriteCallback(): "
"WriteUpdate() for node Id = '%.*s'. New Value = %u", (UA_Int32) strId.length, strId.data,
*((UA_UInt32*) (**externalDataValue).value.data));
UA_String_clear(&strId);
}
/* Callback gets triggered before subscriber has received data received data
* hasn't been copied/handled yet */
static void
subscribeBeforeWriteCallback(UA_Server *server, const UA_NodeId *dataSetReaderId,
const UA_NodeId *readerGroupId, const UA_NodeId *targetVariableId,
void *targetVariableContext, UA_DataValue **externalDataValue) {
(void) server;
(void) dataSetReaderId;
(void) readerGroupId;
(void) targetVariableContext;
assert(targetVariableId != 0);
assert(externalDataValue != 0);
UA_String strId;
UA_String_init(&strId);
UA_NodeId_print(targetVariableId, &strId);
UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER,
"subscribeBeforeWriteCallback(): "
"WriteUpdate() for node Id = '%.*s'",
(UA_Int32) strId.length, strId.data);
UA_String_clear(&strId);
}
/* Define MetaData for TargetVariables */
static void
fillTestDataSetMetaData(UA_DataSetMetaDataType *pMetaData) {
if(pMetaData == NULL)
return;
UA_DataSetMetaDataType_init (pMetaData);
pMetaData->name = UA_STRING ("DataSet 1");
/* Static definition of number of fields size to PUBSUB_CONFIG_FIELD_COUNT
* to create targetVariables */
pMetaData->fieldsSize = PUBSUB_CONFIG_FIELD_COUNT;
pMetaData->fields = (UA_FieldMetaData*)UA_Array_new (pMetaData->fieldsSize,
&UA_TYPES[UA_TYPES_FIELDMETADATA]);
for(size_t i = 0; i < pMetaData->fieldsSize; i++) {
/* UInt32 DataType */
UA_FieldMetaData_init (&pMetaData->fields[i]);
UA_NodeId_copy(&UA_TYPES[UA_TYPES_UINT32].typeId,
&pMetaData->fields[i].dataType);
pMetaData->fields[i].builtInType = UA_NS0ID_UINT32;
pMetaData->fields[i].name = UA_STRING ("UInt32 varibale");
pMetaData->fields[i].valueRank = -1; /* scalar */
}
}
/* Add new connection to the server */
static UA_StatusCode
addPubSubConnection(UA_Server *server, UA_String *transportProfile,
UA_NetworkAddressUrlDataType *networkAddressUrl) {
if((server == NULL) || (transportProfile == NULL) ||
(networkAddressUrl == NULL))
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
/* Configuration creation for the connection */
UA_PubSubConnectionConfig connectionConfig;
memset (&connectionConfig, 0, sizeof(UA_PubSubConnectionConfig));
connectionConfig.name = UA_STRING("UDPMC Connection 1");
connectionConfig.transportProfileUri = *transportProfile;
connectionConfig.enabled = UA_TRUE;
UA_Variant_setScalar(&connectionConfig.address, networkAddressUrl,
&UA_TYPES[UA_TYPES_NETWORKADDRESSURLDATATYPE]);
connectionConfig.publisherIdType = UA_PUBLISHERIDTYPE_UINT32;
connectionConfig.publisherId.uint32 = UA_UInt32_random();
retval |= UA_Server_addPubSubConnection (server, &connectionConfig, &connectionIdentifier);
if (retval != UA_STATUSCODE_GOOD)
return retval;
return retval;
}
/* Add ReaderGroup to the created connection */
static UA_StatusCode
addReaderGroup(UA_Server *server) {
if(server == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_ReaderGroupConfig readerGroupConfig;
memset (&readerGroupConfig, 0, sizeof(UA_ReaderGroupConfig));
readerGroupConfig.name = UA_STRING("ReaderGroup1");
readerGroupConfig.rtLevel = UA_PUBSUB_RT_DETERMINISTIC;
retval |= UA_Server_addReaderGroup(server, connectionIdentifier, &readerGroupConfig,
&readerGroupIdentifier);
return retval;
}
/* Set SubscribedDataSet type to TargetVariables data type
* Add subscribedvariables to the DataSetReader */
static UA_StatusCode
addSubscribedVariables (UA_Server *server) {
if(server == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_NodeId folderId;
UA_NodeId newnodeId;
UA_String folderName = readerConfig.dataSetMetaData.name;
UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
UA_QualifiedName folderBrowseName;
if(folderName.length > 0) {
oAttr.displayName.locale = UA_STRING ("en-US");
oAttr.displayName.text = folderName;
folderBrowseName.namespaceIndex = 1;
folderBrowseName.name = folderName;
} else {
oAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed Variables");
folderBrowseName = UA_QUALIFIEDNAME (1, "Subscribed Variables");
}
UA_Server_addObjectNode(server, UA_NODEID_NULL,
UA_NODEID_NUMERIC (0, UA_NS0ID_OBJECTSFOLDER),
UA_NODEID_NUMERIC (0, UA_NS0ID_ORGANIZES),
folderBrowseName, UA_NODEID_NUMERIC (0,
UA_NS0ID_BASEOBJECTTYPE), oAttr, NULL, &folderId);
/* Set the subscribed data to TargetVariable type */
readerConfig.subscribedDataSetType = UA_PUBSUB_SDS_TARGET;
/* Create the TargetVariables with respect to DataSetMetaData fields */
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize =
readerConfig.dataSetMetaData.fieldsSize;
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables =
(UA_FieldTargetVariable *)UA_calloc(
readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariablesSize,
sizeof(UA_FieldTargetVariable));
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
/* Variable to subscribe data */
UA_VariableAttributes vAttr = UA_VariableAttributes_default;
vAttr.description = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.displayName = UA_LOCALIZEDTEXT ("en-US", "Subscribed UInt32");
vAttr.dataType = UA_TYPES[UA_TYPES_UINT32].typeId;
// Initialize the values at first to create the buffered NetworkMessage
// with correct size and offsets
UA_Variant value;
UA_Variant_init(&value);
UA_UInt32 intValue = 0;
UA_Variant_setScalar(&value, &intValue, &UA_TYPES[UA_TYPES_UINT32]);
vAttr.value = value;
retval |= UA_Server_addVariableNode(server, UA_NODEID_NUMERIC(1, (UA_UInt32)i + 50000),
folderId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
UA_QUALIFIEDNAME(1, "Subscribed UInt32"),
UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),
vAttr, NULL, &newnodeId);
repeatedFieldValues[i] = 0;
repeatedDataValueRT[i] = UA_DataValue_new();
UA_Variant_setScalar(&repeatedDataValueRT[i]->value, &repeatedFieldValues[i],
&UA_TYPES[UA_TYPES_UINT32]);
repeatedDataValueRT[i]->value.storageType = UA_VARIANT_DATA_NODELETE;
repeatedDataValueRT[i]->hasValue = true;
/* Set the value backend of the above create node to 'external value source' */
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &repeatedDataValueRT[i];
valueBackend.backend.external.callback.userWrite = externalDataWriteCallback;
valueBackend.backend.external.callback.notificationRead = externalDataReadNotificationCallback;
UA_Server_setVariableNode_valueBackend(server, newnodeId, valueBackend);
UA_FieldTargetVariable *tv =
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
/* For creating Targetvariables */
UA_FieldTargetDataType_init(ftdt);
ftdt->attributeId = UA_ATTRIBUTEID_VALUE;
ftdt->targetNodeId = newnodeId;
/* set both before and after write callback to show the usage */
tv->beforeWrite = subscribeBeforeWriteCallback;
tv->externalDataValue = &repeatedDataValueRT[i];
tv->afterWrite = subscribeAfterWriteCallback;
}
return retval;
}
/* Add DataSetReader to the ReaderGroup */
static UA_StatusCode
addDataSetReader(UA_Server *server) {
if(server == NULL)
return UA_STATUSCODE_BADINTERNALERROR;
memset(&readerConfig, 0, sizeof(UA_DataSetReaderConfig));
readerConfig.name = UA_STRING("DataSet Reader 1");
/* Parameters to filter which DataSetMessage has to be processed
* by the DataSetReader */
/* The following parameters are used to show that the data published by
* tutorial_pubsub_publish.c is being subscribed and is being updated in
* the information model */
UA_UInt16 publisherIdentifier = 2234;
readerConfig.publisherId.type = &UA_TYPES[UA_TYPES_UINT16];
readerConfig.publisherId.data = &publisherIdentifier;
readerConfig.writerGroupId = 100;
readerConfig.dataSetWriterId = 62541;
readerConfig.messageSettings.encoding = UA_EXTENSIONOBJECT_DECODED;
readerConfig.expectedEncoding = UA_PUBSUB_RT_RAW;
readerConfig.messageSettings.content.decoded.type = &UA_TYPES[UA_TYPES_UADPDATASETREADERMESSAGEDATATYPE];
UA_UadpDataSetReaderMessageDataType *dataSetReaderMessage = UA_UadpDataSetReaderMessageDataType_new();
dataSetReaderMessage->networkMessageContentMask = (UA_UadpNetworkMessageContentMask)
(UA_UADPNETWORKMESSAGECONTENTMASK_PUBLISHERID | UA_UADPNETWORKMESSAGECONTENTMASK_GROUPHEADER |
UA_UADPNETWORKMESSAGECONTENTMASK_SEQUENCENUMBER | UA_UADPNETWORKMESSAGECONTENTMASK_WRITERGROUPID |
UA_UADPNETWORKMESSAGECONTENTMASK_PAYLOADHEADER);
dataSetReaderMessage->dataSetMessageContentMask = UA_UADPDATASETMESSAGECONTENTMASK_SEQUENCENUMBER;
readerConfig.messageSettings.content.decoded.data = dataSetReaderMessage;
readerConfig.dataSetFieldContentMask = UA_DATASETFIELDCONTENTMASK_RAWDATA;
/* Setting up Meta data configuration in DataSetReader */
fillTestDataSetMetaData(&readerConfig.dataSetMetaData);
UA_StatusCode retval = UA_STATUSCODE_GOOD;
retval |= addSubscribedVariables(server);
retval |= UA_Server_addDataSetReader(server, readerGroupIdentifier,
&readerConfig, &readerIdentifier);
for(size_t i = 0; i < readerConfig.dataSetMetaData.fieldsSize; i++) {
UA_FieldTargetVariable *tv =
&readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables[i];
UA_FieldTargetDataType *ftdt = &tv->targetVariable;
UA_FieldTargetDataType_clear(ftdt);
}
UA_free(readerConfig.subscribedDataSet.subscribedDataSetTarget.targetVariables);
UA_free(readerConfig.dataSetMetaData.fields);
UA_UadpDataSetReaderMessageDataType_delete(dataSetReaderMessage);
return retval;
}
static int
run(UA_String *transportProfile, UA_NetworkAddressUrlDataType *networkAddressUrl) {
/* Return value initialized to Status Good */
UA_StatusCode retval = UA_STATUSCODE_GOOD;
UA_Server *server = UA_Server_new();
UA_ServerConfig *config = UA_Server_getConfig(server);
UA_ServerConfig_setMinimal(config, 4801, NULL);
/* API calls */
/* Add PubSubConnection */
retval |= addPubSubConnection(server, transportProfile, networkAddressUrl);
if (retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Add ReaderGroup to the created PubSubConnection */
retval |= addReaderGroup(server);
if (retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Add DataSetReader to the created ReaderGroup */
retval |= addDataSetReader(server);
if (retval != UA_STATUSCODE_GOOD)
return EXIT_FAILURE;
/* Freeze the PubSub configuration (and start implicitly the subscribe callback) */
UA_Server_freezeReaderGroupConfiguration(server, readerGroupIdentifier);
UA_Server_setReaderGroupOperational(server, readerGroupIdentifier);
retval = UA_Server_runUntilInterrupt(server);
UA_Server_unfreezeReaderGroupConfiguration(server, readerGroupIdentifier);
UA_Server_delete(server);
for(UA_Int32 i = 0; i < PUBSUB_CONFIG_FIELD_COUNT; i++) {
UA_DataValue_delete(repeatedDataValueRT[i]);
}
return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char **argv) {
UA_String transportProfile = UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-udp-uadp");
UA_NetworkAddressUrlDataType networkAddressUrl = {UA_STRING_NULL , UA_STRING("opc.udp://224.0.0.22:4840/")};
if(argc > 1) {
if(strcmp(argv[1], "-h") == 0) {
printf("usage: %s <uri> [device]\n", argv[0]);
return EXIT_SUCCESS;
} else if(strncmp(argv[1], "opc.udp://", 10) == 0) {
networkAddressUrl.url = UA_STRING(argv[1]);
} else if(strncmp(argv[1], "opc.eth://", 10) == 0) {
transportProfile =
UA_STRING("http://opcfoundation.org/UA-Profile/Transport/pubsub-eth-uadp");
if(argc < 3) {
printf("Error: UADP/ETH needs an interface name\n");
return EXIT_FAILURE;
}
networkAddressUrl.networkInterface = UA_STRING(argv[2]);
networkAddressUrl.url = UA_STRING(argv[1]);
} else {
printf ("Error: unknown URI\n");
return EXIT_FAILURE;
}
}
return run(&transportProfile, &networkAddressUrl);
}