Azure  

Controlling IoT Modules with Azure IoT Hub: A Deep Dive into Module Twins

Introduction

In modern IoT solutions, especially when working with edge devices, managing configuration and tracking runtime state is just as important as sending telemetry. This is where module twins in Azure IoT Hub play a key role. Module twins act as a bridge between the cloud and individual modules running on a device. They allow you to push configurations from the cloud and receive real-time updates from the module—all without redeploying code.

In this article, we’ll focus on understanding desired properties and reported properties , how they work together, and how to implement them using .NET and Azure CLI.

Prerequisites

Before you begin, make sure you have:

  • An active Microsoft Azure account

  • An IoT Hub instance

  • A registered IoT Edge device with at least one module

  • Azure CLI installed ( az login )

  • .NET SDK installed

Module Twin Properties

A module twin is a JSON document that represents the state of a module. The most important sections are:

Desired Properties

Desired properties are defined by the cloud and represent the configuration that a module is expected to follow.

These properties are used to:

  • Send configuration updates to modules

  • Control behavior without redeployment

  • Maintain a target state for the module

For example, you might set a desired property like:

  
    {
  "interval": 10
}
  

This tells the module to send telemetry every 10 seconds.

Reported Properties

Reported properties (sometimes referred to as general or runtime properties) are sent by the module back to the cloud . They reflect the current state of the module.

These properties are used to:

  • Confirm that configurations are applied

  • Share runtime status or health

  • Provide visibility into module behavior

For example:

  
    {
  "status": "running",
  "interval": 10
}
  

This confirms that the module is running and using the expected configuration.

Updating Desired Properties using Azure CLI

Update desired properties directly from the cloud using Azure CLI:

az iot hub module-twin update \
  --hub-name MyIoTHubDemo \
  --device-id mydevice001 \
  --module-id module1 \
  --set properties.desired.interval=10

Output

{
  "properties": {
    "desired": {
      "interval": 10
    }
  }
}

This sets the target configuration for the module.

Working with Module Twins in .NET

The following example demonstrates a complete flow:

  • Reading desired properties

  • Updating reported properties

  • Reacting to configuration changes

Sample Code

using System;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Client;
using Microsoft.Azure.Devices.Shared;

class Program
{
    private static string connectionString = "<MODULE_CONNECTION_STRING>";
    private static ModuleClient moduleClient;

    static async Task Main(string[] args)
    {
        moduleClient = await ModuleClient.CreateFromConnectionStringAsync(connectionString, TransportType.Mqtt);
        await moduleClient.OpenAsync();

        Console.WriteLine("Module connected.");

        // Read desired properties
        var twin = await moduleClient.GetTwinAsync();
        int interval = twin.Properties.Desired.Contains("interval") ? twin.Properties.Desired["interval"] : 5;

        Console.WriteLine($"Initial interval: {interval}");

        // Update reported properties
        var reported = new TwinCollection();
        reported["status"] = "running";
        reported["interval"] = interval;

        await moduleClient.UpdateReportedPropertiesAsync(reported);
        Console.WriteLine("Reported properties sent.");

        // Handle updates from cloud
        await moduleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredChanged, null);

        Console.WriteLine("Waiting for updates...");
        await Task.Delay(-1);
    }

    private static async Task OnDesiredChanged(TwinCollection desiredProperties, object userContext)
    {
        if (desiredProperties.Contains("interval"))
        {
            int newInterval = desiredProperties["interval"];
            Console.WriteLine($"New interval received: {newInterval}");

            var reported = new TwinCollection();
            reported["interval"] = newInterval;
            reported["ack"] = "updated";

            await moduleClient.UpdateReportedPropertiesAsync(reported);
            Console.WriteLine("Reported properties updated.");
        }
    }
}

Output

Module connected.
Initial interval: 10
Reported properties sent.
Waiting for updates...

New interval received: 20
Reported properties updated. 

Code Explanation

  • CreateFromConnectionStringAsync() + OpenAsync() → Initializes and connects the module to Azure IoT Hub using MQTT

  • GetTwinAsync() → Retrieves module twin and reads interval from desired properties (defaults to 5 if not present)

  • UpdateReportedPropertiesAsync() → Sends module state (status, interval) back to the cloud as reported properties

  • SetDesiredPropertyUpdateCallbackAsync() → Registers a callback to listen for changes in desired properties

  • OnDesiredChanged() → Handles updates, reads new interval, and calls UpdateReportedPropertiesAsync() to sync and acknowledge changes.

Enabling Dynamic Configuration with Module Twins

Desired and reported properties work together to create a feedback loop between cloud and device. The cloud defines what should happen The module reports what is actually happening This approach helps you manage devices remotely, apply updates dynamically, and maintain visibility into system behavior without constant redeployment.

Conclusion

Module twins provide a simple yet powerful way to manage configuration and state in IoT solutions. Desired properties allow you to control modules from the cloud, while reported properties give you visibility into their actual behavior. Using both effectively helps build flexible, scalable, and maintainable IoT systems.