Modbus RTU on Azure IoT Edge

Microsoft is serious about IoT Edge. Azure IoT Edge is now GA for a few months and just last week the version was bumped up to 1.0.1.

The same effort is put into Edge modules. Microsoft provides several modules for different protocols like OPC-UA and Modbus.

In the past I already wrote a couple of times about Modbus TCP in IoT Edge. It’s easy to use and reliable. The Microsoft Modbus module is already available in GA. And I even noticed a reference to “docker pull mcr.microsoft.com/azureiotedge/modbus:1.0”.

If you look deeper into the documentation, you can see that the module supports Modbus RTU too!

It’s always good to learn about other protocols so I arranged some hardware and started a journey.

Let’s see what we need to get started with Modbus RTU.

What do you need for Modbus RTU?

Modbus RTU is based on serial port communication. So there are a few hardware items you need to collect:

  • A Edge Device with a RS232 com port/serial port
  • A straight through serial cable (please check it’s not a null modem)
  • Maybe some gender changers?
  • A Modbus RTU device

Note: At this moment, Modbus RTU is only supported in IoT Edge running on Linux (with Linux containers). That’s the limit of Windows 10 and Docker, not about Iot Edge or the Modbus module!

So in Linux you need:

  • Support for the physical serial port (RS 232) as /dev/ttyS*
  • Sufficient rights to the serial port within Docker

With so many moving part, what could possible go wrong? Let’s check each part, step by step.

Modbus RTU hardware

For my tests, I use a combination of the Advantech Adam 4520 RS-232 converter for to RS-485 (and also RS-422) and the Adam 4168 eight channel relay output module which has eight relays (for read and write):

The Relay output module is great because I get visual feedback about what is happening with the module. But basically each Adam module should work.

The converter is just a RS232 converter and can support up to 32! Adam modules (like the Adam 4168). I can connect and configure to the actual modules behind the converter using Advantech tooling. But the most interesting thing is that each module will have a different Unit ID. Here I just programmed the module as Unit 1. All modules are seen as one big device but in every call the specific unit has to be provided.

I first tested the hardware module on Windows. I used a tool called CAS Modbus Scanner to find out if the hardware was working:

I reference my module as Device 1 (this is the name used for Units in this scanner).

I am actually able to read the coils of the module:

My hardware and cables are working. And I know the right settings to communicate. So I moved over to Linux.

Checking Modbus RTU in Linux

In Linux, you can check your serial ports using

ls -l /dev/ttyS*

This gives:

That are a lot of serial ports, but I have only two of them om my Linux machine. And I get this Permission denied messages…

Luckely, these commands show more information:

sudo stty -a -F /dev/ttyS0

sudo stty -a -F /dev/ttyS1

sudo stty -a -F /dev/ttyS2

I now get confirmation both the two ports on my Industrial PC are accessible in Linux:

The third commands gives an error so indeed only the first two port are active.

Now I need to see if I can do some Modbus RTU on Linux.

I was pointed to this ModPoll tool. It is available both under Windows and Linux and it works on Ubuntu 18.4 too!

The installation is a hassle (it’s only distributed as a Zip file) but once installed, it would run like a charm.

Let’s read the first relay on our module (unit address is 1 by default, the relay is coil 17):

./modpoll -b 9600 -p none -t 0 -r 17 /dev/ttyS0

Oops, this is a disappointment:

We still have no permissions to read data from the port, access is denied.

Let’s give some read/write permissions to the serial port:

sudo chmod a+rw /dev/ttyS0

And we try the Modpoll command again:

And now we get values out of our module using Modbus RTU on Linux Ubuntu. The rights did the trick.

This Modpoll tool is very useful. We can even switch one of the eight relays using:

./modpoll -b 9600 -p none -t 0 -r 18 /dev/ttyS0 1 // Strange, according to the manual, -t should be 1???
modpoll 3.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright (c) 2002-2013 proconX Pty Ltd
Visit http://www.modbusdriver.com for Modbus libraries and tools.
Protocol configuration: Modbus RTU
Slave configuration...: address = 1, start reference = 17, count = 1
Communication.........: /dev/ttyS0, 9600, 8, 1, none, t/o 1.00 s, poll rate 1000 ms
Data type.............: discrete output (coil)
Written 1 reference.

This activates the second relay (coil 18 set to 1).

So we are confident that we have the right permissions. Let’s move over to Docker.

Modbus RTU on Azure IoT Edge

In the Azure Portal, we configure the Modbus module for RTU:

We need to expose the comport in Linux to the Docker container. I just use the same name within the container:

{
  "HostConfig": {
    "Devices": [
      {
        "PathOnHost": "/dev/ttyS0", 
        "PathInContainer": "/dev/ttyS0",
        "CgroupPermissions": "rwm"
      }
    ]
  }
}

And we have to supply the Module Twin:

{
  "properties.desired": {
    "PublishInterval": "1000",
    "SlaveConfigs": {
      "Slave01": {
        "SlaveConnection": "/dev/ttyS0",
        "BaudRate": "9600",
        "DataBits": "8",
        "StopBits": "1",
        "Parity": "NONE",
        "FlowControl": "NONE",
        "HwId": "Adam4168U1",
        "Operations": {
          "Op01": {
            "PollingInterval": "1000",
            "UnitId": "1",
            "StartAddress": "00017",
            "Count": "8",
            "DisplayName": "Relays"
          }
        }
      }
    }
  }
}

Note: Do not forget to add the leading zero’s (5 digits in total). Otherwise the coils will not be read!

And if we deploy our module, it starts running in IoT Edge. These are the messages we receive::

8/26/2018 5:57:54 PM> Device: [linux6900], Data:[{
  "PublishTimestamp":"2018-08-26 15:57:54",
  "Content":[{
    "HwId":"Adam4168U1",
    "Data":[{
      "CorrelationId":"DefaultCorrelationId",
      "SourceTimestamp":"2018-08-26 15:57:50",
      "Values":[
        {"DisplayName":"Relays","Address":"00017","Value":"1"},
        {"DisplayName":"Relays","Address":"00018","Value":"0"},
        {"DisplayName":"Relays","Address":"00019","Value":"0"},
        {"DisplayName":"Relays","Address":"00020","Value":"0"},
        {"DisplayName":"Relays","Address":"00021","Value":"0"},
        {"DisplayName":"Relays","Address":"00022","Value":"0"},
        {"DisplayName":"Relays","Address":"00023","Value":"0"},
        {"DisplayName":"Relays","Address":"00024","Value":"0"}]}]}]}]
Properties:
'content-type': 'application/edge-modbus-json'

The message has had some formatting to make it more readable but you can recognize the relays of Unit 1.

We can proof the Docker container has access to the comport by running

docker exec [RTU module name] ls -l /dev/ttyS*

This will return the list of comports available within the Docker container. It should be on par with Linux itself.

How about commands?

Writing commands back to the module is possible too. You will need a local Module module for that (a custom C# module or an Azure function):

var modbusMessageBody = new ModbusCommandMessage
{
  HwId = command.hwid,
  UId = command.uid,
  Address = command.address,
  Value = command.value,
};

var jsonMessage = JsonConvert.SerializeObject(modbusMessageBody);
var pipeMessage = new Message(Encoding.UTF8.GetBytes(jsonMessage));
pipeMessage.Properties.Add("command-type" , "ModbusWrite");

client.SendEventAsync("output1", pipeMessage).Wait();

Just for fun, I created this Docker module which takes a Module Twin and sends a command. See the page on Docker Hub for more information on how to use it.

Bonus: Persisting serial port rights

Do you remember we had to change the rights of the serial port? Just applying a chmod command to a serial port is not enough when we perform a reboot.

We have to persist the command so it survives a reboot. After each reboot, this command must be executed.

I choose to use the rc.local file in the /etc folder.

I had to create the file and wrote into it:

#!/bin/bash
sudo chmod a+rw /dev/ttyS0
exit 0

After saving the file, perform a ‘chmod 777 rc.local’ so it can be executed on reboot. Setting these file permission is done only once.

Conclusion

The Microsoft Modbus RTU solution is pretty straight forward in usage.

The solution is depending on a lot of ‘moving’ parts but if you check them step-by-step you are confident this will work.

Yes, there are a few thing you have to take care of (the format of the coil number, the rights of the serial port, etc.) but in the end it just works.

I hope that Modbus RTU will become available in Windows 10 LTSB too but for now we have a solid Linux solution.