Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.

Commit a0ae277

Browse files
author
Basel Rustum
authored
Add new PnP Samples for the DigitalTwinClient (#140)
1 parent d3b0794 commit a0ae277

39 files changed

+3382
-0
lines changed

iot-hub/Samples/device/IoTHubDeviceSamples.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceClientStreamingSample
1515
EndProject
1616
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DeviceReconnectionSample", "DeviceReconnectionSample\DeviceReconnectionSample.csproj", "{EA56CE79-4954-40F4-BEDD-5EB7B75149B6}"
1717
EndProject
18+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PnpDeviceSamples", "PnpDeviceSamples", "{F5303EDE-71ED-4F4F-973A-A9604DE9FA04}"
19+
EndProject
20+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TemperatureController", "PnpDeviceSamples\TemperatureController\TemperatureController.csproj", "{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1}"
21+
EndProject
22+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Thermostat", "PnpDeviceSamples\Thermostat\Thermostat.csproj", "{D408FF22-8A9A-4096-98B0-552318700AAC}"
23+
EndProject
24+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PnpHelpers", "PnpDeviceSamples\PnpConvention\PnpHelpers.csproj", "{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F}"
25+
EndProject
1826
Global
1927
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2028
Debug|Any CPU = Debug|Any CPU
@@ -45,10 +53,27 @@ Global
4553
{EA56CE79-4954-40F4-BEDD-5EB7B75149B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
4654
{EA56CE79-4954-40F4-BEDD-5EB7B75149B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
4755
{EA56CE79-4954-40F4-BEDD-5EB7B75149B6}.Release|Any CPU.Build.0 = Release|Any CPU
56+
{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
57+
{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
58+
{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
59+
{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1}.Release|Any CPU.Build.0 = Release|Any CPU
60+
{D408FF22-8A9A-4096-98B0-552318700AAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
61+
{D408FF22-8A9A-4096-98B0-552318700AAC}.Debug|Any CPU.Build.0 = Debug|Any CPU
62+
{D408FF22-8A9A-4096-98B0-552318700AAC}.Release|Any CPU.ActiveCfg = Release|Any CPU
63+
{D408FF22-8A9A-4096-98B0-552318700AAC}.Release|Any CPU.Build.0 = Release|Any CPU
64+
{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
65+
{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F}.Debug|Any CPU.Build.0 = Debug|Any CPU
66+
{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F}.Release|Any CPU.ActiveCfg = Release|Any CPU
67+
{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F}.Release|Any CPU.Build.0 = Release|Any CPU
4868
EndGlobalSection
4969
GlobalSection(SolutionProperties) = preSolution
5070
HideSolutionNode = FALSE
5171
EndGlobalSection
72+
GlobalSection(NestedProjects) = preSolution
73+
{9CC9E3B6-1051-4D01-8D9B-AF5F13FD88C1} = {F5303EDE-71ED-4F4F-973A-A9604DE9FA04}
74+
{D408FF22-8A9A-4096-98B0-552318700AAC} = {F5303EDE-71ED-4F4F-973A-A9604DE9FA04}
75+
{DCFC3AF8-5E7D-4ADD-A08E-D50B3768564F} = {F5303EDE-71ED-4F4F-973A-A9604DE9FA04}
76+
EndGlobalSection
5277
GlobalSection(ExtensibilityGlobals) = postSolution
5378
SolutionGuid = {A9E1FEF7-DB22-48EC-A7D2-0AECA91CDCF0}
5479
EndGlobalSection

iot-hub/Samples/device/PnpDeviceSamples/PnpConvention/PnpConvention.cs

Lines changed: 423 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using FluentAssertions;
5+
using Microsoft.Azure.Devices.Shared;
6+
using Microsoft.VisualStudio.TestTools.UnitTesting;
7+
using Newtonsoft.Json;
8+
using Newtonsoft.Json.Linq;
9+
10+
namespace PnpHelpers
11+
{
12+
class PnpConventionTests
13+
{
14+
/// <summary>
15+
/// Ensures the PnP convention helpers produce content to spec and using different data types
16+
/// </summary>
17+
[TestClass]
18+
[TestCategory("Unit")]
19+
public class PnpHelperTests
20+
{
21+
[TestMethod]
22+
public void CreateRootPropertyPatch()
23+
{
24+
// Format:
25+
// {
26+
// "samplePropertyName": 20
27+
// }
28+
29+
const string propertyName = "someName";
30+
const int propertyValue = 10;
31+
32+
TwinCollection patch = PnpConvention.CreatePropertyPatch(propertyName, propertyValue);
33+
var jObject = JObject.Parse(patch.ToJson());
34+
35+
jObject.Count.Should().Be(1, "there should be a single property added");
36+
jObject.Value<int>(propertyName).Should().Be(propertyValue);
37+
}
38+
39+
[TestMethod]
40+
public void CreateComponentPropertyPatch()
41+
{
42+
// Format:
43+
// {
44+
// "sampleComponentName": {
45+
// "__t": "c",
46+
// "samplePropertyName"": 20
47+
// }
48+
// }
49+
50+
const string componentName = "someComponent";
51+
const string propertyName = "someName";
52+
const int propertyValue = 10;
53+
54+
TwinCollection patch = PnpConvention.CreateComponentPropertyPatch(componentName, propertyName, propertyValue);
55+
56+
var jObject = JObject.Parse(patch.ToJson());
57+
JObject component = jObject.Value<JObject>(componentName);
58+
59+
component.Count.Should().Be(2, "there should be two properties added - the above property and a component identifier {\"__t\": \"c\"}");
60+
component.Value<int>(propertyName).Should().Be(propertyValue);
61+
component[PnpConvention.PropertyComponentIdentifierKey].Should().NotBeNull();
62+
((string)component[PnpConvention.PropertyComponentIdentifierKey]).Should().Be(PnpConvention.PropertyComponentIdentifierValue);
63+
}
64+
65+
[TestMethod]
66+
public void CreateRootPropertyEmbeddedValuePatch()
67+
{
68+
// Format:
69+
// {
70+
// "samplePropertyName": {
71+
// "value": 20,
72+
// "ac": 200,
73+
// "av": 5,
74+
// "ad": "The update was successful."
75+
// }
76+
// }
77+
78+
const string propertyName = "someName";
79+
const int propertyValue = 10;
80+
const int ackCode = 200;
81+
const long ackVersion = 2;
82+
83+
TwinCollection patch = PnpConvention.CreateWritablePropertyResponse(propertyName, propertyValue, ackCode, ackVersion);
84+
85+
var jObject = JObject.Parse(patch.ToJson());
86+
EmbeddedPropertyPatch actualPatch = jObject.ToObject<EmbeddedPropertyPatch>();
87+
88+
// The property patch object should have "value", "ac" and "av" properties set. Since we did not supply an "ackDescription", "ad" should be null.
89+
actualPatch.Value.SerializedValue.Should().Be(JsonConvert.SerializeObject(propertyValue));
90+
actualPatch.Value.AckCode.Should().Be(ackCode);
91+
actualPatch.Value.AckVersion.Should().Be(ackVersion);
92+
actualPatch.Value.AckDescription.Should().BeNull();
93+
}
94+
95+
[TestMethod]
96+
public void CreateComponentPropertyEmbeddedValuePatch()
97+
{
98+
// Format:
99+
// {
100+
// "sampleComponentName": {
101+
// "__t": "c",
102+
// "samplePropertyName": {
103+
// "value": 20,
104+
// "ac": 200,
105+
// "av": 5,
106+
// "ad": "The update was successful."
107+
// }
108+
// }
109+
// }
110+
111+
const string componentName = "someComponentName";
112+
const string propertyName = "someName";
113+
const int propertyValue = 10;
114+
const int ackCode = 200;
115+
const long ackVersion = 2;
116+
const string ackDescription = "The update was successful";
117+
118+
TwinCollection patch = PnpConvention.CreateComponentWritablePropertyResponse(
119+
componentName,
120+
propertyName,
121+
JsonConvert.SerializeObject(propertyValue),
122+
ackCode,
123+
ackVersion,
124+
ackDescription);
125+
126+
var jObject = JObject.Parse(patch.ToJson());
127+
JObject component = jObject.Value<JObject>(componentName);
128+
129+
// There should be two properties added to the component- the above property and a component identifier "__t": "c".
130+
component.Count.Should().Be(2);
131+
component[PnpConvention.PropertyComponentIdentifierKey].Should().NotBeNull();
132+
((string)component[PnpConvention.PropertyComponentIdentifierKey]).Should().Be(PnpConvention.PropertyComponentIdentifierValue);
133+
134+
// The property patch object should have "value", "ac", "av" and "ad" properties set.
135+
EmbeddedPropertyPatch actualPatch = component.ToObject<EmbeddedPropertyPatch>();
136+
actualPatch.Value.SerializedValue.Should().Be(JsonConvert.SerializeObject(propertyValue));
137+
actualPatch.Value.AckCode.Should().Be(ackCode);
138+
actualPatch.Value.AckVersion.Should().Be(ackVersion);
139+
actualPatch.Value.AckDescription.Should().Be(ackDescription);
140+
}
141+
}
142+
143+
internal class EmbeddedPropertyPatch
144+
{
145+
[JsonProperty("someName")]
146+
internal EmbeddedPropertyPatchValue Value { get; set; }
147+
}
148+
149+
internal class EmbeddedPropertyPatchValue
150+
{
151+
[JsonProperty("value")]
152+
internal string SerializedValue { get; set; }
153+
154+
[JsonProperty("ac")]
155+
internal int AckCode { get; set; }
156+
157+
[JsonProperty("av")]
158+
internal long AckVersion { get; set; }
159+
160+
[JsonProperty("ad")]
161+
internal string AckDescription { get; set; }
162+
}
163+
}
164+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp3.1</TargetFramework>
5+
<IsTestProject>True</IsTestProject>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.31.0" />
10+
<PackageReference Include="FluentAssertions" Version="5.10.3" />
11+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
12+
<PackageReference Include="MSTest.TestAdapter" Version="2.1.2" />
13+
<PackageReference Include="MSTest.TestFramework" Version="2.1.2" />
14+
</ItemGroup>
15+
16+
</Project>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using Newtonsoft.Json;
5+
6+
namespace PnpHelpers
7+
{
8+
/// <summary>
9+
/// The payload for a property update response.
10+
/// </summary>
11+
public class WritablePropertyResponse
12+
{
13+
/// <summary>
14+
/// Empty constructor.
15+
/// </summary>
16+
public WritablePropertyResponse() { }
17+
18+
/// <summary>
19+
/// Convenience constructor for specifying the properties.
20+
/// </summary>
21+
/// <param name="propertyValue">The unserialized property value.</param>
22+
/// <param name="ackCode">The acknowledgement code, usually an HTTP Status Code e.g. 200, 400.</param>
23+
/// <param name="ackVersion">The acknowledgement version, as supplied in the property update request.</param>
24+
/// <param name="ackDescription">The acknowledgement description, an optional, human-readable message about the result of the property update.</param>
25+
public WritablePropertyResponse(object propertyValue, int ackCode, long ackVersion, string ackDescription = null)
26+
{
27+
PropertyValue = propertyValue;
28+
AckCode = ackCode;
29+
AckVersion = ackVersion;
30+
AckDescription = ackDescription;
31+
}
32+
33+
/// <summary>
34+
/// The unserialized property value.
35+
/// </summary>
36+
[JsonProperty("value")]
37+
public object PropertyValue { get; set; }
38+
39+
/// <summary>
40+
/// The acknowledgement code, usually an HTTP Status Code e.g. 200, 400.
41+
/// </summary>
42+
[JsonProperty("ac")]
43+
public int AckCode { get; set; }
44+
45+
/// <summary>
46+
/// The acknowledgement version, as supplied in the property update request.
47+
/// </summary>
48+
[JsonProperty("av")]
49+
public long AckVersion { get; set; }
50+
51+
/// <summary>
52+
/// The acknowledgement description, an optional, human-readable message about the result of the property update.
53+
/// </summary>
54+
[JsonProperty("ad", DefaultValueHandling = DefaultValueHandling.Ignore)]
55+
public string AckDescription { get; set; }
56+
}
57+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"@id": "dtmi:azure:DeviceManagement:DeviceInformation;1",
3+
"@type": "Interface",
4+
"displayName": "Device Information",
5+
"contents": [
6+
{
7+
"@type": "Property",
8+
"name": "manufacturer",
9+
"displayName": "Manufacturer",
10+
"schema": "string",
11+
"description": "Company name of the device manufacturer. This could be the same as the name of the original equipment manufacturer (OEM). Ex. Contoso."
12+
},
13+
{
14+
"@type": "Property",
15+
"name": "model",
16+
"displayName": "Device model",
17+
"schema": "string",
18+
"description": "Device model name or ID. Ex. Surface Book 2."
19+
},
20+
{
21+
"@type": "Property",
22+
"name": "swVersion",
23+
"displayName": "Software version",
24+
"schema": "string",
25+
"description": "Version of the software on your device. This could be the version of your firmware. Ex. 1.3.45"
26+
},
27+
{
28+
"@type": "Property",
29+
"name": "osName",
30+
"displayName": "Operating system name",
31+
"schema": "string",
32+
"description": "Name of the operating system on the device. Ex. Windows 10 IoT Core."
33+
},
34+
{
35+
"@type": "Property",
36+
"name": "processorArchitecture",
37+
"displayName": "Processor architecture",
38+
"schema": "string",
39+
"description": "Architecture of the processor on the device. Ex. x64 or ARM."
40+
},
41+
{
42+
"@type": "Property",
43+
"name": "processorManufacturer",
44+
"displayName": "Processor manufacturer",
45+
"schema": "string",
46+
"description": "Name of the manufacturer of the processor on the device. Ex. Intel."
47+
},
48+
{
49+
"@type": "Property",
50+
"name": "totalStorage",
51+
"displayName": "Total storage",
52+
"schema": "double",
53+
"description": "Total available storage on the device in kilobytes. Ex. 2048000 kilobytes."
54+
},
55+
{
56+
"@type": "Property",
57+
"name": "totalMemory",
58+
"displayName": "Total memory",
59+
"schema": "double",
60+
"description": "Total available memory on the device in kilobytes. Ex. 256000 kilobytes."
61+
}
62+
],
63+
"@context": "dtmi:dtdl:context;2"
64+
}

0 commit comments

Comments
 (0)