Skip to content

Kelvin Modbus Bridge App

Modbus is a serial communication protocol developed by Modicon for use with its programmable logic controllers (PLCs). Modbus is often used to connect a supervisory computer with a remote terminal unit (RTU) in supervisory control and data acquisition (SCADA) systems. Modbus is transmitted over serial lines between devices.

The Kelvin Modbus Bridge App is a Kelvin App runs on KICS. It is a data connector application that connects and communicate with devices running Modbus protocol.

Prerequisite:

Retrieving the application

The application should be available on your platform's Application Registry. We can search the application registry with the command:

kelvin appregistry search modbus
[kelvin.sdk][2021-07-06 20:04:54][I] Searching applications that match "modbus"
[kelvin.sdk][2021-07-06 20:04:56][I]
*************************** Applications ***************************
+-----------------------------+------------------------------------+--------+------------------+----------------------------------+
| Name                        | Title                              | Type   | Latest Version   | Updated                          |
|-----------------------------+------------------------------------+--------+------------------+----------------------------------|
| kelvin-bridge-modbus-client | Kelvin Modbus Protocol Application | bridge | 2.0.7            | 2021-07-01 14:37:23.937756+00:00 |
+-----------------------------+------------------------------------+--------+------------------+----------------------------------+

In this case we will be using the application named kelvin-bridge-modbus-client and the latest version is 2.0.7.

Note

Use the app version found in your app registry list.

kelvin appregistry download kelvin-bridge-modbus-client:2.0.7
[kelvin.sdk][2021-07-06 20:16:49][I] Downloading application "kelvin-bridge-modbus-client:2.0.7"
[kelvin.sdk][2021-07-06 20:16:54][R] Successfully logged on registry "alpha.kelvininc.com:5000"
[kelvin.sdk][2021-07-06 20:16:54][I] Pulling "kelvin-bridge-modbus-client:2.0.7" from "alpha.kelvininc.com"
...
[kelvin.sdk][2021-07-06 20:17:01][R] Successfully pulled "kelvin-bridge-modbus-client:2.0.7" from "alpha.kelvininc.com"
[kelvin.sdk][2021-07-06 20:17:01][R] Application "kelvin-bridge-modbus-client:2.0.7" successfully downloaded to the local registry
[kelvin.sdk][2021-07-06 20:17:01][R] Use `kelvin app images unpack` to extract its contents

As suggested by KSDK, let's unpack the image. The image name follows the rule <registry address>/<app name>:<version>, but the best way to find out is by using KSDK to list the available images.

ksdk app images list
[kelvin.sdk][2021-07-06 20:18:07][I]
*************************** Existing Apps ***************************
+------------------------------------------------------------+---------------------+
| Applications                                               | Created             |
|------------------------------------------------------------+---------------------|
| alpha.kelvininc.com:5000/kelvin-bridge-modbus-client:2.0.7 | 2021-07-01 15:37:03 |
+------------------------------------------------------------+---------------------+

Now that we know the app name, we can unpack it to a directory of our choosing.

kelvin app images unpack alpha.kelvininc.com:5000/kelvin-bridge-modbus-client:2.0.7 modbus
[kelvin.sdk][2021-07-06 20:19:50][I] Unpacking application "kelvin-bridge-modbus-client:2.0.7" to directory "modbus"
[kelvin.sdk][2021-07-06 20:19:53][R] Application "kelvin-bridge-modbus-client:2.0.7" successfully unpacked to "modbus"

You should now have the application unpacked to the directory modbus/ and we can proceed to its configuration.

Configuration

The configuration for the whole application and deployment sits on the app.yaml file.

Metrics mapping

The metrics map will map the Modbus addresses to the inputs and outputs of the application.

Each metric has the following general configurations:

Name Description Default/Required
name The metric name Required
asset_name The asset name associated to the metric name Required
data_type The data type of the metric Required
access Whether the metric is RO (read-only) or RW (read-write) Default: RO
Name Applicable to Description
protocol_type All The primitive data type
address All The Modbus' register address to be read
polling_rate All The register polling period
stroke_number Stroke number Indicates the address is the stroke number
buffer_address Downhole/surface The first address of the buffer
buffer_size Downhole/surface The size of the buffer

Example

Below is an example of a Modbus Bridge application's metrics_map configuration for dynacards, the one present in the downloaded configuration file.

app:
  bridge:
    # ...

    metrics_map:
    - name: stroke_number
      asset_name: modbus_simulator
      data_type: raw.uint32
      configuration:
        address: 2518
        protocol_type: uint32
        polling_rate: 15
        stroke_number: true
    - name: downhole
      asset_name: modbus_simulator
      data_type: raw.dynacard
      configuration:
        protocol_type: downhole
        polling_rate: 15
        buffer_address: 46165
        buffer_size: 200
    - name: surface
      asset_name: modbus_simulator
      data_type: raw.dynacard
      configuration:
        protocol_type: surface
        polling_rate: 15
        buffer_address: 45756
        buffer_size: 400

General

General configuration related to the Modbus connection.

Name Description
minus_offset Modbus register offset (1 is the most commonly used value)
timestamp_sync Timestamp synchronization across all metrics read within the same polling period (true or false)
chunk_size Maximum amount of registers to read per request (125 for Serial and 123 for TCP)
debug libmodbus debug logs (true or false)

Example

app:
  bridge:

    configuration:
      minus_offset: 0
      chunk_size: 120
      timestamp_sync: false
      debug: false

      connection:
        # ...

Connection

Modbus connection configuration that can be applied to both the TCP and serial connection.

Common

Name Description
type Establish the connection to ModBus via "tcp" or "serial"
slave_id Slave ID of the remote device to talk in Master mode (1-255)
timeout Timeout interval (in seconds) used to wait for a response
reconnect_delay Delay (in seconds) before attempt to reconnect to the Modbus Slave
retry_attempts Read requests maximum retry attempts
retry_delay Delay (in seconds) between read requests retry attempts

Example

app:
  bridge:

    configuration:
      # ...

      connection:
        slave_id: 255
        timeout: 5
        reconnect_delay: 15
        retry_attempts: 0
        retry_delay: 0

        type: tcp
          # ...

TCP

To connect to Modbus via TCP, the connection type must be tcp.

Name Description
ip IP address of the server to which the client wants to establish a connection
port Port to use (Modbus TCP default is 502)

The ip field can be an IP address as well as an host name. Inside both KICS and KSDK's emulation environment, that host name may point to another running application. That name is <app-name/workload-name>.app.

Example

app:
  bridge:

    configuration:
      connection:
        # ...

        type: tcp
        tcp:
          ip: kelvin-modbus-simulator.app
          port: 502

Serial

To connect to Modbus via TCP, the connection type must be serial.

Name Description
serial_port Name of the serial port handled by the OS, i.e. /dev/ttyS0 or /dev/ttyUSB0
baudrate Baud rate of the communication, i.e. 9600, 19200, 57600, 115200, etc
data_bits Number of bits of data, the allowed values are 5, 6, 7 and 8
parity N for none, E for even, and O for odd
stop_bits The bits of stop, the allowed values are 1 and 2
serial_mode Serial line communication, RS232 and RS485
rts_mode (RS485 only) Mode to communicate on a RS485 serial bus, UP (default) or DOWN
rts_delay (RS485 only) Request to send delay period (in seconds)
error_recovery If set to true, it will attempt to reconnect when disconnected (not recommended for slave mode)

Example

app:
  bridge:
    configuration:
      connection:
        # ...

        type: serial
        serial:
          serial_port: /dev/ttyXRUSB1
          baudrate: 19200
          parity: N
          data_bits: 8
          stop_bits: 1

Building and testing the application

Every time we build an application, we need to bump its version so that the server knows we have a different image.

Info

Because this is a pre-built application, there is no need to build and upload it unless we want to change the default configuration. In most cases there's only need to deploy the new configuration.

In our example, let's bump it to 2.1.0.

Example

spec_version: 2.0.0

info:
  name: kelvin-bridge-modbus-client
  title: Kelvin Modbus Protocol Application
  version: 2.1.0

Additionally, to test the actual communication, we'll be using a Modbus Simulator application that allows us to connect this application to it. The default configuration is already prepared to connect to the simulator.

We can do that by downloading and starting the application like so:

ksdk appregistry download kelvin-modbus-simulator:1.2.0 \
    && ksdk emulation start alpha.kelvininc.com:5000/kelvin-modbus-simulator:1.2.0

Tip

Remember that you can list the downloaded images by executing kelvin app images list.

Warning

On Windows systems, the simulator does not work when using Docker with the WSL 2 backend.

To build the application, we use KSDK.

ksdk app build
[kelvin.sdk][2021-07-06 22:46:30][I] Assessing basic application info..
[kelvin.sdk][2021-07-06 22:46:30][I] Valid schema available locally. Using cached version (~/.config/kelvin/schemas/2.0.0.json)
[kelvin.sdk][2021-07-06 22:46:32][I] Building "Bridge type" application "kelvin-bridge-modbus-client"
[kelvin.sdk][2021-07-06 22:46:33][I] Processing application data types..
[kelvin.sdk][2021-07-06 22:46:33][I] Loading all data type files from directory "~/.config/kelvin/datatypes"
[kelvin.sdk][2021-07-06 22:46:33][R] 13 data types loaded from directory "~/.config/kelvin/datatypes"
[kelvin.sdk][2021-07-06 22:46:33][I] Retrieving data types for "kelvin-bridge-modbus-client"
[kelvin.sdk][2021-07-06 22:46:34][R] Successfully logged on registry "alpha.kelvininc.com:5000"
[kelvin.sdk][2021-07-06 22:46:34][I] Pulling "alpha.kelvininc.com:5000/kelvin-core-cpp:7.0.2-aeaba1b" from "alpha.kelvininc.com"
[kelvin.sdk][2021-07-06 22:46:36][R] Successfully pulled "alpha.kelvininc.com:5000/kelvin-core-cpp:7.0.2-aeaba1b" from "alpha.kelvininc.com"
[kelvin.sdk][2021-07-06 22:46:36][I] Pulling "alpha.kelvininc.com:5000/data-model-builder:6.0.1-081bf89" from "alpha.kelvininc.com"
[kelvin.sdk][2021-07-06 22:46:37][R] Successfully pulled "alpha.kelvininc.com:5000/data-model-builder:6.0.1-081bf89" from "alpha.kelvininc.com"
[kelvin.sdk][2021-07-06 22:46:37][I] Building new image for "kelvin-bridge-modbus-client:2.0.7". Please wait..
[kelvin.sdk][2021-07-06 22:47:16][R] Image successfully built: "kelvin-bridge-modbus-client:2.0.7"

Once the application is built, we can start it locally in an emulation environment:

$ kelvin emulation start --app-config app.yaml --show-logs

Tip

You can stop following the emulation logs with Ctrl+C.

If the application connects to the Modbus Simulator, it should show a log from the storage system for each emitted metric, and can also show logs from the bridge app itself:

Success

[kelvin.sdk][2021-07-06 22:27:34][R] Emulation configuration loaded from: "app.yaml"
[kelvin.sdk][2021-07-06 22:27:35][I] Valid schema available locally. Using cached version (~/.config/kelvin/schemas/2.0.0.json)
[kelvin.sdk][2021-07-06 22:27:38][R] Kelvin Emulation System is online
[kelvin.sdk][2021-07-06 22:27:38][I] Loading configuration and starting the application "kelvin-bridge-modbus-client:2.0.7"
[kelvin.sdk][2021-07-06 22:27:38][I] Valid schema available locally. Using cached version (~/.config/kelvin/schemas/2.0.0.json)
[kelvin.sdk][2021-07-06 22:27:40][R] Overriding existing configuration with "app.yaml"
[kelvin.sdk][2021-07-06 22:27:41][R] Application successfully launched: "kelvin-bridge-modbus-client:2.0.7"
[ApplicationEngine.cpp:  26:I] - Loading configuration file: /opt/kelvin/app/app.yaml
[        Pipeline.cpp:  73:I] - Loading shared object: kelvin_bridge_modbus_client/kelvin_bridge_modbus_client.so (kelvin-bridge-modbus-client) - SUCCESS
[        Pipeline.cpp: 188:I] - Loading kelvin-bridge-modbus-client Refinery input modules:
[        Pipeline.cpp: 193:I] - Loading kelvin-bridge-modbus-client Refinery output modules:
[        Pipeline.cpp: 198:I] - Loading kelvin-bridge-modbus-client Gatekeeper input modules
[        Pipeline.cpp: 203:I] - Loading kelvin-bridge-modbus-client Gatekeeper output modules
[  StorageManager.cpp:  27:I] - Using eXtremeDB storage backend
[ExtremeDbBackend.cpp: 277:I] - [config] Commit policy: MCO_COMMIT_SYNC_FLUSH // Transaction log type: REDO_LOG
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 0 // Memory type: MCO_MEMORY_CONV // Assignment: MCO_MEMORY_ASSIGN_DATABASE // Size: 2097152 // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 1 // Memory type: MCO_MEMORY_CONV // Assignment: MCO_MEMORY_ASSIGN_CACHE // Size: 8388608 // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 2 // Memory type: MCO_MEMORY_FILE // Assignment: MCO_MEMORY_ASSIGN_PERSISTENT // Filename: ./storage/kelvindb_v3.dbs // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 3 // Memory type: MCO_MEMORY_FILE // Assignment: MCO_MEMORY_ASSIGN_LOG // Filename: ./storage/kelvindb_v3.log // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 258:I] - eXtremeDB is running in PAID LICENSE MODE
[ExtremeDbBackend.cpp: 260:I] - eXtremeDB version: interim build eXtremeDB_8.1_1800, rev.25973
[ExtremeDbBackend.cpp:  30:I] - eXtremeDB started successfully.
[   UploadManager.cpp:  28:I] - The UploadManager's worker will not start because it's either disabled or has no address configured
[          Poller.cpp:  70:I] - Successful poller: 0.100000 secs (fd: 7)
[       DataModel.hpp:  89:I] - kelvin-bridge-modbus-client initialize.
[          Poller.cpp:  70:I] - Successful poller: 1.000000 secs (fd: 8)
[          Poller.cpp:  70:I] - Successful poller: 10.000000 secs (fd: 9)
[          Poller.cpp:  70:I] - Successful poller: 15.000000 secs (fd: 10)
[kelvin_bridge_modbus_client.cpp:1014:I] - Reading polling group starting with address 2518 of size 1
[kelvin_bridge_modbus_client.cpp:1084:I] - Reading dynacards associated with stroke number register 'stroke_number'
[ExtremeDbBackend.cpp: 418:I] - Registering scaled_example in rule-defined layout structure (VERTICAL)
[ExtremeDbBackend.cpp: 418:I] - Registering stroke_number in rule-defined layout structure (VERTICAL)
[ExtremeDbBackend.cpp: 421:I] - Registering surface in default layout structure (HORIZONTAL)
[ExtremeDbBackend.cpp: 421:I] - Registering downhole in default layout structure (HORIZONTAL)
[kelvin_bridge_modbus_client.cpp:1014:I] - Reading polling group starting with address 2518 of size 1
[kelvin_bridge_modbus_client.cpp:1084:I] - Reading dynacards associated with stroke number register 'stroke_number'
[kelvin_bridge_modbus_client.cpp:1014:I] - Reading polling group starting with address 2518 of size 1
[kelvin_bridge_modbus_client.cpp:1014:I] - Reading polling group starting with address 2518 of size 1
[kelvin_bridge_modbus_client.cpp:1084:I] - Reading dynacards associated with stroke number register 'stroke_number'
[kelvin_bridge_modbus_client.cpp:1014:I] - Reading polling group starting with address 2518 of size 1
...

Otherwise, if it fails, it will output errors such as:

Fail

[kelvin.sdk][2021-07-06 22:31:58][R] Emulation configuration loaded from: "app.yaml"
[kelvin.sdk][2021-07-06 22:31:59][I] Valid schema available locally. Using cached version (~/.config/kelvin/schemas/2.0.0.json)
[kelvin.sdk][2021-07-06 22:32:02][R] Kelvin Emulation System is online
[kelvin.sdk][2021-07-06 22:32:02][I] Loading configuration and starting the application "kelvin-bridge-modbus-client:2.0.7"
[kelvin.sdk][2021-07-06 22:32:02][I] Valid schema available locally. Using cached version (~/.config/kelvin/schemas/2.0.0.json)
[kelvin.sdk][2021-07-06 22:32:04][I] Stopping container "kelvin-bridge-modbus-client:2.0.7"
[kelvin.sdk][2021-07-06 22:32:06][R] Overriding existing configuration with "app.yaml"
[kelvin.sdk][2021-07-06 22:32:06][R] Application successfully launched: "kelvin-bridge-modbus-client:2.0.7"
[ApplicationEngine.cpp:  26:I] - Loading configuration file: /opt/kelvin/app/app.yaml
[        Pipeline.cpp:  73:I] - Loading shared object: kelvin_bridge_modbus_client/kelvin_bridge_modbus_client.so (kelvin-bridge-modbus-client) - SUCCESS
[        Pipeline.cpp: 188:I] - Loading kelvin-bridge-modbus-client Refinery input modules:
[        Pipeline.cpp: 193:I] - Loading kelvin-bridge-modbus-client Refinery output modules:
[        Pipeline.cpp: 198:I] - Loading kelvin-bridge-modbus-client Gatekeeper input modules
[        Pipeline.cpp: 203:I] - Loading kelvin-bridge-modbus-client Gatekeeper output modules
[  StorageManager.cpp:  27:I] - Using eXtremeDB storage backend
[ExtremeDbBackend.cpp: 277:I] - [config] Commit policy: MCO_COMMIT_SYNC_FLUSH // Transaction log type: REDO_LOG
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 0 // Memory type: MCO_MEMORY_CONV // Assignment: MCO_MEMORY_ASSIGN_DATABASE // Size: 2097152 // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 1 // Memory type: MCO_MEMORY_CONV // Assignment: MCO_MEMORY_ASSIGN_CACHE // Size: 8388608 // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 2 // Memory type: MCO_MEMORY_FILE // Assignment: MCO_MEMORY_ASSIGN_PERSISTENT // Filename: ./storage/kelvindb_v3.dbs // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 318:I] - [config] Dev: 3 // Memory type: MCO_MEMORY_FILE // Assignment: MCO_MEMORY_ASSIGN_LOG // Filename: ./storage/kelvindb_v3.log // Flags: MCO_FILE_OPEN_DEFAULT
[ExtremeDbBackend.cpp: 258:I] - eXtremeDB is running in PAID LICENSE MODE
[ExtremeDbBackend.cpp: 260:I] - eXtremeDB version: interim build eXtremeDB_8.1_1800, rev.25973
[ExtremeDbBackend.cpp:  30:I] - eXtremeDB started successfully.
[   UploadManager.cpp:  28:I] - The UploadManager's worker will not start because it's either disabled or has no address configured
[          Poller.cpp:  70:I] - Successful poller: 0.100000 secs (fd: 7)
[       DataModel.hpp:  89:I] - kelvin-bridge-modbus-client initialize.
[          Poller.cpp:  70:I] - Successful poller: 1.000000 secs (fd: 8)
[          Poller.cpp:  70:I] - Successful poller: 10.000000 secs (fd: 9)
[          Poller.cpp:  70:I] - Successful poller: 15.000000 secs (fd: 10)
[kelvin_bridge_modbus_client.cpp: 581:E] - # Modbus Connection ERROR # Connection refused
[kelvin_bridge_modbus_client.cpp: 582:E] - # Modbus # Retrying in 15 seconds...
...

Tip

As mentioned above, you don't need to rebuild the application when you change the configuration. If you want to emulate the application with a different configuration, you can use:

kelvin emulation start --show-logs --app-config app.yaml

Once all your tests are completed and you are happy with the result, you can stop the emulation with:

kelvin emulation stop