This project provides some helpful functionality to easily create better modules for Azure IoT Edge.
PM > Install-Package Fg.IoTEdgeModule
When creating a new Azure IoT Edge module in Visual Studio, the VS.NET template generates a straightforward console application. If you want to make use of dependency injection, easy integration of ILogger
, and have a better distinction between infrastructure and the application functionality itself, it's better to setup the module as a hosted module.
The CreateModuleClient
extension method on IHostBuilder
allows to easily create, configure and register the ModuleClient
as a singleton service in the host.
Using this method allows you to easily setup your IoT Edge module as a hosted service:
static async Task Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureIoTEdgeModuleClient(TransportType.Mqtt_Tcp_Only, configureModuleClient =>
{
configureModuleClient.OpenAsync().Wait();
configureModuleClient.SetDesiredPropertyUpdateCallbackAsync(OnDesiredPropertiesChanged, configureModuleClient).Wait();
})
.ConfigureLogging(logging => logging.AddConsole(consoleLogging =>
{
consoleLogging.Format = ConsoleLoggerFormat.Systemd;
consoleLogging.TimestampFormat = "dd/MM/yyyy HH:mm:ss zz";
}))
.ConfigureServices(services =>
{
// Add other dependencies to the DI container
// ...
//
services.AddHostedService<App>();
})
.UseConsoleLifetime()
.Build();
await host.RunAsync();
}
private static OnDesiredPropertiesChanged(TwinCollection desiredProperties, object userContext) {}
The actual IoT Edge Module's functionality is in this example implemented in the App
class, which looks like this:
internal class App : BackgroundService
{
private readonly ModuleClient _iotHubModuleClient;
private readonly ILogger<App> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App(ModuleClient iotHubModuleClient, ILogger<App> logger)
{
_iotHubModuleClient = iotHubModuleClient;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while( !cancellationToken.IsCancellationRequested )
{
// Do some work here
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
// TODO: clean up
return Task.CompletedTask;
}
}
When the IoT Edge runtime restarts an IoT Edge module (container), it seems that the running container instance is just killed. To be able to gracefully shutdown the module, it is required that the module is notified when a shutdown is happening.
To be able to do this, the ShutdownHandler
class has been introduced. (This class is taken from the EdgeUtil
codebase is a little bit modified).
Creating an instance of the ShutdownHandler
class offers you a CancellationTokenSource
that is tied to the shutdown process of the running container. In other words: when the container is being termined, the CancellationTokenSource
is being canceled.
This means that the CancellationToken
that is linked to it can be used in the module to determine if the module is being shut down:
var shutdownHandler = ShutdownHandler.Create(shutdownWaitPeriod: TimeSpan.FromSeconds(5), logger: log);
while( !shutdownHandler.CancellationTokenSource.Token.IsCancellationRequested )
{
// do work
}
The ShutdownHandler
also offers a mechanism to make sure that everything can be cleaned up before completely shutting down the container. The shutdown process will wait until the SignalCleanupComplete()
method is called or until the shutdownWaitPeriod
has been elapsed.
var shutdownHandler = ShutdownHandler.Create(shutdownWaitPeriod: TimeSpan.FromSeconds(5), logger: log);
while( !shutdownHandler.CancellationTokenSource.Token.IsCancellationRequested )
{
// do work
}
// Cleanup / Dispose some things
dbConnection.Close();
moduleClient.Dispose();
shutdownHandler.SignalCleanupComplete();
To use the ShutdownHandler
in combination with HostBuilder
/IHost
, the following approach is adivsed:
using( var host = CreateHostBuilder().Build())
{
var logger = host.Services.GetService<ILoggerFactory>().GetLogger<Program>()
var shutdownHandler = ShutdownHandler.Create(TimeSpan.FromSeconds(20), logger)
await host.StartAsync(shutdownHandler.CancellationTokenSource.Token);
logger.LogInformation("Module stopping ... ");
await host.WaitForShutdownAsync(shutdownHandler.CancellationTokenSource.Token);
logger.LogInformation("Module stopped");
shutdownHandler.SignalCleanupComplete();
}
Note that in the above code snippet we do not use RunAsync
, but explicitly call StartAsync
and WaitForShutdownAsync
. This is a workaround for this issue.
This library contains an abstract ModuleConfiguration
class that allows you to abstract configuration-settings for an IoT Edge module.
The ModuleConfiguration
class allows you to easily retrieve the desired properties from the module-twin and update the reported properties to the module twin as well.
To use this functionality, you need to inherit from this base-class and implement some basic functionality:
public class MyModuleConfiguration : ModuleConfiguration
{
public int SomeIntegerProperty {get; private set;}
public string SomeStringProperty {get; private set;}
protected override void InitializeFromTwin(TwinCollection desiredProperties)
{
SomeIntegerProperty = Convert.ToInt32(desiredProperties["SomeIntegerConfigSetting"]);
SomeStringProperty = Convert.ToString(desiredProperties["SomeStringConfigSetting"]);
}
protected override void SetReportedProperties(TwinCollection reportedProperties)
{
reportedProperties["SomeIntegerConfigSetting"] = SomeIntegerProperty;
reportedProperties["SomeStringConfigSetting"] = SomeStringProperty;
}
}
Usage:
var configuration = await ModuleConfiguration.CreateFromTwinAsync<MyModuleConfiguration>(moduleClient, logger);
// Use the settings that originate from the ModuleTwin in some other classes.
var processor = new MyProcessor(configuration.SomeIntegerProperty);