[IoT] Azure IoT整合應用六:取得IoT Hub上裝置的狀態,並發送裝置的離線通知

  • 672
  • 0
  • IoT
  • 2016-11-06

IoT的裝置,除了發送訊息至Azure IoT Hub上之外,後端的管理系統也可以透過取得IoT Hub上裝置的清單,判斷裝置目前是否還有連線並取得最後連線的時間

要取得IoT Hub上裝置的清單功能很簡單,但是因為判斷裝置的連線狀態與發送告警通知是屬於監控行為,所以我的作法是將這個程式作成WebJob,並安排每隔幾分鐘執行一次,這樣就能夠有效的監控裝置目前的連線狀態
由於是後端的監控管理,所以接下來的內容程式碼的部份會比較多一些,不過照著步驟就可以完整的作出監控功能

首先,建立一個Azure WebJob的專案,並且命名為[JobHeartBeatProcessor]

在這個WebJob的專案中,加入[Microsoft.Azure.Devices]的Nuget套件

接著在專案中建立一個Models的資料夾,並在該資料夾下建立一個DeviceEntity.cs的類別庫

將下面的程式碼放入至DeviceEntity.cs的類別庫中

public class DeviceEntity : IComparable<DeviceEntity>
{
    public string Id { get; set; }
    public string PrimaryKey { get; set; }
    public string SecondaryKey { get; set; }
    public string ConnectionString { get; set; }
    public string ConnectionState { get; set; }
    public DateTime LastActivityTime { get; set; }
    public DateTime LastConnectionStateUpdatedTime { get; set; }
    public DateTime LastStateUpdatedTime { get; set; }
    public int MessageCount { get; set; }
    public string State { get; set; }
    public string SuspensionReason { get; set; }

    public int CompareTo(DeviceEntity other)
    {
        return string.Compare(this.Id, other.Id, StringComparison.OrdinalIgnoreCase);
    }

    public override string ToString()
    {
        return $"Device ID = {this.Id}, Primary Key = {this.PrimaryKey}, Secondary Key = {this.SecondaryKey}, ConnectionString = {this.ConnectionString}, ConnState = {this.ConnectionState}, ActivityTime = {this.LastActivityTime}, LastConnState = {this.LastConnectionStateUpdatedTime}, LastStateUpdatedTime = {this.LastStateUpdatedTime}, MessageCount = {this.MessageCount}, State = {this.State}, SuspensionReason = {this.SuspensionReason}\r\n";
    }
}

這段程式碼主要是在定義從IoT Hub上取回的裝置清單結構內容,也就是說這些屬性將會是從IoT Hub上取回的裝置清單欄位

接著,在專案中建立一個Processors的資料夾,並在資料夾下建立一個[DevicesProcessor.cs]的類別庫

 將下面的程式碼,放入至DeviceProcessor.cs的類別庫中

class DevicesProcessor
{
    private List<DeviceEntity> listOfDevices;
    private RegistryManager registryManager;
    private String iotHubConnectionString;
    private int maxCountOfDevices;
    private String protocolGatewayHostName;

    public DevicesProcessor(string iotHubConnenctionString, int devicesCount, string protocolGatewayName)
    {
        this.listOfDevices = new List<DeviceEntity>();
        this.iotHubConnectionString = iotHubConnenctionString;
        this.maxCountOfDevices = devicesCount;
        this.protocolGatewayHostName = protocolGatewayName;
        this.registryManager = RegistryManager.CreateFromConnectionString(iotHubConnectionString);
    }

    public async Task<List<DeviceEntity>> GetDevices()
    {
        try
        {
            DeviceEntity deviceEntity;
            var devices = await registryManager.GetDevicesAsync(maxCountOfDevices);

            if (devices != null)
            {
                foreach (var device in devices)
                {
                    deviceEntity = new DeviceEntity()
                    {
                        Id = device.Id,
                        ConnectionState = device.ConnectionState.ToString(),
                        LastActivityTime = device.LastActivityTime,
                        LastConnectionStateUpdatedTime = device.ConnectionStateUpdatedTime,
                        LastStateUpdatedTime = device.StatusUpdatedTime,
                        MessageCount = device.CloudToDeviceMessageCount,
                        State = device.Status.ToString(),
                        SuspensionReason = device.StatusReason
                    };

                    if (device.Authentication != null &&
                        device.Authentication.SymmetricKey != null)
                    {
                        deviceEntity.PrimaryKey = device.Authentication.SymmetricKey.PrimaryKey;
                        deviceEntity.SecondaryKey = device.Authentication.SymmetricKey.SecondaryKey;
                    }

                    listOfDevices.Add(deviceEntity);
                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        return listOfDevices;
    }
}

這段程式碼主要的功能在於GetDevices這個副程式,建立了一個DeviceProcessor的類別物件後,呼叫GetDevices的副程式並取得所有裝置的清單,而裝置清單的Entity就是放在Models資料夾下的DeviceEntity.cs類別庫定義的物件

接著,在專案目錄下建立一個[HeartBeatProcessor.cs]的類別庫

並將下面的程式碼放入至HeartBeatProcessor.cs的類別庫中

public class HeartBeatProcessor
{
    static int intMaxCountOfDevices = int.Parse(ConfigurationManager.AppSettings["Microsoft.Azure.IoT.MaxDeviceCount"].ToString());
    static string strIoTHubConnectionString = ConfigurationManager.ConnectionStrings["Microsoft.Azure.IoT.ConnectionString"].ToString();

    public static async Task Process()
    {
        // 找出裝置的清單
        DevicesProcessor devicesProcessor = new DevicesProcessor(strIoTHubConnectionString, intMaxCountOfDevices, "");
        List<DeviceEntity> devicesList = await devicesProcessor.GetDevices();
        Console.WriteLine("All Devices Count:" + devicesList.Count.ToString());

        // 過濾出離線的裝置資料
        List<DeviceEntity> offlineDevices = devicesList.FindAll(
            delegate (DeviceEntity device)
            {
                return device.ConnectionState == "Disconnected" && device.State == "Enabled";
            }
        );
        Console.WriteLine("Offline Devices Count:" + offlineDevices.Count.ToString());

        // 取出離線的裝置客戶資料
        for (int i = 0; i < offlineDevices.Count; i++)
        {
            string strDeviceId = offlineDevices[i].Id;
            DateTime dtLastStateTime = offlineDevices[i].LastConnectionStateUpdatedTime;

            Console.WriteLine($"Device [{offlineDevices[i].Id}] is offline, Last updated time :[{dtLastStateTime.ToString()}]");

            /*
                在這邊加入通知或是告警的機制,如透過SnedGrid寄發EMail通知
            */
        }
    }
}

在HeartBeatProcessor.cs的類別庫中,當執行了Process()這個程序後,會呼叫先前建立的DeviceProcessor中的GetDevice()副程式,並在取得裝置清單後先過濾裝置目前的狀態,如果說裝置的[State]欄位為[Enabled],且[ConnectionState]的值為[Disconnected],那麼就必須發送告警的訊息,通知管理者該裝置已經離線了

然後在Program.cs中,將程式碼更改為呼叫HeartBeatProcessor.Process()作為執行的進入點

static void Main()
{
    Console.WriteLine("Start Time:" + DateTime.UtcNow.ToString());
    HeartBeatProcessor.Process().Wait();
    Console.WriteLine("Stop Time:" + DateTime.UtcNow.ToString());
}

程式碼的部份,最後在App.Config檔案裡,加上下面的設定內容

<appSettings>
  <!-- // TODO:在這裡修改取得IoT Hub上裝置的最大數 -->
  <add key="Microsoft.Azure.IoT.MaxDeviceCount" value="1000000" />
</appSettings>
<connectionStrings>
  <!-- // TODO:在這裡修改連線資訊 -->
  <add name="AzureWebJobsDashboard" connectionString="[儲存體的連線字串]" />
  <add name="AzureWebJobsStorage" connectionString="[儲存體的連線字串]" />
  <add name="Microsoft.Azure.IoT.ConnectionString" connectionString="[IoT Hub的連線字串]" />
</connectionStrings>
  • Microsoft.Azure.IoT.MaxDeviceCount:這個設定,表示要一次取得IoT Hub上裝置的最大數量,若是要一次取得很大的數量的話可能會影響效率,這部份需要斟酌一下
  • Microsoft.Azure.IoT.ConnectionString:在這個連線字串的設定中,請給予有取得裝置清單的IoT Hub連線字串,如iothubowner的角色之類的,取得的方式如下圖所示
由於WebJob需要一個儲存體作為執行過程Log的保存區,所以AzureWebJobsDashboardAzureWebJobsStorage這兩個連線字串設定請一定要設,不然WebJob會無法執行

程式碼製作完成後,就可以透過發佈的方式,將這個WebJob放上至WebApp中了 

由於這個WebJob並不是連續執行,所以可以將設定更改為[依排程執行]或是[視需要執行]

設定為[視需要執行]的選項的話,可以搭配設定排程工作集合進行手動排程執行工作,設定的方式可以參考[Azure] 透過手動設定排程工作集合並執行WebJob

發行完成後,可以在WebApp上看到這一個WebJob

查看執行記錄,可以看到執行過程使用Console.WriteLine所顯示的資訊,代表該工作排程確實有被執行,離線的裝置也有被取得了

透過WebJob搭配從IoT Hub取得離線裝置清單的功能,可以讓系統管理者很清楚的知道哪些裝置目前是在離線狀態,即使將這些裝置用在重要的環境中,也可以馬上得知這個裝置的連線狀態,輕鬆的達成監控的目的

參考內容:
https://github.com/Azure/azure-iot-sdks

文章範例程式:
https://github.com/madukapai/maduka-Azure-IoT