# Modbus Server

### Purpose

Purpose of this plugin is to instantiate a Modbus Server on the Ardexa edge device.

### Description

This plugin will read a configuration file, read Modbus registers from a local network, and then publish Modbus data locally. Data can be sourced either via RTU (local serial device) or from local IP addresses. The source Modbus registers can then be remapped to other registers. No data will be sent to the Ardexa cloud. This plugin is just to assist some plant owners that require Modbus data to be shared locally, via a Modbus Server.

### Configuration File

The configuration file is required to manage various aspects of the data collection and conversion. The file must be defined as shown below. There are a few rules to note:

1. The IP/Serial device address **MUST** be at the end of ALL the mbpoll commands
2. Mbpoll commands **MUST** start with `_cmd,` , followed by the mbpoll command which sources the data
3. All the other lines in the configuration file define registers and how they are to be treated.
4. All lines that start with an `#` character are treated as comments and will be ignored.
5. Each of the columns listed below **MUST** not be empty.
6. For `Type`, only the valid strings can be used: `INT16, INT32, FLOAT`.
7. If a `INT32` or `FLOAT` type is used, be sure to leave **2** sequential output registers free.
8. `Scale` can be any floating point number. It can only be used on FLOAT, but **NOT** INT16 or INT32 - which must be set to `1.0`.
9. `_cmd` is the commands to run. Only `modpoll` **or** the `mbpoll` commands can be used to read Modbus registers. `mbpoll` is preferred. The registers being collected **MUST MATCH** the registers listed in the configuration items. If not, you will have empty fields when running a `discover`. The IP address or serial device **MUST** appear as the last entry in the command.
10. Each `Output Register` must be unique. If there are any duplicates, then the script will report an error and not run.

```
#
_cmd, mbpoll -m tcp -a 1 -r 120 -c 50 -t 3 -1 -0 -p 502 192.168.1.10
_cmd, mbpoll -m rtu -a 2 -r 1 -c 2 -t 3 -1 -0 /dev/ttyACM3
#
# Output Register    Type,      Description,    Units,      Scale,  Source Endpoint        Input Register
1,                   INT16,     Pressure,       hPa,        150,    192.168.1.10,            123
2,                   INT32,     Direction,      degs,       1.1,    192.168.1.10,            130
8,                   INT16,     Flow Rate,      L/min,      3.5,    192.168.1.10,            145
9,                   FLOAT,     Radiation,      W/m^2,      1.0,    192.168.1.10,            155
105,                 INT16,     Temp,           C,          0.1,    /dev/ttyACM3,            2
206,                 INT16,     Humidity,       %,          0.01,   /dev/ttyACM3,            3
```

### Usage

This plugin uses a configuration file. The file **must** be named as `modbus_server.txt` in the directory: `/opt/ardexa/config/modbus-server`, and configured as discussed above. Once a single scenario has been defined, a service will be activated that waits for Modbus requests. The `task` element of the scenario will not do anything. The scenario must be activated as a `service`. Due to a technical limitation, in order to save some config data when initially setting up the server, such as a custom port, once the service is created you may have to turn on the `Test Mode` then SAVE, then turn it off and SAVE again. **DO NOT** create multiple scenarios for this service, it is not designed for this and will lead to undefined behaviour.

Also, you need to open a firewall port **manually** once the scenario has been activated. This is a design feature, to warrant that the user is aware of the risks of activating a service (server) on the local device. To open a firewall port on the Ardexa device run the following command: `sudo ufw allow 502`, where `502` should be the port number required. To close all ports, run the command: `sudo ufw default deny incoming`. The Modbus Server port is defined when setting up the server.

If a Modbus request is received, the data defined in the configuration file will be queried and served via the service. The plugin will make a maximum of 3 attempts to read the data, and will cache the results for a maximum of 10 seconds. This cache is to prevent the source endpoints from being overloaded with modbus queries. Queries may take a little longer, so a timeout of a few seconds may be required. If using the `mbpoll` client, this is achieved using the `-o` argument, such as the query: `mbpoll -m tcp -a 1 -r 11 -c 8 -t 4:float -1 -0 -o 8 -p 502 127.0.0.1` - which specifies a timeout of 8 seconds. **DO NOT** connect to this server with multiple clients, it can only handle one connection at a time. If one client has been connected for more than 30 seconds it will be automatically disconnected, this is to prevent dead connections blocking the server.

Clients querying the Modbus server must use address `1`. It will respond to either Input or Holding registers requests. When the service is started, or when running a `discover` command (see below), a PDF file will be written to `/opt/ardexa/config/modbus-server/modbus_server_mapping.pdf`, which will show the parameters of the configuration file, IP address, port and cache settings. It can be downloaded from the edit scenario page by selecting in the drop down.

Please note that for this service to be reliable a **static IP address** must be defined on the Ardexa device. In order to test the plugin is working ok, the service can be checked as follows: `systemctl status modbus-server-plugin.service`

### Test Mode

There is also a test mode, used to serve known values for debugging. this is activated by the switch in the scenario page. These registers and their value can then be checked with a script that is run with the `discover` button. The script and its configuration files, found in `/opt/ardexa/config/modbus-server`, can be downloaded and configured locally to test a remote server. There should be a `test_client.py` script, a `test_server.txt` configuration and some `.dict` lookups. There is also a `multiple` option in the config interface, this will add duplicate test registers in multiples of 100, for example if `multiple` is 2 then registers 1 and 101 will store the current hour.

Some of the test mode registers values are dynamic and others are fixed, as per the list below:

```
register, value

1,           hour
2,           minute
3,           second
4,           True
5,           32 bit unix time int
7,           32 bit uptime float
9,10,11      day of week character, eg. Thu
12,          0x1234
13,          0xabcd
14,          cpu usage 32 bit float
16,          ram usage 32 bit float
18,19,20,21  timezone characters, eg. AEST
22,23,24,25  ip addr numbers as 16 bit ints
26,          32 bit uptime int
30001,       0xff00
30002,       1.2345 32 bit float little endian
30004,       1.2345 32 bit float big endian
30006,       123456 32 bit int little endian
30008,       123456 32 bit int big endian
30010,       2 
30011,       4
30012,       8
30013,       16
30014,       0010000000000000
30015,       308
30016,       51
```
