opc source code
This commit is contained in:
78
third_party/open62541/examples/pubsub_realtime/attic/README.md
vendored
Normal file
78
third_party/open62541/examples/pubsub_realtime/attic/README.md
vendored
Normal 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
|
||||
188
third_party/open62541/examples/pubsub_realtime/attic/README_pubsub_TSN.txt
vendored
Normal file
188
third_party/open62541/examples/pubsub_realtime/attic/README_pubsub_TSN.txt
vendored
Normal 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
|
||||
195
third_party/open62541/examples/pubsub_realtime/attic/Readme_PubSub.txt
vendored
Normal file
195
third_party/open62541/examples/pubsub_realtime/attic/Readme_PubSub.txt
vendored
Normal 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
|
||||
48
third_party/open62541/examples/pubsub_realtime/attic/nodeset/CMakeLists.txt
vendored
Normal file
48
third_party/open62541/examples/pubsub_realtime/attic/nodeset/CMakeLists.txt
vendored
Normal 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)
|
||||
|
||||
4
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubDataModel.csv
vendored
Normal file
4
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubDataModel.csv
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
PublisherInfo,2001,ObjectType
|
||||
Publisher,2004,Object
|
||||
PublisherCounterValue,2005,Variable
|
||||
Pressure,2006,Variable
|
||||
|
80
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubDataModel.xml
vendored
Normal file
80
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubDataModel.xml
vendored
Normal 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>
|
||||
736
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c
vendored
Normal file
736
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_publisher.c
vendored
Normal 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(ðernettransportSettings, 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 = ðernettransportSettings;
|
||||
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;
|
||||
}
|
||||
656
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c
vendored
Normal file
656
third_party/open62541/examples/pubsub_realtime/attic/nodeset/pubsub_nodeset_rt_subscriber.c
vendored
Normal 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;
|
||||
}
|
||||
|
||||
4
third_party/open62541/examples/pubsub_realtime/attic/nodeset/subDataModel.csv
vendored
Normal file
4
third_party/open62541/examples/pubsub_realtime/attic/nodeset/subDataModel.csv
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
SubscriberInfo,2001,ObjectType
|
||||
Subscriber,2004,Object
|
||||
PublisherCounterValue,2005,Variable
|
||||
Pressure,2006,Variable
|
||||
|
80
third_party/open62541/examples/pubsub_realtime/attic/nodeset/subDataModel.xml
vendored
Normal file
80
third_party/open62541/examples/pubsub_realtime/attic/nodeset/subDataModel.xml
vendored
Normal 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>
|
||||
1742
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_loopback.c
vendored
Normal file
1742
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_loopback.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1407
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c
vendored
Normal file
1407
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_loopback_single_thread.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1813
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_publisher.c
vendored
Normal file
1813
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_publisher.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1504
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c
vendored
Normal file
1504
third_party/open62541/examples/pubsub_realtime/attic/pubsub_TSN_publisher_multiple_thread.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
380
third_party/open62541/examples/pubsub_realtime/attic/pubsub_interrupt_publish.c
vendored
Normal file
380
third_party/open62541/examples/pubsub_realtime/attic/pubsub_interrupt_publish.c
vendored
Normal 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;
|
||||
}
|
||||
51
third_party/open62541/examples/pubsub_realtime/attic/start_rt_publish.sh
vendored
Executable file
51
third_party/open62541/examples/pubsub_realtime/attic/start_rt_publish.sh
vendored
Executable 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
|
||||
103
third_party/open62541/examples/pubsub_realtime/attic/vxworks/README.md
vendored
Normal file
103
third_party/open62541/examples/pubsub_realtime/attic/vxworks/README.md
vendored
Normal 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
|
||||
```
|
||||
628
third_party/open62541/examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c
vendored
Normal file
628
third_party/open62541/examples/pubsub_realtime/attic/vxworks/pubsub_interrupt_publish_tsn.c
vendored
Normal 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;
|
||||
}
|
||||
BIN
third_party/open62541/examples/pubsub_realtime/etfa18-pfrommer-tsn-pubsub.pdf
vendored
Normal file
BIN
third_party/open62541/examples/pubsub_realtime/etfa18-pfrommer-tsn-pubsub.pdf
vendored
Normal file
Binary file not shown.
BIN
third_party/open62541/examples/pubsub_realtime/opc-ua-tsn-wrs.pdf
vendored
Executable file
BIN
third_party/open62541/examples/pubsub_realtime/opc-ua-tsn-wrs.pdf
vendored
Executable file
Binary file not shown.
183
third_party/open62541/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c
vendored
Normal file
183
third_party/open62541/examples/pubsub_realtime/server_pubsub_publisher_rt_level.c
vendored
Normal 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;
|
||||
}
|
||||
197
third_party/open62541/examples/pubsub_realtime/server_pubsub_rt_field_information_model.c
vendored
Normal file
197
third_party/open62541/examples/pubsub_realtime/server_pubsub_rt_field_information_model.c
vendored
Normal 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;
|
||||
}
|
||||
381
third_party/open62541/examples/pubsub_realtime/server_pubsub_subscriber_rt_level.c
vendored
Normal file
381
third_party/open62541/examples/pubsub_realtime/server_pubsub_subscriber_rt_level.c
vendored
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user