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.