Hi,
in analogy with a similar question of which I got answer, i would like to deepen about how to generate custom schema complex telemetry for a IoT Central Device using C# for my project.
I have an IoT Central device template that has a schema complex telemetry like the following:
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest;1",
"@type": "Interface",
"contents": [
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData;1",
"@type": "Telemetry",
"displayName": {
"en": "LibraryItemData"
},
"name": "LibraryItemData",
"schema": {
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData:schema;1",
"@type": "Object",
"displayName": {
"en": "Object"
},
"fields": [
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData:schema:ItemId;1",
"displayName": {
"en": "ItemId"
},
"name": "ItemId",
"schema": "string"
},
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData:schema:Description;1",
"displayName": {
"en": "Description"
},
"name": "Description",
"schema": "string"
},
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData:schema:Location;1",
"displayName": {
"en": "Location"
},
"name": "Location",
"schema": "string"
},
{
"@id": "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest:LibraryItemData:schema:DateTimeValue;1",
"displayName": {
"en": "DateTimeValue"
},
"name": "DateTimeValue",
"schema": "dateTime"
}
]
}
}
],
"displayName": {
"en": "LibraryItemGateCheckSensorTest"
},
"@context": [
"dtmi:iotcentral:context;2",
"dtmi:dtdl:context;2"
]
}
and i need to send telemetry data using c#; following an example of TemperatureController https://github.com/Azure/azure-iot-sdk-csharp/tree/main/iothub/device/samples/solutions/PnpDeviceSamples/TemperatureController
i developed a LibraryDataDeviceController.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Devices.Provisioning;
//using Microsoft.Azure.Devices.Provisioning.Client.PlugAndPlay;
using Microsoft.Azure.Devices.Shared;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using PnpHelpers;
namespace Microsoft.Azure.Devices.Client.Samples
{
public class LibraryDataDeviceController
{
private readonly DeviceClient _deviceClient;
private readonly ILogger _logger;
List<string> ItemID = new List<string>() {
"USB 2364",
"USB 2365",
"USB 2366",
"USB 2367",
"USB 2370",
"USB 2373",
"USB 2375",
"USB 2376",
"USB 2501",
"USB 2606"
};
List<string> Description = new List<string>() {
"*Re-inventare la famiglia : guida teorico-pratica per i professionisti dell'educazione / a cura di Laura Formenti Santarcangelo di Romagna : Maggioli, 2016.. XXII, 451 p. ; 21 cm",
"Le *parole dell'etica / Antonio Da Re [Milano] : Bruno Mondadori, 2010 XII, 215 p. ; 21 cm",
"*Teoria e prassi in pedagogia : questioni epistemologiche / a cura di Massimo Baldacci ed Enza Colicchi Roma : Carocci, 2016 258 p. ; 22 cm",
"*Cantar ottave : per una storia culturale dell'intonazione cantata in ottava rima / a cura di Maurizio Agamennone Lucca : Libreria musicale italiana, 2017 XXVI, 184 p. ; 24 cm",
"*sacré dans la vie quotidienne : suivi de Notes pour Le sacré dans la vie quotidienne ou L'homme sans honneur / Michel Leiris ; présentation et notes de Denis Hollier ; préface de Lionel Menasché Paris : Allia, 2018 140 p. ; 17 cm",
"*Didattica e intercultura / Donatello Santarone Roma : Armando, 2012 127 p. ; 20 cm",
"*Teorie e metodi di pedagogia interculturale / Mariangela Giusti Bari ; Roma : Laterza, 2017 VI, 188 p. ; 21 cm",
"L'*italiano accademico : uno studio sulla glottodidattica dell'italiano lingua di studio all'università a studenti in mobilità internazionale / Elena Ballarin Saarbrücken : Edizioni Accademiche Italiane, 2017 243 p. : ill. ; 22 cm",
"*Empirismo e soggettività : Saggio sulla natura umana secondo Hume / Gilles Deleuze ; a cura di Adriano Vinale. - 2. Ed Napoli : Cronopio , 2012 174 p. ; 21 cm",
"La *vita delle piante : metafisica della mescolanza / Emanuele Coccia Bologna : Il mulino, 2018 159 p. ; 21 cm"};
List<string> Location = new List<string>() {
"Main Gate" };
//Random rnd = new Random();
public LibraryDataDeviceController(DeviceClient deviceClient, ILogger logger)
{
_deviceClient = deviceClient ?? throw new ArgumentNullException(nameof(deviceClient));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task PerformOperationsAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await SendLibraryDataAsync(cancellationToken);
Thread.Sleep(30000);
await Task.Delay(5 * 1000, cancellationToken);
}
}
private async Task SendLibraryDataAsync(CancellationToken cancellationToken)
{
string telemetryName = "LibraryItemData";
DateTime time = DateTime.Now;
Random rnd = new Random();
int index = rnd.Next(0,10);
using Message msg = PnpConvention.CreateMessage(telemetryName, ItemID[index] + "," + "Description:" + Description[index] + "," + "Location:" + Location[0] + "," + "DateTime:" + time);
await _deviceClient.SendEventAsync(msg, cancellationToken);
_logger.LogDebug($"Telemetry: Sent - {{ \"{telemetryName}\": {"LibraryItemData"} }}.");
}
}
}
and used the program.cs that has Iot Central Device Template ModelId for my template with complex schema.
using CommandLine;
using Microsoft.Azure.Devices.Logging;
using Microsoft.Azure.Devices.Provisioning.Client;
using Microsoft.Azure.Devices.Provisioning.Client.PlugAndPlay;
using Microsoft.Azure.Devices.Provisioning.Client.Transport;
using Microsoft.Azure.Devices.Shared;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Azure.Devices.Client.Samples
{
public class Program
{
// Iot Central Device Template ModelId
private const string ModelId = "dtmi:digitaltwins:org:archive:download:rfidsmartlibrary:RfidSmartLibraryowl:LibraryItemGateCheckSensorTest;1";
private const string SdkEventProviderPrefix = "Microsoft-Azure-";
public static async Task Main(string[] args)
{
// Parse application parameters
Parameters parameters = null;
ParserResult<Parameters> result = Parser.Default.ParseArguments<Parameters>(args)
.WithParsed(parsedParams =>
{
parameters = parsedParams;
})
.WithNotParsed(errors =>
{
Environment.Exit(1);
});
// Set up logging
ILogger logger = InitializeConsoleDebugLogger();
// Instantiating this seems to do all we need for outputting SDK events to our console log.
using var sdkLog = new ConsoleEventListener(SdkEventProviderPrefix, logger);
if (!parameters.Validate(logger))
{
Console.WriteLine(CommandLine.Text.HelpText.AutoBuild(result, null, null));
Environment.Exit(1);
}
using var cts = parameters.ApplicationRunningTime.HasValue
? new CancellationTokenSource(TimeSpan.FromSeconds(parameters.ApplicationRunningTime.Value))
: new CancellationTokenSource();
logger.LogInformation("Press Control+C to quit the sample.");
Console.CancelKeyPress += (sender, eventArgs) =>
{
eventArgs.Cancel = true;
cts.Cancel();
logger.LogInformation("Sample execution cancellation requested; will exit.");
};
logger.LogDebug($"Set up the device client.");
try
{
using DeviceClient deviceClient = await SetupDeviceClientAsync(parameters, logger, cts.Token);
var sample = new LibraryDataDeviceController(deviceClient, logger);
await sample.PerformOperationsAsync(cts.Token);
// PerformOperationsAsync is designed to run until cancellation has been explicitly requested, either through
// cancellation token expiration or by Console.CancelKeyPress.
// As a result, by the time the control reaches the call to close the device client, the cancellation token source would
// have already had cancellation requested.
// Hence, if you want to pass a cancellation token to any subsequent calls, a new token needs to be generated.
await deviceClient.CloseAsync(CancellationToken.None);
}
catch (Exception ex) when (ex is OperationCanceledException || ex is ProvisioningTransportException)
{
// User canceled the operation. Nothing to do here.
}
}
private static ILogger InitializeConsoleDebugLogger()
{
using ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter(level => level >= LogLevel.Debug)
.AddSystemdConsole(options =>
{
options.TimestampFormat = "[MM/dd/yyyy HH:mm:ss]";
});
});
return loggerFactory.CreateLogger<LibraryDataDeviceController>();
}
private static async Task<DeviceClient> SetupDeviceClientAsync(Parameters parameters, ILogger logger, CancellationToken cancellationToken)
{
DeviceClient deviceClient;
switch (parameters.DeviceSecurityType.ToLowerInvariant())
{
case "dps":
logger.LogDebug($"Initializing via DPS");
DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken);
var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey);
deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod);
break;
case "connectionstring":
logger.LogDebug($"Initializing via IoT hub connection string");
deviceClient = InitializeDeviceClient(parameters.PrimaryConnectionString);
break;
default:
throw new ArgumentException($"Unrecognized value for device provisioning received: {parameters.DeviceSecurityType}." +
$" It should be either \"dps\" or \"connectionString\" (case-insensitive).");
}
return deviceClient;
}
// Provision a device via DPS, by sending the PnP model Id as DPS payload.
private static async Task<DeviceRegistrationResult> ProvisionDeviceAsync(Parameters parameters, CancellationToken cancellationToken)
{
using SecurityProvider symmetricKeyProvider = new SecurityProviderSymmetricKey(parameters.DeviceId, parameters.DeviceSymmetricKey, null);
using ProvisioningTransportHandler mqttTransportHandler = new ProvisioningTransportHandlerMqtt();
ProvisioningDeviceClient pdc = ProvisioningDeviceClient.Create(parameters.DpsEndpoint, parameters.DpsIdScope, symmetricKeyProvider, mqttTransportHandler);
var pnpPayload = new ProvisioningRegistrationAdditionalData
{
JsonData = PnpConvention.CreateDpsPayload(ModelId),
};
return await pdc.RegisterAsync(pnpPayload, cancellationToken);
}
// Initialize the device client instance using connection string based authentication, over Mqtt protocol (TCP, with fallback over Websocket) and
// setting the ModelId into ClientOptions.
private static DeviceClient InitializeDeviceClient(string deviceConnectionString)
{
var options = new ClientOptions
{
ModelId = ModelId,
};
DeviceClient deviceClient = DeviceClient.CreateFromConnectionString(deviceConnectionString, TransportType.Mqtt, options);
return deviceClient;
}
// Initialize the device client instance using symmetric key based authentication, over Mqtt protocol (TCP, with fallback over Websocket)
// and setting the ModelId into ClientOptions.
private static DeviceClient InitializeDeviceClient(string hostname, IAuthenticationMethod authenticationMethod)
{
var options = new ClientOptions
{
ModelId = ModelId,
};
DeviceClient deviceClient = DeviceClient.Create(hostname, authenticationMethod, TransportType.Mqtt, options);
return deviceClient;
}
}
}
so it works but i'm not sure if code and data model are correct; i ask, if it's possible, a code example for such a template and a method to manage template like this.
is it possible to start from a c# model like the one i posted to generate custom telemetry data using a C# program? Consider that device data automatically generated by IoT Central have no meaning! I hope i was clear.
Thanks in advance.
Guido