Skip to main content

另外一个调用直接方法处理实例(Direct Method)

分类:  Azure物联网 标签:  #Azure #IoT Hub # 发布于: 2023-06-13 20:47:06

本节我们再来学习一个新的用于处理直接方法调用的实例,希望大家能够直接从代码中学习到更多,另外需要注意的是,我们所有的代码都是基于.Net 6,所以如果你想从我们的这些代码中收益,您需要安装.Net 6的SDK。

创建一个新的项目

首先使用如下的命令取得设备simDevice的连接字符串,当然这之前你需要先创建好Azure IoT Hub资源以及该设备:

az iot hub device-identity connection-string show --device-id simDevice --hub-name MyIoThubByCli

得到连接字符串之后,先保存起来,后面的代码中需要使用到这个连接字符串。

使用下述的命令创建我们的项目:

dotnet new console -o DirectMethodCallSample
cd DirectMethodCallSample
dotnet add package Microsoft.Azure.Devices.Client
code .

打开VS code或者其他编辑器之后,在根目录下添加一个文件GlobalUsing.cs, 打开该文件,添加如下的引用:

global using Microsoft.Azure.Devices.Client;
global using System.Text.Json;
global using System.Text;
global using System.Text.Json.Serialization;
global using System.Diagnostics;
global using DirectMethodCallSample;

创建一个新类DirectMethodCallSample, 填充如下的内容:

namespace DirectMethodCallSample;
public class MethodSample
{
    private readonly DeviceClient _deviceClient;

    private class DeviceData
    {
        [JsonPropertyName("name")]
        public string? Name { get; set; }
    }

    public MethodSample(DeviceClient deviceClient)
    {
        _deviceClient = deviceClient ?? throw new ArgumentNullException(nameof(deviceClient));
    }

    public async Task RunSampleAsync(TimeSpan sampleRunningTime)
    {
        Console.WriteLine("按Control - C 退出测试应用。");

        using var cts = new CancellationTokenSource(sampleRunningTime);
        Console.CancelKeyPress += (sender, eventArgs) =>
        {
            eventArgs.Cancel = true;
            cts.Cancel();
            Console.WriteLine("应用被取消,即将退出。");
        };

        //设置连接变化call back函数
        _deviceClient.SetConnectionStatusChangesHandler(ConnectionStatusChangeHandler);

        //根据我们对MQTT协议,以及AMQP协议的理解,直接方法调用或者是twin更改,都是监听消息,因此
        //当第一个方法处理加入时,实际上就是在监听来自iot hub的消息。
        // 添加一个回调函数用于直接方法'WriteToConsole'的监听。
        await _deviceClient.SetMethodHandlerAsync("WriteToConsole", WriteToConsoleAsync, null, cts.Token);

        // 添加回调函数用于直接方法'GetDeviceName'调用。
        await _deviceClient.SetMethodHandlerAsync(
            "GetDeviceName",
            GetDeviceNameAsync,
            new DeviceData { Name = "DeviceClientMethodSample" },
            cts.Token);

        var timer = Stopwatch.StartNew();
        Console.WriteLine($"请使用Azure Portal或者Azure IoT Explorer调用直接方法:GetDeviceName或者WriteToConsole。");

        Console.WriteLine($"等待直接方法调用,等待时长为:{sampleRunningTime}  ...");
        while (!cts.IsCancellationRequested
            && (sampleRunningTime == Timeout.InfiniteTimeSpan || timer.Elapsed < sampleRunningTime))
        {
            await Task.Delay(1000);
        }

        // 可以使用如下方法取消对直接方法调用的订阅。
        await _deviceClient.SetMethodHandlerAsync(
            "GetDeviceName",
            null,
            null);

        await _deviceClient.SetMethodHandlerAsync(
            "WriteToConsole",
            null,
            null);
    }

    private void ConnectionStatusChangeHandler(ConnectionStatus status, ConnectionStatusChangeReason reason)
    {
        Console.WriteLine($"\n连接状态变为:{status}.");
        Console.WriteLine($"连接状态改变的原因是: {reason}.\n");
    }

    private Task<MethodResponse> WriteToConsoleAsync(MethodRequest methodRequest, object userContext)
    {
        Console.WriteLine($"\t *** {methodRequest.Name} was called.");
        Console.WriteLine($"\t{methodRequest.DataAsJson}\n");

        return Task.FromResult(new MethodResponse(new byte[0], 200));
    }

    private Task<MethodResponse> GetDeviceNameAsync(MethodRequest methodRequest, object userContext)
    {
        Console.WriteLine($"\t *** {methodRequest.Name} was called.");

        MethodResponse retValue;
        if (userContext == null)
        {
            retValue = new MethodResponse(new byte[0], 500);
        }
        else
        {
            var deviceData = (DeviceData)userContext;
            string result = JsonSerializer.Serialize(deviceData);
            retValue = new MethodResponse(Encoding.UTF8.GetBytes(result), 200);
        }

        return Task.FromResult(retValue);
    }
}

注意该类中关于直接方法调用的影响函数是如何设计的,需要理解针对于MQTT以及AMQP协议,直接方法调用实际上就是订阅不同的TOPIC,也可以直接取消订阅。

添加了该类之后,打开Program.cs,使用如下的内容替换:

Console.WriteLine("使用SAS Key连接字符串连接Azure IoT Hub, 并调用直接方法的实例");

string s_connectionString = "<你的设备连接字符串>";

//验证连接字符串是否是正确的
ValidateConnectionString(args);

DeviceClient s_deviceClient = DeviceClient.CreateFromConnectionString(s_connectionString);

using var deviceClient = DeviceClient.CreateFromConnectionString(
                s_connectionString,
                TransportType.Mqtt);

var runningTime = Timeout.InfiniteTimeSpan;

var sample = new MethodSample(deviceClient);
await sample.RunSampleAsync(runningTime);
await deviceClient.CloseAsync();

Console.WriteLine("Done.");

void ValidateConnectionString(string[] appArgs)
{
    if ( appArgs.Any() )
    {
        try 
        {
            var cs = IotHubConnectionStringBuilder.Create(appArgs[0]);
            s_connectionString = cs.ToString();

        } catch ( Exception )
        {
            Console.WriteLine($"错误:无法识别连接字符串参数 `{appArgs[0]}");
            Environment.Exit(-1);
        }

    }
    else
    {
        try 
        {
            _ = IotHubConnectionStringBuilder.Create(s_connectionString);
        } catch(Exception )
        {
            Console.WriteLine("这个Demo需要使用连接字符串连接到Azure IoT Hub");
            Environment.Exit(-1);
        }
    }
}

通过工具进行直接方法调用该,可以看到调用的结果,另外注意如果这个时候网络出现问题,会发现,SDK会自动进行重连。

如下图: