Introduction to the IoT Edge SDK, part 2

Running logic on the Edge is not that hard, as we have seen in my previous blog. You have been introduced to modules, the gateway configuration and the broker/runtime.

But these were just two modules. Now it’s time to put some data into the Azure IoT Hub.

If we look at the modules provided by Microsoft, we can do the job already. What we need are the following, already available modules:

  1. simulated_device.dll, to generate simulated data
  2. identity_map.dll, it holds a list of device names and private key combinations so the data of devices can be sent securely to the IoT Hub
  3. iothub.dll, to make contact with the IoT Hub and pass data in name of devices

Yes, there are some limitations to these modules but for now, it’s good enough. Let get started.

This blog is the second part of a series:

  • Part one, how to use modules, gateway configuration and the broker
  • Part two, connecting to the IoT Hub
  • Part three, receiving commands from the cloud
  • Part four, IoT Hub Routing

Let’s look at all modules and see how these have to be configured.

Simulated Device Module

Again, we are using that provided simulated device module. We do not have to change anything. This device generated data and it is passed along with the client side identification of the device. As seen in the previous blog, a MAC address is used. We will use it too. Why? Because we will use the standard identity mapping module to get the device key. And it needs a MAC addres.

So we give this device the following arguments:

"args": {
  "macAddress": "01:01:01:01:01:01",
  "messagePeriod": 5000
}

Identity Mapping Module

This module is provided by Microsoft and used in their own examples too. There, devices have a MAC address and Microsoft used a very strict format.

How does it work, this identity mapping?

The module has configuration arguments like:

"args": [
  {
    "macAddress": "01:01:01:01:01:01",
    "deviceId": "IoTEdgeSdkDevice",
    "deviceKey": "[Azure IoT Hub device key]"
  }
]

This argument is, in fact, a JSON array of devices. Here, only one element is provided.

And according to the documentation of this module, when the incoming message has a property named ‘macAddress’ but it lacks the properties ‘deviceName’, ‘deviceKey’ and ‘source:mapping’ then the module will respond and it will try to map the identity.

The module will put the deviceId and deviceKey in the properties of the message and alter the source to ‘mapping’.

Of course, you need an IoT Hub and a device configuration in the cloud. So create one in the Azure portal:

Note: device credentials are stored in plain text on the disk of the gateway device (in this gateway configuration file). Check your local security officer to find out if this is an issue in your situation!

IoT Hub Module

The last step in this journey is adding the module which connects to the IoT Hub.

This module waits for messages with the following properties:

  • The ‘source’ property must be ‘mapping’
  • The ‘deviceName’ and the ‘deviceKey’ must be submitted

The only arguments we need to configure are:

"args": {
  "IoTHubName": "[IoT Hub name]",
  "IoTHubSuffix": "azure-devices.net",
  "Transport": "amqp"
}

This way, the module can construct the complete device connection string to communicate to the IoT Hub.

Note: this tells us the security is in hands of the mapping module.

Printing Module

We have it available, so let’s make use of it. The mapping output is passed to the printing module too.

The complete configuration

By now, we will have this complete configuration to link everything together:

{
  "modules": [
    {
      "name": "simulator",
      "loader": {
        "name": "native",
        "entrypoint": {
          "module.path": "simulated_device.dll"
        }
      },
      "args": {
        "macAddress": "01:01:01:01:01:01",
        "messagePeriod": 5000
      }
    },
    {
      "name": "dotnet_printer_module",
      "loader": {
        "name": "dotnet",
        "entrypoint": {
          "assembly.name": "MyFirstEdge",
          "entry.type": "MyFirstEdge.MessagePrinterModule"
        }
      },
      "args": ""
    },
    {
      "name": "IoTHub",
      "loader": {
        "name": "native",
        "entrypoint": {
          "module.path": "iothub.dll"
        }
      },
      "args": {
        "IoTHubName": "[IoT Hub name]",
        "IoTHubSuffix": "azure-devices.net",
        "Transport": "amqp"
      }
    },
    {
      "name": "mapping",
      "loader": {
        "name": "native",
        "entrypoint": {
          "module.path": "identity_map.dll"
        }
      },
      "args": [
        {
          "macAddress": "01:01:01:01:01:01",
          "deviceId": "IoTEdgeSdkDevice",
          "deviceKey": "[Azure IoT Hub device key]"
        }
      ]
    }
  ],
  "links": [
    {
      "source": "simulator",
      "sink": "mapping"
    },
    {
      "source": "mapping",
      "sink": "IoTHub"
    },
    {
      "source": "mapping",
      "sink": "dotnet_printer_module"
    }
  ]
}

This completes the ‘coding’. It is all about configuration.

Let’s check out the running gateway

You can now run the gateway and see how it behaves. This is the console output:

And this is the IoT Hub ingestion shown in the Device Explorer tool:

The data from the simulated device is passed on to the IoT Hub.

Checking out the printer output

If you link the message from the simulated device to the printer module too, you will get this output in the console:

Device: 01:01:01:01:01:01, Temperature: 11.00
Printer message: {"temperature": 11.00}
Printer properties: Key 'source' / Value 'bleTelemetry'; Key 'macAddress' / Value '01:01:01:01:01:01';
Printer message: {"temperature": 11.00}
Printer properties: Key 'source' / Value 'mapping'; Key 'deviceName' / Value 'IoTEdgeSdkDevice'; Key 'deviceKey' / Value '[device key]';

Device: 01:01:01:01:01:01, Temperature: 12.00
Printer message: {"temperature": 12.00}
Printer properties: Key 'source' / Value 'bleTelemetry'; Key 'macAddress' / Value '01:01:01:01:01:01';
Printer message: {"temperature": 12.00}
Printer properties: Key 'source' / Value 'mapping'; Key 'deviceName' / Value 'IoTEdgeSdkDevice'; Key 'deviceKey' / Value '[device key]';

Yes, the mapper leaves the message content intact. But it changes the properties from ‘source’=’bleTelemetry’;’macAddress’=’01:01:01:01:01:01′ to ‘source’ = ‘mapping’; ‘deviceName’=’IoTEdgeSdkDevice’; ‘deviceKey’='[device key]’

What about a custom device simulation module?

Using the simulated device is a bit lame. Let’s publish messages with data coming from a ‘real’ device yourself:

using System;
using System.Collections.Generic;
using Microsoft.Azure.IoT.Gateway;
using System.Threading;
using Newtonsoft.Json;

namespace MyFirstEdge
{
  public class CustomSensorModule : IGatewayModule, IGatewayModuleStart
  {
    private Broker broker;
    private ConfigDevice[] _configDevices;
    private RealWorldDeviceClient _client;

    public void Create(Broker broker, byte[] configuration)
    {
      this.broker = broker;
      var config = System.Text.Encoding.UTF8.GetString(configuration);
      _configDevices = JsonConvert.DeserializeObject<ConfigDevice[]>(config);
      _client = new RealWorldDeviceClient();
    }

    public void Start()
    {
      var thread = new Thread(new ThreadStart(ThreadBody));
      thread.Start();
      Console.WriteLine("CustomSensorModule started");
    }

     public void Destroy()
     {
       _client.Dispose();
     }

     public void Receive(Message received_message)
     {
       //Just Ignore the Message. Sensor doesn't need to print.
     }

     public void ThreadBody()
     {
       while (true)
       {
         foreach (var device in _configDevices)
         {
           _client.ReadSensors(device.ipAddress, device.name, device.password);

           var data = new Dictionary<string, string>
           {
             { "source", "sensor" },
             { "macAddress", device.macAddress }
           };

           var dataMessage = new DataMessage
           {
             humidity = _client.Humidity,
             temperature = _client.Temperature,
           };

           var message = JsonConvert.SerializeObject(dataMessage);
           var messageToPublish = new Message(message, data);
           this.broker.Publish(messageToPublish);

           Thread.Sleep(5000);
         }
       }
     }
  }

  public class ConfigDevice
  {
    public string ipAddress { get; set; }
    public string macAddress { get; set; }
    public string name { get; set; }
    public string password { get; set; }
  }

  public class DataMessage
  {
    public double humidity { get; set; }
    public double temperature { get; set; }
  }

  public class RealWorldDeviceClient : IDisposable
  {
    private Random _random = new Random();

    public double Humidity { get; set; }

    public double Temperature { get; set; }

    public void ReadSensors(string ipAddress, string name, string password)
    {
      Humidity = Math.Round(_random.NextDouble() * 100, 3, MidpointRounding.AwayFromZero);
      Temperature = Math.Round(_random.NextDouble() * 100, 3, MidpointRounding.AwayFromZero);
    }

    private bool disposedValue = false;

    protected virtual void Dispose(bool disposing)
    {
      if (!disposedValue)
      {
        if (disposing)
        {
          // HOLD: dispose managed state (managed objects).
        }

        disposedValue = true;
      }
    }

    public void Dispose()
    {
      Dispose(true);
    }
  }
}

We can make several observations for this custom sensor module:

  • The module reads its arguments from a JSON array and maps it to classes.
  • Add the NuGet package for JSON to make this work
  • The module uses a thread to read from a client
  • The MAC address is only used so we can map the identity later on
  • The client makes use of the dispose pattern. Use it only if you have sub logic which needs to be disposed of.
  • The message is taken from a class and transformed to JSON
  • The broker is used to publish the message

Then, add the configuration:

...
{
  "name": "custom_sensor_module",
  "loader": {
    "name": "dotnet",
    "entrypoint": {
      "assembly.name": "MyFirstEdge",
      "entry.type": "MyFirstEdge.CustomSensorModule"
    }
  },
  "args": [
    {
      "ipAddress": "192.168.1.2",
      "macAddress": "01:01:01:01:01:01",
      "name": "name",
      "password": "password"
    }
  ]
},
...

Finally, replace the link:

...
{
  "source": "custom_sensor_module",
  "sink": "mapping"
},
...

When we run our new module we see this outcome, complete with temperature and humidity:

Conclusion

We have experienced how to pass data to the IoT Hub using the Azure Gateway SDK. This will give us a head start for connecting devices to the Azure IoT platform.

Yes, the usage of the standard modules, especially the mapper, forces us to use the MAC address, even if we do not have one. In that case you will have to write your own mapper. But be aware, there are some caveats. Check the documentation twice regarding the current behavior.

Next time, we will look at receiving commands back, from the IoT Hub to the actual device. It will be great, that’s true!

In the mean time, I would like to hear from you guys what hardware you are connecting to the internet!

Advertenties