- Main
- Tutorials
- Library
- Samples

How to build IO sample for Embedded OPC UA Server

Tutorial to add IO nodes for Embedded OPC UA Server that represents ioThinx-4533 device IO modules status.

## Table of Contents

    * Beginners Guide
    * User Scenario 1
    * User Scenario 2
    * User Scenario 3
    * Compile Guide
    * Appendix and FAQ

## Beginners Guide

If you are a total beginner to this, start here!

1. make sure your firmware version is above or equal to V1.1
   1 kversion
2. download embeddedopcuaserver-plugin-dev_<version>_armhf.deb to 4533
   - embeddedopcuaserver-plugin-dev_1.1.0_armhf.deb
3. install embeddedopcuaserver-plugin-dev_<version>_armhf.deb
   1 dpkg -i embeddedopcuaserver-plugin-dev_<version>_armhf.deb
4. Check io.conf under `/usr/local/bin/embeddedopcuaserver/Config` fits your ioThinx-4533 IO modules layout
5. Execute `systemctl start embedded-opcua-server` to run up server
6. Start using OPC UA client to browse IO data
7. Other general usage of OPC UA Server, please reference `How to build node sample for Embedded OPC UA Server` tutorial

## User Scenario

### User Scenario 1: Add an diStatus node(config only)

Add Slot 1/Channel 4 diStatus to OPC UA Server, just modify io.conf under `/usr/local/bin/embeddedopcuaserver/Config` to

1 {
2 "slot": [
3 null,
4 [
5 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 10, "name": "diMode", "description": "di mode"},
6 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
7 {"ch": 1, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
8 {"ch": 2, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
9 {"ch": 3, "access": 3, "variant_type": 3, "func_type": 12, "name": "diCounterStatus", "description": "di counter status"},
10 {"ch": 3, "access": 3, "variant_type": 7, "func_type": 13, "name": "diCounterValue", "description": "di counter value"},
11 {"ch": 4, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"}
12 ]
13 ]
14 }

Please notice:

- slot 1 must be 45MR-1600/45MR-1601/45MR-2606
- follow json format to add your own node

### User Scenario 2: Change module slot order(config only)

Adjust io.config to follow ioThinx-4533 real hardware modules order. Now slot 1 is 45MR-1600 and slot 2 is 45MR-2600 which the io.conf may looks like below.

1 {
2 "slot": [
3 null,
4 [
5 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 10, "name": "diMode", "description": "di mode"},
6 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
7 {"ch": 1, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
8 {"ch": 2, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
9 {"ch": 3, "access": 3, "variant_type": 3, "func_type": 12, "name": "diCounterStatus", "description": "di counter status"},
10 {"ch": 3, "access": 3, "variant_type": 7, "func_type": 13, "name": "diCounterValue", "description": "di counter value"},
11 {"ch": 4, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"}
12 ],
13 [
14 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 20, "name": "doMode", "description": "do mode"},
15 {"ch": 0, "access": 3, "variant_type": 3, "func_type": 21, "name": "doStatus", "description": "do status"},
16 {"ch": 1, "access": 3, "variant_type": 3, "func_type": 21, "name": "doStatus", "description": "do status"},
17 {"ch": 2, "access": 3, "variant_type": 3, "func_type": 22, "name": "doPulseStatus", "description": "do pluse status"},
18 {"ch": 2, "access": 3, "variant_type": 7, "func_type": 23, "name": "doPulseCount", "description": "do pluse count"},
19 {"ch": 3, "access": 3, "variant_type": 3, "func_type": 22, "name": "doPulseStatus", "description": "do pluse status"},
20 {"ch": 3, "access": 3, "variant_type": 7, "func_type": 23, "name": "doPulseCount", "description": "do pluse count"}
21 ]
22 ]
23 }

Now switch slot 1 and slot 2 and insert an 45MR-2404 in between which make ioThinx-4533 hardware modules order into slot 1: 45MR-2600, slot 2: 45MR-2404, slot 3: 45MR-1600 and if we don't want to monitor 45MR-2404, the io.conf may replace like below.

1 {
2 "slot": [
3 null,
4 [
5 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 20, "name": "doMode", "description": "do mode"},
6 {"ch": 0, "access": 3, "variant_type": 3, "func_type": 21, "name": "doStatus", "description": "do status"},
7 {"ch": 1, "access": 3, "variant_type": 3, "func_type": 21, "name": "doStatus", "description": "do status"},
8 {"ch": 2, "access": 3, "variant_type": 3, "func_type": 22, "name": "doPulseStatus", "description": "do pluse status"},
9 {"ch": 2, "access": 3, "variant_type": 7, "func_type": 23, "name": "doPulseCount", "description": "do pluse count"},
10 {"ch": 3, "access": 3, "variant_type": 3, "func_type": 22, "name": "doPulseStatus", "description": "do pluse status"},
11 {"ch": 3, "access": 3, "variant_type": 7, "func_type": 23, "name": "doPulseCount", "description": "do pluse count"}
12 ],
13 null,
14 [
15 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 10, "name": "diMode", "description": "di mode"},
16 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
17 {"ch": 1, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
18 {"ch": 2, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"},
19 {"ch": 3, "access": 3, "variant_type": 3, "func_type": 12, "name": "diCounterStatus", "description": "di counter status"},
20 {"ch": 3, "access": 3, "variant_type": 7, "func_type": 13, "name": "diCounterValue", "description": "di counter value"},
21 {"ch": 4, "access": 1, "variant_type": 3, "func_type": 11, "name": "diStatus", "description": "di status"}
22 ]
23 ]
24 }

Please notice:

- slot order is start from 0 and slot 0 means the node from CPU head
- if this io module is no need to be monitor by OPC UA Server, then give an null in the io.conf

### User Scenario 3: Add new IO func_type (coding & config)

Define new FUNC_TYPE in `DataSet.h` and add read/write function case in `io_control_read()` and `io_control_write()` of `io_node_operator.c`. If this io data need to be configured first during the initialization, then configure function should be add in `add_io_nodes()`.

Modify `io.conf` to load hello do info and add new func_type 99 as below.

1 {
2 "slot": [
3 null,
4 [
5 {"ch": 0, "access": 1, "variant_type": 3, "func_type": 20, "name": "doMode", "description": "do mode"},
6 {"ch": 3, "access": 1, "variant_type": 3, "func_type": 99, "name": "helloDO", "description": "hello do"}
7 ]
8 ]
9 }

## Compile Guide

### Using native toolchain

1. Copy sample.tar.gz contained in the programing guide from your PC to ioThinx:
   For example, if the IP address of the ioThinx is "192.168.127.254", use the following command:
   1 user@Linux:~$ scp sample.tar.gz moxa@192.168.127.254:~
2. Extract the sample code
   1 moxa@Moxa:~$ tar zxvf sample.tar.gz
3. Build IO sample code
   1 moxa@Moxa:~$ cd sample/mx_node_sdk/c/io
   Build via cmake & make
   1 moxa@Moxa:~/sample/mx_node_sdk/c/io$ cmake .
   2 moxa@Moxa:~/sample/mx_node_sdk/c/io$ make
4. Move `liboperator.so` to `OperatorLib`
   1 moxa@Moxa:~/sample/mx_node_sdk/c/io$ cp liboperator.so /usr/local/bin/embeddedopcuaserver/OperatorLib/io/
5. Restart server via systemd
   1 moxa@Moxa:~/sample/mx_node_sdk/c/io$ sudo systemctl restart embedded-opcua-server

### Using cross toolchain

1. Copy sample.tar.gz contained in the programing guide to your PC
2. Extract the sample code
   1 user@Linux:~$ tar zxvf sample.tar.gz
3. Build IO sample code
   1 user@Linux:~$ cd sample/mx_node_sdk/c/io
   Build via cmake & make
   1 user@Linux:~/sample/mx_node_sdk/c/io$ cmake . -DCMAKE_TOOLCHAIN_FILE=toolchain-cross.cmake
   2 user@Linux:~/sample/mx_node_sdk/c/io$ make
4. Copy the sample program to ioThinx:
   For example, if the IP address of the ioThinx is "192.168.127.254", use the following command:
   1 user@Linux:~/sample/mx_node_sdk/c/io$ scp liboperator.so moxa@192.168.127.254:~
5. Move `liboperator.so` to `OperatorLib`
   1 moxa@Moxa:~$ cp liboperator.so /usr/local/bin/embeddedopcuaserver/OperatorLib/io/
6. Restart server via systemd
   1 moxa@Moxa:~$ sudo systemctl restart embedded-opcua-server

## Appendix and FAQ

### Programming Source Code

- example
  - quick example to show 2 data nodes onto OPC UA Server
- demo
  - demo sample shows all data types nodes onto OPC UA Server
- io
  - io sample shows io data nodes onto OPC UA Server

1 mx_node_sdk
2 c
3 ├── demo
4 │ ├── CMakeLists.txt
5 │ ├── demo_node_operator.c
6 │ ├── toolchain-cross.cmake
7 │ └── toolchain-native.cmake
8 ├── example
9 │ ├── CMakeLists.txt
10 │ ├── sample_node_operator.c
11 │ ├── toolchain-cross.cmake
12 │ └── toolchain-native.cmake
13 └── io
14 ├── cJSON.c
15 ├── cJSON.h
16 ├── CMakeLists.txt
17 ├── DataSet.h
18 ├── io_node_operator.c
19 ├── toolchain-cross.cmake
20 └── toolchain-native.cmake

### IO Config Format

Explain how to use io.conf to fit your usage

- slot
  - follow array index to represent which slot
- access
  - Node's access right. Please reference MX_NODE_ACCESS_RIGHT
- variant_type
  - MX_Variant's data type. please reference MX_NODE_VALUE_TYPE
- func_type
  - function type which is define in DataSet.h

### Execute OPC UA server

1 moxa@Moxa:~$ sudo systemctl start embedded-opcua-server
2 moxa@Moxa:~$ sudo systemctl stop embedded-opcua-server
3 moxa@Moxa:~$ sudo systemctl restart embedded-opcua-server
4 moxa@Moxa:~$ sudo systemctl status embedded-opcua-server
5 moxa@Moxa:~$ sudo systemctl disable embedded-opcua-server
6 moxa@Moxa:~$ sudo systemctl enable embedded-opcua-server

### Add/Delete OPC UA server account

1 moxa@Moxa:~$ sudo opcuauser --add moxa
2 moxa@Moxa:~$ sudo opcuauser --del moxa
3
4 moxa@Moxa:~$ sudo opcuauser --help
5 Usage: opcuauser [options] username
6
7 Options:
8 -a, --add Add user account
9 -d, --del Delete user account
10 -c, --change Change user password
11 -v, --verion Show version
12 -h, --help Show help
13
14 Example:
15 opcuauser Add user account
16 opcuauser username Change user password
17 opcuauser --help Show help

Please notice that embedded-opcua-server should be reboot to take effect of user account list. Current user account list can be verify by checking `/etc/opcuauser_passwd`.

### Anonymous user account setting

Default Anonymous user account is enable, use `opcuauser` command to add or delete anonymous user will not change anything. If OPC UA Server want to delete anonymous user account, please move to file `/usr/local/bin/embeddedopcuaserver/OPCUAServerData/Config/OPCUA.json`

1 ...
2 "SupportAnonymous":1,
3 ...

Modify setting to disable,

1 ...
2 "SupportAnonymous":0,
3 ...

### Debug

log: Systemd service syslog must be active(running)

- if non-active
  - check `systemctl status embedded-opcua-server`.
- if active
  - check `/var/log/syslog` to see all debug info during server enable.

### Limitation

1. This OPC UA Server can only be executed on ioThinx-4533-LX.
2. Please backup `/usr/local/bin/embeddedopcuaserver/OPCUAServerData/Config` before FWR upgrade.
3. Use Lan1 internet setting as default connection setting.