Code First Azure Digital Twins — a first look

Telstra has a large Internet of Things portfolio, with Digital Twins one of the focus areas for Telstra Purple professional services. All major providers are supported, including Azure Digital Twins.

The team recently took some core bits out of project they are working on with code first Azure Digital Twins and have released it as an open source library, so I thought I would share an initial look at the project.

Why code first? Using a code first approach can make accessing Digital Twins easier for developers. They can use their native programming language and tools to develop their models, without having to learn the intricacies of DTDL (Digital Twins Definition Language) or the REST APIs for interacting with Azure Digital Twins.

The library can be found at https://github.com/telstra/DigitalTwins-CodeFirst-dotnet

Azure Digital Twins Code First library

The library is available on Github, available under an Apache 2.0 licence. The library provides a number of attributes that you can apply to a class that the library can use to serialise to Digital Twins Definition Language (DTDL).

Given a class like the following:

[DigitalTwin(Version = 1, DisplayName = "Digital Factory - Interface Model")]
public class Factory : TwinBase
{
    [TwinProperty] public string? FactoryId { get; set; }
}

The library can generate the corresponding DTDL for you, automatically generating the ID, types, names, and inferred schema:

{
    "@id": "dtmi:factoryexample:models:factory;1",
    "@type": "Interface",
    "@context": "dtmi:dtdl:context;2",
    "displayName": "Digital Factory - Interface Model",
    "contents": [
        {
            "schema": "string",
            "@type": "Property",
            "name": "factoryId"
        }
    ]
}

As well as generating the serialized DTDL models, given an instance of the classes it can also serialise them to the corresponding twins instance.

{
    "$dtId": null,
    "$etag": null,
    "$metadata": {
        "$model": "dtmi:factoryexample:models:factory;1",
        "PropertyMetadata": {}
    },
    "factoryId": "factory1"
}

For another introduction, see this Telstra Dev blog by Dan Simpson

Chocolate factory example

I have added an example how to use the library, based on the Chocolate Factory example from the Azure sample hands on labs.

This consists of a simple model of a factory building, floor, and production line with multiple steps.

The example has not be merged yet (a pull request is pending), but is available in my fork.

The example includes:

  • Show the serialized model DTDL.
  • Show the serialised twin instance DTDL.
  • Run the Microsoft.Azure.DigitalTwins.Parser to validate the model DTDL.

It also has:

  • PowerShell scripts to create digital twins infrastructure in Azure.
  • Use the Azure.DigitalTwins library client to create digital twins models from the Chocolate Factory classes.
  • Use the client to create digital twins instances from the Chocolate Factory classes.

Example code

Example devices code-first in C

DTDL allows inheritance, similar to object inheritance, allowing you to define a base model with common features, and then more specific models.

For example, in the Chocolate Factory example, the fanning step is derived from the generic production step.

Base class:

using System;
using Telstra.Twins;
using Telstra.Twins.Attributes;

namespace FactoryExample.Devices
{
    [DigitalTwin(Version = 1, DisplayName = "Factory Production Steps - Interface Model")]
    public class ProductionStep : TwinBase
    {
        [TwinProperty] public bool FinalStep { get; set; }

        [TwinProperty] public DateTimeOffset? StartTime { get; set; }

        [TwinProperty] public string? StepId { get; set; }

        [TwinRelationship(DisplayName = "Step Link")]
        public ProductionStep? StepLink { get; set; }

        [TwinProperty] public string? StepName { get; set; }
    }
}

Derived class:

using Telstra.Twins.Attributes;
using Telstra.Twins.Semantics;

namespace FactoryExample.Devices
{
    [DigitalTwin(Version = 1, DisplayName = "Factory Production Step: Fanning/Roasting - Interface Model",
        ExtendsModelId = "dtmi:factoryexample:devices:productionstep;1")]
    public class ProductionStepFanning : ProductionStep
    {
        [TwinProperty(SemanticType = SemanticType.Temperature, Unit = TemperatureUnit.DegreeCelsius,
            Writable = true)]
        public double? ChassisTemperature { get; set; }

        [TwinProperty]
        public double? FanSpeed { get; set; }

        [TwinProperty(SemanticType = SemanticType.TimeSpan, Unit = TimeUnit.Minute)]
        public int? RoastingTime { get; set; }

        [TwinProperty(SemanticType = SemanticType.Power, Unit = PowerUnit.Kilowatt)]
        public double? PowerUsage { get; set; }
    }
}

Generated model definition

You can run the example to turn these code-first classes into the corresponding DTDL:

dotnet run -- --serialize model

This results in DTDL containing a base and derived interfaces corresponding to your C# classes:

{
  "@id": "dtmi:factoryexample:devices:productionstep;1",
  "@type": "Interface",
  "@context": "dtmi:dtdl:context;2",
  "displayName": "Factory Production Steps - Interface Model",
  "contents": [
    {
      "schema": "boolean",
      "@type": "Property",
      "name": "finalStep"
    },
    {
      "schema": "dateTime",
      "@type": "Property",
      "name": "startTime"
    },
    {
      "schema": "string",
      "@type": "Property",
      "name": "stepId"
    },
    {
      "schema": "string",
      "@type": "Property",
      "name": "stepName"
    },
    {
      "target": "dtmi:factoryexample:devices:productionstep;1",
      "name": "stepLink",
      "@type": "Relationship",
      "displayName": "Step Link"
    }
  ]
}
{
  "@id": "dtmi:factoryexample:devices:productionstepfanning;1",
  "@type": "Interface",
  "extends": "dtmi:factoryexample:devices:productionstep;1",
  "@context": "dtmi:dtdl:context;2",
  "displayName": "Factory Production Step: Fanning/Roasting - Interface Model",
  "contents": [
    {
      "writable": true,
      "schema": "double",
      "@type": [
        "Property",
        "Temperature"
      ],
      "unit": "degreeCelsius",
      "name": "chassisTemperature"
    },
    {
      "schema": "double",
      "@type": "Property",
      "name": "fanSpeed"
    },
    {
      "schema": "integer",
      "@type": [
        "Property",
        "TimeSpan"
      ],
      "unit": "minute",
      "name": "roastingTime"
    },
    {
      "schema": "double",
      "@type": [
        "Property",
        "Power"
      ],
      "unit": "kilowatt",
      "name": "powerUsage"
    }
  ]
}

Generated model instances

The example can also generate the DTDL for twin instances of the classes:

dotnet run -- --serialize twin

This results in DTDL of an instance of the fanning step, showing both the base and derived properties:

{
  "$dtId": null,
  "$etag": null,
  "$metadata": {
    "$model": "dtmi:factoryexample:devices:productionstepfanning;1",
    "PropertyMetadata": {}
  },
  "chassisTemperature": 50,
  "fanSpeed": 0.5,
  "roastingTime": null,
  "powerUsage": 100,
  "finalStep": false,
  "startTime": "1970-01-01T00:00:00+00:00",
  "stepId": "line1.step1",
  "stepName": "Fanning Step"
}

Digital Twins Explorer results

You can run the example to create an Azure Digital Twins resource and the sample models and instances.

First of all you need to create the needed resources in an Azure developer subscription.

The example includes scripts to create the needed resources:

Install-Module -Name Az -Scope CurrentUser -Force
Install-Module -Name Az.DigitalTwins -Scope CurrentUser -Force
Register-AzResourceProvider -ProviderNamespace Microsoft.DigitalTwins

Connect-AzAccount
Set-AzContext -SubscriptionId $SubscriptionId

$VerbosePreference = 'Continue'
./deploy-infrastructure.ps1

You can then upload the models to Azure:

$rgName = "rg-codefirsttwins-dev-001"
$dtName = "dt-codefirsttwins-0x$((Get-AzContext).Subscription.Id.Substring(0,4))-dev"
$hostName = (Get-AzDigitalTwinsInstance -ResourceGroupName $rgName -ResourceName $dtName).HostName
dotnet run -- --create model --endpoint "https://$hostName"

And the twin instances:

dotnet run -- --create twin --endpoint "https://$hostName"

If you open the Azure Digital Twins Explorer, you can see the models created:

Azure Digital Twins Explorer showing inheritance and relationships between models

And the instances:

Azure Digital Twins Explorer showing model instances

Next steps

The example currently only shows how to create the models and instances, to show the basics of the library. An end to end solution will need to wire up sensors (e.g.simulators for a test example) and some sort of monitoring display.

The library also has some rough edges and needs a bit more work, but is a good way to convert from dotnet classes to Digital Twins Definition Language, and to get familiar with the specifications.

The introduction also mentions an Azure DevOps pipeline component that can be used to create the needed models as part of a pipeline (although I'm not sure if it's released yet).

This is just a first look at the library, but it is something I will keep my eye on and post when I have spent some more time with it.

Leave a Reply

Your email address will not be published.