Skip to content

Commit d4be090

Browse files
Creation of a native independent library (#300)
Co-authored-by: José Simões <[email protected]>
1 parent 1ef38ac commit d4be090

22 files changed

+957
-92
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"files.associations": {
3+
"*.yaml": "home-assistant"
4+
}
5+
}

README.md

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T)
1+
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_Azure.Devices&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_Azure.Devices) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.Azure.Devices.Client.FullyManaged.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T)
22

33
![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png)
44

@@ -11,13 +11,20 @@
1111
| Component | Build Status | NuGet Package |
1212
|:-|---|---|
1313
| nanoFramework.Azure.Devices.Client | [![Build Status](https://dev.azure.com/nanoframework/Azure.Devices/_apis/build/status/nanoFramework.Azure.Devices?repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)](https://dev.azure.com/nanoframework/Azure.Devices/_build/latest?definitionId=75&repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)| [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Azure.Devices.Client.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client/) |
14+
| nanoFramework.Azure.Devices.Client.FullyManaged | [![Build Status](https://dev.azure.com/nanoframework/Azure.Devices/_apis/build/status/nanoFramework.Azure.Devices?repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)](https://dev.azure.com/nanoframework/Azure.Devices/_build/latest?definitionId=75&repoName=nanoframework%2FnanoFramework.Azure.Devices&branchName=main)| [![NuGet](https://img.shields.io/nuget/v/nanoFramework.Azure.Devices.Client.FullyManaged.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/) |
1415

1516
## See it in action
1617

1718
You can watch this video from the Microsoft [IoT Show](https://aka.ms/iotshow) featuring the Azure SDK and a real life example with .NET nanoFramework:
1819

1920
[![IoT Show](https://img.youtube.com/vi/TLYqRdmmj5k/0.jpg)](https://youtu.be/TLYqRdmmj5k)
2021

22+
## nanoFramework.Azure.Devices.Client vs nanoFramework.Azure.Devices.Client.FullyManaged
23+
24+
The `nanoFramework.Azure.Devices.Client.FullyManaged` nuget has been build to be **independent of the native hardware** you are running on. So it will not use the X509Certificate but rather a byte array. It will not use the `nanoFramework.M2Mqtt` library but rather an abstraction called `nanoFramework.M2Mqtt.Core` using an interface.
25+
26+
The main scenario this does allow is to bring your own MQTT broker and run on devices without System.Net so devices without any native networking. This does allow to connect through a modem implementing an MQTT client. You can reuse almost fully the same code you're using for native network enabled devices and the ones using a modem.
27+
2128
## Usage
2229

2330
**Important**: You **must** be connected to a network with a valid IP address **and** a valid date. Please check the examples with the Network Helpers on how to help you making sure you have both.
@@ -65,6 +72,8 @@ R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
6572
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, azureCert: new X509Certificate(AzureRootCA));
6673
```
6774

75+
> Note: when using the FullyManaged library, you will have to pass a `byte[]` rather than a `X509Certificate`. The broker you'll use may or may not support PEM or DER certificate. Please make sure you will use the proper one by checking the vendor documentation. A PEM certificate is a base64 encoded version of the DER certificate, usually found with the `.crt` extension.
76+
6877
You can place your binary certificate in the program resources as well and just get the certificate from it:
6978

7079
```csharp
@@ -200,6 +209,8 @@ void TwinUpdatedEvent(object sender, TwinUpdateEventArgs e)
200209
}
201210
```
202211

212+
> Note: some modem have limitations in the length of the message. The message is what contains the twins. Make sure you'll check the limitations when using the FullyManaged library.
213+
203214
### Sending message
204215

205216
You have to use the `SendMessage` function to send any kind of message or telemetry to Azure IoT. As for the other function, you have the possibility to ensure delivery with a `CancellationToken` than can be cancelled. If one that can't be cancelled is used, the delivery insurance will be ignored and the function will return false.
@@ -249,6 +260,8 @@ void CloudToDeviceMessageEvent(object sender, CloudToDeviceMessageEventArgs e)
249260

250261
Note: the `sender` is a `DeviceClient` class, you can then send a message back, a confirmation or any logic you've put in place.
251262

263+
> Note: some modem have limitations in the length of the message and topic length. The topic length is what contains the property bag. Make sure you'll check the limitations when using the FullyManaged library.
264+
252265
### Method callback
253266

254267
Method callback is supported as well. You can register and unregister your methods. Here are a few examples:
@@ -281,6 +294,8 @@ string RaiseExceptionCallbackTest(int rid, string payload)
281294

282295
**Important**: method names are case sensitive. So make sure you name your functions in C# use the same case.
283296

297+
> Note: some modem have limitations in the length of the message. The message is what contains the payload. Make sure you'll check the limitations when using the FullyManaged library.
298+
284299
### Status update event
285300

286301
A status update event is available:
@@ -299,6 +314,8 @@ void StatusUpdatedEvent(object sender, StatusUpdatedEventArgs e)
299314
}
300315
```
301316

317+
> Note: some modem have limitations in the MQTT implementation so you may not get all the updates. Make sure you'll check the limitations when using the FullyManaged library.
318+
302319
Note that those are status change based, so once the connect or disconnect event arrives, they'll be replaced by other events as soon as something else happened like receiving a twin.
303320

304321
### QoS Level
@@ -307,9 +324,9 @@ By default, the device SDKs connect to an IoT Hub use QoS 1 for message exchange
307324

308325
Here are existing QoS levels that you can use:
309326

310-
* AtMostOnce: The broker/client will deliver the message once, with no confirmation.
311-
* AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required.
312-
* ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake.
327+
- AtMostOnce: The broker/client will deliver the message once, with no confirmation.
328+
- AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required.
329+
- ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake.
313330

314331
While it's possible to configure QoS 0 (AtMostOnce) for faster message exchange, you should note that the delivery isn't guaranteed nor acknowledged. For this reason, QoS 0 is often referred as "fire and forget".
315332

@@ -462,6 +479,8 @@ if(!res)
462479

463480
Additional payload is supported as well. You can set it up as as json string in the `ProvisioningRegistrationAdditionalData` class when calling the `Register` function. When the device has been provisioned, you may have as well additional payload provided.
464481

482+
> Note: some modem have limitations in the length of the message. The message is what contains the payload. Make sure you'll check the limitations when using the FullyManaged library.
483+
465484
## Feedback and documentation
466485

467486
For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).

Tests/DeviceClientTests/DeviceClientTests.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,36 @@ public void EncodeUserPropertiesTest_00()
3535

3636
var encodedProperties = client.EncodeUserProperties(new ArrayList() { _userProperty1, _userProperty2, _userProperty3 });
3737

38-
Assert.Equal(encodedProperties, "prop1=iAmValue1&prop2=33.44&prop3=string+with+%24%2F%23%25+chars");
38+
Assert.AreEqual(encodedProperties, "prop1=iAmValue1&prop2=33.44&prop3=string+with+%24%2F%23%25+chars");
3939
}
4040

4141
[TestMethod]
4242
public void EncodeUserPropertiesTest_01()
4343
{
4444
DeviceClient client = new();
4545

46-
Assert.Throws(typeof(ArgumentException), () =>
46+
Assert.ThrowsException(typeof(ArgumentException), () =>
4747
{
4848
client.EncodeUserProperties(new ArrayList() { _userProperty3, _userPropertyBad1 });
4949
},
5050
"Expecting ArgumentException with invalid user property 01."
5151
);
5252

53-
Assert.Throws(typeof(ArgumentException), () =>
53+
Assert.ThrowsException(typeof(ArgumentException), () =>
5454
{
5555
client.EncodeUserProperties(new ArrayList() { _userPropertyBad2, _userProperty3 });
5656
},
5757
"Expecting ArgumentException with invalid user property 02."
5858
);
5959

60-
Assert.Throws(typeof(InvalidCastException), () =>
60+
Assert.ThrowsException(typeof(InvalidCastException), () =>
6161
{
6262
client.EncodeUserProperties(new ArrayList() { _userProperty1, "Invalid property" });
6363
},
6464
"Expecting ArgumentException with invalid user property 03."
6565
);
6666

67-
Assert.Throws(typeof(InvalidCastException), () =>
67+
Assert.ThrowsException(typeof(InvalidCastException), () =>
6868
{
6969
client.EncodeUserProperties(new ArrayList() { 8888888, "Invalid property" });
7070
},
@@ -79,7 +79,7 @@ public void EncodeContentType_00(string contentType, string encodedContentType)
7979
{
8080
DeviceClient client = new();
8181

82-
Assert.Equal(
82+
Assert.AreEqual(
8383
client.EncodeContentType(contentType),
8484
encodedContentType);
8585
}

Tests/DeviceClientTests/DeviceClientTests.nfproj

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
<SignAssembly>true</SignAssembly>
2727
</PropertyGroup>
2828
<PropertyGroup>
29-
<AssemblyOriginatorKeyFile>..\..\nanoFramework.Azure.Devices.Client\key.snk</AssemblyOriginatorKeyFile>
29+
<AssemblyOriginatorKeyFile>..\..\key.snk</AssemblyOriginatorKeyFile>
3030
</PropertyGroup>
3131
<PropertyGroup>
3232
<DelaySign>false</DelaySign>
@@ -44,13 +44,11 @@
4444
<HintPath>..\..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll</HintPath>
4545
<Private>True</Private>
4646
</Reference>
47-
<Reference Include="nanoFramework.M2Mqtt, Version=5.1.94.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
47+
<Reference Include="nanoFramework.M2Mqtt">
4848
<HintPath>..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.dll</HintPath>
49-
<Private>True</Private>
5049
</Reference>
51-
<Reference Include="nanoFramework.M2Mqtt.Core, Version=0.0.0.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
50+
<Reference Include="nanoFramework.M2Mqtt.Core">
5251
<HintPath>..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.Core.dll</HintPath>
53-
<Private>True</Private>
5452
</Reference>
5553
<Reference Include="nanoFramework.Runtime.Events, Version=1.11.6.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
5654
<HintPath>..\..\packages\nanoFramework.Runtime.Events.1.11.6\lib\nanoFramework.Runtime.Events.dll</HintPath>
@@ -93,7 +91,10 @@
9391
<None Include="packages.config" />
9492
</ItemGroup>
9593
<ItemGroup>
96-
<ProjectReference Include="..\..\nanoFramework.Azure.Devices.Client\Azure.Devices.Client.nfproj" />
94+
<ProjectReference Include="..\..\nanoFramework.Azure.Devices.Client.FullyManaged\Azure.Devices.Client.FullyManaged.nfproj" />
95+
</ItemGroup>
96+
<ItemGroup>
97+
<Content Include="packages.lock.json" />
9798
</ItemGroup>
9899
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets')" />
99100
<ProjectExtensions>

Tests/TwinTests/TwinTest.cs

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,16 @@ public void TestContains()
2323
};
2424
TwinCollection twin = new(twinHash);
2525

26-
Assert.True(twin.Contains("something"), "something");
27-
Assert.Equal("22", (string)twin["something"]);
28-
Assert.True(twin.Contains("else"), "else");
29-
Assert.Equal(42, (int)twin["else"]);
30-
Assert.True(twin.Contains("null"), "null");
26+
Assert.IsTrue(twin.Contains("something"), "something");
27+
Assert.AreEqual("22", (string)twin["something"]);
28+
Assert.IsTrue(twin.Contains("else"), "else");
29+
Assert.AreEqual(42, (int)twin["else"]);
30+
Assert.IsTrue(twin.Contains("null"), "null");
3131
var nullresult = twin["null"];
32-
Assert.Null(nullresult);
32+
Assert.IsNull(nullresult);
3333

34-
Assert.False(twin.Contains("nothing"), "nothing");
35-
Assert.Null(twin["nothing"]);
34+
Assert.IsFalse(twin.Contains("nothing"), "nothing");
35+
Assert.IsNull(twin["nothing"]);
3636
}
3737

3838
[TestMethod]
@@ -51,23 +51,23 @@ public void TestEnumeration()
5151
{
5252
if (coll.Key.ToString() == "something")
5353
{
54-
Assert.Equal("22", coll.Value.ToString());
54+
Assert.AreEqual("22", coll.Value.ToString());
5555
}
5656

5757
if (coll.Key.ToString() == "else")
5858
{
59-
Assert.Equal(42, (int)coll.Value);
59+
Assert.AreEqual(42, (int)coll.Value);
6060
}
6161

6262
if (coll.Key.ToString() == "null")
6363
{
64-
Assert.Null(coll.Value);
64+
Assert.IsNull(coll.Value);
6565
}
6666

6767
count++;
6868
}
6969

70-
Assert.Equal(3, count);
70+
Assert.AreEqual(3, count);
7171
}
7272
}
7373
}

azure-pipelines.yml

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,49 @@ variables:
4545
steps:
4646

4747
# step from template @ nf-tools repo
48-
# all build, update and publish steps
49-
- template: azure-pipelines-templates/class-lib-build.yml@templates
48+
# only build the lib
49+
- template: azure-pipelines-templates/class-lib-build-only.yml@templates
5050
parameters:
5151
sonarCloudProject: 'nanoframework_Azure.Devices'
5252
runUnitTests: true
5353
unitTestRunsettings: '$(System.DefaultWorkingDirectory)/Tests/CryptoTests/nano.runsettings'
5454

55+
# build the 2 libs step
56+
- template: azure-pipelines-templates/class-lib-package.yml@templates
57+
parameters:
58+
nugetPackageName: 'nanoFramework.Azure.Devices.Client'
59+
60+
- template: azure-pipelines-templates/class-lib-package.yml@templates
61+
parameters:
62+
nugetPackageName: 'nanoFramework.Azure.Devices.Client.FullyManaged'
63+
64+
# publish the 2 libs
65+
- template: azure-pipelines-templates/class-lib-publish.yml@templates
66+
67+
# create GitHub release build from main branch
68+
- task: GithubRelease@1
69+
condition: >-
70+
and(
71+
succeeded(),
72+
eq(variables['System.PullRequest.PullRequestId'], ''),
73+
startsWith(variables['Build.SourceBranch'], 'refs/heads/main'),
74+
not(contains(variables['Build.SourceBranch'], 'preview')),
75+
eq(variables['StartReleaseCandidate'], false)
76+
)
77+
displayName: Create/Update GitHub release
78+
inputs:
79+
action: edit
80+
gitHubConnection: 'github.com_nano-$(System.TeamProject)'
81+
tagSource: userSpecifiedTag
82+
tag: v$(MY_NUGET_VERSION)
83+
title: '$(nugetPackageName) Library v$(MY_NUGET_VERSION)'
84+
releaseNotesSource: inline
85+
releaseNotesInline: 'Check the [changelog]($(Build.Repository.Uri)/blob/$(Build.SourceBranchName)/CHANGELOG.md).<br><br><h4>Install from NuGet</h4><br>The following NuGet packages are available for download from this release:<br>:package: [nanoFramework.Azure.Devices.Client](https://www.nuget.org/packages/$(nugetPackageName)/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION).<br>:package: [nanoFramework.Azure.Devices.Client.FullyManaged (Independent of MQTT implementation and native System.Net)](https://www.nuget.org/packages/nanoFramework.Azure.Devices.Client.FullyManaged/$(MY_NUGET_VERSION)) v$(MY_NUGET_VERSION)'
86+
assets: '$(Build.ArtifactStagingDirectory)/*.nupkg'
87+
assetUploadMode: replace
88+
isPreRelease: false
89+
addChangeLog: false
90+
5591
# step from template @ nf-tools repo
5692
# report error
5793
- template: azure-pipelines-templates/discord-webhook-task.yml@templates
File renamed without changes.

0 commit comments

Comments
 (0)