diff --git a/DigitalTwins-CodeFirst-dotnet.sln b/DigitalTwins-CodeFirst-dotnet.sln
index 1eb76ec..f7a43c4 100644
--- a/DigitalTwins-CodeFirst-dotnet.sln
+++ b/DigitalTwins-CodeFirst-dotnet.sln
@@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D8068994
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Telstra.Twins.Test", "Tests\Telstra.Twins.Test\Telstra.Twins.Test.csproj", "{4E5BF74D-C0EF-4D13-872C-F40FC347AD67}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{5BA14B5E-B883-4B92-802D-0BA49C93842B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FactoryExample", "Examples\FactoryExample\FactoryExample.csproj", "{B1000F84-515B-4406-8B11-CC80051C531A}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2249F79B-DD17-4FAF-91B7-3E69A5E9546C}"
ProjectSection(SolutionItems) = preProject
README.md = README.md
@@ -67,8 +71,21 @@ Global
{4E5BF74D-C0EF-4D13-872C-F40FC347AD67}.Release|x64.Build.0 = Release|Any CPU
{4E5BF74D-C0EF-4D13-872C-F40FC347AD67}.Release|x86.ActiveCfg = Release|Any CPU
{4E5BF74D-C0EF-4D13-872C-F40FC347AD67}.Release|x86.Build.0 = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|x64.Build.0 = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Debug|x86.Build.0 = Debug|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|x64.ActiveCfg = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|x64.Build.0 = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|x86.ActiveCfg = Release|Any CPU
+ {B1000F84-515B-4406-8B11-CC80051C531A}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{4E5BF74D-C0EF-4D13-872C-F40FC347AD67} = {D8068994-FDA6-41F1-9196-59AADA892F11}
+ {B1000F84-515B-4406-8B11-CC80051C531A} = {5BA14B5E-B883-4B92-802D-0BA49C93842B}
EndGlobalSection
EndGlobal
diff --git a/DigitalTwins-CodeFirst-dotnet.sln.DotSettings b/DigitalTwins-CodeFirst-dotnet.sln.DotSettings
index c69b61e..6881f4a 100644
--- a/DigitalTwins-CodeFirst-dotnet.sln.DotSettings
+++ b/DigitalTwins-CodeFirst-dotnet.sln.DotSettings
@@ -1,2 +1,3 @@
+ True
True
\ No newline at end of file
diff --git a/Examples/FactoryExample/CreateExample.cs b/Examples/FactoryExample/CreateExample.cs
new file mode 100644
index 0000000..ce9cdb8
--- /dev/null
+++ b/Examples/FactoryExample/CreateExample.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.DigitalTwins.Core;
+using Azure.Identity;
+using Telstra.Twins.Core;
+using Telstra.Twins.Services;
+
+namespace FactoryExample
+{
+ public static class CreateExample
+ {
+ //public static async Task CreateModels(string tenantId, string clientId, string clientSecret, string adtEndpoint)
+ public static async Task CreateModelsAsync(string adtEndpoint, CancellationToken cancellationToken)
+ {
+ var modelLibrary = new ModelLibrary();
+ var serializer = new DigitalTwinSerializer(modelLibrary);
+
+ var models = Program.ModelTypes.Select(x => serializer.SerializeModel(x));
+
+ try
+ {
+ //var client = GetDigitalTwinsClient(tenantId, clientId, clientSecret, adtEndpoint);
+ var client = GetDigitalTwinsClient(adtEndpoint);
+ var response = await client.CreateModelsAsync(models, cancellationToken);
+ Console.WriteLine("CREATE MODELS SUCCESS");
+ foreach (var modelData in response.Value)
+ {
+ Console.WriteLine(
+ $"{modelData.Id}: {modelData.LanguageDisplayNames.FirstOrDefault().Value}");
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"CREATE FAILED: {ex.Message}");
+ }
+ }
+
+ public static async Task CreateTwinsAsync(string adtEndpoint, CancellationToken cancellationToken)
+ {
+ var modelLibrary = new ModelLibrary();
+ var serializer = new DigitalTwinSerializer(modelLibrary);
+
+ var factory = Program.CreateFactoryTwin();
+
+ try
+ {
+ //var client = GetDigitalTwinsClient(tenantId, clientId, clientSecret, adtEndpoint);
+ var client = GetDigitalTwinsClient(adtEndpoint);
+
+ await CreateTwinInstanceAsync(client, factory.FactoryId, serializer.SerializeTwin(factory),
+ cancellationToken);
+
+ foreach (var floor in factory.Floors)
+ {
+ await CreateTwinInstanceAsync(client, floor.FloorId, serializer.SerializeTwin(floor),
+ cancellationToken);
+ await CreateTwinRelationshipAsync(client, "floors", factory.FactoryId,
+ floor.FloorId, cancellationToken);
+
+ foreach (var line in floor.RunsLines)
+ {
+ await CreateTwinInstanceAsync(client, line.LineId, serializer.SerializeTwin(line),
+ cancellationToken);
+ await CreateTwinRelationshipAsync(client, "runsLines", floor.FloorId,
+ line.LineId, cancellationToken);
+
+ foreach (var step in line.RunsSteps)
+ {
+ await CreateTwinInstanceAsync(client, step.StepId, serializer.SerializeTwin(step),
+ cancellationToken);
+ await CreateTwinRelationshipAsync(client, "runsSteps", line.LineId,
+ step.StepId, cancellationToken);
+ }
+
+ foreach (var step in line.RunsSteps)
+ {
+ if (step.StepLink != null)
+ {
+ await CreateTwinRelationshipAsync(client, "stepLink", step.StepId,
+ step.StepLink.StepId, cancellationToken);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"CREATE FAILED: {ex.Message}");
+ }
+ }
+
+ private static async Task CreateTwinInstanceAsync(DigitalTwinsClient client, string? id, string dtdl,
+ CancellationToken cancellationToken)
+ {
+ if (id == null)
+ {
+ throw new ArgumentNullException(nameof(id));
+ }
+
+ var basicDigitalTwin = JsonSerializer.Deserialize(dtdl);
+ var response =
+ await client.CreateOrReplaceDigitalTwinAsync(id, basicDigitalTwin, null, cancellationToken);
+ if (response?.Value != null)
+ {
+ Console.WriteLine("CREATE TWIN SUCCESS: Id={0}, ETag={1}", response.Value.Id,
+ response.Value.ETag);
+ }
+ }
+
+ private static async Task CreateTwinRelationshipAsync(DigitalTwinsClient client,
+ string relationshipName, string? sourceId, string? targetId, CancellationToken cancellationToken)
+ {
+ if (sourceId == null)
+ {
+ throw new ArgumentNullException(nameof(sourceId));
+ }
+
+ if (targetId == null)
+ {
+ throw new ArgumentNullException(nameof(targetId));
+ }
+
+ var relationship = new BasicRelationship
+ {
+ Id = $"{sourceId}_{targetId}",
+ Name = relationshipName,
+ SourceId = sourceId,
+ TargetId = targetId
+ };
+ var response = await client.CreateOrReplaceRelationshipAsync(sourceId, relationship.Id,
+ relationship, null, cancellationToken);
+ Console.WriteLine("CREATE RELATIONSHIP SUCCESS: Id={0}, ETag={1}", response.Value.Id,
+ response.Value.ETag);
+ }
+
+ private static DigitalTwinsClient GetDigitalTwinsClient(string adtEndpoint)
+ //private static DigitalTwinsClient GetDigitalTwinsClient(string tenantId, string clientId, string clientSecret, string adtEndpoint)
+ {
+ // These environment variables are necessary for DefaultAzureCredential to use application Id and client secret to login.
+ //Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", clientSecret);
+ //Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId);
+ //Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId);
+
+ // DefaultAzureCredential supports different authentication mechanisms and determines the appropriate credential type based of the environment it is executing in.
+ // It attempts to use multiple credential types in an order until it finds a working credential.
+ var tokenCredential = new DefaultAzureCredential(true);
+
+ var client = new DigitalTwinsClient(
+ new Uri(adtEndpoint),
+ tokenCredential);
+
+ return client;
+ }
+ }
+}
diff --git a/Examples/FactoryExample/Devices/ProductionStep.cs b/Examples/FactoryExample/Devices/ProductionStep.cs
new file mode 100644
index 0000000..abdff26
--- /dev/null
+++ b/Examples/FactoryExample/Devices/ProductionStep.cs
@@ -0,0 +1,27 @@
+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
+ {
+ // ContainsEquipment
+
+ [TwinProperty] public bool FinalStep { get; set; }
+
+ // HasConnectedDevices
+
+ //[TwinProperty] public ProductionStepStatus OperationStatus { 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; }
+ }
+}
diff --git a/Examples/FactoryExample/Devices/ProductionStepFanning.cs b/Examples/FactoryExample/Devices/ProductionStepFanning.cs
new file mode 100644
index 0000000..18a67ae
--- /dev/null
+++ b/Examples/FactoryExample/Devices/ProductionStepFanning.cs
@@ -0,0 +1,23 @@
+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; }
+ }
+}
diff --git a/Examples/FactoryExample/Devices/ProductionStepGrinding.cs b/Examples/FactoryExample/Devices/ProductionStepGrinding.cs
new file mode 100644
index 0000000..b036e27
--- /dev/null
+++ b/Examples/FactoryExample/Devices/ProductionStepGrinding.cs
@@ -0,0 +1,26 @@
+using Telstra.Twins.Attributes;
+using Telstra.Twins.Semantics;
+
+namespace FactoryExample.Devices
+{
+ [DigitalTwin(Version = 1, DisplayName = "Factory Production Step: Grinding/Crushing - Interface Model",
+ ExtendsModelId = "dtmi:factoryexample:devices:productionstep;1")]
+ public class ProductionStepGrinding : ProductionStep
+ {
+ [TwinProperty(SemanticType = SemanticType.Temperature, Unit = TemperatureUnit.DegreeCelsius,
+ Writable = true)]
+ public double? ChassisTemperature { get; set; }
+
+ [TwinProperty(SemanticType = SemanticType.Force, Unit = ForceUnit.Newton)]
+ public double? Force { get; set; }
+
+ [TwinProperty(SemanticType = SemanticType.TimeSpan, Unit = TimeUnit.Minute)]
+ public int? GrindingTime { get; set; }
+
+ [TwinProperty(SemanticType = SemanticType.Power, Unit = PowerUnit.Kilowatt)]
+ public double? PowerUsage { get; set; }
+
+ [TwinProperty(SemanticType = SemanticType.Frequency, Unit = FrequencyUnit.Hertz)]
+ public double? Vibration { get; set; }
+ }
+}
diff --git a/Examples/FactoryExample/Devices/ProductionStepMoulding.cs b/Examples/FactoryExample/Devices/ProductionStepMoulding.cs
new file mode 100644
index 0000000..7a0f2e1
--- /dev/null
+++ b/Examples/FactoryExample/Devices/ProductionStepMoulding.cs
@@ -0,0 +1,17 @@
+using Telstra.Twins.Attributes;
+using Telstra.Twins.Semantics;
+
+namespace FactoryExample.Devices
+{
+ [DigitalTwin(Version = 1, DisplayName = "Factory Production Step: Moulding - Interface Model",
+ ExtendsModelId = "dtmi:factoryexample:devices:productionstep;1")]
+ public class ProductionStepMoulding : ProductionStep
+ {
+ [TwinProperty(SemanticType = SemanticType.Temperature, Unit = TemperatureUnit.DegreeCelsius,
+ Writable = true)]
+ public double? ChassisTemperature { get; set; }
+
+ [TwinProperty]
+ public double? PowerUsage { get; set; }
+ }
+}
diff --git a/Examples/FactoryExample/Devices/ProductionStepStatus.cs b/Examples/FactoryExample/Devices/ProductionStepStatus.cs
new file mode 100644
index 0000000..ca629e8
--- /dev/null
+++ b/Examples/FactoryExample/Devices/ProductionStepStatus.cs
@@ -0,0 +1,9 @@
+namespace FactoryExample.Devices
+{
+ public enum ProductionStepStatus
+ {
+ Unknown = 0,
+ Offline = 1,
+ Online = 2
+ }
+}
diff --git a/Examples/FactoryExample/FactoryExample.csproj b/Examples/FactoryExample/FactoryExample.csproj
new file mode 100644
index 0000000..864e46f
--- /dev/null
+++ b/Examples/FactoryExample/FactoryExample.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ enable
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Examples/FactoryExample/Models/Factory.cs b/Examples/FactoryExample/Models/Factory.cs
new file mode 100644
index 0000000..870dab7
--- /dev/null
+++ b/Examples/FactoryExample/Models/Factory.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using FactoryExample.Schema;
+using Telstra.Twins;
+using Telstra.Twins.Attributes;
+
+namespace FactoryExample.Models
+{
+ [DigitalTwin(Version = 1, DisplayName = "Digital Factory - Interface Model")]
+ public class Factory : TwinBase
+ {
+ [TwinProperty] public string? Country { get; set; }
+
+ [TwinProperty] public string? FactoryId { get; set; }
+
+ [TwinProperty(Writable = true)] public string? FactoryName { get; set; }
+
+ [TwinRelationship(DisplayName = "Has Floors")]
+ public IList Floors { get; } = new List();
+
+ [TwinProperty] public GeoCord? GeoLocation { get; set; }
+
+ [TwinProperty] public DateTimeOffset LastSupplyDate { get; set; }
+
+ // ServesRetailer
+ // SuppliedBy
+ // TransportationBy
+
+ [TwinProperty(Writable = true)] public string? Tags { get; set; }
+
+ [TwinProperty(Writable = true)] public string? ZipCode { get; set; }
+ }
+}
diff --git a/Examples/FactoryExample/Models/FactoryFloor.cs b/Examples/FactoryExample/Models/FactoryFloor.cs
new file mode 100644
index 0000000..fd99142
--- /dev/null
+++ b/Examples/FactoryExample/Models/FactoryFloor.cs
@@ -0,0 +1,27 @@
+using System.Collections.Generic;
+using Telstra.Twins;
+using Telstra.Twins.Attributes;
+using Telstra.Twins.Semantics;
+
+namespace FactoryExample.Models
+{
+ [DigitalTwin(Version = 1, DisplayName = "Digital Factory - Interface Model")]
+ public class FactoryFloor : TwinBase
+ {
+ [TwinProperty] public double? ComfortIndex { get; set; }
+
+ [TwinProperty(Writable = true)] public string? FloorId { get; set; }
+
+ // FloorHasRooms
+ // FloorHasZones
+
+ [TwinProperty(Writable = true)] public string? FloorName { get; set; }
+
+ [TwinRelationship(DisplayName = "Runs Production Lines")]
+ public IList RunsLines { get; } = new List();
+
+ [TwinProperty(SemanticType = SemanticType.Temperature, Unit = TemperatureUnit.DegreeCelsius,
+ Writable = true)]
+ public double? Temperature { get; set; }
+ }
+}
diff --git a/Examples/FactoryExample/Models/ProductionLine.cs b/Examples/FactoryExample/Models/ProductionLine.cs
new file mode 100644
index 0000000..cb47cb5
--- /dev/null
+++ b/Examples/FactoryExample/Models/ProductionLine.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using FactoryExample.Devices;
+using Telstra.Twins;
+using Telstra.Twins.Attributes;
+
+namespace FactoryExample.Models
+{
+ [DigitalTwin(Version = 1, DisplayName = "Factory Production Line - Interface Model")]
+ public class ProductionLine : TwinBase
+ {
+ // ContainsEquipment
+
+ [TwinProperty(Writable = true)] public string? CurrentProductId { get; set; }
+
+ [TwinProperty(Writable = true)] public string? LineId { get; set; }
+
+ [TwinProperty(Writable = true)] public string? LineName { get; set; }
+
+ //[TwinProperty] public ProductionLineStatus LineOperationStatus { get; set; }
+
+ [TwinProperty(Writable = true)] public int? ProductBatchNumber { get; set; }
+
+ [TwinRelationship(DisplayName = "Runs Steps")]
+ public IList RunsSteps { get; } = new List();
+ }
+}
diff --git a/Examples/FactoryExample/Models/ProductionLineStatus.cs b/Examples/FactoryExample/Models/ProductionLineStatus.cs
new file mode 100644
index 0000000..b1bc2ce
--- /dev/null
+++ b/Examples/FactoryExample/Models/ProductionLineStatus.cs
@@ -0,0 +1,9 @@
+namespace FactoryExample.Models
+{
+ public enum ProductionLineStatus
+ {
+ Unknown = 0,
+ Offline = 1,
+ Online = 2
+ }
+}
diff --git a/Examples/FactoryExample/ParseExample.cs b/Examples/FactoryExample/ParseExample.cs
new file mode 100644
index 0000000..3c5dedc
--- /dev/null
+++ b/Examples/FactoryExample/ParseExample.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure;
+using Microsoft.Azure.DigitalTwins.Parser;
+using Telstra.Twins.Core;
+using Telstra.Twins.Services;
+
+namespace FactoryExample
+{
+ public static class ParseExample
+ {
+ public static async Task ParseModelsAsync(CancellationToken cancellationToken)
+ {
+ var modelLibrary = new ModelLibrary();
+ var serializer = new DigitalTwinSerializer(modelLibrary);
+
+ var models = Program.ModelTypes.Select(x => serializer.SerializeModel(x));
+
+ try
+ {
+ var parser = new ModelParser();
+ var entityInfos = await parser.ParseAsync(models);
+ Console.WriteLine("PARSE SUCCESS:");
+ foreach (var kvp in entityInfos)
+ {
+ Console.WriteLine(
+ $"[{kvp.Key}] = {kvp.Value.EntityKind} {kvp.Value.Id} ({kvp.Value.DisplayName.FirstOrDefault().Value})");
+ }
+ }
+ catch (ParsingException ex)
+ {
+ Console.WriteLine($"PARSE FAILED: {ex.Message}");
+ Console.WriteLine("ERRORS:");
+ var count = 0;
+ foreach (var error in ex.Errors)
+ {
+ Console.WriteLine($"{++count}. {error}");
+ }
+ }
+ catch (RequestFailedException ex)
+ {
+ Console.WriteLine($"REQUEST FAILED: {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/Examples/FactoryExample/Program.cs b/Examples/FactoryExample/Program.cs
new file mode 100644
index 0000000..a9bffa2
--- /dev/null
+++ b/Examples/FactoryExample/Program.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using FactoryExample.Devices;
+using FactoryExample.Models;
+using FactoryExample.Schema;
+using Microsoft.Extensions.Configuration;
+
+namespace FactoryExample
+{
+ public static class Program
+ {
+ public static readonly Type[] ModelTypes =
+ {
+ typeof(Factory), typeof(FactoryFloor), typeof(ProductionLine), typeof(ProductionStep),
+ typeof(ProductionStepGrinding), typeof(ProductionStepFanning), typeof(ProductionStepMoulding)
+ };
+
+ public static Factory CreateFactoryTwin()
+ {
+ var production1Step3Moulding = new ProductionStepMoulding
+ {
+ ChassisTemperature = 50,
+ FinalStep = true,
+ //OperationStatus = ProductionStepStatus.Online,
+ PowerUsage = 100,
+ StartTime = DateTimeOffset.UnixEpoch,
+ StepId = "line1.step3",
+ StepName = "Moulding Step"
+ };
+
+ var production1Step2Grinding = new ProductionStepGrinding
+ {
+ ChassisTemperature = 50,
+ FinalStep = false,
+ Force = 8.0,
+ GrindingTime = 30,
+ //OperationStatus = ProductionStepStatus.Online,
+ PowerUsage = 100,
+ StartTime = DateTimeOffset.UnixEpoch,
+ StepId = "line1.step2",
+ StepName = "Grinding Step",
+ StepLink = production1Step3Moulding
+ };
+
+ var production1Step1Fanning = new ProductionStepFanning()
+ {
+ ChassisTemperature = 50,
+ FinalStep = false,
+ FanSpeed = 0.5,
+ //OperationStatus = ProductionStepStatus.Online,
+ PowerUsage = 100,
+ StartTime = DateTimeOffset.UnixEpoch,
+ StepId = "line1.step1",
+ StepName = "Fanning Step",
+ StepLink = production1Step2Grinding
+ };
+
+ var productionLine1 = new ProductionLine
+ {
+ CurrentProductId = "product5",
+ LineId = "line1",
+ LineName = "Production Line 1",
+ //LineOperationStatus = ProductionLineStatus.Online,
+ ProductBatchNumber = 6
+ };
+ productionLine1.RunsSteps.Add(production1Step1Fanning);
+ productionLine1.RunsSteps.Add(production1Step2Grinding);
+ productionLine1.RunsSteps.Add(production1Step3Moulding);
+
+ var factory1Floor1 = new FactoryFloor
+ {
+ ComfortIndex = 0.8, FloorId = "factory1.floor1", FloorName = "Factory 1 Floor 1", Temperature = 23
+ };
+ factory1Floor1.RunsLines.Add(productionLine1);
+
+ var factory1 = new Factory
+ {
+ Country = "AU",
+ FactoryId = "factory1",
+ FactoryName = "Chocolate Factory",
+ GeoLocation = new GeoCord { Latitude = -27.4705, Longitude = 153.026 },
+ LastSupplyDate = new DateTimeOffset(2021, 11, 17, 18, 37, 0, TimeSpan.FromHours(10)),
+ Tags = String.Empty,
+ ZipCode = "4000"
+ };
+ factory1.Floors.Add(factory1Floor1);
+ return factory1;
+ }
+
+ public static async Task Main(string[] args)
+ {
+ Console.WriteLine("Digital Twin Code First Factory Example");
+
+ var configuration = new ConfigurationBuilder()
+ .AddJsonFile("appsettings.json", true)
+ .AddEnvironmentVariables()
+ .AddCommandLine(args)
+ .Build();
+
+ var show = configuration.GetValue("serialize");
+ if (!string.IsNullOrWhiteSpace(show))
+ {
+ switch (show.ToLowerInvariant())
+ {
+ case "model":
+ SerializeExample.SerializeModels();
+ return;
+ case "twin":
+ SerializeExample.SerializeTwins();
+ return;
+ }
+ }
+
+ var check = configuration.GetValue("parse");
+ if (!string.IsNullOrWhiteSpace(check))
+ {
+ switch (check.ToLowerInvariant())
+ {
+ case "model":
+ await ParseExample.ParseModelsAsync(CancellationToken.None);
+ return;
+ }
+ }
+
+ var create = configuration.GetValue("create");
+ if (!string.IsNullOrWhiteSpace(create))
+ {
+ var adtEndpoint = configuration.GetValue("endpoint");
+ switch (create.ToLowerInvariant())
+ {
+ case "model":
+ await ParseExample.ParseModelsAsync(CancellationToken.None);
+ await CreateExample.CreateModelsAsync(adtEndpoint, CancellationToken.None);
+ return;
+ case "twin":
+ await CreateExample.CreateTwinsAsync(adtEndpoint, CancellationToken.None);
+ return;
+ }
+ }
+
+ ShowHelp();
+ }
+
+ private static void ShowHelp()
+ {
+ Console.WriteLine(" --serialize model : shows serialized model examples");
+ Console.WriteLine(" --serialize twin : shows serialized twin examples");
+ Console.WriteLine(" --parse model : parse and validate the example model");
+ Console.WriteLine(" --create model --endpoint : parse and upload the example model");
+ }
+ }
+}
diff --git a/Examples/FactoryExample/ReadMe.md b/Examples/FactoryExample/ReadMe.md
new file mode 100644
index 0000000..38b717a
--- /dev/null
+++ b/Examples/FactoryExample/ReadMe.md
@@ -0,0 +1,103 @@
+Code First Azure Digital Twins -- End to end example
+====================================================
+
+Requirements:
+
+* dotnet (3.1 LTS or higher)
+* PowerShell (7.0 LTS or higher)
+
+Based on https://github.com/Azure-Samples/digital-twins-samples/tree/master/HandsOnLab
+
+Create infrastructure
+---------------------
+
+You need to create a digital twins instance in Azure to deploy the model to. There is a
+script `deploy-infrastructure.ps1` that will created the needed resources.
+
+To run the script you need to load the required PowerShell modules, then connect to your
+Azure account and set the context for the subscription you want to use, then run the
+script.
+
+``` pwsh
+ 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
+```
+
+Digital Twins Explorer
+----------------------
+
+After you have created the infrastructure, open `https://portal.azure.com/` and go to the resource group.
+
+Open the Azure Digital Twins resource.
+
+Copy the Host name, and then open the Azure Digital Twins Explorer (preview).
+
+For the Azure Digital Twins URL, use `https://`, with the Host name copied from above.
+
+The explorer should start out empty (see below).
+
+Running basic checks
+--------------------
+
+To see the example serialized model:
+
+```
+dotnet run -- --serialize model
+```
+
+To see the example serialized twin instances:
+
+```
+dotnet run -- --serialize twin
+```
+
+To run parse and validate the model with `Microsoft.Azure.DigitalTwins.Parser`:
+
+```
+dotnet run -- --parse model
+```
+
+Running the example to create models and twins
+----------------------------------------------
+
+To upload the models to Azure:
+
+``` pwsh
+$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"
+```
+
+After this, if you refresh the Models in Digital Twins Explorer, you will see the uploaded models.
+
+
+
+You then need to upload the Twin instances:
+
+``` pwsh
+dotnet run -- --create twin --endpoint "https://$hostName"
+```
+
+To see the Twin instances, ensure the query explorer has the default query `SELECT * FROM digitaltwins`,
+and then click Run Query. The twin instances should appear in the Twins list and Twin Graph.
+
+
+
+Cleanup
+-------
+
+After running the example, you can clean up the Azure resources (to save money).
+
+``` pwsh
+./remove-infrastructure.ps1
+```
+
+
diff --git a/Examples/FactoryExample/Schema/GeoCord.cs b/Examples/FactoryExample/Schema/GeoCord.cs
new file mode 100644
index 0000000..8fb250a
--- /dev/null
+++ b/Examples/FactoryExample/Schema/GeoCord.cs
@@ -0,0 +1,11 @@
+using Telstra.Twins.Attributes;
+
+namespace FactoryExample.Schema
+{
+ [DigitalTwin(Version = 1)]
+ public class GeoCord
+ {
+ [TwinProperty("lat")] public double Latitude { get; set; }
+ [TwinProperty("lon")] public double Longitude { get; set; }
+ }
+}
diff --git a/Examples/FactoryExample/SerializeExample.cs b/Examples/FactoryExample/SerializeExample.cs
new file mode 100644
index 0000000..b9d6b72
--- /dev/null
+++ b/Examples/FactoryExample/SerializeExample.cs
@@ -0,0 +1,39 @@
+using System;
+using FactoryExample.Devices;
+using Telstra.Twins.Core;
+using Telstra.Twins.Services;
+
+namespace FactoryExample
+{
+ public static class SerializeExample
+ {
+ public static void SerializeModels()
+ {
+ var modelLibrary = new ModelLibrary();
+ var serializer = new DigitalTwinSerializer(modelLibrary);
+
+ foreach (var modelType in Program.ModelTypes)
+ {
+ var modelDtdl = serializer.SerializeModel(modelType);
+ Console.WriteLine(modelDtdl);
+ Console.WriteLine();
+ }
+ }
+
+ public static void SerializeTwins()
+ {
+ var modelLibrary = new ModelLibrary();
+ var serializer = new DigitalTwinSerializer(modelLibrary);
+
+ var factory = Program.CreateFactoryTwin();
+
+ var twin1Dtdl = serializer.SerializeTwin(factory);
+ Console.WriteLine(twin1Dtdl);
+ Console.WriteLine();
+
+ var twin2Dtdl = serializer.SerializeTwin(factory.Floors[0].RunsLines[0].RunsSteps[0] as ProductionStepFanning);
+ Console.WriteLine(twin2Dtdl);
+ Console.WriteLine();
+ }
+ }
+}
diff --git a/Examples/FactoryExample/deploy-infrastructure.ps1 b/Examples/FactoryExample/deploy-infrastructure.ps1
new file mode 100644
index 0000000..9796c78
--- /dev/null
+++ b/Examples/FactoryExample/deploy-infrastructure.ps1
@@ -0,0 +1,86 @@
+#!/usr/bin/env pwsh
+
+<#
+.SYNOPSIS
+ Deploy the Azure infrastructure for the project.
+
+.NOTES
+
+ Running these scripts requires the following to be installed:
+ * PowerShell, https://github.com/PowerShell/PowerShell
+ * Azure PowerShell module, https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
+ * Azure Digital Twins module (preview installed separately)
+
+ You also need to connect to Azure (log in), and set the desired subscripition context.
+
+ Follow standard naming conventions from Azure Cloud Adoption Framework,
+ with an additional organisation or subscription identifier (after app name) in global names
+ to make them unique.
+ https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming
+
+ Follow standard tagging conventions from Azure Cloud Adoption Framework.
+ https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-tagging
+
+.EXAMPLE
+
+ 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
+#>
+[CmdletBinding()]
+param (
+ ## Number of initial scripts to skip (if they have already been run)
+ [int]$Skip = 0,
+ ## Deployment environment, e.g. Prod, Dev, QA, Stage, Test.
+ [string]$Environment = $ENV:DEPLOY_ENVIRONMENT ?? 'Dev',
+ ## The Azure region where the resource is deployed.
+ [string]$Location = $ENV:DEPLOY_LOCATION ?? 'australiaeast',
+ ## Identifier for the organisation (or subscription) to make global names unique.
+ [string]$OrgId = $ENV:DEPLOY_ORGID ?? "0x$((Get-AzContext).Subscription.Id.Substring(0,4))"
+)
+
+# Following standard naming conventions from Azure Cloud Adoption Framework
+# https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming
+# With an additional organisation or subscription identifier (after app name) in global names to make them unique
+
+# Following standard tagging conventions from Azure Cloud Adoption Framework
+# https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-tagging
+
+
+# Pre-requisites:
+#
+# Running these scripts requires the following to be installed:
+# * PowerShell, https://github.com/PowerShell/PowerShell
+# * Azure PowerShell module, https://docs.microsoft.com/en-us/powershell/azure/install-az-ps
+# Install-Module -Name Az -Scope CurrentUser -Force
+# * Azure Digital Twins module (preview installed separately)
+# Install-Module -Name Az.DigitalTwins -Scope CurrentUser -Force
+# Register-AzResourceProvider -ProviderNamespace Microsoft.DigitalTwins
+#
+# You also need to authenticate and set subscription you are using:
+# Connect-AzAccount
+# Set-AzContext -SubscriptionId $SubscriptionId
+#
+# To see messages, set verbose preference before running:
+# $VerbosePreference = 'Continue'
+# ./deploy-infrastructure.ps1
+
+$ErrorActionPreference="Stop"
+
+$SubscriptionId = (Get-AzContext).Subscription.Id
+Write-Verbose "Using context subscription ID $SubscriptionId"
+
+$scriptItems = Get-ChildItem "$PSScriptRoot/infrastructure" -Filter '*.ps1' `
+ | Sort-Object -Property Name `
+ | Select-Object -Skip $Skip
+
+$scriptItems | ForEach-Object {
+ Write-Verbose "Running $($_.Name)"
+ & $_.FullName -Environment $Environment -Location $Location -OrgId $OrgId
+}
+
+Write-Verbose "Deployment Complete"
diff --git a/Examples/FactoryExample/images/model-graph-example.png b/Examples/FactoryExample/images/model-graph-example.png
new file mode 100644
index 0000000..a331fac
Binary files /dev/null and b/Examples/FactoryExample/images/model-graph-example.png differ
diff --git a/Examples/FactoryExample/images/twins-graph-example.png b/Examples/FactoryExample/images/twins-graph-example.png
new file mode 100644
index 0000000..1ddae21
Binary files /dev/null and b/Examples/FactoryExample/images/twins-graph-example.png differ
diff --git a/Examples/FactoryExample/infrastructure/0001 - Create resource group.ps1 b/Examples/FactoryExample/infrastructure/0001 - Create resource group.ps1
new file mode 100644
index 0000000..177a111
--- /dev/null
+++ b/Examples/FactoryExample/infrastructure/0001 - Create resource group.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+
+[CmdletBinding()]
+param (
+ [string]$Environment,
+ [string]$Location,
+ [string]$OrgId
+)
+
+$appName = 'codefirsttwins'
+
+$rgName = "rg-$appName-$Environment-001".ToLowerInvariant()
+$tags = @{ WorkloadName = 'codefirsttwins'; DataClassification = 'Non-business'; Criticality = 'Low'; `
+ BusinessUnit = 'Demo'; ApplicationName = $appName; Env = $Environment }
+
+Write-Verbose "Creating resource group $rgName in location $Location"
+
+New-AzResourceGroup -Name $rgName -Location $Location -Tag $tags -Force
diff --git a/Examples/FactoryExample/infrastructure/0002 - Create Digital Twins service.ps1 b/Examples/FactoryExample/infrastructure/0002 - Create Digital Twins service.ps1
new file mode 100644
index 0000000..1c3302a
--- /dev/null
+++ b/Examples/FactoryExample/infrastructure/0002 - Create Digital Twins service.ps1
@@ -0,0 +1,31 @@
+#!/usr/bin/env pwsh
+
+[CmdletBinding()]
+param (
+ [string]$Environment,
+ [string]$Location,
+ [string]$OrgId
+)
+
+$appName = 'codefirsttwins'
+$roleName = 'Azure Digital Twins Data Owner'
+
+$rgName = "rg-$appName-$Environment-001".ToLowerInvariant()
+$rg = Get-AzResourceGroup -Name $rgName
+
+$dtName = "dt-$appName-$OrgId-$Environment".ToLowerInvariant()
+
+$contextAccount = (Get-AzContext).Account
+
+Write-Verbose "Creating digital twins service $dtName in resource group $rgName"
+
+New-AzDigitalTwinsInstance -ResourceName $dtName `
+ -ResourceGroupName $rgName -Location $rg.Location -Tag $rg.Tags
+
+Write-Verbose "Assigning data permissions to digital twins service $dtName to $contextAccount"
+
+$user = Get-AzADUser -UserPrincipalName $contextAccount
+$dti = Get-AzDigitalTwinsInstance -ResourceGroupName $rgName -ResourceName $dtName
+
+New-AzRoleAssignment -ObjectId $user.Id -RoleDefinitionName $roleName -Scope $dti.Id
+
diff --git a/Examples/FactoryExample/infrastructure/0003 - Create IOT hub.ps1 b/Examples/FactoryExample/infrastructure/0003 - Create IOT hub.ps1
new file mode 100644
index 0000000..64b8e29
--- /dev/null
+++ b/Examples/FactoryExample/infrastructure/0003 - Create IOT hub.ps1
@@ -0,0 +1,22 @@
+#!/usr/bin/env pwsh
+
+[CmdletBinding()]
+param (
+ [string]$Environment,
+ [string]$Location,
+ [string]$OrgId
+)
+
+$appName = 'codefirsttwins'
+$iotSku = 'S1'
+$iotUnits = 1
+
+$rgName = "rg-$appName-$Environment-001".ToLowerInvariant()
+$rg = Get-AzResourceGroup -Name $rgName
+
+$iotName = "iot-$appName-$OrgId-$Environment".ToLowerInvariant()
+
+Write-Verbose "Creating IOT hub $iotName in resource group $rgName"
+
+New-AzIotHub -Name $iotName -SkuName $iotSku -Units $iotUnits `
+ -ResourceGroupName $rgName -Location $rg.Location -Tag $rg.Tags
diff --git a/Examples/FactoryExample/infrastructure/9001 - Output settings.ps1 b/Examples/FactoryExample/infrastructure/9001 - Output settings.ps1
new file mode 100644
index 0000000..e694aae
--- /dev/null
+++ b/Examples/FactoryExample/infrastructure/9001 - Output settings.ps1
@@ -0,0 +1,18 @@
+#!/usr/bin/env pwsh
+
+[CmdletBinding()]
+param (
+ [string]$Environment,
+ [string]$Location,
+ [string]$OrgId
+)
+
+$appName = 'codefirsttwins'
+$rgName = "rg-$appName-$Environment-001".ToLowerInvariant()
+$dtName = "dt-$appName-$OrgId-$Environment".ToLowerInvariant()
+
+Write-Verbose 'Digital Twins host name:'
+(Get-AzDigitalTwinsInstance -ResourceGroupName $rgName -ResourceName $dtName).HostName
+
+Write-Verbose 'IOT Hub host name:'
+(Get-AzIotHub $rgName).Properties.HostName
diff --git a/Examples/FactoryExample/remove-infrastructure.ps1 b/Examples/FactoryExample/remove-infrastructure.ps1
new file mode 100644
index 0000000..594af16
--- /dev/null
+++ b/Examples/FactoryExample/remove-infrastructure.ps1
@@ -0,0 +1,24 @@
+#!/usr/bin/env pwsh
+
+[CmdletBinding()]
+param (
+ ## Number of initial scripts to skip (if they have already been run)
+ [int]$Skip = 0,
+ ## Deployment environment, e.g. Prod, Dev, QA, Stage, Test.
+ [string]$Environment = $ENV:DEPLOY_ENVIRONMENT ?? 'Dev',
+ ## The Azure region where the resource is deployed.
+ [string]$Location = $ENV:DEPLOY_LOCATION ?? 'australiaeast',
+ ## Identifier for the organisation (or subscription) to make global names unique.
+ [string]$OrgId = $ENV:DEPLOY_ORGID ?? "0x$((Get-AzContext).Subscription.Id.Substring(0,4))"
+)
+
+$ErrorActionPreference="Stop"
+
+$SubscriptionId = (Get-AzContext).Subscription.Id
+Write-Verbose "Removing from context subscription ID $SubscriptionId"
+
+$appName = 'codefirsttwins'
+
+$rgName = "rg-$appName-$Environment-001".ToLowerInvariant()
+
+Remove-AzResourceGroup -Name $rgName