OPCUADSInput
Warning
The OPCUADataSource is only available in distributions where open62541 is installed.
The OPCUADSInput DataSource can be used to receive application data using OPCUA.
This DataSource can read both in synchronous and asynchronous mode. In the first case, the data is read from the OPCUA server in the same thread as the application, while in the second case, the data is read asynchronously on a separate thread (which may be allocated to a different CPU core). The asynchronous mode is not intended to be used for real-time control, but rather for live parameter configuration.
Given that the OPCUA server ports need to be unique in order to avoid port clashes, the configuration file ../Configurations/MassSpring/RTApp-MassSpring-43.cfg will be automatically updated from a Makefile.cfg.
The OPCUA types are hosted using a MARTe2 OPCUAServer, which in this example is instantiated in the same configuration file as the application.
Makefile.cfg. 1+OPCUAServer = {
2 Class = OPCUA::OPCUAServer
3 StackSize = 1048576
4 CPUs = 0x1
5 Run = 1
6 Authentication = None
7 Port = OPCUA_PORT
8 AddressSpace = {
9 MassSpringDemo = {
10 Type = MonitorControl
11 NumberOfElements = 1
12 }
13 MassSpringDemoPerformance = {
14 Type = MonitorPerformance
15 NumberOfElements = 1
16 }
17 MassSpringDemoControlInput = {
18 Type = ControlInput
19 NumberOfElements = 1
20 }
21 }
22}
In this example, the ReferencePosition signal is read from an OPCUA signal and used as the reference for the control of the mass-spring system.
Alias field to access the signal directly from the DataSource without the need to copy the full structure. 1 +GAMReference = {
2 Class = IOGAM
3 InputSignals = {
4 ReferencePosition = {
5 DataSource = OPCUAReader
6 Type = float64
7 Alias = "ControlIn.ReferencePosition"
8 }
9 }
10 OutputSignals = {
11 ReferencePosition = {
12 DataSource = DDB1
13 Type = float64
14 }
15 }
16 }
Makefile.cfg. 1 +OPCUAReader = {
2 Class = OPCUADataSource::OPCUADSInput
3 Address = OPCUA_FULL_URL
4 CPUMask = 0x1
5 StackSize = 10000000
6 Synchronise = "no"
7 Signals = {
8 ControlIn = {
9 NamespaceIndex = 1
10 Type = ControlInput
11 Path = "MassSpringDemoControlInput"
12 }
13 }
14 }
Running the application
make -C ../Configurations/MassSpring/ -f Makefile.cfg
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-43_Gen.cfg -l RealTimeLoader -s State1
Once the application is running, inspect the screen output and verify that the application is running without any issues. The log should show entries similar to the following:
...
$ [Warning - Threads.cpp:185]: Failed to change the thread priority (likely due to insufficient permissions)
$ [Information - RealTimeLoader.cpp:111]: Started application in state State1
$ [Information - MARTeApp.cpp:135]: Application starting
$ [Information - LoggerBroker.cpp:152]: Time [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Time [0:0]:1000000
...
Open another terminal and check that the OPCUA records are being updated with the application data by running another MARTe2 application.
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-40-Monitor_Gen.cfg -l RealTimeLoader -s State1
The output should be similar to the following:
$ [Information - LoggerBroker.cpp:152]: Monitor.Time [0:0]:9540000
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.ReferencePosition [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.Position [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.PositionDisturbed [0:0]:0.005878
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.PositionFiltered [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.PositionM [0:0]:-0.000001
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.Velocity [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.VelocityM [0:0]:-0.000004
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.PositionErr [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.Force [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.ForceAverage [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.ForceStdDev [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.ForceMax [0:0]:0
$ [Information - LoggerBroker.cpp:152]: Monitor.Control.ForceMin [0:0]:0
Note
You can also use any OPCUA client to connect to the MARTe2 OPCUA server and monitor the application data.
For example, if the Python opcua library is installed on your system, you can use the Python script located in ../Test/Integrated/opcua_monitor.py to connect to the server and print the values of the nodes being written by the application.
OPCUA_PORT=`awk '/\+OPCUAServer/,/}/ {if ($1=="Port") print $3}' ../Configurations/MassSpring/RTApp-MassSpring-43_Gen.cfg`;echo "OPCUA_PORT=$OPCUA_PORT"
python3.6 ../Test/Integrated/opcua_monitor.py -p $OPCUA_PORT -s MassSpringDemo
Open another terminal and modify the value of the ReferencePosition OPCUA signal using another MARTe2 application.
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-43-Control_Gen.cfg -l RealTimeLoader -s State1
Note
You can also use any OPCUA client to connect to the MARTe2 OPCUA server and modify the application data.
For example, if the Python opcua library is installed on your system, you can use the Python script located in ../Test/Integrated/opcua_writer.py to connect to the server and modify the value of ReferencePosition.
OPCUA_PORT=`awk '/\+OPCUAServer/,/}/ {if ($1=="Port") print $3}' ../Configurations/MassSpring/RTApp-MassSpring-43_Gen.cfg`;echo "OPCUA_PORT=$OPCUA_PORT"
python3.6 ../Test/Integrated/opcua_writer.py -p $OPCUA_PORT -s MassSpringDemoControlInput.ReferencePosition -v 1.3
The output should be similar to the following:
...
$ [Warning - Threads.cpp:185]: Failed to change the thread priority (likely due to insufficient permissions)
$ [Information - RealTimeLoader.cpp:111]: Started application in state State1
$ [Information - MARTeApp.cpp:135]: Application starting
Open another terminal and, using one of the commands above, check that the ReferencePosition is being updated and that the system is responding to the new target:
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-40-Monitor_Gen.cfg -l RealTimeLoader -s State1
Or:
OPCUA_PORT=`awk '/\+OPCUAServer/,/}/ {if ($1=="Port") print $3}' ../Configurations/MassSpring/RTApp-MassSpring-43_Gen.cfg`;echo "OPCUA_PORT=$OPCUA_PORT"
python3.6 ../Test/Integrated/opcua_monitor.py -p $OPCUA_PORT -s MassSpringDemo
Exercises
Ex. 1: Switch on/off
The objective of this exercise is to use the OPCUADSInput DataSource to switch the force output of the mass-spring system on and off.
Edit the file
../Configurations/MassSpring/RTApp-MassSpring-44.cfgand add a MuxGAM that selects between the signalsForceZeroandForceFromControllerto produce an output signal namedForceinDDB1. Name this GAMGAMMuxSwitchOn.Modify the
ControllerGAMto output the signal namedForceFromControllerinstead ofForce.Modify the
ReferenceGAMto read the full structure from theOPCUADSInputDataSource and copy both theReferencePositionand theSwitchOnsignal toDDB1.Add the MuxGAM to the execution list.
Execute the application:
make -C ../Configurations/MassSpring/ -f Makefile.cfg
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-44_Gen.cfg -l RealTimeLoader -s State1
Set a new target
ReferencePositionby running the following MARTe2 application.
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-43-Control_Gen.cfg -l RealTimeLoader -s State1 #Alternatively, use the Python script described above to update the OPCUA signal.
Observe that the
Positionis not changing and that the system is not responding to the new targetReferencePosition.
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-40-Monitor_Gen.cfg -l RealTimeLoader -s State1 #Alternatively, use the Python script described above to monitor the OPCUA signal.
Switch on the system by setting the switch on/off signal to 1 using the command:
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-44-Control-On_Gen.cfg -l RealTimeLoader -s State1
8.1. Alternatively, use the Python script described above to update the OPCUA signal.
OPCUA_PORT=`awk '/\+OPCUAServer/,/}/ {if ($1=="Port") print $3}' ../Configurations/MassSpring/RTApp-MassSpring-44_Gen.cfg`;echo "OPCUA_PORT=$OPCUA_PORT"
python3.6 ../Test/Integrated/opcua_writer.py -p $OPCUA_PORT -s MassSpringDemoControlInput.SwitchOn -v 1
Observe that the
Positionis now changing and that the system is responding to the new targetReferencePosition, using one of the commands described above.
Solution
The solution is to add a MuxGAM as specified above.
1 +GAMMuxSwitchOn = {
2 Class = MuxGAM
3 InputSignals = {
4 SwitchOn = {
5 DataSource = DDB1
6 Type = uint32
7 NumberOfDimensions = 1
8 NumberOfElements = 1
9 }
10 ForceZero = {
11 DataSource = DDB1
12 Type = float64
13 NumberOfDimensions = 1
14 NumberOfElements = 1
15 }
16 ForceFromController = {
17 DataSource = DDB1
18 Type = float64
19 NumberOfDimensions = 1
20 NumberOfElements = 1
21 }
22 }
23 OutputSignals = {
24 Force = {
25 DataSource = DDB1
26 Type = float64
27 NumberOfDimensions = 1
28 NumberOfElements = 1
29 }
30 }
31 }
Update the GAMReference to read both signals from the DataSource.
1 +GAMReference = {
2 Class = IOGAM
3 InputSignals = {
4 ControlIn = {
5 DataSource = OPCUAReader
6 Type = ControlInput
7 }
8 }
9 OutputSignals = {
10 ReferencePosition = {
11 DataSource = DDB1
12 Type = float64
13 }
14 SwitchOn = {
15 DataSource = DDB1
16 Type = uint32
17 }
18 }
19 }
Note
The Alias could also have been used to access the signals directly from the DataSource in the MuxGAM, without the need to copy the full structure in the ReferenceGAM.
However, the MuxGAM requires the signal to have NumberOfDimensions=1, while the structured signal read from the DataSource has NumberOfDimensions=0. Even if they are binary compatible (array of one element and scalar), another GAM to convert between them would be needed, which would make the configuration more complex.
Add the GAM to the execution list.
1 +States = {
2 Class = ReferenceContainer
3 +State1 = {
4 Class = RealTimeState
5 +Threads = {
6 Class = ReferenceContainer
7 +Thread1 = {
8 Class = RealTimeThread
9 CPUs = 0x1
10 Functions = {GAMPerfMonitor GAMTimer GAMReference GAMDisturbanceWaveform GAMMathDisturbance GAMFilterDisturbance GAMController GAMConstantZero GAMMuxSwitchOn GAMSpringMass GAMMathModel GAMMathPositionErr GAMStats GAMPatchForceDims GAMForceStats GAMHist GAMMathExpr GAMConversion GAMFilterMovingAvg GAMMathTrigger GAMMathTriggerSecond GAMDisplay GAMWriterPerf GAMWriter}
11 }
12 }
13 }
14 }
15 +Scheduler = {
16 Class = GAMScheduler
17 TimingDataSource = Timings
18 }
19}