1. Disclaimer
Information contained in this publication regarding device applications and the like is provided only for your convenience and may be superseded by updates. It is your responsibility to ensure that your application meets with your specifications. EXOLIGENT MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND WHETHER EXPRESS OR IMPLIED, WRITTEN OR ORAL, STATUTORY OR OTHERWISE, RELATED TO THE INFORMATION, INCLUDING BUT NOT LIMITED TO ITS CONDITION, QUALITY, PERFORMANCE, MERCHANTABILITY OR FITNESS FOR PURPOSE. Exoligent disclaims all liability arising from this information and its use. Use of Exoligent devices in life support and/or safety applications is entirely at the buyer’s risk, and the buyer agrees to defend, indemnify and hold harmless Exoligent from any and all damages, claims, suits, or expenses resulting from such use. No licenses are conveyed, implicitly or otherwise, under any Exoligent intellectual property rights unless otherwise stated.
Exoligent SARL
390 rue d’Estienne d’Orves, 92700 Colombes - France
Tel: +33(0) 1 42 42 42 00
Web Site: https://www.exoligent.com/
Copyright © 2022 Exoligent SARL. All rights reserved.
2. Introduction
UAFIP is an open-source software gateway written in C for GNU/Linux operating system whose goal is to create a deterministic gateway between the WorldFIP fieldbus protocol (IEC 61158) and the OPC UA over TSN (IEC 62541) standard for data exchange. This user guide serves as starting point for a user to learn, install and configure this gateway for embedding into their products.
This document is partly based on the very good guide from Kalycito: |
The structure of the UAFIP TSN gateway is divided into three entities:
-
Configurator: json-c – an open-source C JSON parser licensed under MIT (github)
-
OPC UA stack: open62541 – an open-source C (C99) implementation of OPC UA licensed under the Mozilla Public License v2.0 (github)
-
WorldFIP stack: powerfip – a C communication stack to interface with the Exoligent’s FIP coprocessor (doc)
3. Prerequisites
3.1. Hardware requirements
-
x86-based system
-
4-cores
-
Intel i210 Ethernet controller (as this controller supports features necessary for Time-Sensitive Networking)
-
1 x PCIe slot or 1 x mPCIe slot
-
-
FIP controller
-
PowerFIP Exoligent’s board (mPCIe and its derivatives)
-
We used Advantech’s UNO-1483G during our tests.
3.2. Software requirements
-
Debian GNU/Linux 10 and more
-
Pre-compiled real-time kernel
-
A later version of iproute2 package (with support for newer real-time socket options and an IEEE 802.1 Qbv like scheduler)
-
A later version of LinuxPTP package (with support for IEEE 802.1 AS gPTP configuration)
-
-
FIP library/kernel module + UAFIP TSN gateway sources (see last PowerFIP Package)
We used Debian GNU/Linux 11 (bullseye) with PREEMPT-RT 5.10.0-14-rt-amd64 kernel during our tests
We selected Debian 11, as it is one of the more popular distributions. It also has a pre-compiled real-time kernel that can be installed using the Debian package manager. |
4. Environment setup
In this section, we will set up the environment:
-
Install real-time Linux kernel
-
Install FIP controller
-
Configure Ethernet network interface
-
Configure TSN parameters
-
Build UAFIP TSN gateway
4.1. Install real-time Linux Kernel
After installing the OS, update the node using the below command:
$ sudo apt-get update && apt-get upgrade
It might take a while for the update and upgrade to complete. You can then download and install the real-time kernel that is distributed by Debian and reboot the node using the below commands:
$ sudo apt-get install linux-image-rt-amd64 -y $ sudo reboot
After reboot, execute the below command and ensure that the newly installed real-time kernel is the one that is currently running.
The appearance of the “rt” keyword in the print message that appears as part of the command output shows that we have booted into the right kernel:
$ uname -r 5.10.0-14-rt-amd64
4.2. Install FIP controller
In this section, we will install FIP controller on your machine.
Once the real time Linux kernel is installed, shutdown your system and plug a PowerFIP PCIe card into a suitable slot
After reboot and log in user, download the archive of the latest version of the library from the Exoligent website (Download section) or get it from the following linux command (here the example is for the v1.4.0 package):
$ cd ~/Documents $ wget https://www.exoligent.com/wiki/worldfip/pwrfip/download/1.4.0/powerfip-1.4.0-linux.tar.gz $ tar xzvf powerfip-1.4.0-linux.tar.gz $ cd powerfip-1.4.0-linux
Build and install the kernel module for FIP controller:
$ cd ~/Documents/powerfip-1.4.0-linux/driver/linux $ make $ sudo ./install.sh
If you have a PowerFIP PCIe device connected to your machine, you should get the following console trace with the dmesg command:
$ dmesg [78983.022874] powerfip: ==> Init [78983.022934] powerfip: ==> Device probing [78983.023186] powerfip: BAR 0 (0xf6000000 - 0xf6000fff), len = 4096, flags = 0x040200 [78983.023205] powerfip: BAR 1 (0xf4000000 - 0xf5ffffff), len = 33554432, flags = 0x040200 [78983.023883] powerfip: DMA Capability = YES [78983.023887] powerfip: MSI Capability = YES [78983.023888] powerfip: IRQ pin = #1 (0=none, 1=INTA#...4=INTD#) [78983.023890] powerfip: IRQ line = #69 [78983.023891] powerfip: IRQ vectors = 4
Now, install FIP firmware and library:
$ cd ../../ $ sudo ./install.sh
|
The FIP controller is now ready for operation! If you want to test it, refer you to the turnkey examples in the tools/
directory of the distribution.
4.3. Configure Ethernet network interface
In this section, we will set up static IP address and VLAN configuration to the i210 interface. Open the interfaces file using the below commands:
$ sudo nano /etc/network/interfaces
Copy and paste the following lines at the end of the interfaces file in both the nodes and replace “X” as shown in the screenshot below and save the file. E.g., In node 1 (ex: ua_pong node example), set the IP address as 192.168.100.1, and in node 2 (ex: uafip tsn gateway node), set the IP address as 192.168.100.2. The PubSub TSN applications are configured to run with VLAN Id 8. For this purpose, VLAN configuration is done in the i210 interface as below:
auto enp7s0 iface enp7s0 inet static address 192.168.100.X netmask 255.255.255.0 broadcast 192.168.100.255 auto enp7s0.8 iface enp7s0.8 inet static address 192.168.8.X netmask 255.255.255.0
Throughout this QSG we will be using enp7s0 for the interface name, replace it with your nodes’ i210 interface name.
Below is the screenshot of the interfaces files of the two nodes:
Save the interfaces file and restart the networking service:
$ sudo /etc/init.d/networking restart
Now verify the static IP address and the connection between two nodes (from node 1 ping node 2 and vice versa) using the below commands:
$ ip a [...] 3: enp7s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 xdpgeneric/id:34 qdisc mqprio state UP group default qlen 1000 link/ether 74:fe:48:30:39:eb brd ff:ff:ff:ff:ff:ff inet 192.168.100.2/24 brd 192.168.100.255 scope global enp7s0 valid_lft forever preferred_lft forever inet6 fe80::76fe:48ff:fe30:39eb/64 scope link valid_lft forever preferred_lft forever [...]
$ ping 192.168.100.X
4.4. Configure TSN parameters
In this section, we will run some scripts from the Kalycito company (setup.sh, application_dependencies.sh) to configure the TSN parameters.
The setup.sh available in the package does the following,
-
Updates the installed packages in the node
-
Installs iproute2 package for kernel version 5.10
-
Installs Linux PTP version 2.0
-
Checks if 8021q module is loaded; if not, adds the same
The application_dependencies.sh available in the package does the following,
-
Configures traffic control parameters to transmit and receive
-
Sets Egress and Ingress policies
-
Tunes real-time behaviors
-
Runs Linux PTP and PHC2SYS
-
Kernel mechanism to allocate CPU to the processes
The above scripts are included in the powerfip package. To access them, go to the kalycito directory, and run the setup.sh script using the below command:
$ cd ~/Documents/powerfip-1.4.0-linux/tools/uafip_tsn_gateway/scripts/kalycito $ sudo ./setup.sh
We have modified the grub settings, so reboot is required. |
Reboot the nodes after completing the initial setup
$ sudo reboot
- Run PTP and PHC2SYS
-
After the reboot, run the application_dependencies.sh. As shown in the architecture diagram, configure one of the nodes as PTP master and the other node as PTP slave.
To configure the node as PTP master use the below command:
$ cd ~/Documents/powerfip-1.4.0-linux/tools/uafip_tsn_gateway/scripts/kalycito $ sudo ./application_dependencies.sh -i enp2s0 -m
To configure the node as PTP slave use the below command:
$ sudo ./application_dependencies.sh -i enp2s0 -s
After configuring the node as PTP master or slave, the output log can be verified using the below command:
$ tail -f /var/log/ptp4l.log
If you have configured your node as a PTP master, your output log should be similar to the below image. The text “assuming the grand master role” ensures that you have configured the node as PTP master. Once you have seen the output, press Ctrl + C to return to the console.
$ tail -f /var/log/ptp4l.log ptp4l[3194.498]: port 1: link up ptp4l[3194.558]: port 1: FAULTY to LISTENING on INIT_COMPLETE ptp4l[3205.493]: port 1: LISTENING to MASTER on ANNOUNCE_RECEIPT_TIMEOUT_EXPIRES ptp4l[3205.493]: selected local clock 74fe48.fffe.42c2e4 as best master ptp4l[3205.493]: port 1: assuming the grand master role
If you have configured your node as a PTP slave, your output log should be similar to the below image.
The master offset values are represented in nanosecond (ns). This value should be within the range of -1000 ns to +1000 ns |
$ tail -f /var/log/ptp4l.log ptp4l[4527.664]: master offset 4 s2 freq +39804 path delay -17 ptp4l[4527.789]: master offset 5 s2 freq +39806 path delay -17 ptp4l[4527.914]: master offset 0 s2 freq +39799 path delay -17 ptp4l[4528.039]: master offset 0 s2 freq +39799 path delay -17 ptp4l[4528.165]: master offset -6 s2 freq +39791 path delay -17 ptp4l[4528.290]: master offset -4 s2 freq +39793 path delay -17
The application_dependencies.sh would have run PHC2SYS utility to synchronize user clock and hardware clock. You can verify the output log of PHC2SYS in both the nodes using the below command:
$ tail -f /var/log/phc2sys.log
Your PHC2SYS log should be similar to the below image.
The sys offset values are represented in nanosecond (ns). This value should be within the range of -1000 ns to +1000 ns |
Once you have seen the output, press Ctrl + C to return to the console.
4.5. Build UAFIP TSN gateway
4.5.1. XDP Pre-requisties
XDP (Express Data Path) is used by the gateway subscriber for faster processing the data.
For our test with the Debian 11 OS (5.10.0-14-rt-amd64) we use the bpf library v0.3. |
To build and install libbpf library, use the following commands:
$ sudo apt install llvm $ sudo apt install clang $ cd ~/Documents $ git clone https://github.com/libbpf/libbpf.git $ cd libbpf/ $ git checkout 051a4009f94d5633a8f734ca4235f0a78ee90469 $ cd src/ $ sudo OBJDIR=/usr/lib make install
4.5.2. Build open62541
$ sudo apt install build-essential gcc pkg-config cmake python $ cd ~/Documents $ wget https://github.com/open62541/open62541/archive/refs/tags/v1.3.tar.gz -O open62541-1.3.0.tar.gz $ tar xzvf open62541-1.3.0.tar.gz $ cd open62541-1.3.0 $ mkdir build $ cd build $ cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DOPEN62541_VERSION=v1.3.0 -DUA_ENABLE_PUBSUB=ON -DUA_ENABLE_PUBSUB_ETH_UADP=ON -DUA_ENABLE_MALLOC_SINGLETON=ON -DUA_ENABLE_IMMUTABLE_NODES=ON -DUA_ENABLE_PUBSUB_INFORMATIONMODEL=ON -DUA_MULTITHREADING=150 $ make $ sudo make install
4.5.3. Build the gateway
$ sudo apt install libjson-c-dev $ cd ~/Documents/powerfip-1.4.0-linux/tools/uafip_tsn_gateway $ make
4.5.4. Start the gateway
$ cd ~/Documents/powerfip-1.4.0-linux/tools/uafip_tsn_gateway $ sudo setcap cap_sys_nice+ep uafipd $ ./uafipd -f ./config/2sta/sta1.json
4.5.5. Install the gateway as a daemon
Install UAFIP gateway as a daemon:
$ cd ~/Documents/powerfip-1.4.0-linux/tools/uafip_tsn_gateway/svc/ $ sudo ./install_svc.sh Created symlink /etc/systemd/system/multi-user.target.wants/uafipd.service → /etc/systemd/system/uafipd.service.
Once installed, you can manipulate the daemon with the classic commands of systemd:
Daemon status:
$ sudo systemctl status uafipd ● uafipd.service - OPC UA Pub/Sub TSN <-> FIP/WorldFIP gateway Loaded: loaded (/etc/systemd/system/uafipd.service; enabled; vendor preset: enabled) Active: active (running) since Fri 2022-06-10 15:01:52 CEST; 59s ago Main PID: 5910 (uafipd) Tasks: 5 (limit: 9383) Memory: 3.6M CPU: 1.241s CGroup: /system.slice/uafipd.service └─5910 /usr/bin/uafipd -f /etc/exoligent/uafip/config/performance/sta1.json -L /var/log/uafipd.log
Stop the daemon:
$ sudo systemctl stop uafipd
Start the daemon:
$ sudo systemctl start uafipd
Enable the daemon at system startup:
$ sudo systemctl enable uafipd Created symlink /etc/systemd/system/multi-user.target.wants/uafipd.service → /etc/systemd/system/uafipd.service.
Disable the daemon at system startup:
$ sudo systemctl disable uafipd Removed /etc/systemd/system/multi-user.target.wants/uafipd.service.
5. Gateway configuration
The UAFIP TSN gateway can be configured using a JSON file. The OPC PUB/SUB parameters are defined there as well as the parameters of the embedded FIP node.
{
"opc": {
"publisher" : {
"network_interface":"enp7s0",
"url":"opc.eth://74-fe-48-42-c2-e4:8.3",
"publisher_id":"5671",
"dataset_writer_id":"62541",
"writer_group_id":"101",
"publish_period_us":"250"
},
"subscriber" : {
"network_interface":"enp7s0",
"url":"opc.eth://74-fe-48-30-39-eb:8.3",
"publisher_id":"5670",
"dataset_writer_id":"62541",
"writer_group_id":"100"
}
},
"fip": {
"name":"FIP node 1",
"address":"1",
"segment":"0",
"frame_type":"worldfip",
"bitrate":"1mbps",
"turn_around_us":"30",
"silence_us":"150",
"vars": [
{
"name":"[0x3800]",
"type":"cons",
"id":"0x3800",
"payload_bsz":"8",
"refreshment":"yes",
"promptness":"yes",
"prompt_us":"7500",
"event":"none"
},
{
"name":"[0x3801]",
"type":"prod",
"id":"0x3801",
"payload_bsz":"8",
"refreshment":"yes",
"refresh_us":"7500",
"event":"none"
},
{
"name":"[0xFF00]",
"type":"sync",
"id":"0xFF00",
"event":"w_op"
},
{
"name":"[0xFF01]",
"type":"sync",
"id":"0xFF01",
"event":"r_op"
}
]
}
}
See appendix |
The JSON file is a data tree structure composed of nodes (containers) and leaves (data).
Below is a description of the layout of the containers and [arrays]:
-
opc
-
publisher
-
network_interface, (…)
-
-
subcriber
-
network_interface, (…)
-
-
-
fip
-
name, (…), [vars]
-
name, (…)
-
-
bus_arbiter
-
enable, (…), [cycles]
-
[windows]
-
type, (…)
-
[requests] (only inside periodic window)
-
type, (…)
-
-
-
-
-
Name | Status | Type | Description | ||||
---|---|---|---|---|---|---|---|
opc |
required |
object |
OPC-UA configuration container |
||||
publisher |
required |
object |
OPC-UA PUB container |
||||
network_interface |
required |
string |
Publisher network interface |
||||
url |
required |
string |
Publisher MAC address |
||||
publisher_id |
required |
u16 |
Unique ID for a publisher with the message-oriented middleware |
||||
dataset_writer_id |
required |
u16 |
The dataset writer Id identifies the dataset writer in the writer group. It is unique across all dataset writers for a publisher ID |
||||
writer_group_id |
required |
u16 |
Writer Group Identifier. It is unique across writer groups for a publisher ID |
||||
publish_period_us |
required |
u32 |
Publisher cycle time in microseconds |
||||
subscriber |
required |
object |
OPC-UA SUB container |
||||
network_interface |
required |
string |
Subscriber network interface |
||||
url |
required |
string |
Subscriber MAC address |
||||
publisher_id |
required |
u16 |
Unique ID of the publisher to subscribe to |
||||
dataset_writer_id |
required |
u16 |
Dataset Writer ID to subscribe to |
||||
writer_group_id |
required |
u16 |
Writer Group ID to subscribe to |
||||
fip |
required |
object |
FIP configuration container |
||||
name |
optional |
string |
FIP node identification label |
||||
address |
required |
u8 |
FIP node physical address |
||||
segment |
required |
u8 |
FIP node segment number |
||||
frame_type |
required |
enum |
Values:
|
||||
bitrate |
required |
enum |
Values:
|
||||
turn_around_us |
required |
u32 |
Turn-Around time in microseconds |
||||
silence_us |
required |
u32 |
Silence time in microseconds |
||||
vars |
optional |
array |
Array of FIP variables configuration |
||||
name |
optional |
string |
FIP variable identification label |
||||
type |
required |
enum |
Values:
|
||||
id |
required |
u16 |
Unique identifier of a FIP variable |
||||
payload_bsz |
required |
u16 |
FIP variable payload size in bytes
|
||||
refreshment |
optional |
boolean |
Enable/Disable the production status byte (refreshement) for the FIP variable |
||||
refresh_us |
optional |
u32 |
Refreshment period in microseconds
|
||||
promptness |
optional |
boolean |
Enable/Disable the promptness status for a FIP consumed variable
|
||||
prompt_us |
optional |
u32 |
Promptness period in microseconds
|
||||
event |
optional |
enum |
Values:
|
||||
bus_arbiter |
optional |
object |
FIP master configuration container |
||||
enable |
required |
boolean |
Enable/Disable bus arbiter (master) capability |
||||
priority |
required |
u8 |
Should be in [0..15] range (0: highest priority) |
||||
cycles |
required |
array |
Array of Fip master macrocycles |
||||
windows |
required |
array |
Array of BA windows inside a macrocycle |
||||
type |
required |
enum |
Values:
|
||||
end_ustime |
optional |
u32 |
End time of the aperiodic BA window in microseconds
|
||||
requests |
optional |
array |
|
||||
type |
required |
enum |
Values:
|
||||
id |
required |
u16 |
FIP identifier to request during a periodic BA window |
6. Ping-Pong example
The "ping-pong" example is a test between a FIP master node (BA) and an OPC-UA node (client) where an UAFIP TSN gateway acts as an intermediary between the two heterogeneous nodes.
-
The FIP node increments a PING counter (64-bit value) at each macrocycle and produces a RP_DAT[0x3800] FIP frame with the updated counter to the FIP network.
-
This FIP data is consumed by the UAFIP gateway and is transfered via an OPC-UA publisher to the Ethernet network.
-
A remote OPC-UA node (client) subscribes to the gateway’s publisher and returns this value by republishing it (PONG counter).
-
The gateway in turn consumes this OPC value via a subscriber and produces a RP_DAT[0x3801] FIP frame with the updated PONG counter (64-bit value).
-
At each new macrocycle, the FIP node consumes the RP_DAT[0x3801] data [PONG] and compares it to the new value of the RP_DAT[0x3800] variable to be produced [PING].
If the real time capacity of the whole system is maintained, the difference between the consumed and produced counters should never exceed 1 unit for the FIP node.
In order to avoid cycle losses due to initial conditions, the starting order of the different nodes is important! |
- UAFIP_GATEWAY
uno1483g@uno1483g:~/Documents/powerfip/tools/uafip_tsn_gateway$ sudo ./start_rt_gateway.sh "./uafipd -f ./config/ping/uno/sta1.json"
|
- UA_PONG
-
Then, launch the OPC node which will play the PONG role with the command below:
uno2372g@uno2372g:~/Documents/powerfip/tools/uafip_tsn_gateway/tools/ua_pong$ sudo ./ua_pong
Make sure that the settings defined in the powerfip/tools/uafip_tsn_gateway/tools/ua_pong/test.h header file match those of the UAFIP gateway. |
- FIP_PING
-
Finally, launch the FIP node which will play the PING role with the command below:
pctest@pctest:~/Documents/powerfip/tools/pwrfip_ping$ sudo ./pwrfip_ping -i 1 -n 0 -c 10000
Here we use the parameters:
|
If all goes well, the bus arbiter should start and the ping/pong ok
counter start to increment. No loss of FIP macrocycle occurs while the nok
counter remains at 0.
Appendix A: JSON files examples
A.1. Ping (FIP Node 0)
{
"opc": {
"publisher" : {
"network_interface":"enp2s0",
"url":"opc.eth://74-fe-48-30-39-eb:8.3",
"publisher_id":"5670",
"dataset_writer_id":"62541",
"writer_group_id":"100",
"publish_period_us":"250"
},
"subscriber" : {
"network_interface":"enp2s0",
"url":"opc.eth://74-fe-48-42-c2-e4:8.3",
"publisher_id":"5671",
"dataset_writer_id":"62541",
"writer_group_id":"101"
}
},
"fip": {
"name":"FIP node 0",
"address":"0",
"segment":"0",
"frame_type":"worldfip",
"bitrate":"1mbps",
"turn_around_us":"30",
"silence_us":"150",
"vars": [
{
"name":"[0x3800]",
"type":"prod",
"id":"0x3800",
"payload_bsz":"8",
"refreshment":"yes",
"refresh_us":"7500",
"event":"none"
},
{
"name":"[0x3801]",
"type":"cons",
"id":"0x3801",
"payload_bsz":"8",
"refreshment":"yes",
"promptness":"yes",
"prompt_us":"7500",
"event":"none"
},
{
"name":"[0xFF00]",
"type":"sync",
"id":"0xFF00",
"event":"r_op"
},
{
"name":"[0xFF01]",
"type":"sync",
"id":"0xFF01",
"event":"w_op"
}
],
"bus_arbiter": {
"enable":"yes",
"priority":"1",
"cycles": [
{
"windows": [
{
"type":"periodic",
"requests": [
{
"type":"id_dat",
"id":"0x3800"
},
{
"type":"id_dat",
"id":"0xFF00"
}
]
},
{
"type":"wait",
"end_ustime":"2500"
},
{
"type":"periodic",
"requests": [
{
"type":"id_dat",
"id":"0x3801"
},
{
"type":"id_dat",
"id":"0xFF01"
}
]
},
{
"type":"wait",
"end_ustime":"5000"
}
]
}
]
}
}
}
A.2. Pong (FIP Node 1)
{
"opc": {
"publisher" : {
"network_interface":"enp7s0",
"url":"opc.eth://74-fe-48-42-c2-e4:8.3",
"publisher_id":"5671",
"dataset_writer_id":"62541",
"writer_group_id":"101",
"publish_period_us":"250"
},
"subscriber" : {
"network_interface":"enp7s0",
"url":"opc.eth://74-fe-48-30-39-eb:8.3",
"publisher_id":"5670",
"dataset_writer_id":"62541",
"writer_group_id":"100"
}
},
"fip": {
"name":"FIP node 1",
"address":"1",
"segment":"0",
"frame_type":"worldfip",
"bitrate":"1mbps",
"turn_around_us":"30",
"silence_us":"150",
"vars": [
{
"name":"[0x3800]",
"type":"cons",
"id":"0x3800",
"payload_bsz":"8",
"refreshment":"yes",
"promptness":"yes",
"prompt_us":"7500",
"event":"none"
},
{
"name":"[0x3801]",
"type":"prod",
"id":"0x3801",
"payload_bsz":"8",
"refreshment":"yes",
"refresh_us":"7500",
"event":"none"
},
{
"name":"[0xFF00]",
"type":"sync",
"id":"0xFF00",
"event":"w_op"
},
{
"name":"[0xFF01]",
"type":"sync",
"id":"0xFF01",
"event":"r_op"
}
]
}
}
Appendix B: Glossary of acronyms
BA |
Bus Arbiter |
FIP |
Factory Instrumentation Protocol |
OPC-UA |
Open Platform Communications Unified Architecture |
TSN |
Time-Sensitive Networking |
Appendix C: Revision History
Revision | Changes | Authors | Date |
---|---|---|---|
1.0.0 |
First version |
MC |
2022-06-21 |