Skip to content

Sample, TwinCreate in DigitalTwinService and TwinCreateCommand, Sample DTDL models #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,4 @@ src/DTDL.Client/build
src/DTDL.Functions/ApplicationInsights.config
sample/DTDL.SignalR.Client.Wpf/appsettings.json
sample/Moulding.Factory.Acceptance.Test.FlowRunner.Sample/appsettings.json
/sample/Atc.Azure.DigitalTwin.Console.Sample/appsettings.development.json
9 changes: 9 additions & 0 deletions Atc.Azure.DigitalTwin.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.DigitalTwin", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Atc.Azure.DigitalTwin.CLI", "src\Atc.Azure.DigitalTwin.CLI\Atc.Azure.DigitalTwin.CLI.csproj", "{21358DD8-1426-42F6-9A47-6C6B0839E083}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample", "sample", "{B239F551-0DB4-4A8C-9C90-BC424E3F58F7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Atc.Azure.DigitalTwin.Console.Sample", "sample\Atc.Azure.DigitalTwin.Console.Sample\Atc.Azure.DigitalTwin.Console.Sample.csproj", "{44E5F667-1DCD-4956-9546-4449C290C432}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -25,13 +29,18 @@ Global
{21358DD8-1426-42F6-9A47-6C6B0839E083}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21358DD8-1426-42F6-9A47-6C6B0839E083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21358DD8-1426-42F6-9A47-6C6B0839E083}.Release|Any CPU.Build.0 = Release|Any CPU
{44E5F667-1DCD-4956-9546-4449C290C432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44E5F667-1DCD-4956-9546-4449C290C432}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44E5F667-1DCD-4956-9546-4449C290C432}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44E5F667-1DCD-4956-9546-4449C290C432}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0CBBDAF0-BEB0-42AB-A015-56C376BC69DE} = {A0BB13A9-8103-4C15-9A5A-6090385EA52A}
{21358DD8-1426-42F6-9A47-6C6B0839E083} = {A0BB13A9-8103-4C15-9A5A-6090385EA52A}
{44E5F667-1DCD-4956-9546-4449C290C432} = {B239F551-0DB4-4A8C-9C90-BC424E3F58F7}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {04120463-05C5-417B-8D7A-2D7D35B71A07}
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ Azure Digital Twin library, which contains common services for Azure Digital Twi
- [Update](#update)
- [Usage](#usage)
- [Option --help](#option---help)
- [Sample Project](#sample-project)
- [Atc.Azure.DigitalTwin.Console.Sample](#atcazuredigitaltwinconsolesample)
- [Requirements](#requirements)
- [How to contribute](#how-to-contribute)

Expand Down Expand Up @@ -208,14 +210,14 @@ Since the tool is published as a .NET Tool, it can be launched from anywhere usi
atc-azure-digitaltwin --help

USAGE:
Atc.Azure.DigitalTwin.CLI.exe [OPTIONS] <COMMAND>
atc-azure-digitaltwin.exe [OPTIONS] <COMMAND>

EXAMPLES:
Atc.Azure.DigitalTwin.CLI.exe model decommission --tenantId -a <adt-instance-url> -m <model-id>
Atc.Azure.DigitalTwin.CLI.exe model validate -d <directory-path>
Atc.Azure.DigitalTwin.CLI.exe route create
Atc.Azure.DigitalTwin.CLI.exe route delete
Atc.Azure.DigitalTwin.CLI.exe twin count --tenantId -a <adt-instance-url>
atc-azure-digitaltwin.exe model decommission --tenantId -a <adt-instance-url> -m <model-id>
atc-azure-digitaltwin.exe model validate -d <directory-path>
atc-azure-digitaltwin.exe route create
atc-azure-digitaltwin.exe route delete
atc-azure-digitaltwin.exe twin count --tenantId -a <adt-instance-url>

OPTIONS:
-h, --help Prints help information
Expand All @@ -227,6 +229,23 @@ COMMANDS:
twin Operations related to twins
```

# Sample Project

## Atc.Azure.DigitalTwin.Console.Sample

This console application serves as an example to demonstrate the utilization of services provided by the library. It showcases a series of operations related to Digital Twins using the example DTDL (Digital Twins Definition Language) models.

The sample application executes the following operations:

1. **Validate Models:** Validates the DTDL models located in the [models folder](sample/models/).
2. **Load Models:** Loads the DTDL models from the [models folder](sample/models/).
3. **Create Models:** Registers the loaded models with a Digital Twin instance.
4. **Retrieve Model:** Fetches a specific model from the Digital Twin instance.
5. **Create Digital Twin:** Generates a digital twin instance of a Press Machine.
> **Note:** This twin inherits properties from `TwinModelBase`, which provides essential attributes for twin creation.
6. **Retrieve Twins:** Collects all existing twins.
7. **Delete Twin:** Removes the specified twin instance.

# Requirements

* [.NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
Expand Down
7 changes: 6 additions & 1 deletion sample/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ dotnet_diagnostic.S1075.severity = none # Refactor your code not to

##########################################
# Custom - Code Analyzers Rules
##########################################
##########################################

MA0051.maximum_lines_per_method = 200
MA0051.maximum_statements_per_method = 50

dotnet_diagnostic.MA0079.severity = none #
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Atc.Azure.DigitalTwin\Atc.Azure.DigitalTwin.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="appsettings.development.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Atc.Azure.DigitalTwin.Console.Sample.Contracts;

public static class Names
{
public const string PressMachineModelId = "dtmi:com:atc:PressMachine";
public const int PressMachineVersion = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Atc.Azure.DigitalTwin.Console.Sample.Contracts;

public class PressMachine() : TwinModelBase(Names.PressMachineModelId, Names.PressMachineVersion)
{
[JsonPropertyName("manufacturer")]
public string Manufacturer { get; set; } = string.Empty;

[JsonPropertyName("serialNumber")]
public string SerialNumber { get; set; } = string.Empty;

[JsonPropertyName("operationalStatus")]
public string OperationalStatus { get; set; } = string.Empty;

[JsonPropertyName("maintenanceSchedule")]
public string MaintenanceSchedule { get; set; } = string.Empty;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Atc.Azure.DigitalTwin.Console.Sample.Contracts;

public class TwinModelBase
{
[JsonPropertyName(DigitalTwinsJsonPropertyNames.DigitalTwinMetadata)]
public DigitalTwinMetadata Metadata { get; set; } = new ();

[JsonIgnore]
public ETag? ETag
=> ETagAsString is not null
? new ETag(ETagAsString)
: null;

[JsonPropertyName("$etag")]
public string? ETagAsString { get; set; }

public TwinModelBase(
string modelTwinId,
int version)
=> Metadata.ModelId = $"{modelTwinId};{version}";
}
10 changes: 10 additions & 0 deletions sample/Atc.Azure.DigitalTwin.Console.Sample/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
global using System.Text.Json.Serialization;
global using Atc.Azure.DigitalTwin.Console.Sample.Contracts;
global using Atc.Azure.DigitalTwin.Extensions;
global using Atc.Azure.DigitalTwin.Options;
global using Atc.Azure.DigitalTwin.Parsers;
global using Atc.Azure.DigitalTwin.Services;
global using Azure;
global using Azure.DigitalTwins.Core;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Logging;
129 changes: 129 additions & 0 deletions sample/Atc.Azure.DigitalTwin.Console.Sample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
namespace Atc.Azure.DigitalTwin.Console.Sample;

public static class Program
{
public static async Task Main()
{
var configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile("appsettings.development.json", optional: true, reloadOnChange: true)
.Build();

var digitalTwinOptions = new DigitalTwinOptions();
configuration.GetRequiredSection("DigitalTwinOptions").Bind(digitalTwinOptions);

using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});

var logger = loggerFactory.CreateLogger("Program");
using var cts = new CancellationTokenSource();

var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
var targetDirectory = Path.GetFullPath(Path.Combine(baseDirectory, @"..\..\..\.."));
var modelsPath = new DirectoryInfo(Path.Combine(targetDirectory, "models"));

var modelRepositoryService = new ModelRepositoryService(
loggerFactory,
new DigitalTwinParser(loggerFactory));

var modelsValid = await modelRepositoryService.ValidateModels(modelsPath);
if (!modelsValid)
{
logger.LogError("Failed to validate models");
return;
}

var loadedModelContent = await modelRepositoryService.LoadModelContent(modelsPath);
if (!loadedModelContent)
{
logger.LogError($"Failed to load models from {modelsPath.FullName}");
return;
}

var digitalTwinService = new DigitalTwinService(
loggerFactory,
new DigitalTwinsClient(
new Uri(digitalTwinOptions.InstanceUrl),
digitalTwinOptions.GetTokenCredential()));

var twinsModelData = await digitalTwinService.GetModel(
$"{Names.PressMachineModelId};{Names.PressMachineVersion}",
cts.Token);

if (twinsModelData is null)
{
var (succeeded, errorMessage) = await digitalTwinService.CreateModels(
modelRepositoryService.GetModelsContent(),
cts.Token);

if (!succeeded)
{
logger.LogError($"Failed to upload models: {errorMessage}");
return;
}
}

var modelsResponse = digitalTwinService.GetModels(new GetModelsOptions { IncludeModelDefinition = true }, cts.Token);
if (modelsResponse is not null)
{
await foreach (var digitalTwinsModelData in modelsResponse)
{
if (digitalTwinsModelData.DtdlModel is null)
{
continue;
}

logger.LogInformation(digitalTwinsModelData.Id);
logger.LogInformation(digitalTwinsModelData.DtdlModel);
}
}

const string twinId = "my-twin-id";

var pressMachine = new PressMachine
{
Manufacturer = "Acme Corp",
SerialNumber = "SN123456",
OperationalStatus = "Running",
MaintenanceSchedule = "Every second day",
};

var (createTwinSucceeded, createTwinErrorMessage) = await digitalTwinService.CreateOrReplaceDigitalTwin(
twinId,
pressMachine,
cts.Token);

if (!createTwinSucceeded)
{
logger.LogError($"Failed to create twin: {createTwinErrorMessage}");
return;
}

var twinList = await digitalTwinService.GetTwins("SELECT * FROM DIGITALTWINS", cts.Token);

if (twinList is not null)
{
var groupedTwinList = twinList
.GroupBy(x => x.Metadata.ModelId, StringComparer.Ordinal)
.ToList();

foreach (var group in groupedTwinList)
{
logger.LogInformation($"{group.ToList().Count} twin(s) based on model '{group.Key}'.");
}
}

var (deleteTwinSucceeded, deleteTwinErrorMessage) = await digitalTwinService.DeleteTwin(
twinId,
cancellationToken: cts.Token);

if (!deleteTwinSucceeded)
{
logger.LogError($"Failed to delete twin: {deleteTwinErrorMessage}");
return;
}
}
}
6 changes: 6 additions & 0 deletions sample/Atc.Azure.DigitalTwin.Console.Sample/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"DigitalTwinOptions": {
"TenantId": "",
"InstanceUrl": ""
}
}
24 changes: 24 additions & 0 deletions sample/models/BaseEquipment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"@id": "dtmi:com:atc:Equipment;1",
"@type": "Interface",
"@context": "dtmi:dtdl:context;2",
"displayName": {
"en": "Base Equipment",
"es": "Equipo Base",
"fr": "Équipement de Base",
"de": "Basisausrüstung",
"zh": "基础设备"
},
"contents": [
{
"@type": "Property",
"name": "manufacturer",
"schema": "string"
},
{
"@type": "Property",
"name": "serialNumber",
"schema": "string"
}
]
}
Loading