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.

OPCUAServer configuration. Note that the OPCUA server port is automatically replaced by the 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.

IOGAM to copy from the DataSource. Note the use of the 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        }
OPCUADSInput DataSource configuration. Note that the OPCUA address is automatically replaced by the 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.

  1. Edit the file ../Configurations/MassSpring/RTApp-MassSpring-44.cfg and add a MuxGAM that selects between the signals ForceZero and ForceFromController to produce an output signal named Force in DDB1. Name this GAM GAMMuxSwitchOn.

  2. Modify the ControllerGAM to output the signal named ForceFromController instead of Force.

  3. Modify the ReferenceGAM to read the full structure from the OPCUADSInput DataSource and copy both the ReferencePosition and the SwitchOn signal to DDB1.

  4. Add the MuxGAM to the execution list.

  5. Execute the application:

make -C ../Configurations/MassSpring/ -f Makefile.cfg
./MARTeApp.sh -f ../Configurations/MassSpring/RTApp-MassSpring-44_Gen.cfg -l RealTimeLoader -s State1
  1. Set a new target ReferencePosition by 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.
  1. Observe that the Position is not changing and that the system is not responding to the new target ReferencePosition.

./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.
  1. 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
  1. Observe that the Position is now changing and that the system is responding to the new target ReferencePosition, using one of the commands described above.

Solution

The solution is to add a MuxGAM as specified above.

MuxGAM configuration.
 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.

GAMReference configuration
 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.

GAM 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}