Tutorial: Deploy Azure Functions as IoT Edge modules

Applies to: IoT Edge 1.5 checkmark IoT Edge 1.5 IoT Edge 1.4 checkmark IoT Edge 1.4

Important

IoT Edge 1.5 LTS and IoT Edge 1.4 LTS are supported releases. IoT Edge 1.4 LTS is end of life on November 12, 2024. If you are on an earlier release, see Update IoT Edge.

You can use Azure Functions to deploy code that implements your business logic directly to your Azure IoT Edge devices. This tutorial walks you through creating and deploying an Azure Function that filters sensor data on the simulated IoT Edge device. You use the simulated IoT Edge device that you created in the quickstarts. In this tutorial, you learn how to:

  • Use Visual Studio Code to create an Azure Function.
  • Use Visual Studio Code and Docker to create a Docker image and publish it to a container registry.
  • Deploy the module from the container registry to your IoT Edge device.
  • View filtered data.

Diagram of function architecture, showing how to stage and deploy a function module.

The Azure Function that you create in this tutorial filters the temperature data that's generated by your device. The Function only sends messages upstream to Azure IoT Hub when the temperature is above a specified threshold.

If you don't have an Azure trial subscription, create an Azure trial subscription before you begin.

Prerequisites

Before beginning this tutorial, do the tutorial to set up your development environment for Linux container development: Develop Azure IoT Edge modules using Visual Studio Code. After completing that tutorial, you should have the following prerequisites in place:

To develop an IoT Edge module with Azure Functions, install additional prerequisites on your development machine:

Create a function project

The Azure IoT Edge for Visual Studio Code that you installed in the prerequisites provides management capabilities and some code templates. In this section, you use Visual Studio Code to create an IoT Edge solution that contains an Azure Function.

Create a new project

Follow these steps to create a C# Function solution template that's customizable.

  1. Open Visual Studio Code on your development machine.

  2. Open the Visual Studio Code command palette by selecting View > Command Palette.

  3. In the command palette, add and run the command Azure IoT Edge: New IoT Edge solution. Follow these prompts in the command palette to create your solution:

    • Select a folder: choose the location on your development machine for Visual Studio Code to create the solution files.
    • Provide a solution name: add a descriptive name for your solution, like FunctionSolution, or accept the default.|
    • Select a module template: choose Azure Functions - C#.
    • Provide a module name | Name your module CSharpFunction.
    • Provide a Docker image repository for the module. An image repository includes the name of your container registry and the name of your container image. Your container image is pre-populated from the last step. Replace localhost:5000 with the Login server value from your Azure container registry. You can retrieve the Login server from the Overview page of your container registry in the Azure portal. The final string looks like <registry name>.azurecr.cn/csharpfunction.

    Screenshot showing where to add your Docker image repository name in Visual Studio Code.

Add your registry credentials

The environment file of your solution stores the credentials for your container registry and shares them with the IoT Edge runtime. The runtime needs these credentials to pull your private images onto your IoT Edge device.

The IoT Edge extension in Visual Studio Code tries to pull your container registry credentials from Azure and populate them in the environment file. Check to see if your credentials are already in the file. If not, add them now:

  1. In the Visual Studio Code explorer, open the .env file.
  2. Update the fields with the username and password values that you copied from your Azure container registry. You can find them again by going to your container registry in Azure and looking on the Settings > Access keys page.
  3. Save this file.

Note

This tutorial uses admin login credentials for Azure Container Registry, which are convenient for development and test scenarios. When you're ready for production scenarios, we recommend a least-privilege authentication option like service principals. For more information, see Manage access to your container registry.

Set target architecture to AMD64

Running Azure Functions modules on IoT Edge is supported only on Linux AMD64 based containers. The default target architecture for Visual Studio Code is Linux AMD64, but we set it explicitly to Linux AMD64 here.

  1. Open the command palette and search for Azure IoT Edge: Set Default Target Platform for Edge Solution.

  2. In the command palette, select the AMD64 target architecture from the list of options.

Update the module with custom code

Let's add some additional code so your CSharpFunction module processes the messages at the edge before forwarding them to IoT Hub.

  1. In the Visual Studio Code explorer, open modules > CSharpFunction > CSharpFunction.cs.

  2. Replace the contents of the CSharpFunction.cs file with the following code. This code receives telemetry about ambient and machine temperature, and only forwards the message on to IoT Hub if the machine temperature is above a defined threshold.

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text;
    using System.Threading.Tasks;
    using Microsoft.Azure.Devices.Client;
    using Microsoft.Azure.WebJobs;
    using Microsoft.Azure.WebJobs.Extensions.EdgeHub;
    using Microsoft.Azure.WebJobs.Host;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    
    namespace Functions.Samples
    {
        public static class CSharpFunction
        {
            [FunctionName("CSharpFunction")]
            public static async Task FilterMessageAndSendMessage(
                [EdgeHubTrigger("input1")] Message messageReceived,
                [EdgeHub(OutputName = "output1")] IAsyncCollector<Message> output,
                ILogger logger)
            {
                const int temperatureThreshold = 20;
                byte[] messageBytes = messageReceived.GetBytes();
                var messageString = System.Text.Encoding.UTF8.GetString(messageBytes);
    
                if (!string.IsNullOrEmpty(messageString))
                {
                    logger.LogInformation("Info: Received one non-empty message");
                    // Get the body of the message and deserialize it.
                    var messageBody = JsonConvert.DeserializeObject<MessageBody>(messageString);
    
                    if (messageBody != null && messageBody.machine.temperature > temperatureThreshold)
                    {
                        // Send the message to the output as the temperature value is greater than the threshold.
                        using (var filteredMessage = new Message(messageBytes))
                        {
                             // Copy the properties of the original message into the new Message object.
                             foreach (KeyValuePair<string, string> prop in messageReceived.Properties)
                             {filteredMessage.Properties.Add(prop.Key, prop.Value);}
                             // Add a new property to the message to indicate it is an alert.
                             filteredMessage.Properties.Add("MessageType", "Alert");
                             // Send the message.
                             await output.AddAsync(filteredMessage);
                             logger.LogInformation("Info: Received and transferred a message with temperature above the threshold");
                        }
                    }
                }
            }
        }
        //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;}
        }
    }
    
  3. Save the file.

Build and push your IoT Edge solution

In the previous section, you created an IoT Edge solution and modified the CSharpFunction to filter out messages with reported machine temperatures below the acceptable threshold. Now you need to build the solution as a container image and push it to your container registry.

  1. Open the Visual Studio Code integrated terminal by selecting View > Terminal.

  2. Sign in to Docker by entering the following command in the terminal. Sign in with the username, password, and login server from your Azure container registry. You can retrieve these values from the Access keys section of your registry in the Azure portal.

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

    You may receive a security warning recommending the use of --password-stdin. While that best practice is recommended for production scenarios, it's outside the scope of this tutorial. For more information, see the docker login azure --cloud-name AzureChinaCloud reference.

  3. In the Visual Studio Code explorer, right-click the deployment.template.json file and select Build and Push IoT Edge Solution.

    The build and push command starts three operations. First, it creates a new folder in the solution called config that holds the full deployment manifest, which is built out of information in the deployment template and other solution files. Second, it runs docker build to build the container image based on the appropriate dockerfile for your target architecture. Then, it runs docker push to push the image repository to your container registry.

    This process may take several minutes the first time, but is faster the next time that you run the commands.

View your container image

Visual Studio Code outputs a success message when your container image is pushed to your container registry. If you want to confirm the successful operation for yourself, you can view the image in the registry.

  1. In the Azure portal, browse to your Azure container registry.
  2. Select Services > Repositories.
  3. You should see the csharpfunction repository in the list. Select this repository to see more details.
  4. In the Tags section, you should see the 0.0.1-amd64 tag. This tag indicates the version and platform of the image that you built. These values are set in the module.json file in the CSharpFunction folder.

Deploy and run the solution

You can use the Azure portal to deploy your Function module to an IoT Edge device like you did in the quickstart. You can also deploy and monitor modules from within Visual Studio Code. The following sections use the Azure IoT Edge and IoT Hub for Visual Studio Code that was listed in the prerequisites. Install the extensions now, if you haven't already.

  1. In the Visual Studio Code explorer, under the Azure IoT Hub section, expand Devices to see your list of IoT devices.

  2. Right-click the name of your IoT Edge device, and then select Create Deployment for Single Device.

  3. Browse to the solution folder that contains the CSharpFunction. Open the config folder, select the deployment.amd64.json file, and then choose Select Edge Deployment Manifest.

  4. Under your device, expand Modules to see a list of deployed and running modules. Select the refresh button. You should see the new CSharpFunction running along with the SimulatedTemperatureSensor module and the $edgeAgent and $edgeHub.

    It may take a few moments for the new modules to show up. Your IoT Edge device has to retrieve its new deployment information from IoT Hub, start the new containers, and then report the status back to IoT Hub.

    Screenshot showing how to view deployed modules in Visual Studio Code.

View the generated data

You can see all of the messages that arrive at your IoT hub from all your devices by running Azure IoT Hub: Start Monitoring Built-in Event Endpoint in the command palette. To stop monitoring messages, run the command Azure IoT Hub: Stop Monitoring Built-in Event Endpoint in the command palette.

You can also filter the view to see all of the messages that arrive at your IoT hub from a specific device. Right-click the device in the Azure IoT Hub > Devices section of the Visual Studio Code explorer and select Start Monitoring Built-in Event Endpoint.

Clean up resources

If you plan to continue to the next recommended article, you can keep the resources and configurations that you created and reuse them. You can also keep using the same IoT Edge device as a test device.

Otherwise, you can delete the local configurations and the Azure resources that you created in this article to avoid charges.

Delete Azure resources

Deleting Azure resources and resource groups is irreversible. Make sure that you don't accidentally delete the wrong resource group or resources. If you created the IoT hub inside an existing resource group that has resources that you want to keep, delete only the IoT hub resource itself, not the resource group.

To delete the resources:

  1. Sign in to the Azure portal, and then select Resource groups.

  2. Select the name of the resource group that contains your IoT Edge test resources.

  3. Review the list of resources that your resource group contains. If you want to delete all of them, you can select Delete resource group. If you want to delete only some of them, you can select each resource to delete them individually.

Next steps

In this tutorial, you created an Azure Function module with code to filter raw data that's generated by your IoT Edge device.

Continue on to the next tutorials to learn other ways that Azure IoT Edge can help you turn data into business insights at the edge.