Skip to content

Commit

Permalink
Creation of a native independent library (#300)
Browse files Browse the repository at this point in the history
Co-authored-by: José Simões <[email protected]>
  • Loading branch information
Ellerbach and josesimoes authored Sep 4, 2023
1 parent 1ef38ac commit d4be090
Show file tree
Hide file tree
Showing 22 changed files with 957 additions and 92 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files.associations": {
"*.yaml": "home-assistant"
}
}
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![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)
[![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)

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

Expand All @@ -11,13 +11,20 @@
| Component | Build Status | NuGet Package |
|:-|---|---|
| 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/) |
| 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/) |

## See it in action

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:

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

## nanoFramework.Azure.Devices.Client vs nanoFramework.Azure.Devices.Client.FullyManaged

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.

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.

## Usage

**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.
Expand Down Expand Up @@ -65,6 +72,8 @@ R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, azureCert: new X509Certificate(AzureRootCA));
```

> 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.
You can place your binary certificate in the program resources as well and just get the certificate from it:

```csharp
Expand Down Expand Up @@ -200,6 +209,8 @@ void TwinUpdatedEvent(object sender, TwinUpdateEventArgs e)
}
```

> 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.
### Sending message

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.
Expand Down Expand Up @@ -249,6 +260,8 @@ void CloudToDeviceMessageEvent(object sender, CloudToDeviceMessageEventArgs e)

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

> 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.
### Method callback

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

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

> 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.
### Status update event

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

> 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.
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.

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

Here are existing QoS levels that you can use:

* AtMostOnce: The broker/client will deliver the message once, with no confirmation.
* AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required.
* ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake.
- AtMostOnce: The broker/client will deliver the message once, with no confirmation.
- AtLeastOnce: The broker/client will deliver the message at least once, with confirmation required.
- ExactlyOnce: The broker/client will deliver the message exactly once by using a four step handshake.

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".

Expand Down Expand Up @@ -462,6 +479,8 @@ if(!res)

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.

> 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.
## Feedback and documentation

For documentation, providing feedback, issues and finding out how to contribute please refer to the [Home repo](https://github.com/nanoframework/Home).
Expand Down
12 changes: 6 additions & 6 deletions Tests/DeviceClientTests/DeviceClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,36 @@ public void EncodeUserPropertiesTest_00()

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

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

[TestMethod]
public void EncodeUserPropertiesTest_01()
{
DeviceClient client = new();

Assert.Throws(typeof(ArgumentException), () =>
Assert.ThrowsException(typeof(ArgumentException), () =>
{
client.EncodeUserProperties(new ArrayList() { _userProperty3, _userPropertyBad1 });
},
"Expecting ArgumentException with invalid user property 01."
);

Assert.Throws(typeof(ArgumentException), () =>
Assert.ThrowsException(typeof(ArgumentException), () =>
{
client.EncodeUserProperties(new ArrayList() { _userPropertyBad2, _userProperty3 });
},
"Expecting ArgumentException with invalid user property 02."
);

Assert.Throws(typeof(InvalidCastException), () =>
Assert.ThrowsException(typeof(InvalidCastException), () =>
{
client.EncodeUserProperties(new ArrayList() { _userProperty1, "Invalid property" });
},
"Expecting ArgumentException with invalid user property 03."
);

Assert.Throws(typeof(InvalidCastException), () =>
Assert.ThrowsException(typeof(InvalidCastException), () =>
{
client.EncodeUserProperties(new ArrayList() { 8888888, "Invalid property" });
},
Expand All @@ -79,7 +79,7 @@ public void EncodeContentType_00(string contentType, string encodedContentType)
{
DeviceClient client = new();

Assert.Equal(
Assert.AreEqual(
client.EncodeContentType(contentType),
encodedContentType);
}
Expand Down
13 changes: 7 additions & 6 deletions Tests/DeviceClientTests/DeviceClientTests.nfproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<SignAssembly>true</SignAssembly>
</PropertyGroup>
<PropertyGroup>
<AssemblyOriginatorKeyFile>..\..\nanoFramework.Azure.Devices.Client\key.snk</AssemblyOriginatorKeyFile>
<AssemblyOriginatorKeyFile>..\..\key.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup>
<DelaySign>false</DelaySign>
Expand All @@ -44,13 +44,11 @@
<HintPath>..\..\packages\nanoFramework.CoreLibrary.1.14.2\lib\mscorlib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nanoFramework.M2Mqtt, Version=5.1.94.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<Reference Include="nanoFramework.M2Mqtt">
<HintPath>..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nanoFramework.M2Mqtt.Core, Version=0.0.0.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<Reference Include="nanoFramework.M2Mqtt.Core">
<HintPath>..\..\packages\nanoFramework.M2Mqtt.5.1.94\lib\nanoFramework.M2Mqtt.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nanoFramework.Runtime.Events, Version=1.11.6.0, Culture=neutral, PublicKeyToken=c07d481e9758c731">
<HintPath>..\..\packages\nanoFramework.Runtime.Events.1.11.6\lib\nanoFramework.Runtime.Events.dll</HintPath>
Expand Down Expand Up @@ -93,7 +91,10 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\nanoFramework.Azure.Devices.Client\Azure.Devices.Client.nfproj" />
<ProjectReference Include="..\..\nanoFramework.Azure.Devices.Client.FullyManaged\Azure.Devices.Client.FullyManaged.nfproj" />
</ItemGroup>
<ItemGroup>
<Content Include="packages.lock.json" />
</ItemGroup>
<Import Project="$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets" Condition="Exists('$(NanoFrameworkProjectSystemPath)NFProjectSystem.CSharp.targets')" />
<ProjectExtensions>
Expand Down
24 changes: 12 additions & 12 deletions Tests/TwinTests/TwinTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ public void TestContains()
};
TwinCollection twin = new(twinHash);

Assert.True(twin.Contains("something"), "something");
Assert.Equal("22", (string)twin["something"]);
Assert.True(twin.Contains("else"), "else");
Assert.Equal(42, (int)twin["else"]);
Assert.True(twin.Contains("null"), "null");
Assert.IsTrue(twin.Contains("something"), "something");
Assert.AreEqual("22", (string)twin["something"]);
Assert.IsTrue(twin.Contains("else"), "else");
Assert.AreEqual(42, (int)twin["else"]);
Assert.IsTrue(twin.Contains("null"), "null");
var nullresult = twin["null"];
Assert.Null(nullresult);
Assert.IsNull(nullresult);

Assert.False(twin.Contains("nothing"), "nothing");
Assert.Null(twin["nothing"]);
Assert.IsFalse(twin.Contains("nothing"), "nothing");
Assert.IsNull(twin["nothing"]);
}

[TestMethod]
Expand All @@ -51,23 +51,23 @@ public void TestEnumeration()
{
if (coll.Key.ToString() == "something")
{
Assert.Equal("22", coll.Value.ToString());
Assert.AreEqual("22", coll.Value.ToString());
}

if (coll.Key.ToString() == "else")
{
Assert.Equal(42, (int)coll.Value);
Assert.AreEqual(42, (int)coll.Value);
}

if (coll.Key.ToString() == "null")
{
Assert.Null(coll.Value);
Assert.IsNull(coll.Value);
}

count++;
}

Assert.Equal(3, count);
Assert.AreEqual(3, count);
}
}
}
40 changes: 38 additions & 2 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,49 @@ variables:
steps:

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

# build the 2 libs step
- template: azure-pipelines-templates/class-lib-package.yml@templates
parameters:
nugetPackageName: 'nanoFramework.Azure.Devices.Client'

- template: azure-pipelines-templates/class-lib-package.yml@templates
parameters:
nugetPackageName: 'nanoFramework.Azure.Devices.Client.FullyManaged'

# publish the 2 libs
- template: azure-pipelines-templates/class-lib-publish.yml@templates

# create GitHub release build from main branch
- task: GithubRelease@1
condition: >-
and(
succeeded(),
eq(variables['System.PullRequest.PullRequestId'], ''),
startsWith(variables['Build.SourceBranch'], 'refs/heads/main'),
not(contains(variables['Build.SourceBranch'], 'preview')),
eq(variables['StartReleaseCandidate'], false)
)
displayName: Create/Update GitHub release
inputs:
action: edit
gitHubConnection: 'github.com_nano-$(System.TeamProject)'
tagSource: userSpecifiedTag
tag: v$(MY_NUGET_VERSION)
title: '$(nugetPackageName) Library v$(MY_NUGET_VERSION)'
releaseNotesSource: inline
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)'
assets: '$(Build.ArtifactStagingDirectory)/*.nupkg'
assetUploadMode: replace
isPreRelease: false
addChangeLog: false

# step from template @ nf-tools repo
# report error
- template: azure-pipelines-templates/discord-webhook-task.yml@templates
Expand Down
File renamed without changes.
Loading

0 comments on commit d4be090

Please sign in to comment.