Skip to main content

设备上如何利用上报属性(Report Property)更新设备的状态

分类:  Azure物联网 标签:  #Azure #IoT Hub # #入门 #指南 发布于: 2023-06-13 23:00:23

我们之前学习和讨论过设备状态,都在关注设备的连接状态,而实际上设备的状态可以包含更多的信息:例如设备上散热风扇的启动频率和条件,设备上目前的网络是连接在Wifi上还是5G卡上,设备上的某个指示灯是亮着还是熄灭了,这些信息也是设备状态信息,那么设备是如何向云报告这些状态呢?

开始之前我们先要学习一下一个最佳实践:

每次设备和云之间的连接状态从其他状态变为Connected之后,我们都需要让设备从云读取完整的Device Twin, 并根据Device Twin中包含的预期属性(Desired Property), 对设备相应的组件进行相应的配置。

在每次连接建立并拉取设备完整的Device Twin, 配置设备成功后,云是如何了解到该设备是不是将要求的配置配置到位了呢?答案是:设备可以利用reported property向云反馈当前的状态。

官方的文档很多地方提到Desired Property(预期属性)大多数时候和report propety结合使用,但是实际的情况是不一定,在各种场景下完全可以单独利用report property来完成状态报告,例如:

  • 利用report property定期上报连接状态,当然这个不能太频繁。
  • 利用report property上报某个长时间运行工作的状态,例如更新某个组件。
  • 利用report property上报某个组件的当前状态,例如风扇的转速,开关的闭合。

而后端的应用完全可以监听上报属性更改事件,从而对设备的某些及时状态做出反应。

我们下面通过一个例子来演示设备应用和后端应用如何配合利用上报属性。

mkdir DeviceTwinDemo
cd DeviceTwinDemo
dotnet new sln -n DeviceTwinDemo
dotnet new worker -o DeviceApp
dotnet new webapp -o BackendApp
dotnet sln add .\DeviceApp\DeviceApp.csproj
dotnet sln add .\BackendApp\BackendApp.csproj
cd .\DeviceApp\
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
dotnet add package Microsoft.Azure.Devices.Client

注意
我们开始设置设备端的应用,需要注意的是,该设备端的应用设计,我们参考了文章:<>, 请仔细参考一下该文档,另外设备的所有代码您可以在这里找到:https://github.com/hylinux/azure-iot-hub-examples/tree/main/DeviceTwinDemo/DeviceApp

请按照上述的代码添加如下几个文件:

  • ExceptionHelper.cs
  • CustomRetryPolicy.cs
  • DeviceService.cs
  • Program.cs

另外需要注意更改一下.csproj文件:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net7.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <RootNamespace>DeviceApp</RootNamespace>
    <OutputType>exe</OutputType>
    <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PlatformTarget>x64</PlatformTarget>
    <UserSecretsId>6ec0f197-3d58-4a02-93b8-e99574e46609</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Azure.Devices.Client" Version="1.41.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0" />
  </ItemGroup>
</Project>

需要注意的是:<UserSecretsId>6ec0f197-3d58-4a02-93b8-e99574e46609</UserSecretsId>

是按照如下的步骤自动生成的:

dotnet user-secrets init
dotnet user-secrets set "Device:ConnectionString:Primary" "主要连接字符串"
dotnet user-secrets set "Device:ConnectionString:Secondary" "次要连接字符串"
dotnet user-secrets set "Device:Connection:TransportType" "mqtt/amqp/etc"
$env:DOTNET_ENVIRONMENT = "Development"

参考上述代码库和步骤,完成您的代码配置之后,就可以使用:

dotnet run

运行你的设备App了。

为了演示我们本章提到的两个知识点,我们需要仔细参考文件DeviceService.cs的相应方法:

  1. 演示最佳实践:当设备连接状态变为Connected之后,立即让设备从云抓回devicetwin,并解析desired property对设备相应的组件进行配置,代码参考:
    private async void ConnectionStatusChangeHandlerAsync(ConnectionStatus status, ConnectionStatusChangeReason reason)
    {
        _logger.LogDebug($"Connection status changed: status={status}, reason={reason}");
        s_connectionStatus = status;

        switch (status)
        {
            case ConnectionStatus.Connected:
                _logger.LogDebug("### The DeviceClient is CONNECTED; all operations will be carried out as normal.");

                // Call GetTwinAndDetectChangesAsync() to retrieve twin values from the server once the connection status changes into Connected.
                // This can get back "lost" twin updates in a device reconnection from status like Disconnected_Retrying or Disconnected.
                //
                // Howevever, considering how a fleet of devices connected to a hub may behave together, one must consider the implication of performing
                // work on a device (e.g., get twin) when it comes online. If all the devices go offline and then come online at the same time (for example,
                // during a servicing event) it could introduce increased latency or even throttling responses.
                // For more information, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-quotas-throttling#traffic-shaping.
                await GetTwinAndDetectChangesAsync(s_appCancellation!.Token);
                _logger.LogDebug("The client has retrieved twin values after the connection status changes into CONNECTED.");
                break;

上述代码是在监控设备连接状态变化的过程中,如果发现该连接的状态变为Connected,立即调用异步方法GetTwinAndDetectChangesAsync(Token), 我们看一下它的代码:

    private async Task GetTwinAndDetectChangesAsync(CancellationToken cancellationToken)
    {
        // For the following call, we execute with a retry strategy with incrementally increasing delays between retry.
        Twin twin = await s_deviceClient!.GetTwinAsync(cancellationToken);
        _logger.LogInformation($"Device retrieving twin values: {twin.ToJson()}");
        _logger.LogInformation($"Device Id: {twin.DeviceId}");
        _logger.LogInformation($"Twin Etags: {twin.ETag}");
        _logger.LogInformation($"Twin Version:{twin.Version}");

        TwinCollection twinCollection = twin.Properties.Desired;
        long serverDesiredPropertyVersion = twinCollection.Version;

        // Check if the desired property version is outdated on the local side.
        if (serverDesiredPropertyVersion > s_localDesiredPropertyVersion)
        {
            _logger.LogDebug($"The desired property version cached on local is changing from {s_localDesiredPropertyVersion} to {serverDesiredPropertyVersion}.");
            await HandleTwinUpdateNotificationsAsync(twinCollection, cancellationToken);
        }
    }

仔细观察上述代码,还有一个知识点,除了从云拿回Device Twin,另外还将拿回来的Device Twin的版本号和本地存储的版本号进行了对比:

TwinCollection twinCollection = twin.Properties.Desired;
long serverDesiredPropertyVersion = twinCollection.Version;

        // Check if the desired property version is outdated on the local side.
if (serverDesiredPropertyVersion > s_localDesiredPropertyVersion)
{
    _logger.LogDebug($"The desired property version cached on local is changing from {s_localDesiredPropertyVersion} to {serverDesiredPropertyVersion}.");
    await HandleTwinUpdateNotificationsAsync(twinCollection, cancellationToken);
}

如果服务端的版本号大于本地存储的版本号,才会调用方法:HandleTwinUpdateNotificationsAsync

我们再进一步看一下代码:HandleTwinUpdateNotificationsAsync

private async Task HandleTwinUpdateNotificationsAsync(TwinCollection twinUpdateRequest, object userContext)
{
    var reportedProperties = new TwinCollection();

    _logger.LogInformation($"Twin property update requested: \n{twinUpdateRequest.ToJson()}");

    // For the purpose of this sample, we'll blindly accept all twin property write requests.
    foreach (KeyValuePair<string, object> desiredProperty in twinUpdateRequest)
    {
        _logger.LogInformation($"Setting property {desiredProperty.Key} to {desiredProperty.Value}.");
        reportedProperties[desiredProperty.Key] = desiredProperty.Value;
    }

    s_localDesiredPropertyVersion = twinUpdateRequest.Version;
    _logger.LogDebug($"The desired property version on local is currently {s_localDesiredPropertyVersion}.");

    try
    {
        // For the purpose of this sample, we'll blindly accept all twin property write requests.
        await s_deviceClient!.UpdateReportedPropertiesAsync(reportedProperties, s_appCancellation!.Token);
    }
    catch (OperationCanceledException)
    {
        // Fail gracefully on sample exit.
    }
}

这部分代码里需要注意的是更新本地版本号,读取Desired property预期属性,并在属性设置成功之后,调用方法UpdateReportedPropertiesAsync更新

  1. 知识点2:如何从设备上向云上报report property上报属性,这个其实非常简单:
var reportedProperties = new TwinCollection();
reportedProperties[desiredProperty.Key] = desiredProperty.Value;
await s_deviceClient!.UpdateReportedPropertiesAsync(reportedProperties, s_appCancellation!.Token);

直接调用方法UpdateReportedPropertiesAsync 即可完成上报属性的更新。

如上是代码的演示和说明,回到上报属性的更新上:

  • 在上报属性里添加一个新的属性,表示添加一个新的属性。
  • 在上报属性里更改已有属性的值,表示更新该属性。
  • 在上报属性里将已有属性的值置为null, 则表示删除该属性。

我们下一节演示如何通过BackendApp来更改预期属性(Desired Property) 和监控查询上报属性(report Property)的值和更改事件。