Develop and deploy a C# IoT Edge module to your simulated device - preview

You can use IoT Edge modules to deploy code that implements your business logic directly to your IoT Edge devices. This tutorial walks you through creating and deploying an IoT Edge module that filters sensor data. You'll use the simulated IoT Edge device that you created in the Deploy Azure IoT Edge on a simulated device in Windows or Linux tutorials. In this tutorial, you learn how to:

  • Use Visual Studio Code to create an IoT Edge module based on .NET core 2.0
  • Use Visual Studio Code and Docker to create a docker image and publish it to your registry
  • Deploy the module to your IoT Edge device
  • View generated data

The IoT Edge module that you create in this tutorial filters the temperature data generated by your device. It only sends messages upstream if the temperature is above a specified threshold. This type of analysis at the edge is useful for reducing the amount of data communicated to and stored in the cloud.

Prerequisites

Create a container registry

In this tutorial, you use the Azure IoT Edge extension for VS Code to build a module and create a container image from the files. Then you push this image to a registry that stores and manages your images. Finally, you deploy your image from your registry to run on your IoT Edge device.

You can use any Docker-compatible registry for this tutorial. Two popular Docker registry services available in the cloud are Azure Container Registry and Docker Hub. This tutorial uses Azure Container Registry.

  1. In the Azure portal, select Create a resource > Containers > Azure Container Registry.
  2. Give your registry a name, choose a subscription, choose a resource group, and set the SKU to Basic.
  3. Select Create.
  4. Once your container registry is created, navigate to it and select Access keys.
  5. Toggle Admin user to Enable.
  6. Copy the values for Login server, Username, and Password. You'll use these values later in the tutorial.

Create an IoT Edge module project

The following steps show you how to create an IoT Edge module based on .NET core 2.0 using Visual Studio Code and the Azure IoT Edge extension.

  1. In Visual Studio Code, select View > Integrated Terminal to open the VS Code integrated terminal.
  2. In the integrated terminal, enter the following command to install (or update) the AzureIoTEdgeModule template in dotnet:

    dotnet new -i Microsoft.Azure.IoT.Edge.Module
    
  3. Create a project for the new module. The following command creates the project folder, FilterModule, in the current working folder:

    dotnet new aziotedgemodule -n FilterModule
    
  4. Select File > Open Folder.

  5. Browse to the FilterModule folder and click Select Folder to open the project in VS Code.
  6. In VS Code explorer, click Program.cs to open it.

    Open Program.cs

  7. Add the temperatureThreshold variable to the Program class. This variable sets the value that the measured temperature must exceed in order for the data to be sent to IoT Hub.

    static int temperatureThreshold { get; set; } = 25;
    
  8. Add the MessageBody, Machine, and Ambient classes to the Program class. These classes define the expected schema for the body of incoming messages.

    class MessageBody
    {
        public Machine machine {get;set;}
        public Ambient ambient {get; set;}
        public string timeCreated {get; set;}
    }
    class Machine
    {
       public double temperature {get; set;}
       public double pressure {get; set;}         
    }
    class Ambient
    {
       public double temperature {get; set;}
       public int humidity {get; set;}         
    }
    
  9. In the Init method, the code creates and configures a DeviceClient object. This object allows the module to connect to the local Azure IoT Edge runtime to send and receive messages. The connection string used in the Init method is supplied to the module by IoT Edge runtime. After creating the DeviceClient, the code registers a callback for receiving messages from the IoT Edge hub via the input1 endpoint. Replace the SetInputMessageHandlerAsync method with a new one, and add a SetDesiredPropertyUpdateCallbackAsync method for desired properties updates. To make this change, replace the last line of the Init method with the following code:

    // Register callback to be called when a message is received by the module
    // await ioTHubModuleClient.SetImputMessageHandlerAsync("input1", PipeMessage, iotHubModuleClient);
    
    // Attach callback for Twin desired properties updates
    await ioTHubModuleClient.SetDesiredPropertyUpdateCallbackAsync(onDesiredPropertiesUpdate, null);
    
    // Register callback to be called when a message is received by the module
    await ioTHubModuleClient.SetInputMessageHandlerAsync("input1", FilterMessages, ioTHubModuleClient);
    
  10. Add the onDesiredPropertiesUpdate method to the Program class. This method receives updates on the desired properties from the module twin, and updates the temperatureThreshold variable to match. All modules have their own module twin, which lets you configure the code running inside a module directly from the cloud.

    static Task onDesiredPropertiesUpdate(TwinCollection desiredProperties, object userContext)
    {
        try
        {
            Console.WriteLine("Desired property change:");
            Console.WriteLine(JsonConvert.SerializeObject(desiredProperties));
    
            if (desiredProperties["TemperatureThreshold"]!=null)
                temperatureThreshold = desiredProperties["TemperatureThreshold"];
    
        }
        catch (AggregateException ex)
        {
            foreach (Exception exception in ex.InnerExceptions)
            {
                Console.WriteLine();
                Console.WriteLine("Error when receiving desired property: {0}", exception);
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Error when receiving desired property: {0}", ex.Message);
        }
        return Task.CompletedTask;
    }
    
  11. Replace the PipeMessage method with the FilterMessages method. This method is called whenever the module receives a message from the IoT Edge hub. It filters out messages that report temperatures below the temperature threshold set via the module twin. It also adds the MessageType property to the message with the value set to Alert.

    static async Task<MessageResponse> FilterMessages(Message message, object userContext)
    {
        int counterValue = Interlocked.Increment(ref counter);
    
        try {
            DeviceClient deviceClient = (DeviceClient)userContext;
    
            byte[] messageBytes = message.GetBytes();
            string messageString = Encoding.UTF8.GetString(messageBytes);
            Console.WriteLine($"Received message {counterValue}: [{messageString}]");
    
            // Get message body
            var messageBody = JsonConvert.DeserializeObject<MessageBody>(messageString);
    
            if (messageBody != null && messageBody.machine.temperature > temperatureThreshold)
            {
                Console.WriteLine($"Machine temperature {messageBody.machine.temperature} " +
                    $"exceeds threshold {temperatureThreshold}");
                var filteredMessage = new Message(messageBytes);
                foreach (KeyValuePair<string, string> prop in message.Properties)
                {
                    filteredMessage.Properties.Add(prop.Key, prop.Value);
                }
    
                filteredMessage.Properties.Add("MessageType", "Alert");
                await deviceClient.SendEventAsync("output1", filteredMessage);
            }
    
            // Indicate that the message treatment is completed
            return MessageResponse.Completed;
        }
        catch (AggregateException ex)
        {
            foreach (Exception exception in ex.InnerExceptions)
            {
                Console.WriteLine();
                Console.WriteLine("Error in sample: {0}", exception);
            }
            // Indicate that the message treatment is not completed
            DeviceClient deviceClient = (DeviceClient)userContext;
            return MessageResponse.Abandoned;
        }
        catch (Exception ex)
        {
            Console.WriteLine();
            Console.WriteLine("Error in sample: {0}", ex.Message);
            // Indicate that the message treatment is not completed
            DeviceClient deviceClient = (DeviceClient)userContext;
            return MessageResponse.Abandoned;
        }
    }
    
  12. To build the project, right-click the FilterModule.csproj file in the Explorer and click Build IoT Edge module. This process compiles the module and exports the binary and its dependencies into a folder that is used to create a Docker image.

    Build IoT Edge module

Create a Docker image and publish it to your registry

  1. In VS Code explorer, expand the Docker folder. Then expand the folder for your container platform, either linux-x64 or windows-nano.

    Select Docker container platform

  2. Right-click the Dockerfile file and click Build IoT Edge module Docker image.

  3. In the Select Folder window, either browse to or enter ./bin/Debug/netcoreapp2.0/publish. Click Select Folder as EXE_DIR.
  4. In the pop-up text box at the top of the VS Code window, enter the image name. For example: <your container registry address>/filtermodule:latest. The container registry address is the same as the login server that you copied from your registry. It should be in the form of <your container registry name>.azurecr.io.
  5. Sign in to Docker by entering the following command in the VS Code integrated terminal:

    docker login -u <username> -p <password> <Login server>
    

    Use the user name, password, and login server that you copied from your Azure container registry when you created it.

  6. Push the image to your Docker repository. Select View > Command Palette and search for the Edge: Push IoT Edge module Docker image menu command. Enter the image name in the pop-up text box at the top of the VS Code window. Use the same image name you used in step 4.

Add registry credentials to Edge runtime

Add the credentials for your registry to the Edge runtime on the computer where you are running your Edge device. These credentials give the runtime access to pull the container.

  • For Windows, run the following command:

    iotedgectl login --address <your container registry address> --username <username> --password <password> 
    
  • For Linux, run the following command:

    sudo iotedgectl login --address <your container registry address> --username <username> --password <password> 
    

Run the solution

  1. In the Azure portal, navigate to your IoT hub.
  2. Go to IoT Edge (preview) and select your IoT Edge device.
  3. Select Set Modules.
  4. Check that the tempSensor module is automatically populated. If it's not, use the following steps to add it:
    1. Select Add IoT Edge Module.
    2. In the Name field, enter tempSensor.
    3. In the Image URI field, enter microsoft/azureiotedge-simulated-temperature-sensor:1.0-preview.
    4. Leave the other settings unchanged and click Save.
  5. Add the filterModule module that you created in the previous sections.

    1. Select Add IoT Edge Module.
    2. In the Name field, enter filterModule.
    3. In the Image URI field, enter your image address; for example <your container registry address>/filtermodule:latest.
    4. Check the Enable box so that you can edit the module twin.
    5. Replace the JSON in the text box for the module twin with the following JSON:

      {
         "properties.desired":{
            "TemperatureThreshold":25
         }
      }
      
    6. Click Save.

  6. Click Next.
  7. In the Specify Routes step, copy the JSON below into the text box. Modules publish all messages to the Edge runtime. Declarative rules in the runtime define where the messages flow. In this tutorial, you need two routes. The first route transports messages from the temperature sensor to the filter module via the "input1" endpoint, which is the endpoint that you configured with the FilterMessages handler. The second route transports messages from the filter module to IoT Hub. In this route, upstream is a special destination that tells Edge Hub to send messages to IoT Hub.

    {
       "routes":{
          "sensorToFilter":"FROM /messages/modules/tempSensor/outputs/temperatureOutput INTO BrokeredEndpoint(\"/modules/filterModule/inputs/input1\")",
          "filterToIoTHub":"FROM /messages/modules/filterModule/outputs/output1 INTO $upstream"
       }
    }
    
  8. Click Next.

  9. In the Review Template step, click Submit.
  10. Return to the IoT Edge device details page and click Refresh. You should see the new filtermodule running along with the tempSensor module and the IoT Edge runtime.

View generated data

To monitor device to cloud messages sent from your IoT Edge device to your IoT hub:

  1. Configure the Azure IoT Toolkit extension with connection string for your IoT hub:

    1. Open the VS Code explorer by selecting View > Explorer.
    2. In the explorer, click IOT HUB DEVICES and then click .... Click Set IoT Hub Connection String and enter the connection string for the IoT hub that your IoT Edge device connects to in the pop-up window.

      To find the connection string, click the tile for your IoT hub in the Azure portal and then click Shared access policies. In Shared access policies, click the iothubowner policy and copy the IoT Hub connection string in the iothubowner window.

  2. To monitor data arriving at the IoT hub, select View > Command Palette and search for the IoT: Start monitoring D2C message menu command.

  3. To stop monitoring data, use the IoT: Stop monitoring D2C message menu command.

Next steps

In this tutorial, you created an IoT Edge module that contains code to filter raw data generated by your IoT Edge device. You can continue on to either of the following tutorials to learn about other ways that Azure IoT Edge can help you turn data into business insights at the edge.