Skip to content
Back to Blog
1 min read

Azure IoT Edge: Intelligence at the Edge

Streaming every sensor reading to the cloud sounds clean until you cost the bandwidth, or until the 4G link in the back of a truck drops for an hour. IoT Edge is Microsoft’s answer to “do the work where the data lives.” Containers running on the device—stream analytics, ML inference, even Functions—with the cloud for management and the long tail of telemetry. I tend to push aggregations and anomaly detection to the edge and let the cloud get the summaries.

IoT Edge Architecture

┌──────────────────────────────────────────┐
│              Azure Cloud                  │
│  ┌─────────────┐  ┌─────────────────┐   │
│  │  IoT Hub    │  │ Container       │   │
│  │             │  │ Registry        │   │
│  └─────────────┘  └─────────────────┘   │
└──────────────────────────────────────────┘
              │
              ▼
┌──────────────────────────────────────────┐
│           IoT Edge Device                 │
│  ┌─────────────────────────────────────┐ │
│  │         IoT Edge Runtime            │ │
│  │  ┌─────────┐ ┌─────────┐ ┌───────┐ │ │
│  │  │ Module 1│ │ Module 2│ │Module3│ │ │
│  │  │ (ML)    │ │(Stream) │ │(Logic)│ │ │
│  │  └─────────┘ └─────────┘ └───────┘ │ │
│  └─────────────────────────────────────┘ │
│  ┌─────────────┐  ┌─────────────┐       │
│  │  edgeHub    │  │  edgeAgent  │       │
│  └─────────────┘  └─────────────┘       │
└──────────────────────────────────────────┘

Deployment Manifest

{
    "modulesContent": {
        "$edgeAgent": {
            "properties.desired": {
                "modules": {
                    "TempSensor": {
                        "type": "docker",
                        "settings": {
                            "image": "mcr.microsoft.com/azureiotedge-simulated-temperature-sensor:1.0"
                        },
                        "status": "running",
                        "restartPolicy": "always"
                    },
                    "FilterModule": {
                        "type": "docker",
                        "settings": {
                            "image": "myacr.azurecr.io/filter-module:1.0"
                        },
                        "env": {
                            "TEMP_THRESHOLD": { "value": "25" }
                        }
                    }
                }
            }
        },
        "$edgeHub": {
            "properties.desired": {
                "routes": {
                    "sensorToFilter": "FROM /messages/modules/TempSensor/* INTO BrokeredEndpoint(\"/modules/FilterModule/inputs/input1\")",
                    "filterToCloud": "FROM /messages/modules/FilterModule/* INTO $upstream"
                }
            }
        }
    }
}

Custom Module (C#)

public class Program
{
    static double temperatureThreshold = 25;

    static async Task Init()
    {
        var moduleClient = await ModuleClient.CreateFromEnvironmentAsync();
        await moduleClient.OpenAsync();

        // Set input message handler
        await moduleClient.SetInputMessageHandlerAsync("input1", FilterMessages, moduleClient);

        // Handle twin updates
        await moduleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertiesUpdate, null);
    }

    static async Task<MessageResponse> FilterMessages(Message message, object userContext)
    {
        var moduleClient = (ModuleClient)userContext;
        var messageBody = message.GetBytes();
        var data = JsonConvert.DeserializeObject<SensorData>(Encoding.UTF8.GetString(messageBody));

        if (data.Temperature > temperatureThreshold)
        {
            var alertMessage = new Message(messageBody);
            alertMessage.Properties.Add("alertType", "temperature");
            await moduleClient.SendEventAsync("output1", alertMessage);
        }

        return MessageResponse.Completed;
    }
}

ML at the Edge

{
    "MLModule": {
        "type": "docker",
        "settings": {
            "image": "myacr.azurecr.io/ml-module:1.0",
            "createOptions": {
                "HostConfig": {
                    "Binds": ["/var/lib/azurecv:/azurecv"]
                }
            }
        }
    }
}
# Python module with ONNX model
import onnxruntime as rt

class MLModule:
    def __init__(self):
        self.session = rt.InferenceSession('/azurecv/model.onnx')

    def score(self, input_data):
        result = self.session.run(None, {'input': input_data})
        return result

Stream Analytics at Edge

-- Edge SQL query
SELECT
    System.Timestamp() AS WindowEnd,
    AVG(temperature) AS AvgTemp,
    MAX(temperature) AS MaxTemp,
    COUNT(*) AS EventCount
INTO
    AlertOutput
FROM
    SensorInput TIMESTAMP BY timestamp
GROUP BY
    TumblingWindow(second, 30)
HAVING
    AVG(temperature) > 25

Offline Capabilities

{
    "$edgeHub": {
        "properties.desired": {
            "storeAndForwardConfiguration": {
                "timeToLiveSecs": 7200
            }
        }
    }
}

CLI Operations

# Set deployment
az iot edge set-modules \
    --hub-name myiothub \
    --device-id myedgedevice \
    --content deployment.json

# Monitor messages
az iot hub monitor-events \
    --hub-name myiothub \
    --device-id myedgedevice

IoT Edge: cloud intelligence where it matters most.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.