nanoFramework.Azure.Devices.Client 1.2.66

Prefix Reserved
dotnet add package nanoFramework.Azure.Devices.Client --version 1.2.66                
NuGet\Install-Package nanoFramework.Azure.Devices.Client -Version 1.2.66                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="nanoFramework.Azure.Devices.Client" Version="1.2.66" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add nanoFramework.Azure.Devices.Client --version 1.2.66                
#r "nuget: nanoFramework.Azure.Devices.Client, 1.2.66"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install nanoFramework.Azure.Devices.Client as a Cake Addin
#addin nuget:?package=nanoFramework.Azure.Devices.Client&version=1.2.66

// Install nanoFramework.Azure.Devices.Client as a Cake Tool
#tool nuget:?package=nanoFramework.Azure.Devices.Client&version=1.2.66                

Quality Gate Status Reliability Rating License NuGet NuGet #yourfirstpr Discord

nanoFramework logo


Welcome to the .NET nanoFramework Azure.Devices.Client Library repository

Build status

Component Build Status NuGet Package
nanoFramework.Azure.Devices.Client Build Status NuGet
nanoFramework.Azure.Devices.Client.FullyManaged Build Status NuGet

See it in action

You can watch this video from the Microsoft IoT Show featuring the Azure SDK and a real life example with .NET nanoFramework:

IoT Show

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.

This Azure IoT Hub SDK is using MQTT. So you need to ensure you can connect to port 8883 using TLS protocol. If you are connected to an enterprise network, this may be blocked. In most cases, this is not an issue.

The namespaces, the name of the classes and the methods try to get close to the .NET C# Azure IoT SDK. This should allow easier portability of the code between the full .Net framework and nanoFramework environments.

Certificate

You have 2 options to provide the right Azure IoT TLS certificate:

  • Parse it into the constructor
  • Store it into the device

The AzureCertificates contains, for your convenience, the root certificate used to connect to Azure IoT. The current one, a Baltimore Root CA is the one to use up to June 2022. Starting from June 2022, the Digicert Global Root 2 is the one to use. For more information, please read the following blog.

Through the constructor

As of October 15th, 2023, you will have to embed the new Azure DigiCert Global Root G2 certificate into your code to properly communicate with IoT hub services:

const string AzureRootCA = @"-----BEGIN CERTIFICATE-----
MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI
2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx
1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ
q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz
tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ
vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV
5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY
1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4
NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG
Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91
8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe
pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl
MrY=
-----END CERTIFICATE-----
";
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:

X509Certificate azureRootCACert = new X509Certificate(Resources.GetBytes(Resources.BinaryResources.AzureCAcertificate));
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, azureCert: azureRootCACert);

Note: when the certificate expires, you will have to fully reflash the device with the new certificate.

Storing the certificate on the device

You can store the certificate on the device flash and not in the code, so if you have to change the certificate, you'll just have to clean the current store and upload the new one. Edit the network properties:

edit device network

Navigate to the General tab:

device network certificate

Browse to choose your certificate, it can be in a binary (crt, der) or string form (pem, txt) and select ok. The certificate to connect will be selected automatically during the connection.

Creating a DeviceClient

You can connect to Azure IoT Hub using either a symmetric Key or a certificate. The following example shows how to use a symmetric key:

const string DeviceID = "nanoEdgeTwin";
const string IotBrokerAddress = "youriothub.azure-devices.net";
const string SasKey = "yoursaskey";
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey);

Note: please see the previous section to understand how to better parse the certificate for your usage. The example shows the certificate uploaded into the device and not in the code.

Azure IoT Plug&Play

Azure IoT Plug&Play is supported as well. You'll need to provide a model ID when creating the DeviceClient:

DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, modelID:"dtmi:com:example:Thermostat;1");

Note: the model ID has to be passed at the creation of the DeviceClient, it is not possible to pass it later on.

Reporting properties

Reporting Plug & Play properties is supported. He is a comprehensive example and how you can check if you have received one property that you're interested in:

const string TargetTemerature = "targetTemperature";
DeviceClient azureIoT = new DeviceClient(Secrets.IotHub, Secrets.DeviceName, Secrets.SasKey, azureCert: new X509Certificate(Resource.GetBytes(Resource.BinaryResources.AzureRoot)), modelId: "dtmi:com:example:Thermostat;1");
azureIoT.TwinUpdated += AzureTwinUpdated;
azureIoT.Open();

void AzureTwinUpdated(object sender, TwinUpdateEventArgs e)
{
    if (e.Twin.Contains(TargetTemerature))
    {
        // We got an update for the target temperature
        var target = e.Twin[TargetTemerature];
        Debug.WriteLine($"Target temperature updated: {target}");
        PropertyAcknowledge targetReport = new() { Version = (int)e.Twin.Version, Status = PropertyStatus.Completed, Description = "All perfect", Value = target };
        TwinCollection twin = new TwinCollection();
        twin.Add(TargetTemerature, targetReport.BuildAcknowledge());
        azureIoT.UpdateReportedProperties(twin);
    }
}

In this example, the property we are interested in to receive is called targetTemperature. To receive its update, we are subscribing to the twin update. And we can get the value thu the e.Twin[TargetTemerature] once we've checked that the property exist.

The patter to publish a writable property is then quite simple. it's about building a PropertyAcknowledge, creating a TwinCollection, adding it to it with the property name, here our targetTemperature. You can add more properties to report of course. Note that what you add to the TwinCollection is not directly the object but BuildAcknowledge(). One done, just ask the library to update the twin through the UpdateReportedProperties method.

Receiving commands

An IoT Plug & Play command is a method callback. See further in this document how you can use them. In our case, the method is called getMaxMinReport. The name of the method in C# must be the exact same as the name from the DTDL file.

DeviceClient azureIoT = new DeviceClient(Secrets.IotHub, Secrets.DeviceName, Secrets.SasKey, azureCert: new X509Certificate(Resource.GetBytes(Resource.BinaryResources.AzureRoot)), modelId: "dtmi:com:example:Thermostat;1");
azureIoT.AddMethodCallback(getMaxMinReport);
azureIoT.Open();

string getMaxMinReport(int rid, string payload)
{
    TemperatureReporting reporting = new() { avgTemp = 20, maxTemp = 42, minTemp = 12.34, startTime = DateTime.UtcNow.AddDays(-10), endTime = DateTime.UtcNow };
    return JsonConvert.SerializeObject(reporting);
}

In this example, the expected result is an object. Just populate the object and serialize it as a json as the command expect and return it. If any parameter to this command, it will be in the payload.

Getting and updating Twin

You can request your Azure IoT Twin simply by calling the GetTwin function.

var twin = azureIoT.GetTwin(new CancellationTokenSource(20000).Token);
if (twin == null)
{
    Debug.WriteLine($"Can't get the twins");
    azureIoT.Close();
    return;
}

Debug.WriteLine($"Twin DeviceID: {twin.DeviceId}, #desired: {twin.Properties.Desired.Count}, #reported: {twin.Properties.Reported.Count}");

Note: it's important to use a CancellationToken that will be cancelled after a certain amount of time. Otherwise, this will be blocking the thread up to the point the twin is received.

Twins have properties, reported and desired. They are collection and you can get or try to get any element.

You can report your Twin as simple as this:

TwinCollection reported = new TwinCollection();
reported.Add("firmware", "myNano");
reported.Add("sdk", 0.2);
azureIoT.UpdateReportedProperties(reported);

You also have the option to wait for the twin update confirmation, in this case use a CancellationToken that can be cancelled. Otherwise the check will be ignored.

Note: the function will return false if the twin reception confirmation is not checked or if it did not arrive on time.

You can also register for any twin update:

azureIoT.TwinUpdated += TwinUpdatedEvent;

void TwinUpdatedEvent(object sender, TwinUpdateEventArgs e)
{
    Debug.WriteLine($"Twin update received: {e.Twin.Count}");
}

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.

var isReceived = azureIoT.SendMessage($"{{\"Temperature\":42,\"Pressure\":1024}}", new CancellationTokenSource(5000).Token);
Debug.WriteLine($"Message received by IoT Hub: {isReceived}");

Note: The message will be sent with the default service quality of service you created the device with. You won't get any answer for the quality 0. In this case, you can simplify it to:

azureIoT.SendMessage($"{{\"Temperature\":42,\"Pressure\":1024}}");

Cloud to device messages

You can register an event to receive Cloud to device messages:

azureIoT.CloudToDeviceMessage += CloudToDeviceMessageEvent;

// The following example shows how to display all keys in debug
void CloudToDeviceMessageEvent(object sender, CloudToDeviceMessageEventArgs e)
{
    Debug.WriteLine($"Message arrived: {e.Message}");
    foreach (string key in e.Properties.Keys)
    {
        Debug.Write($"  Key: {key} = ");
        if (e.Properties[key] == null)
        {
            Debug.WriteLine("null");
        }
        else
        {
            Debug.WriteLine((string)e.Properties[key]);
        }
    }

    // e.Message contains the message itself
    if(e.Message == "stop")
    {
        ShoudIStop = true;
    }
}

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:

azureIoT.AddMethodCallback(MethodCallbackTest);
azureIoT.AddMethodCallback(MakeAddition);
azureIoT.AddMethodCallback(RaiseExceptionCallbackTest);

string MethodCallbackTest(int rid, string payload)
{
    Debug.WriteLine($"Call back called :-) rid={rid}, payload={payload}");
    return "{\"Yes\":\"baby\",\"itisworking\":42}";
}

string MakeAddition(int rid, string payload)
{
    Hashtable variables = (Hashtable)JsonConvert.DeserializeObject(payload, typeof(Hashtable));
    int arg1 = (int)variables["arg1"];
    int arg2 = (int)variables["arg2"];
    return $"{{\"result\":{arg1 + arg2}}}";
}

string RaiseExceptionCallbackTest(int rid, string payload)
{
    // This will properly return as well the exception error
    throw new Exception("I got you, it's to test the 504");
}

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:

azureIoT.StatusUpdated += StatusUpdatedEvent;

void StatusUpdatedEvent(object sender, StatusUpdatedEventArgs e)
{
    Debug.WriteLine($"Status changed: {e.IoTHubStatus.Status}, {e.IoTHubStatus.Message}");
    // You may want to reconnect or use a similar retry mechanism
    ////if (e.IoTHubStatus.Status == Status.Disconnected)
    ////{
    ////    mqtt.Open();
    ////}
}

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

By default, the device SDKs connect to an IoT Hub use QoS 1 for message exchange with the IoT hub. You can change this by setting the qosLevel argument of the DeviceClient constructor.

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.

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

Module support

Modules are supported, you will have to use the constructor to pass the module ID either with a SAS token, either with a certificate. The rest fully works like a normal device. Everything is fully supported including module direct method, telemetry and of course twins!

For example here with a SAS token. Note that the certificates are fully supported as well. And if you are not storing the Azure root certificate on the device, you'll need to pass it in the constructor.

const string DeviceID = "nanoEdgeTwin";
const string ModuleID = "myModule";
const string IotBrokerAddress = "youriothub.azure-devices.net";
const string SasKey = "yoursaskey";
DeviceClient module = new DeviceClient(IotBrokerAddress, DeviceID, ModuleID, SasKey);

Azure IoT Device Provisioning Service (DPS) support

This SDK also supports the Azure IoT Device Provisioning Service. Group and individual provisioning scenarios are supported either with a symmetric key either with certificates. To understand the mechanism behind DPS, it is recommended to read the documentation.

Provisioning using symmetric key

For symmetric key provisioning you only need the following elements:

  • A registration ID
  • The ID Scope
  • The device name
  • The key or the derived key for group provisioning

The code is then straight forward:

const string RegistrationID = "nanoDPStTest";
const string DpsAddress = "global.azure-devices-provisioning.net";
const string IdScope = "0ne01234567";
const string SasKey = "alongkeyencodedbase64";

// See the previous sections in the SDK help, you either need to have the Azure certificate embedded
// Either passing it in the constructor
X509Certificate azureCA = new X509Certificate(DpsSampleApp.Resources.GetBytes(DpsSampleApp.Resources.BinaryResources.BaltimoreRootCA_crt));
var provisioning = ProvisioningDeviceClient.Create(DpsAddress, IdScope, RegistrationID, SasKey, azureCA);
var myDevice = provisioning.Register(new CancellationTokenSource(60000).Token);

if(myDevice.Status != ProvisioningRegistrationStatusType.Assigned)
{
    Debug.WriteLine($"Registration is not assigned: {myDevice.Status}, error message: {myDevice.ErrorMessage}");
    return;
}

// You can then create the device
var device = new DeviceClient(myDevice.AssignedHub, myDevice.DeviceId, SasKey, nanoFramework.M2Mqtt.Messages.MqttQoSLevel.AtLeastOnce, azureCA);
// Open it and continue like for the previous sections
var res = device.Open();
if(!res)
{
    Debug.WriteLine($"can't open the device");
    return;
}

In case a DPS model is going to be used, the ID of the model has to be passed to the ProvisioningDeviceClient and DeviceClient constructor. The code above requires the following changes.

Add the model ID as a constant:

public const string ModelId = "dtmi:orgpal:palthree:palthree_demo_0;1";

Create the additional payload information with the model ID to be sent along the registration with DPS and pass that to the call to Register().

var pnpPayload = new ProvisioningRegistrationAdditionalData
{
    JsonData = PnpConvention.CreateDpsPayload(ModelId),
};

var myDevice = provisioning.Register(pnpPayload, new CancellationTokenSource(60000).Token);

Create the device client passing the model ID to the respective parameter in the constructor.

var device = new DeviceClient(myDevice.AssignedHub, myDevice.DeviceId, SasKey, nanoFramework.M2Mqtt.Messages.MqttQoSLevel.AtLeastOnce, azureCA, ModelId);

Note: like for the DeviceClient you need to make sure you are connected to a network properly and also have a proper data and time set on the device.

Provisioning using certificates

For symmetric key provisioning you only need the following elements:

  • A registration ID
  • The ID Scope
  • The device name
  • The device certificate
  • Make sure that your IoT Hub is as well aware of the root/intermediate certificate you are using otherwise you won't be able to connect to your IoT Hub once your device is provisioned

The code is then straight forward:

const string RegistrationID = "nanoCertTest";
const string DpsAddress = "global.azure-devices-provisioning.net";
const string IdScope = "0ne0034F11A";

const string cert = @"
-----BEGIN CERTIFICATE-----
Your certificate
-----END CERTIFICATE-----
";

const string privateKey = @"
-----BEGIN ENCRYPTED PRIVATE KEY-----
the encrypted private key
-----END ENCRYPTED PRIVATE KEY-----
";

// See the previous sections in the SDK help, you either need to have the Azure certificate embedded
// Either passing it in the constructor
X509Certificate azureCA = new X509Certificate(DpsSampleApp.Resources.GetBytes(DpsSampleApp.Resources.BinaryResources.BaltimoreRootCA_crt));
// Note: if your private key is not protected with a password, you don't need to pass it
// You can as well store your certificate directly in the device certificate store
// And you can store it as a resource as well if needed
X509Certificate2 deviceCert = new X509Certificate2(cert, privateKey, "1234");

var provisioning = ProvisioningDeviceClient.Create(DpsAddress, IdScope, RegistrationID, deviceCert, azureCA);
var myDevice = provisioning.Register(new CancellationTokenSource(60000).Token);

if(myDevice.Status != ProvisioningRegistrationStatusType.Assigned)
{
    Debug.WriteLine($"Registration is not assigned: {myDevice.Status}, error message: {myDevice.ErrorMessage}");
    return;
}

// You can then create the device
var device = new DeviceClient(myDevice.AssignedHub, myDevice.DeviceId, deviceCert, nanoFramework.M2Mqtt.Messages.MqttQoSLevel.AtLeastOnce, azureCA);
// Open it and continue like for the previous sections
var res = device.Open();
if(!res)
{
    Debug.WriteLine($"can't open the device");
    return;
}

Additional payload

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.

Join our Discord community here.

Credits

The list of contributors to this project can be found at CONTRIBUTORS.

License

The nanoFramework Class Libraries are licensed under the MIT license.

Code of Conduct

This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behaviour in our community. For more information see the .NET Foundation Code of Conduct.

.NET Foundation

This project is supported by the .NET Foundation.

Product Compatible and additional computed target framework versions.
.NET Framework net is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on nanoFramework.Azure.Devices.Client:

Package Downloads
MakoIoT.Device.Services.AzureIotHub

AzureIoTHub bus provider for MAKO-IoT

GitHub repositories (2)

Showing the top 2 popular GitHub repositories that depend on nanoFramework.Azure.Devices.Client:

Repository Stars
dotnet/samples
Sample code referenced by the .NET documentation
nanoframework/Samples
🍬 Code samples from the nanoFramework team used in testing, proof of concepts and other explorational endeavours
Version Downloads Last updated
1.2.66 85 12/23/2024
1.2.62 402 11/4/2024
1.2.59 331 10/11/2024
1.2.58 226 10/3/2024
1.2.56 228 9/26/2024
1.2.53 272 9/10/2024
1.2.50 306 7/30/2024
1.2.47 1,029 5/20/2024
1.2.45 210 5/13/2024
1.2.43 99 5/13/2024
1.2.41 323 4/30/2024
1.2.38 295 4/9/2024
1.2.36 132 4/9/2024
1.2.34 277 4/3/2024
1.2.32 123 4/3/2024
1.2.29 563 1/29/2024
1.2.25 163 1/26/2024
1.2.23 215 1/24/2024
1.2.21 165 1/22/2024
1.2.16 917 11/23/2023
1.2.14 334 11/10/2023
1.2.12 250 11/8/2023
1.2.10 377 10/23/2023
1.2.7 288 10/10/2023
1.2.5 144 10/10/2023
1.2.3 552 9/5/2023
1.2.1 170 9/4/2023
1.1.145 212 8/28/2023
1.1.143 181 8/28/2023
1.1.141 184 8/28/2023
1.1.139 207 8/8/2023
1.1.137 284 7/27/2023
1.1.135 167 7/27/2023
1.1.133 293 5/28/2023
1.1.125 455 2/17/2023
1.1.122 414 1/14/2023
1.1.119 370 12/28/2022
1.1.116 297 12/28/2022
1.1.114 332 12/28/2022
1.1.106 534 11/24/2022
1.1.104 333 11/23/2022
1.1.101 351 11/23/2022
1.1.97 375 11/15/2022
1.1.95 442 11/4/2022
1.1.92 420 10/28/2022
1.1.90 408 10/28/2022
1.1.88 384 10/27/2022
1.1.85 413 10/26/2022
1.1.83 432 10/26/2022
1.1.81 382 10/26/2022
1.1.79 406 10/25/2022
1.1.77 401 10/25/2022
1.1.75 401 10/25/2022
1.1.73 399 10/25/2022
1.1.71 388 10/24/2022
1.1.69 428 10/24/2022
1.1.67 433 10/24/2022
1.1.64 451 10/23/2022
1.1.62 452 10/23/2022
1.1.60 431 10/21/2022
1.1.58 467 10/15/2022
1.1.56 454 10/12/2022
1.1.54 447 10/10/2022
1.1.52 424 10/10/2022
1.1.50 421 10/8/2022
1.1.48 448 10/8/2022
1.1.45 508 9/23/2022
1.1.41 471 9/22/2022
1.1.38 467 9/22/2022
1.1.36 454 9/22/2022
1.1.33 507 9/18/2022
1.1.31 523 9/15/2022
1.1.29 493 9/15/2022
1.1.26 478 9/15/2022
1.1.23 486 9/9/2022
1.1.19 555 8/5/2022
1.1.17 465 8/5/2022
1.1.15 455 8/4/2022
1.1.14 469 8/4/2022
1.1.12 444 8/4/2022
1.1.10 435 8/4/2022
1.1.8 447 8/3/2022
1.1.6 437 8/3/2022
1.1.4 449 8/3/2022
1.1.2 436 8/3/2022
1.0.1.38 447 8/3/2022
1.0.1.36 479 7/26/2022
1.0.1.34 533 7/19/2022
1.0.1.32 551 6/13/2022
1.0.1.30 484 6/13/2022
1.0.1.28 469 6/8/2022
1.0.1.26 470 6/8/2022
1.0.1.22 491 5/26/2022
1.0.1.20 477 5/26/2022
1.0.1.18 461 5/21/2022
1.0.1.16 514 5/19/2022
1.0.1.14 460 5/18/2022
1.0.1.12 451 5/18/2022
1.0.1.10 447 5/18/2022
1.0.1.6 520 5/3/2022
1.0.1.4 443 5/3/2022
1.0.1 455 5/3/2022
1.0.0 588 3/30/2022
1.0.0-preview.255 133 3/28/2022
1.0.0-preview.252 132 3/28/2022
1.0.0-preview.250 133 3/28/2022
1.0.0-preview.246 147 3/17/2022
1.0.0-preview.244 135 3/17/2022
1.0.0-preview.242 126 3/17/2022
1.0.0-preview.240 136 3/15/2022
1.0.0-preview.238 138 3/15/2022
1.0.0-preview.236 189 3/11/2022
1.0.0-preview.234 136 3/8/2022
1.0.0-preview.232 143 3/8/2022
1.0.0-preview.230 122 3/4/2022
1.0.0-preview.228 123 3/3/2022
1.0.0-preview.226 128 3/2/2022
1.0.0-preview.224 163 2/28/2022
1.0.0-preview.222 163 2/24/2022
1.0.0-preview.220 129 2/23/2022
1.0.0-preview.217 128 2/17/2022
1.0.0-preview.215 125 2/17/2022
1.0.0-preview.213 132 2/17/2022
1.0.0-preview.211 124 2/15/2022
1.0.0-preview.209 132 2/8/2022
1.0.0-preview.207 142 2/6/2022
1.0.0-preview.205 137 2/6/2022
1.0.0-preview.203 146 2/4/2022
1.0.0-preview.202 151 2/4/2022
1.0.0-preview.200 143 2/4/2022
1.0.0-preview.198 143 2/4/2022
1.0.0-preview.196 142 2/4/2022
1.0.0-preview.194 143 2/1/2022
1.0.0-preview.192 144 1/28/2022
1.0.0-preview.190 136 1/28/2022
1.0.0-preview.188 141 1/28/2022
1.0.0-preview.186 144 1/28/2022
1.0.0-preview.184 144 1/28/2022
1.0.0-preview.182 147 1/28/2022
1.0.0-preview.180 140 1/28/2022
1.0.0-preview.178 144 1/28/2022
1.0.0-preview.176 134 1/25/2022
1.0.0-preview.174 130 1/22/2022
1.0.0-preview.172 132 1/21/2022
1.0.0-preview.170 132 1/21/2022
1.0.0-preview.168 128 1/21/2022
1.0.0-preview.166 139 1/21/2022
1.0.0-preview.164 134 1/21/2022
1.0.0-preview.162 140 1/21/2022
1.0.0-preview.160 135 1/21/2022
1.0.0-preview.158 134 1/21/2022
1.0.0-preview.156 139 1/21/2022
1.0.0-preview.154 135 1/21/2022
1.0.0-preview.152 134 1/21/2022
1.0.0-preview.150 142 1/14/2022
1.0.0-preview.148 138 1/14/2022
1.0.0-preview.146 138 1/14/2022
1.0.0-preview.143 136 1/12/2022
1.0.0-preview.141 140 1/11/2022
1.0.0-preview.139 140 1/11/2022
1.0.0-preview.136 139 1/6/2022
1.0.0-preview.134 143 1/6/2022
1.0.0-preview.132 137 1/5/2022
1.0.0-preview.130 140 1/5/2022
1.0.0-preview.128 147 1/4/2022
1.0.0-preview.127 150 1/3/2022
1.0.0-preview.126 145 1/3/2022
1.0.0-preview.125 145 1/3/2022
1.0.0-preview.124 147 1/3/2022
1.0.0-preview.123 138 12/31/2021
1.0.0-preview.122 139 12/31/2021
1.0.0-preview.121 140 12/30/2021
1.0.0-preview.120 139 12/29/2021
1.0.0-preview.119 144 12/28/2021
1.0.0-preview.117 144 12/17/2021
1.0.0-preview.115 167 12/5/2021
1.0.0-preview.113 268 12/4/2021
1.0.0-preview.111 154 12/4/2021
1.0.0-preview.109 154 12/3/2021
1.0.0-preview.107 156 12/3/2021
1.0.0-preview.105 146 12/3/2021
1.0.0-preview.103 147 12/3/2021
1.0.0-preview.101 151 12/3/2021
1.0.0-preview.99 160 12/3/2021
1.0.0-preview.97 156 12/3/2021
1.0.0-preview.95 158 12/3/2021
1.0.0-preview.93 149 12/2/2021
1.0.0-preview.91 148 12/2/2021
1.0.0-preview.89 154 12/2/2021
1.0.0-preview.87 151 12/2/2021
1.0.0-preview.85 151 12/2/2021
1.0.0-preview.83 151 12/2/2021
1.0.0-preview.81 149 12/2/2021
1.0.0-preview.79 146 12/2/2021
1.0.0-preview.77 147 12/2/2021
1.0.0-preview.75 151 12/1/2021
1.0.0-preview.73 152 12/1/2021
1.0.0-preview.71 151 12/1/2021
1.0.0-preview.69 2,970 11/25/2021
1.0.0-preview.65 150 11/23/2021
1.0.0-preview.63 160 11/18/2021
1.0.0-preview.61 159 11/14/2021
1.0.0-preview.59 260 11/13/2021
1.0.0-preview.55 195 11/12/2021
1.0.0-preview.53 188 11/11/2021
1.0.0-preview.50 170 11/11/2021
1.0.0-preview.48 195 10/22/2021
1.0.0-preview.46 155 10/19/2021
1.0.0-preview.44 149 10/19/2021
1.0.0-preview.42 205 10/18/2021
1.0.0-preview.40 224 10/16/2021
1.0.0-preview.37 605 9/17/2021
1.0.0-preview.35 176 9/17/2021
1.0.0-preview.32 198 9/13/2021
1.0.0-preview.28 174 8/17/2021
1.0.0-preview.25 213 8/1/2021
1.0.0-preview.23 949 7/5/2021
1.0.0-preview.21 164 7/1/2021
1.0.0-preview.20 212 6/28/2021