Azure IoT Edge UDP Client module

Over the past couple of years, I’ve written several blog posts about supporting protocols on Azure IoT Edge.

We have seen examples like Serial communication, Modbus TCP and RTU, OPC-UA, and WebSockets.

This time, a new protocol is added to this impressive list: User Datagram Protocol (UDP).

Communication using UDP is fast and supports broadcasting to several IP addresses at once.

There are some disadvantages (it has no handshaking dialogues, and thus exposes the user’s program to any unreliability of the underlying network; there is no guarantee of delivery, ordering, or duplicate protection) but if you can cope with that at a higher level, UDP is a handy tool in your protocol toolbox.

In this blog post we will see how UDP messages can be received on the Edge with this demonstration IoT Edge module:

Because each UDP message is different (here we send text messages but this could also be binary values, depending on your challenges), this module is just a reference which explains how it works.

Sending UDP messages in C#

First, we need to figure out how to send (and receive) UDP messages.

I used C# for this project and it is quite straightforward using this reference. As expected, we need a server and a client.

A UDP server can send messages to an IP address and port although other options are available too:

The UDP server exposes a specific port so clients are able to send one or more messages back to that server once they got their first message from that server (the incoming message also exposes the server identification: IP address and port number).

The UDP messages are just byte array values of a certain length where you are in control of the message format:

What is in that byte array is up to you. It could be a string. It could even be JSON. But keep in mind, smaller is better to increase the speed.

In the GitHub repository of this IoT Edge UDP Client module, a separate reference server application is made available so you can test the UDP IoT Edge module.

There, we just send UDP messages to the IP address of a client, here it is our IoT Edge module:

Note:The default IP address of the client is set to 127.0.0.1 and the client port number is set to 11001. You probably need to change the client IP address to the actual host IP address of your Edge device.

Notice the UDP server is listening on port 11000 (there is no logic added for this server for retrieving messages sent back by clients).

Once the server is started, it just sends out simple UDP messages containing a counter:

That counter can be used to check the order of incoming messages.

It can also be used to check for missing messages. If these outputted messages are not picked up by any client, these are lost forever 🙂

The IoT Edge UDP Client

The IoT Edge module is based on the standard C# IoT Edge module template with some modifications.

Receiving UDP messages is done in an extra thread in the program. This way, UDP logic does not interfere with the generic IoT Edge logic (eg. receiving module twin updates).

Then, we listen for incoming messages in an infinite loop:

Each message received is handled with an asynchronous ReceveCallback method. Because UDP is not synchronous by nature, using an async callback will probably increase the speed of our module. We must able to cope with messages coming in in the wrong order, duplicates, or missing messages.

Note: there is a synchronious method for receiving messages. I leave it to you to change the code it serves your needs.

Incoming messages are encoded back to strings the same way as seen on the server, just as a demonstration:

As you can see, you can figure out which server sent this message so we could respond to the server for a specific message.

In our module, we just send all incoming messages potentially to the cloud by outputting them to the internal routing system on the edge (the output is named ‘output1’).

Note: check out the high speed of incoming messages demonstrated by the server. It’s up to you to prevent the IoT Hub from throttling incoming messages because you reached the daily limit of messages. I recommend an extra Filter module that only accepts meaningful messages.

The output of our UDP Client module looks like this:

No messages are missing in this example. This number of handled messages in just one second is quite impressive.

Configuration of the IoT Edge module

The IoT Edge module needs some configuration before you can use it.

The module needs a desired property name ‘clientListeningPort’ because the server needs to know on which port the client is listening. By default, it’s port 11001.

Optionally, you can add the log-level of the outputted errors:

Because we are exposing a UDP port in our module, which is in fact a Docker container running in Moby, we have to inform Docker/Moby about our intents. At the Docker container runtime level configuration, we need to give permissions to exposing this (or a different port if it’s already in use) on the IoT Edge host Operating System:

So, configure UDP access on both the internal and external level:

{
    "ExposedPorts": {
        "11001/udp": {}
    },
    "HostConfig": {
        "PortBindings": {
            "11001/udp": [
                {
                    "HostPort": "11001"
                }
            ]
        }
    }
}

If you run multiple UDP client modules on the same host, you can play with both port numbers to prevent host port number collisions.

Open-source, MIT licensed

This module is made available open-source with an MIT license at the IoT Edge Foundation GitHub location.

If you are not able to build this Docker module yourself, check out the Docker Hub.

Conclusion

Azure IoT Edge offers a versatile solution for various challenges, including UDP protocol support.

This module is a good example of how to set up your own Azure IoT Edge modules. You can adjust and extend this for your own UDP message structure.

Running on a generic operating system, logic running in docker containers is hardly capable to perform at the same level as real-time operating systems do. Still, I’m pleased to see the module is still capable to receive UDP messages at an impressive speed.

To keep the overhead of handling messages low and thus limiting the impact on missing incoming messages, I advise putting incoming messages on a queue so another thread can do some magic with them.