nanoFramework.Azure.Devices.Client 1.2.50
前缀已保留
dotnet add package nanoFramework.Azure.Devices.Client --version 1.2.50
NuGet\Install-Package nanoFramework.Azure.Devices.Client -Version 1.2.50
<PackageReference Include="nanoFramework.Azure.Devices.Client" Version="1.2.50" />
paket add nanoFramework.Azure.Devices.Client --version 1.2.50
#r "nuget: nanoFramework.Azure.Devices.Client, 1.2.50"
// Install nanoFramework.Azure.Devices.Client as a Cake Addin #addin nuget:?package=nanoFramework.Azure.Devices.Client&version=1.2.50 // Install nanoFramework.Azure.Devices.Client as a Cake Tool #tool nuget:?package=nanoFramework.Azure.Devices.Client&version=1.2.50
欢迎使用 .NET nanoFramework Azure.Devices.Client 库存储库
构建状态
组件 | 构建状态 | NuGet 包 |
---|---|---|
nanoFramework.Azure.Devices.Client | ||
nanoFramework.Azure.Devices.Client.FullyManaged |
查看实际操作
您可以观看这段微软IoT 展示视频,其中包含 Azure SDK 和 .NET nanoFramework 的实际示例
nanoFramework.Azure.Devices.Client 与 nanoFramework.Azure.Devices.Client.FullyManaged 对比
The nanoFramework.Azure.Devices.Client.FullyManaged
nuget 已构建为一个与您运行的本地硬件独立的包。因此,它将不使用 X509Certificate,而使用字节数组。它将不使用 nanoFramework.M2Mqtt
库,而使用一个名为 nanoFramework.M2Mqtt.Core
的抽象接口。
这允许的主要场景是携带自己的 MQTT 代理并在无 System.Net 的设备上运行。这允许通过实现 MQTT 客户端的调制解调器进行连接。您可以几乎完全重用用于具有本机网络功能的设备以及使用调制解调器的设备的相同代码。
用法
重要:您必须连接到拥有有效IP地址和日期的网络。请检查网络助手提供的示例,以便确保您两者都有。
此Azure IoT Hub SDK使用MQTT。因此,您需要确保可以使用TLS协议连接到端口号8883。如果您连接到企业网络,这可能会被阻止。在大多数情况下,这不是问题。
命名空间、类名与方法尽量接近.NET C# Azure IoT SDK。这应该可以简化代码在完整的Net框架和nanoFramework环境之间的迁移。
证书
您有2种方式提供正确的Azure IoT TLS证书:
- 将其解析到构造函数中
- 将其存储到设备中
AzureCertificates方便地包含连接到Azure IoT所需的根证书。当前的证书是Baltimore Root CA,直到2022年6月使用。从2022年6月开始,应使用DigiCert Global Root 2。有关更多信息,请阅读以下博客。
通过构造函数
截至2023年10月15日,您必须将新的Azure DigiCert Global Root G2证书嵌入到您的代码中,才能正确与IoT服务进行通信
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));
注意:在使用FullyManaged库时,您必须传递一个
byte[]
而不是一个X509Certificate
。您使用的代理可能支持或可能不支持PEM或DER证书。请确保使用正确的证书,检查供应商的文档。PEM证书是DER证书的base64编码版本,通常以.crt
扩展名存在。
您还可以将二进制证书放置在程序资源中,并仅从此处获取证书
X509Certificate azureRootCACert = new X509Certificate(Resources.GetBytes(Resources.BinaryResources.AzureCAcertificate));
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, azureCert: azureRootCACert);
注意:当证书过期时,您必须用新的证书完全刷新设备。
在设备上存储证书
您可以在设备闪存上存储证书,而不是在代码中,这样如果需要更改证书,只需清除当前存储并上传新证书即可。编辑网络属性
转到常规
选项卡
浏览以选择您的证书,它可以是二进制格式(crt、der)或字符串格式(pem、txt),然后选择确定。在连接期间将自动选择连接所需的证书。
创建DeviceClient
您可以使用对称密钥或证书连接到Azure IoT Hub。以下示例显示了如何使用对称密钥:
const string DeviceID = "nanoEdgeTwin";
const string IotBrokerAddress = "youriothub.azure-devices.net";
const string SasKey = "yoursaskey";
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey);
注意:请参阅上一部分,了解如何更好地解析证书供您使用。示例显示了上传到设备的证书,而不是在代码中。
Azure IoT Plug&Play
Azure IoT Plug&Play也得到支持。创建DeviceClient时,您需要提供模型ID。
DeviceClient azureIoT = new DeviceClient(IotBrokerAddress, DeviceID, SasKey, modelID:"dtmi:com:example:Thermostat;1");
注意:必须在创建DeviceClient时传递模型ID,不可能稍后传递。
报告属性
报告Plug & Play属性得到支持。这是一个全面的示例,说明您如何检查是否收到您感兴趣的一个属性
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);
}
}
在此示例中,我们感兴趣接收的属性名为targetTemperature
。要接收其更新,我们正在订阅元数据更新。一旦我们检查到该属性存在,我们就可以通过e.Twin[TargetTemerature]
获取其值。
发布可写属性的规则非常简单。它涉及到构建一个 PropertyAcknowledge
,创建一个 TwinCollection,并添加属性名称,这里是我们 的 targetTemperature
。当然,您可以添加更多属性以进行报告。请注意,添加到 TwinCollection 中的不是对象本身,而是 BuildAcknowledge()
。完成后,只需调用库的 UpdateReportedProperties
方法来更新双胞胎即可。
接收命令
IoT Plug & Play 命令是一个方法回调。请参阅本文件中有关如何使用它们的更多说明。在我们的案例中,该方法是 getMaxMinReport
。在 C# 中方法的名称 必须 与 DTDL 文件中的名称完全相同。
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);
}
在此示例中,预期的结果是对象。只需填充对象并以其预期的 json 格式序列化即可,作为命令的返回值。如果有任何命令参数,它将包含在负载中。
获取和更新双胞胎
您可以通过调用 GetTwin
函数来请求您的 Azure IoT 双胞胎。
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}");
注意:使用会在一定时间后取消的 CancellationToken
是很重要的。否则,这将阻塞线程,直到收到双胞胎为止。
双胞胎具有保存的和期望的属性。它们是集合,您可以获取或尝试获取任何元素。
您可以通过这种方式简单地报告您的双胞胎:
TwinCollection reported = new TwinCollection();
reported.Add("firmware", "myNano");
reported.Add("sdk", 0.2);
azureIoT.UpdateReportedProperties(reported);
您还可以选择等待双胞胎更新确认,在这种情况下,使用可取消的 CancellationToken
。否则检查将被忽略。
注意:如果未检查双胞胎接收确认或确认未及时到达,函数将返回 false。
您还可以注册任何双胞胎更新。
azureIoT.TwinUpdated += TwinUpdatedEvent;
void TwinUpdatedEvent(object sender, TwinUpdateEventArgs e)
{
Debug.WriteLine($"Twin update received: {e.Twin.Count}");
}
注意:一些调制解调器在消息长度上有限制。消息包含双胞胎信息。确保在使用 FullyManaged 库时检查这些限制。
发送消息
必须使用 SendMessage
函数来向 Azure IoT 发送任何类型的消息或遥测信息。与其他函数一样,您可以确保通过一个可以取消的 CancellationToken
进行传递。如果使用一个不可取消的,传递保证将被忽略,函数将返回 false。
var isReceived = azureIoT.SendMessage($"{{\"Temperature\":42,\"Pressure\":1024}}", new CancellationTokenSource(5000).Token);
Debug.WriteLine($"Message received by IoT Hub: {isReceived}");
注意:消息将以您创建设备时创建的默认服务质量发送。您不会得到 0
质量的任何答案。在这种情况下,您可以将它简化为:
azureIoT.SendMessage($"{{\"Temperature\":42,\"Pressure\":1024}}");
云到设备消息
您可以注册一个事件来接收云到设备消息
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;
}
}
注意:sender
是 DeviceClient
类,然后可以向它发送消息、确认或任何已设置的逻辑。
注意:一些调制解调器在消息长度和主题长度上有限制。主题长度包含属性包。在使用 FullyManaged 库时,请确保检查这些限制。
方法回调
同样支持方法回调。您可以注册和注销您的函数。以下是一些示例
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");
}
重要:方法名称区分大小写。因此,请确保在 C# 中命名函数时使用相同的括号。
注意:一些调制解调器在消息长度上有限制。消息包含负载内容。在使用 FullyManaged 库时,确保检查这些限制。
状态更新事件
有一个状态更新事件可用
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();
////}
}
注意:一些调制解调器在 MQTT 实现上有限制,所以你可能不会得到所有更新。在使用 FullyManaged 库时,请确保检查这些限制。
请注意,这些状态是根据事件变化的,所以一旦接收到连接或断开事件,它们将立即被其他事件替换,例如接收到一个孪生体。
服务质量级别
默认情况下,设备SDK连接到IoT中心使用服务质量级别1与IoT中心进行消息交换。您可以通过设置DeviceClient
构造函数的qosLevel
参数来更改此设置。
以下是您可以使用的服务质量级别
- AtMostOnce:代理/客户端将发送消息一次,而不进行确认。
- AtLeastOnce:代理/客户端将至少发送一次消息,需要确认。
- ExactlyOnce:代理/客户端将通过四步握手精确发送一次消息。
虽然可以将服务质量级别0(AtMostOnce)配置为更快地交换消息,但您应该注意,这种发送是不保证也不被确认的。因此,服务质量级别0通常被称为“发射后遗忘”。
模块支持
支持模块,您必须使用构造函数通过SAS令牌或证书传递模块ID。其余部分完全像正常设备一样工作。一切都得到完全支持,包括模块直接方法、遥测以及当然还有孪生体!
例如,下面使用SAS令牌。请注意,证书也完全支持。如果您没有在设备上存储Azure根证书,则需要将其传递给构造函数。
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 设备配置服务 (DPS) 支持
此SDK也支持Azure IoT设备配置服务。支持通过对称密钥或证书的组或单个配置场景。要了解DPS背后的机制,建议阅读文档。
使用对称密钥进行配置
对于对称密钥配置,您只需要以下元素
- 注册ID
- 标识范围
- 设备名称
- 用于组配置的密钥或导入的密钥
代码非常简单
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;
}
如果打算使用DPS模型,则需要将模型的ID传递给ProvisioningDeviceClient和DeviceClient构造函数。上面的代码需要以下更改。
添加模型ID作为常量
public const string ModelId = "dtmi:orgpal:palthree:palthree_demo_0;1";
创建附加的有效负载信息,包括模型ID,并将其与DPS的注册一起发送,并将它传递到Register()
调用的调用中。
var pnpPayload = new ProvisioningRegistrationAdditionalData
{
JsonData = PnpConvention.CreateDpsPayload(ModelId),
};
var myDevice = provisioning.Register(pnpPayload, new CancellationTokenSource(60000).Token);
通过在构造函数中传递模型ID来创建设备客户端。
var device = new DeviceClient(myDevice.AssignedHub, myDevice.DeviceId, SasKey, nanoFramework.M2Mqtt.Messages.MqttQoSLevel.AtLeastOnce, azureCA, ModelId);
注意:类似于DeviceClient
,您需要确保您已正确连接到网络,并且设备上已正确设置数据和日期。
使用证书进行配置
对于对称密钥配置,您只需要以下元素
- 注册ID
- 标识范围
- 设备名称
- 设备证书
- 确保您的IoT中心也知道您正在使用根/中间证书,否则在您的设备配置后,您将无法连接到您的IoT中心。
代码非常简单
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;
}
附加有效负载
也支持附加有效负载。您可以在调用Register
函数时的ProvisioningRegistrationAdditionalData
类中将它作为JSON字符串设置。当设备已配置时,您也可以提供附加有效负载。
注意:一些调制解调器在消息长度上有限制。消息包含负载内容。在使用 FullyManaged 库时,确保检查这些限制。
反馈和文档
有关文档、提供反馈、问题以及如何贡献力量,请参阅主页仓库。
加入我们的Discord社区这里。
致谢
此项目的贡献者列表可在贡献者中找到。
许可
nanoFramework类库根据MIT许可证许可。
行为准则
本项目采用了贡献者公约中定义的行为准则,以阐明我们社区的期望行为。有关更多信息,请参阅.NET 基金会行为准则。
.NET 基金会
该项目由.NET 基金会支持。
产品 | 版本 兼容和额外的计算目标框架版本。 |
---|---|
.NET 框架 | net 兼容。 |
-
- nanoFramework.CoreLibrary (>= 1.15.5)
- nanoFramework.Json (>= 2.2.122)
- nanoFramework.M2Mqtt (>= 5.1.138)
- nanoFramework.System.Net.Http (>= 1.5.99)
NuGet 包 (1)
显示依赖 nanoFramework.Azure.Devices.Client 的前1个NuGet包
包 | 下载 |
---|---|
MakoIoT.Device.Services.AzureIotHub
AzureIotHub总线提供程序用于MAKO-IoT |
GitHub 仓库 (2)
显示依赖 nanoFramework.Azure.Devices.Client 的前2个热门GitHub仓库
仓库 | 星标 |
---|---|
dotnet/samples
.NET 文档引用的示例代码
|
|
nanoframework/Samples
🍬 来自 nanoFramework 团队用于测试、概念验证和其他探索性努力的代码示例
|
版本 | 下载 | 最后更新 |
---|---|---|
1.2.50 | 86 | 7/30/2024 |
1.2.47 | 495 | 5/20/2024 |
1.2.45 | 155 | 5/13/2024 |
1.2.43 | 63 | 5/13/2024 |
1.2.41 | 229 | 4/30/2024 |
1.2.38 | 247 | 4/9/2024 |
1.2.36 | 103 | 4/9/2024 |
1.2.34 | 222 | 4/3/2024 |
1.2.32 | 95 | 4/3/2024 |
1.2.29 | 482 | 1/29/2024 |
1.2.25 | 119 | 1/26/2024 |
1.2.23 | 165 | 1/24/2024 |
1.2.21 | 125 | 1/22/2024 |
1.2.16 | 769 | 11/23/2023 |
1.2.14 | 259 | 11/10/2023 |
1.2.12 | 183 | 11/8/2023 |
1.2.10 | 315 | 10/23/2023 |
1.2.7 | 240 | 10/10/2023 |
1.2.5 | 115 | 10/10/2023 |
1.2.3 | 462 | 9/5/2023 |
1.2.1 | 139 | 9/4/2023 |
1.1.145 | 182 | 8/28/2023 |
1.1.143 | 151 | 8/28/2023 |
1.1.141 | 159 | 8/28/2023 |
1.1.139 | 181 | 8/8/2023 |
1.1.137 | 254 | 7/27/2023 |
1.1.135 | 139 | 7/27/2023 |
1.1.133 | 265 | 5/28/2023 |
1.1.125 | 425 | 2/17/2023 |
1.1.122 | 389 | 1/14/2023 |
1.1.119 | 343 | 12/28/2022 |
1.1.116 | 272 | 12/28/2022 |
1.1.114 | 305 | 12/28/2022 |
1.1.106 | 507 | 11/24/2022 |
1.1.104 | 301 | 11/23/2022 |
1.1.101 | 321 | 11/23/2022 |
1.1.97 | 346 | 11/15/2022 |
1.1.95 | 416 | 11/4/2022 |
1.1.92 | 389 | 10/28/2022 |
1.1.90 | 381 | 10/28/2022 |
1.1.88 | 356 | 10/27/2022 |
1.1.85 | 386 | 10/26/2022 |
1.1.83 | 406 | 10/26/2022 |
1.1.81 | 354 | 10/26/2022 |
1.1.79 | 377 | 10/25/2022 |
1.1.77 | 372 | 10/25/2022 |
1.1.75 | 367 | 10/25/2022 |
1.1.73 | 372 | 10/25/2022 |
1.1.71 | 361 | 10/24/2022 |
1.1.69 | 399 | 10/24/2022 |
1.1.67 | 406 | 10/24/2022 |
1.1.64 | 422 | 10/23/2022 |
1.1.62 | 425 | 10/23/2022 |
1.1.60 | 403 | 10/21/2022 |
1.1.58 | 442 | 10/15/2022 |
1.1.56 | 425 | 10/12/2022 |
1.1.54 | 416 | 10/10/2022 |
1.1.52 | 398 | 10/10/2022 |
1.1.50 | 396 | 10/8/2022 |
1.1.48 | 417 | 10/8/2022 |
1.1.45 | 480 | 9/23/2022 |
1.1.41 | 441 | 9/22/2022 |
1.1.38 | 438 | 9/22/2022 |
1.1.36 | 428 | 9/22/2022 |
1.1.33 | 476 | 9/18/2022 |
1.1.31 | 494 | 9/15/2022 |
1.1.29 | 465 | 9/15/2022 |
1.1.26 | 449 | 9/15/2022 |
1.1.23 | 452 | 9/9/2022 |
1.1.19 | 527 | 8/5/2022 |
1.1.17 | 436 | 8/5/2022 |
1.1.15 | 427 | 8/4/2022 |
1.1.14 | 442 | 8/4/2022 |
1.1.12 | 415 | 8/4/2022 |
1.1.10 | 404 | 8/4/2022 |
1.1.8 | 421 | 8/3/2022 |
1.1.6 | 421 | 8/3/2022 |
1.1.4 | 435 | 8/3/2022 |
1.1.2 | 411 | 8/3/2022 |
1.0.1.38 | 419 | 8/3/2022 |
1.0.1.36 | 455 | 7/26/2022 |
1.0.1.34 | 507 | 7/19/2022 |
1.0.1.32 | 519 | 6/13/2022 |
1.0.1.30 | 454 | 6/13/2022 |
1.0.1.28 | 438 | 6/8/2022 |
1.0.1.26 | 440 | 6/8/2022 |
1.0.1.22 | 459 | 5/26/2022 |
1.0.1.20 | 444 | 5/26/2022 |
1.0.1.18 | 433 | 5/21/2022 |
1.0.1.16 | 485 | 5/19/2022 |
1.0.1.14 | 445 | 5/18/2022 |
1.0.1.12 | 424 | 5/18/2022 |
1.0.1.10 | 417 | 5/18/2022 |
1.0.1.6 | 490 | 5/3/2022 |
1.0.1.4 | 409 | 5/3/2022 |
1.0.1 | 429 | 5/3/2022 |
1.0.0 | 560 | 3/30/2022 |
1.0.0-preview.255 | 122 | 3/28/2022 |
1.0.0-preview.252 | 119 | 3/28/2022 |
1.0.0-preview.250 | 120 | 3/28/2022 |
1.0.0-preview.246 | 133 | 3/17/2022 |
1.0.0-preview.244 | 123 | 3/17/2022 |
1.0.0-preview.242 | 114 | 3/17/2022 |
1.0.0-preview.240 | 124 | 3/15/2022 |
1.0.0-preview.238 | 122 | 3/15/2022 |
1.0.0-preview.236 | 171 | 3/11/2022 |
1.0.0-preview.234 | 124 | 3/8/2022 |
1.0.0-preview.232 | 130 | 3/8/2022 |
1.0.0-preview.230 | 110 | 3/4/2022 |
1.0.0-preview.228 | 109 | 3/3/2022 |
1.0.0-preview.226 | 116 | 3/2/2022 |
1.0.0-preview.224 | 151 | 2/28/2022 |
1.0.0-preview.222 | 153 | 2/24/2022 |
1.0.0-preview.220 | 116 | 2/23/2022 |
1.0.0-preview.217 | 117 | 2/17/2022 |
1.0.0-preview.215 | 112 | 2/17/2022 |
1.0.0-preview.213 | 117 | 2/17/2022 |
1.0.0-preview.211 | 113 | 2/15/2022 |
1.0.0-preview.209 | 120 | 2/8/2022 |
1.0.0-preview.207 | 128 | 2/6/2022 |
1.0.0-preview.205 | 123 | 2/6/2022 |
1.0.0-preview.203 | 134 | 2/4/2022 |
1.0.0-preview.202 | 139 | 2/4/2022 |
1.0.0-preview.200 | 129 | 2/4/2022 |
1.0.0-preview.198 | 131 | 2/4/2022 |
1.0.0-preview.196 | 128 | 2/4/2022 |
1.0.0-preview.194 | 131 | 2/1/2022 |
1.0.0-preview.192 | 130 | 1/28/2022 |
1.0.0-preview.190 | 124 | 1/28/2022 |
1.0.0-preview.188 | 129 | 1/28/2022 |
1.0.0-preview.186 | 130 | 1/28/2022 |
1.0.0-preview.184 | 130 | 1/28/2022 |
1.0.0-preview.182 | 135 | 1/28/2022 |
1.0.0-preview.180 | 128 | 1/28/2022 |
1.0.0-preview.178 | 134 | 1/28/2022 |
1.0.0-preview.176 | 124 | 1/25/2022 |
1.0.0-preview.174 | 120 | 1/22/2022 |
1.0.0-preview.172 | 120 | 1/21/2022 |
1.0.0-preview.170 | 120 | 1/21/2022 |
1.0.0-preview.168 | 118 | 1/21/2022 |
1.0.0-preview.166 | 129 | 1/21/2022 |
1.0.0-preview.164 | 122 | 1/21/2022 |
1.0.0-preview.162 | 128 | 1/21/2022 |
1.0.0-preview.160 | 125 | 1/21/2022 |
1.0.0-preview.158 | 124 | 1/21/2022 |
1.0.0-preview.156 | 123 | 1/21/2022 |
1.0.0-preview.154 | 125 | 1/21/2022 |
1.0.0-preview.152 | 124 | 1/21/2022 |
1.0.0-preview.150 | 132 | 1/14/2022 |
1.0.0-preview.148 | 126 | 1/14/2022 |
1.0.0-preview.146 | 128 | 1/14/2022 |
1.0.0-preview.143 | 126 | 1/12/2022 |
1.0.0-preview.141 | 130 | 1/11/2022 |
1.0.0-preview.139 | 128 | 1/11/2022 |
1.0.0-preview.136 | 129 | 1/6/2022 |
1.0.0-preview.134 | 133 | 1/6/2022 |
1.0.0-preview.132 | 127 | 1/5/2022 |
1.0.0-preview.130 | 128 | 1/5/2022 |
1.0.0-preview.128 | 135 | 1/4/2022 |
1.0.0-preview.127 | 140 | 1/3/2022 |
1.0.0-preview.126 | 135 | 1/3/2022 |
1.0.0-preview.125 | 133 | 1/3/2022 |
1.0.0-preview.124 | 135 | 1/3/2022 |
1.0.0-preview.123 | 126 | 12/31/2021 |
1.0.0-preview.122 | 127 | 12/31/2021 |
1.0.0-preview.121 | 128 | 12/30/2021 |
1.0.0-preview.120 | 129 | 12/29/2021 |
1.0.0-preview.119 | 134 | 12/28/2021 |
1.0.0-preview.117 | 132 | 12/17/2021 |
1.0.0-preview.115 | 155 | 12/5/2021 |
1.0.0-preview.113 | 256 | 12/4/2021 |
1.0.0-preview.111 | 142 | 12/4/2021 |
1.0.0-preview.109 | 142 | 12/3/2021 |
1.0.0-preview.107 | 142 | 12/3/2021 |
1.0.0-preview.105 | 135 | 12/3/2021 |
1.0.0-preview.103 | 136 | 12/3/2021 |
1.0.0-preview.101 | 140 | 12/3/2021 |
1.0.0-preview.99 | 149 | 12/3/2021 |
1.0.0-preview.97 | 144 | 12/3/2021 |
1.0.0-preview.95 | 145 | 12/3/2021 |
1.0.0-preview.93 | 139 | 12/2/2021 |
1.0.0-preview.91 | 136 | 12/2/2021 |
1.0.0-preview.89 | 144 | 12/2/2021 |
1.0.0-preview.87 | 141 | 12/2/2021 |
1.0.0-preview.85 | 141 | 12/2/2021 |
1.0.0-preview.83 | 141 | 12/2/2021 |
1.0.0-preview.81 | 137 | 12/2/2021 |
1.0.0-preview.79 | 134 | 12/2/2021 |
1.0.0-preview.77 | 134 | 12/2/2021 |
1.0.0-preview.75 | 138 | 12/1/2021 |
1.0.0-preview.73 | 141 | 12/1/2021 |
1.0.0-preview.71 | 141 | 12/1/2021 |
1.0.0-preview.69 | 2,960 | 11/25/2021 |
1.0.0-preview.65 | 140 | 11/23/2021 |
1.0.0-preview.63 | 148 | 11/18/2021 |
1.0.0-preview.61 | 148 | 11/14/2021 |
1.0.0-preview.59 | 248 | 11/13/2021 |
1.0.0-preview.55 | 183 | 11/12/2021 |
1.0.0-preview.53 | 178 | 11/11/2021 |
1.0.0-preview.50 | 160 | 11/11/2021 |
1.0.0-preview.48 | 183 | 10/22/2021 |
1.0.0-preview.46 | 145 | 10/19/2021 |
1.0.0-preview.44 | 137 | 10/19/2021 |
1.0.0-preview.42 | 194 | 10/18/2021 |
1.0.0-preview.40 | 214 | 10/16/2021 |
1.0.0-preview.37 | 594 | 9/17/2021 |
1.0.0-preview.35 | 164 | 9/17/2021 |
1.0.0-preview.32 | 187 | 9/13/2021 |
1.0.0-preview.28 | 164 | 8/17/2021 |
1.0.0-preview.25 | 201 | 8/1/2021 |
1.0.0-preview.23 | 836 | 7/5/2021 |
1.0.0-preview.21 | 152 | 7/1/2021 |
1.0.0-preview.20 | 200 | 6/28/2021 |